[MIRROR] Fixes savefile corruption bug and allows character swapping (#11406)

Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-08-13 11:19:03 -07:00
committed by GitHub
parent c58ead6223
commit 4d3de029e3
22 changed files with 104 additions and 53 deletions

View File

@@ -192,6 +192,12 @@
stun_time -= min(flicker_break_chance / 5, 1) stun_time -= min(flicker_break_chance / 5, 1)
return stun_time 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) /datum/component/shadekin/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui) ui = SStgui.try_update_ui(user, src, ui)
if(!ui) if(!ui)
@@ -208,6 +214,7 @@
"no_retreat" = no_retreat, "no_retreat" = no_retreat,
"nutrition_energy_conversion" = nutrition_energy_conversion, "nutrition_energy_conversion" = nutrition_energy_conversion,
"extended_kin" = extended_kin, "extended_kin" = extended_kin,
"savefile_selected" = correct_savefile_selected()
) )
return data return data
@@ -227,14 +234,14 @@
if(!isnum(new_time)) if(!isnum(new_time))
return FALSE return FALSE
flicker_time = new_time 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 return TRUE
if("adjust_color") 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) 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) if(!set_new_color)
return FALSE return FALSE
flicker_color = set_new_color 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 return TRUE
if("adjust_break") if("adjust_break")
var/new_break_chance = text2num(params["val"]) var/new_break_chance = text2num(params["val"])
@@ -242,7 +249,7 @@
if(!isnum(new_break_chance)) if(!isnum(new_break_chance))
return FALSE return FALSE
flicker_break_chance = new_break_chance 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 return TRUE
if("adjust_distance") if("adjust_distance")
var/new_distance = text2num(params["val"]) var/new_distance = text2num(params["val"])
@@ -250,16 +257,16 @@
if(!isnum(new_distance)) if(!isnum(new_distance))
return FALSE return FALSE
flicker_distance = new_distance 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 return TRUE
if("toggle_retreat") if("toggle_retreat")
var/new_retreat = !no_retreat var/new_retreat = !no_retreat
no_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") if("toggle_nutrition")
var/new_retreat = !nutrition_energy_conversion var/new_retreat = !nutrition_energy_conversion
nutrition_energy_conversion = !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() /mob/living/proc/shadekin_control_panel()
set name = "Shadekin Control Panel" set name = "Shadekin Control Panel"

View File

@@ -2,7 +2,7 @@
The way datum/mind stuff works has been changed a lot. The way datum/mind stuff works has been changed a lot.
Minds now represent IC characters rather than following a client around constantly. Minds now represent IC characters rather than following a client around constantly.
Guidelines for using minds properly: 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 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) - 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: the existing mind of the old mob should be transfered to the new mob like so:
@@ -23,7 +23,7 @@
var/key var/key
var/name //replaces mob/var/original_name var/name //replaces mob/var/original_name
var/mob/living/current 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/active = 0
var/memory var/memory
@@ -508,7 +508,7 @@
mind.key = key mind.key = key
else else
mind = new /datum/mind(key) mind = new /datum/mind(key)
mind.original = src mind.original_character = WEAKREF(src)
if(SSticker) if(SSticker)
SSticker.minds += mind SSticker.minds += mind
else else

View File

@@ -10,7 +10,7 @@
player.current = new mob_path(get_turf(player.current)) player.current = new mob_path(get_turf(player.current))
player.transfer_to(player.current) player.transfer_to(player.current)
if(holder) qdel(holder) if(holder) qdel(holder)
player.original = player.current player.original_character = WEAKREF(player.current)
if(!preserve_appearance && (flags & ANTAG_SET_APPEARANCE)) if(!preserve_appearance && (flags & ANTAG_SET_APPEARANCE))
spawn(3) spawn(3)
var/mob/living/carbon/human/H = player.current var/mob/living/carbon/human/H = player.current

View File

@@ -93,8 +93,9 @@ var/datum/antagonist/technomancer/technomancers
var/text = print_player_lite(player) var/text = print_player_lite(player)
var/obj/item/technomancer_core/core var/obj/item/technomancer_core/core
if(player.original) var/mob/living/original = player.original_character?.resolve()
core = locate() in player.original if(original)
core = locate() in original
if(core) if(core)
text += "<br>Bought [english_list(core.spells)], and used \a [core]." text += "<br>Bought [english_list(core.spells)], and used \a [core]."
else else

View File

