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