diff options
Diffstat (limited to '')
-rw-r--r-- | source/UI/SlotArea.cpp | 569 | ||||
-rw-r--r-- | source/UI/SlotArea.h | 192 | ||||
-rw-r--r-- | source/UI/cWindow.cpp | 385 | ||||
-rw-r--r-- | source/UI/cWindow.h | 172 | ||||
-rw-r--r-- | source/UI/cWindowOwner.h (renamed from source/cWindowOwner.h) | 17 |
5 files changed, 1331 insertions, 4 deletions
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<int, std::vector<cItem> > 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<std::pair<int, cCraftingRecipe> > 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<cPlayer> & 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<cClientHandle> & 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<cPlayer *> cPlayerList; +typedef std::vector<cSlotArea *> 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<cPlayer> & 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<cClientHandle> & 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/cWindowOwner.h b/source/UI/cWindowOwner.h index 6c9a15a45..1ce03ed30 100644 --- a/source/cWindowOwner.h +++ b/source/UI/cWindowOwner.h @@ -1,21 +1,29 @@ #pragma once -#include "cBlockEntity.h" -#include "cEntity.h" +#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; +// class cWindow; /** -Base class for the behavior expected from a class that can handle UI windows for block entities. +Base class for the window owning */ class cWindowOwner { @@ -33,6 +41,7 @@ public: void OpenWindow(cWindow * a_Window) { m_Window = a_Window; + m_Window->SetOwner(this); } cWindow * GetWindow(void) const |