From af6f65b1940240805b3d9159eacf5ef4159b0405 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 13 Jun 2026 16:04:07 +0200 Subject: [PATCH] fix(inventory): drop leaking badge pending-counter; trust server active set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sendActiveBadges incremented pendingUpdatesRef on every edit, and the BadgesEvent handler skipped applying the server's active badges while the counter was > 0, decrementing once per BadgesEvent. The assumption was "one BadgesEvent echo per SetActivatedBadges" — but the emulator's UserWearBadgeEvent answers with a UserBadgesComposer room broadcast, NOT a BadgesEvent, so nothing ever decrements the counter. It leaks upward with every toggle/reorder/swap and then silently drops legitimate later BadgesEvent updates (the server-authoritative active set never reapplies). Remove the counter and always apply the server's active badges on BadgesEvent (edits are already persisted server-side, so this is correct). --- src/hooks/inventory/useInventoryBadges.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/hooks/inventory/useInventoryBadges.ts b/src/hooks/inventory/useInventoryBadges.ts index f456c1f..ae13058 100644 --- a/src/hooks/inventory/useInventoryBadges.ts +++ b/src/hooks/inventory/useInventoryBadges.ts @@ -1,5 +1,5 @@ import { BadgeReceivedEvent, BadgesEvent, RequestBadgesComposer, SetActivatedBadgesComposer } from '@nitrots/nitro-renderer'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useBetween } from 'use-between'; import { GetConfigurationValue, SendMessageComposer, UnseenItemCategory } from '../../api'; import { useMessageEvent } from '../events'; @@ -17,7 +17,6 @@ const useInventoryBadgesState = () => const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); const maxBadgeCount = GetConfigurationValue('user.badges.max.slots', 5); - const pendingUpdatesRef = useRef(0); const isWearingBadge = (badgeCode: string) => activeBadgeCodes.some(code => code === badgeCode); const canWearBadges = () => (activeBadgeCodes.filter(Boolean).length < maxBadgeCount); @@ -35,7 +34,6 @@ const useInventoryBadgesState = () => const sendActiveBadges = (badges: (string | null)[]) => { - pendingUpdatesRef.current++; const composer = new SetActivatedBadgesComposer(); for(let i = 0; i < maxBadgeCount; i++) composer.addActivatedBadge(badges[i] ?? ''); SendMessageComposer(composer); @@ -93,16 +91,14 @@ const useInventoryBadgesState = () => return newValue; }); - // Skip overwriting activeBadgeCodes if we have pending local changes - if(pendingUpdatesRef.current > 0) - { - pendingUpdatesRef.current--; - } - else - { - const serverBadges = parser.getActiveBadgeCodes(); - setActiveBadgeCodes(toFixedSlots(serverBadges)); - } + // The emulator answers SetActivatedBadges (UserWearBadgeEvent) with a + // UserBadgesComposer room broadcast, NOT a BadgesEvent — so there is no + // echo to suppress and the old pendingUpdatesRef counter only ever + // leaked (incremented on every edit, never decremented), which then + // silently dropped legitimate later BadgesEvent updates. The server is + // authoritative here (edits are already persisted), so always apply it. + const serverBadges = parser.getActiveBadgeCodes(); + setActiveBadgeCodes(toFixedSlots(serverBadges)); setBadgeCodes(allBadgeCodes); });