From 36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9 Mon Sep 17 00:00:00 2001 From: Tobias Wilken Date: Tue, 14 Jul 2020 18:56:42 +0200 Subject: Introduce recipe book functionality (#4493) * Introduce recipe book functionality The recipe book helps especially new players. Missing it gives the impression that cuberite is not as advanced as it is. The handling of the recipe book uses the following functions: - Unlock Recipes (https://wiki.vg/index.php?title=Protocol&oldid=14204#Unlock_Recipes) to make recipes available and show the notification for new recipes. Initialization is done on player login for known ones, the update is done when new items are discovered. - Craft Recipe Request (https://wiki.vg/index.php?title=Protocol&oldid=14204#Craft_Recipe_Request) when the user selects a recipe from the recipe book to fill the slots. Known recipes are initialized on player login via `Unlock Recipes` with `Action` 0. As soon as a new recipe is discovered this is added via `Unlock Recipes` with `Action` 1. To be able to know and recognize new recipes the player class is extended with `KnownItems` and `KnownRecipes`. As soon as a player touches an item this is compared to the list of `KnownItems`, if the item is unknown the recipes are checked for this item and the other ingredients are checked with the list of `KnownItems`. If a full match is discovered the recipe is unlocked with the client and stored in the `KnownRecipes`. To unlock recipes the recipe ID is sent to the client. A mapping file (for protocol 1.12.2) translated the minecraft recipe names to ids. The crafting.txt is extended with and minecraft recipe names is possible. Limitations: Only a single recipe is added to the crafting area. Multiple clicks or shift click does not increase the number of builds. Co-authored-by: peterbell10 * Address first issues mentioned by @peterbell10 - Some linting - Extract loading of recipe specific protocol mapping into a function - Build `RecipeNameMap` only once - Use `std::optional` - Extract `LoadRecipe` from `Window` * Start to implement new suggestions * Update with suggestions from @peterbell10 * Some minor cleanup * Update protocol packet IDs * Remove unused include * Include header in cmake * Change a vector to integer counter * Change dromedaryCase method names to PascalCase * Address suggestions from @madmaxoft * Read Protocol subdirectories to load recipe books To load all recipebooks iterate over the `Protocol` subdirectories to find mapping files. Co-authored-by: peterbell10 --- src/UI/CraftingWindow.cpp | 6 ++++ src/UI/CraftingWindow.h | 7 ++--- src/UI/InventoryWindow.cpp | 6 ++++ src/UI/InventoryWindow.h | 6 ++-- src/UI/SlotArea.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++---- src/UI/SlotArea.h | 9 +++--- 6 files changed, 87 insertions(+), 17 deletions(-) (limited to 'src/UI') diff --git a/src/UI/CraftingWindow.cpp b/src/UI/CraftingWindow.cpp index 34599788c..d72e13729 100644 --- a/src/UI/CraftingWindow.cpp +++ b/src/UI/CraftingWindow.cpp @@ -59,3 +59,9 @@ void cCraftingWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & + +void cCraftingWindow::LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId) +{ + auto slotAreaCrafting = static_cast(m_SlotAreas[0]); + slotAreaCrafting->LoadRecipe(a_Player, a_RecipeId); +} diff --git a/src/UI/CraftingWindow.h b/src/UI/CraftingWindow.h index 75026dc67..b0de69704 100644 --- a/src/UI/CraftingWindow.h +++ b/src/UI/CraftingWindow.h @@ -25,8 +25,7 @@ public: cCraftingWindow(); virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; -}; - - - + /** Loads the given Recipe into the crafting grid */ + void LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId); +}; diff --git a/src/UI/InventoryWindow.cpp b/src/UI/InventoryWindow.cpp index 3c787ff7c..a3122d2d9 100644 --- a/src/UI/InventoryWindow.cpp +++ b/src/UI/InventoryWindow.cpp @@ -72,3 +72,9 @@ void cInventoryWindow::DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer + +void cInventoryWindow::LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId) +{ + auto slotAreaCrafting = static_cast(m_SlotAreas[0]); + slotAreaCrafting->LoadRecipe(a_Player, a_RecipeId); +} diff --git a/src/UI/InventoryWindow.h b/src/UI/InventoryWindow.h index 108f58fa0..052439f40 100644 --- a/src/UI/InventoryWindow.h +++ b/src/UI/InventoryWindow.h @@ -26,10 +26,8 @@ public: virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override; + /** Loads the given Recipe into the crafting grid */ + void LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId); protected: cPlayer & m_Player; }; - - - - diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 0bbfb4b12..1552d4bfe 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -665,6 +665,7 @@ void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player) { return; } + a_Player.AddKnownItem(Result); cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1; for (;;) { @@ -780,6 +781,70 @@ void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Play +void cSlotAreaCrafting::LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId) +{ + if (a_RecipeId == 0) + { + return; + } + auto Recipe = cRoot::Get()->GetCraftingRecipes()->GetRecipeById(a_RecipeId); + + int NumItems = 0; + ClearCraftingGrid(a_Player); + + for (auto itrS = Recipe->m_Ingredients.begin(); itrS != Recipe->m_Ingredients.end(); ++itrS) + { + cItem * FoundItem = a_Player.GetInventory().FindItem(itrS->m_Item); + if (FoundItem == nullptr) + { + ClearCraftingGrid(a_Player); + break; + } + cItem Item = FoundItem->CopyOne(); + ++NumItems; + int pos = 1 + itrS->x + m_GridSize * itrS->y; + // Assuming there are ether shaped or unshaped recipes, no mixed ones + if ((itrS->x == -1) && (itrS->y == -1)) + { + pos = NumItems; + } + // Handle x wildcard + else if (itrS->x == -1) + { + for (int i = 0; i < m_GridSize; i++) + { + pos = 1 + i + m_GridSize * itrS->y; + auto itemCheck = GetSlot(pos, a_Player); + if (itemCheck->IsEmpty()) + { + break; + } + } + } + SetSlot(pos, a_Player, Item); + a_Player.GetInventory().RemoveItem(Item); + } +} + + + + + +void cSlotAreaCrafting::ClearCraftingGrid(cPlayer & a_Player) +{ + for (int pos = 1; pos <= m_GridSize * m_GridSize; pos++) + { + auto Item = GetSlot(pos, a_Player); + if (Item->m_ItemCount > 0) + { + a_Player.GetInventory().AddItem(*Item); + SetSlot(pos, a_Player, cItem()); + } + } +} + + + //////////////////////////////////////////////////////////////////////////////// // cSlotAreaAnvil: @@ -2749,8 +2814,3 @@ void cSlotAreaHorse::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bo --a_ItemStack.m_ItemCount; } } - - - - - diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h index 86c0afd51..d363a72e6 100644 --- a/src/UI/SlotArea.h +++ b/src/UI/SlotArea.h @@ -273,6 +273,11 @@ public: // Distributing items into this area is completely disabled virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill) override; + /** Clear the crafting grid */ + void ClearCraftingGrid(cPlayer & a_Player); + + /** Loads the given Recipe into the crafting grid */ + void LoadRecipe(cPlayer & a_Player, UInt32 a_RecipeId); protected: /** Maps player's EntityID -> current recipe. @@ -555,7 +560,3 @@ public: private: cHorse & m_Horse; }; - - - - -- cgit v1.2.3