diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index 52ac9b5f3..f2222dd5e 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -1040,47 +1040,23 @@ Name="UI" > - - - - - - - - - - - - diff --git a/source/BlockID.cpp b/source/BlockID.cpp index 3652938c2..9cdc05c58 100644 --- a/source/BlockID.cpp +++ b/source/BlockID.cpp @@ -251,6 +251,17 @@ AString ItemTypeToString(short a_ItemType) +AString ItemToFullString(const cItem & a_Item) +{ + AString res; + Printf(res, "%s:%d * %d", ItemToString(a_Item).c_str(), a_Item.m_ItemHealth, a_Item.m_ItemCount); + return res; +} + + + + + EMCSBiome StringToBiome(const AString & a_BiomeString) { // If it is a number, return it: diff --git a/source/BlockID.h b/source/BlockID.h index 5833a6d1d..177f2f389 100644 --- a/source/BlockID.h +++ b/source/BlockID.h @@ -637,6 +637,9 @@ extern AString ItemToString(const cItem & a_Item); // tolua_export /// Translates itemtype into a string. If the type is not recognized, the itemtype number is output into the string. extern AString ItemTypeToString(short a_ItemType); // tolua_export +/// Translates a full item into a fully-specified string (including meta and count). If the ItemType is not recognized, the ItemType number is output into the string. +extern AString ItemToFullString(const cItem & a_Item); // tolua_export + /// Translates a biome string to biome enum. Takes either a number or a biome alias (built-in). Returns -1 on failure. extern EMCSBiome StringToBiome(const AString & a_BiomeString); diff --git a/source/CraftingRecipes.cpp b/source/CraftingRecipes.cpp index 38daa1fe8..f75434e75 100644 --- a/source/CraftingRecipes.cpp +++ b/source/CraftingRecipes.cpp @@ -26,7 +26,7 @@ cCraftingGrid::cCraftingGrid(int a_Width, int a_Height) : -cCraftingGrid::cCraftingGrid(cItem * a_Items, int a_Width, int a_Height) : +cCraftingGrid::cCraftingGrid(const cItem * a_Items, int a_Width, int a_Height) : m_Width(a_Width), m_Height(a_Height), m_Items(new cItem[a_Width * a_Height]) diff --git a/source/CraftingRecipes.h b/source/CraftingRecipes.h index 25f8fdae2..74d103f4d 100644 --- a/source/CraftingRecipes.h +++ b/source/CraftingRecipes.h @@ -26,7 +26,7 @@ class cCraftingGrid // tolua_export public: cCraftingGrid(const cCraftingGrid & a_Original); cCraftingGrid(int a_Width, int a_Height); // tolua_export - cCraftingGrid(cItem * a_Items, int a_Width, int a_Height); + cCraftingGrid(const cItem * a_Items, int a_Width, int a_Height); ~cCraftingGrid(); // tolua_begin diff --git a/source/Protocol125.cpp b/source/Protocol125.cpp index adea37824..f9a13668b 100644 --- a/source/Protocol125.cpp +++ b/source/Protocol125.cpp @@ -2,6 +2,12 @@ // Protocol125.cpp // Implements the cProtocol125 class representing the release 1.2.5 protocol (#29) +/* +Documentation: + - protocol: http://wiki.vg/wiki/index.php?title=Protocol&oldid=2513 + - session handling: http://wiki.vg/wiki/index.php?title=Session&oldid=2262 + - slot format: http://wiki.vg/wiki/index.php?title=Slot_Data&oldid=2152 +*/ #include "Globals.h" @@ -14,7 +20,7 @@ #include "cPickup.h" #include "cPlayer.h" #include "cChatColor.h" -#include "cWindow.h" +#include "UI/cWindow.h" #include "cRoot.h" #include "cServer.h" @@ -713,7 +719,7 @@ void cProtocol125::SendWeather(eWeather a_Weather) void cProtocol125::SendWholeInventory(const cInventory & a_Inventory) { cCSLock Lock(m_CSPacket); - SendWholeInventory(0, a_Inventory.c_NumSlots, a_Inventory.GetSlots()); + SendWindowSlots(0, a_Inventory.c_NumSlots, a_Inventory.GetSlots()); } @@ -723,11 +729,9 @@ void cProtocol125::SendWholeInventory(const cInventory & a_Inventory) void cProtocol125::SendWholeInventory(const cWindow & a_Window) { cCSLock Lock(m_CSPacket); - SendWholeInventory( - (char)a_Window.GetWindowID(), - a_Window.GetNumSlots(), - a_Window.GetSlots() - ); + cItems Slots; + a_Window.GetSlots(*(m_Client->GetPlayer()), Slots); + SendWindowSlots(a_Window.GetWindowID(), Slots.size(), &(Slots[0])); } @@ -748,6 +752,10 @@ void cProtocol125::SendWindowClose(char a_WindowID) void cProtocol125::SendWindowOpen(char a_WindowID, char a_WindowType, const AString & a_WindowTitle, char a_NumSlots) { + LOGD("Sending a WindowOpen packet: ID = %d, Type = %d, Title = \"%s\", NumSlots = %d", + a_WindowID, a_WindowType, a_WindowTitle.c_str(), a_NumSlots + ); + if (a_WindowType < 0) { // Do not send for inventory windows @@ -1246,8 +1254,11 @@ void cProtocol125::SendPreChunk(int a_ChunkX, int a_ChunkZ, bool a_ShouldLoad) -void cProtocol125::SendWholeInventory(char a_WindowID, int a_NumItems, const cItem * a_Items) +void cProtocol125::SendWindowSlots(char a_WindowID, int a_NumItems, const cItem * a_Items) { + LOGD("Sending a InventoryWhole packet: WindowID = %d, NumItems = %d", + a_WindowID, a_NumItems + ); WriteByte (PACKET_INVENTORY_WHOLE); WriteByte (a_WindowID); WriteShort((short)a_NumItems); diff --git a/source/Protocol125.h b/source/Protocol125.h index 41bb8deb5..daa2a8c55 100644 --- a/source/Protocol125.h +++ b/source/Protocol125.h @@ -119,8 +119,8 @@ protected: /// Writes a "pre-chunk" packet void SendPreChunk(int a_ChunkX, int a_ChunkZ, bool a_ShouldLoad); - /// Writes a "whole inventory" packet with the specified params - void SendWholeInventory(char a_WindowID, int a_NumItems, const cItem * a_Items); + /// Writes a "set window items" packet with the specified params + void SendWindowSlots(char a_WindowID, int a_NumItems, const cItem * a_Items); /// Writes one item, "slot" as the protocol wiki calls it virtual void WriteItem(const cItem & a_Item); diff --git a/source/UI/SlotArea.cpp b/source/UI/SlotArea.cpp new file mode 100644 index 000000000..744492bff --- /dev/null +++ b/source/UI/SlotArea.cpp @@ -0,0 +1,569 @@ + +// SlotArea.cpp + +// Implements the cSlotArea class and its descendants + +#include "Globals.h" +#include "SlotArea.h" +#include "../cPlayer.h" +#include "../cChestEntity.h" +#include "../cFurnaceEntity.h" +#include "../Items/Item.h" +#include "cWindow.h" +#include "../CraftingRecipes.h" +#include "../cRoot.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotArea: + +cSlotArea::cSlotArea(int a_NumSlots, cWindow & a_ParentWindow) : + m_NumSlots(a_NumSlots), + m_ParentWindow(a_ParentWindow) +{ + LOGD("Created a new cSlotArea with %d slots", a_NumSlots); +} + + + + + +void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + LOGD("Slot area with %d slots clicked at slot number %d, clicked item %s, slot item %s", + GetNumSlots(), a_SlotNum, + ItemToFullString(a_ClickedItem).c_str(), + ItemToFullString(*GetSlot(a_SlotNum, a_Player)).c_str() + ); + + ASSERT((a_SlotNum >= 0) && (a_SlotNum < GetNumSlots())); + + bool bAsync = false; + if (GetSlot(a_SlotNum, a_Player) == NULL) + { + LOGWARNING("GetSlot(%d) returned NULL! Ignoring click", a_SlotNum); + return; + } + + cItem Slot(*GetSlot(a_SlotNum, a_Player)); + if (!Slot.IsEqual(a_ClickedItem)) + { + LOGD("*** Window lost sync ***"); + LOGD("My Type: %i Their Type: %i", Slot.m_ItemID, a_ClickedItem.m_ItemID); + LOGD("My Count: %i Their Count: %i", Slot.m_ItemCount, a_ClickedItem.m_ItemCount); + LOGD("My Dmg: %i Their Dmg: %i", Slot.m_ItemHealth, a_ClickedItem.m_ItemHealth); + bAsync = true; + } + cItem & DraggingItem = a_Player.GetDraggingItem(); + if (a_IsRightClick) + { + // Right clicked + if (DraggingItem.m_ItemID <= 0) // Empty-handed? + { + DraggingItem.m_ItemCount = (char)(((float)Slot.m_ItemCount) / 2.f + 0.5f); + Slot.m_ItemCount -= DraggingItem.m_ItemCount; + DraggingItem.m_ItemID = Slot.m_ItemID; + DraggingItem.m_ItemHealth = Slot.m_ItemHealth; + + if (Slot.m_ItemCount <= 0) + { + Slot.Empty(); + } + } + else if ((Slot.m_ItemID <= 0) || DraggingItem.IsEqual(Slot)) + { + // Drop one item in slot + cItemHandler * Handler = ItemHandler(Slot.m_ItemID); + if ((DraggingItem.m_ItemCount > 0) && (Slot.m_ItemCount < Handler->GetMaxStackSize())) + { + Slot.m_ItemID = DraggingItem.m_ItemID; + Slot.m_ItemCount++; + Slot.m_ItemHealth = DraggingItem.m_ItemHealth; + DraggingItem.m_ItemCount--; + } + if (DraggingItem.m_ItemCount <= 0) + { + DraggingItem.Empty(); + } + } + else if (!DraggingItem.IsEqual(Slot)) + { + // Swap contents + cItem tmp(DraggingItem); + DraggingItem = Slot; + Slot = tmp; + } + } + else + { + // Left-clicked + if (!DraggingItem.IsEqual(Slot)) + { + // Switch contents + cItem tmp(DraggingItem); + DraggingItem = Slot; + Slot = tmp; + } + else + { + // Same type, add items: + cItemHandler * Handler = ItemHandler(DraggingItem.m_ItemID); + int FreeSlots = Handler->GetMaxStackSize() - Slot.m_ItemCount; + if (FreeSlots < 0) + { + ASSERT(!"Bad item stack size - where did we get more items in a slot than allowed?"); + FreeSlots = 0; + } + int Filling = (FreeSlots > DraggingItem.m_ItemCount) ? DraggingItem.m_ItemCount : FreeSlots; + Slot.m_ItemCount += (char)Filling; + DraggingItem.m_ItemCount -= (char)Filling; + if (DraggingItem.m_ItemCount <= 0) + { + DraggingItem.Empty(); + } + } + } + + if (bAsync) + { + m_ParentWindow.BroadcastWholeWindow(); + } + SetSlot(a_SlotNum, a_Player, Slot); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaArmor: + +cSlotAreaArmor::cSlotAreaArmor(cWindow & a_ParentWindow) : + cSlotArea(4, a_ParentWindow) +{ +} + + + + + +const cItem * cSlotAreaArmor::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + // a_SlotNum ranges from 0 to 3, map that to the armor slots in player's inventory, 5 to 8: + return a_Player.GetInventory().GetSlot(a_SlotNum + 5); +} + + + + + +void cSlotAreaArmor::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + *(a_Player.GetInventory().GetSlot(a_SlotNum + 5)) = a_Item; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaChest: + +cSlotAreaChest::cSlotAreaChest(cChestEntity *a_Chest, cWindow &a_ParentWindow) : + cSlotArea(27, a_ParentWindow), + m_Chest(a_Chest) +{ +} + + + + + +const cItem * cSlotAreaChest::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + // a_SlotNum ranges from 0 to 26, use that to index the chest entity's inventory directly: + return m_Chest->GetSlot(a_SlotNum); +} + + + + + +void cSlotAreaChest::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + m_Chest->SetSlot(a_SlotNum, a_Item); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaCrafting: + +cSlotAreaCrafting::cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow) : + cSlotAreaTemporary(1 + a_GridSize * a_GridSize, a_ParentWindow), + m_GridSize(a_GridSize) +{ + ASSERT((a_GridSize == 2) || (a_GridSize == 3)); +} + + + + + +void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + // Override for craft result slot + if (a_SlotNum == 0) + { + ClickedResult(a_Player, a_IsShiftPressed); + return; + } + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + UpdateRecipe(a_Player); +} + + + + + +void cSlotAreaCrafting::OnPlayerRemoved(cPlayer & a_Player) +{ + // Toss all items on the crafting grid: + TossItems(a_Player, 1, m_NumSlots); + + // Remove the current recipe from the player -> recipe map: + for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr) + { + if (itr->first == a_Player.GetUniqueID()) + { + // Remove the player from the recipe map: + m_Recipes.erase(itr); + return; + } + } // for itr - m_Recipes[] + // Player not found - that is acceptable +} + + + + + +void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player, bool a_IsShiftPressed) +{ + const cItem * ResultSlot = GetSlot(0, a_Player); + LOGD("Clicked in craft result slot, item there: %d:%d (%d times)", + ResultSlot->m_ItemID, ResultSlot->m_ItemHealth, ResultSlot->m_ItemCount + ); + cItem & DraggingItem = a_Player.GetDraggingItem(); + + // Get the current recipe: + cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); + + cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1; + cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize); + + // If possible, craft: + if (DraggingItem.IsEmpty()) + { + DraggingItem = Recipe.GetResult(); + Recipe.ConsumeIngredients(Grid); + Grid.CopyToItems(PlayerSlots); + } + else if (DraggingItem.IsEqual(Recipe.GetResult())) + { + cItemHandler * Handler = ItemHandler(Recipe.GetResult().m_ItemID); + if (DraggingItem.m_ItemCount + Recipe.GetResult().m_ItemCount <= Handler->GetMaxStackSize()) + { + DraggingItem.m_ItemCount += Recipe.GetResult().m_ItemCount; + Recipe.ConsumeIngredients(Grid); + Grid.CopyToItems(PlayerSlots); + } + } + + // Get the new recipe and update the result slot: + UpdateRecipe(a_Player); + + // We're done. Send all changes to the client and bail out: + m_ParentWindow.BroadcastWholeWindow(); +} + + + + + +void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player) +{ + cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize); + cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); + cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); +} + + + + + +cCraftingRecipe & cSlotAreaCrafting::GetRecipeForPlayer(cPlayer & a_Player) +{ + for (cRecipeMap::iterator itr = m_Recipes.begin(), end = m_Recipes.end(); itr != end; ++itr) + { + if (itr->first == a_Player.GetUniqueID()) + { + return itr->second; + } + } // for itr - m_Recipes[] + + // Not found. Add a new one: + cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize); + cCraftingRecipe Recipe(Grid); + cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); + m_Recipes.push_back(std::make_pair(a_Player.GetUniqueID(), Recipe)); + return m_Recipes.back().second; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaFurnace: + +cSlotAreaFurnace::cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow) : + cSlotArea(3, a_ParentWindow), + m_Furnace(a_Furnace) +{ +} + + + + + +void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + cItem Fuel = *GetSlot(0, a_Player); + + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + + if (m_Furnace == NULL) + { + LOGERROR("cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + ASSERT(!"cSlotAreaFurnace::Clicked(): m_Furnace == NULL"); + return; + } + + if (Fuel.m_ItemID != GetSlot(0, a_Player)->m_ItemID) + { + m_Furnace->ResetCookTimer(); + } + + if (m_Furnace->StartCooking()) + { + m_ParentWindow.SendWholeWindow(*(a_Player.GetClientHandle())); + } +} + + + + + +const cItem * cSlotAreaFurnace::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + // a_SlotNum ranges from 0 to 2, query the items from the underlying furnace: + return m_Furnace->GetSlot(a_SlotNum); +} + + + + + +void cSlotAreaFurnace::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + m_Furnace->SetSlot(a_SlotNum, a_Item); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaInventory: + +cSlotAreaInventory::cSlotAreaInventory(cWindow & a_ParentWindow) : + cSlotArea(27 + 9, a_ParentWindow) // 27 internal slots, 9 hotbar slots +{ +} + + + + + +void cSlotAreaInventory::Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) +{ + if ((a_Player.GetGameMode() == eGameMode_Creative) && (m_ParentWindow.GetWindowType() == cWindow::Inventory)) + { + // Creative inventory must treat a_ClickedItem as a DraggedItem instead, replacing the inventory slot with it + SetSlot(a_SlotNum, a_Player, a_ClickedItem); + return; + } + + // Survival inventory and all other windows' inventory has the same handling as normal slot areas + super::Clicked(a_Player, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + return; +} + + + + + +const cItem * cSlotAreaInventory::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + // a_SlotNum ranges from 0 to 35, map that to the player's inventory slots 9 to 44 + return a_Player.GetInventory().GetSlot(a_SlotNum + 9); +} + + + + + +void cSlotAreaInventory::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + *(a_Player.GetInventory().GetSlot(a_SlotNum + 9)) = a_Item; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSlotAreaTemporary: + +cSlotAreaTemporary::cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow) : + cSlotArea(a_NumSlots, a_ParentWindow) +{ +} + + + + + +const cItem * cSlotAreaTemporary::GetSlot(int a_SlotNum, cPlayer & a_Player) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + LOGERROR("cSlotAreaTemporary: player \"%s\" not found for slot %d!", a_Player.GetName().c_str(), a_SlotNum); + ASSERT(!"cSlotAreaTemporary: player not found!"); + + // Player not found, this should not happen, ever! Return NULL, but things may break by this. + return NULL; + } + + if (a_SlotNum >= (int)(itr->second.size())) + { + LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!"); + ASSERT(!"cSlotAreaTemporary: asking for more slots than actually stored!"); + return NULL; + } + + LOGD("cSlotAreaTemporary: getting slot %d as %s", a_SlotNum, ItemToFullString(itr->second[a_SlotNum]).c_str()); + + return &(itr->second[a_SlotNum]); +} + + + + + +void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) +{ + LOGD("cSlotAreaTemporary: setting slot %d to %s", a_SlotNum, ItemToFullString(a_Item).c_str()); + + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + // Player not found + LOGWARNING("cSlotAreaTemporary: player not found!"); + return; + } + + if (a_SlotNum >= (int)(itr->second.size())) + { + LOGERROR("cSlotAreaTemporary: asking for more slots than actually stored!"); + return; + } + + itr->second[a_SlotNum] = a_Item; +} + + + + + +void cSlotAreaTemporary::OnPlayerAdded(cPlayer & a_Player) +{ + ASSERT(m_Items.find(a_Player.GetUniqueID()) == m_Items.end()); // The player shouldn't be in the itemmap, otherwise we probably have a leak + m_Items[a_Player.GetUniqueID()].resize(m_NumSlots); // Make the vector the specified size of empty items +} + + + + + +void cSlotAreaTemporary::OnPlayerRemoved(cPlayer & a_Player) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + ASSERT(itr != m_Items.end()); // The player should be in the list, otherwise a call to OnPlayerAdded() was mismatched + m_Items.erase(itr); +} + + + + + +void cSlotAreaTemporary::TossItems(cPlayer & a_Player, int a_Begin, int a_End) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + LOGWARNING("Player tossing items (%s) not found in the item map", a_Player.GetName().c_str()); + return; + } + + cItems Drops; + for (int i = a_Begin; i < a_End; i++) + { + cItem & Item = itr->second[i]; + if (!Item.IsEmpty()) + { + Drops.push_back(Item); + } + Item.Empty(); + } // for i - itr->second[] + + float vX = 0, vY = 0, vZ = 0; + EulerToVector(-a_Player.GetRotation(), a_Player.GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + a_Player.GetWorld()->SpawnItemPickups(Drops, a_Player.GetPosX(), a_Player.GetPosY() + 1.6f, a_Player.GetPosZ(), vX * 2, vY * 2, vZ * 2); +} + + + + + +cItem * cSlotAreaTemporary::GetPlayerSlots(cPlayer & a_Player) +{ + cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); + if (itr == m_Items.end()) + { + return NULL; + } + return &(itr->second[0]); +} + + + + diff --git a/source/UI/SlotArea.h b/source/UI/SlotArea.h new file mode 100644 index 000000000..0e620cd54 --- /dev/null +++ b/source/UI/SlotArea.h @@ -0,0 +1,192 @@ + +// SlotArea.h + +// Interfaces to the cSlotArea class representing a contiguous area of slots in a UI window + + + + +#pragma once + +#include "../cItem.h" + + + +class cWindow; +class cPlayer; +class cChestEntity; +class cFurnaceEntity; +class cCraftingRecipe; + + + + + +class cSlotArea +{ +public: + cSlotArea(int a_NumSlots, cWindow & a_ParentWindow); + + int GetNumSlots(void) const { return m_NumSlots; } + + /// Called to retrieve an item in the specified slot for the specified player. Must return a valid cItem. + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) = 0; + + /// Called to set an item in the specified slot for the specified player + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) = 0; + + /// Called when a player clicks in the window. Parameters taken from the click packet. + virtual void Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem); + + /// Called when a new player opens the same parent window. The window already tracks the player. CS-locked. + virtual void OnPlayerAdded(cPlayer & a_Player) {} ; + + /// Called when one of the players closes the parent window. The window already doesn't track the player. CS-locked. + virtual void OnPlayerRemoved(cPlayer & a_Player) {} ; + +protected: + int m_NumSlots; + cWindow & m_ParentWindow; +} ; + + + + + +class cSlotAreaInventory : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaInventory(cWindow & a_ParentWindow); + + // Creative inventory's click handling is somewhat different from survival inventory's, handle that here: + virtual void Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) override; + + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; +} ; + + + + + +/** A cSlotArea with items layout that is private to each player and is temporary, such as +a crafting grid or an enchantment table. +This common ancestor stores the items in a per-player map. It also implements tossing items from the map. +*/ +class cSlotAreaTemporary : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaTemporary(int a_NumSlots, cWindow & a_ParentWindow); + + // cSlotArea overrides: + virtual const cItem * GetSlot (int a_SlotNum, cPlayer & a_Player) override; + virtual void SetSlot (int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + virtual void OnPlayerAdded (cPlayer & a_Player) override; + virtual void OnPlayerRemoved(cPlayer & a_Player) override; + + /// Tosses the player's items in slots [a_Begin, a_End) (ie. incl. a_Begin, but excl. a_End) + void TossItems(cPlayer & a_Player, int a_Begin, int a_End); + +protected: + typedef std::map > cItemMap; // Maps EntityID -> items + + cItemMap m_Items; + + /// Returns the pointer to the slot array for the player specified. + cItem * GetPlayerSlots(cPlayer & a_Player); +} ; + + + + + +class cSlotAreaCrafting : + public cSlotAreaTemporary +{ + typedef cSlotAreaTemporary super; + +public: + /// a_GridSize is allowed to be only 2 or 3 + cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow); + + // cSlotAreaTemporary overrides: + virtual void Clicked (cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) override; + virtual void OnPlayerRemoved(cPlayer & a_Player) override; + +protected: + /// Maps player's EntityID -> current recipe; not a std::map because cCraftingGrid needs proper constructor params + typedef std::list > cRecipeMap; + + int m_GridSize; + cRecipeMap m_Recipes; + + /// Handles a click in the result slot. Crafts using the current recipe, if possible + void ClickedResult(cPlayer & a_Player, bool a_IsShiftPressed); + + /// Updates the current recipe and result slot based on the ingredients currently in the crafting grid of the specified player + void UpdateRecipe(cPlayer & a_Player); + + /// Retrieves the recipe for the specified player from the map, or creates one if not found + cCraftingRecipe & GetRecipeForPlayer(cPlayer & a_Player); +} ; + + + + + +class cSlotAreaChest : + public cSlotArea +{ +public: + cSlotAreaChest(cChestEntity * a_Chest, cWindow & a_ParentWindow); + + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + +protected: + cChestEntity * m_Chest; +} ; + + + + + +class cSlotAreaFurnace : + public cSlotArea +{ + typedef cSlotArea super; + +public: + cSlotAreaFurnace(cFurnaceEntity * a_Furnace, cWindow & a_ParentWindow); + + virtual void Clicked(cPlayer & a_Player, int a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_ClickedItem) override; + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; + +protected: + cFurnaceEntity * m_Furnace; +} ; + + + + + +class cSlotAreaArmor : + public cSlotArea +{ +public: + cSlotAreaArmor(cWindow & a_ParentWindow); + + virtual const cItem * GetSlot(int a_SlotNum, cPlayer & a_Player) override; + virtual void SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem & a_Item) override; +} ; + + + + diff --git a/source/UI/cWindow.cpp b/source/UI/cWindow.cpp new file mode 100644 index 000000000..475670425 --- /dev/null +++ b/source/UI/cWindow.cpp @@ -0,0 +1,385 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "cWindow.h" +#include "../cItem.h" +#include "../cClientHandle.h" +#include "../cPlayer.h" +#include "../cPickup.h" +#include "../cInventory.h" +#include "cWindowOwner.h" +#include "../items/Item.h" +#include "SlotArea.h" +#include "../cChestEntity.h" + + + + + +char cWindow::m_WindowIDCounter = 1; + + + + + +cWindow::cWindow(cWindow::WindowType a_WindowType, const AString & a_WindowTitle) + : m_WindowID(1 + (m_WindowIDCounter++ % 127)) + , m_WindowType(a_WindowType) + , m_WindowTitle(a_WindowTitle) + , m_Owner(NULL) + , m_IsDestroyed(false) +{ + if (a_WindowType == Inventory) + { + m_WindowID = 0; + } + LOGD("Created a window at %p, type = %d, ID = %i, title = \"%s\".", + this, m_WindowType, m_WindowID, m_WindowTitle.c_str() + ); +} + + + + + +cWindow::~cWindow() +{ + LOGD("Deleted a window at %p", this); +} + + + + + +int cWindow::GetNumSlots(void) const +{ + int res = 0; + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + res += (*itr)->GetNumSlots(); + } // for itr - m_SlotAreas[] + return res; +} + + + + + +void cWindow::GetSlots(cPlayer & a_Player, cItems & a_Slots) const +{ + a_Slots.clear(); + a_Slots.reserve(GetNumSlots()); + for (cSlotAreas::const_iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + int NumSlots = (*itr)->GetNumSlots(); + for (int i = 0; i < NumSlots; i++) + { + + const cItem * Item = (*itr)->GetSlot(i, a_Player); + if (Item == NULL) + { + a_Slots.push_back(cItem()); + } + else + { + a_Slots.push_back(*Item); + } + } + } // for itr - m_SlotAreas[] +} + + + + + +void cWindow::Clicked( + cPlayer & a_Player, + int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, + const cItem & a_ClickedItem +) +{ + LOGD("cWindow::Clicked(): ID %d (exp %d), SlotNum %d", a_WindowID, m_WindowID, a_SlotNum); + + if (a_WindowID != m_WindowID) + { + LOG("WRONG WINDOW ID! (exp %d, got %d) received from \"%s\"", m_WindowID, a_WindowID, a_Player.GetName().c_str()); + return; + } + + if (a_SlotNum == -999) // Outside window click + { + if (a_IsRightClick) + { + a_Player.TossItem(true); + } + else + { + a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + } + return; + } + + int LocalSlotNum = a_SlotNum; + int idx = 0; + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + if (LocalSlotNum < (*itr)->GetNumSlots()) + { + LOGD("SlotArea #%d (%d slots) handling the click", idx, (*itr)->GetNumSlots()); + (*itr)->Clicked(a_Player, LocalSlotNum, a_IsRightClick, a_IsShiftPressed, a_ClickedItem); + return; + } + LocalSlotNum -= (*itr)->GetNumSlots(); + idx++; + } + + LOGWARNING("Slot number higher than available window slots: %d, max %d received from \"%s\"; ignoring.", + a_SlotNum, GetNumSlots(), a_Player.GetName().c_str() + ); +} + + + + + +void cWindow::OpenedByPlayer(cPlayer & a_Player) +{ + { + cCSLock Lock(m_CS); + // If player is already in OpenedBy remove player first + m_OpenedBy.remove(&a_Player); + // Then add player + m_OpenedBy.push_back(&a_Player); + + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + (*itr)->OnPlayerAdded(a_Player); + } // for itr - m_SlotAreas[] + } + + // TODO: Notify all areas that a new player has opened the window + + a_Player.GetClientHandle()->SendWindowOpen(m_WindowID, m_WindowType, m_WindowTitle, GetNumSlots() - c_NumInventorySlots); +} + + + + + +void cWindow::ClosedByPlayer(cPlayer & a_Player) +{ + ASSERT(m_WindowType != Inventory); // Inventory windows must not be closed (the client would repeat the close packet, looping forever) + + // Checks whether the player is still holding an item + if (a_Player.IsDraggingItem()) + { + LOGD("Player holds item! Dropping it..."); + a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + } + + cClientHandle * ClientHandle = a_Player.GetClientHandle(); + if (ClientHandle != NULL) + { + ClientHandle->SendWindowClose(m_WindowID); + } + + { + cCSLock Lock(m_CS); + + for (cSlotAreas::iterator itr = m_SlotAreas.begin(), end = m_SlotAreas.end(); itr != end; ++itr) + { + (*itr)->OnPlayerRemoved(a_Player); + } // for itr - m_SlotAreas[] + + m_OpenedBy.remove(&a_Player); + if (m_OpenedBy.empty()) + { + Destroy(); + } + } + if (m_IsDestroyed) + { + delete this; + } +} + + + + + +void cWindow::OwnerDestroyed() +{ + m_Owner = NULL; + // Close window for each player. Note that the last one needs special handling + while (m_OpenedBy.size() > 1) + { + (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); + } + (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); +} + + + + + +bool cWindow::ForEachPlayer(cItemCallback & a_Callback) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) + { + if (a_Callback.Item(*itr)) + { + return false; + } + } // for itr - m_OpenedBy[] + return true; +} + + + + + +bool cWindow::ForEachClient(cItemCallback & a_Callback) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) + { + if (a_Callback.Item((*itr)->GetClientHandle())) + { + return false; + } + } // for itr - m_OpenedBy[] + return true; +} + + + + + +void cWindow::Destroy() +{ + LOGD("Destroying window %p (type %d)", this, m_WindowType); + if (m_Owner != NULL) + { + m_Owner->CloseWindow(); + m_Owner = NULL; + } + m_IsDestroyed = true; +} + + + + + +void cWindow::SendWholeWindow(cClientHandle & a_Client) +{ + a_Client.SendWholeInventory(*this); +} + + + + + +void cWindow::BroadcastWholeWindow(void) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + SendWholeWindow(*(*itr)->GetClientHandle()); + } // for itr - m_OpenedBy[] +} + + + + + +void cWindow::BroadcastInventoryProgress(short a_Progressbar, short a_Value) +{ + cCSLock Lock(m_CS); + for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) + { + (*itr)->GetClientHandle()->SendInventoryProgress(m_WindowID, a_Progressbar, a_Value); + } // for itr - m_OpenedBy[] +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cInventoryWindow: + +cInventoryWindow::cInventoryWindow(cPlayer & a_Player) : + cWindow(cWindow::Inventory, "MCS-Inventory"), + m_Player(a_Player) +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(2, *this)); // The creative inventory doesn't display it, but it's still counted into slot numbers + m_SlotAreas.push_back(new cSlotAreaArmor(*this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCraftingWindow: + +cCraftingWindow::cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ) : + cWindow(cWindow::Workbench, "MCS-Workbench") +{ + m_SlotAreas.push_back(new cSlotAreaCrafting(3, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cChestWindow: + +cChestWindow::cChestWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cChestEntity * a_Chest) : + cWindow(cWindow::Chest, "MCS-Chest"), + m_World(a_Chest->GetWorld()), + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ) +{ + m_SlotAreas.push_back(new cSlotAreaChest(a_Chest, *this)); + + // TODO: Double chests + + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + + // Send out the chest-open packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 1, E_BLOCK_CHEST); +} + + + + + +cChestWindow::~cChestWindow() +{ + // Send out the chest-close packet: + m_World->BroadcastBlockAction(m_BlockX, m_BlockY, m_BlockZ, 1, 0, E_BLOCK_CHEST); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFurnaceWindow: + +cFurnaceWindow::cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace) : + cWindow(cWindow::Furnace, "MCS-Furnace") +{ + m_SlotAreas.push_back(new cSlotAreaFurnace(a_Furnace, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); +} + + + + diff --git a/source/UI/cWindow.h b/source/UI/cWindow.h new file mode 100644 index 000000000..231e4fdc0 --- /dev/null +++ b/source/UI/cWindow.h @@ -0,0 +1,172 @@ + +// cWindow.h + +// Interfaces to the cWindow class representing a UI window for a specific block + + + + + +#pragma once + +#include "../cItem.h" + + + + + +class cPlayer; +class cWindowOwner; +class cClientHandle; +class cChestEntity; +class cFurnaceEntity; +class cSlotArea; +class cWorld; + +typedef std::list cPlayerList; +typedef std::vector cSlotAreas; + + + + + +/** +Represents a UI window. + +Each window has a list of players that are currently using it +When there's no player using a window, it is destroyed. +A window consists of several areas of slots with similar functionality - for example the crafting grid area, or +the inventory area. Each area knows what its slots are (GetSlot() function) and can handle mouse clicks. +The window acts only as a top-level container for those areas, redirecting the click events to the correct areas. +*/ +class cWindow +{ +public: + enum WindowType + { + Inventory = -1, // This value is never actually sent to a client + Chest = 0, + Workbench = 1, + Furnace = 2, + Dispenser = 3, + Enchantment = 4, + Brewery = 5 + }; + + static const int c_NumInventorySlots = 36; + + cWindow(WindowType a_WindowType, const AString & a_WindowTitle); + virtual ~cWindow(); + + int GetWindowID(void) const { return m_WindowID; } + int GetWindowType(void) const { return m_WindowType; } + + cWindowOwner * GetOwner() { return m_Owner; } + void SetOwner( cWindowOwner * a_Owner ) { m_Owner = a_Owner; } + + int GetNumSlots(void) const; + + /// Fills a_Slots with the slots read from m_SlotAreas[], for the specified player + void GetSlots(cPlayer & a_Player, cItems & a_Slots) const; + + void Clicked( + cPlayer & a_Player, int a_WindowID, + short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, + const cItem & a_ClickedItem + ); + + void OpenedByPlayer(cPlayer & a_Player); + void ClosedByPlayer(cPlayer & a_Player); + + void SendWholeWindow(cClientHandle & a_Client); + void BroadcastWholeWindow(void); + void BroadcastInventoryProgress(short a_Progressbar, short a_Value); + + const AString & GetWindowTitle() const { return m_WindowTitle; } + void SetWindowTitle(const AString & a_WindowTitle ) { m_WindowTitle = a_WindowTitle; } + + void OwnerDestroyed(void); + + /// Calls the callback safely for each player that has this window open; returns true if all players have been enumerated + bool ForEachPlayer(cItemCallback & a_Callback); + + /// Calls the callback safely for each client that has this window open; returns true if all clients have been enumerated + bool ForEachClient(cItemCallback & a_Callback); + +protected: + cSlotAreas m_SlotAreas; + +private: + char m_WindowID; + int m_WindowType; + AString m_WindowTitle; + + cCriticalSection m_CS; + cPlayerList m_OpenedBy; + + bool m_IsDestroyed; + + cWindowOwner * m_Owner; + + static char m_WindowIDCounter; + + void Destroy(void); +} ; + + + + + +class cCraftingWindow : + public cWindow +{ +public: + cCraftingWindow(int a_BlockX, int a_BlockY, int a_BlockZ); +} ; + + + + + +class cFurnaceWindow : + public cWindow +{ +public: + cFurnaceWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceEntity * a_Furnace); +} ; + + + + + +class cChestWindow : + public cWindow +{ +public: + cChestWindow(int a_BlockX, int a_BlockY, int a_BlockZ, cChestEntity * a_Chest); + ~cChestWindow(); + +protected: + cWorld * m_World; + int m_BlockX, m_BlockY, m_BlockZ; // Position of the chest, for the window-close packet +} ; + + + + + +class cInventoryWindow : + public cWindow +{ +public: + cInventoryWindow(cPlayer & a_Player); + +protected: + cPlayer & m_Player; + +} ; + + + + + diff --git a/source/cWindowOwner.h b/source/UI/cWindowOwner.h similarity index 73% rename from source/cWindowOwner.h rename to 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 diff --git a/source/WSSAnvil.cpp b/source/WSSAnvil.cpp index 44b5f7faa..cacd81df9 100644 --- a/source/WSSAnvil.cpp +++ b/source/WSSAnvil.cpp @@ -127,9 +127,9 @@ protected: m_Writer.BeginCompound(""); AddBasicTileEntity(a_Furnace, "Furnace"); m_Writer.BeginList("Items", TAG_Compound); - AddItem(&a_Furnace->GetSlot(0), 0); - AddItem(&a_Furnace->GetSlot(1), 1); - AddItem(&a_Furnace->GetSlot(2), 2); + AddItem(a_Furnace->GetSlot(0), 0); + AddItem(a_Furnace->GetSlot(1), 1); + AddItem(a_Furnace->GetSlot(2), 2); m_Writer.EndList(); m_Writer.AddShort("BurnTime", (Int16)(a_Furnace->GetTimeToBurn() / 50.0)); m_Writer.AddShort("CookTime", (Int16)(a_Furnace->GetTimeCooked() / 50.0)); diff --git a/source/blocks/BlockWorkbench.h b/source/blocks/BlockWorkbench.h index 6c5f0d3e8..998e07d9b 100644 --- a/source/blocks/BlockWorkbench.h +++ b/source/blocks/BlockWorkbench.h @@ -1,9 +1,14 @@ #pragma once #include "Block.h" -#include "../cCraftingWindow.h" +#include "../UI/cWindow.h" #include "../cPlayer.h" -class cBlockWorkbenchHandler : public cBlockHandler + + + + +class cBlockWorkbenchHandler: + public cBlockHandler { public: cBlockWorkbenchHandler(BLOCKTYPE a_BlockID) @@ -11,9 +16,9 @@ public: { } - virtual void OnUse(cWorld *a_World, cPlayer *a_Player, int a_X, int a_Y, int a_Z) override + virtual void OnUse(cWorld * a_World, cPlayer *a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) override { - cWindow* Window = new cCraftingWindow(0, true); + cWindow * Window = new cCraftingWindow(a_BlockX, a_BlockY, a_BlockZ); a_Player->OpenWindow(Window); } @@ -28,4 +33,4 @@ public: } -}; +}; \ No newline at end of file diff --git a/source/cChestEntity.cpp b/source/cChestEntity.cpp index 2a69c7bdb..8914f5f41 100644 --- a/source/cChestEntity.cpp +++ b/source/cChestEntity.cpp @@ -5,7 +5,7 @@ #include "cItem.h" #include "cClientHandle.h" #include "cPlayer.h" -#include "cWindow.h" +#include "UI/cWindow.h" #include "cWorld.h" #include "cRoot.h" #include "cPickup.h" @@ -88,9 +88,9 @@ const cItem * cChestEntity::GetSlot( int a_Slot ) const -void cChestEntity::SetSlot( int a_Slot, cItem & a_Item ) +void cChestEntity::SetSlot(int a_Slot, const cItem & a_Item) { - if( a_Slot > -1 && a_Slot < c_ChestHeight*c_ChestWidth ) + if ((a_Slot > -1) && (a_Slot < c_ChestHeight * c_ChestWidth)) { m_Content[a_Slot] = a_Item; } @@ -170,22 +170,18 @@ void cChestEntity::SendTo(cClientHandle & a_Client) void cChestEntity::UsedBy(cPlayer * a_Player) { - if (!GetWindow()) + if (GetWindow() == NULL) { - cWindow * Window = new cWindow(this, true, cWindow::Chest, 1); - Window->SetSlots(GetContents(), GetChestHeight() * c_ChestWidth); - Window->SetWindowTitle("UberChest"); - OpenWindow( Window ); + OpenWindow(new cChestWindow(m_PosX, m_PosY, m_PosZ, this)); } - if ( GetWindow() ) + if (GetWindow()) { if( a_Player->GetWindow() != GetWindow() ) { a_Player->OpenWindow( GetWindow() ); - GetWindow()->SendWholeWindow( a_Player->GetClientHandle() ); + GetWindow()->SendWholeWindow(*a_Player->GetClientHandle()); } } - m_World->BroadcastBlockAction(m_PosX, m_PosY, m_PosZ, 1, 1, E_BLOCK_CHEST); // This is rather a hack // Instead of marking the chunk as dirty upon chest contents change, we mark it dirty now diff --git a/source/cChestEntity.h b/source/cChestEntity.h index 1e186dc9c..962e9f86d 100644 --- a/source/cChestEntity.h +++ b/source/cChestEntity.h @@ -2,7 +2,7 @@ #pragma once #include "cBlockEntity.h" -#include "cWindowOwner.h" +#include "UI/cWindowOwner.h" @@ -22,10 +22,10 @@ class cNBTData; -class cChestEntity : //tolua_export - public cBlockEntity, //tolua_export - public cBlockEntityWindowOwner //tolua_export -{ //tolua_export +class cChestEntity : // tolua_export + public cBlockEntity, // tolua_export + public cBlockEntityWindowOwner // tolua_export +{ // tolua_export public: cChestEntity(int a_X, int a_Y, int a_Z, cWorld * a_World); virtual ~cChestEntity(); @@ -34,7 +34,7 @@ public: void HandleData( cNBTData* a_NBTData ); const cItem * GetSlot( int a_Slot ) const; //tolua_export - void SetSlot( int a_Slot, cItem & a_Item ); //tolua_export + void SetSlot(int a_Slot, const cItem & a_Item ); //tolua_export bool LoadFromJson( const Json::Value& a_Value ); virtual void SaveToJson(Json::Value& a_Value ) override; diff --git a/source/cClientHandle.cpp b/source/cClientHandle.cpp index 35e5a0ee4..7108c7711 100644 --- a/source/cClientHandle.cpp +++ b/source/cClientHandle.cpp @@ -10,8 +10,7 @@ #include "cInventory.h" #include "cChestEntity.h" #include "cSignEntity.h" -#include "cWindow.h" -#include "cCraftingWindow.h" +#include "UI/cWindow.h" #include "cItem.h" #include "cTorch.h" #include "cDoors.h" @@ -256,7 +255,7 @@ void cClientHandle::Authenticate(void) m_Protocol->SendTimeUpdate(World->GetWorldTime()); // Send inventory - m_Player->GetInventory().SendWholeInventory(this); + m_Player->GetInventory().SendWholeInventory(*this); // Send health m_Player->SendHealth(); @@ -451,14 +450,18 @@ bool cClientHandle::HandleLogin(int a_ProtocolVersion, const AString & a_Usernam void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_HeldItem) { // This is for creative Inventory changes - if (m_Player->GetGameMode() == 1) - { - m_Player->GetInventory().Clicked(a_SlotNum, false, false, a_HeldItem); - } - else + if (m_Player->GetGameMode() != eGameMode_Creative) { LOGWARNING("Got a CreativeInventoryAction packet from user \"%s\" while not in creative mode. Ignoring.", m_Username.c_str()); + return; } + if (m_Player->GetWindow()->GetWindowType() != cWindow::Inventory) + { + LOGWARNING("Got a CreativeInventoryAction packet from user \"%s\" while not in the inventory window. Ignoring.", m_Username.c_str()); + return; + } + + m_Player->GetWindow()->Clicked(*m_Player, 0, a_SlotNum, false, false, a_HeldItem); } @@ -821,11 +824,10 @@ void cClientHandle::HandleWindowClose(char a_WindowID) void cClientHandle::HandleWindowClick(char a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_HeldItem) { - if (a_WindowID == 0) - { - m_Player->GetInventory().Clicked(a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_HeldItem); - return; - } + LOGD("WindowClick: WinID %d, SlotNum %d, IsRclk %d, IsShift %d, Item %s x %d", + a_WindowID, a_SlotNum, a_IsRightClick, a_IsShiftPressed, + ItemToString(a_HeldItem).c_str(), a_HeldItem.m_ItemCount + ); cWindow * Window = m_Player->GetWindow(); if (Window == NULL) diff --git a/source/cCraftingWindow.cpp b/source/cCraftingWindow.cpp deleted file mode 100644 index fee000da3..000000000 --- a/source/cCraftingWindow.cpp +++ /dev/null @@ -1,244 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "cCraftingWindow.h" -#include "cItem.h" -#include "CraftingRecipes.h" -#include "cPlayer.h" -#include "cClientHandle.h" -#include "cInventory.h" -#include "cPickup.h" -#include "cRoot.h" -#include "cWorld.h" -#include "items/Item.h" - - - - - -/* These numbers are valid for the underlying cInventory object, because that's where we're sending the -MoveItem() and HowManyCanFit() function calls -*/ -enum -{ - SLOT_INVENTORY_MIN = 9, - SLOT_INVENTORY_MAX = 35, - SLOT_HOTBAR_MIN = 36, - SLOT_HOTBAR_MAX = 44 -} ; - - - - - -cCraftingWindow::cCraftingWindow( cWindowOwner* a_Owner, bool a_bInventoryVisible ) - : cWindow(a_Owner, a_bInventoryVisible, cWindow::Workbench, 1) -{ - cItem * Slots = new cItem[10]; - SetSlots( Slots, 10 ); -} - - - - - -void cCraftingWindow::Clicked( - cPlayer & a_Player, - int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem -) -{ - bool bDontCook = false; - - cItem * DraggingItem = GetDraggingItem(&a_Player); - if ( - a_IsShiftPressed && - ((DraggingItem == NULL) || DraggingItem->IsEmpty()) - ) - { - ShiftClicked(a_Player, a_SlotNum); - return; - } - - // Override for craft result slot - if (a_SlotNum == 0) - { - LOGD("Clicked in craft result slot, item there: %d:%d (%d times) !!", GetSlot(0)->m_ItemID, GetSlot(0)->m_ItemHealth, GetSlot(0)->m_ItemCount); - cItem * DraggingItem = GetDraggingItem(&a_Player); - if (DraggingItem->IsEmpty()) - { - *DraggingItem = *GetSlot(0); - GetSlot(0)->Empty(); - } - else if (DraggingItem->IsEqual(*GetSlot(0))) - { - cItemHandler * Handler = ItemHandler(GetSlot(0)->m_ItemID); - if (DraggingItem->m_ItemCount + GetSlot(0)->m_ItemCount <= Handler->GetMaxStackSize()) - { - DraggingItem->m_ItemCount += GetSlot(0)->m_ItemCount; - GetSlot(0)->Empty(); - } - else - { - bDontCook = true; - } - } - else - { - bDontCook = true; - } - } - else - { - cWindow::Clicked(a_Player, GetWindowID(), a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_HeldItem); - } - - if ((a_SlotNum >= 0) && (a_SlotNum < 10)) - { - cCraftingGrid Grid(GetSlots() + 1, 3, 3); - cCraftingRecipe Recipe(Grid); - - cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); - - if ((a_SlotNum == 0) && !bDontCook) - { - // Consume the items from the crafting grid: - Recipe.ConsumeIngredients(Grid); - - // Propagate grid back to m_Slots: - Grid.CopyToItems(GetSlots() + 1); - - // Get the recipe for the new grid contents: - cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); - } - *GetSlot(0) = Recipe.GetResult(); - LOGD("%s cooked: %d:%d (%d times) !!", a_Player.GetName().c_str(), GetSlot(0)->m_ItemID, GetSlot(0)->m_ItemHealth, GetSlot(0)->m_ItemCount); - } - SendWholeWindow( a_Player.GetClientHandle() ); - a_Player.GetInventory().SendWholeInventory( a_Player.GetClientHandle() ); - - // Separate packet for result =/ Don't know why - a_Player.GetClientHandle()->SendInventorySlot((char)GetWindowID(), 0, *GetSlot(0)); -} - - - - - -void cCraftingWindow::Close(cPlayer & a_Player) -{ - // Start from slot 1, don't drop what's in the result slot - cItems Drops; - for( int i = 1; i < GetNumSlots(); i++ ) - { - cItem * Item = GetSlot(i); - if (!Item->IsEmpty()) - { - Drops.push_back(*Item); - } - Item->Empty(); - } - 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); - cWindow::Close(a_Player); -} - - - - - -void cCraftingWindow::ShiftClicked(cPlayer & a_Player, short a_SlotNum) -{ - if (a_SlotNum == SLOT_CRAFTING_RESULT) - { - ShiftClickedCraftingResult(a_Player, a_SlotNum); - } - else if ((a_SlotNum >= SLOT_CRAFTING_MIN) && (a_SlotNum <= SLOT_CRAFTING_MAX)) - { - ShiftClickedCraftingGrid(a_Player, a_SlotNum); - } - else - { - // No need to handle inventory shift-click, it is handled by the underlying cSurvivalInventory, surprise surprise ;) - } - SendWholeWindow(a_Player.GetClientHandle()); -} - - - - - -void cCraftingWindow::ShiftClickedCraftingResult(cPlayer & a_Player, short a_Slot) -{ - // Craft until either the recipe changes (due to ingredients) or there's not enough storage for the result - cInventory & Inventory = a_Player.GetInventory(); - cItem * CraftingResult = GetSlot(SLOT_CRAFTING_RESULT); - if ((CraftingResult == NULL) || CraftingResult->IsEmpty()) - { - return; - } - cItem ResultCopy = *CraftingResult; - int HowManyItemsWillFit = Inventory.HowManyCanFit(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - HowManyItemsWillFit += Inventory.HowManyCanFit(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - int HowManyPassesWillFit = HowManyItemsWillFit / CraftingResult->m_ItemCount; - for (int i = 0; i < HowManyPassesWillFit; i++) - { - // First try moving into the hotbar: - int NumMoved = Inventory.MoveItem(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, CraftingResult->m_ItemCount, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - - // If something didn't fit, move into main inventory: - if (NumMoved < CraftingResult->m_ItemCount) - { - NumMoved += Inventory.MoveItem(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, CraftingResult->m_ItemCount - NumMoved, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - ASSERT(NumMoved == CraftingResult->m_ItemCount); // We checked earlier that we can fit this many items - } - - // "Use" the crafting recipe once: - cCraftingGrid Grid(GetSlots() + 1, 3, 3); - cCraftingRecipe Recipe(Grid); - cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); - Recipe.ConsumeIngredients(Grid); - Grid.CopyToItems(GetSlots() + 1); - cRoot::Get()->GetCraftingRecipes()->GetRecipe(&a_Player, Grid, Recipe); - GetSlots()[SLOT_CRAFTING_RESULT] = Recipe.GetResult(); - - // If the recipe changed, abort: - if (!Recipe.GetResult().IsEqual(ResultCopy)) - { - break; - } - } -} - - - - - -void cCraftingWindow::ShiftClickedCraftingGrid(cPlayer & a_Player, short a_Slot) -{ - cInventory & Inventory = a_Player.GetInventory(); - cItem * Item = GetSlot(a_Slot); - if ((Item == NULL) || Item->IsEmpty()) - { - return; - } - - // First try the main inventory: - Item->m_ItemCount -= Inventory.MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - - // If anything left, try the hotbar: - if (Item->m_ItemCount > 0) - { - Item->m_ItemCount -= Inventory.MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - } - if (Item->m_ItemCount == 0) - { - Item->Empty(); - } -} - - - - diff --git a/source/cCraftingWindow.h b/source/cCraftingWindow.h deleted file mode 100644 index 638b1aa53..000000000 --- a/source/cCraftingWindow.h +++ /dev/null @@ -1,43 +0,0 @@ - -#pragma once - -#include "cWindow.h" - - - - -// fwd: -class cWindowOwner; - - - - - -class cCraftingWindow : public cWindow -{ -public: - enum - { - SLOT_CRAFTING_RESULT = 0, - SLOT_CRAFTING_MIN = 1, - SLOT_CRAFTING_MAX = 9, - } ; - - cCraftingWindow(cWindowOwner * a_Owner, bool a_bInventoryVisible); - - virtual void Clicked( - cPlayer & a_Player, - int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem - ) override; - - virtual void Close(cPlayer & a_Player) override; - - void ShiftClicked(cPlayer & a_Player, short a_SlotNum); - void ShiftClickedCraftingResult(cPlayer & a_Player, short a_SlotNum); - void ShiftClickedCraftingGrid (cPlayer & a_Player, short a_SlotNum); -}; - - - - diff --git a/source/cCreativeInventory.cpp b/source/cCreativeInventory.cpp deleted file mode 100644 index e8b3a0d15..000000000 --- a/source/cCreativeInventory.cpp +++ /dev/null @@ -1,59 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "cCreativeInventory.h" -#include "cPlayer.h" -#include "cClientHandle.h" -#include "cWindow.h" -#include "cItem.h" -#include "cRoot.h" - -#include - - - - - -cCreativeInventory::cCreativeInventory(cPlayer * a_Owner) - : cInventory(a_Owner) -{ - -} - - - - - -cCreativeInventory::~cCreativeInventory() -{ -} - - - - - -void cCreativeInventory::Clicked( - short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem -) -{ - if (a_SlotNum == -1) - { - // object thrown out - m_Owner->TossItem(false, a_HeldItem.m_ItemCount, a_HeldItem.m_ItemType, a_HeldItem.m_ItemDamage); - return; - } - - if ((a_SlotNum < c_HotOffset) || (a_SlotNum >= c_NumSlots)) - { - LOG("%s: Invalid slot (%d) in cCreativeInventory::Clicked(). Ignoring...", m_Owner->GetName().c_str(), a_SlotNum); - return; - } - - m_Slots[a_SlotNum] = a_HeldItem; -} - - - - - diff --git a/source/cCreativeInventory.h b/source/cCreativeInventory.h deleted file mode 100644 index 469f0ecd6..000000000 --- a/source/cCreativeInventory.h +++ /dev/null @@ -1,22 +0,0 @@ - -#pragma once - -#include "cInventory.h" - - - - - -class cCreativeInventory - : public cInventory -{ -public: - cCreativeInventory(cPlayer * a_Owner); - ~cCreativeInventory(); - - virtual void Clicked(short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_HeldItem) override; -} ; - - - - diff --git a/source/cFurnaceEntity.cpp b/source/cFurnaceEntity.cpp index 5f0840913..0f51f4ade 100644 --- a/source/cFurnaceEntity.cpp +++ b/source/cFurnaceEntity.cpp @@ -4,7 +4,7 @@ #include "cFurnaceEntity.h" #include "BlockID.h" #include "cItem.h" -#include "cFurnaceWindow.h" +#include "UI/cWindow.h" #include "cPlayer.h" #include "cWorld.h" #include "cClientHandle.h" @@ -18,6 +18,17 @@ + +enum +{ + PROGRESSBAR_SMELTING = 0, + PROGRESSBAR_FUEL = 1, +} ; + + + + + cFurnaceEntity::cFurnaceEntity(int a_X, int a_Y, int a_Z, cWorld * a_World) : cBlockEntity( E_BLOCK_FURNACE, a_X, a_Y, a_Z, a_World ) , m_Items( new cItem[3] ) @@ -72,24 +83,20 @@ void cFurnaceEntity::Destroy() -void cFurnaceEntity::UsedBy( cPlayer * a_Player ) +void cFurnaceEntity::UsedBy(cPlayer * a_Player) { LOG("Used a furnace"); - if( !GetWindow() ) + if (GetWindow() == NULL) { - cWindow* Window = new cFurnaceWindow( this ); - Window->SetSlots( m_Items, 3 ); - Window->SetWindowTitle("UberFurnace"); - OpenWindow( Window ); + OpenWindow(new cFurnaceWindow(m_PosX, m_PosY, m_PosZ, this)); } - if( GetWindow() ) + if (GetWindow() != NULL) { - if( a_Player->GetWindow() != GetWindow() ) + if (a_Player->GetWindow() != GetWindow()) { - a_Player->OpenWindow( GetWindow() ); - - GetWindow()->SendWholeWindow( a_Player->GetClientHandle() ); + a_Player->OpenWindow(GetWindow()); + GetWindow()->SendWholeWindow(*a_Player->GetClientHandle()); } } } @@ -109,12 +116,19 @@ bool cFurnaceEntity::Tick( float a_Dt ) if (m_BurnTime <= 0) { + if (m_TimeCooked > 0) + { + // We have just finished smelting, reset the progress bar: + BroadcastProgress(PROGRESSBAR_SMELTING, 0); + m_TimeCooked = 0; + } // There is no fuel and no flame, no need to tick at all return false; } // DEBUG: LOGD("Furnace: Left: %0.1f Burned: %0.1f Burn time: %0.1f", m_CookTime - m_TimeCooked, m_TimeBurned, m_BurnTime ); + short SmeltingProgress = 0; if ((m_CookingItem != NULL) && ((m_TimeBurned < m_BurnTime) || (m_TimeCooked + a_Dt >= m_CookTime))) { if (m_CookingItem->IsEqual(m_Items[2]) || m_Items[2].IsEmpty()) @@ -140,16 +154,12 @@ bool cFurnaceEntity::Tick( float a_Dt ) m_TimeCooked -= m_CookTime; StartCooking(); } - cWindow * Window = GetWindow(); - if (Window != NULL) - { - short Value = (short)( m_TimeCooked * (180.f / m_CookTime)); - if (Value > 180) Value = 180; - if (Value < 0) Value = 0; - Window->BroadcastInventoryProgress(0, Value); - } + SmeltingProgress = (short)( m_TimeCooked * (180.f / m_CookTime)); + if (SmeltingProgress > 180) SmeltingProgress = 180; + if (SmeltingProgress < 0) SmeltingProgress = 0; } } + BroadcastProgress(PROGRESSBAR_SMELTING, SmeltingProgress); m_TimeBurned += a_Dt; @@ -163,17 +173,15 @@ bool cFurnaceEntity::Tick( float a_Dt ) Window->BroadcastWholeWindow(); } } - if (Window != NULL) + short Value = 0; + if (m_BurnTime > 0.f) { - short Value = 0; - if (m_BurnTime > 0.f) - { - Value = 250 - (short)( m_TimeBurned * (250.f / m_BurnTime)); - if (Value > 250) Value = 250; - if (Value < 0) Value = 0; - } - Window->BroadcastInventoryProgress(1, Value); + Value = 250 - (short)( m_TimeBurned * (250.f / m_BurnTime)); + if (Value > 250) Value = 250; + if (Value < 0) Value = 0; } + BroadcastProgress(PROGRESSBAR_FUEL, Value); + return ((m_CookingItem != NULL) || (m_TimeBurned < m_BurnTime)) && (m_BurnTime > 0.0); // Keep on ticking, if there's more to cook, or if it's cooking } @@ -396,3 +404,16 @@ void cFurnaceEntity::SendTo(cClientHandle & a_Client) + +void cFurnaceEntity::BroadcastProgress(int a_ProgressbarID, short a_Value) +{ + cWindow * Window = GetWindow(); + if (Window != NULL) + { + Window->BroadcastInventoryProgress(a_ProgressbarID, a_Value); + } +} + + + + diff --git a/source/cFurnaceEntity.h b/source/cFurnaceEntity.h index 953644097..43bf615a6 100644 --- a/source/cFurnaceEntity.h +++ b/source/cFurnaceEntity.h @@ -2,7 +2,7 @@ #pragma once #include "cBlockEntity.h" -#include "cWindowOwner.h" +#include "UI/cWindowOwner.h" #include "cItem.h" @@ -49,7 +49,7 @@ public: void ResetCookTimer(); - const cItem & GetSlot(int i) const { return m_Items[i]; } + const cItem * GetSlot(int i) const { return &(m_Items[i]); } void SetSlot(int a_Slot, const cItem & a_Item); @@ -69,6 +69,8 @@ private: float m_TimeCooked; // Amount of time that the current item has been cooking float m_BurnTime; // Amount of time that the current fuel can burn (in total); zero if no fuel burning float m_TimeBurned; // Amount of time that the current fuel has been burning + + void BroadcastProgress(int a_ProgressbarID, short a_Value); }; diff --git a/source/cFurnaceWindow.cpp b/source/cFurnaceWindow.cpp deleted file mode 100644 index 683bf7166..000000000 --- a/source/cFurnaceWindow.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "cFurnaceWindow.h" -#include "cItem.h" -#include "cFurnaceEntity.h" -#include "cPlayer.h" - - - - - -cFurnaceWindow::cFurnaceWindow( cFurnaceEntity* a_Owner ) - : cWindow(a_Owner, true, cWindow::Furnace, 1) - , m_Furnace( a_Owner ) -{ -} - - - - - -void cFurnaceWindow::Clicked( - cPlayer & a_Player, - int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem -) -{ - cItem Fuel = *GetSlot( 0 ); - - cWindow::Clicked(a_Player, a_WindowID, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_HeldItem); - if (m_Furnace != NULL) - { - if ((a_SlotNum >= 0) && (a_SlotNum <= 2)) // them important slots - { - if (Fuel.m_ItemID != GetSlot( 0 )->m_ItemID) - { - m_Furnace->ResetCookTimer(); - } - - if (m_Furnace->StartCooking()) - { - SendWholeWindow(a_Player.GetClientHandle()); - } - } - } -} - - - - - -void cFurnaceWindow::Close( cPlayer & a_Player ) -{ - m_Furnace = NULL; - cWindow::Close( a_Player ); -} - - - - diff --git a/source/cFurnaceWindow.h b/source/cFurnaceWindow.h deleted file mode 100644 index ccaa5a97c..000000000 --- a/source/cFurnaceWindow.h +++ /dev/null @@ -1,37 +0,0 @@ - -#pragma once - -#include "cWindow.h" - - - - - -class cFurnaceEntity; -class cWindowOwner; - - - - - -class cFurnaceWindow : - public cWindow -{ -public: - cFurnaceWindow( cFurnaceEntity* a_Owner ); - - virtual void Clicked( - cPlayer & a_Player, - int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem - ) override; - - virtual void Close( cPlayer & a_Player ) override; - -private: - cFurnaceEntity * m_Furnace; -}; - - - - diff --git a/source/cInventory.cpp b/source/cInventory.cpp index 58165ff09..1e0b4f6ae 100644 --- a/source/cInventory.cpp +++ b/source/cInventory.cpp @@ -4,7 +4,7 @@ #include "cInventory.h" #include "cPlayer.h" #include "cClientHandle.h" -#include "cWindow.h" +#include "UI/cWindow.h" #include "cItem.h" #include "cRoot.h" @@ -18,39 +18,35 @@ cInventory::~cInventory() { - delete [] m_Slots; - delete m_EquippedItem; - if( GetWindow() ) GetWindow()->Close( *m_Owner ); + /* + // TODO + cWindow wnd = GetWindow(); + if (wnd != NULL) + { + wnd->Close(*m_Owner); + } CloseWindow(); + */ } -cInventory::cInventory(cPlayer* a_Owner) +cInventory::cInventory(cPlayer & a_Owner) : + m_Owner(a_Owner) { - m_Owner = a_Owner; - - m_Slots = new cItem[c_NumSlots]; - for(unsigned int i = 0; i < c_NumSlots; i++) + for (unsigned int i = 0; i < c_NumSlots; i++) + { m_Slots[i].Empty(); + } m_CraftSlots = m_Slots + c_CraftOffset; m_ArmorSlots = m_Slots + c_ArmorOffset; m_MainSlots = m_Slots + c_MainOffset; m_HotSlots = m_Slots + c_HotOffset; - m_EquippedItem = new cItem(); - m_EquippedSlot = 0; - - if (GetWindow() == NULL) - { - cWindow * Window = new cWindow( this, false, cWindow::Inventory, 0); - Window->SetSlots(m_Slots, c_NumSlots); - Window->Open(*a_Owner); - OpenWindow(Window); - } + SetEquippedSlot(0); } @@ -97,9 +93,9 @@ bool cInventory::AddItem( cItem & a_Item ) bool cInventory::RemoveItem( cItem & a_Item ) { // First check equipped slot - if( m_EquippedSlot >= 0 && m_EquippedSlot < 9 ) + if ((m_EquippedSlot >= 0) && (m_EquippedSlot < 9)) { - if( m_HotSlots[m_EquippedSlot].m_ItemID == a_Item.m_ItemID ) + if (m_HotSlots[m_EquippedSlot].m_ItemID == a_Item.m_ItemID) { cItem & Item = m_HotSlots[m_EquippedSlot]; if(Item.m_ItemCount > a_Item.m_ItemCount) @@ -118,7 +114,7 @@ bool cInventory::RemoveItem( cItem & a_Item ) } // Then check other slotz - if( a_Item.m_ItemCount > 0 ) + if (a_Item.m_ItemCount > 0) { for(int i = 0; i < 36; i++) { @@ -141,10 +137,7 @@ bool cInventory::RemoveItem( cItem & a_Item ) } } - if( a_Item.m_ItemCount == 0 ) - return true; - else - return false; + return (a_Item.m_ItemCount == 0); } @@ -221,10 +214,17 @@ cItem* cInventory::GetFromHotBar( int a_SlotNum ) -void cInventory::SetEquippedSlot( int a_SlotNum ) +void cInventory::SetEquippedSlot(int a_SlotNum) { - if( a_SlotNum < 0 || a_SlotNum >= 9 ) m_EquippedSlot = 0; - else m_EquippedSlot = (short)a_SlotNum; + if ((a_SlotNum < 0) || (a_SlotNum >= 9)) + { + m_EquippedSlot = 0; + } + else + { + m_EquippedSlot = (short)a_SlotNum; + } + m_EquippedItem = GetFromHotBar(m_EquippedSlot); } @@ -259,36 +259,9 @@ const cItem & cInventory::GetEquippedItem(void) const -void cInventory::SendWholeInventory(cClientHandle * a_Client) +void cInventory::SendWholeInventory(cClientHandle & a_Client) { - a_Client->SendWholeInventory(*this); -} - - - - - -void cInventory::SendWholeInventoryToAll(void) -{ - cWindow * Window = GetWindow(); - if (Window == NULL) - { - return; - } - - class cSender : - public cItemCallback - { - cInventory * m_Inventory; - public: - cSender(cInventory * a_Inventory) : m_Inventory(a_Inventory) {} - virtual bool Item(cClientHandle * a_Client) override - { - m_Inventory->SendWholeInventory(a_Client); - return false; - } - } Sender(this); - Window->ForEachClient(Sender); + a_Client.SendWholeInventory(*this); } @@ -305,7 +278,7 @@ void cInventory::SendSlot(int a_SlotNum) // Sanitize items that are not completely empty (ie. count == 0, but type != empty) Item->Empty(); } - m_Owner->GetClientHandle()->SendInventorySlot(0, a_SlotNum, *Item); + m_Owner.GetClientHandle()->SendInventorySlot(0, a_SlotNum, *Item); } } @@ -437,6 +410,10 @@ void cInventory::SaveToJson(Json::Value & a_Value) bool cInventory::LoadFromJson(Json::Value & a_Value) { int SlotIdx = 0; + + // TODO: Limit the number of slots written to the actual number of slots, + // otherwise an invalid json will crash the server! + for( Json::Value::iterator itr = a_Value.begin(); itr != a_Value.end(); ++itr ) { m_Slots[SlotIdx].FromJson( *itr ); diff --git a/source/cInventory.h b/source/cInventory.h index 96d744baf..bce485852 100644 --- a/source/cInventory.h +++ b/source/cInventory.h @@ -1,7 +1,7 @@ #pragma once -#include "cWindowOwner.h" +#include "cItem.h" @@ -12,7 +12,6 @@ namespace Json class Value; }; -class cItem; class cClientHandle; class cPlayer; @@ -21,10 +20,9 @@ class cPlayer; class cInventory //tolua_export - : public cWindowOwner { //tolua_export public: - cInventory(cPlayer* a_Owner); + cInventory(cPlayer & a_Owner); ~cInventory(); void Clear(); //tolua_export @@ -38,19 +36,17 @@ public: void SaveToJson(Json::Value & a_Value); bool LoadFromJson(Json::Value & a_Value); - void SendWholeInventory( cClientHandle* a_Client ); - void SendWholeInventoryToAll(void); + void SendWholeInventory(cClientHandle & a_Client); - cItem* GetSlot( int a_SlotNum ); //tolua_export - cItem* GetSlots() const { return m_Slots; } - cItem* GetFromHotBar( int a_SlotNum ); //tolua_export + cItem * GetSlot(int a_SlotNum ); //tolua_export + cItem * GetSlots(void) { return m_Slots; } + const cItem * GetSlots(void) const { return m_Slots; } + cItem * GetFromHotBar(int a_HotBarSlotNum); //tolua_export cItem & GetEquippedItem(void); //tolua_export const cItem & GetEquippedItem(void) const; - void SetEquippedSlot( int a_SlotNum ); //tolua_export - short GetEquippedSlot() { return m_EquippedSlot; } //tolua_export - - virtual void Clicked(short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_HeldItem) = 0; + void SetEquippedSlot(int a_SlotNum); //tolua_export + short GetEquippedSlot(void) { return m_EquippedSlot; } //tolua_export void SendSlot( int a_SlotNum ); //tolua_export @@ -74,19 +70,17 @@ public: protected: bool AddToBar( cItem & a_Item, const int a_Offset, const int a_Size, bool* a_bChangedSlots, int a_Mode = 0 ); - cItem* m_Slots; - cItem* m_MainSlots; - cItem* m_CraftSlots; - cItem* m_ArmorSlots; - cItem* m_HotSlots; + cItem m_Slots[c_NumSlots]; + + cItem * m_MainSlots; + cItem * m_CraftSlots; + cItem * m_ArmorSlots; + cItem * m_HotSlots; - cItem* m_EquippedItem; + cItem * m_EquippedItem; short m_EquippedSlot; - cPlayer* m_Owner; - - // cWindowOwner override: - virtual void GetBlockPos(int & a_BlockX, int & a_BlockY, int & a_BlockZ) override {} // UNUSED for an inventory + cPlayer & m_Owner; }; //tolua_export diff --git a/source/cPlayer.cpp b/source/cPlayer.cpp index 4095c633a..1d2774dbf 100644 --- a/source/cPlayer.cpp +++ b/source/cPlayer.cpp @@ -3,13 +3,12 @@ #include "cPlayer.h" #include "cServer.h" -#include "cCreativeInventory.h" -#include "cSurvivalInventory.h" #include "cClientHandle.h" +#include "UI/cWindow.h" +#include "UI/cWindowOwner.h" #include "cWorld.h" #include "cPickup.h" #include "cPluginManager.h" -#include "cWindow.h" #include "cBlockEntity.h" #include "cGroupManager.h" #include "cGroup.h" @@ -48,8 +47,9 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_LastGroundHeight( 0 ) , m_bTouchGround( false ) , m_Stance( 0.0 ) - , m_Inventory( 0 ) - , m_CurrentWindow( 0 ) + , m_Inventory(*this) + , m_CurrentWindow(NULL) + , m_InventoryWindow(NULL) , m_TimeLastPickupCheck( 0.f ) , m_Color('-') , m_ClientHandle( a_Client ) @@ -61,6 +61,9 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) this, GetUniqueID() ); m_EntityType = eEntityType_Player; + + m_InventoryWindow = new cInventoryWindow(*this); + m_CurrentWindow = m_InventoryWindow; SetMaxHealth(20); m_MaxFoodLevel = 20; @@ -69,8 +72,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) m_FoodLevel = m_MaxFoodLevel; m_FoodSaturationLevel = 5.f; - m_Inventory = new cSurvivalInventory( this ); - m_CreativeInventory = new cCreativeInventory(this); cTimer t1; m_LastPlayerListTime = t1.GetNowTime(); @@ -80,10 +81,9 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) m_PlayerName = a_PlayerName; m_bDirtyPosition = true; // So chunks are streamed to player at spawn - if( !LoadFromDisk() ) + if (!LoadFromDisk()) { - m_Inventory->Clear(); - m_CreativeInventory->Clear(); + m_Inventory.Clear(); m_Pos.x = cRoot::Get()->GetDefaultWorld()->GetSpawnX(); m_Pos.y = cRoot::Get()->GetDefaultWorld()->GetSpawnY(); m_Pos.z = cRoot::Get()->GetDefaultWorld()->GetSpawnZ(); @@ -110,11 +110,6 @@ cPlayer::~cPlayer(void) m_ClientHandle = NULL; - delete m_Inventory; - m_Inventory = NULL; - - delete m_CreativeInventory; - LOG("Player %p deleted", this); } @@ -373,9 +368,9 @@ void cPlayer::KilledBy(cEntity * a_Killer) m_bVisible = false; // So new clients don't see the player // Puke out all the items - cItem* Items = m_Inventory->GetSlots(); + cItem * Items = m_Inventory.GetSlots(); cItems Pickups; - for (unsigned int i = 1; i < m_Inventory->c_NumSlots; ++i) + for (unsigned int i = 1; i < m_Inventory.c_NumSlots; ++i) { if( !Items[i].IsEmpty() ) { @@ -419,10 +414,14 @@ Vector3d cPlayer::GetEyePosition() return Vector3d( m_Pos.x, m_Stance, m_Pos.z ); } + + + + void cPlayer::OpenWindow( cWindow* a_Window ) { CloseWindow(m_CurrentWindow ? (char)m_CurrentWindow->GetWindowType() : 0); - a_Window->Open( *this ); + a_Window->OpenedByPlayer(*this); m_CurrentWindow = a_Window; } @@ -432,38 +431,15 @@ void cPlayer::OpenWindow( cWindow* a_Window ) void cPlayer::CloseWindow(char a_WindowType) { - if (a_WindowType == 0) + if (m_CurrentWindow == m_InventoryWindow) { - // Inventory - if ( - (m_Inventory->GetWindow()->GetDraggingItem() != NULL) && - (m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount > 0) - ) - { - LOGD("Player holds item! Dropping it..."); - TossItem( true, m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount ); - } - - //Drop whats in the crafting slots (1, 2, 3, 4) - cItems Drops; - for (int i = 1; i <= 4; i++) - { - cItem* Item = m_Inventory->GetSlot( i ); - if (!Item->IsEmpty()) - { - Drops.push_back(*Item); - } - Item->Empty(); - } - float vX = 0, vY = 0, vZ = 0; - EulerToVector(-GetRotation(), GetPitch(), vZ, vX, vY); - vY = -vY*2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY() + 1.6f, GetPosZ(), vX * 2, vY * 2, vZ * 2); + // The inventory window must not be closed and must not be even sent a close packet + return; } if (m_CurrentWindow != NULL) { - // FIXME: If the player entity is destroyed while having a chest window open, the chest will not close + // TODO: This code should be in cChestWindow instead if ((a_WindowType == 1) && (m_CurrentWindow->GetWindowType() == cWindow::Chest)) { int x, y, z; @@ -471,9 +447,9 @@ void cPlayer::CloseWindow(char a_WindowType) m_World->BroadcastBlockAction(x, y, z, 1, 0, E_BLOCK_CHEST); } - m_CurrentWindow->Close( *this ); + m_CurrentWindow->ClosedByPlayer(*this); } - m_CurrentWindow = NULL; + m_CurrentWindow = m_InventoryWindow; } @@ -515,19 +491,8 @@ void cPlayer::SetGameMode(eGameMode a_GameMode) return; } - short OldSlotNum = 0; - if (m_GameMode == eGameMode_Survival) - { - OldSlotNum = m_Inventory->GetEquippedSlot(); - } - else - { - OldSlotNum = m_CreativeInventory->GetEquippedSlot(); - } m_GameMode = a_GameMode; m_ClientHandle->SendGameMode(a_GameMode); - GetInventory().SendWholeInventory(m_ClientHandle); - GetInventory().SetEquippedSlot(OldSlotNum); } @@ -785,14 +750,18 @@ void cPlayer::TossItem( // Drop an item from the inventory: if (a_bDraggingItem) { - cItem * Item = GetInventory().GetWindow()->GetDraggingItem(); - if (!Item->IsEmpty()) + cItem & Item = GetDraggingItem(); + if (!Item.IsEmpty()) { - Drops.push_back(*Item); - if( Item->m_ItemCount > a_Amount ) - Item->m_ItemCount -= (char)a_Amount; + Drops.push_back(Item); + if (Item.m_ItemCount > a_Amount) + { + Item.m_ItemCount -= (char)a_Amount; + } else - Item->Empty(); + { + Item.Empty(); + } } } else @@ -917,7 +886,7 @@ bool cPlayer::LoadFromDisk() } Json::Value & JSON_PlayerPosition = root["position"]; - if( JSON_PlayerPosition.size() == 3 ) + if (JSON_PlayerPosition.size() == 3) { m_Pos.x = JSON_PlayerPosition[(unsigned int)0].asDouble(); m_Pos.y = JSON_PlayerPosition[(unsigned int)1].asDouble(); @@ -925,7 +894,7 @@ bool cPlayer::LoadFromDisk() } Json::Value & JSON_PlayerRotation = root["rotation"]; - if( JSON_PlayerRotation.size() == 3 ) + if (JSON_PlayerRotation.size() == 3) { m_Rot.x = (float)JSON_PlayerRotation[(unsigned int)0].asDouble(); m_Rot.y = (float)JSON_PlayerRotation[(unsigned int)1].asDouble(); @@ -938,9 +907,7 @@ bool cPlayer::LoadFromDisk() m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); - - m_Inventory->LoadFromJson(root["inventory"]); - m_CreativeInventory->LoadFromJson(root["creativeinventory"]); + m_Inventory.LoadFromJson(root["inventory"]); m_LoadedWorldName = root.get("world", "world").asString(); @@ -971,25 +938,23 @@ bool cPlayer::SaveToDisk() JSON_PlayerRotation.append( Json::Value( m_Rot.z ) ); Json::Value JSON_Inventory; - m_Inventory->SaveToJson( JSON_Inventory ); - - Json::Value JSON_CreativeInventory; - m_CreativeInventory->SaveToJson( JSON_CreativeInventory ); + m_Inventory.SaveToJson( JSON_Inventory ); Json::Value root; root["position"] = JSON_PlayerPosition; root["rotation"] = JSON_PlayerRotation; root["inventory"] = JSON_Inventory; - root["creativeinventory"] = JSON_CreativeInventory; root["health"] = m_Health; root["food"] = m_FoodLevel; root["foodSaturation"] = m_FoodSaturationLevel; root["world"] = GetWorld()->GetName(); - if(m_GameMode == GetWorld()->GetGameMode()) + if (m_GameMode == GetWorld()->GetGameMode()) { root["gamemode"] = (int) eGameMode_NotSet; - }else{ + } + else + { root["gamemode"] = (int) m_GameMode; } diff --git a/source/cPlayer.h b/source/cPlayer.h index 8d652120a..d5b63941c 100644 --- a/source/cPlayer.h +++ b/source/cPlayer.h @@ -2,8 +2,7 @@ #pragma once #include "cPawn.h" -#include "cSurvivalInventory.h" -#include "cCreativeInventory.h" +#include "cInventory.h" #include "Defines.h" @@ -12,7 +11,6 @@ class cGroup; class cWindow; -class cInventory; class cClientHandle; @@ -43,8 +41,8 @@ public: inline bool GetFlying() { return m_bTouchGround; } //tolua_export inline bool IsOnGround(void) const {return m_bTouchGround; } // tolua_export inline const double GetStance(void) const { return m_Pos.y + 1.62; } //tolua_export // TODO: Proper stance when crouching etc. - inline cInventory & GetInventory(void) { if (GetGameMode() == eGameMode_Survival) return *m_Inventory; else return *m_CreativeInventory; } //tolua_export - inline const cInventory & GetInventory(void) const { if (GetGameMode() == eGameMode_Survival) return *m_Inventory; else return *m_CreativeInventory; } + inline cInventory & GetInventory(void) { return m_Inventory; } //tolua_export + inline const cInventory & GetInventory(void) const { return m_Inventory; } inline const cItem & GetEquippedItem(void) const {return GetInventory().GetEquippedItem(); } @@ -118,6 +116,10 @@ public: void UseEquippedItem(void); void SendHealth(); + + // In UI windows, the item that the player is dragging: + bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); } + cItem & GetDraggingItem(void) {return m_DraggingItem; } protected: virtual void Destroyed(); @@ -144,9 +146,9 @@ protected: float m_LastGroundHeight; bool m_bTouchGround; double m_Stance; - cSurvivalInventory* m_Inventory; - cCreativeInventory* m_CreativeInventory; - cWindow* m_CurrentWindow; + cInventory m_Inventory; + cWindow * m_CurrentWindow; + cWindow * m_InventoryWindow; float m_TimeLastPickupCheck; @@ -159,6 +161,8 @@ protected: int m_LastBlockActionCnt; eGameMode m_GameMode; std::string m_IP; + + cItem m_DraggingItem; long long m_LastPlayerListTime; static const unsigned short PLAYER_LIST_TIME_MS = 1000; // 1000 = once per second diff --git a/source/cRoot.cpp b/source/cRoot.cpp index 4c87210c7..6cd696da8 100644 --- a/source/cRoot.cpp +++ b/source/cRoot.cpp @@ -10,8 +10,6 @@ #include "CraftingRecipes.h" #include "cPluginManager.h" #include "cMonsterConfig.h" -#include "cSleep.h" -#include "cThread.h" #include "cFileFormatUpdater.h" #include "cRedstone.h" #include "cPlayer.h" diff --git a/source/cServer.cpp b/source/cServer.cpp index 9653a2542..a4e2607a5 100644 --- a/source/cServer.cpp +++ b/source/cServer.cpp @@ -5,7 +5,6 @@ #include "cServer.h" #include "cClientHandle.h" -#include "cSleep.h" #include "cTimer.h" #include "cMonster.h" #include "cSocket.h" diff --git a/source/cSurvivalInventory.cpp b/source/cSurvivalInventory.cpp deleted file mode 100644 index 3786f86fa..000000000 --- a/source/cSurvivalInventory.cpp +++ /dev/null @@ -1,285 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "cSurvivalInventory.h" -#include "cPlayer.h" -#include "cClientHandle.h" -#include "cWindow.h" -#include "cItem.h" -#include "CraftingRecipes.h" -#include "cRoot.h" -#include "items/Item.h" - - - - - -cSurvivalInventory::cSurvivalInventory(cPlayer* a_Owner) - : cInventory(a_Owner) -{ -} - - - - - -cSurvivalInventory::~cSurvivalInventory() -{ -} - - - - - -void cSurvivalInventory::Clicked( - short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem -) -{ - cWindow * Window = GetWindow(); - if ( - a_IsShiftPressed && // Shift pressed - (Window != NULL) && // Window is valid - (Window->GetDraggingItem()->IsEmpty()) // Not dragging anything - ) - { - ShiftClicked(a_SlotNum); - return; - } - - bool bDontCook = false; - if (Window != NULL) - { - // Override for craft result slot - if (a_SlotNum == (short)c_CraftOffset) - { - LOGD("Clicked in craft result slot, item there: %d:%d (%d times) !!", m_Slots[c_CraftOffset].m_ItemID, m_Slots[c_CraftOffset].m_ItemHealth, m_Slots[c_CraftOffset].m_ItemCount); - cItem * DraggingItem = Window->GetDraggingItem(); - if (DraggingItem->IsEmpty()) - { - *DraggingItem = m_Slots[c_CraftOffset]; - m_Slots[c_CraftOffset].Empty(); - } - else if (DraggingItem->IsEqual(m_Slots[c_CraftOffset])) - { - cItemHandler * Handler = ItemHandler(m_Slots[c_CraftOffset].m_ItemID); - if (DraggingItem->m_ItemCount + m_Slots[c_CraftOffset].m_ItemCount <= Handler->GetMaxStackSize()) - { - DraggingItem->m_ItemCount += m_Slots[c_CraftOffset].m_ItemCount; - m_Slots[c_CraftOffset].Empty(); - } - else - { - bDontCook = true; - } - } - else - { - bDontCook = true; - } - } - else - { - Window->Clicked(*m_Owner, 0, a_SlotNum, a_IsRightClick, a_IsShiftPressed, a_HeldItem); - } - } - else - { - ASSERT(!"No inventory window! WTF?"); - LOG("No Inventory window! WTF"); - } - - if ((a_SlotNum >= (short)c_CraftOffset) && (a_SlotNum < (short)(c_CraftOffset + c_CraftSlots + 1))) - { - cCraftingGrid Grid(m_Slots + c_CraftOffset + 1, 2, 2); - cCraftingRecipe Recipe(Grid); - - cRoot::Get()->GetCraftingRecipes()->GetRecipe(m_Owner, Grid, Recipe); - - if ((a_SlotNum == 0) && !bDontCook) - { - // Consume the items from the crafting grid: - Recipe.ConsumeIngredients(Grid); - - // Propagate grid back to m_Slots: - Grid.CopyToItems(m_Slots + c_CraftOffset + 1); - - // Get the recipe for the new grid contents: - cRoot::Get()->GetCraftingRecipes()->GetRecipe(m_Owner, Grid, Recipe); - } - m_Slots[c_CraftOffset] = Recipe.GetResult(); - LOGD("%s cooked: %d:%d (%d times) !!", m_Owner->GetName().c_str(), m_Slots[c_CraftOffset].m_ItemID, m_Slots[c_CraftOffset].m_ItemHealth, m_Slots[c_CraftOffset].m_ItemCount ); - SendWholeInventory(m_Owner->GetClientHandle()); - } - SendSlot(0); -} - - - - - -void cSurvivalInventory::ShiftClicked(short a_SlotNum) -{ - ASSERT((GetWindow() == NULL) || (GetWindow()->GetDraggingItem()->IsEmpty())); // Cannot handle shift-click if dragging something - - if (a_SlotNum == SLOT_CRAFTING_RESULT) - { - ShiftClickedCraftingResult(a_SlotNum); - } - else if ((a_SlotNum >= SLOT_CRAFTING_MIN) && (a_SlotNum <= SLOT_CRAFTING_MAX)) - { - ShiftClickedCraftingGrid(a_SlotNum); - } - else if ((a_SlotNum >= SLOT_ARMOR_MIN) && (a_SlotNum <= SLOT_ARMOR_MAX)) - { - ShiftClickedArmor(a_SlotNum); - } - else if ((a_SlotNum >= SLOT_HOTBAR_MIN) && (a_SlotNum <= SLOT_HOTBAR_MAX)) - { - ShiftClickedHotbar(a_SlotNum); - } - else - { - ShiftClickedInventory(a_SlotNum); - } - // Because the client tries to guess our actions and is not always right, send the whole inventory: - SendWholeInventoryToAll(); -} - - - - - -void cSurvivalInventory::ShiftClickedCraftingResult(short a_Slot) -{ - // Craft until either the recipe changes (due to ingredients) or there's not enough storage for the result - cItem * CraftingResult = GetSlot(SLOT_CRAFTING_RESULT); - if ((CraftingResult == NULL) || CraftingResult->IsEmpty()) - { - return; - } - cItem ResultCopy = *CraftingResult; - int HowManyItemsWillFit = HowManyCanFit(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - HowManyItemsWillFit += HowManyCanFit(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - int HowManyPassesWillFit = HowManyItemsWillFit / CraftingResult->m_ItemCount; - for (int i = 0; i < HowManyPassesWillFit; i++) - { - // First try moving into the hotbar: - int NumMoved = MoveItem(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, CraftingResult->m_ItemCount, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - - // If something didn't fit, move into main inventory: - if (NumMoved < CraftingResult->m_ItemCount) - { - MoveItem(CraftingResult->m_ItemID, CraftingResult->m_ItemHealth, CraftingResult->m_ItemCount - NumMoved, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - } - - // "Use" the crafting recipe once: - cCraftingGrid Grid(m_Slots + SLOT_CRAFTING_MIN, 2, 2); - cCraftingRecipe Recipe(Grid); - cRoot::Get()->GetCraftingRecipes()->GetRecipe(m_Owner, Grid, Recipe); - Recipe.ConsumeIngredients(Grid); - Grid.CopyToItems(m_Slots + c_CraftOffset + 1); - cRoot::Get()->GetCraftingRecipes()->GetRecipe(m_Owner, Grid, Recipe); - m_Slots[SLOT_CRAFTING_RESULT] = Recipe.GetResult(); - - // If the recipe changed, abort: - if (!Recipe.GetResult().IsEqual(ResultCopy)) - { - break; - } - } -} - - - - - -void cSurvivalInventory::ShiftClickedCraftingGrid(short a_Slot) -{ - // Move the item from the crafting grid into the main inventory: - cItem * Item = GetSlot(a_Slot); - if ((Item == NULL) || Item->IsEmpty()) - { - return; - } - // First try the main inventory: - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - - // If anything left, try the hotbar: - if (Item->m_ItemCount > 0) - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - } - SendSlot(a_Slot); -} - - - - - -void cSurvivalInventory::ShiftClickedArmor(short a_Slot) -{ - // Move the item from the armor slot into the main inventory: - cItem * Item = GetSlot(a_Slot); - if ((Item == NULL) || Item->IsEmpty()) - { - return; - } - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - SendSlot(a_Slot); -} - - - - - -void cSurvivalInventory::ShiftClickedHotbar(short a_Slot) -{ - // Move the item from the hotbar into the main inventory: - cItem * Item = GetSlot(a_Slot); - if ((Item == NULL) || Item->IsEmpty()) - { - return; - } - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_INVENTORY_MIN, SLOT_INVENTORY_MAX); - SendSlot(a_Slot); -} - - - - - -void cSurvivalInventory::ShiftClickedInventory(short a_Slot) -{ - // Move the item from the main inventory into armor slot if it is armor, or the hotbar otherwise: - cItem * Item = GetSlot(a_Slot); - if ((Item == NULL) || Item->IsEmpty()) - { - return; - } - if (ItemCategory::IsHelmet(Item->m_ItemID)) - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_ARMOR_HELMET, SLOT_ARMOR_HELMET); - } - else if (ItemCategory::IsChestPlate(Item->m_ItemID)) - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_ARMOR_CHESTPLATE, SLOT_ARMOR_CHESTPLATE); - } - else if (ItemCategory::IsLeggings(Item->m_ItemID)) - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_ARMOR_LEGGINGS, SLOT_ARMOR_LEGGINGS); - } - else if (ItemCategory::IsBoots(Item->m_ItemID)) - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_ARMOR_BOOTS, SLOT_ARMOR_BOOTS); - } - else - { - Item->m_ItemCount -= MoveItem(Item->m_ItemID, Item->m_ItemHealth, Item->m_ItemCount, SLOT_HOTBAR_MIN, SLOT_HOTBAR_MAX); - } - SendSlot(a_Slot); -} - - - - diff --git a/source/cSurvivalInventory.h b/source/cSurvivalInventory.h deleted file mode 100644 index 3ad385fcf..000000000 --- a/source/cSurvivalInventory.h +++ /dev/null @@ -1,48 +0,0 @@ - -#pragma once - -#include "cInventory.h" - - - - - -class cSurvivalInventory //tolua_export - : public cInventory -{ //tolua_export - - enum - { - SLOT_CRAFTING_RESULT = 0, - SLOT_CRAFTING_MIN = 1, - SLOT_CRAFTING_MAX = 4, - SLOT_ARMOR_MIN = 5, - SLOT_ARMOR_HELMET = 5, - SLOT_ARMOR_CHESTPLATE = 6, - SLOT_ARMOR_LEGGINGS = 7, - SLOT_ARMOR_BOOTS = 8, - SLOT_ARMOR_MAX = 8, - SLOT_INVENTORY_MIN = 9, - SLOT_INVENTORY_MAX = 35, - SLOT_HOTBAR_MIN = 36, - SLOT_HOTBAR_MAX = 44, - } ; - - void ShiftClickedCraftingResult(short a_SlotNum); - void ShiftClickedCraftingGrid (short a_SlotNum); - void ShiftClickedArmor (short a_Slot); - void ShiftClickedHotbar (short a_Slot); - void ShiftClickedInventory (short a_Slot); - -public: - cSurvivalInventory(cPlayer* a_Owner); - ~cSurvivalInventory(); - - virtual void Clicked(short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, const cItem & a_HeldItem) override; - - void ShiftClicked(short a_SlotNum); -}; //tolua_export - - - - diff --git a/source/cWindow.cpp b/source/cWindow.cpp deleted file mode 100644 index 5278122d5..000000000 --- a/source/cWindow.cpp +++ /dev/null @@ -1,374 +0,0 @@ - -#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" - - - - - -cWindow::cWindow( cWindowOwner* a_Owner, bool a_bInventoryVisible, cWindow::WindowType a_WindowType, int a_WindowID) - : m_WindowID( a_WindowID ) - , m_WindowType( a_WindowType ) - , m_Owner( a_Owner ) - , m_bInventoryVisible( a_bInventoryVisible ) - , m_NumSlots( 0 ) - , m_Slots( 0 ) - , m_DraggingItem( 0 ) - , m_IsDestroyed(false) -{ - LOGD("Created a window at %p, type = %d, ID = %i", this, a_WindowType, a_WindowID); - if (!m_bInventoryVisible) - { - m_DraggingItem = new cItem(); - } -} - - - - - -cWindow::~cWindow() -{ - LOGD("Deleting a window at %p", this); - if( !m_bInventoryVisible && m_DraggingItem ) - { - delete m_DraggingItem; - m_DraggingItem = 0; - } - LOGD("Deleted a window at %p", this); -} - - - - - -cItem* cWindow::GetSlot( int a_Slot ) -{ - if(a_Slot > -1 && a_Slot < m_NumSlots) - { - return (m_Slots + a_Slot); - } - return 0; -} - - - - - -cItem* cWindow::GetDraggingItem( cPlayer * a_Player /* = 0 */ ) -{ - if( m_bInventoryVisible && a_Player ) - { - cWindow* Window = a_Player->GetInventory().GetWindow(); - if( Window ) - { - return Window->GetDraggingItem(); - } - } - return m_DraggingItem; -} - - - - - -void cWindow::Clicked( - cPlayer & a_Player, - int a_WindowID, short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem -) -{ - 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 (m_bInventoryVisible) - { - cWindow * Window = a_Player.GetInventory().GetWindow(); - if (Window != NULL) - { - m_DraggingItem = Window->GetDraggingItem(); - } - } - bool bAsync = false; - if (a_SlotNum == -999) // Outside window click - { - if (a_IsRightClick) - { - a_Player.TossItem(true); - } - else - { - a_Player.TossItem(true, m_DraggingItem->m_ItemCount); - } - } - else if (GetSlot(a_SlotNum) != NULL) - { - cItem * Item = GetSlot(a_SlotNum); - if (!Item->IsEqual(a_HeldItem)) - { - LOGD("*** Window lost sync ***"); - LOGD("My Type: %i Their Type: %i", Item->m_ItemID, a_HeldItem.m_ItemID); - LOGD("My Count: %i Their Count: %i", Item->m_ItemCount, a_HeldItem.m_ItemCount); - LOGD("My Dmg: %i Their Dmg: %i", Item->m_ItemHealth, a_HeldItem.m_ItemHealth); - bAsync = true; - } - } - if (m_DraggingItem && (a_SlotNum > -1) && (a_SlotNum < m_NumSlots)) - { - if (!a_IsRightClick) - { - // Left-clicked - if (!m_DraggingItem->IsEqual(m_Slots[a_SlotNum])) - { - // Switch contents - cItem tmp(*m_DraggingItem); - *m_DraggingItem = m_Slots[a_SlotNum]; - m_Slots[a_SlotNum] = tmp; - } - else - { - // Same type, add items: - cItemHandler * Handler = ItemHandler(m_DraggingItem->m_ItemID); - int FreeSlots = Handler->GetMaxStackSize() - m_Slots[a_SlotNum].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 > m_DraggingItem->m_ItemCount) ? m_DraggingItem->m_ItemCount : FreeSlots; - m_Slots[a_SlotNum].m_ItemCount += (char)Filling; - m_DraggingItem->m_ItemCount -= (char)Filling; - if (m_DraggingItem->m_ItemCount <= 0) - { - m_DraggingItem->Empty(); - } - } - } - else - { - // Right clicked - if (m_DraggingItem->m_ItemID <= 0) // Empty-handed? - { - m_DraggingItem->m_ItemCount = (char)(((float)m_Slots[a_SlotNum].m_ItemCount) / 2.f + 0.5f); - m_Slots[a_SlotNum].m_ItemCount -= m_DraggingItem->m_ItemCount; - m_DraggingItem->m_ItemID = m_Slots[a_SlotNum].m_ItemID; - m_DraggingItem->m_ItemHealth = m_Slots[a_SlotNum].m_ItemHealth; - - if (m_Slots[a_SlotNum].m_ItemCount <= 0) - { - m_Slots[a_SlotNum].Empty(); - } - } - else if ((m_Slots[a_SlotNum].m_ItemID <= 0) || m_DraggingItem->IsEqual(m_Slots[a_SlotNum])) - { - // Drop one item in slot - cItemHandler * Handler = ItemHandler(m_Slots[a_SlotNum].m_ItemID); - if ((m_DraggingItem->m_ItemCount > 0) && (m_Slots[a_SlotNum].m_ItemCount < Handler->GetMaxStackSize())) - { - m_Slots[a_SlotNum].m_ItemID = m_DraggingItem->m_ItemID; - m_Slots[a_SlotNum].m_ItemCount++; - m_Slots[a_SlotNum].m_ItemHealth = m_DraggingItem->m_ItemHealth; - m_DraggingItem->m_ItemCount--; - } - if (m_DraggingItem->m_ItemCount <= 0) - { - m_DraggingItem->Empty(); - } - } - else if (!m_DraggingItem->IsEqual(m_Slots[a_SlotNum])) - { - // Swap contents - cItem tmp( *m_DraggingItem ); - *m_DraggingItem = m_Slots[a_SlotNum]; - m_Slots[a_SlotNum] = tmp; - } - } - if (bAsync) - { - // TODO: Handle this thread-safely (m_OpenedBy may change by another cSocketThread - for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) - { - SendWholeWindow((*itr)->GetClientHandle()); - } - if (m_bInventoryVisible || m_OpenedBy.empty()) - { - a_Player.GetInventory().SendWholeInventory( a_Player.GetClientHandle() ); - } - } - } - else if (m_bInventoryVisible) // Click in player inventory - { - cWindow * Window = a_Player.GetInventory().GetWindow(); - if (Window) - { - Window->Clicked(a_Player, a_WindowID, a_SlotNum - 9, a_IsRightClick, a_IsShiftPressed, a_HeldItem); - } - } - if (m_DraggingItem != NULL) - { - LOGD("Dragging: %i", m_DraggingItem->m_ItemCount ); - } -} - - - - - -void cWindow::Open( 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 ); - } - - a_Player.GetClientHandle()->SendWindowOpen(m_WindowID, m_WindowType, m_WindowTitle, m_NumSlots); -} - - - - - -void cWindow::Close( cPlayer & a_Player ) -{ - //Checks wheather the player is still holding an item - if (m_DraggingItem && m_DraggingItem->m_ItemCount > 0) - { - LOGD("Player holds item! Dropping it..."); - a_Player.TossItem(true, m_DraggingItem->m_ItemCount); - } - - cClientHandle * ClientHandle = a_Player.GetClientHandle(); - if (ClientHandle != NULL) - { - ClientHandle->SendWindowClose(m_WindowID); - } - - { - cCSLock Lock(m_CS); - m_OpenedBy.remove( &a_Player ); - if( m_OpenedBy.size() == 0 ) - { - Destroy(); - } - } - if (m_IsDestroyed) - { - delete this; - } -} - - - - - -void cWindow::OwnerDestroyed() -{ - m_Owner = 0; - while( m_OpenedBy.size() > 1 ) - { - (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); - } - (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); -} - - - - - -bool cWindow::ForEachPlayer(cItemCallback & a_Callback) -{ - cCSLock Lock(m_CS); - for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) - { - if (a_Callback.Item(*itr)) - { - return false; - } - } // for itr - m_OpenedBy[] - return true; -} - - - - - -bool cWindow::ForEachClient(cItemCallback & a_Callback) -{ - cCSLock Lock(m_CS); - for (cPlayerList::iterator itr = m_OpenedBy.begin(), end = m_OpenedBy.end(); itr != end; ++itr) - { - if (a_Callback.Item((*itr)->GetClientHandle())) - { - return false; - } - } // for itr - m_OpenedBy[] - return true; -} - - - - - -void cWindow::Destroy() -{ - LOGD("Destroying window %p (type %d)", this, m_WindowType); - if (m_Owner != NULL) - { - m_Owner->CloseWindow(); - m_Owner = NULL; - } - m_IsDestroyed = true; -} - - - - - -void cWindow::SendWholeWindow(cClientHandle * a_Client ) -{ - a_Client->SendWholeInventory(*this); -} - - - - - -void cWindow::BroadcastWholeWindow(void) -{ - cCSLock Lock(m_CS); - for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) - { - SendWholeWindow((*itr)->GetClientHandle()); - } // for itr - m_OpenedBy[] -} - - - - - -void cWindow::BroadcastInventoryProgress(short a_Progressbar, short a_Value) -{ - cCSLock Lock(m_CS); - for (cPlayerList::iterator itr = m_OpenedBy.begin(); itr != m_OpenedBy.end(); ++itr) - { - (*itr)->GetClientHandle()->SendInventoryProgress(m_WindowID, a_Progressbar, a_Value); - } // for itr - m_OpenedBy[] -} - - - - diff --git a/source/cWindow.h b/source/cWindow.h deleted file mode 100644 index 77dc608a4..000000000 --- a/source/cWindow.h +++ /dev/null @@ -1,109 +0,0 @@ - -// cWindow.h - -// Interfaces to the cWindow class representing a UI window for a specific block - - - - - -#pragma once - - -class cPlayer; -class cItem; -class cWindowOwner; -class cClientHandle; - -typedef std::list cPlayerList; - - - - - -/** -Represents a UI window (base class) for a specific block entity. - -There is up to one instance of the class for each block entity -Each window has a list of players that are currently using it -When there's no player using a window, it is destroyed -*/ -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 - }; - - cWindow(cWindowOwner * a_Owner, bool a_bInventoryVisible, WindowType a_WindowType, int a_WindowID); - ~cWindow(); - - int GetWindowID(void) const { return m_WindowID; } - int GetWindowType(void) const { return m_WindowType; } - - cItem* GetSlots(void) const { return m_Slots; } - int GetNumSlots(void) const { return m_NumSlots; } - - cItem* GetSlot( int a_Slot ); - - cItem* GetDraggingItem( cPlayer * a_Player = 0 ); - - // a_Slots is an array of slots of size a_NumSlots - void SetSlots(cItem* a_Slots, int a_NumSlots) { m_Slots = a_Slots; m_NumSlots = a_NumSlots; } - - bool IsInventoryVisible() { return m_bInventoryVisible; } - void SetInventoryVisible( bool a_bVisible ) { m_bInventoryVisible = a_bVisible; } - - virtual void Clicked( - cPlayer & a_Player, int a_WindowID, - short a_SlotNum, bool a_IsRightClick, bool a_IsShiftPressed, - const cItem & a_HeldItem - ); - - virtual void Open( cPlayer & a_Player ); - virtual void Close( cPlayer & a_Player ); - - cWindowOwner* GetOwner() { return m_Owner; } - void SetOwner( cWindowOwner* a_Owner ) { m_Owner = a_Owner; } - - 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 std::string & a_WindowTitle ) { m_WindowTitle = a_WindowTitle; } - - void OwnerDestroyed(void); - - /// Calls the callback safely for each player that has this window open; returns true if all players have been enumerated - bool ForEachPlayer(cItemCallback & a_Callback); - - /// Calls the callback safely for each client that has this window open; returns true if all clients have been enumerated - bool ForEachClient(cItemCallback & a_Callback); - -private: - - void Destroy(); - - char m_WindowID; - int m_WindowType; - AString m_WindowTitle; - - cWindowOwner * m_Owner; - - cCriticalSection m_CS; - cPlayerList m_OpenedBy; - - bool m_bInventoryVisible; - int m_NumSlots; - cItem * m_Slots; - cItem * m_DraggingItem; - bool m_IsDestroyed; -}; \ No newline at end of file