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