Merge pull request #13772 from silicons/respawn_system_2

adds a respawn/return to lobby system, tweaks cryo to preserve everything instead of just ""important items""
This commit is contained in:
DeltaFire
2020-12-22 17:40:33 +01:00
committed by GitHub
32 changed files with 457 additions and 174 deletions

View File

@@ -40,6 +40,7 @@
//#define ROLE_MONSTERHUNTER "monster hunter" Disabled for now
#define ROLE_GHOSTCAFE "ghostcafe"
#define ROLE_MINOR_ANTAG "minorantag"
#define ROLE_RESPAWN "respawnsystem"
//Missing assignment means it's not a gamemode specific role, IT'S NOT A BUG OR ERROR.
//The gamemode specific ones are just so the gamemodes can query whether a player is old enough
//(in game days played) to play that role

View File

@@ -172,8 +172,6 @@
/datum/config_entry/string/hostedby
/datum/config_entry/flag/norespawn
/datum/config_entry/flag/guest_jobban
/datum/config_entry/flag/usewhitelist

View File

@@ -0,0 +1,47 @@
/// Allows usage of respawn system
/datum/config_entry/flag/respawns_enabled
config_entry_value = FALSE
/// Minutes before allowing respawns.
/datum/config_entry/number/respawn_delay
config_entry_value = 15.0
integer = FALSE
/// Minutes before allowing respawn, if user cryo'd.
/datum/config_entry/number/respawn_delay_cryo
config_entry_value = 5.0
integer = FALSE
/// Allows respawning as non-assistant. Overrides all others of this type.
/datum/config_entry/flag/allow_non_assistant_respawn
config_entry_value = FALSE
/// Allows respawning as a combat role, defined as security/head.
/datum/config_entry/flag/allow_combat_role_respawn
config_entry_value = FALSE
/// Allows respawning as the same character as a previous life
/datum/config_entry/flag/allow_same_character_respawn
config_entry_value = FALSE
/// Observing penalizes for respawns, not just joining.
/datum/config_entry/flag/respawn_penalty_includes_observe
config_entry_value = FALSE
/// Minutes from roundstart before someone can respawn
/datum/config_entry/number/respawn_minimum_delay_roundstart
config_entry_value = 30.0
integer = FALSE
/// Gamemode config tags that are banned from respawning
/datum/config_entry/keyed_list/respawn_chaos_gamemodes
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/respawn_chaos_gamemodes/ValidateListEntry(key_name, key_value)
. = ..()
return . && (key_name in config.modes)
/datum/config_entry/keyed_list/respawn_chaos_gamemodes/preprocess_key(key)
. = ..()
return lowertext(key)

View File

@@ -371,6 +371,11 @@ SUBSYSTEM_DEF(ticker)
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
GLOB.joined_player_list += player.ckey
player.create_character(FALSE)
if(player.new_character && player.client && player.client.prefs) // we cannot afford a runtime, ever
LAZYOR(player.client.prefs.slots_joined_as, player.client.prefs.default_slot)
LAZYOR(player.client.prefs.characters_joined_as, player.new_character.real_name)
else
stack_trace("WARNING: Either a player did not have a new_character, did not have a client, or did not have preferences. This is VERY bad.")
else
player.new_player_panel()
CHECK_TICK

View File

