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    }