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            try {
244                worlds.get(id).saveAllChunks(true, null);
245            } catch (MinecraftException e) {
246                e.printStackTrace();
247            }
248            MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(worlds.get(id)));
249            ((WorldServer)worlds.get(id)).flush();
250            setWorld(id, null);
251        }
252        unloadQueue.clear();
253    }
254
255    /**
256     * Return the next free dimension ID. Note: you are not guaranteed a contiguous
257     * block of free ids. Always call for each individual ID you wish to get.
258     * @return the next free dimension ID
259     */
260    public static int getNextFreeDimId() {
261        int next = 0;
262        while (true)
263        {
264            next = dimensionMap.nextClearBit(next);
265            if (dimensions.containsKey(next))
266            {
267                dimensionMap.set(next);
268            }
269            else
270            {
271                return next;
272            }
273        }
274    }
275
276    public static NBTTagCompound saveDimensionDataMap()
277    {
278        int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1 )/ Integer.SIZE];
279        NBTTagCompound dimMap = new NBTTagCompound();
280        for (int i = 0; i < data.length; i++)
281        {
282            int val = 0;
283            for (int j = 0; j < Integer.SIZE; j++)
284            {
285                val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0;
286            }
287            data[i] = val;
288        }
289        dimMap.setIntArray("DimensionArray", data);
290        return dimMap;
291    }
292
293    public static void loadDimensionDataMap(NBTTagCompound compoundTag)
294    {
295        if (compoundTag == null)
296        {
297            dimensionMap.clear();
298            for (Integer id : dimensions.keySet())
299            {
300                if (id >= 0)
301                {
302                    dimensionMap.set(id);
303                }
304            }
305        }
306        else
307        {
308            int[] intArray = compoundTag.getIntArray("DimensionArray");
309            for (int i = 0; i < intArray.length; i++)
310            {
311                for (int j = 0; j < Integer.SIZE; j++)
312                {
313                    dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0);
314                }
315            }
316        }
317    }
318
319    /**
320     * Return the current root directory for the world save. Accesses getSaveHandler from the overworld
321     * @return the root directory of the save
322     */
323    public static File getCurrentSaveRootDirectory()
324    {
325        if (DimensionManager.getWorld(0) != null)
326        {
327            return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getSaveDirectory();
328        }
329        else if (MinecraftServer.getServer() != null)
330        {
331            MinecraftServer srv = MinecraftServer.getServer();
332            SaveHandler saveHandler = (SaveHandler) srv.getActiveAnvilConverter().getSaveLoader(srv.getFolderName(), false);
333            return saveHandler.getSaveDirectory();
334        }
335        else
336        {
337            return null;
338        }
339    }
340}