package greymerk.roguelike.dungeon;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import greymerk.roguelike.Roguelike;
import greymerk.roguelike.config.RogueConfig;
import greymerk.roguelike.dungeon.settings.ISettings;
import greymerk.roguelike.dungeon.settings.SettingsContainer;
import greymerk.roguelike.dungeon.settings.SettingsResolver;
import greymerk.roguelike.dungeon.settings.SpawnCriteria;
import greymerk.roguelike.treasure.ITreasureChest;
import greymerk.roguelike.treasure.Treasure;
import greymerk.roguelike.treasure.TreasureManager;
import greymerk.roguelike.treasure.loot.Book;
import greymerk.roguelike.util.WeightedChoice;
import greymerk.roguelike.worldgen.Cardinal;
import greymerk.roguelike.worldgen.Coord;
import greymerk.roguelike.worldgen.IWorldEditor;
import greymerk.roguelike.worldgen.VanillaStructure;
import greymerk.roguelike.worldgen.shapes.RectSolid;
import net.minecraft.block.material.Material;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.BiomeDictionary.Type;
public class Dungeon implements IDungeon{
private static final String SETTINGS_DIRECTORY = RogueConfig.configDirName + "/settings";
public static SettingsResolver settingsResolver;
private DungeonGenerator generator;
private Coord pos;
private IWorldEditor editor;
static{
try{
RogueConfig.reload(false);
initResolver();
} catch(Exception e) {
// do nothing
}
}
public static void initResolver() throws Exception{
File settingsDir = new File(SETTINGS_DIRECTORY);
if(settingsDir.exists() && !settingsDir.isDirectory()){
throw new Exception("Settings directory is a file");
}
if(!settingsDir.exists()){
settingsDir.mkdir();
}
File[] settingsFiles = settingsDir.listFiles();
Arrays.sort(settingsFiles);
SettingsContainer settings = new SettingsContainer();
settingsResolver = new SettingsResolver(settings);
Map<String, String> files = new HashMap<String, String>();
for(File file : Arrays.asList(settingsFiles)){
try {
String content = Files.toString(file, Charsets.UTF_8);
files.put(file.getName(), content);
} catch (IOException e) {
throw new Exception("Error reading file : " + file.getName());
}
}
settings.parseCustomSettings(files);
}
public Dungeon(IWorldEditor editor){
this.generator = new DungeonGenerator();
this.editor = editor;
}
public void generateNear(Random rand, int x, int z){
if(Dungeon.settingsResolver == null) return;
int attempts = 50;
for(int i = 0;i < attempts;i++){
Coord location = getNearbyCoord(rand, x, z, 40, 100);
if(!validLocation(rand, location.getX(), location.getZ())) continue;
ISettings setting;
try{
setting = Dungeon.settingsResolver.getSettings(editor, rand, location);
} catch(Exception e){
return;
}
if(setting == null) return;
generate(setting, location.getX(), location.getZ());
return;
}
}
public void generate(ISettings settings, int inX, int inZ){
generator.generate(editor, settings, inX, inZ);
this.pos = new Coord(inX, 50, inZ);
Random rand = getRandom(editor, this.pos.getX(), this.pos.getZ());
TreasureManager treasure = editor.getTreasure();
settings.getLootRules().process(rand, treasure);
// TODO: Change start book details
Book book = new Book("Greymerk", "Statistics");
book.addPage("~Architect's Resource Notes~\n\n"
+ "StoneBrick: " + editor.getStat(Blocks.STONEBRICK) + "\n"
+ "Cobblestone: " + editor.getStat(Blocks.COBBLESTONE) + "\n"
+ "Logs: " + (editor.getStat(Blocks.LOG) + editor.getStat(Blocks.LOG2)) + "\n"
+ "Iron Bars: " + editor.getStat(Blocks.IRON_BARS) + "\n"
+ "Chests: " + (editor.getStat(Blocks.CHEST) + editor.getStat(Blocks.TRAPPED_CHEST)) + "\n"
+ "Mob Spawners: " + editor.getStat(Blocks.MOB_SPAWNER) + "\n"
+ "TNT: " + editor.getStat(Blocks.TNT) + "\n"
+ "\n-Greymerk");
book.addPage("Roguelike Dungeons v" + Roguelike.version + "\n"
+ Roguelike.date + "\n\n"
+ "Credits\n\n"
+ "Author: Greymerk\n\n"
+ "Bits: Drainedsoul\n\n"
+ "Ideas: Eniko @enichan");
treasure.addItemToAll(rand, Treasure.STARTER, new WeightedChoice<ItemStack>(book.get(), 1), 1);
}
public static boolean canSpawnInChunk(int chunkX, int chunkZ, IWorldEditor editor){
if(!RogueConfig.getBoolean(RogueConfig.DONATURALSPAWN)) return false;
int dim = editor.getDimension();
List<Integer> wl = new ArrayList<Integer>();
wl.addAll(RogueConfig.getIntList(RogueConfig.DIMENSIONWL));
List<Integer> bl = new ArrayList<Integer>();
bl.addAll(RogueConfig.getIntList(RogueConfig.DIMENSIONBL));
if(!SpawnCriteria.isValidDimension(dim, wl, bl)) return false;
int frequency = RogueConfig.getInt(RogueConfig.SPAWNFREQUENCY);
int min = 8 * frequency / 10;
int max = 32 * frequency / 10;
min = min < 2 ? 2 : min;
max = max < 8 ? 8 : max;
int tempX = chunkX < 0 ? chunkX - (max - 1) : chunkX;
int tempZ = chunkZ < 0 ? chunkZ - (max - 1) : chunkZ;
int m = tempX / max;
int n = tempZ / max;
Random r = editor.getSeededRandom(m, n, 10387312);
m *= max;
n *= max;
m += r.nextInt(max - min);
n += r.nextInt(max - min);
if(!(chunkX == m && chunkZ == n)){
return false;
}
return true;
}
public void spawnInChunk(Random rand, int chunkX, int chunkZ) {
if(Dungeon.canSpawnInChunk(chunkX, chunkZ, editor)){
int x = chunkX * 16 + 4;
int z = chunkZ * 16 + 4;
generateNear(rand, x, z);
}
}
public static int getLevel(int y){
if (y < 15) return 4;
if (y < 25) return 3;
if (y < 35) return 2;
if (y < 45) return 1;
return 0;
}
public boolean validLocation(Random rand, int x, int z){
Coord pos = new Coord(x, 0, z);
Biome biome = editor.getBiome(pos);
Type[] invalidBiomes = new Type[]{
BiomeDictionary.Type.RIVER,
BiomeDictionary.Type.BEACH,
BiomeDictionary.Type.MUSHROOM,
BiomeDictionary.Type.OCEAN
};
for(Type type : invalidBiomes){
if(BiomeDictionary.hasType(biome, type)) return false;
}
Coord stronghold = editor.findNearestStructure(VanillaStructure.STRONGHOLD, pos);
if(stronghold != null){
double strongholdDistance = pos.distance(stronghold);
if(strongholdDistance < 300) return false;
}
int upperLimit = RogueConfig.getInt(RogueConfig.UPPERLIMIT);
int lowerLimit = RogueConfig.getInt(RogueConfig.LOWERLIMIT);
Coord cursor = new Coord(x, upperLimit, z);
if(!editor.isAirBlock(cursor)){
return false;
}
while(!editor.validGroundBlock(cursor)){
cursor.add(Cardinal.DOWN);
if(cursor.getY() < lowerLimit) return false;
if(editor.getBlock(cursor).getMaterial() == Material.WATER) return false;
}
for (Coord c : new RectSolid(new Coord(x - 4, cursor.getY() + 4, z - 4), new Coord(x + 4, cursor.getY() + 4, z + 4))){
if(editor.validGroundBlock(c)){
return false;
}
}
int airCount = 0;
for (Coord c : new RectSolid(new Coord(x - 4, cursor.getY() - 3, z - 4), new Coord(x + 4, cursor.getY() - 3, z + 4))){
if(!editor.validGroundBlock(c)){
airCount++;
}
if(airCount > 8){
return false;
}
}
return true;
}
public static Coord getNearbyCoord(Random rand, int x, int z, int min, int max){
int distance = min + rand.nextInt(max - min);
double angle = rand.nextDouble() * 2 * Math.PI;
int xOffset = (int) (Math.cos(angle) * distance);
int zOffset = (int) (Math.sin(angle) * distance);
Coord nearby = new Coord(x + xOffset, 0, z + zOffset);
return nearby;
}
public static Random getRandom(IWorldEditor editor, int x, int z){
long seed = editor.getSeed() * x * z;
Random rand = new Random();
rand.setSeed(seed);
return rand;
}
@Override
public List<ITreasureChest> getChests() {
return this.editor.getTreasure().getChests();
}
public Coord getPosition() throws Exception {
if(this.pos == null){
throw new Exception("Dungeon not yet generated");
}
return this.pos;
}
}