package eiteam.esteemedinnovation.materials.raw.config;
import com.google.gson.*;
import eiteam.esteemedinnovation.commons.OreDictEntries;
import net.minecraft.block.Block;
import net.minecraft.init.Biomes;
import net.minecraft.init.Blocks;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class OreConfigurationParser {
private OreGenerationDefinition[] ores;
private String filename;
private Gson gson;
public OreConfigurationParser(String filename) {
this.filename = filename;
gson = new GsonBuilder().setPrettyPrinting().create();
}
/**
* Tries to write the default ores if the file does not exist, then parses the file as JSON into this object's
* {@link #ores} array.
*/
public void load() throws IOException {
writeDefault();
parse();
}
/**
* @return All of the OreGenerationDefinitions for this config file.
*/
public OreGenerationDefinition[] getOres() {
return ores;
}
/**
* @return The file name for this configuration object.
*/
public String getFilename() {
return filename;
}
/**
* The default replaceable OreDict values for surface ores (dim 0).
*/
private static final List<String> DEFAULT_SURFACE_REPLACEABLE_OREDICTS = new ArrayList<String>() {{
add(OreDictEntries.STONE_ORE);
add(OreDictEntries.DIRT_ORE);
add(OreDictEntries.SAND_ORE);
add(OreDictEntries.SANDSTONE_ORE);
add(OreDictEntries.GRAVEL_ORE);
add(OreDictEntries.GRASS_ORE);
}};
private static final List<Pair<Block, Integer>> DEFAULT_MESA_REPLACEABLE_BLOCKS = new ArrayList<Pair<Block, Integer>>() {{
add(Pair.of(Blocks.HARDENED_CLAY, OreDictionary.WILDCARD_VALUE));
add(Pair.of(Blocks.STAINED_HARDENED_CLAY, OreDictionary.WILDCARD_VALUE));
}};
/**
* The default replaceable OreDict values for nether ores (dim -1).
*/
private static final List<String> DEFAULT_NETHER_REPLACEABLE_OREDICTS = new ArrayList<String>() {{
add(OreDictEntries.NETHERRACK_ORE);
}};
/**
* The default replaceable OreDict values for end ores (dim 1).
*/
private static final List<String> DEFAULT_END_REPLACEABLE_OREDICTS = new ArrayList<String>() {{
add(OreDictEntries.ENDSTONE_ORE);
}};
/**
* The default {@link OreGenerationDefinition}s, providing the following:
*
* Copper:
* - Extreme Hills (all kinds), overworld, Y80-128, 5 per chunk
* - Hell, nether dimension, Y0-128, 10 per chunk
* - Sky, end dimension, Y0-128, 10 per chunk
*
* Zinc:
* - Desert (and hills), overworld, Y65-80, 5 per chunk
* - Mesa (all kinds), overworld, Y65-80, 5 per chunk
* - Hell, nether dimension, Y0-128, 10 per chunk
* - Sky, end dimension, Y0-128, 10 per chunk
*/
private static final OreGenerationDefinition[] DEFAULT_ORES = {
new OreGenerationDefinition(
new BiomeDefinition[] {
new BiomeDefinition(0, Biomes.EXTREME_HILLS, 60, 90, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(0, Biomes.EXTREME_HILLS_EDGE, 60, 90, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(0, Biomes.EXTREME_HILLS_WITH_TREES, 60, 90, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(-1, Biomes.HELL, 0, 128, 8, 10, DEFAULT_NETHER_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(1, Biomes.SKY, 0, 128, 8, 10, DEFAULT_END_REPLACEABLE_OREDICTS, new ArrayList<>())
},
OreDictEntries.MATERIAL_COPPER),
new OreGenerationDefinition(
new BiomeDefinition[] {
new BiomeDefinition(0, Biomes.DESERT, 40, 70, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(0, Biomes.DESERT_HILLS, 60, 80, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(0, Biomes.MESA, 40, 80, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, DEFAULT_MESA_REPLACEABLE_BLOCKS),
new BiomeDefinition(0, Biomes.MESA_CLEAR_ROCK, 40, 80, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, DEFAULT_MESA_REPLACEABLE_BLOCKS),
new BiomeDefinition(0, Biomes.MESA_ROCK, 40, 80, 8, 5, DEFAULT_SURFACE_REPLACEABLE_OREDICTS, DEFAULT_MESA_REPLACEABLE_BLOCKS),
new BiomeDefinition(-1, Biomes.HELL, 0, 128, 8, 10, DEFAULT_NETHER_REPLACEABLE_OREDICTS, new ArrayList<>()),
new BiomeDefinition(1, Biomes.SKY, 0, 128, 8, 10, DEFAULT_END_REPLACEABLE_OREDICTS, new ArrayList<>())
},
OreDictEntries.MATERIAL_ZINC)
};
/**
* Converts a list of strings into a JsonArray of strings.
*/
private JsonArray writeStringsToArray(Iterable<String> strings) {
JsonArray ary = new JsonArray();
for (String entry : strings) {
ary.add(gson.fromJson(entry, JsonElement.class));
}
return ary;
}
/**
* Converts a list of Block;Integer pairs into a JsonArray of strings. The pair is turned into the format
* domain:path(:metadata) where () is optional.
*/
private JsonArray writeBlocksToArray(Iterable<Pair<Block, Integer>> blocks) {
JsonArray ary = new JsonArray();
for (Pair<Block, Integer> entry : blocks) {
Block block = entry.getLeft();
Integer meta = entry.getRight();
StringBuilder out = new StringBuilder();
out.append(block.getRegistryName());
if (meta != OreDictionary.WILDCARD_VALUE) {
out.append(":").append(meta);
}
ary.add(new JsonPrimitive(out.toString()));
}
return ary;
}
/**
* Converts a {@link BiomeDefinition} into a JsonObject, with keys "Dimension", "Biome", "MinY", "MaxY",
* "MaxVeinSize", "MaxVeinsPerChunk", and "ReplaceableBlocks".
*/
private JsonObject writeBiomeToObject(BiomeDefinition biome) {
JsonObject obj = new JsonObject();
obj.addProperty("Dimension", biome.getDimension());
Biome actualBiome = biome.getBiomeMatcher().getBiome();
obj.addProperty("Biome", actualBiome == null ? "*" : actualBiome.getRegistryName().toString());
obj.addProperty("MinY", biome.getMinY());
obj.addProperty("MaxY", biome.getMaxY());
obj.addProperty("MaxVeinSize", biome.getMaxVeinSize());
obj.addProperty("MaxVeinsPerChunk", biome.getMaxVeinsPerChunk());
JsonArray oreDicts = writeStringsToArray(biome.getReplaceableBlocksOreDict());
JsonArray blocks = writeBlocksToArray(biome.getReplaceableBlocksAndMeta());
blocks.addAll(oreDicts);
obj.add("ReplaceableBlocks", blocks);
return obj;
}
/**
* Converts an array of {@link BiomeDefinition}s into a JsonArray of JsonObjects.
* @see #writeBiomeToObject(BiomeDefinition)
*/
private JsonArray writeBiomesToArray(BiomeDefinition[] biomes) {
JsonArray ary = new JsonArray();
for (BiomeDefinition biome : biomes) {
ary.add(writeBiomeToObject(biome));
}
return ary;
}
/**
* Writes the default ores ({@link #DEFAULT_ORES}) to the file ({@link #getFilename()}) using all of the write
* methods.
*/
private void writeDefault() throws IOException {
File file = new File(getFilename());
if (file.exists() && file.isFile()) {
return;
}
JsonObject main = new JsonObject();
JsonObject ores = new JsonObject();
for (OreGenerationDefinition ore : DEFAULT_ORES) {
ores.add(ore.getOreName(), writeBiomesToArray(ore.getBiomeDefinitions()));
}
main.add("Ores", ores);
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
try (Writer writer = new BufferedWriter(new FileWriter(getFilename()))) {
writer.write(gson.toJson(main));
}
}
/**
* Converts a JsonArray of strings into a List of strings. It excludes all invalid OreDict names, for
* safety, and to skip domain:path(:metadata) values in ReplaceableBlocks.
*/
private List<String> getOreDictsFromArray(JsonArray ary) {
List<String> oreDicts = new ArrayList<>();
ary.forEach((element) -> {
String str = element.getAsString();
if (OreDictionary.doesOreNameExist(str)) {
oreDicts.add(str);
}
});
return oreDicts;
}
/**
* Converts a JsonArray of strings into a List of Block;Integer pairs. It expects string values in the format
* domain:path(:metadata) where () is optional.
*/
private List<Pair<Block, Integer>> getBlockMetaPairsFromArray(JsonArray ary) {
List<Pair<Block, Integer>> blockMetaPairs = new ArrayList<>();
ary.forEach((element) -> {
String str = element.getAsString();
String[] pieces = str.split(":");
String domain = "minecraft";
String path;
int meta = OreDictionary.WILDCARD_VALUE;
if (pieces.length == 1) {
path = pieces[0];
} else if (pieces.length == 2) {
domain = pieces[0];
path = pieces[1];
} else {
if (pieces.length > 3) {
FMLLog.warning("[EI] More than 3 values separated by : in ReplaceableBlocks '%s' for Ore generation config. Expected maximum: 'domain:path:meta'. This may indicate a bug in your config!", str);
}
domain = pieces[0];
path = pieces[1];
meta = Integer.valueOf(pieces[2]);
}
blockMetaPairs.add(Pair.of(Block.REGISTRY.getObject(new ResourceLocation(domain, path)), meta));
});
return blockMetaPairs;
}
/**
* Converts a JsonObject into a {@link BiomeDefinition}. It expects "Biome", "ReplaceableBlocks", "Dimension",
* "MinY", "MaxY", "MaxVeinSize", and "MaxVeinsPerChunk" keys.
*/
private BiomeDefinition parseBiome(JsonObject obj) {
JsonArray replaceableBlocksAry = obj.getAsJsonArray("ReplaceableBlocks");
List<String> oreDicts = getOreDictsFromArray(replaceableBlocksAry);
List<Pair<Block, Integer>> blockMetaPairs = getBlockMetaPairsFromArray(replaceableBlocksAry);
String biomeString = obj.get("Biome").getAsString();
BiomeMatcher biomeMatcher = new BiomeMatcher("*".equals(biomeString) ? null : Biome.REGISTRY.getObject(new ResourceLocation(biomeString)));
return new BiomeDefinition(obj.get("Dimension").getAsInt(), biomeMatcher, obj.get("MinY").getAsInt(),
obj.get("MaxY").getAsInt(), obj.get("MaxVeinSize").getAsInt(), obj.get("MaxVeinsPerChunk").getAsInt(),
oreDicts, blockMetaPairs);
}
/**
* Converts a JsonObject into a list of {@link OreGenerationDefinition}s. The JsonObject is expected to be in the
* format of { "OreName": {object} }, where object is a {@link BiomeDefinition} parsed using
* {@link #parseBiome(JsonObject)}. It skips every OreName that ends with "comment" (because some people like
* _comment, some like comment, some like __comment, etc.).
*/
private List<OreGenerationDefinition> parseOreArrayObject(JsonObject object) {
List<OreGenerationDefinition> ores = new ArrayList<>();
for (Map.Entry<String, JsonElement> ore : object.entrySet()) {
String oreName = ore.getKey();
if (oreName.endsWith("comment")) {
continue;
}
if (oreName.equals(OreDictEntries.MATERIAL_COPPER) || oreName.equals(OreDictEntries.MATERIAL_ZINC)) {
JsonElement value = ore.getValue();
if (value.isJsonArray()) {
JsonArray array = value.getAsJsonArray();
List<BiomeDefinition> biomes = new ArrayList<>();
array.forEach((element) -> {
if (element.isJsonObject()) {
biomes.add(parseBiome(element.getAsJsonObject()));
} else {
throw new IllegalArgumentException(String.format("A value in %s array is not an object.", oreName));
}
});
ores.add(new OreGenerationDefinition(biomes.toArray(new BiomeDefinition[biomes.size()]), oreName));
} else {
throw new IllegalArgumentException(String.format("Ore value for %s must be an array.", oreName));
}
} else {
throw new IllegalArgumentException(String.format("Ore %s is defined, but is not a valid material. Only 'Zinc' and 'Copper' are allowed.", oreName));
}
}
return ores;
}
/**
* Parses the file name's JSON into the ores and fallbacks arrays.
*
* Expects the file to already exist, so it's likely that before calling you should call writeDefault().
*/
private void parse() throws IOException {
JsonParser parser = new JsonParser();
JsonObject main = parser.parse(FileUtils.readFileToString(new File(getFilename()))).getAsJsonObject();
List<OreGenerationDefinition> ores = parseOreArrayObject(main.getAsJsonObject("Ores"));
this.ores = ores.toArray(new OreGenerationDefinition[ores.size()]);
}
}