    001    package net.minecraftforge.common;
    002
    003    import java.io.File;
    004    import java.util.ArrayList;
    005    import java.util.BitSet;
    006    import java.util.Hashtable;
    007    import java.util.Map;
    008    import java.util.Map.Entry;
    009    import java.util.logging.Level;
    010
    011    import com.google.common.collect.ArrayListMultimap;
    012    import com.google.common.collect.ImmutableListMultimap;
    013    import com.google.common.collect.ListMultimap;
    014    import com.google.common.collect.Maps;
    015
    016    import cpw.mods.fml.common.FMLCommonHandler;
    017    import cpw.mods.fml.common.FMLLog;
    018
    019    import net.minecraft.nbt.NBTTagCompound;
    020    import net.minecraft.server.MinecraftServer;
    021    import net.minecraft.world.ChunkCoordIntPair;
    022    import net.minecraft.world.MinecraftException;
    023    import net.minecraft.world.World;
    024    import net.minecraft.world.WorldManager;
    025    import net.minecraft.world.WorldProvider;
    026    import net.minecraft.world.WorldProviderEnd;
    027    import net.minecraft.world.WorldProviderHell;
    028    import net.minecraft.world.WorldProviderSurface;
    029    import net.minecraft.world.WorldServer;
    030    import net.minecraft.world.WorldServerMulti;
    031    import net.minecraft.world.WorldSettings;
    032    import net.minecraft.world.storage.ISaveHandler;
    033    import net.minecraft.world.storage.SaveHandler;
    034    import net.minecraftforge.event.world.WorldEvent;
    035
    036    public 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.containsValue(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            registerProviderType( 0, WorldProviderSurface.class, true);
    065            registerProviderType(-1, WorldProviderHell.class,    true);
    066            registerProviderType( 1, WorldProviderEnd.class,     false);
    067            registerDimension( 0,  0);
    068            registerDimension(-1, -1);
    069            registerDimension( 1,  1);
    070        }
    071
    072        public static void registerDimension(int id, int providerType)
    073        {
    074            if (!providers.containsKey(providerType))
    075            {
    076                throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, provider type %d does not exist", id, providerType));
    077            }
    078            if (dimensions.containsKey(id))
    079            {
    080                throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, One is already registered", id));
    081            }
    082            dimensions.put(id, providerType);
    083            if (id >= 0)
    084            {
    085                dimensionMap.set(id);
    086            }
    087        }
    088
    089        /**
    090         * For unregistering a dimension when the save is changed (disconnected from a server or loaded a new save
    091         */
    092        public static void unregisterDimension(int id)
    093        {
    094            if (!dimensions.containsKey(id))
    095            {
    096                throw new IllegalArgumentException(String.format("Failed to unregister dimension for id %d; No provider registered", id));
    097            }
    098            dimensions.remove(id);
    099        }
    100
    101        public static int getProviderType(int dim)
    102        {
    103            if (!dimensions.containsKey(dim))
    104            {
    105                throw new IllegalArgumentException(String.format("Could not get provider type for dimension %d, does not exist", dim));
    106            }
    107            return dimensions.get(dim);
    108        }
    109
    110        public static WorldProvider getProvider(int dim)
    111        {
    112            return getWorld(dim).provider;
    113        }
    114
    115        public static Integer[] getIDs()
    116        {
    117            return worlds.keySet().toArray(new Integer[worlds.size()]); //Only loaded dims, since usually used to cycle through loaded worlds
    118        }
    119
    120        public static void setWorld(int id, WorldServer world)
    121        {
    122            if (world != null) {
    123                worlds.put(id, world);
    124                MinecraftServer.getServer().worldTickTimes.put(id, new long[100]);
    125                FMLLog.info("Loading dimension %d (%s) (%s)", id, world.getWorldInfo().getWorldName(), world.getMinecraftServer());
    126            } else {
    127                worlds.remove(id);
    128                MinecraftServer.getServer().worldTickTimes.remove(id);
    129                FMLLog.info("Unloading dimension %d", id);
    130            }
    131
    132            ArrayList<WorldServer> tmp = new ArrayList<WorldServer>();
    133            if (worlds.get( 0) != null)
    134                tmp.add(worlds.get( 0));
    135            if (worlds.get(-1) != null)
    136                tmp.add(worlds.get(-1));
    137            if (worlds.get( 1) != null)
    138                tmp.add(worlds.get( 1));
    139
    140            for (Entry<Integer, WorldServer> entry : worlds.entrySet())
    141            {
    142                int dim = entry.getKey();
    143                if (dim >= -1 && dim <= 1)
    144                {
    145                    continue;
    146                }
    147                tmp.add(entry.getValue());
    148            }
    149
    150            MinecraftServer.getServer().worldServers = tmp.toArray(new WorldServer[0]);
    151        }
    152
    153        public static void initDimension(int dim) {
    154            WorldServer overworld = getWorld(0);
    155            if (overworld == null) {
    156                throw new RuntimeException("Cannot Hotload Dim: Overworld is not Loaded!");
    157            }
    158            try {
    159                DimensionManager.getProviderType(dim);
    160            } catch (Exception e) {
    161                System.err.println("Cannot Hotload Dim: " + e.getMessage());
    162                return; //If a provider hasn't been registered then we can't hotload the dim
    163            }
    164            MinecraftServer mcServer = overworld.getMinecraftServer();
    165            ISaveHandler savehandler = overworld.getSaveHandler();
    166            WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo());
    167
    168            WorldServer world = (dim == 0 ? overworld : new WorldServerMulti(mcServer, savehandler, overworld.getWorldInfo().getWorldName(), dim, worldSettings, overworld, mcServer.theProfiler));
    169            world.addWorldAccess(new WorldManager(mcServer, world));
    170            MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(world));
    171            if (!mcServer.isSinglePlayer())
    172            {
    173                world.getWorldInfo().setGameType(mcServer.getGameType());
    174            }
    175
    176            mcServer.setDifficultyForAllWorlds(mcServer.getDifficulty());
    177        }
    178
    179        public static WorldServer getWorld(int id)
    180        {
    181            return worlds.get(id);
    182        }
    183
    184        public static WorldServer[] getWorlds()
    185        {
    186            return worlds.values().toArray(new WorldServer[0]);
    187        }
    188
    189        public static boolean shouldLoadSpawn(int dim)
    190        {
    191            int id = getProviderType(dim);
    192            return spawnSettings.containsKey(id) && spawnSettings.get(id);
    193        }
    194
    195        static
    196        {
    197            init();
    198        }
    199
    200        /**
    201         * Not public API: used internally to get dimensions that should load at
    202         * server startup
    203         * @return
    204         */
    205        public static Integer[] getStaticDimensionIDs()
    206        {
    207            return dimensions.keySet().toArray(new Integer[dimensions.keySet().size()]);
    208        }
    209        public static WorldProvider createProviderFor(int dim)
    210        {
    211            try
    212            {
    213                if (dimensions.containsKey(dim))
    214                {
    215                    WorldProvider provider = providers.get(getProviderType(dim)).newInstance();
    216                    provider.setDimension(dim);
    217                    return provider;
    218                }
    219                else
    220                {
    221                    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
    222                }
    223            }
    224            catch (Exception e)
    225            {
    226                FMLCommonHandler.instance().getFMLLogger().log(Level.SEVERE,String.format("An error occured trying to create an instance of WorldProvider %d (%s)",
    227                        dim, providers.get(getProviderType(dim)).getSimpleName()),e);
    228                throw new RuntimeException(e);
    229            }
    230        }
    231
    232        public static void unloadWorld(int id) {
    233            unloadQueue.add(id);
    234        }
    235
    236        /*
    237        * To be called by the server at the appropriate time, do not call from mod code.
    238        */
    239        public static void unloadWorlds(Hashtable<Integer, long[]> worldTickTimes) {
    240            for (int id : unloadQueue) {
    241                try {
    242                    worlds.get(id).saveAllChunks(true, null);
    243                } catch (MinecraftException e) {
    244                    e.printStackTrace();
    245                }
    246                MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(worlds.get(id)));
    247                ((WorldServer)worlds.get(id)).flush();
    248                setWorld(id, null);
    249            }
    250            unloadQueue.clear();
    251        }
    252
    253        /**
    254         * Return the next free dimension ID. Note: you are not guaranteed a contiguous
    255         * block of free ids. Always call for each individual ID you wish to get.
    256         * @return
    257         */
    258        public static int getNextFreeDimId() {
    259            int next = 0;
    260            while (true)
    261            {
    262                next = dimensionMap.nextClearBit(next);
    263                if (dimensions.containsKey(next))
    264                {
    265                    dimensionMap.set(next);
    266                }
    267                else
    268                {
    269                    return next;
    270                }
    271            }
    272        }
    273
    274        public static NBTTagCompound saveDimensionDataMap()
    275        {
    276            int[] data = new int[(dimensionMap.length() + Integer.SIZE - 1 )/ Integer.SIZE];
    277            NBTTagCompound dimMap = new NBTTagCompound();
    278            for (int i = 0; i < data.length; i++)
    279            {
    280                int val = 0;
    281                for (int j = 0; j < Integer.SIZE; j++)
    282                {
    283                    val |= dimensionMap.get(i * Integer.SIZE + j) ? (1 << j) : 0;
    284                }
    285                data[i] = val;
    286            }
    287            dimMap.setIntArray("DimensionArray", data);
    288            return dimMap;
    289        }
    290
    291        public static void loadDimensionDataMap(NBTTagCompound compoundTag)
    292        {
    293            if (compoundTag == null)
    294            {
    295                dimensionMap.clear();
    296                for (Integer id : dimensions.keySet())
    297                {
    298                    if (id >= 0)
    299                    {
    300                        dimensionMap.set(id);
    301                    }
    302                }
    303            }
    304            else
    305            {
    306                int[] intArray = compoundTag.getIntArray("DimensionArray");
    307                for (int i = 0; i < intArray.length; i++)
    308                {
    309                    for (int j = 0; j < Integer.SIZE; j++)
    310                    {
    311                        dimensionMap.set(i * Integer.SIZE + j, (intArray[i] & (1 << j)) != 0);
    312                    }
    313                }
    314            }
    315        }
    316
    317        /**
    318         * Return the current root directory for the world save. Accesses getSaveHandler from the
    319         * @return
    320         */
    321        public static File getCurrentSaveRootDirectory()
    322        {
    323            if (DimensionManager.getWorld(0) != null)
    324            {
    325                return ((SaveHandler)DimensionManager.getWorld(0).getSaveHandler()).getSaveDirectory();
    326            }
    327            else
    328            {
    329                return null;
    330            }
    331        }
    332    }