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