diff options
Diffstat (limited to '')
-rw-r--r-- | src/UI/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/UI/SlotArea.cpp | 223 | ||||
-rw-r--r-- | src/UI/SlotArea.h | 50 | ||||
-rw-r--r-- | src/UI/VillagerTradeWindow.h | 74 | ||||
-rw-r--r-- | src/UI/Window.cpp | 2 | ||||
-rw-r--r-- | src/UI/Window.h | 2 |
6 files changed, 340 insertions, 12 deletions
diff --git a/src/UI/CMakeLists.txt b/src/UI/CMakeLists.txt index d71e08ade..8d7848756 100644 --- a/src/UI/CMakeLists.txt +++ b/src/UI/CMakeLists.txt @@ -32,6 +32,7 @@ SET (HDRS HopperWindow.h InventoryWindow.h MinecartWithChestWindow.h + VillagerTradeWindow.h WindowOwner.h) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 94fd958d5..94b059fb9 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -6,6 +6,7 @@ #include "Globals.h" #include "SlotArea.h" #include "../Entities/Player.h" +#include "../Mobs/Villager.h" #include "../BlockEntities/BeaconEntity.h" #include "../BlockEntities/BrewingstandEntity.h" #include "../BlockEntities/ChestEntity.h" @@ -15,6 +16,7 @@ #include "../Entities/Minecart.h" #include "../Items/ItemHandler.h" #include "AnvilWindow.h" +#include "VillagerTradeWindow.h" #include "../CraftingRecipes.h" #include "../Root.h" #include "../FastRandom.h" @@ -235,16 +237,11 @@ void cSlotArea::ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ void cSlotArea::DblClicked(cPlayer & a_Player, int a_SlotNum) { + // Assume single-click was sent first + cItem & Dragging = a_Player.GetDraggingItem(); if (Dragging.IsEmpty()) { - // Move the item in the dblclicked slot into hand: - Dragging = *GetSlot(a_SlotNum, a_Player); - cItem EmptyItem; - SetSlot(a_SlotNum, a_Player, EmptyItem); - } - if (Dragging.IsEmpty()) - { LOGD("%s DblClicked with an empty hand over empty slot, ignoring", a_Player.GetName().c_str()); return; } @@ -2217,6 +2214,216 @@ void cSlotAreaMinecartWithChest::SetSlot(int a_SlotNum, cPlayer & a_Player, cons //////////////////////////////////////////////////////////////////////////////// +// cSlotAreaVillagerTrade: + +cSlotAreaVillagerTrade::cSlotAreaVillagerTrade(cVillager & Villager, cWindow & ParentWindow) : + cSlotAreaTemporary(3, ParentWindow), + m_Villager(Villager) +{ +} + + + + + +void cSlotAreaVillagerTrade::UpdateTrade(const cPlayer & TradingPlayer) +{ + const auto & Contents = GetPlayerSlots(TradingPlayer); + const auto & Trade = m_Villager.GetTradeOffer( + static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(TradingPlayer), + Contents[SlotIndices::PrimaryDesire], + Contents[SlotIndices::SecondaryDesire] + ); + const auto TransactionMultiplier = cVillager::GetTransactionMultiplier(Trade, Contents[SlotIndices::PrimaryDesire], Contents[SlotIndices::SecondaryDesire]); + + if (TransactionMultiplier != 0) + { + cItem Recompense(Trade.Recompense); + Recompense.m_ItemCount *= TransactionMultiplier; + Contents[SlotIndices::Recompense] = Recompense; + } + else + { + Contents[SlotIndices::Recompense].Empty(); + } +} + + + + + +void cSlotAreaVillagerTrade::SetSlot(int ActionIndex, cPlayer & TradingPlayer, const cItem & Item) +{ + const auto & Contents = GetPlayerSlots(TradingPlayer); + const auto & RecompenseSlot = Contents[SlotIndices::Recompense]; + + if ((ActionIndex == SlotIndices::Recompense) && !RecompenseSlot.IsEmpty()) + { + // Player clicked recompense slot. Slot was populated, meaning the trade can go ahead. + // Note that parameter Item holds the new remaining count of compensation items (if any) + + auto & Trade = m_Villager.GetTradeOffer( + static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(TradingPlayer), + Contents[SlotIndices::PrimaryDesire], + Contents[SlotIndices::SecondaryDesire] + ); + const auto ActualTransactionMultiplier = static_cast<unsigned>((RecompenseSlot.m_ItemCount - Item.m_ItemCount) / Trade.Recompense.m_ItemCount); + + if (ActualTransactionMultiplier == 0) + { + // Not strictly needed in terms of preserving item count integrity but nothing will change so exit early + // Prevents associated villager from responding to a "trade" with no items exchanged + return; + } + + if (Contents[SlotIndices::PrimaryDesire].IsEmpty()) + { + // (Given that a trade can be made), player must have placed primary desire into secondary slot + + ASSERT(Trade.SecondaryDesire.IsEmpty()); + Contents[SlotIndices::SecondaryDesire].m_ItemCount -= Trade.PrimaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier); + } + else + { + // Primary\secondary desire\offer slots match up respectively + + Contents[SlotIndices::PrimaryDesire].m_ItemCount -= Trade.PrimaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier); + Contents[SlotIndices::SecondaryDesire].m_ItemCount -= Trade.SecondaryDesire.m_ItemCount * static_cast<char>(ActualTransactionMultiplier); + } + + m_Villager.HandleTransaction(Trade, ActualTransactionMultiplier); + } + + super::SetSlot(ActionIndex, TradingPlayer, Item); +} + + + + + +void cSlotAreaVillagerTrade::OnPlayerRemoved(cPlayer & Player) +{ + TossItems(Player, SlotIndices::PrimaryDesire, SlotIndices::SecondaryDesire); + super::OnPlayerRemoved(Player); +} + + + + + +void cSlotAreaVillagerTrade::Clicked(cPlayer & Player, int SlotNumber, eClickAction ClickAction, const cItem & ClickedItem) +{ + auto & DraggingItem = Player.GetDraggingItem(); + if ( + (SlotNumber == SlotIndices::Recompense) && + ((ClickAction == eClickAction::caLeftClick) || (ClickAction == eClickAction::caRightClick)) + ) + { + cItem RecompenseSlot = *GetSlot(SlotIndices::Recompense, Player); + if (RecompenseSlot.IsEmpty()) + { + // No trade possible right now + return; + } + + // Single clicking on recompense slot should transfer one transaction's worth of items to the hand + // As long as there is no dragging item or the dragging item is of the same type as the compensation + + const auto MoveCount = m_Villager.GetTradeOffer( + static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(Player), + *GetSlot(SlotIndices::PrimaryDesire, Player), + *GetSlot(SlotIndices::SecondaryDesire, Player) + ).Recompense.m_ItemCount; + if (DraggingItem.IsEmpty()) + { + DraggingItem = RecompenseSlot; + DraggingItem.m_ItemCount = MoveCount; + RecompenseSlot.m_ItemCount -= MoveCount; + SetSlot(SlotIndices::Recompense, Player, RecompenseSlot); // Perform transaction + } + else if (DraggingItem.IsEqual(RecompenseSlot)) + { + if (DraggingItem.GetMaxStackSize() < (DraggingItem.m_ItemCount + MoveCount)) + { + return; + } + + DraggingItem.m_ItemCount += MoveCount; + RecompenseSlot.m_ItemCount -= MoveCount; + SetSlot(SlotIndices::Recompense, Player, RecompenseSlot); // Perform transaction + } + + return; + } + + super::Clicked(Player, SlotNumber, ClickAction, ClickedItem); + UpdateTrade(Player); + + if (GetSlot(SlotIndices::Recompense, Player)->IsEmpty()) + { + m_Villager.HandleTradeUnavailable(); + } + else + { + m_Villager.HandleTradeAvailable(); + } +} + + + + + +void cSlotAreaVillagerTrade::NumberClicked(cPlayer & Player, int SlotNumber, eClickAction ClickAction) +{ + if (SlotNumber != SlotIndices::Recompense) + { + super::NumberClicked(Player, SlotNumber, ClickAction); + return; + } + + int HotbarSlotIndex = static_cast<int>(ClickAction - caNumber1); + cItem HotbarSlot(Player.GetInventory().GetHotbarSlot(HotbarSlotIndex)); + cItem RecompenseSlot(*GetSlot(SlotNumber, Player)); + + if (!HotbarSlot.IsEmpty()) + { + // Prevent items being inserted into trade compensation slot + return; + } + + const auto MoveCount = m_Villager.GetTradeOffer( + static_cast<cVillagerTradeWindow *>(&m_ParentWindow)->GetPlayerTradeOfferIndex(Player), + *GetSlot(SlotIndices::PrimaryDesire, Player), + *GetSlot(SlotIndices::SecondaryDesire, Player) + ).Recompense.m_ItemCount; + + HotbarSlot = RecompenseSlot; + HotbarSlot.m_ItemCount = MoveCount; + RecompenseSlot.m_ItemCount -= MoveCount; + + Player.GetInventory().SetHotbarSlot(HotbarSlotIndex, HotbarSlot); + SetSlot(SlotNumber, Player, RecompenseSlot); +} + + + + + +void cSlotAreaVillagerTrade::DblClicked(cPlayer & Player, int SlotNumber) +{ + if (SlotNumber == SlotIndices::Recompense) + { + return; + } + + super::DblClicked(Player, SlotNumber); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cSlotAreaInventoryBase: cSlotAreaInventoryBase::cSlotAreaInventoryBase(int a_NumSlots, int a_SlotOffset, cWindow & a_ParentWindow) : @@ -2579,7 +2786,7 @@ void cSlotAreaTemporary::TossItems(cPlayer & a_Player, int a_Begin, int a_End) -cItem * cSlotAreaTemporary::GetPlayerSlots(cPlayer & a_Player) +cItem * cSlotAreaTemporary::GetPlayerSlots(const cPlayer & a_Player) { cItemMap::iterator itr = m_Items.find(a_Player.GetUniqueID()); if (itr == m_Items.end()) diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h index be21cdada..99075aad7 100644 --- a/src/UI/SlotArea.h +++ b/src/UI/SlotArea.h @@ -22,6 +22,7 @@ class cChestEntity; class cEnderChestEntity; class cFurnaceEntity; class cMinecartWithChest; +class cVillager; class cCraftingRecipe; class cWorld; @@ -238,7 +239,7 @@ protected: cItemMap m_Items; /** Returns the pointer to the slot array for the player specified. */ - cItem * GetPlayerSlots(cPlayer & a_Player); + cItem * GetPlayerSlots(const cPlayer & a_Player); } ; @@ -498,7 +499,7 @@ protected: /** Called after an item has been brewed to handle statistics etc. */ void HandleBrewedItem(cPlayer & a_Player, const cItem & a_ClickedItem); -} ; +}; @@ -520,3 +521,48 @@ protected: + +class cSlotAreaVillagerTrade : + public cSlotAreaTemporary +{ + typedef cSlotAreaTemporary super; + +public: + cSlotAreaVillagerTrade(cVillager & Villager, cWindow & a_ParentWindow); + + /** Handles player interaction with a given trade. + Sets appropriate item counts based on input counts and output types. */ + void UpdateTrade(const cPlayer &); + +protected: + + enum SlotIndices : unsigned + { + PrimaryDesire = 0, + SecondaryDesire = 1, + Recompense = 2 + }; + + /** Override to respond to attempted trades. + Updates primary and secondary desire counts, assuming that they are correctly set for the given trade. + Informs the associated villager of the transaction. */ + virtual void SetSlot(int, cPlayer &, const cItem &) override; + + /** Override to toss all items in the trading window on player exit. */ + virtual void OnPlayerRemoved(cPlayer &) override; + + /** Override to update the trade items and inform the associated villager of the trading status. */ + virtual void Clicked(cPlayer &, int, eClickAction, const cItem &) override; + + /** Override to disable transferral of the compensation item to an already populated hotbar slot. + Default behaviour when destination hotbar slot is populated is to swap the source and destination, but this is not possible since the compensation slot cannot be backfilled. */ + virtual void NumberClicked(cPlayer &, int, eClickAction) override; + + /** Override to disable double-click behaviour for the compensation slot. */ + virtual void DblClicked(cPlayer &, int) override; + + /** Override to prevent distribution of items into the trading window. */ + virtual void DistributeStack(cItem &, cPlayer &, bool, bool, bool) override {} + + cVillager & m_Villager; +}; diff --git a/src/UI/VillagerTradeWindow.h b/src/UI/VillagerTradeWindow.h new file mode 100644 index 000000000..ec5ea26e7 --- /dev/null +++ b/src/UI/VillagerTradeWindow.h @@ -0,0 +1,74 @@ + +#pragma once + +#include "Window.h" +#include "SlotArea.h" +#include "../Mobs/Villager.h" + + + + + +class cVillagerTradeWindow : + public cWindow +{ + typedef cWindow super; + +public: + cVillagerTradeWindow(cVillager & Villager) : + cWindow(wtVillagerTrade, Printf("Villager № %i", Villager.GetUniqueID())), + m_Villager(Villager) + { + m_SlotAreas.push_back(new cSlotAreaVillagerTrade(m_Villager, *this)); + m_SlotAreas.push_back(new cSlotAreaInventory(*this)); + m_SlotAreas.push_back(new cSlotAreaHotBar(*this)); + } + + virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override + { + cSlotAreas AreasInOrder; + + if (a_ClickedArea == m_SlotAreas[0]) + { + // Trading Area + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else if (a_ClickedArea == m_SlotAreas[1]) + { + // Inventory + AreasInOrder.push_back(m_SlotAreas[2]); /* Hotbar */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + else + { + // Hotbar + AreasInOrder.push_back(m_SlotAreas[1]); /* Inventory */ + super::DistributeStackToAreas(a_ItemStack, a_Player, AreasInOrder, a_ShouldApply, true); + } + } + + virtual void OpenedByPlayer(cPlayer & Player) override + { + super::OpenedByPlayer(Player); + m_TradeIndexOpenedByPlayer[&Player] = 0; + m_Villager.HandleTradeInProgress(); + } + + void PlayerChangedTradeOffer(const cPlayer & Player, unsigned NewIndex) + { + m_TradeIndexOpenedByPlayer[&Player] = NewIndex; + static_cast<cSlotAreaVillagerTrade *>(m_SlotAreas[0])->UpdateTrade(Player); + m_Villager.HandleTradeInProgress(); + } + + unsigned GetPlayerTradeOfferIndex(const cPlayer & Player) + { + return m_TradeIndexOpenedByPlayer[&Player]; + } + +private: + cVillager & m_Villager; + std::unordered_map<const cPlayer *, unsigned> m_TradeIndexOpenedByPlayer; +}; diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index 8bbc4f482..52364b287 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -71,7 +71,7 @@ const AString cWindow::GetWindowTypeName(void) const case wtDropSpenser: return "minecraft:dispenser"; case wtEnchantment: return "minecraft:enchanting_table"; case wtBrewery: return "minecraft:brewing_stand"; - case wtNPCTrade: return "minecraft:villager"; + case wtVillagerTrade: return "minecraft:villager"; case wtBeacon: return "minecraft:beacon"; case wtAnvil: return "minecraft:anvil"; case wtHopper: return "minecraft:hopper"; diff --git a/src/UI/Window.h b/src/UI/Window.h index d7a29dc47..c834eb0ed 100644 --- a/src/UI/Window.h +++ b/src/UI/Window.h @@ -60,7 +60,7 @@ public: wtDropSpenser = 3, // Dropper or Dispenser wtEnchantment = 4, wtBrewery = 5, - wtNPCTrade = 6, + wtVillagerTrade = 6, wtBeacon = 7, wtAnvil = 8, wtHopper = 9, |