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    }