diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 21f863ec406..9522134a3a6 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -877,6 +877,14 @@
///stops the bible chain from continuing. When all of the effects of the bible smacking have been moved to a signal we can kill this
#define COMSIG_END_BIBLE_CHAIN (1<<0)
+/// Admin helps
+/// From /datum/admin_help/RemoveActive().
+/// Fired when an adminhelp is made inactive either due to closing or resolving.
+#define COMSIG_ADMIN_HELP_MADE_INACTIVE "admin_help_made_inactive"
+
+/// Called when the player replies. From /client/proc/cmd_admin_pm().
+#define COMSIG_ADMIN_HELP_REPLIED "admin_help_replied"
+
///Closets
///From base of [/obj/structure/closet/proc/insert]: (atom/movable/inserted)
#define COMSIG_CLOSET_INSERT "closet_insert"
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 77b3ac86ddc..88f9536bc9a 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -183,6 +183,8 @@
///1000 is an unimportant number, it's just to normalize copied layers
#define RADIAL_CONTENT_LAYER 1000
+#define ADMIN_POPUP_LAYER 1
+
///Plane of the "splash" icon used that shows on the lobby screen. Nothing should ever be above this.
#define SPLASHSCREEN_PLANE 9999
#define SPLASHSCREEN_RENDER_TARGET "SPLASHSCREEN_PLANE"
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 583660220d5..80bade82bc2 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -125,7 +125,8 @@
/datum/config_entry/flag/no_dead_vote // dead people can't vote
-/datum/config_entry/flag/popup_admin_pm // adminPMs to non-admins show in a pop-up 'reply' window when set
+/// Gives the ability to send players a maptext popup.
+/datum/config_entry/flag/popup_admin_pm
/datum/config_entry/number/fps
default = 20
diff --git a/code/datums/components/admin_popup.dm b/code/datums/components/admin_popup.dm
new file mode 100644
index 00000000000..65b97e09b1a
--- /dev/null
+++ b/code/datums/components/admin_popup.dm
@@ -0,0 +1,125 @@
+/// Applied to clients when they receive an admin popup, alerting them to
+/// their ticket.
+/datum/component/admin_popup
+ /// The user's most active ticket. If this is resolved, closed, or replied to,
+ /// then the component will delete itself.
+ var/datum/admin_help/ticket
+
+ var/atom/movable/screen/admin_popup/admin_popup
+
+/datum/component/admin_popup/Initialize(datum/admin_help/ticket)
+ if (!istype(parent, /client))
+ return COMPONENT_INCOMPATIBLE
+
+ if (!istype(ticket))
+ return COMPONENT_INCOMPATIBLE
+
+ create_notice()
+
+ RegisterSignal(
+ ticket,
+ list(
+ COMSIG_ADMIN_HELP_MADE_INACTIVE,
+ COMSIG_ADMIN_HELP_REPLIED,
+ COMSIG_PARENT_QDELETING,
+ ),
+ .proc/delete_self,
+ )
+
+/datum/component/admin_popup/Destroy(force, silent)
+ var/client/parent_client = parent
+
+ parent_client?.screen -= admin_popup
+ QDEL_NULL(admin_popup)
+
+ if (!QDELETED(ticket))
+ UnregisterSignal(ticket, list(
+ COMSIG_ADMIN_HELP_MADE_INACTIVE,
+ COMSIG_ADMIN_HELP_REPLIED,
+ COMSIG_PARENT_QDELETING,
+ ))
+
+ ticket = null
+
+ return ..()
+
+/datum/component/admin_popup/proc/create_notice()
+ if(admin_popup)
+ qdel(admin_popup)
+ admin_popup = new
+
+ var/client/parent_client = parent
+ admin_popup.maptext_width = getviewsize(parent_client.view_size.getView())[1] * world.icon_size
+ parent_client.screen += admin_popup
+
+/datum/component/admin_popup/proc/delete_self()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/// The UI element for admin popups
+/atom/movable/screen/admin_popup
+ icon = null
+ icon_state = null
+ plane = ABOVE_HUD_PLANE
+ layer = ADMIN_POPUP_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ screen_loc = "TOP-5,LEFT"
+ maptext_height = 480
+ maptext_width = 480
+ maptext = ""
+
+ var/static/list/colors = list(
+ COLOR_RED,
+ COLOR_ORANGE,
+ COLOR_YELLOW,
+ COLOR_LIME,
+ COLOR_CYAN,
+ COLOR_PURPLE,
+ )
+
+ /// The last color chosen in the animation, sourced from the static list colors.
+ var/last_color_index = 0
+
+ /// The `world.time` when the last color update occurred.
+ var/last_update_time = 0
+
+/atom/movable/screen/admin_popup/New(loc, ...)
+ . = ..()
+
+ START_PROCESSING(SSobj, src)
+ update_text()
+
+/atom/movable/screen/admin_popup/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/atom/movable/screen/admin_popup/process(delta_time)
+ update_text()
+
+/atom/movable/screen/admin_popup/proc/update_text()
+ // Even if processing time changes, we want this to remain slow.
+ // We want to pester them into reading their ticket, not give them a seizure!
+ if (world.time - last_update_time < 2 SECONDS)
+ return
+
+ last_color_index = (last_color_index % colors.len) + 1
+
+ var/message = ""
+ message += "HEY! An admin is trying to talk to you!
Check your chat window, and click their name to respond!"
+ message += ""
+
+ maptext = MAPTEXT(message)
+ last_update_time = world.time
+
+/// Tries to give the target an admin popup.
+/// If it fails, will send the error to the passed admin.
+/proc/give_admin_popup(client/target, client/admin, message)
+ log_admin("[key_name(admin)] sent an admin popup to [key_name(target)].")
+
+ var/datum/admin_help/current_ticket = target.current_ticket
+ if (!current_ticket)
+ to_chat(admin, span_warning("[key_name(target)] had no active ahelp, aborting."))
+ return
+
+ admin.cmd_admin_pm(target, message)
+ target.AddComponent(/datum/component/admin_popup, current_ticket)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index a73208f839c..f78b4ac53c5 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1055,6 +1055,22 @@
to_chat(H, span_adminnotice("Your prayers have been answered!! You received the best [new_item.name]!"), confidential = TRUE)
SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg'))
+ else if (href_list["adminpopup"])
+ if (!check_rights(R_ADMIN))
+ return
+
+ var/message = input(owner, "As well as a popup, they'll also be sent a message to reply to. What do you want that to be?", "Message") as text|null
+ if (!message)
+ to_chat(owner, span_notice("Popup cancelled."))
+ return
+
+ var/client/target = locate(href_list["adminpopup"])
+ if (!istype(target))
+ to_chat(owner, span_notice("The mob doesn't exist anymore!"))
+ return
+
+ give_admin_popup(target, owner, message)
+
else if(href_list["adminsmite"])
if(!check_rights(R_ADMIN|R_FUN))
return
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 9c5217f33d1..803924b99c7 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -254,6 +254,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(state == AHELP_ACTIVE)
. += ClosureLinks(ref_src)
+ if (CONFIG_GET(flag/popup_admin_pm))
+ . += " (POPUP)"
+
//private
/datum/admin_help/proc/ClosureLinks(ref_src)
if(!ref_src)
@@ -345,6 +348,8 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(initiator && initiator.current_ticket == src)
initiator.current_ticket = null
+ SEND_SIGNAL(src, COMSIG_ADMIN_HELP_MADE_INACTIVE)
+
//Mark open ticket as closed/meme
/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE)
if(state != AHELP_ACTIVE)
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index aaf5c54ea14..67cee2e72eb 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -212,6 +212,8 @@
if(holder && recipient.holder && !current_ticket) //Both are admins, and this is not a reply to our own ticket.
badmin = TRUE
if(recipient.holder && !badmin)
+ SEND_SIGNAL(current_ticket, COMSIG_ADMIN_HELP_REPLIED)
+
if(holder)
to_chat(recipient,
type = MESSAGE_TYPE_ADMINPM,
@@ -243,7 +245,6 @@
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
-
else
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
var/already_logged = FALSE
@@ -277,10 +278,6 @@
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
- //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn
- if(CONFIG_GET(flag/popup_admin_pm))
- INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg)
-
else //neither are admins
if(!current_ticket)
to_chat(src,
@@ -312,16 +309,6 @@
html = span_notice("PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]") ,
confidential = TRUE)
-/client/proc/popup_admin_pm(client/recipient, msg)
- var/sender = src
- var/sendername = key
- var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply
- if(recipient && reply)
- if(sender)
- recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
- else
- adminhelp(reply) //sender has left, adminhelp instead
-
#define TGS_AHELP_USAGE "Usage: ticket "
/proc/TgsPm(target,msg,sender)
target = ckey(target)
diff --git a/config/config.txt b/config/config.txt
index b109a26bd71..3578a57fb71 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -286,9 +286,9 @@ CHECK_RANDOMIZER
## Uncomment this to forbid admins from possessing the singularity.
#FORBID_SINGULO_POSSESSION
-## Uncomment to show a popup 'reply to' window to every non-admin that receives an adminPM.
-## The intention is to make adminPMs more visible. (although I fnd popups annoying so this defaults to off)
-#POPUP_ADMIN_PM
+## Uncomment to give admins the ability to send a maptext popup to players.
+## Only fires when an admin requests it, not every ahelp.
+POPUP_ADMIN_PM
## Uncomment to allow special 'Easter-egg' events on special holidays such as seasonal holidays and stuff like 'Talk Like a Pirate Day' :3 YAARRR
ALLOW_HOLIDAYS
diff --git a/tgstation.dme b/tgstation.dme
index 47a215efa80..6eda4fa316c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -508,6 +508,7 @@
#include "code\datums\changelog\changelog.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\acid.dm"
+#include "code\datums\components\admin_popup.dm"
#include "code\datums\components\anti_magic.dm"
#include "code\datums\components\aquarium.dm"
#include "code\datums\components\area_sound_manager.dm"