diff --git a/source/Defines.h b/source/Defines.h index 8b3c402c8..b76da2f29 100644 --- a/source/Defines.h +++ b/source/Defines.h @@ -1,10 +1,16 @@ #pragma once -#include "BlockID.h" + + + typedef unsigned char Byte; +/// List of slot numbers, used for inventory-painting +typedef std::vector cSlotNums; + + diff --git a/source/Player.cpp b/source/Player.cpp index 776bf3e3f..d659c949a 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -325,6 +325,36 @@ void cPlayer::SendHealth() +void cPlayer::ClearInventoryPaintSlots(void) +{ + // Clear the list of slots that are being inventory-painted. Used by cWindow only + m_InventoryPaintSlots.clear(); +} + + + + + +void cPlayer::AddInventoryPaintSlot(int a_SlotNum) +{ + // Add a slot to the list for inventory painting. Used by cWindow only + m_InventoryPaintSlots.push_back(a_SlotNum); +} + + + + + +const cSlotNums & cPlayer::GetInventoryPaintSlots(void) const +{ + // Return the list of slots currently stored for inventory painting. Used by cWindow only + return m_InventoryPaintSlots; +} + + + + + void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { if (m_GameMode == eGameMode_Creative) diff --git a/source/Player.h b/source/Player.h index 378c5d782..82a932cd0 100644 --- a/source/Player.h +++ b/source/Player.h @@ -31,6 +31,7 @@ public: // tolua_end CLASS_PROTODEF(cPlayer) + cPlayer(cClientHandle * a_Client, const AString & a_PlayerName); virtual ~cPlayer(); @@ -144,15 +145,23 @@ public: void UseEquippedItem(void); - void SendHealth(); + void SendHealth(void); // In UI windows, the item that the player is dragging: bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); } cItem & GetDraggingItem(void) {return m_DraggingItem; } + + // In UI windows, when inventory-painting: + /// Clears the list of slots that are being inventory-painted. To be used by cWindow only + void ClearInventoryPaintSlots(void); + + /// Adds a slot to the list for inventory painting. To be used by cWindow only + void AddInventoryPaintSlot(int a_SlotNum); + + /// Returns the list of slots currently stored for inventory painting. To be used by cWindow only + const cSlotNums & GetInventoryPaintSlots(void) const; protected: - virtual void Destroyed(); - typedef std::map< std::string, bool > PermissionMap; PermissionMap m_ResolvedPermissions; PermissionMap m_Permissions; @@ -197,7 +206,12 @@ protected: long long m_LastPlayerListTime; static const unsigned short PLAYER_LIST_TIME_MS = 1000; // 1000 = once per second - cClientHandle* m_ClientHandle; + cClientHandle * m_ClientHandle; + + cSlotNums m_InventoryPaintSlots; + + + virtual void Destroyed(void); /// Filters out damage for creative mode virtual void DoTakeDamage(TakeDamageInfo & TDI) override; diff --git a/source/UI/SlotArea.cpp b/source/UI/SlotArea.cpp index 1886c515c..0d26edbb7 100644 --- a/source/UI/SlotArea.cpp +++ b/source/UI/SlotArea.cpp @@ -232,7 +232,7 @@ cSlotAreaChest::cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow) -const cItem * cSlotAreaChest::GetSlot(int a_SlotNum, cPlayer & a_Player) +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)); @@ -265,7 +265,7 @@ cSlotAreaDoubleChest::cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEnti -const cItem * cSlotAreaDoubleChest::GetSlot(int a_SlotNum, cPlayer & a_Player) +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) @@ -486,7 +486,7 @@ cSlotAreaDropSpenser::cSlotAreaDropSpenser(cDropSpenserEntity * a_DropSpenser, c -const cItem * cSlotAreaDropSpenser::GetSlot(int a_SlotNum, cPlayer & a_Player) +const cItem * cSlotAreaDropSpenser::GetSlot(int a_SlotNum, cPlayer & a_Player) const { return &(m_DropSpenser->GetSlot(a_SlotNum)); } @@ -545,7 +545,7 @@ void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a -const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & a_Player) +const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & a_Player) const { // a_SlotNum ranges from 0 to 2, query the items from the underlying furnace: return m_Furnace->GetSlot(a_SlotNum); @@ -595,7 +595,7 @@ void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAc -const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) +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); @@ -669,9 +669,9 @@ cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) -const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) +const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) const { - cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + 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); diff --git a/source/UI/SlotArea.h b/source/UI/SlotArea.h index 8358f5316..cdf14baad 100644 --- a/source/UI/SlotArea.h +++ b/source/UI/SlotArea.h @@ -32,7 +32,7 @@ public: int GetNumSlots(void) const { return m_NumSlots; } /// Called to retrieve an item in the specified slot for the specified player. Must return a valid cItem. - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) = 0; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const = 0; /// Called to set an item in the specified slot for the specified player virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) = 0; @@ -78,7 +78,7 @@ public: // Creative inventory's click handling is somewhat different from survival inventory's, handle that here: virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; protected: @@ -154,7 +154,7 @@ public: cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow); // cSlotArea overrides: - virtual const cItem * GetSlot (int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot (int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot (int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; virtual void OnPlayerAdded (cPlayer & a_Player) override; virtual void OnPlayerRemoved(cPlayer & a_Player) override; @@ -221,7 +221,7 @@ class cSlotAreaChest : public: cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow); - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; protected: @@ -238,7 +238,7 @@ class cSlotAreaDoubleChest : public: cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEntity * a_BottomChest, cWindow & a_ParentWindow); - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; protected: @@ -258,7 +258,7 @@ class cSlotAreaDropSpenser : public: cSlotAreaDropSpenser(cDropSpenserEntity * a_DropSpenser, cWindow & a_ParentWindow); - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; protected: @@ -278,7 +278,7 @@ public: cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow); virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; - virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) const override; virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; protected: diff --git a/source/UI/Window.cpp b/source/UI/Window.cpp index c2ce4127e..57a565a05 100644 --- a/source/UI/Window.cpp +++ b/source/UI/Window.cpp @@ -22,13 +22,13 @@ char cWindow::m_WindowIDCounter = 1; -cWindow::cWindow(cWindow::WindowType a_WindowType, const AString & a_WindowTitle) - : m_WindowID(1 + (m_WindowIDCounter++ % 127)) - , m_WindowType(a_WindowType) - , m_WindowTitle(a_WindowTitle) - , m_Owner(NULL) - , m_IsDestroyed(false) - , m_ShouldDistributeToHotbarFirst(true) +cWindow::cWindow(cWindow::WindowType a_WindowType, const AString & a_WindowTitle) : + m_WindowID((++m_WindowIDCounter) % 127), + m_WindowType(a_WindowType), + m_WindowTitle(a_WindowTitle), + m_Owner(NULL), + m_IsDestroyed(false), + m_ShouldDistributeToHotbarFirst(true) { if (a_WindowType == Inventory) { @@ -67,6 +67,40 @@ int cWindow::GetNumSlots(void) const +const cItem * cWindow::GetSlot(cPlayer & a_Player, int a_SlotNum) const +{ + // Return the item at the specified slot for the specified player + int LocalSlotNum = 0; + const cSlotArea * Area = GetSlotArea(a_SlotNum, LocalSlotNum); + if (Area == NULL) + { + LOGWARNING("%s: requesting item from an invalid SlotArea (SlotNum %d), returning NULL.", __FUNCTION__, a_SlotNum); + return NULL; + } + return Area->GetSlot(LocalSlotNum, a_Player); +} + + + + + +void cWindow::SetSlot(cPlayer & a_Player, int a_SlotNum, const cItem & a_Item) +{ + // Set the item to the specified slot for the specified player + int LocalSlotNum = 0; + cSlotArea * Area = GetSlotArea(a_SlotNum, LocalSlotNum); + if (Area == NULL) + { + LOGWARNING("%s: requesting write to an invalid SlotArea (SlotNum %d), ignoring.", __FUNCTION__, a_SlotNum); + return; + } + Area->SetSlot(LocalSlotNum, a_Player, a_Item); +} + + + + + void cWindow::GetSlots(cPlayer & a_Player, cItems & a_Slots) const { a_Slots.clear(); @@ -102,7 +136,7 @@ void cWindow::Clicked( { if (a_WindowID != m_WindowID) { - LOG("WRONG WINDOW ID! (exp %d, got %d) received from \"%s\"", m_WindowID, a_WindowID, a_Player.GetName().c_str()); + LOGWARNING("%s: Wrong window ID (exp %d, got %d) received from \"%s\"; ignoring click.", __FUNCTION__, m_WindowID, a_WindowID, a_Player.GetName().c_str()); return; } @@ -126,6 +160,12 @@ void cWindow::Clicked( // Nothing needed return; } + case caLeftPaintBegin: OnPaintBegin (a_Player); return; + case caRightPaintBegin: OnPaintBegin (a_Player); return; + case caLeftPaintProgress: OnPaintProgress(a_Player, a_SlotNum); return; + case caRightPaintProgress: OnPaintProgress(a_Player, a_SlotNum); return; + case caLeftPaintEnd: OnLeftPaintEnd (a_Player); return; + case caRightPaintEnd: OnRightPaintEnd(a_Player); return; } if (a_SlotNum < 0) @@ -353,6 +393,159 @@ void cWindow::Destroy(void) +cSlotArea * cWindow::GetSlotArea(int a_GlobalSlotNum, int & a_LocalSlotNum) +{ + if ((a_GlobalSlotNum < 0) || (a_GlobalSlotNum >= GetNumSlots())) + { + LOGWARNING("%s: requesting an invalid SlotNum: %d out of %d slots", __FUNCTION__, a_GlobalSlotNum, GetNumSlots() - 1); + ASSERT(!"Invalid SlotNum"); + return NULL; + } + + // Iterate through all the SlotAreas, find the correct one + int LocalSlotNum = a_GlobalSlotNum; + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (LocalSlotNum < (*itr)->GetNumSlots()) + { + a_LocalSlotNum = LocalSlotNum; + return *itr; + } + LocalSlotNum -= (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + + // We shouldn't be here - the check at the beginnning should prevent this. Log and assert + LOGWARNING("%s: GetNumSlots() is out of sync: %d; LocalSlotNum = %d", __FUNCTION__, GetNumSlots(), LocalSlotNum); + ASSERT(!"Invalid GetNumSlots"); + return NULL; +} + + + + + +const cSlotArea * cWindow::GetSlotArea(int a_GlobalSlotNum, int & a_LocalSlotNum) const +{ + if ((a_GlobalSlotNum < 0) || (a_GlobalSlotNum >= GetNumSlots())) + { + LOGWARNING("%s: requesting an invalid SlotNum: %d out of %d slots", __FUNCTION__, a_GlobalSlotNum, GetNumSlots() - 1); + ASSERT(!"Invalid SlotNum"); + return NULL; + } + + // Iterate through all the SlotAreas, find the correct one + int LocalSlotNum = a_GlobalSlotNum; + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (LocalSlotNum < (*itr)->GetNumSlots()) + { + a_LocalSlotNum = LocalSlotNum; + return *itr; + } + LocalSlotNum -= (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + + // We shouldn't be here - the check at the beginnning should prevent this. Log and assert + LOGWARNING("%s: GetNumSlots() is out of sync: %d; LocalSlotNum = %d", __FUNCTION__, GetNumSlots(), LocalSlotNum); + ASSERT(!"Invalid GetNumSlots"); + return NULL; +} + + + + + +void cWindow::OnPaintBegin(cPlayer & a_Player) +{ + // Prepares the internal structures for inventory painting from the specified player + a_Player.ClearInventoryPaintSlots(); +} + + + + + +void cWindow::OnPaintProgress(cPlayer & a_Player, int a_SlotNum) +{ + // Add the slot to the internal structures for inventory painting by the specified player + a_Player.AddInventoryPaintSlot(a_SlotNum); +} + + + + + +void cWindow::OnLeftPaintEnd(cPlayer & a_Player) +{ + // Process the entire action stored in the internal structures for inventory painting + // distribute as many items as possible + + const cSlotNums & SlotNums = a_Player.GetInventoryPaintSlots(); + cItem ToDistribute(a_Player.GetDraggingItem()); + + if ((size_t)(ToDistribute.m_ItemCount) < SlotNums.size()) + { + LOGWARNING("%s: Distributing less items (%d) than slots (%u)", __FUNCTION__, (int)ToDistribute.m_ItemCount, SlotNums.size()); + // This doesn't seem to happen with the 1.5.1 client, so we don't worry about it for now + } + + // Distribute to individual slots, keep track of how many items were actually distributed (full stacks etc.) + int NumDistributed = 0; + int ToEachSlot = (int)ToDistribute.m_ItemCount / SlotNums.size(); + for (cSlotNums::const_iterator itr = SlotNums.begin(), end = SlotNums.end(); itr != end; ++itr) + { + int LocalSlotNum = 0; + cSlotArea * Area = GetSlotArea(*itr, LocalSlotNum); + if (Area == NULL) + { + LOGWARNING("%s: Bad SlotArea for slot %d", __FUNCTION__, *itr); + continue; + } + + // Modify the item at the slot + cItem AtSlot(*Area->GetSlot(LocalSlotNum, a_Player)); + int MaxStack = ItemHandler(AtSlot.m_ItemType)->GetMaxStackSize(); + if (AtSlot.IsEmpty()) + { + // Empty, just move all of it there: + cItem ToStore(ToDistribute); + ToStore.m_ItemCount = std::min(ToEachSlot, (int)MaxStack); + Area->SetSlot(LocalSlotNum, a_Player, ToStore); + NumDistributed += ToStore.m_ItemCount; + } + else + { + int CanStore = std::min(ToEachSlot, (int)MaxStack - AtSlot.m_ItemCount); + AtSlot.m_ItemCount += CanStore; + Area->SetSlot(LocalSlotNum, a_Player, AtSlot); + NumDistributed += CanStore; + } + } // for itr - SlotNums[] + + // Remove the items distributed from the dragging item: + a_Player.GetDraggingItem().m_ItemCount -= NumDistributed; + if (a_Player.GetDraggingItem().m_ItemCount == 0) + { + a_Player.GetDraggingItem().Empty(); + } + + SendWholeWindow(*a_Player.GetClientHandle()); +} + + + + + +void cWindow::OnRightPaintEnd(cPlayer & a_Player) +{ + // Process the entire action stored in the internal structures for inventory painting + // distribute one item into each slot +} + + + + + void cWindow::SendWholeWindow(cClientHandle & a_Client) { a_Client.SendWholeInventory(*this); diff --git a/source/UI/Window.h b/source/UI/Window.h index 86ea1849b..ae06ecf6a 100644 --- a/source/UI/Window.h +++ b/source/UI/Window.h @@ -9,7 +9,7 @@ #pragma once -#include "../Item.h" +#include "../ItemGrid.h" @@ -31,6 +31,8 @@ typedef std::vector cSlotAreas; +// tolua_begin + /** Represents a UI window. @@ -39,6 +41,7 @@ When there's no player using a window, it is destroyed. A window consists of several areas of slots with similar functionality - for example the crafting grid area, or the inventory area. Each area knows what its slots are (GetSlot() function) and can handle mouse clicks. The window acts only as a top-level container for those areas, redirecting the click events to the correct areas. +Inventory painting, introduced in 1.5, is handled by the window, too */ class cWindow { @@ -58,22 +61,35 @@ public: Hopper = 9, }; + // tolua_end + static const int c_NumInventorySlots = 36; cWindow(WindowType a_WindowType, const AString & a_WindowTitle); virtual ~cWindow(); char GetWindowID(void) const { return m_WindowID; } - int GetWindowType(void) const { return m_WindowType; } + int GetWindowType(void) const { return m_WindowType; } // tolua_export cWindowOwner * GetOwner(void) { return m_Owner; } void SetOwner( cWindowOwner * a_Owner ) { m_Owner = a_Owner; } int GetNumSlots(void) const; + // tolua_begin + + /// Returns the item at the specified slot for the specified player. Returns NULL if invalid SlotNum requested + const cItem * GetSlot(cPlayer & a_Player, int a_SlotNum) const; + + /// Sets the item to the specified slot for the specified player + void SetSlot(cPlayer & a_Player, int a_SlotNum, const cItem & a_Item); + + // tolua_end + /// Fills a_Slots with the slots read from m_SlotAreas[], for the specified player void GetSlots(cPlayer & a_Player, cItems & a_Slots) const; + /// Handles a click event from a player void Clicked( cPlayer & a_Player, int a_WindowID, short a_SlotNum, eClickAction a_ClickAction, @@ -87,8 +103,12 @@ public: void BroadcastWholeWindow(void); void BroadcastInventoryProgress(short a_Progressbar, short a_Value); + // tolua_begin + const AString & GetWindowTitle() const { return m_WindowTitle; } void SetWindowTitle(const AString & a_WindowTitle ) { m_WindowTitle = a_WindowTitle; } + + // tolua_end void OwnerDestroyed(void); @@ -125,6 +145,31 @@ protected: static char m_WindowIDCounter; void Destroy(void); + + /** Returns the correct slot area for the specified window-global SlotNum + Also returns the area-local SlotNum corresponding to the GlobalSlotNum + If the global SlotNum is out of range, returns NULL + */ + cSlotArea * GetSlotArea(int a_GlobalSlotNum, int & a_LocalSlotNum); + + /** Returns the correct slot area for the specified window-global SlotNum + Also returns the area-local SlotNum corresponding to the GlobalSlotNum + If the global SlotNum is out of range, returns NULL. + Const version. + */ + const cSlotArea * GetSlotArea(int a_GlobalSlotNum, int & a_LocalSlotNum) const; + + /// Prepares the internal structures for inventory painting from the specified player + void OnPaintBegin(cPlayer & a_Player); + + /// Adds the slot to the internal structures for inventory painting by the specified player + void OnPaintProgress(cPlayer & a_Player, int a_SlotNum); + + /// Processes the entire action stored in the internal structures for inventory painting; distributes as many items as possible + void OnLeftPaintEnd(cPlayer & a_Player); + + /// Processes the entire action stored in the internal structures for inventory painting; distributes one item into each slot + void OnRightPaintEnd(cPlayer & a_Player); } ; @@ -196,3 +241,24 @@ protected: + +// tolua_begin + +/// A window that has been created by a Lua plugin and is handled entirely by that plugin +class cLuaWindow : + public cWindow +{ +public: + /// Create a window of the specified type, with a slot grid of a_SlotsX * a_SlotsY size + cLuaWindow(cWindow::WindowType a_WindowType, int a_SlotsX, int a_SlotsY, const AString & a_Title); + + // tolua_end + +protected: + /// Contents of the non-inventory part + cItemGrid m_Contents; +} ; + + + +