From bc466f07a454271d4845a7e8c7f0822541c5afbd Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Thu, 20 Sep 2012 13:25:54 +0000 Subject: Refactored windows. As described on the forum: http://forum.mc-server.org/showthread.php?tid=561 For now, only basic clicking works; shift-click not implemented yet. git-svn-id: http://mc-server.googlecode.com/svn/trunk@867 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/UI/SlotArea.cpp | 569 +++++++++++++++++++++++++++++++++++++++++++++++ source/UI/SlotArea.h | 192 ++++++++++++++++ source/UI/cWindow.cpp | 385 ++++++++++++++++++++++++++++++++ source/UI/cWindow.h | 172 ++++++++++++++ source/UI/cWindowOwner.h | 125 +++++++++++ 5 files changed, 1443 insertions(+) create mode 100644 source/UI/SlotArea.cpp create mode 100644 source/UI/SlotArea.h create mode 100644 source/UI/cWindow.cpp create mode 100644 source/UI/cWindow.h create mode 100644 source/UI/cWindowOwner.h (limited to 'source/UI') diff --git a/source/UI/SlotArea.cpp b/source/UI/SlotArea.cpp new file mode 100644 index 000000000..744492bff --- /dev/null +++ b/source/UI/SlotArea.cpp @@ -0,0 +1,569 @@ + +// SlotArea.cpp + +// Implements the cSlotArea class and its descendants + +#include "Globals.h" +#include "SlotArea.h" +#include "../cPlayer.h" +#include "../cChestEntity.h" +#include "../cFurnaceEntity.h" +#include "../Items/Item.h" +#include "cWindow.h" +#include "../CraftingRecipes.h" +#include "../cRoot.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotArea: + +cSlotArea::cSlotArea(int a_NumSlots, cWindow & a_ParentWindow) : + m_NumSlots(a_NumSlots), + m_ParentWindow(a_ParentWindow) +{ + LOGD("Created a new cSlotArea with %d slots", a_NumSlots); +} + + + + + +void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + 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() + ); + + ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots())); + + bool bAsync = false; + if (GetSlot(a_SlotNum, a_Player) == NULL) + { + LOGWARNING("GetSlot(%d) returned NULL! Ignoring click", a_SlotNum); + return; + } + + cItem Slot(*GetSlot(a_SlotNum, a_Player)); + if (!Slot.IsEqual(a_ClickedItem)) + { + LOGD("*** Window lost sync ***"); + LOGD("My Type: %i Their Type: %i", Slot.m_ItemID, a_ClickedItem.m_ItemID); + LOGD("My Count: %i Their Count: %i", Slot.m_ItemCount, a_ClickedItem.m_ItemCount); + LOGD("My Dmg: %i Their Dmg: %i", Slot.m_ItemHealth, a_ClickedItem.m_ItemHealth); + bAsync = true; + } + cItem & DraggingItem = a_Player.GetDraggingItem(); + if (a_IsRightClick) + { + // Right clicked + if (DraggingItem.m_ItemID <= 0) // Empty-handed? + { + DraggingItem.m_ItemCount = (char)(((float)Slot.m_ItemCount) / 2.f + 0.5f); + Slot.m_ItemCount -= DraggingItem.m_ItemCount; + DraggingItem.m_ItemID = Slot.m_ItemID; + DraggingItem.m_ItemHealth = Slot.m_ItemHealth; + + if (Slot.m_ItemCount <= 0) + { + Slot.Empty(); + } + } + else if ((Slot.m_ItemID <= 0) || DraggingItem.IsEqual(Slot)) + { + // Drop one item in slot + cItemHandler * Handler = ItemHandler(Slot.m_ItemID); + if ((DraggingItem.m_ItemCount > 0) && (Slot.m_ItemCount < Handler->GetMaxStackSize())) + { + Slot.m_ItemID = DraggingItem.m_ItemID; + Slot.m_ItemCount++; + Slot.m_ItemHealth = DraggingItem.m_ItemHealth; + DraggingItem.m_ItemCount--; + } + if (DraggingItem.m_ItemCount <= 0) + { + DraggingItem.Empty(); + } + } + else if (!DraggingItem.IsEqual(Slot)) + { + // Swap contents + cItem tmp(DraggingItem); + DraggingItem = Slot; + Slot = tmp; + } + } + else + { + // 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_ItemID); + 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; + } + int Filling = (FreeSlots > DraggingItem.m_ItemCount) ? DraggingItem.m_ItemCount : FreeSlots; + Slot.m_ItemCount += (char)Filling; + DraggingItem.m_ItemCount -= (char)Filling; + if (DraggingItem.m_ItemCount <= 0) + { + DraggingItem.Empty(); + } + } + } + + if (bAsync) + { + m_ParentWindow.BroadcastWholeWindow(); + } + SetSlot(a_SlotNum, a_Player, Slot); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaArmor: + +cSlotAreaArmor::cSlotAreaArmor(cWindow & a_ParentWindow) : + cSlotArea(4, a_ParentWindow) +{ +} + + + + + +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) +{ + *(a_Player.GetInventory().GetSlot(a_SlotNum + 5)) = a_Item; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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) +{ + // 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); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + // Override for craft result slot + if (a_SlotNum == 0) + { + ClickedResult(a_Player, a_IsShiftPressed); + return; + } + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + UpdateRecipe(a_Player); +} + + + + + +void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player) +{ + // Toss all items on the crafting grid: + TossItems(a_Player, 1, m_NumSlots); + + // 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::ClickedResult(cPlayer & a_Player, bool a_IsShiftPressed) +{ + const cItem * ResultSlot = GetSlot(0, a_Player); + LOGD("Clicked in craft result slot, item there: %d:%d (%d times)", + ResultSlot->m_ItemID, ResultSlot->m_ItemHealth, ResultSlot->m_ItemCount + ); + cItem & DraggingItem = a_Player.GetDraggingItem(); + + // Get the current recipe: + cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); + + cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1; + cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize); + + // If possible, craft: + if (DraggingItem.IsEmpty()) + { + DraggingItem = Recipe.GetResult(); + Recipe.ConsumeIngredients(Grid); + Grid.CopyToItems(PlayerSlots); + } + else if (DraggingItem.IsEqual(Recipe.GetResult())) + { + cItemHandler * Handler = ItemHandler(Recipe.GetResult().m_ItemID); + if (DraggingItem.m_ItemCount + Recipe.GetResult().m_ItemCount <= Handler->GetMaxStackSize()) + { + DraggingItem.m_ItemCount += Recipe.GetResult().m_ItemCount; + Recipe.ConsumeIngredients(Grid); + Grid.CopyToItems(PlayerSlots); + } + } + + // Get the new recipe and update the result slot: + UpdateRecipe(a_Player); + + // We're done. Send all changes to the client and bail out: + m_ParentWindow.BroadcastWholeWindow(); +} + + + + + +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); +} + + + + + +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[] + + // 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.push_back(std::make_pair(a_Player.GetUniqueID(), Recipe)); + return m_Recipes.back().second; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaFurnace: + +cSlotAreaFurnace::cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow) : + cSlotArea(3, a_ParentWindow), + m_Furnace(a_Furnace) +{ +} + + + + + +void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + cItem Fuel = *GetSlot(0, a_Player); + + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + + if (m_Furnace == NULL) + { + LOGERROR("cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + ASSERT(!"cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + return; + } + + if (Fuel.m_ItemID != GetSlot(0, a_Player)->m_ItemID) + { + m_Furnace->ResetCookTimer(); + } + + if (m_Furnace->StartCooking()) + { + m_ParentWindow.SendWholeWindow(*(a_Player.GetClientHandle())); + } +} + + + + + +const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & 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); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaInventory: + +cSlotAreaInventory::cSlotAreaInventory(cWindow & a_ParentWindow) : + cSlotArea(27 + 9, a_ParentWindow) // 27 internal slots, 9 hotbar slots +{ +} + + + + + +void cSlotAreaInventory::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)) + { + // Creative inventory must treat a_ClickedItem as a DraggedItem instead, replacing the inventory slot with it + SetSlot(a_SlotNum, a_Player, a_ClickedItem); + return; + } + + // Survival inventory and all other windows' inventory has the same handling as normal slot areas + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + return; +} + + + + + +const cItem * cSlotAreaInventory::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); +} + + + + + +void cSlotAreaInventory::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + *(a_Player.GetInventory().GetSlot(a_SlotNum + 9)) = a_Item; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaTemporary: + +cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) : + cSlotArea(a_NumSlots, a_ParentWindow) +{ +} + + + + + +const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + cItemMap::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!"); + + // Player not found, this should not happen, ever! Return NULL, but things may break by this. + return NULL; + } + + if (a_SlotNum >= (int)(itr->second.size())) + { + LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!"); + ASSERT(!"cSlotAreaTemporary: asking for more slots than actually stored!"); + return NULL; + } + + LOGD("cSlotAreaTemporary: getting slot %d as %s", a_SlotNum, ItemToFullString(itr->second[a_SlotNum]).c_str()); + + return &(itr->second[a_SlotNum]); +} + + + + + +void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + LOGD("cSlotAreaTemporary: setting slot %d to %s", a_SlotNum, ItemToFullString(a_Item).c_str()); + + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + // Player not found + LOGWARNING("cSlotAreaTemporary: player not found!"); + return; + } + + if (a_SlotNum >= (int)(itr->second.size())) + { + LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!"); + return; + } + + itr->second[a_SlotNum] = a_Item; +} + + + + + +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(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; + } + + cItems Drops; + for (int i = a_Begin; i < a_End; i++) + { + cItem & Item = itr->second[i]; + if (!Item.IsEmpty()) + { + Drops.push_back(Item); + } + Item.Empty(); + } // for i - itr->second[] + + float vX = 0, vY = 0, vZ = 0; + EulerToVector(-a_Player.GetRotation(), a_Player.GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + a_Player.GetWorld()->SpawnItemPickups(Drops, a_Player.GetPosX(), a_Player.GetPosY() + 1.6f, a_Player.GetPosZ(), vX * 2, vY * 2, vZ * 2); +} + + + + + +cItem * cSlotAreaTemporary::GetPlayerSlots(cPlayer & a_Player) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + return NULL; + } + return &(itr->second[0]); +} + + + + diff --git a/source/UI/SlotArea.h b/source/UI/SlotArea.h new file mode 100644 index 000000000..0e620cd54 --- /dev/null +++ b/source/UI/SlotArea.h @@ -0,0 +1,192 @@ + +// SlotArea.h + +// Interfaces to the cSlotArea class representing a contiguous area of slots in a UI window + + + + +#pragma once + +#include "../cItem.h" + + + +class cWindow; +class cPlayer; +class cChestEntity; +class cFurnaceEntity; +class cCraftingRecipe; + + + + + +class cSlotArea +{ +public: + cSlotArea(int a_NumSlots, cWindow & a_ParentWindow); + + 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; + + /// 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; + + /// 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 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) {} ; + +protected: + int m_NumSlots; + cWindow & m_ParentWindow; +} ; + + + + + +class cSlotAreaInventory : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaInventory(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; +} ; + + + + + +/** A cSlotArea with items layout that is private to each player and is temporary, such as +a crafting grid or an enchantment table. +This common ancestor stores the items in a per-player map. It also implements tossing items from the map. +*/ +class cSlotAreaTemporary : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow); + + // cSlotArea overrides: + 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; + virtual void OnPlayerAdded (cPlayer & a_Player) override; + virtual void OnPlayerRemoved(cPlayer & a_Player) override; + + /// Tosses the player's items in slots [a_Begin, a_End) (ie. incl. a_Begin, but excl. a_End) + void TossItems(cPlayer & a_Player, int a_Begin, int a_End); + +protected: + typedef std::map > cItemMap; // Maps EntityID -> items + + cItemMap m_Items; + + /// Returns the pointer to the slot array for the player specified. + cItem * GetPlayerSlots(cPlayer & a_Player); +} ; + + + + + +class cSlotAreaCrafting : + public cSlotAreaTemporary +{ + typedef cSlotAreaTemporary super; + +public: + /// a_GridSize is allowed to be only 2 or 3 + cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow); + + // cSlotAreaTemporary overrides: + 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; + +protected: + /// Maps player's EntityID -> current recipe; not a std::map because cCraftingGrid needs proper constructor params + typedef std::list > cRecipeMap; + + int m_GridSize; + cRecipeMap m_Recipes; + + /// Handles a click in the result slot. Crafts using the current recipe, if possible + void ClickedResult(cPlayer & a_Player, bool a_IsShiftPressed); + + /// Updates the current recipe and result slot based on the ingredients currently in the crafting grid of the specified player + void UpdateRecipe(cPlayer & a_Player); + + /// Retrieves the recipe for the specified player from the map, or creates one if not found + cCraftingRecipe & GetRecipeForPlayer(cPlayer & a_Player); +} ; + + + + + +class cSlotAreaChest : + public cSlotArea +{ +public: + cSlotAreaChest(cChestEntity * a_Chest, 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; + +protected: + cChestEntity * m_Chest; +} ; + + + + + +class cSlotAreaFurnace : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow); + + 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: + cFurnaceEntity * m_Furnace; +} ; + + + + + +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 new file mode 100644 index 000000000..475670425 --- /dev/null +++ b/source/UI/cWindow.cpp @@ -0,0 +1,385 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "cWindow.h" +#include "../cItem.h" +#include "../cClientHandle.h" +#include "../cPlayer.h" +#include "../cPickup.h" +#include "../cInventory.h" +#include "cWindowOwner.h" +#include "../items/Item.h" +#include "SlotArea.h" +#include "../cChestEntity.h" + + + + + +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) +{ + if (a_WindowType == Inventory) + { + m_WindowID = 0; + } + LOGD("Created a window at %p, type = %d, ID = %i, title = \"%s\".", + this, m_WindowType, m_WindowID, m_WindowTitle.c_str() + ); +} + + + + + +cWindow::~cWindow() +{ + LOGD("Deleted a window at %p", this); +} + + + + + +int cWindow::GetNumSlots(void) const +{ + int res = 0; + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + res += (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + return res; +} + + + + + +void cWindow::GetSlots(cPlayer & a_Player, cItems & a_Slots) const +{ + a_Slots.clear(); + a_Slots.reserve(GetNumSlots()); + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + int NumSlots = (*itr)->GetNumSlots(); + for (int i = 0; i < NumSlots; i++) + { + + const cItem * Item = (*itr)->GetSlot(i, a_Player); + if (Item == NULL) + { + a_Slots.push_back(cItem()); + } + else + { + a_Slots.push_back(*Item); + } + } + } // for itr - m_SlotAreas[] +} + + + + + +void cWindow::Clicked( + cPlayer & a_Player, + int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, + const cItem & a_ClickedItem +) +{ + LOGD("cWindow::Clicked(): ID %d (exp %d), SlotNum %d", a_WindowID, m_WindowID, a_SlotNum); + + 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()); + return; + } + + if (a_SlotNum == -999) // Outside window click + { + if (a_IsRightClick) + { + a_Player.TossItem(true); + } + else + { + a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + } + return; + } + + int LocalSlotNum = a_SlotNum; + int idx = 0; + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (LocalSlotNum < (*itr)->GetNumSlots()) + { + LOGD("SlotArea #%d (%d slots) handling the click", idx, (*itr)->GetNumSlots()); + (*itr)->Clicked(a_Player, LocalSlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + return; + } + LocalSlotNum -= (*itr)->GetNumSlots(); + idx++; + } + + LOGWARNING("Slot number higher than available window slots: %d, max %d received from \"%s\"; ignoring.", + a_SlotNum, GetNumSlots(), a_Player.GetName().c_str() + ); +} + + + + + +void cWindow::OpenedByPlayer(cPlayer & a_Player) +{ + { + cCSLock Lock(m_CS); + // If player is already in OpenedBy remove player first + m_OpenedBy.remove(&a_Player); + // Then add player + m_OpenedBy.push_back(&a_Player); + + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + (*itr)->OnPlayerAdded(a_Player); + } // for itr - m_SlotAreas[] + } + + // TODO: Notify all areas that a new player has opened the window + + a_Player.GetClientHandle()->SendWindowOpen(m_WindowID, m_WindowType, m_WindowTitle, GetNumSlots() - c_NumInventorySlots); +} + + + + + +void cWindow::ClosedByPlayer(cPlayer & a_Player) +{ + ASSERT(m_WindowType != Inventory); // Inventory windows must not be closed (the client would repeat the close packet, looping forever) + + // Checks whether the player is still holding an item + if (a_Player.IsDraggingItem()) + { + LOGD("Player holds item! Dropping it..."); + a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + } + + cClientHandle * ClientHandle = a_Player.GetClientHandle(); + if (ClientHandle != NULL) + { + ClientHandle->SendWindowClose(m_WindowID); + } + + { + cCSLock Lock(m_CS); + + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + (*itr)->OnPlayerRemoved(a_Player); + } // for itr - m_SlotAreas[] + + m_OpenedBy.remove(&a_Player); + if (m_OpenedBy.empty()) + { + Destroy(); + } + } + if (m_IsDestroyed) + { + delete this; + } +} + + + + + +void cWindow::OwnerDestroyed() +{ + m_Owner = NULL; + // Close window for each player. Note that the last one needs special handling + while (m_OpenedBy.size() > 1) + { + (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); + } + (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); +} + + + + + +bool cWindow::ForEachPlayer(cItemCallback & a_Callback) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) + { + if (a_Callback.Item(*itr)) + { + return false; + } + } // for itr - m_OpenedBy[] + return true; +} + + + + + +bool cWindow::ForEachClient(cItemCallback & a_Callback) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) + { + if (a_Callback.Item((*itr)->GetClientHandle())) + { + return false; + } + } // for itr - m_OpenedBy[] + return true; +} + + + + + +void cWindow::Destroy() +{ + LOGD("Destroying window %p (type %d)", this, m_WindowType); + if (m_Owner != NULL) + { + m_Owner->CloseWindow(); + m_Owner = NULL; + } + m_IsDestroyed = true; +} + + + + + +void cWindow::SendWholeWindow(cClientHandle & a_Client) +{ + a_Client.SendWholeInventory(*this); +} + + + + + +void cWindow::BroadcastWholeWindow(void) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + SendWholeWindow(*(*itr)->GetClientHandle()); + } // for itr - m_OpenedBy[] +} + + + + + +void cWindow::BroadcastInventoryProgress(short a_Progressbar, short a_Value) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + (*itr)->GetClientHandle()->SendInventoryProgress(m_WindowID, a_Progressbar, a_Value); + } // for itr - m_OpenedBy[] +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cInventoryWindow: + +cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : + cWindow(cWindow::Inventory, "MCS-Inventory"), + m_Player(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)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCraftingWindow: + +cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(cWindow::Workbench, "MCS-Workbench") +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cChestWindow: + +cChestWindow::cChestWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cChestEntity * a_Chest) : + cWindow(cWindow::Chest, "MCS-Chest"), + m_World(a_Chest->GetWorld()), + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ) +{ + m_SlotAreas.push_back(new cSlotAreaChest(a_Chest, *this)); + + // TODO: Double chests + + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_CHEST); +} + + + + + +cChestWindow::~cChestWindow() +{ + // Send out the chest-close packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, E_BLOCK_CHEST); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFurnaceWindow: + +cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace) : + cWindow(cWindow::Furnace, "MCS-Furnace") +{ + m_SlotAreas.push_back(new cSlotAreaFurnace(a_Furnace, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); +} + + + + diff --git a/source/UI/cWindow.h b/source/UI/cWindow.h new file mode 100644 index 000000000..231e4fdc0 --- /dev/null +++ b/source/UI/cWindow.h @@ -0,0 +1,172 @@ + +// cWindow.h + +// Interfaces to the cWindow class representing a UI window for a specific block + + + + + +#pragma once + +#include "../cItem.h" + + + + + +class cPlayer; +class cWindowOwner; +class cClientHandle; +class cChestEntity; +class cFurnaceEntity; +class cSlotArea; +class cWorld; + +typedef std::list cPlayerList; +typedef std::vector cSlotAreas; + + + + + +/** +Represents a UI window. + +Each window has a list of players that are currently using it +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. +*/ +class cWindow +{ +public: + enum WindowType + { + Inventory = -1, // This value is never actually sent to a client + Chest = 0, + Workbench = 1, + Furnace = 2, + Dispenser = 3, + Enchantment = 4, + Brewery = 5 + }; + + static const int c_NumInventorySlots = 36; + + cWindow(WindowType a_WindowType, const AString & a_WindowTitle); + virtual ~cWindow(); + + int GetWindowID(void) const { return m_WindowID; } + int GetWindowType(void) const { return m_WindowType; } + + cWindowOwner * GetOwner() { return m_Owner; } + void SetOwner( cWindowOwner * a_Owner ) { m_Owner = a_Owner; } + + int GetNumSlots(void) const; + + /// Fills a_Slots with the slots read from m_SlotAreas[], for the specified player + void GetSlots(cPlayer & a_Player, cItems & a_Slots) const; + + void Clicked( + cPlayer & a_Player, int a_WindowID, + short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, + const cItem & a_ClickedItem + ); + + void OpenedByPlayer(cPlayer & a_Player); + void ClosedByPlayer(cPlayer & a_Player); + + void SendWholeWindow(cClientHandle & a_Client); + void BroadcastWholeWindow(void); + void BroadcastInventoryProgress(short a_Progressbar, short a_Value); + + const AString & GetWindowTitle() const { return m_WindowTitle; } + void SetWindowTitle(const AString & a_WindowTitle ) { m_WindowTitle = a_WindowTitle; } + + void OwnerDestroyed(void); + + /// Calls the callback safely for each player that has this window open; returns true if all players have been enumerated + bool ForEachPlayer(cItemCallback & a_Callback); + + /// 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); + +protected: + cSlotAreas m_SlotAreas; + +private: + char m_WindowID; + int m_WindowType; + AString m_WindowTitle; + + cCriticalSection m_CS; + cPlayerList m_OpenedBy; + + bool m_IsDestroyed; + + cWindowOwner * m_Owner; + + static char m_WindowIDCounter; + + void Destroy(void); +} ; + + + + + +class cCraftingWindow : + public cWindow +{ +public: + cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); +} ; + + + + + +class cFurnaceWindow : + public cWindow +{ +public: + cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace); +} ; + + + + + +class cChestWindow : + public cWindow +{ +public: + cChestWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cChestEntity * a_Chest); + ~cChestWindow(); + +protected: + cWorld * m_World; + int m_BlockX, m_BlockY, m_BlockZ; // Position of the chest, for the window-close packet +} ; + + + + + +class cInventoryWindow : + public cWindow +{ +public: + cInventoryWindow(cPlayer & a_Player); + +protected: + cPlayer & m_Player; + +} ; + + + + + diff --git a/source/UI/cWindowOwner.h b/source/UI/cWindowOwner.h new file mode 100644 index 000000000..1ce03ed30 --- /dev/null +++ b/source/UI/cWindowOwner.h @@ -0,0 +1,125 @@ + +#pragma once + +#include "../cBlockEntity.h" +#include "../cEntity.h" +#include "cWindow.h" + +/* +Being a descendant of cWindowOwner means that the class can own one window. That window can be +queried, opened by other players, closed by players and finally destroyed. +Also, a cWindowOwner can be queried for the block coords where the window is displayed. That will be used +for entities / players in motion to close their windows when they get too far away from the window "source". +*/ + + + + + +// class cWindow; + + + + + +/** +Base class for the window owning +*/ +class cWindowOwner +{ +public: + cWindowOwner() : + m_Window(NULL) + { + } + + void CloseWindow(void) + { + m_Window = NULL; + } + + void OpenWindow(cWindow * a_Window) + { + m_Window = a_Window; + m_Window->SetOwner(this); + } + + cWindow * GetWindow(void) const + { + return m_Window; + } + + /// Returns the block position at which the element owning the window is + virtual void GetBlockPos(int & a_BlockX, int & a_BlockY, int & a_BlockZ) = 0; + +private: + cWindow * m_Window; +} ; + + + + + +/** +Window owner that is associated with a block entity (chest, furnace, ...) +*/ +class cBlockEntityWindowOwner : + public cWindowOwner +{ +public: + cBlockEntityWindowOwner(void) : + m_BlockEntity(NULL) + { + } + + void SetBlockEntity(cBlockEntity * a_BlockEntity) + { + m_BlockEntity = a_BlockEntity; + } + + virtual void GetBlockPos(int & a_BlockX, int & a_BlockY, int & a_BlockZ) override + { + a_BlockX = m_BlockEntity->GetPosX(); + a_BlockY = m_BlockEntity->GetPosY(); + a_BlockZ = m_BlockEntity->GetPosZ(); + } + +private: + cBlockEntity * m_BlockEntity; +} ; + + + + + +/** +Window owner that is associated with an entity (chest minecart) +*/ +class cEntityWindowOwner : + public cWindowOwner +{ +public: + cEntityWindowOwner(void) : + m_Entity(NULL) + { + } + + void SetEntity(cEntity * a_Entity) + { + m_Entity = a_Entity; + } + + virtual void GetBlockPos(int & a_BlockX, int & a_BlockY, int & a_BlockZ) override + { + a_BlockX = (int)(m_Entity->GetPosX()); + a_BlockY = (int)(m_Entity->GetPosY()); + a_BlockZ = (int)(m_Entity->GetPosZ()); + } + +private: + cEntity * m_Entity; +} ; + + + + -- cgit v1.2.3