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