diff options
Diffstat (limited to 'src/UI')
-rw-r--r-- | src/UI/SlotArea.cpp | 897 | ||||
-rw-r--r-- | src/UI/SlotArea.h | 313 | ||||
-rw-r--r-- | src/UI/Window.cpp | 886 | ||||
-rw-r--r-- | src/UI/Window.h | 300 | ||||
-rw-r--r-- | src/UI/WindowOwner.h | 125 |
5 files changed, 2521 insertions, 0 deletions
diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp new file mode 100644 index 000000000..7fd7cd996 --- /dev/null +++ b/src/UI/SlotArea.cpp @@ -0,0 +1,897 @@ + +// SlotArea.cpp + +// Implements the cSlotArea class and its descendants + +#include "Globals.h" +#include "SlotArea.h" +#include "../Entities/Player.h" +#include "../BlockEntities/ChestEntity.h" +#include "../BlockEntities/DropSpenserEntity.h" +#include "../BlockEntities/FurnaceEntity.h" +#include "../Items/ItemHandler.h" +#include "Window.h" +#include "../CraftingRecipes.h" +#include "../Root.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotArea: + +cSlotArea::cSlotArea(int a_NumSlots, cWindow & a_ParentWindow) : + m_NumSlots(a_NumSlots), + m_ParentWindow(a_ParentWindow) +{ +} + + + + + +void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) +{ + /* + 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; + } + + switch (a_ClickAction) + { + case caShiftLeftClick: + case caShiftRightClick: + { + ShiftClicked(a_Player, a_SlotNum, a_ClickedItem); + return; + } + + case caDblClick: + { + DblClicked(a_Player, a_SlotNum); + return; + } + } + + cItem Slot(*GetSlot(a_SlotNum, a_Player)); + if (!Slot.IsSameType(a_ClickedItem)) + { + LOGWARNING("*** Window lost sync at item %d in SlotArea with %d items ***", a_SlotNum, m_NumSlots); + LOGWARNING("My item: %s", ItemToFullString(Slot).c_str()); + LOGWARNING("Their item: %s", ItemToFullString(a_ClickedItem).c_str()); + bAsync = true; + } + cItem & DraggingItem = a_Player.GetDraggingItem(); + switch (a_ClickAction) + { + case caRightClick: + { + if (DraggingItem.m_ItemType <= 0) // Empty-handed? + { + DraggingItem.m_ItemCount = (char)(((float)Slot.m_ItemCount) / 2.f + 0.5f); + Slot.m_ItemCount -= DraggingItem.m_ItemCount; + DraggingItem.m_ItemType = Slot.m_ItemType; + DraggingItem.m_ItemDamage = Slot.m_ItemDamage; + + if (Slot.m_ItemCount <= 0) + { + Slot.Empty(); + } + } + else if ((Slot.m_ItemType <= 0) || DraggingItem.IsEqual(Slot)) + { + // Drop one item in slot + cItemHandler * Handler = ItemHandler(Slot.m_ItemType); + if ((DraggingItem.m_ItemCount > 0) && (Slot.m_ItemCount < Handler->GetMaxStackSize())) + { + Slot.m_ItemType = DraggingItem.m_ItemType; + Slot.m_ItemCount++; + Slot.m_ItemDamage = DraggingItem.m_ItemDamage; + DraggingItem.m_ItemCount--; + } + if (DraggingItem.m_ItemCount <= 0) + { + DraggingItem.Empty(); + } + } + else if (!DraggingItem.IsEqual(Slot)) + { + // Swap contents + cItem tmp(DraggingItem); + DraggingItem = Slot; + Slot = tmp; + } + break; + } + + case caLeftClick: + { + // Left-clicked + if (!DraggingItem.IsEqual(Slot)) + { + // Switch contents + cItem tmp(DraggingItem); + DraggingItem = Slot; + Slot = tmp; + } + else + { + // Same type, add items: + cItemHandler * Handler = ItemHandler(DraggingItem.m_ItemType); + int FreeSlots = Handler->GetMaxStackSize() - Slot.m_ItemCount; + if (FreeSlots < 0) + { + ASSERT(!"Bad item stack size - where did we get more items in a slot than allowed?"); + FreeSlots = 0; + } + 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(); + } + } + break; + } + default: + { + LOGWARNING("SlotArea: Unhandled click action: %d (%s)", a_ClickAction, ClickActionToString(a_ClickAction)); + m_ParentWindow.BroadcastWholeWindow(); + return; + } + } // switch (a_ClickAction + + SetSlot(a_SlotNum, a_Player, Slot); + if (bAsync) + { + m_ParentWindow.BroadcastWholeWindow(); + } + +} + + + + + +void cSlotArea::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem) +{ + // Make a copy of the slot, distribute it among the other areas, then update the slot to contain the leftover: + cItem Slot(*GetSlot(a_SlotNum, a_Player)); + m_ParentWindow.DistributeStack(Slot, a_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(); +} + + + + + +void cSlotArea::DblClicked(cPlayer & a_Player, int a_SlotNum) +{ + cItem & Dragging = a_Player.GetDraggingItem(); + if (Dragging.IsEmpty()) + { + // Move the item in the dblclicked slot into hand: + Dragging = *GetSlot(a_SlotNum, a_Player); + cItem EmptyItem; + SetSlot(a_SlotNum, a_Player, EmptyItem); + } + if (Dragging.IsEmpty()) + { + LOGD("%s DblClicked with an empty hand over empty slot, ignoring", a_Player.GetName().c_str()); + return; + } + + // Add as many items from the surrounding area into hand as possible: + // First skip full stacks, then if there's still space, process full stacks as well: + if (!m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, false)) + { + m_ParentWindow.CollectItemsToHand(Dragging, *this, a_Player, true); + } + + m_ParentWindow.BroadcastWholeWindow(); // We need to broadcast, in case the window was a chest opened by multiple players +} + + + + + +void cSlotArea::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_Apply, bool a_KeepEmptySlots) +{ + for (int i = 0; i < m_NumSlots; i++) + { + const cItem * Slot = GetSlot(i, a_Player); + if (!Slot->IsStackableWith(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); + NewSlot.m_ItemCount = Slot->m_ItemCount + NumFit; + SetSlot(i, a_Player, NewSlot); + } + a_ItemStack.m_ItemCount -= NumFit; + if (a_ItemStack.IsEmpty()) + { + return; + } + } // for i - Slots +} + + + + + +bool cSlotArea::CollectItemsToHand(cItem & a_Dragging, cPlayer & a_Player, bool a_CollectFullStacks) +{ + int NumSlots = GetNumSlots(); + for (int i = 0; i < NumSlots; i++) + { + const cItem & SlotItem = *GetSlot(i, a_Player); + if (!SlotItem.IsStackableWith(a_Dragging)) + { + continue; + } + int ToMove = a_Dragging.GetMaxStackSize() - a_Dragging.m_ItemCount; + if (ToMove > SlotItem.m_ItemCount) + { + ToMove = SlotItem.m_ItemCount; + } + a_Dragging.m_ItemCount += ToMove; + cItem NewSlot(SlotItem); + NewSlot.m_ItemCount -= ToMove; + SetSlot(i, a_Player, NewSlot); + if (!NewSlot.IsEmpty()) + { + // There are leftovers in the slot, so a_Dragging must be full + return true; + } + } // for i - Slots[] + // a_Dragging may be full if there were exactly the number of items needed to fill it + return a_Dragging.IsFullStack(); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaChest: + +cSlotAreaChest::cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow) : + cSlotArea(27, a_ParentWindow), + m_Chest(a_Chest) +{ +} + + + + + +const cItem * cSlotAreaChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const +{ + // a_SlotNum ranges from 0 to 26, use that to index the chest entity's inventory directly: + return &(m_Chest->GetSlot(a_SlotNum)); +} + + + + + +void cSlotAreaChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + m_Chest->SetSlot(a_SlotNum, a_Item); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaDoubleChest: + +cSlotAreaDoubleChest::cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEntity * a_BottomChest, cWindow & a_ParentWindow) : + cSlotArea(54, a_ParentWindow), + m_TopChest(a_TopChest), + m_BottomChest(a_BottomChest) +{ +} + + + + + +const cItem * cSlotAreaDoubleChest::GetSlot(int a_SlotNum, cPlayer & a_Player) const +{ + // a_SlotNum ranges from 0 to 53, use that to index the correct chest's inventory: + if (a_SlotNum < 27) + { + return &(m_TopChest->GetSlot(a_SlotNum)); + } + else + { + return &(m_BottomChest->GetSlot(a_SlotNum - 27)); + } +} + + + + + +void cSlotAreaDoubleChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + if (a_SlotNum < 27) + { + m_TopChest->SetSlot(a_SlotNum, a_Item); + } + else + { + m_BottomChest->SetSlot(a_SlotNum - 27, a_Item); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaCrafting: + +cSlotAreaCrafting::cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow) : + cSlotAreaTemporary(1 + a_GridSize * a_GridSize, a_ParentWindow), + m_GridSize(a_GridSize) +{ + ASSERT((a_GridSize == 2) || (a_GridSize == 3)); +} + + + + + +void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) +{ + // Override for craft result slot + if (a_SlotNum == 0) + { + if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick)) + { + ShiftClickedResult(a_Player); + } + else + { + ClickedResult(a_Player); + } + return; + } + super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem); + UpdateRecipe(a_Player); +} + + + + + +void cSlotAreaCrafting::DblClicked(cPlayer & a_Player, int a_SlotNum) +{ + if (a_SlotNum == 0) + { + // Dbl-clicking the crafting result slot shouldn't collect items to hand + return; + } + super::DblClicked(a_Player, a_SlotNum); +} + + + + + +void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player) +{ + // Toss all items on the crafting grid: + TossItems(a_Player, 1, m_NumSlots); + + // 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) +{ + const cItem * ResultSlot = GetSlot(0, a_Player); + 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_ItemType); + 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::ShiftClickedResult(cPlayer & a_Player) +{ + cItem Result(*GetSlot(0, a_Player)); + if (Result.IsEmpty()) + { + return; + } + cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1; + do + { + // Try distributing the result. If it fails, bail out: + cItem ResultCopy(Result); + m_ParentWindow.DistributeStack(ResultCopy, a_Player, this, false); + if (!ResultCopy.IsEmpty()) + { + // Couldn't distribute all of it. Bail out + return; + } + + // Distribute the result, this time for real: + ResultCopy = Result; + m_ParentWindow.DistributeStack(ResultCopy, a_Player, this, true); + + // Remove the ingredients from the crafting grid and update the recipe: + cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); + cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize); + Recipe.ConsumeIngredients(Grid); + Grid.CopyToItems(PlayerSlots); + UpdateRecipe(a_Player); + if (!Recipe.GetResult().IsEqual(Result)) + { + // The recipe has changed, bail out + return; + } + } while (true); +} + + + + + +void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player) +{ + cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize); + cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); + cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); + SetSlot(0, a_Player, Recipe.GetResult()); + m_ParentWindow.SendSlot(a_Player, this, 0); +} + + + + + +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) +{ + m_Furnace->GetContents().AddListener(*this); +} + + + + + +cSlotAreaFurnace::~cSlotAreaFurnace() +{ + m_Furnace->GetContents().RemoveListener(*this); +} + + + + + +void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) +{ + super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem); + + if (m_Furnace == NULL) + { + LOGERROR("cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + ASSERT(!"cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + return; + } +} + + + + + +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)); +} + + + + + +void cSlotAreaFurnace::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + m_Furnace->SetSlot(a_SlotNum, a_Item); +} + + + + + +void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) +{ + // Something has changed in the window, broadcast the entire window to all clients + ASSERT(a_ItemGrid == &(m_Furnace->GetContents())); + + m_ParentWindow.BroadcastWholeWindow(); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaInventoryBase: + +cSlotAreaInventoryBase::cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow) : + cSlotArea(a_NumSlots, a_ParentWindow), + m_SlotOffset(a_SlotOffset) +{ +} + + + + + +void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem) +{ + if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory)) + { + // 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_ClickAction, a_ClickedItem); + return; +} + + + + + +const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) const +{ + // a_SlotNum ranges from 0 to 35, map that to the player's inventory slots according to the internal offset + return &a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset); +} + + + + + +void cSlotAreaInventoryBase::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + a_Player.GetInventory().SetSlot(a_SlotNum + m_SlotOffset, a_Item); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaArmor: + +void cSlotAreaArmor::DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) +{ + if (ItemCategory::IsHelmet(a_ItemStack.m_ItemType) && GetSlot(0, a_Player)->IsEmpty()) + { + if (a_ShouldApply) + { + SetSlot(0, a_Player, a_ItemStack.CopyOne()); + } + a_ItemStack.m_ItemCount -= 1; + } + else if (ItemCategory::IsChestPlate(a_ItemStack.m_ItemType) && GetSlot(1, a_Player)->IsEmpty()) + { + if (a_ShouldApply) + { + SetSlot(1, a_Player, a_ItemStack.CopyOne()); + } + a_ItemStack.m_ItemCount -= 1; + } + else if (ItemCategory::IsLeggings(a_ItemStack.m_ItemType) && GetSlot(2, a_Player)->IsEmpty()) + { + if (a_ShouldApply) + { + SetSlot(2, a_Player, a_ItemStack.CopyOne()); + } + a_ItemStack.m_ItemCount -= 1; + } + else if (ItemCategory::IsBoots(a_ItemStack.m_ItemType) && GetSlot(3, a_Player)->IsEmpty()) + { + if (a_ShouldApply) + { + SetSlot(3, a_Player, a_ItemStack.CopyOne()); + } + a_ItemStack.m_ItemCount -= 1; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaItemGrid: + +cSlotAreaItemGrid::cSlotAreaItemGrid(cItemGrid & a_ItemGrid, cWindow & a_ParentWindow) : + super(a_ItemGrid.GetNumSlots(), a_ParentWindow), + m_ItemGrid(a_ItemGrid) +{ + m_ItemGrid.AddListener(*this); +} + + + + + +cSlotAreaItemGrid::~cSlotAreaItemGrid() +{ + m_ItemGrid.RemoveListener(*this); +} + + + + + +const cItem * cSlotAreaItemGrid::GetSlot(int a_SlotNum, cPlayer & a_Player) const +{ + return &m_ItemGrid.GetSlot(a_SlotNum); +} + + + + + +void cSlotAreaItemGrid::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + m_ItemGrid.SetSlot(a_SlotNum, a_Item); +} + + + + + +void cSlotAreaItemGrid::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) +{ + ASSERT(a_ItemGrid == &m_ItemGrid); + m_ParentWindow.BroadcastSlot(this, a_SlotNum); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaTemporary: + +cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) : + cSlotArea(a_NumSlots, a_ParentWindow) +{ +} + + + + + +const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) const +{ + cItemMap::const_iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + LOGERROR("cSlotAreaTemporary: player \"%s\" not found for slot %d!", a_Player.GetName().c_str(), a_SlotNum); + ASSERT(!"cSlotAreaTemporary: player not found!"); + + // 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; + } + + return &(itr->second[a_SlotNum]); +} + + + + + +void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + // Player not found + LOGWARNING("cSlotAreaTemporary: player not found!"); + return; + } + + 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[] + + double 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 * 3, vY * 3, vZ * 3, true); // 'true' because player created +} + + + + + +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/src/UI/SlotArea.h b/src/UI/SlotArea.h new file mode 100644 index 000000000..b1944d901 --- /dev/null +++ b/src/UI/SlotArea.h @@ -0,0 +1,313 @@ + +// SlotArea.h + +// Interfaces to the cSlotArea class representing a contiguous area of slots in a UI window + + + + +#pragma once + +#include "../Inventory.h" + + + +class cWindow; +class cPlayer; +class cChestEntity; +class cDropSpenserEntity; +class cFurnaceEntity; +class cCraftingRecipe; + + + + + +class cSlotArea +{ +public: + cSlotArea(int a_NumSlots, cWindow & a_ParentWindow); + virtual ~cSlotArea() {} // force a virtual destructor in all subclasses + + 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) 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; + + /// Called when a player clicks in the window. Parameters taken from the click packet. + virtual void Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem); + + /// Called from Clicked when the action is a shiftclick (left or right) + virtual void ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem); + + /// Called from Clicked when the action is a caDblClick + virtual void DblClicked(cPlayer & a_Player, int a_SlotNum); + + /// 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); + + /// Called on DblClicking to collect all stackable items into hand. + /// The items are accumulated in a_Dragging and removed from the slots immediately. + /// If a_CollectFullStacks is false, slots with full stacks are skipped while collecting. + /// Returns true if full stack has been collected in a_Dragging, false if there's space remaining to fill. + virtual bool CollectItemsToHand(cItem & a_Dragging, cPlayer & a_Player, bool a_CollectFullStacks); + +protected: + int m_NumSlots; + cWindow & m_ParentWindow; +} ; + + + + + +/// Handles any part of the inventory, using parameters in constructor to distinguish between the parts +class cSlotAreaInventoryBase : + public cSlotArea +{ + typedef cSlotArea super; + +public: + 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, eClickAction a_ClickAction, const cItem & a_ClickedItem) 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: + int m_SlotOffset; // Index that this area's slot 0 has in the underlying cInventory +} ; + + + + + +/// Handles the main inventory of each player, excluding the armor and hotbar +class cSlotAreaInventory : + public cSlotAreaInventoryBase +{ + typedef cSlotAreaInventoryBase super; + +public: + cSlotAreaInventory(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(cInventory::invInventoryCount, cInventory::invInventoryOffset, a_ParentWindow) + { + } +} ; + + + + + +/// Handles the hotbar of each player +class cSlotAreaHotBar : + public cSlotAreaInventoryBase +{ + typedef cSlotAreaInventoryBase super; + +public: + cSlotAreaHotBar(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(cInventory::invHotbarCount, cInventory::invHotbarOffset, a_ParentWindow) + { + } +} ; + + + + + +/// Handles the armor area of the player's inventory +class cSlotAreaArmor : + public cSlotAreaInventoryBase +{ +public: + cSlotAreaArmor(cWindow & a_ParentWindow) : + cSlotAreaInventoryBase(cInventory::invArmorCount, cInventory::invArmorOffset, 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; +} ; + + + + + +/// Handles any slot area that is representing a cItemGrid; same items for all the players +class cSlotAreaItemGrid : + public cSlotArea, + public cItemGrid::cListener +{ + typedef cSlotArea super; + +public: + cSlotAreaItemGrid(cItemGrid & a_ItemGrid, cWindow & a_ParentWindow); + + virtual ~cSlotAreaItemGrid(); + + 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: + cItemGrid & m_ItemGrid; + + // cItemGrid::cListener overrides: + virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) 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) 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; + + /// 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, eClickAction a_ClickAction, const cItem & a_ClickedItem) override; + virtual void DblClicked (cPlayer & a_Player, int a_SlotNum); + 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<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); + + /// Handles a shift-click in the result slot. Crafts using the current recipe until it changes or no more space for result. + void ShiftClickedResult(cPlayer & a_Player); + + /// 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) const override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + +protected: + cChestEntity * m_Chest; +} ; + + + + + +class cSlotAreaDoubleChest : + public cSlotArea +{ +public: + cSlotAreaDoubleChest(cChestEntity * a_TopChest, cChestEntity * a_BottomChest, cWindow & a_ParentWindow); + + 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: + cChestEntity * m_TopChest; + cChestEntity * m_BottomChest; +} ; + + + + + +class cSlotAreaFurnace : + public cSlotArea, + public cItemGrid::cListener +{ + typedef cSlotArea super; + +public: + cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow); + + virtual ~cSlotAreaFurnace(); + + 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) const override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + +protected: + cFurnaceEntity * m_Furnace; + + // cItemGrid::cListener overrides: + virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override; +} ; + + + + diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp new file mode 100644 index 000000000..f5c62692f --- /dev/null +++ b/src/UI/Window.cpp @@ -0,0 +1,886 @@ +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Window.h" +#include "WindowOwner.h" +#include "SlotArea.h" +#include "../Item.h" +#include "../ClientHandle.h" +#include "../Entities/Player.h" +#include "../Entities/Pickup.h" +#include "../Inventory.h" +#include "../Items/ItemHandler.h" +#include "../BlockEntities/ChestEntity.h" +#include "../BlockEntities/DropSpenserEntity.h" +#include "../BlockEntities/HopperEntity.h" + + + + + +char cWindow::m_WindowIDCounter = 1; + + + + + +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 == wtInventory) + { + m_WindowID = 0; + } +} + + + + + +cWindow::~cWindow() +{ + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + delete *itr; + } + m_SlotAreas.clear(); +} + + + + + +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; +} + + + + + +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); +} + + + + + +bool cWindow::IsSlotInPlayerMainInventory(int a_SlotNum) const +{ + // Returns true if the specified slot is in the Player Main Inventory slotarea + // The player main inventory is always 27 slots, 9 slots from the end of the inventory + return ((a_SlotNum >= GetNumSlots() - 36) && (a_SlotNum < GetNumSlots() - 9)); +} + + + + + +bool cWindow::IsSlotInPlayerHotbar(int a_SlotNum) const +{ + // Returns true if the specified slot is in the Player Hotbar slotarea + // The hotbar is always the last 9 slots + return ((a_SlotNum >= GetNumSlots() - 9) && (a_SlotNum < GetNumSlots())); +} + + + + + +bool cWindow::IsSlotInPlayerInventory(int a_SlotNum) const +{ + // Returns true if the specified slot is in the Player Main Inventory or Hotbar slotareas. Note that returns false for Armor. + // The player combined inventory is always the last 36 slots + return ((a_SlotNum >= GetNumSlots() - 36) && (a_SlotNum < GetNumSlots())); +} + + + + + +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, eClickAction a_ClickAction, + const cItem & a_ClickedItem +) +{ + if (a_WindowID != m_WindowID) + { + 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; + } + + switch (a_ClickAction) + { + case caRightClickOutside: + { + // Toss one of the dragged items: + a_Player.TossItem(true); + return; + } + case caLeftClickOutside: + { + // Toss all dragged items: + a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + return; + } + case caLeftClickOutsideHoldNothing: + case caRightClickOutsideHoldNothing: + { + // 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) + { + // TODO: Other click actions with irrelevant slot number (FS #371) + 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()) + { + (*itr)->Clicked(a_Player, LocalSlotNum, a_ClickAction, 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[] + } + + a_Player.GetClientHandle()->SendWindowOpen(*this); +} + + + + + +bool cWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) +{ + // 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(*this); + } + + { + 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_WindowType != wtInventory) && m_OpenedBy.empty()) + { + Destroy(); + } + } + if (m_IsDestroyed) + { + delete this; + } + + return true; +} + + + + + +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(); + } + (*m_OpenedBy.begin() )->CloseWindow(); +} + + + + + +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::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) + { + if (m_ShouldDistributeToHotbarFirst) + { + // 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: + cSlotAreas::iterator end = m_ShouldDistributeToHotbarFirst ? (m_SlotAreas.end() - 1) : m_SlotAreas.end(); + for (cSlotAreas::iterator itr = m_SlotAreas.begin(); 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 +} + + + + + +bool cWindow::CollectItemsToHand(cItem & a_Dragging, cSlotArea & a_Area, cPlayer & a_Player, bool a_CollectFullStacks) +{ + // First ask the slot areas from a_Area till the end of list: + bool ShouldCollect = false; + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (&a_Area == *itr) + { + ShouldCollect = true; + } + if (!ShouldCollect) + { + continue; + } + if ((*itr)->CollectItemsToHand(a_Dragging, a_Player, a_CollectFullStacks)) + { + // a_Dragging is full + return true; + } + } + + // a_Dragging still not full, ask slot areas before a_Area in the list: + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (*itr == &a_Area) + { + // All areas processed + return false; + } + if ((*itr)->CollectItemsToHand(a_Dragging, a_Player, a_CollectFullStacks)) + { + // a_Dragging is full + return true; + } + } + // Shouldn't reach here + // a_Area is expected to be part of m_SlotAreas[], so the "return false" in the loop above should have returned already + ASSERT(!"This branch should not be reached"); + return false; +} + + + + + +void cWindow::SendSlot(cPlayer & a_Player, cSlotArea * a_SlotArea, int a_RelativeSlotNum) +{ + int SlotBase = 0; + bool Found = false; + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (*itr == a_SlotArea) + { + Found = true; + break; + } + SlotBase += (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + if (!Found) + { + LOGERROR("cWindow::SendSlot(): unknown a_SlotArea"); + ASSERT(!"cWindow::SendSlot(): unknown a_SlotArea"); + return; + } + + a_Player.GetClientHandle()->SendInventorySlot( + m_WindowID, a_RelativeSlotNum + SlotBase, *(a_SlotArea->GetSlot(a_RelativeSlotNum, a_Player)) + ); +} + + + + + +void cWindow::Destroy(void) +{ + if (m_Owner != NULL) + { + m_Owner->CloseWindow(); + m_Owner = NULL; + } + m_IsDestroyed = true; +} + + + + + +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()); + int ToEachSlot = (int)ToDistribute.m_ItemCount / SlotNums.size(); + + int NumDistributed = DistributeItemToSlots(a_Player, ToDistribute, ToEachSlot, 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 + + const cSlotNums & SlotNums = a_Player.GetInventoryPaintSlots(); + cItem ToDistribute(a_Player.GetDraggingItem()); + + int NumDistributed = DistributeItemToSlots(a_Player, ToDistribute, 1, 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()); +} + + + + + +int cWindow::DistributeItemToSlots(cPlayer & a_Player, const cItem & a_Item, int a_NumToEachSlot, const cSlotNums & a_SlotNums) +{ + if ((size_t)(a_Item.m_ItemCount) < a_SlotNums.size()) + { + LOGWARNING("%s: Distributing less items (%d) than slots (%u)", __FUNCTION__, (int)a_Item.m_ItemCount, a_SlotNums.size()); + // This doesn't seem to happen with the 1.5.1 client, so we don't worry about it for now + return 0; + } + + // Distribute to individual slots, keep track of how many items were actually distributed (full stacks etc.) + int NumDistributed = 0; + for (cSlotNums::const_iterator itr = a_SlotNums.begin(), end = a_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 = AtSlot.GetMaxStackSize(); + if (AtSlot.IsEmpty()) + { + // Empty, just move all of it there: + cItem ToStore(a_Item); + ToStore.m_ItemCount = std::min(a_NumToEachSlot, (int)MaxStack); + Area->SetSlot(LocalSlotNum, a_Player, ToStore); + NumDistributed += ToStore.m_ItemCount; + } + else if (AtSlot.IsStackableWith(a_Item)) + { + // Occupied, add and cap at MaxStack: + int CanStore = std::min(a_NumToEachSlot, (int)MaxStack - AtSlot.m_ItemCount); + AtSlot.m_ItemCount += CanStore; + Area->SetSlot(LocalSlotNum, a_Player, AtSlot); + NumDistributed += CanStore; + } + } // for itr - SlotNums[] + return NumDistributed; +} + + + + + +void cWindow::BroadcastSlot(cSlotArea * a_Area, int a_LocalSlotNum) +{ + // Translate local slot num into global slot num: + int SlotNum = 0; + bool HasFound = false; + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (a_Area == *itr) + { + SlotNum += a_LocalSlotNum; + HasFound = true; + break; + } + SlotNum += (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + if (!HasFound) + { + LOGWARNING("%s: Invalid slot area parameter", __FUNCTION__); + ASSERT(!"Invalid slot area"); + return; + } + + // Broadcast the update packet: + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + (*itr)->GetClientHandle()->SendInventorySlot(m_WindowID, SlotNum, *a_Area->GetSlot(a_LocalSlotNum, **itr)); + } // for itr - m_OpenedBy[] +} + + + + + +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::BroadcastProgress(int a_Progressbar, int a_Value) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + (*itr)->GetClientHandle()->SendWindowProperty(*this, a_Progressbar, a_Value); + } // for itr - m_OpenedBy[] +} + + + + + +void cWindow::SetProperty(int a_Property, int a_Value) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) + { + (*itr)->GetClientHandle()->SendWindowProperty(*this, a_Property, a_Value); + } // for itr - m_OpenedBy[] +} + + + + + +void cWindow::SetProperty(int a_Property, int a_Value, cPlayer & a_Player) +{ + a_Player.GetClientHandle()->SendWindowProperty(*this, a_Property, a_Value); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cInventoryWindow: + +cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : + cWindow(wtInventory, "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)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCraftingWindow: + +cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(wtWorkbench, "Crafting Table") +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cChestWindow: + +cChestWindow::cChestWindow(cChestEntity * a_Chest) : + cWindow(wtChest, "Chest"), + m_World(a_Chest->GetWorld()), + m_BlockX(a_Chest->GetPosX()), + m_BlockY(a_Chest->GetPosY()), + m_BlockZ(a_Chest->GetPosZ()) +{ + m_SlotAreas.push_back(new cSlotAreaChest(a_Chest, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + // Play the opening sound: + m_World->BroadcastSoundEffect("random.chestopen", m_BlockX * 8, m_BlockY * 8, m_BlockZ * 8, 1, 1); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_CHEST); +} + + + + + +cChestWindow::cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest) : + cWindow(wtChest, "Double Chest"), + m_World(a_PrimaryChest->GetWorld()), + m_BlockX(a_PrimaryChest->GetPosX()), + m_BlockY(a_PrimaryChest->GetPosY()), + m_BlockZ(a_PrimaryChest->GetPosZ()) +{ + m_SlotAreas.push_back(new cSlotAreaDoubleChest(a_PrimaryChest, a_SecondaryChest, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + + m_ShouldDistributeToHotbarFirst = false; + + // Play the opening sound: + m_World->BroadcastSoundEffect("random.chestopen", m_BlockX * 8, m_BlockY * 8, m_BlockZ * 8, 1, 1); + + // 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); + + m_World->BroadcastSoundEffect("random.chestclosed", m_BlockX * 8, m_BlockY * 8, m_BlockZ * 8, 1, 1); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cDropSpenserWindow: + +cDropSpenserWindow::cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_DropSpenser) : + cWindow(wtDropSpenser, "Dropspenser") +{ + m_ShouldDistributeToHotbarFirst = false; + m_SlotAreas.push_back(new cSlotAreaItemGrid(a_DropSpenser->GetContents(), *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHopperWindow: + +cHopperWindow::cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper) : + super(wtHopper, "Hopper") +{ + m_ShouldDistributeToHotbarFirst = false; + m_SlotAreas.push_back(new cSlotAreaItemGrid(a_Hopper->GetContents(), *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFurnaceWindow: + +cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace) : + cWindow(wtFurnace, "Furnace") +{ + m_ShouldDistributeToHotbarFirst = false; + 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/src/UI/Window.h b/src/UI/Window.h new file mode 100644 index 000000000..c44b900d7 --- /dev/null +++ b/src/UI/Window.h @@ -0,0 +1,300 @@ + +// Window.h + +// Interfaces to the cWindow class representing a UI window for a specific block + + + + + +#pragma once + +#include "../ItemGrid.h" + + + + + +class cPlayer; +class cWindowOwner; +class cClientHandle; +class cChestEntity; +class cDropSpenserEntity; +class cFurnaceEntity; +class cHopperEntity; +class cSlotArea; +class cWorld; + +typedef std::list<cPlayer *> cPlayerList; +typedef std::vector<cSlotArea *> cSlotAreas; + + + + + +// tolua_begin + +/** +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. +Inventory painting, introduced in 1.5, is handled by the window, too +*/ +class cWindow +{ +public: + enum WindowType + { + wtInventory = -1, // This value is never actually sent to a client + wtChest = 0, + wtWorkbench = 1, + wtFurnace = 2, + wtDropSpenser = 3, // Dropper or Dispenser + wtEnchantment = 4, + wtBrewery = 5, + wtNPCTrade = 6, + wtBeacon = 7, + wtAnvil = 8, + wtHopper = 9, + // Unknown: 10 + wtAnimalChest = 11, + }; + + // 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; } // tolua_export + int GetWindowType(void) const { return m_WindowType; } // tolua_export + + cWindowOwner * GetOwner(void) { return m_Owner; } + void SetOwner( cWindowOwner * a_Owner ) { m_Owner = a_Owner; } + + /// Returns the total number of slots + int GetNumSlots(void) const; + + /// Returns the number of slots, excluding the player's inventory (used for network protocols) + int GetNumNonInventorySlots(void) const { return GetNumSlots() - c_NumInventorySlots; } + + // 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); + + /// Returns true if the specified slot is in the Player Main Inventory slotarea + bool IsSlotInPlayerMainInventory(int a_SlotNum) const; + + /// Returns true if the specified slot is in the Player Hotbar slotarea + bool IsSlotInPlayerHotbar(int a_SlotNum) const; + + /// Returns true if the specified slot is in the Player Main Inventory or Hotbar slotareas. Note that returns false for Armor. + bool IsSlotInPlayerInventory(int a_SlotNum) const; + + // 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, + const cItem & a_ClickedItem + ); + + void OpenedByPlayer(cPlayer & a_Player); + + /// Called when a player closes this window; notifies all slot areas. Returns true if close accepted + virtual bool ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse); + + /// Sends the specified slot's contents to all clients of this window; the slot is specified as local in an area + void BroadcastSlot(cSlotArea * a_Area, int a_LocalSlotNum); + + /// Sends the contents of the whole window to the specified client + void SendWholeWindow(cClientHandle & a_Client); + + /// Sends the contents of the whole window to all clients of this window. + void BroadcastWholeWindow(void); + + /// Sends the progressbar to all clients of this window (same as SetProperty) + void BroadcastProgress(int a_Progressbar, int a_Value); + + // tolua_begin + + const AString & GetWindowTitle() const { return m_WindowTitle; } + void SetWindowTitle(const AString & a_WindowTitle ) { m_WindowTitle = a_WindowTitle; } + + /// Sends the UpdateWindowProperty (0x69) packet to all clients of the window + void SetProperty(int a_Property, int a_Value); + + /// Sends the UpdateWindowPropert(0x69) packet to the specified player + void SetProperty(int a_Property, int a_Value, cPlayer & a_Player); + + // tolua_end + + 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); + + /** 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); + + /// Called on DblClicking to collect all stackable items from all areas into hand, starting with the specified area. + /// The items are accumulated in a_Dragging and removed from the SlotAreas immediately. + /// If a_CollectFullStacks is false, slots with full stacks in the area are skipped while collecting. + /// Returns true if full stack has been collected, false if there's space remaining to fill. + bool CollectItemsToHand(cItem & a_Dragging, cSlotArea & a_Area, cPlayer & a_Player, bool a_CollectFullStacks); + + /// Used by cSlotAreas to send individual slots to clients, a_RelativeSlotNum is the slot number relative to a_SlotArea + void SendSlot(cPlayer & a_Player, cSlotArea * a_SlotArea, int a_RelativeSlotNum); + +protected: + cSlotAreas m_SlotAreas; + + char m_WindowID; + int m_WindowType; + AString m_WindowTitle; + + cCriticalSection m_CS; + cPlayerList m_OpenedBy; + + bool m_IsDestroyed; + bool m_ShouldDistributeToHotbarFirst; ///< If set (default), shift+click tries to distribute to hotbar first, then other areas. False for doublechests + + cWindowOwner * m_Owner; + + static char m_WindowIDCounter; + + /// Sets the internal flag as "destroyed"; notifies the owner that the window is destroying + virtual 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); + + /// Distributes a_NumToEachSlot items into the slots specified in a_SlotNums; returns the total number of items distributed + int DistributeItemToSlots(cPlayer & a_Player, const cItem & a_Item, int a_NumToEachSlot, const cSlotNums & a_SlotNums); +} ; // tolua_export + + + + + +class cCraftingWindow : + public cWindow +{ + typedef cWindow super; +public: + cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); +} ; + + + + + +class cFurnaceWindow : + public cWindow +{ + typedef cWindow super; +public: + cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace); +} ; + + + + + +class cDropSpenserWindow : + public cWindow +{ + typedef cWindow super; +public: + cDropSpenserWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserEntity * a_Dispenser); +} ; + + + + + +class cHopperWindow : + public cWindow +{ + typedef cWindow super; +public: + cHopperWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperEntity * a_Hopper); +} ; + + + + + +class cChestWindow : + public cWindow +{ +public: + cChestWindow(cChestEntity * a_Chest); + cChestWindow(cChestEntity * a_PrimaryChest, cChestEntity * a_SecondaryChest); + ~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/src/UI/WindowOwner.h b/src/UI/WindowOwner.h new file mode 100644 index 000000000..d41abf66d --- /dev/null +++ b/src/UI/WindowOwner.h @@ -0,0 +1,125 @@ + +#pragma once + +#include "../BlockEntities/BlockEntity.h" +#include "../Entities/Entity.h" +#include "Window.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)floor(m_Entity->GetPosX() + 0.5); + a_BlockY = (int)floor(m_Entity->GetPosY() + 0.5); + a_BlockZ = (int)floor(m_Entity->GetPosZ() + 0.5); + } + +private: + cEntity * m_Entity; +} ; + + + + |