diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm
index 3626d744a499..edace04ca2ec 100644
--- a/code/__DEFINES/chat.dm
+++ b/code/__DEFINES/chat.dm
@@ -27,6 +27,7 @@
#define MESSAGE_TYPE_ATTACKLOG "attacklog"
#define MESSAGE_TYPE_DEBUG "debug"
#define MESSAGE_TYPE_SUBTLE "subtle" // DARKPACK EDIT ADD - SUBTLE
+#define MESSAGE_TYPE_MENTOR "mentor" // DARKPACK EDIT ADD - MENTORS
/// Max length of chat message in characters
#define CHAT_MESSAGE_MAX_LENGTH 110
diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index 62ee94f072fc..ecaa3226d0bb 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -161,6 +161,7 @@
#define LOG_CATEGORY_GAME_VOTE "game-vote"
#define LOG_CATEGORY_GAME_WHISPER "game-whisper"
#define LOG_CATEGORY_GAME_GHOST_POLLS "game-ghost-polls"
+#define LOG_CATEGORY_GAME_MENTOR "game-mentor" // DARKPACK EDIT ADD - Mentors
// HREF categories
#define LOG_CATEGORY_HREF "href"
diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm
index fe70a5366b76..d6fe98f9572f 100644
--- a/code/__DEFINES/span.dm
+++ b/code/__DEFINES/span.dm
@@ -91,6 +91,8 @@
#define span_memo(str) ("" + str + "")
#define span_memoedit(str) ("" + str + "")
#define span_policy(str) ("" + str + "")
+#define span_mentor(str) ("" + str + "") // DARKPACK EDIT ADD - MENTOR
+#define span_mentornotice(str) ("" + str + "") // DARKPACK EDIT ADD - MENTOR
#define span_message(str) ("" + str + "")
#define span_mind_control(str) ("" + str + "")
#define span_minorannounce(str) ("" + str + "")
diff --git a/code/game/world.dm b/code/game/world.dm
index a243dd09e383..e07668eb6547 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -164,7 +164,7 @@ GLOBAL_VAR(restart_counter)
SetupLogs()
load_admins(initial = TRUE)
-
+ load_mentors(initial = TRUE) // DARKPACK EDIT ADD - Mentors
load_poll_data()
// Initialize RETA system - code/modules/reta/reta_system.dm
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 7d4b20ee66df..1da97852223c 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -38,6 +38,11 @@ GLOBAL_LIST_INIT(unrecommended_builds, list(
if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null
return
+ // DARKPACK EDIT ADD BEGIN - MENTOR
+ if(mentor_client_procs(href_list))
+ return
+ // DARKPACK EDIT ADD END
+
#ifndef TESTING
if (LOWER_TEXT(hsrc_command) == "_debug") //disable the integrated byond vv in the client side debugging tools since it doesn't respect vv read protections
return
diff --git a/config/admin_ranks.txt b/config/admin_ranks.txt
index e09a3b570974..c3215410ef78 100644
--- a/config/admin_ranks.txt
+++ b/config/admin_ranks.txt
@@ -83,3 +83,10 @@ Name = Coder
Include = DEBUG VAREDIT SERVER SPAWN POLL
Exclude = AUTOADMIN
Edit =
+
+// DARKPACK EDIT ADD START - Mentors
+Name = Mentor
+Include =
+Exclude = AUTOADMIN
+Edit =
+// DARKPACK EDIT ADD END - Mentors
diff --git a/config/darkpack_config.txt b/config/darkpack_config.txt
index bd565bc978fd..5e8029099242 100644
--- a/config/darkpack_config.txt
+++ b/config/darkpack_config.txt
@@ -1,3 +1,11 @@
+## Defines whether or not mentors can see ckeys alongside mobnames.
+## uncomment to hide ckeys from mentors
+#MENTORS_MOBNAME_ONLY
+
+## Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system.
+## uncomment to use mentors.txt instead of SQL
+#MENTOR_LEGACY_SYSTEM
+
## Uncomment to use Requiem jump windup
#JUMP_WINDUP
## Comment to remove Apocrypha post-jump slowdown
diff --git a/modular_darkpack/master_files/code/controllers/configuration/entries/darkpack_config_entries.dm b/modular_darkpack/master_files/code/controllers/configuration/entries/darkpack_config_entries.dm
new file mode 100644
index 000000000000..4f39472d1b55
--- /dev/null
+++ b/modular_darkpack/master_files/code/controllers/configuration/entries/darkpack_config_entries.dm
@@ -0,0 +1,6 @@
+/// Defines whether or not mentors can see ckeys alongside mobnames.
+/datum/config_entry/flag/mentors_mobname_only
+
+/// Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system.
+/datum/config_entry/flag/mentor_legacy_system
+ protection = CONFIG_ENTRY_LOCKED
diff --git a/modular_darkpack/master_files/code/modules/client/preferences/_admin.dm b/modular_darkpack/master_files/code/modules/client/preferences/_admin.dm
new file mode 100644
index 000000000000..3720993e1c5e
--- /dev/null
+++ b/modular_darkpack/master_files/code/modules/client/preferences/_admin.dm
@@ -0,0 +1,8 @@
+/datum/preference/toggle/admin
+ abstract_type = /datum/preference/toggle/admin
+
+/datum/preference/toggle/admin/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ return is_admin(preferences.parent)
diff --git a/modular_darkpack/master_files/code/modules/client/preferences/auto_dementor.dm b/modular_darkpack/master_files/code/modules/client/preferences/auto_dementor.dm
new file mode 100644
index 000000000000..5268db765597
--- /dev/null
+++ b/modular_darkpack/master_files/code/modules/client/preferences/auto_dementor.dm
@@ -0,0 +1,5 @@
+/datum/preference/toggle/admin/auto_dementor
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "auto_dementor_pref"
+ savefile_identifier = PREFERENCE_PLAYER
+ default_value = FALSE // We want people to not automatically dementor by default, otherwise they just don't know about the fact they're mentors.
diff --git a/modular_darkpack/master_files/code/modules/logging/categories/log_categories_game.dm b/modular_darkpack/master_files/code/modules/logging/categories/log_categories_game.dm
new file mode 100644
index 000000000000..fbae2fe2d25f
--- /dev/null
+++ b/modular_darkpack/master_files/code/modules/logging/categories/log_categories_game.dm
@@ -0,0 +1,4 @@
+/datum/log_category/game_mentor
+ category = LOG_CATEGORY_GAME_MENTOR
+ master_category = /datum/log_category/game
+
diff --git a/modular_darkpack/modules/mentor/code/_globalvars.dm b/modular_darkpack/modules/mentor/code/_globalvars.dm
new file mode 100644
index 000000000000..6a1ce0c19d98
--- /dev/null
+++ b/modular_darkpack/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_darkpack/modules/mentor/code/client_procs.dm b/modular_darkpack/modules/mentor/code/client_procs.dm
new file mode 100644
index 000000000000..2e64191f0f6a
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/client_procs.dm
@@ -0,0 +1,43 @@
+/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
+
+/client/proc/mentor_datum_set()
+ mentor_datum = GLOB.mentor_datums[ckey]
+ if(!mentor_datum && is_admin(src)) // 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) && prefs.read_preference(/datum/preference/toggle/admin/auto_dementor))
+ cmd_mentor_dementor()
+
+
+/**
+ * Returns whether or not the user is qualified as a mentor.
+ *
+ * Arguments:
+ * * admin_bypass - Whether or not admins can succeed this check, even if they
+ * do not actually possess the role. Defaults to `TRUE`.
+ */
+/client/proc/is_mentor(admin_bypass = TRUE)
+ if(mentor_datum || (admin_bypass && check_rights_for(src, R_ADMIN)))
+ return TRUE
diff --git a/modular_darkpack/modules/mentor/code/dementor.dm b/modular_darkpack/modules/mentor/code/dementor.dm
new file mode 100644
index 000000000000..075f8d6ad1c5
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/dementor.dm
@@ -0,0 +1,22 @@
+/client/proc/cmd_mentor_dementor()
+ set category = "Mentor"
+ set name = "dementor"
+ if(!is_mentor())
+ return
+ remove_mentor_verbs()
+ GLOB.mentors -= src
+ to_chat(src, span_interface("You are no longer a mentor."))
+ log_mentor("MENTOR: [src] dementored.")
+ 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
+ to_chat(src, span_interface("You are now a mentor."))
+ log_mentor("MENTOR: [src] rementored.")
+ remove_verb(src,/client/proc/cmd_mentor_rementor)
+
diff --git a/modular_darkpack/modules/mentor/code/logging.dm b/modular_darkpack/modules/mentor/code/logging.dm
new file mode 100644
index 000000000000..fe6f7142adcc
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/logging.dm
@@ -0,0 +1,15 @@
+GLOBAL_LIST_EMPTY(mentorlog)
+GLOBAL_PROTECT(mentorlog)
+
+/proc/log_mentor(text, list/data)
+ GLOB.mentorlog.Add(text)
+ logger.Log(LOG_CATEGORY_GAME_MENTOR, text, data)
+
+/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_darkpack/modules/mentor/code/mentor.dm b/modular_darkpack/modules/mentor/code/mentor.dm
new file mode 100644
index 000000000000..232254e6114c
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/mentor.dm
@@ -0,0 +1,123 @@
+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.
+
+/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/remove_mentor()
+ if(owner)
+ owner.remove_mentor_verbs()
+ GLOB.mentors -= owner
+ owner.mentor_datum = null
+ owner = null
+ log_admin_private("[target] was removed from the rank of mentor.")
+ GLOB.mentor_datums -= target
+ qdel(src)
+
+/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(no_update, initial = FALSE)
+ if(!initial)
+ if(!global.config.PreConfigReload())
+ return
+
+ var/dbfail
+ if(!CONFIG_GET(flag/mentor_legacy_system) && !SSdbcore.Connect())
+ message_admins("Failed to connect to database while loading mentors. Loading from backup.")
+ log_sql("Failed to connect to database while loading mentors. Loading from backup.")
+ dbfail = TRUE
+
+ GLOB.mentor_datums.Cut()
+ for(var/client/C in GLOB.mentors)
+ C.remove_mentor_verbs()
+ C.mentor_datum = null
+ GLOB.mentors.Cut()
+
+ if(CONFIG_GET(flag/mentor_legacy_system))
+ var/list/lines = world.file2list("config/mentors.txt")
+ for(var/line in lines)
+ if(!length(line))
+ continue
+ if(findtextEx(line, "#", 1, 2))
+ continue
+ new /datum/mentors(line)
+ return
+
+ if(!dbfail)
+ var/datum/db_query/query_load_mentors = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
+ if(!query_load_mentors.warn_execute())
+ message_admins("Error loading mentors from database. Loading from backup.")
+ log_sql("Error loading mentors from database. Loading from backup.")
+ dbfail = TRUE
+ else
+ while(query_load_mentors.NextRow())
+ var/mentor_ckey = ckey(query_load_mentors.item[1])
+ new /datum/mentors(mentor_ckey)
+ QDEL_NULL(query_load_mentors)
+
+ if(dbfail)
+ var/backup_file = file2text("data/mentors_backup.json")
+ if(backup_file == null)
+ log_world("Unable to locate mentors backup file.")
+ return
+ var/list/backup_file_json = json_decode(backup_file)
+ for(var/backup_mentor_ckey in backup_file_json["mentors"])
+ if(GLOB.mentor_datums[ckey(backup_mentor_ckey)])
+ continue
+ new /datum/mentors(ckey(backup_mentor_ckey))
+
+ return dbfail
+
+
+/client
+ /// Acts the same way holder does towards admin: it holds the mentor datum. if set, the guy's a mentor.
+ var/datum/mentors/mentor_datum
diff --git a/modular_darkpack/modules/mentor/code/mentor_verbs.dm b/modular_darkpack/modules/mentor/code/mentor_verbs.dm
new file mode 100644
index 000000000000..1852dee4c8c6
--- /dev/null
+++ b/modular_darkpack/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_darkpack/modules/mentor/code/mentorhelp.dm b/modular_darkpack/modules/mentor/code/mentorhelp.dm
new file mode 100644
index 000000000000..e174205ed0a5
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/mentorhelp.dm
@@ -0,0 +1,103 @@
+/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(30 SECONDS) // Gotta love BYOND, god this is disgusting
+ 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 = span_mentor("MENTORHELP: [key_name_mentor(src, TRUE, FALSE, TRUE, show_char)]: [msg]")
+ log_mentor("MENTORHELP: [key_name_mentor(src, FALSE, FALSE, FALSE, FALSE)]: [msg]")
+
+ for(var/mentor in GLOB.mentors)
+ var/client/mentor_client = mentor
+ if(mentor_client)
+ SEND_SOUND(mentor_client, 'sound/items/bikehorn.ogg')
+ to_chat(mentor_client, mentor_msg)
+
+ to_chat(src, span_mentor("PM to-Mentors: [msg]"))
+ return
+
+/proc/get_mentor_counts()
+ . = list("total" = 0, "afk" = 0, "present" = 0)
+ for(var/mentor in GLOB.mentors)
+ var/client/mentor_client = mentor
+ .["total"]++
+ if(mentor_client.is_afk())
+ .["afk"]++
+ else
+ .["present"]++
+
+/proc/key_name_mentor(whom, include_link = null, include_name = FALSE, include_follow = FALSE, char_name_only = FALSE)
+ var/mob/target_mob
+ var/client/target_client
+ var/key
+ var/ckey
+
+ if(!whom)
+ return "*null*"
+ if(istype(whom, /client))
+ target_client = whom
+ target_mob = target_client?.mob
+ key = target_client?.key
+ ckey = target_client?.ckey
+ else if(ismob(whom))
+ target_mob = whom
+ target_client = target_mob.client
+ key = target_mob.key
+ ckey = target_mob.ckey
+ else if(istext(whom))
+ key = whom
+ ckey = ckey(whom)
+ target_client = GLOB.directory[ckey]
+ if(target_client)
+ target_mob = target_client?.mob
+ else
+ return "*invalid*"
+
+ . = ""
+
+ if(!ckey)
+ include_link = FALSE
+
+ if(key)
+ if(include_link)
+ if(CONFIG_GET(flag/mentors_mobname_only))
+ . += ""
+ else
+ . += ""
+
+ if(target_client && target_client?.holder && target_client?.holder.fakekey)
+ . += "Administrator"
+ else if (char_name_only && CONFIG_GET(flag/mentors_mobname_only))
+ if(istype(target_client?.mob,/mob/dead/new_player) || istype(target_client?.mob, /mob/dead/observer)) //If they're in the lobby or observing, display their ckey
+ . += key
+ else if(target_client && target_client?.mob) //If they're playing/in the round, only show the mob name
+ . += target_client?.mob.name
+ else //If for some reason neither of those are applicable and they're mentorhelping, show ckey
+ . += key
+ else
+ . += key
+ if(!target_client)
+ . += "\[DC\]"
+
+ if(include_link)
+ . += ""
+ else
+ . += "*no key*"
+
+ /*
+ if(include_follow)
+ . += " (F)"
+ */
+ return .
diff --git a/modular_darkpack/modules/mentor/code/mentorpm.dm b/modular_darkpack/modules/mentor/code/mentorpm.dm
new file mode 100644
index 000000000000..75626a0c4cac
--- /dev/null
+++ b/modular_darkpack/modules/mentor/code/mentorpm.dm
@@ -0,0 +1,93 @@
+//shows a list of clients we could send PMs to, then forwards our choice to cmd_Mentor_pm
+/client/proc/cmd_mentor_pm_panel() // We're not using this and I'm debating removing the code as it's dead and useless. We don't need mentors PMing people out of the blue. That's not really how we operate.
+ set category = "Mentor"
+ set name = "Mentor PM"
+ if(!is_mentor())
+ to_chat(src, span_danger("Error: Mentor-PM-Panel: Only Mentors and Admins may use this command."))
+ return
+ var/list/client/targets[0]
+ for(var/client/T) // What a cursed proc this is
+ targets["[T]"] = T
+
+ var/list/sorted = sort_list(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", TRUE, "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 target is the target client
+ *
+ * Arguments:
+ * * whom - The target of the mentor PM.
+ * * msg - The content of the mentor PM.
+ */
+/client/proc/cmd_mentor_pm(whom, msg)
+ var/client/target
+ if(ismob(whom))
+ var/mob/mob_target = whom
+ target = mob_target.client
+ else if(istext(whom))
+ target = GLOB.directory[whom]
+ else if(istype(whom,/client))
+ target = whom
+ if(!target)
+ if(is_mentor())
+ to_chat(src, span_danger("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, span_purple(span_mentor("[src] has started replying to [whom]'s mhelp.")))
+
+ //get message text, limit it's length.and clean/escape html
+ if(!msg)
+ msg = tgui_input_text(src, "Message:", "Private message")
+
+ if(!msg)
+ if (is_mentor(whom))
+ to_chat(GLOB.mentors, span_mentor(span_purple("[src] has stopped their reply to [whom]'s mhelp.")))
+ return
+
+ if(!target)
+ if(is_mentor())
+ to_chat(src, span_danger("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 (!target.is_mentor() && !is_mentor())
+ return
+
+ if(!msg)
+ if (is_mentor(whom))
+ to_chat(GLOB.mentors, span_mentor(span_purple("[src] has stopped their reply to [whom]'s mhelp.")))
+ return
+ log_mentor("Mentor PM: [key_name(src)]->[key_name(target)]: [msg]")
+
+ msg = emoji_parse(msg)
+ SEND_SOUND(target, 'sound/items/bikehorn.ogg')
+ var/show_char = CONFIG_GET(flag/mentors_mobname_only)
+ if(target.is_mentor())
+ if(is_mentor())//both are mentors
+ to_chat(target, span_mentor(span_purple("Mentor PM from-[key_name_mentor(src, target, TRUE, FALSE, FALSE)]: [msg]")))
+ to_chat(src, span_mentor(span_blue("Mentor PM to-[key_name_mentor(target, target, TRUE, FALSE, FALSE)]: [msg]")))
+
+ else //recipient is a mentor but sender is not
+ to_chat(target, span_mentor(span_purple("Reply PM from-[key_name_mentor(src, target, TRUE, FALSE, show_char)]: [msg]")))
+ to_chat(src, span_mentor("Mentor PM to-[key_name_mentor(target, target, TRUE, FALSE, FALSE)]: [msg]"))
+
+ else
+ if(is_mentor()) //sender is a mentor but recipient is not.
+ to_chat(target, span_mentor(span_purple("Mentor PM from-[key_name_mentor(src, target, TRUE, FALSE, FALSE)]: [msg]")))
+ to_chat(src, span_mentor("Mentor PM to-[key_name_mentor(target, target, TRUE, FALSE, show_char)]: [msg]"))
+
+ //we don't use message_Mentors here because the sender/receiver might get it too // We should make it an argument for that proc to ignore the sender, then. :(
+ var/show_char_sender = !is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
+ var/show_char_recip = !target.is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
+ for(var/it in GLOB.mentors)
+ var/client/mentor = it
+ if(mentor?.key != key && mentor?.key != target.key) //check client/mentor is an Mentor and isn't the sender or recipient
+ to_chat(mentor, span_mentor("Mentor PM: [key_name_mentor(src, mentor, FALSE, FALSE, show_char_sender)]->[key_name_mentor(target, mentor, FALSE, FALSE, show_char_recip)]: [span_blue(msg)]")) //inform mentor
diff --git a/modular_darkpack/modules/mentor/code/mentorsay.dm b/modular_darkpack/modules/mentor/code/mentorsay.dm
new file mode 100644
index 000000000000..77d83d3c9363
--- /dev/null
+++ b/modular_darkpack/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 = span_mentor("MENTOR: [key_name(src, 0, 0)]: [msg]")
+ else
+ msg = span_mentor("MENTOR: [key_name(src, 0, 0)]: [msg]")
+ to_chat(GLOB.admins | GLOB.mentors, msg)
diff --git a/modular_darkpack/modules/mentor/code/mentorwho.dm b/modular_darkpack/modules/mentor/code/mentorwho.dm
new file mode 100644
index 000000000000..efcaa7f759d8
--- /dev/null
+++ b/modular_darkpack/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 += span_infoplain("\t[C][suffix]\n")
+ to_chat(src, msg)
diff --git a/modular_darkpack/modules/mentor/readme.md b/modular_darkpack/modules/mentor/readme.md
new file mode 100644
index 000000000000..db0ac70a2648
--- /dev/null
+++ b/modular_darkpack/modules/mentor/readme.md
@@ -0,0 +1,31 @@
+## 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:
+
+- `tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/darkpack_auto_dementor.tsx`
+
+### Credits:
+
+chazzyjazzy - porting to Darkpack
+
+Azarak - Porting, tweaks
+Poojawa - Implementation
diff --git a/tgstation.dme b/tgstation.dme
index 7c0a3128bd57..8dbf4a51e5e2 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6986,6 +6986,7 @@
#include "modular_darkpack\master_files\code\__HELPERS\maths.dm"
#include "modular_darkpack\master_files\code\_globalvars\configuration.dm"
#include "modular_darkpack\master_files\code\_onclick\hud\new_player.dm"
+#include "modular_darkpack\master_files\code\controllers\configuration\entries\darkpack_config_entries.dm"
#include "modular_darkpack\master_files\code\controllers\configuration\entries\game_options.dm"
#include "modular_darkpack\master_files\code\controllers\configuration\entries\general.dm"
#include "modular_darkpack\master_files\code\datums\actions\action.dm"
@@ -7018,12 +7019,15 @@
#include "modular_darkpack\master_files\code\game\turfs\open\space\space.dm"
#include "modular_darkpack\master_files\code\modules\cargo\expressconsole.dm"
#include "modular_darkpack\master_files\code\modules\client\preferences_savefile.dm"
+#include "modular_darkpack\master_files\code\modules\client\preferences\_admin.dm"
#include "modular_darkpack\master_files\code\modules\client\preferences\_preference.dm"
+#include "modular_darkpack\master_files\code\modules\client\preferences\auto_dementor.dm"
#include "modular_darkpack\master_files\code\modules\client\preferences\country_of_origin.dm"
#include "modular_darkpack\master_files\code\modules\economy\account.dm"
#include "modular_darkpack\master_files\code\modules\fishing\sources\subtypes\structures.dm"
#include "modular_darkpack\master_files\code\modules\fishing\sources\subtypes\turfs.dm"
#include "modular_darkpack\master_files\code\modules\hydroponics\grown.dm"
+#include "modular_darkpack\master_files\code\modules\logging\categories\log_categories_game.dm"
#include "modular_darkpack\master_files\code\modules\mob\mob_defines.dm"
#include "modular_darkpack\master_files\code\modules\mob\mob_helpers.dm"
#include "modular_darkpack\master_files\code\modules\mob\dead\observer\observer.dm"
@@ -7377,6 +7381,16 @@
#include "modular_darkpack\modules\masquerade\code\subsystem\masquerade.dm"
#include "modular_darkpack\modules\matrix\code\job.dm"
#include "modular_darkpack\modules\matrix\code\matrix.dm"
+#include "modular_darkpack\modules\mentor\code\_globalvars.dm"
+#include "modular_darkpack\modules\mentor\code\client_procs.dm"
+#include "modular_darkpack\modules\mentor\code\dementor.dm"
+#include "modular_darkpack\modules\mentor\code\logging.dm"
+#include "modular_darkpack\modules\mentor\code\mentor.dm"
+#include "modular_darkpack\modules\mentor\code\mentor_verbs.dm"
+#include "modular_darkpack\modules\mentor\code\mentorhelp.dm"
+#include "modular_darkpack\modules\mentor\code\mentorpm.dm"
+#include "modular_darkpack\modules\mentor\code\mentorsay.dm"
+#include "modular_darkpack\modules\mentor\code\mentorwho.dm"
#include "modular_darkpack\modules\merits_flaws\code\_darkpack_quirk.dm"
#include "modular_darkpack\modules\merits_flaws\code\config.dm"
#include "modular_darkpack\modules\merits_flaws\code\negative_quirks\amnesia.dm"
diff --git a/tgui/packages/tgui-panel/chat/constants.ts b/tgui/packages/tgui-panel/chat/constants.ts
index f910853732b6..6c096b9a309a 100644
--- a/tgui/packages/tgui-panel/chat/constants.ts
+++ b/tgui/packages/tgui-panel/chat/constants.ts
@@ -30,6 +30,7 @@ export const MESSAGE_TYPE_WARNING = 'warning';
export const MESSAGE_TYPE_DEADCHAT = 'deadchat';
export const MESSAGE_TYPE_OOC = 'ooc';
export const MESSAGE_TYPE_LOOC = 'looc'; // DARKPACK EDIT ADD
+export const MESSAGE_TYPE_MENTOR = 'mentor'; // DARKPACK EDIT ADD - MENTOR
export const MESSAGE_TYPE_ADMINPM = 'adminpm';
export const MESSAGE_TYPE_COMBAT = 'combat';
export const MESSAGE_TYPE_ADMINCHAT = 'adminchat';
@@ -133,6 +134,12 @@ export const MESSAGE_TYPES: MessageType[] = [
description: 'Subtle and Subtler actions.',
selector: '.subtle, .subtler',
}, // DARKPACK EDIT ADD END
+ { // DARKPACK EDIT ADD START - MENTOR
+ type: MESSAGE_TYPE_MENTOR,
+ name: 'Mentor Log',
+ description: 'Mentor PMs and other mentor things.',
+ selector: '.mentor, .mentornotice',
+ }, // DARKPACK EDIT ADD END
{
type: MESSAGE_TYPE_UNKNOWN,
name: 'Unsorted',
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
index e85ad03be6dd..d7aa5202147e 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
@@ -1299,6 +1299,10 @@ $border-width-px: $border-width * 1px;
/* DARKPACK EDIT ADD START */
+.mentor {
+ color: #8a2be2;
+}
+
.looc {
color: #d8b555;
}
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
index 8076e62204f8..9d4284568f8a 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
@@ -1280,6 +1280,10 @@ $border-width-px: $border-width * 1px;
/* DARKPACK EDIT ADD START */
+.mentor {
+ color: #8a2be2;
+}
+
.looc {
color: #6699cc;
font-weight: bold;
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/darkpack_auto_dementor.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/darkpack_auto_dementor.tsx
new file mode 100644
index 000000000000..f1e08fae6efc
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/darkpack_auto_dementor.tsx
@@ -0,0 +1,9 @@
+// THIS IS A DARKPACK UI FILE
+import { CheckboxInput, type FeatureToggle } from '../base';
+
+export const auto_dementor_pref: FeatureToggle = {
+ name: 'Auto dementor',
+ category: 'ADMIN',
+ description: 'When enabled, you will automatically dementor.',
+ component: CheckboxInput,
+};