001package net.minecraft.world.chunk.storage; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.File; 006import java.io.IOException; 007import java.util.ArrayList; 008import java.util.HashSet; 009import java.util.Iterator; 010import java.util.List; 011import java.util.Set; 012import java.util.logging.Level; 013 014import cpw.mods.fml.common.FMLLog; 015 016import net.minecraft.entity.Entity; 017import net.minecraft.entity.EntityList; 018import net.minecraft.nbt.CompressedStreamTools; 019import net.minecraft.nbt.NBTTagCompound; 020import net.minecraft.nbt.NBTTagList; 021import net.minecraft.tileentity.TileEntity; 022import net.minecraft.world.ChunkCoordIntPair; 023import net.minecraft.world.MinecraftException; 024import net.minecraft.world.NextTickListEntry; 025import net.minecraft.world.World; 026import net.minecraft.world.chunk.Chunk; 027import net.minecraft.world.chunk.NibbleArray; 028import net.minecraft.world.storage.IThreadedFileIO; 029import net.minecraft.world.storage.ThreadedFileIOBase; 030import net.minecraftforge.common.MinecraftForge; 031import net.minecraftforge.event.world.ChunkDataEvent; 032 033public 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 nbttagcompound = null; 053 ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(par2, par3); 054 Object object = this.syncLockObject; 055 056 synchronized (this.syncLockObject) 057 { 058 if (this.pendingAnvilChunksCoordinates.contains(chunkcoordintpair)) 059 { 060 for (int k = 0; k < this.chunksToRemove.size(); ++k) 061 { 062 if (((AnvilChunkLoaderPending)this.chunksToRemove.get(k)).chunkCoordinate.equals(chunkcoordintpair)) 063 { 064 nbttagcompound = ((AnvilChunkLoaderPending)this.chunksToRemove.get(k)).nbtTags; 065 break; 066 } 067 } 068 } 069 } 070 071 if (nbttagcompound == null) 072 { 073 DataInputStream datainputstream = RegionFileCache.getChunkInputStream(this.chunkSaveLocation, par2, par3); 074 075 if (datainputstream == null) 076 { 077 return null; 078 } 079 080 nbttagcompound = CompressedStreamTools.read(datainputstream); 081 } 082 083 return this.checkedReadChunkFromNBT(par1World, par2, par3, nbttagcompound); 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 par1World.func_98180_V().func_98232_c("Chunk file at " + par2 + "," + par3 + " is missing level data, skipping"); 094 return null; 095 } 096 else if (!par4NBTTagCompound.getCompoundTag("Level").hasKey("Sections")) 097 { 098 par1World.func_98180_V().func_98232_c("Chunk file at " + par2 + "," + par3 + " is missing block data, skipping"); 099 return null; 100 } 101 else 102 { 103 Chunk chunk = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level")); 104 105 if (!chunk.isAtLocation(par2, par3)) 106 { 107 par1World.func_98180_V().func_98232_c("Chunk file at " + par2 + "," + par3 + " is in the wrong location; relocating. (Expected " + par2 + ", " + par3 + ", got " + chunk.xPosition + ", " + chunk.zPosition + ")"); 108 par4NBTTagCompound.setInteger("xPos", par2); 109 par4NBTTagCompound.setInteger("zPos", par3); 110 chunk = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level")); 111 } 112 113 MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Load(chunk, par4NBTTagCompound)); 114 return chunk; 115 } 116 } 117 118 public void saveChunk(World par1World, Chunk par2Chunk) throws MinecraftException, IOException 119 { 120 par1World.checkSessionLock(); 121 122 try 123 { 124 NBTTagCompound nbttagcompound = new NBTTagCompound(); 125 NBTTagCompound nbttagcompound1 = new NBTTagCompound(); 126 nbttagcompound.setTag("Level", nbttagcompound1); 127 this.writeChunkToNBT(par2Chunk, par1World, nbttagcompound1); 128 this.func_75824_a(par2Chunk.getChunkCoordIntPair(), nbttagcompound); 129 MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Save(par2Chunk, nbttagcompound)); 130 } 131 catch (Exception exception) 132 { 133 exception.printStackTrace(); 134 } 135 } 136 137 protected void func_75824_a(ChunkCoordIntPair par1ChunkCoordIntPair, NBTTagCompound par2NBTTagCompound) 138 { 139 Object object = this.syncLockObject; 140 141 synchronized (this.syncLockObject) 142 { 143 if (this.pendingAnvilChunksCoordinates.contains(par1ChunkCoordIntPair)) 144 { 145 for (int i = 0; i < this.chunksToRemove.size(); ++i) 146 { 147 if (((AnvilChunkLoaderPending)this.chunksToRemove.get(i)).chunkCoordinate.equals(par1ChunkCoordIntPair)) 148 { 149 this.chunksToRemove.set(i, 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 anvilchunkloaderpending = null; 167 Object object = this.syncLockObject; 168 169 synchronized (this.syncLockObject) 170 { 171 if (this.chunksToRemove.isEmpty()) 172 { 173 return false; 174 } 175 176 anvilchunkloaderpending = (AnvilChunkLoaderPending)this.chunksToRemove.remove(0); 177 this.pendingAnvilChunksCoordinates.remove(anvilchunkloaderpending.chunkCoordinate); 178 } 179 180 if (anvilchunkloaderpending != null) 181 { 182 try 183 { 184 this.writeChunkNBTTags(anvilchunkloaderpending); 185 } 186 catch (Exception exception) 187 { 188 exception.printStackTrace(); 189 } 190 } 191 192 return true; 193 } 194 195 private void writeChunkNBTTags(AnvilChunkLoaderPending par1AnvilChunkLoaderPending) throws IOException 196 { 197 DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, par1AnvilChunkLoaderPending.chunkCoordinate.chunkXPos, par1AnvilChunkLoaderPending.chunkCoordinate.chunkZPos); 198 CompressedStreamTools.write(par1AnvilChunkLoaderPending.nbtTags, dataoutputstream); 199 dataoutputstream.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[] aextendedblockstorage = par1Chunk.getBlockStorageArray(); 231 NBTTagList nbttaglist = new NBTTagList("Sections"); 232 boolean flag = !par2World.provider.hasNoSky; 233 ExtendedBlockStorage[] aextendedblockstorage1 = aextendedblockstorage; 234 int i = aextendedblockstorage.length; 235 NBTTagCompound nbttagcompound1; 236 237 for (int j = 0; j < i; ++j) 238 { 239 ExtendedBlockStorage extendedblockstorage = aextendedblockstorage1[j]; 240 241 if (extendedblockstorage != null) 242 { 243 nbttagcompound1 = new NBTTagCompound(); 244 nbttagcompound1.setByte("Y", (byte)(extendedblockstorage.getYLocation() >> 4 & 255)); 245 nbttagcompound1.setByteArray("Blocks", extendedblockstorage.getBlockLSBArray()); 246 247 if (extendedblockstorage.getBlockMSBArray() != null) 248 { 249 nbttagcompound1.setByteArray("Add", extendedblockstorage.getBlockMSBArray().data); 250 } 251 252 nbttagcompound1.setByteArray("Data", extendedblockstorage.getMetadataArray().data); 253 nbttagcompound1.setByteArray("BlockLight", extendedblockstorage.getBlocklightArray().data); 254 255 if (flag) 256 { 257 nbttagcompound1.setByteArray("SkyLight", extendedblockstorage.getSkylightArray().data); 258 } 259 else 260 { 261 nbttagcompound1.setByteArray("SkyLight", new byte[extendedblockstorage.getBlocklightArray().data.length]); 262 } 263 264 nbttaglist.appendTag(nbttagcompound1); 265 } 266 } 267 268 par3NBTTagCompound.setTag("Sections", nbttaglist); 269 par3NBTTagCompound.setByteArray("Biomes", par1Chunk.getBiomeArray()); 270 par1Chunk.hasEntities = false; 271 NBTTagList nbttaglist1 = new NBTTagList(); 272 Iterator iterator; 273 274 for (i = 0; i < par1Chunk.entityLists.length; ++i) 275 { 276 iterator = par1Chunk.entityLists[i].iterator(); 277 278 while (iterator.hasNext()) 279 { 280 Entity entity = (Entity)iterator.next(); 281 nbttagcompound1 = new NBTTagCompound(); 282 283 try 284 { 285 if (entity.addEntityID(nbttagcompound1)) 286 { 287 par1Chunk.hasEntities = true; 288 nbttaglist1.appendTag(nbttagcompound1); 289 } 290 } 291 catch (Exception e) 292 { 293 FMLLog.log(Level.SEVERE, e, 294 "An Entity type %s has thrown an exception trying to write state. It will not persist. Report this to the mod author", 295 entity.getClass().getName()); 296 } 297 } 298 } 299 300 par3NBTTagCompound.setTag("Entities", nbttaglist1); 301 NBTTagList nbttaglist2 = new NBTTagList(); 302 iterator = par1Chunk.chunkTileEntityMap.values().iterator(); 303 304 while (iterator.hasNext()) 305 { 306 TileEntity tileentity = (TileEntity)iterator.next(); 307 nbttagcompound1 = new NBTTagCompound(); 308 try 309 { 310 tileentity.writeToNBT(nbttagcompound1); 311 nbttaglist2.appendTag(nbttagcompound1); 312 } 313 catch (Exception e) 314 { 315 FMLLog.log(Level.SEVERE, e, 316 "A TileEntity type %s has throw an exception trying to write state. It will not persist. Report this to the mod author", 317 tileentity.getClass().getName()); 318 } 319 } 320 321 par3NBTTagCompound.setTag("TileEntities", nbttaglist2); 322 List list = par2World.getPendingBlockUpdates(par1Chunk, false); 323 324 if (list != null) 325 { 326 long k = par2World.getTotalWorldTime(); 327 NBTTagList nbttaglist3 = new NBTTagList(); 328 Iterator iterator1 = list.iterator(); 329 330 while (iterator1.hasNext()) 331 { 332 NextTickListEntry nextticklistentry = (NextTickListEntry)iterator1.next(); 333 NBTTagCompound nbttagcompound2 = new NBTTagCompound(); 334 nbttagcompound2.setInteger("i", nextticklistentry.blockID); 335 nbttagcompound2.setInteger("x", nextticklistentry.xCoord); 336 nbttagcompound2.setInteger("y", nextticklistentry.yCoord); 337 nbttagcompound2.setInteger("z", nextticklistentry.zCoord); 338 nbttagcompound2.setInteger("t", (int)(nextticklistentry.scheduledTime - k)); 339 nbttagcompound2.setInteger("p", nextticklistentry.field_82754_f); 340 nbttaglist3.appendTag(nbttagcompound2); 341 } 342 343 par3NBTTagCompound.setTag("TileTicks", nbttaglist3); 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 i = par2NBTTagCompound.getInteger("xPos"); 354 int j = par2NBTTagCompound.getInteger("zPos"); 355 Chunk chunk = new Chunk(par1World, i, j); 356 chunk.heightMap = par2NBTTagCompound.getIntArray("HeightMap"); 357 chunk.isTerrainPopulated = par2NBTTagCompound.getBoolean("TerrainPopulated"); 358 NBTTagList nbttaglist = par2NBTTagCompound.getTagList("Sections"); 359 byte b0 = 16; 360 ExtendedBlockStorage[] aextendedblockstorage = new ExtendedBlockStorage[b0]; 361 boolean flag = !par1World.provider.hasNoSky; 362 363 for (int k = 0; k < nbttaglist.tagCount(); ++k) 364 { 365 NBTTagCompound nbttagcompound1 = (NBTTagCompound)nbttaglist.tagAt(k); 366 byte b1 = nbttagcompound1.getByte("Y"); 367 ExtendedBlockStorage extendedblockstorage = new ExtendedBlockStorage(b1 << 4, flag); 368 extendedblockstorage.setBlockLSBArray(nbttagcompound1.getByteArray("Blocks")); 369 370 if (nbttagcompound1.hasKey("Add")) 371 { 372 extendedblockstorage.setBlockMSBArray(new NibbleArray(nbttagcompound1.getByteArray("Add"), 4)); 373 } 374 375 extendedblockstorage.setBlockMetadataArray(new NibbleArray(nbttagcompound1.getByteArray("Data"), 4)); 376 extendedblockstorage.setBlocklightArray(new NibbleArray(nbttagcompound1.getByteArray("BlockLight"), 4)); 377 378 if (flag) 379 { 380 extendedblockstorage.setSkylightArray(new NibbleArray(nbttagcompound1.getByteArray("SkyLight"), 4)); 381 } 382 383 extendedblockstorage.removeInvalidBlocks(); 384 aextendedblockstorage[b1] = extendedblockstorage; 385 } 386 387 chunk.setStorageArrays(aextendedblockstorage); 388 389 if (par2NBTTagCompound.hasKey("Biomes")) 390 { 391 chunk.setBiomeArray(par2NBTTagCompound.getByteArray("Biomes")); 392 } 393 394 NBTTagList nbttaglist1 = par2NBTTagCompound.getTagList("Entities"); 395 396 if (nbttaglist1 != null) 397 { 398 for (int l = 0; l < nbttaglist1.tagCount(); ++l) 399 { 400 NBTTagCompound nbttagcompound2 = (NBTTagCompound)nbttaglist1.tagAt(l); 401 Entity entity = EntityList.createEntityFromNBT(nbttagcompound2, par1World); 402 chunk.hasEntities = true; 403 404 if (entity != null) 405 { 406 chunk.addEntity(entity); 407 Entity entity1 = entity; 408 409 for (NBTTagCompound nbttagcompound3 = nbttagcompound2; nbttagcompound3.hasKey("Riding"); nbttagcompound3 = nbttagcompound3.getCompoundTag("Riding")) 410 { 411 Entity entity2 = EntityList.createEntityFromNBT(nbttagcompound3.getCompoundTag("Riding"), par1World); 412 413 if (entity2 != null) 414 { 415 chunk.addEntity(entity2); 416 entity1.mountEntity(entity2); 417 } 418 419 entity1 = entity2; 420 } 421 } 422 } 423 } 424 425 NBTTagList nbttaglist2 = par2NBTTagCompound.getTagList("TileEntities"); 426 427 if (nbttaglist2 != null) 428 { 429 for (int i1 = 0; i1 < nbttaglist2.tagCount(); ++i1) 430 { 431 NBTTagCompound nbttagcompound4 = (NBTTagCompound)nbttaglist2.tagAt(i1); 432 TileEntity tileentity = TileEntity.createAndLoadEntity(nbttagcompound4); 433 434 if (tileentity != null) 435 { 436 chunk.addTileEntity(tileentity); 437 } 438 } 439 } 440 441 if (par2NBTTagCompound.hasKey("TileTicks")) 442 { 443 NBTTagList nbttaglist3 = par2NBTTagCompound.getTagList("TileTicks"); 444 445 if (nbttaglist3 != null) 446 { 447 for (int j1 = 0; j1 < nbttaglist3.tagCount(); ++j1) 448 { 449 NBTTagCompound nbttagcompound5 = (NBTTagCompound)nbttaglist3.tagAt(j1); 450 par1World.scheduleBlockUpdateFromLoad(nbttagcompound5.getInteger("x"), nbttagcompound5.getInteger("y"), nbttagcompound5.getInteger("z"), nbttagcompound5.getInteger("i"), nbttagcompound5.getInteger("t"), nbttagcompound5.getInteger("p")); 451 } 452 } 453 } 454 455 return chunk; 456 } 457}