From c61f74f7e9a49af18f995495fc5a85fdc3627945 Mon Sep 17 00:00:00 2001 From: Cameron Lennox Date: Wed, 13 Aug 2025 12:20:20 -0400 Subject: [PATCH] Fixes savefile corruption bug and allows character swapping (#18230) * Converts original to a weakref * More pref stuff * Record Updater * No in-round pref changing here * Shadekin warning * irrelevent * make this booleanlike --- .../components/species/shadekin/shadekin.dm | 19 ++++++++---- code/datums/mind.dm | 6 ++-- code/game/antagonist/antagonist_update.dm | 2 +- code/game/antagonist/outsider/technomancer.dm | 5 ++-- code/game/antagonist/station/rogue_ai.dm | 2 +- code/game/gamemodes/game_mode.dm | 30 ++++++++++--------- code/game/gamemodes/objective.dm | 3 +- code/game/machinery/computer/message.dm | 7 +++-- code/game/machinery/computer/robot.dm | 3 +- .../preference_setup/general/03_body.dm | 8 +++++ .../modules/client/preferences/_preference.dm | 9 +++++- code/modules/client/preferences_tgui.dm | 3 -- code/modules/client/record_updater.dm | 9 +++++- .../mob/living/carbon/human/emote_vr.dm | 2 -- code/modules/mob/living/silicon/robot/laws.dm | 7 +++-- code/modules/mob/mob.dm | 5 ++-- code/modules/mob/mob_helpers.dm | 10 ++++--- code/modules/mob/new_player/new_player.dm | 2 +- code/modules/mob/transform_procs.dm | 4 +-- .../bay_prefs/general/SubtabBody.tsx | 6 ++++ .../PreferencesMenu/bay_prefs/general/data.ts | 1 + .../tgui/interfaces/ShadekinConfig.tsx | 14 +++++++-- 22 files changed, 104 insertions(+), 53 deletions(-) diff --git a/code/datums/components/species/shadekin/shadekin.dm b/code/datums/components/species/shadekin/shadekin.dm index d9cf5980957..7f07218ad10 100644 --- a/code/datums/components/species/shadekin/shadekin.dm +++ b/code/datums/components/species/shadekin/shadekin.dm @@ -192,6 +192,12 @@ stun_time -= min(flicker_break_chance / 5, 1) return stun_time +///Sees if the savefile we have selected in CHARACTER SETUP is the same as our ACTIVE CHARACTER savefile. +/datum/component/shadekin/proc/correct_savefile_selected() + if(owner.client.prefs.default_slot == owner.mind.loaded_from_slot) + return TRUE + return FALSE + /datum/component/shadekin/tgui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -208,6 +214,7 @@ "no_retreat" = no_retreat, "nutrition_energy_conversion" = nutrition_energy_conversion, "extended_kin" = extended_kin, + "savefile_selected" = correct_savefile_selected() ) return data @@ -227,14 +234,14 @@ if(!isnum(new_time)) return FALSE flicker_time = new_time - ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_time, new_time, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_time, new_time, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) return TRUE if("adjust_color") var/set_new_color = tgui_color_picker(ui.user, "Select a color you wish the lights to flicker as (Default is #E0EFF0)", "Color Selector", flicker_color) if(!set_new_color) return FALSE flicker_color = set_new_color - ui.user.write_preference_directly(/datum/preference/color/living/flicker_color, set_new_color, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/color/living/flicker_color, set_new_color, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) return TRUE if("adjust_break") var/new_break_chance = text2num(params["val"]) @@ -242,7 +249,7 @@ if(!isnum(new_break_chance)) return FALSE flicker_break_chance = new_break_chance - ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_break_chance, new_break_chance, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_break_chance, new_break_chance, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) return TRUE if("adjust_distance") var/new_distance = text2num(params["val"]) @@ -250,16 +257,16 @@ if(!isnum(new_distance)) return FALSE flicker_distance = new_distance - ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_distance, new_distance, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_distance, new_distance, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) return TRUE if("toggle_retreat") var/new_retreat = !no_retreat no_retreat = !no_retreat - ui.user.write_preference_directly(/datum/preference/toggle/living/dark_retreat_toggle, new_retreat, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/toggle/living/dark_retreat_toggle, new_retreat, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) if("toggle_nutrition") var/new_retreat = !nutrition_energy_conversion nutrition_energy_conversion = !nutrition_energy_conversion - ui.user.write_preference_directly(/datum/preference/toggle/living/shadekin_nutrition_conversion, new_retreat, WRITE_PREF_MANUAL) + ui.user.write_preference_directly(/datum/preference/toggle/living/shadekin_nutrition_conversion, new_retreat, WRITE_PREF_MANUAL, save_to_played_slot = TRUE) /mob/living/proc/shadekin_control_panel() set name = "Shadekin Control Panel" diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 93ad33bf1b0..a6626511eac 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -2,7 +2,7 @@ The way datum/mind stuff works has been changed a lot. Minds now represent IC characters rather than following a client around constantly. Guidelines for using minds properly: - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + - Never mind.transfer_to(ghost). The var/current and var/original_character of a mind must always be of type mob/living! ghost.mind is however used as a reference to the ghost's corpse - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) the existing mind of the old mob should be transfered to the new mob like so: @@ -23,7 +23,7 @@ var/key var/name //replaces mob/var/original_name var/mob/living/current - var/mob/living/original //TODO: remove.not used in any meaningful way ~Carn. First I'll need to tweak the way silicon-mobs handle minds. + var/datum/weakref/original_character //replaces /mob/living/original var/active = 0 var/memory @@ -508,7 +508,7 @@ mind.key = key else mind = new /datum/mind(key) - mind.original = src + mind.original_character = WEAKREF(src) if(SSticker) SSticker.minds += mind else diff --git a/code/game/antagonist/antagonist_update.dm b/code/game/antagonist/antagonist_update.dm index 7d19f0de6ae..db8183326eb 100644 --- a/code/game/antagonist/antagonist_update.dm +++ b/code/game/antagonist/antagonist_update.dm @@ -10,7 +10,7 @@ player.current = new mob_path(get_turf(player.current)) player.transfer_to(player.current) if(holder) qdel(holder) - player.original = player.current + player.original_character = WEAKREF(player.current) if(!preserve_appearance && (flags & ANTAG_SET_APPEARANCE)) spawn(3) var/mob/living/carbon/human/H = player.current diff --git a/code/game/antagonist/outsider/technomancer.dm b/code/game/antagonist/outsider/technomancer.dm index 112ff3dd548..38083baa65a 100644 --- a/code/game/antagonist/outsider/technomancer.dm +++ b/code/game/antagonist/outsider/technomancer.dm @@ -93,8 +93,9 @@ var/datum/antagonist/technomancer/technomancers var/text = print_player_lite(player) var/obj/item/technomancer_core/core - if(player.original) - core = locate() in player.original + var/mob/living/original = player.original_character?.resolve() + if(original) + core = locate() in original if(core) text += "
Bought [english_list(core.spells)], and used \a [core]." else diff --git a/code/game/antagonist/station/rogue_ai.dm b/code/game/antagonist/station/rogue_ai.dm index 147019bfa51..2521943c263 100644 --- a/code/game/antagonist/station/rogue_ai.dm +++ b/code/game/antagonist/station/rogue_ai.dm @@ -89,7 +89,7 @@ var/datum/antagonist/rogue_ai/malf player.current = new mob_path(get_turf(player.current), null, null, 1) player.transfer_to(player.current) if(holder) qdel(holder) - player.original = player.current + player.original_character = WEAKREF(player.current) return player.current /datum/antagonist/rogue_ai/set_antag_name(var/mob/living/silicon/player) diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 18d3c9c8d25..cdce34f3925 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -502,21 +502,23 @@ GLOBAL_LIST_EMPTY(additional_antag_types) continue //Happy connected client for(var/mob/observer/dead/D in GLOB.dead_mob_list) - if(D.mind && (D.mind.original == L || D.mind.current == L)) - if(L.stat == DEAD) - if(L.suiciding) //Suicider - msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Suicide"))])
" - continue //Disconnected client + if(D.mind) + var/mob/living/original = D.mind.original_character?.resolve() + if((original && original == L) || D.mind.current == L) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Suicide"))])
" + continue //Disconnected client + else + msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] (Dead)
" + continue //Dead mob, ghost abandoned else - msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] (Dead)
" - continue //Dead mob, ghost abandoned - else - if(D.can_reenter_corpse) - msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Adminghosted"))])
" - continue //Lolwhat - else - msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Ghosted"))])
" - continue //Ghosted while alive + if(D.can_reenter_corpse) + msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Adminghosted"))])
" + continue //Lolwhat + else + msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Ghosted"))])
" + continue //Ghosted while alive msg = span_notice(msg)// close the span from right at the top diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index b894823eeab..c20faad032b 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -325,7 +325,8 @@ GLOBAL_LIST_EMPTY(all_objectives) /datum/objective/survive/check_completion() if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current)) return 0 //Brains no longer win survive objectives. --NEO - if(issilicon(owner.current) && owner.current != owner.original) + var/mob/living/original = owner.original_character?.resolve() + if(issilicon(owner.current) && (original && (owner.current != original))) return 0 return 1 diff --git a/code/game/machinery/computer/message.dm b/code/game/machinery/computer/message.dm index 46e3d87844e..fe615a59842 100644 --- a/code/game/machinery/computer/message.dm +++ b/code/game/machinery/computer/message.dm @@ -144,8 +144,8 @@ continue sendPDAs["[P.name]"] = "\ref[P]" data["possibleRecipients"] = sendPDAs - - data["isMalfAI"] = ((isAI(user) || isrobot(user)) && (user.mind.special_role && user.mind.original == user)) + var/mob/living/original = user.mind.original_character?.resolve() + data["isMalfAI"] = ((isAI(user) || isrobot(user)) && (user.mind.special_role && (original && original == user))) return data @@ -210,7 +210,8 @@ temp = noserver //Hack the Console to get the password if("hack") - if((isAI(ui.user) || isrobot(ui.user)) && (ui.user.mind.special_role && ui.user.mind.original == ui.user)) + var/mob/living/original = ui.user.mind.original_character?.resolve() + if((isAI(ui.user) || isrobot(ui.user)) && (ui.user.mind.special_role && (original && original == ui.user))) hacking = 1 update_icon() //Time it takes to bruteforce is dependant on the password length. diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index dc706443325..0e5cb7f1ea2 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -92,7 +92,8 @@ return TRUE if(!isAI(user)) return FALSE - return (user.mind.special_role && user.mind.original == user) + var/mob/living/original = user.mind.original_character?.resolve() + return (user.mind.special_role && (original && original == user)) /** * Check if the user is allowed to hack a specific borg diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 880ab01a248..84ca33502a0 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -319,6 +319,7 @@ var/const/preview_icons = 'icons/mob/human_races/preview.dmi' data["b_type"] = pref.b_type data["digitigrade"] = pref.digitigrade + data["tail_layering"] = pref.read_preference(/datum/preference/choiced/human/tail_layering) data["synth_color_toggle"] = pref.synth_color data["synth_color"] = pref.read_preference(/datum/preference/color/human/synth_color) @@ -996,6 +997,13 @@ var/const/preview_icons = 'icons/mob/human_races/preview.dmi' pref.digitigrade = !pref.digitigrade return TOPIC_REFRESH_UPDATE_PREVIEW + if("set_tail_layering") + var/new_tail_layering = tgui_input_list(user, "Select a tail layer.", "Set Tail Layer", GLOB.tail_layer_options, + pref.read_preference(/datum/preference/choiced/human/tail_layering)) + if(new_tail_layering) + pref.update_preference_by_type(/datum/preference/choiced/human/tail_layering, new_tail_layering) + return TOPIC_REFRESH_UPDATE_PREVIEW + if("synth_color_toggle") pref.synth_color = !pref.synth_color return TOPIC_REFRESH_UPDATE_PREVIEW diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm index 93b463e195b..069640f416f 100644 --- a/code/modules/client/preferences/_preference.dm +++ b/code/modules/client/preferences/_preference.dm @@ -318,10 +318,17 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) /// Write a /datum/preference type and return its value directly to the json. /// Please use SScharacter_setup.queue_preferences_save(prefs) when you edit multiple at once and set direct_write to WRITE_PREF_MANUAL -/mob/proc/write_preference_directly(preference_type, preference_value, write_mode = WRITE_PREF_INSTANT) +/// Additionally, if you want something to be changed IN ROUND and change a pref for THAT CHARACTER'S SAVESLOT, ensure save_to_played_slot = TRUE! +/mob/proc/write_preference_directly(preference_type, preference_value, write_mode = WRITE_PREF_INSTANT, save_to_played_slot) + var/remembered_default + if(save_to_played_slot && (mind.loaded_from_slot != client?.prefs?.default_slot)) + remembered_default = client?.prefs?.default_slot + client?.prefs?.load_character(mind.loaded_from_slot) var/success = client?.prefs?.write_preference_by_type(preference_type, preference_value, write_mode) if(success) client?.prefs?.value_cache[preference_type] = preference_value + if(remembered_default) + client?.prefs?.return_to_character_slot(src, remembered_default) return success /// Set a /datum/preference entry. diff --git a/code/modules/client/preferences_tgui.dm b/code/modules/client/preferences_tgui.dm index 24dc0147f91..90b4dbedb05 100644 --- a/code/modules/client/preferences_tgui.dm +++ b/code/modules/client/preferences_tgui.dm @@ -136,9 +136,6 @@ switch(action) // Basic actions if("load") - if(!isnewplayer(ui.user)) - to_chat(ui.user, span_userdanger("You can't change your character slot while being in round.")) - return FALSE if(!IsGuestKey(ui.user.key)) open_load_dialog(ui.user) . = TRUE diff --git a/code/modules/client/record_updater.dm b/code/modules/client/record_updater.dm index a992fb93aab..c092fd3ca61 100644 --- a/code/modules/client/record_updater.dm +++ b/code/modules/client/record_updater.dm @@ -76,6 +76,14 @@ var/global/client_record_update_lock = FALSE playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0) return "Update syncronization failed (OOC: Record's owner is offline)" + var/datum/preferences/P = C.prefs + if(P.default_slot != M.mind.loaded_from_slot) + if(COM && !QDELETED(COM)) + COM.visible_message(span_notice("\The [COM] buzzes!")) + playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0) + to_chat(M, span_warning("[user] attempted to update your [record_string] record, but your current character slot does not match your played slot. Please ensure your currently played character is selected in your Character Setup.")) + return "Update syncronization failed (OOC: Player's current character slot does not match their played slot. They have been informed.)" + var/choice = tgui_alert(M, "Your [record_string] record has been updated from the a records console by [user]. Please review the changes made to your [record_string] record. Accepting these changes will SAVE your CURRENT character slot! If your new [record_string] record has errors, it is recomended to have it corrected IC instead of editing it yourself.", "Record Updated", list("Review Changes","DENY")) if(!choice || choice == "DENY") message_admins("[active.fields["name"]] refused [record_string] record update from [user] without review.") @@ -84,7 +92,6 @@ var/global/client_record_update_lock = FALSE playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0) return "Update syncronization failed (OOC: Player refused without review)" - var/datum/preferences/P = C.prefs var/new_data = strip_html_simple(tgui_input_text(M,"Please review [user]'s changes to your [record_string] record before confirming. Confirming will SAVE your CURRENT character slot! If your new [record_string] record major errors, it is recomended to have it corrected IC instead of editing it yourself.","Character Preference", html_decode(active.fields["notes"]), MAX_RECORD_LENGTH, TRUE, prevent_enter = TRUE), MAX_RECORD_LENGTH) if(!new_data) message_admins("[active.fields["name"]] refused [record_string] record update from [user] with review.") diff --git a/code/modules/mob/living/carbon/human/emote_vr.dm b/code/modules/mob/living/carbon/human/emote_vr.dm index 79173a72f87..5fe03ce32ea 100644 --- a/code/modules/mob/living/carbon/human/emote_vr.dm +++ b/code/modules/mob/living/carbon/human/emote_vr.dm @@ -76,8 +76,6 @@ if(!tail_option) return tail_layering = tail_option - write_preference_directly(/datum/preference/choiced/human/tail_layering, input) - update_tail_showing() /mob/living/carbon/human/verb/hide_wings_vr() diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index af480641857..080b691630d 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -6,6 +6,9 @@ /mob/living/silicon/robot/show_laws(var/everyone = 0) laws_sanity_check() var/who + var/mob/living/original + if(mind) + original = mind.original_character?.resolve() if (everyone) who = world @@ -21,7 +24,7 @@ photosync() to_chat(src, span_infoplain(span_bold("Laws synced with AI, be sure to note any changes."))) // TODO: Update to new antagonist system. - if(mind && mind.special_role == "traitor" && mind.original == src) + if(mind && mind.special_role == "traitor" && (original && original == src)) to_chat(src, span_infoplain(span_bold("Remember, your AI does NOT share or know about your law 0."))) else to_chat(src, span_infoplain(span_bold("No AI selected to sync laws with, disabling lawsync protocol."))) @@ -32,7 +35,7 @@ if(shell) //AI shell to_chat(who, span_infoplain(span_bold("Remember, you are an AI remotely controlling your shell, other AIs can be ignored."))) // TODO: Update to new antagonist system. - else if(mind && (mind.special_role == "traitor" && mind.original == src) && connected_ai) + else if(mind && (mind.special_role == "traitor" && (original && original == src)) && connected_ai) to_chat(who, span_infoplain(span_bold("Remember, [connected_ai.name] is technically your master, but your objective comes first."))) else if(connected_ai) to_chat(who, span_infoplain(span_bold("Remember, [connected_ai.name] is your master, other AIs can be ignored."))) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index ce27989b5fd..3db25d8e243 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -44,8 +44,9 @@ if(mind) if(mind.current == src) mind.current = null - if(mind.original == src) - mind.original = null + var/mob/living/original = mind.original_character?.resolve() + if(original && original == src) + mind.original_character = null . = ..() update_client_z(null) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 5c600302b57..f0a9a5f0fbf 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -464,8 +464,9 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) var/realname = C.mob.real_name if(C.mob.mind) mindname = C.mob.mind.name - if(C.mob.mind.original && C.mob.mind.original.real_name) - realname = C.mob.mind.original.real_name + var/mob/living/original = C.mob.mind.original_character?.resolve() + if(original && original.real_name) + realname = original.real_name if(mindname && mindname != realname) name = "[realname] died as [mindname]" else @@ -529,10 +530,11 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) C = O else if(istype(O, /datum/mind)) var/datum/mind/M = O + var/mob/living/original = M.original_character?.resolve() if(M.current && M.current.client) C = M.current.client - else if(M.original && M.original.client) - C = M.original.client + else if(original && original.client) + C = original.client if(C) var/name diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 96fb89c2280..4971c2e8c5d 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -413,7 +413,7 @@ if(mind) mind.active = 0 //we wish to transfer the key manually - mind.original = new_character + mind.original_character = WEAKREF(new_character) mind.loaded_from_ckey = client.ckey mind.loaded_from_slot = client.prefs.default_slot mind.transfer_to(new_character) //won't transfer key since the mind is not active diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 628d4159b7a..83833764c0a 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -100,7 +100,7 @@ if(mind) mind.transfer_to(O) - O.mind.original = O + O.mind.original_character = WEAKREF(O) else O.key = key @@ -159,7 +159,7 @@ if(mind) //TODO mind.transfer_to(O) if(O.mind.assigned_role == JOB_CYBORG) - O.mind.original = O + O.mind.original_character = WEAKREF(O) else if(mind && mind.special_role) O.mind.store_memory("In case you look at this after being borged, the objectives are only here until I find a way to make them not show up for you, as I can't simply delete them without screwing up round-end reporting. --NeoFite") else diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabBody.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabBody.tsx index f91020fcd05..475593c262c 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabBody.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabBody.tsx @@ -43,6 +43,7 @@ export const SubtabBody = (props: { s_tone, b_type, digitigrade, + tail_layering, synth_color, synth_color_toggle, synth_markings, @@ -130,6 +131,11 @@ export const SubtabBody = (props: { {digitigrade ? 'Yes' : 'No'} + + +