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