diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index b5a6252ff..bc88f4e7b 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -444,12 +444,18 @@ void cChunkMap::ChunkLighted( -bool cChunkMap::GetChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback) +bool cChunkMap::GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback) { + if (!a_Callback.Coords(a_Coords.m_ChunkX, a_Coords.m_ChunkZ)) + { + // The callback doesn't want the data + return false; + } cCSLock Lock(m_CSChunks); - cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkZ); + cChunkPtr Chunk = GetChunkNoGen(a_Coords); if ((Chunk == nullptr) || !Chunk->IsValid()) { + // The chunk is not present return false; } Chunk->GetAllData(a_Callback); diff --git a/src/ChunkMap.h b/src/ChunkMap.h index 8f0ac3a7c..4a3dd2dfd 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -108,7 +108,9 @@ public: const cChunkDef::BlockNibbles & a_SkyLight ); - bool GetChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback); + /** Calls the callback with the chunk's data, if available (with ChunkCS locked). + Returns true if the chunk was reported successfully, false if not (chunk not present or callback failed). */ + bool GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback); /** Copies the chunk's blocktypes into a_Blocks; returns true if successful */ bool GetChunkBlockTypes (int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_Blocks); @@ -439,7 +441,7 @@ private: typedef std::list cChunkStays; - cCriticalSection m_CSChunks; + mutable cCriticalSection m_CSChunks; /** A map of chunk coordinates to chunk pointers Uses a map (as opposed to unordered_map) because sorted maps are apparently faster */ @@ -468,7 +470,7 @@ private: // Deprecated in favor of the vector version cChunkPtr GetChunkNoGen(int a_ChunkX, int a_ChunkZ) { - return GetChunkNoGen(cChunkCoords(a_ChunkX, a_ChunkZ)); + return GetChunkNoGen({a_ChunkX, a_ChunkZ}); } /** Constructs a chunk, returning it. Doesn't load, doesn't generate */ diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index a11852ed6..6312dfdea 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -241,7 +241,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_setGetChunkData(a_ChunkX, a_ChunkZ, a_Callback); + return m_ChunkMap->GetChunkData(a_Coords, a_Callback); } diff --git a/src/World.h b/src/World.h index 26e108e80..3ffbfecf0 100644 --- a/src/World.h +++ b/src/World.h @@ -234,7 +234,9 @@ public: const cChunkDef::BlockNibbles & a_SkyLight ); - bool GetChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback); + /** Calls the callback with the chunk's data, if available (with ChunkCS locked). + Returns true if the chunk was reported successfully, false if not (chunk not present or callback failed). */ + bool GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback) const; /** Gets the chunk's blocks, only the block types */ bool GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes); diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index f53dba68b..0a18cb7b3 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -5,6 +5,7 @@ #include "Globals.h" #include "NBTChunkSerializer.h" #include "EnchantmentSerializer.h" +#include "../ChunkDataCallback.h" #include "../ItemGrid.h" #include "../StringCompression.h" #include "../UUID.h" @@ -47,1032 +48,1139 @@ -cNBTChunkSerializer::cNBTChunkSerializer(cFastNBTWriter & a_Writer) : - m_BiomesAreValid(false), - m_Writer(a_Writer), - m_IsTagOpen(false), - m_HasHadEntity(false), - m_HasHadBlockEntity(false), - m_IsLightValid(false) +/** Collects and stores the chunk data via the cChunkDataCallback interface */ +class SerializerCollector: + public cChunkDataCopyCollector { -} +public: + + // The data collected from the chunk: + cChunkDef::BiomeMap mBiomes; + UInt8 mVanillaBiomes[cChunkDef::Width * cChunkDef::Width]; + int mVanillaHeightMap[cChunkDef::Width * cChunkDef::Width]; + bool mBiomesAreValid; + + /** True if a tag has been opened in the callbacks and not yet closed. */ + bool mIsTagOpen; + + /** True if any Entity has already been received and processed. */ + bool mHasHadEntity; + + /** True if any BlockEntity has already been received and processed. */ + bool mHasHadBlockEntity; + + /** True if the chunk lighting is valid. */ + bool mIsLightValid; + + /** The NBT writer used to store the data. */ + cFastNBTWriter & mWriter; -void cNBTChunkSerializer::Finish(void) -{ - if (m_IsTagOpen) + SerializerCollector(cFastNBTWriter & aWriter): + mBiomesAreValid(false), + mIsTagOpen(false), + mHasHadEntity(false), + mHasHadBlockEntity(false), + mIsLightValid(false), + mWriter(aWriter) { - m_Writer.EndList(); } - // If light not valid, reset it to defaults: - if (!m_IsLightValid) + + + + + virtual void LightIsValid(bool a_IsLightValid) override { - m_Data.FillBlockLight(0x00); - m_Data.FillSkyLight(0x0f); + mIsLightValid = a_IsLightValid; } - // Check if "Entity" and "TileEntities" lists exists. MCEdit requires this. - if (!m_HasHadEntity) + + + + + virtual void HeightMap(const cChunkDef::HeightMap * a_HeightMap) override { - m_Writer.BeginList("Entities", TAG_Compound); - m_Writer.EndList(); - } - if (!m_HasHadBlockEntity) - { - m_Writer.BeginList("TileEntities", TAG_Compound); - m_Writer.EndList(); - } -} - - - - - -void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AString & a_CompoundName) -{ - m_Writer.BeginCompound(a_CompoundName); - m_Writer.AddShort("id", static_cast(a_Item.m_ItemType)); - m_Writer.AddShort("Damage", static_cast((a_Item.m_ItemDamage))); - m_Writer.AddByte ("Count", static_cast(a_Item.m_ItemCount)); - if (a_Slot >= 0) - { - m_Writer.AddByte ("Slot", static_cast(a_Slot)); - } - - // Write the tag compound (for enchantment, firework, custom name and repair cost): - if ( - (!a_Item.m_Enchantments.IsEmpty()) || - ((a_Item.m_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR)) || - (a_Item.m_RepairCost > 0) || - (a_Item.m_CustomName != "") || - (!a_Item.m_LoreTable.empty()) - ) - { - m_Writer.BeginCompound("tag"); - if (a_Item.m_RepairCost > 0) - { - m_Writer.AddInt("RepairCost", a_Item.m_RepairCost); - } - - if ((a_Item.m_CustomName != "") || (!a_Item.m_LoreTable.empty())) - { - m_Writer.BeginCompound("display"); - if (a_Item.m_CustomName != "") - { - m_Writer.AddString("Name", a_Item.m_CustomName); - } - if (!a_Item.m_LoreTable.empty()) - { - m_Writer.BeginList("Lore", TAG_String); - - for (const auto & Line : a_Item.m_LoreTable) - { - m_Writer.AddString("", Line); - } - - m_Writer.EndList(); - } - m_Writer.EndCompound(); - } - - if ((a_Item.m_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR)) - { - cFireworkItem::WriteToNBTCompound(a_Item.m_FireworkItem, m_Writer, static_cast(a_Item.m_ItemType)); - } - - if (!a_Item.m_Enchantments.IsEmpty()) - { - const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; - EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, m_Writer, TagName); - } - m_Writer.EndCompound(); - } - - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddItemGrid(const cItemGrid & a_Grid, int a_BeginSlotNum) -{ - int NumSlots = a_Grid.GetNumSlots(); - for (int i = 0; i < NumSlots; i++) - { - const cItem & Item = a_Grid.GetSlot(i); - if (Item.IsEmpty()) + for (int RelX = 0; RelX < cChunkDef::Width; RelX++) { - continue; - } - AddItem(Item, i + a_BeginSlotNum); - } // for i - chest slots[] -} - - - - - -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::AddBeaconEntity(cBeaconEntity * a_Entity) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Entity, "Beacon"); - m_Writer.AddInt("Levels", a_Entity->GetBeaconLevel()); - m_Writer.AddInt("Primary", static_cast(a_Entity->GetPrimaryEffect())); - m_Writer.AddInt("Secondary", static_cast(a_Entity->GetSecondaryEffect())); - m_Writer.BeginList("Items", TAG_Compound); - AddItemGrid(a_Entity->GetContents()); - m_Writer.EndList(); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddBedEntity(cBedEntity * a_Entity) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Entity, "Bed"); - m_Writer.AddInt("color", a_Entity->GetColor()); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddBrewingstandEntity(cBrewingstandEntity * a_Brewingstand) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Brewingstand, "Brewingstand"); - m_Writer.BeginList("Items", TAG_Compound); - AddItemGrid(a_Brewingstand->GetContents()); - m_Writer.EndList(); - m_Writer.AddShort("BrewTime", a_Brewingstand->GetTimeBrewed()); - m_Writer.AddShort("Fuel", a_Brewingstand->GetRemainingFuel()); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddChestEntity(cChestEntity * a_Entity, BLOCKTYPE a_ChestType) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Entity, "Chest"); - m_Writer.BeginList("Items", TAG_Compound); - AddItemGrid(a_Entity->GetContents()); - 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); - AddItemGrid(a_Entity->GetContents()); - m_Writer.EndList(); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddDropperEntity(cDropperEntity * a_Entity) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Entity, "Dropper"); - m_Writer.BeginList("Items", TAG_Compound); - AddItemGrid(a_Entity->GetContents()); - 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); - AddItemGrid(a_Furnace->GetContents()); - m_Writer.EndList(); - m_Writer.AddShort("BurnTime", static_cast(a_Furnace->GetFuelBurnTimeLeft())); - m_Writer.AddShort("CookTime", static_cast(a_Furnace->GetTimeCooked())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddHopperEntity(cHopperEntity * a_Entity) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Entity, "Hopper"); - m_Writer.BeginList("Items", TAG_Compound); - AddItemGrid(a_Entity->GetContents()); - m_Writer.EndList(); - 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::AddMobSpawnerEntity(cMobSpawnerEntity * a_MobSpawner) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_MobSpawner, "MobSpawner"); - m_Writer.AddShort("Entity", static_cast(a_MobSpawner->GetEntity())); - m_Writer.AddString("EntityId", cMonster::MobTypeToVanillaName(a_MobSpawner->GetEntity())); - m_Writer.AddShort("Delay", a_MobSpawner->GetSpawnDelay()); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddNoteEntity(cNoteEntity * a_Note) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_Note, "Music"); - m_Writer.AddByte("note", static_cast(a_Note->GetPitch())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddCommandBlockEntity(cCommandBlockEntity * a_CmdBlock) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_CmdBlock, "Control"); - m_Writer.AddString("Command", a_CmdBlock->GetCommand()); - m_Writer.AddInt ("SuccessCount", a_CmdBlock->GetResult()); - m_Writer.AddString("LastOutput", a_CmdBlock->GetLastOutput()); - m_Writer.AddByte ("TrackOutput", 1); // TODO 2014-01-18 xdot: Figure out what TrackOutput is and save it. - 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::AddMobHeadEntity(cMobHeadEntity * a_MobHead) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_MobHead, "Skull"); - m_Writer.AddByte ("SkullType", a_MobHead->GetType() & 0xFF); - m_Writer.AddByte ("Rot", a_MobHead->GetRotation() & 0xFF); - - // The new Block Entity format for a Mob Head. See: https://minecraft.gamepedia.com/Head#Block_entity - m_Writer.BeginCompound("Owner"); - m_Writer.AddString("Id", a_MobHead->GetOwnerUUID().ToShortString()); - m_Writer.AddString("Name", a_MobHead->GetOwnerName()); - m_Writer.BeginCompound("Properties"); - m_Writer.BeginList("textures", TAG_Compound); - m_Writer.BeginCompound(""); - m_Writer.AddString("Signature", a_MobHead->GetOwnerTextureSignature()); - m_Writer.AddString("Value", a_MobHead->GetOwnerTexture()); - m_Writer.EndCompound(); - m_Writer.EndList(); - m_Writer.EndCompound(); - m_Writer.EndCompound(); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddFlowerPotEntity(cFlowerPotEntity * a_FlowerPot) -{ - m_Writer.BeginCompound(""); - AddBasicTileEntity(a_FlowerPot, "FlowerPot"); - m_Writer.AddInt ("Item", static_cast(a_FlowerPot->GetItem().m_ItemType)); - m_Writer.AddInt ("Data", static_cast(a_FlowerPot->GetItem().m_ItemDamage)); - 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->GetYaw()); - m_Writer.AddDouble("", a_Entity->GetPitch()); - m_Writer.EndList(); - m_Writer.AddFloat("Health", a_Entity->GetHealth()); -} - - - - - -void cNBTChunkSerializer::AddBoatEntity(cBoat * a_Boat) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_Boat, "Boat"); - m_Writer.AddString("Type", cBoat::MaterialToString(a_Boat->GetMaterial())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddEnderCrystalEntity(cEnderCrystal * a_EnderCrystal) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_EnderCrystal, "EnderCrystal"); - m_Writer.EndCompound(); -} - - - - - -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 Cuberite, 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) -{ - m_Writer.BeginCompound(""); - - switch (a_Minecart->GetPayload()) - { - case cMinecart::mpChest: + for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++) { - AddBasicEntity(a_Minecart, "MinecartChest"); - // Add chest contents into the Items tag: - AddMinecartChestContents(static_cast(a_Minecart)); - break; - } - case cMinecart::mpFurnace: - { - AddBasicEntity(a_Minecart, "MinecartFurnace"); - // TODO: Add "Push" and "Fuel" tags - break; - } - case cMinecart::mpHopper: - { - AddBasicEntity(a_Minecart, "MinecartHopper"); - // TODO: Add hopper contents? - break; - } - case cMinecart::mpTNT: - { - AddBasicEntity(a_Minecart, "MinecartTNT"); - break; - } - case cMinecart::mpNone: - { - AddBasicEntity(a_Minecart, "MinecartRideable"); - break; - } - } // switch (Payload) - - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) -{ - const char * EntityClass = nullptr; - switch (a_Monster->GetMobType()) - { - case mtBat: EntityClass = "Bat"; break; - case mtBlaze: EntityClass = "Blaze"; break; - case mtCaveSpider: EntityClass = "CaveSpider"; break; - case mtChicken: EntityClass = "Chicken"; break; - case mtCow: EntityClass = "Cow"; break; - case mtCreeper: EntityClass = "Creeper"; break; - case mtEnderDragon: EntityClass = "EnderDragon"; break; - case mtEnderman: EntityClass = "Enderman"; break; - case mtGhast: EntityClass = "Ghast"; break; - case mtGiant: EntityClass = "Giant"; break; - case mtGuardian: EntityClass = "Guardian"; break; - case mtHorse: EntityClass = "Horse"; break; - case mtIronGolem: EntityClass = "VillagerGolem"; break; - case mtMagmaCube: EntityClass = "LavaSlime"; break; - case mtMooshroom: EntityClass = "MushroomCow"; break; - case mtOcelot: EntityClass = "Ozelot"; break; - case mtPig: EntityClass = "Pig"; break; - case mtRabbit: EntityClass = "Rabbit"; break; - case mtSheep: EntityClass = "Sheep"; break; - case mtSilverfish: EntityClass = "Silverfish"; break; - case mtSkeleton: EntityClass = "Skeleton"; break; - case mtSlime: EntityClass = "Slime"; break; - case mtSnowGolem: EntityClass = "SnowMan"; break; - case mtSpider: EntityClass = "Spider"; break; - case mtSquid: EntityClass = "Squid"; break; - case mtVillager: EntityClass = "Villager"; break; - case mtWitch: EntityClass = "Witch"; break; - case mtWither: EntityClass = "WitherBoss"; break; - case mtWolf: EntityClass = "Wolf"; break; - case mtZombie: EntityClass = "Zombie"; break; - case mtZombiePigman: EntityClass = "PigZombie"; break; - default: - { - ASSERT(!"Unhandled monster type"); - return; - } - } // switch (payload) - - 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", (a_Monster->CanPickUpLoot())? 1 : 0); - m_Writer.AddString("CustomName", a_Monster->GetCustomName()); - m_Writer.AddByte("CustomNameVisible", static_cast(a_Monster->IsCustomNameAlwaysVisible())); - - // Mob was leashed - if (a_Monster->IsLeashed() || (a_Monster->GetLeashToPos() != nullptr)) - { - m_Writer.AddByte("Leashed", 1); - - const Vector3d * LeashedToPos = nullptr; - - if (a_Monster->GetLeashToPos() != nullptr) - { - LeashedToPos = a_Monster->GetLeashToPos(); - } - else if (a_Monster->GetLeashedTo()->IsLeashKnot()) - { - LeashedToPos = & a_Monster->GetLeashedTo()->GetPosition(); - } - - if (LeashedToPos != nullptr) - { - m_Writer.BeginCompound("Leash"); - m_Writer.AddDouble("X", LeashedToPos->x); - m_Writer.AddDouble("Y", LeashedToPos->y); - m_Writer.AddDouble("Z", LeashedToPos->z); - m_Writer.EndCompound(); + int Height = cChunkDef::GetHeight(*a_HeightMap, RelX, RelZ); + mVanillaHeightMap[(RelZ << 4) | RelX] = Height; } } + } - switch (a_Monster->GetMobType()) + + + + + virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) override + { + memcpy(mBiomes, a_BiomeMap, sizeof(mBiomes)); + for (size_t i = 0; i < ARRAYCOUNT(mBiomes); i++) { - case mtBat: + if ((*a_BiomeMap)[i] < 255) { - m_Writer.AddByte("BatFlags", static_cast(a_Monster)->IsHanging()); - break; + // Normal MC biome, copy as-is: + mVanillaBiomes[i] = static_cast((*a_BiomeMap)[i]); } - case mtCreeper: + else { - const cCreeper *Creeper = static_cast(a_Monster); - m_Writer.AddByte("powered", Creeper->IsCharged()); - m_Writer.AddByte("ignited", Creeper->IsBlowing()); - break; + // TODO: MCS-specific biome, need to map to some basic MC biome: + ASSERT(!"Unimplemented MCS-specific biome"); + return; } - case mtEnderman: + } // for i - mBiomeMap[] + mBiomesAreValid = true; + } + + + + + + virtual void Entity(cEntity * a_Entity) override + { + // Add entity into NBT: + if (mIsTagOpen) + { + if (!mHasHadEntity) { - const cEnderman *Enderman = static_cast(a_Monster); - m_Writer.AddShort("carried", static_cast(Enderman->GetCarriedBlock())); - m_Writer.AddShort("carriedData", static_cast(Enderman->GetCarriedMeta())); - break; - } - case mtHorse: - { - const cHorse *Horse = static_cast(a_Monster); - m_Writer.AddByte("ChestedHorse", Horse->IsChested()? 1 : 0); - m_Writer.AddByte("EatingHaystack", Horse->IsEating()? 1 : 0); - m_Writer.AddByte("Tame", Horse->IsTame()? 1: 0); - m_Writer.AddInt ("Type", Horse->GetHorseType()); - m_Writer.AddInt ("Color", Horse->GetHorseColor()); - m_Writer.AddInt ("Style", Horse->GetHorseStyle()); - m_Writer.AddInt ("ArmorType", Horse->GetHorseArmour()); - m_Writer.AddByte("Saddle", Horse->IsSaddled()? 1 : 0); - m_Writer.AddInt ("Age", Horse->GetAge()); - break; - } - case mtMagmaCube: - { - m_Writer.AddInt("Size", static_cast(a_Monster)->GetSize()); - break; - } - case mtOcelot: - { - const auto *Ocelot = static_cast(a_Monster); - if (!Ocelot->GetOwnerName().empty()) - { - m_Writer.AddString("Owner", Ocelot->GetOwnerName()); - } - if (!Ocelot->GetOwnerUUID().IsNil()) - { - m_Writer.AddString("OwnerUUID", Ocelot->GetOwnerUUID().ToShortString()); - } - m_Writer.AddByte("Sitting", Ocelot->IsSitting() ? 1 : 0); - m_Writer.AddInt("CatType", Ocelot->GetOcelotType()); - m_Writer.AddInt("Age", Ocelot->GetAge()); - break; - } - case mtPig: - { - m_Writer.AddInt("Age", static_cast(a_Monster)->GetAge()); - break; - } - case mtRabbit: - { - const cRabbit * Rabbit = static_cast(a_Monster); - m_Writer.AddInt("RabbitType", static_cast(Rabbit->GetRabbitType())); - m_Writer.AddInt("MoreCarrotTicks", Rabbit->GetMoreCarrotTicks()); - m_Writer.AddInt("Age", Rabbit->GetAge()); - break; - } - case mtSheep: - { - const cSheep *Sheep = static_cast(a_Monster); - m_Writer.AddByte("Sheared", Sheep->IsSheared()? 1 : 0); - m_Writer.AddByte("Color", static_cast(Sheep->GetFurColor())); - m_Writer.AddInt ("Age", Sheep->GetAge()); - break; - } - case mtSlime: - { - m_Writer.AddInt("Size", static_cast(a_Monster)->GetSize()); - break; - } - case mtSkeleton: - { - m_Writer.AddByte("SkeletonType", (static_cast(a_Monster)->IsWither() ? 1 : 0)); - break; - } - case mtVillager: - { - const cVillager *Villager = static_cast(a_Monster); - m_Writer.AddInt("Profession", Villager->GetVilType()); - m_Writer.AddInt("Age", Villager->GetAge()); - break; - } - case mtWither: - { - m_Writer.AddInt("Invul", static_cast(static_cast(a_Monster)->GetWitherInvulnerableTicks())); - break; - } - case mtWolf: - { - const cWolf *Wolf = static_cast(a_Monster); - if (!Wolf->GetOwnerName().empty()) - { - m_Writer.AddString("Owner", Wolf->GetOwnerName()); - } - if (!Wolf->GetOwnerUUID().IsNil()) - { - m_Writer.AddString("OwnerUUID", Wolf->GetOwnerUUID().ToShortString()); - } - m_Writer.AddByte("Sitting", Wolf->IsSitting() ? 1 : 0); - m_Writer.AddByte("Angry", Wolf->IsAngry() ? 1 : 0); - m_Writer.AddByte("CollarColor", static_cast(Wolf->GetCollarColor())); - m_Writer.AddInt ("Age", Wolf->GetAge()); - break; - } - case mtZombie: - { - const cZombie *Zombie = static_cast(a_Monster); - m_Writer.AddByte("IsVillager", Zombie->IsVillagerZombie() ? 1 : 0); - m_Writer.AddByte("IsConverting", Zombie->IsConverting() ? 1 : 0); - m_Writer.AddInt ("Age", Zombie->GetAge()); - break; - } - case mtZombiePigman: - { - m_Writer.AddInt("Age", static_cast(a_Monster)->GetAge()); - break; - } - case mtBlaze: - case mtCaveSpider: - case mtChicken: - case mtCow: - case mtEnderDragon: - case mtGhast: - case mtGiant: - case mtGuardian: - case mtIronGolem: - case mtMooshroom: - case mtSilverfish: - case mtSnowGolem: - case mtSpider: - case mtSquid: - case mtWitch: - { - // Other mobs have no special tags. - break; - } - case mtInvalidType: - { - ASSERT(!"cNBTChunkSerializer::AddMonsterEntity: Recieved mob of invalid type"); - break; + mWriter.EndList(); + mWriter.BeginList("Entities", TAG_Compound); } } - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddPickupEntity(cPickup * a_Pickup) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_Pickup, "Item"); - AddItem(a_Pickup->GetItem(), -1, "Item"); - m_Writer.AddShort("Age", static_cast(a_Pickup->GetAge())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddProjectileEntity(cProjectileEntity * a_Projectile) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_Projectile, a_Projectile->GetMCAClassName()); - m_Writer.AddByte("inGround", a_Projectile->IsInGround() ? 1 : 0); - - switch (a_Projectile->GetProjectileKind()) + else { - case cProjectileEntity::pkArrow: - { - cArrowEntity * Arrow = static_cast(a_Projectile); + mWriter.BeginList("Entities", TAG_Compound); + } + mIsTagOpen = true; + mHasHadEntity = true; - m_Writer.AddShort("xTile", static_cast(Arrow->GetBlockHit().x)); - m_Writer.AddShort("yTile", static_cast(Arrow->GetBlockHit().y)); - m_Writer.AddShort("zTile", static_cast(Arrow->GetBlockHit().z)); - m_Writer.AddByte("pickup", Arrow->GetPickupState()); - m_Writer.AddDouble("damage", Arrow->GetDamageCoeff()); - break; - } - case cProjectileEntity::pkSplashPotion: - { - cSplashPotionEntity * Potion = static_cast(a_Projectile); - - m_Writer.AddInt("EffectType", static_cast(Potion->GetEntityEffectType())); - m_Writer.AddInt("EffectDuration", static_cast(Potion->GetEntityEffect().GetDuration())); - m_Writer.AddShort("EffectIntensity", Potion->GetEntityEffect().GetIntensity()); - m_Writer.AddDouble("EffectDistanceModifier", Potion->GetEntityEffect().GetDistanceModifier()); - m_Writer.AddInt("PotionName", Potion->GetPotionColor()); - break; - } - case cProjectileEntity::pkGhastFireball: - { - m_Writer.AddInt("ExplosionPower", 1); - break; - } - case cProjectileEntity::pkFireCharge: - case cProjectileEntity::pkWitherSkull: - case cProjectileEntity::pkEnderPearl: - { - break; - } + switch (a_Entity->GetEntityType()) + { + case cEntity::etBoat: AddBoatEntity (static_cast (a_Entity)); break; + case cEntity::etEnderCrystal: AddEnderCrystalEntity(static_cast (a_Entity)); break; + case cEntity::etFallingBlock: AddFallingBlockEntity(static_cast (a_Entity)); break; + case cEntity::etMinecart: AddMinecartEntity (static_cast (a_Entity)); break; + case cEntity::etMonster: AddMonsterEntity (static_cast (a_Entity)); break; + case cEntity::etPickup: AddPickupEntity (static_cast (a_Entity)); break; + case cEntity::etProjectile: AddProjectileEntity (static_cast(a_Entity)); break; + case cEntity::etTNT: AddTNTEntity (static_cast (a_Entity)); break; + case cEntity::etExpOrb: AddExpOrbEntity (static_cast (a_Entity)); break; + case cEntity::etItemFrame: AddItemFrameEntity (static_cast (a_Entity)); break; + case cEntity::etLeashKnot: AddLeashKnotEntity (static_cast (a_Entity)); break; + case cEntity::etPainting: AddPaintingEntity (static_cast (a_Entity)); break; + case cEntity::etPlayer: return; // Players aren't saved into the world + case cEntity::etFloater: return; // Floaters aren't saved either default: { - ASSERT(!"Unsaved projectile entity!"); + ASSERT(!"Unhandled entity type is being saved"); + break; } - } // switch (ProjectileKind) - - if (!a_Projectile->GetCreatorName().empty()) - { - m_Writer.AddString("ownerName", a_Projectile->GetCreatorName()); } - m_Writer.EndCompound(); -} + } -void cNBTChunkSerializer::AddHangingEntity(cHangingEntity * a_Hanging) -{ - m_Writer.AddInt("TileX", FloorC(a_Hanging->GetPosX())); - m_Writer.AddInt("TileY", FloorC(a_Hanging->GetPosY())); - m_Writer.AddInt("TileZ", FloorC(a_Hanging->GetPosZ())); - m_Writer.AddByte("Facing", a_Hanging->GetProtocolFacing()); -} - - - - - -void cNBTChunkSerializer::AddTNTEntity(cTNTEntity * a_TNT) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_TNT, "PrimedTnt"); - m_Writer.AddByte("Fuse", static_cast(a_TNT->GetFuseTicks())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddExpOrbEntity(cExpOrb * a_ExpOrb) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_ExpOrb, "XPOrb"); - m_Writer.AddShort("Age", static_cast(a_ExpOrb->GetAge())); - m_Writer.AddShort("Value", static_cast(a_ExpOrb->GetReward())); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddItemFrameEntity(cItemFrame * a_ItemFrame) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_ItemFrame, "ItemFrame"); - AddHangingEntity(a_ItemFrame); - AddItem(a_ItemFrame->GetItem(), -1, "Item"); - m_Writer.AddByte("ItemRotation", static_cast(a_ItemFrame->GetItemRotation())); - m_Writer.AddFloat("ItemDropChance", 1.0F); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddLeashKnotEntity(cLeashKnot * a_LeashKnot) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_LeashKnot, "LeashKnot"); - AddHangingEntity(a_LeashKnot); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddPaintingEntity(cPainting * a_Painting) -{ - m_Writer.BeginCompound(""); - AddBasicEntity(a_Painting, "Painting"); - AddHangingEntity(a_Painting); - m_Writer.AddString("Motive", a_Painting->GetName()); - m_Writer.EndCompound(); -} - - - - - -void cNBTChunkSerializer::AddMinecartChestContents(cMinecartWithChest * a_Minecart) -{ - m_Writer.BeginList("Items", TAG_Compound); - for (int i = 0; i < cMinecartWithChest::ContentsHeight * cMinecartWithChest::ContentsWidth; i++) + virtual void BlockEntity(cBlockEntity * a_Entity) override + { + if (mIsTagOpen) { - const cItem & Item = a_Minecart->GetSlot(i); + if (!mHasHadBlockEntity) + { + mWriter.EndList(); + mWriter.BeginList("TileEntities", TAG_Compound); + } + } + else + { + mWriter.BeginList("TileEntities", TAG_Compound); + } + mIsTagOpen = true; + + // Add tile-entity into NBT: + switch (a_Entity->GetBlockType()) + { + case E_BLOCK_BEACON: AddBeaconEntity (static_cast (a_Entity)); break; + case E_BLOCK_BED: AddBedEntity (static_cast (a_Entity)); break; + case E_BLOCK_BREWING_STAND: AddBrewingstandEntity(static_cast(a_Entity)); break; + case E_BLOCK_CHEST: AddChestEntity (static_cast (a_Entity), a_Entity->GetBlockType()); break; + case E_BLOCK_COMMAND_BLOCK: AddCommandBlockEntity(static_cast(a_Entity)); break; + case E_BLOCK_DISPENSER: AddDispenserEntity (static_cast (a_Entity)); break; + case E_BLOCK_DROPPER: AddDropperEntity (static_cast (a_Entity)); break; + case E_BLOCK_ENDER_CHEST: /* No data to be saved */ break; + case E_BLOCK_FLOWER_POT: AddFlowerPotEntity (static_cast (a_Entity)); break; + case E_BLOCK_FURNACE: AddFurnaceEntity (static_cast (a_Entity)); break; + case E_BLOCK_HEAD: AddMobHeadEntity (static_cast (a_Entity)); break; + case E_BLOCK_HOPPER: AddHopperEntity (static_cast (a_Entity)); break; + case E_BLOCK_JUKEBOX: AddJukeboxEntity (static_cast (a_Entity)); break; + case E_BLOCK_LIT_FURNACE: AddFurnaceEntity (static_cast (a_Entity)); break; + case E_BLOCK_MOB_SPAWNER: AddMobSpawnerEntity (static_cast (a_Entity)); break; + case E_BLOCK_NOTE_BLOCK: AddNoteEntity (static_cast (a_Entity)); break; + case E_BLOCK_SIGN_POST: AddSignEntity (static_cast (a_Entity)); break; + case E_BLOCK_TRAPPED_CHEST: AddChestEntity (static_cast (a_Entity), a_Entity->GetBlockType()); break; + case E_BLOCK_WALLSIGN: AddSignEntity (static_cast (a_Entity)); break; + + default: + { + ASSERT(!"Unhandled block entity saved into Anvil"); + } + } + mHasHadBlockEntity = true; + } + + + + + + void Finish(void) + { + if (mIsTagOpen) + { + mWriter.EndList(); + } + + // If light not valid, reset it to defaults: + if (!mIsLightValid) + { + m_Data.FillBlockLight(0x00); + m_Data.FillSkyLight(0x0f); + } + + // Check if "Entity" and "TileEntities" lists exists. MCEdit requires this. + if (!mHasHadEntity) + { + mWriter.BeginList("Entities", TAG_Compound); + mWriter.EndList(); + } + if (!mHasHadBlockEntity) + { + mWriter.BeginList("TileEntities", TAG_Compound); + mWriter.EndList(); + } + } + + + + + + /** Writes an item into the writer. + If aSlot >= 0, adds the Slot tag. + The compound is named as requested (empty name by default). */ + void AddItem(const cItem & a_Item, int a_Slot, const AString & a_CompoundName = AString()) + { + mWriter.BeginCompound(a_CompoundName); + mWriter.AddShort("id", static_cast(a_Item.m_ItemType)); + mWriter.AddShort("Damage", static_cast((a_Item.m_ItemDamage))); + mWriter.AddByte ("Count", static_cast(a_Item.m_ItemCount)); + if (a_Slot >= 0) + { + mWriter.AddByte ("Slot", static_cast(a_Slot)); + } + + // Write the tag compound (for enchantment, firework, custom name and repair cost): + if ( + (!a_Item.m_Enchantments.IsEmpty()) || + ((a_Item.m_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR)) || + (a_Item.m_RepairCost > 0) || + (a_Item.m_CustomName != "") || + (!a_Item.m_LoreTable.empty()) + ) + { + mWriter.BeginCompound("tag"); + if (a_Item.m_RepairCost > 0) + { + mWriter.AddInt("RepairCost", a_Item.m_RepairCost); + } + + if ((a_Item.m_CustomName != "") || (!a_Item.m_LoreTable.empty())) + { + mWriter.BeginCompound("display"); + if (a_Item.m_CustomName != "") + { + mWriter.AddString("Name", a_Item.m_CustomName); + } + if (!a_Item.m_LoreTable.empty()) + { + mWriter.BeginList("Lore", TAG_String); + + for (const auto & Line : a_Item.m_LoreTable) + { + mWriter.AddString("", Line); + } + + mWriter.EndList(); + } + mWriter.EndCompound(); + } + + if ((a_Item.m_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR)) + { + cFireworkItem::WriteToNBTCompound(a_Item.m_FireworkItem, mWriter, static_cast(a_Item.m_ItemType)); + } + + if (!a_Item.m_Enchantments.IsEmpty()) + { + const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; + EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, mWriter, TagName); + } + mWriter.EndCompound(); + } + + mWriter.EndCompound(); + } + + + + + + /** Writes an item grid into the writer. + Begins the stored slot numbers with a_BeginSlotNum. + Note that it doesn't begin nor end the list tag, so that multiple grids may be concatenated together using this function. */ + void AddItemGrid(const cItemGrid & a_Grid, int a_BeginSlotNum = 0) + { + int NumSlots = a_Grid.GetNumSlots(); + for (int i = 0; i < NumSlots; i++) + { + const cItem & Item = a_Grid.GetSlot(i); if (Item.IsEmpty()) { continue; } - AddItem(Item, i); - } - m_Writer.EndList(); -} + AddItem(Item, i + a_BeginSlotNum); + } // for i - slots[] + } -void cNBTChunkSerializer::LightIsValid(bool a_IsLightValid) + void AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID) + { + mWriter.AddInt ("x", a_Entity->GetPosX()); + mWriter.AddInt ("y", a_Entity->GetPosY()); + mWriter.AddInt ("z", a_Entity->GetPosZ()); + mWriter.AddString("id", a_EntityTypeID); + } + + + + + + void AddBeaconEntity(cBeaconEntity * a_Entity) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Beacon"); + mWriter.AddInt("Levels", a_Entity->GetBeaconLevel()); + mWriter.AddInt("Primary", static_cast(a_Entity->GetPrimaryEffect())); + mWriter.AddInt("Secondary", static_cast(a_Entity->GetSecondaryEffect())); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Entity->GetContents()); + mWriter.EndList(); + mWriter.EndCompound(); + } + + + + + + void AddBedEntity(cBedEntity * a_Entity) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Bed"); + mWriter.AddInt("color", a_Entity->GetColor()); + mWriter.EndCompound(); + } + + + + + + void AddBrewingstandEntity(cBrewingstandEntity * a_Brewingstand) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Brewingstand, "Brewingstand"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Brewingstand->GetContents()); + mWriter.EndList(); + mWriter.AddShort("BrewTime", a_Brewingstand->GetTimeBrewed()); + mWriter.AddShort("Fuel", a_Brewingstand->GetRemainingFuel()); + mWriter.EndCompound(); + } + + + + + + void AddChestEntity(cChestEntity * a_Entity, BLOCKTYPE a_ChestType) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Chest"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Entity->GetContents()); + mWriter.EndList(); + mWriter.EndCompound(); + } + + + + + + void AddDispenserEntity(cDispenserEntity * a_Entity) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Trap"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Entity->GetContents()); + mWriter.EndList(); + mWriter.EndCompound(); + } + + + + + + void AddDropperEntity(cDropperEntity * a_Entity) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Dropper"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Entity->GetContents()); + mWriter.EndList(); + mWriter.EndCompound(); + } + + + + + + void AddFurnaceEntity(cFurnaceEntity * a_Furnace) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Furnace, "Furnace"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Furnace->GetContents()); + mWriter.EndList(); + mWriter.AddShort("BurnTime", static_cast(a_Furnace->GetFuelBurnTimeLeft())); + mWriter.AddShort("CookTime", static_cast(a_Furnace->GetTimeCooked())); + mWriter.EndCompound(); + } + + + + + + void AddHopperEntity(cHopperEntity * a_Entity) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Entity, "Hopper"); + mWriter.BeginList("Items", TAG_Compound); + AddItemGrid(a_Entity->GetContents()); + mWriter.EndList(); + mWriter.EndCompound(); + } + + + + + + void AddJukeboxEntity(cJukeboxEntity * a_Jukebox) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Jukebox, "RecordPlayer"); + mWriter.AddInt("Record", a_Jukebox->GetRecord()); + mWriter.EndCompound(); + } + + + + + + void AddMobSpawnerEntity(cMobSpawnerEntity * a_MobSpawner) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_MobSpawner, "MobSpawner"); + mWriter.AddShort("Entity", static_cast(a_MobSpawner->GetEntity())); + mWriter.AddString("EntityId", cMonster::MobTypeToVanillaName(a_MobSpawner->GetEntity())); + mWriter.AddShort("Delay", a_MobSpawner->GetSpawnDelay()); + mWriter.EndCompound(); + } + + + + + + void AddNoteEntity(cNoteEntity * a_Note) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Note, "Music"); + mWriter.AddByte("note", static_cast(a_Note->GetPitch())); + mWriter.EndCompound(); + } + + + + + + void AddCommandBlockEntity(cCommandBlockEntity * a_CmdBlock) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_CmdBlock, "Control"); + mWriter.AddString("Command", a_CmdBlock->GetCommand()); + mWriter.AddInt ("SuccessCount", a_CmdBlock->GetResult()); + mWriter.AddString("LastOutput", a_CmdBlock->GetLastOutput()); + mWriter.AddByte ("TrackOutput", 1); // TODO 2014-01-18 xdot: Figure out what TrackOutput is and save it. + mWriter.EndCompound(); + } + + + + + + void AddSignEntity(cSignEntity * a_Sign) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_Sign, "Sign"); + mWriter.AddString("Text1", a_Sign->GetLine(0)); + mWriter.AddString("Text2", a_Sign->GetLine(1)); + mWriter.AddString("Text3", a_Sign->GetLine(2)); + mWriter.AddString("Text4", a_Sign->GetLine(3)); + mWriter.EndCompound(); + } + + + + + + void AddMobHeadEntity(cMobHeadEntity * a_MobHead) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_MobHead, "Skull"); + mWriter.AddByte ("SkullType", a_MobHead->GetType() & 0xFF); + mWriter.AddByte ("Rot", a_MobHead->GetRotation() & 0xFF); + + // The new Block Entity format for a Mob Head. See: https://minecraft.gamepedia.com/Head#Block_entity + mWriter.BeginCompound("Owner"); + mWriter.AddString("Id", a_MobHead->GetOwnerUUID().ToShortString()); + mWriter.AddString("Name", a_MobHead->GetOwnerName()); + mWriter.BeginCompound("Properties"); + mWriter.BeginList("textures", TAG_Compound); + mWriter.BeginCompound(""); + mWriter.AddString("Signature", a_MobHead->GetOwnerTextureSignature()); + mWriter.AddString("Value", a_MobHead->GetOwnerTexture()); + mWriter.EndCompound(); + mWriter.EndList(); + mWriter.EndCompound(); + mWriter.EndCompound(); + mWriter.EndCompound(); + } + + + + + + void AddFlowerPotEntity(cFlowerPotEntity * a_FlowerPot) + { + mWriter.BeginCompound(""); + AddBasicTileEntity(a_FlowerPot, "FlowerPot"); + mWriter.AddInt ("Item", static_cast(a_FlowerPot->GetItem().m_ItemType)); + mWriter.AddInt ("Data", static_cast(a_FlowerPot->GetItem().m_ItemDamage)); + mWriter.EndCompound(); + } + + + + + + void AddBasicEntity(cEntity * a_Entity, const AString & a_ClassName) + { + mWriter.AddString("id", a_ClassName); + mWriter.BeginList("Pos", TAG_Double); + mWriter.AddDouble("", a_Entity->GetPosX()); + mWriter.AddDouble("", a_Entity->GetPosY()); + mWriter.AddDouble("", a_Entity->GetPosZ()); + mWriter.EndList(); + mWriter.BeginList("Motion", TAG_Double); + mWriter.AddDouble("", a_Entity->GetSpeedX()); + mWriter.AddDouble("", a_Entity->GetSpeedY()); + mWriter.AddDouble("", a_Entity->GetSpeedZ()); + mWriter.EndList(); + mWriter.BeginList("Rotation", TAG_Double); + mWriter.AddDouble("", a_Entity->GetYaw()); + mWriter.AddDouble("", a_Entity->GetPitch()); + mWriter.EndList(); + mWriter.AddFloat("Health", a_Entity->GetHealth()); + } + + + + + + void AddBoatEntity(cBoat * a_Boat) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_Boat, "Boat"); + mWriter.AddString("Type", cBoat::MaterialToString(a_Boat->GetMaterial())); + mWriter.EndCompound(); + } + + + + + + void AddEnderCrystalEntity(cEnderCrystal * a_EnderCrystal) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_EnderCrystal, "EnderCrystal"); + mWriter.EndCompound(); + } + + + + + + void AddFallingBlockEntity(cFallingBlock * a_FallingBlock) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_FallingBlock, "FallingSand"); + mWriter.AddInt("TileID", a_FallingBlock->GetBlockType()); + mWriter.AddByte("Data", a_FallingBlock->GetBlockMeta()); + mWriter.AddByte("Time", 1); // Unused in Cuberite, Vanilla said to need nonzero + mWriter.AddByte("DropItem", 1); + mWriter.AddByte("HurtEntities", a_FallingBlock->GetBlockType() == E_BLOCK_ANVIL); + mWriter.EndCompound(); + } + + + + + + void AddMinecartEntity(cMinecart * a_Minecart) + { + mWriter.BeginCompound(""); + + switch (a_Minecart->GetPayload()) + { + case cMinecart::mpChest: + { + AddBasicEntity(a_Minecart, "MinecartChest"); + // Add chest contents into the Items tag: + AddMinecartChestContents(static_cast(a_Minecart)); + break; + } + case cMinecart::mpFurnace: + { + AddBasicEntity(a_Minecart, "MinecartFurnace"); + // TODO: Add "Push" and "Fuel" tags + break; + } + case cMinecart::mpHopper: + { + AddBasicEntity(a_Minecart, "MinecartHopper"); + // TODO: Add hopper contents? + break; + } + case cMinecart::mpTNT: + { + AddBasicEntity(a_Minecart, "MinecartTNT"); + break; + } + case cMinecart::mpNone: + { + AddBasicEntity(a_Minecart, "MinecartRideable"); + break; + } + } // switch (Payload) + + mWriter.EndCompound(); + } + + + + + + void AddMonsterEntity(cMonster * a_Monster) + { + const char * EntityClass = nullptr; + switch (a_Monster->GetMobType()) + { + case mtBat: EntityClass = "Bat"; break; + case mtBlaze: EntityClass = "Blaze"; break; + case mtCaveSpider: EntityClass = "CaveSpider"; break; + case mtChicken: EntityClass = "Chicken"; break; + case mtCow: EntityClass = "Cow"; break; + case mtCreeper: EntityClass = "Creeper"; break; + case mtEnderDragon: EntityClass = "EnderDragon"; break; + case mtEnderman: EntityClass = "Enderman"; break; + case mtGhast: EntityClass = "Ghast"; break; + case mtGiant: EntityClass = "Giant"; break; + case mtGuardian: EntityClass = "Guardian"; break; + case mtHorse: EntityClass = "Horse"; break; + case mtIronGolem: EntityClass = "VillagerGolem"; break; + case mtMagmaCube: EntityClass = "LavaSlime"; break; + case mtMooshroom: EntityClass = "MushroomCow"; break; + case mtOcelot: EntityClass = "Ozelot"; break; + case mtPig: EntityClass = "Pig"; break; + case mtRabbit: EntityClass = "Rabbit"; break; + case mtSheep: EntityClass = "Sheep"; break; + case mtSilverfish: EntityClass = "Silverfish"; break; + case mtSkeleton: EntityClass = "Skeleton"; break; + case mtSlime: EntityClass = "Slime"; break; + case mtSnowGolem: EntityClass = "SnowMan"; break; + case mtSpider: EntityClass = "Spider"; break; + case mtSquid: EntityClass = "Squid"; break; + case mtVillager: EntityClass = "Villager"; break; + case mtWitch: EntityClass = "Witch"; break; + case mtWither: EntityClass = "WitherBoss"; break; + case mtWolf: EntityClass = "Wolf"; break; + case mtZombie: EntityClass = "Zombie"; break; + case mtZombiePigman: EntityClass = "PigZombie"; break; + default: + { + ASSERT(!"Unhandled monster type"); + return; + } + } // switch (payload) + + mWriter.BeginCompound(""); + AddBasicEntity(a_Monster, EntityClass); + mWriter.BeginList("DropChances", TAG_Float); + mWriter.AddFloat("", a_Monster->GetDropChanceWeapon()); + mWriter.AddFloat("", a_Monster->GetDropChanceHelmet()); + mWriter.AddFloat("", a_Monster->GetDropChanceChestplate()); + mWriter.AddFloat("", a_Monster->GetDropChanceLeggings()); + mWriter.AddFloat("", a_Monster->GetDropChanceBoots()); + mWriter.EndList(); + mWriter.AddByte("CanPickUpLoot", (a_Monster->CanPickUpLoot())? 1 : 0); + mWriter.AddString("CustomName", a_Monster->GetCustomName()); + mWriter.AddByte("CustomNameVisible", static_cast(a_Monster->IsCustomNameAlwaysVisible())); + + // Mob was leashed + if (a_Monster->IsLeashed() || (a_Monster->GetLeashToPos() != nullptr)) + { + mWriter.AddByte("Leashed", 1); + + const Vector3d * LeashedToPos = nullptr; + + if (a_Monster->GetLeashToPos() != nullptr) + { + LeashedToPos = a_Monster->GetLeashToPos(); + } + else if (a_Monster->GetLeashedTo()->IsLeashKnot()) + { + LeashedToPos = & a_Monster->GetLeashedTo()->GetPosition(); + } + + if (LeashedToPos != nullptr) + { + mWriter.BeginCompound("Leash"); + mWriter.AddDouble("X", LeashedToPos->x); + mWriter.AddDouble("Y", LeashedToPos->y); + mWriter.AddDouble("Z", LeashedToPos->z); + mWriter.EndCompound(); + } + } + + switch (a_Monster->GetMobType()) + { + case mtBat: + { + mWriter.AddByte("BatFlags", static_cast(a_Monster)->IsHanging()); + break; + } + case mtCreeper: + { + const cCreeper *Creeper = static_cast(a_Monster); + mWriter.AddByte("powered", Creeper->IsCharged()); + mWriter.AddByte("ignited", Creeper->IsBlowing()); + break; + } + case mtEnderman: + { + const cEnderman *Enderman = static_cast(a_Monster); + mWriter.AddShort("carried", static_cast(Enderman->GetCarriedBlock())); + mWriter.AddShort("carriedData", static_cast(Enderman->GetCarriedMeta())); + break; + } + case mtHorse: + { + const cHorse *Horse = static_cast(a_Monster); + mWriter.AddByte("ChestedHorse", Horse->IsChested()? 1 : 0); + mWriter.AddByte("EatingHaystack", Horse->IsEating()? 1 : 0); + mWriter.AddByte("Tame", Horse->IsTame()? 1: 0); + mWriter.AddInt ("Type", Horse->GetHorseType()); + mWriter.AddInt ("Color", Horse->GetHorseColor()); + mWriter.AddInt ("Style", Horse->GetHorseStyle()); + mWriter.AddInt ("ArmorType", Horse->GetHorseArmour()); + mWriter.AddByte("Saddle", Horse->IsSaddled()? 1 : 0); + mWriter.AddInt ("Age", Horse->GetAge()); + break; + } + case mtMagmaCube: + { + mWriter.AddInt("Size", static_cast(a_Monster)->GetSize()); + break; + } + case mtOcelot: + { + const auto *Ocelot = static_cast(a_Monster); + if (!Ocelot->GetOwnerName().empty()) + { + mWriter.AddString("Owner", Ocelot->GetOwnerName()); + } + if (!Ocelot->GetOwnerUUID().IsNil()) + { + mWriter.AddString("OwnerUUID", Ocelot->GetOwnerUUID().ToShortString()); + } + mWriter.AddByte("Sitting", Ocelot->IsSitting() ? 1 : 0); + mWriter.AddInt("CatType", Ocelot->GetOcelotType()); + mWriter.AddInt("Age", Ocelot->GetAge()); + break; + } + case mtPig: + { + mWriter.AddInt("Age", static_cast(a_Monster)->GetAge()); + break; + } + case mtRabbit: + { + const cRabbit * Rabbit = static_cast(a_Monster); + mWriter.AddInt("RabbitType", static_cast(Rabbit->GetRabbitType())); + mWriter.AddInt("MoreCarrotTicks", Rabbit->GetMoreCarrotTicks()); + mWriter.AddInt("Age", Rabbit->GetAge()); + break; + } + case mtSheep: + { + const cSheep *Sheep = static_cast(a_Monster); + mWriter.AddByte("Sheared", Sheep->IsSheared()? 1 : 0); + mWriter.AddByte("Color", static_cast(Sheep->GetFurColor())); + mWriter.AddInt ("Age", Sheep->GetAge()); + break; + } + case mtSlime: + { + mWriter.AddInt("Size", static_cast(a_Monster)->GetSize()); + break; + } + case mtSkeleton: + { + mWriter.AddByte("SkeletonType", (static_cast(a_Monster)->IsWither() ? 1 : 0)); + break; + } + case mtVillager: + { + const cVillager *Villager = static_cast(a_Monster); + mWriter.AddInt("Profession", Villager->GetVilType()); + mWriter.AddInt("Age", Villager->GetAge()); + break; + } + case mtWither: + { + mWriter.AddInt("Invul", static_cast(static_cast(a_Monster)->GetWitherInvulnerableTicks())); + break; + } + case mtWolf: + { + const cWolf *Wolf = static_cast(a_Monster); + if (!Wolf->GetOwnerName().empty()) + { + mWriter.AddString("Owner", Wolf->GetOwnerName()); + } + if (!Wolf->GetOwnerUUID().IsNil()) + { + mWriter.AddString("OwnerUUID", Wolf->GetOwnerUUID().ToShortString()); + } + mWriter.AddByte("Sitting", Wolf->IsSitting() ? 1 : 0); + mWriter.AddByte("Angry", Wolf->IsAngry() ? 1 : 0); + mWriter.AddByte("CollarColor", static_cast(Wolf->GetCollarColor())); + mWriter.AddInt ("Age", Wolf->GetAge()); + break; + } + case mtZombie: + { + const cZombie *Zombie = static_cast(a_Monster); + mWriter.AddByte("IsVillager", Zombie->IsVillagerZombie() ? 1 : 0); + mWriter.AddByte("IsConverting", Zombie->IsConverting() ? 1 : 0); + mWriter.AddInt ("Age", Zombie->GetAge()); + break; + } + case mtZombiePigman: + { + mWriter.AddInt("Age", static_cast(a_Monster)->GetAge()); + break; + } + case mtBlaze: + case mtCaveSpider: + case mtChicken: + case mtCow: + case mtEnderDragon: + case mtGhast: + case mtGiant: + case mtGuardian: + case mtIronGolem: + case mtMooshroom: + case mtSilverfish: + case mtSnowGolem: + case mtSpider: + case mtSquid: + case mtWitch: + { + // Other mobs have no special tags. + break; + } + case mtInvalidType: + { + ASSERT(!"NBTChunkSerializer::SerializerCollector::AddMonsterEntity: Recieved mob of invalid type"); + break; + } + } + mWriter.EndCompound(); + } + + + + + + void AddPickupEntity(cPickup * a_Pickup) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_Pickup, "Item"); + AddItem(a_Pickup->GetItem(), -1, "Item"); + mWriter.AddShort("Age", static_cast(a_Pickup->GetAge())); + mWriter.EndCompound(); + } + + + + + + void AddProjectileEntity(cProjectileEntity * a_Projectile) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_Projectile, a_Projectile->GetMCAClassName()); + mWriter.AddByte("inGround", a_Projectile->IsInGround() ? 1 : 0); + + switch (a_Projectile->GetProjectileKind()) + { + case cProjectileEntity::pkArrow: + { + cArrowEntity * Arrow = static_cast(a_Projectile); + + mWriter.AddShort("xTile", static_cast(Arrow->GetBlockHit().x)); + mWriter.AddShort("yTile", static_cast(Arrow->GetBlockHit().y)); + mWriter.AddShort("zTile", static_cast(Arrow->GetBlockHit().z)); + mWriter.AddByte("pickup", Arrow->GetPickupState()); + mWriter.AddDouble("damage", Arrow->GetDamageCoeff()); + break; + } + case cProjectileEntity::pkSplashPotion: + { + cSplashPotionEntity * Potion = static_cast(a_Projectile); + + mWriter.AddInt("EffectType", static_cast(Potion->GetEntityEffectType())); + mWriter.AddInt("EffectDuration", static_cast(Potion->GetEntityEffect().GetDuration())); + mWriter.AddShort("EffectIntensity", Potion->GetEntityEffect().GetIntensity()); + mWriter.AddDouble("EffectDistanceModifier", Potion->GetEntityEffect().GetDistanceModifier()); + mWriter.AddInt("PotionName", Potion->GetPotionColor()); + break; + } + case cProjectileEntity::pkGhastFireball: + { + mWriter.AddInt("ExplosionPower", 1); + break; + } + case cProjectileEntity::pkFireCharge: + case cProjectileEntity::pkWitherSkull: + case cProjectileEntity::pkEnderPearl: + { + break; + } + default: + { + ASSERT(!"Unsaved projectile entity!"); + } + } // switch (ProjectileKind) + + if (!a_Projectile->GetCreatorName().empty()) + { + mWriter.AddString("ownerName", a_Projectile->GetCreatorName()); + } + mWriter.EndCompound(); + } + + + + + + void AddHangingEntity(cHangingEntity * a_Hanging) + { + mWriter.AddInt("TileX", FloorC(a_Hanging->GetPosX())); + mWriter.AddInt("TileY", FloorC(a_Hanging->GetPosY())); + mWriter.AddInt("TileZ", FloorC(a_Hanging->GetPosZ())); + mWriter.AddByte("Facing", a_Hanging->GetProtocolFacing()); + } + + + + + + void AddTNTEntity(cTNTEntity * a_TNT) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_TNT, "PrimedTnt"); + mWriter.AddByte("Fuse", static_cast(a_TNT->GetFuseTicks())); + mWriter.EndCompound(); + } + + + + + + void AddExpOrbEntity(cExpOrb * a_ExpOrb) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_ExpOrb, "XPOrb"); + mWriter.AddShort("Age", static_cast(a_ExpOrb->GetAge())); + mWriter.AddShort("Value", static_cast(a_ExpOrb->GetReward())); + mWriter.EndCompound(); + } + + + + + + void AddItemFrameEntity(cItemFrame * a_ItemFrame) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_ItemFrame, "ItemFrame"); + AddHangingEntity(a_ItemFrame); + AddItem(a_ItemFrame->GetItem(), -1, "Item"); + mWriter.AddByte("ItemRotation", static_cast(a_ItemFrame->GetItemRotation())); + mWriter.AddFloat("ItemDropChance", 1.0F); + mWriter.EndCompound(); + } + + + + + + void AddLeashKnotEntity(cLeashKnot * a_LeashKnot) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_LeashKnot, "LeashKnot"); + AddHangingEntity(a_LeashKnot); + mWriter.EndCompound(); + } + + + + + + void AddPaintingEntity(cPainting * a_Painting) + { + mWriter.BeginCompound(""); + AddBasicEntity(a_Painting, "Painting"); + AddHangingEntity(a_Painting); + mWriter.AddString("Motive", a_Painting->GetName()); + mWriter.EndCompound(); + } + + + + + + void AddMinecartChestContents(cMinecartWithChest * a_Minecart) + { + mWriter.BeginList("Items", TAG_Compound); + for (int i = 0; i < cMinecartWithChest::ContentsHeight * cMinecartWithChest::ContentsWidth; i++) + { + const cItem & Item = a_Minecart->GetSlot(i); + if (Item.IsEmpty()) + { + continue; + } + AddItem(Item, i); + } + mWriter.EndList(); + } +}; // SerializerCollector + + + + + +//////////////////////////////////////////////////////////////////////////////// +// NBTChunkSerializer: + +bool NBTChunkSerializer::serialize(const cWorld & aWorld, cChunkCoords aCoords, cFastNBTWriter & aWriter) { - m_IsLightValid = a_IsLightValid; + SerializerCollector serializer(aWriter); + aWriter.BeginCompound("Level"); + aWriter.AddInt("xPos", aCoords.m_ChunkX); + aWriter.AddInt("zPos", aCoords.m_ChunkZ); + if (!aWorld.GetChunkData(aCoords, serializer)) + { + aWriter.EndCompound(); // "Level" + return false; + } + serializer.Finish(); // Close NBT tags + + // Save biomes, both MCS (IntArray) and MC-vanilla (ByteArray): + if (serializer.mBiomesAreValid) + { + aWriter.AddByteArray("Biomes", reinterpret_cast(serializer.mVanillaBiomes), ARRAYCOUNT(serializer.mVanillaBiomes)); + aWriter.AddIntArray ("MCSBiomes", reinterpret_cast(serializer.mBiomes), ARRAYCOUNT(serializer.mBiomes)); + } + + // Save heightmap (Vanilla require this): + aWriter.AddIntArray("HeightMap", reinterpret_cast(serializer.mVanillaHeightMap), ARRAYCOUNT(serializer.mVanillaHeightMap)); + + // Save blockdata: + aWriter.BeginList("Sections", TAG_Compound); + for (size_t Y = 0; Y != cChunkData::NumSections; ++Y) + { + auto section = serializer.m_Data.GetSection(Y); + if (section == nullptr) + { + continue; + } + + aWriter.BeginCompound(""); + aWriter.AddByteArray("Blocks", reinterpret_cast(section->m_BlockTypes), ARRAYCOUNT(section->m_BlockTypes)); + aWriter.AddByteArray("Data", reinterpret_cast(section->m_BlockMetas), ARRAYCOUNT(section->m_BlockMetas)); + + #ifdef DEBUG_SKYLIGHT + aWriter.AddByteArray("BlockLight", reinterpret_cast(section->m_BlockSkyLight), ARRAYCOUNT(section->m_BlockSkyLight)); + #else + aWriter.AddByteArray("BlockLight", reinterpret_cast(section->m_BlockLight), ARRAYCOUNT(section->m_BlockLight)); + #endif + + aWriter.AddByteArray("SkyLight", reinterpret_cast(section->m_BlockSkyLight), ARRAYCOUNT(section->m_BlockSkyLight)); + aWriter.AddByte("Y", static_cast(Y)); + aWriter.EndCompound(); + } + aWriter.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.mIsLightValid) + { + aWriter.AddByte("MCSIsLightValid", 1); + } + + // Save the world age to the chunk data. Required by vanilla and mcedit. + aWriter.AddLong("LastUpdate", aWorld.GetWorldAge()); + + // Store the flag that the chunk has all the ores, trees, dungeons etc. Cuberite chunks are always complete. + aWriter.AddByte("TerrainPopulated", 1); + + aWriter.EndCompound(); // "Level" + return true; } - - - - - -void cNBTChunkSerializer::HeightMap(const cChunkDef::HeightMap * a_HeightMap) -{ - for (int RelX = 0; RelX < cChunkDef::Width; RelX++) - { - for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++) - { - int Height = cChunkDef::GetHeight(*a_HeightMap, RelX, RelZ); - m_VanillaHeightMap[(RelZ << 4) | RelX] = Height; - } - } -} - - - - - -void cNBTChunkSerializer::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) -{ - memcpy(m_Biomes, a_BiomeMap, sizeof(m_Biomes)); - for (size_t i = 0; i < ARRAYCOUNT(m_Biomes); i++) - { - if ((*a_BiomeMap)[i] < 255) - { - // Normal MC biome, copy as-is: - m_VanillaBiomes[i] = static_cast((*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::etBoat: AddBoatEntity (static_cast (a_Entity)); break; - case cEntity::etEnderCrystal: AddEnderCrystalEntity(static_cast (a_Entity)); break; - case cEntity::etFallingBlock: AddFallingBlockEntity(static_cast (a_Entity)); break; - case cEntity::etMinecart: AddMinecartEntity (static_cast (a_Entity)); break; - case cEntity::etMonster: AddMonsterEntity (static_cast (a_Entity)); break; - case cEntity::etPickup: AddPickupEntity (static_cast (a_Entity)); break; - case cEntity::etProjectile: AddProjectileEntity (static_cast(a_Entity)); break; - case cEntity::etTNT: AddTNTEntity (static_cast (a_Entity)); break; - case cEntity::etExpOrb: AddExpOrbEntity (static_cast (a_Entity)); break; - case cEntity::etItemFrame: AddItemFrameEntity (static_cast (a_Entity)); break; - case cEntity::etLeashKnot: AddLeashKnotEntity (static_cast (a_Entity)); break; - case cEntity::etPainting: AddPaintingEntity (static_cast (a_Entity)); break; - case cEntity::etPlayer: return; // Players aren't saved into the world - case cEntity::etFloater: return; // Floaters aren't saved either - 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_BEACON: AddBeaconEntity (static_cast (a_Entity)); break; - case E_BLOCK_BED: AddBedEntity (static_cast (a_Entity)); break; - case E_BLOCK_BREWING_STAND: AddBrewingstandEntity(static_cast(a_Entity)); break; - case E_BLOCK_CHEST: AddChestEntity (static_cast (a_Entity), a_Entity->GetBlockType()); break; - case E_BLOCK_COMMAND_BLOCK: AddCommandBlockEntity(static_cast(a_Entity)); break; - case E_BLOCK_DISPENSER: AddDispenserEntity (static_cast (a_Entity)); break; - case E_BLOCK_DROPPER: AddDropperEntity (static_cast (a_Entity)); break; - case E_BLOCK_ENDER_CHEST: /* No data to be saved */ break; - case E_BLOCK_FLOWER_POT: AddFlowerPotEntity (static_cast (a_Entity)); break; - case E_BLOCK_FURNACE: AddFurnaceEntity (static_cast (a_Entity)); break; - case E_BLOCK_HEAD: AddMobHeadEntity (static_cast (a_Entity)); break; - case E_BLOCK_HOPPER: AddHopperEntity (static_cast (a_Entity)); break; - case E_BLOCK_JUKEBOX: AddJukeboxEntity (static_cast (a_Entity)); break; - case E_BLOCK_LIT_FURNACE: AddFurnaceEntity (static_cast (a_Entity)); break; - case E_BLOCK_MOB_SPAWNER: AddMobSpawnerEntity (static_cast (a_Entity)); break; - case E_BLOCK_NOTE_BLOCK: AddNoteEntity (static_cast (a_Entity)); break; - case E_BLOCK_SIGN_POST: AddSignEntity (static_cast (a_Entity)); break; - case E_BLOCK_TRAPPED_CHEST: AddChestEntity (static_cast (a_Entity), a_Entity->GetBlockType()); break; - case E_BLOCK_WALLSIGN: AddSignEntity (static_cast (a_Entity)); break; - - default: - { - ASSERT(!"Unhandled block entity saved into Anvil"); - } - } - m_HasHadBlockEntity = true; -} - - - - diff --git a/src/WorldStorage/NBTChunkSerializer.h b/src/WorldStorage/NBTChunkSerializer.h index a7cb1de1e..b14a58c40 100644 --- a/src/WorldStorage/NBTChunkSerializer.h +++ b/src/WorldStorage/NBTChunkSerializer.h @@ -1,139 +1,27 @@ - // NBTChunkSerializer.h -// Declares the cNBTChunkSerializer class that is used for saving individual chunks into NBT format used by Anvil - - - - - #pragma once -#include "../ChunkDataCallback.h" - // fwd: class cFastNBTWriter; -class cEntity; -class cBlockEntity; -class cBoat; -class cBeaconEntity; -class cBedEntity; -class cBrewingstandEntity; -class cChestEntity; -class cCommandBlockEntity; -class cDispenserEntity; -class cDropperEntity; -class cEnderCrystal; -class cFurnaceEntity; -class cHopperEntity; -class cJukeboxEntity; -class cNoteEntity; -class cSignEntity; -class cMobHeadEntity; -class cMobSpawnerEntity; -class cFlowerPotEntity; -class cFallingBlock; -class cMinecart; -class cMinecartWithChest; -class cMonster; -class cPickup; -class cItemGrid; -class cProjectileEntity; -class cTNTEntity; -class cExpOrb; -class cHangingEntity; -class cItemFrame; -class cLeashKnot; -class cPainting; +class cWorld; -class cNBTChunkSerializer : - public cChunkDataCopyCollector +/** Saves the chunk data into a NBT format, used by the Anvil storage. +The Writer is expected to be set up so that the serializer can write the chunk's top level "Level" NBT tag immediately. +Provides a single static entry point that does all the work, through a hidden worker class in the CPP file. */ +class NBTChunkSerializer { public: - cChunkDef::BiomeMap m_Biomes; - unsigned char m_VanillaBiomes[cChunkDef::Width * cChunkDef::Width]; - int m_VanillaHeightMap[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 cChunkDataCopyCollector we inherit: - - cChunkData m_Data */ - - 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 = ""); - - /** Writes an item grid into the writer; begins the stored slot numbers with a_BeginSlotNum. Note that it doesn't begin nor end the list tag */ - void AddItemGrid(const cItemGrid & a_Grid, int a_BeginSlotNum = 0); - - // Block entities: - void AddBasicTileEntity (cBlockEntity * a_Entity, const char * a_EntityTypeID); - void AddBeaconEntity (cBeaconEntity * a_Entity); - void AddBedEntity (cBedEntity * a_Entity); - void AddBrewingstandEntity(cBrewingstandEntity * a_Brewingstand); - void AddChestEntity (cChestEntity * a_Entity, BLOCKTYPE a_ChestType); - void AddDispenserEntity (cDispenserEntity * a_Entity); - void AddDropperEntity (cDropperEntity * a_Entity); - void AddFurnaceEntity (cFurnaceEntity * a_Furnace); - void AddHopperEntity (cHopperEntity * a_Entity); - void AddJukeboxEntity (cJukeboxEntity * a_Jukebox); - void AddMobSpawnerEntity (cMobSpawnerEntity * a_MobSpawner); - void AddNoteEntity (cNoteEntity * a_Note); - void AddSignEntity (cSignEntity * a_Sign); - void AddMobHeadEntity (cMobHeadEntity * a_MobHead); - void AddCommandBlockEntity(cCommandBlockEntity * a_CmdBlock); - void AddFlowerPotEntity (cFlowerPotEntity * a_FlowerPot); - - // Entities: - void AddBasicEntity (cEntity * a_Entity, const AString & a_ClassName); - void AddBoatEntity (cBoat * a_Boat); - void AddEnderCrystalEntity(cEnderCrystal * a_EnderCrystal); - void AddFallingBlockEntity(cFallingBlock * a_FallingBlock); - void AddMinecartEntity (cMinecart * a_Minecart); - void AddMonsterEntity (cMonster * a_Monster); - void AddPickupEntity (cPickup * a_Pickup); - void AddProjectileEntity (cProjectileEntity * a_Projectile); - void AddHangingEntity (cHangingEntity * a_Hanging); - void AddTNTEntity (cTNTEntity * a_TNT); - void AddExpOrbEntity (cExpOrb * a_ExpOrb); - void AddItemFrameEntity (cItemFrame * a_ItemFrame); - void AddLeashKnotEntity (cLeashKnot * a_LeashKnot); - void AddPaintingEntity (cPainting * a_Painting); - - void AddMinecartChestContents(cMinecartWithChest * a_Minecart); - - // cChunkDataSeparateCollector overrides: - virtual void LightIsValid(bool a_IsLightValid) override; - virtual void HeightMap(const cChunkDef::HeightMap * a_HeightMap) 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 - - - + /** Serializes the chunk into the specified writer. + Returns true on success, false on failure (chunk not present etc.) */ + static bool serialize(const cWorld & aWorld, cChunkCoords aCoords, cFastNBTWriter & aWriter); +}; diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index d2f3284f5..8c6c9fb67 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -491,68 +491,11 @@ void cWSSAnvil::CopyNBTData(const cParsedNBT & a_NBT, int a_Tag, const AString & 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)) + if (!NBTChunkSerializer::serialize(*m_World, a_Chunk, a_Writer)) { - LOGWARNING("Cannot get chunk [%d, %d] data for NBT saving", a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ); + LOGWARNING("Failed to save chunk %s.", a_Chunk.ToString()); return false; } - Serializer.Finish(); // Close NBT tags - - // Save biomes, both MCS (IntArray) and MC-vanilla (ByteArray): - if (Serializer.m_BiomesAreValid) - { - a_Writer.AddByteArray("Biomes", reinterpret_cast(Serializer.m_VanillaBiomes), ARRAYCOUNT(Serializer.m_VanillaBiomes)); - a_Writer.AddIntArray ("MCSBiomes", reinterpret_cast(Serializer.m_Biomes), ARRAYCOUNT(Serializer.m_Biomes)); - } - - // Save heightmap (Vanilla require this): - a_Writer.AddIntArray("HeightMap", reinterpret_cast(Serializer.m_VanillaHeightMap), ARRAYCOUNT(Serializer.m_VanillaHeightMap)); - - // Save blockdata: - a_Writer.BeginList("Sections", TAG_Compound); - for (size_t Y = 0; Y != cChunkData::NumSections; ++Y) - { - auto Section = Serializer.m_Data.GetSection(Y); - if (Section == nullptr) - { - continue; - } - - a_Writer.BeginCompound(""); - a_Writer.AddByteArray("Blocks", reinterpret_cast(Section->m_BlockTypes), ARRAYCOUNT(Section->m_BlockTypes)); - a_Writer.AddByteArray("Data", reinterpret_cast(Section->m_BlockMetas), ARRAYCOUNT(Section->m_BlockMetas)); - - #ifdef DEBUG_SKYLIGHT - a_Writer.AddByteArray("BlockLight", reinterpret_cast(Section->m_BlockSkyLight), ARRAYCOUNT(Section->m_BlockSkyLight)); - #else - a_Writer.AddByteArray("BlockLight", reinterpret_cast(Section->m_BlockLight), ARRAYCOUNT(Section->m_BlockLight)); - #endif - - a_Writer.AddByteArray("SkyLight", reinterpret_cast(Section->m_BlockSkyLight), ARRAYCOUNT(Section->m_BlockSkyLight)); - a_Writer.AddByte("Y", static_cast(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); - } - - // Save the world age to the chunk data. Required by vanilla and mcedit. - a_Writer.AddLong("LastUpdate", m_World->GetWorldAge()); - - // Store the flag that the chunk has all the ores, trees, dungeons etc. MCS chunks are always complete. - a_Writer.AddByte("TerrainPopulated", 1); - - a_Writer.EndCompound(); // "Level" return true; }