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 }