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