diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 122c0a041e7..cbd39a49a21 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -270,6 +270,9 @@ // Macro to get the current elapsed round time, rather than total world runtime #define ROUND_TIME (round_start_time ? (world.time - round_start_time) : 0) +// Macro that returns true if it's too early in a round to freely ghost out +#define TOO_EARLY_TO_GHOST (config && (ROUND_TIME < (config.round_abandon_penalty_period))) + // Used by radios to indicate that they have sent a message via something other than subspace #define RADIO_CONNECTION_FAIL 0 #define RADIO_CONNECTION_NON_SUBSPACE 1 diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 0852622f2c6..5c11daabdba 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -435,14 +435,14 @@ /proc/SecondsToTicks(var/seconds) return seconds * 10 -proc/pollCandidates(var/Question, var/be_special_type, var/antag_age_check = 0, var/poll_time = 300) +proc/pollCandidates(var/Question, var/be_special_type, var/antag_age_check = 0, var/poll_time = 300, var/ignore_respawnability = 0) var/roletext = be_special_type ? get_roletext(be_special_type) : null var/list/mob/dead/observer/candidates = list() var/time_passed = world.time if(!Question) Question = "Would you like to be a special role?" - for(var/mob/dead/observer/G in player_list) + for(var/mob/dead/observer/G in (ignore_respawnability ? player_list : respawnable_list)) if(!G.key || !G.client) continue if(be_special_type) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index b6dc1c58b8f..644740c9e20 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -22,6 +22,7 @@ var/global/list/spirits = list() //List of all the spirits, including Masks var/global/list/living_mob_list = list() //List of all alive mobs, including clientless. Excludes /mob/new_player var/global/list/dead_mob_list = list() //List of all dead mobs, including clientless. Excludes /mob/new_player var/global/list/respawnable_list = list() //List of all mobs, dead or in mindless creatures that still be respawned. +var/global/list/non_respawnable_keys = list() //List of ckeys that are excluded from respawning for remainder of round. var/global/list/simple_animal_list = list() //List of all simple animals, including clientless var/global/list/snpc_list = list() //List of all snpc's, including clientless diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 5cedd40abfc..09596d4445c 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -61,6 +61,7 @@ var/ToRban = 0 var/automute_on = 0 //enables automuting/spam prevention var/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access. + var/round_abandon_penalty_period = 30 MINUTES // Time from round start during which ghosting out is penalized var/reactionary_explosions = 0 //If we use reactionary explosions, explosions that react to walls and doors @@ -560,6 +561,9 @@ if("max_loadout_points") config.max_loadout_points = text2num(value) + if("round_abandon_penalty_period") + config.round_abandon_penalty_period = MinutesToTicks(text2num(value)) + else diary << "Unknown setting in configuration: '[name]'" diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 3732c68d623..727f4d77d8c 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -174,7 +174,9 @@ var/mob/living/occupant = null // Person waiting to be despawned. var/orient_right = null // Flips the sprite. - var/time_till_despawn = 9000 // 15 minutes-ish safe period before being despawned. + // 15 minutes-ish safe period before being despawned. + var/time_till_despawn = 9000 // This is reduced by 90% if a player manually enters cryo + var/willing_time_divisor = 10 var/time_entered = 0 // Used to keep track of the safe period. var/obj/item/device/radio/intercom/announce // @@ -382,7 +384,12 @@ announce.autosay("[occupant.real_name] [on_store_message]", "[on_store_name]") visible_message("\The [src] hums and hisses as it moves [occupant.real_name] into storage.") - // Delete the mob. + // Ghost and delete the mob. + if(!occupant.get_ghost(1)) + if(TOO_EARLY_TO_GHOST) + occupant.ghostize(0) // Players despawned too early may not re-enter the game + else + occupant.ghostize(1) qdel(occupant) occupant = null name = initial(name) @@ -404,6 +411,7 @@ var/willing = null //We don't want to allow people to be forced into despawning. var/mob/living/M = G:affecting + time_till_despawn = initial(time_till_despawn) if(!istype(M) || M.stat == DEAD) to_chat(user, "Dead people can not be put into cryo.") @@ -412,7 +420,7 @@ if(M.client) if(alert(M,"Would you like to enter long-term storage?",,"Yes","No") == "Yes") if(!M || !G || !G:affecting) return - willing = 1 + willing = willing_time_divisor else willing = 1 @@ -433,6 +441,8 @@ M.client.perspective = EYE_PERSPECTIVE M.client.eye = src + time_till_despawn = initial(time_till_despawn) / willing + else //because why the fuck would you keep going if the mob isn't in the pod to_chat(user, "You stop putting [M] into the cryopod.") return @@ -502,11 +512,12 @@ var/willing = null //We don't want to allow people to be forced into despawning. + time_till_despawn = initial(time_till_despawn) if(L.client) if(alert(L,"Would you like to enter cryosleep?",,"Yes","No") == "Yes") if(!L) return - willing = 1 + willing = willing_time_divisor else willing = 1 @@ -523,6 +534,7 @@ to_chat(user, "\The [src] is in use.") return L.loc = src + time_till_despawn = initial(time_till_despawn) / willing if(L.client) L.client.perspective = EYE_PERSPECTIVE @@ -622,6 +634,7 @@ usr.client.eye = src usr.loc = src src.occupant = usr + time_till_despawn = initial(time_till_despawn) / willing_time_divisor if(orient_right) icon_state = "[occupied_icon_state]-r" diff --git a/code/game/response_team.dm b/code/game/response_team.dm index 5f8543ca934..abda62f9420 100644 --- a/code/game/response_team.dm +++ b/code/game/response_team.dm @@ -114,13 +114,17 @@ var/ert_request_answered = 0 active_team = response_team_type send_emergency_team = 1 - var/list/ert_candidates = pollCandidates("Join the Emergency Response Team?",, responseteam_age, 600) + var/list/ert_candidates = pollCandidates("Join the Emergency Response Team?",, responseteam_age, 600, 1) if(!ert_candidates.len) active_team.cannot_send_team() send_emergency_team = 0 return var/teamsize = 0 - for(var/mob/dead/observer/M in ert_candidates) + // Respawnable players get first dibs + for(var/mob/dead/observer/M in (ert_candidates & respawnable_list)) + teamsize += M.JoinResponseTeam() + // If there's still open slots, non-respawnable players can fill them + for(var/mob/dead/observer/M in (ert_candidates - respawnable_list)) teamsize += M.JoinResponseTeam() send_emergency_team = 0 if (!teamsize) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 787bbcb5d9f..252636abcda 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1252,6 +1252,7 @@ message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] back to the Lobby.") var/mob/new_player/NP = new() + non_respawnable_keys -= M.ckey NP.ckey = M.ckey qdel(M) diff --git a/code/modules/mob/dead/observer/login.dm b/code/modules/mob/dead/observer/login.dm index 7e3d3f6c4bf..eab9faf9a7a 100644 --- a/code/modules/mob/dead/observer/login.dm +++ b/code/modules/mob/dead/observer/login.dm @@ -3,5 +3,9 @@ if(ghostimage) ghostimage.icon_state = src.icon_state updateghostimages() + + if(non_respawnable_keys[ckey]) + can_reenter_corpse = 0 + respawnable_list -= src update_interface() \ No newline at end of file diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index b15761d8c57..59c26c640bc 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -129,11 +129,15 @@ Works together with spawning an observer, noted above. /mob/proc/ghostize(var/flags = GHOST_CAN_REENTER) if(key) + if(non_respawnable_keys[ckey]) + flags &= ~GHOST_CAN_REENTER var/mob/dead/observer/ghost = new(src, flags) //Transfer safety to observer spawning proc. ghost.timeofdeath = src.timeofdeath //BS12 EDIT respawnable_list -= src if(ghost.can_reenter_corpse) respawnable_list += ghost + else + non_respawnable_keys[ckey] = 1 ghost.key = key if(!(ghost.client && ghost.client.holder) && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. ghost.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing! @@ -148,12 +152,28 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set desc = "Relinquish your life and enter the land of the dead." var/mob/M = src + var/warningmsg = null + var/obj/machinery/cryopod/P = istype(loc, /obj/machinery/cryopod) && loc - if(stat == DEAD) + if(P) + if(TOO_EARLY_TO_GHOST) + warningmsg = "It's too early in the shift to enter cryo" + // If it's not too early, we'll skip straight to ghosting out without penalty + else if(suiciding && TOO_EARLY_TO_GHOST) + warningmsg = "You have committed suicide too early in the round" + else if(stat != DEAD) + warningmsg = "You are alive" + else if(non_respawnable_keys[ckey]) + warningmsg = "You have lost your right to respawn" + + if(!warningmsg) ghostize(1) else - var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost now, you probably won't be able to rejoin the round! You can't change your mind, so choose wisely!)","Are you sure you want to ghost?","Ghost","Stay in body") - if(response != "Ghost") return //didn't want to ghost after-all + var/response + var/alertmsg = "Are you -sure- you want to ghost?\n([warningmsg]. If you ghost now, you probably won't be able to rejoin the round! You can't change your mind, so choose wisely!)" + response = alert(src, alertmsg,"Are you sure you want to ghost?","Ghost","Stay in body") + if(response != "Ghost") + return //didn't want to ghost after-all resting = 1 var/mob/dead/observer/ghost = ghostize(0) //0 parameter is so we can never re-enter our body, "Charlie, you can never come baaaack~" :3 ghost.timeofdeath = world.time // Because the living mob won't have a time of death and we want the respawn timer to work properly. @@ -162,8 +182,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp Morgue = M.loc if(Morgue) Morgue.update() - if(istype(M.loc, /obj/machinery/cryopod)) - var/obj/machinery/cryopod/P = M.loc + if(P) if(!P.control_computer) P.find_control_computer(urgent=1) if(P.control_computer) diff --git a/code/modules/mob/living/carbon/brain/posibrain.dm b/code/modules/mob/living/carbon/brain/posibrain.dm index aad88276473..ea9dffd3ca0 100644 --- a/code/modules/mob/living/carbon/brain/posibrain.dm +++ b/code/modules/mob/living/carbon/brain/posibrain.dm @@ -13,6 +13,7 @@ req_access = list(access_robotics) mecha = null//This does not appear to be used outside of reference in mecha.dm. var/silenced = 0 //if set to 1, they can't talk. + var/next_ping_at = 0 /obj/item/device/mmi/posibrain/attack_self(mob/user as mob) @@ -53,6 +54,8 @@ return 0 if(jobban_isbanned(O, "Cyborg") || jobban_isbanned(O,"nonhumandept")) return 0 + if(!O.can_reenter_corpse) + return 0 if(O.client) return 1 return 0 @@ -190,7 +193,11 @@ /obj/item/device/mmi/posibrain/attack_ghost(var/mob/dead/observer/O) if(searching) volunteer(O) - else + return + if(brainmob && brainmob.key) + return // No point pinging a posibrain with a player already inside + if(check_observer(O) && (world.time >= next_ping_at)) + next_ping_at = world.time + (20 SECONDS) var/turf/T = get_turf_or_move(src.loc) for(var/mob/M in viewers(T)) M.show_message("\blue The positronic brain pings softly.") diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm index 3a316b0140b..aeed17ef9fd 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm @@ -113,7 +113,7 @@ if(istype(src,/mob/dead/observer)) var/mob/dead/observer/G = src if(G.has_enabled_antagHUD == 1 && config.antag_hud_restricted) - to_chat(usr, "\blue Upon using the antagHUD you forfeited the ability to join the round.") + to_chat(usr, "Upon using the antagHUD you forfeited the ability to join the round.") return if(G.started_as_observer == 1) joinedasobserver = 1 @@ -133,6 +133,9 @@ to_chat(usr, "You must wait 10 minutes to respawn as a drone!") return + if(alert("Are you sure you want to respawn as a drone?", "Are you sure?", "Yes", "No") != "Yes") + return + for(var/obj/machinery/drone_fabricator/DF in world) if(DF.stat & NOPOWER || !DF.produce_drones) continue diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 9c8d1cab95d..19065faad8c 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -403,6 +403,8 @@ return 0 if(O.mind && O.mind.current && O.mind.current.stat != DEAD) return 0 + if(!O.can_reenter_corpse) + return 0 if(O.has_enabled_antagHUD == 1 && config.antag_hud_restricted) return 0 return 1 diff --git a/config/example/config.txt b/config/example/config.txt index 2f723c704f4..eb44c6d828a 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -324,3 +324,8 @@ DISABLE_SPACE_RUINS ## How many loadout points players may spend in character setup #MAX_LOADOUT_POINTS 5 + +# How many minutes players must wait, from round start, before they can ghost out +# and still qualify for re-entering the round. Defaults to 30. +# Setting this to 0 will disable the penalty period +#ROUND_ABANDON_PENALTY_PERIOD 30