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