@@ -164,7 +164,7 @@
. = list()
.["version"] = GLOB.game_version
.["mode"] = "hidden" //CIT CHANGE - hides the gamemode in topic() calls to prevent meta'ing the gamemode
.["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE
.["respawn"] = config ? CONFIG_GET(flag/respawns_enabled) : FALSE
.["enter"] = GLOB.enter_allowed
.["vote"] = CONFIG_GET(flag/allow_vote_mode)
.["ai"] = CONFIG_GET(flag/allow_ai)

View File

@@ -22,17 +22,16 @@
//Used for logging people entering cryosleep and important items they are carrying.
var/list/frozen_crew = list()
var/list/frozen_items = list()
// Used for containing rare items traitors need to steal, so it's not
// game-over if they get iced
var/list/objective_items = list()
// A cache of theft datums so you don't have to re-create them for
// each item check
var/list/theft_cache = list()
var/list/obj/stored_packages = list()
var/allow_items = TRUE
/obj/machinery/computer/cryopod/deconstruct()
. = ..()
for(var/i in stored_packages)
var/obj/O = i
O.forceMove(drop_location())
/obj/machinery/computer/cryopod/attack_ai()
attack_hand()
@@ -67,11 +66,11 @@
if(3)
dat += "<a href='byond://?src=[REF(src)];menu=1'><< Back</a><br><br>"
dat += "<h3>Recently stored objects</h3><br/><hr/><br/>"
if(!frozen_items.len)
if(!stored_packages.len)
dat += "There has been no storage usage at this terminal.<br/>"
else
for(var/obj/item/I in frozen_items)
dat += "[I.name]<br/>"
for(var/obj/O in stored_packages)
dat += "[O.name]<br/>"
dat += "<hr/>"
var/datum/browser/popup = new(user, "cryopod_console", "Cryogenic System Control")
@@ -87,25 +86,27 @@
add_fingerprint(user)
if(href_list["item"])
if(!allowed(user))
if(!allowed(user) && !(obj_flags & EMAGGED))
to_chat(user, "<span class='warning'>Access Denied.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
updateUsrDialog()
return
if(!allow_items) return
if(frozen_items.len == 0)
if(!allow_items)
return
if(stored_packages.len == 0)
to_chat(user, "<span class='notice'>There is nothing to recover from storage.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
updateUsrDialog()
return
var/obj/item/I = input(user, "Please choose which object to retrieve.","Object recovery",null) as null|anything in frozen_items
var/obj/I = input(user, "Please choose which object to retrieve.","Object recovery",null) as null|anything in stored_packages
playsound(src, "terminal_type", 25, 0)
if(!I)
return
if(!(I in frozen_items))
if(!(I in stored_packages))
to_chat(user, "<span class='notice'>\The [I] is no longer in storage.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
updateUsrDialog()
@@ -117,20 +118,21 @@
I.forceMove(drop_location())
if(user && Adjacent(user) && user.can_hold_items())
user.put_in_hands(I)
frozen_items -= I
stored_packages -= I
updateUsrDialog()
else if(href_list["allitems"])
playsound(src, "terminal_type", 25, 0)
if(!allowed(user))
if(!allowed(user) && !(obj_flags & EMAGGED))
to_chat(user, "<span class='warning'>Access Denied.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
updateUsrDialog()
return
if(!allow_items)
return
if(frozen_items.len == 0)
if(stored_packages.len == 0)
to_chat(user, "<span class='notice'>There is nothing to recover from storage.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
@@ -138,10 +140,10 @@
visible_message("<span class='notice'>The console beeps happily as it disgorges the desired objects.</span>")
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
for(var/obj/item/I in frozen_items)
I.forceMove(drop_location())
frozen_items -= I
updateUsrDialog()
for(var/obj/O in stored_packages)
O.forceMove(get_turf(src))
stored_packages.Cut()
updateUsrDialog()
else if (href_list["menu"])
src.menu = text2num(href_list["menu"])
@@ -159,6 +161,13 @@
/obj/machinery/computer/cryopod/contents_explosion()
return
/obj/machinery/computer/cryopod/contents_explosion()
return //don't blow everyone's shit up.
/// The box
/obj/item/storage/box/blue/cryostorage_items
w_class = WEIGHT_CLASS_HUGE
//Cryopods themselves.
/obj/machinery/cryopod
name = "cryogenic freezer"
@@ -177,44 +186,9 @@
var/despawn_world_time = null // Used to keep track of the safe period.
var/obj/machinery/computer/cryopod/control_computer
var/item_storage_type = /obj/item/storage/box/blue/cryostorage_items //with how storage components work this can be anything the player can open or anything with a storage component.
var/last_no_computer_message = 0
// These items are preserved when the process() despawn proc occurs.
var/static/list/preserve_items = typecacheof(list(
/obj/item/hand_tele,
/obj/item/card/id/captains_spare,
/obj/item/aicard,
/obj/item/mmi,
/obj/item/paicard,
/obj/item/gun,
/obj/item/pinpointer,
/obj/item/clothing/shoes/magboots,
/obj/item/areaeditor/blueprints,
/obj/item/clothing/head/helmet/space,
/obj/item/clothing/suit/space,
/obj/item/clothing/suit/armor,
/obj/item/defibrillator/compact,
/obj/item/reagent_containers/hypospray/CMO,
/obj/item/clothing/accessory/medal/gold/captain,
/obj/item/clothing/gloves/krav_maga,
/obj/item/nullrod,
/obj/item/tank/jetpack,
/obj/item/documents,
/obj/item/nuke_core_container
))
// These items will NOT be preserved
var/static/list/do_not_preserve_items = typecacheof(list(
/obj/item/mmi/posibrain,
/obj/item/gun/energy/laser/mounted,
/obj/item/gun/energy/e_gun/advtaser/mounted,
/obj/item/gun/ballistic/revolver/grenadelauncher/cyborg,
/obj/item/gun/energy/disabler/cyborg,
/obj/item/gun/energy/e_gun/advtaser/cyborg,
/obj/item/gun/energy/printer,
/obj/item/gun/energy/kinetic_accelerator/cyborg,
/obj/item/gun/energy/laser/cyborg
))
/obj/machinery/cryopod/Initialize(mapload)
. = ..()
update_icon()
@@ -286,73 +260,89 @@
despawn_occupant()
#define CRYO_DESTROY 0
#define CRYO_PRESERVE 1
#define CRYO_OBJECTIVE 2
#define CRYO_IGNORE 3
#define CRYO_DESTROY_LATER 4
/obj/machinery/cryopod/proc/should_preserve_item(obj/item/I)
for(var/datum/objective_item/steal/T in control_computer.theft_cache)
if(istype(I, T.targetitem) && T.check_special_completion(I))
return CRYO_OBJECTIVE
if(preserve_items[I] && !do_not_preserve_items[I])
return CRYO_PRESERVE
return CRYO_DESTROY
// This function can not be undone; do not call this unless you are sure
/obj/machinery/cryopod/proc/despawn_occupant()
if(!control_computer)
find_control_computer()
var/mob/living/mob_occupant = occupant
var/list/obj/item/cryo_items = list()
var/list/obj/item/storing = list()
var/list/obj/item/destroying = list()
var/list/obj/item/destroy_later = list()
investigate_log("Despawning [key_name(mob_occupant)].", INVESTIGATE_CRYOGENICS)
//Handle Borg stuff first
var/atom/target_store = (control_computer?.allow_items && control_computer) || src //the double control computer check makes it return the control computer.
var/drop_to_ground = !istype(target_store, /obj/machinery/computer/cryopod)
var/mind_identity = mob_occupant.mind?.name
var/occupant_identity = mob_occupant.real_name
if(iscyborg(mob_occupant))
var/mob/living/silicon/robot/R = mob_occupant
if(R.mmi?.brain)
cryo_items[R.mmi] = CRYO_DESTROY_LATER
cryo_items[R.mmi.brain] = CRYO_DESTROY_LATER
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
cryo_items[O] = should_preserve_item(O)
O.forceMove(src)
R.module.remove_module(I, TRUE) //delete the module itself so it doesn't transfer over.
//Drop all items into the pod.
for(var/obj/item/I in mob_occupant)
if(cryo_items[I] == CRYO_IGNORE || cryo_items[I] ==CRYO_DESTROY_LATER)
continue
cryo_items[I] = should_preserve_item(I)
mob_occupant.transferItemToLoc(I, src, TRUE)
if(I.contents.len) //Make sure we catch anything not handled by qdel() on the items.
if(cryo_items[I] != CRYO_DESTROY) // Don't remove the contents of things that need preservation
destroy_later += R.mmi
destroy_later += R.mmi.brain
for(var/i in R.module)
if(!isitem(i))
destroying += i
continue
for(var/obj/item/O in I.contents)
cryo_items[O] = should_preserve_item(O)
O.forceMove(src)
for(var/A in cryo_items)
var/obj/item/I = A
if(QDELETED(I)) //edge cases and DROPDEL.
continue
var/preserve = cryo_items[I]
if(preserve == CRYO_DESTROY_LATER)
continue
if(preserve != CRYO_IGNORE)
if(preserve == CRYO_DESTROY)
qdel(I)
else if(control_computer?.allow_items)
control_computer.frozen_items += I
if(preserve == CRYO_OBJECTIVE)
control_computer.objective_items += I
I.moveToNullspace()
var/obj/item/I = i
// let's be honest we only care about the trash bag don't beat around the bush
if(SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE))
storing += I.contents
for(var/atom/movable/AM in I.contents)
AM.forceMove(src)
R.module.remove_module(I, TRUE)
else
var/list/gear = list()
if(iscarbon(mob_occupant)) // sorry simp-le-mobs deserve no mercy
var/mob/living/carbon/C = mob_occupant
gear = C.get_all_gear()
for(var/i in gear)
var/obj/item/I = i
I.forceMove(src)
if(!istype(I))
destroying += I
continue
if(I.item_flags & (DROPDEL | ABSTRACT))
destroying += I
continue
if(HAS_TRAIT(I, TRAIT_NODROP))
destroying += I
continue
// WEE WOO SNOWFLAKE TIME
if(istype(I, /obj/item/pda))
var/obj/item/pda/P = I
if((P.owner == mind_identity) || (P.owner == occupant_identity))
destroying += P
else
storing += P
else if(istype(I, /obj/item/card/id))
var/obj/item/card/id/idcard = I
if((idcard.registered_name == mind_identity) || (idcard.registered_name == occupant_identity))
destroying += idcard
else
storing += idcard
else
I.forceMove(loc)
cryo_items -= I
storing += I
// get rid of mobs
for(var/mob/living/L in mob_occupant.GetAllContents() - mob_occupant)
L.forceMove(drop_location())
if(storing.len)
var/obj/O = new item_storage_type
O.name = "cryogenic retrieval package: [mob_occupant.real_name]"
for(var/i in storing)
var/obj/item/I = i
I.forceMove(O)
O.forceMove(drop_to_ground? target_store.drop_location() : target_store)
if((target_store == control_computer) && !drop_to_ground)
control_computer.stored_packages += O
QDEL_LIST(destroying)
//Update any existing objectives involving this mob.
for(var/i in GLOB.objectives)
@@ -414,22 +404,13 @@
// Ghost and delete the mob.
if(!mob_occupant.get_ghost(1))
mob_occupant.ghostize(FALSE, penalize = TRUE, voluntary = TRUE)
mob_occupant.ghostize(FALSE, penalize = TRUE, voluntary = TRUE, cryo = TRUE)
QDEL_NULL(occupant)
for(var/I in cryo_items) //only "CRYO_DESTROY_LATER" atoms are left)
var/atom/A = I
if(!QDELETED(A))
qdel(A)
QDEL_LIST(destroy_later)
open_machine()
name = initial(name)
#undef CRYO_DESTROY
#undef CRYO_PRESERVE
#undef CRYO_OBJECTIVE
#undef CRYO_IGNORE
#undef CRYO_DESTROY_LATER
/obj/machinery/cryopod/MouseDrop_T(mob/living/target, mob/user)
if(!istype(target) || user.incapacitated() || !target.Adjacent(user) || !Adjacent(user) || !ismob(target) || (!ishuman(user) && !iscyborg(user)) || !istype(user.loc, /turf) || target.buckled)
return

View File

@@ -679,8 +679,8 @@
set category = "Server"
set desc="Respawn basically"
set name="Toggle Respawn"
var/new_nores = !CONFIG_GET(flag/norespawn)
CONFIG_SET(flag/norespawn, new_nores)
var/new_nores = CONFIG_GET(flag/respawns_enabled)
CONFIG_SET(flag/respawns_enabled, !new_nores)
if (!new_nores)
to_chat(world, "<B>You may now respawn.</B>", confidential = TRUE)
else

View File

@@ -78,8 +78,11 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/mark_datum_mapview,
/client/proc/hide_verbs, /*hides all our adminverbs*/
/client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/
/datum/admins/proc/open_borgopanel
/datum/admins/proc/open_borgopanel,
/client/proc/admin_cmd_respawn_return_to_lobby,
/client/proc/admin_cmd_remove_ghost_respawn_timer
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_ban)
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound))

View File

@@ -936,6 +936,12 @@
else
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=[ROLE_MIND_TRANSFER];jobban4=[REF(M)]'>Mind Transfer Potion</a></td>"
//Respawns
if(jobban_isbanned(M, ROLE_RESPAWN))
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=[ROLE_RESPAWN];jobban4=[REF(M)]'><font color=red>Respawns</font></a></td>"
else
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=[ROLE_RESPAWN];jobban4=[REF(M)]'>Respawns</a></td>"
dat += "</tr></table>"
usr << browse(dat, "window=jobban2;size=800x450")
return
@@ -1799,12 +1805,15 @@
if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes")
return
log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.")
message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby.")
log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby, removing their respawn restrictions if they existed.")
message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby, removing their respawn restrictions if they existed.")
var/mob/dead/new_player/NP = new()
NP.ckey = M.ckey
qdel(M)
if(GLOB.preferences_datums[NP.ckey])
var/datum/preferences/P = GLOB.preferences_datums[NP.ckey]
P.respawn_restrictions_active = FALSE
else if(href_list["tdome1"])
if(!check_rights(R_FUN))

View File

@@ -12,11 +12,29 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
var/max_save_slots = 24
//non-preference stuff
var/muted = 0
// Intra-round persistence begin
/// Flags for admin mutes
var/muted = NONE
/// Last IP the person was seen on
var/last_ip
/// Last CID the person was seen on
var/last_id
/// Do we log their clicks to disk?
var/log_clicks = FALSE
/// Characters they have joined the round under - Lazylist of names
var/list/characters_joined_as
/// Slots they have joined the round under - Lazylist of numbers
var/list/slots_joined_as
/// Are we currently subject to respawn restrictions? Usually set by us using the "respawn" verb, but can be lifted by admins.
var/respawn_restrictions_active = FALSE
/// time of death we consider for respawns
var/respawn_time_of_death = -INFINITY
/// did they DNR? used to prevent respawns.
var/dnr_triggered = FALSE
/// did they cryo on their last ghost?
var/respawn_did_cryo = FALSE
// Intra-round persistence end
var/icon/custom_holoform_icon
var/list/cached_holoform_icons

View File

@@ -73,6 +73,12 @@
/// Starting skill modifiers.
var/list/starting_modifiers
// These can be flags but I don't care because they're never changed
/// Can you always join as this job even while respawning (should probably only be on for assistant)
var/always_can_respawn_as = FALSE
/// Is this job considered a combat role for respawning? (usually sec/command)
var/considered_combat_role = FALSE
/**
* Checks if we should be created on a certain map
*/
@@ -118,6 +124,12 @@
//Used for a special check of whether to allow a client to latejoin as this job.
/datum/job/proc/special_check_latejoin(client/C)
var/joined = LAZYLEN(C.prefs?.characters_joined_as)
if(C.prefs?.respawn_restrictions_active && (joined || CONFIG_GET(flag/respawn_penalty_includes_observe)))
if(!CONFIG_GET(flag/allow_non_assistant_respawn) && always_can_respawn_as)
return FALSE
if(!CONFIG_GET(flag/allow_combat_role_respawn) && considered_combat_role)
return FALSE
return TRUE
/datum/job/proc/GetAntagRep()

View File

@@ -16,6 +16,7 @@
display_order = JOB_DISPLAY_ORDER_AI
var/do_special_check = TRUE
threat = 5
considered_combat_role = TRUE
starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)

View File

@@ -18,6 +18,7 @@ Assistant
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_ASSISTANT
dresscodecompliant = FALSE
always_can_respawn_as = TRUE
threat = 0.2
/datum/job/assistant/get_access()

View File

@@ -14,6 +14,7 @@
exp_requirements = 180
exp_type = EXP_TYPE_COMMAND
exp_type_department = EXP_TYPE_COMMAND
considered_combat_role = TRUE
outfit = /datum/outfit/job/captain

View File

@@ -15,6 +15,7 @@
exp_requirements = 180
exp_type = EXP_TYPE_CREW
exp_type_department = EXP_TYPE_ENGINEERING
considered_combat_role = TRUE
outfit = /datum/outfit/job/ce
plasma_outfit = /datum/outfit/plasmaman/ce

View File

@@ -15,6 +15,7 @@
exp_requirements = 180
exp_type = EXP_TYPE_CREW
exp_type_department = EXP_TYPE_MEDICAL
considered_combat_role = TRUE
outfit = /datum/outfit/job/cmo
plasma_outfit = /datum/outfit/plasmaman/cmo

View File

@@ -11,6 +11,7 @@
minimal_player_age = 21
exp_requirements = 120
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
starting_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)

