package net.minecraft.world.gen;
import com.google.common.collect.Lists;
import cpw.mods.fml.common.registry.GameRegistry;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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.AnvilChunkLoader;
import net.minecraft.world.chunk.storage.IChunkLoader;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.chunkio.ChunkIOExecutor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ChunkProviderServer implements IChunkProvider
{
private static final Logger logger = LogManager.getLogger();
private Set droppedChunksSet = Collections.newSetFromMap(new ConcurrentHashMap());
/** a dummy chunk, returned in place of an actual chunk. */
private Chunk dummyChunk;
/** chunk generator object. Calls to load nonexistent chunks are forwarded to this object. */
public IChunkProvider serverChunkGenerator;
public IChunkLoader chunkLoader;
/**
* if set, this flag forces a request to load a chunk to load the chunk rather than defaulting to the dummy if
* possible
*/
public boolean chunkLoadOverride = true;
/** map of chunk Id's to Chunk instances */
public LongHashMap id2ChunkMap = new LongHashMap();
public List loadedChunks = new ArrayList();
public WorldServer worldObj;
private Set<Long> loadingChunks = com.google.common.collect.Sets.newHashSet();
private static final String __OBFID = "CL_00001436";
public ChunkProviderServer(WorldServer p_i1520_1_, IChunkLoader p_i1520_2_, IChunkProvider p_i1520_3_)
{
this.dummyChunk = new EmptyChunk(p_i1520_1_, 0, 0);
this.worldObj = p_i1520_1_;
this.chunkLoader = p_i1520_2_;
this.serverChunkGenerator = p_i1520_3_;
}
/**
* Checks to see if a chunk exists at x, y
*/
public boolean chunkExists(int p_73149_1_, int p_73149_2_)
{
return this.id2ChunkMap.containsItem(ChunkCoordIntPair.chunkXZ2Int(p_73149_1_, p_73149_2_));
}
public List func_152380_a()
{
return this.loadedChunks;
}
public void dropChunk(int p_73241_1_, int p_73241_2_)
{
if (this.worldObj.provider.canRespawnHere() && DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId))
{
ChunkCoordinates chunkcoordinates = this.worldObj.getSpawnPoint();
int k = p_73241_1_ * 16 + 8 - chunkcoordinates.posX;
int l = p_73241_2_ * 16 + 8 - chunkcoordinates.posZ;
short short1 = 128;
if (k < -short1 || k > short1 || l < -short1 || l > short1)
{
this.droppedChunksSet.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(p_73241_1_, p_73241_2_)));
}
}
else
{
this.droppedChunksSet.add(Long.valueOf(ChunkCoordIntPair.chunkXZ2Int(p_73241_1_, p_73241_2_)));
}
}
/**
* marks all chunks for unload, ignoring those near the spawn
*/
public void unloadAllChunks()
{
Iterator iterator = this.loadedChunks.iterator();
while (iterator.hasNext())
{
Chunk chunk = (Chunk)iterator.next();
this.dropChunk(chunk.xPosition, chunk.zPosition);
}
}
/**
* loads or generates the chunk at the chunk location specified
*/
public Chunk loadChunk(int p_73158_1_, int p_73158_2_)
{
return loadChunk(p_73158_1_, p_73158_2_, null);
}
public Chunk loadChunk(int par1, int par2, Runnable runnable)
{
long k = ChunkCoordIntPair.chunkXZ2Int(par1, par2);
this.droppedChunksSet.remove(Long.valueOf(k));
Chunk chunk = (Chunk)this.id2ChunkMap.getValueByKey(k);
AnvilChunkLoader loader = null;
if (this.chunkLoader instanceof AnvilChunkLoader)
{
loader = (AnvilChunkLoader) this.chunkLoader;
}
// We can only use the queue for already generated chunks
if (chunk == null && loader != null && loader.chunkExists(this.worldObj, par1, par2))
{
if (runnable != null)
{
ChunkIOExecutor.queueChunkLoad(this.worldObj, loader, this, par1, par2, runnable);
return null;
}
else
{
chunk = ChunkIOExecutor.syncChunkLoad(this.worldObj, loader, this, par1, par2);
}
}
else if (chunk == null)
{
chunk = this.originalLoadChunk(par1, par2);
}
// If we didn't load the chunk async and have a callback run it now
if (runnable != null)
{
runnable.run();
}
return chunk;
}
public Chunk originalLoadChunk(int p_73158_1_, int p_73158_2_)
{
long k = ChunkCoordIntPair.chunkXZ2Int(p_73158_1_, p_73158_2_);
this.droppedChunksSet.remove(Long.valueOf(k));
Chunk chunk = (Chunk)this.id2ChunkMap.getValueByKey(k);
if (chunk == null)
{
boolean added = loadingChunks.add(k);
if (!added)
{
cpw.mods.fml.common.FMLLog.bigWarning("There is an attempt to load a chunk (%d,%d) in dimension %d that is already being loaded. This will cause weird chunk breakages.", p_73158_1_, p_73158_2_, worldObj.provider.dimensionId);
}
chunk = ForgeChunkManager.fetchDormantChunk(k, this.worldObj);
if (chunk == null)
{
chunk = this.loadChunkFromFile(p_73158_1_, p_73158_2_);
}
if (chunk == null)
{
if (this.serverChunkGenerator == null)
{
chunk = this.dummyChunk;
}
else
{
try
{
chunk = this.serverChunkGenerator.provideChunk(p_73158_1_, p_73158_2_);
}
catch (Throwable throwable)
{
CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Exception generating new chunk");
CrashReportCategory crashreportcategory = crashreport.makeCategory("Chunk to be generated");
crashreportcategory.addCrashSection("Location", String.format("%d,%d", new Object[] {Integer.valueOf(p_73158_1_), Integer.valueOf(p_73158_2_)}));
crashreportcategory.addCrashSection("Position hash", Long.valueOf(k));
crashreportcategory.addCrashSection("Generator", this.serverChunkGenerator.makeString());
throw new ReportedException(crashreport);
}
}
}
this.id2ChunkMap.add(k, chunk);
this.loadedChunks.add(chunk);
loadingChunks.remove(k);
chunk.onChunkLoad();
chunk.populateChunk(this, this, p_73158_1_, p_73158_2_);
}
return chunk;
}
/**
* 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 p_73154_1_, int p_73154_2_)
{
Chunk chunk = (Chunk)this.id2ChunkMap.getValueByKey(ChunkCoordIntPair.chunkXZ2Int(p_73154_1_, p_73154_2_));
return chunk == null ? (!this.worldObj.findingSpawnPoint && !this.chunkLoadOverride ? this.dummyChunk : this.loadChunk(p_73154_1_, p_73154_2_)) : chunk;
}
private Chunk loadChunkFromFile(int p_73239_1_, int p_73239_2_)
{
if (this.chunkLoader == null)
{
return null;
}
else
{
try
{
Chunk chunk = this.chunkLoader.loadChunk(this.worldObj, p_73239_1_, p_73239_2_);
if (chunk != null)
{
chunk.lastSaveTime = this.worldObj.getTotalWorldTime();
if (this.serverChunkGenerator != null)
{
this.serverChunkGenerator.recreateStructures(p_73239_1_, p_73239_2_);
}
}
return chunk;
}
catch (Exception exception)
{
logger.error("Couldn\'t load chunk", exception);
return null;
}
}
}
private void saveChunkExtraData(Chunk p_73243_1_)
{
if (this.chunkLoader != null)
{
try
{
this.chunkLoader.saveExtraChunkData(this.worldObj, p_73243_1_);
}
catch (Exception exception)
{
logger.error("Couldn\'t save entities", exception);
}
}
}
private void saveChunkData(Chunk p_73242_1_)
{
if (this.chunkLoader != null)
{
try
{
p_73242_1_.lastSaveTime = this.worldObj.getTotalWorldTime();
this.chunkLoader.saveChunk(this.worldObj, p_73242_1_);
}
catch (IOException ioexception)
{
logger.error("Couldn\'t save chunk", ioexception);
}
catch (MinecraftException minecraftexception)
{
logger.error("Couldn\'t save chunk; already in use by another instance of Minecraft?", minecraftexception);
}
}
}
/**
* Populates chunk with ores etc etc
*/
public void populate(IChunkProvider p_73153_1_, int p_73153_2_, int p_73153_3_)
{
Chunk chunk = this.provideChunk(p_73153_2_, p_73153_3_);
if (!chunk.isTerrainPopulated)
{
chunk.func_150809_p();
if (this.serverChunkGenerator != null)
{
this.serverChunkGenerator.populate(p_73153_1_, p_73153_2_, p_73153_3_);
GameRegistry.generateWorld(p_73153_2_, p_73153_3_, worldObj, serverChunkGenerator, p_73153_1_);
chunk.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 p_73151_1_, IProgressUpdate p_73151_2_)
{
int i = 0;
ArrayList arraylist = Lists.newArrayList(this.loadedChunks);
for (int j = 0; j < arraylist.size(); ++j)
{
Chunk chunk = (Chunk)arraylist.get(j);
if (p_73151_1_)
{
this.saveChunkExtraData(chunk);
}
if (chunk.needsSaving(p_73151_1_))
{
this.saveChunkData(chunk);
chunk.isModified = false;
++i;
if (i == 24 && !p_73151_1_)
{
return false;
}
}
}
return true;
}
/**
* Save extra data not associated with any Chunk. Not saved during autosave, only during world unload. Currently
* unimplemented.
*/
public void saveExtraData()
{
if (this.chunkLoader != null)
{
this.chunkLoader.saveExtraData();
}
}
/**
* Unloads chunks that are marked to be unloaded. This is not guaranteed to unload every such chunk.
*/
public boolean unloadQueuedChunks()
{
if (!this.worldObj.disableLevelSaving)
{
for (ChunkCoordIntPair forced : this.worldObj.getPersistentChunks().keySet())
{
this.droppedChunksSet.remove(ChunkCoordIntPair.chunkXZ2Int(forced.chunkXPos, forced.chunkZPos));
}
for (int i = 0; i < 100; ++i)
{
if (!this.droppedChunksSet.isEmpty())
{
Long olong = (Long)this.droppedChunksSet.iterator().next();
Chunk chunk = (Chunk)this.id2ChunkMap.getValueByKey(olong.longValue());
if (chunk != null)
{
chunk.onChunkUnload();
this.saveChunkData(chunk);
this.saveChunkExtraData(chunk);
this.loadedChunks.remove(chunk);
ForgeChunkManager.putDormantChunk(ChunkCoordIntPair.chunkXZ2Int(chunk.xPosition, chunk.zPosition), chunk);
if(loadedChunks.size() == 0 && ForgeChunkManager.getPersistentChunksFor(this.worldObj).size() == 0 && !DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId)){
DimensionManager.unloadWorld(this.worldObj.provider.dimensionId);
return serverChunkGenerator.unloadQueuedChunks();
}
}
this.droppedChunksSet.remove(olong);
this.id2ChunkMap.remove(olong.longValue());
}
}
if (this.chunkLoader != null)
{
this.chunkLoader.chunkTick();
}
}
return this.serverChunkGenerator.unloadQueuedChunks();
}
/**
* Returns if the IChunkProvider supports saving.
*/
public boolean canSave()
{
return !this.worldObj.disableLevelSaving;
}
/**
* Converts the instance data to a readable string.
*/
public String makeString()
{
return "ServerChunkCache: " + this.id2ChunkMap.getNumHashElements() + " Drop: " + this.droppedChunksSet.size();
}
/**
* Returns a list of creatures of the specified type that can spawn at the given location.
*/
public List getPossibleCreatures(EnumCreatureType p_73155_1_, int p_73155_2_, int p_73155_3_, int p_73155_4_)
{
return this.serverChunkGenerator.getPossibleCreatures(p_73155_1_, p_73155_2_, p_73155_3_, p_73155_4_);
}
public ChunkPosition findClosestStructure(World p_147416_1_, String p_147416_2_, int p_147416_3_, int p_147416_4_, int p_147416_5_)
{
return this.serverChunkGenerator.findClosestStructure(p_147416_1_, p_147416_2_, p_147416_3_, p_147416_4_, p_147416_5_);
}
public int getLoadedChunkCount()
{
return this.id2ChunkMap.getNumHashElements();
}
public void recreateStructures(int p_82695_1_, int p_82695_2_) {}
}