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}