View File

@@ -15,6 +15,7 @@
outfit = /datum/outfit/job/detective
plasma_outfit = /datum/outfit/plasmaman/detective
considered_combat_role = TRUE
access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)

View File

@@ -15,7 +15,7 @@
exp_requirements = 180
exp_type = EXP_TYPE_CREW
exp_type_department = EXP_TYPE_SERVICE
considered_combat_role = TRUE
outfit = /datum/outfit/job/hop
plasma_outfit = /datum/outfit/plasmaman/hop

View File

@@ -14,6 +14,7 @@
minimal_player_age = 10
exp_requirements = 300
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
exp_type_department = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/hos

View File

@@ -15,6 +15,7 @@
exp_requirements = 180
exp_type = EXP_TYPE_CREW
exp_type_department = EXP_TYPE_SUPPLY
considered_combat_role = TRUE
outfit = /datum/outfit/job/quartermaster

View File

@@ -15,6 +15,7 @@
exp_type_department = EXP_TYPE_SCIENCE
exp_requirements = 180
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
outfit = /datum/outfit/job/rd
plasma_outfit = /datum/outfit/plasmaman/rd

View File

@@ -12,6 +12,7 @@
minimal_player_age = 7
exp_requirements = 300
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
outfit = /datum/outfit/job/security
plasma_outfit = /datum/outfit/plasmaman/security

