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

@@ -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));
}
}