summaryrefslogtreecommitdiffstats
path: root/src/UI
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/UI/CMakeLists.txt1
-rw-r--r--src/UI/SlotArea.cpp223
-rw-r--r--src/UI/SlotArea.h50
-rw-r--r--src/UI/VillagerTradeWindow.h74
-rw-r--r--src/UI/Window.cpp2
-rw-r--r--src/UI/Window.h2
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,