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    }