@@ -89,7 +89,7 @@ var/datum/antagonist/rogue_ai/malf
player.current = new mob_path(get_turf(player.current), null, null, 1) player.current = new mob_path(get_turf(player.current), null, null, 1)
player.transfer_to(player.current) player.transfer_to(player.current)
if(holder) qdel(holder) if(holder) qdel(holder)
player.original = player.current player.original_character = WEAKREF(player.current)
return player.current return player.current
/datum/antagonist/rogue_ai/set_antag_name(var/mob/living/silicon/player) /datum/antagonist/rogue_ai/set_antag_name(var/mob/living/silicon/player)

View File

@@ -530,21 +530,23 @@ GLOBAL_LIST_EMPTY(additional_antag_types)
continue //Happy connected client continue //Happy connected client
for(var/mob/observer/dead/D in GLOB.dead_mob_list) for(var/mob/observer/dead/D in GLOB.dead_mob_list)
if(D.mind && (D.mind.original == L || D.mind.current == L)) if(D.mind)
if(L.stat == DEAD) var/mob/living/original = D.mind.original_character?.resolve()
if(L.suiciding) //Suicider if((original && original == L) || D.mind.current == L)
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Suicide"))])<br>" if(L.stat == DEAD)
continue //Disconnected client if(L.suiciding) //Suicider
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Suicide"))])<br>"
continue //Disconnected client
else
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] (Dead)<br>"
continue //Dead mob, ghost abandoned
else else
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] (Dead)<br>" if(D.can_reenter_corpse)
continue //Dead mob, ghost abandoned msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Adminghosted"))])<br>"
else continue //Lolwhat
if(D.can_reenter_corpse) else
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Adminghosted"))])<br>" msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Ghosted"))])<br>"
continue //Lolwhat continue //Ghosted while alive
else
msg += "[span_bold(L.name)] ([ckey(D.mind.key)]), the [L.job] ([span_red(span_bold("Ghosted"))])<br>"
continue //Ghosted while alive
continue // CHOMPEdit: Escape infinite loop in case there's nobody connected. Shouldn't happen ever, but. continue // CHOMPEdit: Escape infinite loop in case there's nobody connected. Shouldn't happen ever, but.

View File

@@ -325,7 +325,8 @@ GLOBAL_LIST_EMPTY(all_objectives)
/datum/objective/survive/check_completion() /datum/objective/survive/check_completion()
if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current)) if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current))
return 0 //Brains no longer win survive objectives. --NEO 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 0
return 1 return 1

View File

@@ -144,8 +144,8 @@
continue continue
sendPDAs["[P.name]"] = "\ref[P]" sendPDAs["[P.name]"] = "\ref[P]"
data["possibleRecipients"] = sendPDAs data["possibleRecipients"] = sendPDAs
var/mob/living/original = user.mind.original_character?.resolve()
data["isMalfAI"] = ((isAI(user) || isrobot(user)) && (user.mind.special_role && user.mind.original == user)) data["isMalfAI"] = ((isAI(user) || isrobot(user)) && (user.mind.special_role && (original && original == user)))
return data return data
@@ -210,7 +210,8 @@
temp = noserver temp = noserver
//Hack the Console to get the password //Hack the Console to get the password
if("hack") 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 hacking = 1
update_icon() update_icon()
//Time it takes to bruteforce is dependant on the password length. //Time it takes to bruteforce is dependant on the password length.

View File

@@ -92,7 +92,8 @@
return TRUE return TRUE
if(!isAI(user)) if(!isAI(user))
return FALSE 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 * Check if the user is allowed to hack a specific borg

View File

@@ -319,6 +319,7 @@ var/const/preview_icons = 'icons/mob/human_races/preview.dmi'
data["b_type"] = pref.b_type data["b_type"] = pref.b_type
data["digitigrade"] = pref.digitigrade 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_toggle"] = pref.synth_color
data["synth_color"] = pref.read_preference(/datum/preference/color/human/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 pref.digitigrade = !pref.digitigrade
return TOPIC_REFRESH_UPDATE_PREVIEW 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") if("synth_color_toggle")
pref.synth_color = !pref.synth_color pref.synth_color = !pref.synth_color
return TOPIC_REFRESH_UPDATE_PREVIEW return TOPIC_REFRESH_UPDATE_PREVIEW

View File

@@ -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. /// 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 /// 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) var/success = client?.prefs?.write_preference_by_type(preference_type, preference_value, write_mode)
if(success) if(success)
client?.prefs?.value_cache[preference_type] = preference_value client?.prefs?.value_cache[preference_type] = preference_value
if(remembered_default)
client?.prefs?.return_to_character_slot(src, remembered_default)
return success return success
/// Set a /datum/preference entry. /// Set a /datum/preference entry.

View File

@@ -136,9 +136,6 @@
switch(action) switch(action)
// Basic actions // Basic actions
if("load") 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)) if(!IsGuestKey(ui.user.key))
open_load_dialog(ui.user) open_load_dialog(ui.user)
. = TRUE . = TRUE

