Skip to content

Commit

Permalink
Merge branch '3.3.5-aoe-loot-mail-excess' into 3.3.5-andrew_world
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-wroe committed Sep 8, 2024
2 parents 65f808b + 6847dc5 commit dd3ee14
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 25 deletions.
6 changes: 6 additions & 0 deletions src/server/game/AI/ScriptedAI/ScriptedCreature.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,12 @@ inline void GetCreatureListWithEntryInGrid(Container& container, WorldObject* so
source->GetCreatureListWithEntryInGrid(container, entry, maxSearchRange);
}

template <typename Container>
inline void GetDeadCreatureListInGrid(Container& container, WorldObject* source, float maxSearchRange, bool alive = false)
{
source->GetDeadCreatureListInGrid(container, maxSearchRange, alive);
}

template <typename Container>
inline void GetGameObjectListWithEntryInGrid(Container& container, WorldObject* source, uint32 entry, float maxSearchRange)
{
Expand Down
12 changes: 12 additions & 0 deletions src/server/game/Entities/Object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3128,6 +3128,14 @@ void WorldObject::GetCreatureListWithEntryInGrid(Container& creatureContainer, u
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}

template <typename Container>
void WorldObject::GetDeadCreatureListInGrid(Container& creaturedeadContainer, float maxSearchRange, bool alive /*= false*/) const
{
Trinity::AllDeadCreaturesInRange check(this, maxSearchRange, alive);
Trinity::CreatureListSearcher<Trinity::AllDeadCreaturesInRange> searcher(this, creaturedeadContainer, check);
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}

template <typename Container>
void WorldObject::GetPlayerListInGrid(Container& playerContainer, float maxSearchRange, bool alive /*= true*/) const
{
Expand Down Expand Up @@ -3585,6 +3593,10 @@ template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::list<
template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::deque<Creature*>&, uint32, float) const;
template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::vector<Creature*>&, uint32, float) const;

template TC_GAME_API void WorldObject::GetDeadCreatureListInGrid(std::list<Creature*>&, float, bool) const;
template TC_GAME_API void WorldObject::GetDeadCreatureListInGrid(std::deque<Creature*>&, float, bool) const;
template TC_GAME_API void WorldObject::GetDeadCreatureListInGrid(std::vector<Creature*>&, float, bool) const;

template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::list<Player*>&, float, bool) const;
template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::deque<Player*>&, float, bool) const;
template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::vector<Player*>&, float, bool) const;
3 changes: 3 additions & 0 deletions src/server/game/Entities/Object/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
template <typename Container>
void GetCreatureListWithEntryInGrid(Container& creatureContainer, uint32 entry, float maxSearchRange = 250.0f) const;

template <typename Container>
void GetDeadCreatureListInGrid(Container& creaturedeadContainer, float maxSearchRange, bool alive = false) const;

template <typename Container>
void GetPlayerListInGrid(Container& playerContainer, float maxSearchRange, bool alive = true) const;

Expand Down
51 changes: 38 additions & 13 deletions src/server/game/Entities/Player/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15115,7 +15115,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver,
SendNewItem(item, quest->RewardItemIdCount[i], true, false, false, false);
}
else if (quest->IsDFQuest())
SendItemRetrievalMail(itemId, quest->RewardItemIdCount[i]);
SendItemRetrievalMail({ { itemId, quest->RewardItemIdCount[i], GenerateItemRandomPropertyId(itemId) } });
}
}
}
Expand Down Expand Up @@ -24783,8 +24783,16 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot)

if (!item || item->is_looted)
{
SendEquipError(EQUIP_ERR_ALREADY_LOOTED, nullptr, nullptr);
return;
if (sConfigMgr->GetBoolDefault("AOE.LOOT.enable", true))
{
//SendEquipError(EQUIP_ERR_ALREADY_LOOTED, nullptr, nullptr); prevents error already loot from spamming
return;
}
else
{
SendEquipError(EQUIP_ERR_ALREADY_LOOTED, nullptr, nullptr);
return;
}
}

