    001package net.minecraft.world.gen;
    002
    003import java.io.IOException;
    004import java.util.ArrayList;
    005import java.util.HashSet;
    006import java.util.Iterator;
    007import java.util.List;
    008import java.util.Set;
    009
    010import net.minecraftforge.common.DimensionManager;
    011import net.minecraftforge.common.ForgeChunkManager;
    012
    013import cpw.mods.fml.common.registry.GameRegistry;
    014import net.minecraft.crash.CrashReport;
    015import net.minecraft.crash.CrashReportCategory;
    016import net.minecraft.entity.EnumCreatureType;
    017import net.minecraft.util.ChunkCoordinates;
    018import net.minecraft.util.IProgressUpdate;
    019import net.minecraft.util.LongHashMap;
    020import net.minecraft.util.ReportedException;
    021import net.minecraft.world.ChunkCoordIntPair;
    022import net.minecraft.world.ChunkPosition;
    023import net.minecraft.world.MinecraftException;
    024import net.minecraft.world.World;
    025import net.minecraft.world.WorldServer;
    026import net.minecraft.world.chunk.Chunk;
    027import net.minecraft.world.chunk.EmptyChunk;
    028import net.minecraft.world.chunk.IChunkProvider;
    029import net.minecraft.world.chunk.storage.IChunkLoader;
    030
    031public 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 worldObj;
    049
    050    public ChunkProviderServer(WorldServer par1WorldServer, IChunkLoader par2IChunkLoader, IChunkProvider par3IChunkProvider)
    051    {
    052        this.defaultEmptyChunk = new EmptyChunk(par1WorldServer, 0, 0);
    053        this.worldObj = 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.worldObj.provider.canRespawnHere() && DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId))
    073        {
    074            ChunkCoordinates var3 = this.worldObj.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, this.worldObj);
    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.worldObj.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.worldObj, par1, par2);
    183
    184                if (var3 != null)
    185                {
    186                    var3.lastSaveTime = this.worldObj.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.worldObj, 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.worldObj.getTotalWorldTime();
    232                this.currentChunkLoader.saveChunk(this.worldObj, 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, worldObj, 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.worldObj.canNotSave)
    315        {
    316            for (ChunkCoordIntPair forced : this.worldObj.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(this.worldObj).size() == 0 && !DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId)) {
    335                        DimensionManager.unloadWorld(this.worldObj.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.worldObj.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}