    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    }