    001package net.minecraft.world.chunk.storage;
    002
    003import java.io.DataInputStream;
    004import java.io.DataOutputStream;
    005import java.io.File;
    006import java.io.IOException;
    007import java.util.ArrayList;
    008import java.util.HashSet;
    009import java.util.Iterator;
    010import java.util.List;
    011import java.util.Set;
    012import java.util.logging.Level;
    013
    014import cpw.mods.fml.common.FMLLog;
    015
    016import net.minecraft.entity.Entity;
    017import net.minecraft.entity.EntityList;
    018import net.minecraft.nbt.CompressedStreamTools;
    019import net.minecraft.nbt.NBTTagCompound;
    020import net.minecraft.nbt.NBTTagList;
    021import net.minecraft.tileentity.TileEntity;
    022import net.minecraft.world.ChunkCoordIntPair;
    023import net.minecraft.world.MinecraftException;
    024import net.minecraft.world.NextTickListEntry;
    025import net.minecraft.world.World;
    026import net.minecraft.world.chunk.Chunk;
    027import net.minecraft.world.chunk.NibbleArray;
    028import net.minecraft.world.storage.IThreadedFileIO;
    029import net.minecraft.world.storage.ThreadedFileIOBase;
    030import net.minecraftforge.common.MinecraftForge;
    031import net.minecraftforge.event.world.ChunkDataEvent;
    032
    033public 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}