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    }