From 75937077136b9ec2eaba49c74543bdee323f68ff Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Sat, 9 Mar 2013 14:35:43 +0000 Subject: Pickups are now being saved into Anvil. Also changed cEntity rotation datatype to double git-svn-id: http://mc-server.googlecode.com/svn/trunk@1262 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/WorldStorage/WSSAnvil.cpp | 518 ++++++++++++++++++--------------------- 1 file changed, 232 insertions(+), 286 deletions(-) (limited to 'source/WorldStorage/WSSAnvil.cpp') 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" @@ -37,238 +42,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: @@ -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 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; } -- cgit v1.2.3