diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 4e333dac06..3778d0bba2 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -57,6 +57,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_SKYBOX 30
#define INIT_ORDER_MAPPING 25
#define INIT_ORDER_DECALS 20
+#define INIT_ORDER_PING 18
#define INIT_ORDER_JOB 17
#define INIT_ORDER_ALARM 16 // Must initialize before atoms.
#define INIT_ORDER_ATOMS 15
@@ -89,6 +90,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_NIGHTSHIFT 5
#define FIRE_PRIORITY_ORBIT 8
#define FIRE_PRIORITY_VOTE 9
+#define FIRE_PRIORITY_PING 9
#define FIRE_PRIORITY_AI 10
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_ALARM 20
diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm
new file mode 100644
index 0000000000..4078028418
--- /dev/null
+++ b/code/controllers/subsystems/ping.dm
@@ -0,0 +1,30 @@
+#define PING_BUFFER_TIME 25
+
+SUBSYSTEM_DEF(ping)
+ name = "Ping"
+ wait = 6
+ flags = SS_NO_INIT|SS_POST_FIRE_TIMING
+ priority = FIRE_PRIORITY_PING
+ init_order = INIT_ORDER_PING
+ runlevels = RUNLEVEL_LOBBY|RUNLEVEL_SETUP|RUNLEVEL_GAME|RUNLEVEL_POSTGAME
+ var/list/currentrun
+
+/datum/controller/subsystem/ping/fire(resumed = FALSE)
+ if (!resumed)
+ src.currentrun = GLOB.clients.Copy()
+
+ var/list/currentrun = src.currentrun
+ while (length(currentrun))
+ var/client/C = currentrun[currentrun.len]
+ currentrun.len--
+ if (!C || world.time - C.connection_time < PING_BUFFER_TIME)
+ if (MC_TICK_CHECK)
+ return
+ continue
+ winset(C, null, "command=.update_ping+[world.time+world.tick_lag*world.tick_usage/100]")
+ if (MC_TICK_CHECK) //one day, when ss13 has 1000 people per server, you guys are gonna be glad I added this tick check
+ return
+
+ currentrun = null
+
+#undef PING_BUFFER_TIME
\ No newline at end of file
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 2c0d6ca7ac..a74918c1a7 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -73,3 +73,6 @@
var/connection_realtime
///world.timeofday they connected
var/connection_timeofday
+
+ var/lastping = 0
+ var/avgping = 0
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 60b635932c..8c7d36a3bf 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -490,4 +490,4 @@ client/verb/character_setup()
//Went fine
else
ip_reputation = score
- return TRUE
+ return TRUE
\ No newline at end of file
diff --git a/code/game/verbs/advanced_who.dm b/code/modules/client/verbs/advanced_who.dm
similarity index 95%
rename from code/game/verbs/advanced_who.dm
rename to code/modules/client/verbs/advanced_who.dm
index 4498136b2b..d618405c1f 100644
--- a/code/game/verbs/advanced_who.dm
+++ b/code/modules/client/verbs/advanced_who.dm
@@ -66,6 +66,8 @@
entry += "[round(seconds / 60)] minutes, "
entry += "[seconds % 60] seconds)"
+ entry += "
"
+ entry += " ([round(C.avgping, 1)]ms)"
entry += " | "
entry += " (?)"
entry += " | "
@@ -85,6 +87,8 @@
entry += " - In Lobby
"
else
entry += " - Playing
"
+
+ entry += " ([round(C.avgping, 1)]ms)"
Lines += entry
msg += ""
diff --git a/code/game/verbs/ignore.dm b/code/modules/client/verbs/ignore.dm
similarity index 100%
rename from code/game/verbs/ignore.dm
rename to code/modules/client/verbs/ignore.dm
diff --git a/code/game/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
similarity index 97%
rename from code/game/verbs/ooc.dm
rename to code/modules/client/verbs/ooc.dm
index d7b649505d..e51c3423d8 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -1,186 +1,186 @@
-
-/client/verb/ooc(msg as text)
- set name = "OOC"
- set category = "OOC"
-
- if(say_disabled) //This is here to try to identify lag problems
- to_chat(usr, "Speech is currently admin-disabled.")
- return
-
- if(!mob) return
- if(IsGuestKey(key))
- to_chat(src, "Guests may not use OOC.")
- return
-
- msg = sanitize(msg)
- if(!msg) return
-
- if(!is_preference_enabled(/datum/client_preference/show_ooc))
- to_chat(src, "You have OOC muted.")
- return
-
- if(!holder)
- if(!config.ooc_allowed)
- to_chat(src, "OOC is globally muted.")
- return
- if(!config.dooc_allowed && (mob.stat == DEAD))
- to_chat(usr, "OOC for dead mobs has been turned off.")
- return
- if(prefs.muted & MUTE_OOC)
- to_chat(src, "You cannot use OOC (muted).")
- return
- if(findtext(msg, "byond://") && !config.allow_byond_links)
- to_chat(src, "Advertising other servers is not allowed.")
- log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
- return
- if(findtext(msg, "discord.gg") && !config.allow_discord_links)
- to_chat(src, "Advertising discords is not allowed.")
- log_admin("[key_name(src)] has attempted to advertise a discord server in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to advertise a discord server in OOC: [msg]")
- return
- if((findtext(msg, "http://") || findtext(msg, "https://")) && !config.allow_url_links)
- to_chat(src, "Posting external links is not allowed.")
- log_admin("[key_name(src)] has attempted to post a link in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
- return
-
- log_ooc(msg, src)
-
- if(msg)
- handle_spam_prevention(MUTE_OOC)
-
- var/ooc_style = "everyone"
- if(holder && !holder.fakekey)
- ooc_style = "elevated"
- if(holder.rights & R_ADMIN)
- ooc_style = "admin"
- else if(holder.rights & R_EVENT)
- ooc_style = "event_manager"
- else if(holder.rights & R_DEBUG)
- ooc_style = "developer"
- else if(holder.rights & R_MOD)
- ooc_style = "moderator"
-
-
- for(var/client/target in GLOB.clients)
- if(target.is_preference_enabled(/datum/client_preference/show_ooc))
- if(target.is_key_ignored(key)) // If we're ignored by this person, then do nothing.
- continue
- var/display_name = src.key
- if(holder)
- if(holder.fakekey)
- if(target.holder)
- display_name = "[holder.fakekey]/([src.key])"
- else
- display_name = holder.fakekey
- if(holder && !holder.fakekey && (holder.rights & R_ADMIN|R_FUN|R_EVENT) && config.allow_admin_ooccolor && (src.prefs.ooccolor != initial(src.prefs.ooccolor))) // keeping this for the badmins
- to_chat(target, "" + create_text_tag("ooc", "OOC:", target) + " [display_name]: [msg]")
- else
- to_chat(target, "" + create_text_tag("ooc", "OOC:", target) + " [display_name]: [msg]")
-
-/client/verb/looc(msg as text)
- set name = "LOOC"
- set desc = "Local OOC, seen only by those in view."
- set category = "OOC"
-
- if(say_disabled) //This is here to try to identify lag problems
- to_chat(usr, "Speech is currently admin-disabled.")
- return
-
- if(!mob)
- return
-
- if(IsGuestKey(key))
- to_chat(src, "Guests may not use OOC.")
- return
-
- msg = sanitize(msg)
- if(!msg)
- return
-
- if(!is_preference_enabled(/datum/client_preference/show_looc))
- to_chat(src, "You have LOOC muted.")
- return
-
- if(!holder)
- if(!config.looc_allowed)
- to_chat(src, "LOOC is globally muted.")
- return
- if(!config.dooc_allowed && (mob.stat == DEAD))
- to_chat(usr, "OOC for dead mobs has been turned off.")
- return
- if(prefs.muted & MUTE_OOC)
- to_chat(src, "You cannot use OOC (muted).")
- return
- if(findtext(msg, "byond://") && !config.allow_byond_links)
- to_chat(src, "Advertising other servers is not allowed.")
- log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
- return
- if(findtext(msg, "discord.gg") && !config.allow_discord_links)
- to_chat(src, "Advertising discords is not allowed.")
- log_admin("[key_name(src)] has attempted to advertise a discord server in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to advertise a discord server in OOC: [msg]")
- return
- if((findtext(msg, "http://") || findtext(msg, "https://")) && !config.allow_url_links)
- to_chat(src, "Posting external links is not allowed.")
- log_admin("[key_name(src)] has attempted to post a link in OOC: [msg]")
- message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
- return
-
- log_looc(msg,src)
-
- if(msg)
- handle_spam_prevention(MUTE_OOC)
-
- var/mob/source = mob.get_looc_source()
- var/turf/T = get_turf(source)
- if(!T) return
- var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
- var/list/m_viewers = in_range["mobs"]
-
- var/list/receivers = list() //Clients, not mobs.
- var/list/r_receivers = list()
-
- var/display_name = key
- if(holder && holder.fakekey)
- display_name = holder.fakekey
- if(mob.stat != DEAD)
- display_name = mob.name
-
- // Everyone in normal viewing range of the LOOC
- for(var/mob/viewer in m_viewers)
- if(viewer.client && viewer.client.is_preference_enabled(/datum/client_preference/show_looc))
- receivers |= viewer.client
- else if(istype(viewer,/mob/observer/eye)) // For AI eyes and the like
- var/mob/observer/eye/E = viewer
- if(E.owner && E.owner.client)
- receivers |= E.owner.client
-
- // Admins with RLOOC displayed who weren't already in
- for(var/client/admin in admins)
- if(!(admin in receivers) && admin.is_preference_enabled(/datum/client_preference/holder/show_rlooc))
- r_receivers |= admin
-
- // Send a message
- for(var/client/target in receivers)
- var/admin_stuff = ""
-
- if(target in admins)
- admin_stuff += "/([key])"
-
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]: [msg]")
-
- for(var/client/target in r_receivers)
- var/admin_stuff = "/([key])([admin_jump_link(mob, target.holder)])"
-
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]: [msg]")
-
-/mob/proc/get_looc_source()
- return src
-
-/mob/living/silicon/ai/get_looc_source()
- if(eyeobj)
- return eyeobj
- return src
+
+/client/verb/ooc(msg as text)
+ set name = "OOC"
+ set category = "OOC"
+
+ if(say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, "Speech is currently admin-disabled.")
+ return
+
+ if(!mob) return
+ if(IsGuestKey(key))
+ to_chat(src, "Guests may not use OOC.")
+ return
+
+ msg = sanitize(msg)
+ if(!msg) return
+
+ if(!is_preference_enabled(/datum/client_preference/show_ooc))
+ to_chat(src, "You have OOC muted.")
+ return
+
+ if(!holder)
+ if(!config.ooc_allowed)
+ to_chat(src, "OOC is globally muted.")
+ return
+ if(!config.dooc_allowed && (mob.stat == DEAD))
+ to_chat(usr, "OOC for dead mobs has been turned off.")
+ return
+ if(prefs.muted & MUTE_OOC)
+ to_chat(src, "You cannot use OOC (muted).")
+ return
+ if(findtext(msg, "byond://") && !config.allow_byond_links)
+ to_chat(src, "Advertising other servers is not allowed.")
+ log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
+ return
+ if(findtext(msg, "discord.gg") && !config.allow_discord_links)
+ to_chat(src, "Advertising discords is not allowed.")
+ log_admin("[key_name(src)] has attempted to advertise a discord server in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to advertise a discord server in OOC: [msg]")
+ return
+ if((findtext(msg, "http://") || findtext(msg, "https://")) && !config.allow_url_links)
+ to_chat(src, "Posting external links is not allowed.")
+ log_admin("[key_name(src)] has attempted to post a link in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
+ return
+
+ log_ooc(msg, src)
+
+ if(msg)
+ handle_spam_prevention(MUTE_OOC)
+
+ var/ooc_style = "everyone"
+ if(holder && !holder.fakekey)
+ ooc_style = "elevated"
+ if(holder.rights & R_ADMIN)
+ ooc_style = "admin"
+ else if(holder.rights & R_EVENT)
+ ooc_style = "event_manager"
+ else if(holder.rights & R_DEBUG)
+ ooc_style = "developer"
+ else if(holder.rights & R_MOD)
+ ooc_style = "moderator"
+
+
+ for(var/client/target in GLOB.clients)
+ if(target.is_preference_enabled(/datum/client_preference/show_ooc))
+ if(target.is_key_ignored(key)) // If we're ignored by this person, then do nothing.
+ continue
+ var/display_name = src.key
+ if(holder)
+ if(holder.fakekey)
+ if(target.holder)
+ display_name = "[holder.fakekey]/([src.key])"
+ else
+ display_name = holder.fakekey
+ if(holder && !holder.fakekey && (holder.rights & R_ADMIN|R_FUN|R_EVENT) && config.allow_admin_ooccolor && (src.prefs.ooccolor != initial(src.prefs.ooccolor))) // keeping this for the badmins
+ to_chat(target, "" + create_text_tag("ooc", "OOC:", target) + " [display_name]: [msg]")
+ else
+ to_chat(target, "" + create_text_tag("ooc", "OOC:", target) + " [display_name]: [msg]")
+
+/client/verb/looc(msg as text)
+ set name = "LOOC"
+ set desc = "Local OOC, seen only by those in view."
+ set category = "OOC"
+
+ if(say_disabled) //This is here to try to identify lag problems
+ to_chat(usr, "Speech is currently admin-disabled.")
+ return
+
+ if(!mob)
+ return
+
+ if(IsGuestKey(key))
+ to_chat(src, "Guests may not use OOC.")
+ return
+
+ msg = sanitize(msg)
+ if(!msg)
+ return
+
+ if(!is_preference_enabled(/datum/client_preference/show_looc))
+ to_chat(src, "You have LOOC muted.")
+ return
+
+ if(!holder)
+ if(!config.looc_allowed)
+ to_chat(src, "LOOC is globally muted.")
+ return
+ if(!config.dooc_allowed && (mob.stat == DEAD))
+ to_chat(usr, "OOC for dead mobs has been turned off.")
+ return
+ if(prefs.muted & MUTE_OOC)
+ to_chat(src, "You cannot use OOC (muted).")
+ return
+ if(findtext(msg, "byond://") && !config.allow_byond_links)
+ to_chat(src, "Advertising other servers is not allowed.")
+ log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]")
+ return
+ if(findtext(msg, "discord.gg") && !config.allow_discord_links)
+ to_chat(src, "Advertising discords is not allowed.")
+ log_admin("[key_name(src)] has attempted to advertise a discord server in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to advertise a discord server in OOC: [msg]")
+ return
+ if((findtext(msg, "http://") || findtext(msg, "https://")) && !config.allow_url_links)
+ to_chat(src, "Posting external links is not allowed.")
+ log_admin("[key_name(src)] has attempted to post a link in OOC: [msg]")
+ message_admins("[key_name_admin(src)] has attempted to post a link in OOC: [msg]")
+ return
+
+ log_looc(msg,src)
+
+ if(msg)
+ handle_spam_prevention(MUTE_OOC)
+
+ var/mob/source = mob.get_looc_source()
+ var/turf/T = get_turf(source)
+ if(!T) return
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ var/list/m_viewers = in_range["mobs"]
+
+ var/list/receivers = list() //Clients, not mobs.
+ var/list/r_receivers = list()
+
+ var/display_name = key
+ if(holder && holder.fakekey)
+ display_name = holder.fakekey
+ if(mob.stat != DEAD)
+ display_name = mob.name
+
+ // Everyone in normal viewing range of the LOOC
+ for(var/mob/viewer in m_viewers)
+ if(viewer.client && viewer.client.is_preference_enabled(/datum/client_preference/show_looc))
+ receivers |= viewer.client
+ else if(istype(viewer,/mob/observer/eye)) // For AI eyes and the like
+ var/mob/observer/eye/E = viewer
+ if(E.owner && E.owner.client)
+ receivers |= E.owner.client
+
+ // Admins with RLOOC displayed who weren't already in
+ for(var/client/admin in admins)
+ if(!(admin in receivers) && admin.is_preference_enabled(/datum/client_preference/holder/show_rlooc))
+ r_receivers |= admin
+
+ // Send a message
+ for(var/client/target in receivers)
+ var/admin_stuff = ""
+
+ if(target in admins)
+ admin_stuff += "/([key])"
+
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]: [msg]")
+
+ for(var/client/target in r_receivers)
+ var/admin_stuff = "/([key])([admin_jump_link(mob, target.holder)])"
+
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]: [msg]")
+
+/mob/proc/get_looc_source()
+ return src
+
+/mob/living/silicon/ai/get_looc_source()
+ if(eyeobj)
+ return eyeobj
+ return src
diff --git a/code/modules/client/verbs/ping.dm b/code/modules/client/verbs/ping.dm
new file mode 100644
index 0000000000..eae9755d36
--- /dev/null
+++ b/code/modules/client/verbs/ping.dm
@@ -0,0 +1,22 @@
+/client/verb/update_ping(time as num)
+ set instant = TRUE
+ set name = ".update_ping"
+ var/ping = pingfromtime(time)
+ lastping = ping
+ if (!avgping)
+ avgping = ping
+ else
+ avgping = MC_AVERAGE_SLOW(avgping, ping)
+
+/client/proc/pingfromtime(time)
+ return ((world.time+world.tick_lag*world.tick_usage/100)-time)*100
+
+/client/verb/display_ping(time as num)
+ set instant = TRUE
+ set name = ".display_ping"
+ to_chat(src, "Round trip ping took [round(pingfromtime(time),1)]ms")
+
+/client/verb/ping()
+ set name = "Ping"
+ set category = "OOC"
+ winset(src, null, "command=.display_ping+[world.time+world.tick_lag*world.tick_usage/100]")
\ No newline at end of file
diff --git a/code/game/verbs/who.dm b/code/modules/client/verbs/who.dm
similarity index 95%
rename from code/game/verbs/who.dm
rename to code/modules/client/verbs/who.dm
index e82b4ccb32..622fa4fd00 100644
--- a/code/game/verbs/who.dm
+++ b/code/modules/client/verbs/who.dm
@@ -1,186 +1,190 @@
-
-/client/verb/who()
- set name = "Who"
- set category = "OOC"
-
- var/msg = "Current Players:\n"
-
- var/list/Lines = list()
-
- if(holder && (R_ADMIN & holder.rights || R_MOD & holder.rights))
- for(var/client/C in GLOB.clients)
- var/entry = "\t[C.key]"
- if(C.holder && C.holder.fakekey)
- entry += " (as [C.holder.fakekey])"
- entry += " - Playing as [C.mob.real_name]"
- switch(C.mob.stat)
- if(UNCONSCIOUS)
- entry += " - Unconscious"
- if(DEAD)
- if(isobserver(C.mob))
- var/mob/observer/dead/O = C.mob
- if(O.started_as_observer)
- entry += " - Observing"
- else
- entry += " - DEAD"
- else
- entry += " - DEAD"
-
- var/age
- if(isnum(C.player_age))
- age = C.player_age
- else
- age = 0
-
- if(age <= 1)
- age = "[age]"
- else if(age < 10)
- age = "[age]"
-
- entry += " - [age]"
-
- if(is_special_character(C.mob))
- entry += " - Antagonist"
-
- if(C.is_afk())
- var/seconds = C.last_activity_seconds()
- entry += " (AFK - "
- entry += "[round(seconds / 60)] minutes, "
- entry += "[seconds % 60] seconds)"
-
- entry += " (?)"
- Lines += entry
- else
- for(var/client/C in GLOB.clients)
- if(C.holder && C.holder.fakekey)
- Lines += C.holder.fakekey
- else
- Lines += C.key
-
- for(var/line in sortList(Lines))
- msg += "[line]\n"
-
- msg += "Total Players: [length(Lines)]"
- to_chat(src,msg)
-
-/client/verb/staffwho()
- set category = "Admin"
- set name = "Staffwho"
-
- var/msg = ""
- var/modmsg = ""
- var/devmsg = ""
- var/eventMmsg = ""
- var/num_mods_online = 0
- var/num_admins_online = 0
- var/num_devs_online = 0
- var/num_event_managers_online = 0
- if(holder)
- for(var/client/C in admins)
- if(R_ADMIN & C.holder.rights || (!R_MOD & C.holder.rights && !R_EVENT & C.holder.rights)) //Used to determine who shows up in admin rows
-
- if(C.holder.fakekey && (!R_ADMIN & holder.rights && !R_MOD & holder.rights)) //Event Managerss can't see stealthmins
- continue
-
- msg += "\t[C] is a [C.holder.rank]"
-
- if(C.holder.fakekey)
- msg += " (as [C.holder.fakekey])"
-
- if(isobserver(C.mob))
- msg += " - Observing"
- else if(istype(C.mob,/mob/new_player))
- msg += " - Lobby"
- else
- msg += " - Playing"
-
- if(C.is_afk())
- var/seconds = C.last_activity_seconds()
- msg += " (AFK - "
- msg += "[round(seconds / 60)] minutes, "
- msg += "[seconds % 60] seconds)"
- msg += "\n"
-
- num_admins_online++
- else if(R_MOD & C.holder.rights) //Who shows up in mod rows.
- modmsg += "\t[C] is a [C.holder.rank]"
-
- if(isobserver(C.mob))
- modmsg += " - Observing"
- else if(istype(C.mob,/mob/new_player))
- modmsg += " - Lobby"
- else
- modmsg += " - Playing"
-
- if(C.is_afk())
- var/seconds = C.last_activity_seconds()
- modmsg += " (AFK - "
- modmsg += "[round(seconds / 60)] minutes, "
- modmsg += "[seconds % 60] seconds)"
- modmsg += "\n"
- num_mods_online++
-
- else if(R_SERVER & C.holder.rights)
- devmsg += "\t[C] is a [C.holder.rank]"
- if(isobserver(C.mob))
- devmsg += " - Observing"
- else if(istype(C.mob,/mob/new_player))
- devmsg += " - Lobby"
- else
- devmsg += " - Playing"
-
- if(C.is_afk())
- var/seconds = C.last_activity_seconds()
- devmsg += "(AFK - "
- devmsg += "[round(seconds / 60)] minutes, "
- devmsg += "[seconds % 60] seconds)"
- devmsg += "\n"
- num_devs_online++
-
- else if(R_EVENT & C.holder.rights)
- eventMmsg += "\t[C] is a [C.holder.rank]"
- if(isobserver(C.mob))
- eventMmsg += " - Observing"
- else if(istype(C.mob,/mob/new_player))
- eventMmsg += " - Lobby"
- else
- eventMmsg += " - Playing"
-
- if(C.is_afk())
- var/seconds = C.last_activity_seconds()
- eventMmsg += " (AFK - "
- eventMmsg += "[round(seconds / 60)] minutes, "
- eventMmsg += "[seconds % 60] seconds)"
- eventMmsg += "\n"
- num_event_managers_online++
-
- else
- for(var/client/C in admins)
- if(R_ADMIN & C.holder.rights || (!R_MOD & C.holder.rights && !R_EVENT & C.holder.rights))
- if(!C.holder.fakekey)
- msg += "\t[C] is a [C.holder.rank]\n"
- num_admins_online++
- else if (R_MOD & C.holder.rights)
- modmsg += "\t[C] is a [C.holder.rank]\n"
- num_mods_online++
- else if (R_SERVER & C.holder.rights)
- devmsg += "\t[C] is a [C.holder.rank]\n"
- num_devs_online++
- else if (R_EVENT & C.holder.rights)
- eventMmsg += "\t[C] is a [C.holder.rank]\n"
- num_event_managers_online++
-
- if(config.admin_irc)
- to_chat(src, "Adminhelps are also sent to IRC. If no admins are available in game try anyway and an admin on IRC may see it and respond.")
- msg = "Current Admins ([num_admins_online]):\n" + msg
-
- if(config.show_mods)
- msg += "\n Current Moderators ([num_mods_online]):\n" + modmsg
-
- if(config.show_devs)
- msg += "\n Current Developers ([num_devs_online]):\n" + devmsg
-
- if(config.show_event_managers)
- msg += "\n Current Event Managers ([num_event_managers_online]):\n" + eventMmsg
-
- to_chat(src,msg)
+
+/client/verb/who()
+ set name = "Who"
+ set category = "OOC"
+
+ var/msg = "Current Players:\n"
+
+ var/list/Lines = list()
+
+ if(holder && (R_ADMIN & holder.rights || R_MOD & holder.rights))
+ for(var/client/C in GLOB.clients)
+ var/entry = "\t[C.key]"
+ if(C.holder && C.holder.fakekey)
+ entry += " (as [C.holder.fakekey])"
+ entry += " - Playing as [C.mob.real_name]"
+ switch(C.mob.stat)
+ if(UNCONSCIOUS)
+ entry += " - Unconscious"
+ if(DEAD)
+ if(isobserver(C.mob))
+ var/mob/observer/dead/O = C.mob
+ if(O.started_as_observer)
+ entry += " - Observing"
+ else
+ entry += " - DEAD"
+ else
+ entry += " - DEAD"
+
+ var/age
+ if(isnum(C.player_age))
+ age = C.player_age
+ else
+ age = 0
+
+ if(age <= 1)
+ age = "[age]"
+ else if(age < 10)
+ age = "[age]"
+
+ entry += " - [age]"
+
+ if(is_special_character(C.mob))
+ entry += " - Antagonist"
+
+ if(C.is_afk())
+ var/seconds = C.last_activity_seconds()
+ entry += " (AFK - "
+ entry += "[round(seconds / 60)] minutes, "
+ entry += "[seconds % 60] seconds)"
+
+ entry += " (?)"
+ entry += " ([round(C.avgping, 1)]ms)"
+ Lines += entry
+ else
+ for(var/client/C in GLOB.clients)
+ var/entry
+ if(C.holder && C.holder.fakekey)
+ entry = C.holder.fakekey
+ else
+ entry = C.key
+ entry += " ([round(C.avgping, 1)]ms)"
+ Lines += entry
+
+ for(var/line in sortList(Lines))
+ msg += "[line]\n"
+
+ msg += "Total Players: [length(Lines)]"
+ to_chat(src,msg)
+
+/client/verb/staffwho()
+ set category = "Admin"
+ set name = "Staffwho"
+
+ var/msg = ""
+ var/modmsg = ""
+ var/devmsg = ""
+ var/eventMmsg = ""
+ var/num_mods_online = 0
+ var/num_admins_online = 0
+ var/num_devs_online = 0
+ var/num_event_managers_online = 0
+ if(holder)
+ for(var/client/C in admins)
+ if(R_ADMIN & C.holder.rights || (!R_MOD & C.holder.rights && !R_EVENT & C.holder.rights)) //Used to determine who shows up in admin rows
+
+ if(C.holder.fakekey && (!R_ADMIN & holder.rights && !R_MOD & holder.rights)) //Event Managerss can't see stealthmins
+ continue
+
+ msg += "\t[C] is a [C.holder.rank]"
+
+ if(C.holder.fakekey)
+ msg += " (as [C.holder.fakekey])"
+
+ if(isobserver(C.mob))
+ msg += " - Observing"
+ else if(istype(C.mob,/mob/new_player))
+ msg += " - Lobby"
+ else
+ msg += " - Playing"
+
+ if(C.is_afk())
+ var/seconds = C.last_activity_seconds()
+ msg += " (AFK - "
+ msg += "[round(seconds / 60)] minutes, "
+ msg += "[seconds % 60] seconds)"
+ msg += "\n"
+
+ num_admins_online++
+ else if(R_MOD & C.holder.rights) //Who shows up in mod rows.
+ modmsg += "\t[C] is a [C.holder.rank]"
+
+ if(isobserver(C.mob))
+ modmsg += " - Observing"
+ else if(istype(C.mob,/mob/new_player))
+ modmsg += " - Lobby"
+ else
+ modmsg += " - Playing"
+
+ if(C.is_afk())
+ var/seconds = C.last_activity_seconds()
+ modmsg += " (AFK - "
+ modmsg += "[round(seconds / 60)] minutes, "
+ modmsg += "[seconds % 60] seconds)"
+ modmsg += "\n"
+ num_mods_online++
+
+ else if(R_SERVER & C.holder.rights)
+ devmsg += "\t[C] is a [C.holder.rank]"
+ if(isobserver(C.mob))
+ devmsg += " - Observing"
+ else if(istype(C.mob,/mob/new_player))
+ devmsg += " - Lobby"
+ else
+ devmsg += " - Playing"
+
+ if(C.is_afk())
+ var/seconds = C.last_activity_seconds()
+ devmsg += "(AFK - "
+ devmsg += "[round(seconds / 60)] minutes, "
+ devmsg += "[seconds % 60] seconds)"
+ devmsg += "\n"
+ num_devs_online++
+
+ else if(R_EVENT & C.holder.rights)
+ eventMmsg += "\t[C] is a [C.holder.rank]"
+ if(isobserver(C.mob))
+ eventMmsg += " - Observing"
+ else if(istype(C.mob,/mob/new_player))
+ eventMmsg += " - Lobby"
+ else
+ eventMmsg += " - Playing"
+
+ if(C.is_afk())
+ var/seconds = C.last_activity_seconds()
+ eventMmsg += " (AFK - "
+ eventMmsg += "[round(seconds / 60)] minutes, "
+ eventMmsg += "[seconds % 60] seconds)"
+ eventMmsg += "\n"
+ num_event_managers_online++
+
+ else
+ for(var/client/C in admins)
+ if(R_ADMIN & C.holder.rights || (!R_MOD & C.holder.rights && !R_EVENT & C.holder.rights))
+ if(!C.holder.fakekey)
+ msg += "\t[C] is a [C.holder.rank]\n"
+ num_admins_online++
+ else if (R_MOD & C.holder.rights)
+ modmsg += "\t[C] is a [C.holder.rank]\n"
+ num_mods_online++
+ else if (R_SERVER & C.holder.rights)
+ devmsg += "\t[C] is a [C.holder.rank]\n"
+ num_devs_online++
+ else if (R_EVENT & C.holder.rights)
+ eventMmsg += "\t[C] is a [C.holder.rank]\n"
+ num_event_managers_online++
+
+ if(config.admin_irc)
+ to_chat(src, "Adminhelps are also sent to IRC. If no admins are available in game try anyway and an admin on IRC may see it and respond.")
+ msg = "Current Admins ([num_admins_online]):\n" + msg
+
+ if(config.show_mods)
+ msg += "\n Current Moderators ([num_mods_online]):\n" + modmsg
+
+ if(config.show_devs)
+ msg += "\n Current Developers ([num_devs_online]):\n" + devmsg
+
+ if(config.show_event_managers)
+ msg += "\n Current Event Managers ([num_event_managers_online]):\n" + eventMmsg
+
+ to_chat(src,msg)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index bf183013b6..95890becac 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -627,6 +627,8 @@
if(.)
if(statpanel("Status"))
+ if(client)
+ stat(null, "Ping: [round(client.lastping, 1)]ms (Average: [round(client.avgping, 1)]ms)")
stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)")
if(ticker && ticker.current_state != GAME_STATE_PREGAME)
stat("Station Time", stationtime2text())
diff --git a/polaris.dme b/polaris.dme
index f497af1b64..d757573bba 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -235,6 +235,7 @@
#include "code\controllers\subsystems\orbits.dm"
#include "code\controllers\subsystems\overlays.dm"
#include "code\controllers\subsystems\persistence.dm"
+#include "code\controllers\subsystems\ping.dm"
#include "code\controllers\subsystems\planets.dm"
#include "code\controllers\subsystems\radiation.dm"
#include "code\controllers\subsystems\shuttles.dm"
@@ -1335,11 +1336,7 @@
#include "code\game\turfs\unsimulated\planetary.dm"
#include "code\game\turfs\unsimulated\shuttle.dm"
#include "code\game\turfs\unsimulated\walls.dm"
-#include "code\game\verbs\advanced_who.dm"
-#include "code\game\verbs\ignore.dm"
-#include "code\game\verbs\ooc.dm"
#include "code\game\verbs\suicide.dm"
-#include "code\game\verbs\who.dm"
#include "code\js\byjax.dm"
#include "code\js\menus.dm"
#include "code\modules\admin\admin.dm"
@@ -1462,8 +1459,8 @@
#include "code\modules\ai\ai_holder_targeting.dm"
#include "code\modules\ai\interfaces.dm"
#include "code\modules\ai\say_list.dm"
-#include "code\modules\ai\aI_holder_subtypes\simple_mob_ai.dm"
-#include "code\modules\ai\aI_holder_subtypes\slime_xenobio_ai.dm"
+#include "code\modules\ai\ai_holder_subtypes\simple_mob_ai.dm"
+#include "code\modules\ai\ai_holder_subtypes\slime_xenobio_ai.dm"
#include "code\modules\alarm\alarm.dm"
#include "code\modules\alarm\alarm_handler.dm"
#include "code\modules\alarm\atmosphere_alarm.dm"
@@ -1581,6 +1578,11 @@
#include "code\modules\client\preference_setup\traits\trait_defines.dm"
#include "code\modules\client\preference_setup\traits\traits.dm"
#include "code\modules\client\preference_setup\volume_sliders\01_volume.dm"
+#include "code\modules\client\verbs\advanced_who.dm"
+#include "code\modules\client\verbs\ignore.dm"
+#include "code\modules\client\verbs\ooc.dm"
+#include "code\modules\client\verbs\ping.dm"
+#include "code\modules\client\verbs\who.dm"
#include "code\modules\clothing\chameleon.dm"
#include "code\modules\clothing\clothing.dm"
#include "code\modules\clothing\clothing_accessories.dm"