    001    package net.minecraft.world.gen;
    002
    003    import java.io.IOException;
    004    import java.util.ArrayList;
    005    import java.util.HashSet;
    006    import java.util.Iterator;
    007    import java.util.List;
    008    import java.util.Set;
    009
    010    import net.minecraftforge.common.DimensionManager;
    011    import net.minecraftforge.common.ForgeChunkManager;
    012
    013    import cpw.mods.fml.common.registry.GameRegistry;
    014    import net.minecraft.crash.CrashReport;
    015    import net.minecraft.crash.CrashReportCategory;
    016    import net.minecraft.entity.EnumCreatureType;
    017    import net.minecraft.util.ChunkCoordinates;
    018    import net.minecraft.util.IProgressUpdate;
    019    import net.minecraft.util.LongHashMap;
    020    import net.minecraft.util.ReportedException;
    021    import net.minecraft.world.ChunkCoordIntPair;
    022    import net.minecraft.world.ChunkPosition;
    023    import net.minecraft.world.MinecraftException;
    024    import net.minecraft.world.World;
    025    import net.minecraft.world.WorldServer;
    026    import net.minecraft.world.chunk.Chunk;
    027    import net.minecraft.world.chunk.EmptyChunk;
    028    import net.minecraft.world.chunk.IChunkProvider;
    029    import net.minecraft.world.chunk.storage.IChunkLoader;
    030
    031    public class ChunkProviderServer implements IChunkProvider
    032    {
    033        /**
    034         * used by unload100OldestChunks to iterate the loadedChunkHashMap for unload (underlying assumption, first in,
    035         * first out)
    036         */
    037        private Set chunksToUnload = new HashSet();
    038        private Chunk defaultEmptyChunk;
    039        private IChunkProvider currentChunkProvider;
    040        public IChunkLoader currentChunkLoader;
    041
    042        /**
    043         * if this is false, the defaultEmptyChunk will be returned by the provider
    044         */
    045        public boolean loadChunkOnProvideRequest = true;
    046        private LongHashMap loadedChunkHashMap = new LongHashMap();
    047        private List loadedChunks = new ArrayList();
    048        private WorldServer currentServer;
    049
    050        public ChunkProviderServer(WorldServer par1WorldServer, IChunkLoader par2IChunkLoader, IChunkProvider par3IChunkProvider)
    051        {
    052            this.defaultEmptyChunk = new EmptyChunk(par1WorldServer, 0, 0);
    053            this.currentServer = par1WorldServer;
    054            this.currentChunkLoader = par2IChunkLoader;
    055            this.currentChunkProvider = par3IChunkProvider;
    056        }
    057
    058        /**
    059         * Checks to see if a chunk exists at x, y
    060         */
    061        public boolean chunkExists(int par1, int par2)
    062        {
    063            return this.loadedChunkHashMap.containsItem(ChunkCoordIntPair.chunkXZ2Int(par1, par2));
    064        }
    065
    066        /**
    067         * marks chunk for unload by "unload100OldestChunks"  if there is no spawn point, or if the center of the chunk is
    068         * outside 200 blocks (x or z) of the spawn
    069         */
    070        public void unloadChunksIfNotNearSpawn(int par1, int par2)
    071        {
    072            if (this.currentServer.provider.canRespawnHere() && DimensionManager.shouldLoadSpawn(currentServer.provider.dimensionId))
    073            {
    074                ChunkCoordinates var3 = this.currentServer.getSpawnPoint();
    075                int var4 = par1 * 16 + 8 - var3.posX;
    076                int var5 = par2 * 16 + 8 - var3.posZ;
    077                short var6 = 128;
    078
    079                if (var4 < -var6 || var4 > var6 || var5 < -var6 || var5 > var6)
    080                {
    081                    this.chunksToUnload.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(par1, par2)));
    082                }
    083            }
    084            else
    085            {
    086                this.chunksToUnload.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(par1, par2)));
    087            }
    088        }
    089
    090        /**
    091         * marks all chunks for unload, ignoring those near the spawn
    092         */
    093        public void unloadAllChunks()
    094        {
    095            Iterator var1 = this.loadedChunks.iterator();
    096
    097            while (var1.hasNext())
    098            {
    099                Chunk var2 = (Chunk)var1.next();
    100                this.unloadChunksIfNotNearSpawn(var2.xPosition, var2.zPosition);
    101            }
    102        }
    103
    104        /**
    105         * loads or generates the chunk at the chunk location specified
    106         */
    107        public Chunk loadChunk(int par1, int par2)
    108        {
    109            long var3 = ChunkCoordIntPair.chunkXZ2Int(par1, par2);
    110            this.chunksToUnload.remove(Long.valueOf(var3));
    111            Chunk var5 = (Chunk)this.loadedChunkHashMap.getValueByKey(var3);
    112
    113            if (var5 == null)
    114            {
    115                var5 = ForgeChunkManager.fetchDormantChunk(var3, currentServer);
    116                if (var5 == null)
    117                {
    118                    var5 = this.safeLoadChunk(par1, par2);
    119                }
    120
    121                if (var5 == null)
    122                {
    123                    if (this.currentChunkProvider == null)
    124                    {
    125                        var5 = this.defaultEmptyChunk;
    126                    }
    127                    else
    128                    {
    129                        try
    130                        {
    131                            var5 = this.currentChunkProvider.provideChunk(par1, par2);
    132                        }
    133                        catch (Throwable var9)
    134                        {
    135                            CrashReport var7 = CrashReport.makeCrashReport(var9, "Exception generating new chunk");
    136                            CrashReportCategory var8 = var7.makeCategory("Chunk to be generated");
    137                            var8.addCrashSection("Location", String.format("%d,%d", new Object[] {Integer.valueOf(par1), Integer.valueOf(par2)}));
    138                            var8.addCrashSection("Position hash", Long.valueOf(var3));
    139                            var8.addCrashSection("Generator", this.currentChunkProvider.makeString());
    140                            throw new ReportedException(var7);
    141                        }
    142                    }
    143                }
    144
    145                this.loadedChunkHashMap.add(var3, var5);
    146                this.loadedChunks.add(var5);
    147
    148                if (var5 != null)
    149                {
    150                    var5.onChunkLoad();
    151                }
    152
    153                var5.populateChunk(this, this, par1, par2);
    154            }
    155
    156            return var5;
    157        }
    158
    159        /**
    160         * Will return back a chunk, if it doesn't exist and its not a MP client it will generates all the blocks for the
    161         * specified chunk from the map seed and chunk seed
    162         */
    163        public Chunk provideChunk(int par1, int par2)
    164        {
    165            Chunk var3 = (Chunk)this.loadedChunkHashMap.getValueByKey(ChunkCoordIntPair.chunkXZ2Int(par1, par2));
    166            return var3 == null ? (!this.currentServer.findingSpawnPoint && !this.loadChunkOnProvideRequest ? this.defaultEmptyChunk : this.loadChunk(par1, par2)) : var3;
    167        }
    168
    169        /**
    170         * used by loadChunk, but catches any exceptions if the load fails.
    171         */
    172        private Chunk safeLoadChunk(int par1, int par2)
    173        {
    174            if (this.currentChunkLoader == null)
    175            {
    176                return null;
    177            }
    178            else
    179            {
    180                try
    181                {
    182                    Chunk var3 = this.currentChunkLoader.loadChunk(this.currentServer, par1, par2);
    183
    184                    if (var3 != null)
    185                    {
    186                        var3.lastSaveTime = this.currentServer.getTotalWorldTime();
    187
    188                        if (this.currentChunkProvider != null)
    189                        {
    190                            this.currentChunkProvider.recreateStructures(par1, par2);
    191                        }
    192                    }
    193
    194                    return var3;
    195                }
    196                catch (Exception var4)
    197                {
    198                    var4.printStackTrace();
    199                    return null;
    200                }
    201            }
    202        }
    203
    204        /**
    205         * used by saveChunks, but catches any exceptions if the save fails.
    206         */
    207        private void safeSaveExtraChunkData(Chunk par1Chunk)
    208        {
    209            if (this.currentChunkLoader != null)
    210            {
    211                try
    212                {
    213                    this.currentChunkLoader.saveExtraChunkData(this.currentServer, par1Chunk);
    214                }
    215                catch (Exception var3)
    216                {
    217                    var3.printStackTrace();
    218                }
    219            }
    220        }
    221
    222        /**
    223         * used by saveChunks, but catches any exceptions if the save fails.
    224         */
    225        private void safeSaveChunk(Chunk par1Chunk)
    226        {
    227            if (this.currentChunkLoader != null)
    228            {
    229                try
    230                {
    231                    par1Chunk.lastSaveTime = this.currentServer.getTotalWorldTime();
    232                    this.currentChunkLoader.saveChunk(this.currentServer, par1Chunk);
    233                }
    234                catch (IOException var3)
    235                {
    236                    var3.printStackTrace();
    237                }
    238                catch (MinecraftException var4)
    239                {
    240                    var4.printStackTrace();
    241                }
    242            }
    243        }
    244
    245        /**
    246         * Populates chunk with ores etc etc
    247         */
    248        public void populate(IChunkProvider par1IChunkProvider, int par2, int par3)
    249        {
    250            Chunk var4 = this.provideChunk(par2, par3);
    251
    252            if (!var4.isTerrainPopulated)
    253            {
    254                var4.isTerrainPopulated = true;
    255
    256                if (this.currentChunkProvider != null)
    257                {
    258                    this.currentChunkProvider.populate(par1IChunkProvider, par2, par3);
    259                    GameRegistry.generateWorld(par2, par3, currentServer, currentChunkProvider, par1IChunkProvider);
    260                    var4.setChunkModified();
    261                }
    262            }
    263        }
    264
    265        /**
    266         * Two modes of operation: if passed true, save all Chunks in one go.  If passed false, save up to two chunks.
    267         * Return true if all chunks have been saved.
    268         */
    269        public boolean saveChunks(boolean par1, IProgressUpdate par2IProgressUpdate)
    270        {
    271            int var3 = 0;
    272
    273            for (int var4 = 0; var4 < this.loadedChunks.size(); ++var4)
    274            {
    275                Chunk var5 = (Chunk)this.loadedChunks.get(var4);
    276
    277                if (par1)
    278                {
    279                    this.safeSaveExtraChunkData(var5);
    280                }
    281
    282                if (var5.needsSaving(par1))
    283                {
    284                    this.safeSaveChunk(var5);
    285                    var5.isModified = false;
    286                    ++var3;
    287
    288                    if (var3 == 24 && !par1)
    289                    {
    290                        return false;
    291                    }
    292                }
    293            }
    294
    295            if (par1)
    296            {
    297                if (this.currentChunkLoader == null)
    298                {
    299                    return true;
    300                }
    301
    302                this.currentChunkLoader.saveExtraData();
    303            }
    304
    305            return true;
    306        }
    307
    308        /**
    309         * Unloads the 100 oldest chunks from memory, due to a bug with chunkSet.add() never being called it thinks the list
    310         * is always empty and will not remove any chunks.
    311         */
    312        public boolean unload100OldestChunks()
    313        {
    314            if (!this.currentServer.canNotSave)
    315            {
    316                for (ChunkCoordIntPair forced : currentServer.getPersistentChunks().keySet())
    317                {
    318                    this.chunksToUnload.remove(ChunkCoordIntPair.chunkXZ2Int(forced.chunkXPos, forced.chunkZPos));
    319                }
    320
    321                for (int var1 = 0; var1 < 100; ++var1)
    322                {
    323                    if (!this.chunksToUnload.isEmpty())
    324                    {
    325                        Long var2 = (Long)this.chunksToUnload.iterator().next();
    326                        Chunk var3 = (Chunk)this.loadedChunkHashMap.getValueByKey(var2.longValue());
    327                        var3.onChunkUnload();
    328                        this.safeSaveChunk(var3);
    329                        this.safeSaveExtraChunkData(var3);
    330                        this.chunksToUnload.remove(var2);
    331                        this.loadedChunkHashMap.remove(var2.longValue());
    332                        this.loadedChunks.remove(var3);
    333                        ForgeChunkManager.putDormantChunk(ChunkCoordIntPair.chunkXZ2Int(var3.xPosition, var3.zPosition), var3);
    334                        if(loadedChunks.size() == 0 && ForgeChunkManager.getPersistentChunksFor(currentServer).size() == 0 && !DimensionManager.shouldLoadSpawn(currentServer.provider.dimensionId)) {
    335                            DimensionManager.unloadWorld(currentServer.provider.dimensionId);
    336                            return currentChunkProvider.unload100OldestChunks();
    337                        }
    338                    }
    339                }
    340
    341                if (this.currentChunkLoader != null)
    342                {
    343                    this.currentChunkLoader.chunkTick();
    344                }
    345            }
    346
    347            return this.currentChunkProvider.unload100OldestChunks();
    348        }
    349
    350        /**
    351         * Returns if the IChunkProvider supports saving.
    352         */
    353        public boolean canSave()
    354        {
    355            return !this.currentServer.canNotSave;
    356        }
    357
    358        /**
    359         * Converts the instance data to a readable string.
    360         */
    361        public String makeString()
    362        {
    363            return "ServerChunkCache: " + this.loadedChunkHashMap.getNumHashElements() + " Drop: " + this.chunksToUnload.size();
    364        }
    365
    366        /**
    367         * Returns a list of creatures of the specified type that can spawn at the given location.
    368         */
    369        public List getPossibleCreatures(EnumCreatureType par1EnumCreatureType, int par2, int par3, int par4)
    370        {
    371            return this.currentChunkProvider.getPossibleCreatures(par1EnumCreatureType, par2, par3, par4);
    372        }
    373
    374        /**
    375         * Returns the location of the closest structure of the specified type. If not found returns null.
    376         */
    377        public ChunkPosition findClosestStructure(World par1World, String par2Str, int par3, int par4, int par5)
    378        {
    379            return this.currentChunkProvider.findClosestStructure(par1World, par2Str, par3, par4, par5);
    380        }
    381
    382        public int getLoadedChunkCount()
    383        {
    384            return this.loadedChunkHashMap.getNumHashElements();
    385        }
    386
    387        public void recreateStructures(int par1, int par2) {}
    388    }