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 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    }