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