package net.minecraft.world.gen; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.ForgeChunkManager; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.EnumCreatureType; import net.minecraft.util.ChunkCoordinates; import net.minecraft.util.IProgressUpdate; import net.minecraft.util.LongHashMap; import net.minecraft.util.ReportedException; import net.minecraft.world.ChunkCoordIntPair; import net.minecraft.world.ChunkPosition; import net.minecraft.world.MinecraftException; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.EmptyChunk; import net.minecraft.world.chunk.IChunkProvider; import net.minecraft.world.chunk.storage.IChunkLoader; public class ChunkProviderServer implements IChunkProvider { /** * used by unload100OldestChunks to iterate the loadedChunkHashMap for unload (underlying assumption, first in, * first out) */ private Set chunksToUnload = new HashSet(); private Chunk defaultEmptyChunk; private IChunkProvider currentChunkProvider; public IChunkLoader currentChunkLoader; /** * if this is false, the defaultEmptyChunk will be returned by the provider */ public boolean loadChunkOnProvideRequest = true; private LongHashMap loadedChunkHashMap = new LongHashMap(); private List loadedChunks = new ArrayList(); private WorldServer worldObj; public ChunkProviderServer(WorldServer par1WorldServer, IChunkLoader par2IChunkLoader, IChunkProvider par3IChunkProvider) { this.defaultEmptyChunk = new EmptyChunk(par1WorldServer, 0, 0); this.worldObj = par1WorldServer; this.currentChunkLoader = par2IChunkLoader; this.currentChunkProvider = par3IChunkProvider; } /** * Checks to see if a chunk exists at x, y */ public boolean chunkExists(int par1, int par2) { return this.loadedChunkHashMap.containsItem(ChunkCoordIntPair.chunkXZ2Int(par1, par2)); } /** * marks chunk for unload by "unload100OldestChunks" if there is no spawn point, or if the center of the chunk is * outside 200 blocks (x or z) of the spawn */ public void unloadChunksIfNotNearSpawn(int par1, int par2) { if (this.worldObj.provider.canRespawnHere() && DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId)) { ChunkCoordinates var3 = this.worldObj.getSpawnPoint(); int var4 = par1 * 16 + 8 - var3.posX; int var5 = par2 * 16 + 8 - var3.posZ; short var6 = 128; if (var4 < -var6 || var4 > var6 || var5 < -var6 || var5 > var6) { this.chunksToUnload.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(par1, par2))); } } else { this.chunksToUnload.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(par1, par2))); } } /** * marks all chunks for unload, ignoring those near the spawn */ public void unloadAllChunks() { Iterator var1 = this.loadedChunks.iterator(); while (var1.hasNext()) { Chunk var2 = (Chunk)var1.next(); this.unloadChunksIfNotNearSpawn(var2.xPosition, var2.zPosition); } } /** * loads or generates the chunk at the chunk location specified */ public Chunk loadChunk(int par1, int par2) { long var3 = ChunkCoordIntPair.chunkXZ2Int(par1, par2); this.chunksToUnload.remove(Long.valueOf(var3)); Chunk var5 = (Chunk)this.loadedChunkHashMap.getValueByKey(var3); if (var5 == null) { var5 = ForgeChunkManager.fetchDormantChunk(var3, this.worldObj); if (var5 == null) { var5 = this.safeLoadChunk(par1, par2); } if (var5 == null) { if (this.currentChunkProvider == null) { var5 = this.defaultEmptyChunk; } else { try { var5 = this.currentChunkProvider.provideChunk(par1, par2); } catch (Throwable var9) { CrashReport var7 = CrashReport.makeCrashReport(var9, "Exception generating new chunk"); CrashReportCategory var8 = var7.makeCategory("Chunk to be generated"); var8.addCrashSection("Location", String.format("%d,%d", new Object[] {Integer.valueOf(par1), Integer.valueOf(par2)})); var8.addCrashSection("Position hash", Long.valueOf(var3)); var8.addCrashSection("Generator", this.currentChunkProvider.makeString()); throw new ReportedException(var7); } } } this.loadedChunkHashMap.add(var3, var5); this.loadedChunks.add(var5); if (var5 != null) { var5.onChunkLoad(); } var5.populateChunk(this, this, par1, par2); } return var5; } /** * Will return back a chunk, if it doesn't exist and its not a MP client it will generates all the blocks for the * specified chunk from the map seed and chunk seed */ public Chunk provideChunk(int par1, int par2) { Chunk var3 = (Chunk)this.loadedChunkHashMap.getValueByKey(ChunkCoordIntPair.chunkXZ2Int(par1, par2)); return var3 == null ? (!this.worldObj.findingSpawnPoint && !this.loadChunkOnProvideRequest ? this.defaultEmptyChunk : this.loadChunk(par1, par2)) : var3; } /** * used by loadChunk, but catches any exceptions if the load fails. */ private Chunk safeLoadChunk(int par1, int par2) { if (this.currentChunkLoader == null) { return null; } else { try { Chunk var3 = this.currentChunkLoader.loadChunk(this.worldObj, par1, par2); if (var3 != null) { var3.lastSaveTime = this.worldObj.getTotalWorldTime(); if (this.currentChunkProvider != null) { this.currentChunkProvider.recreateStructures(par1, par2); } } return var3; } catch (Exception var4) { var4.printStackTrace(); return null; } } } /** * used by saveChunks, but catches any exceptions if the save fails. */ private void safeSaveExtraChunkData(Chunk par1Chunk) { if (this.currentChunkLoader != null) { try { this.currentChunkLoader.saveExtraChunkData(this.worldObj, par1Chunk); } catch (Exception var3) { var3.printStackTrace(); } } } /** * used by saveChunks, but catches any exceptions if the save fails. */ private void safeSaveChunk(Chunk par1Chunk) { if (this.currentChunkLoader != null) { try { par1Chunk.lastSaveTime = this.worldObj.getTotalWorldTime(); this.currentChunkLoader.saveChunk(this.worldObj, par1Chunk); } catch (IOException var3) { var3.printStackTrace(); } catch (MinecraftException var4) { var4.printStackTrace(); } } } /** * Populates chunk with ores etc etc */ public void populate(IChunkProvider par1IChunkProvider, int par2, int par3) { Chunk var4 = this.provideChunk(par2, par3); if (!var4.isTerrainPopulated) { var4.isTerrainPopulated = true; if (this.currentChunkProvider != null) { this.currentChunkProvider.populate(par1IChunkProvider, par2, par3); GameRegistry.generateWorld(par2, par3, worldObj, currentChunkProvider, par1IChunkProvider); var4.setChunkModified(); } } } /** * Two modes of operation: if passed true, save all Chunks in one go. If passed false, save up to two chunks. * Return true if all chunks have been saved. */ public boolean saveChunks(boolean par1, IProgressUpdate par2IProgressUpdate) { int var3 = 0; for (int var4 = 0; var4 < this.loadedChunks.size(); ++var4) { Chunk var5 = (Chunk)this.loadedChunks.get(var4); if (par1) { this.safeSaveExtraChunkData(var5); } if (var5.needsSaving(par1)) { this.safeSaveChunk(var5); var5.isModified = false; ++var3; if (var3 == 24 && !par1) { return false; } } } if (par1) { if (this.currentChunkLoader == null) { return true; } this.currentChunkLoader.saveExtraData(); } return true; } /** * Unloads the 100 oldest chunks from memory, due to a bug with chunkSet.add() never being called it thinks the list * is always empty and will not remove any chunks. */ public boolean unload100OldestChunks() { if (!this.worldObj.canNotSave) { for (ChunkCoordIntPair forced : this.worldObj.getPersistentChunks().keySet()) { this.chunksToUnload.remove(ChunkCoordIntPair.chunkXZ2Int(forced.chunkXPos, forced.chunkZPos)); } for (int var1 = 0; var1 < 100; ++var1) { if (!this.chunksToUnload.isEmpty()) { Long var2 = (Long)this.chunksToUnload.iterator().next(); Chunk var3 = (Chunk)this.loadedChunkHashMap.getValueByKey(var2.longValue()); var3.onChunkUnload(); this.safeSaveChunk(var3); this.safeSaveExtraChunkData(var3); this.chunksToUnload.remove(var2); this.loadedChunkHashMap.remove(var2.longValue()); this.loadedChunks.remove(var3); ForgeChunkManager.putDormantChunk(ChunkCoordIntPair.chunkXZ2Int(var3.xPosition, var3.zPosition), var3); if(loadedChunks.size() == 0 && ForgeChunkManager.getPersistentChunksFor(this.worldObj).size() == 0 && !DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId)) { DimensionManager.unloadWorld(this.worldObj.provider.dimensionId); return currentChunkProvider.unload100OldestChunks(); } } } if (this.currentChunkLoader != null) { this.currentChunkLoader.chunkTick(); } } return this.currentChunkProvider.unload100OldestChunks(); } /** * Returns if the IChunkProvider supports saving. */ public boolean canSave() { return !this.worldObj.canNotSave; } /** * Converts the instance data to a readable string. */ public String makeString() { return "ServerChunkCache: " + this.loadedChunkHashMap.getNumHashElements() + " Drop: " + this.chunksToUnload.size(); } /** * Returns a list of creatures of the specified type that can spawn at the given location. */ public List getPossibleCreatures(EnumCreatureType par1EnumCreatureType, int par2, int par3, int par4) { return this.currentChunkProvider.getPossibleCreatures(par1EnumCreatureType, par2, par3, par4); } /** * Returns the location of the closest structure of the specified type. If not found returns null. */ public ChunkPosition findClosestStructure(World par1World, String par2Str, int par3, int par4, int par5) { return this.currentChunkProvider.findClosestStructure(par1World, par2Str, par3, par4, par5); } public int getLoadedChunkCount() { return this.loadedChunkHashMap.getNumHashElements(); } public void recreateStructures(int par1, int par2) {} }