aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Loot
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2024-08-26 15:02:22 +0200
committerShauren <shauren.trinity@gmail.com>2024-08-26 15:02:22 +0200
commit3e28ee080a1cf3c7cd332a8d1e0808505b4ea9d4 (patch)
tree0be0cc52e53927111501c0b9b31859f2d25c890e /src/server/game/Loot
parent6b4270850f8d93a9f4c67a61c85a62a9d3e7e127 (diff)
Core/Loot: Implemented currency loot
Diffstat (limited to 'src/server/game/Loot')
-rw-r--r--src/server/game/Loot/Loot.cpp240
-rw-r--r--src/server/game/Loot/Loot.h35
-rw-r--r--src/server/game/Loot/LootItemStorage.cpp66
-rw-r--r--src/server/game/Loot/LootItemStorage.h5
-rw-r--r--src/server/game/Loot/LootMgr.cpp119
-rw-r--r--src/server/game/Loot/LootMgr.h1
6 files changed, 323 insertions, 143 deletions
diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp
index d1ab49f662e..6314e9d9f01 100644
--- a/src/server/game/Loot/Loot.cpp
+++ b/src/server/game/Loot/Loot.cpp
@@ -17,8 +17,8 @@
#include "Loot.h"
#include "Containers.h"
-#include "DatabaseEnv.h"
#include "DB2Stores.h"
+#include "DatabaseEnv.h"
#include "GameTime.h"
#include "Group.h"
#include "Item.h"
@@ -40,26 +40,26 @@
//
// Constructor, copies most fields from LootStoreItem and generates random count
-LootItem::LootItem(LootStoreItem const& li)
+LootItem::LootItem(LootStoreItem const& li) : itemid(li.itemid), conditions(li.conditions), needs_quest(li.needs_quest)
{
- itemid = li.itemid;
- LootListId = 0;
- conditions = li.conditions;
-
- ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
- freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP);
- follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES));
-
- needs_quest = li.needs_quest;
-
- randomBonusListId = GenerateItemRandomBonusListId(itemid);
- context = ItemContext::NONE;
- count = 0;
- is_looted = false;
- is_blocked = false;
- is_underthreshold = false;
- is_counted = false;
- rollWinnerGUID = ObjectGuid::Empty;
+ switch (li.type)
+ {
+ case LootStoreItem::Type::Item:
+ {
+ randomBonusListId = GenerateItemRandomBonusListId(itemid);
+ type = LootItemType::Item;
+ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
+ freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP);
+ follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES));
+ break;
+ }
+ case LootStoreItem::Type::Currency:
+ type = LootItemType::Currency;
+ freeforall = true;
+ break;
+ default:
+ break;
+ }
}
LootItem::LootItem(LootItem const&) = default;
@@ -71,10 +71,35 @@ LootItem::~LootItem() = default;
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot) const
{
- return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
+ switch (type)
+ {
+ case LootItemType::Item:
+ return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
+ case LootItemType::Currency:
+ return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions);
+ default:
+ break;
+ }
+ return false;
+}
+
+bool LootItem::AllowedForPlayer(Player const* player, LootStoreItem const& lootStoreItem, bool strictUsabilityCheck)
+{
+ switch (lootStoreItem.type)
+ {
+ case LootStoreItem::Type::Item:
+ return ItemAllowedForPlayer(player, nullptr, lootStoreItem.itemid, lootStoreItem.needs_quest,
+ !lootStoreItem.needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem.itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
+ strictUsabilityCheck, lootStoreItem.conditions);
+ case LootStoreItem::Type::Currency:
+ return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions);
+ default:
+ break;
+ }
+ return false;
}
-bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
+bool LootItem::ItemAllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
ConditionsReference const& conditions)
{
// DB conditions check
@@ -128,14 +153,38 @@ bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot, uint32 i
return true;
}
-void LootItem::AddAllowedLooter(const Player* player)
+bool LootItem::CurrencyAllowedForPlayer(Player const* player, uint32 currencyId, bool needs_quest, ConditionsReference const& conditions)
+{
+ // DB conditions check
+ if (!conditions.Meets(player))
+ return false;
+
+ CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyId);
+ if (!currency)
+ return false;
+
+ // not show loot for not own team
+ if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsHordeOnly) && player->GetTeam() != HORDE)
+ return false;
+
+ if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsAllianceOnly) && player->GetTeam() != ALLIANCE)
+ return false;
+
+ // check quest requirements
+ if (needs_quest && !player->HasQuestForCurrency(currencyId))
+ return false;
+
+ return true;
+}
+
+void LootItem::AddAllowedLooter(Player const* player)
{
allowedGUIDs.insert(player->GetGUID());
}
bool LootItem::HasAllowedLooter(ObjectGuid const& looter) const
{
- return allowedGUIDs.find(looter) != allowedGUIDs.end();
+ return allowedGUIDs.contains(looter);
}
Optional<LootSlotType> LootItem::GetUiTypeForPlayer(Player const* player, Loot const& loot) const
@@ -143,7 +192,7 @@ Optional<LootSlotType> LootItem::GetUiTypeForPlayer(Player const* player, Loot c
if (is_looted)
return {};
- if (allowedGUIDs.find(player->GetGUID()) == allowedGUIDs.end())
+ if (!allowedGUIDs.contains(player->GetGUID()))
return {};
if (freeforall)
@@ -604,7 +653,8 @@ void LootRoll::Finish(RollVoteMap::const_iterator winnerItr)
{
for (uint32 i = 0; i < loot.items.size(); ++i)
if (LootItem* disenchantLoot = loot.LootItemInSlot(i, player))
- player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context);
+ if (disenchantLoot->type == LootItemType::Item)
+ player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context);
}
else
m_loot->NotifyItemRemoved(m_lootItem->LootListId, m_map);
@@ -791,7 +841,7 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo
for (LootItem& item : items)
{
- if (!item.follow_loot_rules || item.freeforall)
+ if (!item.follow_loot_rules || item.freeforall || item.type != LootItemType::Item)
continue;
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid))
@@ -826,23 +876,40 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo
// Inserts the item into the loot (called by LootTemplate processors)
void Loot::AddItem(LootStoreItem const& item)
{
- ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
- if (!proto)
- return;
+ switch (item.type)
+ {
+ case LootStoreItem::Type::Item:
+ {
+ ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
+ if (!proto)
+ return;
- uint32 count = urand(item.mincount, item.maxcount);
- uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
+ uint32 count = urand(item.mincount, item.maxcount);
+ uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
- for (uint32 i = 0; i < stacks && items.size() < MAX_NR_LOOT_ITEMS; ++i)
- {
- LootItem generatedLoot(item);
- generatedLoot.context = _itemContext;
- generatedLoot.count = std::min(count, proto->GetMaxStackSize());
- generatedLoot.LootListId = items.size();
- generatedLoot.BonusListIDs = ItemBonusMgr::GetBonusListsForItem(generatedLoot.itemid, _itemContext);
-
- items.push_back(generatedLoot);
- count -= proto->GetMaxStackSize();
+ for (uint32 i = 0; i < stacks && items.size() < MAX_NR_LOOT_ITEMS; ++i)
+ {
+ LootItem generatedLoot(item);
+ generatedLoot.context = _itemContext;
+ generatedLoot.count = std::min(count, proto->GetMaxStackSize());
+ generatedLoot.LootListId = items.size();
+ generatedLoot.BonusListIDs = ItemBonusMgr::GetBonusListsForItem(generatedLoot.itemid, _itemContext);
+
+ items.push_back(generatedLoot);
+ count -= proto->GetMaxStackSize();
+ }
+ break;
+ }
+ case LootStoreItem::Type::Currency:
+ {
+ LootItem generatedLoot(item);
+ generatedLoot.count = urand(item.mincount, item.maxcount);
+ generatedLoot.LootListId = items.size();
+ items.push_back(generatedLoot);
+ break;
+ }
+ default:
+ break;
}
}
@@ -867,17 +934,36 @@ bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool
if (!lootItem->rollWinnerGUID.IsEmpty() && lootItem->rollWinnerGUID != GetGUID())
continue;
- ItemPosCountVec dest;
- InventoryResult msg = player->CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count);
- if (msg != EQUIP_ERR_OK && slot != NULL_SLOT)
- msg = player->CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
- if (msg != EQUIP_ERR_OK && bag != NULL_BAG)
- msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
- if (msg != EQUIP_ERR_OK)
+ switch (lootItem->type)
{
- player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid);
- allLooted = false;
- continue;
+ case LootItemType::Item:
+ {
+ ItemPosCountVec dest;
+ InventoryResult msg = player->CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count);
+ if (msg != EQUIP_ERR_OK && slot != NULL_SLOT)
+ msg = player->CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
+ if (msg != EQUIP_ERR_OK && bag != NULL_BAG)
+ msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count);
+ if (msg != EQUIP_ERR_OK)
+ {
+ player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid);
+ allLooted = false;
+ continue;
+ }
+
+ if (Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, &lootItem->BonusListIDs))
+ {
+ player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast, GetDungeonEncounterId());
+ player->ApplyItemLootedSpell(pItem, true);
+ }
+ else
+ player->ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(lootItem->itemid));
+
+ break;
+ }
+ case LootItemType::Currency:
+ player->ModifyCurrency(lootItem->itemid, lootItem->count, CurrencyGainSource::Loot);
+ break;
}
if (ffaitem)
@@ -887,14 +973,6 @@ bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool
lootItem->is_looted = true;
--unlootedCount;
-
- if (Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, &lootItem->BonusListIDs))
- {
- player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast, GetDungeonEncounterId());
- player->ApplyItemLootedSpell(pItem, true);
- }
- else
- player->ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(lootItem->itemid));
}
return allLooted;
@@ -966,18 +1044,12 @@ bool Loot::hasItemFor(Player const* player) const
{
// quest items
for (LootItem const& lootItem : items)
- if (!lootItem.is_looted && !lootItem.follow_loot_rules && lootItem.GetAllowedLooters().find(player->GetGUID()) != lootItem.GetAllowedLooters().end())
+ if (!lootItem.is_looted && !lootItem.follow_loot_rules && lootItem.GetAllowedLooters().contains(player->GetGUID()))
return true;
if (NotNormalLootItemList const* ffaItems = Trinity::Containers::MapGetValuePtr(GetPlayerFFAItems(), player->GetGUID()))
- {
- bool hasFfaItem = std::ranges::any_of(*ffaItems, [&](NotNormalLootItem const& ffaItem)
- {
- return !ffaItem.is_looted;
- });
- if (hasFfaItem)
+ if (std::ranges::any_of(*ffaItems, std::identity(), &NotNormalLootItem::is_looted))
return true;
- }
return false;
}
@@ -1004,11 +1076,33 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player co
if (!uiType)
continue;
- WorldPackets::Loot::LootItemData& lootItem = packet.Items.emplace_back();
- lootItem.LootListID = item.LootListId;
- lootItem.UIType = *uiType;
- lootItem.Quantity = item.count;
- lootItem.Loot.Initialize(item);
+ switch (item.type)
+ {
+ case LootItemType::Item:
+ {
+ WorldPackets::Loot::LootItemData& lootItem = packet.Items.emplace_back();
+ lootItem.LootListID = item.LootListId;
+ lootItem.UIType = *uiType;
+ lootItem.Quantity = item.count;
+ lootItem.Loot.Initialize(item);
+ break;
+ }
+ case LootItemType::Currency:
+ {
+ WorldPackets::Loot::LootCurrency& lootCurrency = packet.Currencies.emplace_back();
+ lootCurrency.CurrencyID = item.itemid;
+ lootCurrency.Quantity = item.count;
+ lootCurrency.LootListID = item.LootListId;
+ lootCurrency.UIType = *uiType;
+
+ // fake visible quantity for SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT - handled in Player::ModifyCurrency
+ lootCurrency.Quantity = float(lootCurrency.Quantity) * viewer->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT, sCurrencyTypesStore.AssertEntry(item.itemid)->CategoryID);
+ break;
+ }
+ default:
+ break;
+ }
+
}
}
diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h
index 7534b23c672..3b5cbca7fb5 100644
--- a/src/server/game/Loot/Loot.h
+++ b/src/server/game/Loot/Loot.h
@@ -23,6 +23,7 @@
#include "Define.h"
#include "Duration.h"
#include "ItemEnchantmentMgr.h"
+#include "LootItemType.h"
#include "ObjectGuid.h"
#include "Optional.h"
#include "SharedDefines.h"
@@ -174,30 +175,30 @@ enum class LootRollIneligibilityReason : uint32
struct TC_GAME_API LootItem
{
- uint32 itemid;
- uint32 LootListId;
- ItemRandomBonusListId randomBonusListId;
+ uint32 itemid = 0;
+ uint32 LootListId = 0;
+ ItemRandomBonusListId randomBonusListId = 0;
std::vector<int32> BonusListIDs;
- ItemContext context;
+ ItemContext context = ItemContext::NONE;
ConditionsReference conditions; // additional loot condition
GuidSet allowedGUIDs;
ObjectGuid rollWinnerGUID; // Stores the guid of person who won loot, if his bags are full only he can see the item in loot list!
- uint8 count : 8;
- bool is_looted : 1;
- bool is_blocked : 1;
- bool freeforall : 1; // free for all
- bool is_underthreshold : 1;
- bool is_counted : 1;
- bool needs_quest : 1; // quest drop
- bool follow_loot_rules : 1;
+ uint32 count = 0;
+ LootItemType type = LootItemType::Item;
+ bool is_looted : 1 = false;
+ bool is_blocked : 1 = false;
+ bool freeforall : 1 = false; // free for all
+ bool is_underthreshold : 1 = false;
+ bool is_counted : 1 = false;
+ bool needs_quest : 1 = false; // quest drop
+ bool follow_loot_rules : 1 = false;
// Constructor, copies most fields from LootStoreItem, generates random count and random suffixes/properties
- // Should be called for non-reference LootStoreItem entries only (reference = 0)
+ // Should be called for non-reference LootStoreItem entries only
explicit LootItem(LootStoreItem const& li);
// Empty constructor for creating an empty LootItem to be filled in with DB data
- LootItem() : itemid(0), LootListId(0), randomBonusListId(0), context(ItemContext::NONE), count(0), is_looted(false), is_blocked(false),
- freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) { }
+ LootItem() = default;
LootItem(LootItem const&);
LootItem(LootItem&&) noexcept;
@@ -207,8 +208,10 @@ struct TC_GAME_API LootItem
// Basic checks for player/item compatibility - if false no chance to see the item in the loot - used only for loot generation
bool AllowedForPlayer(Player const* player, Loot const* loot) const;
- static bool AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
+ static bool AllowedForPlayer(Player const* player, LootStoreItem const& lootStoreItem, bool strictUsabilityCheck);
+ static bool ItemAllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
ConditionsReference const& conditions);
+ static bool CurrencyAllowedForPlayer(Player const* player, uint32 currencyId, bool needs_quest, ConditionsReference const& conditions);
void AddAllowedLooter(Player const* player);
GuidSet const& GetAllowedLooters() const { return allowedGUIDs; }
bool HasAllowedLooter(ObjectGuid const& looter) const;
diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp
index 9dc8cae1de6..c0362ea093b 100644
--- a/src/server/game/Loot/LootItemStorage.cpp
+++ b/src/server/game/Loot/LootItemStorage.cpp
@@ -25,6 +25,7 @@
#include "ObjectMgr.h"
#include "Player.h"
#include "StringConvert.h"
+#include "Util.h"
#include <sstream>
#include <unordered_map>
@@ -70,18 +71,19 @@ void LootItemStorage::LoadStorageFromDB()
StoredLootContainer& storedContainer = _lootItemStore.try_emplace(key, key).first->second;
LootItem lootItem;
- lootItem.itemid = fields[1].GetUInt32();
- lootItem.count = fields[2].GetUInt32();
- lootItem.LootListId = fields[3].GetUInt32();
- lootItem.follow_loot_rules = fields[4].GetBool();
- lootItem.freeforall = fields[5].GetBool();
- lootItem.is_blocked = fields[6].GetBool();
- lootItem.is_counted = fields[7].GetBool();
- lootItem.is_underthreshold = fields[8].GetBool();
- lootItem.needs_quest = fields[9].GetBool();
- lootItem.randomBonusListId = fields[10].GetUInt32();
- lootItem.context = ItemContext(fields[11].GetUInt8());
- for (std::string_view bonusList : Trinity::Tokenize(fields[12].GetStringView(), ' ', false))
+ lootItem.type = static_cast<LootItemType>(fields[1].GetInt8());
+ lootItem.itemid = fields[2].GetUInt32();
+ lootItem.count = fields[3].GetUInt32();
+ lootItem.LootListId = fields[4].GetUInt32();
+ lootItem.follow_loot_rules = fields[5].GetBool();
+ lootItem.freeforall = fields[6].GetBool();
+ lootItem.is_blocked = fields[7].GetBool();
+ lootItem.is_counted = fields[8].GetBool();
+ lootItem.is_underthreshold = fields[9].GetBool();
+ lootItem.needs_quest = fields[10].GetBool();
+ lootItem.randomBonusListId = fields[11].GetUInt32();
+ lootItem.context = ItemContext(fields[12].GetUInt8());
+ for (std::string_view bonusList : Trinity::Tokenize(fields[13].GetStringView(), ' ', false))
if (Optional<int32> bonusListID = Trinity::StringTo<int32>(bonusList))
lootItem.BonusListIDs.push_back(*bonusListID);
@@ -226,7 +228,7 @@ void LootItemStorage::RemoveStoredLootForContainer(uint64 containerId)
CharacterDatabase.CommitTransaction(trans);
}
-void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count, uint32 itemIndex)
+void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
{
// write
std::unique_lock<std::shared_mutex> lock(*GetLock());
@@ -235,7 +237,7 @@ void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, uint3
if (itr == _lootItemStore.end())
return;
- itr->second.RemoveItem(itemId, count, itemIndex);
+ itr->second.RemoveItem(type, itemId, count, itemIndex);
}
void LootItemStorage::AddNewStoredLoot(uint64 containerId, Loot* loot, Player* player)
@@ -301,23 +303,24 @@ void StoredLootContainer::AddLootItem(LootItem const& lootItem, CharacterDatabas
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_ITEMS);
- // container_id, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
+ // container_id, item_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
stmt->setUInt64(0, _containerId);
- stmt->setUInt32(1, lootItem.itemid);
- stmt->setUInt32(2, lootItem.count);
- stmt->setUInt32(3, lootItem.LootListId);
- stmt->setBool(4, lootItem.follow_loot_rules);
- stmt->setBool(5, lootItem.freeforall);
- stmt->setBool(6, lootItem.is_blocked);
- stmt->setBool(7, lootItem.is_counted);
- stmt->setBool(8, lootItem.is_underthreshold);
- stmt->setBool(9, lootItem.needs_quest);
- stmt->setInt32(10, lootItem.randomBonusListId);
- stmt->setUInt8(11, AsUnderlyingType(lootItem.context));
+ stmt->setInt8(1, AsUnderlyingType(lootItem.type));
+ stmt->setUInt32(2, lootItem.itemid);
+ stmt->setUInt32(3, lootItem.count);
+ stmt->setUInt32(4, lootItem.LootListId);
+ stmt->setBool(5, lootItem.follow_loot_rules);
+ stmt->setBool(6, lootItem.freeforall);
+ stmt->setBool(7, lootItem.is_blocked);
+ stmt->setBool(8, lootItem.is_counted);
+ stmt->setBool(9, lootItem.is_underthreshold);
+ stmt->setBool(10, lootItem.needs_quest);
+ stmt->setInt32(11, lootItem.randomBonusListId);
+ stmt->setUInt8(13, AsUnderlyingType(lootItem.context));
std::ostringstream bonusListIDs;
for (int32 bonusListID : lootItem.BonusListIDs)
bonusListIDs << bonusListID << ' ';
- stmt->setString(12, bonusListIDs.str());
+ stmt->setString(13, bonusListIDs.str());
trans->Append(stmt);
}
@@ -346,7 +349,7 @@ void StoredLootContainer::RemoveMoney()
CharacterDatabase.Execute(stmt);
}
-void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count, uint32 itemIndex)
+void StoredLootContainer::RemoveItem(LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
{
auto bounds = _lootItems.equal_range(itemId);
for (auto itr = bounds.first; itr != bounds.second; ++itr)
@@ -361,8 +364,9 @@ void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count, uint32 itemInd
// Deletes a single item associated with an openable item from the DB
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM);
stmt->setUInt64(0, _containerId);
- stmt->setUInt32(1, itemId);
- stmt->setUInt32(2, count);
- stmt->setUInt32(3, itemIndex);
+ stmt->setInt8(1, AsUnderlyingType(type));
+ stmt->setUInt32(2, itemId);
+ stmt->setUInt32(3, count);
+ stmt->setUInt32(4, itemIndex);
CharacterDatabase.Execute(stmt);
}
diff --git a/src/server/game/Loot/LootItemStorage.h b/src/server/game/Loot/LootItemStorage.h
index 3a0c635e96b..8df0a0c6a34 100644
--- a/src/server/game/Loot/LootItemStorage.h
+++ b/src/server/game/Loot/LootItemStorage.h
@@ -22,6 +22,7 @@
#include "DatabaseEnvFwd.h"
#include "DBCEnums.h"
#include "ItemEnchantmentMgr.h"
+#include "LootItemType.h"
#include <shared_mutex>
#include <unordered_map>
@@ -61,7 +62,7 @@ class StoredLootContainer
void AddMoney(uint32 money, CharacterDatabaseTransaction trans);
void RemoveMoney();
- void RemoveItem(uint32 itemId, uint32 count, uint32 itemIndex);
+ void RemoveItem(LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex);
uint64 GetContainer() const { return _containerId; }
uint32 GetMoney() const { return _money; }
@@ -83,7 +84,7 @@ class LootItemStorage
bool LoadStoredLoot(Item* item, Player* player);
void RemoveStoredMoneyForContainer(uint64 containerId);
void RemoveStoredLootForContainer(uint64 containerId);
- void RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count, uint32 itemIndex);
+ void RemoveStoredLootItemForContainer(uint64 containerId, LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex);
void AddNewStoredLoot(uint64 containerId, Loot* loot, Player* player);
private:
diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp
index de99abab57e..2104cc0c5ab 100644
--- a/src/server/game/Loot/LootMgr.cpp
+++ b/src/server/game/Loot/LootMgr.cpp
@@ -66,9 +66,7 @@ struct LootGroupInvalidSelector
if (!(item->lootmode & _lootMode))
return true;
- if (_personalLooter && !LootItem::AllowedForPlayer(_personalLooter, nullptr, item->itemid, item->needs_quest,
- !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- true, item->conditions))
+ if (_personalLooter && !LootItem::AllowedForPlayer(_personalLooter, *item, true))
return true;
return false;
@@ -265,6 +263,14 @@ bool LootStoreItem::Roll(bool rate) const
}
case Type::Reference:
return roll_chance_f(chance * (rate ? sWorld->getRate(RATE_DROP_ITEM_REFERENCED) : 1.0f));
+ case Type::Currency:
+ {
+ CurrencyTypesEntry const* currency = sCurrencyTypesStore.AssertEntry(itemid);
+
+ float qualityModifier = currency && rate && QualityToRate[currency->Quality] != MAX_RATES ? sWorld->getRate(QualityToRate[currency->Quality]) : 1.0f;
+
+ return roll_chance_f(chance * qualityModifier);
+ }
default:
break;
}
@@ -327,6 +333,38 @@ bool LootStoreItem::IsValid(LootStore const& store, uint32 entry) const
return false;
}
break;
+ case Type::Currency:
+ {
+ if (!sCurrencyTypesStore.HasRecord(itemid))
+ {
+ TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: currency does not exist - skipped",
+ store.GetName(), entry, type, itemid);
+ return false;
+ }
+
+ if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
+ {
+ TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: equal-chanced grouped entry, but group not defined - skipped",
+ store.GetName(), entry, type, itemid);
+ return false;
+ }
+
+ if (chance != 0 && chance < 0.0001f) // loot with low chance
+ {
+ TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: low chance ({}) - skipped",
+ store.GetName(), entry, type, itemid, chance);
+ return false;
+ }
+
+ if (maxcount < mincount) // wrong max count
+ {
+ TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} ItemType {} Item {}: MaxCount ({}) less that MinCount ({}) - skipped",
+ store.GetName(), entry, type, itemid, int32(maxcount), mincount);
+ return false;
+ }
+ break;
+ }
+
default:
TC_LOG_ERROR("sql.sql", "Table '{}' Entry {} Item {}: invalid ItemType {}, skipped",
store.GetName(), entry, itemid, type);
@@ -387,15 +425,11 @@ LootStoreItem const* LootTemplate::LootGroup::Roll(uint16 lootMode, Player const
bool LootTemplate::LootGroup::HasDropForPlayer(Player const* player, bool strictUsabilityCheck) const
{
for (std::unique_ptr<LootStoreItem> const& lootStoreItem : ExplicitlyChanced)
- if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
- !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- strictUsabilityCheck, lootStoreItem->conditions))
+ if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
return true;
for (std::unique_ptr<LootStoreItem> const& lootStoreItem : EqualChanced)
- if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
- !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- strictUsabilityCheck, lootStoreItem->conditions))
+ if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
return true;
return false;
@@ -416,10 +450,24 @@ bool LootTemplate::LootGroup::HasQuestDrop() const
// True if group includes at least 1 quest drop entry for active quests of the player
bool LootTemplate::LootGroup::HasQuestDropForPlayer(Player const* player) const
{
- if (std::ranges::any_of(ExplicitlyChanced, [player](uint32 itemId) { return player->HasQuestForItem(itemId); }, &LootStoreItem::itemid))
+ auto hasQuestForLootItem = [player](std::unique_ptr<LootStoreItem> const& item)
+ {
+ switch (item->type)
+ {
+ case LootStoreItem::Type::Item:
+ return player->HasQuestForItem(item->itemid);
+ case LootStoreItem::Type::Currency:
+ return player->HasQuestForCurrency(item->itemid);
+ default:
+ break;
+ }
+ return false;
+ };
+
+ if (std::ranges::any_of(ExplicitlyChanced, hasQuestForLootItem))
return true;
- if (std::ranges::any_of(EqualChanced, [player](uint32 itemId) { return player->HasQuestForItem(itemId); }, &LootStoreItem::itemid))
+ if (std::ranges::any_of(EqualChanced, hasQuestForLootItem))
return true;
return false;
@@ -519,6 +567,22 @@ void LootTemplate::CopyConditions(LootItem* li) const
// Copies the conditions list from a template item to a LootItemData
for (std::unique_ptr<LootStoreItem> const& item : Entries)
{
+ switch (item->type)
+ {
+ case LootStoreItem::Type::Item:
+ if (li->type != LootItemType::Item)
+ continue;
+ break;
+ case LootStoreItem::Type::Reference:
+ continue;
+ case LootStoreItem::Type::Currency:
+ if (li->type != LootItemType::Currency)
+ continue;
+ break;
+ default:
+ break;
+ }
+
if (item->itemid != li->itemid)
continue;
@@ -554,12 +618,10 @@ void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId
switch (item->type)
{
case LootStoreItem::Type::Item:
+ case LootStoreItem::Type::Currency:
// Plain entries (not a reference, not grouped)
// Chance is already checked, just add
- if (!personalLooter
- || LootItem::AllowedForPlayer(personalLooter, nullptr, item->itemid, item->needs_quest,
- !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- true, item->conditions))
+ if (!personalLooter || LootItem::AllowedForPlayer(personalLooter, *item, true))
loot.AddItem(*item);
break;
@@ -616,9 +678,7 @@ void LootTemplate::ProcessPersonalLoot(std::unordered_map<Player*, std::unique_p
// Chance is already checked, just add
std::vector<Player*> lootersForItem = getLootersForItem([&](Player const* looter)
{
- return LootItem::AllowedForPlayer(looter, nullptr, item->itemid, item->needs_quest,
- !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- true, item->conditions);
+ return LootItem::AllowedForPlayer(looter, *item, true);
});
if (!lootersForItem.empty())
@@ -668,6 +728,19 @@ void LootTemplate::ProcessPersonalLoot(std::unordered_map<Player*, std::unique_p
break;
}
+ case LootStoreItem::Type::Currency:
+ {
+ // Plain entries (not a reference, not grouped)
+ // Chance is already checked, just add
+ std::vector<Player*> lootersForItem = getLootersForItem([&](Player const* looter)
+ {
+ return LootItem::AllowedForPlayer(looter, *item, true);
+ });
+
+ for (Player* looter : lootersForItem)
+ personalLoot[looter]->AddItem(*item);
+ break;
+ }
default:
break;
}
@@ -712,9 +785,8 @@ bool LootTemplate::HasDropForPlayer(Player const* player, uint8 groupId, bool st
switch (lootStoreItem->type)
{
case LootStoreItem::Type::Item:
- if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest,
- !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
- strictUsabilityCheck, lootStoreItem->conditions))
+ case LootStoreItem::Type::Currency:
+ if (LootItem::AllowedForPlayer(player, *lootStoreItem, strictUsabilityCheck))
return true; // active quest drop found
break;
case LootStoreItem::Type::Reference:
@@ -758,6 +830,7 @@ bool LootTemplate::HasQuestDrop(LootTemplateMap const& store, uint8 groupId) con
switch (item->type)
{
case LootStoreItem::Type::Item:
+ case LootStoreItem::Type::Currency:
if (item->needs_quest)
return true; // quest drop found
break;
@@ -815,6 +888,10 @@ bool LootTemplate::HasQuestDropForPlayer(LootTemplateMap const& store, Player co
return true;
break;
}
+ case LootStoreItem::Type::Currency:
+ if (player->HasQuestForCurrency(item->itemid))
+ return true; // active quest drop found
+ break;
default:
break;
}
diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h
index 1237347714f..92501e53ef5 100644
--- a/src/server/game/Loot/LootMgr.h
+++ b/src/server/game/Loot/LootMgr.h
@@ -41,6 +41,7 @@ struct TC_GAME_API LootStoreItem
{
Item = 0,
Reference = 1,
+ Currency = 2,
};
uint32 itemid; // id of the item