From 99b4c2bd09abb0c853e6c26f9b864172a0a049d1 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 21 Jun 2017 07:47:26 +0200 Subject: [PATCH] Anvil storage: load block entities from both old and new name-styles. (#3784) * WSSAnvil: Load the sign text from JSON, too. --- src/WorldStorage/WSSAnvil.cpp | 198 +++++++++++++++++++++------------- src/WorldStorage/WSSAnvil.h | 11 +- 2 files changed, 129 insertions(+), 80 deletions(-) diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 03cec9c58..3bb82ee40 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -9,6 +9,7 @@ #include "FastNBT.h" #include "EnchantmentSerializer.h" #include "zlib/zlib.h" +#include "json/json.h" #include "../World.h" #include "../BlockID.h" #include "../Item.h" @@ -858,7 +859,37 @@ void cWSSAnvil::LoadItemGridFromNBT(cItemGrid & a_ItemGrid, const cParsedNBT & a -bool cWSSAnvil::CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, const char * a_ExpectedType, int a_BlockX, int a_BlockY, int a_BlockZ) +AString cWSSAnvil::DecodeSignLine(const AString & a_Line) +{ + if (a_Line.empty()) + { + return AString(); + } + if (a_Line[0] != '{') + { + return a_Line; + } + + // Try to parse the JSON: + Json::Value root; + Json::Reader reader; + if (!reader.parse(a_Line, root, false) || !root.isObject()) + { + return a_Line; + } + const auto & txt = root["text"]; + if (txt.isString()) + { + return txt.asString(); + } + return a_Line; +} + + + + + +bool cWSSAnvil::CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, const AStringVector & a_ExpectedTypes, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the given tag is a compound: if (a_NBT.GetType(a_TagIdx) != TAG_Compound) @@ -874,12 +905,24 @@ bool cWSSAnvil::CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, con } // Compare the value: - if (strncmp(a_NBT.GetData(TagID), a_ExpectedType, static_cast(a_NBT.GetDataLength(TagID))) == 0) + for (const auto & et: a_ExpectedTypes) { - return true; + if (strncmp(a_NBT.GetData(TagID), et.c_str(), static_cast(a_NBT.GetDataLength(TagID))) == 0) + { + return true; + } } - LOGWARNING("Block entity type mismatch: exp \"%s\", got \"%s\". The block entity at {%d, %d, %d} will lose all its properties.", - a_ExpectedType, + + // Expectation not met, output an error into the log: + AString expectedTypes; + for (const auto & et : a_ExpectedTypes) + { + expectedTypes.append(", \""); + expectedTypes.append(et); + expectedTypes.push_back('\"'); + } + LOGWARNING("Block entity type mismatch: exp %s, got \"%s\". The block entity at {%d, %d, %d} will lose all its properties.", + expectedTypes.c_str() + 2, // Skip the first ", " that is extra in the string AString(a_NBT.GetData(TagID), static_cast(a_NBT.GetDataLength(TagID))).c_str(), a_BlockX, a_BlockY, a_BlockZ ); @@ -893,7 +936,8 @@ bool cWSSAnvil::CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, con cBlockEntity * cWSSAnvil::LoadBeaconFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Beacon", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({"Beacon", "minecraft:beacon"}); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -935,7 +979,8 @@ cBlockEntity * cWSSAnvil::LoadBeaconFromNBT(const cParsedNBT & a_NBT, int a_TagI cBlockEntity * cWSSAnvil::LoadBrewingstandFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Brewingstand", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Brewingstand", "minecraft:brewing_stand" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -993,23 +1038,11 @@ cBlockEntity * cWSSAnvil::LoadChestFromNBT(const cParsedNBT & a_NBT, int a_TagId { // Check if the data has a proper type: // Note that older Cuberite code used "TrappedChest" for trapped chests; new code mimics vanilla and uses "Chest" throughout, but we allow migration here: - if (a_NBT.GetType(a_TagIdx) != TAG_Compound) + static const AStringVector expectedTypes({ "Chest", "TrappedChest", "minecraft:chest" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } - int TagID = a_NBT.FindChildByName(a_TagIdx, "id"); - if ((TagID < 0) || (a_NBT.GetType(TagID) != TAG_String)) - { - return nullptr; - } - auto BlockEntityType = a_NBT.GetString(TagID); - if ((BlockEntityType != "Chest") && (BlockEntityType != "TrappedChest")) - { - LOGWARNING("Block entity type mismatch at {%d, %d, %d}: got \"%s\", expected \"Chest\". Chest contents will be lost.", - a_BlockX, a_BlockY, a_BlockZ, BlockEntityType.c_str() - ); - return nullptr; - } int Items = a_NBT.FindChildByName(a_TagIdx, "Items"); if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List)) @@ -1028,7 +1061,8 @@ cBlockEntity * cWSSAnvil::LoadChestFromNBT(const cParsedNBT & a_NBT, int a_TagId cBlockEntity * cWSSAnvil::LoadCommandBlockFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Control", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Control", "minecraft:command_block" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1065,7 +1099,8 @@ cBlockEntity * cWSSAnvil::LoadCommandBlockFromNBT(const cParsedNBT & a_NBT, int cBlockEntity * cWSSAnvil::LoadDispenserFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Trap", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Trap", "minecraft:dispenser" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1087,7 +1122,8 @@ cBlockEntity * cWSSAnvil::LoadDispenserFromNBT(const cParsedNBT & a_NBT, int a_T cBlockEntity * cWSSAnvil::LoadDropperFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Dropper", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Dropper", "minecraft:dropper" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1109,7 +1145,8 @@ cBlockEntity * cWSSAnvil::LoadDropperFromNBT(const cParsedNBT & a_NBT, int a_Tag cBlockEntity * cWSSAnvil::LoadFlowerPotFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "FlowerPot", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "FlowerPot", "minecraft:flower_pot" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1147,7 +1184,8 @@ cBlockEntity * cWSSAnvil::LoadFlowerPotFromNBT(const cParsedNBT & a_NBT, int a_T cBlockEntity * cWSSAnvil::LoadFurnaceFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Furnace", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Furnace", "minecraft:furnace" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1203,10 +1241,56 @@ cBlockEntity * cWSSAnvil::LoadFurnaceFromNBT(const cParsedNBT & a_NBT, int a_Tag +cBlockEntity * cWSSAnvil::LoadHopperFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) +{ + // Check if the data has a proper type: + static const AStringVector expectedTypes({ "Hopper", "minecraft:hopper" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) + { + return nullptr; + } + + int Items = a_NBT.FindChildByName(a_TagIdx, "Items"); + if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List)) + { + return nullptr; // Make it an empty hopper - the chunk loader will provide an empty cHopperEntity for this + } + auto Hopper = cpp14::make_unique(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, m_World); + LoadItemGridFromNBT(Hopper->GetContents(), a_NBT, Items); + return Hopper.release(); +} + + + + + +cBlockEntity * cWSSAnvil::LoadJukeboxFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) +{ + // Check if the data has a proper type: + static const AStringVector expectedTypes({ "RecordPlayer", "minecraft:jukebox" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) + { + return nullptr; + } + + auto Jukebox = cpp14::make_unique(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, m_World); + int Record = a_NBT.FindChildByName(a_TagIdx, "Record"); + if (Record >= 0) + { + Jukebox->SetRecord(a_NBT.GetInt(Record)); + } + return Jukebox.release(); +} + + + + + cBlockEntity * cWSSAnvil::LoadMobSpawnerFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "MobSpawner", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "MobSpawner", "minecraft:mob_spawner" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1251,53 +1335,11 @@ cBlockEntity * cWSSAnvil::LoadMobSpawnerFromNBT(const cParsedNBT & a_NBT, int a_ -cBlockEntity * cWSSAnvil::LoadHopperFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) -{ - // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Hopper", a_BlockX, a_BlockY, a_BlockZ)) - { - return nullptr; - } - - int Items = a_NBT.FindChildByName(a_TagIdx, "Items"); - if ((Items < 0) || (a_NBT.GetType(Items) != TAG_List)) - { - return nullptr; // Make it an empty hopper - the chunk loader will provide an empty cHopperEntity for this - } - auto Hopper = cpp14::make_unique(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, m_World); - LoadItemGridFromNBT(Hopper->GetContents(), a_NBT, Items); - return Hopper.release(); -} - - - - - -cBlockEntity * cWSSAnvil::LoadJukeboxFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) -{ - // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "RecordPlayer", a_BlockX, a_BlockY, a_BlockZ)) - { - return nullptr; - } - - auto Jukebox = cpp14::make_unique(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, m_World); - int Record = a_NBT.FindChildByName(a_TagIdx, "Record"); - if (Record >= 0) - { - Jukebox->SetRecord(a_NBT.GetInt(Record)); - } - return Jukebox.release(); -} - - - - - cBlockEntity * cWSSAnvil::LoadMobHeadFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Skull", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Skull", "minecraft:skull" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1369,7 +1411,8 @@ cBlockEntity * cWSSAnvil::LoadMobHeadFromNBT(const cParsedNBT & a_NBT, int a_Tag cBlockEntity * cWSSAnvil::LoadNoteBlockFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Music", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Music", "minecraft:noteblock" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1390,7 +1433,8 @@ cBlockEntity * cWSSAnvil::LoadNoteBlockFromNBT(const cParsedNBT & a_NBT, int a_T cBlockEntity * cWSSAnvil::LoadSignFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ) { // Check if the data has a proper type: - if (!CheckBlockEntityType(a_NBT, a_TagIdx, "Sign", a_BlockX, a_BlockY, a_BlockZ)) + static const AStringVector expectedTypes({ "Sign", "minecraft:sign" }); + if (!CheckBlockEntityType(a_NBT, a_TagIdx, expectedTypes, a_BlockX, a_BlockY, a_BlockZ)) { return nullptr; } @@ -1400,25 +1444,25 @@ cBlockEntity * cWSSAnvil::LoadSignFromNBT(const cParsedNBT & a_NBT, int a_TagIdx int currentLine = a_NBT.FindChildByName(a_TagIdx, "Text1"); if (currentLine >= 0) { - Sign->SetLine(0, a_NBT.GetString(currentLine)); + Sign->SetLine(0, DecodeSignLine(a_NBT.GetString(currentLine))); } currentLine = a_NBT.FindChildByName(a_TagIdx, "Text2"); if (currentLine >= 0) { - Sign->SetLine(1, a_NBT.GetString(currentLine)); + Sign->SetLine(1, DecodeSignLine(a_NBT.GetString(currentLine))); } currentLine = a_NBT.FindChildByName(a_TagIdx, "Text3"); if (currentLine >= 0) { - Sign->SetLine(2, a_NBT.GetString(currentLine)); + Sign->SetLine(2, DecodeSignLine(a_NBT.GetString(currentLine))); } currentLine = a_NBT.FindChildByName(a_TagIdx, "Text4"); if (currentLine >= 0) { - Sign->SetLine(3, a_NBT.GetString(currentLine)); + Sign->SetLine(3, DecodeSignLine(a_NBT.GetString(currentLine))); } return Sign.release(); diff --git a/src/WorldStorage/WSSAnvil.h b/src/WorldStorage/WSSAnvil.h index f9f43305b..bda836a7a 100755 --- a/src/WorldStorage/WSSAnvil.h +++ b/src/WorldStorage/WSSAnvil.h @@ -145,10 +145,15 @@ protected: Slots outside the ItemGrid range are ignored */ void LoadItemGridFromNBT(cItemGrid & a_ItemGrid, const cParsedNBT & a_NBT, int a_ItemsTagIdx, int s_SlotOffset = 0); - /** Returns true iff the "id" child tag inside the specified tag equals the specified expected type. + /** Decodes the text contained within a sign. + Older versions used direct string representation, newer versions use JSON-formatted string. + This function extracts the text from either version. */ + AString DecodeSignLine(const AString & a_Line); + + /** Returns true iff the "id" child tag inside the specified tag equals (case-sensitive) any of the specified expected types. Logs a warning to the console on mismatch. The coordinates are used only for the log message. */ - bool CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, const char * a_ExpectedType, int a_BlockX, int a_BlockY, int a_BlockZ); + bool CheckBlockEntityType(const cParsedNBT & a_NBT, int a_TagIdx, const AStringVector & a_ExpectedTypes, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadBeaconFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadBrewingstandFromNBT(const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); @@ -158,10 +163,10 @@ protected: cBlockEntity * LoadDropperFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadFlowerPotFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadFurnaceFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); - cBlockEntity * LoadMobSpawnerFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadHopperFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadJukeboxFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadMobHeadFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); + cBlockEntity * LoadMobSpawnerFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadNoteBlockFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ); cBlockEntity * LoadSignFromNBT (const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ);