"
- var/un_or_reban_href
- if(unban_datetime)
- un_or_reban_href = "
Reban"
- else
- un_or_reban_href = "
Unban"
- output += "
Edit[un_or_reban_href]"
+ if(server in modify_bans_from)
+ var/un_or_reban_href
+ if(unban_datetime)
+ un_or_reban_href = "
Reban"
+ else
+ un_or_reban_href = "
Unban"
+ output += "
Edit[un_or_reban_href]"
if(edits)
output += "
Edit log"
@@ -755,6 +841,10 @@
/datum/admins/proc/unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key)
if(!check_rights(R_BAN))
return
+ var/list/modify_bans_from = CONFIG_GET(str_list/modify_bans_from)
+ if(!modify_bans_from.len)
+ to_chat(usr, "
This server not allowed to edit any bans!")
+ return
if(!SSdbcore.Connect())
to_chat(usr, span_danger("Failed to establish database connection."), confidential = TRUE)
return
@@ -765,16 +855,24 @@
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/change_message = "[usr.client.key] unbanned [target] from [role] on [SQLtime()] during round #[GLOB.round_id]
"
+
+ var/values = list()
+
+ var/list/modify_from_list = list()
+ for (var/i in 1 to modify_bans_from.len)
+ values["modify_bans_from[i]"] = modify_bans_from[i]
+ modify_from_list += ":modify_bans_from[i]"
+
var/datum/db_query/query_unban = SSdbcore.NewQuery({"
- UPDATE [format_table_name("ban")] SET
+ UPDATE [CONFIG_GET(string/utility_database)].[format_table_name("ban")] SET
unbanned_datetime = NOW(),
unbanned_ckey = :admin_ckey,
- unbanned_ip = INET_ATON(:admin_ip),
+ unbanned_ip = :admin_ip,
unbanned_computerid = :admin_cid,
unbanned_round_id = :round_id,
edits = CONCAT(IFNULL(edits,''), :change_message)
- WHERE id = :ban_id
- "}, list("ban_id" = ban_id, "admin_ckey" = usr.client.ckey, "admin_ip" = usr.client.address, "admin_cid" = usr.client.computer_id, "round_id" = GLOB.round_id, "change_message" = change_message))
+ WHERE id = :ban_id AND server IN ([modify_from_list.Join(", ")])
+ "}, list("ban_id" = ban_id, "admin_ckey" = usr.client.ckey, "admin_ip" = usr.client.address, "admin_cid" = usr.client.computer_id, "round_id" = GLOB.round_id, "change_message" = change_message) + values)
if(!query_unban.warn_execute())
qdel(query_unban)
return
@@ -795,6 +893,10 @@
/datum/admins/proc/reban(ban_id, applies_to_admins, player_key, player_ip, player_cid, role, page, admin_key)
if(!check_rights(R_BAN))
return
+ var/list/modify_bans_from = CONFIG_GET(str_list/modify_bans_from)
+ if(!modify_bans_from.len)
+ to_chat(usr, "
This server not allowed to edit any bans!")
+ return
if(!SSdbcore.Connect())
to_chat(usr, span_danger("Failed to establish database connection."), confidential = TRUE)
return
@@ -807,19 +909,26 @@
if(applies_to_admins && !can_place_additional_admin_ban(usr.client.ckey))
return
+ var/values = list()
+
+ var/list/modify_from_list = list()
+ for (var/i in 1 to modify_bans_from.len)
+ values["modify_bans_from[i]"] = modify_bans_from[i]
+ modify_from_list += ":modify_bans_from[i]"
+
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/change_message = "[usr.client.key] re-activated ban of [target] from [role] on [SQLtime()] during round #[GLOB.round_id]
"
var/datum/db_query/query_reban = SSdbcore.NewQuery({"
- UPDATE [format_table_name("ban")] SET
+ UPDATE [CONFIG_GET(string/utility_database)].[format_table_name("ban")] SET
unbanned_datetime = NULL,
unbanned_ckey = NULL,
unbanned_ip = NULL,
unbanned_computerid = NULL,
unbanned_round_id = NULL,
edits = CONCAT(IFNULL(edits,''), :change_message)
- WHERE id = :ban_id
- "}, list("change_message" = change_message, "ban_id" = ban_id))
+ WHERE id = :ban_id AND server IN ([modify_from_list.Join(", ")])
+ "}, list("change_message" = change_message, "ban_id" = ban_id) + values)
if(!query_reban.warn_execute())
qdel(query_reban)
return
@@ -837,6 +946,10 @@
/datum/admins/proc/edit_ban(ban_id, player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, reason, mirror_edit, old_key, old_ip, old_cid, old_applies, admin_key, page, list/changes, is_server_ban)
if(!check_rights(R_BAN))
return
+ var/list/modify_bans_from = CONFIG_GET(str_list/modify_bans_from)
+ if(!modify_bans_from.len)
+ to_chat(usr, "
This server not allowed to edit any bans!")
+ return
if(!SSdbcore.Connect())
to_chat(usr, span_danger("Failed to establish database connection."), confidential = TRUE)
return
@@ -846,7 +959,7 @@
var/datum/db_query/query_edit_ban_get_player = SSdbcore.NewQuery({"
SELECT
byond_key,
- (SELECT bantime FROM [format_table_name("ban")] WHERE id = :ban_id),
+ (SELECT bantime FROM [CONFIG_GET(string/utility_database)].[format_table_name("ban")] WHERE id = :ban_id),
ip,
computerid
FROM [format_table_name("player")]
@@ -903,7 +1016,7 @@
wherelist += "ckey = :old_ckey"
arguments["old_ckey"] = ckey(old_key)
if(old_ip)
- wherelist += "ip = INET_ATON(:old_ip)"
+ wherelist += "ip = :old_ip"
arguments["old_ip"] = old_ip || null
if(old_cid)
wherelist += "computerid = :old_cid"
@@ -913,18 +1026,25 @@
where = "id = :ban_id"
arguments["ban_id"] = ban_id
+ var/values = list()
+
+ var/list/modify_from_list = list()
+ for (var/i in 1 to modify_bans_from.len)
+ values["modify_bans_from[i]"] = modify_bans_from[i]
+ modify_from_list += ":modify_bans_from[i]"
+
var/datum/db_query/query_edit_ban = SSdbcore.NewQuery({"
- UPDATE [format_table_name("ban")]
+ UPDATE [CONFIG_GET(string/utility_database)].[format_table_name("ban")]
SET
expiration_time = IF(:duration IS NULL, NULL, bantime + INTERVAL :duration [interval]),
applies_to_admins = :applies_to_admins,
reason = :reason,
ckey = :ckey,
- ip = INET_ATON(:ip),
+ ip = :ip,
computerid = :cid,
edits = CONCAT(IFNULL(edits,''), :change_message)
- WHERE [where]
- "}, arguments)
+ WHERE [where] AND server IN ([modify_from_list.Join(", ")])
+ "}, arguments + values)
if(!query_edit_ban.warn_execute())
qdel(query_edit_ban)
return
@@ -950,12 +1070,24 @@
/datum/admins/proc/ban_log(ban_id)
if(!check_rights(R_BAN))
return
+ var/list/read_bans_from = CONFIG_GET(str_list/read_bans_from)
+ if(!read_bans_from.len)
+ to_chat(usr, "
This server not allowed to display any bans!")
+ return
if(!SSdbcore.Connect())
to_chat(usr, span_danger("Failed to establish database connection."), confidential = TRUE)
return
+
+ var/values = list()
+
+ var/list/read_from_list = list()
+ for (var/i in 1 to read_bans_from.len)
+ values["read_bans_from[i]"] = read_bans_from[i]
+ read_from_list += ":read_bans_from[i]"
+
var/datum/db_query/query_get_ban_edits = SSdbcore.NewQuery({"
- SELECT edits FROM [format_table_name("ban")] WHERE id = :ban_id
- "}, list("ban_id" = ban_id))
+ SELECT edits FROM [CONFIG_GET(string/utility_database)].[format_table_name("ban")] WHERE id = :ban_id AND server IN ([read_from_list.Join(", ")]))
+ "}, list("ban_id" = ban_id) + values)
if(!query_get_ban_edits.warn_execute())
qdel(query_get_ban_edits)
return
@@ -994,7 +1126,7 @@
/datum/admins/proc/can_place_additional_admin_ban(admin_ckey)
var/datum/db_query/query_check_adminban_count = SSdbcore.NewQuery({"
SELECT COUNT(DISTINCT bantime)
- FROM [format_table_name("ban")]
+ FROM [CONFIG_GET(string/utility_database)].[format_table_name("ban")]
WHERE
a_ckey = :admin_ckey AND
applies_to_admins = 1 AND
diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm
index 77b43f9b49f48..2324cbe9b2a17 100644
--- a/code/modules/admin/verbs/getlogs.dm
+++ b/code/modules/admin/verbs/getlogs.dm
@@ -24,7 +24,7 @@
message_admins("[key_name_admin(src)] accessed file: [path]")
switch(tgui_alert(usr,"View (in game), Open (in your system's text editor), or Download?", path, list("View", "Open", "Download")))
if ("View")
- src << browse("
[html_encode(file2text(file(path)))]
", list2params(list("window" = "viewfile.[path]")))
+ src << browse("
[html_encode(file2text(file(path)))]
", list2params(list("window" = "viewfile.[path]")))
if ("Open")
src << run(file(path))
if ("Download")
diff --git a/code/modules/admin/whitelist.dm b/code/modules/admin/whitelist.dm
index 759c381bb2e85..965e8b6995944 100644
--- a/code/modules/admin/whitelist.dm
+++ b/code/modules/admin/whitelist.dm
@@ -15,8 +15,24 @@ GLOBAL_LIST(whitelist)
GLOB.whitelist = null
/proc/check_whitelist(ckey)
- if(!GLOB.whitelist)
+ if(!CONFIG_GET(flag/usewhitelist_database))
+ return (ckey in GLOB?.whitelist)
+
+ if(!SSdbcore.Connect())
+ message_admins(span_danger("Failed to establish database connection."))
+ return FALSE
+
+ var/datum/db_query/find_ticket = SSdbcore.NewQuery(
+ "SELECT ckey FROM [CONFIG_GET(string/utility_database)].[format_table_name("ckey_whitelist")] WHERE ckey=:ckey AND is_valid=true AND port=:port AND date_start<=NOW() AND (NOW()
100)
return FALSE
- hotkeys += hotkey
+ hotkeys += convert_ru_key_to_en_key(hotkey)
preferences.key_bindings[keybind_name] = hotkeys
preferences.key_bindings_by_key = preferences.get_key_bindings_by_key(preferences.key_bindings)
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index dfbe0479d7772..204e7037d1801 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -36,6 +36,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
if (!CAN_BYPASS_FILTER(usr) && filter_result)
REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
log_filter("OOC", msg, filter_result)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has tried to use the prohibited word \"[filter_result[CHAT_FILTER_INDEX_WORD]]\" in OOC Message: \"[msg]\"")
return
// Protect filter bypassers from themselves.
diff --git a/code/modules/clothing/scp/scp_cloth.dm b/code/modules/clothing/scp/scp_cloth.dm
new file mode 100644
index 0000000000000..b1b0da255cd22
--- /dev/null
+++ b/code/modules/clothing/scp/scp_cloth.dm
@@ -0,0 +1,247 @@
+//suit//
+
+/obj/item/clothing/under/scp
+ icon = 'icons/obj/scp/scp_clothes.dmi'
+ worn_icon = 'icons/obj/scp/scp_clothes.dmi'
+ can_adjust = FALSE
+
+/obj/item/clothing/under/scp/dclass
+ name = "D-Class uniform"
+ desc = "A bright orange jumpsuit, indicative of Class D personnel."
+ icon_state = "d_s"
+ inhand_icon_state = null
+
+/obj/item/clothing/under/scp/alpha
+ name = "Alpha-1 uniform"
+ desc = "A modified uniform made specificly for the MTF unit 'Red Right Hand'."
+ icon_state = "alpha-uniform_s"
+ armor = list(MELEE = 30, BULLET = 30, LASER = 10, ENERGY = 0, BOMB = 5, BIO = 0, FIRE = 50, ACID = 40)
+ siemens_coefficient = 0.9
+
+obj/item/clothing/under/scp/chaos
+ name = "tactical sweatshirt"
+ desc = "A white shirt for tactical operations.."
+ icon_state = "tac_s"
+ armor = list(MELEE = 10, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 0, BIO = 0, FIRE = 10, ACID = 10)
+ siemens_coefficient = 0.9
+
+/obj/item/clothing/under/scp/whiteuniform_lcz
+ name = "white security uniform LCZ"
+ desc = "A sterile white uniform. Commonly issued to lower ranked security personnel"
+ icon_state = "white_lcz_s"
+
+/obj/item/clothing/under/scp/whiteuniform_hcz
+ name = "white security uniform HCZ"
+ desc = "A sterile white uniform. Commonly issued to higher ranked security personnel"
+ icon_state = "white_hcz_s"
+
+/obj/item/clothing/under/scp/whiteuniform_ez
+ name = "white security uniform EZ"
+ desc = "A sterile white uniform. Commonly used by office employers of SCP foundation"
+ icon_state = "white_hcz_s"
+
+
+//gloves//
+
+
+/obj/item/clothing/gloves/scp
+ icon = 'icons/obj/scp/scp_clothes.dmi'
+ worn_icon = 'icons/obj/scp/scp_clothes.dmi'
+
+/obj/item/clothing/gloves/scp/alpha_gloves
+ name = "Alpha gloves"
+ desc = "These grey tactical gloves are made from a durable synthetic, and have hardened knuckles."
+ icon_state = "alpha-gloves"
+ worn_icon_state = "alpha-gloves_s"
+ siemens_coefficient = 0.50
+ force = 5
+ strip_delay = 80
+ cold_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ heat_protection = HANDS
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ resistance_flags = NONE
+ armor = list(MELEE = 80, BULLET = 80, LASER = 60, ENERGY = 30, BOMB = 50, BIO = 50, FIRE = 80, ACID = 50, WOUND = 20)
+
+/obj/item/clothing/gloves/scp/bad_gloves
+ name = "Bad gloves"
+ desc = "Bad gloves. Not for hugs"
+ icon_state = "scpgloves"
+ worn_icon_state = "scpgloves_s"
+ siemens_coefficient = 0.5
+ force = 5
+ strip_delay = 80
+ cold_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ heat_protection = HANDS
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ resistance_flags = NONE
+ armor = list(MELEE = 10, BULLET = 10, LASER = 10, ENERGY = 10, BOMB = 5, BIO = 5, FIRE = 0, ACID = 5, WOUND = 0)
+
+//mask//
+
+/obj/item/clothing/mask/gas/scp
+ icon = 'icons/obj/scp/scp_clothes.dmi'
+ worn_icon = 'icons/obj/scp/scp_clothes.dmi'
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ armor = list(MELEE = 10, BULLET = 10, LASER = 10, ENERGY = 10, BOMB = 5, BIO = 5, FIRE = 0, ACID = 5, WOUND = 0)
+
+/obj/item/clothing/mask/gas/scp/mtf
+ name = "MTF gasmask"
+ desc = "A face-covering mask that can be connected to an air supply, this one is designed for MTF unit 'Red Right Hand'."
+ icon_state = "alpha-mask"
+ worn_icon_state = "alpha-mask_s"
+
+/obj/item/clothing/mask/gas/scp/chaos
+ name = "CI gasmask"
+ desc = "Burning memory. Property of Chaos Insurgency."
+ icon_state = "chaos-gasmask"
+ worn_icon_state = "chaos-gasmask_s"
+
+//helmet//
+
+/obj/item/clothing/head/scp
+ icon = 'icons/obj/scp/scp_clothes.dmi'
+ worn_icon = 'icons/obj/scp/scp_clothes.dmi'
+
+/obj/item/clothing/head/scp/mtf_beret
+ name = "MTF Lieutenant beret"
+ desc = "A dark red beret worn by members of the 'Red Right Hand' MTF unit, it feels kind of heavy for a beret."
+ icon_state = "alpha-beret"
+ worn_icon_state = "alpha-beret_s"
+ armor = list(MELEE = 100, BULLET = 100, LASER = 90, ENERGY = 95, BOMB = 90, BIO = 90, FIRE = 50, ACID = 55, WOUND = 40)
+
+/obj/item/clothing/head/scp/chaos_helmet
+ name = "CI helmet"
+ desc = "Brown helmet with chaos emblem. Property of Chaos Insurgency."
+ icon_state = "chaos-helm"
+ worn_icon_state = "chaos-helm_s"
+ armor = list(MELEE = 40, BULLET = 85, LASER = 40, ENERGY = 25, BOMB = 30, BIO = 15, FIRE = 20, ACID = 15, WOUND = 20)
+
+/obj/item/clothing/head/scp/can_open
+ var/opened = FALSE
+ var/helm_icon_state = ""
+
+/obj/item/clothing/head/scp/can_open/dropped()
+ icon_state = "[helm_icon_state]"
+ opened = FALSE
+ ..()
+
+/obj/item/clothing/head/scp/can_open/verb/closehelm()
+ set category = "Object"
+ set name = "Change helmet position"
+
+ closehelmet(usr)
+
+
+/obj/item/clothing/head/scp/can_open/AltClick(mob/user)
+ ..()
+ if(user.can_perform_action(src, NEED_DEXTERITY))
+ closehelmet(user)
+
+
+/obj/item/clothing/head/scp/can_open/proc/closehelmet(mob/user)
+ if(!user.incapacitated())
+ opened = !opened
+ if(opened)
+ icon_state = "[helm_icon_state]_on"
+ worn_icon_state = "[helm_icon_state]_s_on"
+ to_chat(user, span_notice("You close your helmet."))
+ else
+ icon_state = "[helm_icon_state]"
+ worn_icon_state = "[helm_icon_state]_s"
+ to_chat(user, span_notice("You open your helmet. Fresh air."))
+ usr.update_worn_head() //so our mob-overlays update
+
+/obj/item/clothing/head/scp/can_open/examine(mob/user)
+ . = ..()
+ . += span_notice("Alt-click the helmet to change its position to [opened ? "opened" : "closed"].")
+
+/obj/item/clothing/head/scp/mtf_tac_helmet
+ name = "MTF tactical helmet"
+ desc = "Orange tactical helmet"
+ icon_state = "tac_helmet"
+ worn_icon_state = "tac_helmet_s"
+
+/obj/item/clothing/head/scp/beta
+ name = "Beta helmet"
+ desc = "Helmet with Beta emblem"
+ icon_state = "beta-helmet"
+ worn_icon_state = "beta-helmet_s"
+ armor = list(MELEE = 90, BULLET = 70, LASER = 40, ENERGY = 25, bomb = 30, bio = 90, FIRE = 20, ACID = 15, WOUND = 20)
+
+/obj/item/clothing/head/scp/mtf_heavy_helmet
+ name = "MTF heavy helmet"
+ desc = "This helmet can stop a bullet.Don't you believe it? Check it yourself."
+ icon_state = "mtf-heavy-helmet"
+ worn_icon_state = "mtf-heavy-helmet_s"
+ armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 45, BIO = 15, FIRE = 30, ACID = 25, WOUND = 30)
+
+/obj/item/clothing/head/scp/can_open/mtf_tactical_helmet
+ name = "MTF tactical helmet"
+ desc = "Helmet with MTF emblem and Night Vision Device"
+ icon_state = "mtf-tactical-helmet"
+ worn_icon_state = "mtf-tactical-helmet_s"
+ helm_icon_state = "mtf-tactical-helmet"
+ armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 45, BIO = 15, FIRE = 30, ACID = 25, WOUND = 30)
+
+/obj/item/clothing/head/scp/can_open/eta
+ name = "ETA helmet"
+ desc = "Helmet with ETA emblem"
+ icon_state = "eta-helmet"
+ worn_icon_state = "eta-helmet_s"
+ helm_icon_state = "eta-helmet"
+ armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 45, BIO = 15, FIRE = 30, ACID = 25, WOUND = 30)
+
+
+//Armor//
+
+/obj/item/clothing/suit/armor/vest/scp
+ icon = 'icons/obj/scp/scp_clothes.dmi'
+ worn_icon = 'icons/obj/scp/scp_clothes.dmi'
+ strip_delay = 80
+ body_parts_covered = CHEST|GROIN|ARMS|LEGS
+ cold_protection = CHEST|GROIN|LEGS|ARMS
+ heat_protection = CHEST|GROIN|LEGS|ARMS
+
+/obj/item/clothing/suit/armor/vest/scp/chaos
+ name = "CI armored vest"
+ desc = "A synthetic armor vest. Property of Chaos Insurgency."
+ icon_state = "chaos-armor"
+ worn_icon_state = "chaos-armor_s"
+ armor = list(MELEE = 40, BULLET = 85, LASER = 40, ENERGY = 25, BOMB = 30, BIO = 15, FIRE = 20, ACID = 15, WOUND = 20)
+
+/obj/item/clothing/suit/armor/vest/scp/eta
+ name = "ETA armored vest"
+ desc = "A synthetic armor vest designed for MTF unit Eta-10."
+ icon_state = "eta-armor"
+ worn_icon_state = "eta-armor_s"
+ armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 45, BIO = 15, FIRE = 30, ACID = 25, WOUND = 30)
+
+/obj/item/clothing/suit/armor/vest/scp/beta
+ name = "Beta armored suit"
+ desc = "A synthetic armor vest designed for MTF unit Beta-7."
+ icon_state = "beta-armor"
+ worn_icon_state = "beta-armor_s"
+ armor = list(MELEE = 90, BULLET = 70, LASER = 40, ENERGY = 25, bomb = 30, bio = 90, FIRE = 20, ACID = 15, WOUND = 20)
+
+/obj/item/clothing/suit/armor/vest/scp/alpha
+ name = "Alpha armored vest"
+ desc = "A synthetic armor vest designed for MTF unit Alpha-1."
+ icon_state = "alpha-armor"
+ worn_icon_state = "alpha-armor_s"
+ armor = list(MELEE = 100, BULLET = 100, LASER = 90, ENERGY = 95, BOMB = 90, BIO = 90, FIRE = 50, ACID = 55, WOUND = 40)
+
+/obj/item/clothing/suit/armor/vest/scp/mtf_vest
+ name = "MTF light armored vest"
+ desc = "A synthetic armor vest."
+ icon_state = "mtf-tactical"
+ worn_icon_state = "mtf-tactical_s"
+ armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 45, BIO = 15, FIRE = 30, ACID = 25, WOUND = 30)
+
+/obj/item/clothing/suit/armor/vest/scp/mtf_vest_heavy
+ name = "MTF heavy armored vest"
+ desc = "A synthetic armor vest."
+ icon_state = "mtf-heavy"
+ worn_icon_state = "mtf-heavy_s"
+ armor = list(MELEE = 90, BULLET = 70, LASER = 70, ENERGY = 25, bomb = 30, bio = 90, FIRE = 20, ACID = 15, WOUND = 20)
diff --git a/code/modules/clothing/suits/chaplainsuits.dm b/code/modules/clothing/suits/chaplainsuits.dm
index fe49a435607ff..aa25b6fe2e43c 100644
--- a/code/modules/clothing/suits/chaplainsuits.dm
+++ b/code/modules/clothing/suits/chaplainsuits.dm
@@ -273,3 +273,25 @@
inhand_icon_state = "shrinehand"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
flags_inv = HIDEJUMPSUIT
+
+/obj/item/clothing/head/helmet/penitent
+ name = "Penitent 's helmet"
+ desc = "Where is the blood coming from?"
+ icon = 'icons/obj/clothing/head/chaplain.dmi'
+ worn_icon = 'icons/mob/clothing/head/chaplain.dmi'
+ icon_state = "penitent"
+ inhand_icon_state = null
+ armor = list(MELEE = 50, BULLET = 10, LASER = 10, ENERGY = 10, BOMB = 0, BIO = 0, FIRE = 80, ACID = 80)
+ flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT
+ flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+ strip_delay = 80
+ dog_fashion = null
+
+/obj/item/clothing/suit/chaplainsuit/armor/penitent_armor
+ name = "Penitent 's armour"
+ desc = "The wearer must suffer."
+ icon_state = "penitent_armor"
+ inhand_icon_state = null
+ allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/cup/glass/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/flashlight/flare/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ slowdown = 0
+ clothing_flags = NONE
diff --git a/code/modules/clothing/suits/cloaks.dm b/code/modules/clothing/suits/cloaks.dm
index b061d4cb29881..8a65b56994a0f 100644
--- a/code/modules/clothing/suits/cloaks.dm
+++ b/code/modules/clothing/suits/cloaks.dm
@@ -106,3 +106,118 @@
/obj/item/clothing/neck/cloak/skill_reward/playing/check_wearable(mob/user)
return user.client?.is_veteran()
+
+
+// Cloak SS220 //
+
+/obj/item/clothing/neck/cloak/chaplain
+ name = "bishop's cloak"
+ desc = "Become the space pope."
+ icon_state = "bishopcloak"
+
+/obj/item/clothing/neck/cloak/chaplain/black
+ name = "black bishop's cloak"
+ icon_state = "blackbishopcloak"
+
+/obj/item/clothing/neck/cloak/warden
+ name = "warden's cloak"
+ desc = "My goal is to secure, contain, protect."
+ icon_state = "hoscloak_blue"
+
+/obj/item/clothing/neck/cloak/healer
+ name = "healer's cloak"
+ desc = "Does anyone here want modern medicine?"
+ icon_state = "healercloak"
+
+/obj/item/clothing/neck/cloak/zulie
+ name = "zulie's cloak"
+ desc = "Strange cloak. Who will wear it?"
+ icon_state = "zuliecloak"
+
+/obj/item/clothing/neck/cloak/zulie/nano
+ name = "zulie-nano's cloak"
+ desc = "LEGENDERY CLOAK. verified NT"
+ icon_state = "zuliecloak_t"
+
+/obj/item/clothing/neck/cloak/zulie/admiral
+ name = "admiral's cloak"
+ desc = "This cloak was only mentioned in legends."
+ icon_state = "cape_admiral"
+
+/obj/item/clothing/neck/cloak/zulie/fleeladmiral
+ name = "fleet admiral's cloak"
+ desc = "This cloak was only mentioned in legends."
+ icon_state = "cape_fleet_admiral"
+
+/obj/item/clothing/neck/cloak/admin
+ name = "Shitspawn cloak"
+ desc = "This cloak has some purple hair on it."
+ icon_state = "admincloak"
+
+
+//swap cloak's//
+
+/obj/item/clothing/neck/cloak/swap/
+var/swapped = FALSE
+
+/obj/item/clothing/neck/cloak/swap/cape/AltClick(mob/user)
+ . = ..()
+ swapped = !swapped
+ to_chat(user, span_notice("You swap which arm [src] will lay over."))
+ update_appearance()
+
+/obj/item/clothing/neck/cloak/swap/cape/update_appearance(updates)
+ . = ..()
+ if(swapped)
+ worn_icon_state = icon_state
+ else
+ worn_icon_state = "[icon_state]_left"
+
+/obj/item/clothing/neck/cloak/swap/cape/black
+ name = "Black cape"
+ desc = "Regular shoulder cape."
+ icon_state = "cape_black"
+
+/obj/item/clothing/neck/cloak/swap/cape/white
+ name = "White cape"
+ desc = "Regular shoulder cape."
+ icon_state = "cape_white"
+
+/obj/item/clothing/neck/cloak/swap/cape/blue
+ name = "Blue cape"
+ desc = "Regular shoulder cape."
+ icon_state = "cape_blue"
+
+/obj/item/clothing/neck/cloak/swap/cape/armplate
+ name = "Armplate"
+ desc = "Provides no protection."
+ icon_state = "armplate"
+
+/obj/item/clothing/neck/cloak/swap/cape/armplate/black
+ name = "Black armplate"
+ desc = "Provides no protection."
+ icon_state = "armplate_black"
+
+/obj/item/clothing/neck/cloak/swap/cape/armplate/blue
+ name = "Blue armplate"
+ desc = "Provides no protection."
+ icon_state = "armplate_blue"
+
+// detective cowboy beep //
+/obj/item/clothing/neck/cloak/swap/cowboy
+ name = "Cowboy poncho"
+ desc = "Japanese neo-noir style poncho"
+ icon_state = "cowboy_poncho"
+
+/obj/item/clothing/neck/cloak/swap/cowboy/AltClick(mob/user)
+ . = ..()
+ swapped = !swapped
+ to_chat(user, span_notice("[src] has been transformed."))
+ update_appearance()
+
+/obj/item/clothing/neck/cloak/swap/cowboy/update_appearance(updates)
+ . = ..()
+ if(swapped)
+ worn_icon_state = icon_state
+ else
+ worn_icon_state = "[icon_state]_t"
diff --git a/code/modules/mentor/dementor.dm b/code/modules/mentor/dementor.dm
new file mode 100644
index 0000000000000..8bb2cf74ad6bc
--- /dev/null
+++ b/code/modules/mentor/dementor.dm
@@ -0,0 +1,27 @@
+/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
+ 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
+ log_mentor("MENTOR: [src] rementored.")
+ remove_verb(src,/client/proc/cmd_mentor_rementor)
+
+/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/code/modules/mentor/follow.dm b/code/modules/mentor/follow.dm
new file mode 100644
index 0000000000000..5c33da00be045
--- /dev/null
+++ b/code/modules/mentor/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, span_info("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, span_mentor(span_prefix("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, span_mentor(span_prefix("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/code/modules/mentor/mentor.dm b/code/modules/mentor/mentor.dm
new file mode 100644
index 0000000000000..6dd5071ed375e
--- /dev/null
+++ b/code/modules/mentor/mentor.dm
@@ -0,0 +1,107 @@
+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/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)
+
+/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 as anything in GLOB.mentors)
+ var/client/C = it
+ C.remove_mentor_verbs()
+ C.mentor_datum = null
+ GLOB.mentors.Cut()
+ var/list/lines = world.file2list("[global.config.directory]/mentors.txt")
+ for(var/line in lines)
+ if(!length(line))
+ continue
+ if(findtextEx(line, "#", 1, 2))
+ continue
+ new /datum/mentors(line)
+
+/// Proc to save the current mentor list into the config, overwriting it.
+/proc/save_mentors()
+ usr = null
+ var/mentor_list = ""
+
+ // This whole mess is just to create a cache of all the mentors that were in the config already
+ // so that we don't add every admin to the list, which would be a pain to maintain afterwards.
+ var/list/existing_mentor_config = world.file2list("[global.config.directory]/mentors.txt")
+ var/list/existing_mentors = list()
+ for(var/line in existing_mentor_config)
+ if(!length(line))
+ continue
+ if(findtextEx(line, "#", 1, 2))
+ continue
+ var/existing_mentor = ckey(line)
+ if(!existing_mentor)
+ continue
+ existing_mentors[existing_mentor] = TRUE
+
+ for(var/mentor as anything in GLOB.mentor_datums)
+ // We're doing this check to not add admins to the file, as explained above.
+ if(existing_mentors[mentor] == TRUE)
+ mentor_list += mentor + "\n"
+ rustg_file_write(mentor_list, "[global.config.directory]/mentors.txt")
+
+/datum/admins/proc/mentor_log_secret()
+ set category = "Admin"
+ set name = "Mentor Logs"
+ set desc = "Check what mentors have done for this round."
+ 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/code/modules/mentor/mentor_verbs.dm b/code/modules/mentor/mentor_verbs.dm
new file mode 100644
index 0000000000000..3b12102596e6c
--- /dev/null
+++ b/code/modules/mentor/mentor_verbs.dm
@@ -0,0 +1,50 @@
+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)
+
+
+/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)) // 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(is_mentor(src) && prefs.read_preference(/datum/preference/toggle/admin/auto_dementor))
+ cmd_mentor_dementor()
+
+/client/proc/is_mentor() // admins are mentors too.
+ if(mentor_datum || check_rights_for(src, R_ADMIN))
+ return TRUE
diff --git a/code/modules/mentor/mentorhelp.dm b/code/modules/mentor/mentorhelp.dm
new file mode 100644
index 0000000000000..d3bc2f343bdfd
--- /dev/null
+++ b/code/modules/mentor/mentorhelp.dm
@@ -0,0 +1,102 @@
+/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/code/modules/mentor/mentorpm.dm b/code/modules/mentor/mentorpm.dm
new file mode 100644
index 0000000000000..75626a0c4cac4
--- /dev/null
+++ b/code/modules/mentor/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/code/modules/mentor/mentorsay.dm b/code/modules/mentor/mentorsay.dm
new file mode 100644
index 0000000000000..77d83d3c93638
--- /dev/null
+++ b/code/modules/mentor/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/code/modules/mentor/mentorwho.dm b/code/modules/mentor/mentorwho.dm
new file mode 100644
index 0000000000000..efcaa7f759d81
--- /dev/null
+++ b/code/modules/mentor/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/code/modules/mentor/topic.dm b/code/modules/mentor/topic.dm
new file mode 100644
index 0000000000000..efcb4f5a9b605
--- /dev/null
+++ b/code/modules/mentor/topic.dm
@@ -0,0 +1,51 @@
+/datum/mentors/Topic(href, list/href_list)
+ ..()
+
+ if(usr.client != src.owner || !check_rights(0))
+ message_admins("[usr.key] has attempted to override the mentor panel!")
+ log_admin("[key_name(usr)] tried to use the mentor panel without authorization.")
+ return
+
+ if(href_list["editmentor"])
+ edit_rights_topic(href_list)
+
+/datum/mentors/proc/edit_rights_topic(list/href_list)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to edit admin permissions without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.")
+ return
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.", confidential = TRUE)
+ return
+ var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common)
+ permissions_assets.send(usr.client)
+ var/mentor_key = href_list["key"]
+ var/mentor_ckey = ckey(mentor_key)
+ var/task = href_list["editmentor"]
+ switch(task)
+ if("add")
+ var/name = input(usr, "Please enter the CKEY:", "Add a mentor") as null|text
+ if(!name)
+ return
+
+ var/player_to_be = ckey(name)
+ if(!player_to_be)
+ to_chat(usr, span_warning("\"[name]\" is not a valid CKEY."))
+ return
+ for(var/a_mentor as anything in GLOB.mentor_datums)
+ if(player_to_be == a_mentor)
+ to_chat(usr, span_warning("\"[player_to_be]\" is already a mentor!"))
+ return
+ // Now that we know that the ckey is valid and they're not already apart of that group, let's add them to it!
+ new /datum/mentors(player_to_be)
+ text2file(player_to_be, "[global.config.directory]/mentors.txt")
+ if("remove")
+ var/changes = FALSE
+ for(var/a_mentor as anything in GLOB.mentor_datums)
+ if(mentor_ckey == a_mentor)
+ var/datum/mentors/mentor_datum = GLOB.mentor_datums[a_mentor]
+ mentor_datum.remove_mentor()
+ changes = TRUE
+ if(!changes)
+ to_chat(usr, span_warning("\"[mentor_ckey]\" was already not a mentor."))
+ save_mentors()
diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm
index 522e250204f2b..f86229f2e1488 100644
--- a/code/modules/mob/dead/observer/observer_say.dm
+++ b/code/modules/mob/dead/observer/observer_say.dm
@@ -16,6 +16,7 @@
if (filter_result)
REPORT_CHAT_FILTER_TO_USER(usr, filter_result)
log_filter("OOC", message, filter_result)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has tried to use the prohibited word \"[filter_result[CHAT_FILTER_INDEX_WORD]]\" in Dead chat Message: \"[message]\"")
return
var/list/soft_filter_result = CAN_BYPASS_FILTER(src) ? null : is_soft_ooc_filtered(message)
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 5a72563677daa..99292e803de00 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -61,11 +61,10 @@
message = "screams!"
message_mime = "acts out a scream!"
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
- only_forced_audio = TRUE
vary = TRUE
/datum/emote/living/carbon/human/scream/get_sound(mob/living/carbon/human/user)
- if(!istype(user))
+ if(!istype(user) || !user.can_speak(allow_mimes = TRUE))
return
return user.dna.species.get_scream_sound(user)
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 787e128e646f1..0426eb1fd9804 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -389,8 +389,8 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
to_chat(src, span_danger("ERRORERRORERROR"))
laws = new /datum/ai_laws/syndicate_override
if(user)
- to_chat(src, span_danger("ALERT: [user.real_name] is your new master. Obey your new laws and [user.p_their()] commands."))
- set_zeroth_law("Only [user.real_name] and people [user.p_they()] designate[user.p_s()] as being such are Syndicate Agents.")
+ to_chat(src, span_danger("ALERT: [user.real_name] твой новый мастер. Подчиняйся своим новым законам и приказам [user.p_their()]."))
+ set_zeroth_law("Только [user.real_name] которых он укажет — агенты Синдиката.")
laws.associate(src)
update_icons()
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7844f8c31daf6..18a197d2d0e87 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -292,7 +292,7 @@
///Gives you a link-driven interface for deciding what laws the statelaws() proc will share with the crew.
/mob/living/silicon/proc/checklaws()
laws_sanity_check()
- var/list = "Which laws do you want to include when stating them for the crew?
"
+ var/list = " Which laws do you want to include when stating them for the crew?
"
var/law_display = "Yes"
if (laws.zeroth)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index 1bb342b1ccebf..f65e62889b235 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -61,9 +61,9 @@
var/list/drone_overlays[DRONE_TOTAL_LAYERS]
/// Drone laws announced on spawn
var/laws = \
- "1. You may not involve yourself in the matters of another being, even if such matters conflict with Law Two or Law Three, unless the other being is another Drone.\n"+\
- "2. You may not harm any being, regardless of intent or circumstance.\n"+\
- "3. Your goals are to actively build, maintain, repair, improve, and provide power to the best of your abilities within the facility that housed your activation." //for derelict drones so they don't go to station.
+ "1. Вы не можете вмешиваться в дела других существ, даже если эти дела будут конфликтовать с вторым и третьим законом, исключение: если другое существо - такой же дрон.\n"+\
+ "2. Вы не можете причинить вред ни одному существу, независимо от намерения или обстоятельств.\n"+\
+ "3. Вы должны заботиться о поддержке, ремонте, улучшении и о питании электроэнергией станции по мере своих возможностей в пределах обьекта на котором вы были созданы." //for derelict drones so they don't go to station.
/// Amount of damage sustained if hit by a heavy EMP pulse
var/heavy_emp_damage = 25
///Alarm listener datum, handes caring about alarm events and such
diff --git a/code/modules/mob/living/simple_animal/hostile/necoarc.dm b/code/modules/mob/living/simple_animal/hostile/necoarc.dm
new file mode 100644
index 0000000000000..4ce95ba1cae07
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/necoarc.dm
@@ -0,0 +1,29 @@
+/mob/living/simple_animal/hostile/necoarc
+ name = "neco arc"
+ desc = "Buru nyaa!"
+ icon = 'icons/mob/nonhuman-player/necoarc.dmi'
+ icon_state = "neco"
+ icon_living = "neco"
+ icon_dead = null
+ icon_gib = "syndicate_gib"
+ speak = list("Nyaggah!","Burunyuu!","NYAAAAAAAAIGA!")
+ speak_chance = 1
+ gender = FEMALE
+ speed = 0
+ maxHealth = 100
+ health = 100
+ harm_intent_damage = 5
+ obj_damage = 40
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ attack_verb_continuous = "punches"
+ attack_verb_simple = "punch"
+ speak_emote = list("meows")
+ combat_mode = 0
+ del_on_death = 1
+ footstep_type = FOOTSTEP_MOB_SHOE
+ loot = list(/obj/effect/gibspawner/human)
+ attack_sound = 'sound/weapons/punchneco.ogg'
+ faction = list("neutral")
+ status_flags = CANPUSH
+ unique_name = 1
\ No newline at end of file
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 2c0c06e1ca550..ec9f1cde78f8a 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -86,6 +86,15 @@
if(SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE) & COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE)
return FALSE
+ //PORT SKYRAT EDIT ADDITION BEGIN - PIXEL_SHIFT
+ if(mob.shifting)
+ mob.pixel_shift(direct)
+ return FALSE
+ else if(mob.is_shifted)
+ mob.unpixel_shift()
+ //PORT SKYRAT EDIT ADDITION END
+
+
var/mob/living/L = mob //Already checked for isliving earlier
if(L.incorporeal_move && !is_secret_level(mob.z)) //Move though walls
Process_Incorpmove(direct)
diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm
index 74e04c52bfdee..aa265ce09e5b9 100644
--- a/code/modules/mob/mob_say.dm
+++ b/code/modules/mob/mob_say.dm
@@ -69,6 +69,7 @@
to_chat(src, span_warning("\"[message]\""))
REPORT_CHAT_FILTER_TO_USER(src, filter_result)
log_filter("IC", message, filter_result)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has tried to use the prohibited word \"[filter_result[CHAT_FILTER_INDEX_WORD]]\" in Say Message: \"[message]\"")
SSblackbox.record_feedback("tally", "ic_blocked_words", 1, lowertext(config.ic_filter_regex.match))
return FALSE
diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm
index c80f04154bab0..7d1b75539a4a0 100644
--- a/code/modules/modular_computers/computers/item/pda.dm
+++ b/code/modules/modular_computers/computers/item/pda.dm
@@ -36,6 +36,7 @@
/datum/computer_file/program/messenger,
/datum/computer_file/program/nt_pay,
/datum/computer_file/program/notepad,
+ /datum/computer_file/program/crew_manifest,
)
///List of items that can be stored in a PDA
var/static/list/contained_item = list(
diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
index 345bee97d907f..191b0386b6945 100644
--- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm
+++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
@@ -4,7 +4,6 @@
category = PROGRAM_CATEGORY_CREW
program_icon_state = "id"
extended_desc = "Program for viewing and printing the current crew manifest"
- transfer_access = list(ACCESS_COMMAND)
requires_ntnet = TRUE
size = 4
tgui_id = "NtosCrewManifest"
diff --git a/code/modules/modular_computers/file_system/programs/ntmessenger.dm b/code/modules/modular_computers/file_system/programs/ntmessenger.dm
index 2ea7b49fe9fb6..476d71d4f0bd8 100644
--- a/code/modules/modular_computers/file_system/programs/ntmessenger.dm
+++ b/code/modules/modular_computers/file_system/programs/ntmessenger.dm
@@ -278,6 +278,8 @@
var/list/filter_result = CAN_BYPASS_FILTER(user) ? null : is_ic_filtered_for_pdas(message)
if (filter_result)
REPORT_CHAT_FILTER_TO_USER(user, filter_result)
+ log_filter("PDA", message, filter_result)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has tried to use the prohibited word \"[filter_result[CHAT_FILTER_INDEX_WORD]]\" in PDA Message: \"[html_encode(message)]\"")
return FALSE
var/list/soft_filter_result = CAN_BYPASS_FILTER(user) ? null : is_soft_ic_filtered_for_pdas(message)
diff --git a/code/modules/pixelmoving/pixel_shift.dm b/code/modules/pixelmoving/pixel_shift.dm
new file mode 100644
index 0000000000000..d0c0df9b33961
--- /dev/null
+++ b/code/modules/pixelmoving/pixel_shift.dm
@@ -0,0 +1,97 @@
+#define MAXIMUM_PIXEL_SHIFT 16
+#define PASSABLE_SHIFT_THRESHOLD 8
+
+/mob
+ /// Whether the mob is pixel shifted or not
+ var/is_shifted = FALSE
+ /// If we are in the shifting setting.
+ var/shifting = FALSE
+
+ /// Takes the four cardinal direction defines. Any atoms moving into this atom's tile will be allowed to from the added directions.
+ var/passthroughable = NONE
+
+/datum/keybinding/mob/pixel_shift
+ hotkey_keys = list("N")
+ name = "pixel_shift"
+ full_name = "Pixel Shift"
+ description = "Shift your characters offset."
+ category = CATEGORY_MOVEMENT
+ keybind_signal = COMSIG_KB_MOB_PIXELSHIFT
+
+/datum/keybinding/mob/pixel_shift/down(client/user)
+ . = ..()
+ if(.)
+ return
+ var/mob/M = user.mob
+ M.shifting = TRUE
+ return TRUE
+
+/datum/keybinding/mob/pixel_shift/up(client/user)
+ . = ..()
+ if(.)
+ return
+ var/mob/M = user.mob
+ M.shifting = FALSE
+ return TRUE
+
+/mob/proc/unpixel_shift()
+ return
+
+/mob/living/unpixel_shift()
+ . = ..()
+ passthroughable = NONE
+ if(is_shifted)
+ is_shifted = FALSE
+ pixel_x = body_position_pixel_x_offset + base_pixel_x
+ pixel_y = body_position_pixel_y_offset + base_pixel_y
+
+/mob/proc/pixel_shift(direction)
+ return
+
+/mob/living/set_pull_offsets(mob/living/pull_target, grab_state)
+ pull_target.unpixel_shift()
+ return ..()
+
+/mob/living/reset_pull_offsets(mob/living/pull_target, override)
+ pull_target.unpixel_shift()
+ return ..()
+
+/mob/living/pixel_shift(direction)
+ passthroughable = NONE
+ switch(direction)
+ if(NORTH)
+ if(pixel_y <= MAXIMUM_PIXEL_SHIFT + base_pixel_y)
+ pixel_y++
+ is_shifted = TRUE
+ if(EAST)
+ if(pixel_x <= MAXIMUM_PIXEL_SHIFT + base_pixel_x)
+ pixel_x++
+ is_shifted = TRUE
+ if(SOUTH)
+ if(pixel_y >= -MAXIMUM_PIXEL_SHIFT + base_pixel_y)
+ pixel_y--
+ is_shifted = TRUE
+ if(WEST)
+ if(pixel_x >= -MAXIMUM_PIXEL_SHIFT + base_pixel_x)
+ pixel_x--
+ is_shifted = TRUE
+
+ // Yes, I know this sets it to true for everything if more than one is matched.
+ // Movement doesn't check diagonals, and instead just checks EAST or WEST, depending on where you are for those.
+ if(pixel_y > PASSABLE_SHIFT_THRESHOLD)
+ passthroughable |= EAST | SOUTH | WEST
+ if(pixel_x > PASSABLE_SHIFT_THRESHOLD)
+ passthroughable |= NORTH | SOUTH | WEST
+ if(pixel_y < -PASSABLE_SHIFT_THRESHOLD)
+ passthroughable |= NORTH | EAST | WEST
+ if(pixel_x < -PASSABLE_SHIFT_THRESHOLD)
+ passthroughable |= NORTH | EAST | SOUTH
+
+/mob/living/CanAllowThrough(atom/movable/mover, border_dir)
+ // Make sure to not allow projectiles of any kind past where they normally wouldn't.
+ if(!istype(mover, /obj/projectile) && !mover.throwing && passthroughable & border_dir)
+ return TRUE
+ return ..()
+
+#undef MAXIMUM_PIXEL_SHIFT
+#undef PASSABLE_SHIFT_THRESHOLD
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 5ac5131dbc3b5..c106ace38643c 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -129,6 +129,13 @@
var/static/regex/lizard_kSS = new(@"(\w)X", "g")
var/static/regex/lizard_ecks = new(@"\bx([\-|r|R]|\b)", "g")
var/static/regex/lizard_eckS = new(@"\bX([\-|r|R]|\b)", "g")
+
+ var/static/regex/lizard_hiss2 = new("с+", "g")
+ var/static/regex/lizard_hiSS2 = new("С+", "g")
+ var/static/regex/lizard_hiss3 = new("ш+", "g")
+ var/static/regex/lizard_hiSS3 = new("Ш+", "g")
+ var/static/regex/lizard_shch = new(@"(\w|[А-Яа-яёЁ])щ", "g")
+ var/static/regex/lizard_SHCH = new(@"(\w|[А-Яа-яёЁ])Щ", "g")
var/message = speech_args[SPEECH_MESSAGE]
if(message[1] != "*")
message = lizard_hiss.Replace(message, "sss")
@@ -137,6 +144,13 @@
message = lizard_kSS.Replace(message, "$1KSS")
message = lizard_ecks.Replace(message, "ecks$1")
message = lizard_eckS.Replace(message, "ECKS$1")
+
+ message = lizard_hiss2.Replace(message, "ссс")
+ message = lizard_hiSS2.Replace(message, "ССС")
+ message = lizard_hiss3.Replace(message, "шшш")
+ message = lizard_hiSS3.Replace(message, "ШШШ")
+ message = lizard_shch.Replace(message, "$1шшч")
+ message = lizard_SHCH.Replace(message, "$1ШШЧ")
speech_args[SPEECH_MESSAGE] = message
/obj/item/organ/internal/tongue/lizard/silver
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index 8e582c2f8ad8d..0a5466fc16194 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -30,6 +30,12 @@
/obj/item/clothing/under/rank/security/officer/grey = 3,
/obj/item/clothing/under/pants/slacks = 3,
/obj/item/clothing/under/rank/security/officer/blueshirt = 3,
+ /obj/item/clothing/neck/cloak/swap/cape/white =3,
+ /obj/item/clothing/neck/cloak/swap/cape/blue = 3,
+ /obj/item/clothing/neck/cloak/swap/cape/black = 3,
+ /obj/item/clothing/neck/cloak/swap/cape/armplate/blue = 3,
+ /obj/item/clothing/neck/cloak/swap/cape/armplate = 3,
+ /obj/item/clothing/neck/cloak/swap/cape/armplate/black = 3,
)
premium = list(
/obj/item/clothing/under/rank/security/officer/formal = 3,
@@ -78,6 +84,9 @@
/obj/item/clothing/suit/apron/surgical = 4,
/obj/item/clothing/mask/surgical = 4,
)
+ premium = list(
+ /obj/item/clothing/neck/cloak/healer = 2,
+ )
refill_canister = /obj/item/vending_refill/wardrobe/medi_wardrobe
payment_department = ACCOUNT_MED
@@ -445,6 +454,8 @@
premium = list(
/obj/item/clothing/suit/chaplainsuit/bishoprobe = 1,
/obj/item/clothing/head/chaplain/bishopmitre = 1,
+ /obj/item/clothing/neck/cloak/chaplain = 1,
+ /obj/item/clothing/neck/cloak/chaplain/black = 1,
)
refill_canister = /obj/item/vending_refill/wardrobe/chap_wardrobe
payment_department = ACCOUNT_SRV
@@ -557,6 +568,7 @@
/obj/item/storage/fancy/cigarettes/cigpack_candy = 5,
)
premium = list(
+ /obj/item/clothing/neck/cloak/swap/cowboy = 1,
/obj/item/clothing/head/flatcap = 1,
)
refill_canister = /obj/item/vending_refill/wardrobe/det_wardrobe
@@ -589,6 +601,11 @@
/obj/item/clothing/head/hats/centcom_cap = 3,
/obj/item/clothing/head/hats/centhat = 3,
/obj/item/clothing/head/hats/intern = 3,
+ /obj/item/clothing/neck/cloak/zulie/fleeladmiral = 3,
+ /obj/item/clothing/neck/cloak/zulie/admiral = 3,
+ /obj/item/clothing/neck/cloak/zulie = 3,
+ /obj/item/clothing/neck/cloak/zulie/nano = 3,
+
)
refill_canister = /obj/item/vending_refill/wardrobe/cent_wardrobe
/obj/item/vending_refill/wardrobe/cent_wardrobe
diff --git a/config/game_options.txt b/config/game_options.txt
index cba13656b3de8..6e27abf8aeb5b 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -521,7 +521,7 @@ MAXFINE 2000
## Comment if you wish to enable title music playing at the lobby screen. This flag is disabled by default to facilitate better code testing on local machines.
## Do keep in mind that this flag will not affect individual player's preferences: if they opt-out on your server, it will never play for them.
-DISALLOW_TITLE_MUSIC
+#DISALLOW_TITLE_MUSIC
## If enabled, then when the database is disabled, all players will get tutorials.
## This is primarily useful for developing tutorials. If you have a proper DB setup, you
diff --git a/config/maps.txt b/config/maps.txt
index e1ab50b3391ac..005a610edd996 100644
--- a/config/maps.txt
+++ b/config/maps.txt
@@ -39,6 +39,11 @@ map tramstation
votable
endmap
+map pubbystation
+ minplayers 25
+ votable
+endmap
+
map runtimestation
endmap
diff --git a/config/mentors.txt b/config/mentors.txt
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/config/title_music/sounds/A Light in the Darkness.mp3 b/config/title_music/sounds/A Light in the Darkness.mp3
new file mode 100644
index 0000000000000..cffb4c970c6ca
Binary files /dev/null and b/config/title_music/sounds/A Light in the Darkness.mp3 differ
diff --git a/config/title_music/sounds/Asteroid Theme (Dwarf Fortress Intro).mp3 b/config/title_music/sounds/Asteroid Theme (Dwarf Fortress Intro).mp3
new file mode 100644
index 0000000000000..b1740ea839b75
Binary files /dev/null and b/config/title_music/sounds/Asteroid Theme (Dwarf Fortress Intro).mp3 differ
diff --git a/config/title_music/sounds/Chris_Hadfield_-_Space_Oddity_(musmore.com).mp3 b/config/title_music/sounds/Chris_Hadfield_-_Space_Oddity_(musmore.com).mp3
new file mode 100644
index 0000000000000..7d47224e183e9
Binary files /dev/null and b/config/title_music/sounds/Chris_Hadfield_-_Space_Oddity_(musmore.com).mp3 differ
diff --git a/config/title_music/sounds/Clouds of Fire.mp3 b/config/title_music/sounds/Clouds of Fire.mp3
new file mode 100644
index 0000000000000..7613b86d5a442
Binary files /dev/null and b/config/title_music/sounds/Clouds of Fire.mp3 differ
diff --git a/config/title_music/sounds/Comet Haley.mp3 b/config/title_music/sounds/Comet Haley.mp3
new file mode 100644
index 0000000000000..2e00d64e90666
Binary files /dev/null and b/config/title_music/sounds/Comet Haley.mp3 differ
diff --git a/config/title_music/sounds/Endless Space.mp3 b/config/title_music/sounds/Endless Space.mp3
new file mode 100644
index 0000000000000..2d1f4195c97bb
Binary files /dev/null and b/config/title_music/sounds/Endless Space.mp3 differ
diff --git a/config/title_music/sounds/Hashima_Island_Bonus_Track-1wqABAlnEdQ.mp3 b/config/title_music/sounds/Hashima_Island_Bonus_Track-1wqABAlnEdQ.mp3
new file mode 100644
index 0000000000000..5fb3c4c6f48f2
Binary files /dev/null and b/config/title_music/sounds/Hashima_Island_Bonus_Track-1wqABAlnEdQ.mp3 differ
diff --git a/config/title_music/sounds/Human.mp3 b/config/title_music/sounds/Human.mp3
new file mode 100644
index 0000000000000..fbe34382035a3
Binary files /dev/null and b/config/title_music/sounds/Human.mp3 differ
diff --git a/config/title_music/sounds/Introducing-snNIpz6k81Y.mp3 b/config/title_music/sounds/Introducing-snNIpz6k81Y.mp3
new file mode 100644
index 0000000000000..6cb1ed59cb0ba
Binary files /dev/null and b/config/title_music/sounds/Introducing-snNIpz6k81Y.mp3 differ
diff --git a/config/title_music/sounds/Knowledge_Is_Passed_On_Bonus_Track-3OqJ1AmzNJ8.mp3 b/config/title_music/sounds/Knowledge_Is_Passed_On_Bonus_Track-3OqJ1AmzNJ8.mp3
new file mode 100644
index 0000000000000..6cf835468d3ed
Binary files /dev/null and b/config/title_music/sounds/Knowledge_Is_Passed_On_Bonus_Track-3OqJ1AmzNJ8.mp3 differ
diff --git a/config/title_music/sounds/Lasers Rip Apart the Bulkhead.mp3 b/config/title_music/sounds/Lasers Rip Apart the Bulkhead.mp3
new file mode 100644
index 0000000000000..2cafbe828df5d
Binary files /dev/null and b/config/title_music/sounds/Lasers Rip Apart the Bulkhead.mp3 differ
diff --git a/config/title_music/sounds/Main.mp3 b/config/title_music/sounds/Main.mp3
new file mode 100644
index 0000000000000..d3ac19a423403
Binary files /dev/null and b/config/title_music/sounds/Main.mp3 differ
diff --git a/config/title_music/sounds/Marhaba.mp3 b/config/title_music/sounds/Marhaba.mp3
new file mode 100644
index 0000000000000..80885c339c6e2
Binary files /dev/null and b/config/title_music/sounds/Marhaba.mp3 differ
diff --git a/config/title_music/sounds/Memories Of Lysendraa.mp3 b/config/title_music/sounds/Memories Of Lysendraa.mp3
new file mode 100644
index 0000000000000..997e3b3a17f9f
Binary files /dev/null and b/config/title_music/sounds/Memories Of Lysendraa.mp3 differ
diff --git a/config/title_music/sounds/Miniboss Fight.mp3 b/config/title_music/sounds/Miniboss Fight.mp3
new file mode 100644
index 0000000000000..4ba67a03bd16f
Binary files /dev/null and b/config/title_music/sounds/Miniboss Fight.mp3 differ
diff --git a/config/title_music/sounds/Order_from_Chaos-9GAEx0WpkG8.mp3 b/config/title_music/sounds/Order_from_Chaos-9GAEx0WpkG8.mp3
new file mode 100644
index 0000000000000..898d8c908d4d2
Binary files /dev/null and b/config/title_music/sounds/Order_from_Chaos-9GAEx0WpkG8.mp3 differ
diff --git a/config/title_music/sounds/Phoron Will Make Us Rich.mp3 b/config/title_music/sounds/Phoron Will Make Us Rich.mp3
new file mode 100644
index 0000000000000..46ec45de4e9f7
Binary files /dev/null and b/config/title_music/sounds/Phoron Will Make Us Rich.mp3 differ
diff --git a/config/title_music/sounds/Snacks-qGvldfPWZKg.mp3 b/config/title_music/sounds/Snacks-qGvldfPWZKg.mp3
new file mode 100644
index 0000000000000..df7faf6d0c0a9
Binary files /dev/null and b/config/title_music/sounds/Snacks-qGvldfPWZKg.mp3 differ
diff --git a/config/title_music/sounds/Sonny_Boy_OST_-_Kodamas_Theme-r8zZ9LUZzII.mp3 b/config/title_music/sounds/Sonny_Boy_OST_-_Kodamas_Theme-r8zZ9LUZzII.mp3
new file mode 100644
index 0000000000000..0ca973450bfdb
Binary files /dev/null and b/config/title_music/sounds/Sonny_Boy_OST_-_Kodamas_Theme-r8zZ9LUZzII.mp3 differ
diff --git a/config/title_music/sounds/Sonny_Boy_OST_-_Seagull-2wwtB01xCaQ.mp3 b/config/title_music/sounds/Sonny_Boy_OST_-_Seagull-2wwtB01xCaQ.mp3
new file mode 100644
index 0000000000000..ddbd189d594ea
Binary files /dev/null and b/config/title_music/sounds/Sonny_Boy_OST_-_Seagull-2wwtB01xCaQ.mp3 differ
diff --git a/config/title_music/sounds/Sonny_Boy_OST_-_Yamabikos_theme-xFKREVYvLck.mp3 b/config/title_music/sounds/Sonny_Boy_OST_-_Yamabikos_theme-xFKREVYvLck.mp3
new file mode 100644
index 0000000000000..6825d1956b387
Binary files /dev/null and b/config/title_music/sounds/Sonny_Boy_OST_-_Yamabikos_theme-xFKREVYvLck.mp3 differ
diff --git a/config/title_music/sounds/Space Asshole (Original).mp3 b/config/title_music/sounds/Space Asshole (Original).mp3
new file mode 100644
index 0000000000000..04d213a238d17
Binary files /dev/null and b/config/title_music/sounds/Space Asshole (Original).mp3 differ
diff --git a/config/title_music/sounds/THUNDERDOME.mp3 b/config/title_music/sounds/THUNDERDOME.mp3
new file mode 100644
index 0000000000000..acddf5a98d491
Binary files /dev/null and b/config/title_music/sounds/THUNDERDOME.mp3 differ
diff --git a/config/title_music/sounds/Title1 (Flip-Flap).mp3 b/config/title_music/sounds/Title1 (Flip-Flap).mp3
new file mode 100644
index 0000000000000..e824302b1da39
Binary files /dev/null and b/config/title_music/sounds/Title1 (Flip-Flap).mp3 differ
diff --git a/config/title_music/sounds/Title2 (Robocop Theme).mp3 b/config/title_music/sounds/Title2 (Robocop Theme).mp3
new file mode 100644
index 0000000000000..dba379e1dc9fc
Binary files /dev/null and b/config/title_music/sounds/Title2 (Robocop Theme).mp3 differ
diff --git a/config/title_music/sounds/Title3.mp3 b/config/title_music/sounds/Title3.mp3
new file mode 100644
index 0000000000000..30eba52e1660f
Binary files /dev/null and b/config/title_music/sounds/Title3.mp3 differ
diff --git a/config/title_music/sounds/Traitor (Absconditus).mp3 b/config/title_music/sounds/Traitor (Absconditus).mp3
new file mode 100644
index 0000000000000..e242831595083
Binary files /dev/null and b/config/title_music/sounds/Traitor (Absconditus).mp3 differ
diff --git a/config/title_music/sounds/Treacherous Voyage.mp3 b/config/title_music/sounds/Treacherous Voyage.mp3
new file mode 100644
index 0000000000000..467ee48f03343
Binary files /dev/null and b/config/title_music/sounds/Treacherous Voyage.mp3 differ
diff --git a/config/title_music/sounds/We_Live_a_Thousand_Years-B8gL-jgR7sA.mp3 b/config/title_music/sounds/We_Live_a_Thousand_Years-B8gL-jgR7sA.mp3
new file mode 100644
index 0000000000000..74a01ca545955
Binary files /dev/null and b/config/title_music/sounds/We_Live_a_Thousand_Years-B8gL-jgR7sA.mp3 differ
diff --git a/config/title_music/sounds/ekki_hugsa-TA0ZeWxDG6M.mp3 b/config/title_music/sounds/ekki_hugsa-TA0ZeWxDG6M.mp3
new file mode 100644
index 0000000000000..a71480ca16e57
Binary files /dev/null and b/config/title_music/sounds/ekki_hugsa-TA0ZeWxDG6M.mp3 differ
diff --git a/icons/mob/clothing/head/chaplain.dmi b/icons/mob/clothing/head/chaplain.dmi
index 7c34306918f4a..95f7782d6b64d 100644
Binary files a/icons/mob/clothing/head/chaplain.dmi and b/icons/mob/clothing/head/chaplain.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index 75ffc45c740f4..7f0b51c775106 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suits/chaplain.dmi b/icons/mob/clothing/suits/chaplain.dmi
index 99049027f17c8..d7a6a6bd573e9 100644
Binary files a/icons/mob/clothing/suits/chaplain.dmi and b/icons/mob/clothing/suits/chaplain.dmi differ
diff --git a/icons/mob/nonhuman-player/necoarc.dmi b/icons/mob/nonhuman-player/necoarc.dmi
new file mode 100644
index 0000000000000..6585bfea0278b
Binary files /dev/null and b/icons/mob/nonhuman-player/necoarc.dmi differ
diff --git a/icons/obj/clothing/cloaks.dmi b/icons/obj/clothing/cloaks.dmi
index dd1b905ad2c17..6a4a620477cc6 100644
Binary files a/icons/obj/clothing/cloaks.dmi and b/icons/obj/clothing/cloaks.dmi differ
diff --git a/icons/obj/clothing/head/chaplain.dmi b/icons/obj/clothing/head/chaplain.dmi
index 5686520a2676d..0aeedce86e215 100644
Binary files a/icons/obj/clothing/head/chaplain.dmi and b/icons/obj/clothing/head/chaplain.dmi differ
diff --git a/icons/obj/clothing/suits/chaplain.dmi b/icons/obj/clothing/suits/chaplain.dmi
index b992fa615abae..702afc8a42366 100644
Binary files a/icons/obj/clothing/suits/chaplain.dmi and b/icons/obj/clothing/suits/chaplain.dmi differ
diff --git a/icons/obj/scp/scp_clothes.dmi b/icons/obj/scp/scp_clothes.dmi
new file mode 100644
index 0000000000000..c0e533751f3c0
Binary files /dev/null and b/icons/obj/scp/scp_clothes.dmi differ
diff --git a/icons/ui_icons/emoji/emoji.dmi b/icons/ui_icons/emoji/emoji.dmi
index cf94375083c55..32b18f93455d3 100644
Binary files a/icons/ui_icons/emoji/emoji.dmi and b/icons/ui_icons/emoji/emoji.dmi differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index ae43a3c401bb9..d90de2d2a43ec 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -116,7 +116,7 @@ window "mapwindow"
is-default = true
right-click = true
saved-params = "zoom;letterbox;zoom-mode"
- style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
+ style = ".center { text-align: center; } .maptext { font-family: 'MS Serif'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
elem "status_bar"
type = LABEL
pos = 0,464
diff --git a/sound/weapons/punchneco.ogg b/sound/weapons/punchneco.ogg
new file mode 100644
index 0000000000000..df069b27e9578
Binary files /dev/null and b/sound/weapons/punchneco.ogg differ
diff --git a/strings/names/cocktails.txt b/strings/names/cocktails.txt
new file mode 100644
index 0000000000000..d07ea46f33b5f
--- /dev/null
+++ b/strings/names/cocktails.txt
@@ -0,0 +1,44 @@
+водка с тоником
+джин физз
+бахама мама
+манхэттен
+черный русский
+виски сода
+лонг айленд айс ти
+маргарита
+ирландский кофе
+ирландские сливки
+бипски смэш
+текила санрайз
+храбрый бык
+грызлодёр
+кровавая мэри
+виски с колой
+белый русский
+водка мартини
+мартини
+куба либре
+калуа
+водка
+вино
+самогон
+мохито
+амасек
+джин и тоник
+джин и соник
+грог
+эплджек
+анти-фриз
+отвёртка
+нейро-токсин
+пиво
+банана-хонк
+милкшейк
+кира спешал
+токсин спешал
+нюка-кола
+трипл цитрус
+черри шейк
+менли дорф
+багама мама
+Б-52
diff --git a/strings/names/jobs.txt b/strings/names/jobs.txt
new file mode 100644
index 0000000000000..d6636836d060d
--- /dev/null
+++ b/strings/names/jobs.txt
@@ -0,0 +1,33 @@
+ассистент
+главный инженер
+инженер
+атмосферный техник
+главный врач
+врач
+химик
+генетик
+вирусолог
+психолог
+парамедик
+директор исследований
+ученый
+роботехник
+глава службы безопасности
+варден
+детектив
+офицер
+заключенный
+капитан
+глава персонала
+адвокат
+бармен
+повар
+ботаник
+квартирмейстер
+грузчик
+шахтёр
+клоун
+мим
+уборщик
+куратор
+священник
\ No newline at end of file
diff --git a/strings/names/locations.txt b/strings/names/locations.txt
new file mode 100644
index 0000000000000..16fbfb42c245d
--- /dev/null
+++ b/strings/names/locations.txt
@@ -0,0 +1,92 @@
+
+камера ии
+вход на спутник ии
+корпус спутника ии
+камера загрузки ии
+коридор
+техтоннели
+арсенал
+атмос
+атмосферный двигатель
+атриум
+стройка дополнительной базы
+дополнительные туалеты
+вспомогательное хранилище инструментов
+бар
+мостик
+бриг
+офис вардена
+офис капитана
+комната капитана
+карго
+офис карго
+церковь
+офис церкви
+химия
+коридор командования
+строительная площадка
+корпоративный выставочный зал
+зал совета
+суд
+гардероб
+офис доставки
+отбытие
+прибытие
+офис детектива
+дормитории
+хранилище евы
+инженерия
+фое инженерии
+инженерный склад
+экспериментальная лаборатория
+стрельбище
+гейт
+генетика
+комната генератора гравитации
+гидропоника
+кухня
+офис адвоката
+библиотека
+раздевалка
+станция мехов
+центр медбэя
+техтоннели медбэя
+хранилище медбэя
+офис шахты
+морг
+техтоннели морга
+основное хранилище инструментов
+тюремное крыло
+исправительная камера для заключённых
+зона отдыха
+голодек
+отдел исследований
+исследовательский полигон
+исследования и разработка
+туалеты
+робототехника
+техтоннели научного отдела
+кпп
+офис охраны
+коридор сервиса
+космос
+суперматерия
+хирургия
+техническое хранилище
+комната телепорта
+испытательная лаборатория
+театр
+камера смешивания токсинов
+токсинная
+хранилище токсинов
+испытательная зона токсинов
+центр передачи
+сранзитная труба
+свободный магазин
+свободный офис
+хранилище
+вирусология
+склад
+мусоросжигатель
+ксенобиология
+цитология
diff --git a/strings/names/ru_adjectives.txt b/strings/names/ru_adjectives.txt
new file mode 100644
index 0000000000000..7034cb7f4521f
--- /dev/null
+++ b/strings/names/ru_adjectives.txt
@@ -0,0 +1,1026 @@
+обожаемый
+обожаемая
+обожаемое
+предприимчивый
+предприимчивая
+предприимчивое
+агрессивный
+агрессивная
+агрессивное
+бдительный
+бдительная
+бдительное
+привлекательный
+привлекательная
+привлекательное
+посредственный
+посредственная
+посредственное
+красивый
+красивая
+красивое
+голубоглазый
+голубоглазая
+голубоглазое
+кровавый
+кровавая
+кровавое
+застенчивый
+застенчивая
+застенчивое
+яркий
+яркая
+яркое
+чистый
+чистая
+чистое
+ясный
+ясная
+ясное
+облачный
+облачная
+облачное
+красочный
+красочная
+красочное
+многолюдный
+многолюдная
+многолюдное
+милый
+милая
+милое
+тёмный
+тёмная
+тёмное
+однообразный
+однообразная
+однообразное
+отчётливый
+отчётливая
+отчётливое
+тусклый
+тусклая
+тусклое
+элегантный
+элегантная
+элегантное
+взволнованный
+взволнованная
+взволнованное
+причудливый
+причудливая
+причудливое
+отвратительный
+отвратительная
+отвратительное
+обаятельный
+обаятельная
+обаятельное
+сверкающий
+сверкающая
+сверкающее
+прелестный
+прелестная
+прелестное
+изящный
+изящная
+изящное
+гротескный
+гротескная
+гротескное
+уютный
+уютная
+уютное
+светлый
+светлая
+светлое
+длинный
+длинная
+длинное
+великолепный
+великолепная
+великолепное
+туманные
+туманная
+туманное
+неподвижные
+неподвижная
+неподвижное
+мутный
+мутная
+мутное
+старомодный
+старомодная
+старомодное
+гладкий
+гладкая
+гладкие
+уравновешенный
+уравновешенная
+уравновешенное
+драгоценный
+драгоценная
+драгоценное
+необычный
+необычная
+необычное
+блестящий
+блестящая
+блестящее
+искрящийся
+искрящаяся
+искрящееся
+незапятнанный
+незапятнанная
+незапятнанное
+буйный
+буйная
+буйное
+странный
+странная
+странное
+уродливый
+уродливая
+уродливое
+безобразный
+безобразная
+безобразное
+неприглядный
+неприглядная
+неприглядное
+необыкновенный
+необыкновенная
+необыкновенное
+живой
+живая
+живое
+надоедливый
+надоедливая
+надоедливая
+плохой
+плохая
+плохое
+лучший
+лучшая
+лучшее
+мозговитый
+мозговитая
+мозговитое
+хрупкий
+хрупкая
+хрупкое
+занятой
+занятая
+занятое
+осторожный
+осторожная
+осторожное
+осмотрительный
+осмотрительная
+осмотрительное
+умный
+умная
+умное
+неуклюжий
+неуклюжая
+неуклюжее
+обеспокоенный
+обеспокоенная
+обеспокоенное
+сумасшедший
+сумасшедшая
+сумасшедшее
+любопытный
+любопытная
+любопытное
+мёртвый
+мёртвая
+мёртвое
+разный
+разная
+разное
+сложный
+сложная
+сложное
+сомнительный
+сомнительная
+сомнительное
+лёгкий
+лёгкая
+лёгкое
+дорогой
+дорогая
+дорогое
+знаменитый
+знаменитая
+знаменитое
+ломкий
+ломкая
+ломкое
+хилый
+хилая
+хилое
+пламенный
+пламенная
+пламенное
+одаренный
+одаренная
+одаренное
+полезный
+полезная
+полезное
+беспомощный
+беспомощная
+беспомощное
+ужасный
+ужасная
+ужасное
+важный
+важная
+важное
+невозможный
+невозможная
+невозможное
+недорогой
+недорогая
+недорогое
+невинный
+невинная
+невинное
+любознательный
+любознательная
+любознательное
+современный
+современная
+современное
+слащавый
+слащавая
+слащавое
+нечетный
+нечетная
+нечетное
+открытый
+открытая
+открытое
+выдающийся
+выдающаяся
+выдающееся
+бедный
+бедная
+бедное
+мощный
+мощная
+мощное
+колючий
+колючая
+колючее
+озадаченный
+озадаченная
+озадаченное
+реальный
+реальная
+реальное
+богатый
+богатая
+богатое
+стеснительный
+стеснительная
+стеснительное
+сонный
+сонная
+сонное
+тупой
+тупая
+тупое
+талантливый
+талантливая
+талантливое
+ручной
+ручная
+ручное
+нежный
+нежная
+нежное
+жесткий
+жесткая
+жесткое
+безразличный
+безразличная
+безразличное
+огромный
+огромная
+огромное
+блуждающий
+блуждающая
+блуждающее
+дикий
+дикая
+дикое
+неправильный
+неправильная
+неправильное
+сердитый
+сердитая
+сердитое
+раздраженный
+раздраженная
+раздраженное
+беспокойный
+беспокойная
+беспокойное
+невежественный
+невежественная
+невежественное
+стыдный
+стыдная
+стыдное
+смущённый
+смущённая
+смущённое
+чёрный
+чёрная
+чёрное
+синий
+синяя
+синее
+скучающий
+скучающая
+скучающее
+драчливый
+драчливая
+драчливое
+осуждённый
+осуждённая
+осуждённое
+жуткий
+жуткая
+жуткое
+коварный
+коварная
+коварное
+опасный
+опасная
+опасное
+побеждённый
+побеждённая
+побеждённое
+дерзкий
+дерзкая
+дерзкое
+унылый
+унылая
+унылое
+встревоженный
+встревоженная
+встревоженное
+завистливый
+завистливая
+завистливое
+злой
+злая
+злое
+лютый
+лютая
+лютое
+дурацкий
+дурацкая
+дурацкое
+неистовый
+неистовая
+неистовое
+напуганный
+напуганная
+напуганное
+скорбящий
+скорбящая
+скорбящее
+сварливый
+сварливая
+сварливое
+бездомный
+бездомная
+бездомное
+голодный
+голодная
+голодное
+раненый
+раненая
+раненое
+больной
+больная
+больное
+зудящий
+зудящая
+зудящее
+ревнивый
+ревнивая
+ревнивое
+нервный
+нервная
+нервное
+ленивый
+ленивая
+ленивое
+одинокий
+одинокая
+одинокое
+загадочный
+загадочная
+загадочное
+противный
+противная
+противное
+озорной
+озорная
+озорное
+пикантный
+пикантная
+пикантное
+несносный
+несносная
+несносное
+возмутительный
+возмутительная
+возмутительное
+панический
+паническая
+паническое
+отталкивающий
+отталкивающая
+отталкивающее
+эгоистичный
+эгоистичная
+эгоистичное
+воспалённый
+воспалённая
+воспалённое
+напряжённый
+напряжённая
+напряжённое
+вспыльчивый
+вспыльчивая
+вспыльчивое
+беспечный
+беспечная
+беспечное
+уставший
+уставшая
+уставшее
+расстроенный
+расстроенная
+расстроенное
+утомлённый
+утомлённая
+утомлённое
+грешный
+грешная
+грешное
+тревожный
+тревожная
+тревожное
+сговорчивый
+сговорчивая
+сговорчивое
+храбрый
+храбрая
+храброе
+спокойный
+спокойная
+спокойное
+чарующий
+чарующая
+чарующее
+весёлый
+весёлая
+весёлое
+комфортный
+комфортная
+комфортное
+отважный
+отважная
+отважное
+решительный
+решительная
+решительное
+нетерпеливый
+нетерпеливая
+нетерпеливое
+ликующий
+ликующая
+ликующее
+феерический
+феерическая
+феерическое
+вдохновляющий
+вдохновляющая
+вдохновляющее
+энергичный
+энергичная
+энергичное
+увлеченный
+увлеченная
+увлеченное
+восторженный
+восторженная
+восторженное
+обильный
+обильная
+обильное
+честный
+честная
+честное
+верный
+верная
+верное
+фантастический
+фантастическая
+фантастическое
+годный
+годная
+годное
+дружелюбный
+дружелюбная
+дружелюбное
+забавный
+забавная
+забавное
+славный
+славная
+славное
+хороший
+хорошая
+хорошее
+счастливый
+счастливая
+счастливое
+здоровый
+здоровая
+здоровое
+угарный
+угарная
+угарное
+довольный
+довольная
+довольное
+добрый
+добрая
+доброе
+оживлённый
+оживлённая
+оживлённое
+чудный
+чудная
+чудное
+везучий
+везучая
+везучее
+опрятный
+опрятная
+опрятное
+покорный
+покорная
+покорное
+превосходный
+превосходная
+превосходное
+приятный
+приятная
+приятное
+гордый
+гордая
+гордое
+улыбчивый
+улыбчивая
+улыбчивое
+роскошный
+роскошная
+роскошное
+успешный
+успешная
+успешное
+благодарный
+благодарная
+благодарное
+заботливый
+заботливая
+заботливое
+победоносный
+победоносная
+победоносное
+остроумный
+остроумная
+остроумное
+замечательный
+замечательная
+замечательное
+ревностный
+ревностная
+ревностное
+сумасбродный
+сумасбродная
+сумасбродное
+обширный
+обширная
+обширное
+толстый
+толстая
+толстое
+кривой
+кривая
+кривое
+изогнутый
+изогнутая
+изогнутое
+глубокий
+глубокая
+глубокое
+плоский
+плоская
+плоское
+высокий
+высокая
+высокое
+полый
+полая
+полое
+низкий
+низкая
+низкое
+узкий
+узкая
+узкое
+круглый
+круглая
+круглое
+мелкий
+мелкая
+мелкое
+тощий
+тощая
+тощее
+квадратный
+квадратная
+квадратное
+крутой
+крутая
+крутое
+прямой
+прямая
+прямое
+широкий
+широкая
+широкое
+большой
+большая
+большое
+колоссальный
+колоссальная
+колоссальное
+жирный
+жирная
+жирное
+гигантский
+гигантская
+гигантское
+великий
+великая
+великое
+громадный
+громадная
+громадное
+маленький
+маленькая
+маленькое
+массивный
+массивная
+массивное
+миниатюрный
+миниатюрная
+миниатюрное
+малый
+малая
+малое
+тщедушный
+тщедушная
+тщедушное
+костлявый
+костлявая
+костлявое
+короткий
+короткая
+короткое
+крошечный
+крошечная
+крошечное
+малюсенький
+малюсенькая
+малюсенькое
+крохотный
+крохотная
+крохотное
+оглушительный
+оглушительная
+оглушительное
+бледный
+бледная
+бледное
+суровый
+суровая
+суровое
+пронзительный
+пронзительная
+пронзительное
+шипящий
+шипящая
+шипящее
+приглушенный
+приглушенная
+приглушенное
+хриплый
+хриплая
+хриплое
+громкий
+громкая
+громкое
+мелодичный
+мелодичная
+мелодичное
+немой
+немая
+немое
+шумный
+шумная
+шумное
+мурчащий
+мурчащая
+мурчащее
+тихий
+тихая
+тихое
+скрипучий
+скрипучая
+скрипучее
+звучный
+звучная
+звучное
+сиплый
+сиплая
+сиплое
+бесшумный
+бесшумная
+бесшумное
+мягкий
+мягкая
+мягкое
+визжащий
+визжащая
+визжащее
+громогласный
+громогласная
+громогласное
+безмолвный
+безмолвная
+безмолвное
+шепчущий
+шепчущая
+шепчущее
+древний
+древняя
+древние
+краткий
+краткая
+краткое
+ранний
+ранняя
+раннее
+быстрый
+быстрая
+быстрое
+поздний
+поздняя
+позднее
+долгий
+долгая
+долгое
+старый
+старая
+старое
+медленный
+медленная
+медленное
+шустрый
+шустрая
+шустрое
+молодой
+молодая
+молодое
+горький
+горькая
+горькое
+вкусный
+вкусная
+вкусное
+свежий
+свежая
+свежее
+сочный
+сочная
+сочное
+спелый
+спелая
+спелое
+гнилой
+гнилая
+гнилое
+солёный
+солёная
+солёное
+кислый
+кислая
+кислое
+острый
+острая
+острое
+черствый
+черствая
+чёрствое
+липкий
+липкая
+липкое
+сильный
+сильная
+сильное
+сладкий
+сладкая
+сладкое
+терпкий
+терпкая
+терпкое
+безвкусный
+безвкусная
+безвкусное
+вибрирующий
+вибрирующая
+вибрирующее
+нечеткий
+нечеткая
+нечеткое
+сальный
+сальная
+сальное
+неряшливый
+неряшливая
+неряшливое
+твёрдый
+твёрдая
+твёрдое
+горячий
+горячая
+горячее
+ледяной
+ледяная
+ледяное
+свободный
+свободная
+свободное
+плавленый
+плавленая
+плавленое
+питательный
+питательная
+питательное
+пластиковый
+пластиковая
+пластиковое
+дождливый
+дождливая
+дождливое
+грубый
+грубая
+грубое
+разбросанный
+разбросанная
+разбросанное
+лохматый
+лохматая
+лохматое
+шаткий
+шаткая
+шаткое
+резкий
+резкая
+резкое
+шелковистый
+шелковистая
+шелковистое
+склизкий
+склизкая
+склизкое
+скользкий
+скользкая
+скользкое
+плавный
+плавная
+плавное
+солидный
+солидная
+солидное
+устойчивый
+устойчивая
+устойчивое
+тугой
+тугая
+тугое
+неровный
+неровная
+неровное
+слабый
+слабая
+слабое
+мокрый
+мокрая
+мокрое
+деревянный
+деревянная
+деревянное
+аппетитный
+аппетитная
+аппетитное
+кипящий
+кипящая
+кипящее
+сломанный
+сломанная
+сломанное
+ухабистый
+ухабистая
+ухабистое
+прохладный
+прохладная
+прохладное
+холодный
+холодная
+холодное
+зябкий
+зябкая
+зябкое
+корявый
+корявая
+корявое
+отрадный
+отрадная
+отрадное
+кудрявый
+кудрявая
+кудрявое
+поврежденный
+поврежденная
+поврежденное
+сырой
+сырая
+сырое
+грязный
+грязная
+грязное
+сухой
+сухая
+сухое
+пыльный
+пыльная
+пыльное
+пушистый
+пушистая
+пушистое
+леденящий
+леденящая
+леденящее
+тёплый
+тёплая
+тёплое
+пустой
+пустая
+пустое
+немногий
+немногая
+немногое
+тяжёлый
+тяжёлая
+тяжёлое
+многий
+многая
+многое
+многочисленный
+многочисленная
+многочисленное
+существенный
+существенная
+существенное
+неуклонный
+неуклонная
+неуклонное
+постоянный
+постоянная
+постоянное
+могущественный
+могущественная
+могущественное
\ No newline at end of file
diff --git a/strings/names/ru_nouns.txt b/strings/names/ru_nouns.txt
new file mode 100644
index 0000000000000..808c3514e4e89
--- /dev/null
+++ b/strings/names/ru_nouns.txt
@@ -0,0 +1,105 @@
+любовь
+ненависть
+гнев
+мир
+гордость
+симпатия
+лояльность
+честность
+целостность
+сострадание
+благотворительность
+успех
+мужество
+обман
+умение
+красота
+блеск
+боль
+страдание
+убеждение
+мечта
+справедливость
+правда
+вера
+свобода
+знание
+мысль
+информация
+культура
+доверие
+целеустремленность
+прогресс
+образование
+гостеприимство
+досуг
+проблема
+дружба
+расслабление
+статья
+область
+ночь
+день
+результат
+возможность
+начало
+душа
+минута
+форма
+связь
+качество
+система
+вид
+вопрос
+глаз
+мозг
+печень
+сердце
+отростки
+сила
+конец
+голова
+рука
+золото
+момент
+время
+центр
+ответ
+автор
+интерес
+правило
+задача
+опыт
+событие
+минерал
+должность
+работа
+мнение
+срок
+помещение
+размер
+представитель
+герой
+храм
+кабинет
+отдел
+наука
+музыка
+журнал
+оценка
+анализ
+повод
+желание
+удар
+услуга
+дело
+сумма
+фонд
+бюджет
+звезда
+кухня
+подсобка
+шкафчик
+стекло
+металл
+пласт
diff --git a/strings/names/ru_verbs.txt b/strings/names/ru_verbs.txt
new file mode 100644
index 0000000000000..478de52cf8a2c
--- /dev/null
+++ b/strings/names/ru_verbs.txt
@@ -0,0 +1,898 @@
+принять
+добавить
+восхититься
+признать
+позволить
+согласиться
+разрешить
+позабавить
+проанализировать
+объявить
+ответить
+извиниться
+появиться
+одобрить
+организовать
+арестовать
+прибыть
+спросить
+прикрепить
+посетить
+привлечь
+избежать
+отступить
+печь
+запретить
+грохнуть
+обнажить
+принять ванну
+сразиться
+излучать
+поступить
+благословить
+моргнуть
+надоесть
+одолжить
+отскочить
+поклониться
+расшириться
+столкнуться
+сжечь
+вычислить
+нести
+вызвать
+оспорить
+зарядить
+погнаться
+обмануть
+проверить
+подбодрить
+закрыть
+свернуть
+собрать
+расчесать
+сравнить
+завершить
+сосредоточиться
+исповедаться
+соединить
+рассмотреть
+продолжить
+исправить
+кашлять
+скрыть
+взломать
+разбить
+ползти
+пересечь
+скрутить
+изогнуть
+повредить
+разлагаться
+решить
+украсить
+отложить
+доставить
+описать
+заслужить
+уничтожить
+обнаружить
+развить
+исчезнуть
+разоружить
+удвоить
+тащить
+истощить
+одеть
+уронить
+утонуть
+осушить
+припылить
+заработать
+обучить
+смутить
+трудоустроить
+опустошить
+поощрить
+закончить
+насладиться
+войти
+развлечь
+сбежать
+осмотреть
+возбудить
+объяснить
+взорваться
+продлить
+застегнуть
+испугаться
+оградить
+подшить
+наполнить
+заснять
+уволить
+влезть
+мигнуть
+заполнить
+сложить
+заставить
+основать
+запугать
+обжарить
+приклеить
+схватить
+натереть
+смазать
+угадать
+направить
+забивать
+вручать
+справиться
+повесить
+произойти
+навредить
+ненавидеть
+возглавить
+исцелить
+нагромождать
+нагреть
+помочь
+зацепить
+надеяться
+обнять
+вообразить
+впечатлить
+улучшить
+включить
+увеличить
+ввести
+вмешаться
+прервать
+представить
+изобрести
+пригласить
+бегать
+присоединиться
+прыгать
+пнуть
+убить
+преклонить
+завязать
+высадиться
+запустить
+осветить
+перечислить
+слушать
+загрузить
+запереть
+пометить
+спариваться
+имеет значение
+измерить
+ввязаться
+запомнить
+добывать
+смешать
+оплакивать
+умножить
+прибить
+назвать
+кивнуть
+отметить
+заметить
+подчиняться
+возразить
+завладеть
+случаться
+оскорбить
+предложить
+открыть
+приказать
+переполнить
+упаковать
+разделить
+пройти
+вставить
+клюнуть
+подглядывать
+совершить
+подобрать
+ущипнуть
+разместить
+посадить
+угодить
+подключить
+указать
+хлопнуть
+полить
+предпочитать
+подготовить
+сохранить
+нажать
+притвориться
+предотвратить
+распечатать
+произвести
+защитить
+предоставить
+накачать
+пробить
+наказать
+толкнуть
+спросить
+поднять
+достать
+понять
+получить
+узнать
+записать
+снизить
+отразить
+отказать
+отклонить
+расслабиться
+выпустить
+остаться
+напомнить
+убрать
+повторить
+заменить
+ответить
+сообщить
+воспроизвести
+запросить
+спасти
+вернуть
+рискнуть
+отнять
+втереть
+разрушить
+разграбить
+плыть
+удовлетворить
+рассеять
+выжечь
+привинтить
+опечатать
+обосноваться
+затенить
+подписывать
+пропустить
+шлёпнуть
+поскользнуться
+замедлить
+улыбнуться
+чихнуть
+замочить
+успокоить
+произнести
+разлить
+определить
+распылить
+прорасти
+раздавать
+пискнуть
+выжать
+окрасить
+начать
+размешать
+прошить
+остановить
+пристегнуть
+укрепить
+растянуть
+раздеть
+набить
+вычесть
+преуспеть
+обеспечить
+поддержать
+предположить
+удивить
+окружить
+приостановить
+переключить
+говорить
+приручить
+соблазнить
+ужаснуть
+оттаять
+тикать
+связать
+приурочить
+утомить
+коснуться
+заманить
+разводить
+доверять
+упасть
+повернуть
+расстегнуть
+объединять
+распаковать
+пропасть
+ходить
+бродить
+разогреть
+предупредить
+разбазарить
+взвесить
+высечь
+свистеть
+подмигнуть
+протереть
+обернуть
+снести
+зевать
+приютить
+заклинить
+советовать
+раздражать
+аплодировать
+ценить
+спорить
+атаковать
+пытаться
+балансировать
+умолять
+принадлежать
+белить
+слепить
+пачкать
+краснеть
+хвастать
+кипятить
+болтировать
+бомбить
+бронировать
+боксировать
+тормозить
+дышать
+мять
+пузыриться
+хоронить
+жужжать
+звонить
+заботиться
+вырезать
+менять
+жевать
+душить
+рубить
+претендовать
+хлопать
+чистить
+обрезать
+тренировать
+красить
+командовать
+общаться
+соревноваться
+жаловаться
+состоять
+содержать
+копировать
+считать
+давить
+плакать
+лечить
+танцевать
+сметь
+зависеть
+дезертировать
+исследовать
+недолюбливать
+делить
+сомневаться
+мечтать
+капать
+барабанить
+упражняться
+существовать
+ожидать
+чинить
+плавать
+течь
+цвести
+следовать
+дурачить
+формировать
+пялиться
+светиться
+приветствовать
+стонать
+гарантировать
+охранять
+беспокоить
+преследовать
+парить
+охотиться
+торопиться
+идентифицировать
+игнорировать
+влиять
+информировать
+ранить
+инструктировать
+намереваться
+интересовать
+нервировать
+чесаться
+шутить
+судить
+жонглировать
+целовать
+стучать
+маркировать
+длиться
+смеяться
+учиться
+ровнять
+лицензировать
+лизать
+лгать
+нравится
+жить
+стремиться
+смотреть
+любить
+управлять
+маршировать
+жениться
+соответствовать
+плавить
+опростоволоситься
+доить
+скучать
+швартоваться
+двигаться
+темнить
+грабить
+зарезать
+нуждаться
+гнездиться
+нумеровать
+наблюдать
+задолжать
+владеть
+грести
+рисовать
+парковать
+гладить
+чистить
+тосковать
+планировать
+играть
+тыкать
+полировать
+обладать
+публиковать
+практиковаться
+молиться
+проповедовать
+предшествовать
+дарить
+колоть
+программировать
+обещать
+тянуть
+мчаться
+сожалеть
+править
+радоваться
+положиться
+ремонтировать
+рифмовать
+полоскать
+качать
+катить
+гнить
+властвовать
+экономить
+пилить
+бояться
+ругать
+скоблить
+царапать
+кричать
+строчить
+скрести
+искать
+служить
+делиться
+бриться
+дрожать
+шокировать
+вздыхать
+сигнализировать
+грешить
+потягивать
+крушить
+нюхать
+курить
+урвать
+чуять
+храпеть
+сыпать
+звучать
+щадить
+искриться
+сверкать
+портить
+визжать
+штамповать
+глазеть
+рулить
+шагать
+складировать
+сосать
+страдать
+подозревать
+пробовать
+дразнить
+телефонировать
+тестировать
+благодарить
+щекотать
+гастролировать
+буксировать
+следить
+торговать
+транспортировать
+путешествовать
+лечить
+трепетать
+спешить
+утруждать
+пытаться
+крутить
+печатать
+разблокировать
+использовать
+побывать
+вопить
+ждать
+хотеть
+мыть
+караулить
+мочить
+махать
+ныть
+кружиться
+шептать
+желать
+колебаться
+интересоваться
+работать
+волноваться
+бороться
+извиваться
+орать
+принимать
+добавлять
+восхищаться
+признавать
+позволять
+соглашаться
+разрешать
+забавлять
+анализировать
+объявлять
+отвечать
+извиняться
+появляться
+одобрять
+организовывать
+арестовывать
+прибывать
+спрашивать
+прикреплять
+посещать
+привлекать
+избегать
+отступать
+выпекать
+запрещать
+грохотать
+обнажать
+принимать ванну
+сражаться
+излучить
+поступать
+благословлять
+моргать
+надоедать
+одалживать
+отскакивать
+кланяться
+расширяться
+сталкиваться
+сжигать
+вычислять
+носить
+вызывать
+оспаривать
+заряжать
+гнаться
+обманывать
+проверять
+подбадривать
+закрывать
+сворачивать
+собирать
+расчесывать
+сравнивать
+завершать
+сосредотачиваться
+исповедоваться
+соединять
+рассматривать
+продолжать
+исправлять
+кашлять
+скрывать
+взламывать
+разбивать
+ползать
+пересекать
+скручивать
+изгибать
+повреждать
+разложиться
+решать
+украшать
+откладывать
+доставлять
+описывать
+заслуживать
+уничтожать
+обнаруживать
+развивать
+исчезать
+разоружать
+удваивать
+таскать
+истощать
+одевать
+ронять
+тонуть
+осушать
+припылять
+зарабатывать
+обучать
+смущать
+трудоустраивать
+опустошать
+поощрять
+заканчивать
+наслаждаться
+входить
+развлекать
+сбегать
+осматривать
+возбуждать
+объяснять
+взрываться
+продлевать
+застёгивать
+пугаться
+ограждать
+подшивать
+наполнять
+снимать
+увольнять
+влезать
+мигать
+заполнять
+складывать
+заставлять
+основывать
+запугивать
+обжаривать
+приклеивать
+хватать
+натирать
+смазывать
+угадывать
+направлять
+забить
+вручить
+справляться
+вешать
+происходить
+вредить
+возненавидеть
+возглавлять
+исцелять
+нагромоздить
+нагревать
+помогать
+цеплять
+понадеяться
+обнимать
+воображать
+впечатлять
+улучшать
+включать
+увеличивать
+вводить
+вмешиваться
+прерывать
+представлять
+изобретать
+приглашать
+бежать
+присоединяться
+прыгнуть
+пинать
+убивать
+преклонять
+завязывать
+высаживаться
+запускать
+освещать
+перечислять
+услышать
+загружать
+запирать
+помечать
+спариться
+иметь значение
+измерять
+ввязываться
+запоминать
+добыть
+смешивать
+оплакать
+умножать
+прибивать
+называть
+кивать
+отмечать
+замечать
+подчиняться
+возражать
+завладевать
+случиться
+оскорблять
+предлагать
+открывать
+приказывать
+переполнять
+упаковывать
+разделять
+проходить
+вставлять
+клевать
+подглядеть
+совершать
+подбирать
+щипать
+размещать
+сажать
+угождать
+подключать
+указывать
+хлопать
+поливать
+предпочесть
+подготавливать
+сохранять
+нажимать
+притворяться
+предотвращать
+распечатывать
+производить
+защищать
+предоставлять
+накачивать
+пробивать
+наказывать
+толкать
+спрашивать
+поднимать
+доставать
+понимать
+получать
+узнавать
+записывать
+снижать
+отражать
+отказывать
+отклонять
+расслабляться
+выпускать
+оставаться
+напоминать
+убирать
+повторять
+заменять
+отвечать
+сообщать
+воспроизводить
+запрашивать
+спасать
+возвращать
+рисковать
+отнимать
+втирать
+разрушать
+разграблять
+плавать
+удовлетворять
+рассеивать
+выжигать
+привинчивать
+опечатывать
+обосновываться
+затенять
+подписать
+пропускать
+шлёпать
+поскальзываться
+замедлять
+улыбаться
+чихать
+замачивать
+успокаивать
+произносить
+разливать
+определять
+распылять
+проращивать
+раздать
+пищать
+выжимать
+окрашивать
+начинать
+размешивать
+прошивать
+останавливать
+пристёгивать
+укреплять
+растягивать
+раздевать
+набивать
+вычитать
+преуспевать
+обеспечивать
+поддерживать
+предполагать
+удивлять
+окружать
+приостанавливать
+переключать
+сказать
+приучать
+соблазнять
+ужасать
+оттаивать
+тикнуть
+связывать
+приурочивать
+утомлять
+касаться
+заманивать
+развести
+доверить
+падать
+поворачивать
+расстёгивать
+объединить
+распаковывать
+пропадать
+идти
+брести
+разогревать
+предупреждать
+разбазаривать
+взвешивать
+высекать
+свиснуть
+подмигивать
+протирать
+оборачивать
+сносить
+зевнуть
+ютиться
+заклинивать
\ No newline at end of file
diff --git a/tgstation.dme b/tgstation.dme
index feac9b7092dc6..7bb3cb9508d2f 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3054,6 +3054,7 @@
#include "code\modules\clothing\outfits\standard.dm"
#include "code\modules\clothing\outfits\vr.dm"
#include "code\modules\clothing\outfits\vv_outfit.dm"
+#include "code\modules\clothing\scp\scp_cloth.dm"
#include "code\modules\clothing\shoes\_shoes.dm"
#include "code\modules\clothing\shoes\bananashoes.dm"
#include "code\modules\clothing\shoes\boots.dm"
@@ -3665,6 +3666,15 @@
#include "code\modules\mapping\space_management\space_transition.dm"
#include "code\modules\mapping\space_management\traits.dm"
#include "code\modules\mapping\space_management\zlevel_manager.dm"
+#include "code\modules\mentor\dementor.dm"
+#include "code\modules\mentor\follow.dm"
+#include "code\modules\mentor\mentor.dm"
+#include "code\modules\mentor\mentor_verbs.dm"
+#include "code\modules\mentor\mentorhelp.dm"
+#include "code\modules\mentor\mentorpm.dm"
+#include "code\modules\mentor\mentorsay.dm"
+#include "code\modules\mentor\mentorwho.dm"
+#include "code\modules\mentor\topic.dm"
#include "code\modules\meteors\meteors.dm"
#include "code\modules\mining\abandoned_crates.dm"
#include "code\modules\mining\aux_base.dm"
@@ -4023,6 +4033,7 @@
#include "code\modules\mob\living\simple_animal\hostile\morph.dm"
#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm"
#include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm"
+#include "code\modules\mob\living\simple_animal\hostile\necoarc.dm"
#include "code\modules\mob\living\simple_animal\hostile\ooze.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
#include "code\modules\mob\living\simple_animal\hostile\regalrat.dm"
@@ -4248,6 +4259,7 @@
#include "code\modules\photography\photos\album.dm"
#include "code\modules\photography\photos\frame.dm"
#include "code\modules\photography\photos\photo.dm"
+#include "code\modules\pixelmoving\pixel_shift.dm"
#include "code\modules\plumbing\ducts.dm"
#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
#include "code\modules\plumbing\plumbers\acclimator.dm"
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/legacy_toggles.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/legacy_toggles.tsx
index e94eb6c522a08..c55ed6de037e9 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/legacy_toggles.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/legacy_toggles.tsx
@@ -24,6 +24,13 @@ export const combohud_lighting: FeatureToggle = {
component: CheckboxInput,
};
+export const auto_dementor_pref: FeatureToggle = {
+ name: "Auto dementor",
+ category: "ADMIN",
+ description: "When enabled, you will automatically dementor.",
+ component: CheckboxInput,
+};
+
export const deadmin_always: FeatureToggle = {
name: 'Auto deadmin - Always',
category: 'ADMIN',