if (!item->AllowedForPlayer(this))
Expand Down Expand Up @@ -26251,13 +26259,15 @@ void Player::SendRefundInfo(Item* item)
SendDirectMessage(&data);
}

bool Player::AddItem(uint32 itemId, uint32 count)
bool Player::AddItem(uint32 itemId, uint32 count, InventoryResult* error)
{
uint32 noSpaceForCount = 0;
ItemPosCountVec dest;
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, count, &noSpaceForCount);
if (msg != EQUIP_ERR_OK)
count -= noSpaceForCount;
if (error)
*error = msg;

if (count == 0 || dest.empty())
{
Expand Down Expand Up @@ -26397,19 +26407,34 @@ void Player::RefundItem(Item* item)
CharacterDatabase.CommitTransaction(trans);
}

void Player::SendItemRetrievalMail(uint32 itemEntry, uint32 count)
void Player::SendItemRetrievalMail(std::vector<std::tuple<uint32 /*entry*/, uint32 /*count*/, int32 /*randomPropertyId*/>> const& items)
{
MailSender sender(MAIL_CREATURE, 34337 /* The Postmaster */);
MailDraft draft("Recovered Item", "We recovered a lost item in the twisting nether and noted that it was yours.$B$BPlease find said object enclosed."); // This is the text used in Cataclysm, it probably wasn't changed.
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();

if (Item* item = Item::CreateItem(itemEntry, count, nullptr))
auto it = items.begin();
while (it != items.end())
{
item->SaveToDB(trans);
draft.AddItem(item);
}
MailSender sender(MAIL_CREATURE, 34337 /* The Postmaster */);
MailDraft draft("Recovered Item", "We recovered a lost item in the twisting nether and noted that it was yours.$B$BPlease find said object enclosed."); // This is the text used in Cataclysm, it probably wasn't changed.

uint32 addedItemCount = 0;
while (it != items.end())
{
auto& [itemEntry, count, randomPropertyId] = *it;
if (Item* item = Item::CreateItem(itemEntry, count, nullptr))
{
if (randomPropertyId)
item->SetItemRandomProperties(randomPropertyId);
item->SaveToDB(trans);
draft.AddItem(item);
addedItemCount++;
}
++it;
if (addedItemCount >= MAX_MAIL_ITEMS)
break;
}

draft.SendMailTo(trans, MailReceiver(this, GetGUID().GetCounter()), sender);
draft.SendMailTo(trans, MailReceiver(this, GetGUID().GetCounter()), sender);
}
CharacterDatabase.CommitTransaction(trans);
}

Expand Down
4 changes: 2 additions & 2 deletions src/server/game/Entities/Player/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void LoadCorpse(PreparedQueryResult result);
void LoadPet();

bool AddItem(uint32 itemId, uint32 count);
bool AddItem(uint32 itemId, uint32 count, InventoryResult* error = nullptr);

/*********************************************************/
/*** GOSSIP SYSTEM ***/
Expand Down Expand Up @@ -1394,7 +1394,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>

PlayerMails const& GetMails() const { return m_mail; }

void SendItemRetrievalMail(uint32 itemEntry, uint32 count); // Item retrieval mails sent by The Postmaster (34337), used in multiple places.
void SendItemRetrievalMail(std::vector<std::tuple<uint32 /*entry*/, uint32 /*count*/, int32 /*randomPropertyId*/>> const& items); // Item retrieval mails sent by The Postmaster (34337), used in multiple places.

/*********************************************************/
/*** MAILED ITEMS SYSTEM ***/
Expand Down
22 changes: 22 additions & 0 deletions src/server/game/Grids/Notifiers/GridNotifiers.h
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,28 @@ namespace Trinity
float m_fRange;
};

