    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    }