package com.bioxx.tfc2.world; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.PriorityBlockingQueue; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.relauncher.Side; import com.bioxx.jmapgen.*; import com.bioxx.jmapgen.IslandParameters.Feature; import com.bioxx.jmapgen.IslandParameters.Feature.FeatureSig; import com.bioxx.tfc2.TFC; import com.bioxx.tfc2.api.*; import com.bioxx.tfc2.api.AnimalSpawnRegistry.SpawnGroup; import com.bioxx.tfc2.api.events.IslandGenEvent; import com.bioxx.tfc2.api.trees.TreeRegistry; import com.bioxx.tfc2.api.types.ClimateTemp; import com.bioxx.tfc2.api.types.Moisture; import com.bioxx.tfc2.api.types.StoneType; import com.bioxx.tfc2.api.util.Helper; import com.bioxx.tfc2.api.util.IThreadCompleteListener; import com.bioxx.tfc2.networking.server.SMapRequestPacket; public class WorldGen implements IThreadCompleteListener { private static WorldGen instance; private static WorldGen instanceClient; private static boolean SHOULD_RESET_SERVER = false; private static boolean SHOULD_RESET_CLIENT = false; final java.util.Map<Integer, CachedIsland> islandCache; public World world; public static final int ISLAND_SIZE = 4096; private Queue<Integer> mapQueue; private ThreadBuild[] buildThreads; public static WorldGen getInstance() { if(FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) return instance; return instanceClient; } public static void ClearInstances() { SHOULD_RESET_CLIENT = true; SHOULD_RESET_SERVER = true; } public WorldGen(World w) { world = w; islandCache = Collections.synchronizedMap(new ConcurrentHashMap<Integer, CachedIsland>()); mapQueue = new PriorityBlockingQueue<Integer>(); buildThreads = new ThreadBuild[TFCOptions.maxThreadsForIslandGen]; } public static void initialize(World world) { if(FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { if(instance == null || SHOULD_RESET_SERVER) { instance = new WorldGen(world); SHOULD_RESET_SERVER = false; } } else { if(instanceClient == null || SHOULD_RESET_CLIENT) { instanceClient = new WorldGen(world); SHOULD_RESET_CLIENT = false; } } } public IslandMap getIslandMap(BlockPos pos) { return getIslandMap(pos.getX(), pos.getZ()); } /** * Retrieves an island map from the cache or creates it if needed. This is a pass-through method to an internal method which retrieves * the island map and hands neighboring island maps off to other threads for generation. Coordinates should already be in MapCoords form. */ public IslandMap getIslandMap(int x, int z) { int id = Helper.combineCoords(x, z); //First we try to load the map from disk if it exists if(!islandCache.containsKey(id)) { loadMap(x, z); } //If the map did not exist on disk then create it from scratch if(!islandCache.containsKey(id)) { if(FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT) createFakeMap(x, z); else createIsland(x, z); } return getMap(x, z); } private IslandMap createFakeMap(int x, int z) { long seed = world.getSeed()+Helper.combineCoords(x, z); TFC.network.sendToServer(new SMapRequestPacket(x, z)); return createFakeMap(x, z, seed, false); } public IslandMap createFakeMap(int x, int z, long seed, boolean overwrite) { IslandParameters id = createParams(seed, x, z); IslandMap mapgen = new IslandMap(ISLAND_SIZE, seed); mapgen.newIsland(id); mapgen.generateFake(); CachedIsland ci = new CachedIsland(mapgen); if(!islandCache.containsKey(Helper.combineCoords(x, z))) islandCache.put(Helper.combineCoords(x, z), ci); else if(overwrite && islandCache.containsKey(Helper.combineCoords(x, z))) { islandCache.remove(Helper.combineCoords(x, z)); islandCache.put(Helper.combineCoords(x, z), ci); } return mapgen; } private IslandMap getMap(int x, int z) { int id = Helper.combineCoords(x, z); CachedIsland ci = islandCache.get(id); //Should only ever be 0 if this map was created but never accessed by the game. if(ci.lastAccess == 0) { //Add the neighbor maps to the mapQueue for generation in another thread mapQueue.add(Helper.combineCoords(x+1, z)); mapQueue.add(Helper.combineCoords(x+1, z-1)); mapQueue.add(Helper.combineCoords(x, z-1)); mapQueue.add(Helper.combineCoords(x-1, z-1)); mapQueue.add(Helper.combineCoords(x-1, z)); mapQueue.add(Helper.combineCoords(x-1, z+1)); mapQueue.add(Helper.combineCoords(x, z+1)); mapQueue.add(Helper.combineCoords(x+1, z+1)); } return ci.getIslandMap(); } public boolean isMapLoaded(int x, int z) { return isMapLoaded(Helper.combineCoords(x, z)); } public boolean isMapLoaded(int id) { return islandCache.get(id) != null; } private IslandMap createIsland(int x, int z) { long seed = world.getSeed()+Helper.combineCoords(x, z); IslandGenEvent.Pre preEvent = new IslandGenEvent.Pre(createParams(seed, x, z)); Global.EVENT_BUS.post(preEvent); IslandMap mapgen = new IslandMap(ISLAND_SIZE, seed); mapgen.newIsland(preEvent.params); mapgen.getIslandData().islandLevel = Math.abs(x); if(x == 0) { mapgen.getIslandData().unlockIsland(); } mapgen.generateFull(); IslandGenEvent.Post postEvent = new IslandGenEvent.Post(mapgen); Global.EVENT_BUS.post(postEvent); CachedIsland ci = new CachedIsland(postEvent.islandMap); saveMap(ci); islandCache.put(Helper.combineCoords(x, z), ci); return ci.island; } private IslandParameters createParams(long seed, int x, int z) { IslandParameters id = new IslandParameters(seed, ISLAND_SIZE, 0.5, 0.3); Random r = new Random(seed); id.setCoords(x, z); int fcount = 2+r.nextInt(1)+r.nextInt(1); Feature.setupFeatures(r); //Choose Major Features for(int i = 0; i < fcount; i++) { Feature f = Feature.getRandomFeature(FeatureSig.Major); if(f == Feature.Canyons) id.setFeatures(Feature.Gorges); if(f == Feature.NoLand && x == 0) continue; if((f == Feature.SharperMountains || f == Feature.EvenSharperMountains) && (id.hasFeature(Feature.SharperMountains) || id.hasFeature(Feature.EvenSharperMountains))) { i--; continue; } if(id.hasFeature(f)){i--; continue;} else id.setFeatures(f); } //Choose Minor Features fcount = r.nextInt(3)-r.nextInt(1)-r.nextInt(1); for(int i = 0; i < fcount; i++) { Feature f = Feature.getRandomFeature(FeatureSig.Minor); if(id.hasFeature(f)){i--; continue;} else id.setFeatures(f); } //id.setFeatures(Feature.Volcano); if(id.hasFeature(Feature.LowLand)) id.removeFeatures(Feature.SharperMountains, Feature.EvenSharperMountains); //Remove all other features if this is supposed to be open ocean. if(id.hasFeature(Feature.NoLand)) { id.clearFeatures(); id.setFeatures(Feature.NoLand); } //All plots too far north or south will be water. The world is only 9 islands tall. if(z > 4 || z < -4) { id.clearFeatures(); id.setFeatures(Feature.NoLand); } RandomCollection<Integer> heightPot = new RandomCollection<Integer>(r); heightPot.add(0.1, 64); heightPot.add(0.8, 96); heightPot.add(0.1, 128); id.islandMaxHeight = heightPot.next(); if(id.hasFeature(Feature.LowLand)) { id.islandMaxHeight = 32; } RandomCollection<StoneType> stonePot = new RandomCollection<StoneType>(r); //If the island has a volcano then we need to make sure that the island is properly volcanic. if(!id.hasFeature(Feature.Volcano)) { for(StoneType s : StoneType.values()) { stonePot.add(1.0, s); } } else { id.islandMaxHeight = 128; for(StoneType s : StoneType.getForSubTypes(StoneType.SubType.IgneousExtrusive)) { stonePot.add(1.0, s); } } id.setSurfaceRock(stonePot.next()); ClimateTemp t = ClimateTemp.TEMPERATE; //Tropical or Temperate if(Math.abs(z) == 0) { t = ClimateTemp.TROPICAL; } else if(Math.abs(z) == 1) { t = r.nextBoolean() ? ClimateTemp.TEMPERATE : ClimateTemp.SUBTROPICAL; } else if(Math.abs(z) == 2) { t = ClimateTemp.TEMPERATE; } else if(Math.abs(z) == 3) { t = r.nextBoolean() ? ClimateTemp.TEMPERATE : ClimateTemp.SUBPOLAR; } else if(Math.abs(z) == 4) { t = ClimateTemp.POLAR; } id.setIslandTemp(t); id.setFeatures(Feature.Valleys); Moisture m = Moisture.fromVal(r.nextDouble()); id.setIslandMoisture(m); if(m == Moisture.LOW && r.nextDouble() < 0.4) { id.setFeatures(Feature.Desert); } String common = TreeRegistry.instance.getRandomTreeTypeForIsland(r, t, m); String uncommon = TreeRegistry.instance.getRandomTreeTypeForIsland(r, t, m); String rare = TreeRegistry.instance.getRandomTreeTypeForIsland(r, t, m); id.setTrees(common, uncommon, rare); /*** * Animals */ ArrayList<SpawnGroup> spawnGroups = AnimalSpawnRegistry.getInstance().getValidSpawnGroups(id); int max = Math.min(spawnGroups.size(), 4+r.nextInt(Math.max(spawnGroups.size()-4, 1))); for(int i = 0; i < max; i++) { SpawnGroup group = spawnGroups.get(r.nextInt(spawnGroups.size())); id.animalSpawnGroups.add(group); spawnGroups.remove(group); } /** * Crops */ ArrayList<Crop> suitableCrops = Crop.getCropsForTemp(t); int numCrops = 1 + (id.hasFeature(Feature.DiverseCrops) ? 1+r.nextInt(2) : 0); numCrops = Math.min(numCrops, suitableCrops.size()); for(int i = 0; i < numCrops; i++) { id.addCrop(suitableCrops.get(r.nextInt(suitableCrops.size()))); } return id; } public void resetCache() { synchronized(islandCache) { for(CachedIsland c : islandCache.values()) { saveMap(c); } islandCache.clear(); } } public void trimCache() { long now = System.currentTimeMillis(); int key; Set<Integer> keys = islandCache.keySet(); CachedIsland c; for(Iterator<Integer> iter = keys.iterator(); iter.hasNext();) { key = iter.next(); c = islandCache.get(key); if(c != null && now-c.lastAccess > 20000)//20 seconds of no access will trim the map { saveMap(c); islandCache.remove(key); } } } public void saveMap(CachedIsland island) { try { File file1 = world.getSaveHandler().getMapFileFromName("Map " + island.island.getParams().getXCoord() + "," + island.island.getParams().getZCoord()); if (file1 != null) { NBTTagCompound islandNBT = new NBTTagCompound(); island.island.writeToNBT(islandNBT); NBTTagCompound finalNBT = new NBTTagCompound(); finalNBT.setTag("mapdata", islandNBT); island.island.getParams().writeToNBT(finalNBT); finalNBT.setLong("lastAccess", island.lastAccess); FileOutputStream fileoutputstream = new FileOutputStream(file1); CompressedStreamTools.writeCompressed(finalNBT, fileoutputstream); fileoutputstream.close(); } } catch (Exception exception) { exception.printStackTrace(); } } public CachedIsland loadMap(int x, int z) { try { File file1 = world.getSaveHandler().getMapFileFromName("Map " + x + "," + z); if (file1 != null && file1.exists()) { FileInputStream input = new FileInputStream(file1); NBTTagCompound nbt = CompressedStreamTools.readCompressed(input); input.close(); IslandParameters ip = new IslandParameters(); ip.readFromNBT(nbt); long seed = world.getSeed()+Helper.combineCoords(x, z); IslandMap m = new IslandMap(ISLAND_SIZE, seed); m.newIsland(ip); m.readFromNBT(nbt.getCompoundTag("mapdata")); CachedIsland ci = new CachedIsland(m); ci.lastAccess = nbt.getLong("lastAccess"); islandCache.put(Helper.combineCoords(x, z), ci); return ci; } } catch (Exception exception) { TFC.log.warn("Error Loading Island: " + x + ", " + z + " | Will rebuild Island Map"); //exception.printStackTrace(); } return null; } private boolean doesMapExist(int x, int z) { File file1 = world.getSaveHandler().getMapFileFromName("Map " + x + "," + z); return (file1 != null && file1.exists()); } public void buildFromQueue() { if(mapQueue.size() == 0) return; for(int i = 0; i < buildThreads.length; i++) { if(buildThreads[i] == null && mapQueue.size() > 0) { int id = mapQueue.poll(); if(doesMapExist(Helper.getXCoord(id), Helper.getYCoord(id))) return; buildThreads[i] = new ThreadBuild(i, "Map Build Thread: "+i, id); buildThreads[i].setPriority(2); buildThreads[i].addListener(this); buildThreads[i].start(); } } } public void runUpdateLoop() { for(CachedIsland ci : islandCache.values()) { ci.update(); } } private class ThreadBuild extends Thread { private String threadName; private Thread t; private int id; public final int threadID; public ThreadBuild(int threadid, String n, int cantorizedID) { threadName = n; id = cantorizedID; threadID = threadid; } private final Set<IThreadCompleteListener> listeners = new CopyOnWriteArraySet<IThreadCompleteListener>(); public final void addListener(final IThreadCompleteListener listener) { listeners.add(listener); } public final void removeListener(final IThreadCompleteListener listener) { listeners.remove(listener); } private final void notifyListeners() { for (IThreadCompleteListener listener : listeners) { listener.notifyOfThreadComplete(this); } } @Override public void run() { try { createIsland(Helper.getXCoord(id),Helper.getYCoord(id)); } catch(Exception e) { e.printStackTrace(); } finally { notifyListeners(); } } @Override public void start() { if (t == null) { t = new Thread (this, threadName); t.start(); } } } @Override public void notifyOfThreadComplete(Thread thread) { buildThreads[((ThreadBuild)thread).threadID] = null; } }