View File

@@ -12,6 +12,7 @@
minimal_player_age = 7
exp_requirements = 300
exp_type = EXP_TYPE_CREW
considered_combat_role = TRUE
outfit = /datum/outfit/job/warden
plasma_outfit = /datum/outfit/plasmaman/warden

View File

@@ -375,7 +375,9 @@
ready = PLAYER_NOT_READY
return FALSE
var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
var/mintime = max(CONFIG_GET(number/respawn_delay), (SSticker.round_start_time + (CONFIG_GET(number/respawn_minimum_delay_roundstart) * 600)) - world.time, 0)
var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to respawn for [round(mintime / 600, 0.1)] minutes!!","Player Setup","Yes","No")
if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
ready = PLAYER_NOT_READY
@@ -397,6 +399,7 @@
stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised")
transfer_ckey(observer, FALSE)
observer.client = client
observer.client.prefs?.respawn_time_of_death = world.time
observer.set_ghost_appearance()
if(observer.client && observer.client.prefs)
observer.real_name = observer.client.prefs.real_name
@@ -463,6 +466,9 @@
alert(src, "An administrator has disabled late join spawning.")
return FALSE
if(!respawn_latejoin_check(notify = TRUE))
return FALSE
var/arrivals_docked = TRUE
if(SSshuttle.arrivals)
close_spawn_windows() //In case we get held up
@@ -526,6 +532,8 @@
GLOB.joined_player_list += character.ckey
GLOB.latejoiners += character
LAZYOR(character.client.prefs.slots_joined_as, character.client.prefs.default_slot)
LAZYOR(character.client.prefs.characters_joined_as, character.real_name)
if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais.
if(SSshuttle.emergency)

