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