package zmaster587.advancedRocketry.dimension; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import zmaster587.advancedRocketry.AdvancedRocketry; import zmaster587.advancedRocketry.api.AdvancedRocketryAPI; import zmaster587.advancedRocketry.api.Configuration; import zmaster587.advancedRocketry.api.dimension.IDimensionProperties; import zmaster587.advancedRocketry.api.dimension.solar.IGalaxy; import zmaster587.advancedRocketry.api.dimension.solar.StellarBody; import zmaster587.advancedRocketry.api.satellite.SatelliteBase; import zmaster587.advancedRocketry.api.stations.ISpaceObject; import zmaster587.advancedRocketry.network.PacketDimInfo; import zmaster587.advancedRocketry.stations.SpaceObject; import zmaster587.advancedRocketry.stations.SpaceObjectManager; import zmaster587.libVulpes.network.PacketHandler; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.WorldProvider; public class DimensionManager implements IGalaxy { //TODO: fix satellites not unloading on disconnect private Random random; private static DimensionManager instance = (DimensionManager) (AdvancedRocketryAPI.dimensionManager = new DimensionManager());public static final String workingPath = "advRocketry"; public static final String filePath = workingPath + "/temp.dat"; public static int dimOffset = 0; private boolean hasBeenInitiallized = false; public static String prevBuild; //Stat tracking public static boolean hasReachedMoon; public static boolean hasReachedWarp; //Reference to the worldProvider for any dimension created through this system, normally WorldProviderPlanet, set in AdvancedRocketry.java in preinit public static Class<? extends WorldProvider> planetWorldProvider; private HashMap<Integer,DimensionProperties> dimensionList; private HashMap<Integer, StellarBody> starList; public static final int GASGIANT_DIMID_OFFSET = 0x100; //Offset by 256 private static long nextSatelliteId; private static StellarBody sol; //The default properties belonging to the overworld public static DimensionProperties overworldProperties; //the default property for any dimension created in space, normally, space over earth public static DimensionProperties defaultSpaceDimensionProperties; public static StellarBody getSol() { return getInstance().getStar(0); } public static DimensionManager getInstance() { return instance; }; public DimensionManager() { dimensionList = new HashMap<Integer,DimensionProperties>(); starList = new HashMap<Integer, StellarBody>(); sol = new StellarBody(); sol.setTemperature(100); sol.setId(0); sol.setName("Sol"); addStar(sol); overworldProperties = new DimensionProperties(0); overworldProperties.setAtmosphereDensityDirect(100); overworldProperties.averageTemperature = 100; overworldProperties.gravitationalMultiplier = 1f; overworldProperties.orbitalDist = 100; overworldProperties.skyColor = new float[] {1f, 1f, 1f}; overworldProperties.setStar(sol); overworldProperties.setName("Earth"); overworldProperties.isNativeDimension = false; defaultSpaceDimensionProperties = new DimensionProperties(SpaceObjectManager.WARPDIMID, false); defaultSpaceDimensionProperties.setAtmosphereDensityDirect(0); defaultSpaceDimensionProperties.averageTemperature = 0; defaultSpaceDimensionProperties.gravitationalMultiplier = 0.1f; defaultSpaceDimensionProperties.orbitalDist = 100; defaultSpaceDimensionProperties.skyColor = new float[] {0f,0f,0f}; defaultSpaceDimensionProperties.setStar(sol); defaultSpaceDimensionProperties.setName("Space"); defaultSpaceDimensionProperties.fogColor = new float[] {0f,0f,0f}; defaultSpaceDimensionProperties.setParentPlanet(overworldProperties,false); defaultSpaceDimensionProperties.orbitalDist = 1; random = new Random(System.currentTimeMillis()); } /** * @return an Integer array of dimensions registered with this DimensionManager */ public Integer[] getRegisteredDimensions() { Integer ret[] = new Integer[dimensionList.size()]; return dimensionList.keySet().toArray(ret); } /** * @return List of dimensions registered with this manager that are currently loaded on the server/integrated server */ public Integer[] getLoadedDimensions() { return getRegisteredDimensions(); } /** * Increments the nextAvalible satellite ID and returns one * @return next avalible id for satellites */ public long getNextSatelliteId() { return nextSatelliteId++; } /** * @param satId long id of the satellite * @return a reference to the satellite object with the supplied ID */ public SatelliteBase getSatellite(long satId) { //Hack to allow monitoring stations to properly reload after a server restart //Because there should never be a tile in the world where no planets have been generated load file first //Worst thing that can happen is there is no file and it gets genned later and the monitor does not reconnect if(!hasBeenInitiallized) { zmaster587.advancedRocketry.dimension.DimensionManager.getInstance().loadDimensions(zmaster587.advancedRocketry.dimension.DimensionManager.filePath); } SatelliteBase satellite = overworldProperties.getSatellite(satId); if(satellite != null) return satellite; for(int i : DimensionManager.getInstance().getLoadedDimensions()) { if( (satellite = DimensionManager.getInstance().getDimensionProperties(i).getSatellite(satId)) != null ) return satellite; } return null; } //TODO: fix naming system /** * @param dimId id to register the planet with * @return the name for the next planet */ private String getNextName(int dimId) { return "Sol-" + dimId; } /** * Called every tick to tick satellites */ public void tickDimensions() { //Tick satellites overworldProperties.tick(); for(int i : DimensionManager.getInstance().getLoadedDimensions()) { DimensionManager.getInstance().getDimensionProperties(i).tick(); } } public void tickDimensionsClient() { //Tick satellites overworldProperties.updateOrbit(); for(int i : DimensionManager.getInstance().getLoadedDimensions()) { DimensionManager.getInstance().getDimensionProperties(i).updateOrbit(); } } /** * Sets the properies supplied for the supplied dimensionID, if the dimension does not exist, it is added to the list but not registered with minecraft * @param dimId id to set the properties of * @param properties to set for that dimension */ public void setDimProperties( int dimId, DimensionProperties properties) { dimensionList.put(new Integer(dimId),properties); } /** * Iterates though the list of existing dimIds, and returns the closest free id greater than two * @return next free id */ public int getNextFreeDim(int offset) { for(int i = offset; i < 10000; i++) { if(!net.minecraftforge.common.DimensionManager.isDimensionRegistered(i) && !dimensionList.containsKey(i)) return i; } return -1; } public int getNextFreeStarId() { for(int i = 0; i < Integer.MAX_VALUE; i++) { if(!starList.containsKey(i)) return i; } return -1; } public int getTemperature(StellarBody star, int orbitalDistance, int atmPressure) { return (star.getTemperature() + (100 - orbitalDistance)*15 + atmPressure*18)/20; } public DimensionProperties generateRandom(int starId, int atmosphereFactor, int distanceFactor, int gravityFactor) { return generateRandom(starId, 100, 100, 100, atmosphereFactor, distanceFactor, gravityFactor); } public DimensionProperties generateRandom(int starId, String name, int atmosphereFactor, int distanceFactor, int gravityFactor) { return generateRandom(starId, name, 100, 100, 100, atmosphereFactor, distanceFactor, gravityFactor); } /** * Creates and registers a planet with the given properties, Xfactor is the amount of variance from the supplied base property; ie: base - (factor/2) <= generated property value <= base - (factor/2) * @param name name of the planet * @param baseAtmosphere * @param baseDistance * @param baseGravity * @param atmosphereFactor * @param distanceFactor * @param gravityFactor * @return the new dimension properties created for this planet */ public DimensionProperties generateRandom(int starId, String name, int baseAtmosphere, int baseDistance, int baseGravity,int atmosphereFactor, int distanceFactor, int gravityFactor) { DimensionProperties properties = new DimensionProperties(getNextFreeDim(dimOffset)); if(properties.getId() == -1) return null; if(name == "") properties.setName(getNextName(properties.getId())); else { properties.setName(name); } properties.setAtmosphereDensityDirect(MathHelper.clamp_int(baseAtmosphere + random.nextInt(atmosphereFactor) - atmosphereFactor/2, 0, 200)); int newDist = properties.orbitalDist = MathHelper.clamp_int(baseDistance + random.nextInt(distanceFactor),0,200); properties.gravitationalMultiplier = Math.min(Math.max(0.05f,(baseGravity + random.nextInt(gravityFactor) - gravityFactor/2)/100f), 1.3f); double minDistance; int walkDist = 0; do { minDistance = Double.MAX_VALUE; for(IDimensionProperties properties2 : getStar(starId).getPlanets()) { int dist = Math.abs(((DimensionProperties)properties2).orbitalDist - newDist); if(minDistance > dist) minDistance = dist; } newDist = properties.orbitalDist + walkDist; if(walkDist > -1) walkDist = -walkDist - 1; else walkDist = -walkDist; } while(minDistance < 4); properties.orbitalDist = newDist; properties.orbitalPhi = (random.nextGaussian() -0.5d)*180; properties.rotationalPhi = (random.nextGaussian() -0.5d)*180; //Get Star Color properties.setStar(getStar(starId)); //Linear is easier. Earth is nominal! properties.averageTemperature = getTemperature(properties.getStar(), properties.orbitalDist, properties.getAtmosphereDensity()); properties.skyColor[0] *= 1 - MathHelper.clamp_float(random.nextFloat()*0.1f + (70 - properties.averageTemperature)/100f,0.2f,1); properties.skyColor[1] *= 1 - (random.nextFloat()*.5f); properties.skyColor[2] *= 1 - MathHelper.clamp_float(random.nextFloat()*0.1f + (properties.averageTemperature - 70)/100f,0,1); properties.rotationalPeriod = (int) (Math.pow((1/properties.gravitationalMultiplier),3) * 24000); properties.addBiomes(properties.getViableBiomes()); registerDim(properties, true); return properties; } public DimensionProperties generateRandom(int starId, int baseAtmosphere, int baseDistance, int baseGravity,int atmosphereFactor, int distanceFactor, int gravityFactor) { return generateRandom(starId, "", baseAtmosphere, baseDistance, baseGravity, atmosphereFactor, distanceFactor, gravityFactor); } public DimensionProperties generateRandomGasGiant(int starId, String name, int baseAtmosphere, int baseDistance, int baseGravity,int atmosphereFactor, int distanceFactor, int gravityFactor) { DimensionProperties properties = new DimensionProperties(getNextFreeDim(dimOffset)); if(name == "") properties.setName(getNextName(properties.getId())); else { properties.setName(name); } properties.setAtmosphereDensityDirect(MathHelper.clamp_int(baseAtmosphere + random.nextInt(atmosphereFactor) - atmosphereFactor/2, 0, 200)); properties.orbitalDist = MathHelper.clamp_int(baseDistance + random.nextInt(distanceFactor),0,200); //System.out.println(properties.orbitalDist); properties.gravitationalMultiplier = Math.min(Math.max(0.05f,(baseGravity + random.nextInt(gravityFactor) - gravityFactor/2)/100f), 1.3f); double minDistance; do { minDistance = Double.MAX_VALUE; properties.orbitTheta = random.nextInt(360)*(2f*Math.PI)/360f; for(IDimensionProperties properties2 : getStar(starId).getPlanets()) { double dist = Math.abs(((DimensionProperties)properties2).orbitTheta - properties.orbitTheta); if(dist < minDistance) minDistance = dist; } } while(minDistance < (Math.PI/40f)); //Get Star Color properties.setStar(getStar(starId)); //Linear is easier. Earth is nominal! properties.averageTemperature = getTemperature(properties.getStar(), properties.orbitalDist, properties.getAtmosphereDensity()); properties.setGasGiant(); //TODO: add gasses registerDim(properties, true); return properties; } /** * * @param dimId dimension id to check * @return true if it can be traveled to, in general if it has a surface */ public boolean canTravelTo(int dimId){ return net.minecraftforge.common.DimensionManager.isDimensionRegistered(dimId) && dimId != -1 && !getDimensionProperties(dimId).isGasGiant(); } /** * Attempts to register a dimension with {@link DimensionProperties}, if the dimension has not yet been registered, sends a packet containing the dimension information to all connected clients * @param properties {@link DimensionProperties} to register * @return false if the dimension has not been registered, true if it is being newly registered */ public boolean registerDim(DimensionProperties properties, boolean registerWithForge) { boolean bool = registerDimNoUpdate(properties, registerWithForge); if(bool) PacketHandler.sendToAll(new PacketDimInfo(properties.getId(), properties)); return bool; } /** * Attempts to register a dimension without sending an update to the client * @param properties {@link DimensionProperties} to register * @param registerWithForge if true also registers the dimension with forge * @return true if the dimension has NOT been registered before, false if the dimension IS registered exist already */ public boolean registerDimNoUpdate(DimensionProperties properties, boolean registerWithForge) { int dimId = properties.getId(); Integer dim = new Integer(dimId); if(dimensionList.containsKey(dim)) return false; //Avoid registering gas giants as dimensions if(registerWithForge && !properties.isGasGiant() && !net.minecraftforge.common.DimensionManager.isDimensionRegistered(dim)) { net.minecraftforge.common.DimensionManager.registerProviderType(properties.getId(), DimensionManager.planetWorldProvider, false); net.minecraftforge.common.DimensionManager.registerDimension(dimId, dimId); } dimensionList.put(dimId, properties); return true; } /** * Unregisters all dimensions associated with this DimensionManager from both Minecraft and this DimnensionManager */ public void unregisterAllDimensions() { for(Entry<Integer, DimensionProperties> dimSet : dimensionList.entrySet()) { if(dimSet.getValue().isNativeDimension && !dimSet.getValue().isGasGiant()) { net.minecraftforge.common.DimensionManager.unregisterProviderType(dimSet.getKey()); net.minecraftforge.common.DimensionManager.unregisterDimension(dimSet.getKey()); } } dimensionList.clear(); starList.clear(); starList.put(0, sol); } /** * Deletes and unregisters the dimensions, as well as all child dimensions, from the game * @param dimId the dimensionId to delete */ public void deleteDimension(int dimId) { DimensionProperties properties = dimensionList.get(dimId); properties.getStar().removePlanet(properties); if(properties.isMoon()) { properties.getParentProperties().removeChild(properties.getId()); } if(properties.hasChildren()) { Iterator<Integer> iterator = properties.getChildPlanets().iterator(); while (iterator.hasNext()){ Integer child = iterator.next(); iterator.remove(); //Avoid CME deleteDimension(child); PacketHandler.sendToAll(new PacketDimInfo(child, null)); } } //TODO: check for world loaded // If not native to AR let the mod it's registered to handle it if(!properties.isNativeDimension && net.minecraftforge.common.DimensionManager.isDimensionRegistered(dimId)) { net.minecraftforge.common.DimensionManager.unloadWorld(dimId); net.minecraftforge.common.DimensionManager.unregisterProviderType(dimId); net.minecraftforge.common.DimensionManager.unregisterDimension(dimId); dimensionList.remove(new Integer(dimId)); } //Delete World Folder File file = new File(net.minecraftforge.common.DimensionManager.getCurrentSaveRootDirectory(), workingPath + "/DIM" + dimId ); try { FileUtils.deleteDirectory(file); } catch(IOException e) { e.printStackTrace(); } } /** * * @param dimId id of the dimention of which to get the properties * @return DimensionProperties representing the dimId given */ @Override public DimensionProperties getDimensionProperties(int dimId) { DimensionProperties properties = dimensionList.get(new Integer(dimId)); if(dimId == Configuration.spaceDimId || dimId == Integer.MIN_VALUE) { return defaultSpaceDimensionProperties; } return properties == null ? overworldProperties : properties; } /** * @param id star id for which to get the object * @return the {@link StellarBody} object */ public StellarBody getStar(int id) { StellarBody star = starList.get(new Integer(id)); //if(star == null) //AdvancedRocketry.logger.warning("Attempted to get null star for ID " + id); return star; } /** * @return a list of star ids */ public Set<Integer> getStarIds() { return starList.keySet(); } public Collection<StellarBody> getStars() { return starList.values(); } /** * Adds a star to the handler * @param star star to add */ public void addStar(StellarBody star) { starList.put(star.getId(), star); } /** * Removes the star from the handler * @param id id of the star to remove */ public void removeStar(int id) { //TODO: actually remove subPlanets et starList.remove(id); } /** * Saves all dimension data, satellites, and space stations to disk, SHOULD NOT BE CALLED OUTSIDE OF WORLDSAVEEVENT * @param filePath file path to which to save the data */ public void saveDimensions(String filePath) { NBTTagCompound nbt = new NBTTagCompound(); NBTTagCompound dimListnbt = new NBTTagCompound(); //Save SolarSystems first NBTTagCompound solarSystem = new NBTTagCompound(); for(Entry<Integer, StellarBody> stars: starList.entrySet()) { NBTTagCompound solarNBT = new NBTTagCompound(); stars.getValue().writeToNBT(solarNBT); solarSystem.setTag(stars.getKey().toString(), solarNBT); } nbt.setTag("starSystems", solarSystem); //Save satelliteId nbt.setLong("nextSatelliteId", nextSatelliteId); //Save Overworld NBTTagCompound dimNbt = new NBTTagCompound(); overworldProperties.writeToNBT(dimNbt); dimListnbt.setTag("0", dimNbt); for(Entry<Integer, DimensionProperties> dimSet : dimensionList.entrySet()) { dimNbt = new NBTTagCompound(); dimSet.getValue().writeToNBT(dimNbt); dimListnbt.setTag(dimSet.getKey().toString(), dimNbt); } nbt.setTag("dimList", dimListnbt); //Stats NBTTagCompound stats = new NBTTagCompound(); stats.setBoolean("hasReachedMoon", hasReachedMoon); stats.setBoolean("hasReachedWarp", hasReachedWarp); nbt.setTag("stat", stats); NBTTagCompound nbtTag = new NBTTagCompound(); SpaceObjectManager.getSpaceManager().writeToNBT(nbtTag); nbt.setTag("spaceObjects", nbtTag); FileOutputStream outStream; try { File file = new File(net.minecraftforge.common.DimensionManager.getCurrentSaveRootDirectory(), filePath); if(!file.exists()) file.createNewFile(); outStream = new FileOutputStream(file); CompressedStreamTools.writeCompressed(nbt, outStream); outStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * @param dimId integer id of the dimension * @return true if the dimension exists and is registered */ public boolean isDimensionCreated( int dimId) { return dimensionList.containsKey(new Integer(dimId)) || dimId == Configuration.spaceDimId; } /** * Loads all information to rebuild the galaxy and solar systems from disk into the current instance of DimensionManager * @param filePath file path from which to load the information */ public boolean loadDimensions(String filePath) { hasBeenInitiallized = true; FileInputStream inStream; NBTTagCompound nbt; try { File file = new File(net.minecraftforge.common.DimensionManager.getCurrentSaveRootDirectory(), filePath); if(!file.exists()) { new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - file.getName().length())).mkdirs(); file.createNewFile(); return false; } inStream = new FileInputStream(file); nbt = CompressedStreamTools.readCompressed(inStream); inStream.close(); } catch (EOFException e) { //Silence you fool! //Patch to fix JEI printing when trying to load planets too early return false; } catch (FileNotFoundException e) { e.printStackTrace(); return false; } catch (IOException e) { //TODO: try not to obliterate planets in the future e.printStackTrace(); return false; } //Load SolarSystems first NBTTagCompound solarSystem = nbt.getCompoundTag("starSystems"); if(solarSystem.hasNoTags()) return false; NBTTagCompound stats = nbt.getCompoundTag("stat"); hasReachedMoon = stats.getBoolean("hasReachedMoon"); hasReachedWarp = stats.getBoolean("hasReachedWarp"); for(Object key : solarSystem.func_150296_c()) { NBTTagCompound solarNBT = solarSystem.getCompoundTag((String)key); StellarBody star = new StellarBody(); star.readFromNBT(solarNBT); starList.put(star.getId(), star); } nbt.setTag("starSystems", solarSystem); nextSatelliteId = nbt.getLong("nextSatelliteId"); NBTTagCompound dimListNbt = nbt.getCompoundTag("dimList"); for(Object key : dimListNbt.func_150296_c()) { String keyString = (String)key; //Special Handling for overworld if(keyString.equals("0")) { overworldProperties.readFromNBT(dimListNbt.getCompoundTag(keyString)); } else { DimensionProperties propeties = DimensionProperties.createFromNBT(Integer.parseInt(keyString) ,dimListNbt.getCompoundTag(keyString)); if(propeties != null) { int keyInt = Integer.parseInt(keyString); if(!net.minecraftforge.common.DimensionManager.isDimensionRegistered(keyInt) && propeties.isNativeDimension && !propeties.isGasGiant()) { net.minecraftforge.common.DimensionManager.registerProviderType(keyInt, DimensionManager.planetWorldProvider, false); net.minecraftforge.common.DimensionManager.registerDimension(keyInt, keyInt); //propeties.isNativeDimension = true; } dimensionList.put(new Integer(keyInt), propeties); } else{ Logger.getLogger("advancedRocketry").warning("Null Dimension Properties Recieved"); } //TODO: print unable to register world } } //Check for tag in case old version of Adv rocketry is in use if(nbt.hasKey("spaceObjects")) { NBTTagCompound nbtTag = nbt.getCompoundTag("spaceObjects"); SpaceObjectManager.getSpaceManager().readFromNBT(nbtTag); } //Try to fix invalid objects for(ISpaceObject i : SpaceObjectManager.getSpaceManager().getSpaceObjects()) { if(!isDimensionCreated(i.getOrbitingPlanetId()) && i.getOrbitingPlanetId() != 0 && i.getOrbitingPlanetId() != SpaceObjectManager.WARPDIMID) { AdvancedRocketry.logger.warn("Dimension ID " + i.getOrbitingPlanetId() + " is not registered and a space station is orbiting it, moving to dimid 0"); i.setOrbitingBody(0); } } prevBuild = nbt.getString("prevVersion"); nbt.setString("prevVersion", AdvancedRocketry.version); return true; } /** * @param destinationDimId * @param dimension * @return true if the two dimensions are in the same planet/moon system */ public boolean areDimensionsInSamePlanetMoonSystem(int destinationDimId, int dimension) { //This is a mess, clean up later if(dimension == SpaceObjectManager.WARPDIMID || destinationDimId == SpaceObjectManager.WARPDIMID) return false; DimensionProperties properties = getDimensionProperties(dimension); DimensionProperties properties2 = getDimensionProperties(destinationDimId); while(properties.getParentProperties() != null) properties = properties.getParentProperties(); while(properties2.getParentProperties() != null) properties2 = properties2.getParentProperties(); return areDimensionsInSamePlanetMoonSystem(properties, destinationDimId) || areDimensionsInSamePlanetMoonSystem(properties2, dimension); } private boolean areDimensionsInSamePlanetMoonSystem(DimensionProperties properties, int id) { if(properties.getId() == id) return true; for(int child : properties.getChildPlanets()) { if(areDimensionsInSamePlanetMoonSystem(getDimensionProperties(child), id)) return true; } return false; } public static DimensionProperties getEffectiveDimId(World world, int x, int z) { int dimId = world.provider.dimensionId; if(dimId == Configuration.spaceDimId) { ISpaceObject obj = SpaceObjectManager.getSpaceManager().getSpaceStationFromBlockCoords(x, z); if(obj != null) return (DimensionProperties) obj.getProperties().getParentProperties(); else return defaultSpaceDimensionProperties; } else return getInstance().getDimensionProperties(dimId); } }