View File

@@ -264,7 +264,7 @@ Transfer_mind is there to check if mob is being deleted/not going to have a body
Works together with spawning an observer, noted above.
*/
/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE, voluntary = FALSE)
/mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE, voluntary = FALSE, cryo = FALSE)
var/sig_flags = SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special, penalize)
penalize = !(sig_flags & COMPONENT_DO_NOT_PENALIZE_GHOSTING) && (suiciding || penalize) // suicide squad.
voluntary_ghosted = voluntary
@@ -277,6 +277,12 @@ Works together with spawning an observer, noted above.
if (client && client.prefs && client.prefs.auto_ooc)
if (!(client.prefs.chat_toggles & CHAT_OOC))
client.prefs.chat_toggles ^= CHAT_OOC
if(ckey && penalize)
var/datum/preferences/P = GLOB.preferences_datums[ckey]
if(P)
P.respawn_restrictions_active = TRUE
P.respawn_time_of_death = world.time
P.respawn_did_cryo = cryo
transfer_ckey(ghost, FALSE)
ghost.client.init_verbs()
if(penalize)
@@ -423,12 +429,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(usr, "<span class='warning'>You're already stuck out of your body!</span>")
return FALSE
var/response = alert(src, "Are you sure you want to prevent (almost) all means of resuscitation? This cannot be undone. ","Are you sure you want to stay dead?","Yes","No")
var/response = alert(src, "Are you sure you want to prevent (almost) all means of resuscitation? This cannot be undone. THIS WILL ALSO STOP YOU FROM RESPAWNING!!!","Are you sure you want to stay dead and never respawn?","Yes","No")
if(response != "Yes")
return
can_reenter_corpse = FALSE
to_chat(src, "You can no longer be brought back into your body.")
client.prefs?.dnr_triggered = TRUE
to_chat(src, "You can no longer be brought back into your body or respawn.")
return TRUE
/mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source, flashwindow = TRUE)

