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    }