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'}
+
+
+