diff options
author | Tobias Wilken <TooAngel@TooAngel.de> | 2020-07-14 18:56:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-14 18:56:42 +0200 |
commit | 36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9 (patch) | |
tree | 04c224231a800002692a296131af4988dd465845 /src | |
parent | Custom command depend is automatic (diff) | |
download | cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar.gz cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar.bz2 cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar.lz cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar.xz cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.tar.zst cuberite-36eab1b3237dbeeaaf5b48808bf0d47eb4bd32e9.zip |
Diffstat (limited to 'src')
32 files changed, 761 insertions, 73 deletions
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 3cad17849..9fbc9f89d 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -14,6 +14,8 @@ #include "BlockEntities/ChestEntity.h" #include "BlockEntities/CommandBlockEntity.h" #include "BlockEntities/SignEntity.h" +#include "UI/InventoryWindow.h" +#include "UI/CraftingWindow.h" #include "UI/Window.h" #include "UI/AnvilWindow.h" #include "UI/BeaconWindow.h" @@ -1692,7 +1694,7 @@ void cClientHandle::HandleWindowClick(UInt8 a_WindowID, Int16 a_SlotNum, eClickA LOGWARNING("Player \"%s\" clicked in a non-existent window. Ignoring", m_Username.c_str()); return; } - + m_Player->AddKnownItem(a_HeldItem); Window->Clicked(*m_Player, a_WindowID, a_SlotNum, a_ClickAction, a_HeldItem); } @@ -3129,6 +3131,46 @@ void cClientHandle::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_Blo +void cClientHandle::SendUnlockRecipe(UInt32 a_RecipeId) +{ + m_Protocol->SendUnlockRecipe(a_RecipeId); +} + + + + + +void cClientHandle::SendInitRecipes(UInt32 a_RecipeId) +{ + m_Protocol->SendInitRecipes(a_RecipeId); +} + + + + + +void cClientHandle::HandleCraftRecipe(UInt32 a_RecipeId) +{ + auto * Window = m_Player->GetWindow(); + if (Window == nullptr) + { + return; + } + + if (Window->GetWindowType() == cWindow::wtInventory) + { + static_cast<cInventoryWindow *>(Window)->LoadRecipe(*m_Player, a_RecipeId); + } + else if (Window->GetWindowType() == cWindow::wtWorkbench) + { + static_cast<cCraftingWindow *>(Window)->LoadRecipe(*m_Player, a_RecipeId); + } +} + + + + + void cClientHandle::SendWeather(eWeather a_Weather) { m_Protocol->SendWeather(a_Weather); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 8495fb239..1d988b137 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -221,6 +221,13 @@ public: // tolua_export void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity); void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4); void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ); + + /** Send a newly discovered recipe to show the notification and unlock in the recipe book */ + void SendUnlockRecipe (UInt32 a_RecipeId); + + /** Send already known recipes without notification but visible in the recipe book */ + void SendInitRecipes (UInt32 a_RecipeId); + void SendWeather (eWeather a_Weather); void SendWholeInventory (const cWindow & a_Window); void SendWindowClose (const cWindow & a_Window); @@ -371,6 +378,9 @@ public: // tolua_export void HandleWindowClick (UInt8 a_WindowID, Int16 a_SlotNum, eClickAction a_ClickAction, const cItem & a_HeldItem); void HandleWindowClose (UInt8 a_WindowID); + /** Called when a recipe from the recipe book is selected */ + void HandleCraftRecipe (UInt32 a_RecipeId); + /** Called when the protocol has finished logging the user in. Return true to allow the user in; false to kick them. */ diff --git a/src/CraftingRecipes.cpp b/src/CraftingRecipes.cpp index bdc98e151..7cd41ec97 100644 --- a/src/CraftingRecipes.cpp +++ b/src/CraftingRecipes.cpp @@ -268,6 +268,7 @@ void cCraftingRecipe::Dump(void) cCraftingRecipes::cCraftingRecipes(void) { LoadRecipes(); + PopulateRecipeNameMap(); } @@ -283,6 +284,72 @@ cCraftingRecipes::~cCraftingRecipes() +bool cCraftingRecipes::IsNewCraftableRecipe(const cRecipe * a_Recipe, const cItem & a_Item, const std::set<cItem, cItem::sItemCompare> & a_KnownItems) +{ + bool ContainsNewItem = false; + for (const auto & Ingredient : a_Recipe->m_Ingredients) + { + if ( + (Ingredient.m_Item.m_ItemType == a_Item.m_ItemType) && + ( + (Ingredient.m_Item.m_ItemDamage == a_Item.m_ItemDamage) || + (Ingredient.m_Item.m_ItemDamage == -1) + ) + ) + { + ContainsNewItem = true; + } + if (a_KnownItems.find(Ingredient.m_Item) == a_KnownItems.end()) + { + return false; + } + } + return ContainsNewItem; +} + + + + + +std::vector<UInt32> cCraftingRecipes::FindNewRecipesForItem(const cItem & a_Item, const std::set<cItem, cItem::sItemCompare> & a_KnownItems) +{ + std::vector<UInt32> Recipes; + for (UInt32 i = 0; i < m_Recipes.size(); i++) + { + if (m_Recipes[i]->m_RecipeName.empty()) + { + continue; + } + if (IsNewCraftableRecipe(m_Recipes[i], a_Item, a_KnownItems)) + { + Recipes.push_back(i); + } + } + return Recipes; +} + + + + + +const std::map<AString, UInt32> & cCraftingRecipes::GetRecipeNameMap() +{ + return m_RecipeNameMap; +} + + + + + +cCraftingRecipes::cRecipe * cCraftingRecipes::GetRecipeById(UInt32 a_RecipeId) +{ + return m_Recipes[a_RecipeId]; +} + + + + + void cCraftingRecipes::GetRecipe(cPlayer & a_Player, cCraftingGrid & a_CraftingGrid, cCraftingRecipe & a_Recipe) { // Allow plugins to intercept recipes using a pre-craft hook: @@ -355,6 +422,21 @@ void cCraftingRecipes::LoadRecipes(void) +void cCraftingRecipes::PopulateRecipeNameMap(void) +{ + for (UInt32 i = 0; i < m_Recipes.size(); i++) + { + if (!m_Recipes[i]->m_RecipeName.empty()) + { + m_RecipeNameMap.emplace(m_Recipes[i]->m_RecipeName, i); + } + } +} + + + + + void cCraftingRecipes::ClearRecipes(void) { for (cRecipes::iterator itr = m_Recipes.begin(); itr != m_Recipes.end(); ++itr) @@ -384,8 +466,15 @@ void cCraftingRecipes::AddRecipeLine(int a_LineNum, const AString & a_RecipeLine std::unique_ptr<cCraftingRecipes::cRecipe> Recipe = cpp14::make_unique<cCraftingRecipes::cRecipe>(); + AStringVector RecipeSplit = StringSplit(Sides[0], ":"); + const auto * resultPart = &RecipeSplit[0]; + if (RecipeSplit.size() > 1) + { + resultPart = &RecipeSplit[1]; + Recipe->m_RecipeName = RecipeSplit[0]; + } // Parse the result: - AStringVector ResultSplit = StringSplit(Sides[0], ","); + AStringVector ResultSplit = StringSplit(*resultPart, ","); if (ResultSplit.empty()) { LOGWARNING("crafting.txt: line %d: Result is empty, ignoring the recipe.", a_LineNum); @@ -1059,7 +1148,3 @@ void cCraftingRecipes::HandleDyedLeather(const cItem * a_CraftingGrid, cCrafting a_Recipe->m_Result.m_ItemColor.SetColor(result_red, result_green, result_blue); } } - - - - diff --git a/src/CraftingRecipes.h b/src/CraftingRecipes.h index 9659e53fc..7e4ac86ae 100644 --- a/src/CraftingRecipes.h +++ b/src/CraftingRecipes.h @@ -10,10 +10,6 @@ #include "Item.h" - - - - // fwd: cPlayer.h class cPlayer; @@ -104,7 +100,20 @@ protected: +/** +The crafting recipes are the configurations to build a result item out of a set +of ingredient items. + +The recipes are configured in the `crafting.txt`. When populating the crafting +grid in game (inventory or crafting table), the items are compared to the +ingredients to find a matching recipe and show and craft the result. +Each recipe is defined via the result, the ingredients and the minecraft recipe +name. + +To handle the crafting recipes internally efficient the vector index of the +`cRecipes` is used as `RecipeId`. +*/ class cCraftingRecipes { public: @@ -117,7 +126,8 @@ public: /** Returns the recipe for current crafting grid. Doesn't modify the grid. Clears a_Recipe if no recipe found. */ void GetRecipe(cPlayer & a_Player, cCraftingGrid & a_CraftingGrid, cCraftingRecipe & a_Recipe); -protected: + /** Find recipes and returns the RecipeIds which contain the new item and all ingredients are in the known items */ + std::vector<UInt32> FindNewRecipesForItem(const cItem & a_Item, const std::set<cItem, cItem::sItemCompare> & a_KnownItems); struct cRecipeSlot { @@ -132,11 +142,21 @@ protected: { cRecipeSlots m_Ingredients; cItem m_Result; + AString m_RecipeName; // Size of the regular items in the recipe; "anywhere" items are excluded: int m_Width; int m_Height; } ; + + /** Returns the recipe by id */ + cRecipe * GetRecipeById(UInt32 a_RecipeId); + + /** Gets a map of all recipes with name and recipe id */ + const std::map<AString, UInt32> & GetRecipeNameMap(); + +protected: + typedef std::vector<cRecipe *> cRecipes; cRecipes m_Recipes; @@ -170,8 +190,22 @@ protected: /** Searches for anything dye related for leather, calculates the appropriate color value, and sets the resulting value. */ void HandleDyedLeather(const cItem * a_CraftingGrid, cCraftingRecipes::cRecipe * a_Recipe, int a_GridStride, int a_GridWidth, int a_GridHeight); -} ; - - - +private: + /** Mapping the minecraft recipe names to the internal cuberite recipe Ids */ + std::map<AString, UInt32> m_RecipeNameMap; + + /** + Checks if all ingredients of the a_Recipe are within the a_KnownItems list and + if the a_NewItem is part of the ingredients. + This makes sure to only find 'newly discovered' recipes. + */ + bool IsNewCraftableRecipe( + const cRecipe * a_Recipe, + const cItem & a_NewItem, + const std::set<cItem, cItem::sItemCompare> & a_KnownItems + ); + + /** Populates the RecipeNameMap */ + void PopulateRecipeNameMap(void); +} ; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index d5d9f49af..df410575e 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -31,6 +31,8 @@ #include "../JsonUtils.h" #include "json/json.h" +#include "../CraftingRecipes.h" + // 6000 ticks or 5 minutes #define PLAYER_INVENTORY_SAVE_INTERVAL 6000 @@ -194,6 +196,18 @@ bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World) cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this); + if (m_KnownRecipes.empty()) + { + m_ClientHandle->SendInitRecipes(0); + } + else + { + for (const auto KnownRecipe : m_KnownRecipes) + { + m_ClientHandle->SendInitRecipes(KnownRecipe); + } + } + return true; } @@ -201,6 +215,47 @@ bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World) +void cPlayer::AddKnownItem(const cItem & a_Item) +{ + if (a_Item.m_ItemType < 0) + { + return; + } + + auto Response = m_KnownItems.insert(a_Item.CopyOne()); + if (!Response.second) + { + // The item was already known, bail out: + return; + } + + // Process the recipes that got unlocked by this newly-known item: + auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems); + for (const auto & RecipeId : Recipes) + { + AddKnownRecipe(RecipeId); + } +} + + + + + +void cPlayer::AddKnownRecipe(UInt32 a_RecipeId) +{ + auto Response = m_KnownRecipes.insert(a_RecipeId); + if (!Response.second) + { + // The recipe was already known, bail out: + return; + } + m_ClientHandle->SendUnlockRecipe(a_RecipeId); +} + + + + + cPlayer::~cPlayer(void) { if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this)) @@ -2229,6 +2284,26 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) m_CurrentXp = root.get("xpCurrent", 0).asInt(); m_IsFlying = root.get("isflying", 0).asBool(); + Json::Value & JSON_KnownItems = root["knownItems"]; + for (UInt32 i = 0; i < JSON_KnownItems.size(); i++) + { + cItem Item; + Item.FromJson(JSON_KnownItems[i]); + m_KnownItems.insert(Item); + } + + const auto & RecipeNameMap = cRoot::Get()->GetCraftingRecipes()->GetRecipeNameMap(); + + Json::Value & JSON_KnownRecipes = root["knownRecipes"]; + for (UInt32 i = 0; i < JSON_KnownRecipes.size(); i++) + { + auto RecipeId = RecipeNameMap.find(JSON_KnownRecipes[i].asString()); + if (RecipeId != RecipeNameMap.end()) + { + m_KnownRecipes.insert(RecipeId->second); + } + } + m_GameMode = static_cast<eGameMode>(root.get("gamemode", eGameMode_NotSet).asInt()); if (m_GameMode == eGameMode_Creative) @@ -2327,10 +2402,27 @@ bool cPlayer::SaveToDisk() Json::Value JSON_EnderChestInventory; cEnderChestEntity::SaveToJson(JSON_EnderChestInventory, m_EnderChestContents); + Json::Value JSON_KnownItems; + for (const auto & KnownItem : m_KnownItems) + { + Json::Value JSON_Item; + KnownItem.GetJson(JSON_Item); + JSON_KnownItems.append(JSON_Item); + } + + Json::Value JSON_KnownRecipes; + for (auto KnownRecipe : m_KnownRecipes) + { + auto Recipe = cRoot::Get()->GetCraftingRecipes()->GetRecipeById(KnownRecipe); + JSON_KnownRecipes.append(Recipe->m_RecipeName); + } + Json::Value root; root["position"] = JSON_PlayerPosition; root["rotation"] = JSON_PlayerRotation; root["inventory"] = JSON_Inventory; + root["knownItems"] = JSON_KnownItems; + root["knownRecipes"] = JSON_KnownRecipes; root["equippedItemSlot"] = m_Inventory.GetEquippedSlotNum(); root["enderchestinventory"] = JSON_EnderChestInventory; root["health"] = m_Health; @@ -3095,13 +3187,3 @@ float cPlayer::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_Ex return Super::GetExplosionExposureRate(a_ExplosionPosition, a_ExlosionPower) / 30.0f; } - - - - - - - - - - diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 11d448b11..c52d6bbdc 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -600,6 +600,10 @@ public: /** get player explosion exposure rate */ virtual float GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) override; + /** Adds an Item to the list of known items. + If the item is already known, does nothing. */ + void AddKnownItem(const cItem & a_Item); + protected: typedef std::vector<std::vector<AString> > AStringVectorVector; @@ -753,6 +757,12 @@ protected: /** The main hand of the player */ eMainHand m_MainHand; + /** List on known recipes as Ids */ + std::set<UInt32> m_KnownRecipes; + + /** List of known items as Ids */ + std::set<cItem, cItem::sItemCompare> m_KnownItems; + virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; /** Sets the speed and sends it to the client, so that they are forced to move so. */ @@ -795,4 +805,8 @@ private: If he is not on ground it also gets divided by 5. */ float GetDigSpeed(BLOCKTYPE a_Block); + /** Add the recipe Id to the known recipes. + If the recipe is already known, does nothing. */ + void AddKnownRecipe(UInt32 RecipeId); + } ; // tolua_export diff --git a/src/Inventory.cpp b/src/Inventory.cpp index 42c243f17..6509dfe5d 100644 --- a/src/Inventory.cpp +++ b/src/Inventory.cpp @@ -105,6 +105,8 @@ int cInventory::HowManyCanFit(const cItem & a_ItemStack, int a_BeginSlotNum, int int cInventory::AddItem(const cItem & a_Item, bool a_AllowNewStacks) { + m_Owner.AddKnownItem(a_Item); + cItem ToAdd(a_Item); int res = 0; @@ -207,6 +209,26 @@ int cInventory::RemoveItem(const cItem & a_ItemStack) +cItem * cInventory::FindItem(const cItem & a_RecipeItem) +{ + cItem * Item = m_ShieldSlots.FindItem(a_RecipeItem); + if (Item != nullptr) + { + return Item; + } + Item = m_HotbarSlots.FindItem(a_RecipeItem); + if (Item != nullptr) + { + return Item; + } + + return m_InventorySlots.FindItem(a_RecipeItem); +} + + + + + bool cInventory::RemoveOneEquippedItem(void) { if (m_HotbarSlots.GetSlot(m_EquippedSlotNum).IsEmpty()) @@ -863,7 +885,3 @@ void cInventory::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) SendSlot(Base + a_SlotNum); } - - - - diff --git a/src/Inventory.h b/src/Inventory.h index 7436d7528..7221ded00 100644 --- a/src/Inventory.h +++ b/src/Inventory.h @@ -88,6 +88,9 @@ public: Returns the number of items that were removed. */ int RemoveItem(const cItem & a_ItemStack); + /** Finds an item based on ItemType and ItemDamage (<- defines the itemType, too) */ + cItem * FindItem(const cItem & a_RecipeItem); + /** Removes one item out of the currently equipped item stack, returns true if successful, false if empty-handed */ bool RemoveOneEquippedItem(void); @@ -210,7 +213,3 @@ protected: // cItemGrid::cListener override: virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override; }; // tolua_export - - - - diff --git a/src/Item.h b/src/Item.h index 175f044b5..2f7b1a238 100644 --- a/src/Item.h +++ b/src/Item.h @@ -169,6 +169,31 @@ public: AStringVector m_LoreTable; // Exported in ManualBindings.cpp + /** + Compares two items for the same type or category. Type of item is defined + via `m_ItemType` and `m_ItemDamage`. Some items (e.g. planks) have the same + `m_ItemType` and the wood kind is defined via `m_ItemDamage`. `-1` is used + as placeholder for all kinds (e.g. all kind of planks). + + Items are different when the `ItemType` is different or the `ItemDamage` + is different and unequal -1. + */ + struct sItemCompare + { + bool operator() (const cItem & a_Lhs, const cItem & a_Rhs) const + { + if (a_Lhs.m_ItemType != a_Rhs.m_ItemType) + { + return (a_Lhs.m_ItemType < a_Rhs.m_ItemType); + } + if ((a_Lhs.m_ItemDamage == -1) || (a_Rhs.m_ItemDamage == -1)) + { + return false; // -1 is a wildcard, damage of -1 alway compares equal + } + return (a_Lhs.m_ItemDamage < a_Rhs.m_ItemDamage); + } + }; + // tolua_begin int m_RepairCost; diff --git a/src/ItemGrid.cpp b/src/ItemGrid.cpp index 045f083c8..790f078fc 100644 --- a/src/ItemGrid.cpp +++ b/src/ItemGrid.cpp @@ -440,6 +440,31 @@ int cItemGrid::RemoveItem(const cItem & a_ItemStack) +cItem * cItemGrid::FindItem(const cItem & a_RecipeItem) +{ + if (!m_Slots.IsStorageAllocated()) + { + return nullptr; + } + + for (int i = 0; i < m_Slots.size(); i++) + { + // Items are equal if none is greater the other + auto compare = cItem::sItemCompare{}; + if (!compare(a_RecipeItem, m_Slots[i]) && + !compare(m_Slots[i], a_RecipeItem)) + { + return &m_Slots[i]; + } + } + + return nullptr; +} + + + + + int cItemGrid::ChangeSlotCount(int a_SlotNum, int a_AddToCount) { if (!IsValidSlotNum(a_SlotNum)) @@ -825,7 +850,3 @@ void cItemGrid::TriggerListeners(int a_SlotNum) } // for itr - m_Listeners[] m_IsInTriggerListeners = false; } - - - - diff --git a/src/ItemGrid.h b/src/ItemGrid.h index ee2dc79f3..52cfbc84a 100644 --- a/src/ItemGrid.h +++ b/src/ItemGrid.h @@ -102,6 +102,9 @@ public: Returns the number of items that were removed. */ int RemoveItem(const cItem & a_ItemStack); + /** Finds an item based on ItemType and ItemDamage (<- defines the itemType, too) */ + cItem * FindItem(const cItem & a_RecipeItem); + /** Adds (or subtracts, if a_AddToCount is negative) to the count of items in the specified slot. If the slot is empty, ignores the call. Returns the new count. @@ -198,6 +201,3 @@ protected: int AddItemToSlot(const cItem & a_ItemStack, int a_Slot, int a_Num, int a_MaxStack); } ; // tolua_end - - - diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index e197853cb..40eecde07 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources( Protocol_1_13.cpp ProtocolPalettes.cpp ProtocolRecognizer.cpp + RecipeMapper.cpp Authenticator.h ChunkDataSerializer.h @@ -29,4 +30,5 @@ target_sources( Protocol_1_13.h ProtocolPalettes.h ProtocolRecognizer.h + RecipeMapper.h ) diff --git a/src/Protocol/Packetizer.cpp b/src/Protocol/Packetizer.cpp index 6afea8a36..12bfcc0dd 100644 --- a/src/Protocol/Packetizer.cpp +++ b/src/Protocol/Packetizer.cpp @@ -121,6 +121,7 @@ AString cPacketizer::PacketTypeToStr(cProtocol::ePacketType a_PacketType) case cProtocol::pktTimeUpdate: return "pktTimeUpdate"; case cProtocol::pktTitle: return "pktTitle"; case cProtocol::pktUnloadChunk: return "pktUnloadChunk"; + case cProtocol::pktUnlockRecipe: return "pktUnlockRecipe"; case cProtocol::pktUpdateBlockEntity: return "pktUpdateBlockEntity"; case cProtocol::pktUpdateHealth: return "pktUpdateHealth"; case cProtocol::pktUpdateScore: return "pktUpdateScore"; @@ -134,7 +135,3 @@ AString cPacketizer::PacketTypeToStr(cProtocol::ePacketType a_PacketType) } return Printf("Unknown packet type: 0x%02x", a_PacketType); } - - - - diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 12382b954..e1d901321 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -132,6 +132,7 @@ public: pktTimeUpdate, pktTitle, pktUnloadChunk, + pktUnlockRecipe, pktUpdateBlockEntity, pktUpdateHealth, pktUpdateScore, @@ -225,6 +226,8 @@ public: virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) = 0; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) = 0; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) = 0; + virtual void SendUnlockRecipe (UInt32 a_RecipeID) = 0; + virtual void SendInitRecipes (UInt32 a_RecipeID) = 0; virtual void SendWeather (eWeather a_Weather) = 0; virtual void SendWholeInventory (const cWindow & a_Window) = 0; virtual void SendWindowClose (const cWindow & a_Window) = 0; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 73f8e0ff1..3f3982c90 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -51,6 +51,7 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) case PROTO_VERSION_1_11_1: return "1.11.1"; case PROTO_VERSION_1_12: return "1.12"; case PROTO_VERSION_1_12_1: return "1.12.1"; + case PROTO_VERSION_1_12_2: return "1.12.2"; case PROTO_VERSION_1_13: return "1.13"; } ASSERT(!"Unknown protocol version"); @@ -921,6 +922,26 @@ void cProtocolRecognizer::SendUseBed(const cEntity & a_Entity, int a_BlockX, int +void cProtocolRecognizer::SendUnlockRecipe(UInt32 a_RecipeID) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendUnlockRecipe(a_RecipeID); +} + + + + + +void cProtocolRecognizer::SendInitRecipes(UInt32 a_RecipeID) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendInitRecipes(a_RecipeID); +} + + + + + void cProtocolRecognizer::SendWeather(eWeather a_Weather) { ASSERT(m_Protocol != nullptr); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index f82dab08a..c5d180b44 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -127,6 +127,8 @@ public: virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override; + virtual void SendUnlockRecipe (UInt32 a_RecipeID) override; + virtual void SendInitRecipes (UInt32 a_RecipeID) override; virtual void SendWeather (eWeather a_Weather) override; virtual void SendWholeInventory (const cWindow & a_Window) override; virtual void SendWindowClose (const cWindow & a_Window) override; diff --git a/src/Protocol/Protocol_1_12.cpp b/src/Protocol/Protocol_1_12.cpp index dba85435b..6998f73bf 100644 --- a/src/Protocol/Protocol_1_12.cpp +++ b/src/Protocol/Protocol_1_12.cpp @@ -25,6 +25,7 @@ Implements the 1.12 protocol classes: #include "../Root.h" #include "../Server.h" #include "../ClientHandle.h" +#include "../CraftingRecipes.h" #include "../Bindings/PluginManager.h" #include "../JsonUtils.h" @@ -1007,6 +1008,7 @@ UInt32 cProtocol_1_12::GetPacketID(cProtocol::ePacketType a_Packet) case pktTeleportEntity: return 0x4b; case pktTimeUpdate: return 0x46; case pktTitle: return 0x47; + case pktUnlockRecipe: return 0x30; case pktUpdateBlockEntity: return 0x09; case pktUpdateHealth: return 0x40; case pktUpdateScore: return 0x44; @@ -1019,10 +1021,27 @@ UInt32 cProtocol_1_12::GetPacketID(cProtocol::ePacketType a_Packet) +void cProtocol_1_12::HandleCraftRecipe(cByteBuffer & a_ByteBuffer) +{ + HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, WindowID); + HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt32, RecipeID); + HANDLE_READ(a_ByteBuffer, ReadBool, bool, MakeAll); + auto CuberiteRecipeId = cRoot::Get()->GetRecipeMapper()->GetCuberiteRecipeId(RecipeID, m_Client->GetProtocolVersion()); + if (CuberiteRecipeId.has_value()) + { + m_Client->HandleCraftRecipe(CuberiteRecipeId.value()); + } +} + + + + + void cProtocol_1_12::HandlePacketCraftingBookData(cByteBuffer & a_ByteBuffer) { + // TODO not yet used, not sure if it is needed + // https://wiki.vg/index.php?title=Protocol&oldid=14204#Crafting_Book_Data a_ByteBuffer.SkipRead(a_ByteBuffer.GetReadableSpace() - 1); - m_Client->GetPlayer()->SendMessageInfo("The green crafting book feature is not implemented yet."); } @@ -1170,6 +1189,7 @@ UInt32 cProtocol_1_12_1::GetPacketID(ePacketType a_Packet) case pktRespawn: return 0x35; case pktScoreboardObjective: return 0x42; case pktSpawnPosition: return 0x46; + case pktUnlockRecipe: return 0x31; case pktUpdateHealth: return 0x41; case pktUpdateScore: return 0x45; case pktUseBed: return 0x30; @@ -1277,7 +1297,7 @@ bool cProtocol_1_12_1::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketT case 0x0f: HandlePacketPlayerLook(a_ByteBuffer); return true; case 0x10: HandlePacketVehicleMove(a_ByteBuffer); return true; case 0x11: HandlePacketBoatSteer(a_ByteBuffer); return true; - case 0x12: break; // Craft Recipe Request - not yet implemented + case 0x12: HandleCraftRecipe(a_ByteBuffer); return true; case 0x13: HandlePacketPlayerAbilities(a_ByteBuffer); return true; case 0x14: HandlePacketBlockDig(a_ByteBuffer); return true; case 0x15: HandlePacketEntityAction(a_ByteBuffer); return true; @@ -1397,3 +1417,55 @@ void cProtocol_1_12_2::SendKeepAlive(UInt32 a_PingID) cPacketizer Pkt(*this, pktKeepAlive); Pkt.WriteBEInt64(a_PingID); } + + + + + +void cProtocol_1_12_2::SendUnlockRecipe(UInt32 a_RecipeID) +{ + ASSERT(m_State == 3); // In game mode? + + auto ProtocolRecipeId = cRoot::Get()->GetRecipeMapper()->GetProtocolRecipeId(a_RecipeID, m_Client->GetProtocolVersion()); + if (ProtocolRecipeId.has_value()) + { + cPacketizer Pkt(*this, pktUnlockRecipe); + Pkt.WriteVarInt32(1); + Pkt.WriteBool(true); + Pkt.WriteBool(false); + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(ProtocolRecipeId.value()); + } +} + + + + + +void cProtocol_1_12_2::SendInitRecipes(UInt32 a_RecipeID) +{ + ASSERT(m_State == 3); // In game mode? + + auto ProtocolRecipeId = cRoot::Get()->GetRecipeMapper()->GetProtocolRecipeId(a_RecipeID, m_Client->GetProtocolVersion()); + if (!ProtocolRecipeId.has_value()) + { + return; + } + + cPacketizer Pkt(*this, pktUnlockRecipe); + Pkt.WriteVarInt32(0); + Pkt.WriteBool(true); + Pkt.WriteBool(false); + if (a_RecipeID == 0) + { + Pkt.WriteVarInt32(0); + Pkt.WriteVarInt32(0); + } + else + { + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(ProtocolRecipeId.value()); + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(ProtocolRecipeId.value()); + } +} diff --git a/src/Protocol/Protocol_1_12.h b/src/Protocol/Protocol_1_12.h index 38c025e9e..c1b81955a 100644 --- a/src/Protocol/Protocol_1_12.h +++ b/src/Protocol/Protocol_1_12.h @@ -20,7 +20,7 @@ Declares the 1.12 protocol classes: #include "Protocol_1_11.h" - +#include "RecipeMapper.h" @@ -36,6 +36,7 @@ public: protected: virtual bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) override; virtual void HandlePacketAdvancementTab(cByteBuffer & a_ByteBuffer); + virtual void HandleCraftRecipe(cByteBuffer & a_ByteBuffer); virtual void HandlePacketCraftingBookData(cByteBuffer & a_ByteBuffer); virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override; @@ -86,8 +87,6 @@ protected: virtual void HandlePacketKeepAlive(cByteBuffer & a_ByteBuffer) override; virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; virtual void SendKeepAlive(UInt32 a_PingID) override; + virtual void SendUnlockRecipe(UInt32 a_RecipeID) override; + virtual void SendInitRecipes(UInt32 a_RecipeID) override; }; - - - - diff --git a/src/Protocol/Protocol_1_13.cpp b/src/Protocol/Protocol_1_13.cpp index fc048fe70..1dcecaa4b 100644 --- a/src/Protocol/Protocol_1_13.cpp +++ b/src/Protocol/Protocol_1_13.cpp @@ -140,6 +140,7 @@ UInt32 cProtocol_1_13::GetPacketID(ePacketType a_PacketType) case pktTimeUpdate: return 0x4a; case pktTitle: return 0x4b; case pktUnloadChunk: return 0x1f; + case pktUnlockRecipe: return 0x32; case pktUpdateHealth: return 0x44; case pktUpdateScore: return 0x48; case pktUpdateSign: return GetPacketID(pktUpdateBlockEntity); diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index b5d78e457..469f01c39 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -1587,6 +1587,26 @@ void cProtocol_1_8_0::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_B +void cProtocol_1_8_0::SendUnlockRecipe(UInt32 a_RecipeID) +{ + // Client doesn't support this feature + return; +} + + + + + +void cProtocol_1_8_0::SendInitRecipes(UInt32 a_RecipeID) +{ + // Client doesn't support this feature + return; +} + + + + + void cProtocol_1_8_0::SendWeather(eWeather a_Weather) { ASSERT(m_State == 3); // In game mode? diff --git a/src/Protocol/Protocol_1_8.h b/src/Protocol/Protocol_1_8.h index 42903a921..a8232104b 100644 --- a/src/Protocol/Protocol_1_8.h +++ b/src/Protocol/Protocol_1_8.h @@ -114,6 +114,8 @@ public: virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override; + virtual void SendUnlockRecipe (UInt32 a_RecipeID) override; + virtual void SendInitRecipes (UInt32 a_RecipeID) override; virtual void SendWeather (eWeather a_Weather) override; virtual void SendWholeInventory (const cWindow & a_Window) override; virtual void SendWindowClose (const cWindow & a_Window) override; diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp index 8327eaf40..784c26f34 100644 --- a/src/Protocol/Protocol_1_9.cpp +++ b/src/Protocol/Protocol_1_9.cpp @@ -567,6 +567,12 @@ UInt32 cProtocol_1_9_0::GetPacketID(cProtocol::ePacketType a_Packet) case pktWindowItems: return 0x14; case pktWindowOpen: return 0x13; case pktWindowProperty: return 0x15; + + // Unsupported packets + case pktUnlockRecipe: + { + break; + } } UNREACHABLE("Unsupported outgoing packet type"); } diff --git a/src/Protocol/RecipeMapper.cpp b/src/Protocol/RecipeMapper.cpp new file mode 100644 index 000000000..2757fdfd6 --- /dev/null +++ b/src/Protocol/RecipeMapper.cpp @@ -0,0 +1,128 @@ +#include "Globals.h" +#include "RecipeMapper.h" +#include "../Root.h" + +cRecipeMapper::cRecipeMapper(void) +{ + AString path = "Protocol"; + auto contents = cFile::GetFolderContents(path); + for (const auto & content: contents) + { + auto fullName = path + cFile::PathSeparator() + content; + if (cFile::IsFolder(fullName)) + { + loadRecipes(content); + } + } +} + + + + + +void cRecipeMapper::loadRecipes(const AString & a_ProtocolVersion) +{ + cFile f; + if (!f.Open("Protocol/" + a_ProtocolVersion + "/base.recipes.txt", cFile::fmRead)) + { + LOGWARNING("Cannot open file \"Protocol/%s/base.recipes.txt\", no recipe book recipes will be available!", a_ProtocolVersion); + return; + } + AString Everything; + if (!f.ReadRestOfFile(Everything)) + { + LOGWARNING("Cannot read file \"Protocol/%s/base.recipes.txt\", no recipe book recipes will be available!", a_ProtocolVersion); + return; + } + f.Close(); + + // Split it into lines, then process each line as a single recipe: + AStringVector Split = StringSplit(Everything, "\n"); + m_ProtocolVersionMap[a_ProtocolVersion] = {}; + const auto & RecipeNameMap = cRoot::Get()->GetCraftingRecipes()->GetRecipeNameMap(); + + int LineNum = 1; + for (AStringVector::const_iterator itr = Split.begin(); itr != Split.end(); ++itr, ++LineNum) + { + // Remove anything after a '#' sign and trim away the whitespace: + AString Recipe = TrimString(itr->substr(0, itr->find('#'))); + if (Recipe.empty()) + { + // Empty recipe + continue; + } + AddRecipeLine(a_ProtocolVersion, LineNum, Recipe, RecipeNameMap); + } + LOG("Loaded %s %zu recipe book", a_ProtocolVersion, m_ProtocolVersionMap[a_ProtocolVersion].size()); +} + + + + + +cRecipeMapper::~cRecipeMapper() +{ +} + + + + + +void cRecipeMapper::AddRecipeLine(const AString & a_ProtocolVersion, int a_LineNum, const AString & a_RecipeLine, const std::map<AString, UInt32> & a_RecipeNameMap) +{ + AStringVector Sides = StringSplit(a_RecipeLine, " "); + UInt32 Id; + if (Sides.size() != 2) + { + LOGINFO("Recipe incompletely configured %s", a_RecipeLine); + return; + } + StringToInteger<UInt32>(Sides[0], Id); + + auto RecipeIndex = a_RecipeNameMap.find(Sides[1]); + if (RecipeIndex == a_RecipeNameMap.end()) + { + return; + } + m_ProtocolVersionMap[a_ProtocolVersion].emplace(Id, RecipeIndex->second); +} + + + + + +std::optional<UInt32> cRecipeMapper::GetProtocolRecipeId(UInt32 a_RecipeId, UInt32 a_ProtocolVersion) +{ + auto ProtocolMap = m_ProtocolVersionMap.find(cRoot::Get()->GetProtocolVersionTextFromInt(static_cast<int>(a_ProtocolVersion))); + if (ProtocolMap == m_ProtocolVersionMap.end()) + { + return {}; + } + for (const auto & item: ProtocolMap->second) + { + if (item.second == a_RecipeId) + { + return item.first; + } + } + return {}; +} + + + + + +std::optional<UInt32> cRecipeMapper::GetCuberiteRecipeId(UInt32 a_ProtocolRecipeId, UInt32 a_ProtocolVersion) +{ + auto ProtocolMap = m_ProtocolVersionMap.find(cRoot::Get()->GetProtocolVersionTextFromInt(static_cast<int>(a_ProtocolVersion))); + if (ProtocolMap == m_ProtocolVersionMap.end()) + { + return {}; + } + auto Element = ProtocolMap->second.find(a_ProtocolRecipeId); + if (Element != ProtocolMap->second.end()) + { + return Element->second; + } + return {}; +} diff --git a/src/Protocol/RecipeMapper.h b/src/Protocol/RecipeMapper.h new file mode 100644 index 000000000..1cac62f92 --- /dev/null +++ b/src/Protocol/RecipeMapper.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../CraftingRecipes.h" +#include <optional> + +/** +The RecipeMapper handles the translation of crafting recipes into protocol +specific recipe Ids. +The crafting recipes are identified by the RecipeId. +The actual configuration is stored in the protocol specific configuration +directory, e.g. `Server/Protocol/1.12.2/base.recipes.txt` +*/ +class cRecipeMapper +{ +public: + cRecipeMapper(void); + ~cRecipeMapper(); + + /** Translates the cuberite RecipeId to the protocol specific RecipeId */ + std::optional<UInt32> GetProtocolRecipeId(UInt32 a_RecipeId, UInt32 a_ProtocolVersion); + + /** Translates the protocol specific RecipeId to the cuberite RecipeId */ + std::optional<UInt32> GetCuberiteRecipeId(UInt32 a_ProtocolRecipeId, UInt32 a_ProtocolVersion); + +private: + /** A mapping for each protocol from the protocol specific RecipeId and the cuberite RecipeId */ + std::map<AString, std::map<UInt32, UInt32>> m_ProtocolVersionMap; + + /** Load Recipes from the protocol specific mapping file */ + void loadRecipes(const AString & a_ProtocolVersion); + + /** Handles a single line of the protocol specific mapping file */ + void AddRecipeLine(const AString & a_ProtocolVersion, int a_LineNum, const AString & a_RecipeLine, const std::map<AString, UInt32> & a_RecipeNameMap); + +}; diff --git a/src/Root.cpp b/src/Root.cpp index 0cece9e6d..b3e7f61ee 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -24,6 +24,7 @@ #include "BrewingRecipes.h" #include "FurnaceRecipe.h" #include "CraftingRecipes.h" +#include "Protocol/RecipeMapper.h" #include "Bindings/PluginManager.h" #include "MonsterConfig.h" #include "Entities/Player.h" @@ -213,6 +214,7 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) m_RankManager.reset(new cRankManager()); m_RankManager->Initialize(*m_MojangAPI); m_CraftingRecipes = new cCraftingRecipes(); + m_RecipeMapper.reset(new cRecipeMapper()); m_FurnaceRecipe = new cFurnaceRecipe(); m_BrewingRecipes.reset(new cBrewingRecipes()); diff --git a/src/Root.h b/src/Root.h index 6c84e6bf7..2393871da 100644 --- a/src/Root.h +++ b/src/Root.h @@ -18,6 +18,7 @@ class cItem; class cMonsterConfig; class cBrewingRecipes; class cCraftingRecipes; +class cRecipeMapper; class cFurnaceRecipe; class cWebAdmin; class cPluginManager; @@ -89,6 +90,7 @@ public: cMonsterConfig * GetMonsterConfig(void) { return m_MonsterConfig; } cCraftingRecipes * GetCraftingRecipes(void) { return m_CraftingRecipes; } // tolua_export + cRecipeMapper * GetRecipeMapper(void) { return m_RecipeMapper.get(); } cFurnaceRecipe * GetFurnaceRecipe (void) { return m_FurnaceRecipe; } // Exported in ManualBindings.cpp with quite a different signature cBrewingRecipes * GetBrewingRecipes (void) { return m_BrewingRecipes.get(); } // Exported in ManualBindings.cpp @@ -229,6 +231,7 @@ private: cMonsterConfig * m_MonsterConfig; cCraftingRecipes * m_CraftingRecipes; + std::unique_ptr<cRecipeMapper> m_RecipeMapper; cFurnaceRecipe * m_FurnaceRecipe; std::unique_ptr<cBrewingRecipes> m_BrewingRecipes; cWebAdmin * m_WebAdmin; @@ -275,8 +278,3 @@ private: static void InputThread(cRoot & a_Params); }; // tolua_export - - - - - 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<cSlotAreaCrafting *>(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<cSlotAreaCrafting *>(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; }; - - - - |