summaryrefslogtreecommitdiffstats
path: root/source/WorldStorage
diff options
context:
space:
mode:
authormadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-03-09 15:35:43 +0100
committermadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-03-09 15:35:43 +0100
commit75937077136b9ec2eaba49c74543bdee323f68ff (patch)
treeee8b20b2b3866118dd1187cafa730a9c4e90b82a /source/WorldStorage
parentCore: added PortsIPv6 setting to webadmin (contributed by STR_Warrior) (diff)
downloadcuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar.gz
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar.bz2
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar.lz
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar.xz
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.tar.zst
cuberite-75937077136b9ec2eaba49c74543bdee323f68ff.zip
Diffstat (limited to 'source/WorldStorage')
-rw-r--r--source/WorldStorage/FastNBT.cpp7
-rw-r--r--source/WorldStorage/FastNBT.h4
-rw-r--r--source/WorldStorage/NBTChunkSerializer.cpp417
-rw-r--r--source/WorldStorage/NBTChunkSerializer.h101
-rw-r--r--source/WorldStorage/WSSAnvil.cpp518
-rw-r--r--source/WorldStorage/WSSAnvil.h17
6 files changed, 775 insertions, 289 deletions
diff --git a/source/WorldStorage/FastNBT.cpp b/source/WorldStorage/FastNBT.cpp
index 4a9a9df9f..d5dc2b923 100644
--- a/source/WorldStorage/FastNBT.cpp
+++ b/source/WorldStorage/FastNBT.cpp
@@ -385,9 +385,10 @@ void cFastNBTWriter::BeginList(const AString & a_Name, eTagType a_ChildrenType)
m_Result.append(4, (char)0);
++m_CurrentStack;
- m_Stack[m_CurrentStack].m_Type = TAG_List;
- m_Stack[m_CurrentStack].m_Pos = m_Result.size() - 4;
- m_Stack[m_CurrentStack].m_Count = 0;
+ m_Stack[m_CurrentStack].m_Type = TAG_List;
+ m_Stack[m_CurrentStack].m_Pos = m_Result.size() - 4;
+ m_Stack[m_CurrentStack].m_Count = 0;
+ m_Stack[m_CurrentStack].m_ItemType = a_ChildrenType;
}
diff --git a/source/WorldStorage/FastNBT.h b/source/WorldStorage/FastNBT.h
index 5a4b69866..1cae4879f 100644
--- a/source/WorldStorage/FastNBT.h
+++ b/source/WorldStorage/FastNBT.h
@@ -247,6 +247,7 @@ protected:
int m_Type; // TAG_Compound or TAG_List
int m_Pos; // for TAG_List, the position of the list count
int m_Count; // for TAG_List, the element count
+ eTagType m_ItemType; // for TAG_List, the element type
} ;
static const int MAX_STACK = 50; // Highliy doubtful that an NBT would be constructed this many levels deep
@@ -263,6 +264,9 @@ protected:
inline void TagCommon(const AString & a_Name, eTagType a_Type)
{
+ // If we're directly inside a list, check that the list is of the correct type:
+ ASSERT((m_Stack[m_CurrentStack].m_Type != TAG_List) || (m_Stack[m_CurrentStack].m_ItemType == a_Type));
+
if (IsStackTopCompound())
{
// Compound: add the type and name:
diff --git a/source/WorldStorage/NBTChunkSerializer.cpp b/source/WorldStorage/NBTChunkSerializer.cpp
new file mode 100644
index 000000000..f41bd5e97
--- /dev/null
+++ b/source/WorldStorage/NBTChunkSerializer.cpp
@@ -0,0 +1,417 @@
+
+// NBTChunkSerializer.cpp
+
+
+#include "Globals.h"
+#include "NBTChunkSerializer.h"
+#include "../BlockID.h"
+#include "../ChestEntity.h"
+#include "../DispenserEntity.h"
+#include "../FurnaceEntity.h"
+#include "../SignEntity.h"
+#include "../NoteEntity.h"
+#include "../JukeboxEntity.h"
+#include "../Item.h"
+#include "../StringCompression.h"
+#include "../Entity.h"
+#include "../OSSupport/MakeDir.h"
+#include "FastNBT.h"
+#include "../FallingBlock.h"
+#include "../Minecart.h"
+#include "../Mobs/Monster.h"
+#include "../Pickup.h"
+
+
+
+
+cNBTChunkSerializer::cNBTChunkSerializer(cFastNBTWriter & a_Writer) :
+ m_BiomesAreValid(false),
+ m_Writer(a_Writer),
+ m_IsTagOpen(false),
+ m_HasHadEntity(false),
+ m_HasHadBlockEntity(false),
+ m_IsLightValid(false)
+{
+}
+
+
+
+
+
+void cNBTChunkSerializer::Finish(void)
+{
+ if (m_IsTagOpen)
+ {
+ m_Writer.EndList();
+ }
+
+ // If light not valid, reset it to all zeroes:
+ if (!m_IsLightValid)
+ {
+ memset(m_BlockLight, 0, sizeof(m_BlockLight));
+ memset(m_BlockSkyLight, 0, sizeof(m_BlockSkyLight));
+ }
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AString & a_CompoundName)
+{
+ m_Writer.BeginCompound(a_CompoundName);
+ m_Writer.AddShort("id", (short)(a_Item.m_ItemType));
+ m_Writer.AddShort("Damage", a_Item.m_ItemDamage);
+ m_Writer.AddByte ("Count", a_Item.m_ItemCount);
+ if (a_Slot >= 0)
+ {
+ m_Writer.AddByte ("Slot", (unsigned char)a_Slot);
+ }
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID)
+{
+ m_Writer.AddInt ("x", a_Entity->GetPosX());
+ m_Writer.AddInt ("y", a_Entity->GetPosY());
+ m_Writer.AddInt ("z", a_Entity->GetPosZ());
+ m_Writer.AddString("id", a_EntityTypeID);
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddChestEntity(cChestEntity * a_Entity)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Entity, "Chest");
+ m_Writer.BeginList("Items", TAG_Compound);
+ for (int i = 0; i < cChestEntity::c_ChestHeight * cChestEntity::c_ChestWidth; i++)
+ {
+ const cItem * Item = a_Entity->GetSlot(i);
+ if ((Item == NULL) || Item->IsEmpty())
+ {
+ continue;
+ }
+ AddItem(*Item, i);
+ } // for i - chest slots[]
+ m_Writer.EndList();
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddDispenserEntity(cDispenserEntity * a_Entity)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Entity, "Trap");
+ m_Writer.BeginList("Items", TAG_Compound);
+ for (int i = 0; i < 9; i++)
+ {
+ const cItem * Item = a_Entity->GetSlot(i);
+ if ((Item == NULL) || Item->IsEmpty())
+ {
+ continue;
+ }
+ AddItem(*Item, i);
+ } // for i - contents[]
+ m_Writer.EndList();
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddFurnaceEntity(cFurnaceEntity * a_Furnace)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Furnace, "Furnace");
+ m_Writer.BeginList("Items", TAG_Compound);
+ AddItem(*(a_Furnace->GetSlot(0)), 0);
+ AddItem(*(a_Furnace->GetSlot(1)), 1);
+ AddItem(*(a_Furnace->GetSlot(2)), 2);
+ m_Writer.EndList();
+ m_Writer.AddShort("BurnTime", (Int16)(a_Furnace->GetTimeToBurn() / 50.0));
+ m_Writer.AddShort("CookTime", (Int16)(a_Furnace->GetTimeCooked() / 50.0));
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddSignEntity(cSignEntity * a_Sign)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Sign, "Sign");
+ m_Writer.AddString("Text1", a_Sign->GetLine(0));
+ m_Writer.AddString("Text2", a_Sign->GetLine(1));
+ m_Writer.AddString("Text3", a_Sign->GetLine(2));
+ m_Writer.AddString("Text4", a_Sign->GetLine(3));
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddNoteEntity(cNoteEntity * a_Note)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Note, "Music");
+ m_Writer.AddByte("note", a_Note->GetPitch());
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddJukeboxEntity(cJukeboxEntity * a_Jukebox)
+{
+ m_Writer.BeginCompound("");
+ AddBasicTileEntity(a_Jukebox, "RecordPlayer");
+ m_Writer.AddInt("Record", a_Jukebox->GetRecord());
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddBasicEntity(cEntity * a_Entity, const AString & a_ClassName)
+{
+ m_Writer.AddString("id", a_ClassName);
+ m_Writer.BeginList("Pos", TAG_Double);
+ m_Writer.AddDouble("", a_Entity->GetPosX());
+ m_Writer.AddDouble("", a_Entity->GetPosY());
+ m_Writer.AddDouble("", a_Entity->GetPosZ());
+ m_Writer.EndList();
+ m_Writer.BeginList("Motion", TAG_Double);
+ m_Writer.AddDouble("", a_Entity->GetSpeedX());
+ m_Writer.AddDouble("", a_Entity->GetSpeedY());
+ m_Writer.AddDouble("", a_Entity->GetSpeedZ());
+ m_Writer.EndList();
+ m_Writer.BeginList("Rotation", TAG_Double);
+ m_Writer.AddDouble("", a_Entity->GetRotation());
+ m_Writer.AddDouble("", a_Entity->GetPitch());
+ m_Writer.EndList();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddFallingBlockEntity(cFallingBlock * a_FallingBlock)
+{
+ m_Writer.BeginCompound("");
+ AddBasicEntity(a_FallingBlock, "FallingSand");
+ m_Writer.AddInt("TileID", a_FallingBlock->GetBlockType());
+ m_Writer.AddByte("Data", a_FallingBlock->GetBlockMeta());
+ m_Writer.AddByte("Time", 1); // Unused in MCServer, Vanilla said to need nonzero
+ m_Writer.AddByte("DropItem", 1);
+ m_Writer.AddByte("HurtEntities", a_FallingBlock->GetBlockType() == E_BLOCK_ANVIL);
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddMinecartEntity(cMinecart * a_Minecart)
+{
+ const char * EntityClass = NULL;
+ switch (a_Minecart->GetPayload())
+ {
+ case cMinecart::mpNone: EntityClass = "MinecarRideable"; break;
+ case cMinecart::mpChest: EntityClass = "MinecartChest"; break;
+ case cMinecart::mpFurnace: EntityClass = "MinecartFurnace"; break;
+ default:
+ {
+ ASSERT(!"Unhandled minecart payload type");
+ return;
+ }
+ } // switch (payload)
+
+ m_Writer.BeginCompound("");
+ AddBasicEntity(a_Minecart, EntityClass);
+ switch (a_Minecart->GetPayload())
+ {
+ case cMinecart::mpChest:
+ {
+ // Add chest contents into the Items tag:
+ AddMinecartChestContents((cMinecartWithChest *)a_Minecart);
+ break;
+ }
+
+ case cMinecart::mpFurnace:
+ {
+ // TODO: Add "Push" and "Fuel" tags
+ break;
+ }
+ } // switch (Payload)
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster)
+{
+ // TODO
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddPickupEntity(cPickup * a_Pickup)
+{
+ m_Writer.BeginCompound("");
+ AddBasicEntity(a_Pickup, "Item");
+ AddItem(a_Pickup->GetItem(), -1, "Item");
+ m_Writer.AddShort("Health", a_Pickup->GetHealth());
+ m_Writer.AddShort("Age", a_Pickup->GetAge());
+ m_Writer.EndCompound();
+}
+
+
+
+
+
+void cNBTChunkSerializer::AddMinecartChestContents(cMinecartWithChest * a_Minecart)
+{
+ m_Writer.BeginList("Items", TAG_Compound);
+ for (int i = 0; i < cMinecartWithChest::NumSlots; i++)
+ {
+ const cItem & Item = a_Minecart->GetItem(i);
+ if (Item.IsEmpty())
+ {
+ continue;
+ }
+ AddItem(Item, i);
+ }
+ m_Writer.EndList();
+}
+
+
+
+
+
+bool cNBTChunkSerializer::LightIsValid(bool a_IsLightValid)
+{
+ m_IsLightValid = a_IsLightValid;
+ return a_IsLightValid; // We want lighting only if it's valid, otherwise don't bother
+}
+
+
+
+
+
+void cNBTChunkSerializer::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap)
+{
+ memcpy(m_Biomes, a_BiomeMap, sizeof(m_Biomes));
+ for (int i = 0; i < ARRAYCOUNT(m_Biomes); i++)
+ {
+ if ((*a_BiomeMap)[i] < 255)
+ {
+ // Normal MC biome, copy as-is:
+ m_VanillaBiomes[i] = (unsigned char)((*a_BiomeMap)[i]);
+ }
+ else
+ {
+ // TODO: MCS-specific biome, need to map to some basic MC biome:
+ ASSERT(!"Unimplemented MCS-specific biome");
+ return;
+ }
+ } // for i - m_BiomeMap[]
+ m_BiomesAreValid = true;
+}
+
+
+
+
+
+void cNBTChunkSerializer::Entity(cEntity * a_Entity)
+{
+ // Add entity into NBT:
+ if (m_IsTagOpen)
+ {
+ if (!m_HasHadEntity)
+ {
+ m_Writer.EndList();
+ m_Writer.BeginList("Entities", TAG_Compound);
+ }
+ }
+ else
+ {
+ m_Writer.BeginList("Entities", TAG_Compound);
+ }
+ m_IsTagOpen = true;
+ m_HasHadEntity = true;
+
+ switch (a_Entity->GetEntityType())
+ {
+ case cEntity::etFallingBlock: AddFallingBlockEntity((cFallingBlock *)a_Entity); break;
+ case cEntity::etMinecart: AddMinecartEntity ((cMinecart *) a_Entity); break;
+ case cEntity::etMonster: AddMonsterEntity ((cMonster *) a_Entity); break;
+ case cEntity::etPickup: AddPickupEntity ((cPickup *) a_Entity); break;
+ case cEntity::etPlayer: return; // Players aren't saved into the world
+ default:
+ {
+ ASSERT(!"Unhandled entity type is being saved");
+ break;
+ }
+ }
+}
+
+
+
+
+
+void cNBTChunkSerializer::BlockEntity(cBlockEntity * a_Entity)
+{
+ if (m_IsTagOpen)
+ {
+ if (!m_HasHadBlockEntity)
+ {
+ m_Writer.EndList();
+ m_Writer.BeginList("TileEntities", TAG_Compound);
+ }
+ }
+ else
+ {
+ m_Writer.BeginList("TileEntities", TAG_Compound);
+ }
+ m_IsTagOpen = true;
+
+ // Add tile-entity into NBT:
+ switch (a_Entity->GetBlockType())
+ {
+ case E_BLOCK_CHEST: AddChestEntity ((cChestEntity *) a_Entity); break;
+ case E_BLOCK_DISPENSER: AddDispenserEntity ((cDispenserEntity *) a_Entity); break;
+ case E_BLOCK_FURNACE: AddFurnaceEntity ((cFurnaceEntity *) a_Entity); break;
+ case E_BLOCK_SIGN_POST:
+ case E_BLOCK_WALLSIGN: AddSignEntity ((cSignEntity *) a_Entity); break;
+ case E_BLOCK_NOTE_BLOCK: AddNoteEntity ((cNoteEntity *) a_Entity); break;
+ case E_BLOCK_JUKEBOX: AddJukeboxEntity ((cJukeboxEntity *) a_Entity); break;
+ default:
+ {
+ ASSERT(!"Unhandled block entity saved into Anvil");
+ }
+ }
+ m_HasHadBlockEntity = true;
+}
+
+
+
+
diff --git a/source/WorldStorage/NBTChunkSerializer.h b/source/WorldStorage/NBTChunkSerializer.h
new file mode 100644
index 000000000..78847dbc0
--- /dev/null
+++ b/source/WorldStorage/NBTChunkSerializer.h
@@ -0,0 +1,101 @@
+
+// NBTChunkSerializer.h
+
+// Declares the cNBTChunkSerializer class that is used for saving individual chunks into NBT format used by Anvil
+
+
+
+
+
+#pragma once
+
+#include "../ChunkDef.h"
+
+
+
+
+
+// fwd:
+class cFastNBTWriter;
+class cEntity;
+class cBlockEntity;
+class cChestEntity;
+class cFurnaceEntity;
+class cDispenserEntity;
+class cSignEntity;
+class cNoteEntity;
+class cJukeboxEntity;
+class cFallingBlock;
+class cMinecart;
+class cMinecartWithChest;
+class cMinecartWithFurnace;
+class cMonster;
+class cPickup;
+
+
+
+
+class cNBTChunkSerializer :
+ public cChunkDataSeparateCollector
+{
+public:
+ cChunkDef::BiomeMap m_Biomes;
+ unsigned char m_VanillaBiomes[cChunkDef::Width * cChunkDef::Width];
+ bool m_BiomesAreValid;
+
+
+ cNBTChunkSerializer(cFastNBTWriter & a_Writer);
+
+ /// Close NBT tags that we've opened
+ void Finish(void);
+
+ bool IsLightValid(void) const {return m_IsLightValid; }
+
+protected:
+
+ /* From cChunkDataSeparateCollector we inherit:
+ - m_BlockTypes[]
+ - m_BlockMetas[]
+ - m_BlockLight[]
+ - m_BlockSkyLight[]
+ */
+
+ cFastNBTWriter & m_Writer;
+
+ bool m_IsTagOpen; // True if a tag has been opened in the callbacks and not yet closed.
+ bool m_HasHadEntity; // True if any Entity has already been received and processed
+ bool m_HasHadBlockEntity; // True if any BlockEntity has already been received and processed
+ bool m_IsLightValid; // True if the chunk lighting is valid
+
+
+ /// Writes an item into the writer, if slot >= 0, adds the Slot tag. The compound is named as requested.
+ void AddItem(const cItem & a_Item, int a_Slot, const AString & a_CompoundName = "");
+
+ // Block entities:
+ void AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID);
+ void AddChestEntity(cChestEntity * a_Entity);
+ void AddDispenserEntity(cDispenserEntity * a_Entity);
+ void AddFurnaceEntity(cFurnaceEntity * a_Furnace);
+ void AddSignEntity(cSignEntity * a_Sign);
+ void AddNoteEntity(cNoteEntity * a_Note);
+ void AddJukeboxEntity(cJukeboxEntity * a_Jukebox);
+ void AddBasicEntity(cEntity * a_Entity, const AString & a_ClassName);
+
+ // Entities:
+ void AddFallingBlockEntity(cFallingBlock * a_FallingBlock);
+ void AddMinecartEntity (cMinecart * a_Minecart);
+ void AddMonsterEntity (cMonster * a_Monster);
+ void AddPickupEntity (cPickup * a_Pickup);
+
+ void AddMinecartChestContents(cMinecartWithChest * a_Minecart);
+
+ // cChunkDataSeparateCollector overrides:
+ virtual bool LightIsValid(bool a_IsLightValid) override;
+ virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) override;
+ virtual void Entity(cEntity * a_Entity) override;
+ virtual void BlockEntity(cBlockEntity * a_Entity) override;
+} ; // class cNBTChunkSerializer
+
+
+
+
diff --git a/source/WorldStorage/WSSAnvil.cpp b/source/WorldStorage/WSSAnvil.cpp
index 70e280a57..de9a3c3ae 100644
--- a/source/WorldStorage/WSSAnvil.cpp
+++ b/source/WorldStorage/WSSAnvil.cpp
@@ -5,6 +5,7 @@
#include "Globals.h"
#include "WSSAnvil.h"
+#include "NBTChunkSerializer.h"
#include "../World.h"
#include "zlib.h"
#include "../BlockID.h"
@@ -19,6 +20,10 @@
#include "../Entity.h"
#include "../OSSupport/MakeDir.h"
#include "FastNBT.h"
+#include "../FallingBlock.h"
+#include "../Minecart.h"
+#include "../Mobs/Monster.h"
+#include "../Pickup.h"
@@ -38,238 +43,6 @@ Since only the header is actually in the memory, this number can be high, but st
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// cNBTChunkSerializer
-
-class cNBTChunkSerializer :
- public cChunkDataSeparateCollector
-{
-public:
- cChunkDef::BiomeMap m_Biomes;
- unsigned char m_VanillaBiomes[cChunkDef::Width * cChunkDef::Width];
- bool m_BiomesAreValid;
-
-
- cNBTChunkSerializer(cFastNBTWriter & a_Writer) :
- m_BiomesAreValid(false),
- m_Writer(a_Writer),
- m_IsTagOpen(false),
- m_HasHadEntity(false),
- m_HasHadBlockEntity(false),
- m_IsLightValid(false)
- {
- }
-
-
- /// Close NBT tags that we've opened
- void Finish(void)
- {
- if (m_IsTagOpen)
- {
- m_Writer.EndList();
- }
-
- // If light not valid, reset it to all zeroes:
- if (!m_IsLightValid)
- {
- memset(m_BlockLight, 0, sizeof(m_BlockLight));
- memset(m_BlockSkyLight, 0, sizeof(m_BlockSkyLight));
- }
- }
-
-
- bool IsLightValid(void) const {return m_IsLightValid; }
-
-protected:
-
- /* From cChunkDataSeparateCollector we inherit:
- - m_BlockTypes[]
- - m_BlockMetas[]
- - m_BlockLight[]
- - m_BlockSkyLight[]
- */
-
- cFastNBTWriter & m_Writer;
-
- bool m_IsTagOpen; // True if a tag has been opened in the callbacks and not yet closed.
- bool m_HasHadEntity; // True if any Entity has already been received and processed
- bool m_HasHadBlockEntity; // True if any BlockEntity has already been received and processed
- bool m_IsLightValid; // True if the chunk lighting is valid
-
-
- void AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID)
- {
- m_Writer.AddInt ("x", a_Entity->GetPosX());
- m_Writer.AddInt ("y", a_Entity->GetPosY());
- m_Writer.AddInt ("z", a_Entity->GetPosZ());
- m_Writer.AddString("id", a_EntityTypeID);
- }
-
-
- void AddItem(const cItem * a_Item, int a_Slot)
- {
- m_Writer.BeginCompound("");
- m_Writer.AddShort("id", (short)(a_Item->m_ItemType));
- m_Writer.AddShort("Damage", a_Item->m_ItemDamage);
- m_Writer.AddByte ("Count", a_Item->m_ItemCount);
- m_Writer.AddByte ("Slot", (unsigned char)a_Slot);
- m_Writer.EndCompound();
- }
-
-
- void AddChestEntity(cChestEntity * a_Entity)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Entity, "Chest");
- m_Writer.BeginList("Items", TAG_Compound);
- for (int i = 0; i < cChestEntity::c_ChestHeight * cChestEntity::c_ChestWidth; i++)
- {
- const cItem * Item = a_Entity->GetSlot(i);
- if ((Item == NULL) || Item->IsEmpty())
- {
- continue;
- }
- AddItem(Item, i);
- }
- m_Writer.EndList();
- m_Writer.EndCompound();
- }
-
-
- void AddDispenserEntity(cDispenserEntity * a_Entity)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Entity, "Trap");
- m_Writer.BeginList("Items", TAG_Compound);
- for (int i = 0; i < 9; i++)
- {
- const cItem * Item = a_Entity->GetSlot(i);
- if ((Item == NULL) || Item->IsEmpty())
- {
- continue;
- }
- AddItem(Item, i);
- }
- m_Writer.EndList();
- m_Writer.EndCompound();
- }
-
-
- void AddFurnaceEntity(cFurnaceEntity * a_Furnace)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Furnace, "Furnace");
- m_Writer.BeginList("Items", TAG_Compound);
- AddItem(a_Furnace->GetSlot(0), 0);
- AddItem(a_Furnace->GetSlot(1), 1);
- AddItem(a_Furnace->GetSlot(2), 2);
- m_Writer.EndList();
- m_Writer.AddShort("BurnTime", (Int16)(a_Furnace->GetTimeToBurn() / 50.0));
- m_Writer.AddShort("CookTime", (Int16)(a_Furnace->GetTimeCooked() / 50.0));
- m_Writer.EndCompound();
- }
-
-
- void AddSignEntity(cSignEntity * a_Sign)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Sign, "Sign");
- m_Writer.AddString("Text1", a_Sign->GetLine(0));
- m_Writer.AddString("Text2", a_Sign->GetLine(1));
- m_Writer.AddString("Text3", a_Sign->GetLine(2));
- m_Writer.AddString("Text4", a_Sign->GetLine(3));
- m_Writer.EndCompound();
- }
-
- void AddNoteEntity(cNoteEntity * a_Note)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Note, "Music");
- m_Writer.AddByte("note", a_Note->GetPitch());
- m_Writer.EndCompound();
- }
-
- void AddJukeboxEntity(cJukeboxEntity * a_Jukebox)
- {
- m_Writer.BeginCompound("");
- AddBasicTileEntity(a_Jukebox, "RecordPlayer");
- m_Writer.AddInt("Record", a_Jukebox->GetRecord());
- m_Writer.EndCompound();
- }
-
- virtual bool LightIsValid(bool a_IsLightValid) override
- {
- m_IsLightValid = a_IsLightValid;
- return a_IsLightValid; // We want lighting only if it's valid, otherwise don't bother
- }
-
-
- virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) override
- {
- memcpy(m_Biomes, a_BiomeMap, sizeof(m_Biomes));
- for (int i = 0; i < ARRAYCOUNT(m_Biomes); i++)
- {
- if ((*a_BiomeMap)[i] < 255)
- {
- // Normal MC biome, copy as-is:
- m_VanillaBiomes[i] = (unsigned char)((*a_BiomeMap)[i]);
- }
- else
- {
- // TODO: MCS-specific biome, need to map to some basic MC biome:
- ASSERT(!"Unimplemented MCS-specific biome");
- return;
- }
- } // for i - m_BiomeMap[]
- m_BiomesAreValid = true;
- }
-
-
- virtual void Entity(cEntity * a_Entity) override
- {
- // TODO: Add entity into NBT:
- }
-
-
- virtual void BlockEntity(cBlockEntity * a_Entity)
- {
- if (m_IsTagOpen)
- {
- if (!m_HasHadBlockEntity)
- {
- m_Writer.EndList();
- m_Writer.BeginList("TileEntities", TAG_Compound);
- }
- }
- else
- {
- m_Writer.BeginList("TileEntities", TAG_Compound);
- }
- m_IsTagOpen = true;
-
- // Add tile-entity into NBT:
- switch (a_Entity->GetBlockType())
- {
- case E_BLOCK_CHEST: AddChestEntity ((cChestEntity *) a_Entity); break;
- case E_BLOCK_DISPENSER: AddDispenserEntity ((cDispenserEntity *) a_Entity); break;
- case E_BLOCK_FURNACE: AddFurnaceEntity ((cFurnaceEntity *) a_Entity); break;
- case E_BLOCK_SIGN_POST:
- case E_BLOCK_WALLSIGN: AddSignEntity ((cSignEntity *) a_Entity); break;
- case E_BLOCK_NOTE_BLOCK: AddNoteEntity ((cNoteEntity *) a_Entity); break;
- case E_BLOCK_JUKEBOX: AddJukeboxEntity ((cJukeboxEntity *) a_Entity); break;
- default:
- {
- ASSERT(!"Unhandled block entity saved into Anvil");
- }
- }
- m_HasHadBlockEntity = true;
- }
-} ; // class cNBTChunkSerializer
-
-
-
-
-
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWSSAnvil:
cWSSAnvil::cWSSAnvil(cWorld * a_World) :
@@ -378,6 +151,9 @@ bool cWSSAnvil::SetChunkData(const cChunkCoords & a_Chunk, const AString & a_Dat
{
return false;
}
+ LOGD("Saving chunk [%d, %d] into region file %s",
+ a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, File->GetFileName().c_str()
+ );
return File->SetChunkData(a_Chunk, a_Data);
}
@@ -392,6 +168,10 @@ cWSSAnvil::cMCAFile * cWSSAnvil::LoadMCAFile(const cChunkCoords & a_Chunk)
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)
@@ -478,6 +258,7 @@ bool cWSSAnvil::SaveChunkToData(const cChunkCoords & a_Chunk, AString & a_Data)
return false;
}
Writer.Finish();
+
CompressString(Writer.GetResult().data(), Writer.GetResult().size(), a_Data);
return true;
}
@@ -719,9 +500,26 @@ cChunkDef::BiomeMap * cWSSAnvil::LoadBiomeMapFromNBT(cChunkDef::BiomeMap * a_Bio
-void cWSSAnvil::LoadEntitiesFromNBT(cEntityList & a_Entitites, const cParsedNBT & a_NBT, int a_TagIdx)
+void cWSSAnvil::LoadEntitiesFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
{
- // TODO: Load the entities
+ 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[]
}
@@ -778,6 +576,37 @@ void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntityList & a_BlockEntities, con
+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);
+
+ // TODO: enchantments and other item properties
+ return true;
+}
+
+
+
+
+
void cWSSAnvil::LoadChestFromNBT(cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx)
{
ASSERT(a_NBT.GetType(a_TagIdx) == TAG_Compound);
@@ -800,25 +629,10 @@ void cWSSAnvil::LoadChestFromNBT(cBlockEntityList & a_BlockEntities, const cPars
continue;
}
cItem Item;
- int ID = a_NBT.FindChildByName(Child, "id");
- if ((ID < 0) || (a_NBT.GetType(ID) != TAG_Short))
- {
- continue;
- }
- Item.m_ItemType = (ENUM_ITEM_ID)(a_NBT.GetShort(ID));
- int Damage = a_NBT.FindChildByName(Child, "Damage");
- if ((Damage < 0) || (a_NBT.GetType(Damage) != TAG_Short))
- {
- continue;
- }
- Item.m_ItemDamage = a_NBT.GetShort(Damage);
- int Count = a_NBT.FindChildByName(Child, "Count");
- if ((Count < 0) || (a_NBT.GetType(Count) != TAG_Byte))
+ if (LoadItemFromNBT(Item, a_NBT, Child))
{
- continue;
+ Chest->SetSlot(a_NBT.GetByte(Slot), Item);
}
- Item.m_ItemCount = a_NBT.GetByte(Count);
- Chest->SetSlot(a_NBT.GetByte(Slot), Item);
} // for itr - ItemDefs[]
a_BlockEntities.push_back(Chest.release());
}
@@ -849,25 +663,10 @@ void cWSSAnvil::LoadDispenserFromNBT(cBlockEntityList & a_BlockEntities, const c
continue;
}
cItem Item;
- int ID = a_NBT.FindChildByName(Child, "id");
- if ((ID < 0) || (a_NBT.GetType(ID) != TAG_Short))
- {
- continue;
- }
- Item.m_ItemType = (ENUM_ITEM_ID)(a_NBT.GetShort(ID));
- int Damage = a_NBT.FindChildByName(Child, "Damage");
- if ((Damage < 0) || (a_NBT.GetType(Damage) != TAG_Short))
+ if (LoadItemFromNBT(Item, a_NBT, Child))
{
- continue;
- }
- Item.m_ItemDamage = a_NBT.GetShort(Damage);
- int Count = a_NBT.FindChildByName(Child, "Count");
- if ((Count < 0) || (a_NBT.GetType(Count) != TAG_Byte))
- {
- continue;
+ Dispenser->SetSlot(a_NBT.GetByte(Slot), Item);
}
- Item.m_ItemCount = a_NBT.GetByte(Count);
- Dispenser->SetSlot(a_NBT.GetByte(Slot), Item);
} // for itr - ItemDefs[]
a_BlockEntities.push_back(Dispenser.release());
}
@@ -898,25 +697,10 @@ void cWSSAnvil::LoadFurnaceFromNBT(cBlockEntityList & a_BlockEntities, const cPa
continue;
}
cItem Item;
- int ID = a_NBT.FindChildByName(Child, "id");
- if ((ID < 0) || (a_NBT.GetType(ID) != TAG_Short))
- {
- continue;
- }
- Item.m_ItemType = (ENUM_ITEM_ID)(a_NBT.GetShort(ID));
- int Damage = a_NBT.FindChildByName(Child, "Damage");
- if ((Damage < 0) || (a_NBT.GetType(Damage) != TAG_Short))
- {
- continue;
- }
- Item.m_ItemDamage = a_NBT.GetShort(Damage);
- int Count = a_NBT.FindChildByName(Child, "Count");
- if ((Count < 0) || (a_NBT.GetType(Count) != TAG_Byte))
+ if (LoadItemFromNBT(Item, a_NBT, Child))
{
- continue;
+ Furnace->SetSlot(a_NBT.GetByte(Slot), Item);
}
- Item.m_ItemCount = a_NBT.GetByte(Count);
- Furnace->SetSlot(a_NBT.GetByte(Slot), Item);
} // for itr - ItemDefs[]
int BurnTime = a_NBT.FindChildByName(a_TagIdx, "BurnTime");
if (BurnTime >= 0)
@@ -1022,6 +806,159 @@ void cWSSAnvil::LoadJukeboxFromNBT(cBlockEntityList & a_BlockEntities, const cPa
+void cWSSAnvil::LoadEntityFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_EntityTagIdx, const char * a_IDTag, int a_IDTagLength)
+{
+ 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
+ }
+ }
+ 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);
+ }
+ if (strncmp(a_IDTag, "Item", a_IDTagLength) == 0)
+ {
+ LoadPickupFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ }
+ // TODO: other entities
+}
+
+
+
+
+
+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)
+{
+ // TODO
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartCFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ // TODO
+}
+
+
+
+
+
+void cWSSAnvil::LoadMinecartFFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx)
+{
+ // TODO
+}
+
+
+
+
+
+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));
+ if (!LoadEntityBaseFromNBT(*Pickup.get(), a_NBT, a_TagIdx))
+ {
+ return;
+ }
+ a_Entities.push_back(Pickup.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::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");
@@ -1162,6 +1099,7 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri
{
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;
}
@@ -1183,15 +1121,18 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri
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;
}
@@ -1199,9 +1140,14 @@ bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const AStri
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);
- m_File.Seek(0);
+ 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;
}
diff --git a/source/WorldStorage/WSSAnvil.h b/source/WorldStorage/WSSAnvil.h
index cdd225bd5..640edd210 100644
--- a/source/WorldStorage/WSSAnvil.h
+++ b/source/WorldStorage/WSSAnvil.h
@@ -111,6 +111,9 @@ protected:
/// Loads the chunk's BlockEntities from NBT data (a_Tag is the Level\\TileEntities list tag; may be -1)
void LoadBlockEntitiesFromNBT(cBlockEntityList & a_BlockEntitites, const cParsedNBT & a_NBT, int a_Tag);
+ /// Loads a cItem contents from the specified NBT tag; returns true if successful. Doesn't load the Slot tag
+ bool LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_TagIdx);
+
void LoadChestFromNBT (cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx);
void LoadDispenserFromNBT (cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx);
void LoadFurnaceFromNBT (cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx);
@@ -118,6 +121,20 @@ protected:
void LoadNoteFromNBT (cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx);
void LoadJukeboxFromNBT (cBlockEntityList & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx);
+ void LoadEntityFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_EntityTagIdx, const char * a_IDTag, int a_IDTagLength);
+
+ void LoadFallingBlockFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx);
+ void LoadMinecartRFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx);
+ void LoadMinecartCFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx);
+ void LoadMinecartFFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx);
+ void LoadPickupFromNBT (cEntityList & a_Entities, const cParsedNBT & a_NBT, int a_TagIdx);
+
+ /// Loads entity common data from the NBT compound; returns true if successful
+ bool LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx);
+
+ /// Loads an array of doubles of the specified length from the specified NBT list tag a_TagIdx; returns true if successful
+ bool LoadDoublesListFromNBT(double * a_Doubles, int a_NumDoubles, const cParsedNBT & a_NBT, int a_TagIdx);
+
/// Helper function for extracting the X, Y, and Z int subtags of a NBT compound; returns true if successful
bool GetBlockEntityNBTPos(const cParsedNBT & a_NBT, int a_TagIdx, int & a_X, int & a_Y, int & a_Z);