From 82af527b617db6ffccdd774b1a124c60b672d56f Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Thu, 1 Jan 2026 10:50:09 -0500 Subject: [PATCH] Fixes the quirk menu not updating (#94673) --- .../client/preferences/middleware/quirks.dm | 7 ++- code/modules/tgui/external.dm | 5 +- code/modules/tgui/tgui.dm | 8 ++- tgui/packages/tgfont/static/tgfont.css | 26 ++++---- .../CharacterPreferences/QuirksPage.tsx | 62 +++++++++++++------ .../preferences/features/dropdowns.tsx | 1 + 6 files changed, 69 insertions(+), 40 deletions(-) diff --git a/code/modules/client/preferences/middleware/quirks.dm b/code/modules/client/preferences/middleware/quirks.dm index 34725325eaf..8d6c6355866 100644 --- a/code/modules/client/preferences/middleware/quirks.dm +++ b/code/modules/client/preferences/middleware/quirks.dm @@ -27,7 +27,6 @@ if(response != "Yes") return TRUE - /datum/preference_middleware/quirks/post_set_preference(mob/user, preference, value) if(preference != "species") return @@ -107,11 +106,12 @@ // If the client is sending an invalid give_quirk, that means that // something went wrong with the client prediction, so we should // catch it back up to speed. - preferences.update_static_data(user) + preferences.update_static_data(user, always_instant = TRUE) return TRUE preferences.all_quirks = new_quirks preferences.character_preview_view?.update_body() + preferences.update_static_data(user, always_instant = TRUE) return TRUE @@ -126,11 +126,12 @@ // If the client is sending an invalid remove_quirk, that means that // something went wrong with the client prediction, so we should // catch it back up to speed. - preferences.update_static_data(user) + preferences.update_static_data(user, always_instant = TRUE) return TRUE preferences.all_quirks = new_quirks preferences.character_preview_view?.update_body() + preferences.update_static_data(user, always_instant = TRUE) return TRUE diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 574edae9f73..c1e61ccec6c 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -55,12 +55,13 @@ * * required user the mob currently interacting with the ui * optional ui ui to be updated + * always_instant when set to true stops the ui update cooldown from happening */ -/datum/proc/update_static_data(mob/user, datum/tgui/ui) +/datum/proc/update_static_data(mob/user, datum/tgui/ui, always_instant) if(!ui) ui = SStgui.get_open_ui(user, src) if(ui) - ui.send_full_update() + ui.send_full_update(always_instant = always_instant) /** * public diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 37bbeca0e85..89d25e98516 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -219,11 +219,12 @@ * * optional custom_data list Custom data to send instead of ui_data. * optional force bool Send an update even if UI is not interactive. + * optional always_instant bool Send and update regardless of the cooldown. */ -/datum/tgui/proc/send_full_update(custom_data, force) +/datum/tgui/proc/send_full_update(custom_data, force, always_instant) if(!user.client || !initialized || closing) return - if(!COOLDOWN_FINISHED(src, refresh_cooldown)) + if(!always_instant && !COOLDOWN_FINISHED(src, refresh_cooldown)) refreshing = TRUE addtimer(CALLBACK(src, PROC_REF(send_full_update), custom_data, force), COOLDOWN_TIMELEFT(src, refresh_cooldown), TIMER_UNIQUE) return @@ -233,7 +234,8 @@ custom_data, with_data = should_update_data, with_static_data = TRUE)) - COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN) + if(!always_instant) + COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN) /** * public diff --git a/tgui/packages/tgfont/static/tgfont.css b/tgui/packages/tgfont/static/tgfont.css index 45e15d6a555..7b340f899b3 100644 --- a/tgui/packages/tgfont/static/tgfont.css +++ b/tgui/packages/tgfont/static/tgfont.css @@ -7,25 +7,25 @@ [class*=" tg-"] { /* biome-ignore lint/complexity/noImportantStyles: Just leave it */ font-family: "tgfont" !important; - font-size: undefined; + font-size: inherit; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } :root { - --tg-air-tank-slash: "ea01"; - --tg-air-tank: "ea02"; - --tg-bad-touch: "ea03"; - --tg-image-minus: "ea04"; - --tg-image-plus: "ea05"; - --tg-nanotrasen-logo: "ea06"; - --tg-non-binary: "ea07"; - --tg-prosthetic-full: "ea08"; - --tg-prosthetic-leg: "ea09"; - --tg-sound-minus: "ea0a"; - --tg-sound-plus: "ea0b"; - --tg-syndicate-logo: "ea0c"; + --tg-air-tank-slash: "\ea01"; + --tg-air-tank: "\ea02"; + --tg-bad-touch: "\ea03"; + --tg-image-minus: "\ea04"; + --tg-image-plus: "\ea05"; + --tg-nanotrasen-logo: "\ea06"; + --tg-non-binary: "\ea07"; + --tg-prosthetic-full: "\ea08"; + --tg-prosthetic-leg: "\ea09"; + --tg-sound-minus: "\ea0a"; + --tg-sound-plus: "\ea0b"; + --tg-syndicate-logo: "\ea0c"; } .tg-air-tank-slash::before { content: var(--tg-air-tank-slash); diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx index fd6f011f30f..7e163c97244 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx @@ -35,7 +35,7 @@ function getColorValueClass(quirk: Quirk) { function getCorrespondingPreferences( customization_options: string[], - relevant_preferences: Record, + relevant_preferences: Record = {}, ) { return Object.fromEntries( filter(Object.entries(relevant_preferences), ([key, value]) => @@ -51,19 +51,21 @@ type QuirkListProps = { }; type QuirkProps = { - onClick: (quirkName: string, quirk: Quirk) => void; + handleClick: (quirkName: string, quirk: Quirk) => void; randomBodyEnabled: boolean; selected: boolean; serverData: ServerData; + quirkActionLocked: boolean; }; function QuirkList(props: QuirkProps & QuirkListProps) { const { quirks = [], selected, - onClick, + handleClick, serverData, randomBodyEnabled, + quirkActionLocked, } = props; return ( @@ -71,12 +73,13 @@ function QuirkList(props: QuirkProps & QuirkListProps) { {quirks.map(([quirkKey, quirk]) => ( ))} @@ -91,7 +94,7 @@ type QuirkDisplayProps = { } & QuirkProps; function QuirkDisplay(props: QuirkDisplayProps) { - const { quirk, quirkKey, onClick, selected } = props; + const { quirk, quirkKey, handleClick, selected, quirkActionLocked } = props; const { icon, value, name, description, customizable, failTooltip } = quirk; const [customizationExpanded, setCustomizationExpanded] = useState(false); @@ -101,13 +104,18 @@ function QuirkDisplay(props: QuirkDisplayProps) { const child = ( { - event.stopPropagation(); + style={{ + opacity: props.quirkActionLocked ? 0.6 : 1, + pointerEvents: props.quirkActionLocked ? 'none' : 'auto', + }} + onClick={() => { + if (quirkActionLocked) + return; if (selected) { setCustomizationExpanded(false); } - onClick(quirkKey, quirk); + handleClick(quirkKey, quirk); }} > @@ -303,6 +311,19 @@ function QuirkPage() { data.selected_quirks = selected_quirks; } + const [quirkActionLocked, setQuirkActionLocked] = useState(false); + + function withQuirkDebounce(debounce: () => void, delay = 200) { + if (quirkActionLocked) return; + + setQuirkActionLocked(true); + debounce(); + + setTimeout(() => { + setQuirkActionLocked(false); + }, delay); + } + const [searchQuery, setSearchQuery] = useState(''); const server_data = useServerPrefs(); if (!server_data) return; @@ -422,14 +443,16 @@ function QuirkPage() { { + quirkActionLocked={quirkActionLocked} + handleClick={(quirkName, quirk) => { if (getReasonToNotAdd(quirkName) !== undefined) { return; } - setSelectedQuirks(selectedQuirks.concat(quirkName)); - - act('give_quirk', { quirk: quirk.name }); + withQuirkDebounce(() => { + setSelectedQuirks(selectedQuirks.concat(quirkName)); + act('give_quirk', { quirk: quirk.name }); + }); }} quirks={quirks .filter(([quirkName, _]) => { @@ -483,18 +506,19 @@ function QuirkPage() { { + quirkActionLocked={quirkActionLocked} + handleClick={(quirkName, quirk) => { if (getReasonToNotRemove(quirkName) !== undefined) { return; } - setSelectedQuirks( - selectedQuirks.filter( - (otherQuirk) => quirkName !== otherQuirk, - ), - ); + withQuirkDebounce(() => { + setSelectedQuirks( + selectedQuirks.filter((otherQuirk) => quirkName !== otherQuirk), + ); - act('remove_quirk', { quirk: quirk.name }); + act('remove_quirk', { quirk: quirk.name }); + }); }} quirks={quirks .filter(([quirkName, _]) => { diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx index 5f9093f7ab8..ba75f768824 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx @@ -136,6 +136,7 @@ export function FeatureIconnedDropdownInput(props: IconnedDropdownInputProps) { options={dropdownOptions} selected={value} width="100%" + menuWidth="max-content" /> ); }