diff options
29 files changed, 655 insertions, 4 deletions
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index d7dc83043..c7d47219a 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -6943,7 +6943,7 @@ These ItemGrids are available in the API and can be manipulated by the plugins, }, m_Enchantments = { - Type = "{{cEnchantments|cEnchantments}}}", + Type = "{{cEnchantments|cEnchantments}}", Notes = "The enchantments of the item.", }, m_ItemCount = @@ -6971,6 +6971,11 @@ These ItemGrids are available in the API and can be manipulated by the plugins, Type = "number", Notes = "The repair cost of the item. The anvil need this value", }, + m_BookContent = + { + Type = "{{cBookContent|cBookContent}}", + Notes = "If it's a written or a writeable book, it contains the information of the book: Author, title and pages", + }, }, AdditionalInfo = { @@ -7014,6 +7019,106 @@ local Item5 = cItem(E_ITEM_DIAMOND_CHESTPLATE, 1, 0, "thorns=1;unbreaking=3"); }, }, }, + cBookContent = + { + Desc = [[ +This class contains the information for a signed or writeable book: The author, title and the pages. A page of the writeable book is a simple string. For a signed book it can be a json string. For the json string use {{cCompositeChat}} to create a page and then the function {{cCompositeChat#CreateJsonString_1|CreateJsonString}} to get the json string. +]], + Functions = + { + constructor = + { + Notes = "Creates a empty book", + }, + SetAuthor = + { + Params = + { + { + Name = "Author", + Type = "string", + }, + }, + Notes = "Set the author of the book", + }, + GetAuthor = + { + Returns = + { + { + Type = "string", + }, + }, + Notes = "Get the author of the book", + }, + SetTitle = + { + Params = + { + { + Name = "Title", + Type = "string", + }, + }, + Notes = "Set the title of the book", + }, + GetTitle = + { + Returns = + { + { + Type = "string", + }, + }, + Notes = "Returns the title of the book", + }, + AddPage = + { + Params = + { + { + Name = "Page", + Type = "string", + }, + }, + Notes = "Add a page to the end of the book. Note: A page can be a json string", + }, + GetPages = + { + Returns = + { + { + Type = "table", + }, + }, + Notes = "Returns the pages of the book as a table", + }, + SetPages = + { + Params = + { + { + Type = "table", + }, + }, + Notes = "Set the pages of the book", + }, + Clear = + { + Notes = "Clears the whole book", + }, + IsEmpty = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the book has no author, title and no pages", + }, + }, + }, cItemFrame = { Functions = diff --git a/Server/Plugins/APIDump/Hooks/OnPlayerEditedBook.lua b/Server/Plugins/APIDump/Hooks/OnPlayerEditedBook.lua new file mode 100644 index 000000000..275bd129d --- /dev/null +++ b/Server/Plugins/APIDump/Hooks/OnPlayerEditedBook.lua @@ -0,0 +1,26 @@ +return +{ + HOOK_PLAYER_EDITED_BOOK = + { + CalledWhen = "A player has edited a book.", + DefaultFnName = "OnPlayerEditedBook", -- also used as pagename + Desc = [[ + This hook is called whenever a {{cPlayer|player}} has edited a book. + See also the {{OnPlayerEditingBook|HOOK_PLAYER_EDITING_BOOK}} hook for a similar hook, is called when a + player is editing a book. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player that edited the book" }, + { Name = "BookContent", Type = "{{cBookContent}}", Notes = "The class that contains the current info of the book" }, + }, + Returns = [[ + If the function returns false or no value, Cuberite calls other plugins with this event. If the + function returns true, no other plugin is called for this event.</p> + ]], + }, -- HOOK_PLAYER_EDITED_BOOK +} + + + + diff --git a/Server/Plugins/APIDump/Hooks/OnPlayerEditingBook.lua b/Server/Plugins/APIDump/Hooks/OnPlayerEditingBook.lua new file mode 100644 index 000000000..96521c361 --- /dev/null +++ b/Server/Plugins/APIDump/Hooks/OnPlayerEditingBook.lua @@ -0,0 +1,26 @@ +return +{ + HOOK_PLAYER_EDITING_BOOK = + { + CalledWhen = "A player is editing a book.", + DefaultFnName = "OnPlayerEditingBook", -- also used as pagename + Desc = [[ + This hook is called whenever a {{cPlayer|player}} is editing a book. + See also the {{OnPlayerEditingBook|HOOK_PLAYER_EDITED_BOOK}} hook for a similar hook, is called when a + player has edited a book. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player that is editing the book" }, + { Name = "BookContent", Type = "{{cBookContent}}", Notes = "The class that contains the current info of the book" }, + }, + Returns = [[ + If the function returns false or no value, Cuberite calls other plugins with this event. If the function returns true, + no other plugin's callback is called and the editing of the book is denied. + ]], + }, -- HOOK_PLAYER_EDITING_BOOK +} + + + + diff --git a/src/Bindings/AllToLua.pkg b/src/Bindings/AllToLua.pkg index 778cdf42c..0f775ccce 100644 --- a/src/Bindings/AllToLua.pkg +++ b/src/Bindings/AllToLua.pkg @@ -72,6 +72,8 @@ $cfile "../Scoreboard.h" $cfile "../Statistics.h" $cfile "../Protocol/MojangAPI.h" $cfile "../UUID.h" +$cfile "../BookContent.h" + // Entities: $cfile "../Entities/Entity.h" diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 0ab21467b..7b1635a4a 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -143,6 +143,7 @@ set(BINDING_DEPENDENCIES ../Vector3.h ../WebAdmin.h ../World.h + ../BookContent.h ) if (NOT MSVC) diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index c87e9ed20..b6ef2b275 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -3967,6 +3967,62 @@ static int tolua_cEntity_GetSpeed(lua_State * tolua_S) +static int tolua_cBookContent_GetPages(lua_State * tolua_S) +{ + // cBookContent::GetPages() -> table of strings + + cLuaState L(tolua_S); + if (!L.CheckParamSelf("cBookContent")) + { + return 0; + } + + cBookContent * BookContent = reinterpret_cast<cBookContent *>(tolua_tousertype(tolua_S, 1, nullptr)); + L.Push(BookContent->GetPages()); + return 1; +} + + + + + +static int tolua_cBookContent_SetPages(lua_State * tolua_S) +{ + // cBookContent::SetPages(table) + + cLuaState L(tolua_S); + if ( + !L.CheckParamSelf("cBookContent") || + !L.CheckParamTable(2)) + { + return 0; + } + + cBookContent * BookContent = reinterpret_cast<cBookContent *>(tolua_tousertype(tolua_S, 1, nullptr)); + + // Convert the input table into AStringVector: + AStringVector Pages; + int NumPages = luaL_getn(L, 2); + Pages.reserve(static_cast<size_t>(NumPages)); + for (int i = 1; i <= NumPages; i++) + { + lua_rawgeti(L, 2, i); + AString Page; + L.GetStackValue(-1, Page); + if (!Page.empty()) + { + Pages.push_back(Page); + } + lua_pop(L, 1); + } + BookContent->SetPages(Pages); + return 0; +} + + + + + void cManualBindings::Bind(lua_State * tolua_S) { tolua_beginmodule(tolua_S, nullptr); @@ -4194,6 +4250,11 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_variable(tolua_S, "PostParams", tolua_get_HTTPRequest_PostParams, nullptr); tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cBookContent"); + tolua_function(tolua_S, "GetPages", tolua_cBookContent_GetPages); + tolua_function(tolua_S, "SetPages", tolua_cBookContent_SetPages); + tolua_endmodule(tolua_S); + BindNetwork(tolua_S); BindRankManager(tolua_S); BindWorld(tolua_S); diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 22e8f15e2..f7de4768f 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -85,6 +85,8 @@ public: virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) = 0; virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) = 0; virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) = 0; + virtual bool OnPlayerEditedBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) = 0; + virtual bool OnPlayerEditingBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) = 0; virtual bool OnPlayerShooting (cPlayer & a_Player) = 0; virtual bool OnPlayerSpawned (cPlayer & a_Player) = 0; virtual bool OnPlayerTossingItem (cPlayer & a_Player) = 0; diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 5af336a95..1d8024334 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -712,6 +712,24 @@ bool cPluginLua::OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Ent +bool cPluginLua::OnPlayerEditedBook(cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) +{ + return CallSimpleHooks(cPluginManager::HOOK_PLAYER_EDITED_BOOK, &a_Player, &a_BookContent, a_IsSigned); +} + + + + + +bool cPluginLua::OnPlayerEditingBook(cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) +{ + return CallSimpleHooks(cPluginManager::HOOK_PLAYER_EDITING_BOOK, &a_Player, &a_BookContent, a_IsSigned); +} + + + + + bool cPluginLua::OnPlayerShooting(cPlayer & a_Player) { return CallSimpleHooks(cPluginManager::HOOK_PLAYER_SHOOTING, &a_Player); @@ -1070,6 +1088,8 @@ const char * cPluginLua::GetHookFnName(int a_HookType) case cPluginManager::HOOK_PLAYER_PLACING_BLOCK: return "OnPlayerPlacingBlock"; case cPluginManager::HOOK_PLAYER_RIGHT_CLICK: return "OnPlayerRightClick"; case cPluginManager::HOOK_PLAYER_RIGHT_CLICKING_ENTITY: return "OnPlayerRightClickingEntity"; + case cPluginManager::HOOK_PLAYER_EDITED_BOOK: return "OnPlayerEditedBook"; + case cPluginManager::HOOK_PLAYER_EDITING_BOOK: return "OnPlayerSigningdBook"; case cPluginManager::HOOK_PLAYER_SHOOTING: return "OnPlayerShooting"; case cPluginManager::HOOK_PLAYER_SPAWNED: return "OnPlayerSpawned"; case cPluginManager::HOOK_PLAYER_TOSSING_ITEM: return "OnPlayerTossingItem"; diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 4de5751e7..82b78cbe0 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -106,6 +106,8 @@ public: virtual bool OnPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange) override; virtual bool OnPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) override; virtual bool OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) override; + virtual bool OnPlayerEditedBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) override; + virtual bool OnPlayerEditingBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) override; virtual bool OnPlayerShooting (cPlayer & a_Player) override; virtual bool OnPlayerSpawned (cPlayer & a_Player) override; virtual bool OnEntityTeleport (cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) override; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 1d977fcde..125a6bfc8 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -1094,6 +1094,44 @@ bool cPluginManager::CallHookPlayerRightClickingEntity(cPlayer & a_Player, cEnti +bool cPluginManager::CallHookPlayerEditedBook(cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) +{ + FIND_HOOK(HOOK_PLAYER_EDITED_BOOK); + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnPlayerEditedBook(a_Player, a_BookContent, a_IsSigned)) + { + return true; + } + } + return false; +} + + + + + +bool cPluginManager::CallHookPlayerEditingBook(cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned) +{ + FIND_HOOK(HOOK_PLAYER_EDITING_BOOK); + VERIFY_HOOK; + + for (PluginList::iterator itr = Plugins->second.begin(); itr != Plugins->second.end(); ++itr) + { + if ((*itr)->OnPlayerEditingBook(a_Player, a_BookContent, a_IsSigned)) + { + return true; + } + } + return false; +} + + + + + bool cPluginManager::CallHookPlayerShooting(cPlayer & a_Player) { FIND_HOOK(HOOK_PLAYER_SHOOTING); diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index f3fc3551a..204a70e67 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -29,6 +29,7 @@ class cWorld; class cSettingsRepositoryInterface; class cDeadlockDetect; struct TakeDamageInfo; +class cBookContent; typedef std::shared_ptr<cPlugin> cPluginPtr; typedef std::vector<cPluginPtr> cPluginPtrs; @@ -117,6 +118,8 @@ public: HOOK_PLAYER_PLACING_BLOCK, HOOK_PLAYER_RIGHT_CLICK, HOOK_PLAYER_RIGHT_CLICKING_ENTITY, + HOOK_PLAYER_EDITING_BOOK, + HOOK_PLAYER_EDITED_BOOK, HOOK_PLAYER_SHOOTING, HOOK_PLAYER_SPAWNED, HOOK_ENTITY_TELEPORT, @@ -264,6 +267,8 @@ public: bool CallHookPlayerPlacingBlock (cPlayer & a_Player, const sSetBlock & a_BlockChange); bool CallHookPlayerRightClick (cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ); bool CallHookPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity); + bool CallHookPlayerEditedBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned); + bool CallHookPlayerEditingBook (cPlayer & a_Player, cBookContent & a_BookContent, bool a_IsSigned); bool CallHookPlayerShooting (cPlayer & a_Player); bool CallHookPlayerSpawned (cPlayer & a_Player); bool CallHookPlayerTossingItem (cPlayer & a_Player); diff --git a/src/BookContent.cpp b/src/BookContent.cpp new file mode 100644 index 000000000..e30401c20 --- /dev/null +++ b/src/BookContent.cpp @@ -0,0 +1,90 @@ + +// BookContent.cpp + +#include "Globals.h" +#include "BookContent.h" + + + + + +void cBookContent::Clear() +{ + m_Author.clear(); + m_Title.clear(); + m_Pages.clear(); +} + + + + + +bool cBookContent::IsEmpty(void) const +{ + return ( + m_Author.empty() && + m_Title.empty() && + m_Pages.empty() + ); +} + + + + + +void cBookContent::ParseFromNBT(int TagTag, cBookContent & a_BookContent, const cParsedNBT & a_NBT, bool a_SaveAsJson) +{ + int AuthorTag = a_NBT.FindChildByName(TagTag, "author"); + if ((AuthorTag > 0) && (a_NBT.GetType(AuthorTag) == TAG_String)) + { + a_BookContent.SetAuthor(a_NBT.GetString(AuthorTag)); + } + + int TitleTag = a_NBT.FindChildByName(TagTag, "title"); + if ((TitleTag > 0) && (a_NBT.GetType(TitleTag) == TAG_String)) + { + a_BookContent.SetTitle(a_NBT.GetString(TitleTag)); + } + + int PagesTag = a_NBT.FindChildByName(TagTag, "pages"); + if ((PagesTag > 0) && (a_NBT.GetType(PagesTag) == TAG_List)) + { + for (int PageTag = a_NBT.GetFirstChild(PagesTag); PageTag >= 0; PageTag = a_NBT.GetNextSibling(PageTag)) + { + if (a_NBT.GetType(PageTag) == TAG_String) + { + if (a_SaveAsJson) + { + Json::Value Page; + Page["text"] = a_NBT.GetString(PageTag); + a_BookContent.AddPage(Page.toStyledString()); + } + else + { + a_BookContent.AddPage(a_NBT.GetString(PageTag)); + } + } + } + } +} + + + + + +void cBookContent::WriteToNBTCompound(const cBookContent & a_BookContent, cFastNBTWriter & a_Writer) +{ + if (a_BookContent.IsEmpty()) + { + return; + } + + a_Writer.AddString("author", a_BookContent.GetAuthor()); + a_Writer.AddString("title", a_BookContent.GetTitle()); + a_Writer.BeginList("pages", TAG_String); + for (const auto & Page : a_BookContent.GetPages()) + { + a_Writer.AddString("", Page); + } + a_Writer.EndList(); +} diff --git a/src/BookContent.h b/src/BookContent.h new file mode 100644 index 000000000..042ebff50 --- /dev/null +++ b/src/BookContent.h @@ -0,0 +1,64 @@ + +// BookContent.h + +#pragma once + +#include "WorldStorage/FastNBT.h" +#include "json/json.h" + + + + +// tolua_begin +class cBookContent +{ +public: + /** Creates a empty book */ + cBookContent() {} + + /** Set the author of the book */ + void SetAuthor(const AString & a_Author) { m_Author = a_Author; } + + /** Returns the author of the book */ + const AString GetAuthor(void) const { return m_Author; } + + /** Set the title of the book */ + void SetTitle(const AString & a_Title) { m_Title = a_Title; } + + /** Returns the title of the book */ + const AString GetTitle(void) const { return m_Title; } + + /** Add a page to the end of the book */ + void AddPage(const AString & a_Page) { m_Pages.emplace_back(a_Page); } + + /** Clears the whole book */ + void Clear(); + + /** Returns true if the book has no author, no title and no pages */ + bool IsEmpty(void) const; + + // tolua_end + + /** Returns a AStringVector ref to the pages. Used in ManualBindings and for saving the book */ + const AStringVector & GetPages(void) const { return m_Pages; } + + /** Set the pages. Used in ManualBindings */ + void SetPages(const AStringVector & a_Pages) { m_Pages = a_Pages; } + + /** Read the book content from nbt. The boolean a_SaveAsJson is optional. If a player creates a book, the text should be in a json string */ + static void ParseFromNBT(int TagTag, cBookContent & a_BookContent, const cParsedNBT & a_NBT, bool a_SaveAsJson = false); + + /** Write book content to nbt */ + static void WriteToNBTCompound(const cBookContent & a_BookContent, cFastNBTWriter & a_Writer); + +private: + /** Author of the book */ + AString m_Author; + + /** Title of the book */ + AString m_Title; + + /** Contains the pages */ + AStringVector m_Pages; + +}; // tolua_export diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7eb4e903..a71da1363 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ SET (SRCS VoronoiMap.cpp WebAdmin.cpp World.cpp + BookContent.cpp main.cpp ) @@ -153,6 +154,7 @@ SET (HDRS VoronoiMap.h WebAdmin.h World.h + BookContent.h XMLParser.h ) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index caa2d8fd8..9dbcc684b 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -2295,6 +2295,15 @@ void cClientHandle::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlock +void cClientHandle::SendOpenBook(const short a_Hand) +{ + m_Protocol->SendOpenBook(a_Hand); +} + + + + + void cClientHandle::SendCameraSetTo(const cEntity & a_Entity) { m_Protocol->SendCameraSetTo(a_Entity); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 09188f2ae..adf6bd683 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -144,6 +144,7 @@ public: // tolua_export void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage); void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // tolua_export void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes); + void SendOpenBook (const short a_Hand); void SendCameraSetTo (const cEntity & a_Entity); void SendChat (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChat (const cCompositeChat & a_Message); diff --git a/src/Item.cpp b/src/Item.cpp index 9b17f1c37..412c66ec3 100644 --- a/src/Item.cpp +++ b/src/Item.cpp @@ -183,6 +183,31 @@ void cItem::GetJson(Json::Value & a_OutValue) const a_OutValue["FadeColours"] = m_FireworkItem.FadeColoursToString(m_FireworkItem); } + if ( + !m_BookContent.IsEmpty() && + ( + (m_ItemType == E_ITEM_WRITTEN_BOOK) || (m_ItemType == E_ITEM_BOOK_AND_QUILL) + ) + ) + { + if (!m_BookContent.GetAuthor().empty()) + { + a_OutValue["author"] = m_BookContent.GetAuthor(); + } + if (!m_BookContent.GetTitle().empty()) + { + a_OutValue["title"] = m_BookContent.GetTitle(); + } + if (!m_BookContent.GetPages().empty()) + { + a_OutValue["pages"] = Json::Value(Json::arrayValue); + for (const auto & Page : m_BookContent.GetPages()) + { + a_OutValue["pages"].append(Page); + } + } + } + a_OutValue["RepairCost"] = m_RepairCost; } } @@ -229,6 +254,19 @@ void cItem::FromJson(const Json::Value & a_Value) m_FireworkItem.FadeColoursFromString(a_Value.get("FadeColours", "").asString(), m_FireworkItem); } + if ((m_ItemType == E_ITEM_WRITTEN_BOOK) || (m_ItemType == E_ITEM_BOOK_AND_QUILL)) + { + m_BookContent.SetAuthor(a_Value.get("author", "").asString()); + m_BookContent.SetTitle(a_Value.get("title", "").asString()); + if (a_Value.isMember("pages")) + { + for (Json::Value::ArrayIndex i = 0; i != a_Value["pages"].size(); i++) + { + m_BookContent.AddPage(a_Value["pages"][i].asString()); + } + } + } + m_RepairCost = a_Value.get("RepairCost", 0).asInt(); } } diff --git a/src/Item.h b/src/Item.h index 493061d93..664d63195 100644 --- a/src/Item.h +++ b/src/Item.h @@ -13,7 +13,7 @@ #include "Enchantments.h" #include "WorldStorage/FireworksSerializer.h" #include "Color.h" - +#include "BookContent.h" @@ -109,6 +109,7 @@ public: m_RepairCost = 0; m_FireworkItem.EmptyData(); m_ItemColor.Clear(); + m_BookContent.Clear(); } @@ -119,6 +120,8 @@ public: m_ItemDamage = 0; m_RepairCost = 0; m_ItemColor.Clear(); + m_BookContent.Clear(); + m_Enchantments.Clear(); } @@ -230,6 +233,7 @@ public: int m_RepairCost; cFireworkItem m_FireworkItem; cColor m_ItemColor; + cBookContent m_BookContent; }; // tolua_end diff --git a/src/Items/CMakeLists.txt b/src/Items/CMakeLists.txt index 72858591a..5ab12feb2 100644 --- a/src/Items/CMakeLists.txt +++ b/src/Items/CMakeLists.txt @@ -56,6 +56,7 @@ SET (HDRS ItemSword.h ItemThrowable.h ItemAxe.h + ItemWrittenBook.h ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp index e0c5bb56c..234c0dbf6 100644 --- a/src/Items/ItemHandler.cpp +++ b/src/Items/ItemHandler.cpp @@ -56,6 +56,7 @@ #include "ItemSword.h" #include "ItemThrowable.h" #include "ItemAxe.h" +#include "ItemWrittenBook.h" #include "../Blocks/BlockHandler.h" @@ -153,6 +154,7 @@ cItemHandler * cItemHandler::CreateItemHandler(int a_ItemType) case E_ITEM_SPAWN_EGG: return new cItemSpawnEggHandler(a_ItemType); case E_ITEM_STRING: return new cItemStringHandler(a_ItemType); case E_ITEM_SUGARCANE: return new cItemSugarcaneHandler(a_ItemType); + case E_ITEM_WRITTEN_BOOK: return new cItemWrittenBookHandler(a_ItemType); case E_ITEM_WOODEN_HOE: case E_ITEM_STONE_HOE: diff --git a/src/Items/ItemWrittenBook.h b/src/Items/ItemWrittenBook.h new file mode 100644 index 000000000..37f1254c5 --- /dev/null +++ b/src/Items/ItemWrittenBook.h @@ -0,0 +1,30 @@ + +// ItemWrittenBook.h + +#pragma once + +#include "ItemHandler.h" +#include "../Entities/Player.h" +#include "../Inventory.h" + + + +class cItemWrittenBookHandler : + public cItemHandler +{ +public: + cItemWrittenBookHandler(int a_ItemType) + : cItemHandler(a_ItemType) + { + } + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override + { + // TODO: Currently only main haind. If second hand is implemented, fix this + a_Player->GetClientHandle()->SendOpenBook(0); + return true; + } +} ; diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 54c5b7223..9d9497163 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -65,6 +65,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) = 0; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) = 0; + virtual void SendOpenBook (const short a_Hand) = 0; virtual void SendCameraSetTo (const cEntity & a_Entity) = 0; virtual void SendChat (const AString & a_Message, eChatType a_Type) = 0; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) = 0; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index cb2a4fc9d..06e942716 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -191,6 +191,17 @@ void cProtocolRecognizer::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSe + +void cProtocolRecognizer::SendOpenBook(const short a_Hand) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendOpenBook(a_Hand); +} + + + + + void cProtocolRecognizer::SendCameraSetTo(const cEntity & a_Entity) { ASSERT(m_Protocol != nullptr); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 295c6db16..544914cf3 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -61,6 +61,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendOpenBook (const short a_Hand) override; virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h index b04e5c5f0..a76dbd8d8 100644 --- a/src/Protocol/Protocol_1_8.h +++ b/src/Protocol/Protocol_1_8.h @@ -54,6 +54,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendOpenBook (const short a_Hand) override {} virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp index 7fc6cf5f1..f49a942b8 100644 --- a/src/Protocol/Protocol_1_9.cpp +++ b/src/Protocol/Protocol_1_9.cpp @@ -263,6 +263,17 @@ void cProtocol_1_9_0::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlo +void cProtocol_1_9_0::SendOpenBook(const short a_Hand) +{ + cPacketizer Pkt(*this, 0x18); // Plugin Channel + Pkt.WriteString("MC|BOpen"); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Hand)); +} + + + + + void cProtocol_1_9_0::SendCameraSetTo(const cEntity & a_Entity) { cPacketizer Pkt(*this, 0x36); // Camera Packet (Attach the camera of a player at another entity in spectator mode) @@ -2917,6 +2928,55 @@ void cProtocol_1_9_0::HandleVanillaPluginMessage(cByteBuffer & a_ByteBuffer, con m_Client->HandleNPCTrade(SlotNum); return; } + else if ((a_Channel == "MC|BSign") || (a_Channel == "MC|BEdit")) + { + HANDLE_READ(a_ByteBuffer, ReadBEInt16, Int16, ItemID); + if (ItemID != E_ITEM_BOOK_AND_QUILL) + { + // Item is not a writeable book + return; + } + + // Skip item count (1 byte) and item damage (2 bytes) + a_ByteBuffer.SkipRead(3); + + // Read nbt content + AString BookData; + a_ByteBuffer.ReadString(BookData, a_ByteBuffer.GetReadableSpace() - 1); + cParsedNBT NBT(BookData.c_str(), BookData.size()); + + cItem BookItem; + if (a_Channel == "MC|BSign") + { + BookItem = cItem(E_ITEM_WRITTEN_BOOK); + // Add the text to a json string + cBookContent::ParseFromNBT(0, BookItem.m_BookContent, NBT, true); + } + else + { + BookItem = cItem(E_ITEM_BOOK_AND_QUILL); + cBookContent::ParseFromNBT(0, BookItem.m_BookContent, NBT); + } + + cPlayer * Player = m_Client->GetPlayer(); + + // If true, player has clicked on the sign button + bool IsSigned = (BookItem.m_ItemType == E_ITEM_WRITTEN_BOOK) ? true : false; + + if (cRoot::Get()->GetPluginManager()->CallHookPlayerEditingBook(*Player, BookItem.m_BookContent, IsSigned)) + { + // Plugin denied the editing of the book + return; + } + + // Book has been edited, inform plugins + cRoot::Get()->GetPluginManager()->CallHookPlayerEditedBook(*Player, BookItem.m_BookContent, IsSigned); + + cInventory & inv = Player->GetInventory(); + inv.SetHotbarSlot(inv.GetEquippedSlotNum(), BookItem); + SendWholeInventory(*Player->GetWindow()); // TODO: Use SendSlot + return; + } LOG("Unhandled vanilla plugin channel: \"%s\".", a_Channel.c_str()); // Read the payload and send it through to the clienthandle: @@ -3334,12 +3394,26 @@ void cProtocol_1_9_0::WriteItem(cPacketizer & a_Pkt, const cItem & a_Item) a_Pkt.WriteBEInt16(a_Item.m_ItemDamage); } - if (a_Item.m_Enchantments.IsEmpty() && a_Item.IsBothNameAndLoreEmpty() && (ItemType != E_ITEM_FIREWORK_ROCKET) && (ItemType != E_ITEM_FIREWORK_STAR) && !a_Item.m_ItemColor.IsValid() && (ItemType != E_ITEM_POTION) && (ItemType != E_ITEM_SPAWN_EGG)) + if ( + a_Item.m_Enchantments.IsEmpty() && + a_Item.IsBothNameAndLoreEmpty() && + (ItemType != E_ITEM_FIREWORK_ROCKET) && (ItemType != E_ITEM_FIREWORK_STAR) && + !a_Item.m_ItemColor.IsValid() && + (ItemType != E_ITEM_POTION) && + (ItemType != E_ITEM_SPAWN_EGG) && + (ItemType != E_ITEM_WRITTEN_BOOK) && (ItemType != E_ITEM_BOOK_AND_QUILL)) { a_Pkt.WriteBEInt8(0); return; } + if ((ItemType == E_ITEM_BOOK_AND_QUILL) && (a_Item.m_BookContent.GetPages().size() == 0)) + { + // Don't send any nbt tag if the book is writeable and has no pages + // If a tag with a empty pages list is send, the player can't enter anything + a_Pkt.WriteBEInt8(0); + return; + } // Send the enchantments and custom names: cFastNBTWriter Writer; @@ -3450,6 +3524,31 @@ void cProtocol_1_9_0::WriteItem(cPacketizer & a_Pkt, const cItem & a_Item) Writer.EndCompound(); } } + if ((a_Item.m_ItemType == E_ITEM_WRITTEN_BOOK) || (a_Item.m_ItemType == E_ITEM_BOOK_AND_QUILL)) + { + if (a_Item.m_ItemType == E_ITEM_WRITTEN_BOOK) + { + // Only send author and title for a signed book + Writer.AddString("author", a_Item.m_BookContent.GetAuthor()); + Writer.AddString("title", a_Item.m_BookContent.GetTitle()); + } + if (a_Item.m_BookContent.GetPages().size() > 0) + { + Writer.BeginList("pages", TAG_String); + for (auto & Page : a_Item.m_BookContent.GetPages()) + { + Writer.AddString("", Page); + } + Writer.EndList(); + } + else + { + // A signed book, has a empty page + Writer.BeginList("pages", TAG_String); + Writer.AddString("", ""); + Writer.EndList(); + } + } Writer.Finish(); diff --git a/src/Protocol/Protocol_1_9.h b/src/Protocol/Protocol_1_9.h index b4fdc7f67..4b941726c 100644 --- a/src/Protocol/Protocol_1_9.h +++ b/src/Protocol/Protocol_1_9.h @@ -60,6 +60,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendOpenBook (const short a_Hand) override; virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 1e8543648..d2356b71b 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -106,7 +106,7 @@ void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AStrin // 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_ItemType == E_ITEM_FIREWORK_ROCKET) || (a_Item.m_ItemType == E_ITEM_FIREWORK_STAR) || (a_Item.m_ItemType == E_ITEM_WRITTEN_BOOK)) || (a_Item.m_ItemType == E_ITEM_BOOK_AND_QUILL) || (a_Item.m_RepairCost > 0) || (a_Item.m_CustomName != "") || (!a_Item.m_LoreTable.empty()) @@ -149,6 +149,10 @@ void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AStrin const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, m_Writer, TagName); } + if ((a_Item.m_ItemType == E_ITEM_WRITTEN_BOOK) || (a_Item.m_ItemType == E_ITEM_BOOK_AND_QUILL)) + { + cBookContent::WriteToNBTCompound(a_Item.m_BookContent, m_Writer); + } m_Writer.EndCompound(); } diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index a3251481f..83f5a310d 100755 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -837,6 +837,10 @@ bool cWSSAnvil::LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_ cFireworkItem::ParseFromNBT(a_Item.m_FireworkItem, a_NBT, FireworksTag, static_cast<ENUM_ITEM_ID>(a_Item.m_ItemType)); } + if ((a_Item.m_ItemType == E_ITEM_WRITTEN_BOOK) || (a_Item.m_ItemType == E_ITEM_BOOK_AND_QUILL)) + { + cBookContent::ParseFromNBT(TagTag, a_Item.m_BookContent, a_NBT); + } return true; } |