summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/ClientHandle.cpp44
-rw-r--r--src/ClientHandle.h10
-rw-r--r--src/CraftingRecipes.cpp95
-rw-r--r--src/CraftingRecipes.h52
-rw-r--r--src/Entities/Player.cpp102
-rw-r--r--src/Entities/Player.h14
-rw-r--r--src/Inventory.cpp26
-rw-r--r--src/Inventory.h7
-rw-r--r--src/Item.h25
-rw-r--r--src/ItemGrid.cpp29
-rw-r--r--src/ItemGrid.h6
-rw-r--r--src/Protocol/CMakeLists.txt2
-rw-r--r--src/Protocol/Packetizer.cpp5
-rw-r--r--src/Protocol/Protocol.h3
-rw-r--r--src/Protocol/ProtocolRecognizer.cpp21
-rw-r--r--src/Protocol/ProtocolRecognizer.h2
-rw-r--r--src/Protocol/Protocol_1_12.cpp76
-rw-r--r--src/Protocol/Protocol_1_12.h9
-rw-r--r--src/Protocol/Protocol_1_13.cpp1
-rw-r--r--src/Protocol/Protocol_1_8.cpp20
-rw-r--r--src/Protocol/Protocol_1_8.h2
-rw-r--r--src/Protocol/Protocol_1_9.cpp6
-rw-r--r--src/Protocol/RecipeMapper.cpp128
-rw-r--r--src/Protocol/RecipeMapper.h35
-rw-r--r--src/Root.cpp2
-rw-r--r--src/Root.h8
-rw-r--r--src/UI/CraftingWindow.cpp6
-rw-r--r--src/UI/CraftingWindow.h7
-rw-r--r--src/UI/InventoryWindow.cpp6
-rw-r--r--src/UI/InventoryWindow.h6
-rw-r--r--src/UI/SlotArea.cpp70
-rw-r--r--src/UI/SlotArea.h9
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;
};
-
-
-
-