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 <peterbell10@live.co.uk> * 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 <peterbell10@live.co.uk>
This commit is contained in:
co-authored by
peterbell10
parent
6e3f6d58a4
commit
36eab1b323
+43
-1
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+90
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+43
-9
@@ -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);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+92
-10
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+22
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+25
@@ -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;
|
||||
|
||||
+25
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
@@ -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());
|
||||
|
||||
|
||||
+3
-5
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+65
-5
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+5
-4
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user