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.contains(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 }