001package net.minecraftforge.common;
002
003import java.io.File;
004import java.util.ArrayList;
005import java.util.BitSet;
006import java.util.Hashtable;
007import java.util.Map;
008import java.util.Map.Entry;
009import java.util.logging.Level;
010
011import com.google.common.collect.ArrayListMultimap;
012import com.google.common.collect.ImmutableListMultimap;
013import com.google.common.collect.ListMultimap;
014import com.google.common.collect.Maps;
015
016import cpw.mods.fml.common.FMLCommonHandler;
017import cpw.mods.fml.common.FMLLog;
018
019import net.minecraft.nbt.NBTTagCompound;
020import net.minecraft.server.MinecraftServer;
021import net.minecraft.world.ChunkCoordIntPair;
022import net.minecraft.world.MinecraftException;
023import net.minecraft.world.World;
024import net.minecraft.world.WorldManager;
025import net.minecraft.world.WorldProvider;
026import net.minecraft.world.WorldProviderEnd;
027import net.minecraft.world.WorldProviderHell;
028import net.minecraft.world.WorldProviderSurface;
029import net.minecraft.world.WorldServer;
030import net.minecraft.world.WorldServerMulti;
031import net.minecraft.world.WorldSettings;
032import net.minecraft.world.storage.ISaveHandler;
033import net.minecraft.world.storage.SaveHandler;
034import net.minecraftforge.event.world.WorldEvent;
035
036public 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}