mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-20 03:32:48 +00:00
fix(currency): SetCurrency names its source the way live did
Live filled SetCurrency's source fields per source type (16,816 decoded live SetCurrency): pickups carry the position the client picked the coins up at (the one in its PickupCurrency) and no source; mission, achievement and activity rewards name the player (object and LOT 1); vendor buys, sells and buybacks name the vendor and its LOT; trades carry the trade ID; death, mail and everything else name nothing. No live SetCurrency sets loot_type. DLU wrote a present source LOT of 0, no object and a zero position for all of them. The position is the one the client uses: for pickups it projects it to the screen as the start of the coin counter animation (LWOCharacterComponent::SetCurrency), so it started from the world origin before. PickupCurrency now reads that position (the client always sends it). The trade ID is an 8-byte object ID in the client; DLU declared it 4 bytes, which only never broke because it was never set. Check: pick up coins and watch the coin counter animate from where the coins were; buy and sell at a vendor, finish a mission and trade coins with another player, and the coin total updates each time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -590,7 +590,38 @@ const NiPoint3& Character::GetRespawnPoint(LWOMAPID map) const {
|
||||
return pair->second;
|
||||
}
|
||||
|
||||
void Character::SetCoins(int64_t newCoins, eLootSourceType lootSource) {
|
||||
CoinSource CoinSource::Object(const Entity& source) {
|
||||
CoinSource coinSource;
|
||||
coinSource.object = source.GetObjectID();
|
||||
coinSource.lot = source.GetLOT();
|
||||
return coinSource;
|
||||
}
|
||||
|
||||
CoinSource CoinSource::Position(const NiPoint3& position) {
|
||||
CoinSource coinSource;
|
||||
coinSource.position = position;
|
||||
return coinSource;
|
||||
}
|
||||
|
||||
CoinSource CoinSource::Trade(const LWOOBJID tradeID) {
|
||||
CoinSource coinSource;
|
||||
coinSource.tradeID = tradeID;
|
||||
return coinSource;
|
||||
}
|
||||
|
||||
GameMessages::SetCurrency Character::MakeSetCurrency(const LWOOBJID player, const int64_t coins, const eLootSourceType coinSource, const CoinSource& source) {
|
||||
GameMessages::SetCurrency setCurrency;
|
||||
setCurrency.target = player;
|
||||
setCurrency.currency = coins;
|
||||
setCurrency.position = source.position;
|
||||
setCurrency.sourceLOT = source.lot;
|
||||
setCurrency.sourceID = source.object;
|
||||
setCurrency.sourceTradeID = source.tradeID;
|
||||
setCurrency.sourceType = coinSource;
|
||||
return setCurrency;
|
||||
}
|
||||
|
||||
void Character::SetCoins(int64_t newCoins, eLootSourceType lootSource, const CoinSource& source) {
|
||||
if (newCoins < 0) {
|
||||
newCoins = 0;
|
||||
}
|
||||
@@ -600,15 +631,7 @@ void Character::SetCoins(int64_t newCoins, eLootSourceType lootSource) {
|
||||
m_Coins = newCoins;
|
||||
|
||||
auto* entity = Game::entityManager->GetEntity(m_ObjectID);
|
||||
GameMessages::SetCurrency setCurrency;
|
||||
setCurrency.target = entity->GetObjectID();
|
||||
setCurrency.currency = m_Coins;
|
||||
setCurrency.lootType = 0;
|
||||
setCurrency.sourceLOT = 0;
|
||||
setCurrency.sourceID = 0;
|
||||
setCurrency.sourceTradeID = 0;
|
||||
setCurrency.sourceType = lootSource;
|
||||
setCurrency.SendToClient(entity->GetSystemAddress());
|
||||
MakeSetCurrency(entity->GetObjectID(), m_Coins, lootSource, source).SendToClient(entity->GetSystemAddress());
|
||||
|
||||
// Live followed every coin gain with CurrencyCollected (the amount gained); losses had none
|
||||
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
|
||||
@@ -17,6 +17,26 @@ class Entity;
|
||||
enum class ePermissionMap : uint64_t;
|
||||
enum class eGameMasterLevel : uint8_t;
|
||||
enum class eLootSourceType : uint32_t;
|
||||
namespace GameMessages {
|
||||
struct SetCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a coin change came from, as live filled SetCurrency (18,417 live SetCurrency):
|
||||
* pickups carry the position the client picked the coins up at (the client starts the coin animation there);
|
||||
* mission, achievement and activity rewards name the player (LOT 1); vendors name the vendor and its LOT;
|
||||
* trades carry the trade ID. Everything else (death, mail) names nothing.
|
||||
*/
|
||||
struct CoinSource {
|
||||
NiPoint3 position{ NiPoint3Constant::ZERO };
|
||||
LWOOBJID object{ LWOOBJID_EMPTY };
|
||||
LOT lot{ LOT_NULL };
|
||||
LWOOBJID tradeID{ LWOOBJID_EMPTY };
|
||||
|
||||
static CoinSource Object(const Entity& source);
|
||||
static CoinSource Position(const NiPoint3& position);
|
||||
static CoinSource Trade(LWOOBJID tradeID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Meta information about a character, like their name and style
|
||||
@@ -352,7 +372,12 @@ public:
|
||||
* @param newCoins the amount of coins to update by
|
||||
* @param coinSource The source of the loot
|
||||
*/
|
||||
void SetCoins(int64_t newCoins, eLootSourceType coinSource);
|
||||
void SetCoins(int64_t newCoins, eLootSourceType coinSource, const CoinSource& source = {});
|
||||
|
||||
/**
|
||||
* The SetCurrency live sent for a coin change.
|
||||
*/
|
||||
static GameMessages::SetCurrency MakeSetCurrency(LWOOBJID player, int64_t coins, eLootSourceType coinSource, const CoinSource& source);
|
||||
|
||||
/**
|
||||
* Get the entity this character belongs to
|
||||
|
||||
@@ -177,8 +177,8 @@ void Trade::Complete() {
|
||||
|
||||
// Now actually do the trade. Nothing is created or destroyed, so the economy ledger records transfers instead.
|
||||
EconomyLedger::ScopedItemTransfer transfer;
|
||||
characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB, eLootSourceType::TRADE);
|
||||
characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA, eLootSourceType::TRADE);
|
||||
characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB, eLootSourceType::TRADE, CoinSource::Trade(m_TradeId));
|
||||
characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA, eLootSourceType::TRADE, CoinSource::Trade(m_TradeId));
|
||||
|
||||
const auto zone = Game::server ? Game::server->GetZoneID() : 0;
|
||||
const auto recordCoins = [zone](const uint64_t coins, const LWOOBJID from, const LWOOBJID to) {
|
||||
|
||||
@@ -207,7 +207,7 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL);
|
||||
}
|
||||
|
||||
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);
|
||||
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR, CoinSource::Object(*m_Parent));
|
||||
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
@@ -278,7 +278,7 @@ void VendorComponent::SellToVendor(Entity& player, const SystemAddress& sysAddr,
|
||||
|
||||
MakeRoomInBuyback(*inv, *item, static_cast<uint32_t>(std::max(count, 0)));
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR, CoinSource::Object(*m_Parent));
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::SELL_SUCCESS);
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ void VendorComponent::BuybackFromVendor(Entity& player, const SystemAddress& sys
|
||||
|
||||
//inv->RemoveItem(count, -1, iObjID);
|
||||
inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false);
|
||||
character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR);
|
||||
character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR, CoinSource::Object(*m_Parent));
|
||||
//Game::entityManager->SerializeEntity(player); // so inventory updates
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace GameMessages {
|
||||
bitStream.Write(position.z);
|
||||
BitStreamUtils::WriteOptional(bitStream, sourceLOT, LOT_NULL);
|
||||
BitStreamUtils::WriteOptional(bitStream, sourceID, LWOOBJID_EMPTY);
|
||||
BitStreamUtils::WriteOptional(bitStream, sourceTradeID, 0);
|
||||
BitStreamUtils::WriteOptional(bitStream, sourceTradeID, LWOOBJID_EMPTY);
|
||||
BitStreamUtils::WriteOptional(bitStream, sourceType, eLootSourceType::NONE);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace GameMessages {
|
||||
VALIDATE_READ(bitStream.Read(position.z));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sourceLOT, LOT_NULL));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sourceID, LWOOBJID_EMPTY));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sourceTradeID, 0));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sourceTradeID, LWOOBJID_EMPTY));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, sourceType, eLootSourceType::NONE));
|
||||
return true;
|
||||
}
|
||||
@@ -115,10 +115,17 @@ namespace GameMessages {
|
||||
|
||||
void PickupCurrency::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(currency);
|
||||
bitStream.Write(position.x);
|
||||
bitStream.Write(position.y);
|
||||
bitStream.Write(position.z);
|
||||
}
|
||||
|
||||
bool PickupCurrency::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return bitStream.Read(currency);
|
||||
VALIDATE_READ(bitStream.Read(currency));
|
||||
VALIDATE_READ(bitStream.Read(position.x));
|
||||
VALIDATE_READ(bitStream.Read(position.y));
|
||||
VALIDATE_READ(bitStream.Read(position.z));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PickupCurrency::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
@@ -126,7 +133,7 @@ namespace GameMessages {
|
||||
|
||||
auto* ch = entity.GetCharacter();
|
||||
if (ch && entity.PickupCoins(currency)) {
|
||||
ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::PICKUP);
|
||||
ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::PICKUP, CoinSource::Position(position));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ namespace GameMessages {
|
||||
NiPoint3 position{ NiPoint3Constant::ZERO };
|
||||
LOT sourceLOT{ LOT_NULL }; // optional
|
||||
LWOOBJID sourceID{ LWOOBJID_EMPTY }; // optional
|
||||
// optional (default 0). DLU writes it as an int32_t; lu_packets has an object ID (8 bytes). Only ever 0.
|
||||
int32_t sourceTradeID{ 0 };
|
||||
LWOOBJID sourceTradeID{ LWOOBJID_EMPTY }; // optional; the client reads an object ID (8 bytes)
|
||||
eLootSourceType sourceType{ eLootSourceType::NONE }; // optional
|
||||
};
|
||||
|
||||
@@ -109,6 +108,7 @@ namespace GameMessages {
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
uint32_t currency{};
|
||||
NiPoint3 position{ NiPoint3Constant::ZERO }; // where the client picked the coins up
|
||||
};
|
||||
|
||||
// Client -> server.
|
||||
|
||||
@@ -528,7 +528,7 @@ void Mission::YieldRewards() {
|
||||
|
||||
if (info.reward_currency_repeatable > 0 || coinsToSend > 0) {
|
||||
eLootSourceType lootSource = info.isMission ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT;
|
||||
character->SetCoins(character->GetCoins() + info.reward_currency_repeatable + coinsToSend, lootSource);
|
||||
character->SetCoins(character->GetCoins() + info.reward_currency_repeatable + coinsToSend, lootSource, CoinSource::Object(*entity));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -556,7 +556,7 @@ void Mission::YieldRewards() {
|
||||
|
||||
if (info.reward_currency > 0 || coinsToSend > 0) {
|
||||
eLootSourceType lootSource = info.isMission ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT;
|
||||
character->SetCoins(character->GetCoins() + info.reward_currency + coinsToSend, lootSource);
|
||||
character->SetCoins(character->GetCoins() + info.reward_currency + coinsToSend, lootSource, CoinSource::Object(*entity));
|
||||
}
|
||||
|
||||
if (info.reward_maxinventory > 0) {
|
||||
|
||||
@@ -570,7 +570,7 @@ void Loot::GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t acti
|
||||
|
||||
auto* character = player->GetCharacter();
|
||||
|
||||
character->SetCoins(character->GetCoins() + coins, eLootSourceType::ACTIVITY);
|
||||
character->SetCoins(character->GetCoins() + coins, eLootSourceType::ACTIVITY, CoinSource::Object(*player));
|
||||
}
|
||||
|
||||
void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) {
|
||||
|
||||
Reference in New Issue
Block a user