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