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