View File

@@ -0,0 +1,181 @@
// ADMIN VERBS BEGIN
/**
* Fully returns a player to lobby, allowing them to bypass all respawn restrictions
* Works on ghosts or new players (lobby players)
* If a lobby player is selected, their restrictions are removed.
*/
/client/proc/admin_cmd_respawn_return_to_lobby()
set name = "Respawn Player (Unrestricted)"
set desc = "Gives a player an unrestricted respawn, resetting all respawn restrictions for them."
set category = "Admin"
var/list/mob/valid = list()
for(var/mob/dead/observer/I in GLOB.dead_mob_list)
if(!I.client)
continue
valid["[I.ckey] - Observing: [I]"] = I.ckey
for(var/mob/dead/new_player/I in GLOB.dead_mob_list)
if(!I.client || !I.client.prefs.respawn_restrictions_active)
continue
valid["[I.ckey] - IN LOBBY"] = I.ckey
if(!valid.len)
to_chat(src, "<span class='warning'>No player found that is either a ghost or is in lobby with restrictions active.</span>")
return
var/ckey = valid[input(src, "Choose a player (only showing logged in players who have restrictions)", "Unrestricted Respawn") as null|anything in valid]
var/client/player = GLOB.directory[ckey]
if(!player)
to_chat(src, "<span class='warning'>Client not found.</span>")
return
var/mob/M = player.mob
if(istype(M, /mob/dead/observer))
var/mob/dead/observer/O = M
var/confirm = alert(src, "Send [O]([ckey]) back to the lobby without respawn restrictions?", "Send to Lobby", "Yes", "No")
if(confirm != "Yes")
return
message_admins("[key_name_admin(src)] gave [key_name_admin(O)] a full respawn and sent them back to the lobby.")
log_admin("[key_name(src)] gave [key_name(O)] a full respawn and sent them back to the lobby.")
to_chat(O, "<span class='userdanger'>You have been given a full respawn.</span>")
O.do_respawn(FALSE)
O.client.prefs.dnr_triggered = FALSE
else if(istype(M, /mob/dead/new_player))
var/mob/dead/new_player/NP = M
var/confirm = alert(src, "Remove [NP]'s respawn restrictions?", "Remove Restrictions", "Yes", "No")
if(confirm != "Yes")
return
message_admins("[key_name_admin(src)] removed [ckey]'s respawn restrictions.")
log_admin("[key_name(src)] removed [ckey]'s respawn restrictions")
NP.client.prefs.respawn_restrictions_active = FALSE
NP.client.prefs.dnr_triggered = FALSE
to_chat(NP, "<span class='boldnotie'>Your respawn restrictions have been removed.")
else
CRASH("Invalid mobtype")
/**
* Allows a ghost to bypass respawn delay without lifting respawn restrictions
*/
/client/proc/admin_cmd_remove_ghost_respawn_timer()
set name = "Remove Respawn Timer for Player"
set desc = "Removes a player's respawn timer without removing their respawning restrictions."
set category = "Admin"
var/list/mob/dead/observer/valid = list()
for(var/mob/dead/observer/I in GLOB.dead_mob_list)
if(!I.client)
continue
valid["[I.ckey] - [I.name]"] = I
if(!valid.len)
to_chat(src, "<span class='warning'>No logged in ghosts found.</span>")
return
var/mob/dead/observer/O = valid[input(src, "Choose a player (only showing logged in)", "Remove Respawn Timer") as null|anything in valid]
if(!O.client)
to_chat(src, "<span class='warning'>[O] has no client.</span>")
return
var/timeleft = O.time_left_to_respawn()
if(!timeleft)
to_chat(src, "<span class='warning'>[O] can already respawn.")
return
message_admins("[key_name_admin(src)] removed [key_name_admin(O)]'s respawn timer.")
log_admin("[key_name(src)] removed [key_name(O)]'s respawn timer.")
O.client.prefs.respawn_time_of_death = -INFINITY
to_chat(O, "<span class='boldnotice'>Your respawn timer has been removed.")
// ADMIN VERBS END
/**
* Checks if we can latejoin on the currently selected slot, taking into account respawn status.
*/
/mob/dead/new_player/proc/respawn_latejoin_check(notify = FALSE)
if(!client.prefs.respawn_restrictions_active)
return TRUE
var/can_same_person = CONFIG_GET(flag/allow_same_character_respawn)
if(can_same_person)
return TRUE
var/nameless = client.prefs.nameless
var/randomname = client.prefs.be_random_name
var/randombody = client.prefs.be_random_body
if(randombody && (nameless || randomname))
return TRUE // somewhat unrecognizable
if(client.prefs.slots_joined_as && (client.prefs.default_slot in client.prefs.slots_joined_as))
if(notify)
to_chat(src, "<span class='userdanger'>You cannot respawn on the same slot. Joined slots: [english_list(client.prefs.slots_joined_as)].")
return FALSE
if((!nameless && !randomname) && (client.prefs.characters_joined_as && (client.prefs.real_name in client.prefs.characters_joined_as)))
if(notify)
to_chat(src, "<span class='userdanger'>You cannot respawn on the same character. Joined slots: [english_list(client.prefs.characters_joined_as)].")
return FALSE
return TRUE
/**
* Attempts to respawn.
*/
/mob/dead/observer/verb/respawn()
set name = "Respawn"
set category = "OOC"
if(!CONFIG_GET(flag/respawns_enabled))
to_chat(src, "<span class='warning'>Respawns are disabled in configuration.</span>")
return
if(client.prefs.dnr_triggered)
to_chat(src, "<span class='danger'>You cannot respawn as you have enabled DNR.</span>")
return
var/roundstart_timeleft = (SSticker.round_start_time + (CONFIG_GET(number/respawn_minimum_delay_roundstart) * 600)) - world.time
if(roundstart_timeleft > 0)
to_chat(src, "<span class='warning'>It's been too short of a time since the round started! Please wait [CEILING(roundstart_timeleft / 600, 0.1)] more minutes.</span>")
return
var/list/banned_modes = CONFIG_GET(keyed_list/respawn_chaos_gamemodes)
if(SSticker.mode && banned_modes[lowertext(SSticker.mode.config_tag)])
to_chat(src, "<span class='warning'>The current mode tag, [SSticker.mode.config_tag], is not eligible for respawn.</span>")
return
var/timeleft = time_left_to_respawn()
if(timeleft)
to_chat(src, "<span class='warning'>It's been too short of a time since you died/observed! Please wait [round(timeleft / 600, 0.1)] more minutes.</span>")
return
do_respawn(TRUE)
/**
* Gets time left until we can respawn. Returns 0 if we can respawn now.
*/
/mob/dead/observer/verb/time_left_to_respawn()
ASSERT(client)
return max(0, ((client.prefs.respawn_did_cryo? CONFIG_GET(number/respawn_delay_cryo) : CONFIG_GET(number/respawn_delay)) MINUTES + client.prefs.respawn_time_of_death) - world.time)
/**
* Handles respawning
*/
/mob/dead/observer/proc/do_respawn(penalize)
if(!client)
return
if(isnull(penalize))
penalize = client.prefs.respawn_restrictions_active
client.prefs.respawn_restrictions_active = penalize
to_chat(src, "<span class='userdanger'>You have been respawned to the lobby. \
Remember to take heed of rules regarding round knowledge - notably, that ALL past lives are forgotten. \
Any character you join as has NO knowledge of round events unless specified otherwise by an admin.</span>")
message_admins("[key_name_admin(src)] was respawned to lobby [penalize? "with" : "without"] restrictions.")
log_game("[key_name(src)] was respawned to lobby [penalize? "with" : "without"] restrictions.")
transfer_to_lobby()
/**
* Actual proc that removes us and puts us back on lobby
*/
/mob/dead/observer/proc/transfer_to_lobby()
if(!client) // if no one's in us we can just be deleted
qdel(src)
return
client.screen.Cut()
client.view_size.resetToDefault()
client.generate_clickcatcher()
client.apply_clickcatcher()
client.view_size.setDefault(getScreenSize(client.prefs.widescreenpref))
client.view_size.resetToDefault()
var/mob/dead/new_player/M = new /mob/dead/new_player
M.ckey = ckey

