fix(inventory): drop leaking badge pending-counter; trust server active set

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).
This commit is contained in:
simoleo89
2026-06-13 16:04:07 +02:00
parent 39fbfdd9e2
commit af6f65b194
+9 -13
View File
@@ -1,5 +1,5 @@
import { BadgeReceivedEvent, BadgesEvent, RequestBadgesComposer, SetActivatedBadgesComposer } from '@nitrots/nitro-renderer'; 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 { useBetween } from 'use-between';
import { GetConfigurationValue, SendMessageComposer, UnseenItemCategory } from '../../api'; import { GetConfigurationValue, SendMessageComposer, UnseenItemCategory } from '../../api';
import { useMessageEvent } from '../events'; import { useMessageEvent } from '../events';
@@ -17,7 +17,6 @@ const useInventoryBadgesState = () =>
const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker();
const maxBadgeCount = GetConfigurationValue<number>('user.badges.max.slots', 5); const maxBadgeCount = GetConfigurationValue<number>('user.badges.max.slots', 5);
const pendingUpdatesRef = useRef(0);
const isWearingBadge = (badgeCode: string) => activeBadgeCodes.some(code => code === badgeCode); const isWearingBadge = (badgeCode: string) => activeBadgeCodes.some(code => code === badgeCode);
const canWearBadges = () => (activeBadgeCodes.filter(Boolean).length < maxBadgeCount); const canWearBadges = () => (activeBadgeCodes.filter(Boolean).length < maxBadgeCount);
@@ -35,7 +34,6 @@ const useInventoryBadgesState = () =>
const sendActiveBadges = (badges: (string | null)[]) => const sendActiveBadges = (badges: (string | null)[]) =>
{ {
pendingUpdatesRef.current++;
const composer = new SetActivatedBadgesComposer(); const composer = new SetActivatedBadgesComposer();
for(let i = 0; i < maxBadgeCount; i++) composer.addActivatedBadge(badges[i] ?? ''); for(let i = 0; i < maxBadgeCount; i++) composer.addActivatedBadge(badges[i] ?? '');
SendMessageComposer(composer); SendMessageComposer(composer);
@@ -93,16 +91,14 @@ const useInventoryBadgesState = () =>
return newValue; return newValue;
}); });
// Skip overwriting activeBadgeCodes if we have pending local changes // The emulator answers SetActivatedBadges (UserWearBadgeEvent) with a
if(pendingUpdatesRef.current > 0) // UserBadgesComposer room broadcast, NOT a BadgesEvent — so there is no
{ // echo to suppress and the old pendingUpdatesRef counter only ever
pendingUpdatesRef.current--; // leaked (incremented on every edit, never decremented), which then
} // silently dropped legitimate later BadgesEvent updates. The server is
else // authoritative here (edits are already persisted), so always apply it.
{ const serverBadges = parser.getActiveBadgeCodes();
const serverBadges = parser.getActiveBadgeCodes(); setActiveBadgeCodes(toFixedSlots(serverBadges));
setActiveBadgeCodes(toFixedSlots(serverBadges));
}
setBadgeCodes(allBadgeCodes); setBadgeCodes(allBadgeCodes);
}); });