Mentor System (#5235)

* reworked mentor system, again. wew

* Update admin.txt

* uhg
This commit is contained in:
Poojawa
2018-02-05 04:09:56 -06:00
committed by GitHub
parent 0eff0b730b
commit 03b33075e5
31 changed files with 502 additions and 214 deletions
+2
View File
@@ -99,3 +99,5 @@
//Brainslugs
#define isborer(A) (istype(A, /mob/living/simple_animal/borer))
#define CITADEL_MENTOR_OOC_COLOUR "#ad396e"
+4 -4
View File
@@ -17,11 +17,11 @@ proc/TextPreview(var/string,var/len=40)
else
return "[copytext(string, 1, 37)]..."
GLOBAL_LIST_EMPTY(mentor_log)
GLOBAL_PROTECT(mentor_log)
GLOBAL_LIST_EMPTY(mentorlog)
GLOBAL_PROTECT(mentorlog)
GLOBAL_LIST_EMPTY(whitelisted_species_list)
/proc/log_mentor(text)
GLOB.mentor_log.Add(text)
GLOB.world_game_log << "\[[time_stamp()]]MENTOR: [text]"
GLOB.mentorlog.Add(text)
GLOB.world_game_log << "\[[time_stamp()]]MENTOR: [text]"
-3
View File
@@ -86,9 +86,6 @@ GLOBAL_LIST_INIT(dildo_colors, list(//mostly neon colors
"Purple" = "#e300ff"//purple
))
//mentor stuff
GLOBAL_LIST_EMPTY(mentors)
//Looc stuff
GLOBAL_VAR_INIT(looc_allowed, 1)
GLOBAL_VAR_INIT(dlooc_allowed, 1)
+4 -1
View File
@@ -44,7 +44,10 @@
body += "<br><br><b>Show related accounts by:</b> "
body += "\[ <a href='?_src_=holder;[HrefToken()];showrelatedacc=cid;client=[REF(M.client)]'>CID</a> | "
body += "<a href='?_src_=holder;[HrefToken()];showrelatedacc=ip;client=[REF(M.client)]'>IP</a> \]"
body += "<br><br>"
body += "<A href='?_src_=holder;[HrefToken()];makementor=[M.ckey]'>Make mentor</A> | "
body += "<A href='?_src_=holder;[HrefToken()];removementor=[M.ckey]'>Remove mentor</A>"
body += "<br>"
body += "<br><br>\[ "
body += "<a href='?_src_=vars;[HrefToken()];Vars=[REF(M)]'>VV</a> - "
+2
View File
@@ -107,6 +107,8 @@
dat += "No-one has done anything this round!"
usr << browse(dat, "window=admin_log")
if("mentor_log")
CitadelMentorLogSecret()
if("list_job_debug")
var/dat = "<B>Job Debug info.</B><HR>"
for(var/line in SSjob.job_debug)
+2
View File
@@ -22,6 +22,8 @@
if(!CheckAdminHref(href, href_list))
return
citaTopic(href, href_list) // Citadel
if(href_list["ahelp"])
if(!check_rights(R_ADMIN, TRUE))
return
+3
View File
@@ -87,6 +87,8 @@
hsrc = holder
if("usr")
hsrc = mob
if("mentor") // CITADEL
hsrc = mentor_datum // CITADEL END
if("prefs")
if (inprefs)
return
@@ -183,6 +185,7 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
else if(GLOB.deadmins[ckey])
verbs += /client/proc/readmin
connecting_admin = TRUE
mentor_datum_set()// Citadel mentor_holder setting
//preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum)
prefs = GLOB.preferences_datums[ckey]
+2
View File
@@ -67,6 +67,8 @@
to_chat(C, "<span class='adminobserverooc'><span class='prefix'>OOC:</span> <EM>[keyname][holder.fakekey ? "/([holder.fakekey])" : ""]:</EM> <span class='message'>[msg]</span></span>")
else
to_chat(C, "<font color='[GLOB.normal_ooc_colour]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[holder.fakekey ? holder.fakekey : key]:</EM> <span class='message'>[msg]</span></span></font>")
else if(is_mentor()) // Citadel Mentors
to_chat(C, "<font color='[CITADEL_MENTOR_OOC_COLOUR]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[keyname]:</EM> <span class='message'>[msg]</span></span>") // hippie end
else if(!(key in C.prefs.ignoring))
to_chat(C, "<font color='[GLOB.normal_ooc_colour]'><span class='ooc'><span class='prefix'>OOC:</span> <EM>[keyname]:</EM> <span class='message'>[msg]</span></span></font>")
+1 -1
View File
@@ -84,6 +84,6 @@
continue //Don't show afk admins to adminwho
if(!C.holder.fakekey)
msg += "\t[C] is a [C.holder.rank]\n"
msg += "<span class='info'>Adminhelps are also sent to IRC. If no admins are available in game adminhelp anyways and an admin on IRC will see it and respond.</span>"
msg += "<span class='info'>Adminhelps are also sent to Discord. If no admins are available in game adminhelp anyways and an admin on Discord will see it and respond.</span>"
to_chat(src, msg)
-50
View File
@@ -1,50 +0,0 @@
/client/proc/mentor_follow(var/mob/living/M)
if(!check_mentor())
return
if(isnull(M))
return
if(!istype(usr, /mob))
return
if(!holder)
var/datum/mentors/mentor = GLOB.mentor_datums[usr.client.ckey]
mentor.following = M
/* else
holder.following = M*/
usr.reset_perspective(M)
src.verbs += /client/proc/mentor_unfollow
to_chat(GLOB.admins, "<span class='mentor'><span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is now following <EM>[key_name(M)]</span>")
to_chat(usr, "<span class='info'>You are now following [M]. Click the \"Stop Following\" button in the Mentor tab to stop.</span>")
log_mentor("[key_name(usr)] began following [key_name(M)]")
/client/proc/mentor_unfollow()
set category = "Mentor"
set name = "Stop Following"
set desc = "Stop following the followed."
if(!check_mentor())
return
usr.reset_perspective(null)
src.verbs -= /client/proc/mentor_unfollow
var/following = null
if(!holder)
var/datum/mentors/mentor = GLOB.mentor_datums[usr.client.ckey]
following = mentor.following
/*else
following = holder.following*/
to_chat(GLOB.admins, "<span class='mentor'><span class='prefix'>MENTOR:</span> <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(following)]</span>")
to_chat(usr, "<span class='info'>You are no longer following [following].</span>")
log_mentor("[key_name(usr)] stopped following [key_name(following)]")
following = null
-46
View File
@@ -1,46 +0,0 @@
GLOBAL_LIST(mentor_datums)
/datum/mentors
var/client/owner = null
var/following = null
/datum/mentors/New(ckey)
if(!ckey)
del(src)
return
GLOB.mentor_datums[ckey] = src
/datum/mentors/proc/associate(client/C)
if(istype(C))
owner = C
GLOB.mentors |= C
/datum/mentors/proc/disassociate()
if(owner)
GLOB.mentors -= owner
owner = null
/client/proc/dementor()
var/mentor = GLOB.mentor_datums[ckey]
GLOB.mentor_datums -= ckey
qdel(mentor)
return 1
/proc/check_mentor()
if(usr && usr.client)
var/mentor = GLOB.mentor_datums[usr.client.ckey]
if(mentor || check_rights(R_ADMIN,0))
return 1
return 0
/proc/check_mentor_other(var/client/C)
if(C)
var/mentor = GLOB.mentor_datums[C.ckey]
if(C.holder && C.holder.rank)
if(C.holder.rank.rights & R_ADMIN)
return 1
else if(mentor)
return 1
return 0
-43
View File
@@ -1,43 +0,0 @@
/proc/load_mentors()
//clear the datums references
GLOB.mentor_datums.Cut()
GLOB.mentors.Cut()
if(!config.mentor_legacy_system)
if(!GLOB.dbcon.IsConnected())
world.log << "Failed to connect to database in load_mentors()."
GLOB.diary << "Failed to connect to database in load_mentors()."
config.mentor_legacy_system = 1
load_mentors()
return
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
query.Execute()
while(query.NextRow())
var/ckey = ckey(query.item[1])
var/datum/mentors/D = new(ckey) //create the mentor datum and store it for later use
if(!D) continue //will occur if an invalid rank is provided
D.associate(GLOB.directory[ckey]) //find the client for a ckey if they are connected and associate them with the new mentor datum
else
world.log << "Using legacy mentor system."
var/list/Lines = file2list("config/mentors.txt")
//process each line seperately
for(var/line in Lines)
if(!length(line)) continue
if(findtextEx(line,"#",1,2)) continue
//ckey is before the first "="
var/ckey = ckey(line)
if(!ckey) continue
var/datum/mentors/D = new(ckey) //create the admin datum and store it for later use
if(!D) continue //will occur if an invalid rank is provided
D.associate(GLOB.directory[ckey]) //find the client for a ckey if they are connected and associate them with the new admin datum
#ifdef TESTING
var/msg = "mentors Built:\n"
for(var/ckey in GLOB.mentor_datums)
msg += "\t[ckey] - mentor\n"
testing(msg)
#endif
-139
View File
@@ -1,139 +0,0 @@
/client/proc/mentor_memo()
set name = "Mentor Memos"
set category = "Server"
if(!check_rights(0)) return
if(!GLOB.dbcon.IsConnected())
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
return
var/memotask = input(usr,"Choose task.","Memo") in list("Show","Write","Edit","Remove")
if(!memotask)
return
mentor_memo_output(memotask)
/client/proc/show_mentor_memo()
set name = "Show Memos"
set category = "Mentor"
if(!check_mentor()) return
if(!GLOB.dbcon.IsConnected())
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
return
mentor_memo_output("Show")
/client/proc/mentor_memo_output(task)
if(!task)
return
if(!GLOB.dbcon.IsConnected())
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
return
var/sql_ckey = sanitizeSQL(src.ckey)
switch(task)
if("Write")
var/DBQuery/query_memocheck = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")] WHERE ckey = '[sql_ckey]'")
if(!query_memocheck.Execute())
var/err = query_memocheck.ErrorMsg()
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
return
if(query_memocheck.NextRow())
to_chat(src, "You already have set a memo.")
return
var/memotext = input(src,"Write your Memo","Memo") as message
if(!memotext)
return
memotext = sanitizeSQL(memotext)
var/timestamp = SQLtime()
var/DBQuery/query_memoadd = GLOB.dbcon.NewQuery("INSERT INTO [format_table_name("mentor_memo")] (ckey, memotext, timestamp) VALUES ('[sql_ckey]', '[memotext]', '[timestamp]')")
if(!query_memoadd.Execute())
var/err = query_memoadd.ErrorMsg()
log_game("SQL ERROR adding new memo. Error : \[[err]\]\n")
return
log_admin("[key_name(src)] has set a mentor memo: [memotext]")
message_admins("[key_name_admin(src)] has set a mentor memo:<br>[memotext]")
if("Edit")
var/DBQuery/query_memolist = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")]")
if(!query_memolist.Execute())
var/err = query_memolist.ErrorMsg()
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
return
var/list/memolist = list()
while(query_memolist.NextRow())
var/lkey = query_memolist.item[1]
memolist += "[lkey]"
if(!memolist.len)
to_chat(src, "No memos found in database.")
return
var/target_ckey = input(src, "Select whose memo to edit", "Select memo") as null|anything in memolist
if(!target_ckey)
return
var/target_sql_ckey = sanitizeSQL(target_ckey)
var/DBQuery/query_memofind = GLOB.dbcon.NewQuery("SELECT memotext FROM [format_table_name("mentor_memo")] WHERE ckey = '[target_sql_ckey]'")
if(!query_memofind.Execute())
var/err = query_memofind.ErrorMsg()
log_game("SQL ERROR obtaining memotext from memo table. Error : \[[err]\]\n")
return
if(query_memofind.NextRow())
var/old_memo = query_memofind.item[1]
var/new_memo = input("Input new memo", "New Memo", "[old_memo]", null) as message
if(!new_memo)
return
new_memo = sanitizeSQL(new_memo)
var/edit_text = "Edited by [sql_ckey] on [SQLtime()] from<br>[old_memo]<br>to<br>[new_memo]<hr>"
edit_text = sanitizeSQL(edit_text)
var/DBQuery/update_query = GLOB.dbcon.NewQuery("UPDATE [format_table_name("mentor_memo")] SET memotext = '[new_memo]', last_editor = '[sql_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE ckey = '[target_sql_ckey]'")
if(!update_query.Execute())
var/err = update_query.ErrorMsg()
log_game("SQL ERROR editing memo. Error : \[[err]\]\n")
return
if(target_sql_ckey == sql_ckey)
log_admin("[key_name(src)] has edited their mentor memo from [old_memo] to [new_memo]")
message_admins("[key_name_admin(src)] has edited their mentor memo from<br>[old_memo]<br>to<br>[new_memo]")
else
log_admin("[key_name(src)] has edited [target_sql_ckey]'s mentor memo from [old_memo] to [new_memo]")
message_admins("[key_name_admin(src)] has edited [target_sql_ckey]'s mentor memo from<br>[old_memo]<br>to<br>[new_memo]")
if("Show")
var/DBQuery/query_memoshow = GLOB.dbcon.NewQuery("SELECT ckey, memotext, timestamp, last_editor FROM [format_table_name("mentor_memo")]")
if(!query_memoshow.Execute())
var/err = query_memoshow.ErrorMsg()
log_game("SQL ERROR obtaining ckey, memotext, timestamp, last_editor from memo table. Error : \[[err]\]\n")
return
var/output = null
while(query_memoshow.NextRow())
var/ckey = query_memoshow.item[1]
var/memotext = query_memoshow.item[2]
var/timestamp = query_memoshow.item[3]
var/last_editor = query_memoshow.item[4]
output += "<span class='memo'>Mentor memo by <span class='prefix'>[ckey]</span> on [timestamp]"
if(last_editor)
output += "<br><span class='memoedit'>Last edit by [last_editor] <A href='?_src_=holder;mentormemoeditlist=[ckey]'>(Click here to see edit log)</A></span>"
output += "<br>[memotext]</span><br>"
if(!output)
to_chat(src, "No memos found in database.")
return
to_chat(src, output)
if("Remove")
var/DBQuery/query_memodellist = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")]")
if(!query_memodellist.Execute())
var/err = query_memodellist.ErrorMsg()
log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n")
return
var/list/memolist = list()
while(query_memodellist.NextRow())
var/ckey = query_memodellist.item[1]
memolist += "[ckey]"
if(!memolist.len)
to_chat(src, "No memos found in database.")
return
var/target_ckey = input(src, "Select whose mentor memo to delete", "Select mentor memo") as null|anything in memolist
if(!target_ckey)
return
var/target_sql_ckey = sanitizeSQL(target_ckey)
var/DBQuery/query_memodel = GLOB.dbcon.NewQuery("DELETE FROM [format_table_name("memo")] WHERE ckey = '[target_sql_ckey]'")
if(!query_memodel.Execute())
var/err = query_memodel.ErrorMsg()
log_game("SQL ERROR removing memo. Error : \[[err]\]\n")
return
if(target_sql_ckey == sql_ckey)
log_admin("[key_name(src)] has removed their mentor memo.")
message_admins("[key_name_admin(src)] has removed their mentor memo.")
else
log_admin("[key_name(src)] has removed [target_sql_ckey]'s mentor memo.")
message_admins("[key_name_admin(src)] has removed [target_sql_ckey]'s mentor memo.")
-101
View File
@@ -1,101 +0,0 @@
/client/verb/mentorhelp(msg as text)
set category = "Mentor"
set name = "mentorhelp"
//remove out adminhelp verb temporarily to prevent spamming of mentors.
src.verbs -= /client/verb/mentorhelp
spawn(300)
src.verbs += /client/verb/mentorhelp // 30 second cool-down for mentorhelp
//clean the input msg
if(!msg) return
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
if(!msg) return
if(!mob) return //this doesn't happen
var/show_char = config.mentors_mobname_only
var/mentor_msg = "<span class='mentornotice'><b><font color='purple'>MENTORHELP:</b> <b>[key_name_mentor(src, 1, 0, 0, show_char)]</b>: [msg]</font></span>"
var/admin_msg = "<span class='mentornotice'><b><font color='purple'>MENTORHELP:</b> <b>[ADMIN_FULLMONTY(src.mob)]</b>: [msg]</font></span>"
log_mentor("MENTORHELP: [key_name_mentor(src, 0, 0, 0, 0)]: [msg]")
for(var/client/X in GLOB.mentors)
to_chat(X, 'sound/items/bikehorn.ogg')
to_chat(X, mentor_msg)
for(var/client/A in GLOB.admins)
to_chat(A, 'sound/items/bikehorn.ogg')
to_chat(A, admin_msg)
to_chat(src, "<span class='mentornotice'><font color='purple'>PM to-<b>Mentors</b>: [msg]</font></span>")
return
/proc/get_mentor_counts()
. = list("total" = 0, "afk" = 0, "present" = 0)
for(var/client/X in GLOB.mentors)
.["total"]++
if(X.is_afk())
.["afk"]++
else
.["present"]++
/proc/key_name_mentor(var/whom, var/include_link = null, var/include_name = 0, var/include_follow = 0, var/char_name_only = 0)
var/mob/M
var/client/C
var/key
var/ckey
if(!whom) return "*null*"
if(istype(whom, /client))
C = whom
M = C.mob
key = C.key
ckey = C.ckey
else if(ismob(whom))
M = whom
C = M.client
key = M.key
ckey = M.ckey
else if(istext(whom))
key = whom
ckey = ckey(whom)
C = GLOB.directory[ckey]
if(C)
M = C.mob
else
return "*invalid*"
. = ""
if(!ckey)
include_link = 0
if(key)
if(include_link)
if(config.mentors_mobname_only)
. += "<a href='?mentor_msg=\ref[M]'>"
else
. += "<a href='?mentor_msg=[ckey]'>"
if(C && C.holder && C.holder.fakekey)
. += "Administrator"
else if (char_name_only && config.mentors_mobname_only)
if(istype(C.mob,/mob/dead/new_player) || istype(C.mob, /mob/dead/observer)) //If they're in the lobby or observing, display their ckey
. += key
else if(C && C.mob) //If they're playing/in the round, only show the mob name
. += C.mob.name
else //If for some reason neither of those are applicable and they're mentorhelping, show ckey
. += key
else
. += key
if(!C)
. += "\[DC\]"
if(include_link)
. += "</a>"
else
. += "*no key*"
if(include_follow)
. += " (<a href='?mentor_follow=\ref[M]'>F</a>)"
return .
-74
View File
@@ -1,74 +0,0 @@
//shows a list of clients we could send PMs to, then forwards our choice to cmd_Mentor_pm
/client/proc/cmd_mentor_pm_panel()
set category = "Mentor"
set name = "Mentor PM"
if(!holder)
to_chat(src, "<font color='red'>Error: Mentor-PM-Panel: Only Mentors may use this command.</font>")
return
var/list/client/targets[0]
for(var/client/T)
targets["[T]"] = T
var/list/sorted = sortList(targets)
var/target = input(src,"To whom shall we send a message?","Mentor PM",null) in sorted|null
cmd_mentor_pm(targets[target],null)
feedback_add_details("Mentor_verb","APM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//takes input from cmd_mentor_pm_context, cmd_Mentor_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_mentor_pm(whom, msg)
var/client/C
if(ismob(whom))
var/mob/M = whom
C = M.client
else if(istext(whom))
C = GLOB.directory[whom]
else if(istype(whom,/client))
C = whom
if(!C)
if(holder) to_chat(src, "<font color='red'>Error: Mentor-PM: Client not found.</font>")
else mentorhelp(msg) //Mentor we are replying to left. Mentorhelp instead
return
//get message text, limit it's length.and clean/escape html
if(!msg)
msg = input(src,"Message:", "Private message") as text|null
if(!msg) return
if(!C)
if(holder) to_chat(src, "<font color='red'>Error: Mentor-PM: Client not found.</font>")
else mentorhelp(msg) //Mentor we are replying to has vanished, Mentorhelp instead
return
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
if(!msg) return
log_mentor("Mentor PM: [key_name(src)]->[key_name(C)]: [msg]")
msg = emoji_parse(msg)
C << 'sound/items/bikehorn.ogg'
var/show_char = config.mentors_mobname_only
if(check_mentor_other(C))
if(check_mentor()) //both are mentors
to_chat(C, "<font color='purple'>Mentor PM from-<b>[key_name_mentor(src, C, 1, 0, 0)]</b>: [msg]</font>")
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, 0)]</b>: [msg]</font>")
else //recipient is an mentor but sender is not
to_chat(C, "<font color='purple'>Reply PM from-<b>[key_name_mentor(src, C, 1, 0, show_char)]</b>: [msg]</font>")
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, 0)]</b>: [msg]</font>")
else
if(check_mentor()) //sender is an mentor but recipient is not.
to_chat(C, "<font color='purple'>Mentor PM from-<b>[key_name_mentor(src, C, 1, 0, 0)]</b>: [msg]</font>")
to_chat(src, "<font color='green'>Mentor PM to-<b>[key_name_mentor(C, C, 1, 0, show_char)]</b>: [msg]</font>")
//we don't use message_Mentors here because the sender/receiver might get it too
var/show_char_sender = !check_mentor_other(src) && config.mentors_mobname_only
var/show_char_recip = !check_mentor_other(C) && config.mentors_mobname_only
for(var/client/X in GLOB.mentors)
if(X.key!=key && X.key!=C.key) //check client/X is an Mentor and isn't the sender or recipient
to_chat(X, "<B><font color='green'>Mentor PM: [key_name_mentor(src, X, 0, 0, show_char_sender)]-&gt;[key_name_mentor(C, X, 0, 0, show_char_recip)]:</B> \blue [msg]</font>") //inform X
for(var/client/A in GLOB.admins)
if(A.key!=key && A.key!=C.key) //check client/A is an Mentor and isn't the sender or recipient
to_chat(A, "<B><font color='green'>Mentor PM: [key_name_mentor(src, A, 0, 0, show_char_sender)]-&gt;[key_name_mentor(C, A, 0, 0, show_char_recip)]:</B> \blue [msg]</font>") //inform A
-22
View File
@@ -1,22 +0,0 @@
/client/proc/cmd_mentor_say(msg as text)
set category = "Mentor"
set name = "Msay" //Gave this shit a shorter name so you only have to time out "msay" rather than "mentor say" to use it --NeoFite
set hidden = 1
if(!check_mentor()) return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg) return
msg = emoji_parse(msg)
log_mentor("MSAY: [key_name(src)] : [msg]")
if(check_rights(R_ADMIN,0))
msg = "<span class='mentoradmin'><span class='prefix'>MENTOR:</span> <EM>[key_name(src, 0, 0)]</EM>: <span class='message'>[msg]</span></span>"
to_chat(GLOB.mentors, msg)
to_chat(GLOB.admins, msg)
else
msg = "<span class='mentor'><span class='prefix'>MENTOR:</span> <EM>[key_name(src, 0, 0)]</EM>: <span class='message'>[msg]</span></span>"
to_chat(GLOB.mentors, msg)
to_chat(GLOB.admins, msg)