View File

@@ -76,6 +76,14 @@ var/global/client_record_update_lock = FALSE
playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0) playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
return "Update syncronization failed (OOC: Record's owner is offline)" 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")) 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") if(!choice || choice == "DENY")
message_admins("[active.fields["name"]] refused [record_string] record update from [user] without review.") 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) playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
return "Update syncronization failed (OOC: Player refused without review)" 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) 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) if(!new_data)
message_admins("[active.fields["name"]] refused [record_string] record update from [user] with review.") message_admins("[active.fields["name"]] refused [record_string] record update from [user] with review.")

View File

@@ -76,8 +76,6 @@
if(!tail_option) if(!tail_option)
return return
tail_layering = tail_option tail_layering = tail_option
write_preference_directly(/datum/preference/choiced/human/tail_layering, input)
update_tail_showing() update_tail_showing()
/mob/living/carbon/human/verb/hide_wings_vr() /mob/living/carbon/human/verb/hide_wings_vr()

View File

@@ -6,6 +6,9 @@
/mob/living/silicon/robot/show_laws(var/everyone = 0) /mob/living/silicon/robot/show_laws(var/everyone = 0)
laws_sanity_check() laws_sanity_check()
var/who var/who
var/mob/living/original
if(mind)
original = mind.original_character?.resolve()
if (everyone) if (everyone)
who = world who = world
@@ -21,7 +24,7 @@
photosync() photosync()
to_chat(src, span_infoplain(span_bold("Laws synced with AI, be sure to note any changes."))) to_chat(src, span_infoplain(span_bold("Laws synced with AI, be sure to note any changes.")))
// TODO: Update to new antagonist system. // 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."))) to_chat(src, span_infoplain(span_bold("Remember, your AI does NOT share or know about your law 0.")))
else else
to_chat(src, span_infoplain(span_bold("No AI selected to sync laws with, disabling lawsync protocol."))) 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 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."))) 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. // 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."))) to_chat(who, span_infoplain(span_bold("Remember, [connected_ai.name] is technically your master, but your objective comes first.")))
else if(connected_ai) else if(connected_ai)
to_chat(who, span_infoplain(span_bold("Remember, [connected_ai.name] is your master, other AIs can be ignored."))) to_chat(who, span_infoplain(span_bold("Remember, [connected_ai.name] is your master, other AIs can be ignored.")))

View File

@@ -44,8 +44,9 @@
if(mind) if(mind)
if(mind.current == src) if(mind.current == src)
mind.current = null mind.current = null
if(mind.original == src) var/mob/living/original = mind.original_character?.resolve()
mind.original = null if(original && original == src)
mind.original_character = null
. = ..() . = ..()
update_client_z(null) update_client_z(null)

View File

@@ -464,8 +464,9 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
var/realname = C.mob.real_name var/realname = C.mob.real_name
if(C.mob.mind) if(C.mob.mind)
mindname = C.mob.mind.name mindname = C.mob.mind.name
if(C.mob.mind.original && C.mob.mind.original.real_name) var/mob/living/original = C.mob.mind.original_character?.resolve()
realname = C.mob.mind.original.real_name if(original && original.real_name)
realname = original.real_name
if(mindname && mindname != realname) if(mindname && mindname != realname)
name = "[realname] died as [mindname]" name = "[realname] died as [mindname]"
else else
@@ -529,10 +530,11 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT)
C = O C = O
else if(istype(O, /datum/mind)) else if(istype(O, /datum/mind))
var/datum/mind/M = O var/datum/mind/M = O
var/mob/living/original = M.original_character?.resolve()
if(M.current && M.current.client) if(M.current && M.current.client)
C = M.current.client C = M.current.client
else if(M.original && M.original.client) else if(original && original.client)
C = M.original.client C = original.client
if(C) if(C)
var/name var/name

View File

