diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm index 1dbab89d88e..614a4c10f16 100644 --- a/code/modules/admin/secrets.dm +++ b/code/modules/admin/secrets.dm @@ -9,8 +9,9 @@
Admin Log
Show Admin List
+ Mentor Log

- "} + "}//SKYRAT EDIT ADDITION - MENTOR if(check_rights(R_ADMIN,0)) dat += {" @@ -97,6 +98,11 @@ var/datum/round_event/E var/ok = FALSE switch(item) + //SKYRAT EDIT ADDITION BEGIN - MENTOR + if("mentor_log") + MentorLogSecret() + //SKYRAT EDIT ADDITION END + if("admin_log") var/dat = "Admin Log
" for(var/l in GLOB.admin_log) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index f761a7b57a2..bf644814151 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -36,6 +36,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/Topic(href, href_list, hsrc) if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null return + //SKYRAT EDIT ADDITION BEGIN - MENTOR + if(mentor_client_procs(href_list)) + return + //SKYRAT EDIT ADDITION END // asset_cache var/asset_cache_job diff --git a/config/skyrat/mentors.txt b/config/skyrat/mentors.txt new file mode 100644 index 00000000000..928da766c44 --- /dev/null +++ b/config/skyrat/mentors.txt @@ -0,0 +1,2 @@ +thattraitorgeorgemellons +Azalis diff --git a/config/skyrat/skyrat_config.txt b/config/skyrat/skyrat_config.txt index af58770f756..10cc57cdce1 100644 --- a/config/skyrat/skyrat_config.txt +++ b/config/skyrat/skyrat_config.txt @@ -1,3 +1,9 @@ +## Comment this out if you want to use the SQL based mentor system, the legacy system uses mentors.txt. +## You need to set up your database to use the SQL based system. +## This flag is automatically enabled if SQL_ENABLED isn't +## But currently the SQL system is not implemented and it'll read from mentors.txt nonetheless +MENTOR_LEGACY_SYSTEM + ALERT_AMBER_UPTO A major security emergency has developed. Security staff may have weapons unholstered at all times. Random searches are allowed and advised. ALERT_AMBER_DOWNTO A major security emergency is still underway. Non-security personnel are required to obey all relevant instructions from security staff. ALERT_ORANGE_UPTO A major engineering emergency has developed. Non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff. @@ -6,4 +12,4 @@ ALERT_VIOLET_UPTO A major medical emergency has developed. Non-medical personnel ALERT_VIOLET_DOWNTO A major medical emergency has developed. Non-medical personnel are required to obey all relevant instructions from medical staff. ## How long until someone can be put in cryo if they are SSD -CRYO_MIN_SSD_TIME 15 \ No newline at end of file +CRYO_MIN_SSD_TIME 15 diff --git a/modular_skyrat/modules/mentor/code/_globalvars.dm b/modular_skyrat/modules/mentor/code/_globalvars.dm new file mode 100644 index 00000000000..6a1ce0c19d9 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/_globalvars.dm @@ -0,0 +1,3 @@ +//all clients whom are mentors +GLOBAL_LIST_EMPTY(mentors) +GLOBAL_PROTECT(mentors) diff --git a/modular_skyrat/modules/mentor/code/client_procs.dm b/modular_skyrat/modules/mentor/code/client_procs.dm new file mode 100644 index 00000000000..18a12bb73a5 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/client_procs.dm @@ -0,0 +1,45 @@ +/client/New() + . = ..() + mentor_datum_set() + +/client/Destroy() + if(GLOB.mentors[src]) + GLOB.mentors -= src + return ..() + +/client/proc/mentor_client_procs(href_list) + if(href_list["mentor_msg"]) + if(CONFIG_GET(flag/mentors_mobname_only)) + var/mob/M = locate(href_list["mentor_msg"]) + cmd_mentor_pm(M,null) + else + cmd_mentor_pm(href_list["mentor_msg"],null) + return TRUE + + //Mentor Follow + if(href_list["mentor_follow"]) + var/mob/living/M = locate(href_list["mentor_follow"]) + + if(istype(M)) + mentor_follow(M) + return TRUE + + if(href_list["mentor_unfollow"]) + if(mentor_datum.following) + mentor_unfollow() + return TRUE + +/client/proc/mentor_datum_set(admin) + mentor_datum = GLOB.mentor_datums[ckey] + if(!mentor_datum && check_rights_for(src, R_ADMIN,0)) // admin with no mentor datum?let's fix that + new /datum/mentors(ckey) + if(mentor_datum) + mentor_datum.owner = src + GLOB.mentors[src] = TRUE + add_mentor_verbs() + if(check_rights_for(src, R_ADMIN,0)) + cmd_mentor_dementor() + +/client/proc/is_mentor() // admins are mentors too. + if(mentor_datum || check_rights_for(src, R_ADMIN,0)) + return TRUE diff --git a/modular_skyrat/modules/mentor/code/config.dm b/modular_skyrat/modules/mentor/code/config.dm new file mode 100644 index 00000000000..a1d1053796a --- /dev/null +++ b/modular_skyrat/modules/mentor/code/config.dm @@ -0,0 +1,8 @@ +/datum/configuration + var/mentors_mobname_only = FALSE // Only display mob name to mentors in mentorhelps + var/mentor_legacy_system = FALSE // Whether to use the legacy mentor system (flat file) instead of SQL + +/datum/config_entry/flag/mentors_mobname_only + +/datum/config_entry/flag/mentor_legacy_system //Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED diff --git a/modular_skyrat/modules/mentor/code/dementor.dm b/modular_skyrat/modules/mentor/code/dementor.dm new file mode 100644 index 00000000000..d0ab511de36 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/dementor.dm @@ -0,0 +1,19 @@ +/client/proc/cmd_mentor_dementor() + set category = "Mentor" + set name = "dementor" + if(!is_mentor()) + return + remove_mentor_verbs() + if (/client/proc/mentor_unfollow in verbs) + mentor_unfollow() + GLOB.mentors -= src + add_verb(src,/client/proc/cmd_mentor_rementor) + +/client/proc/cmd_mentor_rementor() + set category = "Mentor" + set name = "rementor" + if(!is_mentor()) + return + add_mentor_verbs() + GLOB.mentors[src] = TRUE + remove_verb(src,/client/proc/cmd_mentor_rementor) diff --git a/modular_skyrat/modules/mentor/code/follow.dm b/modular_skyrat/modules/mentor/code/follow.dm new file mode 100644 index 00000000000..49760a34078 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/follow.dm @@ -0,0 +1,28 @@ +/client/proc/mentor_follow(mob/living/M) + if(!is_mentor()) + return + var/orbiting = TRUE + if(!isobserver(usr)) + mentor_datum.following = M + usr.reset_perspective(M) + add_verb(src,/client/proc/mentor_unfollow) + to_chat(usr, "Click the \"Stop Following\" button here or in the Mentor tab to stop following [key_name(M)].") + orbiting = FALSE + else + var/mob/dead/observer/O = usr + O.ManualFollow(M) + to_chat(GLOB.admins, "MENTOR: [key_name(usr)] is now [orbiting ? "orbiting" : "following"] [key_name(M)][key_name(M)][orbiting ? " as a ghost" : ""].") + log_mentor("[key_name(usr)] [orbiting ? "is now orbiting" : "began following"][key_name(M)][orbiting ? " as a ghost" : ""].") + +/client/proc/mentor_unfollow() + set category = "Mentor" + set name = "Stop Following" + set desc = "Stop following the followed." + + if(!is_mentor()) + return + usr.reset_perspective() + remove_verb(src,/client/proc/mentor_unfollow) + to_chat(GLOB.admins, "MENTOR: [key_name(usr)] is no longer following [key_name(mentor_datum.following)].") + log_mentor("[key_name(usr)] stopped following [key_name(mentor_datum.following)].") + mentor_datum.following = null diff --git a/modular_skyrat/modules/mentor/code/logging.dm b/modular_skyrat/modules/mentor/code/logging.dm new file mode 100644 index 00000000000..b34be3d2137 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/logging.dm @@ -0,0 +1,15 @@ +GLOBAL_LIST_EMPTY(mentorlog) +GLOBAL_PROTECT(mentorlog) + +/proc/log_mentor(text) + GLOB.mentorlog.Add(text) + WRITE_FILE(GLOB.world_game_log, "MENTOR: [text]") + +/datum/admins/proc/MentorLogSecret() + var/dat = "Mentor Log
" + for(var/l in GLOB.mentorlog) + dat += "
  • [l]
  • " + + if(!GLOB.mentorlog.len) + dat += "No mentors have done anything this round!" + usr << browse(dat, "window=mentor_log") diff --git a/modular_skyrat/modules/mentor/code/mentor.dm b/modular_skyrat/modules/mentor/code/mentor.dm new file mode 100644 index 00000000000..90e390a7a2c --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentor.dm @@ -0,0 +1,91 @@ +GLOBAL_LIST_EMPTY(mentor_datums) +GLOBAL_PROTECT(mentor_datums) + +GLOBAL_VAR_INIT(mentor_href_token, GenerateToken()) +GLOBAL_PROTECT(mentor_href_token) + +/datum/mentors + var/name = "someone's mentor datum" + var/client/owner // the actual mentor, client type + var/target // the mentor's ckey + var/href_token // href token for mentor commands, uses the same token used by admins. + var/mob/following + +/datum/mentors/New(ckey) + if(!ckey) + QDEL_IN(src, 0) + CRASH("Mentor datum created without a ckey") + target = ckey(ckey) + name = "[ckey]'s mentor datum" + href_token = GenerateToken() + GLOB.mentor_datums[target] = src + //set the owner var and load commands + owner = GLOB.directory[ckey] + if(owner) + owner.mentor_datum = src + owner.add_mentor_verbs() + if(!check_rights_for(owner, R_ADMIN,0)) // don't add admins to mentor list. + GLOB.mentors[owner] = TRUE + +/datum/mentors/proc/CheckMentorHREF(href, href_list) + var/auth = href_list["mentor_token"] + . = auth && (auth == href_token || auth == GLOB.mentor_href_token) + if(.) + return + var/msg = !auth ? "no" : "a bad" + message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") + if(CONFIG_GET(flag/debug_admin_hrefs)) + message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") + log_world("UAH: [href]") + return TRUE + log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") + +/proc/RawMentorHrefToken(forceGlobal = FALSE) + var/tok = GLOB.mentor_href_token + if(!forceGlobal && usr) + var/client/C = usr.client + to_chat(world, C) + to_chat(world, usr) + if(!C) + CRASH("No client for HrefToken()!") + var/datum/mentors/holder = C.mentor_datum + if(holder) + tok = holder.href_token + return tok + +/proc/MentorHrefToken(forceGlobal = FALSE) + return "mentor_token=[RawMentorHrefToken(forceGlobal)]" + +/proc/load_mentors() + usr = null + GLOB.mentor_datums.Cut() + for(var/it in GLOB.mentors) + var/client/C = it + C.remove_mentor_verbs() + C.mentor_datum = null + GLOB.mentors.Cut() + //if(CONFIG_GET(flag/mentor_legacy_system))//legacy + var/list/lines = world.file2list("config/skyrat/mentors.txt") + for(var/line in lines) + if(!length(line)) + continue + if(findtextEx(line, "#", 1, 2)) + continue + new /datum/mentors(line) + /*else//Database + if(!SSdbcore.Connect()) + log_world("Failed to connect to database in load_mentors(). Reverting to legacy system.") + WRITE_FILE(GLOB.world_game_log, "Failed to connect to database in load_mentors(). Reverting to legacy system.") + CONFIG_SET(flag/mentor_legacy_system, TRUE) + load_mentors() + return + var/datum/DBQuery/query_load_mentors = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]") + if(!query_load_mentors.Execute()) + return + while(query_load_mentors.NextRow()) + var/ckey = ckey(query_load_mentors.item[1]) + new /datum/mentors(ckey)*/ + +// new client var: mentor_datum. Acts the same way holder does towards admin: it holds the mentor datum. if set, the guy's a mentor. +/client + var/datum/mentors/mentor_datum diff --git a/modular_skyrat/modules/mentor/code/mentor_verbs.dm b/modular_skyrat/modules/mentor/code/mentor_verbs.dm new file mode 100644 index 00000000000..5989c3b12e6 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentor_verbs.dm @@ -0,0 +1,12 @@ +GLOBAL_LIST_INIT(mentor_verbs, list( + /client/proc/cmd_mentor_say, + /client/proc/cmd_mentor_dementor + )) +GLOBAL_PROTECT(mentor_verbs) + +/client/proc/add_mentor_verbs() + if(mentor_datum) + add_verb(src,GLOB.mentor_verbs) + +/client/proc/remove_mentor_verbs() + remove_verb(src,GLOB.mentor_verbs) diff --git a/modular_skyrat/modules/mentor/code/mentorhelp.dm b/modular_skyrat/modules/mentor/code/mentorhelp.dm new file mode 100644 index 00000000000..9e1d3e8a01d --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentorhelp.dm @@ -0,0 +1,99 @@ +/client/verb/mentorhelp(msg as text) + set category = "Mentor" + set name = "Mentorhelp" + + //clean the input msg + if(!msg) return + + //remove out mentorhelp verb temporarily to prevent spamming of mentors. + remove_verb(src,/client/verb/mentorhelp) + spawn(300) + add_verb(src,/client/verb/mentorhelp) // 30 second cool-down for mentorhelp + + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg || !mob) + return + + var/show_char = CONFIG_GET(flag/mentors_mobname_only) + var/mentor_msg = "MENTORHELP: [key_name_mentor(src, 1, 0, 1, show_char)]: [msg]" + log_mentor("MENTORHELP: [key_name_mentor(src, 0, 0, 0, 0)]: [msg]") + + for(var/it in GLOB.mentors) + var/client/X = it + SEND_SOUND(X, 'sound/items/bikehorn.ogg') + to_chat(X, mentor_msg) + + to_chat(src, "PM to-Mentors: [msg]") + return + +/proc/get_mentor_counts() + . = list("total" = 0, "afk" = 0, "present" = 0) + for(var/X in GLOB.mentors) + var/client/C = X + .["total"]++ + if(C.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_GET(flag/mentors_mobname_only)) + . += "" + else + . += "" + + if(C && C.holder && C.holder.fakekey) + . += "Administrator" + else if (char_name_only && CONFIG_GET(flag/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) + . += "" + else + . += "*no key*" + + if(include_follow) + . += " (F)" + + return . diff --git a/modular_skyrat/modules/mentor/code/mentorpm.dm b/modular_skyrat/modules/mentor/code/mentorpm.dm new file mode 100644 index 00000000000..fce7c475287 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentorpm.dm @@ -0,0 +1,87 @@ +//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(!is_mentor()) + to_chat(src, "Error: Mentor-PM-Panel: Only Mentors and Admins may use this command.") + 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) + SSblackbox.record_feedback("tally", "Mentor_verb", 1, "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(is_mentor()) to_chat(src, "Error: Mentor-PM: Client not found.") + else mentorhelp(msg) //Mentor we are replying to left. Mentorhelp instead(check below) + return + + if(is_mentor(whom)) + to_chat(GLOB.mentors, "[src] has started replying to [whom]'s mhelp.") + + //get message text, limit it's length.and clean/escape html + if(!msg) + msg = input(src,"Message:", "Private message") as text|null + + if(!msg) + if (is_mentor(whom)) + to_chat(GLOB.mentors, "[src] has stopped their reply to [whom]'s mhelp.") + return + + if(!C) + if(is_mentor()) + to_chat(src, "Error: Mentor-PM: Client not found.") + else + mentorhelp(msg) //Mentor we are replying to has vanished, Mentorhelp instead (how the fuck does this work?let's hope it works,shrug) + return + + // Neither party is a mentor, they shouldn't be PMing! + if (!C.is_mentor() && !is_mentor()) + return + + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) + if (is_mentor(whom)) + to_chat(GLOB.mentors, "[src] has stopped their reply to [whom]'s mhelp.") + 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_GET(flag/mentors_mobname_only) + if(C.is_mentor()) + if(is_mentor())//both are mentors + to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0, 0)]: [msg]") + to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, 0)]: [msg]") + + else //recipient is a mentor but sender is not + to_chat(C, "Reply PM from-[key_name_mentor(src, C, 1, 0, show_char)]: [msg]") + to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, 0)]: [msg]") + + else + if(is_mentor()) //sender is a mentor but recipient is not. + to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0, 0)]: [msg]") + to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, show_char)]: [msg]") + + //we don't use message_Mentors here because the sender/receiver might get it too + var/show_char_sender = !is_mentor() && CONFIG_GET(flag/mentors_mobname_only) + var/show_char_recip = !C.is_mentor() && CONFIG_GET(flag/mentors_mobname_only) + for(var/it in GLOB.mentors) + var/client/X = it + if(X.key!=key && X.key!=C.key) //check client/X is an Mentor and isn't the sender or recipient + to_chat(X, "Mentor PM: [key_name_mentor(src, X, 0, 0, show_char_sender)]->[key_name_mentor(C, X, 0, 0, show_char_recip)]: [msg]") //inform X diff --git a/modular_skyrat/modules/mentor/code/mentorsay.dm b/modular_skyrat/modules/mentor/code/mentorsay.dm new file mode 100644 index 00000000000..b69ce29047d --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentorsay.dm @@ -0,0 +1,19 @@ +/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(!is_mentor()) + return + + msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) + if(!msg) + return + + msg = emoji_parse(msg) + log_mentor("MSAY: [key_name(src)] : [msg]") + + if(check_rights_for(src, R_ADMIN,0)) + msg = "MENTOR: [key_name(src, 0, 0)]: [msg]" + else + msg = "MENTOR: [key_name(src, 0, 0)]: [msg]" + to_chat(GLOB.admins | GLOB.mentors, msg) diff --git a/modular_skyrat/modules/mentor/code/mentorwho.dm b/modular_skyrat/modules/mentor/code/mentorwho.dm new file mode 100644 index 00000000000..b374a078232 --- /dev/null +++ b/modular_skyrat/modules/mentor/code/mentorwho.dm @@ -0,0 +1,22 @@ +/client/verb/mentorwho() + set category = "Mentor" + set name = "Mentorwho" + var/msg = "Current Mentors:\n" + for(var/X in GLOB.mentors) + var/client/C = X + if(!C) + GLOB.mentors -= C + continue // weird runtime that happens randomly + var/suffix = "" + if(holder) + if(isobserver(C.mob)) + suffix += " - Observing" + else if(istype(C.mob,/mob/dead/new_player)) + suffix += " - Lobby" + else + suffix += " - Playing" + + if(C.is_afk()) + suffix += " (AFK)" + msg += "\t[C][suffix]\n" + to_chat(src, msg) diff --git a/modular_skyrat/modules/mentor/readme.md b/modular_skyrat/modules/mentor/readme.md new file mode 100644 index 00000000000..7b2ce2ad6cf --- /dev/null +++ b/modular_skyrat/modules/mentor/readme.md @@ -0,0 +1,29 @@ +## Title: Mentor + +MODULE ID: MENTOR + +### Description: + +Adds a mentor system, allowing for players to send a-help like messages to mentors, asking them about game mechanics and help. + +### TG Proc Changes: + +- code/modules/client/client_procs.dm > client/Topic() +- /code/modules/admin/secrets.dm > /datum/admins/proc/Secrets_topic(), /datum/admins/proc/Secrets() + +### Defines: + +- N/A + +### Master file additions + +- N/A + +### Included files that are not contained in this module: + +- N/A + +### Credits: + +Azarak - Porting, tweaks +Poojawa - Implementation diff --git a/tgstation.dme b/tgstation.dme index 5cd1f94148c..b50e62fcec1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3463,6 +3463,18 @@ #include "modular_skyrat\modules\icspawning\code\game\objects\items\cards_ids.dm" #include "modular_skyrat\modules\icspawning\code\modules\clothing\outfits\standard.dm" #include "modular_skyrat\modules\icspawning\code\modules\spells\spell.dm" +#include "modular_skyrat\modules\mentor\code\_globalvars.dm" +#include "modular_skyrat\modules\mentor\code\client_procs.dm" +#include "modular_skyrat\modules\mentor\code\config.dm" +#include "modular_skyrat\modules\mentor\code\dementor.dm" +#include "modular_skyrat\modules\mentor\code\follow.dm" +#include "modular_skyrat\modules\mentor\code\logging.dm" +#include "modular_skyrat\modules\mentor\code\mentor.dm" +#include "modular_skyrat\modules\mentor\code\mentor_verbs.dm" +#include "modular_skyrat\modules\mentor\code\mentorhelp.dm" +#include "modular_skyrat\modules\mentor\code\mentorpm.dm" +#include "modular_skyrat\modules\mentor\code\mentorsay.dm" +#include "modular_skyrat\modules\mentor\code\mentorwho.dm" #include "modular_skyrat\modules\inflatables\code\inflatable.dm" #include "modular_skyrat\modules\jukebox\code\controllers\subsystem\jukeboxes.dm" #include "modular_skyrat\modules\jukebox\code\controllers\subsystem\game\machinery\dance_machine.dm"