// NBTChunkSerializer.cpp #include "Globals.h" #include "NBTChunkSerializer.h" #include "EnchantmentSerializer.h" #include "NamespaceSerializer.h" #include "../ChunkDataCallback.h" #include "../ItemGrid.h" #include "../StringCompression.h" #include "../UUID.h" #include "FastNBT.h" #include "../BlockEntities/BeaconEntity.h" #include "../BlockEntities/BedEntity.h" #include "../BlockEntities/BrewingstandEntity.h" #include "../BlockEntities/ChestEntity.h" #include "../BlockEntities/CommandBlockEntity.h" #include "../BlockEntities/DispenserEntity.h" #include "../BlockEntities/DropperEntity.h" #include "../BlockEntities/EnchantingTableEntity.h" #include "../BlockEntities/EnderChestEntity.h" #include "../BlockEntities/EndPortalEntity.h" #include "../BlockEntities/FurnaceEntity.h" #include "../BlockEntities/HopperEntity.h" #include "../BlockEntities/JukeboxEntity.h" #include "../BlockEntities/MobSpawnerEntity.h" #include "../BlockEntities/NoteEntity.h" #include "../BlockEntities/SignEntity.h" #include "../BlockEntities/MobHeadEntity.h" #include "../BlockEntities/FlowerPotEntity.h" #include "../Entities/Entity.h" #include "../Entities/EnderCrystal.h" #include "../Entities/FallingBlock.h" #include "../Entities/Boat.h" #include "../Entities/Minecart.h" #include "../Entities/Pickup.h" #include "../Entities/ArrowEntity.h" #include "../Entities/SplashPotionEntity.h" #include "../Entities/TNTEntity.h" #include "../Entities/ExpOrb.h" #include "../Entities/HangingEntity.h" #include "../Entities/ItemFrame.h" #include "../Entities/LeashKnot.h" #include "../Entities/Painting.h" #include "../Mobs/IncludeAllMonsters.h" /** Collects and stores the chunk data via the cChunkDataCallback interface */ class SerializerCollector final : public cChunkDataCopyCollector { public: // The data collected from the chunk: UInt8 Biomes[cChunkDef::Width * cChunkDef::Width]; int Heights[cChunkDef::Width * cChunkDef::Width]; /** 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; SerializerCollector(cFastNBTWriter & aWriter): mIsTagOpen(false), mHasHadEntity(false), mHasHadBlockEntity(false), mIsLightValid(false), mWriter(aWriter) { } virtual void LightIsValid(bool a_IsLightValid) override { mIsLightValid = a_IsLightValid; } virtual void HeightMap(const cChunkDef::HeightMap & a_HeightMap) override { for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++) { for (int RelX = 0; RelX < cChunkDef::Width; RelX++) { Heights[RelX + RelZ * cChunkDef::Width] = cChunkDef::GetHeight(a_HeightMap, RelX, RelZ); } } } virtual void BiomeMap(const cChunkDef::BiomeMap & a_BiomeMap) override { for (size_t i = 0; i < ARRAYCOUNT(Biomes); i++) { if (a_BiomeMap[i] < 255) { // Normal MC biome, copy as-is: Biomes[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 - mBiomeMap[] } virtual void Entity(cEntity * a_Entity) override { // Add entity into NBT: if (mIsTagOpen) { if (!mHasHadEntity) { mWriter.EndList(); mWriter.BeginList("Entities", TAG_Compound); } } else { mWriter.BeginList("Entities", TAG_Compound); } mIsTagOpen = true; mHasHadEntity = 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; } } } virtual void BlockEntity(cBlockEntity * a_Entity) override { if (mIsTagOpen) { 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_ENCHANTMENT_TABLE: AddEnchantingTableEntity(static_cast(a_Entity)); break; case E_BLOCK_ENDER_CHEST: AddEnderchestEntity (static_cast (a_Entity)); break; case E_BLOCK_END_PORTAL: AddEndPortalEntity (static_cast (a_Entity)); 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(); } // 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 + a_BeginSlotNum); } // for i - slots[] } 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 AddEnchantingTableEntity(cEnchantingTableEntity * a_Entity) { mWriter.BeginCompound(""); AddBasicTileEntity(a_Entity, "EnchantingTable"); if (!a_Entity->GetCustomName().empty()) { mWriter.AddString("CustomName", a_Entity->GetCustomName()); } mWriter.EndCompound(); } void AddEnderchestEntity(cEnderChestEntity * a_Entity) { mWriter.BeginCompound(""); AddBasicTileEntity(a_Entity, "EnderChest"); mWriter.EndCompound(); } void AddEndPortalEntity(cEndPortalEntity * a_Entity) { mWriter.BeginCompound(""); AddBasicTileEntity(a_Entity, "EndPortal"); 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.AddString("EntityId", NamespaceSerializer::From(a_MobSpawner->GetEntity())); mWriter.AddShort("SpawnCount", a_MobSpawner->GetSpawnCount()); mWriter.AddShort("SpawnRange", a_MobSpawner->GetSpawnRange()); mWriter.AddShort("Delay", a_MobSpawner->GetSpawnDelay()); mWriter.AddShort("MinSpawnDelay", a_MobSpawner->GetMinSpawnDelay()); mWriter.AddShort("MaxSpawnDelay", a_MobSpawner->GetMaxSpawnDelay()); mWriter.AddShort("MaxNearbyEntities", a_MobSpawner->GetMaxNearbyEntities()); mWriter.AddShort("RequiredPlayerRange", a_MobSpawner->GetRequiredPlayerRange()); mWriter.EndCompound(); } void AddNoteEntity(cNoteEntity * a_Note) { mWriter.BeginCompound(""); AddBasicTileEntity(a_Note, "Music"); mWriter.AddByte("note", static_cast(a_Note->GetNote())); 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 std::string_view 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.AddByte("ShowBottom", a_EnderCrystal->ShowsBottom() ? 1 : 0); if (a_EnderCrystal->DisplaysBeam()) { mWriter.BeginCompound("BeamTarget"); const auto & BeamTarget = a_EnderCrystal->GetBeamTarget(); mWriter.AddInt("X", BeamTarget.x); mWriter.AddInt("Y", BeamTarget.y); mWriter.AddInt("Z", BeamTarget.z); mWriter.EndCompound(); } 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) { mWriter.BeginCompound(""); AddBasicEntity(a_Monster, NamespaceSerializer::From(a_Monster->GetMobType())); 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 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: { mWriter.AddInt("Age", static_cast(a_Monster)->GetAge()); break; } case mtZombiePigman: { mWriter.AddInt("Age", static_cast(a_Monster)->GetAge()); break; } case mtZombieVillager: { const cZombieVillager *ZombieVillager = reinterpret_cast(a_Monster); mWriter.AddInt("Profession", ZombieVillager->GetProfession()); mWriter.AddInt("ConversionTime", ZombieVillager->ConversionTime()); mWriter.AddInt("Age", ZombieVillager->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 mtSkeleton: case mtSnowGolem: case mtSpider: case mtSquid: case mtWitch: case mtWitherSkeleton: { // Other mobs have no special tags. break; } case mtCat: case mtCod: case mtDolphin: case mtDonkey: case mtDrowned: case mtElderGuardian: case mtEndermite: case mtEvoker: case mtFox: case mtHoglin: case mtHusk: case mtIllusioner: case mtLlama: case mtMule: case mtPanda: case mtParrot: case mtPhantom: case mtPiglin: case mtPiglinBrute: case mtPillager: case mtPolarBear: case mtPufferfish: case mtRavager: case mtSalmon: case mtShulker: case mtSkeletonHorse: case mtStray: case mtStrider: case mtTraderLlama: case mtTropicalFish: case mtTurtle: case mtVex: case mtVindicator: case mtWanderingTrader: case mtZoglin: case mtZombieHorse: { // All the entities not added LOGD("Saving unimplemented entity type: %d", NamespaceSerializer::From(a_Monster->GetMobType())); 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: void NBTChunkSerializer::Serialize(const cWorld & aWorld, cChunkCoords aCoords, cFastNBTWriter & aWriter) { SerializerCollector serializer(aWriter); aWriter.BeginCompound("Level"); aWriter.AddInt("xPos", aCoords.m_ChunkX); aWriter.AddInt("zPos", aCoords.m_ChunkZ); [[maybe_unused]] const bool Result = aWorld.GetChunkData(aCoords, serializer); // Chunk must be present in order to save ASSERT(Result); serializer.Finish(); // Close NBT tags // Save biomes: aWriter.AddByteArray("Biomes", reinterpret_cast(serializer.Biomes), ARRAYCOUNT(serializer.Biomes)); // Save heightmap (Vanilla require this): aWriter.AddIntArray("HeightMap", reinterpret_cast(serializer.Heights), ARRAYCOUNT(serializer.Heights)); // Save blockdata: aWriter.BeginList("Sections", TAG_Compound); ChunkDef_ForEachSection(serializer.m_BlockData, serializer.m_LightData, { aWriter.BeginCompound(""); if (Blocks != nullptr) { aWriter.AddByteArray("Blocks", reinterpret_cast(Blocks->data()), Blocks->size()); } else { aWriter.AddByteArray("Blocks", ChunkBlockData::SectionBlockCount, ChunkBlockData::DefaultValue); } if (Metas != nullptr) { aWriter.AddByteArray("Data", reinterpret_cast(Metas->data()), Metas->size()); } else { aWriter.AddByteArray("Data", ChunkBlockData::SectionMetaCount, ChunkBlockData::DefaultMetaValue); } if (BlockLights != nullptr) { aWriter.AddByteArray("BlockLight", reinterpret_cast(BlockLights->data()), BlockLights->size()); } else { aWriter.AddByteArray("BlockLight", ChunkLightData::SectionLightCount, ChunkLightData::DefaultBlockLightValue); } if (SkyLights != nullptr) { aWriter.AddByteArray("SkyLight", reinterpret_cast(SkyLights->data()), SkyLights->size()); } else { aWriter.AddByteArray("SkyLight", ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue); } 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" }