    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 nbttagcompound = null;
    053        ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(par2, par3);
    054        Object object = this.syncLockObject;
    055
    056        synchronized (this.syncLockObject)
    057        {
    058            if (this.pendingAnvilChunksCoordinates.contains(chunkcoordintpair))
    059            {
    060                for (int k = 0; k < this.chunksToRemove.size(); ++k)
    061                {
    062                    if (((AnvilChunkLoaderPending)this.chunksToRemove.get(k)).chunkCoordinate.equals(chunkcoordintpair))
    063                    {
    064                        nbttagcompound = ((AnvilChunkLoaderPending)this.chunksToRemove.get(k)).nbtTags;
    065                        break;
    066                    }
    067                }
    068            }
    069        }
    070
    071        if (nbttagcompound == null)
    072        {
    073            DataInputStream datainputstream = RegionFileCache.getChunkInputStream(this.chunkSaveLocation, par2, par3);
    074
    075            if (datainputstream == null)
    076            {
    077                return null;
    078            }
    079
    080            nbttagcompound = CompressedStreamTools.read(datainputstream);
    081        }
    082
    083        return this.checkedReadChunkFromNBT(par1World, par2, par3, nbttagcompound);
    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            par1World.getWorldLogAgent().logSevere("Chunk file at " + par2 + "," + par3 + " is missing level data, skipping");
    094            return null;
    095        }
    096        else if (!par4NBTTagCompound.getCompoundTag("Level").hasKey("Sections"))
    097        {
    098            par1World.getWorldLogAgent().logSevere("Chunk file at " + par2 + "," + par3 + " is missing block data, skipping");
    099            return null;
    100        }
    101        else
    102        {
    103            Chunk chunk = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level"));
    104
    105            if (!chunk.isAtLocation(par2, par3))
    106            {
    107                par1World.getWorldLogAgent().logSevere("Chunk file at " + par2 + "," + par3 + " is in the wrong location; relocating. (Expected " + par2 + ", " + par3 + ", got " + chunk.xPosition + ", " + chunk.zPosition + ")");
    108                par4NBTTagCompound.setInteger("xPos", par2);
    109                par4NBTTagCompound.setInteger("zPos", par3);
    110                chunk = this.readChunkFromNBT(par1World, par4NBTTagCompound.getCompoundTag("Level"));
    111            }
    112
    113            MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Load(chunk, par4NBTTagCompound));
    114            return chunk;
    115        }
    116    }
    117
    118    public void saveChunk(World par1World, Chunk par2Chunk) throws MinecraftException, IOException
    119    {
    120        par1World.checkSessionLock();
    121
    122        try
    123        {
    124            NBTTagCompound nbttagcompound = new NBTTagCompound();
    125            NBTTagCompound nbttagcompound1 = new NBTTagCompound();
    126            nbttagcompound.setTag("Level", nbttagcompound1);
    127            this.writeChunkToNBT(par2Chunk, par1World, nbttagcompound1);
    128            MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Save(par2Chunk, nbttagcompound));
    129            this.func_75824_a(par2Chunk.getChunkCoordIntPair(), nbttagcompound);
    130        }
    131        catch (Exception exception)
    132        {
    133            exception.printStackTrace();
    134        }
    135    }
    136
    137    protected void func_75824_a(ChunkCoordIntPair par1ChunkCoordIntPair, NBTTagCompound par2NBTTagCompound)
    138    {
    139        Object object = this.syncLockObject;
    140
    141        synchronized (this.syncLockObject)
    142        {
    143            if (this.pendingAnvilChunksCoordinates.contains(par1ChunkCoordIntPair))
    144            {
    145                for (int i = 0; i < this.chunksToRemove.size(); ++i)
    146                {
    147                    if (((AnvilChunkLoaderPending)this.chunksToRemove.get(i)).chunkCoordinate.equals(par1ChunkCoordIntPair))
    148                    {
    149                        this.chunksToRemove.set(i, 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 anvilchunkloaderpending = null;
    167        Object object = this.syncLockObject;
    168
    169        synchronized (this.syncLockObject)
    170        {
    171            if (this.chunksToRemove.isEmpty())
    172            {
    173                return false;
    174            }
    175
    176            anvilchunkloaderpending = (AnvilChunkLoaderPending)this.chunksToRemove.remove(0);
    177            this.pendingAnvilChunksCoordinates.remove(anvilchunkloaderpending.chunkCoordinate);
    178        }
    179
    180        if (anvilchunkloaderpending != null)
    181        {
    182            try
    183            {
    184                this.writeChunkNBTTags(anvilchunkloaderpending);
    185            }
    186            catch (Exception exception)
    187            {
    188                exception.printStackTrace();
    189            }
    190        }
    191
    192        return true;
    193    }
    194
    195    private void writeChunkNBTTags(AnvilChunkLoaderPending par1AnvilChunkLoaderPending) throws IOException
    196    {
    197        DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, par1AnvilChunkLoaderPending.chunkCoordinate.chunkXPos, par1AnvilChunkLoaderPending.chunkCoordinate.chunkZPos);
    198        CompressedStreamTools.write(par1AnvilChunkLoaderPending.nbtTags, dataoutputstream);
    199        dataoutputstream.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[] aextendedblockstorage = par1Chunk.getBlockStorageArray();
    231        NBTTagList nbttaglist = new NBTTagList("Sections");
    232        boolean flag = !par2World.provider.hasNoSky;
    233        ExtendedBlockStorage[] aextendedblockstorage1 = aextendedblockstorage;
    234        int i = aextendedblockstorage.length;
    235        NBTTagCompound nbttagcompound1;
    236
    237        for (int j = 0; j < i; ++j)
    238        {
    239            ExtendedBlockStorage extendedblockstorage = aextendedblockstorage1[j];
    240
    241            if (extendedblockstorage != null)
    242            {
    243                nbttagcompound1 = new NBTTagCompound();
    244                nbttagcompound1.setByte("Y", (byte)(extendedblockstorage.getYLocation() >> 4 & 255));
    245                nbttagcompound1.setByteArray("Blocks", extendedblockstorage.getBlockLSBArray());
    246
    247                if (extendedblockstorage.getBlockMSBArray() != null)
    248                {
    249                    nbttagcompound1.setByteArray("Add", extendedblockstorage.getBlockMSBArray().data);
    250                }
    251
    252                nbttagcompound1.setByteArray("Data", extendedblockstorage.getMetadataArray().data);
    253                nbttagcompound1.setByteArray("BlockLight", extendedblockstorage.getBlocklightArray().data);
    254
    255                if (flag)
    256                {
    257                    nbttagcompound1.setByteArray("SkyLight", extendedblockstorage.getSkylightArray().data);
    258                }
    259                else
    260                {
    261                    nbttagcompound1.setByteArray("SkyLight", new byte[extendedblockstorage.getBlocklightArray().data.length]);
    262                }
    263
    264                nbttaglist.appendTag(nbttagcompound1);
    265            }
    266        }
    267
    268        par3NBTTagCompound.setTag("Sections", nbttaglist);
    269        par3NBTTagCompound.setByteArray("Biomes", par1Chunk.getBiomeArray());
    270        par1Chunk.hasEntities = false;
    271        NBTTagList nbttaglist1 = new NBTTagList();
    272        Iterator iterator;
    273
    274        for (i = 0; i < par1Chunk.entityLists.length; ++i)
    275        {
    276            iterator = par1Chunk.entityLists[i].iterator();
    277
    278            while (iterator.hasNext())
    279            {
    280                Entity entity = (Entity)iterator.next();
    281                nbttagcompound1 = new NBTTagCompound();
    282
    283                try
    284                {
    285                    if (entity.addEntityID(nbttagcompound1))
    286                    {
    287                        par1Chunk.hasEntities = true;
    288                        nbttaglist1.appendTag(nbttagcompound1);
    289                    }
    290                }
    291                catch (Exception e)
    292                {
    293                    FMLLog.log(Level.SEVERE, e,
    294                            "An Entity type %s has thrown an exception trying to write state. It will not persist. Report this to the mod author",
    295                            entity.getClass().getName());
    296                }
    297            }
    298        }
    299
    300        par3NBTTagCompound.setTag("Entities", nbttaglist1);
    301        NBTTagList nbttaglist2 = new NBTTagList();
    302        iterator = par1Chunk.chunkTileEntityMap.values().iterator();
    303
    304        while (iterator.hasNext())
    305        {
    306            TileEntity tileentity = (TileEntity)iterator.next();
    307            nbttagcompound1 = new NBTTagCompound();
    308            try
    309            {
    310                tileentity.writeToNBT(nbttagcompound1);
    311                nbttaglist2.appendTag(nbttagcompound1);
    312            }
    313            catch (Exception e)
    314            {
    315                FMLLog.log(Level.SEVERE, e,
    316                        "A TileEntity type %s has throw an exception trying to write state. It will not persist. Report this to the mod author",
    317                        tileentity.getClass().getName());
    318            }
    319        }
    320
    321        par3NBTTagCompound.setTag("TileEntities", nbttaglist2);
    322        List list = par2World.getPendingBlockUpdates(par1Chunk, false);
    323
    324        if (list != null)
    325        {
    326            long k = par2World.getTotalWorldTime();
    327            NBTTagList nbttaglist3 = new NBTTagList();
    328            Iterator iterator1 = list.iterator();
    329
    330            while (iterator1.hasNext())
    331            {
    332                NextTickListEntry nextticklistentry = (NextTickListEntry)iterator1.next();
    333                NBTTagCompound nbttagcompound2 = new NBTTagCompound();
    334                nbttagcompound2.setInteger("i", nextticklistentry.blockID);
    335                nbttagcompound2.setInteger("x", nextticklistentry.xCoord);
    336                nbttagcompound2.setInteger("y", nextticklistentry.yCoord);
    337                nbttagcompound2.setInteger("z", nextticklistentry.zCoord);
    338                nbttagcompound2.setInteger("t", (int)(nextticklistentry.scheduledTime - k));
    339                nbttagcompound2.setInteger("p", nextticklistentry.field_82754_f);
    340                nbttaglist3.appendTag(nbttagcompound2);
    341            }
    342
    343            par3NBTTagCompound.setTag("TileTicks", nbttaglist3);
    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 i = par2NBTTagCompound.getInteger("xPos");
    354        int j = par2NBTTagCompound.getInteger("zPos");
    355        Chunk chunk = new Chunk(par1World, i, j);
    356        chunk.heightMap = par2NBTTagCompound.getIntArray("HeightMap");
    357        chunk.isTerrainPopulated = par2NBTTagCompound.getBoolean("TerrainPopulated");
    358        NBTTagList nbttaglist = par2NBTTagCompound.getTagList("Sections");
    359        byte b0 = 16;
    360        ExtendedBlockStorage[] aextendedblockstorage = new ExtendedBlockStorage[b0];
    361        boolean flag = !par1World.provider.hasNoSky;
    362
    363        for (int k = 0; k < nbttaglist.tagCount(); ++k)
    364        {
    365            NBTTagCompound nbttagcompound1 = (NBTTagCompound)nbttaglist.tagAt(k);
    366            byte b1 = nbttagcompound1.getByte("Y");
    367            ExtendedBlockStorage extendedblockstorage = new ExtendedBlockStorage(b1 << 4, flag);
    368            extendedblockstorage.setBlockLSBArray(nbttagcompound1.getByteArray("Blocks"));
    369
    370            if (nbttagcompound1.hasKey("Add"))
    371            {
    372                extendedblockstorage.setBlockMSBArray(new NibbleArray(nbttagcompound1.getByteArray("Add"), 4));
    373            }
    374
    375            extendedblockstorage.setBlockMetadataArray(new NibbleArray(nbttagcompound1.getByteArray("Data"), 4));
    376            extendedblockstorage.setBlocklightArray(new NibbleArray(nbttagcompound1.getByteArray("BlockLight"), 4));
    377
    378            if (flag)
    379            {
    380                extendedblockstorage.setSkylightArray(new NibbleArray(nbttagcompound1.getByteArray("SkyLight"), 4));
    381            }
    382
    383            extendedblockstorage.removeInvalidBlocks();
    384            aextendedblockstorage[b1] = extendedblockstorage;
    385        }
    386
    387        chunk.setStorageArrays(aextendedblockstorage);
    388
    389        if (par2NBTTagCompound.hasKey("Biomes"))
    390        {
    391            chunk.setBiomeArray(par2NBTTagCompound.getByteArray("Biomes"));
    392        }
    393
    394        NBTTagList nbttaglist1 = par2NBTTagCompound.getTagList("Entities");
    395
    396        if (nbttaglist1 != null)
    397        {
    398            for (int l = 0; l < nbttaglist1.tagCount(); ++l)
    399            {
    400                NBTTagCompound nbttagcompound2 = (NBTTagCompound)nbttaglist1.tagAt(l);
    401                Entity entity = EntityList.createEntityFromNBT(nbttagcompound2, par1World);
    402                chunk.hasEntities = true;
    403
    404                if (entity != null)
    405                {
    406                    chunk.addEntity(entity);
    407                    Entity entity1 = entity;
    408
    409                    for (NBTTagCompound nbttagcompound3 = nbttagcompound2; nbttagcompound3.hasKey("Riding"); nbttagcompound3 = nbttagcompound3.getCompoundTag("Riding"))
    410                    {
    411                        Entity entity2 = EntityList.createEntityFromNBT(nbttagcompound3.getCompoundTag("Riding"), par1World);
    412
    413                        if (entity2 != null)
    414                        {
    415                            chunk.addEntity(entity2);
    416                            entity1.mountEntity(entity2);
    417                        }
    418
    419                        entity1 = entity2;
    420                    }
    421                }
    422            }
    423        }
    424
    425        NBTTagList nbttaglist2 = par2NBTTagCompound.getTagList("TileEntities");
    426
    427        if (nbttaglist2 != null)
    428        {
    429            for (int i1 = 0; i1 < nbttaglist2.tagCount(); ++i1)
    430            {
    431                NBTTagCompound nbttagcompound4 = (NBTTagCompound)nbttaglist2.tagAt(i1);
    432                TileEntity tileentity = TileEntity.createAndLoadEntity(nbttagcompound4);
    433
    434                if (tileentity != null)
    435                {
    436                    chunk.addTileEntity(tileentity);
    437                }
    438            }
    439        }
    440
    441        if (par2NBTTagCompound.hasKey("TileTicks"))
    442        {
    443            NBTTagList nbttaglist3 = par2NBTTagCompound.getTagList("TileTicks");
    444
    445            if (nbttaglist3 != null)
    446            {
    447                for (int j1 = 0; j1 < nbttaglist3.tagCount(); ++j1)
    448                {
    449                    NBTTagCompound nbttagcompound5 = (NBTTagCompound)nbttaglist3.tagAt(j1);
    450                    par1World.scheduleBlockUpdateFromLoad(nbttagcompound5.getInteger("x"), nbttagcompound5.getInteger("y"), nbttagcompound5.getInteger("z"), nbttagcompound5.getInteger("i"), nbttagcompound5.getInteger("t"), nbttagcompound5.getInteger("p"));
    451                }
    452            }
    453        }
    454
    455        return chunk;
    456    }
    457}