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