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 }