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        private 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.getWorldTime());
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.getWorldTime();
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    }