View File

@@ -66,6 +66,10 @@
GLOB.alive_mob_list -= src
if(!gibbed)
GLOB.dead_mob_list += src
if(ckey)
var/datum/preferences/P = GLOB.preferences_datums[ckey]
if(P)
P.respawn_time_of_death = world.time
set_drugginess(0)
set_disgust(0)
SetSleeping(0, 0)

View File

@@ -457,39 +457,6 @@
else
to_chat(src, "You don't have a mind datum for some reason, so you can't add a note to it.")
/mob/verb/abandon_mob()
set name = "Respawn"
set category = "OOC"
if (CONFIG_GET(flag/norespawn))
return
if ((stat != DEAD || !( SSticker )))
to_chat(usr, "<span class='boldnotice'>You must be dead to use this!</span>")
return
log_game("[key_name(usr)] used abandon mob.")
to_chat(usr, "<span class='boldnotice'>Please roleplay correctly!</span>")
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
return
client.screen.Cut()
client.screen += client.void
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
return
var/mob/dead/new_player/M = new /mob/dead/new_player()
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
qdel(M)
return
M.key = key
// M.Login() //wat
return
/mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE)
if(!new_mob || (!ckey && new_mob.ckey))
CRASH("transfer_ckey() called [new_mob ? "on ckey-less mob with a player mob as target" : "without a valid mob target"]!")