@@ -423,7 +423,7 @@
if(mind) if(mind)
mind.active = 0 //we wish to transfer the key manually 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_ckey = client.ckey
mind.loaded_from_slot = client.prefs.default_slot mind.loaded_from_slot = client.prefs.default_slot
mind.transfer_to(new_character) //won't transfer key since the mind is not active mind.transfer_to(new_character) //won't transfer key since the mind is not active

View File

@@ -100,7 +100,7 @@
if(mind) if(mind)
mind.transfer_to(O) mind.transfer_to(O)
O.mind.original = O O.mind.original_character = WEAKREF(O)
else else
O.key = key O.key = key
@@ -163,7 +163,7 @@
if(mind) //TODO if(mind) //TODO
mind.transfer_to(O) mind.transfer_to(O)
if(O.mind.assigned_role == JOB_CYBORG) if(O.mind.assigned_role == JOB_CYBORG)
O.mind.original = O O.mind.original_character = WEAKREF(O)
else if(mind && mind.special_role) 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") 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 else

View File

@@ -43,6 +43,7 @@ export const SubtabBody = (props: {
s_tone, s_tone,
b_type, b_type,
digitigrade, digitigrade,
tail_layering,
synth_color, synth_color,
synth_color_toggle, synth_color_toggle,
synth_markings, synth_markings,
@@ -130,6 +131,11 @@ export const SubtabBody = (props: {
{digitigrade ? 'Yes' : 'No'} {digitigrade ? 'Yes' : 'No'}
</Button> </Button>
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Tail Layering">
<Button inline onClick={() => act('set_tail_layering')}>
{tail_layering}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Blood Type"> <LabeledList.Item label="Blood Type">
<Button inline onClick={() => act('blood_type')}> <Button inline onClick={() => act('blood_type')}>
{b_type} {b_type}

View File

@@ -181,6 +181,7 @@ export type BodyData = {
body_markings: Record<string, BodyMarking>; body_markings: Record<string, BodyMarking>;
tail_style: string; tail_style: string;
tail_layering: string;
tail_color1: string; tail_color1: string;
tail_color2: string; tail_color2: string;
tail_color3: string; tail_color3: string;

View File

@@ -11,6 +11,7 @@ import {
Stack, Stack,
Tooltip, Tooltip,
} from 'tgui-core/components'; } from 'tgui-core/components';
import { BooleanLike } from 'tgui-core/react';
type Data = { type Data = {
stun_time: number; stun_time: number;
@@ -18,8 +19,9 @@ type Data = {
flicker_color: string | null; flicker_color: string | null;
flicker_break_chance: number; flicker_break_chance: number;
flicker_distance: number; flicker_distance: number;
no_retreat: number; no_retreat: BooleanLike;
extended_kin: number; extended_kin: BooleanLike;
savefile_selected: BooleanLike;
nutrition_energy_conversion: number; nutrition_energy_conversion: number;
}; };
@@ -33,6 +35,7 @@ export const ShadekinConfig = (props) => {
flicker_break_chance, flicker_break_chance,
flicker_distance, flicker_distance,
no_retreat, no_retreat,
savefile_selected,
extended_kin, extended_kin,
nutrition_energy_conversion, nutrition_energy_conversion,
} = data; } = data;
@@ -40,7 +43,7 @@ export const ShadekinConfig = (props) => {
const isSubtle = const isSubtle =
flicker_time < 5 || flicker_break_chance < 5 || flicker_distance < 5; flicker_time < 5 || flicker_break_chance < 5 || flicker_distance < 5;
const windowHeight = (isSubtle ? 220 : 190) + (extended_kin ? 95 : 0); const windowHeight = (isSubtle ? 220 : 190) + (extended_kin ? 95 : 0) + (savefile_selected ? 0 : 90);
return ( return (
<Window width={300} height={windowHeight} theme="abductor"> <Window width={300} height={windowHeight} theme="abductor">
@@ -51,6 +54,11 @@ export const ShadekinConfig = (props) => {
<NoticeBox>Subtle Phasing, causes {stun_time} s stun.</NoticeBox> <NoticeBox>Subtle Phasing, causes {stun_time} s stun.</NoticeBox>
</Stack.Item> </Stack.Item>
)} )}
{!savefile_selected && (
<Stack.Item>
<NoticeBox>WARNING: Your current selected savefile (in Character Setup) is not the same as your currently loaded savefile. Please select it to prevent savefile corruption.</NoticeBox>
</Stack.Item>
)}
<Stack.Item> <Stack.Item>
<Section fill title="Light Settings"> <Section fill title="Light Settings">
<LabeledList> <LabeledList>