1
0
Fork 0
cuberite-2a/src/UI/SlotArea.cpp

2884 lines
65 KiB
C++
Raw Normal View History

2014-12-13 14:06:55 +00:00
// SlotArea.cpp
// Implements the cSlotArea class and its descendants
#include "Globals.h"
#include "SlotArea.h"
#include "../Entities/Player.h"
2014-07-30 19:59:35 +00:00
#include "../BlockEntities/BeaconEntity.h"
2015-09-24 08:48:33 +00:00
#include "../BlockEntities/BrewingstandEntity.h"
#include "../BlockEntities/ChestEntity.h"
#include "../BlockEntities/DropSpenserEntity.h"
#include "../BlockEntities/EnderChestEntity.h"
#include "../BlockEntities/FurnaceEntity.h"
2014-09-12 22:18:02 +00:00
#include "../Entities/Minecart.h"
#include "../Items/ItemHandler.h"
2014-12-13 14:06:55 +00:00
#include "AnvilWindow.h"
#include "../CraftingRecipes.h"
#include "../Root.h"
#include "../FastRandom.h"
#include "../BlockArea.h"
#include "../EffectID.h"
#include "../ClientHandle.h"
#include "../Mobs/Horse.h"
////////////////////////////////////////////////////////////////////////////////
// cSlotArea:
cSlotArea::cSlotArea(int a_NumSlots, cWindow & a_ParentWindow) :
m_NumSlots(a_NumSlots),
m_ParentWindow(a_ParentWindow)
{
}
void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
/*
2014-07-17 20:50:58 +00:00
LOGD("Slot area with %d slots clicked at slot number %d, clicked item %s, slot item %s",
GetNumSlots(), a_SlotNum,
ItemToFullString(a_ClickedItem).c_str(),
ItemToFullString(*GetSlot(a_SlotNum, a_Player)).c_str()
);
*/
2016-02-05 21:45:45 +00:00
ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
return;
}
2016-02-05 21:45:45 +00:00
2016-10-12 12:38:45 +00:00
if (a_Player.IsGameModeSpectator())
{
// Block the action of the player and make sure, the inventory doesn't get out of sync
a_Player.GetClientHandle()->SendInventorySlot(-1, -1, cItem()); // Reset the dragged item
SetSlot(a_SlotNum, a_Player, *GetSlot(a_SlotNum, a_Player)); // Update the current slot
return;
}
switch (a_ClickAction)
{
case caShiftLeftClick:
case caShiftRightClick:
{
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
case caDblClick:
{
DblClicked(a_Player, a_SlotNum);
return;
}
2014-07-05 12:00:04 +00:00
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
2014-07-05 22:40:59 +00:00
case caDropKey:
case caCtrlDropKey:
{
DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
return;
}
2014-07-09 12:30:06 +00:00
case caNumber1:
case caNumber2:
case caNumber3:
case caNumber4:
case caNumber5:
case caNumber6:
case caNumber7:
case caNumber8:
case caNumber9:
{
NumberClicked(a_Player, a_SlotNum, a_ClickAction);
return;
}
default:
{
break;
}
}
2016-02-05 21:45:45 +00:00
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
cItem & DraggingItem = a_Player.GetDraggingItem();
switch (a_ClickAction)
{
case caRightClick:
{
if (DraggingItem.m_ItemType <= 0) // Empty-handed?
{
DraggingItem = Slot.CopyOne(); // Obtain copy of slot to preserve lore, enchantments, etc.
2014-01-16 21:41:08 +00:00
DraggingItem.m_ItemCount = static_cast<char>(static_cast<float>(Slot.m_ItemCount) / 2.f + 0.5f);
Slot.m_ItemCount -= DraggingItem.m_ItemCount;
2014-01-16 18:44:11 +00:00
if (Slot.m_ItemCount <= 0)
{
Slot.Empty();
}
}
else if ((Slot.m_ItemType <= 0) || DraggingItem.IsEqual(Slot))
{
// Drop one item in slot
cItemHandler * Handler = ItemHandler(Slot.m_ItemType);
if ((DraggingItem.m_ItemCount > 0) && (Slot.m_ItemCount < Handler->GetMaxStackSize()))
{
2014-01-16 21:41:08 +00:00
char OldSlotCount = Slot.m_ItemCount;
Slot = DraggingItem.CopyOne(); // See above
2014-01-16 21:41:08 +00:00
OldSlotCount++;
Slot.m_ItemCount = OldSlotCount;
DraggingItem.m_ItemCount--;
}
if (DraggingItem.m_ItemCount <= 0)
{
DraggingItem.Empty();
}
}
else if (!DraggingItem.IsEqual(Slot))
{
// Swap contents
cItem tmp(DraggingItem);
DraggingItem = Slot;
Slot = tmp;
}
break;
}
2016-02-05 21:45:45 +00:00
case caLeftClick:
{
// Left-clicked
if (!DraggingItem.IsEqual(Slot))
{
// Switch contents
cItem tmp(DraggingItem);
DraggingItem = Slot;
Slot = tmp;
}
else
{
// Same type, add items:
cItemHandler * Handler = ItemHandler(DraggingItem.m_ItemType);
int FreeSlots = Handler->GetMaxStackSize() - Slot.m_ItemCount;
if (FreeSlots < 0)
{
ASSERT(!"Bad item stack size - where did we get more items in a slot than allowed?");
FreeSlots = 0;
}
char Filling = static_cast<char>((FreeSlots > DraggingItem.m_ItemCount) ? DraggingItem.m_ItemCount : FreeSlots);
Slot.m_ItemCount += Filling;
DraggingItem.m_ItemCount -= Filling;
if (DraggingItem.m_ItemCount <= 0)
{
DraggingItem.Empty();
}
}
break;
}
default:
{
LOGWARNING("SlotArea: Unhandled click action: %d (%s)", a_ClickAction, ClickActionToString(a_ClickAction));
m_ParentWindow.BroadcastWholeWindow();
return;
}
} // switch (a_ClickAction
2016-02-05 21:45:45 +00:00
SetSlot(a_SlotNum, a_Player, Slot);
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
}
void cSlotArea::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem)
{
// Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover:
cItem Slot(*GetSlot(a_SlotNum, a_Player));
m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true);
if (Slot.IsEmpty())
{
2014-08-27 23:21:54 +00:00
// Empty the slot completely, the client doesn't like left-over ItemType with zero count
Slot.Empty();
}
SetSlot(a_SlotNum, a_Player, Slot);
2016-02-05 21:45:45 +00:00
// Some clients try to guess our actions and not always right (armor slots in 1.2.5), so we fix them:
m_ParentWindow.BroadcastWholeWindow();
}
void cSlotArea::DblClicked(cPlayer & a_Player, int a_SlotNum)
{
cItem & Dragging = a_Player.GetDraggingItem();
if (Dragging.IsEmpty())
{
// Move the item in the dblclicked slot into hand:
Dragging = *GetSlot(a_SlotNum, a_Player);
cItem EmptyItem;
SetSlot(a_SlotNum, a_Player, EmptyItem);
}
if (Dragging.IsEmpty())
{
LOGD("%s DblClicked with an empty hand over empty slot, ignoring", a_Player.GetName().c_str());
return;
}
2016-02-05 21:45:45 +00:00
// Add as many items from the surrounding area into hand as possible:
// First skip full stacks, then if there's still space, process full stacks as well:
if (!m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, false))
{
m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, true);
}
2016-02-05 21:45:45 +00:00
m_ParentWindow.BroadcastWholeWindow(); // We need to broadcast, in case the window was a chest opened by multiple players
}
2014-07-05 12:00:04 +00:00
void cSlotArea::MiddleClicked(cPlayer & a_Player, int a_SlotNum)
{
cItem Slot(*GetSlot(a_SlotNum, a_Player));
cItem & DraggingItem = a_Player.GetDraggingItem();
if (!a_Player.IsGameModeCreative() || Slot.IsEmpty() || !DraggingItem.IsEmpty())
{
return;
}
DraggingItem = Slot;
DraggingItem.m_ItemCount = DraggingItem.GetMaxStackSize();
}
2014-07-05 22:40:59 +00:00
void cSlotArea::DropClicked(cPlayer & a_Player, int a_SlotNum, bool a_DropStack)
{
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (Slot.IsEmpty())
{
return;
}
cItem ItemToDrop = Slot.CopyOne();
if (a_DropStack)
{
ItemToDrop.m_ItemCount = Slot.m_ItemCount;
}
Slot.m_ItemCount -= ItemToDrop.m_ItemCount;
if (Slot.m_ItemCount <= 0)
{
Slot.Empty();
}
SetSlot(a_SlotNum, a_Player, Slot);
a_Player.TossPickup(ItemToDrop);
}
2014-07-09 12:30:06 +00:00
void cSlotArea::NumberClicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction)
{
if ((a_ClickAction < caNumber1) || (a_ClickAction > caNumber9))
{
return;
}
int HotbarSlot = static_cast<int>(a_ClickAction - caNumber1);
2014-07-09 12:30:06 +00:00
cItem ItemInHotbar(a_Player.GetInventory().GetHotbarSlot(HotbarSlot));
cItem ItemInSlot(*GetSlot(a_SlotNum, a_Player));
// The items are equal. Do nothing.
if (ItemInHotbar.IsEqual(ItemInSlot))
{
return;
}
a_Player.GetInventory().SetHotbarSlot(HotbarSlot, ItemInSlot);
SetSlot(a_SlotNum, a_Player, ItemInHotbar);
}
void cSlotArea::OnPlayerAdded(cPlayer & a_Player)
{
UNUSED(a_Player);
}
void cSlotArea::OnPlayerRemoved(cPlayer & a_Player)
{
UNUSED(a_Player);
}
void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
for (int i = 0; i < m_NumSlots; i++)
{
int SlotNum = (a_BackFill) ? (m_NumSlots - 1 - i) : i;
const cItem * Slot = GetSlot(SlotNum, a_Player);
2014-01-16 19:00:49 +00:00
if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
{
// Different items
continue;
}
char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
if (NumFit <= 0)
{
// Full stack already
continue;
}
NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);
2014-05-04 09:11:07 +00:00
if (a_ShouldApply)
{
cItem NewSlot(a_ItemStack);
NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
SetSlot(SlotNum, a_Player, NewSlot);
}
a_ItemStack.m_ItemCount -= NumFit;
if (a_ItemStack.IsEmpty())
{
return;
}
} // for i - Slots
}
bool cSlotArea::CollectItemsToHand(cItem & a_Dragging, cPlayer & a_Player, bool a_CollectFullStacks)
{
int NumSlots = GetNumSlots();
for (int i = 0; i < NumSlots; i++)
{
const cItem & SlotItem = *GetSlot(i, a_Player);
2014-01-16 19:00:49 +00:00
if (!SlotItem.IsEqual(a_Dragging))
{
continue;
}
int ToMove = a_Dragging.GetMaxStackSize() - a_Dragging.m_ItemCount;
if (ToMove > SlotItem.m_ItemCount)
{
ToMove = SlotItem.m_ItemCount;
}
a_Dragging.m_ItemCount += ToMove;
cItem NewSlot(SlotItem);
NewSlot.m_ItemCount -= ToMove;
SetSlot(i, a_Player, NewSlot);
if (!NewSlot.IsEmpty())
{
// There are leftovers in the slot, so a_Dragging must be full
return true;
}
} // for i - Slots[]
// a_Dragging may be full if there were exactly the number of items needed to fill it
return a_Dragging.IsFullStack();
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaChest:
cSlotAreaChest::cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow) :
cSlotArea(27, a_ParentWindow),
m_Chest(a_Chest)
{
}
const cItem * cSlotAreaChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
// a_SlotNum ranges from 0 to 26, use that to index the chest entity's inventory directly:
return &(m_Chest->GetSlot(a_SlotNum));
}
void cSlotAreaChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
m_Chest->SetSlot(a_SlotNum, a_Item);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaDoubleChest:
cSlotAreaDoubleChest::cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEntity * a_BottomChest, cWindow & a_ParentWindow) :
cSlotArea(54, a_ParentWindow),
m_TopChest(a_TopChest),
m_BottomChest(a_BottomChest)
{
}
const cItem * cSlotAreaDoubleChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
// a_SlotNum ranges from 0 to 53, use that to index the correct chest's inventory:
if (a_SlotNum < 27)
{
return &(m_TopChest->GetSlot(a_SlotNum));
}
else
{
return &(m_BottomChest->GetSlot(a_SlotNum - 27));
}
}
void cSlotAreaDoubleChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
if (a_SlotNum < 27)
{
m_TopChest->SetSlot(a_SlotNum, a_Item);
}
else
{
m_BottomChest->SetSlot(a_SlotNum - 27, a_Item);
}
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaCrafting:
cSlotAreaCrafting::cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow) :
cSlotAreaTemporary(1 + a_GridSize * a_GridSize, a_ParentWindow),
m_GridSize(a_GridSize)
{
ASSERT((a_GridSize == 2) || (a_GridSize == 3));
}
void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
2014-07-05 12:00:04 +00:00
if (a_ClickAction == caMiddleClick)
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
// Override for craft result slot
if (a_SlotNum == 0)
{
if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick))
{
ShiftClickedResult(a_Player);
}
2014-07-05 22:40:59 +00:00
else if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
{
DropClickedResult(a_Player);
}
else
{
ClickedResult(a_Player);
}
return;
}
2014-07-05 12:00:04 +00:00
2020-04-13 16:38:06 +00:00
Super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
UpdateRecipe(a_Player);
}
void cSlotAreaCrafting::DblClicked(cPlayer & a_Player, int a_SlotNum)
{
if (a_SlotNum == 0)
{
// Dbl-clicking the crafting result slot shouldn't collect items to hand
return;
}
2020-04-13 16:38:06 +00:00
Super::DblClicked(a_Player, a_SlotNum);
}
void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player)
{
// Toss all items on the crafting grid:
TossItems(a_Player, 1, m_NumSlots);
2016-02-05 21:45:45 +00:00
// Remove the current recipe from the player -> recipe map:
for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr)
{
if (itr->first == a_Player.GetUniqueID())
{
// Remove the player from the recipe map:
m_Recipes.erase(itr);
return;
}
} // for itr - m_Recipes[]
// Player not found - that is acceptable
}
void cSlotAreaCrafting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
// Update the recipe after setting the slot, if the slot is not the result slot:
2020-04-13 16:38:06 +00:00
Super::SetSlot(a_SlotNum, a_Player, a_Item);
if (a_SlotNum != 0)
{
UpdateRecipe(a_Player);
}
}
void cSlotAreaCrafting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
UNUSED(a_ItemStack);
UNUSED(a_Player);
UNUSED(a_ShouldApply);
UNUSED(a_KeepEmptySlots);
UNUSED(a_BackFill);
}
void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player)
{
cItem & DraggingItem = a_Player.GetDraggingItem();
// Get the current recipe:
cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
const cItem & Result = Recipe.GetResult();
cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);
// If possible, craft:
if (DraggingItem.IsEmpty())
{
DraggingItem = Result;
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
HandleCraftItem(Result, a_Player);
}
else if (DraggingItem.IsEqual(Result))
{
cItemHandler * Handler = ItemHandler(Result.m_ItemType);
if (DraggingItem.m_ItemCount + Result.m_ItemCount <= Handler->GetMaxStackSize())
{
DraggingItem.m_ItemCount += Result.m_ItemCount;
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
HandleCraftItem(Result, a_Player);
}
}
// Get the new recipe and update the result slot:
UpdateRecipe(a_Player);
2016-02-05 21:45:45 +00:00
// We're done. Send all changes to the client and bail out:
m_ParentWindow.BroadcastWholeWindow();
}
void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player)
{
cItem Result(*GetSlot(0, a_Player));
if (Result.IsEmpty())
{
return;
}
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>
2020-07-14 16:56:42 +00:00
a_Player.AddKnownItem(Result);
cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
2014-01-07 15:00:19 +00:00
for (;;)
{
// Try distributing the result. If it fails, bail out:
cItem ResultCopy(Result);
m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, false);
if (!ResultCopy.IsEmpty())
{
// Couldn't distribute all of it. Bail out
return;
}
2016-02-05 21:45:45 +00:00
// Distribute the result, this time for real:
ResultCopy = Result;
m_ParentWindow.DistributeStack(ResultCopy, 0, a_Player, this, true);
// Remove the ingredients from the crafting grid and update the recipe:
cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
UpdateRecipe(a_Player);
// Broadcast the window, we sometimes move items to different locations than Vanilla, causing needless desyncs:
m_ParentWindow.BroadcastWholeWindow();
2016-02-05 21:45:45 +00:00
// Added achievements processing
HandleCraftItem(Result, a_Player);
// If the recipe has changed, bail out:
if (!Recipe.GetResult().IsEqual(Result))
{
return;
}
2014-01-07 15:00:19 +00:00
}
}
2014-07-05 22:40:59 +00:00
void cSlotAreaCrafting::DropClickedResult(cPlayer & a_Player)
{
// Get the current recipe:
cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
const cItem & Result = Recipe.GetResult();
cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);
a_Player.TossPickup(Result);
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
HandleCraftItem(Result, a_Player);
UpdateRecipe(a_Player);
}
void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player)
{
cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize);
cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
cRoot::Get()->GetCraftingRecipes()->GetRecipe(a_Player, Grid, Recipe);
SetSlot(0, a_Player, Recipe.GetResult());
}
cCraftingRecipe & cSlotAreaCrafting::GetRecipeForPlayer(cPlayer & a_Player)
{
for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr)
{
if (itr->first == a_Player.GetUniqueID())
{
return itr->second;
}
} // for itr - m_Recipes[]
2016-02-05 21:45:45 +00:00
// Not found. Add a new one:
cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize);
cCraftingRecipe Recipe(Grid);
cRoot::Get()->GetCraftingRecipes()->GetRecipe(a_Player, Grid, Recipe);
m_Recipes.emplace_back(a_Player.GetUniqueID(), Recipe);
return m_Recipes.back().second;
}
void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Player)
{
switch (a_Result.m_ItemType)
{
case E_BLOCK_WORKBENCH: a_Player.AwardAchievement(Statistic::AchBuildWorkBench); break;
case E_BLOCK_FURNACE: a_Player.AwardAchievement(Statistic::AchBuildFurnace); break;
case E_BLOCK_CAKE: a_Player.AwardAchievement(Statistic::AchBakeCake); break;
case E_BLOCK_ENCHANTMENT_TABLE: a_Player.AwardAchievement(Statistic::AchEnchantments); break;
case E_BLOCK_BOOKCASE: a_Player.AwardAchievement(Statistic::AchBookcase); break;
case E_ITEM_WOODEN_PICKAXE: a_Player.AwardAchievement(Statistic::AchBuildPickaxe); break;
case E_ITEM_WOODEN_SWORD: a_Player.AwardAchievement(Statistic::AchBuildSword); break;
case E_ITEM_STONE_PICKAXE: a_Player.AwardAchievement(Statistic::AchBuildBetterPickaxe); break;
case E_ITEM_WOODEN_HOE: a_Player.AwardAchievement(Statistic::AchBuildHoe); break;
case E_ITEM_BREAD: a_Player.AwardAchievement(Statistic::AchMakeBread); break;
default: break;
}
}
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>
2020-07-14 16:56:42 +00:00
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());
}
}
}
////////////////////////////////////////////////////////////////////////////////
2014-04-30 22:47:57 +00:00
// cSlotAreaAnvil:
2014-12-13 14:06:55 +00:00
cSlotAreaAnvil::cSlotAreaAnvil(cWindow & a_ParentWindow) :
2014-04-30 22:47:57 +00:00
cSlotAreaTemporary(3, a_ParentWindow),
m_MaximumCost(0),
2014-06-04 10:39:21 +00:00
m_StackSizeToBeUsedInRepair(0)
2014-04-30 22:47:57 +00:00
{
}
void cSlotAreaAnvil::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
if (a_SlotNum != 2)
{
2020-04-13 16:38:06 +00:00
Super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
UpdateResult(a_Player);
return;
}
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
return;
}
2014-07-05 12:00:04 +00:00
switch (a_ClickAction)
2014-05-04 09:11:07 +00:00
{
2014-07-05 12:00:04 +00:00
case caDblClick:
{
return;
}
case caShiftLeftClick:
case caShiftRightClick:
{
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
2014-07-05 22:40:59 +00:00
case caDropKey:
case caCtrlDropKey:
{
if (CanTakeResultItem(a_Player))
{
DropClicked(a_Player, a_SlotNum, true);
OnTakeResult(a_Player);
}
return;
}
2014-07-05 12:00:04 +00:00
default:
{
break;
}
2014-05-04 09:11:07 +00:00
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
cItem & DraggingItem = a_Player.GetDraggingItem();
if (Slot.IsEmpty())
{
return;
}
if (!DraggingItem.IsEmpty())
{
if (!(DraggingItem.IsEqual(Slot) && ((DraggingItem.m_ItemCount + Slot.m_ItemCount) <= cItemHandler::GetItemHandler(Slot)->GetMaxStackSize())))
{
return;
}
}
if (!CanTakeResultItem(a_Player))
{
return;
}
cItem NewItem = cItem(Slot);
NewItem.m_ItemCount += DraggingItem.m_ItemCount;
Slot.Empty();
DraggingItem.Empty();
SetSlot(a_SlotNum, a_Player, Slot);
DraggingItem = NewItem;
2014-05-04 09:11:07 +00:00
OnTakeResult(a_Player);
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
}
2014-05-04 09:11:07 +00:00
void cSlotAreaAnvil::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem)
{
if (a_SlotNum != 2)
{
2020-04-13 16:38:06 +00:00
Super::ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
2014-05-04 09:11:07 +00:00
UpdateResult(a_Player);
return;
}
// Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover:
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (Slot.IsEmpty() || !CanTakeResultItem(a_Player))
{
return;
}
m_ParentWindow.DistributeStack(Slot, a_SlotNum, a_Player, this, true);
2014-05-04 09:11:07 +00:00
if (Slot.IsEmpty())
{
Slot.Empty();
OnTakeResult(a_Player);
}
SetSlot(a_SlotNum, a_Player, Slot);
2016-02-05 21:45:45 +00:00
2014-05-04 09:11:07 +00:00
// Some clients try to guess our actions and not always right (armor slots in 1.2.5), so we fix them:
m_ParentWindow.BroadcastWholeWindow();
}
void cSlotAreaAnvil::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
2014-05-04 09:11:07 +00:00
{
for (int i = 0; i < 2; i++)
{
int SlotNum = (a_BackFill) ? (2 - 1 - i) : i;
const cItem * Slot = GetSlot(SlotNum, a_Player);
2014-05-04 09:11:07 +00:00
if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
{
// Different items
continue;
}
char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
2014-05-04 09:11:07 +00:00
if (NumFit <= 0)
{
// Full stack already
continue;
}
NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);
2014-05-04 09:11:07 +00:00
if (a_ShouldApply)
{
cItem NewSlot(a_ItemStack);
NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
SetSlot(SlotNum, a_Player, NewSlot);
2014-05-04 09:11:07 +00:00
}
a_ItemStack.m_ItemCount -= NumFit;
if (a_ItemStack.IsEmpty())
{
UpdateResult(a_Player);
return;
}
} // for i - Slots
UpdateResult(a_Player);
}
void cSlotAreaAnvil::OnTakeResult(cPlayer & a_Player)
{
if (!a_Player.IsGameModeCreative())
{
2014-05-16 17:33:57 +00:00
a_Player.DeltaExperience(-cPlayer::XpForLevel(m_MaximumCost));
}
SetSlot(0, a_Player, cItem());
if (m_StackSizeToBeUsedInRepair > 0)
{
const cItem * Item = GetSlot(1, a_Player);
if (!Item->IsEmpty() && (Item->m_ItemCount > m_StackSizeToBeUsedInRepair))
{
cItem NewSecondItem(*Item);
NewSecondItem.m_ItemCount -= m_StackSizeToBeUsedInRepair;
m_StackSizeToBeUsedInRepair = 0;
SetSlot(1, a_Player, NewSecondItem);
}
else
{
SetSlot(1, a_Player, cItem());
}
}
else
{
SetSlot(1, a_Player, cItem());
}
m_ParentWindow.SetProperty(0, static_cast<short>(m_MaximumCost));
m_MaximumCost = 0;
static_cast<cAnvilWindow &>(m_ParentWindow).SetRepairedItemName("", nullptr);
const Vector3i BlockPos = static_cast<cAnvilWindow &>(m_ParentWindow).GetBlockPos();
BLOCKTYPE Block;
NIBBLETYPE BlockMeta;
a_Player.GetWorld()->GetBlockTypeMeta(BlockPos, Block, BlockMeta);
2017-06-13 19:35:30 +00:00
if (!a_Player.IsGameModeCreative() && (Block == E_BLOCK_ANVIL) && GetRandomProvider().RandBool(0.12))
{
NIBBLETYPE Orientation = BlockMeta & 0x3;
NIBBLETYPE AnvilDamage = BlockMeta >> 2;
++AnvilDamage;
if (AnvilDamage > 2)
{
// Anvil will break
a_Player.GetWorld()->SetBlock(BlockPos, E_BLOCK_AIR, 0);
a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_BREAK, BlockPos, 0);
a_Player.CloseWindow(false);
}
else
{
a_Player.GetWorld()->SetBlockMeta(BlockPos, static_cast<NIBBLETYPE>(Orientation | (AnvilDamage << 2)));
a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_USE, BlockPos, 0);
}
}
else
{
a_Player.GetWorld()->BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_ANVIL_USE, BlockPos, 0);
}
}
bool cSlotAreaAnvil::CanTakeResultItem(cPlayer & a_Player)
{
return (
(
2014-05-05 13:49:56 +00:00
a_Player.IsGameModeCreative() || // Is the player in gamemode?
(a_Player.GetXpLevel() >= m_MaximumCost) // or the player have enough exp?
) &&
(!GetSlot(2, a_Player)->IsEmpty()) && // Is a item in the result slot?
(m_MaximumCost > 0) // When no maximum cost is set, the item isn't set from the UpdateResult() method and can't be a valid enchanting result.
);
2014-04-30 22:47:57 +00:00
}
void cSlotAreaAnvil::OnPlayerRemoved(cPlayer & a_Player)
{
TossItems(a_Player, 0, 2);
2020-04-13 16:38:06 +00:00
Super::OnPlayerRemoved(a_Player);
2014-04-30 22:47:57 +00:00
}
void cSlotAreaAnvil::UpdateResult(cPlayer & a_Player)
{
const cItem Target(*GetSlot(0, a_Player));
const cItem Sacrifice(*GetSlot(1, a_Player));
2016-02-05 21:45:45 +00:00
// Output initialised as copy of target.
cItem Output(Target);
if (Target.IsEmpty())
2014-04-30 22:47:57 +00:00
{
Output.Empty();
SetSlot(2, a_Player, Output);
m_ParentWindow.SetProperty(0, 0);
m_MaximumCost = 0;
2014-04-30 22:47:57 +00:00
return;
}
m_MaximumCost = 0;
m_StackSizeToBeUsedInRepair = 0;
int RepairCost = Target.m_RepairCost;
2014-04-30 22:47:57 +00:00
int NeedExp = 0;
if (!Sacrifice.IsEmpty())
2014-04-30 22:47:57 +00:00
{
RepairCost += Sacrifice.m_RepairCost;
// Can we repair with sacrifce material?
if (Target.IsDamageable() && cItemHandler::GetItemHandler(Target)->CanRepairWithRawMaterial(Sacrifice.m_ItemType))
2014-04-30 22:47:57 +00:00
{
// Tool and armor repair with special item (iron / gold / diamond / ...)
int DamageDiff = std::min(static_cast<int>(Target.m_ItemDamage), static_cast<int>(Target.GetMaxDamage()) / 4);
if (DamageDiff <= 0)
2014-04-30 22:47:57 +00:00
{
// No enchantment
Output.Empty();
SetSlot(2, a_Player, Output);
m_ParentWindow.SetProperty(0, 0);
m_MaximumCost = 0;
2014-04-30 22:47:57 +00:00
return;
}
char NumItemsConsumed = 0;
// Repair until out of materials, or fully repaired:
while ((DamageDiff > 0) && (NumItemsConsumed < Sacrifice.m_ItemCount))
2014-04-30 22:47:57 +00:00
{
Output.m_ItemDamage -= DamageDiff;
NeedExp += std::max(1, DamageDiff / 100) + static_cast<int>(Target.m_Enchantments.Count());
DamageDiff = std::min(static_cast<int>(Output.m_ItemDamage), static_cast<int>(Target.GetMaxDamage()) / 4);
2014-04-30 22:47:57 +00:00
++NumItemsConsumed;
2014-04-30 22:47:57 +00:00
}
m_StackSizeToBeUsedInRepair = NumItemsConsumed;
2014-04-30 22:47:57 +00:00
}
else // Combining tools / armour
2014-04-30 22:47:57 +00:00
{
const bool IsEnchantBook = (Sacrifice.m_ItemType == E_ITEM_ENCHANTED_BOOK);
// No result if we can't combine the items
if (!IsEnchantBook && (!Target.IsSameType(Sacrifice) || !Target.IsDamageable()))
2014-04-30 22:47:57 +00:00
{
// No enchantment
Output.Empty();
SetSlot(2, a_Player, Output);
m_ParentWindow.SetProperty(0, 0);
m_MaximumCost = 0;
2014-04-30 22:47:57 +00:00
return;
}
// Can we repair with sacrifice tool / armour?
if (Target.IsDamageable() && !IsEnchantBook && (Target.m_ItemDamage!=0))
2014-04-30 22:47:57 +00:00
{
// Durability = MaxDamage - m_ItemDamage = how far from broken
const short TargetDurability = Target.GetMaxDamage() - Target.m_ItemDamage;
const short SacrificeDurability = Sacrifice.GetMaxDamage() - Sacrifice.m_ItemDamage;
// How much durability to repair by.
const short RepairDurability = SacrificeDurability + Target.GetMaxDamage() * 12 / 100;
2014-04-30 22:47:57 +00:00
// Don't give item a negative damage:
short NewItemDamage = std::max<short>(Target.GetMaxDamage() - (TargetDurability + RepairDurability), 0);
if (NewItemDamage < Target.m_ItemDamage)
{
Output.m_ItemDamage = NewItemDamage;
NeedExp += std::max(1, RepairDurability / 100);
}
2014-04-30 22:47:57 +00:00
}
// Add the enchantments from the sacrifice to the target
int EnchantmentCost = Output.AddEnchantmentsFromItem(Sacrifice);
NeedExp += EnchantmentCost;
2014-04-30 22:47:57 +00:00
}
}
int NameChangeExp = 0;
const AString & RepairedItemName = static_cast<cAnvilWindow*>(&m_ParentWindow)->GetRepairedItemName();
2014-04-30 23:25:04 +00:00
if (RepairedItemName.empty())
2014-04-30 22:47:57 +00:00
{
// Remove custom name
if (!Target.m_CustomName.empty())
2014-04-30 22:47:57 +00:00
{
NameChangeExp = (Target.IsDamageable()) ? 7 : (Target.m_ItemCount * 5);
2014-04-30 22:47:57 +00:00
NeedExp += NameChangeExp;
Output.m_CustomName = "";
2014-04-30 22:47:57 +00:00
}
}
else if (RepairedItemName != Target.m_CustomName)
2014-04-30 22:47:57 +00:00
{
// Change custom name
NameChangeExp = (Target.IsDamageable()) ? 7 : (Target.m_ItemCount * 5);
2014-04-30 22:47:57 +00:00
NeedExp += NameChangeExp;
if (!Target.m_CustomName.empty())
2014-04-30 22:47:57 +00:00
{
RepairCost += NameChangeExp / 2;
}
Output.m_CustomName = RepairedItemName;
2014-04-30 22:47:57 +00:00
}
m_MaximumCost = RepairCost + NeedExp;
2014-04-30 22:47:57 +00:00
if (NeedExp < 0)
{
Output.Empty();
2014-04-30 22:47:57 +00:00
}
if ((NameChangeExp == NeedExp) && (NameChangeExp > 0) && (m_MaximumCost >= 40))
2014-04-30 22:47:57 +00:00
{
m_MaximumCost = 39;
2014-04-30 22:47:57 +00:00
}
if ((m_MaximumCost >= 40) && !a_Player.IsGameModeCreative())
2014-04-30 22:47:57 +00:00
{
Output.Empty();
2014-04-30 22:47:57 +00:00
}
if (!Output.IsEmpty())
2014-04-30 22:47:57 +00:00
{
RepairCost = std::max(Target.m_RepairCost, Sacrifice.m_RepairCost);
if (!Output.m_CustomName.empty())
2014-04-30 22:47:57 +00:00
{
2014-05-07 18:43:37 +00:00
RepairCost -= 9;
2014-04-30 22:47:57 +00:00
}
2014-05-07 18:43:37 +00:00
RepairCost = std::max(RepairCost, 0);
2014-04-30 22:47:57 +00:00
RepairCost += 2;
Output.m_RepairCost = RepairCost;
}
// If after everything, output will be the same then no point enchanting:
if (Target.IsEqual(Output))
{
Output.Empty();
m_MaximumCost = 0;
}
2014-04-30 22:47:57 +00:00
SetSlot(2, a_Player, Output);
m_ParentWindow.SetProperty(0, static_cast<Int16>(m_MaximumCost));
2014-04-30 22:47:57 +00:00
}
2014-07-30 19:59:35 +00:00
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaBeacon:
cSlotAreaBeacon::cSlotAreaBeacon(cBeaconEntity * a_Beacon, cWindow & a_ParentWindow) :
cSlotArea(1, a_ParentWindow),
m_Beacon(a_Beacon)
{
m_Beacon->GetContents().AddListener(*this);
}
cSlotAreaBeacon::~cSlotAreaBeacon()
{
m_Beacon->GetContents().RemoveListener(*this);
}
2014-07-30 19:59:35 +00:00
bool cSlotAreaBeacon::IsPlaceableItem(short a_ItemType)
{
switch (a_ItemType)
{
case E_ITEM_EMERALD:
case E_ITEM_DIAMOND:
case E_ITEM_GOLD:
case E_ITEM_IRON:
{
return true;
}
default:
{
return false;
}
2014-07-30 19:59:35 +00:00
}
}
void cSlotAreaBeacon::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
2014-07-30 19:59:35 +00:00
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
2014-07-30 19:59:35 +00:00
return;
}
switch (a_ClickAction)
{
case caShiftLeftClick:
case caShiftRightClick:
{
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
case caDropKey:
case caCtrlDropKey:
{
DropClicked(a_Player, a_SlotNum, false);
return;
}
case caNumber1:
case caNumber2:
case caNumber3:
case caNumber4:
case caNumber5:
case caNumber6:
case caNumber7:
case caNumber8:
case caNumber9:
{
NumberClicked(a_Player, a_SlotNum, a_ClickAction);
return;
}
default:
{
break;
}
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
cItem & DraggingItem = a_Player.GetDraggingItem();
if (DraggingItem.IsEmpty())
{
DraggingItem = Slot;
Slot.Empty();
}
else if (Slot.IsEmpty())
{
if (!IsPlaceableItem(DraggingItem.m_ItemType))
{
return;
}
2016-02-05 21:45:45 +00:00
2014-07-30 19:59:35 +00:00
Slot = DraggingItem.CopyOne();
DraggingItem.m_ItemCount -= 1;
if (DraggingItem.m_ItemCount <= 0)
{
DraggingItem.Empty();
}
}
else if (DraggingItem.m_ItemCount == 1)
{
if (!IsPlaceableItem(DraggingItem.m_ItemCount))
{
return;
}
// Switch contents
cItem tmp(DraggingItem);
DraggingItem = Slot;
Slot = tmp;
}
SetSlot(a_SlotNum, a_Player, Slot);
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
}
void cSlotAreaBeacon::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
2014-07-30 19:59:35 +00:00
{
const cItem * Slot = GetSlot(0, a_Player);
if (!Slot->IsEmpty() || !IsPlaceableItem(a_ItemStack.m_ItemType) || (a_ItemStack.m_ItemCount != 1))
{
return;
}
if (a_ShouldApply)
{
SetSlot(0, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.Empty();
}
const cItem * cSlotAreaBeacon::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
UNUSED(a_Player);
return &(m_Beacon->GetSlot(a_SlotNum));
}
void cSlotAreaBeacon::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
UNUSED(a_Player);
m_Beacon->SetSlot(a_SlotNum, a_Item);
}
void cSlotAreaBeacon::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
UNUSED(a_SlotNum);
// Something has changed in the window, broadcast the entire window to all clients
ASSERT(a_ItemGrid == &(m_Beacon->GetContents()));
m_ParentWindow.BroadcastWholeWindow();
}
2014-04-30 22:47:57 +00:00
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaEnchanting:
cSlotAreaEnchanting::cSlotAreaEnchanting(cWindow & a_ParentWindow, Vector3i a_BlockPos) :
2015-05-18 14:43:24 +00:00
cSlotAreaTemporary(2, a_ParentWindow),
m_BlockPos(a_BlockPos)
{
}
2014-04-15 19:10:44 +00:00
void cSlotAreaEnchanting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
2014-04-14 09:48:12 +00:00
{
2014-04-15 19:10:44 +00:00
ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
2014-04-15 19:10:44 +00:00
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
2014-04-15 19:10:44 +00:00
return;
}
2014-08-27 23:21:54 +00:00
2014-04-15 19:10:44 +00:00
switch (a_ClickAction)
{
case caShiftLeftClick:
case caShiftRightClick:
{
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
case caDblClick:
{
2014-08-28 12:58:03 +00:00
DblClicked(a_Player, a_SlotNum);
2014-04-15 19:10:44 +00:00
return;
}
2014-07-05 12:00:04 +00:00
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
2014-08-27 23:21:54 +00:00
case caDropKey:
case caCtrlDropKey:
{
DropClicked(a_Player, a_SlotNum, false);
return;
}
case caNumber1:
case caNumber2:
case caNumber3:
case caNumber4:
case caNumber5:
case caNumber6:
case caNumber7:
case caNumber8:
case caNumber9:
{
NumberClicked(a_Player, a_SlotNum, a_ClickAction);
return;
}
2014-04-15 19:10:44 +00:00
default:
{
break;
}
}
2016-02-05 21:45:45 +00:00
2014-04-15 19:10:44 +00:00
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
cItem & DraggingItem = a_Player.GetDraggingItem();
if (a_SlotNum == 1)
{
// Lapis slot can have a full stack handle it normally, also check for empty hand
if ((DraggingItem.IsEmpty()) || ((DraggingItem.m_ItemType == E_ITEM_DYE) && (DraggingItem.m_ItemDamage == E_META_DYE_BLUE)))
{
return cSlotArea::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}
2014-08-27 23:21:54 +00:00
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
return;
}
// Slot 0 is where the item to enhance goes.
2014-08-27 23:21:54 +00:00
if (DraggingItem.IsEmpty())
2014-04-15 19:10:44 +00:00
{
2014-08-28 13:21:50 +00:00
// DraggingItem is empty -> Switch draggingitem and slot
2014-08-27 23:21:54 +00:00
if (!Slot.IsEmpty())
2014-04-15 19:10:44 +00:00
{
2014-08-28 12:58:03 +00:00
std::swap(DraggingItem, Slot);
2014-04-15 19:10:44 +00:00
}
2014-08-27 23:21:54 +00:00
}
else if (Slot.IsEmpty())
{
2014-08-28 13:21:50 +00:00
// DraggingItem isn't empty and slot is empty -> Set one dragging item in the slot
2014-08-27 23:21:54 +00:00
Slot = DraggingItem.CopyOne();
DraggingItem.m_ItemCount -= 1;
if (DraggingItem.m_ItemCount <= 0)
2014-04-15 19:10:44 +00:00
{
2014-08-27 23:21:54 +00:00
DraggingItem.Empty();
2014-04-15 19:10:44 +00:00
}
}
2014-08-27 23:21:54 +00:00
else if ((DraggingItem.m_ItemCount == 1) && !DraggingItem.IsEqual(Slot))
2014-04-15 19:10:44 +00:00
{
2014-08-28 13:21:50 +00:00
// DraggingItem and slot aren't empty -> Switch items
2014-08-28 12:58:03 +00:00
std::swap(DraggingItem, Slot);
2014-04-15 19:10:44 +00:00
}
2014-08-27 23:21:54 +00:00
SetSlot(a_SlotNum, a_Player, Slot);
if (bAsync)
2014-04-15 19:10:44 +00:00
{
2014-08-27 23:21:54 +00:00
m_ParentWindow.BroadcastWholeWindow();
2014-04-15 19:10:44 +00:00
}
}
2014-04-14 09:48:12 +00:00
2014-04-15 19:10:44 +00:00
void cSlotAreaEnchanting::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots, bool a_BackFill)
2014-04-15 19:10:44 +00:00
{
if ((a_ItemStack.m_ItemType == E_ITEM_DYE) && (a_ItemStack.m_ItemDamage == E_META_DYE_BLUE))
{
// It's lapis, put it in the lapis spot.
const cItem * Slot = GetSlot(1, a_Player);
char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
if (NumFit <= 0)
{
// Full stack already
return;
}
NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);
if (a_Apply)
{
cItem NewSlot(a_ItemStack);
NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
SetSlot(1, a_Player, NewSlot);
}
a_ItemStack.m_ItemCount -= NumFit;
// Return so we don't put overflow into the enchantment slot
return;
}
2014-04-15 19:10:44 +00:00
const cItem * Slot = GetSlot(0, a_Player);
if (!Slot->IsEmpty())
{
2014-04-15 19:10:44 +00:00
return;
}
2014-04-15 19:10:44 +00:00
if (a_Apply)
{
SetSlot(0, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.m_ItemCount -= 1;
if (a_ItemStack.m_ItemCount <= 0)
{
a_ItemStack.Empty();
}
2014-08-27 23:21:54 +00:00
}
2014-04-15 19:10:44 +00:00
2014-08-27 23:21:54 +00:00
void cSlotAreaEnchanting::OnPlayerAdded(cPlayer & a_Player)
{
2020-04-13 16:38:06 +00:00
Super::OnPlayerAdded(a_Player);
2014-04-15 19:10:44 +00:00
UpdateResult(a_Player);
}
void cSlotAreaEnchanting::OnPlayerRemoved(cPlayer & a_Player)
{
// Toss the item in the enchanting slot, as well as lapis
TossItems(a_Player, 0, m_NumSlots);
2014-04-19 13:10:50 +00:00
2020-04-13 16:38:06 +00:00
Super::OnPlayerRemoved(a_Player);
}
2014-08-27 23:21:54 +00:00
void cSlotAreaEnchanting::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
2020-04-13 16:38:06 +00:00
Super::SetSlot(a_SlotNum, a_Player, a_Item);
2014-08-27 23:21:54 +00:00
UpdateResult(a_Player);
}
2014-04-15 19:10:44 +00:00
void cSlotAreaEnchanting::UpdateResult(cPlayer & a_Player)
{
2014-04-14 09:48:12 +00:00
cItem Item = *GetSlot(0, a_Player);
if (!cItem::IsEnchantable(Item.m_ItemType) || !Item.m_Enchantments.IsEmpty())
{
return;
}
// Pseudocode found at: https://minecraft.gamepedia.com/Enchanting_mechanics
const auto Bookshelves = std::min(GetBookshelvesCount(*a_Player.GetWorld()), 15U);
// A PRNG initialised using the player's enchantment seed.
auto Random = a_Player.GetEnchantmentRandomProvider();
// Calculate the levels for the offered enchantment options:
const auto Base = (Random.RandInt(1U, 8U) + (Bookshelves / 2) + Random.RandInt(0U, Bookshelves));
const std::array<unsigned, 3> OptionLevels
{
std::max(Base / 3, 1U),
(Base * 2) / 3 + 1,
std::max(Base, Bookshelves * 2)
};
// Properties set according to: https://wiki.vg/Protocol#Window_Property
// Fake a "seed" for the client to draw Standard Galactic Alphabet glyphs:
m_ParentWindow.SetProperty(3, Random.RandInt<short>());
// Calculate an enchanting possibility for each option (top, middle and bottom) and send details to window:
for (size_t i = 0; i != OptionLevels.size(); i++)
{
// A copy of the item.
cItem EnchantedItem = Item.CopyOne();
// Enchant based on the number of levels:
EnchantedItem.EnchantByXPLevels(OptionLevels[i], Random);
LOGD("Generated enchanted item %d with enchantments: %s", i, EnchantedItem.m_Enchantments.ToString());
// Send the level requirement for the enchantment option:
m_ParentWindow.SetProperty(i, static_cast<short>(OptionLevels[i]));
// Get the first enchantment ID, which must exist:
ASSERT(EnchantedItem.m_Enchantments.begin() != EnchantedItem.m_Enchantments.end());
const auto EnchantmentID = static_cast<short>(EnchantedItem.m_Enchantments.begin()->first);
// Send the enchantment ID of the first enchantment on our item:
m_ParentWindow.SetProperty(4 + i, EnchantmentID);
const auto EnchantmentLevel = static_cast<short>(EnchantedItem.m_Enchantments.GetLevel(EnchantmentID));
ASSERT(EnchantmentLevel > 0);
// Send the level for the first enchantment on our item:
m_ParentWindow.SetProperty(7 + i, EnchantmentLevel);
// Store the item we've enchanted as an option to be retrieved later:
m_EnchantedItemOptions[i] = std::move(EnchantedItem);
}
}
unsigned cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
{
2014-04-15 19:10:44 +00:00
cBlockArea Area;
Area.Read(a_World, m_BlockPos - Vector3i(2, 0, 2), m_BlockPos + Vector3i(2, 1, 2));
2014-04-17 11:15:35 +00:00
static const struct
{
int m_BookX, m_BookY, m_BookZ; // Coords to check for bookcases
2014-04-19 13:10:50 +00:00
int m_AirX, m_AirY, m_AirZ; // Coords to check for air; if not air, the bookcase won't be counted
2014-04-17 11:15:35 +00:00
} CheckCoords[] =
{
{ 0, 0, 0, 1, 0, 1 }, // Bookcase at {0, 0, 0}, air at {1, 0, 1}
{ 0, 0, 1, 1, 0, 1 }, // Bookcase at {0, 0, 1}, air at {1, 0, 1}
{ 0, 0, 2, 1, 0, 2 }, // Bookcase at {0, 0, 2}, air at {1, 0, 2}
{ 0, 0, 3, 1, 0, 3 }, // Bookcase at {0, 0, 3}, air at {1, 0, 3}
{ 0, 0, 4, 1, 0, 3 }, // Bookcase at {0, 0, 4}, air at {1, 0, 3}
{ 1, 0, 4, 1, 0, 3 }, // Bookcase at {1, 0, 4}, air at {1, 0, 3}
{ 2, 0, 4, 2, 0, 3 }, // Bookcase at {2, 0, 4}, air at {2, 0, 3}
{ 3, 0, 4, 3, 0, 3 }, // Bookcase at {3, 0, 4}, air at {3, 0, 3}
{ 4, 0, 4, 3, 0, 3 }, // Bookcase at {4, 0, 4}, air at {3, 0, 3}
{ 4, 0, 3, 3, 0, 3 }, // Bookcase at {4, 0, 3}, air at {3, 0, 3}
{ 4, 0, 2, 3, 0, 2 }, // Bookcase at {4, 0, 2}, air at {3, 0, 2}
{ 4, 0, 1, 3, 0, 1 }, // Bookcase at {4, 0, 1}, air at {3, 0, 1}
{ 4, 0, 0, 3, 0, 1 }, // Bookcase at {4, 0, 0}, air at {3, 0, 1}
{ 3, 0, 0, 3, 0, 1 }, // Bookcase at {3, 0, 0}, air at {3, 0, 1}
{ 2, 0, 0, 2, 0, 1 }, // Bookcase at {2, 0, 0}, air at {2, 0, 1}
{ 1, 0, 0, 1, 0, 1 }, // Bookcase at {1, 0, 0}, air at {1, 0, 1}
{ 0, 1, 0, 1, 1, 1 }, // Bookcase at {0, 1, 0}, air at {1, 1, 1}
{ 0, 1, 1, 1, 1, 1 }, // Bookcase at {0, 1, 1}, air at {1, 1, 1}
{ 0, 1, 2, 1, 1, 2 }, // Bookcase at {0, 1, 2}, air at {1, 1, 2}
{ 0, 1, 3, 1, 1, 3 }, // Bookcase at {0, 1, 3}, air at {1, 1, 3}
{ 0, 1, 4, 1, 1, 3 }, // Bookcase at {0, 1, 4}, air at {1, 1, 3}
{ 1, 1, 4, 1, 1, 3 }, // Bookcase at {1, 1, 4}, air at {1, 1, 3}
{ 2, 1, 4, 2, 1, 3 }, // Bookcase at {2, 1, 4}, air at {2, 1, 3}
{ 3, 1, 4, 3, 1, 3 }, // Bookcase at {3, 1, 4}, air at {3, 1, 3}
{ 4, 1, 4, 3, 1, 3 }, // Bookcase at {4, 1, 4}, air at {3, 1, 3}
{ 4, 1, 3, 3, 1, 3 }, // Bookcase at {4, 1, 3}, air at {3, 1, 3}
{ 4, 1, 2, 3, 1, 2 }, // Bookcase at {4, 1, 2}, air at {3, 1, 2}
{ 4, 1, 1, 3, 1, 1 }, // Bookcase at {4, 1, 1}, air at {3, 1, 1}
{ 4, 1, 0, 3, 1, 1 }, // Bookcase at {4, 1, 0}, air at {3, 1, 1}
{ 3, 1, 0, 3, 1, 1 }, // Bookcase at {3, 1, 0}, air at {3, 1, 1}
{ 2, 1, 0, 2, 1, 1 }, // Bookcase at {2, 1, 0}, air at {2, 1, 1}
{ 1, 1, 0, 1, 1, 1 }, // Bookcase at {1, 1, 0}, air at {1, 1, 1}
};
unsigned Bookshelves = 0;
2014-04-17 11:15:35 +00:00
for (size_t i = 0; i < ARRAYCOUNT(CheckCoords); i++)
{
if (
(Area.GetRelBlockType(CheckCoords[i].m_AirX, CheckCoords[i].m_AirY, CheckCoords[i].m_AirZ) == E_BLOCK_AIR) && // There's air in the checkspot
(Area.GetRelBlockType(CheckCoords[i].m_BookX, CheckCoords[i].m_BookY, CheckCoords[i].m_BookZ) == E_BLOCK_BOOKCASE) // There's bookcase in the wanted place
2014-08-27 23:21:54 +00:00
)
2014-04-15 19:10:44 +00:00
{
2014-04-17 11:15:35 +00:00
Bookshelves++;
2014-04-15 19:10:44 +00:00
}
2014-04-17 11:15:35 +00:00
} // for i - CheckCoords
2014-04-15 19:10:44 +00:00
return Bookshelves;
}
cItem cSlotAreaEnchanting::SelectEnchantedOption(size_t a_EnchantOption)
{
ASSERT(a_EnchantOption < m_EnchantedItemOptions.size());
return std::move(m_EnchantedItemOptions[a_EnchantOption]);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaEnderChest:
cSlotAreaEnderChest::cSlotAreaEnderChest(cEnderChestEntity * a_EnderChest, cWindow & a_ParentWindow) :
cSlotArea(27, a_ParentWindow),
m_EnderChest(a_EnderChest)
{
}
const cItem * cSlotAreaEnderChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
2014-06-29 10:36:38 +00:00
return &(a_Player.GetEnderChestContents().GetSlot(a_SlotNum));
}
void cSlotAreaEnderChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
2014-06-29 10:36:38 +00:00
a_Player.GetEnderChestContents().SetSlot(a_SlotNum, a_Item);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaFurnace:
cSlotAreaFurnace::cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow) :
cSlotArea(3, a_ParentWindow),
m_Furnace(a_Furnace)
{
m_Furnace->GetContents().AddListener(*this);
}
cSlotAreaFurnace::~cSlotAreaFurnace()
{
m_Furnace->GetContents().RemoveListener(*this);
}
void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
2014-10-20 20:55:07 +00:00
if (m_Furnace == nullptr)
{
2014-10-20 20:55:07 +00:00
LOGERROR("cSlotAreaFurnace::Clicked(): m_Furnace == nullptr");
ASSERT(!"cSlotAreaFurnace::Clicked(): m_Furnace == nullptr");
return;
}
2014-05-30 21:41:17 +00:00
if (a_SlotNum == 2)
{
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
2014-05-30 21:41:17 +00:00
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
2014-05-30 21:41:17 +00:00
return;
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
2014-07-05 22:40:59 +00:00
switch (a_ClickAction)
{
2014-07-05 22:40:59 +00:00
case caShiftLeftClick:
case caShiftRightClick:
{
HandleSmeltItem(Slot, a_Player);
ShiftClicked(a_Player, a_SlotNum, Slot);
return;
}
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
case caDropKey:
case caCtrlDropKey:
{
DropClicked(a_Player, a_SlotNum, (a_SlotNum == caCtrlDropKey));
Slot.m_ItemCount = Slot.m_ItemCount - GetSlot(a_SlotNum, a_Player)->m_ItemCount;
HandleSmeltItem(Slot, a_Player);
return;
}
default:
{
break;
}
}
cItem & DraggingItem = a_Player.GetDraggingItem();
2014-05-30 21:41:17 +00:00
if (!DraggingItem.IsEmpty())
{
2014-05-30 22:25:20 +00:00
if (a_ClickAction == caDblClick)
{
return;
}
2014-05-30 21:41:17 +00:00
if (!DraggingItem.IsEqual(Slot))
{
return;
}
if ((DraggingItem.m_ItemCount + Slot.m_ItemCount) > Slot.GetMaxStackSize())
{
return;
}
DraggingItem.m_ItemCount += Slot.m_ItemCount;
HandleSmeltItem(Slot, a_Player);
2014-05-30 21:41:17 +00:00
Slot.Empty();
}
else
{
switch (a_ClickAction)
{
case caDblClick:
{
DblClicked(a_Player, a_SlotNum);
return;
}
case caLeftClick:
{
2014-05-30 22:27:24 +00:00
DraggingItem = Slot;
HandleSmeltItem(Slot, a_Player);
2014-05-30 21:41:17 +00:00
Slot.Empty();
break;
}
case caRightClick:
{
DraggingItem = Slot.CopyOne();
DraggingItem.m_ItemCount = static_cast<char>(static_cast<float>(Slot.m_ItemCount) / 2.f + 0.5f);
2014-05-30 21:41:17 +00:00
Slot.m_ItemCount -= DraggingItem.m_ItemCount;
if (Slot.m_ItemCount <= 0)
2014-05-30 21:41:17 +00:00
{
Slot.Empty();
}
HandleSmeltItem(DraggingItem, a_Player);
2014-05-30 21:41:17 +00:00
break;
}
default:
{
ASSERT(!"Unhandled click type!");
}
}
}
SetSlot(a_SlotNum, a_Player, Slot);
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
return;
}
2020-04-13 16:38:06 +00:00
Super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}
void cSlotAreaFurnace::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
int SlotNum;
cFurnaceRecipe * FurnaceRecipes = cRoot::Get()->GetFurnaceRecipe();
if (FurnaceRecipes->GetRecipeFrom(a_ItemStack) != nullptr)
{
SlotNum = 0;
}
else if (FurnaceRecipes->IsFuel(a_ItemStack))
{
SlotNum = 1;
}
else
{
return;
}
const cItem * Slot = GetSlot(SlotNum, a_Player);
if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
{
// Different items
return;
}
char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
if (NumFit <= 0)
{
// Full stack already
return;
}
NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);
if (a_ShouldApply)
{
cItem NewSlot(a_ItemStack);
NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
SetSlot(SlotNum, a_Player, NewSlot);
}
a_ItemStack.m_ItemCount -= NumFit;
if (a_ItemStack.IsEmpty())
{
return;
}
}
const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
2014-05-30 21:41:17 +00:00
UNUSED(a_Player);
// a_SlotNum ranges from 0 to 2, query the items from the underlying furnace:
return &(m_Furnace->GetSlot(a_SlotNum));
}
void cSlotAreaFurnace::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
m_Furnace->SetSlot(a_SlotNum, a_Item);
}
void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
2014-05-30 21:41:17 +00:00
UNUSED(a_SlotNum);
// Something has changed in the window, broadcast the entire window to all clients
ASSERT(a_ItemGrid == &(m_Furnace->GetContents()));
m_ParentWindow.BroadcastWholeWindow();
}
void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player)
{
int Reward = m_Furnace->GetAndResetReward();
if (Reward > 0)
{
a_Player.GetWorld()->SpawnSplitExperienceOrbs(a_Player.GetPosX(), a_Player.GetPosY(), a_Player.GetPosZ(), Reward);
}
/** TODO 2014-05-12 xdot: Figure out when to call this method. */
switch (a_Result.m_ItemType)
{
case E_ITEM_IRON: a_Player.AwardAchievement(Statistic::AchAcquireIron); break;
case E_ITEM_COOKED_FISH: a_Player.AwardAchievement(Statistic::AchCookFish); break;
default: break;
}
}
2015-09-24 08:48:33 +00:00
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaBrewingstand:
cSlotAreaBrewingstand::cSlotAreaBrewingstand(cBrewingstandEntity * a_Brewingstand, cWindow & a_ParentWindow) :
cSlotArea(5, a_ParentWindow),
2015-09-24 08:48:33 +00:00
m_Brewingstand(a_Brewingstand)
{
m_Brewingstand->GetContents().AddListener(*this);
}
cSlotAreaBrewingstand::~cSlotAreaBrewingstand()
{
m_Brewingstand->GetContents().RemoveListener(*this);
}
void cSlotAreaBrewingstand::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
if (m_Brewingstand == nullptr)
{
LOGERROR("cSlotAreaBrewingstand::Clicked(): m_Brewingstand == nullptr");
ASSERT(!"cSlotAreaBrewingstand::Clicked(): m_Brewingstand == nullptr");
return;
}
if (GetSlot(a_SlotNum, a_Player) == nullptr)
2015-09-24 08:48:33 +00:00
{
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
return;
}
2015-09-24 08:48:33 +00:00
cItem Slot(*GetSlot(a_SlotNum, a_Player));
cItem & DraggingItem = a_Player.GetDraggingItem();
cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
2015-09-24 08:48:33 +00:00
if ((a_SlotNum >= 0) && (a_SlotNum <= 2))
{
// Bottle slots
2015-09-24 08:48:33 +00:00
switch (a_ClickAction)
{
case caLeftClick:
case caRightClick:
{
if (BR->IsBottle(Slot))
{
HandleBrewedItem(a_Player, Slot);
}
if (!DraggingItem.IsEmpty() && !BR->IsBottle(DraggingItem))
{
// Deny placing a invalid item into the bottle slot
return;
}
break;
}
2015-09-24 08:48:33 +00:00
case caShiftLeftClick:
case caShiftRightClick:
{
if (BR->IsBottle(Slot))
{
HandleBrewedItem(a_Player, Slot);
}
2020-04-13 16:38:06 +00:00
Super::ShiftClicked(a_Player, a_SlotNum, Slot);
break;
2015-09-24 08:48:33 +00:00
}
default:
2015-09-24 08:48:33 +00:00
{
if (!DraggingItem.IsEmpty() && !BR->IsBottle(DraggingItem))
{
// Deny placing a invalid item into the bottle slot
return;
}
break;
2015-09-24 08:48:33 +00:00
}
}
}
if ((a_SlotNum == 3) && !DraggingItem.IsEmpty())
{
// Ingredient slot
switch (a_ClickAction)
{
case caShiftLeftClick:
case caShiftRightClick:
2015-09-24 08:48:33 +00:00
{
2020-04-13 16:38:06 +00:00
Super::ShiftClicked(a_Player, a_SlotNum, Slot);
break;
2015-09-24 08:48:33 +00:00
}
default:
{
if (!BR->IsIngredient(DraggingItem))
{
// Deny placing a invalid item into the ingredient slot
return;
}
2015-09-24 08:48:33 +00:00
break;
}
}
}
2015-09-24 08:48:33 +00:00
if ((a_SlotNum == 4) && !DraggingItem.IsEmpty())
{
// Fuel slot
switch (a_ClickAction)
2015-09-24 08:48:33 +00:00
{
case caShiftLeftClick:
case caShiftRightClick:
2015-09-24 08:48:33 +00:00
{
2020-04-13 16:38:06 +00:00
Super::ShiftClicked(a_Player, a_SlotNum, Slot);
break;
}
default:
{
if (!BR->IsFuel(DraggingItem))
2015-09-24 08:48:33 +00:00
{
// Deny placing a invalid item into the fuel slot
2015-09-24 08:48:33 +00:00
return;
}
break;
2015-09-24 08:48:33 +00:00
}
}
}
2020-04-13 16:38:06 +00:00
Super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
2015-09-24 08:48:33 +00:00
}
void cSlotAreaBrewingstand::HandleBrewedItem(cPlayer & a_Player, const cItem & a_ClickedItem)
2015-09-24 08:48:33 +00:00
{
// Award an achievement if the item is not a water bottle (is a real brewed potion)
if (a_ClickedItem.m_ItemDamage > 0)
{
a_Player.AwardAchievement(Statistic::AchPotion);
}
2015-09-24 08:48:33 +00:00
}
void cSlotAreaBrewingstand::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
int SlotNum = -1;
cBrewingRecipes * BR = cRoot::Get()->GetBrewingRecipes();
if (BR->IsBottle(a_ItemStack))
{
for (int i = 0;i < 3;i++)
{
if (GetSlot(i, a_Player)->IsEmpty())
{
SlotNum = i;
break;
}
}
if (SlotNum == -1)
{
// All slots are full
return;
}
}
else if (BR->IsIngredient(a_ItemStack))
{
SlotNum = 3;
}
else
{
return;
}
const cItem * Slot = GetSlot(SlotNum, a_Player);
if (!Slot->IsEqual(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots))
{
// Different items
return;
}
char NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount;
if (NumFit <= 0)
{
// Full stack already
return;
}
NumFit = std::min(NumFit, a_ItemStack.m_ItemCount);
if (a_ShouldApply)
{
cItem NewSlot(a_ItemStack);
NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit;
SetSlot(SlotNum, a_Player, NewSlot);
}
a_ItemStack.m_ItemCount -= NumFit;
if (a_ItemStack.IsEmpty())
{
return;
}
}
const cItem * cSlotAreaBrewingstand::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
UNUSED(a_Player);
// a_SlotNum ranges from 0 to 3, query the items from the underlying brewing stand:
return &(m_Brewingstand->GetSlot(a_SlotNum));
}
void cSlotAreaBrewingstand::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
UNUSED(a_Player);
m_Brewingstand->SetSlot(a_SlotNum, a_Item);
}
void cSlotAreaBrewingstand::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
UNUSED(a_SlotNum);
// Something has changed in the window, broadcast the entire window to all clients
ASSERT(a_ItemGrid == &(m_Brewingstand->GetContents()));
m_ParentWindow.BroadcastWholeWindow();
}
2014-09-12 22:18:02 +00:00
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaMinecartWithChest:
cSlotAreaMinecartWithChest::cSlotAreaMinecartWithChest(cMinecartWithChest * a_Chest, cWindow & a_ParentWindow) :
cSlotArea(27, a_ParentWindow),
m_Chest(a_Chest)
{
}
const cItem * cSlotAreaMinecartWithChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
// a_SlotNum ranges from 0 to 26, use that to index the minecart chest entity's inventory directly:
UNUSED(a_Player);
return &(m_Chest->GetSlot(a_SlotNum));
}
void cSlotAreaMinecartWithChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
UNUSED(a_Player);
m_Chest->SetSlot(a_SlotNum, a_Item);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaInventoryBase:
cSlotAreaInventoryBase::cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow) :
cSlotArea(a_NumSlots, a_ParentWindow),
m_SlotOffset(a_SlotOffset)
{
}
void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory))
{
2014-07-05 22:40:59 +00:00
if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
{
DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
return;
}
2016-02-05 21:45:45 +00:00
// Creative inventory must treat a_ClickedItem as a DraggedItem instead, replacing the inventory slot with it
SetSlot(a_SlotNum, a_Player, a_ClickedItem);
return;
}
2016-02-05 21:45:45 +00:00
// Survival inventory and all other windows' inventory has the same handling as normal slot areas
2020-04-13 16:38:06 +00:00
Super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}
const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
// a_SlotNum ranges from 0 to 35, map that to the player's inventory slots according to the internal offset
return &a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset);
}
void cSlotAreaInventoryBase::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
a_Player.GetInventory().SetSlot(a_SlotNum + m_SlotOffset, a_Item);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaArmor:
void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
if (ItemCategory::IsHelmet(a_ItemStack.m_ItemType) && GetSlot(0, a_Player)->IsEmpty())
{
if (a_ShouldApply)
{
SetSlot(0, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.m_ItemCount -= 1;
}
else if (ItemCategory::IsChestPlate(a_ItemStack.m_ItemType) && GetSlot(1, a_Player)->IsEmpty())
{
if (a_ShouldApply)
{
SetSlot(1, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.m_ItemCount -= 1;
}
else if (ItemCategory::IsLeggings(a_ItemStack.m_ItemType) && GetSlot(2, a_Player)->IsEmpty())
{
if (a_ShouldApply)
{
SetSlot(2, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.m_ItemCount -= 1;
}
else if (ItemCategory::IsBoots(a_ItemStack.m_ItemType) && GetSlot(3, a_Player)->IsEmpty())
{
if (a_ShouldApply)
{
SetSlot(3, a_Player, a_ItemStack.CopyOne());
}
a_ItemStack.m_ItemCount -= 1;
}
}
2014-04-24 18:41:25 +00:00
void cSlotAreaArmor::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots()));
2014-07-26 11:29:25 +00:00
// When the player is in creative mode, the client sends the new item as a_ClickedItem, not the current item in the slot.
if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory))
{
if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
{
DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
return;
}
2016-02-05 21:45:45 +00:00
SetSlot(a_SlotNum, a_Player, a_ClickedItem);
return;
}
2014-04-24 18:41:25 +00:00
bool bAsync = false;
2014-10-20 20:55:07 +00:00
if (GetSlot(a_SlotNum, a_Player) == nullptr)
2014-04-24 18:41:25 +00:00
{
2014-10-20 20:55:07 +00:00
LOGWARNING("GetSlot(%d) returned nullptr! Ignoring click", a_SlotNum);
2014-04-24 18:41:25 +00:00
return;
}
2014-07-05 12:00:04 +00:00
switch (a_ClickAction)
2014-04-24 18:41:25 +00:00
{
2014-07-05 12:00:04 +00:00
case caDblClick:
{
// Armors haven't a dbl click
return;
}
case caShiftLeftClick:
case caShiftRightClick:
{
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
case caMiddleClick:
{
MiddleClicked(a_Player, a_SlotNum);
return;
}
default:
{
break;
}
2014-04-24 18:41:25 +00:00
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
if (!Slot.IsSameType(a_ClickedItem))
{
LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots);
LOGWARNING("My item: %s", ItemToFullString(Slot).c_str());
LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str());
bAsync = true;
}
cItem & DraggingItem = a_Player.GetDraggingItem();
if ((a_ClickAction != caRightClick) && (a_ClickAction != caLeftClick))
{
LOGWARNING("SlotArea: Unhandled click action: %d (%s)", a_ClickAction, ClickActionToString(a_ClickAction));
m_ParentWindow.BroadcastWholeWindow();
return;
}
if (DraggingItem.IsEmpty() || CanPlaceArmorInSlot(a_SlotNum, DraggingItem))
2014-04-24 18:41:25 +00:00
{
// Swap contents
cItem tmp(DraggingItem);
DraggingItem = Slot;
Slot = tmp;
}
SetSlot(a_SlotNum, a_Player, Slot);
if (bAsync)
{
m_ParentWindow.BroadcastWholeWindow();
}
}
bool cSlotAreaArmor::CanPlaceArmorInSlot(int a_SlotNum, const cItem & a_Item)
2014-04-24 18:41:25 +00:00
{
switch (a_SlotNum)
{
2015-05-24 12:37:36 +00:00
case 0: return (ItemCategory::IsHelmet(a_Item.m_ItemType) || (a_Item.m_ItemType == E_BLOCK_PUMPKIN));
2014-04-24 18:41:25 +00:00
case 1: return ItemCategory::IsChestPlate(a_Item.m_ItemType);
2015-05-24 12:37:36 +00:00
case 2: return ItemCategory::IsLeggings(a_Item.m_ItemType);
case 3: return ItemCategory::IsBoots(a_Item.m_ItemType);
default: return false;
2014-04-24 18:41:25 +00:00
}
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaItemGrid:
cSlotAreaItemGrid::cSlotAreaItemGrid(cItemGrid & a_ItemGrid, cWindow & a_ParentWindow) :
2020-04-13 16:38:06 +00:00
Super(a_ItemGrid.GetNumSlots(), a_ParentWindow),
m_ItemGrid(a_ItemGrid)
{
m_ItemGrid.AddListener(*this);
}
cSlotAreaItemGrid::~cSlotAreaItemGrid()
{
m_ItemGrid.RemoveListener(*this);
}
const cItem * cSlotAreaItemGrid::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
return &m_ItemGrid.GetSlot(a_SlotNum);
}
void cSlotAreaItemGrid::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
m_ItemGrid.SetSlot(a_SlotNum, a_Item);
}
void cSlotAreaItemGrid::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
ASSERT(a_ItemGrid == &m_ItemGrid);
m_ParentWindow.BroadcastSlot(this, a_SlotNum);
}
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaTemporary:
cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) :
cSlotArea(a_NumSlots, a_ParentWindow)
{
}
const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
cItemMap::const_iterator itr = m_Items.find(a_Player.GetUniqueID());
if (itr == m_Items.end())
{
LOGERROR("cSlotAreaTemporary: player \"%s\" not found for slot %d!", a_Player.GetName().c_str(), a_SlotNum);
ASSERT(!"cSlotAreaTemporary: player not found!");
2016-02-05 21:45:45 +00:00
2014-10-20 20:55:07 +00:00
// Player not found, this should not happen, ever! Return nullptr, but things may break by this.
return nullptr;
}
2016-02-05 21:45:45 +00:00
if (a_SlotNum >= static_cast<int>(itr->second.size()))
{
LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!");
ASSERT(!"cSlotAreaTemporary: asking for more slots than actually stored!");
2014-10-20 20:55:07 +00:00
return nullptr;
}
2016-02-05 21:45:45 +00:00
return &(itr->second[static_cast<size_t>(a_SlotNum)]);
}
void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
if (itr == m_Items.end())
{
// Player not found
LOGWARNING("cSlotAreaTemporary: player not found!");
return;
}
2016-02-05 21:45:45 +00:00
if (a_SlotNum >= static_cast<int>(itr->second.size()))
{
LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!");
return;
}
2016-02-05 21:45:45 +00:00
itr->second[static_cast<size_t>(a_SlotNum)] = a_Item;
m_ParentWindow.SendSlot(a_Player, this, a_SlotNum);
}
void cSlotAreaTemporary::OnPlayerAdded(cPlayer & a_Player)
{
ASSERT(m_Items.find(a_Player.GetUniqueID()) == m_Items.end()); // The player shouldn't be in the itemmap, otherwise we probably have a leak
m_Items[a_Player.GetUniqueID()].resize(static_cast<size_t>(m_NumSlots)); // Make the vector the specified size of empty items
}
void cSlotAreaTemporary::OnPlayerRemoved(cPlayer & a_Player)
{
cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
ASSERT(itr != m_Items.end()); // The player should be in the list, otherwise a call to OnPlayerAdded() was mismatched
m_Items.erase(itr);
}
void cSlotAreaTemporary::TossItems(cPlayer & a_Player, int a_Begin, int a_End)
{
cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
if (itr == m_Items.end())
{
LOGWARNING("Player tossing items (%s) not found in the item map", a_Player.GetName().c_str());
return;
}
2016-02-05 21:45:45 +00:00
cItems Drops;
for (int i = a_Begin; i < a_End; i++)
{
cItem & Item = itr->second[static_cast<size_t>(i)];
if (!Item.IsEmpty())
{
Drops.push_back(Item);
}
Item.Empty();
} // for i - itr->second[]
2016-02-05 21:45:45 +00:00
a_Player.TossItems(Drops);
}
cItem * cSlotAreaTemporary::GetPlayerSlots(cPlayer & a_Player)
{
cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID());
if (itr == m_Items.end())
{
2014-10-20 20:55:07 +00:00
return nullptr;
}
2014-10-21 12:20:06 +00:00
return itr->second.data();
}
2015-09-24 08:48:33 +00:00
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaHorse:
cSlotAreaHorse::cSlotAreaHorse(cHorse & a_Horse, cWindow & a_ParentWindow) :
cSlotArea(2, a_ParentWindow),
m_Horse(a_Horse)
{
}
void cSlotAreaHorse::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
cItem & DraggingItem = a_Player.GetDraggingItem();
switch (a_ClickAction)
{
case caLeftClick:
case caRightClick:
case caDblClick:
{
// Check for invalid item types
if (DraggingItem.IsEmpty())
{
break;
}
switch (a_SlotNum)
{
case SaddleSlot:
{
if (DraggingItem.m_ItemType != E_ITEM_SADDLE)
{
return;
}
break;
}
case ArmorSlot:
{
if (!ItemCategory::IsHorseArmor(DraggingItem.m_ItemType))
{
return;
}
break;
}
default: break;
}
}
default: break;
}
cSlotArea::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
}
const cItem * cSlotAreaHorse::GetSlot(int a_SlotNum, cPlayer & a_Player) const
{
static const cItem InvalidItem;
switch (a_SlotNum)
{
case SaddleSlot: return &m_Horse.GetHorseSaddle();
case ArmorSlot: return &m_Horse.GetHorseArmorItem();
default:
{
LOGWARN("cSlotAreaHorse::GetSlot: Invalid slot number %d", a_SlotNum);
return &InvalidItem;
}
}
}
void cSlotAreaHorse::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item)
{
switch (a_SlotNum)
{
case SaddleSlot: m_Horse.SetHorseSaddle(a_Item); break;
case ArmorSlot: m_Horse.SetHorseArmor(a_Item); break;
default:
{
LOGWARN("cSlotAreaHorse::SetSlot: Invalid slot number %d", a_SlotNum);
}
}
}
void cSlotAreaHorse::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots, bool a_BackFill)
{
if (ItemCategory::IsHorseArmor(a_ItemStack.m_ItemType) && m_Horse.GetHorseArmorItem().IsEmpty())
{
if (a_ShouldApply)
{
m_Horse.SetHorseArmor(a_ItemStack.CopyOne());
}
--a_ItemStack.m_ItemCount;
}
else if ((a_ItemStack.m_ItemType == E_ITEM_SADDLE) && !m_Horse.IsSaddled())
{
if (a_ShouldApply)
{
m_Horse.SetHorseSaddle(a_ItemStack.CopyOne());
}
--a_ItemStack.m_ItemCount;
}
}