001 package net.minecraft.world.chunk.storage; 002 003 import java.io.DataInputStream; 004 import java.io.DataOutputStream; 005 import java.io.File; 006 import java.io.IOException; 007 import java.util.ArrayList; 008 import java.util.HashSet; 009 import java.util.Iterator; 010 import java.util.List; 011 import java.util.Set; 012 import java.util.logging.Level; 013 014 import cpw.mods.fml.common.FMLLog; 015 016 import net.minecraft.entity.Entity; 017 import net.minecraft.entity.EntityList; 018 import net.minecraft.nbt.CompressedStreamTools; 019 import net.minecraft.nbt.NBTTagCompound; 020 import net.minecraft.nbt.NBTTagList; 021 import net.minecraft.tileentity.TileEntity; 022 import net.minecraft.world.ChunkCoordIntPair; 023 import net.minecraft.world.MinecraftException; 024 import net.minecraft.world.NextTickListEntry; 025 import net.minecraft.world.World; 026 import net.minecraft.world.chunk.Chunk; 027 import net.minecraft.world.chunk.NibbleArray; 028 import net.minecraft.world.storage.IThreadedFileIO; 029 import net.minecraft.world.storage.ThreadedFileIOBase; 030 import net.minecraftforge.common.MinecraftForge; 031 import net.minecraftforge.event.world.ChunkDataEvent; 032 033 public class AnvilChunkLoader implements IChunkLoader, IThreadedFileIO 034 { 035 private List chunksToRemove = new ArrayList(); 036 private Set pendingAnvilChunksCoordinates = new HashSet(); 037 private Object syncLockObject = new Object(); 038 039 /** Save directory for chunks using the Anvil format */ 040 public final File chunkSaveLocation; 041 042 public AnvilChunkLoader(File par1File) 043 { 044 this.chunkSaveLocation = par1File; 045 } 046 047 /** 048 * Loads the specified(XZ) chunk into the specified world. 049 */ 050 public Chunk loadChunk(World par1World, int par2, int par3) throws IOException 051 { 052 NBTTagCompound var4 = null; 053 ChunkCoordIntPair var5 = new ChunkCoordIntPair(par2, par3); 054 Object var6 = this.syncLockObject; 055 056 synchronized (this.syncLockObject) 057 { 058 if (this.pendingAnvilChunksCoordinates.contains(var5)) 059 { 060 for (int var7 = 0; var7 < this.chunksToRemove.size(); ++var7) 061 { 062 if (((AnvilChunkLoaderPending)this.chunksToRemove.get(var7)).chunkCoordinate.equals(var5)) 063 { 064 var4 = ((AnvilChunkLoaderPending)this.chunksToRemove.get(var7)).nbtTags; 065 break; 066 } 067 } 068 } 069 } 070 071 if (var4 == null) 072 { 073 DataInputStream var10 = RegionFileCache.getChunkInputStream(this.chunkSaveLocation, par2, par3); 074 075 if (var10 == null) 076 { 077 return null; 078 } 079 080 var4 = CompressedStreamTools.read(var10); 081 } 082 083 return this.checkedReadChunkFromNBT(par1World, par2, par3, var4); 084 } 085 086 /** 087 * Wraps readChunkFromNBT. Checks the coordinates and several NBT tags. 088 */ 089 protected Chunk checkedReadChunkFromNBT(World par1World, int par2, int par3, NBTTagCompound par4NBTTagCompound) 090 { 091 if (!par4NBTTagCompound.hasKey("Level")) 092 { 093 System.out.println("Chunk file at " + par2 + "," + par3 + " is missing level data, skipping"); 094 return null; 095 } 096 else if (!par4NBTTagCompound.getCompoundTag("Level").hasKey("Sections")) 097 { 098 System.out.println("Chunk file at " + par2 + "," + par3 + " is missing block data, skipping"); 099 return null; 100 } 101 else 102 { 103 Chunk var5 = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level")); 104 105 if (!var5.isAtLocation(par2, par3)) 106 { 107 System.out.println("Chunk file at " + par2 + "," + par3 + " is in the wrong location; relocating. (Expected " + par2 + ", " + par3 + ", got " + var5.xPosition + ", " + var5.zPosition + ")"); 108 par4NBTTagCompound.setInteger("xPos", par2); 109 par4NBTTagCompound.setInteger("zPos", par3); 110 var5 = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level")); 111 } 112 113 MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Load(var5, par4NBTTagCompound)); 114 return var5; 115 } 116 } 117 118 public void saveChunk(World par1World, Chunk par2Chunk) throws MinecraftException, IOException 119 { 120 par1World.checkSessionLock(); 121 122 try 123 { 124 NBTTagCompound var3 = new NBTTagCompound(); 125 NBTTagCompound var4 = new NBTTagCompound(); 126 var3.setTag("Level", var4); 127 this.writeChunkToNBT(par2Chunk, par1World, var4); 128 this.func_75824_a(par2Chunk.getChunkCoordIntPair(), var3); 129 MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Save(par2Chunk, var3)); 130 } 131 catch (Exception var5) 132 { 133 var5.printStackTrace(); 134 } 135 } 136 137 protected void func_75824_a(ChunkCoordIntPair par1ChunkCoordIntPair, NBTTagCompound par2NBTTagCompound) 138 { 139 Object var3 = this.syncLockObject; 140 141 synchronized (this.syncLockObject) 142 { 143 if (this.pendingAnvilChunksCoordinates.contains(par1ChunkCoordIntPair)) 144 { 145 for (int var4 = 0; var4 < this.chunksToRemove.size(); ++var4) 146 { 147 if (((AnvilChunkLoaderPending)this.chunksToRemove.get(var4)).chunkCoordinate.equals(par1ChunkCoordIntPair)) 148 { 149 this.chunksToRemove.set(var4, new AnvilChunkLoaderPending(par1ChunkCoordIntPair, par2NBTTagCompound)); 150 return; 151 } 152 } 153 } 154 155 this.chunksToRemove.add(new AnvilChunkLoaderPending(par1ChunkCoordIntPair, par2NBTTagCompound)); 156 this.pendingAnvilChunksCoordinates.add(par1ChunkCoordIntPair); 157 ThreadedFileIOBase.threadedIOInstance.queueIO(this); 158 } 159 } 160 161 /** 162 * Returns a boolean stating if the write was unsuccessful. 163 */ 164 public boolean writeNextIO() 165 { 166 AnvilChunkLoaderPending var1 = null; 167 Object var2 = this.syncLockObject; 168 169 synchronized (this.syncLockObject) 170 { 171 if (this.chunksToRemove.isEmpty()) 172 { 173 return false; 174 } 175 176 var1 = (AnvilChunkLoaderPending)this.chunksToRemove.remove(0); 177 this.pendingAnvilChunksCoordinates.remove(var1.chunkCoordinate); 178 } 179 180 if (var1 != null) 181 { 182 try 183 { 184 this.writeChunkNBTTags(var1); 185 } 186 catch (Exception var4) 187 { 188 var4.printStackTrace(); 189 } 190 } 191 192 return true; 193 } 194 195 private void writeChunkNBTTags(AnvilChunkLoaderPending par1AnvilChunkLoaderPending) throws IOException 196 { 197 DataOutputStream var2 = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, par1AnvilChunkLoaderPending.chunkCoordinate.chunkXPos, par1AnvilChunkLoaderPending.chunkCoordinate.chunkZPos); 198 CompressedStreamTools.write(par1AnvilChunkLoaderPending.nbtTags, var2); 199 var2.close(); 200 } 201 202 /** 203 * Save extra data associated with this Chunk not normally saved during autosave, only during chunk unload. 204 * Currently unused. 205 */ 206 public void saveExtraChunkData(World par1World, Chunk par2Chunk) {} 207 208 /** 209 * Called every World.tick() 210 */ 211 public void chunkTick() {} 212 213 /** 214 * Save extra data not associated with any Chunk. Not saved during autosave, only during world unload. Currently 215 * unused. 216 */ 217 public void saveExtraData() {} 218 219 /** 220 * Writes the Chunk passed as an argument to the NBTTagCompound also passed, using the World argument to retrieve 221 * the Chunk's last update time. 222 */ 223 private void writeChunkToNBT(Chunk par1Chunk, World par2World, NBTTagCompound par3NBTTagCompound) 224 { 225 par3NBTTagCompound.setInteger("xPos", par1Chunk.xPosition); 226 par3NBTTagCompound.setInteger("zPos", par1Chunk.zPosition); 227 par3NBTTagCompound.setLong("LastUpdate", par2World.getTotalWorldTime()); 228 par3NBTTagCompound.setIntArray("HeightMap", par1Chunk.heightMap); 229 par3NBTTagCompound.setBoolean("TerrainPopulated", par1Chunk.isTerrainPopulated); 230 ExtendedBlockStorage[] var4 = par1Chunk.getBlockStorageArray(); 231 NBTTagList var5 = new NBTTagList("Sections"); 232 boolean var6 = !par2World.provider.hasNoSky; 233 ExtendedBlockStorage[] var7 = var4; 234 int var8 = var4.length; 235 NBTTagCompound var11; 236 237 for (int var9 = 0; var9 < var8; ++var9) 238 { 239 ExtendedBlockStorage var10 = var7[var9]; 240 241 if (var10 != null) 242 { 243 var11 = new NBTTagCompound(); 244 var11.setByte("Y", (byte)(var10.getYLocation() >> 4 & 255)); 245 var11.setByteArray("Blocks", var10.getBlockLSBArray()); 246 247 if (var10.getBlockMSBArray() != null) 248 { 249 var11.setByteArray("Add", var10.getBlockMSBArray().data); 250 } 251 252 var11.setByteArray("Data", var10.getMetadataArray().data); 253 var11.setByteArray("BlockLight", var10.getBlocklightArray().data); 254 255 if (var6) 256 { 257 var11.setByteArray("SkyLight", var10.getSkylightArray().data); 258 } 259 else 260 { 261 var11.setByteArray("SkyLight", new byte[var10.getBlocklightArray().data.length]); 262 } 263 264 var5.appendTag(var11); 265 } 266 } 267 268 par3NBTTagCompound.setTag("Sections", var5); 269 par3NBTTagCompound.setByteArray("Biomes", par1Chunk.getBiomeArray()); 270 par1Chunk.hasEntities = false; 271 NBTTagList var16 = new NBTTagList(); 272 Iterator var18; 273 274 for (var8 = 0; var8 < par1Chunk.entityLists.length; ++var8) 275 { 276 var18 = par1Chunk.entityLists[var8].iterator(); 277 278 while (var18.hasNext()) 279 { 280 Entity var21 = (Entity)var18.next(); 281 par1Chunk.hasEntities = true; 282 var11 = new NBTTagCompound(); 283 284 285 try 286 { 287 if (var21.addEntityID(var11)) 288 { 289 var16.appendTag(var11); 290 } 291 } 292 catch (Exception e) 293 { 294 FMLLog.log(Level.SEVERE, e, 295 "An Entity type %s has thrown an exception trying to write state. It will not persist. Report this to the mod author", 296 var21.getClass().getName()); 297 } 298 } 299 } 300 301 par3NBTTagCompound.setTag("Entities", var16); 302 NBTTagList var17 = new NBTTagList(); 303 var18 = par1Chunk.chunkTileEntityMap.values().iterator(); 304 305 while (var18.hasNext()) 306 { 307 TileEntity var22 = (TileEntity)var18.next(); 308 var11 = new NBTTagCompound(); 309 try 310 { 311 var22.writeToNBT(var11); 312 var17.appendTag(var11); 313 } 314 catch (Exception e) 315 { 316 FMLLog.log(Level.SEVERE, e, 317 "A TileEntity type %s has throw an exception trying to write state. It will not persist. Report this to the mod author", 318 var22.getClass().getName()); 319 } 320 } 321 322 par3NBTTagCompound.setTag("TileEntities", var17); 323 List var20 = par2World.getPendingBlockUpdates(par1Chunk, false); 324 325 if (var20 != null) 326 { 327 long var19 = par2World.getTotalWorldTime(); 328 NBTTagList var12 = new NBTTagList(); 329 Iterator var13 = var20.iterator(); 330 331 while (var13.hasNext()) 332 { 333 NextTickListEntry var14 = (NextTickListEntry)var13.next(); 334 NBTTagCompound var15 = new NBTTagCompound(); 335 var15.setInteger("i", var14.blockID); 336 var15.setInteger("x", var14.xCoord); 337 var15.setInteger("y", var14.yCoord); 338 var15.setInteger("z", var14.zCoord); 339 var15.setInteger("t", (int)(var14.scheduledTime - var19)); 340 var12.appendTag(var15); 341 } 342 343 par3NBTTagCompound.setTag("TileTicks", var12); 344 } 345 } 346 347 /** 348 * Reads the data stored in the passed NBTTagCompound and creates a Chunk with that data in the passed World. 349 * Returns the created Chunk. 350 */ 351 private Chunk readChunkFromNBT(World par1World, NBTTagCompound par2NBTTagCompound) 352 { 353 int var3 = par2NBTTagCompound.getInteger("xPos"); 354 int var4 = par2NBTTagCompound.getInteger("zPos"); 355 Chunk var5 = new Chunk(par1World, var3, var4); 356 var5.heightMap = par2NBTTagCompound.getIntArray("HeightMap"); 357 var5.isTerrainPopulated = par2NBTTagCompound.getBoolean("TerrainPopulated"); 358 NBTTagList var6 = par2NBTTagCompound.getTagList("Sections"); 359 byte var7 = 16; 360 ExtendedBlockStorage[] var8 = new ExtendedBlockStorage[var7]; 361 boolean var9 = !par1World.provider.hasNoSky; 362 363 for (int var10 = 0; var10 < var6.tagCount(); ++var10) 364 { 365 NBTTagCompound var11 = (NBTTagCompound)var6.tagAt(var10); 366 byte var12 = var11.getByte("Y"); 367 ExtendedBlockStorage var13 = new ExtendedBlockStorage(var12 << 4, var9); 368 var13.setBlockLSBArray(var11.getByteArray("Blocks")); 369 370 if (var11.hasKey("Add")) 371 { 372 var13.setBlockMSBArray(new NibbleArray(var11.getByteArray("Add"), 4)); 373 } 374 375 var13.setBlockMetadataArray(new NibbleArray(var11.getByteArray("Data"), 4)); 376 var13.setBlocklightArray(new NibbleArray(var11.getByteArray("BlockLight"), 4)); 377 378 if (var9) 379 { 380 var13.setSkylightArray(new NibbleArray(var11.getByteArray("SkyLight"), 4)); 381 } 382 383 var13.removeInvalidBlocks(); 384 var8[var12] = var13; 385 } 386 387 var5.setStorageArrays(var8); 388 389 if (par2NBTTagCompound.hasKey("Biomes")) 390 { 391 var5.setBiomeArray(par2NBTTagCompound.getByteArray("Biomes")); 392 } 393 394 NBTTagList var16 = par2NBTTagCompound.getTagList("Entities"); 395 396 if (var16 != null) 397 { 398 for (int var15 = 0; var15 < var16.tagCount(); ++var15) 399 { 400 NBTTagCompound var17 = (NBTTagCompound)var16.tagAt(var15); 401 Entity var22 = EntityList.createEntityFromNBT(var17, par1World); 402 var5.hasEntities = true; 403 404 if (var22 != null) 405 { 406 var5.addEntity(var22); 407 } 408 } 409 } 410 411 NBTTagList var19 = par2NBTTagCompound.getTagList("TileEntities"); 412 413 if (var19 != null) 414 { 415 for (int var18 = 0; var18 < var19.tagCount(); ++var18) 416 { 417 NBTTagCompound var20 = (NBTTagCompound)var19.tagAt(var18); 418 TileEntity var14 = TileEntity.createAndLoadEntity(var20); 419 420 if (var14 != null) 421 { 422 var5.addTileEntity(var14); 423 } 424 } 425 } 426 427 if (par2NBTTagCompound.hasKey("TileTicks")) 428 { 429 NBTTagList var23 = par2NBTTagCompound.getTagList("TileTicks"); 430 431 if (var23 != null) 432 { 433 for (int var21 = 0; var21 < var23.tagCount(); ++var21) 434 { 435 NBTTagCompound var24 = (NBTTagCompound)var23.tagAt(var21); 436 par1World.scheduleBlockUpdateFromLoad(var24.getInteger("x"), var24.getInteger("y"), var24.getInteger("z"), var24.getInteger("i"), var24.getInteger("t")); 437 } 438 } 439 } 440 441 return var5; 442 } 443 }