class AllDeadCreaturesInRange
{
public:
AllDeadCreaturesInRange(WorldObject const* obj, float range, bool reqAlive = true) : _obj(obj), _range(range), _reqAlive(reqAlive) { }

bool operator()(Unit* unit) const
{
if (_reqAlive && unit->IsAlive())
return false;

if (!_obj->IsWithinDistInMap(unit, _range))
return false;

return true;
}

private:
WorldObject const* _obj;
float _range;
bool _reqAlive;
};

class PlayerAtMinimumRangeAway
{
public:
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Groups/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1637,7 +1637,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap)
{
LootItem* lootItem = loot.LootItemInSlot(i, player);
player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid);
player->SendItemRetrievalMail(lootItem->itemid, lootItem->count);
player->SendItemRetrievalMail({ { lootItem->itemid, static_cast<uint32>(lootItem->count), lootItem->randomPropertyId } });
}
}
}
Expand Down
117 changes: 108 additions & 9 deletions src/server/game/Handlers/LootHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "WorldSession.h"
#include "Common.h"
#include "Config.h"
#include "Corpse.h"
#include "Creature.h"
#include "GameObject.h"
Expand Down Expand Up @@ -79,16 +80,92 @@ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recvData)
}
else
{
Creature* creature = GetPlayer()->GetMap()->GetCreature(lguid);
Group* group = player->GetGroup();
Creature* creature = player->GetMap()->GetCreature(lguid);
if ((!group || group->GetMembersCount() == 1) && creature && sConfigMgr->GetBoolDefault("AOE.LOOT.enable", true))
{
std::map<uint32, std::map<int32, uint32>> items;

float range = 30.0f;
std::vector<Creature*> creaturedie;
player->GetDeadCreatureListInGrid(creaturedie, range);
std::string filter = sConfigMgr->GetStringDefault("AOE.MAIL.filter", "");
std::vector<std::string_view> filters = Trinity::Tokenize(filter, ',', false);
for (std::vector<Creature*>::iterator itr = creaturedie.begin(); itr != creaturedie.end(); ++itr)
{
Creature* c = *itr;
loot = &c->loot;

uint8 maxSlot = loot->GetMaxSlotInLootFor(player);
for (int i = 0; i < maxSlot; ++i)
{
if (LootItem* item = loot->LootItemInSlot(i, player))
{
InventoryResult res = EQUIP_ERR_OK;
if (player->AddItem(item->itemid, item->count, &res))
{
player->SendNotifyLootItemRemoved(lootSlot);
player->SendLootRelease(lguid);
}
else if (filter.empty() || std::find(filters.begin(), filters.end(), std::to_string(res)) == filters.end())
{
items[item->itemid][item->randomPropertyId] += item->count;
}
}
}

// This if covers a issue with skinning being infinite by Aokromes
if (!creature->IsAlive())
creature->AllLootRemovedFromCorpse();

loot->clear();

if (loot->isLooted() && loot->empty())
{
c->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
c->AllLootRemovedFromCorpse();
}
}

bool lootAllowed = creature && creature->IsAlive() == (player->GetClass() == CLASS_ROGUE && creature->loot.loot_type == LOOT_PICKPOCKETING);
if (!lootAllowed || !creature->IsWithinDistInMap(_player, INTERACTION_DISTANCE))
if (!items.empty())
{
std::vector<std::tuple<uint32, uint32, int32>> itemsStacked;
for (auto& entryToData : items)
{
for (auto& randomPropToCount : entryToData.second)
{
uint32 entry = entryToData.first;
int32 randomPropertyId = randomPropToCount.first;
uint32 count = randomPropToCount.second;
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(entry);
uint32 maxStack = itemTemplate->GetMaxStackSize();
while (count > maxStack)
{
itemsStacked.push_back({ entry, maxStack, randomPropertyId });
count -= maxStack;
}
if (count > 0)
{
itemsStacked.push_back({ entry, count, randomPropertyId });
}
}
}
player->SendItemRetrievalMail(itemsStacked);
player->GetSession()->SendAreaTriggerMessage("Your items have been mailed to you.");
}
}
else
{
player->SendLootError(lguid, lootAllowed ? LOOT_ERROR_TOO_FAR : LOOT_ERROR_DIDNT_KILL);
return;
bool lootAllowed = creature && creature->IsAlive() == (player->GetClass() == CLASS_ROGUE && creature->loot.loot_type == LOOT_PICKPOCKETING);
if (!lootAllowed || !creature->IsWithinDistInMap(_player, INTERACTION_DISTANCE))
{
player->SendLootError(lguid, lootAllowed ? LOOT_ERROR_TOO_FAR : LOOT_ERROR_DIDNT_KILL);
return;
}

loot = &creature->loot;
}

loot = &creature->loot;
}

