package cyano.basemetals;
import cyano.basemetals.data.AdditionalLootTables;
import cyano.basemetals.data.DataConstants;
import cyano.basemetals.registry.CrusherRecipeRegistry;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.logging.log4j.Level;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
* This is the entry point for this mod. If you are writing your own mod that
* uses this mod, the classes of interest to you are the init classes (classes
* in package cyano.basemetals.init) and the CrusherRecipeRegistry class (in
* package cyano.basemetals.registry). Note that you should add
* 'dependencies = "required-after:basemetals"' to your @Mod annotation
* (e.g. <br>
* @Mod(modid = "moremetals", name="More Metals!", version = "1.2.3", dependencies = "required-after:basemetals")
* <br>)
* @author DrCyano
*
*/
@Mod(
modid = BaseMetals.MODID,
name=BaseMetals.NAME,
version = BaseMetals.VERSION,
acceptedMinecraftVersions = "[1.10.2,)") // see VersionRange.createFromVersionSpec(String) for explanation of this convoluted feature
// updateJSON = "https://raw.githubusercontent.com/cyanobacterium/BaseMetals/master/update.json")
public class BaseMetals
{
//TODO: use metal plates to modify or repair shields
public static BaseMetals INSTANCE = null;
/** ID of this mod */
public static final String MODID = "basemetals";
/** display name of this mod */
public static final String NAME ="Base Metals";
/** Version number, in Major.Minor.Build format. The minor number is increased whenever a change
* is made that has the potential to break compatibility with other mods that depend on this one. */
public static final String VERSION = "2.4.0";
static {
// Forge says this needs to be statically initialized here.
FluidRegistry.enableUniversalBucket();
}
// /** If true, some metals can be used to brew potions */
// public static boolean enablePotionRecipes = true;
/** If true, hammers cannot crush ores that they cannot mine */
public static boolean enforceHardness = true;
/** If true, then crack hammers can mine on all the same blocks that their pick-axe equivalent
* can mine. If false, then the hammer is 1 step weaker than the pick-axe */
public static boolean strongHammers = true;
/** If true, hammers cannot be crafted */
public static boolean disableAllHammers = false;
/** For when the user adds specific recipies via the config file */
public static List<String> userCrusherRecipes = new ArrayList<>();
/** location of ore-spawn files */
public static Path oreSpawnFolder = null;
/** if true, then this mod will scan the Ore Dictionary for obvious hammer recipes from other mods */
public static boolean autoDetectRecipes = true;
/** if true, then this mod will require the orespawn mod */
public static boolean requireOreSpawn = true;
@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
INSTANCE = this;
// load config
Configuration config = new Configuration(event.getSuggestedConfigurationFile());
config.load();
// enablePotionRecipes = config.getBoolean("enable_potions", "options", enablePotionRecipes,
// "If true, then some metals can be used to brew potions.");
disableAllHammers = config.getBoolean("disable_crack_hammer", "options", disableAllHammers,
"If true, then the crack hammer cannot be crafted.");
enforceHardness = config.getBoolean("enforce_hardness", "options", enforceHardness,
"If true, then the crack hammer cannot crush ingots into powders if that \n"
+ "crackhammer is not hard enough to crush the ingot's ore.");
strongHammers = config.getBoolean("strong_hammers", "options", strongHammers,
"If true, then the crack hammer can crush ingots/ores that a pickaxe of the same \n"
+ "material can harvest. If false, then your crack hammer must be made of a harder \n"
+ "material than the ore you are crushing.");
autoDetectRecipes = config.getBoolean("automatic_recipes", "options", autoDetectRecipes,
"If true, then Base Metals will scan the Ore Dictionary to automatically add a \n"
+ "Crack Hammer recipe for every material that has an ore, dust, and ingot.");
requireOreSpawn = config.getBoolean("using_orespawn", "options", requireOreSpawn,
"If false, then Base Metals will not require DrCyano's Ore Spawn mod. \n" +
"Set to false if using another mod to manually handle ore generation.");
ConfigCategory userRecipeCat = config.getCategory("hammer recipes");
userRecipeCat.setComment(
"This section allows you to add your own recipes for the Crack Hammer (and other rock \n"
+ "crushers). Recipes are specified in semicolon (;) delimited lists of formulas in the \n"
+ "format modid:name#y->x*modid:name#y, where x is the number of items in a stack and y \n"
+ "is the metadata value. Note that both x and y are optional, so you can use the \n"
+ "formula modid:name->modid:name for most items/blocks. \n\n"
+ "All properties in this section will be parsed for formulas, regardless their name. \n"
+ "This lets you organize your recipe lists for easier reading.");
if(userRecipeCat.keySet().size()==0){
Property prop = new Property("custom","",Property.Type.STRING);
prop.setComment("Example: minecraft:stained_glass#11->minecraft:dye#4; minecraft:wool->4*minecraft:string");
userRecipeCat.put("custom", prop);
}
for(Property p : userRecipeCat.values()){
String[] recipes = p.getString().split(";");
for(String r : recipes){
String recipe = r.trim();
if(recipe.isEmpty()) continue;
if(recipe.contains("->") == false){
throw new IllegalArgumentException ("Malformed hammer recipe expression '"+recipe+"'. Should be in format 'modid:itemname->modid:itemname'");
}
userCrusherRecipes.add(recipe);
}
}
config.save();
if(requireOreSpawn) {
if(!net.minecraftforge.fml.common.Loader.isModLoaded("orespawn")){
HashSet<ArtifactVersion> orespawnMod = new HashSet<>();
orespawnMod.add(new DefaultArtifactVersion("1.0.0"));
throw new MissingModsException(orespawnMod, "orespawn", "DrCyano's Ore Spawn Mod");
}
oreSpawnFolder = Paths.get(event.getSuggestedConfigurationFile().toPath().getParent().toString(), "orespawn");
Path oreSpawnFile = Paths.get(oreSpawnFolder.toString(), MODID + ".json");
if (Files.exists(oreSpawnFile) == false) {
try {
Files.createDirectories(oreSpawnFile.getParent());
Files.write(oreSpawnFile, Arrays.asList(DataConstants.defaultOreSpawnJSON.split("\n")), Charset.forName("UTF-8"));
} catch (IOException e) {
FMLLog.severe(MODID + ": Error: Failed to write file " + oreSpawnFile);
}
}
}
cyano.basemetals.init.Fluids.init();
cyano.basemetals.init.Materials.init();
cyano.basemetals.init.ItemGroups.init();
cyano.basemetals.init.Blocks.init();
cyano.basemetals.init.Items.init();
cyano.basemetals.init.VillagerTrades.init();
Path ALTPath = Paths.get(event.getSuggestedConfigurationFile().getParent(),"additional-loot-tables");
Path myLootFolder = ALTPath.resolve(MODID);
if(Files.notExists(myLootFolder)){
try{
Files.createDirectories(myLootFolder.resolve("chests"));
Files.write(myLootFolder.resolve("chests").resolve("abandoned_mineshaft.json"),
Arrays.asList( AdditionalLootTables.abandoned_mineshaft));
Files.write(myLootFolder.resolve("chests").resolve("desert_pyramid.json"),
Arrays.asList( AdditionalLootTables.desert_pyramid));
Files.write(myLootFolder.resolve("chests").resolve("end_city_treasure.json"),
Arrays.asList( AdditionalLootTables.end_city_treasure));
Files.write(myLootFolder.resolve("chests").resolve("jungle_temple.json"),
Arrays.asList( AdditionalLootTables.jungle_temple));
Files.write(myLootFolder.resolve("chests").resolve("nether_bridge.json"),
Arrays.asList( AdditionalLootTables.nether_bridge));
Files.write(myLootFolder.resolve("chests").resolve("simple_dungeon.json"),
Arrays.asList( AdditionalLootTables.simple_dungeon));
Files.write(myLootFolder.resolve("chests").resolve("spawn_bonus_chest.json"),
Arrays.asList( AdditionalLootTables.spawn_bonus_chest));
Files.write(myLootFolder.resolve("chests").resolve("stronghold_corridor.json"),
Arrays.asList( AdditionalLootTables.stronghold_corridor));
Files.write(myLootFolder.resolve("chests").resolve("stronghold_crossing.json"),
Arrays.asList( AdditionalLootTables.stronghold_crossing));
Files.write(myLootFolder.resolve("chests").resolve("village_blacksmith.json"),
Arrays.asList( AdditionalLootTables.village_blacksmith));
} catch(IOException ex){
FMLLog.log(Level.ERROR,ex,"%s: Failed to extract additional loot tables",MODID);
}
}
if(event.getSide() == Side.CLIENT){
clientPreInit(event);
}
if(event.getSide() == Side.SERVER){
serverPreInit(event);
}
}
@SideOnly(Side.CLIENT)
private void clientPreInit(FMLPreInitializationEvent event){
// client-only code
cyano.basemetals.init.Fluids.bakeModels(MODID);
}
@SideOnly(Side.SERVER)
private void serverPreInit(FMLPreInitializationEvent event){
// server-only code
}
@EventHandler
public void init(FMLInitializationEvent event)
{
cyano.basemetals.init.Recipes.init();
cyano.basemetals.init.DungeonLoot.init();
cyano.basemetals.init.Entities.init();
cyano.basemetals.init.Achievements.init();
if(event.getSide() == Side.CLIENT){
clientInit(event);
}
if(event.getSide() == Side.SERVER){
serverInit(event);
}
}
@SideOnly(Side.CLIENT)
private void clientInit(FMLInitializationEvent event){
// client-only code
cyano.basemetals.init.Items.registerItemRenders(event);
cyano.basemetals.init.Blocks.registerItemRenders(event);
}
@SideOnly(Side.SERVER)
private void serverInit(FMLInitializationEvent event){
// server-only code
}
@EventHandler
public void postInit(FMLPostInitializationEvent event)
{
cyano.basemetals.init.WorldGen.init();
// parse user crusher recipes
for(String recipe : userCrusherRecipes){
FMLLog.info(MODID+": adding custom crusher recipe '"+recipe+"'");
int i = recipe.indexOf("->");
String inputStr = recipe.substring(0,i);
String outputStr = recipe.substring(i+2,recipe.length());
ItemStack input = parseStringAsItemStack(inputStr,true);
ItemStack output = parseStringAsItemStack(outputStr,false);
if(input == null || output == null){
FMLLog.severe("Failed to add recipe formula '"+recipe+"' because the blocks/items could not be found");
} else {
CrusherRecipeRegistry.addNewCrusherRecipe(input, output);
}
}
if(autoDetectRecipes){
// add recipe for every X where the Ore Dictionary has dustX, oreX, and ingotX
Set<String> dictionary = new HashSet<>();
dictionary.addAll(Arrays.asList(OreDictionary.getOreNames()));
for(String entry : dictionary){
if(entry.contains("Mercury")) continue;
if(entry.startsWith("dust")){
String X = entry.substring("dust".length());
String dustX = entry;
String ingotX = "ingot".concat(X);
String oreX = "ore".concat(X);
if(dictionary.contains(oreX) && dictionary.contains(ingotX) && !OreDictionary.getOres(dustX).isEmpty()){
ItemStack dustX1 = OreDictionary.getOres(dustX).get(0).copy();
dustX1.stackSize = 1;
ItemStack dustX2 = dustX1.copy();
dustX2.stackSize = 2;
// recipe found
// but is it already registered
List<ItemStack> oreBlocks = OreDictionary.getOres(oreX);
boolean alreadyHasOreRecipe = true;
for(ItemStack i : oreBlocks){
alreadyHasOreRecipe = alreadyHasOreRecipe
&& (CrusherRecipeRegistry.getInstance().getRecipeForInputItem(i) != null);
}
List<ItemStack> ingotStacks = OreDictionary.getOres(ingotX);
boolean alreadyHasIngotRecipe = true;
for(ItemStack i : ingotStacks){
alreadyHasIngotRecipe = alreadyHasIngotRecipe
&& (CrusherRecipeRegistry.getInstance().getRecipeForInputItem(i) != null);
}
if(!alreadyHasOreRecipe){
FMLLog.info(MODID+": automatically adding custom crusher recipe \"%s\" -> %s",oreX,dustX2);
CrusherRecipeRegistry.addNewCrusherRecipe(oreX, dustX2);
}
if(!alreadyHasIngotRecipe){
FMLLog.info(MODID+": automatically adding custom crusher recipe \"%s\" -> %s",ingotX,dustX1);
CrusherRecipeRegistry.addNewCrusherRecipe(ingotX, dustX1);
}
}
}
}
}
if(event.getSide() == Side.CLIENT){
clientPostInit(event);
}
if(event.getSide() == Side.SERVER){
serverPostInit(event);
}
CrusherRecipeRegistry.getInstance().clearCache();
}
@SideOnly(Side.CLIENT)
private void clientPostInit(FMLPostInitializationEvent event){
// client-only code
}
@SideOnly(Side.SERVER)
private void serverPostInit(FMLPostInitializationEvent event){
// server-only code
}
/**
* Parses a String in the format (stack-size)*(modid):(item/block name)#(metadata value). The
* stacksize and metadata value parameters are optional.
* @param str A String describing an itemstack (e.g. "4*minecraft:dye#15" or "minecraft:bow")
* @param allowWildcard If true, then item strings that do not specify a metadata value will use
* the OreDictionary wildcard value. If false, then the default meta value is 0 instead.
* @return An ItemStack representing the item, or null if the item is not found
*/
public static ItemStack parseStringAsItemStack(String str, boolean allowWildcard){
str = str.trim();
int count = 1;
int meta;
if(allowWildcard){
meta = OreDictionary.WILDCARD_VALUE;
} else {
meta = 0;
}
int nameStart = 0;
int nameEnd = str.length();
if(str.contains("*")){
count = Integer.parseInt(str.substring(0,str.indexOf("*")).trim());
nameStart = str.indexOf("*")+1;
}
if(str.contains("#")){
meta = Integer.parseInt(str.substring(str.indexOf("#")+1,str.length()).trim());
nameEnd = str.indexOf("#");
}
String id = str.substring(nameStart,nameEnd).trim();
if(Block.getBlockFromName(id) != null){
// is a block
return new ItemStack(Block.getBlockFromName(id),count,meta);
} else if(Item.getByNameOrId(id) != null){
// is an item
return new ItemStack(Item.getByNameOrId(id),count,meta);
} else {
// item not found
FMLLog.severe("Failed to find item or block for ID '"+id+"'");
return null;
}
}
}