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 }