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 }