001package net.minecraftforge.common; 002 003import java.io.File; 004import java.util.ArrayList; 005import java.util.BitSet; 006import java.util.Hashtable; 007import java.util.Map; 008import java.util.Map.Entry; 009import java.util.logging.Level; 010 011import com.google.common.collect.ArrayListMultimap; 012import com.google.common.collect.ImmutableListMultimap; 013import com.google.common.collect.ListMultimap; 014import com.google.common.collect.Maps; 015 016import cpw.mods.fml.common.FMLCommonHandler; 017import cpw.mods.fml.common.FMLLog; 018 019import net.minecraft.nbt.NBTTagCompound; 020import net.minecraft.server.MinecraftServer; 021import net.minecraft.world.ChunkCoordIntPair; 022import net.minecraft.world.MinecraftException; 023import net.minecraft.world.World; 024import net.minecraft.world.WorldManager; 025import net.minecraft.world.WorldProvider; 026import net.minecraft.world.WorldProviderEnd; 027import net.minecraft.world.WorldProviderHell; 028import net.minecraft.world.WorldProviderSurface; 029import net.minecraft.world.WorldServer; 030import net.minecraft.world.WorldServerMulti; 031import net.minecraft.world.WorldSettings; 032import net.minecraft.world.storage.ISaveHandler; 033import net.minecraft.world.storage.SaveHandler; 034import net.minecraftforge.event.world.WorldEvent; 035 036public class DimensionManager 037{ 038 private static Hashtable<Integer, Class<? extends WorldProvider>> providers = new Hashtable<Integer, Class<? extends WorldProvider>>(); 039 private static Hashtable<Integer, Boolean> spawnSettings = new Hashtable<Integer, Boolean>(); 040 private static Hashtable<Integer, WorldServer> worlds = new Hashtable<Integer, WorldServer>(); 041 private static boolean hasInit = false; 042 private static Hashtable<Integer, Integer> dimensions = new Hashtable<Integer, Integer>(); 043 private static Map<World, ListMultimap<ChunkCoordIntPair, String>> persistentChunkStore = Maps.newHashMap(); //FIXME: Unused? 044 private static ArrayList<Integer> unloadQueue = new ArrayList<Integer>(); 045 private static BitSet dimensionMap = new BitSet(Long.SIZE << 4); 046 047 public static boolean registerProviderType(int id, Class<? extends WorldProvider> provider, boolean keepLoaded) 048 { 049 if (providers.containsKey(id)) 050 { 051 return false; 052 } 053 providers.put(id, provider); 054 spawnSettings.put(id, keepLoaded); 055 return true; 056 } 057 058 public static void init() 059 { 060 if (hasInit) 061 { 062 return; 063 } 064 065 hasInit = true; 066 067 registerProviderType( 0, WorldProviderSurface.class, true); 068 registerProviderType(-1, WorldProviderHell.class, true); 069 registerProviderType( 1, WorldProviderEnd.class, false); 070 registerDimension( 0, 0); 071 registerDimension(-1, -1); 072 registerDimension( 1, 1); 073 } 074 075 public static void registerDimension(int id, int providerType) 076 { 077 if (!providers.containsKey(providerType)) 078 { 079 throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, provider type %d does not exist", id, providerType)); 080 } 081 if (dimensions.containsKey(id)) 082 { 083 throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, One is already registered", id)); 084 } 085 dimensions.put(id, providerType); 086 if (id >= 0) 087 { 088 dimensionMap.set(id); 089 } 090 } 091 092 /** 093 * For unregistering a dimension when the save is changed (disconnected from a server or loaded a new save 094 */ 095 public static void unregisterDimension(int id) 096 { 097 if (!dimensions.containsKey(id)) 098 { 099 throw new IllegalArgumentException(String.format("Failed to unregister dimension for id %d; No provider registered", id)); 100 } 101 dimensions.remove(id); 102 } 103 104 public static int getProviderType(int dim) 105 { 106 if (!dimensions.containsKey(dim)) 107 { 108 throw new IllegalArgumentException(String.format("Could not get provider type for dimension %d, does not exist", dim)); 109 } 110 return dimensions.get(dim); 111 } 112 113 public static WorldProvider getProvider(int dim) 114 { 115 return getWorld(dim).provider; 116 } 117 118 public static Integer[] getIDs() 119 { 120 return worlds.keySet().toArray(new Integer[worlds.size()]); //Only loaded dims, since usually used to cycle through loaded worlds 121 } 122 123 public static void setWorld(int id, WorldServer world) 124 { 125 if (world != null) { 126 worlds.put(id, world); 127 MinecraftServer.getServer().worldTickTimes.put(id, new long[100]); 128 FMLLog.info("Loading dimension %d (%s) (%s)", id, world.getWorldInfo().getWorldName(), world.getMinecraftServer()); 129 } else { 130 worlds.remove(id); 131 MinecraftServer.getServer().worldTickTimes.remove(id); 132 FMLLog.info("Unloading dimension %d", id); 133 } 134 135 ArrayList<WorldServer> tmp = new ArrayList<WorldServer>(); 136 if (worlds.get( 0) != null) 137 tmp.add(worlds.get( 0)); 138 if (worlds.get(-1) != null) 139 tmp.add(worlds.get(-1)); 140 if (worlds.get( 1) != null) 141 tmp.add(worlds.get( 1)); 142 143 for (Entry<Integer, WorldServer> entry : worlds.entrySet()) 144 { 145 int dim = entry.getKey(); 146 if (dim >= -1 && dim <= 1) 147 { 148 continue; 149 } 150 tmp.add(entry.getValue()); 151 } 152 153 MinecraftServer.getServer().worldServers = tmp.toArray(new WorldServer[tmp.size()]); 154 } 155 156 public static void initDimension(int dim) { 157 WorldServer overworld = getWorld(0); 158 if (overworld == null) { 159 throw new RuntimeException("Cannot Hotload Dim: Overworld is not Loaded!"); 160 } 161 try { 162 DimensionManager.getProviderType(dim); 163 } catch (Exception e) { 164 System.err.println("Cannot Hotload Dim: " + e.getMessage()); 165 return; //If a provider hasn't been registered then we can't hotload the dim 166 } 167 MinecraftServer mcServer = overworld.getMinecraftServer(); 168 ISaveHandler savehandler = overworld.getSaveHandler(); 169 WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo()); 170 171 WorldServer world = (dim == 0 ? overworld : new WorldServerMulti(mcServer, savehandler, overworld.getWorldInfo().getWorldName(), dim, worldSettings, overworld, mcServer.theProfiler)); 172 world.addWorldAccess(new WorldManager(mcServer, world)); 173 MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world)); 174 if (!mcServer.isSinglePlayer()) 175 { 176 world.getWorldInfo().setGameType(mcServer.getGameType()); 177 } 178 179 mcServer.setDifficultyForAllWorlds(mcServer.getDifficulty()); 180 } 181 182 public static WorldServer getWorld(int id) 183 { 184 return worlds.get(id); 185 } 186 187 public static WorldServer[] getWorlds() 188 { 189 return worlds.values().toArray(new WorldServer[worlds.size()]); 190 } 191 192 public static boolean shouldLoadSpawn(int dim) 193 { 194 int id = getProviderType(dim); 195 return spawnSettings.containsKey(id) && spawnSettings.get(id); 196 } 197 198 static 199 { 200 init(); 201 } 202 203 /** 204 * Not public API: used internally to get dimensions that should load at 205 * server startup 206 */ 207 public static Integer[] getStaticDimensionIDs() 208 { 209 return dimensions.keySet().toArray(new Integer[dimensions.keySet().size()]); 210 } 211 public static WorldProvider createProviderFor(int dim) 212 { 213 try 214 { 215 if (dimensions.containsKey(dim)) 216 { 217 WorldProvider provider = providers.get(getProviderType(dim)).newInstance(); 218 provider.setDimension(dim); 219 return provider; 220 } 221 else 222 { 223 throw new RuntimeException(String.format("No WorldProvider bound for dimension %d", dim)); //It's going to crash anyway at this point. Might as well be informative 224 } 225 } 226 catch (Exception e) 227 { 228 FMLCommonHandler.instance().getFMLLogger().log(Level.SEVERE,String.format("An error occured trying to create an instance of WorldProvider %d (%s)", 229 dim, providers.get(getProviderType(dim)).getSimpleName()),e); 230 throw new RuntimeException(e); 231 } 232 } 233 234 public static void unloadWorld(int id) { 235 unloadQueue.add(id); 236 } 237 238 /* 239 * To be called by the server at the appropriate time, do not call from mod code. 240 */ 241 public static void unloadWorlds(Hashtable<Integer, long[]> worldTickTimes) { 242 for (int id : unloadQueue) { 243 try { 244 worlds.get(id).saveAllChunks(true, null); 245 } catch (MinecraftException e) { 246 e.printStackTrace(); 247 } 248 MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(worlds.get(id))); 249 ((WorldServer)worlds.get(id)).flush(); 250 setWorld(id, null); 251 } 252 unloadQueue.clear(); 253 } 254 255 /** 256 * Return the next free dimension ID. Note: you are not guaranteed a contiguous 257 * block of free ids. Always call for each individual ID you wish to get. 258 * @return the next free dimension ID 259 */ 260 public static int getNextFreeDimId() { 261 int next = 0; 262 while (true) 263 { 264 next = dimensionMap.nextClearBit(next); 265 if (dimensions.containsKey(next)) 266 { 267 dimensionMap.set(next); 268 } 269 else 270 { 271 return next; 272 } 273 } 274 } 275 276 public static NBTTagCompound saveDimensionDataMap() 277 { 278 int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1 )/ Integer.SIZE]; 279 NBTTagCompound dimMap = new NBTTagCompound(); 280 for (int i = 0; i < data.length; i++) 281 { 282 int val = 0; 283 for (int j = 0; j < Integer.SIZE; j++) 284 { 285 val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0; 286 } 287 data[i] = val; 288 } 289 dimMap.setIntArray("DimensionArray", data); 290 return dimMap; 291 } 292 293 public static void loadDimensionDataMap(NBTTagCompound compoundTag) 294 { 295 if (compoundTag == null) 296 { 297 dimensionMap.clear(); 298 for (Integer id : dimensions.keySet()) 299 { 300 if (id >= 0) 301 { 302 dimensionMap.set(id); 303 } 304 } 305 } 306 else 307 { 308 int[] intArray = compoundTag.getIntArray("DimensionArray"); 309 for (int i = 0; i < intArray.length; i++) 310 { 311 for (int j = 0; j < Integer.SIZE; j++) 312 { 313 dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0); 314 } 315 } 316 } 317 } 318 319 /** 320 * Return the current root directory for the world save. Accesses getSaveHandler from the overworld 321 * @return the root directory of the save 322 */ 323 public static File getCurrentSaveRootDirectory() 324 { 325 if (DimensionManager.getWorld(0) != null) 326 { 327 return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getSaveDirectory(); 328 } 329 else 330 { 331 return null; 332 } 333 } 334}