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