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:
Aaron Kimbrell
2026-09-29 02:55:26 -05:00
parent f7bb178c2d
commit 81db3a94ac
11 changed files with 148 additions and 28 deletions

View File

@@ -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