mirror of
https://github.com/cuberite/cuberite.git
synced 2025-01-07 03:16:55 +08:00
parent
da0778dfaa
commit
5481249d0f
@ -12859,7 +12859,7 @@ end
|
||||
{
|
||||
Notes = "An inventory window",
|
||||
},
|
||||
wtNPCTrade =
|
||||
wtVillagerTrade =
|
||||
{
|
||||
Notes = "A villager trade window",
|
||||
},
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "UI/AnvilWindow.h"
|
||||
#include "UI/BeaconWindow.h"
|
||||
#include "UI/EnchantingWindow.h"
|
||||
#include "UI/VillagerTradeWindow.h"
|
||||
#include "Item.h"
|
||||
#include "Mobs/Monster.h"
|
||||
#include "ChatColor.h"
|
||||
@ -670,8 +671,14 @@ void cClientHandle::RemoveFromAllChunks()
|
||||
|
||||
void cClientHandle::HandleNPCTrade(int a_SlotNum)
|
||||
{
|
||||
// TODO
|
||||
LOGWARNING("%s: Not implemented yet", __FUNCTION__);
|
||||
auto CurrentWindow = GetPlayer()->GetWindow();
|
||||
if ((CurrentWindow == nullptr) || CurrentWindow->GetWindowType() != cWindow::WindowType::wtVillagerTrade)
|
||||
{
|
||||
Kick("Received trade selection when no trade in progress - hacked client?");
|
||||
return;
|
||||
}
|
||||
|
||||
static_cast<cVillagerTradeWindow *>(CurrentWindow)->PlayerChangedTradeOffer(*GetPlayer(), static_cast<unsigned>(a_SlotNum));
|
||||
}
|
||||
|
||||
|
||||
@ -3111,6 +3118,11 @@ void cClientHandle::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_Blo
|
||||
m_Protocol->SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ);
|
||||
}
|
||||
|
||||
void cClientHandle::SendVillagerTradeList(const cWindow & TradeWindow, const std::vector<VillagerTradeOffer> & TradeOffers)
|
||||
{
|
||||
m_Protocol->SendVillagerTradeList(TradeWindow, TradeOffers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -36,6 +36,7 @@ class cFallingBlock;
|
||||
class cCompositeChat;
|
||||
class cStatManager;
|
||||
class cMap;
|
||||
struct VillagerTradeOffer;
|
||||
class cClientHandle;
|
||||
typedef std::shared_ptr<cClientHandle> cClientHandlePtr;
|
||||
|
||||
@ -221,6 +222,7 @@ public: // tolua_export
|
||||
void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity);
|
||||
void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4);
|
||||
void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ);
|
||||
void SendVillagerTradeList (const cWindow &, const std::vector<VillagerTradeOffer> &);
|
||||
void SendWeather (eWeather a_Weather);
|
||||
void SendWholeInventory (const cWindow & a_Window);
|
||||
void SendWindowClose (const cWindow & a_Window);
|
||||
|
@ -666,7 +666,7 @@ bool cEntity::ArmorCoversAgainst(eDamageType a_DamageType)
|
||||
|
||||
int cEntity::GetEnchantmentCoverAgainst(const cEntity * a_Attacker, eDamageType a_DamageType, int a_Damage)
|
||||
{
|
||||
int TotalEPF = 0.0;
|
||||
int TotalEPF = 0;
|
||||
|
||||
const cItem ArmorItems[] = { GetEquippedHelmet(), GetEquippedChestplate(), GetEquippedLeggings(), GetEquippedBoots() };
|
||||
for (size_t i = 0; i < ARRAYCOUNT(ArmorItems); i++)
|
||||
|
@ -1135,14 +1135,14 @@ std::unique_ptr<cMonster> cMonster::NewMonsterFromType(eMonsterType a_MobType)
|
||||
}
|
||||
case mtVillager:
|
||||
{
|
||||
int VillagerType = Random.RandInt(6);
|
||||
if (VillagerType == 6)
|
||||
int VillagerType = Random.RandInt(13);
|
||||
if (VillagerType == 13)
|
||||
{
|
||||
// Give farmers a better chance of spawning
|
||||
VillagerType = 0;
|
||||
}
|
||||
|
||||
return cpp14::make_unique<cVillager>(static_cast<cVillager::eVillagerType>(VillagerType));
|
||||
return cpp14::make_unique<cVillager>(static_cast<cVillager::VillagerCareer>(VillagerType), 1U);
|
||||
}
|
||||
case mtHorse:
|
||||
{
|
||||
|
@ -2,21 +2,53 @@
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "Villager.h"
|
||||
#include "../UI/VillagerTradeWindow.h"
|
||||
#include "../Entities/Player.h"
|
||||
#include "../World.h"
|
||||
#include "../BlockArea.h"
|
||||
#include "../Blocks/BlockHandler.h"
|
||||
#include "../BlockInServerPluginInterface.h"
|
||||
#include "../ClientHandle.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cVillager::cVillager(eVillagerType VillagerType) :
|
||||
cVillager::cVillager(VillagerCareer Career, unsigned TradeTier) :
|
||||
super("Villager", mtVillager, "entity.villager.hurt", "entity.villager.death", 0.6, 1.8),
|
||||
cEntityWindowOwner(this),
|
||||
m_ActionCountDown(-1),
|
||||
m_Type(VillagerType),
|
||||
m_Career(Career),
|
||||
m_TradeTier(TradeTier),
|
||||
m_VillagerAction(false)
|
||||
{
|
||||
UpdateTradeTier(m_TradeTier);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cVillager::VillagerProfession cVillager::VillagerCareerToProfession(VillagerCareer Career)
|
||||
{
|
||||
switch (Career)
|
||||
{
|
||||
case VillagerCareer::Farmer:
|
||||
case VillagerCareer::Fisherman:
|
||||
case VillagerCareer::Shepherd:
|
||||
case VillagerCareer::Fletcher: return cVillager::VillagerProfession::Farmer;
|
||||
case VillagerCareer::Librarian:
|
||||
case VillagerCareer::Cartographer: return cVillager::VillagerProfession::Librarian;
|
||||
case VillagerCareer::Cleric: return cVillager::VillagerProfession::Priest;
|
||||
case VillagerCareer::Armorer:
|
||||
case VillagerCareer::WeaponSmith:
|
||||
case VillagerCareer::ToolSmith: return cVillager::VillagerProfession::Blacksmith;
|
||||
case VillagerCareer::Butcher:
|
||||
case VillagerCareer::Leatherworker: return cVillager::VillagerProfession::Butcher;
|
||||
case VillagerCareer::Nitwit: return cVillager::VillagerProfession::Nitwit;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unhandled career");
|
||||
}
|
||||
|
||||
|
||||
@ -32,10 +64,7 @@ bool cVillager::DoTakeDamage(TakeDamageInfo & a_TDI)
|
||||
|
||||
if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPlayer())
|
||||
{
|
||||
if (GetRandomProvider().RandBool(1.0 / 6.0))
|
||||
{
|
||||
m_World->BroadcastEntityStatus(*this, esVillagerAngry);
|
||||
}
|
||||
m_World->BroadcastEntityStatus(*this, esVillagerAngry);
|
||||
}
|
||||
|
||||
if (a_TDI.DamageType == dtLightning)
|
||||
@ -63,27 +92,18 @@ void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||
if (m_ActionCountDown > -1)
|
||||
{
|
||||
m_ActionCountDown--;
|
||||
if (m_ActionCountDown == 0)
|
||||
if ((m_ActionCountDown == 0) && (m_Career == VillagerCareer::Farmer))
|
||||
{
|
||||
switch (m_Type)
|
||||
{
|
||||
case vtFarmer:
|
||||
{
|
||||
HandleFarmerPlaceCrops();
|
||||
}
|
||||
}
|
||||
HandleFarmerPlaceCrops();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_VillagerAction)
|
||||
{
|
||||
switch (m_Type)
|
||||
if (m_Career == VillagerCareer::Farmer)
|
||||
{
|
||||
case vtFarmer:
|
||||
{
|
||||
HandleFarmerTryHarvestCrops();
|
||||
}
|
||||
HandleFarmerTryHarvestCrops();
|
||||
}
|
||||
m_VillagerAction = false;
|
||||
return;
|
||||
@ -95,11 +115,169 @@ void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_Type)
|
||||
if (m_Career == VillagerCareer::Farmer)
|
||||
{
|
||||
case vtFarmer:
|
||||
HandleFarmerPrepareFarmCrops();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cVillager::OnRightClicked(cPlayer & InteractingPlayer)
|
||||
{
|
||||
if (m_TradeOffers.empty())
|
||||
{
|
||||
// Client handles this ok, but the contract of GetTradeOffer says it must return something
|
||||
return;
|
||||
}
|
||||
|
||||
// If the window is not created, open it anew:
|
||||
if (GetWindow() == nullptr)
|
||||
{
|
||||
OpenWindow(new cVillagerTradeWindow(*this));
|
||||
}
|
||||
|
||||
InteractingPlayer.OpenWindow(*GetWindow());
|
||||
InteractingPlayer.GetClientHandle()->SendVillagerTradeList(*GetWindow(), m_TradeOffers);
|
||||
GetWorld()->BroadcastSoundEffect("entity.villager.trading", GetPosX(), GetPosY(), GetPosZ(), 1, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
VillagerTradeOffer & cVillager::GetTradeOffer(unsigned PlayerOfferIndex, const cItem & PrimarySlot, const cItem & SecondarySlot)
|
||||
{
|
||||
if ((PlayerOfferIndex != 0) || GetTransactionMultiplier(m_TradeOffers[PlayerOfferIndex], PrimarySlot, SecondarySlot) != 0)
|
||||
{
|
||||
return m_TradeOffers[PlayerOfferIndex];
|
||||
}
|
||||
|
||||
for (auto & Offer : m_TradeOffers)
|
||||
{
|
||||
if (GetTransactionMultiplier(Offer, PrimarySlot, SecondarySlot) != 0)
|
||||
{
|
||||
HandleFarmerPrepareFarmCrops();
|
||||
return Offer;
|
||||
}
|
||||
}
|
||||
|
||||
return m_TradeOffers[PlayerOfferIndex];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
unsigned cVillager::GetTransactionMultiplier(const VillagerTradeOffer & Trade, const cItem & PrimarySlot, const cItem & SecondarySlot)
|
||||
{
|
||||
if (Trade.IsTradeExhausted())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (
|
||||
PrimarySlot.IsEqual(Trade.PrimaryDesire) &&
|
||||
(PrimarySlot.m_ItemCount >= Trade.PrimaryDesire.m_ItemCount) &&
|
||||
SecondarySlot.IsEqual(Trade.SecondaryDesire) &&
|
||||
(SecondarySlot.m_ItemCount >= Trade.SecondaryDesire.m_ItemCount)
|
||||
)
|
||||
{
|
||||
// Slots matched normally: find common denominator of transaction multiplier
|
||||
return std::min(
|
||||
{
|
||||
static_cast<unsigned>(PrimarySlot.m_ItemCount / Trade.PrimaryDesire.m_ItemCount),
|
||||
Trade.SecondaryDesire.IsEmpty() ? std::numeric_limits<unsigned>::max() : static_cast<unsigned>(SecondarySlot.m_ItemCount / Trade.SecondaryDesire.m_ItemCount),
|
||||
static_cast<unsigned>(Trade.Recompense.GetMaxStackSize() / Trade.Recompense.m_ItemCount)
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (
|
||||
Trade.SecondaryDesire.IsEmpty() &&
|
||||
PrimarySlot.IsEmpty() &&
|
||||
SecondarySlot.IsEqual(Trade.PrimaryDesire) &&
|
||||
(SecondarySlot.m_ItemCount >= Trade.PrimaryDesire.m_ItemCount)
|
||||
)
|
||||
{
|
||||
// Slots crossed - primary desire in secondary slot: swap comparison accordingly
|
||||
return std::min(
|
||||
static_cast<unsigned>(SecondarySlot.m_ItemCount / Trade.PrimaryDesire.m_ItemCount),
|
||||
static_cast<unsigned>(Trade.Recompense.GetMaxStackSize() / Trade.Recompense.m_ItemCount)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cVillager::HandleTradeAvailable() const
|
||||
{
|
||||
GetWorld()->BroadcastSoundEffect("entity.villager.yes", GetPosX(), GetPosY(), GetPosZ(), 1, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cVillager::HandleTradeInProgress() const
|
||||
{
|
||||
GetWorld()->BroadcastSoundEffect("entity.villager.trading", GetPosX(), GetPosY(), GetPosZ(), 1, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cVillager::HandleTradeUnavailable() const
|
||||
{
|
||||
GetWorld()->BroadcastSoundEffect("entity.villager.no", GetPosX(), GetPosY(), GetPosZ(), 1, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cVillager::HandleTransaction(VillagerTradeOffer & Trade, unsigned TransactionMultiplier)
|
||||
{
|
||||
GetWorld()->BroadcastSoundEffect("entity.villager.yes", GetPosX(), GetPosY(), GetPosZ(), 1, 1);
|
||||
Trade.Transactions += TransactionMultiplier;
|
||||
|
||||
for (unsigned Try = 0; Try != TransactionMultiplier; Try++)
|
||||
{
|
||||
// One singular transaction has a 10% chance of upgrading the villager's trading tier
|
||||
|
||||
if (GetRandomProvider().RandBool(0.1))
|
||||
{
|
||||
UpdateTradeTier(m_TradeTier + 1);
|
||||
|
||||
class SendNewTradesCallback : public cItemCallback<cClientHandle>
|
||||
{
|
||||
public:
|
||||
SendNewTradesCallback(const cWindow & VillagerTradeWindow, const std::vector<VillagerTradeOffer> & VillagerTradeOffers) :
|
||||
TradeWindow(VillagerTradeWindow),
|
||||
TradeOffers(VillagerTradeOffers)
|
||||
{
|
||||
}
|
||||
|
||||
bool Item(cClientHandle * Handle)
|
||||
{
|
||||
Handle->SendVillagerTradeList(TradeWindow, TradeOffers);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const cWindow & TradeWindow;
|
||||
const std::vector<VillagerTradeOffer> & TradeOffers;
|
||||
|
||||
} SNTC(*GetWindow(), m_TradeOffers);
|
||||
GetWindow()->ForEachClient(SNTC);
|
||||
|
||||
m_World->BroadcastEntityStatus(*this, esVillagerHappy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,6 +285,7 @@ void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Farmer functions:
|
||||
|
||||
@ -195,6 +374,101 @@ void cVillager::HandleFarmerPlaceCrops()
|
||||
|
||||
|
||||
|
||||
void cVillager::UpdateTradeTier(size_t DesiredTier)
|
||||
{
|
||||
struct CareerHash
|
||||
{
|
||||
size_t operator()(const VillagerCareer & Career) const
|
||||
{
|
||||
return static_cast<size_t>(Career);
|
||||
}
|
||||
};
|
||||
using Offer = VillagerTradeOffer;
|
||||
|
||||
static const std::unordered_map<
|
||||
VillagerCareer,
|
||||
std::vector<
|
||||
// Element index: trading tier (0-based)
|
||||
std::vector<Offer> // Trade offer
|
||||
>,
|
||||
CareerHash // TODO for C++14: unneeded
|
||||
> TradeMatrix =
|
||||
{
|
||||
{
|
||||
VillagerCareer::Farmer,
|
||||
{
|
||||
{ // Tier 1 (equivalent)
|
||||
Offer(cItem(E_ITEM_WHEAT, 18), cItem(E_ITEM_EMERALD)),
|
||||
Offer(cItem(E_ITEM_POTATO, 15), cItem(E_ITEM_EMERALD)),
|
||||
Offer(cItem(E_ITEM_CARROT, 15), cItem(E_ITEM_EMERALD))
|
||||
},
|
||||
{ // Tier 2
|
||||
Offer(cItem(E_BLOCK_PUMPKIN, 8), cItem(E_ITEM_EMERALD)),
|
||||
Offer(cItem(E_ITEM_EMERALD), cItem(E_ITEM_PUMPKIN_PIE, 2))
|
||||
},
|
||||
{ // Tier 3
|
||||
Offer(cItem(E_BLOCK_MELON, 7), cItem(E_ITEM_EMERALD)),
|
||||
Offer(cItem(E_ITEM_EMERALD), cItem(E_ITEM_RED_APPLE, 5))
|
||||
},
|
||||
{ // Tier 4
|
||||
Offer(cItem(E_ITEM_EMERALD), cItem(E_ITEM_COOKIE, 6)),
|
||||
Offer(cItem(E_ITEM_EMERALD), cItem(E_ITEM_CAKE))
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Fisherman, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Shepherd, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Fletcher, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Librarian, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Cartographer, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Cleric, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Armorer, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::WeaponSmith, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::ToolSmith, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Butcher, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Leatherworker, {}
|
||||
},
|
||||
{
|
||||
VillagerCareer::Nitwit, {}
|
||||
}
|
||||
};
|
||||
|
||||
auto Tiers = TradeMatrix.find(m_Career)->second;
|
||||
|
||||
m_TradeTier = std::min(Tiers.size(), DesiredTier);
|
||||
m_TradeOffers.clear();
|
||||
|
||||
for (unsigned Tier = 0; Tier < m_TradeTier; Tier++)
|
||||
{
|
||||
m_TradeOffers.insert(m_TradeOffers.end(), Tiers[Tier].cbegin(), Tiers[Tier].cend());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType)
|
||||
{
|
||||
switch (a_BlockType)
|
||||
|
@ -3,37 +3,123 @@
|
||||
|
||||
#include "PassiveMonster.h"
|
||||
#include "Blocks/ChunkInterface.h"
|
||||
#include "../UI/WindowOwner.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct VillagerTradeOffer
|
||||
{
|
||||
VillagerTradeOffer(const cItem & PrimaryTradeDesire, const cItem & TradeRecompense) :
|
||||
PrimaryDesire(PrimaryTradeDesire),
|
||||
Recompense(TradeRecompense),
|
||||
Transactions(0),
|
||||
MaximumTransactions(10)
|
||||
{
|
||||
}
|
||||
|
||||
VillagerTradeOffer(const cItem & PrimaryTradeDesire, const cItem & SecondaryTradeDesire, const cItem & TradeRecompense) :
|
||||
PrimaryDesire(PrimaryTradeDesire),
|
||||
SecondaryDesire(SecondaryTradeDesire),
|
||||
Recompense(TradeRecompense),
|
||||
Transactions(0),
|
||||
MaximumTransactions(10)
|
||||
{
|
||||
}
|
||||
|
||||
cItem PrimaryDesire;
|
||||
cItem SecondaryDesire;
|
||||
cItem Recompense;
|
||||
|
||||
bool IsTradeExhausted() const
|
||||
{
|
||||
return Transactions == MaximumTransactions;
|
||||
}
|
||||
|
||||
unsigned Transactions;
|
||||
unsigned MaximumTransactions;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cVillager :
|
||||
public cPassiveMonster
|
||||
public cPassiveMonster,
|
||||
public cEntityWindowOwner
|
||||
{
|
||||
typedef cPassiveMonster super;
|
||||
|
||||
public:
|
||||
|
||||
enum eVillagerType
|
||||
enum class VillagerProfession
|
||||
{
|
||||
vtFarmer = 0,
|
||||
vtLibrarian = 1,
|
||||
vtPriest = 2,
|
||||
vtBlacksmith = 3,
|
||||
vtButcher = 4,
|
||||
vtGeneric = 5,
|
||||
vtMax
|
||||
} ;
|
||||
Farmer = 0,
|
||||
Librarian = 1,
|
||||
Priest = 2,
|
||||
Blacksmith = 3,
|
||||
Butcher = 4,
|
||||
Nitwit = 5
|
||||
};
|
||||
|
||||
cVillager(eVillagerType VillagerType);
|
||||
enum class VillagerCareer
|
||||
{
|
||||
Farmer,
|
||||
Fisherman,
|
||||
Shepherd,
|
||||
Fletcher,
|
||||
Librarian,
|
||||
Cartographer,
|
||||
Cleric,
|
||||
Armorer,
|
||||
WeaponSmith,
|
||||
ToolSmith,
|
||||
Butcher,
|
||||
Leatherworker,
|
||||
Nitwit
|
||||
};
|
||||
|
||||
cVillager(VillagerCareer, unsigned);
|
||||
CLASS_PROTODEF(cVillager)
|
||||
|
||||
/** Converts internal career to equivalent Vanilla profession.
|
||||
Returns an enumeration with Vanilla-compatible values. */
|
||||
static VillagerProfession VillagerCareerToProfession(VillagerCareer);
|
||||
|
||||
/** Returns the career of the villager. */
|
||||
VillagerCareer GetCareer() const { return m_Career; }
|
||||
|
||||
/** Returns a trading offer of the villager subject to criteria.
|
||||
If the player has selected the first trade offer, the first available trade offer matching the input is returned, or the first trade offer if nothing was found.
|
||||
For any other offer selection, the selected offer is returned. */
|
||||
VillagerTradeOffer & GetTradeOffer(unsigned, const cItem &, const cItem &);
|
||||
|
||||
/** Returns number of transactions of a single trade can be completed, given input and output items. */
|
||||
static unsigned GetTransactionMultiplier(const VillagerTradeOffer &, const cItem &, const cItem &);
|
||||
|
||||
/** Handles events where players interact with an available trade.
|
||||
This includes adding a desired item to fulfil or exceed the offer, or otherwise interacting with a trade when it can be performed. */
|
||||
void HandleTradeAvailable() const;
|
||||
|
||||
/** Handles events where players interact with the trading window.
|
||||
This event does not include interactions with the currently presented trade itself. */
|
||||
void HandleTradeInProgress() const;
|
||||
|
||||
/** Handles events where players interact with an unavailable trade.
|
||||
This includes adding a desired item to fulfil or exceed the offer, or otherwise interacting with a trade when it can be performed. */
|
||||
void HandleTradeUnavailable() const;
|
||||
|
||||
/** Convenience function for setting correct item counts and upgrading trade tiers if necessary. */
|
||||
void HandleTransaction(VillagerTradeOffer &, unsigned TransactionMultiplier);
|
||||
|
||||
// cEntity overrides
|
||||
virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override;
|
||||
virtual void Tick (std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
|
||||
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
|
||||
virtual void OnRightClicked(cPlayer &) override;
|
||||
|
||||
private:
|
||||
|
||||
// cVillager functions
|
||||
/** return true if the given blocktype are: crops, potatoes or carrots. */
|
||||
bool IsBlockFarmable(BLOCKTYPE a_BlockType);
|
||||
|
||||
@ -48,19 +134,18 @@ public:
|
||||
void HandleFarmerPlaceCrops();
|
||||
|
||||
// Get and set functions.
|
||||
int GetVilType(void) const { return m_Type; }
|
||||
Vector3i GetCropsPos(void) const { return m_CropsPos; }
|
||||
bool DoesHaveActionActivated(void) const { return m_VillagerAction; }
|
||||
Vector3i GetCropsPos(void) const { return m_CropsPos; }
|
||||
bool DoesHaveActionActivated(void) const { return m_VillagerAction; }
|
||||
|
||||
private:
|
||||
/** Levels-up the villager to the given tier, if not already at maximum for their career.
|
||||
Levelling-up unlocks additional trades available at that level, and re-enables all previously exhausted trade offers. */
|
||||
void UpdateTradeTier(size_t);
|
||||
|
||||
int m_ActionCountDown;
|
||||
int m_Type;
|
||||
VillagerCareer m_Career;
|
||||
size_t m_TradeTier;
|
||||
std::vector<VillagerTradeOffer> m_TradeOffers;
|
||||
bool m_VillagerAction;
|
||||
Vector3i m_CropsPos;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
@ -33,6 +33,7 @@ class cFallingBlock;
|
||||
class cCompositeChat;
|
||||
class cStatManager;
|
||||
class cPacketizer;
|
||||
struct VillagerTradeOffer;
|
||||
|
||||
|
||||
|
||||
@ -140,6 +141,7 @@ public:
|
||||
virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) = 0;
|
||||
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) = 0;
|
||||
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) = 0;
|
||||
virtual void SendVillagerTradeList (const cWindow &, const std::vector<VillagerTradeOffer> &) = 0;
|
||||
virtual void SendWeather (eWeather a_Weather) = 0;
|
||||
virtual void SendWholeInventory (const cWindow & a_Window) = 0;
|
||||
virtual void SendWindowClose (const cWindow & a_Window) = 0;
|
||||
|
@ -957,6 +957,16 @@ void cProtocolRecognizer::SendUseBed(const cEntity & a_Entity, int a_BlockX, int
|
||||
|
||||
|
||||
|
||||
void cProtocolRecognizer::SendVillagerTradeList(const cWindow & TradeWindow, const std::vector<VillagerTradeOffer> & TradeOffers)
|
||||
{
|
||||
ASSERT(m_Protocol != nullptr);
|
||||
m_Protocol->SendVillagerTradeList(TradeWindow, TradeOffers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cProtocolRecognizer::SendWeather(eWeather a_Weather)
|
||||
{
|
||||
ASSERT(m_Protocol != nullptr);
|
||||
|
@ -136,6 +136,7 @@ public:
|
||||
virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override;
|
||||
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
|
||||
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override;
|
||||
virtual void SendVillagerTradeList (const cWindow &, const std::vector<VillagerTradeOffer> &) override;
|
||||
virtual void SendWeather (eWeather a_Weather) override;
|
||||
virtual void SendWholeInventory (const cWindow & a_Window) override;
|
||||
virtual void SendWindowClose (const cWindow & a_Window) override;
|
||||
|
@ -916,7 +916,7 @@ void cProtocol_1_10_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_
|
||||
|
||||
a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION);
|
||||
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType()));
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(cVillager::VillagerCareerToProfession(Villager.GetCareer())));
|
||||
break;
|
||||
} // case mtVillager
|
||||
|
||||
|
@ -1076,7 +1076,7 @@ void cProtocol_1_11_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_
|
||||
|
||||
a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION);
|
||||
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType()));
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(cVillager::VillagerCareerToProfession(Villager.GetCareer())));
|
||||
break;
|
||||
} // case mtVillager
|
||||
|
||||
|
@ -889,7 +889,7 @@ void cProtocol_1_12::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mo
|
||||
|
||||
a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION);
|
||||
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType()));
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(cVillager::VillagerCareerToProfession(Villager.GetCareer())));
|
||||
break;
|
||||
} // case mtVillager
|
||||
|
||||
|
@ -1597,6 +1597,15 @@ void cProtocol_1_8_0::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_B
|
||||
|
||||
|
||||
|
||||
void cProtocol_1_8_0::SendVillagerTradeList(const cWindow &, const std::vector<VillagerTradeOffer> &)
|
||||
{
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cProtocol_1_8_0::SendWeather(eWeather a_Weather)
|
||||
{
|
||||
ASSERT(m_State == 3); // In game mode?
|
||||
@ -3577,7 +3586,7 @@ void cProtocol_1_8_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_M
|
||||
{
|
||||
auto & Villager = reinterpret_cast<const cVillager &>(a_Mob);
|
||||
a_Pkt.WriteBEUInt8(0x50);
|
||||
a_Pkt.WriteBEInt32(Villager.GetVilType());
|
||||
a_Pkt.WriteBEInt32(static_cast<Int32>(cVillager::VillagerCareerToProfession(Villager.GetCareer())));
|
||||
a_Pkt.WriteBEUInt8(0x0c);
|
||||
a_Pkt.WriteBEInt8(Villager.IsBaby() ? -1 : 0);
|
||||
break;
|
||||
|
@ -129,6 +129,7 @@ public:
|
||||
virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override;
|
||||
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
|
||||
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override;
|
||||
virtual void SendVillagerTradeList (const cWindow &, const std::vector<VillagerTradeOffer> &) override;
|
||||
virtual void SendWeather (eWeather a_Weather) override;
|
||||
virtual void SendWholeInventory (const cWindow & a_Window) override;
|
||||
virtual void SendWindowClose (const cWindow & a_Window) override;
|
||||
|
@ -1642,6 +1642,40 @@ void cProtocol_1_9_0::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_B
|
||||
|
||||
|
||||
|
||||
void cProtocol_1_9_0::SendVillagerTradeList(const cWindow & TradeWindow, const std::vector<VillagerTradeOffer> & TradeOffers)
|
||||
{
|
||||
ASSERT(m_State == 3); // In game mode?
|
||||
|
||||
cPacketizer Pkt(*this, 0x18); // Plugin message
|
||||
Pkt.WriteString("MC|TrList");
|
||||
Pkt.WriteBEInt32(static_cast<Int32>(TradeWindow.GetWindowID()));
|
||||
Pkt.WriteBEUInt8(static_cast<UInt8>(TradeOffers.size()));
|
||||
|
||||
for (const auto & Trade : TradeOffers)
|
||||
{
|
||||
WriteItem(Pkt, Trade.PrimaryDesire);
|
||||
WriteItem(Pkt, Trade.Recompense);
|
||||
|
||||
if (Trade.SecondaryDesire.IsEmpty())
|
||||
{
|
||||
Pkt.WriteBool(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pkt.WriteBool(true);
|
||||
WriteItem(Pkt, Trade.SecondaryDesire);
|
||||
}
|
||||
|
||||
Pkt.WriteBool(Trade.IsTradeExhausted());
|
||||
Pkt.WriteBEInt32(static_cast<Int32>(Trade.Transactions));
|
||||
Pkt.WriteBEInt32(static_cast<Int32>(Trade.MaximumTransactions));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cProtocol_1_9_0::SendWeather(eWeather a_Weather)
|
||||
{
|
||||
ASSERT(m_State == 3); // In game mode?
|
||||
@ -4044,7 +4078,7 @@ void cProtocol_1_9_0::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_M
|
||||
|
||||
a_Pkt.WriteBEUInt8(12); // Index 12: Type
|
||||
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType()));
|
||||
a_Pkt.WriteVarInt32(static_cast<UInt32>(cVillager::VillagerCareerToProfession(Villager.GetCareer())));
|
||||
break;
|
||||
} // case mtVillager
|
||||
|
||||
|
@ -135,6 +135,7 @@ public:
|
||||
virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override;
|
||||
virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override;
|
||||
virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ) override;
|
||||
virtual void SendVillagerTradeList (const cWindow &, const std::vector<VillagerTradeOffer> &) override;
|
||||
virtual void SendWeather (eWeather a_Weather) override;
|
||||
virtual void SendWholeInventory (const cWindow & a_Window) override;
|
||||
virtual void SendWindowClose (const cWindow & a_Window) override;
|
||||
|
@ -32,6 +32,7 @@ SET (HDRS
|
||||
HopperWindow.h
|
||||
InventoryWindow.h
|
||||
MinecartWithChestWindow.h
|
||||
VillagerTradeWindow.h
|
||||
WindowOwner.h)
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
|
@ -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,15 +237,10 @@ 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;
|
||||
@ -2216,6 +2213,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:
|
||||
|
||||
@ -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())
|
||||
|
@ -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;
|
||||
};
|
||||
|
74
src/UI/VillagerTradeWindow.h
Normal file
74
src/UI/VillagerTradeWindow.h
Normal file
@ -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;
|
||||
};
|
@ -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";
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
wtDropSpenser = 3, // Dropper or Dispenser
|
||||
wtEnchantment = 4,
|
||||
wtBrewery = 5,
|
||||
wtNPCTrade = 6,
|
||||
wtVillagerTrade = 6,
|
||||
wtBeacon = 7,
|
||||
wtAnvil = 8,
|
||||
wtHopper = 9,
|
||||
|
@ -664,8 +664,9 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster)
|
||||
case mtVillager:
|
||||
{
|
||||
const cVillager *Villager = reinterpret_cast<const cVillager *>(a_Monster);
|
||||
m_Writer.AddInt("Profession", Villager->GetVilType());
|
||||
m_Writer.AddInt("Age", Villager->GetAge());
|
||||
m_Writer.AddInt("Profession", static_cast<Int32>(cVillager::VillagerCareerToProfession(Villager->GetCareer())));
|
||||
m_Writer.AddInt("Career", static_cast<Int32>(Villager->GetCareer()));
|
||||
m_Writer.AddInt("Age", Villager->GetAge());
|
||||
break;
|
||||
}
|
||||
case mtWither:
|
||||
|
@ -2837,9 +2837,13 @@ void cWSSAnvil::LoadVillagerFromNBT(cEntityList & a_Entities, const cParsedNBT &
|
||||
return;
|
||||
}
|
||||
|
||||
int Type = a_NBT.GetInt(TypeIdx);
|
||||
int CareerIdx = a_NBT.FindChildByName(a_TagIdx, "Career");
|
||||
if (CareerIdx < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<cVillager> Monster = cpp14::make_unique<cVillager>(cVillager::eVillagerType(Type));
|
||||
std::unique_ptr<cVillager> Monster = cpp14::make_unique<cVillager>(cVillager::VillagerCareer(a_NBT.GetInt(TypeIdx)), 1U);
|
||||
if (!LoadEntityBaseFromNBT(*Monster.get(), a_NBT, a_TagIdx))
|
||||
{
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user