mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-21 20:11:04 +01:00
This has zero reason to exist in our code base. We have no procs or variables tied to this. I removed it to make future modifications cleaner. --------- Signed-off-by: Cody Brittain <1779662+Generalcamo@users.noreply.github.com>
567 lines
17 KiB
Plaintext
567 lines
17 KiB
Plaintext
#ifndef T_BOARD
|
|
#error T_BOARD macro is not defined but we need it!
|
|
#endif
|
|
|
|
/*
|
|
* Cryogenic refrigeration unit. Basically a despawner.
|
|
* Stealing a lot of concepts/code from sleepers due to massive laziness.
|
|
* The despawn tick will only fire if it's been more than time_till_despawned ticks
|
|
* since time_entered, which is world.time when the occupant moves in.
|
|
* ~ Zuhayr
|
|
*/
|
|
|
|
//Main cryopod console.
|
|
|
|
/obj/machinery/computer/cryopod
|
|
name = "cryogenic oversight console"
|
|
desc = "An interface between crew and the cryogenic storage oversight systems."
|
|
icon = 'icons/obj/computer.dmi'
|
|
icon_state = "altcomputerw"
|
|
circuit = /obj/item/circuitboard/cryopodcontrol
|
|
density = FALSE
|
|
interact_offline = TRUE
|
|
appearance_flags = TILE_BOUND // prevents people from viewing the overlay through a wall
|
|
|
|
icon_screen = "cryo"
|
|
icon_scanline = "altcomputerw-scanline"
|
|
light_color = LIGHT_COLOR_GREEN
|
|
obj_flags = OBJ_FLAG_MOVES_UNSUPPORTED
|
|
|
|
var/mode = null
|
|
|
|
var/list/frozen_items = list()
|
|
|
|
var/storage_type = "Crewmembers"
|
|
var/storage_name = "Cryogenic Oversight Control"
|
|
var/allow_items = TRUE
|
|
|
|
/obj/machinery/computer/cryopod/robot
|
|
name = "robotic storage console"
|
|
desc = "An interface between crew and the robotic storage systems"
|
|
icon_state = "altcomputerw"
|
|
circuit = /obj/item/circuitboard/robotstoragecontrol
|
|
|
|
icon_screen = "cryo_robot"
|
|
light_color = LIGHT_COLOR_PURPLE
|
|
|
|
storage_type = "Cyborgs"
|
|
storage_name = "Robotic Storage Control"
|
|
allow_items = FALSE
|
|
|
|
/obj/machinery/computer/cryopod/living_quarters
|
|
name = "living quarters oversight console"
|
|
desc = "An interface between the main ship and the living quarters where the crew lives."
|
|
circuit = /obj/item/circuitboard/living_quarters_cryo
|
|
|
|
storage_name = "Living Quarters Oversight Control"
|
|
allow_items = TRUE
|
|
|
|
/obj/machinery/computer/cryopod/attack_ai(mob/user)
|
|
if(!ai_can_interact(user))
|
|
return
|
|
src.attack_hand(user)
|
|
|
|
/obj/machinery/computer/cryopod/attack_hand(mob/user)
|
|
if(stat & (NOPOWER|BROKEN))
|
|
return
|
|
|
|
user.set_machine(src)
|
|
src.add_fingerprint(usr)
|
|
|
|
var/dat
|
|
|
|
if(!(ROUND_IS_STARTED))
|
|
return
|
|
|
|
dat += "<hr><b>[storage_name]</b><br>"
|
|
dat += "<i>Welcome, [user.real_name].</i><br><hr><br>"
|
|
if(allow_items)
|
|
dat += "<a href='byond://?src=[REF(src)];view=1'>View Objects</a><br>"
|
|
dat += "<a href='byond://?src=[REF(src)];item=1'>Recover Object</a><br>"
|
|
dat += "<a href='byond://?src=[REF(src)];allitems=1'>Recover All Objects</a><br>"
|
|
|
|
var/datum/browser/cryocon_win = new(user, "cryopod_console", "Cryogenic Oversight Console")
|
|
cryocon_win.set_content(dat)
|
|
cryocon_win.open()
|
|
|
|
/obj/machinery/computer/cryopod/Topic(href, href_list)
|
|
if(..())
|
|
return
|
|
|
|
var/mob/user = usr
|
|
|
|
src.add_fingerprint(user)
|
|
|
|
if(href_list["view"])
|
|
if(!allow_items)
|
|
return
|
|
if(!length(frozen_items))
|
|
to_chat(user, SPAN_WARNING("There are no stored objects."))
|
|
return
|
|
|
|
var/dat = "<center><b>Recently Stored Objects</b></center><br><hr>"
|
|
for(var/obj/item/I in frozen_items)
|
|
dat += " - [I.name]<br>"
|
|
dat += "<hr>"
|
|
|
|
var/datum/browser/cryoitems_win = new(user, "cryoitems", "Cryogenic Storage Log")
|
|
cryoitems_win.set_content(dat)
|
|
cryoitems_win.open()
|
|
|
|
else if(href_list["item"])
|
|
if(!allow_items)
|
|
return
|
|
|
|
if(frozen_items.len <= 0)
|
|
to_chat(user, SPAN_WARNING("There is nothing to recover from storage."))
|
|
return
|
|
|
|
var/obj/item/I = tgui_input_list(user, "Please choose which object to retrieve.", "Object Recovery", frozen_items)
|
|
if(!I)
|
|
return
|
|
|
|
if(!(I in frozen_items))
|
|
to_chat(user, SPAN_WARNING("\The [I] is no longer in storage."))
|
|
return
|
|
|
|
visible_message(SPAN_NOTICE("The console beeps happily as it disgorges \the [I]."), range = 3)
|
|
|
|
I.forceMove(get_turf(src))
|
|
if(Adjacent(user))
|
|
user.put_in_hands(I)
|
|
frozen_items -= I
|
|
log_and_message_admins("has retrieved \an [I] from \the [src]", user, get_turf(src))
|
|
|
|
else if(href_list["allitems"])
|
|
if(!allow_items)
|
|
return
|
|
|
|
if(frozen_items.len <= 0)
|
|
to_chat(user, SPAN_WARNING("There is nothing to recover from storage."))
|
|
return
|
|
|
|
visible_message(SPAN_NOTICE("The console beeps happily as it disgorges the desired objects."), range = 3)
|
|
|
|
for(var/obj/item/I in frozen_items)
|
|
I.forceMove(get_turf(src))
|
|
frozen_items -= I
|
|
log_and_message_admins("has retrieved all the items from \the [src]", user, get_turf(src))
|
|
|
|
src.updateUsrDialog()
|
|
return
|
|
|
|
/obj/item/circuitboard/cryopodcontrol
|
|
name = T_BOARD("cryogenic oversight console")
|
|
build_path = /obj/machinery/computer/cryopod
|
|
origin_tech = list(TECH_DATA = 3)
|
|
|
|
/obj/item/circuitboard/robotstoragecontrol
|
|
name = T_BOARD("robotic storage console")
|
|
build_path = /obj/machinery/computer/cryopod/robot
|
|
origin_tech = list(TECH_DATA = 3)
|
|
|
|
/obj/item/circuitboard/living_quarters_cryo
|
|
name = T_BOARD("living quarters oversight console")
|
|
build_path = /obj/machinery/computer/cryopod/living_quarters
|
|
origin_tech = list(TECH_DATA = 3)
|
|
|
|
//Decorative structures to go alongside cryopods.
|
|
/obj/structure/cryofeed
|
|
name = "cryogenic feed"
|
|
desc = "A bewildering tangle of machinery and pipes."
|
|
icon = 'icons/obj/machinery/cryopod.dmi'
|
|
icon_state = "cryo_rear"
|
|
anchored = TRUE
|
|
dir = WEST
|
|
|
|
/obj/structure/cryofeed/pipes
|
|
name = "cryogenic feed pipes"
|
|
desc = "A bewildering tangle of pipes."
|
|
icon = 'icons/obj/machinery/cryopod.dmi'
|
|
icon_state = "cryo_rear_pipes"
|
|
|
|
//Cryopods themselves.
|
|
/obj/machinery/cryopod
|
|
name = "cryogenic freezer"
|
|
desc = "A man-sized pod for entering suspended animation."
|
|
icon = 'icons/obj/machinery/cryopod.dmi'
|
|
icon_state = "cryo_pod"
|
|
density = TRUE
|
|
anchored = TRUE
|
|
dir = WEST
|
|
var/on_store_message = "has entered"
|
|
var/on_store_location = "long-term storage"
|
|
var/on_store_name = "Cryogenic Oversight"
|
|
var/on_enter_occupant_message = "You feel cool air surround you. You go numb as your senses turn inward."
|
|
var/on_enter_sound = 'sound/machines/cryopod/cryopod_enter.ogg'
|
|
var/on_exit_sound = 'sound/machines/cryopod/cryopod_exit.ogg'
|
|
var/on_store_sound = 'sound/machines/cryopod/cryopod_store.ogg'
|
|
var/allow_occupant_types = list(/mob/living/carbon/human)
|
|
var/disallow_occupant_types = list()
|
|
|
|
var/mob/occupant // Person waiting to be despawned.
|
|
var/time_till_despawn = 1200 // Two minute safe period before being despawned.
|
|
var/time_till_force_cryo = 3000 // Five minutes safe period until they're despawned even if active.
|
|
var/time_entered = 0 // Used to keep track of the safe period.
|
|
var/obj/item/radio/intercom/announce
|
|
|
|
var/obj/machinery/computer/cryopod/control_computer
|
|
|
|
// These items are preserved when the process() despawn proc occurs.
|
|
var/list/items_blacklist = list(
|
|
/obj/item/organ,
|
|
/obj/item/implant,
|
|
/obj/item/card/id,
|
|
/obj/item/modular_computer,
|
|
/obj/item/radio/headset,
|
|
/obj/item/encryptionkey
|
|
)
|
|
|
|
//For subtypes of the blacklist that are allowed to be kept
|
|
var/list/items_whitelist = list(
|
|
/obj/item/card/id/captains_spare
|
|
)
|
|
|
|
/obj/machinery/cryopod/feedback_hints(mob/user, distance, is_adjacent)
|
|
. += ..()
|
|
if(occupant)
|
|
. += SPAN_NOTICE("<b>[occupant]</b> [occupant.get_pronoun("is")] inside \the [initial(name)].")
|
|
|
|
/obj/machinery/cryopod/robot
|
|
name = "robotic storage unit"
|
|
desc = "A storage unit for robots."
|
|
icon = 'icons/obj/robot_storage.dmi'
|
|
icon_state = "pod"
|
|
on_store_location = "robotic storage"
|
|
on_store_name = "Robotic Storage Oversight"
|
|
on_enter_sound = 'sound/machines/cryopod/lift_enter.ogg'
|
|
on_exit_sound = 'sound/machines/cryopod/lift_exit.ogg'
|
|
on_enter_occupant_message = "The storage unit broadcasts a sleep signal to you. Your systems start to shut down, and you enter low-power mode."
|
|
allow_occupant_types = list(/mob/living/silicon/robot)
|
|
|
|
/obj/machinery/cryopod/living_quarters
|
|
name = "living quarters lift"
|
|
desc = "A lift heading to the living quarters."
|
|
icon = 'icons/obj/crew_quarters_lift.dmi'
|
|
icon_state = "pod"
|
|
on_store_message = "has departed for"
|
|
on_store_location = "the living quarters"
|
|
on_store_name = "Living Quarters Oversight"
|
|
on_enter_sound = 'sound/machines/cryopod/lift_enter.ogg'
|
|
on_exit_sound = 'sound/machines/cryopod/lift_exit.ogg'
|
|
on_enter_occupant_message = "The elevator door closes slowly, ready to bring you down to the living quarters."
|
|
disallow_occupant_types = list(/mob/living/silicon/robot)
|
|
|
|
/obj/machinery/cryopod/living_quarters/update_icon()
|
|
ClearOverlays()
|
|
var/image/I = image(icon, "pod_top")
|
|
AddOverlays(I)
|
|
|
|
if(occupant)
|
|
I = image(icon, "pod_back")
|
|
AddOverlays(I)
|
|
|
|
name = "[name] ([occupant])"
|
|
I = image(occupant.icon, occupant.icon_state, dir = SOUTH)
|
|
I.overlays = occupant.overlays
|
|
I.pixel_z = 11
|
|
AddOverlays(I)
|
|
|
|
I = image(icon, "pod_door")
|
|
AddOverlays(I)
|
|
else
|
|
name = initial(name)
|
|
|
|
/obj/machinery/cryopod/Destroy()
|
|
if(occupant)
|
|
occupant.forceMove(loc)
|
|
occupant.resting = TRUE
|
|
return ..()
|
|
|
|
/obj/machinery/cryopod/Initialize()
|
|
..()
|
|
update_icon()
|
|
return INITIALIZE_HINT_LATELOAD
|
|
|
|
/obj/machinery/cryopod/LateInitialize()
|
|
. = ..()
|
|
find_control_computer()
|
|
|
|
/obj/machinery/cryopod/can_hold_dropped_items()
|
|
return FALSE
|
|
|
|
/obj/machinery/cryopod/proc/find_control_computer(urgent=0)
|
|
for(var/obj/machinery/computer/cryopod/C in get_area(src))
|
|
control_computer = C
|
|
break
|
|
|
|
return control_computer != null
|
|
|
|
/obj/machinery/cryopod/proc/check_occupant_allowed(mob/M)
|
|
var/correct_type = FALSE
|
|
for(var/type in allow_occupant_types)
|
|
if(istype(M, type))
|
|
correct_type = TRUE
|
|
break
|
|
|
|
if(!correct_type)
|
|
return FALSE
|
|
|
|
for(var/type in disallow_occupant_types)
|
|
if(istype(M, type))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
//Lifted from Unity stasis.dm and refactored. ~Zuhayr
|
|
/obj/machinery/cryopod/process()
|
|
if(occupant)
|
|
//Allow a two minute gap between entering the pod and actually despawning.
|
|
if((world.time - time_entered < time_till_despawn) && occupant.ckey)
|
|
return
|
|
|
|
if(!occupant.client && occupant.stat != DEAD) //Occupant is living and has no client.
|
|
despawn_occupant()
|
|
|
|
else if(world.time - time_entered > time_till_force_cryo)
|
|
despawn_occupant()
|
|
|
|
// This function can not be undone; do not call this unless you are sure
|
|
// Also make sure there is a valid control computer
|
|
/obj/machinery/cryopod/robot/despawn_occupant()
|
|
var/mob/living/silicon/robot/R = occupant
|
|
if(!istype(R))
|
|
return ..()
|
|
|
|
qdel(R.mmi)
|
|
for(var/obj/item/I in R.module) // the tools the borg has; metal, glass, guns etc
|
|
for(var/obj/item/O in I) // the things inside the tools, if anything; mainly for janiborg trash bags
|
|
O.forceMove(R)
|
|
qdel(I)
|
|
qdel(R.module)
|
|
|
|
return ..()
|
|
|
|
// This function can not be undone; do not call this unless you are sure
|
|
// Also make sure there is a valid control computer
|
|
/obj/machinery/cryopod/proc/despawn_occupant()
|
|
var/list/items = occupant.get_contents()
|
|
var/turf/T = get_turf(src)
|
|
//Drop all items into the pod.
|
|
for(var/obj/item/W in occupant)
|
|
occupant.drop_from_inventory(W, src)
|
|
//Prepare items tnat require modification before dropping
|
|
for(var/obj/item/W in items)
|
|
if(istype(W, /obj/item/mmi))
|
|
var/obj/item/mmi/brain = W
|
|
if(brain.brainmob && brain.brainmob.client && brain.brainmob.key)
|
|
brain.forceMove(T)
|
|
items -= brain
|
|
continue
|
|
|
|
else if(istype(W, /obj/item/rig))
|
|
var/obj/item/rig/R = W
|
|
R.open = FALSE
|
|
R.locked = TRUE
|
|
R.offline = TRUE
|
|
R.sealing = FALSE
|
|
R.canremove = TRUE
|
|
|
|
if(!is_type_in_list(W, items_whitelist) && is_type_in_list(W, items_blacklist))
|
|
items.Remove(W)
|
|
qdel(W)
|
|
|
|
for(var/obj/item/W in items)
|
|
if(W.loc == src)
|
|
if(control_computer && control_computer?.allow_items)
|
|
control_computer.frozen_items += W
|
|
W.forceMove(control_computer)
|
|
else
|
|
W.forceMove(T)
|
|
|
|
if(is_station_level(z))
|
|
GLOB.global_announcer.autosay("[occupant.real_name], [occupant.mind.role_alt_title], [on_store_message] [on_store_location].", "[on_store_name]")
|
|
visible_message(SPAN_NOTICE("\The [src] hums and hisses as it moves [occupant] to [on_store_location]."))
|
|
playsound(loc, on_store_sound, 25)
|
|
if(ishuman(occupant))
|
|
var/mob/living/carbon/human/H = occupant
|
|
if(H.ghost_spawner)
|
|
var/datum/ghostspawner/human/GS = H.ghost_spawner.resolve()
|
|
LAZYREMOVE(GS.spawned_mobs, WEAKREF(H))
|
|
GS.count--
|
|
|
|
// Let SSjobs handle the rest.
|
|
SSjobs.DespawnMob(occupant)
|
|
occupant = null
|
|
update_icon()
|
|
|
|
/obj/machinery/cryopod/attackby(obj/item/attacking_item, mob/user)
|
|
var/obj/item/grab/G = attacking_item
|
|
if(istype(G))
|
|
if(occupant)
|
|
to_chat(user, SPAN_WARNING("\The [src] is in use."))
|
|
return TRUE
|
|
if(!ismob(G.affecting))
|
|
return TRUE
|
|
if(!check_occupant_allowed(G.affecting))
|
|
return TRUE
|
|
|
|
var/mob/living/M = G.affecting
|
|
go_in(user, M)
|
|
return TRUE
|
|
|
|
/obj/machinery/cryopod/mouse_drop_receive(atom/dropped, mob/user, params)
|
|
if(!istype(user, /mob/living))
|
|
return
|
|
|
|
if(!check_occupant_allowed(dropped))
|
|
return
|
|
|
|
var/mob/living/M = dropped
|
|
if(istype(M))
|
|
go_in(user, M)
|
|
|
|
/obj/machinery/cryopod/verb/move_inside()
|
|
set name = "Enter Pod"
|
|
set category = "Object"
|
|
set src in oview(1)
|
|
|
|
if(use_check_and_message(usr) || !check_occupant_allowed(usr))
|
|
return
|
|
|
|
go_in(usr, usr, TRUE) //if you're going in of your own volition, you're probably willing
|
|
|
|
/obj/machinery/cryopod/proc/go_in(mob/user, mob/living/M, var/willing = FALSE) // user refers to the person doing the putting into, M refers to the person being put in
|
|
if(!M.bucklecheck(user)) //We must make sure the person is unbuckled before they go in
|
|
return
|
|
if(M.stat == DEAD)
|
|
to_chat(user, SPAN_WARNING("Dead people can not be put into \the [src]."))
|
|
return
|
|
for(var/mob/living/carbon/slime/S in range(1, M))
|
|
if(S.victim == M)
|
|
to_chat(usr, SPAN_WARNING("[M.name] will not fit into \the [src] because they have a slime latched onto their head!"))
|
|
if(S.victim == user)
|
|
to_chat(usr, SPAN_WARNING("You cannot fit into \the [src] do this while a slime is latched onto your head!"))
|
|
return
|
|
if(!willing && M.client)
|
|
var/original_loc = M.loc
|
|
if(alert(M, "Would you like to enter [on_store_location]?", , "Yes", "No") == "Yes")
|
|
if(!M || M.loc != original_loc)
|
|
return
|
|
willing = TRUE
|
|
else
|
|
willing = TRUE
|
|
|
|
if(!willing)
|
|
return
|
|
|
|
user.visible_message(SPAN_NOTICE("\The [user] starts [M == user ? "climbing into" : "putting \the [M] into"] \the [name]."), SPAN_NOTICE("You start [M == user ? "climbing into" : "putting \the [M] into"] \the [name]."), range = 3)
|
|
if(do_after(user, 2 SECOND, M, DO_UNIQUE))
|
|
if(!M)
|
|
return TRUE
|
|
|
|
if(occupant)
|
|
to_chat(user, SPAN_WARNING("\The [src] is already in use."))
|
|
else
|
|
to_chat(user, SPAN_NOTICE("You stop [M == user ? "climbing into" : "putting \the [M] into"] \the [name]."))
|
|
return
|
|
|
|
to_chat(M, SPAN_NOTICE("[on_enter_occupant_message]"))
|
|
playsound(loc, on_enter_sound, 25)
|
|
to_chat(M, SPAN_DANGER("Press Ghost in the OOC tab to leave, your character will shortly be removed from the round and the slot you occupy will be freed."))
|
|
set_occupant(M)
|
|
|
|
if(isipc(M))
|
|
save_ipc_tag(M)
|
|
|
|
// Book keeping!
|
|
var/turf/location = get_turf(src)
|
|
log_admin("[key_name_admin(M)] has entered a [initial(src.name)].")
|
|
message_admins(SPAN_NOTICE("[key_name_admin(M)] has entered a [initial(src.name)].(<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[location.x];Y=[location.y];Z=[location.z]'>JMP</a>)"))
|
|
|
|
//Despawning occurs when process() is called with an occupant without a client.
|
|
src.add_fingerprint(user)
|
|
|
|
/obj/machinery/cryopod/verb/eject()
|
|
set name = "Eject Pod"
|
|
set category = "Object"
|
|
set src in oview(1)
|
|
if(use_check_and_message(usr))
|
|
return
|
|
|
|
//Eject any items that aren't meant to be in the pod.
|
|
var/list/items = src.contents
|
|
if(occupant)
|
|
items -= occupant
|
|
if(announce)
|
|
items -= announce
|
|
|
|
for(var/obj/item/W in items)
|
|
W.forceMove(get_turf(src))
|
|
|
|
src.go_out()
|
|
add_fingerprint(usr)
|
|
|
|
/obj/machinery/cryopod/proc/go_out()
|
|
if(!occupant)
|
|
return
|
|
|
|
if(occupant.client)
|
|
occupant.client.eye = src.occupant.client.mob
|
|
occupant.client.perspective = MOB_PERSPECTIVE
|
|
occupant.reset_death_timers()
|
|
|
|
occupant.forceMove(get_turf(src))
|
|
occupant = null
|
|
playsound(loc, on_exit_sound, 25)
|
|
update_icon()
|
|
|
|
/obj/machinery/cryopod/proc/set_occupant(var/mob/living/carbon/occupant)
|
|
src.occupant = occupant
|
|
occupant.forceMove(src)
|
|
occupant.stop_pulling()
|
|
if(occupant.client)
|
|
occupant.client.perspective = EYE_PERSPECTIVE
|
|
occupant.client.eye = src
|
|
time_entered = world.time
|
|
occupant.set_respawn_time()
|
|
update_icon()
|
|
|
|
/obj/machinery/cryopod/update_icon()
|
|
flick("[initial(icon_state)]-anim", src)
|
|
if(occupant)
|
|
name = "[name] ([occupant])"
|
|
if(stat & BROKEN)
|
|
icon_state = "[initial(icon_state)]-broken-closed"
|
|
if(stat & NOPOWER)
|
|
icon_state = "[initial(icon_state)]-closed"
|
|
else
|
|
icon_state = "[initial(icon_state)]-working"
|
|
return
|
|
else
|
|
name = initial(name)
|
|
if(stat & BROKEN)
|
|
icon_state = "[initial(icon_state)]-broken"
|
|
else
|
|
icon_state = initial(icon_state)
|
|
|
|
/obj/machinery/cryopod/relaymove(mob/living/user, direction)
|
|
. = ..()
|
|
|
|
go_out()
|
|
|
|
/obj/machinery/cryopod/proc/save_ipc_tag(var/mob/M)
|
|
var/choice = alert(M, "Would you like to save your tag data?", "Tag Persistence", "Yes", "No")
|
|
if(choice == "Yes")
|
|
var/mob/living/carbon/human/H = M
|
|
var/obj/item/organ/internal/machine/ipc_tag/tag = H.internal_organs_by_name[BP_IPCTAG]
|
|
if(tag)
|
|
M.client.prefs.machine_ownership_status = tag.ownership_info
|
|
M.client.prefs.machine_serial_number = tag.serial_number
|
|
M.client.prefs.citizenship = tag.citizenship_info
|
|
M.client.prefs.machine_tag_status = TRUE
|
|
else if(isnull(tag) || !tag)
|
|
M.client.prefs.machine_tag_status = FALSE
|
|
M.client.prefs.save_character()
|
|
M.client.prefs.save_preferences()
|