From fdf83870925d30560ec7f853fdc19ec6c9e29155 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Thu, 20 Sep 2012 20:10:46 +0000 Subject: UI: Added shift-click support to most slot areas, except crafting. Also fixed survival inventory's crafting grid not working. git-svn-id: http://mc-server.googlecode.com/svn/trunk@868 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/Protocol132.cpp | 3 +- source/UI/SlotArea.cpp | 131 +++++++++++++++++++++++++++++++++++++++---------- source/UI/SlotArea.h | 88 +++++++++++++++++++++++++++------ source/UI/cWindow.cpp | 44 ++++++++++++++++- source/UI/cWindow.h | 6 +++ source/cPlayer.cpp | 3 ++ 6 files changed, 232 insertions(+), 43 deletions(-) diff --git a/source/Protocol132.cpp b/source/Protocol132.cpp index 37343e443..207266d99 100644 --- a/source/Protocol132.cpp +++ b/source/Protocol132.cpp @@ -572,12 +572,13 @@ void cProtocol132::WriteItem(const cItem & a_Item) ItemType = -1; } - WriteShort(ItemType); if (a_Item.IsEmpty()) { + WriteShort(-1); return; } + WriteShort(ItemType); WriteByte (a_Item.m_ItemCount); WriteShort(a_Item.m_ItemDamage); diff --git a/source/UI/SlotArea.cpp b/source/UI/SlotArea.cpp index 744492bff..60929be2f 100644 --- a/source/UI/SlotArea.cpp +++ b/source/UI/SlotArea.cpp @@ -48,6 +48,17 @@ void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, return; } + if (a_IsShiftPressed) + { + if (!a_Player.IsDraggingItem()) + { + ShiftClicked(a_Player, a_SlotNum, a_ClickedItem); + return; + } + LOGD("Shift clicked, but the player is draggint an item: %s", ItemToFullString(a_Player.GetDraggingItem())); + return; + } + cItem Slot(*GetSlot(a_SlotNum, a_Player)); if (!Slot.IsEqual(a_ClickedItem)) { @@ -138,31 +149,57 @@ void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cSlotAreaArmor: - -cSlotAreaArmor::cSlotAreaArmor(cWindow & a_ParentWindow) : - cSlotArea(4, a_ParentWindow) +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_Player, this, true); + if (Slot.IsEmpty()) + { + // Empty the slot completely, the cilent doesn't like left-over ItemType with zero count + Slot.Empty(); + } + SetSlot(a_SlotNum, a_Player, Slot); + + // Some clients try to guess our actions and not always right (armor slots in 1.2.5), so we fix them: + m_ParentWindow.BroadcastWholeWindow(); } -const cItem * cSlotAreaArmor::GetSlot(int a_SlotNum, cPlayer & a_Player) -{ - // a_SlotNum ranges from 0 to 3, map that to the armor slots in player's inventory, 5 to 8: - return a_Player.GetInventory().GetSlot(a_SlotNum + 5); -} - - - - - -void cSlotAreaArmor::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots) { - *(a_Player.GetInventory().GetSlot(a_SlotNum + 5)) = a_Item; + for (int i = 0; i < m_NumSlots; i++) + { + const cItem * Slot = GetSlot(i, a_Player); + if (!Slot->IsSameType(a_ItemStack) && (!Slot->IsEmpty() || a_KeepEmptySlots)) + { + // Different items + continue; + } + int NumFit = ItemHandler(Slot->m_ItemType)->GetMaxStackSize() - Slot->m_ItemCount; + if (NumFit <= 0) + { + // Full stack already + continue; + } + if (NumFit > a_ItemStack.m_ItemCount) + { + NumFit = a_ItemStack.m_ItemCount; + } + if (a_Apply) + { + cItem NewSlot(a_ItemStack.m_ItemType, Slot->m_ItemCount + NumFit, a_ItemStack.m_ItemDamage); + SetSlot(i, a_Player, NewSlot); + } + a_ItemStack.m_ItemCount -= NumFit; + if (a_ItemStack.IsEmpty()) + { + return; + } + } // for i - Slots } @@ -390,10 +427,11 @@ void cSlotAreaFurnace::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cSlotAreaInventory: +// cSlotAreaInventoryBase: -cSlotAreaInventory::cSlotAreaInventory(cWindow & a_ParentWindow) : - cSlotArea(27 + 9, a_ParentWindow) // 27 internal slots, 9 hotbar slots +cSlotAreaInventoryBase::cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow) : + cSlotArea(a_NumSlots, a_ParentWindow), + m_SlotOffset(a_SlotOffset) { } @@ -401,7 +439,7 @@ cSlotAreaInventory::cSlotAreaInventory(cWindow & a_ParentWindow) : -void cSlotAreaInventory::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) { if ((a_Player.GetGameMode() == eGameMode_Creative) && (m_ParentWindow.GetWindowType() == cWindow::Inventory)) { @@ -419,19 +457,62 @@ void cSlotAreaInventory::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRig -const cItem * cSlotAreaInventory::GetSlot(int a_SlotNum, cPlayer & a_Player) +const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) { // a_SlotNum ranges from 0 to 35, map that to the player's inventory slots 9 to 44 - return a_Player.GetInventory().GetSlot(a_SlotNum + 9); + return a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset); } -void cSlotAreaInventory::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +void cSlotAreaInventoryBase::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) { - *(a_Player.GetInventory().GetSlot(a_SlotNum + 9)) = a_Item; + *(a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset)) = a_Item; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaArmor: + +void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +{ + if (ItemCategory::IsHelmet(a_ItemStack.m_ItemType) && GetSlot(0, a_Player)->IsEmpty()) + { + if (a_ShouldApply) + { + SetSlot(0, a_Player, cItem(a_ItemStack.m_ItemType, 1, a_ItemStack.m_ItemDamage)); + } + 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, cItem(a_ItemStack.m_ItemType, 1, a_ItemStack.m_ItemDamage)); + } + 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, cItem(a_ItemStack.m_ItemType, 1, a_ItemStack.m_ItemDamage)); + } + 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, cItem(a_ItemStack.m_ItemType, 1, a_ItemStack.m_ItemDamage)); + } + a_ItemStack.m_ItemCount -= 1; + } } diff --git a/source/UI/SlotArea.h b/source/UI/SlotArea.h index 0e620cd54..0f3182036 100644 --- a/source/UI/SlotArea.h +++ b/source/UI/SlotArea.h @@ -38,12 +38,23 @@ public: /// Called when a player clicks in the window. Parameters taken from the click packet. virtual void Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem); + /// Called from Clicked if it is a valid shiftclick + virtual void ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem); + /// Called when a new player opens the same parent window. The window already tracks the player. CS-locked. virtual void OnPlayerAdded(cPlayer & a_Player) {} ; /// Called when one of the players closes the parent window. The window already doesn't track the player. CS-locked. virtual void OnPlayerRemoved(cPlayer & a_Player) {} ; + /** Called to store as much of a_ItemStack in the area as possible. a_ItemStack is modified to reflect the change. + The default implementation searches each slot for available space and distributes the stack there. + if a_ShouldApply is true, the changes are written into the slots; + if a_ShouldApply is false, only a_ItemStack is modified to reflect the number of fits (for fit-testing purposes) + If a_KeepEmptySlots is true, empty slots will be skipped and won't be filled + */ + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots); + protected: int m_NumSlots; cWindow & m_ParentWindow; @@ -53,19 +64,75 @@ protected: -class cSlotAreaInventory : +/// Handles any part of the inventory, using parameters in constructor to distinguish between the parts +class cSlotAreaInventoryBase : public cSlotArea { typedef cSlotArea super; public: - cSlotAreaInventory(cWindow & a_ParentWindow); + cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow); // Creative inventory's click handling is somewhat different from survival inventory's, handle that here: virtual void Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) override; virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + +protected: + int m_SlotOffset; // Index that this area's slot 0 has in the underlying cInventory +} ; + + + + + +/// Handles the "inner" inventory of each player, excluding the armor and hotbar +class cSlotAreaInventory : + public cSlotAreaInventoryBase +{ + typedef cSlotAreaInventoryBase super; + +public: + cSlotAreaInventory(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(27, 9, a_ParentWindow) // 27 slots, starting at inventory index 9 + { + } +} ; + + + + + +/// Handles the "outer" inevntory of each player - the hotbar +class cSlotAreaHotBar : + public cSlotAreaInventoryBase +{ + typedef cSlotAreaInventoryBase super; + +public: + cSlotAreaHotBar(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(9, 36, a_ParentWindow) + { + } +} ; + + + + + +/// Handles the armor area of the inventory +class cSlotAreaArmor : + public cSlotAreaInventoryBase +{ +public: + cSlotAreaArmor(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(4, 5, a_ParentWindow) + { + } + + // Distributing the stack is allowed only for compatible items (helmets into helmet slot etc.) + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override; } ; @@ -119,6 +186,9 @@ public: virtual void Clicked (cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) override; virtual void OnPlayerRemoved(cPlayer & a_Player) override; + // Distributing items into this area is completely disabled + virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override {} + protected: /// Maps player's EntityID -> current recipe; not a std::map because cCraftingGrid needs proper constructor params typedef std::list > cRecipeMap; @@ -176,17 +246,3 @@ protected: - -class cSlotAreaArmor : - public cSlotArea -{ -public: - cSlotAreaArmor(cWindow & a_ParentWindow); - - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; - virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; -} ; - - - - diff --git a/source/UI/cWindow.cpp b/source/UI/cWindow.cpp index 475670425..71d2a1200 100644 --- a/source/UI/cWindow.cpp +++ b/source/UI/cWindow.cpp @@ -256,7 +256,45 @@ bool cWindow::ForEachClient(cItemCallback & a_Callback) -void cWindow::Destroy() +void cWindow::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, cSlotArea * a_ExcludeArea, bool a_ShouldApply) +{ + // Ask each slot area to take as much of the stack as it can. + // First ask only slots that already have the same kind of item + // Then ask any remaining slots + for (int Pass = 0; Pass < 2; ++Pass) + { + // First distribute into the hotbar: + if (a_ExcludeArea != m_SlotAreas.back()) + { + m_SlotAreas.back()->DistributeStack(a_ItemStack, a_Player, a_ShouldApply, (Pass == 0)); + if (a_ItemStack.IsEmpty()) + { + // Distributed it all + return; + } + } + // The distribute to all other areas: + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end() - 1; itr != end; ++itr) + { + if (*itr == a_ExcludeArea) + { + continue; + } + (*itr)->DistributeStack(a_ItemStack, a_Player, a_ShouldApply, (Pass == 0)); + if (a_ItemStack.IsEmpty()) + { + // Distributed it all + return; + } + } // for itr - m_SlotAreas[] + } // for Pass - repeat twice +} + + + + + +void cWindow::Destroy(void) { LOGD("Destroying window %p (type %d)", this, m_WindowType); if (m_Owner != NULL) @@ -316,6 +354,7 @@ cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : m_SlotAreas.push_back(new cSlotAreaCrafting(2, *this)); // The creative inventory doesn't display it, but it's still counted into slot numbers m_SlotAreas.push_back(new cSlotAreaArmor(*this)); m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); } @@ -330,6 +369,7 @@ cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : { m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); } @@ -351,6 +391,7 @@ cChestWindow::cChestWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cChestEntit // TODO: Double chests m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); // Send out the chest-open packet: m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_CHEST); @@ -378,6 +419,7 @@ cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnac { m_SlotAreas.push_back(new cSlotAreaFurnace(a_Furnace, *this)); m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); } diff --git a/source/UI/cWindow.h b/source/UI/cWindow.h index 231e4fdc0..980a1e711 100644 --- a/source/UI/cWindow.h +++ b/source/UI/cWindow.h @@ -93,6 +93,12 @@ public: /// Calls the callback safely for each client that has this window open; returns true if all clients have been enumerated bool ForEachClient(cItemCallback & a_Callback); + /** Called on shift-clicking to distribute the stack into other areas; Modifies a_ItemStack as it is distributed! + if a_ShouldApply is true, the changes are written into the slots; + if a_ShouldApply is false, only a_ItemStack is modified to reflect the number of fits (for fit-testing purposes) + */ + void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, cSlotArea * a_ExcludeArea, bool a_ShouldApply); + protected: cSlotAreas m_SlotAreas; diff --git a/source/cPlayer.cpp b/source/cPlayer.cpp index 1d2774dbf..e0d1eef6e 100644 --- a/source/cPlayer.cpp +++ b/source/cPlayer.cpp @@ -64,6 +64,7 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) m_InventoryWindow = new cInventoryWindow(*this); m_CurrentWindow = m_InventoryWindow; + m_InventoryWindow->OpenedByPlayer(*this); SetMaxHealth(20); m_MaxFoodLevel = 20; @@ -110,6 +111,8 @@ cPlayer::~cPlayer(void) m_ClientHandle = NULL; + delete m_InventoryWindow; + LOG("Player %p deleted", this); } -- cgit v1.2.3