feat(loot): rewards, purchases, mail and trades say where items came from

The other item sources live marked in AddItemToInventoryClientSync's extra
info, with the keys and values live sent:
- mission and achievement rewards: _Metric_Mission_ID_Int and source LOT 1,
  the player (225 of 225 live rewards)
- activity rewards: _Metric_Activity_ID_Int and the activity object's LOT
- vendor purchases: the vendor's LOT and _Metric_Currency_Delta_Int, the coins
  paid as a negative number (left out when the item costs no coins)
- mail attachments: _Metric_Mail_ID_Int64
- traded items: _Metric_Transaction_ID_Int64, the trade's ID

Loot::GiveLoot gains overloads that pass them on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-30 07:46:05 -05:00
parent 898ae024f0
commit ed2d329781
6 changed files with 43 additions and 11 deletions

View File

@@ -523,14 +523,22 @@ Loot::Return Loot::RollLootMatrix(Entity* player, uint32_t matrixIndex) {
}
void Loot::GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType) {
GiveLoot(player, matrixIndex, lootSourceType, LootMetrics{});
}
void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType) {
GiveLoot(player, result, lootSourceType, LootMetrics{});
}
void Loot::GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType, const LootMetrics& metrics) {
player = player->GetOwner(); // If the owner is overwritten, we collect that here
const auto result = RollLootMatrix(player, matrixIndex);
GiveLoot(player, result, lootSourceType);
GiveLoot(player, result, lootSourceType, metrics);
}
void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType) {
void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType, const LootMetrics& metrics) {
player = player->GetOwner(); // if the owner is overwritten, we collect that here
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
@@ -539,7 +547,7 @@ void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType
return;
for (const auto& pair : result) {
inventoryComponent->AddItem(pair.first, pair.second, lootSourceType);
inventoryComponent->AddItem(pair.first, pair.second, lootSourceType, eInventoryType::INVALID, {}, LWOOBJID_EMPTY, true, false, LWOOBJID_EMPTY, eInventoryType::INVALID, 0, false, -1, metrics);
}
}
@@ -577,7 +585,11 @@ void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t acti
const auto [minCoins, maxCoins] = GetActivityCoinRange(*selectedReward);
GiveLoot(player, selectedReward->LootMatrixIndex, eLootSourceType::ACTIVITY);
// Live sent the activity's ID and the activity object's LOT with each reward item
LootMetrics metrics{};
metrics.activityId = static_cast<int32_t>(activityID);
if (const auto* const sourceEntity = Game::entityManager->GetEntity(source)) metrics.sourceLot = sourceEntity->GetLOT();
GiveLoot(player, selectedReward->LootMatrixIndex, eLootSourceType::ACTIVITY, metrics);
uint32_t coins = static_cast<uint32_t>(minCoins + GeneralUtils::GenerateRandomNumber<float>(0, 1) * (maxCoins - minCoins));

View File

@@ -8,6 +8,7 @@
#include <vector>
class Entity;
struct LootMetrics;
struct CDActivityRewards;
struct CDCurrencyTable;
@@ -31,6 +32,9 @@ namespace Loot {
void CacheMatrix(const uint32_t matrixIndex);
void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType = eLootSourceType::NONE);
void GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType = eLootSourceType::NONE);
// As above, telling the client where the items came from (LootMetrics)
void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType, const LootMetrics& metrics);
void GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType, const LootMetrics& metrics);
void GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0);
void DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins);
void DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam = false, bool forceFfa = false);

View File

@@ -239,7 +239,10 @@ namespace Mail {
LwoNameValue config;
config.InsertLines(playerMail->itemConfig);
const auto claimed = inv->ReceiveItem(playerMail->itemID, playerMail->itemLOT, playerMail->itemCount, eLootSourceType::MAIL, config, playerMail->itemSubkey);
// Live sent the mail's ID with the attachment
ReceiveItemOptions options{};
options.metrics.mailId = static_cast<uint64_t>(mailID);
const auto claimed = inv->ReceiveItem(playerMail->itemID, playerMail->itemLOT, playerMail->itemCount, eLootSourceType::MAIL, config, playerMail->itemSubkey, false, options);
Database::Get()->ClaimMailItem(mailID);
DashboardNotify::Changed("mail", playerMail->receiverId);