summaryrefslogtreecommitdiffstats
path: root/src/WorldStorage
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/WorldStorage/FastNBT.cpp (renamed from source/WorldStorage/FastNBT.cpp)0
-rw-r--r--src/WorldStorage/FastNBT.h (renamed from source/WorldStorage/FastNBT.h)0
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp (renamed from source/WorldStorage/NBTChunkSerializer.cpp)0
-rw-r--r--src/WorldStorage/NBTChunkSerializer.h (renamed from source/WorldStorage/NBTChunkSerializer.h)0
-rw-r--r--src/WorldStorage/WSSAnvil.cpp1555
-rw-r--r--src/WorldStorage/WSSAnvil.h (renamed from source/WorldStorage/WSSAnvil.h)0
-rw-r--r--src/WorldStorage/WSSCompact.cpp1009
-rw-r--r--src/WorldStorage/WSSCompact.h (renamed from source/WorldStorage/WSSCompact.h)0
-rw-r--r--src/WorldStorage/WorldStorage.cpp (renamed from source/WorldStorage/WorldStorage.cpp)0
-rw-r--r--src/WorldStorage/WorldStorage.h135
10 files changed, 2699 insertions, 0 deletions
diff --git a/source/WorldStorage/FastNBT.cpp b/src/WorldStorage/FastNBT.cpp
index e55011069..e55011069 100644
--- a/source/WorldStorage/FastNBT.cpp
+++ b/src/WorldStorage/FastNBT.cpp
diff --git a/source/WorldStorage/FastNBT.h b/src/WorldStorage/FastNBT.h
index 7323c29cb..7323c29cb 100644
--- a/source/WorldStorage/FastNBT.h
+++ b/src/WorldStorage/FastNBT.h
diff --git a/source/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index c9013b1b3..c9013b1b3 100644
--- a/source/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
diff --git a/source/WorldStorage/NBTChunkSerializer.h b/src/WorldStorage/NBTChunkSerializer.h
index 9d4ac208c..9d4ac208c 100644
--- a/source/WorldStorage/NBTChunkSerializer.h
+++ b/src/WorldStorage/NBTChunkSerializer.h
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
new file mode 100644
index 000000000..79be4dfa2
--- /dev/null
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -0,0 +1,1555 @@
+
+// WSSAnvil.cpp
+
+// Implements the cWSSAnvil class representing the Anvil world storage scheme
+
+#include "Globals.h"
+#include "WSSAnvil.h"
+#include "NBTChunkSerializer.h"
+#include "../World.h"
+#include "zlib/zlib.h"
+#include "../BlockID.h"
+#include "../BlockEntities/ChestEntity.h"
+#include "../BlockEntities/DispenserEntity.h"
+#include "../BlockEntities/DropperEntity.h"
+#include "../BlockEntities/FurnaceEntity.h"
+#include "../BlockEntities/HopperEntity.h"
+#include "../BlockEntities/JukeboxEntity.h"
+#include "../BlockEntities/NoteEntity.h"
+#include "../BlockEntities/SignEntity.h"
+#include "../Item.h"
+#include "../ItemGrid.h"
+#include "../StringCompression.h"
+#include "FastNBT.h"
+#include "../Mobs/Monster.h"
+#include "../Entities/Boat.h"
+#include "../Entities/FallingBlock.h"
+#include "../Entities/Minecart.h"
+#include "../Entities/Pickup.h"
+#include "../Entities/ProjectileEntity.h"
+
+
+
+
+
+/** If defined, the BlockSkyLight values will be copied over to BlockLight upon chunk saving,
+thus making skylight visible in Minutor's Lighting mode
+*/
+// #define DEBUG_SKYLIGHT
+
+/** Maximum number of MCA files that are cached in memory.
+Since only the header is actually in the memory, this number can be high, but still, each file means an OS FS handle.
+*/
+#define MAX_MCA_FILES 32
+
+/// The maximum size of an inflated chunk; raw chunk data is 192 KiB, allow 64 KiB more of entities
+#define CHUNK_INFLATE_MAX 256 KiB
+
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cWSSAnvil:
+
+cWSSAnvil::cWSSAnvil(cWorld * a_World) :
+ super(a_World)
+{
+ // Create a level.dat file for mapping tools, if it doesn't already exist:
+ AString fnam;
+ Printf(fnam, "%s/level.dat", a_World->GetName().c_str());
+ if (!cFile::Exists(fnam))
+ {
+ cFastNBTWriter Writer;
+ Writer.BeginCompound("");
+ Writer.AddInt("SpawnX", (int)(a_World->GetSpawnX()));
+ Writer.AddInt("SpawnY", (int)(a_World->GetSpawnY()));
+ Writer.AddInt("SpawnZ", (int)(a_World->GetSpawnZ()));
+ Writer.EndCompound();
+ Writer.Finish();
+
+ #ifdef _DEBUG
+ cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size());
+ ASSERT(TestParse.IsValid());
+ #endif // _DEBUG
+
+ gzFile gz = gzopen((FILE_IO_PREFIX + fnam).c_str(), "wb");
+ if (gz != NULL)
+ {
+ gzwrite(gz, Writer.GetResult().data(), Writer.GetResult().size());
+ }
+ gzclose(gz);
+ }
+}
+
+
+
+
+
+cWSSAnvil::~cWSSAnvil()
+{
+ cCSLock Lock(m_CS);
+ for (cMCAFiles::iterator itr = m_Files.begin(); itr != m_Files.end(); ++itr)
+ {
+ delete *itr;
+ } // for itr - m_Files[]
+}
+
+
+
+
+
+bool cWSSAnvil::LoadChunk(const cChunkCoords & a_Chunk)
+{
+ AString ChunkData;
+ if (!GetChunkData(a_Chunk, ChunkData))
+ {
+ // The reason for failure is already printed in GetChunkData()
+ return false;
+ }
+
+ return LoadChunkFromData(a_Chunk, ChunkData);
+}
+
+
+
+
+
+bool cWSSAnvil::SaveChunk(const cChunkCoords & a_Chunk)
+{
+ AString ChunkData;
+ if (!SaveChunkToData(a_Chunk, ChunkData))
+ {
+ LOGWARNING("Cannot serialize chunk [%d, %d] into data", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ);
+ return false;
+ }
+ if (!SetChunkData(a_Chunk, ChunkData))
+ {
+ LOGWARNING("Cannot store chunk [%d, %d] data", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ);
+ return false;
+ }
+
+ // Everything successful
+ return true;
+}
+
+
+
+
+
+bool cWSSAnvil::GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data)
+{
+ cCSLock Lock(m_CS);
+ cMCAFile * File = LoadMCAFile(a_Chunk);
+ if (File == NULL)
+ {
+ return false;
+ }
+ return File->GetChunkData(a_Chunk, a_Data);
+}
+
+
+
+
+
+bool cWSSAnvil::SetChunkData(const cChunkCoords & a_Chunk, const AString & a_Data)
+{
+ cCSLock Lock(m_CS);
+ cMCAFile * File = LoadMCAFile(a_Chunk);
+ if (File == NULL)
+ {
+ return false;
+ }
+ return File->SetChunkData(a_Chunk, a_Data);
+}
+
+
+
+
+
+cWSSAnvil::cMCAFile * cWSSAnvil::LoadMCAFile(const cChunkCoords & a_Chunk)
+{
+ // ASSUME m_CS is locked
+ ASSERT(m_CS.IsLocked());
+
+ const int RegionX = FAST_FLOOR_DIV(a_Chunk.m_ChunkX, 32);
+ const int RegionZ = FAST_FLOOR_DIV(a_Chunk.m_ChunkZ, 32);
+ ASSERT(a_Chunk.m_ChunkX - RegionX * 32 >= 0);
+ ASSERT(a_Chunk.m_ChunkZ - RegionZ * 32 >= 0);
+ ASSERT(a_Chunk.m_ChunkX - RegionX * 32 < 32);
+ ASSERT(a_Chunk.m_ChunkZ - RegionZ * 32 < 32);
+
+ // Is it already cached?
+ for (cMCAFiles::iterator itr = m_Files.begin(); itr != m_Files.end(); ++itr)
+ {
+ if (((*itr) != NULL) && ((*itr)->GetRegionX() == RegionX) && ((*itr)->GetRegionZ() == RegionZ))
+ {
+ // Move the file to front and return it:
+ cMCAFile * f = *itr;
+ if (itr != m_Files.begin())
+ {
+ m_Files.erase(itr);
+ m_Files.push_front(f);
+ }
+ return f;
+ }
+ }
+
+ // Load it anew:
+ AString FileName;
+ Printf(FileName, "%s/region", m_World->GetName().c_str());
+ cFile::CreateFolder(FILE_IO_PREFIX + FileName);
+ AppendPrintf(FileName, "/r.%d.%d.mca", RegionX, RegionZ);
+ cMCAFile * f = new cMCAFile(FileName, RegionX, RegionZ);
+ if (f == NULL)
+ {
+ return NULL;
+ }
+ m_Files.push_front(f);
+
+ // If there are too many MCA files cached, delete the last one used:
+ if (m_Files.size() > MAX_MCA_FILES)
+ {
+ delete m_Files.back();
+ m_Files.pop_back();
+ }
+ return f;
+}
+
+
+
+
+
+bool cWSSAnvil::LoadChunkFromData(const cChunkCoords & a_Chunk, const AString & a_Data)
+{
+ // Decompress the data:
+ char Uncompressed[CHUNK_INFLATE_MAX];
+ z_stream strm;
+ strm.zalloc = (alloc_func)NULL;
+ strm.zfree = (free_func)NULL;
+ strm.opaque = NULL;
+ inflateInit(&strm);
+ strm.next_out = (Bytef *)Uncompressed;
+ strm.avail_out = sizeof(Uncompressed);
+ strm.next_in = (Bytef *)a_Data.data();
+ strm.avail_in = a_Data.size();
+ int res = inflate(&strm, Z_FINISH);
+ inflateEnd(&strm);
+ if (res != Z_STREAM_END)
+ {
+ return false;
+ }
+
+ // Parse the NBT data:
+ cParsedNBT NBT(Uncompressed, strm.total_out);
+ if (!NBT.IsValid())
+ {
+ // NBT Parsing failed
+ return false;
+ }
+
+ // Load the data from NBT:
+ return LoadChunkFromNBT(a_Chunk, NBT);
+}
+
+
+
+
+
+bool cWSSAnvil::SaveChunkToData(const cChunkCoords & a_Chunk, AString & a_Data)
+{
+ cFastNBTWriter Writer;
+ if (!SaveChunkToNBT(a_Chunk, Writer))
+ {
+ LOGWARNING("Cannot save chunk [%d, %d] to NBT", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ);
+ return false;
+ }
+ Writer.Finish();
+
+ CompressString(Writer.GetResult().data(), Writer.GetResult().size(), a_Data);
+ return true;
+}
+
+
+
+
+
+bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT & a_NBT)
+{
+ // The data arrays, in MCA-native y/z/x ordering (will be reordered for the final chunk data)
+ cChunkDef::BlockTypes BlockTypes;
+ cChunkDef::BlockNibbles MetaData;
+ cChunkDef::BlockNibbles BlockLight;
+ cChunkDef::BlockNibbles SkyLight;
+
+ memset(BlockTypes, E_BLOCK_AIR, sizeof(BlockTypes));
+ memset(MetaData, 0, sizeof(MetaData));
+ memset(SkyLight, 0xff, sizeof(SkyLight)); // By default, data not present in the NBT means air, which means full skylight
+ memset(BlockLight, 0x00, sizeof(BlockLight));
+
+ // Load the blockdata, blocklight and skylight:
+ int Level = a_NBT.FindChildByName(0, "Level");
+ if (Level < 0)
+ {
+ return false;
+ }
+ int Sections = a_NBT.FindChildByName(Level, "Sections");
+ if ((Sections < 0) || (a_NBT.GetType(Sections) != TAG_List) || (a_NBT.GetChildrenType(Sections) != TAG_Compound))
+ {
+ return false;
+ }
+ for (int Child = a_NBT.GetFirstChild(Sections); Child >= 0; Child = a_NBT.GetNextSibling(Child))
+ {
+ int y = 0;
+ int SectionY = a_NBT.FindChildByName(Child, "Y");
+ if ((SectionY < 0) || (a_NBT.GetType(SectionY) != TAG_Byte))
+ {
+ continue;
+ }
+ y = a_NBT.GetByte(SectionY);
+ if ((y < 0) || (y > 15))
+ {
+ continue;
+ }
+ CopyNBTData(a_NBT, Child, "Blocks", (char *)&(BlockTypes[y * 4096]), 4096);
+ CopyNBTData(a_NBT, Child, "Data", (char *)&(MetaData[y * 2048]), 2048);
+ CopyNBTData(a_NBT, Child, "SkyLight", (char *)&(SkyLight[y * 2048]), 2048);
+ CopyNBTData(a_NBT, Child, "BlockLight", (char *)&(BlockLight[y * 2048]), 2048);
+ } // for itr - LevelSections[]
+
+ // Load the biomes from NBT, if present and valid. First try MCS-style, then Vanilla-style:
+ cChunkDef::BiomeMap BiomeMap;
+ cChunkDef::BiomeMap * Biomes = LoadBiomeMapFromNBT(&BiomeMap, a_NBT, a_NBT.FindChildByName(Level, "MCSBiomes"));
+ if (Biomes == NULL)
+ {
+ // MCS-style biomes not available, load vanilla-style:
+ Biomes = LoadVanillaBiomeMapFromNBT(&BiomeMap, a_NBT, a_NBT.FindChildByName(Level, "Biomes"));
+ }
+
+ // Load the entities from NBT:
+ cEntityList Entities;
+ cBlockEntityList BlockEntities;
+ LoadEntitiesFromNBT (Entities, a_NBT, a_NBT.FindChildByName(Level, "Entities"));
+ LoadBlockEntitiesFromNBT(BlockEntities, a_NBT, a_NBT.FindChildByName(Level, "TileEntities"), BlockTypes, MetaData);
+
+ bool IsLightValid = (a_NBT.FindChildByName(Level, "MCSIsLightValid") > 0);
+
+ /*
+ // Uncomment this block for really cool stuff :)
+ // DEBUG magic: Invert the underground, so that we can see the MC generator in action :)
+ bool ShouldInvert[cChunkDef::Width * cChunkDef::Width];
+ memset(ShouldInvert, 0, sizeof(ShouldInvert));
+ for (int y = cChunkDef::Height - 1; y >= 0; y--)
+ {
+ for (int x = 0; x < cChunkDef::Width; x++) for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ int Index = cChunkDef::MakeIndexNoCheck(x, y, z);
+ if (ShouldInvert[x + cChunkDef::Width * z])
+ {
+ BlockTypes[Index] = (BlockTypes[Index] == E_BLOCK_AIR) ? E_BLOCK_STONE : E_BLOCK_AIR;
+ }
+ else
+ {
+ switch (BlockTypes[Index])
+ {
+ case E_BLOCK_AIR:
+ case E_BLOCK_LEAVES:
+ {
+ // nothing needed
+ break;
+ }
+ default:
+ {
+ ShouldInvert[x + cChunkDef::Width * z] = true;
+ }
+ }
+ BlockTypes[Index] = E_BLOCK_AIR;
+ }
+ }
+ } // for y
+ //*/
+
+ m_World->SetChunkData(
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ,
+ BlockTypes, MetaData,
+ IsLightValid ? BlockLight : NULL,
+ IsLightValid ? SkyLight : NULL,
+ NULL, Biomes,
+ Entities, BlockEntities,
+ false
+ );
+ return true;
+}
+
+
+
+
+void cWSSAnvil::CopyNBTData(const cParsedNBT & a_NBT, int a_Tag, const AString & a_ChildName, char * a_Destination, int a_Length)
+{
+ int Child = a_NBT.FindChildByName(a_Tag, a_ChildName);
+ if ((Child >= 0) && (a_NBT.GetType(Child) == TAG_ByteArray) && (a_NBT.GetDataLength(Child) == a_Length))
+ {
+ memcpy(a_Destination, a_NBT.GetData(Child), a_Length);
+ }
+}
+
+
+
+
+
+bool cWSSAnvil::SaveChunkToNBT(const cChunkCoords & a_Chunk, cFastNBTWriter & a_Writer)
+{
+ a_Writer.BeginCompound("Level");
+ a_Writer.AddInt("xPos", a_Chunk.m_ChunkX);
+ a_Writer.AddInt("zPos", a_Chunk.m_ChunkZ);
+ cNBTChunkSerializer Serializer(a_Writer);
+ if (!m_World->GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, Serializer))
+ {
+ LOGWARNING("Cannot get chunk [%d, %d] data for NBT saving", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ);
+ return false;
+ }
+ Serializer.Finish(); // Close NBT tags
+
+ // Save biomes, both MCS (IntArray) and MC-vanilla (ByteArray):
+ if (Serializer.m_BiomesAreValid)
+ {
+ a_Writer.AddByteArray("Biomes", (const char *)(Serializer.m_VanillaBiomes), ARRAYCOUNT(Serializer.m_VanillaBiomes));
+ a_Writer.AddIntArray ("MCSBiomes", (const int *)(Serializer.m_Biomes), ARRAYCOUNT(Serializer.m_Biomes));
+ }
+
+ // Save blockdata:
+ a_Writer.BeginList("Sections", TAG_Compound);
+ int SliceSizeBlock = cChunkDef::Width * cChunkDef::Width * 16;
+ int SliceSizeNibble = SliceSizeBlock / 2;
+ const char * BlockTypes = (const char *)(Serializer.m_BlockTypes);
+ const char * BlockMetas = (const char *)(Serializer.m_BlockMetas);
+ #ifdef DEBUG_SKYLIGHT
+ const char * BlockLight = (const char *)(Serializer.m_BlockSkyLight);
+ #else
+ const char * BlockLight = (const char *)(Serializer.m_BlockLight);
+ #endif
+ const char * BlockSkyLight = (const char *)(Serializer.m_BlockSkyLight);
+ for (int Y = 0; Y < 16; Y++)
+ {
+ a_Writer.BeginCompound("");
+ a_Writer.AddByteArray("Blocks", BlockTypes + Y * SliceSizeBlock, SliceSizeBlock);
+ a_Writer.AddByteArray("Data", BlockMetas + Y * SliceSizeNibble, SliceSizeNibble);
+ a_Writer.AddByteArray("SkyLight", BlockSkyLight + Y * SliceSizeNibble, SliceSizeNibble);
+ a_Writer.AddByteArray("BlockLight", BlockLight + Y * SliceSizeNibble, SliceSizeNibble);
+ a_Writer.AddByte("Y", (unsigned char)Y);
+ a_Writer.EndCompound();
+ }
+ a_Writer.EndList(); // "Sections"
+
+ // Store the information that the lighting is valid.
+ // For compatibility reason, the default is "invalid" (missing) - this means older data is re-lighted upon loading.
+ if (Serializer.IsLightValid())
+ {
+ a_Writer.AddByte("MCSIsLightValid", 1);
+ }
+
+ a_Writer.EndCompound(); // "Level"
+ return true;
+}
+
+
+
+
+
+cChunkDef::BiomeMap * cWSSAnvil::LoadVanillaBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_ByteArray))
+ {
+ return NULL;
+ }
+ if (a_NBT.GetDataLength(a_TagIdx) != 16 * 16)
+ {
+ // The biomes stored don't match in size
+ return NULL;
+ }
+ const unsigned char * VanillaBiomeData = (const unsigned char *)(a_NBT.GetData(a_TagIdx));
+ for (int i = 0; i < ARRAYCOUNT(*a_BiomeMap); i++)
+ {
+ if ((VanillaBiomeData)[i] == 0xff)
+ {
+ // Unassigned biomes
+ return NULL;
+ }
+ (*a_BiomeMap)[i] = (EMCSBiome)(VanillaBiomeData[i]);
+ }
+ return a_BiomeMap;
+}
+
+
+
+
+
+cChunkDef::BiomeMap * cWSSAnvil::LoadBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_IntArray))
+ {
+ return NULL;
+ }
+ if (a_NBT.GetDataLength(a_TagIdx) != sizeof(*a_BiomeMap))
+ {
+ // The biomes stored don't match in size
+ return NULL;
+ }
+ const int * BiomeData = (const int *)(a_NBT.GetData(a_TagIdx));
+ for (int i = 0; i < ARRAYCOUNT(*a_BiomeMap); i++)
+ {
+ (*a_BiomeMap)[i] = (EMCSBiome)(ntohl(BiomeData[i]));
+ if ((*a_BiomeMap)[i] == 0xff)
+ {
+ // Unassigned biomes
+ return NULL;
+ }
+ }
+ return a_BiomeMap;
+}
+
+
+
+
+
+void cWSSAnvil::LoadEntitiesFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_List))
+ {
+ return;
+ }
+
+ for (int Child = a_NBT.GetFirstChild(a_TagIdx); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ if (a_NBT.GetType(Child) != TAG_Compound)
+ {
+ continue;
+ }
+ int sID = a_NBT.FindChildByName(Child, "id");
+ if (sID < 0)
+ {
+ continue;
+ }
+ LoadEntityFromNBT(a_Entities, a_NBT, Child, a_NBT.GetData(sID), a_NBT.GetDataLength(sID));
+ } // for Child - a_NBT[]
+}
+
+
+
+
+
+void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE * a_BlockTypes, NIBBLETYPE * a_BlockMetas)
+{
+ if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_List))
+ {
+ return;
+ }
+
+ for (int Child = a_NBT.GetFirstChild(a_TagIdx); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ if (a_NBT.GetType(Child) != TAG_Compound)
+ {
+ continue;
+ }
+ int sID = a_NBT.FindChildByName(Child, "id");
+ if (sID < 0)
+ {
+ continue;
+ }
+ if (strncmp(a_NBT.GetData(sID), "Chest", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadChestFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Dropper", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadDropperFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Furnace", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadFurnaceFromNBT(a_BlockEntities, a_NBT, Child, a_BlockTypes, a_BlockMetas);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Hopper", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadHopperFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Music", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadNoteFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "RecordPlayer", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadJukeboxFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Sign", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadSignFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ else if (strncmp(a_NBT.GetData(sID), "Trap", a_NBT.GetDataLength(sID)) == 0)
+ {
+ LoadDispenserFromNBT(a_BlockEntities, a_NBT, Child);
+ }
+ // TODO: Other block entities
+ } // for Child - tag children
+}
+
+
+
+
+
+bool cWSSAnvil::LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ int ID = a_NBT.FindChildByName(a_TagIdx, "id");
+ if ((ID < 0) || (a_NBT.GetType(ID) != TAG_Short))
+ {
+ return false;
+ }
+ a_Item.m_ItemType = (ENUM_ITEM_ID)(a_NBT.GetShort(ID));
+
+ int Damage = a_NBT.FindChildByName(a_TagIdx, "Damage");
+ if ((Damage < 0) || (a_NBT.GetType(Damage) != TAG_Short))
+ {
+ return false;
+ }
+ a_Item.m_ItemDamage = a_NBT.GetShort(Damage);
+
+ int Count = a_NBT.FindChildByName(a_TagIdx, "Count");
+ if ((Count < 0) || (a_NBT.GetType(Count) != TAG_Byte))
+ {
+ return false;
+ }
+ a_Item.m_ItemCount = a_NBT.GetByte(Count);
+
+ // Find the "tag" tag, used for enchantments and other extra data
+ int TagTag = a_NBT.FindChildByName(a_TagIdx, "tag");
+ if (TagTag <= 0)
+ {
+ // No extra data
+ return true;
+ }
+
+ // Load enchantments:
+ const char * EnchName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench";
+ int EnchTag = a_NBT.FindChildByName(TagTag, EnchName);
+ if (EnchTag > 0)
+ {
+ a_Item.m_Enchantments.ParseFromNBT(a_NBT, EnchTag);
+ }
+
+ return true;
+}
+
+
+
+
+
+void cWSSAnvil::LoadItemGridFromNBT(cItemGrid & a_ItemGrid, const cParsedNBT & a_NBT, int a_ItemsTagIdx, int a_SlotOffset)
+{
+ int NumSlots = a_ItemGrid.GetNumSlots();
+ for (int Child = a_NBT.GetFirstChild(a_ItemsTagIdx); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ int SlotTag = a_NBT.FindChildByName(Child, "Slot");
+ if ((SlotTag < 0) || (a_NBT.GetType(SlotTag) != TAG_Byte))
+ {
+ continue;
+ }
+ int SlotNum = (int)(a_NBT.GetByte(SlotTag)) - a_SlotOffset;
+ if ((SlotNum < 0) || (SlotNum >= NumSlots))
+ {
+ // SlotNum outside of the range
+ continue;
+ }
+ cItem Item;
+ if (LoadItemFromNBT(Item, a_NBT, Child))
+ {
+ a_ItemGrid.SetSlot(SlotNum, Item);
+ }
+ } // for itr - ItemDefs[]
+}
+
+
+
+
+
+void cWSSAnvil::LoadChestFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty chest - the chunk loader will provide an empty cChestEntity for this
+ }
+ std::auto_ptr<cChestEntity> Chest(new cChestEntity(x, y, z, m_World));
+ LoadItemGridFromNBT(Chest->GetContents(), a_NBT, Items);
+ a_BlockEntities.push_back(Chest.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadDispenserFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty dispenser - the chunk loader will provide an empty cDispenserEntity for this
+ }
+ std::auto_ptr<cDispenserEntity> Dispenser(new cDispenserEntity(x, y, z, m_World));
+ LoadItemGridFromNBT(Dispenser->GetContents(), a_NBT, Items);
+ a_BlockEntities.push_back(Dispenser.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadDropperFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty dropper - the chunk loader will provide an empty cDropperEntity for this
+ }
+ std::auto_ptr<cDropperEntity> Dropper(new cDropperEntity(x, y, z, m_World));
+ LoadItemGridFromNBT(Dropper->GetContents(), a_NBT, Items);
+ a_BlockEntities.push_back(Dropper.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadFurnaceFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE * a_BlockTypes, NIBBLETYPE * a_BlockMetas)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty furnace - the chunk loader will provide an empty cFurnaceEntity for this
+ }
+
+ // Convert coords to relative:
+ int RelX = x;
+ int RelZ = z;
+ int ChunkX, ChunkZ;
+ cChunkDef::AbsoluteToRelative(RelX, y, RelZ, ChunkX, ChunkZ);
+
+ // Create the furnace entity, with proper BlockType and BlockMeta info:
+ BLOCKTYPE BlockType = cChunkDef::GetBlock(a_BlockTypes, RelX, y, RelZ);
+ NIBBLETYPE BlockMeta = cChunkDef::GetNibble(a_BlockMetas, RelX, y, RelZ);
+ std::auto_ptr<cFurnaceEntity> Furnace(new cFurnaceEntity(x, y, z, BlockType, BlockMeta, m_World));
+
+ // Load slots:
+ for (int Child = a_NBT.GetFirstChild(Items); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ int Slot = a_NBT.FindChildByName(Child, "Slot");
+ if ((Slot < 0) || (a_NBT.GetType(Slot) != TAG_Byte))
+ {
+ continue;
+ }
+ cItem Item;
+ if (LoadItemFromNBT(Item, a_NBT, Child))
+ {
+ Furnace->SetSlot(a_NBT.GetByte(Slot), Item);
+ }
+ } // for itr - ItemDefs[]
+
+ // Load burn time:
+ int BurnTime = a_NBT.FindChildByName(a_TagIdx, "BurnTime");
+ if (BurnTime >= 0)
+ {
+ Int16 bt = a_NBT.GetShort(BurnTime);
+ // Anvil doesn't store the time that the fuel can burn. We simply "reset" the current value to be the 100%
+ Furnace->SetBurnTimes(bt, 0);
+ }
+
+ // Load cook time:
+ int CookTime = a_NBT.FindChildByName(a_TagIdx, "CookTime");
+ if (CookTime >= 0)
+ {
+ Int16 ct = a_NBT.GetShort(CookTime);
+ // Anvil doesn't store the time that an item takes to cook. We simply use the default - 10 seconds (200 ticks)
+ Furnace->SetCookTimes(200, ct);
+ }
+
+ // Restart cooking:
+ Furnace->ContinueCooking();
+ a_BlockEntities.push_back(Furnace.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadHopperFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty hopper - the chunk loader will provide an empty cHopperEntity for this
+ }
+ std::auto_ptr<cHopperEntity> Hopper(new cHopperEntity(x, y, z, m_World));
+ LoadItemGridFromNBT(Hopper->GetContents(), a_NBT, Items);
+ a_BlockEntities.push_back(Hopper.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadJukeboxFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ std::auto_ptr<cJukeboxEntity> Jukebox(new cJukeboxEntity(x, y, z, m_World));
+ int Record = a_NBT.FindChildByName(a_TagIdx, "Record");
+ if (Record >= 0)
+ {
+ Jukebox->SetRecord(a_NBT.GetInt(Record));
+ }
+ a_BlockEntities.push_back(Jukebox.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadNoteFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ std::auto_ptr<cNoteEntity> Note(new cNoteEntity(x, y, z, m_World));
+ int note = a_NBT.FindChildByName(a_TagIdx, "note");
+ if (note >= 0)
+ {
+ Note->SetPitch(a_NBT.GetByte(note));
+ }
+ a_BlockEntities.push_back(Note.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadSignFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
+ int x, y, z;
+ if (!GetBlockEntityNBTPos(a_NBT, a_TagIdx, x, y, z))
+ {
+ return;
+ }
+ std::auto_ptr<cSignEntity> Sign(new cSignEntity(E_BLOCK_SIGN_POST, x, y, z, m_World));
+
+ int currentLine = a_NBT.FindChildByName(a_TagIdx, "Text1");
+ if (currentLine >= 0)
+ {
+ Sign->SetLine(0, a_NBT.GetString(currentLine));
+ }
+
+ currentLine = a_NBT.FindChildByName(a_TagIdx, "Text2");
+ if (currentLine >= 0)
+ {
+ Sign->SetLine(1, a_NBT.GetString(currentLine));
+ }
+
+ currentLine = a_NBT.FindChildByName(a_TagIdx, "Text3");
+ if (currentLine >= 0)
+ {
+ Sign->SetLine(2, a_NBT.GetString(currentLine));
+ }
+
+ currentLine = a_NBT.FindChildByName(a_TagIdx, "Text4");
+ if (currentLine >= 0)
+ {
+ Sign->SetLine(3, a_NBT.GetString(currentLine));
+ }
+
+ a_BlockEntities.push_back(Sign.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadEntityFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_EntityTagIdx, const char * a_IDTag, int a_IDTagLength)
+{
+ if (strncmp(a_IDTag, "Boat", a_IDTagLength) == 0)
+ {
+ LoadBoatFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "FallingBlock", a_IDTagLength) == 0)
+ {
+ LoadFallingBlockFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Minecart", a_IDTagLength) == 0)
+ {
+ // It is a minecart, old style, find out the type:
+ int TypeTag = a_NBT.FindChildByName(a_EntityTagIdx, "Type");
+ if ((TypeTag < 0) || (a_NBT.GetType(TypeTag) != TAG_Int))
+ {
+ return;
+ }
+ switch (a_NBT.GetInt(TypeTag))
+ {
+ case 0: LoadMinecartRFromNBT(a_Entities, a_NBT, a_EntityTagIdx); break; // Rideable minecart
+ case 1: LoadMinecartCFromNBT(a_Entities, a_NBT, a_EntityTagIdx); break; // Minecart with chest
+ case 2: LoadMinecartFFromNBT(a_Entities, a_NBT, a_EntityTagIdx); break; // Minecart with furnace
+ case 3: LoadMinecartTFromNBT(a_Entities, a_NBT, a_EntityTagIdx); break; // Minecart with TNT
+ case 4: LoadMinecartHFromNBT(a_Entities, a_NBT, a_EntityTagIdx); break; // Minecart with Hopper
+ }
+ }
+ else if (strncmp(a_IDTag, "MinecartRideable", a_IDTagLength) == 0)
+ {
+ LoadMinecartRFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "MinecartChest", a_IDTagLength) == 0)
+ {
+ LoadMinecartCFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "MinecartFurnace", a_IDTagLength) == 0)
+ {
+ LoadMinecartFFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "MinecartTNT", a_IDTagLength) == 0)
+ {
+ LoadMinecartTFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "MinecartHopper", a_IDTagLength) == 0)
+ {
+ LoadMinecartHFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Item", a_IDTagLength) == 0)
+ {
+ LoadPickupFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Arrow", a_IDTagLength) == 0)
+ {
+ LoadArrowFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Snowball", a_IDTagLength) == 0)
+ {
+ LoadSnowballFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Egg", a_IDTagLength) == 0)
+ {
+ LoadEggFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "Fireball", a_IDTagLength) == 0)
+ {
+ LoadFireballFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "SmallFireball", a_IDTagLength) == 0)
+ {
+ LoadFireChargeFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ else if (strncmp(a_IDTag, "ThrownEnderpearl", a_IDTagLength) == 0)
+ {
+ LoadThrownEnderpearlFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ // TODO: other entities
+}
+
+
+
+
+
+void cWSSAnvil::LoadBoatFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cBoat> Boat(new cBoat(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Boat.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+ a_Entities.push_back(Boat.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadFallingBlockFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ // TODO
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartRFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cEmptyMinecart> Minecart(new cEmptyMinecart(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Minecart.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+ a_Entities.push_back(Minecart.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartCFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ int Items = a_NBT.FindChildByName(a_TagIdx, "Items");
+ if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List))
+ {
+ return; // Make it an empty chest - the chunk loader will provide an empty cChestEntity for this
+ }
+ std::auto_ptr<cMinecartWithChest> Minecart(new cMinecartWithChest(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Minecart.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+ for (int Child = a_NBT.GetFirstChild(Items); Child != -1; Child = a_NBT.GetNextSibling(Child))
+ {
+ int Slot = a_NBT.FindChildByName(Child, "Slot");
+ if ((Slot < 0) || (a_NBT.GetType(Slot) != TAG_Byte))
+ {
+ continue;
+ }
+ cItem Item;
+ if (LoadItemFromNBT(Item, a_NBT, Child))
+ {
+ Minecart->SetSlot(a_NBT.GetByte(Slot), Item);
+ }
+ } // for itr - ItemDefs[]
+ a_Entities.push_back(Minecart.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartFFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cMinecartWithFurnace> Minecart(new cMinecartWithFurnace(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Minecart.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // TODO: Load the Push and Fuel tags
+
+ a_Entities.push_back(Minecart.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartTFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cMinecartWithTNT> Minecart(new cMinecartWithTNT(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Minecart.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // TODO: Everything to do with TNT carts
+
+ a_Entities.push_back(Minecart.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartHFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cMinecartWithHopper> Minecart(new cMinecartWithHopper(0, 0, 0));
+ if (!LoadEntityBaseFromNBT(*Minecart.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // TODO: Everything to do with hopper carts
+
+ a_Entities.push_back(Minecart.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadPickupFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ int ItemTag = a_NBT.FindChildByName(a_TagIdx, "Item");
+ if ((ItemTag < 0) || (a_NBT.GetType(ItemTag) != TAG_Compound))
+ {
+ return;
+ }
+ cItem Item;
+ if (!LoadItemFromNBT(Item, a_NBT, ItemTag))
+ {
+ return;
+ }
+ std::auto_ptr<cPickup> Pickup(new cPickup(0, 0, 0, Item, false)); // Pickup delay doesn't matter, just say false
+ if (!LoadEntityBaseFromNBT(*Pickup.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+ a_Entities.push_back(Pickup.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadArrowFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cArrowEntity> Arrow(new cArrowEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*Arrow.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Load pickup state:
+ int PickupIdx = a_NBT.FindChildByName(a_TagIdx, "pickup");
+ if (PickupIdx > 0)
+ {
+ Arrow->SetPickupState((cArrowEntity::ePickupState)a_NBT.GetByte(PickupIdx));
+ }
+ else
+ {
+ // Try the older "player" tag:
+ int PlayerIdx = a_NBT.FindChildByName(a_TagIdx, "player");
+ if (PlayerIdx > 0)
+ {
+ Arrow->SetPickupState((a_NBT.GetByte(PlayerIdx) == 0) ? cArrowEntity::psNoPickup : cArrowEntity::psInSurvivalOrCreative);
+ }
+ }
+
+ // Load damage:
+ int DamageIdx = a_NBT.FindChildByName(a_TagIdx, "damage");
+ if (DamageIdx > 0)
+ {
+ Arrow->SetDamageCoeff(a_NBT.GetDouble(DamageIdx));
+ }
+
+ // Store the new arrow in the entities list:
+ a_Entities.push_back(Arrow.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadSnowballFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cThrownSnowballEntity> Snowball(new cThrownSnowballEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*Snowball.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Store the new snowball in the entities list:
+ a_Entities.push_back(Snowball.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadEggFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cThrownEggEntity> Egg(new cThrownEggEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*Egg.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Store the new egg in the entities list:
+ a_Entities.push_back(Egg.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadFireballFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cGhastFireballEntity> Fireball(new cGhastFireballEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*Fireball.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Store the new fireball in the entities list:
+ a_Entities.push_back(Fireball.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadFireChargeFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cFireChargeEntity> FireCharge(new cFireChargeEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*FireCharge.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Store the new FireCharge in the entities list:
+ a_Entities.push_back(FireCharge.release());
+}
+
+
+
+
+
+void cWSSAnvil::LoadThrownEnderpearlFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ std::auto_ptr<cThrownEnderPearlEntity> Enderpearl(new cThrownEnderPearlEntity(NULL, 0, 0, 0, Vector3d(0, 0, 0)));
+ if (!LoadProjectileBaseFromNBT(*Enderpearl.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+
+ // Store the new enderpearl in the entities list:
+ a_Entities.push_back(Enderpearl.release());
+}
+
+
+
+
+
+bool cWSSAnvil::LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ double Pos[3];
+ if (!LoadDoublesListFromNBT(Pos, 3, a_NBT, a_NBT.FindChildByName(a_TagIdx, "Pos")))
+ {
+ return false;
+ }
+ a_Entity.SetPosition(Pos[0], Pos[1], Pos[2]);
+
+ double Speed[3];
+ if (!LoadDoublesListFromNBT(Speed, 3, a_NBT, a_NBT.FindChildByName(a_TagIdx, "Motion")))
+ {
+ return false;
+ }
+ a_Entity.SetSpeed(Speed[0], Speed[1], Speed[2]);
+
+ double Rotation[3];
+ if (!LoadDoublesListFromNBT(Rotation, 2, a_NBT, a_NBT.FindChildByName(a_TagIdx, "Rotation")))
+ {
+ return false;
+ }
+ a_Entity.SetRotation(Rotation[0]);
+ a_Entity.SetRoll (Rotation[1]);
+
+ return true;
+}
+
+
+
+
+
+bool cWSSAnvil::LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ if (!LoadEntityBaseFromNBT(a_Entity, a_NBT, a_TagIdx))
+ {
+ return false;
+ }
+
+ bool IsInGround = false;
+ int InGroundIdx = a_NBT.FindChildByName(a_TagIdx, "inGround");
+ if (InGroundIdx > 0)
+ {
+ IsInGround = (a_NBT.GetByte(InGroundIdx) != 0);
+ }
+ a_Entity.SetIsInGround(IsInGround);
+
+ // TODO: Load inTile, TileCoords
+
+ return true;
+}
+
+
+
+
+
+bool cWSSAnvil::LoadDoublesListFromNBT(double * a_Doubles, int a_NumDoubles, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_List) || (a_NBT.GetChildrenType(a_TagIdx) != TAG_Double))
+ {
+ return false;
+ }
+ int idx = 0;
+ for (int Tag = a_NBT.GetFirstChild(a_TagIdx); (Tag > 0) && (idx < a_NumDoubles); Tag = a_NBT.GetNextSibling(Tag), ++idx)
+ {
+ a_Doubles[idx] = a_NBT.GetDouble(Tag);
+ } // for Tag - PosTag[]
+ return (idx == a_NumDoubles); // Did we read enough doubles?
+}
+
+
+
+
+
+bool cWSSAnvil::GetBlockEntityNBTPos(const cParsedNBT & a_NBT, int a_TagIdx, int & a_X, int & a_Y, int & a_Z)
+{
+ int x = a_NBT.FindChildByName(a_TagIdx, "x");
+ if ((x < 0) || (a_NBT.GetType(x) != TAG_Int))
+ {
+ return false;
+ }
+ int y = a_NBT.FindChildByName(a_TagIdx, "y");
+ if ((y < 0) || (a_NBT.GetType(y) != TAG_Int))
+ {
+ return false;
+ }
+ int z = a_NBT.FindChildByName(a_TagIdx, "z");
+ if ((z < 0) || (a_NBT.GetType(z) != TAG_Int))
+ {
+ return false;
+ }
+ a_X = a_NBT.GetInt(x);
+ a_Y = a_NBT.GetInt(y);
+ a_Z = a_NBT.GetInt(z);
+ return true;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cWSSAnvil::cMCAFile:
+
+cWSSAnvil::cMCAFile::cMCAFile(const AString & a_FileName, int a_RegionX, int a_RegionZ) :
+ m_RegionX(a_RegionX),
+ m_RegionZ(a_RegionZ),
+ m_FileName(a_FileName)
+{
+}
+
+
+
+
+
+bool cWSSAnvil::cMCAFile::OpenFile(bool a_IsForReading)
+{
+ if (m_File.IsOpen())
+ {
+ // Already open
+ return true;
+ }
+
+ if (a_IsForReading)
+ {
+ if (!cFile::Exists(m_FileName))
+ {
+ // We want to read and the file doesn't exist. Fail.
+ return false;
+ }
+ }
+
+ if (!m_File.Open(m_FileName, cFile::fmReadWrite))
+ {
+ // The file failed to open
+ return false;
+ }
+
+ // Load the header:
+ if (m_File.Read(m_Header, sizeof(m_Header)) != sizeof(m_Header))
+ {
+ // Cannot read the header - perhaps the file has just been created?
+ // Try writing a NULL header (both chunk offsets and timestamps):
+ memset(m_Header, 0, sizeof(m_Header));
+ if (
+ (m_File.Write(m_Header, sizeof(m_Header)) != sizeof(m_Header)) || // Real header - chunk offsets
+ (m_File.Write(m_Header, sizeof(m_Header)) != sizeof(m_Header)) // Bogus data for the chunk timestamps
+ )
+ {
+ LOGWARNING("Cannot process MCA header in file \"%s\", chunks in that file will be lost", m_FileName.c_str());
+ m_File.Close();
+ return false;
+ }
+ }
+ return true;
+}
+
+
+
+
+
+bool cWSSAnvil::cMCAFile::GetChunkData(const cChunkCoords & a_Chunk, AString & a_Data)
+{
+ if (!OpenFile(true))
+ {
+ return false;
+ }
+
+ int LocalX = a_Chunk.m_ChunkX % 32;
+ if (LocalX < 0)
+ {
+ LocalX = 32 + LocalX;
+ }
+ int LocalZ = a_Chunk.m_ChunkZ % 32;
+ if (LocalZ < 0)
+ {
+ LocalZ = 32 + LocalZ;
+ }
+ unsigned ChunkLocation = ntohl(m_Header[LocalX + 32 * LocalZ]);
+ unsigned ChunkOffset = ChunkLocation >> 8;
+
+ m_File.Seek(ChunkOffset * 4096);
+
+ int ChunkSize = 0;
+ if (m_File.Read(&ChunkSize, 4) != 4)
+ {
+ return false;
+ }
+ ChunkSize = ntohl(ChunkSize);
+ char CompressionType = 0;
+ if (m_File.Read(&CompressionType, 1) != 1)
+ {
+ return false;
+ }
+ if (CompressionType != 2)
+ {
+ // Chunk is in an unknown compression
+ return false;
+ }
+ ChunkSize--;
+
+ // HACK: This depends on the internal knowledge that AString's data() function returns the internal buffer directly
+ a_Data.assign(ChunkSize, '\0');
+ return (m_File.Read((void *)a_Data.data(), ChunkSize) == ChunkSize);
+}
+
+
+
+
+
+bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AString & a_Data)
+{
+ if (!OpenFile(false))
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], opening file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+
+ int LocalX = a_Chunk.m_ChunkX % 32;
+ if (LocalX < 0)
+ {
+ LocalX = 32 + LocalX;
+ }
+ int LocalZ = a_Chunk.m_ChunkZ % 32;
+ if (LocalZ < 0)
+ {
+ LocalZ = 32 + LocalZ;
+ }
+
+ unsigned ChunkSector = FindFreeLocation(LocalX, LocalZ, a_Data);
+
+ // Store the chunk data:
+ m_File.Seek(ChunkSector * 4096);
+ unsigned ChunkSize = htonl(a_Data.size() + 1);
+ if (m_File.Write(&ChunkSize, 4) != 4)
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], writing(1) data to file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+ char CompressionType = 2;
+ if (m_File.Write(&CompressionType, 1) != 1)
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], writing(2) data to file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+ if (m_File.Write(a_Data.data(), a_Data.size()) != (int)(a_Data.size()))
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], writing(3) data to file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+
+ // Store the header:
+ ChunkSize = (a_Data.size() + MCA_CHUNK_HEADER_LENGTH + 4095) / 4096; // Round data size *up* to nearest 4KB sector, make it a sector number
+ ASSERT(ChunkSize < 256);
+ m_Header[LocalX + 32 * LocalZ] = htonl((ChunkSector << 8) | ChunkSize);
+ if (m_File.Seek(0) < 0)
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], seeking in file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+ if (m_File.Write(m_Header, sizeof(m_Header)) != sizeof(m_Header))
+ {
+ LOGWARNING("Cannot save chunk [%d, %d], writing header to file \"%s\" failed", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, GetFileName().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+
+
+
+
+unsigned cWSSAnvil::cMCAFile::FindFreeLocation(int a_LocalX, int a_LocalZ, const AString & a_Data)
+{
+ // See if it fits the current location:
+ unsigned ChunkLocation = ntohl(m_Header[a_LocalX + 32 * a_LocalZ]);
+ unsigned ChunkLen = ChunkLocation & 0xff;
+ if (a_Data.size() + MCA_CHUNK_HEADER_LENGTH <= (ChunkLen * 4096))
+ {
+ return ChunkLocation >> 8;
+ }
+
+ // Doesn't fit, append to the end of file (we're wasting a lot of space, TODO: fix this later)
+ unsigned MaxLocation = 2 << 8; // Minimum sector is #2 - after the headers
+ for (int i = 0; i < ARRAYCOUNT(m_Header); i++)
+ {
+ ChunkLocation = ntohl(m_Header[i]);
+ ChunkLocation = ChunkLocation + ((ChunkLocation & 0xff) << 8); // Add the number of sectors used; don't care about the 4th byte
+ if (MaxLocation < ChunkLocation)
+ {
+ MaxLocation = ChunkLocation;
+ }
+ } // for i - m_Header[]
+ return MaxLocation >> 8;
+}
+
+
+
+
diff --git a/source/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h
index 7685d2236..7685d2236 100644
--- a/source/WorldStorage/WSSAnvil.h
+++ b/src/WorldStorage/WSSAnvil.h
diff --git a/src/WorldStorage/WSSCompact.cpp b/src/WorldStorage/WSSCompact.cpp
new file mode 100644
index 000000000..287938b24
--- /dev/null
+++ b/src/WorldStorage/WSSCompact.cpp
@@ -0,0 +1,1009 @@
+
+// WSSCompact.cpp
+
+// Interfaces to the cWSSCompact class representing the "compact" storage schema (PAK-files)
+
+#include "Globals.h"
+#include "WSSCompact.h"
+#include "../World.h"
+#include "zlib/zlib.h"
+#include "json/json.h"
+#include "../StringCompression.h"
+#include "../BlockEntities/ChestEntity.h"
+#include "../BlockEntities/DispenserEntity.h"
+#include "../BlockEntities/FurnaceEntity.h"
+#include "../BlockEntities/JukeboxEntity.h"
+#include "../BlockEntities/NoteEntity.h"
+#include "../BlockEntities/SignEntity.h"
+
+
+
+
+
+#pragma pack(push, 1)
+/// The chunk header, as stored in the file:
+struct cWSSCompact::sChunkHeader
+{
+ int m_ChunkX;
+ int m_ChunkZ;
+ int m_CompressedSize;
+ int m_UncompressedSize;
+} ;
+#pragma pack(pop)
+
+
+
+
+
+/// The maximum number of PAK files that are cached
+const int MAX_PAK_FILES = 16;
+
+/// The maximum number of unsaved chunks before the cPAKFile saves them to disk
+const int MAX_DIRTY_CHUNKS = 16;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cJsonChunkSerializer:
+
+cJsonChunkSerializer::cJsonChunkSerializer(void) :
+ m_HasJsonData(false)
+{
+}
+
+
+
+
+
+void cJsonChunkSerializer::Entity(cEntity * a_Entity)
+{
+ // TODO: a_Entity->SaveToJson(m_Root);
+}
+
+
+
+
+
+void cJsonChunkSerializer::BlockEntity(cBlockEntity * a_BlockEntity)
+{
+ const char * SaveInto = NULL;
+ switch (a_BlockEntity->GetBlockType())
+ {
+ case E_BLOCK_CHEST: SaveInto = "Chests"; break;
+ case E_BLOCK_DISPENSER: SaveInto = "Dispensers"; break;
+ case E_BLOCK_DROPPER: SaveInto = "Droppers"; break;
+ case E_BLOCK_FURNACE: SaveInto = "Furnaces"; break;
+ case E_BLOCK_SIGN_POST: SaveInto = "Signs"; break;
+ case E_BLOCK_WALLSIGN: SaveInto = "Signs"; break;
+ case E_BLOCK_NOTE_BLOCK: SaveInto = "Notes"; break;
+ case E_BLOCK_JUKEBOX: SaveInto = "Jukeboxes"; break;
+
+ default:
+ {
+ ASSERT(!"Unhandled blocktype in BlockEntities list while saving to JSON");
+ break;
+ }
+ } // switch (BlockEntity->GetBlockType())
+ if (SaveInto == NULL)
+ {
+ return;
+ }
+
+ Json::Value val;
+ a_BlockEntity->SaveToJson(val);
+ m_Root[SaveInto].append(val);
+ m_HasJsonData = true;
+}
+
+
+
+
+
+bool cJsonChunkSerializer::LightIsValid(bool a_IsLightValid)
+{
+ if (!a_IsLightValid)
+ {
+ return false;
+ }
+ m_Root["IsLightValid"] = true;
+ m_HasJsonData = true;
+ return true;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cWSSCompact:
+
+cWSSCompact::~cWSSCompact()
+{
+ for (cPAKFiles::iterator itr = m_PAKFiles.begin(); itr != m_PAKFiles.end(); ++itr)
+ {
+ delete *itr;
+ }
+}
+
+
+
+
+
+bool cWSSCompact::LoadChunk(const cChunkCoords & a_Chunk)
+{
+ AString ChunkData;
+ int UncompressedSize = 0;
+ if (!GetChunkData(a_Chunk, UncompressedSize, ChunkData))
+ {
+ // The reason for failure is already printed in GetChunkData()
+ return false;
+ }
+
+ return LoadChunkFromData(a_Chunk, UncompressedSize, ChunkData, m_World);
+}
+
+
+
+
+
+bool cWSSCompact::SaveChunk(const cChunkCoords & a_Chunk)
+{
+ cCSLock Lock(m_CS);
+
+ cPAKFile * f = LoadPAKFile(a_Chunk);
+ if (f == NULL)
+ {
+ // For some reason we couldn't locate the file
+ LOG("Cannot locate a proper PAK file for chunk [%d, %d]", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ);
+ return false;
+ }
+ return f->SaveChunk(a_Chunk, m_World);
+}
+
+
+
+
+
+cWSSCompact::cPAKFile * cWSSCompact::LoadPAKFile(const cChunkCoords & a_Chunk)
+{
+ // ASSUMES that m_CS has been locked
+
+ // We need to retain this weird conversion code, because some edge chunks are in the wrong PAK file
+ const int LayerX = FAST_FLOOR_DIV(a_Chunk.m_ChunkX, 32);
+ const int LayerZ = FAST_FLOOR_DIV(a_Chunk.m_ChunkZ, 32);
+
+ // Is it already cached?
+ for (cPAKFiles::iterator itr = m_PAKFiles.begin(); itr != m_PAKFiles.end(); ++itr)
+ {
+ if (((*itr) != NULL) && ((*itr)->GetLayerX() == LayerX) && ((*itr)->GetLayerZ() == LayerZ))
+ {
+ // Move the file to front and return it:
+ cPAKFile * f = *itr;
+ if (itr != m_PAKFiles.begin())
+ {
+ m_PAKFiles.erase(itr);
+ m_PAKFiles.push_front(f);
+ }
+ return f;
+ }
+ }
+
+ // Load it anew:
+ AString FileName;
+ Printf(FileName, "%s/X%i_Z%i.pak", m_World->GetName().c_str(), LayerX, LayerZ );
+ cPAKFile * f = new cPAKFile(FileName, LayerX, LayerZ);
+ if (f == NULL)
+ {
+ return NULL;
+ }
+ m_PAKFiles.push_front(f);
+
+ // If there are too many PAK files cached, delete the last one used:
+ if (m_PAKFiles.size() > MAX_PAK_FILES)
+ {
+ delete m_PAKFiles.back();
+ m_PAKFiles.pop_back();
+ }
+ return f;
+}
+
+
+
+
+
+bool cWSSCompact::GetChunkData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, AString & a_Data)
+{
+ cCSLock Lock(m_CS);
+ cPAKFile * f = LoadPAKFile(a_Chunk);
+ if (f == NULL)
+ {
+ return false;
+ }
+ return f->GetChunkData(a_Chunk, a_UncompressedSize, a_Data);
+}
+
+
+
+
+
+/*
+// TODO: Rewrite saving to use the same principles as loading
+bool cWSSCompact::SetChunkData(const cChunkCoords & a_Chunk, int a_UncompressedSize, const AString & a_Data)
+{
+ cCSLock Lock(m_CS);
+ cPAKFile * f = LoadPAKFile(a_Chunk);
+ if (f == NULL)
+ {
+ return false;
+ }
+ return f->SetChunkData(a_Chunk, a_UncompressedSize, a_Data);
+}
+*/
+
+
+
+
+
+bool cWSSCompact::EraseChunkData(const cChunkCoords & a_Chunk)
+{
+ cCSLock Lock(m_CS);
+ cPAKFile * f = LoadPAKFile(a_Chunk);
+ if (f == NULL)
+ {
+ return false;
+ }
+ return f->EraseChunkData(a_Chunk);
+}
+
+
+
+
+
+void cWSSCompact::LoadEntitiesFromJson(Json::Value & a_Value, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities, cWorld * a_World)
+{
+ // Load chests
+ Json::Value AllChests = a_Value.get("Chests", Json::nullValue);
+ if (!AllChests.empty())
+ {
+ for (Json::Value::iterator itr = AllChests.begin(); itr != AllChests.end(); ++itr )
+ {
+ Json::Value & Chest = *itr;
+ cChestEntity * ChestEntity = new cChestEntity(0,0,0, a_World);
+ if (!ChestEntity->LoadFromJson( Chest ) )
+ {
+ LOGERROR("ERROR READING CHEST FROM JSON!" );
+ delete ChestEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back( ChestEntity );
+ }
+ } // for itr - AllChests[]
+ }
+
+ // Load dispensers
+ Json::Value AllDispensers = a_Value.get("Dispensers", Json::nullValue);
+ if( !AllDispensers.empty() )
+ {
+ for( Json::Value::iterator itr = AllDispensers.begin(); itr != AllDispensers.end(); ++itr )
+ {
+ Json::Value & Dispenser = *itr;
+ cDispenserEntity * DispenserEntity = new cDispenserEntity(0,0,0, a_World);
+ if( !DispenserEntity->LoadFromJson( Dispenser ) )
+ {
+ LOGERROR("ERROR READING DISPENSER FROM JSON!" );
+ delete DispenserEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back( DispenserEntity );
+ }
+ } // for itr - AllDispensers[]
+ }
+
+ // Load furnaces
+ Json::Value AllFurnaces = a_Value.get("Furnaces", Json::nullValue);
+ if( !AllFurnaces.empty() )
+ {
+ for( Json::Value::iterator itr = AllFurnaces.begin(); itr != AllFurnaces.end(); ++itr )
+ {
+ Json::Value & Furnace = *itr;
+ // TODO: The block type and meta aren't correct, there's no way to get them here
+ cFurnaceEntity * FurnaceEntity = new cFurnaceEntity(0, 0, 0, E_BLOCK_FURNACE, 0, a_World);
+ if (!FurnaceEntity->LoadFromJson(Furnace))
+ {
+ LOGERROR("ERROR READING FURNACE FROM JSON!" );
+ delete FurnaceEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back(FurnaceEntity);
+ }
+ } // for itr - AllFurnaces[]
+ }
+
+ // Load signs
+ Json::Value AllSigns = a_Value.get("Signs", Json::nullValue);
+ if( !AllSigns.empty() )
+ {
+ for( Json::Value::iterator itr = AllSigns.begin(); itr != AllSigns.end(); ++itr )
+ {
+ Json::Value & Sign = *itr;
+ cSignEntity * SignEntity = new cSignEntity( E_BLOCK_SIGN_POST, 0,0,0, a_World);
+ if ( !SignEntity->LoadFromJson( Sign ) )
+ {
+ LOGERROR("ERROR READING SIGN FROM JSON!" );
+ delete SignEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back( SignEntity );
+ }
+ } // for itr - AllSigns[]
+ }
+
+ // Load note blocks
+ Json::Value AllNotes = a_Value.get("Notes", Json::nullValue);
+ if( !AllNotes.empty() )
+ {
+ for( Json::Value::iterator itr = AllNotes.begin(); itr != AllNotes.end(); ++itr )
+ {
+ Json::Value & Note = *itr;
+ cNoteEntity * NoteEntity = new cNoteEntity(0, 0, 0, a_World);
+ if ( !NoteEntity->LoadFromJson( Note ) )
+ {
+ LOGERROR("ERROR READING NOTE BLOCK FROM JSON!" );
+ delete NoteEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back( NoteEntity );
+ }
+ } // for itr - AllNotes[]
+ }
+
+ // Load jukeboxes
+ Json::Value AllJukeboxes = a_Value.get("Jukeboxes", Json::nullValue);
+ if( !AllJukeboxes.empty() )
+ {
+ for( Json::Value::iterator itr = AllJukeboxes.begin(); itr != AllJukeboxes.end(); ++itr )
+ {
+ Json::Value & Jukebox = *itr;
+ cJukeboxEntity * JukeboxEntity = new cJukeboxEntity(0, 0, 0, a_World);
+ if ( !JukeboxEntity->LoadFromJson( Jukebox ) )
+ {
+ LOGERROR("ERROR READING JUKEBOX FROM JSON!" );
+ delete JukeboxEntity;
+ }
+ else
+ {
+ a_BlockEntities.push_back( JukeboxEntity );
+ }
+ } // for itr - AllJukeboxes[]
+ }
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cWSSCompact::cPAKFile
+
+#define READ(Var) \
+ if (f.Read(&Var, sizeof(Var)) != sizeof(Var)) \
+ { \
+ LOGERROR("ERROR READING %s FROM FILE %s (line %d); file offset %d", #Var, m_FileName.c_str(), __LINE__, f.Tell()); \
+ return; \
+ }
+
+cWSSCompact::cPAKFile::cPAKFile(const AString & a_FileName, int a_LayerX, int a_LayerZ) :
+ m_FileName(a_FileName),
+ m_LayerX(a_LayerX),
+ m_LayerZ(a_LayerZ),
+ m_NumDirty(0),
+ m_ChunkVersion( CHUNK_VERSION ), // Init with latest version
+ m_PakVersion( PAK_VERSION )
+{
+ cFile f;
+ if (!f.Open(m_FileName, cFile::fmRead))
+ {
+ return;
+ }
+
+ // Read headers:
+ READ(m_PakVersion);
+ if (m_PakVersion != 1)
+ {
+ LOGERROR("File \"%s\" is in an unknown pak format (%d)", m_FileName.c_str(), m_PakVersion);
+ return;
+ }
+
+ READ(m_ChunkVersion);
+ switch( m_ChunkVersion )
+ {
+ case 1:
+ m_ChunkSize.Set(16, 128, 16);
+ break;
+ case 2:
+ case 3:
+ m_ChunkSize.Set(16, 256, 16);
+ break;
+ default:
+ LOGERROR("File \"%s\" is in an unknown chunk format (%d)", m_FileName.c_str(), m_ChunkVersion);
+ return;
+ };
+
+ short NumChunks = 0;
+ READ(NumChunks);
+
+ // Read chunk headers:
+ for (int i = 0; i < NumChunks; i++)
+ {
+ sChunkHeader * Header = new sChunkHeader;
+ READ(*Header);
+ m_ChunkHeaders.push_back(Header);
+ } // for i - chunk headers
+
+ // Read chunk data:
+ if (f.ReadRestOfFile(m_DataContents) == -1)
+ {
+ LOGERROR("Cannot read file \"%s\" contents", m_FileName.c_str());
+ return;
+ }
+
+ if( m_ChunkVersion == 1 ) // Convert chunks to version 2
+ {
+ UpdateChunk1To2();
+ }
+#if AXIS_ORDER == AXIS_ORDER_XZY
+ if( m_ChunkVersion == 2 ) // Convert chunks to version 3
+ {
+ UpdateChunk2To3();
+ }
+#endif
+}
+
+
+
+
+
+cWSSCompact::cPAKFile::~cPAKFile()
+{
+ if (m_NumDirty > 0)
+ {
+ SynchronizeFile();
+ }
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ delete *itr;
+ }
+}
+
+
+
+
+
+bool cWSSCompact::cPAKFile::GetChunkData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, AString & a_Data)
+{
+ int ChunkX = a_Chunk.m_ChunkX;
+ int ChunkZ = a_Chunk.m_ChunkZ;
+ sChunkHeader * Header = NULL;
+ int Offset = 0;
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ if (((*itr)->m_ChunkX == ChunkX) && ((*itr)->m_ChunkZ == ChunkZ))
+ {
+ Header = *itr;
+ break;
+ }
+ Offset += (*itr)->m_CompressedSize;
+ }
+ if ((Header == NULL) || (Offset + Header->m_CompressedSize > (int)m_DataContents.size()))
+ {
+ // Chunk not found / data invalid
+ return false;
+ }
+
+ a_UncompressedSize = Header->m_UncompressedSize;
+ a_Data.assign(m_DataContents, Offset, Header->m_CompressedSize);
+ return true;
+}
+
+
+
+
+
+bool cWSSCompact::cPAKFile::SaveChunk(const cChunkCoords & a_Chunk, cWorld * a_World)
+{
+ if (!SaveChunkToData(a_Chunk, a_World))
+ {
+ return false;
+ }
+ if (m_NumDirty > MAX_DIRTY_CHUNKS)
+ {
+ SynchronizeFile();
+ }
+ return true;
+}
+
+
+
+
+
+void cWSSCompact::cPAKFile::UpdateChunk1To2()
+{
+ int Offset = 0;
+ AString NewDataContents;
+ int ChunksConverted = 0;
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ sChunkHeader * Header = *itr;
+
+ if( ChunksConverted % 32 == 0 )
+ {
+ LOGINFO("Updating \"%s\" version 1 to version 2: %d %%", m_FileName.c_str(), (ChunksConverted * 100) / m_ChunkHeaders.size() );
+ }
+ ChunksConverted++;
+
+ AString Data;
+ int UncompressedSize = Header->m_UncompressedSize;
+ Data.assign(m_DataContents, Offset, Header->m_CompressedSize);
+ Offset += Header->m_CompressedSize;
+
+ // Crude data integrity check:
+ int ExpectedSize = (16*128*16)*2 + (16*128*16)/2; // For version 1
+ if (UncompressedSize < ExpectedSize)
+ {
+ LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing",
+ Header->m_ChunkX, Header->m_ChunkZ,
+ UncompressedSize, ExpectedSize
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+
+ // Decompress the data:
+ AString UncompressedData;
+ {
+ int errorcode = UncompressString(Data.data(), Data.size(), UncompressedData, UncompressedSize);
+ if (errorcode != Z_OK)
+ {
+ LOGERROR("Error %d decompressing data for chunk [%d, %d]",
+ errorcode,
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+ }
+
+ if (UncompressedSize != (int)UncompressedData.size())
+ {
+ LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]",
+ UncompressedSize, UncompressedData.size(),
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+
+
+ // Old version is 128 blocks high with YZX axis order
+ char ConvertedData[cChunkDef::BlockDataSize];
+ int Index = 0;
+ unsigned int InChunkOffset = 0;
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z )
+ {
+ for( int y = 0; y < 128; ++y )
+ {
+ ConvertedData[Index++] = UncompressedData[y + z * 128 + x * 128 * 16 + InChunkOffset];
+ }
+ // Add 128 empty blocks after an old y column
+ memset(ConvertedData + Index, E_BLOCK_AIR, 128);
+ Index += 128;
+ }
+ InChunkOffset += (16 * 128 * 16);
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Metadata
+ {
+ for( int y = 0; y < 64; ++y )
+ {
+ ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset];
+ }
+ memset(ConvertedData + Index, 0, 64);
+ Index += 64;
+ }
+ InChunkOffset += (16 * 128 * 16) / 2;
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Block light
+ {
+ for( int y = 0; y < 64; ++y )
+ {
+ ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset];
+ }
+ memset(ConvertedData + Index, 0, 64);
+ Index += 64;
+ }
+ InChunkOffset += (16*128*16)/2;
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) // Sky light
+ {
+ for( int y = 0; y < 64; ++y )
+ {
+ ConvertedData[Index++] = UncompressedData[y + z * 64 + x * 64 * 16 + InChunkOffset];
+ }
+ memset(ConvertedData + Index, 0, 64);
+ Index += 64;
+ }
+ InChunkOffset += (16 * 128 * 16) / 2;
+
+ AString Converted(ConvertedData, ARRAYCOUNT(ConvertedData));
+
+ // Add JSON data afterwards
+ if (UncompressedData.size() > InChunkOffset)
+ {
+ Converted.append( UncompressedData.begin() + InChunkOffset, UncompressedData.end() );
+ }
+
+ // Re-compress data
+ AString CompressedData;
+ {
+ int errorcode = CompressString(Converted.data(), Converted.size(), CompressedData);
+ if (errorcode != Z_OK)
+ {
+ LOGERROR("Error %d compressing data for chunk [%d, %d]",
+ errorcode,
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ continue;
+ }
+ }
+
+ // Save into file's cache
+ Header->m_UncompressedSize = Converted.size();
+ Header->m_CompressedSize = CompressedData.size();
+ NewDataContents.append( CompressedData );
+ }
+
+ // Done converting
+ m_DataContents = NewDataContents;
+ m_ChunkVersion = 2;
+ SynchronizeFile();
+
+ LOGINFO("Updated \"%s\" version 1 to version 2", m_FileName.c_str() );
+}
+
+
+
+
+
+void cWSSCompact::cPAKFile::UpdateChunk2To3()
+{
+ int Offset = 0;
+ AString NewDataContents;
+ int ChunksConverted = 0;
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ sChunkHeader * Header = *itr;
+
+ if( ChunksConverted % 32 == 0 )
+ {
+ LOGINFO("Updating \"%s\" version 2 to version 3: %d %%", m_FileName.c_str(), (ChunksConverted * 100) / m_ChunkHeaders.size() );
+ }
+ ChunksConverted++;
+
+ AString Data;
+ int UncompressedSize = Header->m_UncompressedSize;
+ Data.assign(m_DataContents, Offset, Header->m_CompressedSize);
+ Offset += Header->m_CompressedSize;
+
+ // Crude data integrity check:
+ const int ExpectedSize = (16*256*16)*2 + (16*256*16)/2; // For version 2
+ if (UncompressedSize < ExpectedSize)
+ {
+ LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing",
+ Header->m_ChunkX, Header->m_ChunkZ,
+ UncompressedSize, ExpectedSize
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+
+ // Decompress the data:
+ AString UncompressedData;
+ {
+ int errorcode = UncompressString(Data.data(), Data.size(), UncompressedData, UncompressedSize);
+ if (errorcode != Z_OK)
+ {
+ LOGERROR("Error %d decompressing data for chunk [%d, %d]",
+ errorcode,
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+ }
+
+ if (UncompressedSize != (int)UncompressedData.size())
+ {
+ LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]",
+ UncompressedSize, UncompressedData.size(),
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ Offset += Header->m_CompressedSize;
+ continue;
+ }
+
+ char ConvertedData[ExpectedSize];
+ memset(ConvertedData, 0, ExpectedSize);
+
+ // Cannot use cChunk::MakeIndex because it might change again?????????
+ // For compatibility, use what we know is current
+ #define MAKE_2_INDEX( x, y, z ) ( y + (z * 256) + (x * 256 * 16) )
+ #define MAKE_3_INDEX( x, y, z ) ( x + (z * 16) + (y * 16 * 16) )
+
+ unsigned int InChunkOffset = 0;
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y ) // YZX Loop order is important, in 1.1 Y was first then Z then X
+ {
+ ConvertedData[ MAKE_3_INDEX(x, y, z) ] = UncompressedData[InChunkOffset];
+ ++InChunkOffset;
+ } // for y, z, x
+
+
+ unsigned int index2 = 0;
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y )
+ {
+ ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4);
+ ++index2;
+ }
+ InChunkOffset += index2 / 2;
+ index2 = 0;
+
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y )
+ {
+ ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4);
+ ++index2;
+ }
+ InChunkOffset += index2 / 2;
+ index2 = 0;
+
+ for( int x = 0; x < 16; ++x ) for( int z = 0; z < 16; ++z ) for( int y = 0; y < 256; ++y )
+ {
+ ConvertedData[ InChunkOffset + MAKE_3_INDEX(x, y, z)/2 ] |= ( (UncompressedData[ InChunkOffset + index2/2 ] >> ((index2&1)*4) ) & 0x0f ) << ((x&1)*4);
+ ++index2;
+ }
+ InChunkOffset += index2 / 2;
+ index2 = 0;
+
+ AString Converted(ConvertedData, ExpectedSize);
+
+ // Add JSON data afterwards
+ if (UncompressedData.size() > InChunkOffset)
+ {
+ Converted.append( UncompressedData.begin() + InChunkOffset, UncompressedData.end() );
+ }
+
+ // Re-compress data
+ AString CompressedData;
+ {
+ int errorcode = CompressString(Converted.data(), Converted.size(), CompressedData);
+ if (errorcode != Z_OK)
+ {
+ LOGERROR("Error %d compressing data for chunk [%d, %d]",
+ errorcode,
+ Header->m_ChunkX, Header->m_ChunkZ
+ );
+ continue;
+ }
+ }
+
+ // Save into file's cache
+ Header->m_UncompressedSize = Converted.size();
+ Header->m_CompressedSize = CompressedData.size();
+ NewDataContents.append( CompressedData );
+ }
+
+ // Done converting
+ m_DataContents = NewDataContents;
+ m_ChunkVersion = 3;
+ SynchronizeFile();
+
+ LOGINFO("Updated \"%s\" version 2 to version 3", m_FileName.c_str() );
+}
+
+
+
+
+
+bool cWSSCompact::LoadChunkFromData(const cChunkCoords & a_Chunk, int & a_UncompressedSize, const AString & a_Data, cWorld * a_World)
+{
+ // Crude data integrity check:
+ if (a_UncompressedSize < cChunkDef::BlockDataSize)
+ {
+ LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d bytes out of %d needed), erasing",
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ,
+ a_UncompressedSize, cChunkDef::BlockDataSize
+ );
+ EraseChunkData(a_Chunk);
+ return false;
+ }
+
+ // Decompress the data:
+ AString UncompressedData;
+ int errorcode = UncompressString(a_Data.data(), a_Data.size(), UncompressedData, a_UncompressedSize);
+ if (errorcode != Z_OK)
+ {
+ LOGERROR("Error %d decompressing data for chunk [%d, %d]",
+ errorcode,
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ
+ );
+ return false;
+ }
+
+ if (a_UncompressedSize != (int)UncompressedData.size())
+ {
+ LOGWARNING("Uncompressed data size differs (exp %d bytes, got %d) for chunk [%d, %d]",
+ a_UncompressedSize, UncompressedData.size(),
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ
+ );
+ return false;
+ }
+
+ cEntityList Entities;
+ cBlockEntityList BlockEntities;
+ bool IsLightValid = false;
+
+ if (a_UncompressedSize > cChunkDef::BlockDataSize)
+ {
+ Json::Value root; // will contain the root value after parsing.
+ Json::Reader reader;
+ if ( !reader.parse( UncompressedData.data() + cChunkDef::BlockDataSize, root, false ) )
+ {
+ LOGERROR("Failed to parse trailing JSON in chunk [%d, %d]!",
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ
+ );
+ }
+ else
+ {
+ LoadEntitiesFromJson(root, Entities, BlockEntities, a_World);
+ IsLightValid = root.get("IsLightValid", false).asBool();
+ }
+ }
+
+ BLOCKTYPE * BlockData = (BLOCKTYPE *)UncompressedData.data();
+ NIBBLETYPE * MetaData = (NIBBLETYPE *)(BlockData + cChunkDef::MetaOffset);
+ NIBBLETYPE * BlockLight = (NIBBLETYPE *)(BlockData + cChunkDef::LightOffset);
+ NIBBLETYPE * SkyLight = (NIBBLETYPE *)(BlockData + cChunkDef::SkyLightOffset);
+
+ a_World->SetChunkData(
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ,
+ BlockData, MetaData,
+ IsLightValid ? BlockLight : NULL,
+ IsLightValid ? SkyLight : NULL,
+ NULL, NULL,
+ Entities, BlockEntities,
+ false
+ );
+
+ return true;
+}
+
+
+
+
+
+bool cWSSCompact::cPAKFile::EraseChunkData(const cChunkCoords & a_Chunk)
+{
+ int ChunkX = a_Chunk.m_ChunkX;
+ int ChunkZ = a_Chunk.m_ChunkZ;
+ int Offset = 0;
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ if (((*itr)->m_ChunkX == ChunkX) && ((*itr)->m_ChunkZ == ChunkZ))
+ {
+ m_DataContents.erase(Offset, (*itr)->m_CompressedSize);
+ delete *itr;
+ itr = m_ChunkHeaders.erase(itr);
+ return true;
+ }
+ Offset += (*itr)->m_CompressedSize;
+ }
+
+ return false;
+}
+
+
+
+
+
+bool cWSSCompact::cPAKFile::SaveChunkToData(const cChunkCoords & a_Chunk, cWorld * a_World)
+{
+ // Serialize the chunk:
+ cJsonChunkSerializer Serializer;
+ if (!a_World->GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, Serializer))
+ {
+ // Chunk not valid
+ LOG("cWSSCompact: Trying to save chunk [%d, %d, %d] that has no data, ignoring request.", a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ);
+ return false;
+ }
+
+ AString Data;
+ Data.assign((const char *)Serializer.GetBlockData(), cChunkDef::BlockDataSize);
+ if (Serializer.HasJsonData())
+ {
+ AString JsonData;
+ Json::StyledWriter writer;
+ JsonData = writer.write(Serializer.GetRoot());
+ Data.append(JsonData);
+ }
+
+ // Compress the data:
+ AString CompressedData;
+ int errorcode = CompressString(Data.data(), Data.size(), CompressedData);
+ if ( errorcode != Z_OK )
+ {
+ LOGERROR("Error %i compressing data for chunk [%d, %d, %d]", errorcode, a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ);
+ return false;
+ }
+
+ // Erase any existing data for the chunk:
+ EraseChunkData(a_Chunk);
+
+ // Save the header:
+ sChunkHeader * Header = new sChunkHeader;
+ if (Header == NULL)
+ {
+ LOGWARNING("Cannot create a new chunk header to save chunk [%d, %d, %d]", a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ);
+ return false;
+ }
+ Header->m_CompressedSize = (int)CompressedData.size();
+ Header->m_ChunkX = a_Chunk.m_ChunkX;
+ Header->m_ChunkZ = a_Chunk.m_ChunkZ;
+ Header->m_UncompressedSize = (int)Data.size();
+ m_ChunkHeaders.push_back(Header);
+
+ m_DataContents.append(CompressedData.data(), CompressedData.size());
+
+ m_NumDirty++;
+ return true;
+}
+
+
+
+
+
+#define WRITE(Var) \
+ if (f.Write(&Var, sizeof(Var)) != sizeof(Var)) \
+ { \
+ LOGERROR("cWSSCompact: ERROR writing %s to file \"%s\" (line %d); file offset %d", #Var, m_FileName.c_str(), __LINE__, f.Tell()); \
+ return; \
+ }
+
+void cWSSCompact::cPAKFile::SynchronizeFile(void)
+{
+ cFile f;
+ if (!f.Open(m_FileName, cFile::fmWrite))
+ {
+ LOGERROR("Cannot open PAK file \"%s\" for writing", m_FileName.c_str());
+ return;
+ }
+
+ WRITE(m_PakVersion);
+ WRITE(m_ChunkVersion);
+ short NumChunks = (short)m_ChunkHeaders.size();
+ WRITE(NumChunks);
+ for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr)
+ {
+ WRITE(**itr);
+ }
+ if (f.Write(m_DataContents.data(), m_DataContents.size()) != (int)m_DataContents.size())
+ {
+ LOGERROR("cWSSCompact: ERROR writing chunk contents to file \"%s\" (line %d); file offset %d", m_FileName.c_str(), __LINE__, f.Tell());
+ return;
+ }
+ m_NumDirty = 0;
+}
+
+
+
+
diff --git a/source/WorldStorage/WSSCompact.h b/src/WorldStorage/WSSCompact.h
index e6a013eaf..e6a013eaf 100644
--- a/source/WorldStorage/WSSCompact.h
+++ b/src/WorldStorage/WSSCompact.h
diff --git a/source/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp
index f290ec128..f290ec128 100644
--- a/source/WorldStorage/WorldStorage.cpp
+++ b/src/WorldStorage/WorldStorage.cpp
diff --git a/src/WorldStorage/WorldStorage.h b/src/WorldStorage/WorldStorage.h
new file mode 100644
index 000000000..98e7e1686
--- /dev/null
+++ b/src/WorldStorage/WorldStorage.h
@@ -0,0 +1,135 @@
+
+// WorldStorage.h
+
+// Interfaces to the cWorldStorage class representing the chunk loading / saving thread
+// This class decides which storage schema to use for saving; it queries all available schemas for loading
+// Also declares the base class for all storage schemas, cWSSchema
+// Helper serialization class cJsonChunkSerializer is declared as well
+
+
+
+
+
+#pragma once
+#ifndef WORLDSTORAGE_H_INCLUDED
+#define WORLDSTORAGE_H_INCLUDED
+
+#include "../ChunkDef.h"
+#include "../OSSupport/IsThread.h"
+#include "json/json.h"
+
+
+
+
+
+// fwd:
+class cWorld;
+
+
+
+
+
+/// Interface that all the world storage schemas need to implement
+class cWSSchema abstract
+{
+public:
+ cWSSchema(cWorld * a_World) : m_World(a_World) {}
+ virtual ~cWSSchema() {} // Force the descendants' destructors to be virtual
+
+ virtual bool LoadChunk(const cChunkCoords & a_Chunk) = 0;
+ virtual bool SaveChunk(const cChunkCoords & a_Chunk) = 0;
+ virtual const AString GetName(void) const = 0;
+
+protected:
+
+ cWorld * m_World;
+} ;
+
+typedef std::list<cWSSchema *> cWSSchemaList;
+
+
+
+
+
+/// The actual world storage class
+class cWorldStorage :
+ public cIsThread
+{
+ typedef cIsThread super;
+
+public:
+
+ cWorldStorage(void);
+ ~cWorldStorage();
+
+ void QueueLoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ, bool a_Generate); // Queues the chunk for loading; if not loaded, the chunk will be generated if a_Generate is true
+ void QueueSaveChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
+
+ /// Signals that a message should be output to the console when all the chunks have been saved
+ void QueueSavedMessage(void);
+
+ /// Loads the chunk specified; returns true on success, false on failure
+ bool LoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
+
+ void UnqueueLoad(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
+ void UnqueueSave(const cChunkCoords & a_Chunk);
+
+ bool Start(cWorld * a_World, const AString & a_StorageSchemaName); // Hide the cIsThread's Start() method, we need to provide args
+ void Stop(void); // Hide the cIsThread's Stop() method, we need to signal the event
+ void WaitForFinish(void);
+ void WaitForQueuesEmpty(void);
+
+ int GetLoadQueueLength(void);
+ int GetSaveQueueLength(void);
+
+protected:
+
+ struct sChunkLoad
+ {
+ int m_ChunkX;
+ int m_ChunkY;
+ int m_ChunkZ;
+ bool m_Generate; // If true, the chunk will be generated if it cannot be loaded
+
+ sChunkLoad(int a_ChunkX, int a_ChunkY, int a_ChunkZ, bool a_Generate) : m_ChunkX(a_ChunkX), m_ChunkY(a_ChunkY), m_ChunkZ(a_ChunkZ), m_Generate(a_Generate) {}
+ } ;
+
+ typedef std::list<sChunkLoad> sChunkLoadQueue;
+
+ cWorld * m_World;
+ AString m_StorageSchemaName;
+
+ // Both queues are locked by the same CS
+ cCriticalSection m_CSQueues;
+ sChunkLoadQueue m_LoadQueue;
+ cChunkCoordsList m_SaveQueue;
+
+ cEvent m_Event; // Set when there's any addition to the queues
+ cEvent m_evtRemoved; // Set when an item has been removed from the queue, either by the worker thread or the Unqueue methods
+
+ /// All the storage schemas (all used for loading)
+ cWSSchemaList m_Schemas;
+
+ /// The one storage schema used for saving
+ cWSSchema * m_SaveSchema;
+
+ void InitSchemas(void);
+
+ virtual void Execute(void) override;
+
+ /// Loads one chunk from the queue (if any queued); returns true if there are more chunks in the load queue
+ bool LoadOneChunk(void);
+
+ /// Saves one chunk from the queue (if any queued); returns true if there are more chunks in the save queue
+ bool SaveOneChunk(void);
+} ;
+
+
+
+
+
+#endif // WORLDSTORAGE_H_INCLUDED
+
+
+
+