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 }