Merge pull request #1453 from thatsIch/f-1452-auto-gen-custom-recipes

Closes #1452: Implements auto-generation of custom recipes
This commit is contained in:
thatsIch 2015-06-03 23:18:35 +02:00
commit 17465e68e8
10 changed files with 669 additions and 25 deletions

View File

@ -99,6 +99,7 @@ sourceSets {
resources {
srcDir "src/main/resources/"
include "assets/appliedenergistics2/recipes/*.recipe",
"assets/appliedenergistics2/recipes/README.html",
"assets/appliedenergistics2/lang/*.lang",
"assets/appliedenergistics2/textures/blocks/*",
"assets/appliedenergistics2/textures/guis/*",

View File

@ -1,7 +1,7 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2013 AlgorithmX2
* Copyright (c) 2013 - 2015 AlgorithmX2
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@ -25,10 +25,17 @@ package appeng.api.recipes;
import java.io.BufferedReader;
import javax.annotation.Nonnull;
public interface IRecipeLoader
{
BufferedReader getFile( String s ) throws Exception;
/**
* @param filePath the path to the to be loaded file
*
* @return reader handler of the file
*
* @throws Exception if reading goes wrong
*/
BufferedReader getFile( @Nonnull String filePath ) throws Exception;
}

View File

@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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

View File

@ -0,0 +1,99 @@
/*
* 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.core;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FileUtils;
import appeng.api.recipes.IRecipeHandler;
import appeng.recipes.loader.ConfigLoader;
import appeng.recipes.loader.JarLoader;
import appeng.recipes.loader.RecipeResourceCopier;
/**
* handles the decision if recipes should be loaded from jar, loaded from file system or force copied from jar
*
* @author thatsIch
* @version rv3 - 12.05.2015
* @since rv3 12.05.2015
*/
public class RecipeLoader implements Runnable
{
private final IRecipeHandler handler;
/**
* @param handler handler to load the recipes
*
* @throws NullPointerException if handler is <tt>null</tt>
*/
public RecipeLoader( @Nonnull IRecipeHandler handler )
{
Preconditions.checkNotNull( handler );
this.handler = handler;
}
@Override
public void run()
{
// setup copying
final RecipeResourceCopier copier = new RecipeResourceCopier( "assets/appliedenergistics2/recipes/" );
final File configDirectory = AppEng.instance().getConfigDirectory();
final File generatedRecipesDir = new File( configDirectory, "generated-recipes" );
final File userRecipesDir = new File( configDirectory, "user-recipes" );
final File readmeGenDest = new File( generatedRecipesDir, "README.html" );
final File readmeUserDest = new File( userRecipesDir, "README.html" );
// generates generated and user recipes dir
// will clean the generated every time to keep it up to date
// copies over the recipes in the jar over to the generated folder
// copies over the readmes
try
{
FileUtils.forceMkdir( generatedRecipesDir );
FileUtils.forceMkdir( userRecipesDir );
FileUtils.cleanDirectory( generatedRecipesDir );
copier.copyTo( generatedRecipesDir );
FileUtils.copyFile( readmeGenDest, readmeUserDest );
// parse recipes prioritising the user scripts by using the generated as template
this.handler.parseRecipes( new ConfigLoader( generatedRecipesDir, userRecipesDir ), "index.recipe" );
}
// on failure use jar parsing
catch( IOException e )
{
AELog.error( e );
this.handler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
catch( URISyntaxException e )
{
AELog.error( e );
this.handler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
}
}

View File

@ -108,8 +108,6 @@ import appeng.recipes.handlers.Pulverizer;
import appeng.recipes.handlers.Shaped;
import appeng.recipes.handlers.Shapeless;
import appeng.recipes.handlers.Smelt;
import appeng.recipes.loader.ConfigLoader;
import appeng.recipes.loader.JarLoader;
import appeng.recipes.ores.OreDictionaryHandler;
import appeng.spatial.BiomeGenStorage;
import appeng.spatial.StorageWorldProvider;
@ -516,14 +514,8 @@ public final class Registration
// Perform ore camouflage!
ItemMultiMaterial.instance.makeUnique();
if( AEConfig.instance.isFeatureEnabled( AEFeature.CustomRecipes ) )
{
this.recipeHandler.parseRecipes( new ConfigLoader( AppEng.instance().getConfigDirectory() ), "index.recipe" );
}
else
{
this.recipeHandler.parseRecipes( new JarLoader( "/assets/appliedenergistics2/recipes/" ), "index.recipe" );
}
final Runnable recipeLoader = new RecipeLoader( this.recipeHandler );
recipeLoader.run();
partHelper.registerNewLayer( "appeng.parts.layers.LayerISidedInventory", "net.minecraft.inventory.ISidedInventory" );
partHelper.registerNewLayer( "appeng.parts.layers.LayerIFluidHandler", "net.minecraftforge.fluids.IFluidHandler" );

View File

@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@ -63,7 +63,7 @@ public enum AEFeature
UnsupportedDeveloperTools( "Misc", false ), Creative( "Misc" ),
GrinderLogging( "Misc", false ), Logging( "Misc" ), IntegrationLogging( "Misc", false ), CustomRecipes( "Crafting", false ), WebsiteRecipes( "Misc", false ),
GrinderLogging( "Misc", false ), Logging( "Misc" ), IntegrationLogging( "Misc", false ), WebsiteRecipes( "Misc", false ),
enableFacadeCrafting( "Crafting" ), inWorldSingularity( "Crafting" ), inWorldFluix( "Crafting" ), inWorldPurification( "Crafting" ), UpdateLogging( "Misc", false ),

View File

@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@ -23,24 +23,35 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import appeng.api.recipes.IRecipeLoader;
public final class ConfigLoader implements IRecipeLoader
{
private final File rootDirectory;
private final File generatedRecipesDir;
private final File userRecipesDir;
public ConfigLoader( File rootDirectory )
public ConfigLoader( File generatedRecipesDir, File userRecipesDir )
{
this.rootDirectory = rootDirectory;
this.generatedRecipesDir = generatedRecipesDir;
this.userRecipesDir = userRecipesDir;
}
@Override
public BufferedReader getFile( String s ) throws Exception
public BufferedReader getFile( @Nonnull String relativeFilePath ) throws Exception
{
final File f = new File( this.rootDirectory, s );
Preconditions.checkNotNull( relativeFilePath );
Preconditions.checkArgument( !relativeFilePath.isEmpty(), "Supplying an empty String will result creating a reader of a folder." );
return new BufferedReader( new InputStreamReader( new FileInputStream( f ), "UTF-8" ) );
final File generatedFile = new File( this.generatedRecipesDir, relativeFilePath );
final File userFile = new File( this.userRecipesDir, relativeFilePath );
final File toBeLoaded = ( userFile.exists() && userFile.isFile() ) ? userFile : generatedFile;
return new BufferedReader( new InputStreamReader( new FileInputStream( toBeLoaded ), "UTF-8" ) );
}
}

View File

@ -1,6 +1,6 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
* 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
@ -21,6 +21,9 @@ package appeng.recipes.loader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import appeng.api.recipes.IRecipeLoader;
@ -36,8 +39,11 @@ public class JarLoader implements IRecipeLoader
}
@Override
public BufferedReader getFile( String s ) throws Exception
public BufferedReader getFile( @Nonnull String s ) throws Exception
{
Preconditions.checkNotNull( s );
Preconditions.checkArgument( !s.isEmpty() );
return new BufferedReader( new InputStreamReader( this.getClass().getResourceAsStream( this.rootPath + s ), "UTF-8" ) );
}
}

View File

@ -0,0 +1,181 @@
/*
* 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.loader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FileUtils;
/**
* copies recipes in jars onto file system
* includes the readme,
* needs to be modified if other files needs to be handled
*
* @author thatsIch
* @version rv3 - 11.05.2015
* @since rv3 11.05.2015
*/
public class RecipeResourceCopier
{
/**
* Most expected size of recipes found
*/
private static final int INITIAL_RESOURCE_CAPACITY = 20;
private static final Pattern DOT_COMPILE_PATTERN = Pattern.compile( ".", Pattern.LITERAL );
/**
* copy source in the jar
*/
private final String root;
/**
* @param root source root folder of the recipes inside the jar.
*
* @throws NullPointerException if root is <tt>null</tt>
*/
public RecipeResourceCopier( @Nonnull String root )
{
Preconditions.checkNotNull( root );
this.root = root;
}
/**
* copies recipes found in the root to destination.
*
* @param destination destination folder to which the recipes are copied to
*
* @throws URISyntaxException {@see #getResourceListing}
* @throws IOException {@see #getResourceListing} and if copying the detected resource to file is not possible
* @throws NullPointerException if either parameter is <tt>null</tt>
* @throws IllegalArgumentException if destination is not a directory
*/
public void copyTo( @Nonnull File destination ) throws URISyntaxException, IOException
{
Preconditions.checkNotNull( destination );
Preconditions.checkArgument( destination.isDirectory() );
final String[] listing = this.getResourceListing( this.getClass(), this.root );
for( String list : listing )
{
if( list.endsWith( ".recipe" ) || list.endsWith( ".html" ) )
{
final InputStream inStream = this.getClass().getResourceAsStream( '/' + this.root + list );
final File outFile = new File( destination, list );
if( !outFile.exists() )
{
if( inStream != null )
{
FileUtils.copyInputStreamToFile( inStream, outFile );
inStream.close();
}
}
}
}
}
/**
* List directory contents for a resource folder. Not recursive.
* This is basically a brute-force implementation.
* Works for regular files and also JARs.
*
* @param clazz Any java class that lives in the same place as the resources you want.
* @param path Should end with "/", but not start with one.
*
* @return Just the name of each member item, not the full paths.
*
* @throws URISyntaxException if it is a file path and the URL can not be converted to URI
* @throws IOException if jar path can not be decoded
* @throws UnsupportedOperationException if it is neither in jar nor in file path
*/
@Nonnull
private String[] getResourceListing( Class<?> clazz, String path ) throws URISyntaxException, IOException
{
assert clazz != null;
assert path != null;
URL dirURL = clazz.getClassLoader().getResource( path );
if( dirURL != null && dirURL.getProtocol().equals( "file" ) )
{
// A file path: easy enough
return new File( dirURL.toURI() ).list();
}
if( dirURL == null )
{
/*
* In case of a jar file, we can't actually find a directory.
* Have to assume the same jar as clazz.
*/
final String me = DOT_COMPILE_PATTERN.matcher( clazz.getName() ).replaceAll( "/" ) + ".class";
dirURL = clazz.getClassLoader().getResource( me );
}
if( dirURL != null && dirURL.getProtocol().equals( "jar" ) )
{
/* A JAR path */
final String jarPath = dirURL.getPath().substring( 5, dirURL.getPath().indexOf( '!' ) ); //strip out only the JAR file
final JarFile jar = new JarFile( URLDecoder.decode( jarPath, "UTF-8" ) );
try
{
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
final Collection<String> result = new HashSet<String>( INITIAL_RESOURCE_CAPACITY ); //avoid duplicates in case it is a subdirectory
while( entries.hasMoreElements() )
{
final String name = entries.nextElement().getName();
if( name.startsWith( path ) )
{ //filter according to the path
String entry = name.substring( path.length() );
final int checkSubDir = entry.indexOf( '/' );
if( checkSubDir >= 0 )
{
// if it is a subdirectory, we just return the directory name
entry = entry.substring( 0, checkSubDir );
}
result.add( entry );
}
}
return result.toArray( new String[result.size()] );
}
finally
{
jar.close();
}
}
throw new UnsupportedOperationException( "Cannot list files for URL " + dirURL );
}
}

View File

@ -0,0 +1,347 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Custom Recipes</title>
<link rel="shortcut icon" type="image/x-icon"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAACSSURBVDhPlZHLDYAwDEO7DWcunLsAWzBIN+toQS6xiNr0Z8kSafMeQoRRrvMQVMe9AMw5l25LLLwtsXCMsXRZUsPec1fiwY9IqT1DGwkuvTf2BNxX/BPgYvUTeKb4L7ALXBrdKd7+tnr2zjAr/mUkmcJMvVh/AtqFGSuxglvOOcxQYgXLMAOAgm2YSSkJqqOTEF4xoGOuk3A5dwAAAABJRU5ErkJggg==">
<style type="text/css">
body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
max-width: 80ch;
}
div#content {
/*position: relative;*/
/*left: 25%;*/
max-width: 1000px;
margin: 0 auto;
padding: 15px;
display: block;
}
div#content li {
margin-bottom: 0.4em;
word-wrap: break-word;
}
code {
font-size: 13px;
white-space: pre-wrap;
padding: 1px 5px;
font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;
background-color: #eee;
margin: 0;
border: 0;
word-wrap: break-word;
line-height: 13px;
}
nav {
float: right;
width: 40%;
background: #eeeeee;
font-size: 0.8em;
padding-left: 15px;
margin: 0 0 0.5em 0.5em;
max-height: 200px;
word-wrap: break-word;
display: block;
overflow: auto;
}
h1, h2, h3, h4 {
margin-top: 20px;
margin-bottom: 10px;
line-height: 1.1;
}
h1 {
font-size: 36px;
font-weight: 500;
}
pre {
border: 1px solid #CCC;
background-color: #f6f6f2;
word-wrap: normal;
overflow: auto;
font-size: 13px;
padding: 5px;
width: auto;
max-height: 600px;
margin-bottom: 1em;
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
display: block;
white-space: pre;
line-height: 1.3;
border-spacing: 2px;
text-align: left;
color: #222;
max-width: 1024px;
}
blockquote {
margin: 0 0 10px;
padding: 10px;
background-color: #fff9e3;
border-left: 2px solid #ffeb8e;
quotes: none;
line-height: 1.3;
max-width: 1024px;
}
a {
color: #0c65a5;
cursor: pointer;
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
line-height: 1.3;
}
a:hover {
color: #30a7fc;
text-decoration: none;
}
</style>
</head>
<body>
<div id="content">
<div id="header">
<h1>Custom Recipes</h1>
</div>
<div id="body">
<nav role="navigation">
<h2>Navigation</h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#auto-generation">Auto-Generation</a></li>
<li><a href="#modification">Recipe Modification</a></li>
<ul>
<li><a href="#item-references">Item References</a></li>
<li><a href="#recipe-types">Recipe Types</a></li>
<ul>
<li><a href="#shapeless">Shapeless</a></li>
<li><a href="#spahed">Shaped</a></li>
<li><a href="#smelt">Smelt</a></li>
<li><a href="#grind">Grind</a></li>
<li><a href="#grindfz">Grindfz</a></li>
<li><a href="#mekcrusher">Mekcrusher</a></li>
<li><a href="#mekechamber">Mekechamber</a></li>
<li><a href="#hccrusher">Hccrusher</a></li>
<li><a href="#crusher">Crusher</a></li>
<li><a href="#macerator">Macerator</a></li>
<li><a href="#pulverizer">Pulverizer</a></li>
<li><a href="#inscribe">Inscribe</a></li>
<li><a href="#press">Press</a></li>
</ul>
<li><a href="#recipe-function">Recipe Function</a></li>
<ul>
<li><a href="#alias">Alias</a></li>
<li><a href="#ore">Ore</a></li>
<li><a href="#group">Group</a></li>
<li><a href="#import">Import</a></li>
</ul>
</ul>
</ul>
</nav>
<h2 id="introduction">Introduction</h2>
<p>This file is the README of Applied Energistics 2 for Custom Recipes. Applied Energistics 2 is extremely configurable and supports 100% customize-able
recipes if desired. This page will direct you on how to get started. The entry file for the recipe system is <code>index.recipe</code>.</p>
<h2 id="auto-generation">Auto-Generation</h2>
<p>AE2 will generate 2 folders in your AE2 configuration folder <code>config/AppliedEneristics2/</code>. One is <code>generated-recipes/</code> and the other is <code>user-recipes/</code>. Recipes will be automatically exported from AE2 into <code>generated-recipes/</code> on every start-up containing always the latest valid recipes. </p>
<blockquote>
You require file system permissions to generate the folders. Recipes will be loaded directly from AE2 if user has no permissions.
</blockquote>
<h2 id="recipe-modification">Recipe Modification</h2>
<p>You can modify the recipes by copying over the recipes from <code>generated-recipes/</code> into <code>user-recipes/</code>. You do <b>not</b> need to copy over all files, but the ones you will be changing. AE2 will prefer loading a user recipe if one would replace a generated recipe.</p>
<p>That way changed recipes by us are not overwriting your modified files.</p>
<h3 id="item-references">Item References</h3>
<p>In Minecraft each item is referenced by a namespace and a name, for example all of minecrafts items use the namespace <code>minecraft</code>. A glass block would be <code>minecraft:glass</code>.</p>
<p>The recipe system also exposes access to the oredictionary via a namespace, so you can use <code>oredictionary:glass</code> to use any type of glass.</p>
<h3 id="recipe-types">Recipe Types</h3>
<p>Different recipe types are there to interface with specific machines or mods. Use a <code>,</code> to add a new row to the recipe.</p>
<h4 id="shapeless">Shapeless</h4>
<ul>
<li>crafted in a minecraft crafting table</li>
<li>can take any shape as long as the inputs are correct</li>
<li>takes up to 9 items as input</li>
<li>outputs a single item as output, optionally with quantity</li>
<li>Example: <code>Log -> Planks</code> recipe</li>
<li>
AE2: <br />
<code>shapeless = minecraft:log -> 4 minecraft:planks</code>
</li>
</ul>
<h4 id="shaped">Shaped</h4>
<ul>
<li>crafted in a minecraft crafting table</li>
<li>requires the specific shape</li>
<li>takes up to 9 items as input</li>
<li>outputs a single item as output, optionally with quantity</li>
<li>Example: <code>8 Cobblestone -> Furnace</code> recipe</li>
<li>
AE2:
<pre>shaped =
minecraft:cobblestone minecraft:cobblestone minecraft:cobblestone,
minecraft:cobblestone _ minecraft:cobblestone,
minecraft:cobblestone minecraft:cobblestone minecraft:cobblestone
-> minecraft:furnace</pre>
</li>
</ul>
<h4 id="smelt">Smelt</h4>
<ul>
<li>items to be burned in furnaces</li>
<li>takes 1 item as input</li>
<li>takes 1 item as output, optionally with quantity</li>
<li>Example: <code>Log -> Charcoal</code> recipe</li>
<li>
AE2: <br />
<code>smelt = minecraft:log -> minecraft:coal:1</code>
</li>
</ul>
<h4 id="grind">Grind</h4>
<ul>
<li>items to be grinded in the grindstone</li>
<li>takes 1 item as input</li>
<li>takes 1 item as output, optionally with quantity</li>
<li>Example: <code>Gravel -> Flint</code> recipe</li>
<li>
AE2: <br />
<code>grind = minecraft:gravel -> minecraft:flint</code>
</li>
</ul>
<h4 id="grindfz">Grindfz</h4>
<p>Same as <code>grind</code> for Factorization Grinder</p>
<h4 id="mekcrusher">Mekcrusher</h4>
<p>Same as <code>grind</code> for Mekanism Crusher</p>
<h4 id="mekechamber">Mekechamber</h4>
<p>Same as <code>grind</code> for Mekanism Enrichment Chamber</p>
<h4 id="hccrusher">Hccrusher</h4>
<p>Same as <code>grind</code> for HydrauliCraft Crusher</p>
<h4 id="crusher">Crusher</h4>
<p>Same as <code>grind</code> for RailCraft Crusher</p>
<h4 id="macerator">Macerator</h4>
<p>Same as <code>grind</code> for IndustrialCraft Macerator</p>
<h4 id="pulverizer">Pulverizer</h4>
<p>Same as <code>grind</code> for Thermal Expansion Pulverizer</p>
<h4 id="inscribe">Inscribe</h4>
<ul>
<li>used in the AE2 Inscriber</li>
<li>takes 2 or 3 items as input, first item is the center item</li>
<li>takes 1 item as output, optionally with quantity</li>
<li>center item is consumed</li>
<li>
Example: duplicate logic processor plate <br />
<code>Iron Block + Logic Processor Plate -> Logic Processor Plate</code>
</li>
<li>
AE2:
<pre>inscribe =
minecraft:iron_block
appliedenergistics2:ItemMaterial.LogicProcessorPress
-> appliedenergistics2:ItemMaterial.LogicProcessorPress</pre>
</li>
</ul>
<h4 id="press">Press</h4>
<ul>
<li>used in the AE2 Inscriber</li>
<li>takes 2 or 3 items as input, first item is the center item</li>
<li>takes 1 item as output, optionally with quantity</li>
<li>all items are consumed</li>
<li>
Example: create logic processor <br />
<code>Redstone + logic processor print + silicon print -> logic processor</code>
</li>
<li>
AE2:
<pre>press =
minecraft:redstone
appliedenergistics2:ItemMaterial.LogicProcessorPrint
appliedenergistics2:ItemMaterial.SiliconPrint
-> appliedenergistics2:ItemMaterial.LogicProcessor</pre>
</li>
</ul>
<h3 id="recipe-function">Recipe Function</h3>
<p>Adds convenience for easier recipe management.</p>
<h4 id="alias">Alias</h4>
<ul>
<li>creates a shorthand for a longer value</li>
<li>
AE2: alias <code>appliedenergistics2</code> to <code>ae2</code><br />
<code>alias = ae2 -> appliedenergistics2</code>
</li>
<li><code>appliedenergistics2:ItemMaterial.LogicProcessorPrint</code> could be aliased to <code>ae2:ItemMaterial.LogicProcessorPrint</code></li>
</ul>
<h4 id="ore">Ore</h4>
<ul>
<li>lets you add items into an ore dictionary value.</li>
<li>
AE2: add minecraft wool to OreDictionary as <code>blockWool</code><br />
<code>ore = minecraft:wool:* -> blockWool</code>
</li>
<li>oredictioned items can be accessed as <code>oredictionary:blockWool</code> for example</li>
</ul>
<h4 id="group">Group</h4>
<ul>
<li>lets you create a item group for 1 or more inputs</li>
<li>
Example: define a common group for both different AE2 interfaces<br />
<code>Block Interface + Part Interface = Interface</code>
</li>
<li>
AE2: <br />
<code>group = ae2:BlockInterface ae2:ItemPart.Interface -> interface</code>
</li>
<li>
This also enables shortening annoying names for example<br />
<code>group = ae2:ToolNetherQuartzWrench -> wrench</code>
</li>
</ul>
<h4 id="import">Import</h4>
<ul>
<li>lets you load an additional recipe file</li>
<li>
AE2: import all recipes for stairs<br />
<code>import = stairs.recipe</code>
</li>
<li>Any knowledge through <code>alias, ore, group</code> are carried over to the imported file</li>
</ul>
<!--<pre>-->
<!--Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.-->
<!--</pre>-->
<!--<blockquote>-->
<!--A readme (or read me) fdsile contains information about other files in a directory or archive and is very commonly distributed with computer software.-->
<!--</blockquote>-->
</div>
</div>
</body>
</html>