001package net.minecraftforge.common; 002 003import java.io.File; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.BitSet; 007import java.util.HashSet; 008import java.util.Hashtable; 009import java.util.List; 010import java.util.ListIterator; 011import java.util.Map; 012import java.util.Map.Entry; 013import java.util.Set; 014import java.util.concurrent.ConcurrentMap; 015import java.util.logging.Level; 016 017import com.google.common.collect.ArrayListMultimap; 018import com.google.common.collect.HashMultiset; 019import com.google.common.collect.ImmutableListMultimap; 020import com.google.common.collect.ListMultimap; 021import com.google.common.collect.Lists; 022import com.google.common.collect.MapMaker; 023import com.google.common.collect.Maps; 024import com.google.common.collect.Multiset; 025import com.google.common.collect.Sets; 026 027import cpw.mods.fml.common.FMLCommonHandler; 028import cpw.mods.fml.common.FMLLog; 029 030import net.minecraft.nbt.NBTTagCompound; 031import net.minecraft.server.MinecraftServer; 032import net.minecraft.world.ChunkCoordIntPair; 033import net.minecraft.world.MinecraftException; 034import net.minecraft.world.World; 035import net.minecraft.world.WorldManager; 036import net.minecraft.world.WorldProvider; 037import net.minecraft.world.WorldProviderEnd; 038import net.minecraft.world.WorldProviderHell; 039import net.minecraft.world.WorldProviderSurface; 040import net.minecraft.world.WorldServer; 041import net.minecraft.world.WorldServerMulti; 042import net.minecraft.world.WorldSettings; 043import net.minecraft.world.storage.ISaveHandler; 044import net.minecraft.world.storage.SaveHandler; 045import net.minecraftforge.event.world.WorldEvent; 046 047public class DimensionManager 048{ 049 private static Hashtable<Integer, Class<? extends WorldProvider>> providers = new Hashtable<Integer, Class<? extends WorldProvider>>(); 050 private static Hashtable<Integer, Boolean> spawnSettings = new Hashtable<Integer, Boolean>(); 051 private static Hashtable<Integer, WorldServer> worlds = new Hashtable<Integer, WorldServer>(); 052 private static boolean hasInit = false; 053 private static Hashtable<Integer, Integer> dimensions = new Hashtable<Integer, Integer>(); 054 private static ArrayList<Integer> unloadQueue = new ArrayList<Integer>(); 055 private static BitSet dimensionMap = new BitSet(Long.SIZE << 4); 056 private static ConcurrentMap<World, World> weakWorldMap = new MapMaker().weakKeys().weakValues().<World,World>makeMap(); 057 private static Multiset<Integer> leakedWorlds = HashMultiset.create(); 058 059 public static boolean registerProviderType(int id, Class<? extends WorldProvider> provider, boolean keepLoaded) 060 { 061 if (providers.containsKey(id)) 062 { 063 return false; 064 } 065 providers.put(id, provider); 066 spawnSettings.put(id, keepLoaded); 067 return true; 068 } 069 070 /** 071 * Unregisters a Provider type, and returns a array of all dimensions that are 072 * registered to this provider type. 073 * If the return size is greater then 0, it is required that the caller either 074 * change those dimensions's registered type, or replace this type before the 075 * world is attempted to load, else the loader will throw an exception. 076 * 077 * @param id The provider type ID to unreigster 078 * @return An array containing all dimension IDs still registered to this provider type. 079 */ 080 public static int[] unregisterProviderType(int id) 081 { 082 if (!providers.containsKey(id)) 083 { 084 return new int[0]; 085 } 086 providers.remove(id); 087 spawnSettings.remove(id); 088 089 int[] ret = new int[dimensions.size()]; 090 int x = 0; 091 for (Map.Entry<Integer, Integer> ent : dimensions.entrySet()) 092 { 093 if (ent.getValue() == id) 094 { 095 ret[x++] = ent.getKey(); 096 } 097 } 098 099 return Arrays.copyOf(ret, x); 100 } 101 102 public static void init() 103 { 104 if (hasInit) 105 { 106 return; 107 } 108 109 hasInit = true; 110 111 registerProviderType( 0, WorldProviderSurface.class, true); 112 registerProviderType(-1, WorldProviderHell.class, true); 113 registerProviderType( 1, WorldProviderEnd.class, false); 114 registerDimension( 0, 0); 115 registerDimension(-1, -1); 116 registerDimension( 1, 1); 117 } 118 119 public static void registerDimension(int id, int providerType) 120 { 121 if (!providers.containsKey(providerType)) 122 { 123 throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, provider type %d does not exist", id, providerType)); 124 } 125 if (dimensions.containsKey(id)) 126 { 127 throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, One is already registered", id)); 128 } 129 dimensions.put(id, providerType); 130 if (id >= 0) 131 { 132 dimensionMap.set(id); 133 } 134 } 135 136 /** 137 * For unregistering a dimension when the save is changed (disconnected from a server or loaded a new save 138 */ 139 public static void unregisterDimension(int id) 140 { 141 if (!dimensions.containsKey(id)) 142 { 143 throw new IllegalArgumentException(String.format("Failed to unregister dimension for id %d; No provider registered", id)); 144 } 145 dimensions.remove(id); 146 } 147 148 public static int getProviderType(int dim) 149 { 150 if (!dimensions.containsKey(dim)) 151 { 152 throw new IllegalArgumentException(String.format("Could not get provider type for dimension %d, does not exist", dim)); 153 } 154 return dimensions.get(dim); 155 } 156 157 public static WorldProvider getProvider(int dim) 158 { 159 return getWorld(dim).provider; 160 } 161 162 public static Integer[] getIDs(boolean check) 163 { 164 if (check) 165 { 166 List<World> allWorlds = Lists.newArrayList(weakWorldMap.keySet()); 167 allWorlds.removeAll(worlds.values()); 168 for (ListIterator<World> li = allWorlds.listIterator(); li.hasNext(); ) 169 { 170 World w = li.next(); 171 leakedWorlds.add(System.identityHashCode(w)); 172 } 173 for (World w : allWorlds) 174 { 175 int leakCount = leakedWorlds.count(System.identityHashCode(w)); 176 if (leakCount == 5) 177 { 178 FMLLog.fine("The world %x (%s) may have leaked: first encounter (5 occurences).\n", System.identityHashCode(w), w.getWorldInfo().getWorldName()); 179 } 180 else if (leakCount % 5 == 0) 181 { 182 FMLLog.fine("The world %x (%s) may have leaked: seen %d times.\n", System.identityHashCode(w), w.getWorldInfo().getWorldName(), leakCount); 183 } 184 } 185 } 186 return getIDs(); 187 } 188 public static Integer[] getIDs() 189 { 190 return worlds.keySet().toArray(new Integer[worlds.size()]); //Only loaded dims, since usually used to cycle through loaded worlds 191 } 192 193 public static void setWorld(int id, WorldServer world) 194 { 195 if (world != null) { 196 worlds.put(id, world); 197 weakWorldMap.put(world, world); 198 MinecraftServer.getServer().worldTickTimes.put(id, new long[100]); 199 FMLLog.info("Loading dimension %d (%s) (%s)", id, world.getWorldInfo().getWorldName(), world.getMinecraftServer()); 200 } else { 201 worlds.remove(id); 202 MinecraftServer.getServer().worldTickTimes.remove(id); 203 FMLLog.info("Unloading dimension %d", id); 204 } 205 206 ArrayList<WorldServer> tmp = new ArrayList<WorldServer>(); 207 if (worlds.get( 0) != null) 208 tmp.add(worlds.get( 0)); 209 if (worlds.get(-1) != null) 210 tmp.add(worlds.get(-1)); 211 if (worlds.get( 1) != null) 212 tmp.add(worlds.get( 1)); 213 214 for (Entry<Integer, WorldServer> entry : worlds.entrySet()) 215 { 216 int dim = entry.getKey(); 217 if (dim >= -1 && dim <= 1) 218 { 219 continue; 220 } 221 tmp.add(entry.getValue()); 222 } 223 224 MinecraftServer.getServer().worldServers = tmp.toArray(new WorldServer[tmp.size()]); 225 } 226 227 public static void initDimension(int dim) { 228 WorldServer overworld = getWorld(0); 229 if (overworld == null) { 230 throw new RuntimeException("Cannot Hotload Dim: Overworld is not Loaded!"); 231 } 232 try { 233 DimensionManager.getProviderType(dim); 234 } catch (Exception e) { 235 System.err.println("Cannot Hotload Dim: " + e.getMessage()); 236 return; //If a provider hasn't been registered then we can't hotload the dim 237 } 238 MinecraftServer mcServer = overworld.getMinecraftServer(); 239 ISaveHandler savehandler = overworld.getSaveHandler(); 240 WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo()); 241 242 WorldServer world = (dim == 0 ? overworld : new WorldServerMulti(mcServer, savehandler, overworld.getWorldInfo().getWorldName(), dim, worldSettings, overworld, mcServer.theProfiler, overworld.getWorldLogAgent())); 243 world.addWorldAccess(new WorldManager(mcServer, world)); 244 MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world)); 245 if (!mcServer.isSinglePlayer()) 246 { 247 world.getWorldInfo().setGameType(mcServer.getGameType()); 248 } 249 250 mcServer.setDifficultyForAllWorlds(mcServer.getDifficulty()); 251 } 252 253 public static WorldServer getWorld(int id) 254 { 255 return worlds.get(id); 256 } 257 258 public static WorldServer[] getWorlds() 259 { 260 return worlds.values().toArray(new WorldServer[worlds.size()]); 261 } 262 263 public static boolean shouldLoadSpawn(int dim) 264 { 265 int id = getProviderType(dim); 266 return spawnSettings.containsKey(id) && spawnSettings.get(id); 267 } 268 269 static 270 { 271 init(); 272 } 273 274 /** 275 * Not public API: used internally to get dimensions that should load at 276 * server startup 277 */ 278 public static Integer[] getStaticDimensionIDs() 279 { 280 return dimensions.keySet().toArray(new Integer[dimensions.keySet().size()]); 281 } 282 public static WorldProvider createProviderFor(int dim) 283 { 284 try 285 { 286 if (dimensions.containsKey(dim)) 287 { 288 WorldProvider provider = providers.get(getProviderType(dim)).newInstance(); 289 provider.setDimension(dim); 290 return provider; 291 } 292 else 293 { 294 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 295 } 296 } 297 catch (Exception e) 298 { 299 FMLCommonHandler.instance().getFMLLogger().log(Level.SEVERE,String.format("An error occured trying to create an instance of WorldProvider %d (%s)", 300 dim, providers.get(getProviderType(dim)).getSimpleName()),e); 301 throw new RuntimeException(e); 302 } 303 } 304 305 public static void unloadWorld(int id) { 306 unloadQueue.add(id); 307 } 308 309 /* 310 * To be called by the server at the appropriate time, do not call from mod code. 311 */ 312 public static void unloadWorlds(Hashtable<Integer, long[]> worldTickTimes) { 313 for (int id : unloadQueue) { 314 WorldServer w = worlds.get(id); 315 try { 316 if (w != null) 317 { 318 w.saveAllChunks(true, null); 319 } 320 else 321 { 322 FMLLog.warning("Unexpected world unload - world %d is already unloaded", id); 323 } 324 } catch (MinecraftException e) { 325 e.printStackTrace(); 326 } 327 finally 328 { 329 if (w != null) 330 { 331 MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w)); 332 w.flush(); 333 setWorld(id, null); 334 } 335 } 336 } 337 unloadQueue.clear(); 338 } 339 340 /** 341 * Return the next free dimension ID. Note: you are not guaranteed a contiguous 342 * block of free ids. Always call for each individual ID you wish to get. 343 * @return the next free dimension ID 344 */ 345 public static int getNextFreeDimId() { 346 int next = 0; 347 while (true) 348 { 349 next = dimensionMap.nextClearBit(next); 350 if (dimensions.containsKey(next)) 351 { 352 dimensionMap.set(next); 353 } 354 else 355 { 356 return next; 357 } 358 } 359 } 360 361 public static NBTTagCompound saveDimensionDataMap() 362 { 363 int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1 )/ Integer.SIZE]; 364 NBTTagCompound dimMap = new NBTTagCompound(); 365 for (int i = 0; i < data.length; i++) 366 { 367 int val = 0; 368 for (int j = 0; j < Integer.SIZE; j++) 369 { 370 val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0; 371 } 372 data[i] = val; 373 } 374 dimMap.setIntArray("DimensionArray", data); 375 return dimMap; 376 } 377 378 public static void loadDimensionDataMap(NBTTagCompound compoundTag) 379 { 380 if (compoundTag == null) 381 { 382 dimensionMap.clear(); 383 for (Integer id : dimensions.keySet()) 384 { 385 if (id >= 0) 386 { 387 dimensionMap.set(id); 388 } 389 } 390 } 391 else 392 { 393 int[] intArray = compoundTag.getIntArray("DimensionArray"); 394 for (int i = 0; i < intArray.length; i++) 395 { 396 for (int j = 0; j < Integer.SIZE; j++) 397 { 398 dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0); 399 } 400 } 401 } 402 } 403 404 /** 405 * Return the current root directory for the world save. Accesses getSaveHandler from the overworld 406 * @return the root directory of the save 407 */ 408 public static File getCurrentSaveRootDirectory() 409 { 410 if (DimensionManager.getWorld(0) != null) 411 { 412 return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getWorldDirectory(); 413 } 414 else if (MinecraftServer.getServer() != null) 415 { 416 MinecraftServer srv = MinecraftServer.getServer(); 417 SaveHandler saveHandler = (SaveHandler) srv.getActiveAnvilConverter().getSaveLoader(srv.getFolderName(), false); 418 return saveHandler.getWorldDirectory(); 419 } 420 else 421 { 422 return null; 423 } 424 } 425}