View File

@@ -11,6 +11,7 @@ $include dynamic_config.txt
$include plushies/defines.txt
$include job_threats.txt
$include policy.txt
$include respawns.txt
# You can use the @ character at the beginning of a config option to lock it from being edited in-game
# Example usage:
@@ -206,9 +207,6 @@ VOTE_AUTOTRANSFER_MAXIMUM 4
## players' votes default to "No vote" (otherwise, default to "No change")
# DEFAULT_NO_VOTE
## disable abandon mob
NORESPAWN
## disables calling del(src) on newmobs if they logout before spawnin in
# DONT_DEL_NEWMOB

29
config/respawns.txt Normal file
View File

@@ -0,0 +1,29 @@
## Allow usage of the respawn system
RESPAWNS_ENABLED
## Minutes delay before allowing respawns, either from death or observing. Not an integer.
RESPAWN_DELAY 15.0
## Minutes delay before allowing respawns, if the user cryo'd. Not an integer.
RESPAWN_DELAY_CRYO 5.0
## Allow respawning as anything but an assistant.
ALLOW_NON_ASSISTANT_RESPAWN
## Allow respawning as security and command. Only works if ALLOW_NON_ASSISTANT_RESPAWN is on.
# ALLOW_COMBAT_ROLE_RESPAWN
## Allow respawning as the same character
# ALLOW_SAME_CHARACTER_RESPAWN
## Observing is considered a respawn for the purposes of role lockouts. Defaults to disabled. When disabled, only RESPAWNING rather than returning from observe locks you out.
# RESPAWN_PENALTY_INCLUDES_OBSERVE
## Time in minutes from round start before respawn is enabled
RESPAWN_MINIMUM_DELAY_ROUNDSTART 30.0
## Gamemode (config tags!) banlist for respawn
RESPAWN_CHAOS_GAMEMODES WIZARD
RESPAWN_CHAOS_GAMEMODES NUCLEAR
RESPAWN_CHAOS_GAMEMODES CLONWOPS
RESPAWN_CHOAS_GAMEMODES REVOLUTION

View File

@@ -295,6 +295,7 @@
#include "code\controllers\configuration\entries\plushies.dm"
#include "code\controllers\configuration\entries\policy.dm"
#include "code\controllers\configuration\entries\resources.dm"
#include "code\controllers\configuration\entries\respawns.dm"
#include "code\controllers\configuration\entries\stamina_combat.dm"
#include "code\controllers\subsystem\acid.dm"
#include "code\controllers\subsystem\adjacent_air.dm"
@@ -2454,6 +2455,7 @@
#include "code\modules\mob\dead\observer\observer.dm"
#include "code\modules\mob\dead\observer\observer_movement.dm"
#include "code\modules\mob\dead\observer\orbit.dm"
#include "code\modules\mob\dead\observer\respawn.dm"
#include "code\modules\mob\dead\observer\say.dm"
#include "code\modules\mob\living\blood.dm"
#include "code\modules\mob\living\bloodcrawl.dm"