From 0fac429fac488b236b1dc8f56510b689e58d07fb Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Sun, 9 Nov 2025 11:57:28 +0100 Subject: [PATCH] fix(transmog): fix transmog issues. Untested commit. --- CHANGELOG.md | 1 + server/channelserver/handlers_plate.go | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff766146a..4edde08e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed incomplete saves during logout - character savedata now persisted even during ungraceful disconnects - Fixed double-save bug in logout flow that caused unnecessary database operations - Fixed save operation ordering - now saves data before session cleanup instead of after +- Fixed stale transmog/armor appearance shown to other players - user binary cache now invalidated when plate data is saved ### Security diff --git a/server/channelserver/handlers_plate.go b/server/channelserver/handlers_plate.go index e5aa9a247..61d629d87 100644 --- a/server/channelserver/handlers_plate.go +++ b/server/channelserver/handlers_plate.go @@ -11,6 +11,11 @@ // The logout flow includes a safety check via savePlateDataToDatabase() // to ensure no data loss if packets are lost or client disconnects. // +// Cache Management: +// When plate data is saved, the server's user binary cache (types 2-3) +// is invalidated to ensure other players see updated appearance immediately. +// This prevents stale transmog/armor being displayed after zone changes. +// // Thread Safety: // All handlers use session-scoped database operations, making them // inherently thread-safe as each session is single-threaded. @@ -114,6 +119,13 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { } } + // Invalidate user binary cache so other players see updated appearance + // User binary types 2 and 3 contain equipment/appearance data + s.server.userBinaryPartsLock.Lock() + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 2}) + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 3}) + s.server.userBinaryPartsLock.Unlock() + saveDuration := time.Since(saveStart) s.logger.Info("PlateData saved successfully", zap.Uint32("charID", s.charID), @@ -189,6 +201,13 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Failed to save platebox", zap.Error(err)) } } + + // Invalidate user binary cache so other players see updated appearance + s.server.userBinaryPartsLock.Lock() + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 2}) + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 3}) + s.server.userBinaryPartsLock.Unlock() + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } @@ -228,6 +247,13 @@ func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) { zap.Duration("duration", saveDuration), ) } + + // Invalidate user binary cache so other players see updated appearance + s.server.userBinaryPartsLock.Lock() + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 2}) + delete(s.server.userBinaryParts, userBinaryPartID{charID: s.charID, index: 3}) + s.server.userBinaryPartsLock.Unlock() + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) }