diff --git a/code/_globalvars/lists/mob_lists.dm b/code/_globalvars/lists/mob_lists.dm
index b4db2713228..709cc9075db 100644
--- a/code/_globalvars/lists/mob_lists.dm
+++ b/code/_globalvars/lists/mob_lists.dm
@@ -26,10 +26,9 @@ GLOBAL_LIST_EMPTY(dead_mob_list) //List of all dead mobs, including clientles
GLOBAL_LIST_EMPTY(non_respawnable_keys) //List of ckeys that are excluded from respawning for remainder of round.
GLOBAL_LIST_INIT(simple_animals, list(list(), list(), list(), list())) //One for each AI_* status define, List of all simple animals, including clientless
GLOBAL_LIST_EMPTY(bots_list) //List of all bots(beepsky, medibots,etc)
+GLOBAL_LIST_EMPTY(roundstart_observer_keys) //List of ckeys who ghosted before the game began.
+GLOBAL_LIST_EMPTY(antag_hud_users) // ckeys of (non-admin) users who have enabled ahud at some point during the game
-GLOBAL_LIST_EMPTY(med_hud_users)
-GLOBAL_LIST_EMPTY(sec_hud_users)
-GLOBAL_LIST_EMPTY(antag_hud_users)
GLOBAL_LIST_EMPTY(surgeries_list)
GLOBAL_LIST_EMPTY(hear_radio_list) //Mobs that hear the radio even if there's no client
diff --git a/code/controllers/subsystem/SSghost_spawns.dm b/code/controllers/subsystem/SSghost_spawns.dm
index 25415b6ab61..6a5c9927c8a 100644
--- a/code/controllers/subsystem/SSghost_spawns.dm
+++ b/code/controllers/subsystem/SSghost_spawns.dm
@@ -178,8 +178,10 @@ SUBSYSTEM_DEF(ghost_spawns)
if(GLOB.configuration.jobs.enable_exp_restrictions && min_hours)
if(M.client.get_exp_type_num(EXP_TYPE_LIVING) < min_hours * 60)
return
- if(check_antaghud && cannotPossess(M))
- return
+ if(check_antaghud && isobserver(M))
+ var/mob/dead/observer/O = M
+ if(!O.check_ahud_rejoin_eligibility())
+ return
return TRUE
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index 2f652cf151d..9636f79c195 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -947,7 +947,7 @@ structure_check() searches for nearby cultist structures required for the invoca
continue
if(iscultist(O) || jobban_isbanned(O, ROLE_CULTIST))
continue
- if(O.has_enabled_antagHUD || QDELETED(src) || QDELETED(O))
+ if(!HAS_TRAIT(ghost, TRAIT_RESPAWNABLE) || QDELETED(src) || QDELETED(O))
continue
if(O.mind.current && HAS_TRAIT(O.mind.current, SCRYING))
continue
diff --git a/code/modules/admin/misc_admin_procs.dm b/code/modules/admin/misc_admin_procs.dm
index c31abb1294a..c96d1b37aee 100644
--- a/code/modules/admin/misc_admin_procs.dm
+++ b/code/modules/admin/misc_admin_procs.dm
@@ -118,6 +118,8 @@ GLOBAL_VAR_INIT(nologevent, 0)
body += "Related accounts by IP: [jointext(M.client.related_accounts_ip, " - ")]
"
if(M.ckey)
+ body += "Enabled AntagHUD: [M.has_ahudded() ? "TRUE" : "false"]
"
+ body += "Roundstart observer: [M.is_roundstart_observer() ? "true" : "false"]
"
body += "Kick | "
body += "Ban | "
body += "Jobban | "
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 12de20e5eca..2fe653b8dbb 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -291,10 +291,10 @@
var/action=""
if(GLOB.configuration.general.allow_antag_hud)
+ GLOB.antag_hud_users.Cut()
for(var/mob/dead/observer/g in get_ghosts())
if(g.antagHUD)
g.antagHUD = FALSE // Disable it on those that have it enabled
- g.has_enabled_antagHUD = FALSE // We'll allow them to respawn
to_chat(g, "The Administrators have disabled AntagHUD ")
GLOB.configuration.general.allow_antag_hud = FALSE
to_chat(src, "AntagHUD usage has been disabled")
@@ -332,7 +332,7 @@
to_chat(g, "The administrator has placed restrictions on joining the round if you use AntagHUD")
to_chat(g, "Your AntagHUD has been disabled, you may choose to re-enabled it but will be under restrictions ")
g.antagHUD = FALSE
- g.has_enabled_antagHUD = FALSE
+ GLOB.antag_hud_users -= g.ckey
action = "placed restrictions"
GLOB.configuration.general.restrict_antag_hud_rejoin = TRUE
to_chat(src, "AntagHUD restrictions have been enabled")
diff --git a/code/modules/awaymissions/mob_spawn.dm b/code/modules/awaymissions/mob_spawn.dm
index 1679d57f145..83b6294addd 100644
--- a/code/modules/awaymissions/mob_spawn.dm
+++ b/code/modules/awaymissions/mob_spawn.dm
@@ -82,9 +82,14 @@
if(jobban_isbanned(user, banType) || jobban_isbanned(user, ROLE_SYNDICATE))
to_chat(user, "You are jobanned!")
return FALSE
- if(cannotPossess(user))
- to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.")
+ if(!HAS_TRAIT(user, TRAIT_RESPAWNABLE))
+ to_chat(user, "You currently do not have respawnability!")
return FALSE
+ if(isobserver(user))
+ var/mob/dead/observer/O = user
+ if(!O.check_ahud_rejoin_eligibility())
+ to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.")
+ return FALSE
if(time_check(user))
return FALSE
return TRUE
diff --git a/code/modules/mining/lavaland/loot/colossus_loot.dm b/code/modules/mining/lavaland/loot/colossus_loot.dm
index d4126deb2be..9fdd6258241 100644
--- a/code/modules/mining/lavaland/loot/colossus_loot.dm
+++ b/code/modules/mining/lavaland/loot/colossus_loot.dm
@@ -221,7 +221,7 @@
if(ready_to_deploy)
if(!istype(user)) // No revs allowed
return
- if(cannotPossess(user))
+ if(!user.check_ahud_rejoin_eligibility())
to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.")
return
var/be_helper = alert("Become a Lightgeist? (Warning, You can no longer be cloned!)",,"Yes","No")
diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm
index 3250aa20176..91200ba4e13 100644
--- a/code/modules/mob/dead/observer/observer_base.dm
+++ b/code/modules/mob/dead/observer/observer_base.dm
@@ -828,3 +828,17 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/datum/spawners_menu/menu = new /datum/spawners_menu(src)
menu.ui_interact(src)
+
+/**
+ * Check if a player has antag-hudded, and if so, if they can rejoin.
+ * Returns TRUE if the player can rejoin, and FALSE if the player is ineligible to rejoin.
+ * If allow_roundstart_observers is FALSE (TRUE by default), then any observers who were able to ahud due to joining roundstart will be excluded as well.
+ */
+/mob/dead/observer/proc/check_ahud_rejoin_eligibility(allow_roundstart_observers = TRUE)
+ if(!GLOB.configuration.general.restrict_antag_hud_rejoin || !has_ahudded())
+ return TRUE
+
+ if(is_roundstart_observer())
+ return allow_roundstart_observers
+ return FALSE
+
diff --git a/code/modules/mob/living/carbon/brain/robotic_brain.dm b/code/modules/mob/living/carbon/brain/robotic_brain.dm
index f22a6012d78..c4fb7566917 100644
--- a/code/modules/mob/living/carbon/brain/robotic_brain.dm
+++ b/code/modules/mob/living/carbon/brain/robotic_brain.dm
@@ -62,7 +62,7 @@
to_chat(O, "\A [src] has been activated. (Teleport | Sign Up)")
/obj/item/mmi/robotic_brain/proc/check_observer(mob/dead/observer/O)
- if(cannotPossess(O))
+ if(!O.check_ahud_rejoin_eligibility())
return FALSE
if(jobban_isbanned(O, "Cyborg") || jobban_isbanned(O, "nonhumandept"))
return FALSE
@@ -121,6 +121,10 @@
to_chat(brainmob, "Remember, the purpose of your existence is to serve [imprinted_master]'s every word, unless lawed or placed into a mech in the future.")
brainmob.mind.assigned_role = "Positronic Brain"
+ if(brainmob.has_ahudded())
+ log_admin("[key_name(brainmob)] has joined as a robot brain, after having toggled antag hud.")
+ message_admins("[key_name(brainmob)] has joined as a robot brain, after having toggled antag hud.")
+
visible_message("[src] chimes quietly.")
become_occupied(occupied_icon)
@@ -155,7 +159,7 @@
if(!check_observer(O))
to_chat(O, "You cannot be \a [src].")
return
- if(cannotPossess(O))
+ if(!O.check_ahud_rejoin_eligibility())
to_chat(O, "Upon using the antagHUD you forfeited the ability to join the round.")
return
if(jobban_isbanned(O, "Cyborg") || jobban_isbanned(O, "nonhumandept"))
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 06b880e98c8..a299c6fae87 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -114,6 +114,10 @@
var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger)
M.toff = TRUE
+ if(has_ahudded())
+ message_admins("[key_name(src)] has joined as a pAI, having previously enabled antag hud.")
+ log_admin("[key_name(src)] has joined as a pAI, having previously enabled antag hud.")
+
// Software modules. No these var names have nothing to do with photoshop
for(var/PS in subtypesof(/datum/pai_software))
var/datum/pai_software/PSD = new PS(src)
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
index c0edae00b92..a5a49536a63 100644
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ b/code/modules/mob/living/silicon/pai/recruit.dm
@@ -355,7 +355,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
return 0
if(!player_old_enough_antag(O.client,ROLE_PAI))
return 0
- if(cannotPossess(O))
+ if(!O.check_ahud_rejoin_eligibility())
return 0
if(!HAS_TRAIT(O, TRAIT_RESPAWNABLE))
return 0
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 250c2a5a2c8..412423c9286 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -121,7 +121,7 @@
var/joinedasobserver = FALSE
if(isobserver(src))
var/mob/dead/observer/G = src
- if(cannotPossess(G))
+ if(!G.check_ahud_rejoin_eligibility())
to_chat(usr, "Upon using the antagHUD you forfeited the ability to join the round.")
return
if(G.started_as_observer == TRUE)
diff --git a/code/modules/mob/living/silicon/robot/drone/maint_drone.dm b/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
index b5b2ca28e59..aed0e9b16b4 100644
--- a/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/maint_drone.dm
@@ -291,7 +291,7 @@
/mob/living/silicon/robot/drone/proc/request_player()
for(var/mob/dead/observer/O in GLOB.player_list)
- if(cannotPossess(O))
+ if(!O.check_ahud_rejoin_eligibility())
continue
if(jobban_isbanned(O, "nonhumandept") || jobban_isbanned(O, "Drone"))
continue
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ghost_interaction.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ghost_interaction.dm
index b7340a936e0..bbf76c1d31d 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ghost_interaction.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ghost_interaction.dm
@@ -16,8 +16,10 @@
humanize_prompt += " Role: [spider_role_summary]"
if(user.ckey in GLOB.ts_ckey_blacklist)
error_on_humanize = "You are not able to control any terror spider this round."
- else if(cannotPossess(user))
- error_on_humanize = "You have enabled antag HUD and are unable to re-enter the round."
+ else if(isobserver(user))
+ var/mob/dead/observer/O = user
+ if(!O.check_ahud_rejoin_eligibility())
+ error_on_humanize = "You have enabled antag HUD and are unable to re-enter the round."
else if(!ai_playercontrol_allowtype)
error_on_humanize = "This specific type of terror spider is not player-controllable."
else if(degenerate)
diff --git a/code/modules/mob/mob_misc_procs.dm b/code/modules/mob/mob_misc_procs.dm
index 99a2207ae6f..31bcb52bd86 100644
--- a/code/modules/mob/mob_misc_procs.dm
+++ b/code/modules/mob/mob_misc_procs.dm
@@ -137,13 +137,6 @@
return 1
-/proc/cannotPossess(A)
- var/mob/dead/observer/G = A
- if(G.has_enabled_antagHUD && GLOB.configuration.general.restrict_antag_hud_rejoin)
- return 1
- return 0
-
-
/proc/iscuffed(A)
if(iscarbon(A))
var/mob/living/carbon/C = A
@@ -808,6 +801,12 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM
/mob/proc/attempt_listen_to_deadsay()
+/mob/proc/is_roundstart_observer()
+ return (ckey in GLOB.roundstart_observer_keys)
+
+/mob/proc/has_ahudded()
+ return (ckey in GLOB.antag_hud_users)
+
/// Proc to PROPERLY set mob invisibility, huds gotta get set too!
/mob/proc/set_invisible(invis_value)
if(invis_value)
diff --git a/code/modules/mob/mob_vars.dm b/code/modules/mob/mob_vars.dm
index f66f9da307a..a4b9e8281ba 100644
--- a/code/modules/mob/mob_vars.dm
+++ b/code/modules/mob/mob_vars.dm
@@ -122,8 +122,6 @@
var/move_on_shuttle = TRUE // Can move on the shuttle.
- /// Whether antagHUD has been enabled previously.
- var/has_enabled_antagHUD = FALSE
var/antagHUD = FALSE // Whether AntagHUD is active right now
var/can_change_intents = TRUE //all mobs can change intents by default.
///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb)
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 825f2751327..ef933df7c01 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -186,8 +186,9 @@
src << browse(null, "window=playersetup")
spawning = TRUE
stop_sound_channel(CHANNEL_LOBBYMUSIC)
-
-
+ if(SSticker.current_state < GAME_STATE_PLAYING)
+ GLOB.roundstart_observer_keys |= ckey
+ to_chat(src, "As you observed before the round started, you can freely toggle antag-hud without losing respawnability.")
observer.started_as_observer = 1
close_spawn_windows()
var/obj/O = locate("landmark*Observer-Start")
diff --git a/code/modules/response_team/ert.dm b/code/modules/response_team/ert.dm
index 55e6ab2a086..47b6a68617c 100644
--- a/code/modules/response_team/ert.dm
+++ b/code/modules/response_team/ert.dm
@@ -48,12 +48,14 @@ GLOBAL_LIST_EMPTY(ert_request_messages)
to_chat(src, "This role is not yet available to you. You need to wait another [player_age_check] days.")
return FALSE
- if(cannotPossess(src))
+ return TRUE
+
+/mob/dead/observer/JoinResponseTeam()
+ . = ..()
+ if(!check_ahud_rejoin_eligibility())
to_chat(src, "Upon using the antagHUD you forfeited the ability to join the round.")
return FALSE
- return TRUE
-
/proc/trigger_armed_response_team(datum/response_team/response_team_type, commander_slots, security_slots, medical_slots, engineering_slots, janitor_slots, paranormal_slots, cyborg_slots, cyborg_security)
GLOB.response_team_members = list()
GLOB.active_team = response_team_type
diff --git a/code/modules/tgui/modules/ghost_hud_panel.dm b/code/modules/tgui/modules/ghost_hud_panel.dm
index 54a52787f8b..34494876445 100644
--- a/code/modules/tgui/modules/ghost_hud_panel.dm
+++ b/code/modules/tgui/modules/ghost_hud_panel.dm
@@ -62,14 +62,22 @@ GLOBAL_DATUM_INIT(ghost_hud_panel, /datum/ui_module/ghost_hud_panel, new)
to_chat(ghost, "You have been banned from using this feature.")
return FALSE
// Check if this is the first time they're turning on Antag HUD.
- if(!check_rights(R_ADMIN | R_MOD, FALSE) && !ghost.has_enabled_antagHUD && GLOB.configuration.general.restrict_antag_hud_rejoin)
+ if(!check_rights(R_ADMIN | R_MOD, FALSE) && !ghost.is_roundstart_observer() && GLOB.configuration.general.restrict_antag_hud_rejoin && !ghost.has_ahudded())
var/response = alert(ghost, "If you turn this on, you will not be able to take any part in the round.", "Are you sure you want to enable antag HUD?", "Yes", "No")
if(response == "No")
return FALSE
- ghost.has_enabled_antagHUD = TRUE
ghost.can_reenter_corpse = FALSE
REMOVE_TRAIT(ghost, TRAIT_RESPAWNABLE, GHOSTED)
+ log_admin("[key_name(ghost)] has enabled antaghud as an observer and forfeited respawnability.")
+ message_admins("[key_name(ghost)] has enabled antaghud as an observer and forfeited respawnability.")
+
+
+ else if(ghost.is_roundstart_observer() && !ghost.has_ahudded())
+ log_admin("[key_name(ghost)] has enabled antaghud for the first time as a roundstart observer, keeping respawnability.")
+
+ GLOB.antag_hud_users |= ghost.ckey
+
ghost.antagHUD = TRUE
for(var/datum/atom_hud/antag/H in GLOB.huds)