diff options
Diffstat (limited to 'src/WorldStorage')
-rw-r--r-- | src/WorldStorage/FastNBT.h | 14 | ||||
-rw-r--r-- | src/WorldStorage/MapSerializer.cpp | 276 | ||||
-rw-r--r-- | src/WorldStorage/MapSerializer.h | 86 | ||||
-rw-r--r-- | src/WorldStorage/NBTChunkSerializer.cpp | 8 | ||||
-rw-r--r-- | src/WorldStorage/WSSAnvil.cpp | 183 | ||||
-rw-r--r-- | src/WorldStorage/WSSAnvil.h | 7 |
6 files changed, 572 insertions, 2 deletions
diff --git a/src/WorldStorage/FastNBT.h b/src/WorldStorage/FastNBT.h index b84eda1a1..49f97c458 100644 --- a/src/WorldStorage/FastNBT.h +++ b/src/WorldStorage/FastNBT.h @@ -172,8 +172,18 @@ public: inline float GetFloat(int a_Tag) const { ASSERT(m_Tags[a_Tag].m_Type == TAG_Float); - Int32 tmp = GetBEInt(m_Data + m_Tags[a_Tag].m_DataStart); - return *((float *)&tmp); + + // Cause a compile-time error if sizeof(float) != 4 + // If your platform produces a compiler error here, you'll need to add code that manually decodes 32-bit floats + char Check1[5 - sizeof(float)]; // sizeof(float) <= 4 + char Check2[sizeof(float) - 3]; // sizeof(float) >= 4 + UNUSED(Check1); + UNUSED(Check2); + + Int32 i = GetBEInt(m_Data + m_Tags[a_Tag].m_DataStart); + float f; + memcpy(&f, &i, sizeof(f)); + return f; } inline double GetDouble(int a_Tag) const diff --git a/src/WorldStorage/MapSerializer.cpp b/src/WorldStorage/MapSerializer.cpp new file mode 100644 index 000000000..a4a0aab57 --- /dev/null +++ b/src/WorldStorage/MapSerializer.cpp @@ -0,0 +1,276 @@ + +// MapSerializer.cpp + + +#include "Globals.h" +#include "MapSerializer.h" +#include "../StringCompression.h" +#include "zlib/zlib.h" +#include "FastNBT.h" + +#include "../Map.h" +#include "../World.h" + + + + + +cMapSerializer::cMapSerializer(const AString& a_WorldName, cMap * a_Map) + : m_Map(a_Map) +{ + AString DataPath; + Printf(DataPath, "%s/data", a_WorldName.c_str()); + + Printf(m_Path, "%s/map_%i.dat", DataPath.c_str(), a_Map->GetID()); + + cFile::CreateFolder(FILE_IO_PREFIX + DataPath); +} + + + + + +bool cMapSerializer::Load(void) +{ + AString Data = cFile::ReadWholeFile(FILE_IO_PREFIX + m_Path); + if (Data.empty()) + { + return false; + } + + AString Uncompressed; + int res = UncompressStringGZIP(Data.data(), Data.size(), Uncompressed); + + if (res != Z_OK) + { + return false; + } + + // Parse the NBT data: + cParsedNBT NBT(Uncompressed.data(), Uncompressed.size()); + if (!NBT.IsValid()) + { + // NBT Parsing failed + return false; + } + + return LoadMapFromNBT(NBT); +} + + + + + +bool cMapSerializer::Save(void) +{ + cFastNBTWriter Writer; + + SaveMapToNBT(Writer); + + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG + + cFile File; + if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmWrite)) + { + return false; + } + + AString Compressed; + int res = CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed); + + if (res != Z_OK) + { + return false; + } + + File.Write(Compressed.data(), Compressed.size()); + File.Close(); + + return true; +} + + + + + +void cMapSerializer::SaveMapToNBT(cFastNBTWriter & a_Writer) +{ + a_Writer.BeginCompound("data"); + + a_Writer.AddByte("scale", m_Map->GetScale()); + a_Writer.AddByte("dimension", (int) m_Map->GetDimension()); + + a_Writer.AddShort("width", m_Map->GetWidth()); + a_Writer.AddShort("height", m_Map->GetHeight()); + + a_Writer.AddInt("xCenter", m_Map->GetCenterX()); + a_Writer.AddInt("zCenter", m_Map->GetCenterZ()); + + const cMap::cColorList & Data = m_Map->GetData(); + a_Writer.AddByteArray("colors", (char *) &Data[0], Data.size()); + + a_Writer.EndCompound(); +} + + + + + +bool cMapSerializer::LoadMapFromNBT(const cParsedNBT & a_NBT) +{ + int Data = a_NBT.FindChildByName(0, "data"); + if (Data < 0) + { + return false; + } + + int CurrLine = a_NBT.FindChildByName(Data, "scale"); + if (CurrLine >= 0) + { + unsigned int Scale = a_NBT.GetByte(CurrLine); + m_Map->SetScale(Scale); + } + + CurrLine = a_NBT.FindChildByName(Data, "dimension"); + if (CurrLine >= 0) + { + eDimension Dimension = (eDimension) a_NBT.GetByte(CurrLine); + + ASSERT(Dimension == m_Map->m_World->GetDimension()); + } + + CurrLine = a_NBT.FindChildByName(Data, "width"); + if (CurrLine >= 0) + { + unsigned int Width = a_NBT.GetShort(CurrLine); + m_Map->m_Width = Width; + } + + CurrLine = a_NBT.FindChildByName(Data, "height"); + if (CurrLine >= 0) + { + unsigned int Height = a_NBT.GetShort(CurrLine); + m_Map->m_Height = Height; + } + + CurrLine = a_NBT.FindChildByName(Data, "xCenter"); + if (CurrLine >= 0) + { + int CenterX = a_NBT.GetInt(CurrLine); + m_Map->m_CenterX = CenterX; + } + + CurrLine = a_NBT.FindChildByName(Data, "zCenter"); + if (CurrLine >= 0) + { + int CenterZ = a_NBT.GetInt(CurrLine); + m_Map->m_CenterZ = CenterZ; + } + + unsigned int NumPixels = m_Map->GetNumPixels(); + m_Map->m_Data.resize(NumPixels); + + CurrLine = a_NBT.FindChildByName(Data, "colors"); + if ((CurrLine >= 0) && (a_NBT.GetType(CurrLine) == TAG_ByteArray)) + { + memcpy(&m_Map->m_Data[0], a_NBT.GetData(CurrLine), NumPixels); + } + + return true; +} + + + + + +cIDCountSerializer::cIDCountSerializer(const AString & a_WorldName) : m_MapCount(0) +{ + AString DataPath; + Printf(DataPath, "%s/data", a_WorldName.c_str()); + + Printf(m_Path, "%s/idcounts.dat", DataPath.c_str()); + + cFile::CreateFolder(FILE_IO_PREFIX + DataPath); +} + + + + + +bool cIDCountSerializer::Load(void) +{ + AString Data = cFile::ReadWholeFile(FILE_IO_PREFIX + m_Path); + if (Data.empty()) + { + return false; + } + + // NOTE: idcounts.dat is not compressed (raw format) + + // Parse the NBT data: + cParsedNBT NBT(Data.data(), Data.size()); + if (!NBT.IsValid()) + { + // NBT Parsing failed + return false; + } + + int CurrLine = NBT.FindChildByName(0, "map"); + if (CurrLine >= 0) + { + m_MapCount = (int)NBT.GetShort(CurrLine) + 1; + } + else + { + m_MapCount = 0; + } + + return true; +} + + + + + +bool cIDCountSerializer::Save(void) +{ + cFastNBTWriter Writer; + + if (m_MapCount > 0) + { + Writer.AddShort("map", m_MapCount - 1); + } + + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG + + cFile File; + if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmWrite)) + { + return false; + } + + // NOTE: idcounts.dat is not compressed (raw format) + + File.Write(Writer.GetResult().data(), Writer.GetResult().size()); + File.Close(); + + return true; +} + + + + + + + + diff --git a/src/WorldStorage/MapSerializer.h b/src/WorldStorage/MapSerializer.h new file mode 100644 index 000000000..eb7678a08 --- /dev/null +++ b/src/WorldStorage/MapSerializer.h @@ -0,0 +1,86 @@ + +// MapSerializer.h + +// Declares the cMapSerializer class that is used for saving maps into NBT format used by Anvil + + + + + +#pragma once + + + + + +// fwd: +class cFastNBTWriter; +class cParsedNBT; +class cMap; + + + + +/** Utility class used to serialize maps. */ +class cMapSerializer +{ +public: + + cMapSerializer(const AString& a_WorldName, cMap * a_Map); + + /** Try to load the scoreboard */ + bool Load(void); + + /** Try to save the scoreboard */ + bool Save(void); + + +private: + + void SaveMapToNBT(cFastNBTWriter & a_Writer); + + bool LoadMapFromNBT(const cParsedNBT & a_NBT); + + cMap * m_Map; + + AString m_Path; + + +} ; + + + + +/** Utility class used to serialize item ID counts. + * + * In order to perform bounds checking (while loading), + * the last registered ID of each item is serialized to an NBT file. + */ +class cIDCountSerializer +{ +public: + + cIDCountSerializer(const AString & a_WorldName); + + /** Try to load the ID counts */ + bool Load(void); + + /** Try to save the ID counts */ + bool Save(void); + + inline unsigned int GetMapCount(void) const { return m_MapCount; } + + inline void SetMapCount(unsigned int a_MapCount) { m_MapCount = a_MapCount; } + + +private: + + AString m_Path; + + unsigned int m_MapCount; + +}; + + + + diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 2a1eda523..c1c659b36 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -409,6 +409,14 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) m_Writer.BeginCompound(""); AddBasicEntity(a_Monster, EntityClass); + m_Writer.BeginList("DropChances", TAG_Float); + m_Writer.AddFloat("", a_Monster->GetDropChanceWeapon()); + m_Writer.AddFloat("", a_Monster->GetDropChanceHelmet()); + m_Writer.AddFloat("", a_Monster->GetDropChanceChestplate()); + m_Writer.AddFloat("", a_Monster->GetDropChanceLeggings()); + m_Writer.AddFloat("", a_Monster->GetDropChanceBoots()); + m_Writer.EndList(); + m_Writer.AddByte("CanPickUpLoot", (char)a_Monster->CanPickUpLoot()); switch (a_Monster->GetMobType()) { case cMonster::mtBat: diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index d4490c7fe..05332d23d 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -1485,6 +1485,11 @@ void cWSSAnvil::LoadBatFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NB { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1500,6 +1505,11 @@ void cWSSAnvil::LoadBlazeFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1515,6 +1525,11 @@ void cWSSAnvil::LoadCaveSpiderFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1530,6 +1545,11 @@ void cWSSAnvil::LoadChickenFromNBT(cEntityList & a_Entities, const cParsedNBT & { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1545,6 +1565,11 @@ void cWSSAnvil::LoadCowFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NB { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1560,6 +1585,11 @@ void cWSSAnvil::LoadCreeperFromNBT(cEntityList & a_Entities, const cParsedNBT & { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1575,6 +1605,11 @@ void cWSSAnvil::LoadEnderDragonFromNBT(cEntityList & a_Entities, const cParsedNB { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1591,6 +1626,11 @@ void cWSSAnvil::LoadEndermanFromNBT(cEntityList & a_Entities, const cParsedNBT & return; } + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } + a_Entities.push_back(Monster.release()); } @@ -1606,6 +1646,11 @@ void cWSSAnvil::LoadGhastFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ return; } + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } + a_Entities.push_back(Monster.release()); } @@ -1620,6 +1665,11 @@ void cWSSAnvil::LoadGiantFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1646,6 +1696,11 @@ void cWSSAnvil::LoadHorseFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1661,6 +1716,11 @@ void cWSSAnvil::LoadIronGolemFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1682,6 +1742,11 @@ void cWSSAnvil::LoadMagmaCubeFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1697,6 +1762,11 @@ void cWSSAnvil::LoadMooshroomFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1712,6 +1782,11 @@ void cWSSAnvil::LoadOcelotFromNBT(cEntityList & a_Entities, const cParsedNBT & a { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1727,6 +1802,11 @@ void cWSSAnvil::LoadPigFromNBT(cEntityList & a_Entities, const cParsedNBT & a_NB { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1748,6 +1828,11 @@ void cWSSAnvil::LoadSheepFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1763,6 +1848,11 @@ void cWSSAnvil::LoadSilverfishFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1784,6 +1874,11 @@ void cWSSAnvil::LoadSkeletonFromNBT(cEntityList & a_Entities, const cParsedNBT & { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1805,6 +1900,11 @@ void cWSSAnvil::LoadSlimeFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1820,6 +1920,11 @@ void cWSSAnvil::LoadSnowGolemFromNBT(cEntityList & a_Entities, const cParsedNBT { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1835,6 +1940,11 @@ void cWSSAnvil::LoadSpiderFromNBT(cEntityList & a_Entities, const cParsedNBT & a { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1850,6 +1960,11 @@ void cWSSAnvil::LoadSquidFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1871,6 +1986,11 @@ void cWSSAnvil::LoadVillagerFromNBT(cEntityList & a_Entities, const cParsedNBT & { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1886,6 +2006,11 @@ void cWSSAnvil::LoadWitchFromNBT(cEntityList & a_Entities, const cParsedNBT & a_ { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1901,6 +2026,11 @@ void cWSSAnvil::LoadWitherFromNBT(cEntityList & a_Entities, const cParsedNBT & a { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1916,6 +2046,10 @@ void cWSSAnvil::LoadWolfFromNBT(cEntityList & a_Entities, const cParsedNBT & a_N { return; } + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } int OwnerIdx = a_NBT.FindChildByName(a_TagIdx, "Owner"); if (OwnerIdx > 0) { @@ -1964,6 +2098,11 @@ void cWSSAnvil::LoadZombieFromNBT(cEntityList & a_Entities, const cParsedNBT & a { return; } + + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } a_Entities.push_back(Monster.release()); } @@ -1980,6 +2119,11 @@ void cWSSAnvil::LoadPigZombieFromNBT(cEntityList & a_Entities, const cParsedNBT return; } + if (!LoadMonsterBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx)) + { + return; + } + a_Entities.push_back(Monster.release()); } @@ -2023,6 +2167,27 @@ bool cWSSAnvil::LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_N +bool cWSSAnvil::LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & a_NBT, int a_TagIdx) +{ + float DropChance[5]; + if (!LoadFloatsListFromNBT(DropChance, 5, a_NBT, a_NBT.FindChildByName(a_TagIdx, "DropChance"))) + { + return false; + } + a_Monster.SetDropChanceWeapon(DropChance[0]); + a_Monster.SetDropChanceHelmet(DropChance[1]); + a_Monster.SetDropChanceChestplate(DropChance[2]); + a_Monster.SetDropChanceLeggings(DropChance[3]); + a_Monster.SetDropChanceBoots(DropChance[4]); + bool CanPickUpLoot = (a_NBT.GetByte(a_NBT.FindChildByName(a_TagIdx, "CanPickUpLoot")) == 1); + a_Monster.SetCanPickUpLoot(CanPickUpLoot); + return true; +} + + + + + bool cWSSAnvil::LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIdx) { if (!LoadEntityBaseFromNBT(a_Entity, a_NBT, a_TagIdx)) @@ -2065,6 +2230,24 @@ bool cWSSAnvil::LoadDoublesListFromNBT(double * a_Doubles, int a_NumDoubles, con +bool cWSSAnvil::LoadFloatsListFromNBT(float * a_Floats, int a_NumFloats, const cParsedNBT & a_NBT, int a_TagIdx) +{ + if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_List) || (a_NBT.GetChildrenType(a_TagIdx) != TAG_Float)) + { + return false; + } + int idx = 0; + for (int Tag = a_NBT.GetFirstChild(a_TagIdx); (Tag > 0) && (idx < a_NumFloats); Tag = a_NBT.GetNextSibling(Tag), ++idx) + { + a_Floats[idx] = a_NBT.GetFloat(Tag); + } // for Tag - PosTag[] + return (idx == a_NumFloats); // 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"); diff --git a/src/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h index 541371560..4acf3f2a1 100644 --- a/src/WorldStorage/WSSAnvil.h +++ b/src/WorldStorage/WSSAnvil.h @@ -10,6 +10,7 @@ #include "WorldStorage.h" #include "FastNBT.h" +#include "../Mobs/Monster.h" @@ -194,12 +195,18 @@ protected: /// 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 monster common data from the NBT compound; returns true if successful + bool LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT & a_NBT, int a_TagIdx); + /// Loads projectile common data from the NBT compound; returns true if successful bool LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cParsedNBT & a_NBT, int a_TagIx); /// 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); + /// Loads an array of floats of the specified length from the specified NBT list tag a_TagIdx; returns true if successful + bool LoadFloatsListFromNBT(float * a_Floats, int a_NumFloats, 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); |