mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
fix(loot): activity reward coins use the reward's ChallengeRating level
ActivityRewards coins were always read from CurrencyTable npcminlevel 1. Live used the reward row's ChallengeRating as the npcminlevel, and level 1 when the currency index has no row for that level: - FV foot races (ChallengeRating 4, index 1) gave 36 and 48 coins, which only level 4 (30-50) fits; level 1 is 3-5. - Frakjaw's chest (activity 58, ChallengeRating 6) gave 250 each to a team of 2: its currency indices 123-126 only have a level 6 row (500), so DLU gave nothing. - Quickbuilds, wishing wells and chests have ChallengeRating 1; survival and the shooting galleries have ratings with no row and keep level 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,6 @@
|
||||
#include "MessageType/Chat.h"
|
||||
#include "ObjectIDManager.h"
|
||||
|
||||
#include "CDCurrencyTableTable.h"
|
||||
#include "CDActivityRewardsTable.h"
|
||||
#include "CDActivitiesTable.h"
|
||||
#include "LeaderboardManager.h"
|
||||
@@ -532,16 +531,7 @@ void ActivityInstance::RewardParticipant(Entity* participant) {
|
||||
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([this](CDActivityRewards entry) { return (entry.objectTemplate == m_ActivityInfo.ActivityID); });
|
||||
|
||||
if (!activityRewards.empty()) {
|
||||
uint32_t minCoins = 0;
|
||||
uint32_t maxCoins = 0;
|
||||
|
||||
auto* currencyTableTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == activityRewards[0].CurrencyIndex && entry.npcminlevel == 1); });
|
||||
|
||||
if (!currencyTable.empty()) {
|
||||
minCoins = currencyTable[0].minvalue;
|
||||
maxCoins = currencyTable[0].maxvalue;
|
||||
}
|
||||
const auto [minCoins, maxCoins] = Loot::GetActivityCoinRange(activityRewards[0]);
|
||||
|
||||
Loot::DropLoot(participant, m_Parent->GetObjectID(), activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
|
||||
}
|
||||
|
||||
@@ -547,6 +547,24 @@ void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> Loot::GetActivityCoinRange(const std::vector<CDCurrencyTable>& currencyRows, const uint32_t challengeRating) {
|
||||
const auto atLevel = [¤cyRows](const uint32_t level) {
|
||||
return std::ranges::find_if(currencyRows, [level](const CDCurrencyTable& row) { return row.npcminlevel == level; });
|
||||
};
|
||||
|
||||
auto row = atLevel(challengeRating);
|
||||
if (row == currencyRows.end()) row = atLevel(1);
|
||||
if (row == currencyRows.end()) return { 0, 0 };
|
||||
return { row->minvalue, row->maxvalue };
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> Loot::GetActivityCoinRange(const CDActivityRewards& reward) {
|
||||
auto* const currencyTableTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
const auto currencyIndex = reward.CurrencyIndex;
|
||||
const auto rows = currencyTableTable->Query([currencyIndex](const CDCurrencyTable& entry) { return entry.currencyIndex == currencyIndex; });
|
||||
return GetActivityCoinRange(rows, reward.ChallengeRating);
|
||||
}
|
||||
|
||||
void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) {
|
||||
CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable<CDActivityRewardsTable>();
|
||||
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([activityID](CDActivityRewards entry) { return (entry.objectTemplate == activityID); });
|
||||
@@ -561,16 +579,7 @@ void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t acti
|
||||
if (!selectedReward)
|
||||
return;
|
||||
|
||||
uint32_t minCoins = 0;
|
||||
uint32_t maxCoins = 0;
|
||||
|
||||
CDCurrencyTableTable* currencyTableTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([selectedReward](CDCurrencyTable entry) { return (entry.currencyIndex == selectedReward->CurrencyIndex && entry.npcminlevel == 1); });
|
||||
|
||||
if (currencyTable.size() > 0) {
|
||||
minCoins = currencyTable[0].minvalue;
|
||||
maxCoins = currencyTable[0].maxvalue;
|
||||
}
|
||||
const auto [minCoins, maxCoins] = GetActivityCoinRange(*selectedReward);
|
||||
|
||||
GiveLoot(player, selectedReward->LootMatrixIndex, eLootSourceType::ACTIVITY);
|
||||
|
||||
@@ -612,16 +621,7 @@ void Loot::DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t acti
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t minCoins = 0;
|
||||
uint32_t maxCoins = 0;
|
||||
|
||||
CDCurrencyTableTable* currencyTableTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([selectedReward](CDCurrencyTable entry) { return (entry.currencyIndex == selectedReward->CurrencyIndex && entry.npcminlevel == 1); });
|
||||
|
||||
if (currencyTable.size() > 0) {
|
||||
minCoins = currencyTable[0].minvalue;
|
||||
maxCoins = currencyTable[0].maxvalue;
|
||||
}
|
||||
const auto [minCoins, maxCoins] = GetActivityCoinRange(*selectedReward);
|
||||
|
||||
DropLoot(player, source, selectedReward->LootMatrixIndex, minCoins, maxCoins);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class Entity;
|
||||
struct CDActivityRewards;
|
||||
struct CDCurrencyTable;
|
||||
|
||||
namespace GameMessages {
|
||||
struct DropClientLoot;
|
||||
@@ -28,6 +33,14 @@ namespace Loot {
|
||||
void DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam = false, bool forceFfa = false);
|
||||
void DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0);
|
||||
|
||||
/**
|
||||
* The coin range {min, max} of an ActivityRewards row, as live rolled it: the reward's ChallengeRating is the
|
||||
* CurrencyTable npcminlevel, and level 1 is used when the currency index has no row for that level.
|
||||
* currencyRows are the CurrencyTable rows of the reward's CurrencyIndex. {0, 0} when neither level has a row.
|
||||
*/
|
||||
std::pair<uint32_t, uint32_t> GetActivityCoinRange(const std::vector<CDCurrencyTable>& currencyRows, uint32_t challengeRating);
|
||||
std::pair<uint32_t, uint32_t> GetActivityCoinRange(const CDActivityRewards& reward);
|
||||
|
||||
// How far from where it spawns a dropped item lands, as live dropped them (median 10.0 units, uniform direction).
|
||||
constexpr float ITEM_DROP_DISTANCE = 10.0f;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ set(DGAMETEST_SOURCES
|
||||
"HolidayEventsTests.cpp"
|
||||
"SetCurrencySourceTests.cpp"
|
||||
"LootDropPositionTests.cpp"
|
||||
"LootActivityCoinsTests.cpp"
|
||||
"SpiderQueenTests.cpp"
|
||||
"TacArcTests.cpp"
|
||||
)
|
||||
|
||||
44
tests/dGameTests/LootActivityCoinsTests.cpp
Normal file
44
tests/dGameTests/LootActivityCoinsTests.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "Loot.h"
|
||||
#include "CDCurrencyTableTable.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// ActivityRewards coins: the reward's ChallengeRating is the CurrencyTable npcminlevel, level 1 when that level has
|
||||
// no row, as live rolled them.
|
||||
|
||||
namespace {
|
||||
CDCurrencyTable Row(const uint32_t index, const uint32_t level, const uint32_t min, const uint32_t max) {
|
||||
return CDCurrencyTable{ .currencyIndex = index, .npcminlevel = level, .minvalue = min, .maxvalue = max, .id = 0 };
|
||||
}
|
||||
|
||||
// CurrencyTable index 1 as the client data has it
|
||||
const std::vector<CDCurrencyTable> index1 = { Row(1, 1, 3, 5), Row(1, 2, 5, 9), Row(1, 3, 9, 15), Row(1, 4, 30, 50), Row(1, 5, 60, 100) };
|
||||
}
|
||||
|
||||
// FV foot races (ChallengeRating 4): live gave 36 and 48 coins, which only level 4 (30-50) fits
|
||||
TEST(LootActivityCoinsTests, ChallengeRatingPicksTheLevel) {
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index1, 4), std::make_pair(30u, 50u));
|
||||
}
|
||||
|
||||
// Quickbuilds, wishing wells and chests (ChallengeRating 1)
|
||||
TEST(LootActivityCoinsTests, ChallengeRatingOneIsLevelOne) {
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index1, 1), std::make_pair(3u, 5u));
|
||||
}
|
||||
|
||||
// Survival and shooting galleries: their ChallengeRating has no row, so level 1 is used
|
||||
TEST(LootActivityCoinsTests, MissingLevelFallsBackToLevelOne) {
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index1, 7), std::make_pair(3u, 5u));
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index1, 0), std::make_pair(3u, 5u));
|
||||
}
|
||||
|
||||
// Frakjaw's chest (activity 58, ChallengeRating 6): index 125 has only a level 6 row of 500, which live split
|
||||
// between a team of 2
|
||||
TEST(LootActivityCoinsTests, IndexWithOnlyTheChallengeLevel) {
|
||||
const std::vector<CDCurrencyTable> index125 = { Row(125, 6, 500, 500) };
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index125, 6), std::make_pair(500u, 500u));
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(index125, 2), std::make_pair(0u, 0u));
|
||||
}
|
||||
|
||||
TEST(LootActivityCoinsTests, NoRowsNoCoins) {
|
||||
EXPECT_EQ(Loot::GetActivityCoinRange(std::vector<CDCurrencyTable>{}, 1), std::make_pair(0u, 0u));
|
||||
}
|
||||
Reference in New Issue
Block a user