player->StoreLootItem(lootSlot, loot);
Expand All @@ -114,7 +191,7 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/)
{
case HighGuid::GameObject:
{
GameObject* go = GetPlayer()->GetMap()->GetGameObject(guid);
GameObject* go = player->GetMap()->GetGameObject(guid);

// do not check distance for GO if player is the owner of it (ex. fishing bobber)
if (go && ((go->GetOwnerGUID() == player->GetGUID() || go->IsWithinDistInMap(player))))
Expand Down Expand Up @@ -195,6 +272,28 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/)
}
else
{
Creature* creature = player->GetMap()->GetCreature(guid);
if (creature && sConfigMgr->GetBoolDefault("AOE.LOOT.enable", true))
{
Group* group = player->GetGroup();
if (!group || group->GetMembersCount() == 1)
{
float range = 30.0f;
uint32 gold = 0;
Creature* c = nullptr;
std::vector<Creature*> creaturedie;
player->GetDeadCreatureListInGrid(creaturedie, range);
for (std::vector<Creature*>::iterator itr = creaturedie.begin(); itr != creaturedie.end(); ++itr)
{
c = *itr;
Loot* loot = &c->loot;
gold += loot->gold;
loot->gold = 0;
}
loot->gold += gold;
}
}

player->ModifyMoney(loot->gold);
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_MONEY, loot->gold);

Expand Down Expand Up @@ -265,7 +364,7 @@ void WorldSession::DoLootRelease(ObjectGuid lguid)

if (lguid.IsGameObject())
{
GameObject* go = GetPlayer()->GetMap()->GetGameObject(lguid);
GameObject* go = player->GetMap()->GetGameObject(lguid);

// not check distance for GO in case owned GO (fishing bobber case, for example) or Fishing hole GO
if (!go || ((go->GetOwnerGUID() != _player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(_player)))
Expand Down Expand Up @@ -349,7 +448,7 @@ void WorldSession::DoLootRelease(ObjectGuid lguid)
}
else
{
Creature* creature = GetPlayer()->GetMap()->GetCreature(lguid);
Creature* creature = player->GetMap()->GetCreature(lguid);

bool lootAllowed = creature && creature->IsAlive() == (player->GetClass() == CLASS_ROGUE && creature->loot.loot_type == LOOT_PICKPOCKETING);
if (!lootAllowed || !creature->IsWithinDistInMap(_player, INTERACTION_DISTANCE))
Expand Down
26 changes: 26 additions & 0 deletions src/server/worldserver/worldserver.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4161,3 +4161,29 @@ SoloLFG.Enable = 1

#
###################################################################################################

###################################################################################################
#AOE Looting and Mail Excess to Player#
#######################################
#
# AOE Looting and Mail Excess
# Enable this if you want to enable Area of effect with looting (MOP Feature)
# Excess items that is looted in will be mailed to the player
#
# AOE.LOOT.enable
# Description: Enables Module
# Default: 0 - (Disabled)
# 1 - (Enabled)

AOE.LOOT.enable = 1

# AOE.MAIL.filter
# Description: Filter out items that cannot be stored and do not mail them
# Example: "17,10" - (EQUIP_ERR_CANT_CARRY_MORE_OF_THIS,
# EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM)
# Default: "" - (Disabled)

AOE.MAIL.filter = ""

#
###################################################################################################

0 comments on commit dd3ee14

Please sign in to comment.