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     */
207    public static Integer[] getStaticDimensionIDs()
208    {
209        return dimensions.keySet().toArray(new Integer[dimensions.keySet().size()]);
210    }
211    public static WorldProvider createProviderFor(int dim)
212    {
213        try
214        {
215            if (dimensions.containsKey(dim))
216            {
217                WorldProvider provider = providers.get(getProviderType(dim)).newInstance();
218                provider.setDimension(dim);
219                return provider;
220            }
221            else
222            {
223                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
224            }
225        }
226        catch (Exception e)
227        {
228            FMLCommonHandler.instance().getFMLLogger().log(Level.SEVERE,String.format("An error occured trying to create an instance of WorldProvider %d (%s)",
229                    dim, providers.get(getProviderType(dim)).getSimpleName()),e);
230            throw new RuntimeException(e);
231        }
232    }
233
234    public static void unloadWorld(int id) {
235        unloadQueue.add(id);
236    }
237
238    /*
239    * To be called by the server at the appropriate time, do not call from mod code.
240    */
241    public static void unloadWorlds(Hashtable<Integer, long[]> worldTickTimes) {
242        for (int id : unloadQueue) {
243            WorldServer w = worlds.get(id);
244            try {
245                if (w != null)
246                {
247                    w.saveAllChunks(true, null);
248                }
249                else
250                {
251                    FMLLog.warning("Unexpected world unload - world %d is already unloaded", id);
252                }
253            } catch (MinecraftException e) {
254                e.printStackTrace();
255            }
256            finally
257            {
258                if (w != null)
259                {
260                    MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w));
261                    w.flush();
262                    setWorld(id, null);
263                }
264            }
265        }
266        unloadQueue.clear();
267    }
268
269    /**
270     * Return the next free dimension ID. Note: you are not guaranteed a contiguous
271     * block of free ids. Always call for each individual ID you wish to get.
272     * @return the next free dimension ID
273     */
274    public static int getNextFreeDimId() {
275        int next = 0;
276        while (true)
277        {
278            next = dimensionMap.nextClearBit(next);
279            if (dimensions.containsKey(next))
280            {
281                dimensionMap.set(next);
282            }
283            else
284            {
285                return next;
286            }
287        }
288    }
289
290    public static NBTTagCompound saveDimensionDataMap()
291    {
292        int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1 )/ Integer.SIZE];
293        NBTTagCompound dimMap = new NBTTagCompound();
294        for (int i = 0; i < data.length; i++)
295        {
296            int val = 0;
297            for (int j = 0; j < Integer.SIZE; j++)
298            {
299                val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0;
300            }
301            data[i] = val;
302        }
303        dimMap.setIntArray("DimensionArray", data);
304        return dimMap;
305    }
306
307    public static void loadDimensionDataMap(NBTTagCompound compoundTag)
308    {
309        if (compoundTag == null)
310        {
311            dimensionMap.clear();
312            for (Integer id : dimensions.keySet())
313            {
314                if (id >= 0)
315                {
316                    dimensionMap.set(id);
317                }
318            }
319        }
320        else
321        {
322            int[] intArray = compoundTag.getIntArray("DimensionArray");
323            for (int i = 0; i < intArray.length; i++)
324            {
325                for (int j = 0; j < Integer.SIZE; j++)
326                {
327                    dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0);
328                }
329            }
330        }
331    }
332
333    /**
334     * Return the current root directory for the world save. Accesses getSaveHandler from the overworld
335     * @return the root directory of the save
336     */
337    public static File getCurrentSaveRootDirectory()
338    {
339        if (DimensionManager.getWorld(0) != null)
340        {
341            return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getSaveDirectory();
342        }
343        else if (MinecraftServer.getServer() != null)
344        {
345            MinecraftServer srv = MinecraftServer.getServer();
346            SaveHandler saveHandler = (SaveHandler) srv.getActiveAnvilConverter().getSaveLoader(srv.getFolderName(), false);
347            return saveHandler.getSaveDirectory();
348        }
349        else
350        {
351            return null;
352        }
353    }
354}