diff options
| author | Shauren <shauren.trinity@gmail.com> | 2022-09-16 16:58:03 +0200 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2022-09-16 16:58:03 +0200 |
| commit | 3ef5079feeedfdafc9d3c1d9f865e96dbc77ecc8 (patch) | |
| tree | c88a3e2c1a8ae8459eb43fa63c66081c37393170 /src/server/game/Loot | |
| parent | 9700b2a78680452d80025121a031da340af51348 (diff) | |
Core/Loot: Move loot rolls from Group to Loot
* Partial port of cmangos/mangos-wotlk@ffdf9a05d67a04c3c0304e9b021807fa5b867583
Diffstat (limited to 'src/server/game/Loot')
| -rw-r--r-- | src/server/game/Loot/Loot.cpp | 653 | ||||
| -rw-r--r-- | src/server/game/Loot/Loot.h | 110 | ||||
| -rw-r--r-- | src/server/game/Loot/LootItemStorage.cpp | 8 |
3 files changed, 714 insertions, 57 deletions
diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp index a03c4a5e860..8f086facb80 100644 --- a/src/server/game/Loot/Loot.cpp +++ b/src/server/game/Loot/Loot.cpp @@ -16,9 +16,12 @@ */ #include "Loot.h" +#include "Containers.h" #include "DatabaseEnv.h" #include "DB2Stores.h" +#include "GameTime.h" #include "Group.h" +#include "Item.h" #include "ItemTemplate.h" #include "Log.h" #include "LootMgr.h" @@ -39,22 +42,22 @@ LootItem::LootItem(LootStoreItem const& li) { itemid = li.itemid; - itemIndex = 0; + LootListId = 0; conditions = li.conditions; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid); freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP); - follow_loot_rules = proto && (proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES)); + 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 = 0; - is_blocked = 0; - is_underthreshold = 0; - is_counted = 0; + is_looted = false; + is_blocked = false; + is_underthreshold = false; + is_counted = false; rollWinnerGUID = ObjectGuid::Empty; } @@ -124,12 +127,420 @@ void LootItem::AddAllowedLooter(const Player* player) } // +// ------- Loot Roll ------- +// + +// Send the roll for the whole group +void LootRoll::SendStartRoll() +{ + ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(m_lootItem->itemid)); + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotEmitedYet) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + WorldPackets::Loot::StartLootRoll startLootRoll; + startLootRoll.LootObj = m_loot->GetGUID(); + startLootRoll.MapID = m_map->GetId(); + startLootRoll.RollTime = LOOT_ROLL_TIMEOUT; + startLootRoll.Method = m_loot->GetLootMethod(); + startLootRoll.ValidRolls = m_voteMask; + // In NEED_BEFORE_GREED need disabled for non-usable item for player + if (m_loot->GetLootMethod() == NEED_BEFORE_GREED && player->CanRollForItemInLFG(itemTemplate, m_map) != EQUIP_ERR_OK) + startLootRoll.ValidRolls &= ~ROLL_FLAG_TYPE_NEED; + + FillPacket(startLootRoll.Item); + startLootRoll.Item.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING; + + player->SendDirectMessage(startLootRoll.Write()); + } + + // Handle auto pass option + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::Pass) + continue; + + SendRoll(playerGuid, -1, RollVote::Pass, {}); + } +} + +// Send all passed message +void LootRoll::SendAllPassed() +{ + WorldPackets::Loot::LootAllPassed lootAllPassed; + lootAllPassed.LootObj = m_loot->GetGUID(); + FillPacket(lootAllPassed.Item); + lootAllPassed.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootAllPassed.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotValid) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootAllPassed.GetRawPacket()); + } +} + +// Send roll of targetGuid to the whole group (included targuetGuid) +void LootRoll::SendRoll(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType, Optional<ObjectGuid> const& rollWinner) +{ + WorldPackets::Loot::LootRollBroadcast lootRoll; + lootRoll.LootObj = m_loot->GetGUID(); + lootRoll.Player = targetGuid; + lootRoll.Roll = rollNumber; + lootRoll.RollType = AsUnderlyingType(rollType); + lootRoll.Autopassed = false; + FillPacket(lootRoll.Item); + lootRoll.Item.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING; + lootRoll.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote == RollVote::NotValid) + continue; + + if (playerGuid == rollWinner) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootRoll.GetRawPacket()); + } + + if (rollWinner) + { + if (Player* player = ObjectAccessor::GetPlayer(m_map, *rollWinner)) + { + lootRoll.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootRoll.Clear(); + player->SendDirectMessage(lootRoll.Write()); + } + } +} + +// Send roll 'value' of the whole group and the winner to the whole group +void LootRoll::SendLootRollWon(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType) +{ + // Send roll values + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + switch (roll.Vote) + { + case RollVote::Pass: + break; + case RollVote::NotEmitedYet: + case RollVote::NotValid: + SendRoll(playerGuid, 0, RollVote::Pass, targetGuid); + break; + default: + SendRoll(playerGuid, roll.RollNumber, roll.Vote, targetGuid); + break; + } + } + + WorldPackets::Loot::LootRollWon lootRollWon; + lootRollWon.LootObj = m_loot->GetGUID(); + lootRollWon.Winner = targetGuid; + lootRollWon.Roll = rollNumber; + lootRollWon.RollType = AsUnderlyingType(rollType); + FillPacket(lootRollWon.Item); + lootRollWon.Item.UIType = LOOT_SLOT_TYPE_LOCKED; + lootRollWon.MainSpec = true; // offspec rolls not implemented + lootRollWon.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote == RollVote::NotValid) + continue; + + if (playerGuid == targetGuid) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootRollWon.GetRawPacket()); + } + + if (Player* player = ObjectAccessor::GetPlayer(m_map, targetGuid)) + { + lootRollWon.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootRollWon.Clear(); + player->SendDirectMessage(lootRollWon.Write()); + } +} + +void LootRoll::FillPacket(WorldPackets::Loot::LootItemData& lootItem) const +{ + lootItem.Quantity = m_lootItem->count; + lootItem.LootListID = m_lootListId + 1; + lootItem.CanTradeToTapList = m_lootItem->allowedGUIDs.size() > 1; + lootItem.Loot.Initialize(*m_lootItem); +} + +LootRoll::~LootRoll() +{ + if (m_isStarted) + SendAllPassed(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotEmitedYet) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->RemoveLootRoll(this); + } +} + +// Try to start the group roll for the specified item (it may fail for quest item or any condition +// If this method return false the roll have to be removed from the container to avoid any problem +bool LootRoll::TryToStart(Map* map, Loot& loot, uint32 lootListId, uint16 enchantingSkill) +{ + if (!m_isStarted) + { + if (lootListId >= loot.items.size()) + return false; + + m_map = map; + + // initialize the data needed for the roll + m_lootItem = &loot.items[lootListId]; + + m_loot = &loot; + m_lootListId = lootListId; + m_lootItem->is_blocked = true; // block the item while rolling + + uint32 playerCount = 0; + for (ObjectGuid allowedLooter : m_lootItem->GetAllowedLooters()) + { + Player* plr = ObjectAccessor::GetPlayer(m_map, allowedLooter); + if (!plr || !m_lootItem->AllowedForPlayer(plr)) // check if player meet the condition to be able to roll this item + { + m_rollVoteMap[allowedLooter].Vote = RollVote::NotValid; + continue; + } + // initialize player vote map + m_rollVoteMap[allowedLooter].Vote = plr->GetPassOnGroupLoot() ? RollVote::Pass : RollVote::NotEmitedYet; + if (!plr->GetPassOnGroupLoot()) + plr->AddLootRoll(this); + + ++playerCount; + } + + // initialize item prototype and check enchant possibilities for this group + ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(m_lootItem->itemid)); + m_voteMask = ROLL_ALL_TYPE_MASK; + if (itemTemplate->HasFlag(ITEM_FLAG2_CAN_ONLY_ROLL_GREED)) + m_voteMask = RollMask(m_voteMask & ~ROLL_FLAG_TYPE_NEED); + if (ItemDisenchantLootEntry const* disenchant = GetItemDisenchantLoot(); !disenchant || disenchant->SkillRequired > enchantingSkill) + m_voteMask = RollMask(m_voteMask & ~ROLL_FLAG_TYPE_DISENCHANT); + + if (playerCount > 1) // check if more than one player can loot this item + { + // start the roll + SendStartRoll(); + m_endTime = GameTime::Now() + LOOT_ROLL_TIMEOUT; + m_isStarted = true; + return true; + } + // no need to start roll if one or less player can loot this item so place it under threshold + m_lootItem->is_underthreshold = true; + m_lootItem->is_blocked = false; + } + return false; +} + +// Add vote from playerGuid +bool LootRoll::PlayerVote(Player* player, RollVote vote) +{ + ObjectGuid const& playerGuid = player->GetGUID(); + RollVoteMap::iterator voterItr = m_rollVoteMap.find(playerGuid); + if (voterItr == m_rollVoteMap.end()) + return false; + + voterItr->second.Vote = vote; + + if (vote != RollVote::Pass && vote != RollVote::NotValid) + voterItr->second.RollNumber = urand(1, 100); + + switch (vote) + { + case RollVote::Pass: // Player choose pass + { + SendRoll(playerGuid, -1, RollVote::Pass, {}); + break; + } + case RollVote::Need: // player choose Need + { + SendRoll(playerGuid, 0, RollVote::Need, {}); + player->UpdateCriteria(CriteriaType::RollAnyNeed, 1); + break; + } + case RollVote::Greed: // player choose Greed + { + SendRoll(playerGuid, -1, RollVote::Greed, {}); + player->UpdateCriteria(CriteriaType::RollAnyGreed, 1); + break; + } + case RollVote::Disenchant: // player choose Disenchant + { + SendRoll(playerGuid, -1, RollVote::Disenchant, {}); + player->UpdateCriteria(CriteriaType::RollAnyGreed, 1); + break; + } + default: // Roll removed case + return false; + } + return true; +} + +// check if we can found a winner for this roll or if timer is expired +bool LootRoll::UpdateRoll() +{ + RollVoteMap::const_iterator winnerItr = m_rollVoteMap.end(); + + if (AllPlayerVoted(winnerItr) || m_endTime <= GameTime::Now()) + { + Finish(winnerItr); + return true; + } + return false; +} + +bool LootRoll::IsLootItem(ObjectGuid const& lootObject, uint32 lootListId) const +{ + return m_loot->GetGUID() == lootObject && m_lootListId == lootListId; +} + +/** +* \brief Check if all player have voted and return true in that case. Also return current winner. +* \param winnerItr > will be different than m_rollCoteMap.end() if winner exist. (Someone voted greed or need) +* \returns true if all players voted +**/ +bool LootRoll::AllPlayerVoted(RollVoteMap::const_iterator& winnerItr) +{ + uint32 notVoted = 0; + bool isSomeoneNeed = false; + + winnerItr = m_rollVoteMap.end(); + for (RollVoteMap::const_iterator itr = m_rollVoteMap.begin(); itr != m_rollVoteMap.end(); ++itr) + { + switch (itr->second.Vote) + { + case RollVote::Need: + if (!isSomeoneNeed || winnerItr == m_rollVoteMap.end() || itr->second.RollNumber > winnerItr->second.RollNumber) + { + isSomeoneNeed = true; // first passage will force to set winner because need is prioritized + winnerItr = itr; + } + break; + case RollVote::Greed: + case RollVote::Disenchant: + if (!isSomeoneNeed) // if at least one need is detected then winner can't be a greed + { + if (winnerItr == m_rollVoteMap.end() || itr->second.RollNumber > winnerItr->second.RollNumber) + winnerItr = itr; + } + break; + // Explicitly passing excludes a player from winning loot, so no action required. + case RollVote::Pass: + break; + case RollVote::NotEmitedYet: + ++notVoted; + break; + default: + break; + } + } + + return notVoted == 0; +} + +ItemDisenchantLootEntry const* LootRoll::GetItemDisenchantLoot() const +{ + WorldPackets::Item::ItemInstance itemInstance; + itemInstance.Initialize(*m_lootItem); + + BonusData bonusData; + bonusData.Initialize(itemInstance); + if (!bonusData.CanDisenchant) + return nullptr; + + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(m_lootItem->itemid); + uint32 itemLevel = Item::GetItemLevel(itemTemplate, bonusData, 1, 0, 0, 0, 0, false, 0); + return Item::GetDisenchantLoot(itemTemplate, bonusData.Quality, itemLevel); +} + +// terminate the roll +void LootRoll::Finish(RollVoteMap::const_iterator winnerItr) +{ + m_lootItem->is_blocked = false; + if (winnerItr == m_rollVoteMap.end()) + { + SendAllPassed(); + } + else + { + m_lootItem->rollWinnerGUID = winnerItr->first; + + SendLootRollWon(winnerItr->first, winnerItr->second.RollNumber, winnerItr->second.Vote); + + if (Player* player = ObjectAccessor::FindConnectedPlayer(winnerItr->first)) + { + if (winnerItr->second.Vote == RollVote::Need) + player->UpdateCriteria(CriteriaType::RollNeed, m_lootItem->itemid, winnerItr->second.RollNumber); + else if (winnerItr->second.Vote == RollVote::Disenchant) + player->UpdateCriteria(CriteriaType::CastSpell, 13262); + else + player->UpdateCriteria(CriteriaType::RollGreed, m_lootItem->itemid, winnerItr->second.RollNumber); + + if (winnerItr->second.Vote == RollVote::Disenchant) + { + ItemDisenchantLootEntry const* disenchant = ASSERT_NOTNULL(GetItemDisenchantLoot()); + Loot loot(m_map, m_loot->GetOwnerGUID(), LOOT_DISENCHANTING, nullptr); + loot.FillLoot(disenchant->ID, LootTemplates_Disenchant, player, true, false, LOOT_MODE_DEFAULT, ItemContext::NONE); + if (!loot.AutoStore(player, NULL_BAG, NULL_SLOT, true)) + { + uint32 maxSlot = loot.GetMaxSlotInLootFor(player); + for (uint32 i = 0; i < maxSlot; ++i) + if (LootItem* disenchantLoot = loot.LootItemInSlot(i, player)) + player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context); + } + else + m_loot->NotifyItemRemoved(m_lootItem->LootListId, m_map); + } + else + player->StoreLootItem(m_loot->GetOwnerGUID(), m_lootListId, m_loot); + } + } + m_isStarted = false; +} + +// // --------- Loot --------- // -Loot::Loot(Map* map, ObjectGuid owner, LootType type, LootMethod lootMethod) : gold(0), unlootedCount(0), loot_type(type), maxDuplicates(1), +Loot::Loot(Map* map, ObjectGuid owner, LootType type, Group const* group) : gold(0), unlootedCount(0), loot_type(type), maxDuplicates(1), _guid(map ? ObjectGuid::Create<HighGuid::LootObject>(map->GetId(), 0, map->GenerateLowGuid<HighGuid::LootObject>()) : ObjectGuid::Empty), - _owner(owner), _itemContext(ItemContext::NONE), _lootMethod(lootMethod) + _owner(owner), _itemContext(ItemContext::NONE), _lootMethod(group ? group->GetLootMethod() : FREE_FOR_ALL), + _lootMaster(group ? group->GetMasterLooterGuid() : ObjectGuid::Empty), _wasOpened(false) { } @@ -163,8 +574,28 @@ void Loot::clear() unlootedCount = 0; roundRobinPlayer.Clear(); loot_type = LOOT_NONE; - i_LootValidatorRefManager.clearReferences(); _itemContext = ItemContext::NONE; + _rolls.clear(); +} + +void Loot::NotifyLootList(Map const* map) const +{ + WorldPackets::Loot::LootList lootList; + + lootList.Owner = GetOwnerGUID(); + lootList.LootObj = GetGUID(); + + if (GetLootMethod() == MASTER_LOOT && hasOverThresholdItem()) + lootList.Master = GetLootMasterGUID(); + + if (!roundRobinPlayer.IsEmpty()) + lootList.RoundRobinWinner = roundRobinPlayer; + + lootList.Write(); + + for (ObjectGuid allowedLooterGuid : _allowedLooters) + if (Player* allowedLooter = ObjectAccessor::GetPlayer(map, allowedLooterGuid)) + allowedLooter->SendDirectMessage(lootList.GetRawPacket()); } void Loot::NotifyItemRemoved(uint8 lootIndex, Map const* map) @@ -230,6 +661,59 @@ void Loot::NotifyMoneyRemoved(Map const* map) } } +void Loot::OnLootOpened(Map* map, ObjectGuid looter) +{ + AddLooter(looter); + if (!_wasOpened) + { + _wasOpened = true; + + if (_lootMethod == GROUP_LOOT || _lootMethod == NEED_BEFORE_GREED) + { + uint16 maxEnchantingSkill = 0; + for (ObjectGuid allowedLooterGuid : _allowedLooters) + if (Player* allowedLooter = ObjectAccessor::GetPlayer(map, allowedLooterGuid)) + maxEnchantingSkill = std::max(maxEnchantingSkill, allowedLooter->GetSkillValue(SKILL_ENCHANTING)); + + uint32 lootListId = 0; + for (; lootListId < items.size(); ++lootListId) + { + LootItem& item = items[lootListId]; + if (!item.is_blocked) + continue; + + auto&& [itr, inserted] = _rolls.try_emplace(lootListId); + if (!itr->second.TryToStart(map, *this, lootListId, maxEnchantingSkill)) + _rolls.erase(itr); + } + + for (; lootListId - items.size() < quest_items.size(); ++lootListId) + { + LootItem& item = quest_items[lootListId - items.size()]; + if (!item.is_blocked) + continue; + + auto&& [itr, inserted] = _rolls.try_emplace(lootListId); + if (!itr->second.TryToStart(map, *this, lootListId, maxEnchantingSkill)) + _rolls.erase(itr); + } + } + else if (_lootMethod == MASTER_LOOT) + { + if (looter == _lootMaster) + { + if (Player* lootMaster = ObjectAccessor::GetPlayer(map, looter)) + { + WorldPackets::Loot::MasterLootCandidateList masterLootCandidateList; + masterLootCandidateList.LootObj = GetGUID(); + masterLootCandidateList.Players = _allowedLooters; + lootMaster->SendDirectMessage(masterLootCandidateList.Write()); + } + } + } + } +} + void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount) { if (maxAmount > 0) @@ -274,19 +758,52 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) if (Player const* player = itr->GetSource()) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter - if (player->IsInMap(lootOwner)) - FillNotNormalLootFor(player, player->IsAtGroupRewardDistance(lootOwner)); + if (player->IsAtGroupRewardDistance(lootOwner)) + FillNotNormalLootFor(player); - for (uint8 i = 0; i < items.size(); ++i) + auto processLootItem = [&](LootItem& item) { - if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(items[i].itemid)) + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid)) + { if (proto->GetQuality() < uint32(group->GetLootThreshold())) - items[i].is_underthreshold = true; + item.is_underthreshold = true; + else + { + switch (_lootMethod) + { + case MASTER_LOOT: + case GROUP_LOOT: + case NEED_BEFORE_GREED: + { + item.is_blocked = true; + break; + } + default: + break; + } + } + } + }; + + for (LootItem& item : items) + { + if (item.freeforall) + continue; + + processLootItem(item); + } + + for (LootItem& item : quest_items) + { + if (!item.follow_loot_rules) + continue; + + processLootItem(item); } } // ... for personal loot else - FillNotNormalLootFor(lootOwner, true); + FillNotNormalLootFor(lootOwner); return true; } @@ -309,7 +826,7 @@ void Loot::AddItem(LootStoreItem const& item, Player const* player) LootItem generatedLoot(item); generatedLoot.context = _itemContext; generatedLoot.count = std::min(count, proto->GetMaxStackSize()); - generatedLoot.itemIndex = lootItems.size(); + generatedLoot.LootListId = lootItems.size(); if (_itemContext != ItemContext::NONE) { std::set<uint32> bonusListIDs = sDB2Manager.GetDefaultItemBonusTree(generatedLoot.itemid, _itemContext); @@ -342,6 +859,63 @@ void Loot::AddItem(LootStoreItem const& item, Player const* player) } } +bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool createdByPlayer) +{ + bool allLooted = true; + uint32 max_slot = GetMaxSlotInLootFor(player); + for (uint32 i = 0; i < max_slot; ++i) + { + NotNormalLootItem* qitem = nullptr; + NotNormalLootItem* ffaitem = nullptr; + NotNormalLootItem* conditem = nullptr; + + LootItem* lootItem = LootItemInSlot(i, player, &qitem, &ffaitem, &conditem); + if (!lootItem || lootItem->is_looted) + continue; + + if (!lootItem->AllowedForPlayer(player)) + continue; + + if (!qitem && lootItem->is_blocked) + continue; + + // dont allow protected item to be looted by someone else + 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) + { + player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid); + allLooted = false; + continue; + } + + if (qitem) + qitem->is_looted = true; + else if (ffaitem) + ffaitem->is_looted = true; + else if (conditem) + conditem->is_looted = true; + + if (!lootItem->freeforall) + lootItem->is_looted = true; + + --unlootedCount; + + Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, lootItem->BonusListIDs); + player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast); + player->ApplyItemLootedSpell(pItem, true); + } + + return allLooted; +} + LootItem const* Loot::GetItemInSlot(uint32 lootSlot) const { if (lootSlot < items.size()) @@ -564,6 +1138,26 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v } break; } + case ROUND_ROBIN_PERMISSION: + { + for (uint8 i = 0; i < items.size(); ++i) + { + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.empty() && items[i].AllowedForPlayer(viewer)) + { + if (!roundRobinPlayer.IsEmpty() && viewer->GetGUID() != roundRobinPlayer) + // item shall not be displayed. + continue; + + WorldPackets::Loot::LootItemData lootItem; + lootItem.LootListID = i + 1; + lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootItem.Quantity = items[i].count; + lootItem.Loot.Initialize(items[i]); + packet.Items.push_back(lootItem); + } + } + break; + } case ALL_PERMISSION: case OWNER_PERMISSION: { @@ -613,6 +1207,7 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT; break; case GROUP_PERMISSION: + case ROUND_ROBIN_PERMISSION: if (!item.is_blocked) lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; else @@ -675,6 +1270,7 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT; break; case GROUP_PERMISSION: + case ROUND_ROBIN_PERMISSION: if (!item.is_blocked) lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; else @@ -691,9 +1287,21 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v } } -void Loot::FillNotNormalLootFor(Player const* player, bool presentAtLooting) +void Loot::Update() +{ + for (auto itr = _rolls.begin(); itr != _rolls.end(); ) + { + if (itr->second.UpdateRoll()) + itr = _rolls.erase(itr); + else + ++itr; + } +} + +void Loot::FillNotNormalLootFor(Player const* player) { ObjectGuid plguid = player->GetGUID(); + _allowedLooters.insert(plguid); NotNormalLootItemMap::const_iterator qmapitr = PlayerQuestItems.find(plguid); if (qmapitr == PlayerQuestItems.end()) @@ -705,7 +1313,7 @@ void Loot::FillNotNormalLootFor(Player const* player, bool presentAtLooting) qmapitr = PlayerNonQuestNonFFAConditionalItems.find(plguid); if (qmapitr == PlayerNonQuestNonFFAConditionalItems.end()) - FillNonQuestNonFFAConditionalLoot(player, presentAtLooting); + FillNonQuestNonFFAConditionalLoot(player); } NotNormalLootItemList* Loot::FillFFALoot(Player const* player) @@ -744,6 +1352,8 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player->GetGroup() && ((GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || GetLootMethod() != MASTER_LOOT)))) { + item.AddAllowedLooter(player); + ql->push_back(NotNormalLootItem(i)); // quest items get blocked when they first appear in a @@ -752,7 +1362,7 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) // increase once if one looter only, looter-times if free for all if (item.freeforall || !item.is_blocked) ++unlootedCount; - if (!player->GetGroup() || (GetLootMethod() != GROUP_LOOT)) + if (!player->GetGroup() || (GetLootMethod() != GROUP_LOOT && GetLootMethod() != ROUND_ROBIN)) item.is_blocked = true; if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS) @@ -769,7 +1379,7 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) return ql; } -NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* player, bool presentAtLooting) +NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* player) { NotNormalLootItemList* ql = new NotNormalLootItemList(); @@ -778,8 +1388,7 @@ NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* pla LootItem &item = items[i]; if (!item.is_looted && !item.freeforall && (item.AllowedForPlayer(player))) { - if (presentAtLooting) - item.AddAllowedLooter(player); + item.AddAllowedLooter(player); if (!item.conditions.empty()) { ql->push_back(NotNormalLootItem(i)); diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h index 68007173919..a75862f05dc 100644 --- a/src/server/game/Loot/Loot.h +++ b/src/server/game/Loot/Loot.h @@ -18,20 +18,25 @@ #ifndef Loot_h__ #define Loot_h__ -#include "Define.h" #include "ConditionMgr.h" #include "DBCEnums.h" +#include "Define.h" +#include "Duration.h" #include "ItemEnchantmentMgr.h" #include "ObjectGuid.h" -#include "RefManager.h" +#include "Optional.h" #include "SharedDefines.h" #include <unordered_map> #include <vector> +constexpr Minutes LOOT_ROLL_TIMEOUT = 1min; + +class Group; class Item; class LootStore; class Map; class Player; +struct ItemDisenchantLootEntry; struct Loot; struct LootStoreItem; @@ -39,6 +44,7 @@ namespace WorldPackets { namespace Loot { + struct LootItemData; class LootResponse; } } @@ -52,6 +58,16 @@ enum RollType MAX_ROLL_TYPE = 4 }; +enum class RollVote +{ + Pass = 0, + Need = 1, + Greed = 2, + Disenchant = 3, + NotEmitedYet = 4, + NotValid = 5 +}; + enum RollMask { ROLL_FLAG_TYPE_PASS = 0x01, @@ -71,8 +87,10 @@ enum RollMask enum LootMethod : uint8 { FREE_FOR_ALL = 0, + ROUND_ROBIN = 1, MASTER_LOOT = 2, GROUP_LOOT = 3, + NEED_BEFORE_GREED = 4, PERSONAL_LOOT = 5 }; @@ -82,6 +100,7 @@ enum PermissionTypes GROUP_PERMISSION = 1, MASTER_PERMISSION = 2, RESTRICTED_PERMISSION = 3, + ROUND_ROBIN_PERMISSION = 4, OWNER_PERMISSION = 5, NONE_PERMISSION = 6 }; @@ -156,7 +175,7 @@ enum LootSlotType struct TC_GAME_API LootItem { uint32 itemid; - uint32 itemIndex; + uint32 LootListId; ItemRandomBonusListId randomBonusListId; std::vector<int32> BonusListIDs; ItemContext context; @@ -177,8 +196,8 @@ struct TC_GAME_API LootItem explicit LootItem(LootStoreItem const& li); // Empty constructor for creating an empty LootItem to be filled in with DB data - LootItem() : itemid(0), itemIndex(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() : 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) { } // Basic checks for player/item compatibility - if false no chance to see the item in the loot bool AllowedForPlayer(Player const* player, bool isGivenByMasterLooter = false) const; @@ -204,28 +223,50 @@ typedef std::unordered_map<ObjectGuid, NotNormalLootItemList*> NotNormalLootItem //===================================================== -class LootValidatorRef : public Reference<Loot, LootValidatorRef> +struct PlayerRollVote { -public: - LootValidatorRef() { } - void targetObjectDestroyLink() override { } - void sourceObjectDestroyLink() override { } + PlayerRollVote() : Vote(RollVote::NotValid), RollNumber(0) { } + RollVote Vote; + uint8 RollNumber; }; -//===================================================== - -class LootValidatorRefManager : public RefManager<Loot, LootValidatorRef> +class LootRoll { public: - typedef LinkedListHead::Iterator<LootValidatorRef> iterator; + using RollVoteMap = std::unordered_map<ObjectGuid, PlayerRollVote>; - LootValidatorRef* getFirst() { return (LootValidatorRef*)RefManager<Loot, LootValidatorRef>::getFirst(); } + LootRoll() : m_map(nullptr), m_isStarted(false), m_lootItem(nullptr), m_loot(nullptr), m_lootListId(0), m_voteMask(), m_endTime(TimePoint::min()) { } + ~LootRoll(); - iterator begin() { return iterator(getFirst()); } - iterator end() { return iterator(nullptr); } -}; + LootRoll(LootRoll const&) = delete; + LootRoll(LootRoll&&) = delete; + LootRoll& operator=(LootRoll const&) = delete; + LootRoll& operator=(LootRoll&&) = delete; -//===================================================== + bool TryToStart(Map* map, Loot& loot, uint32 lootListId, uint16 enchantingSkill); + bool PlayerVote(Player* player, RollVote vote); + bool UpdateRoll(); + + bool IsLootItem(ObjectGuid const& lootObject, uint32 lootListId) const; + +private: + void SendStartRoll(); + void SendAllPassed(); + void SendRoll(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType, Optional<ObjectGuid> const& rollWinner); + void SendLootRollWon(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType); + void FillPacket(WorldPackets::Loot::LootItemData& lootItem) const; + void Finish(RollVoteMap::const_iterator winnerItr); + bool AllPlayerVoted(RollVoteMap::const_iterator& winnerItr); + ItemDisenchantLootEntry const* GetItemDisenchantLoot() const; + Map* m_map; + RollVoteMap m_rollVoteMap; + bool m_isStarted; + LootItem* m_lootItem; + Loot* m_loot; + uint32 m_lootListId; + RollMask m_voteMask; + TimePoint m_endTime; +}; struct TC_GAME_API Loot { @@ -241,27 +282,29 @@ struct TC_GAME_API Loot LootType loot_type; // required for achievement system uint8 maxDuplicates; // Max amount of items with the same entry that can drop (default is 1; on 25 man raid mode 3) - explicit Loot(Map* map, ObjectGuid owner, LootType type, LootMethod lootMethod); + explicit Loot(Map* map, ObjectGuid owner, LootType type, Group const* group); ~Loot(); + Loot(Loot const&) = delete; + Loot(Loot&&) = delete; + Loot& operator=(Loot const&) = delete; + Loot& operator=(Loot&&) = delete; + ObjectGuid const& GetGUID() const { return _guid; } ObjectGuid const& GetOwnerGUID() const { return _owner; } LootMethod GetLootMethod() const { return _lootMethod; } - - // if loot becomes invalid this reference is used to inform the listener - void addLootValidatorRef(LootValidatorRef* pLootValidatorRef) - { - i_LootValidatorRefManager.insertFirst(pLootValidatorRef); - } + ObjectGuid const& GetLootMasterGUID() const { return _lootMaster; } void clear(); bool empty() const { return items.empty() && gold == 0; } bool isLooted() const { return gold == 0 && unlootedCount == 0; } + void NotifyLootList(Map const* map) const; void NotifyItemRemoved(uint8 lootIndex, Map const* map); void NotifyQuestItemRemoved(uint8 questIndex, Map const* map); void NotifyMoneyRemoved(Map const* map); + void OnLootOpened(Map* map, ObjectGuid looter); void AddLooter(ObjectGuid GUID) { PlayersLooting.insert(GUID); } void RemoveLooter(ObjectGuid GUID) { PlayersLooting.erase(GUID); } @@ -271,6 +314,8 @@ struct TC_GAME_API Loot // Inserts the item into the loot (called by LootTemplate processors) void AddItem(LootStoreItem const& item, Player const* player); + bool AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast = false, bool createdByPlayer = false); + LootItem const* GetItemInSlot(uint32 lootSlot) const; LootItem* LootItemInSlot(uint32 lootslot, Player* player, NotNormalLootItem** qitem = nullptr, NotNormalLootItem** ffaitem = nullptr, NotNormalLootItem** conditem = nullptr); uint32 GetMaxSlotInLootFor(Player* player) const; @@ -281,26 +326,29 @@ struct TC_GAME_API Loot // Builds data for SMSG_LOOT_RESPONSE void BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* viewer, PermissionTypes permission = ALL_PERMISSION) const; + void Update(); + private: - void FillNotNormalLootFor(Player const* player, bool presentAtLooting); + void FillNotNormalLootFor(Player const* player); NotNormalLootItemList* FillFFALoot(Player const* player); NotNormalLootItemList* FillQuestLoot(Player const* player); - NotNormalLootItemList* FillNonQuestNonFFAConditionalLoot(Player const* player, bool presentAtLooting); + NotNormalLootItemList* FillNonQuestNonFFAConditionalLoot(Player const* player); GuidSet PlayersLooting; NotNormalLootItemMap PlayerQuestItems; NotNormalLootItemMap PlayerFFAItems; NotNormalLootItemMap PlayerNonQuestNonFFAConditionalItems; - // All rolls are registered here. They need to know, when the loot is not valid anymore - LootValidatorRefManager i_LootValidatorRefManager; - // Loot GUID ObjectGuid _guid; ObjectGuid _owner; // The WorldObject that holds this loot ItemContext _itemContext; LootMethod _lootMethod; + std::unordered_map<uint32, LootRoll> _rolls; // used if an item is under rolling + ObjectGuid _lootMaster; + GuidUnorderedSet _allowedLooters; + bool _wasOpened; // true if at least one player received the loot content }; class TC_GAME_API AELootResult diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp index 69b68acde70..605b2e6c2f9 100644 --- a/src/server/game/Loot/LootItemStorage.cpp +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -33,7 +33,7 @@ namespace std::unordered_map<uint64, StoredLootContainer> _lootItemStore; } -StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), ItemIndex(lootItem.itemIndex), FollowRules(lootItem.follow_loot_rules), +StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), ItemIndex(lootItem.LootListId), FollowRules(lootItem.follow_loot_rules), FFA(lootItem.freeforall), Blocked(lootItem.is_blocked), Counted(lootItem.is_counted), UnderThreshold(lootItem.is_underthreshold), NeedsQuest(lootItem.needs_quest), RandomBonusListId(lootItem.randomBonusListId), Context(lootItem.context), BonusListIDs(lootItem.BonusListIDs) { @@ -81,7 +81,7 @@ void LootItemStorage::LoadStorageFromDB() LootItem lootItem; lootItem.itemid = fields[1].GetUInt32(); lootItem.count = fields[2].GetUInt32(); - lootItem.itemIndex = fields[3].GetUInt32(); + lootItem.LootListId = fields[3].GetUInt32(); lootItem.follow_loot_rules = fields[4].GetBool(); lootItem.freeforall = fields[5].GetBool(); lootItem.is_blocked = fields[6].GetBool(); @@ -161,7 +161,7 @@ bool LootItemStorage::LoadStoredLoot(Item* item, Player* player) LootItem li; li.itemid = storedItemPair.first; li.count = storedItemPair.second.Count; - li.itemIndex = storedItemPair.second.ItemIndex; + li.LootListId = storedItemPair.second.ItemIndex; li.follow_loot_rules = storedItemPair.second.FollowRules; li.freeforall = storedItemPair.second.FFA; li.is_blocked = storedItemPair.second.Blocked; @@ -303,7 +303,7 @@ void StoredLootContainer::AddLootItem(LootItem const& lootItem, CharacterDatabas stmt->setUInt64(0, _containerId); stmt->setUInt32(1, lootItem.itemid); stmt->setUInt32(2, lootItem.count); - stmt->setUInt32(3, lootItem.itemIndex); + stmt->setUInt32(3, lootItem.LootListId); stmt->setBool(4, lootItem.follow_loot_rules); stmt->setBool(5, lootItem.freeforall); stmt->setBool(6, lootItem.is_blocked); |
