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}