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 }