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