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 }