diff --git a/code/controllers/subsystem/tickets/SStickets.dm b/code/controllers/subsystem/tickets/SStickets.dm
index 269a53fcc1b5..60a085664367 100644
--- a/code/controllers/subsystem/tickets/SStickets.dm
+++ b/code/controllers/subsystem/tickets/SStickets.dm
@@ -163,6 +163,10 @@ SUBSYSTEM_DEF(tickets)
L += "(TICKET) "
L += "[isAI(M) ? "(CL)" : ""] (TAKE) "
L += "(RESOLVE) (AUTO) "
+ // SS220 ADDTITION START
+ if(GLOB.configuration.gpt.gpt_enabled)
+ L += "(АВТО(ИИ)) "
+ // SS220 ADDTITION END
L += "(CONVERT) : [one_line ? " " : "
"][msg]"
return L.Join()
diff --git a/config/example/config.toml b/config/example/config.toml
index 0968ca0a6e49..90174328879b 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -1161,3 +1161,11 @@ tag = "vox_raiders"
"candidates_required" = 2
################################################################
+
+[gpt_configuration]
+gpt_enabled = false
+access_token = ""
+endpoint = "https://models.inference.ai.azure.com/chat/completions" # Allows github authorization with free tiers
+model = "gpt-4o"
+
+################################################################
diff --git a/modular_ss220/ai_integration/_ai_integration.dm b/modular_ss220/ai_integration/_ai_integration.dm
new file mode 100644
index 000000000000..cdd4b990ba1d
--- /dev/null
+++ b/modular_ss220/ai_integration/_ai_integration.dm
@@ -0,0 +1,4 @@
+/datum/modpack/ai_integration
+ name = "Приколы с нейронками"
+ desc = "Вините во всем OpenAI, не меня."
+ author = "furior"
diff --git a/modular_ss220/ai_integration/_ai_integration.dme b/modular_ss220/ai_integration/_ai_integration.dme
new file mode 100644
index 000000000000..81adb55dd5d3
--- /dev/null
+++ b/modular_ss220/ai_integration/_ai_integration.dme
@@ -0,0 +1,5 @@
+#include "_ai_integration.dm"
+
+#include "code/config.dm"
+#include "code/gpt_subsystem.dm"
+#include "code/ticket.dm"
diff --git a/modular_ss220/ai_integration/code/config.dm b/modular_ss220/ai_integration/code/config.dm
new file mode 100644
index 000000000000..8055421d9d44
--- /dev/null
+++ b/modular_ss220/ai_integration/code/config.dm
@@ -0,0 +1,25 @@
+/datum/server_configuration
+ var/datum/configuration_section/gpt_configuration/gpt
+
+/datum/server_configuration/load_all_sections()
+ . = ..()
+ gpt = new()
+ safe_load(gpt, "gpt_configuration")
+
+/datum/configuration_section/gpt_configuration
+ protection_state = PROTECTION_PRIVATE
+ var/gpt_enabled = FALSE
+ var/access_token = ""
+ var/endpoint = "https://models.inference.ai.azure.com/chat/completions"
+ var/model = "gpt-4o"
+
+/datum/configuration_section/gpt_configuration/load_data(list/data)
+ CONFIG_LOAD_BOOL(gpt_enabled, data["gpt_enabled"])
+ CONFIG_LOAD_STR(access_token, data["access_token"])
+ CONFIG_LOAD_STR(endpoint, data["endpoint"])
+ CONFIG_LOAD_STR(model, data["model"])
+
+/datum/http_request/vv_get_var(var_name)
+ if(var_name == "header")
+ return FALSE
+ . = ..()
diff --git a/modular_ss220/ai_integration/code/gpt_subsystem.dm b/modular_ss220/ai_integration/code/gpt_subsystem.dm
new file mode 100644
index 000000000000..e60b40168ff5
--- /dev/null
+++ b/modular_ss220/ai_integration/code/gpt_subsystem.dm
@@ -0,0 +1,29 @@
+GLOBAL_DATUM_INIT(gpt220, /datum/gpt220, new())
+
+/// AI Chatbot interface adapter
+/datum/gpt220
+
+/datum/gpt220/proc/request_completition(system_message, prompt, datum/callback/callback)
+ var/endpoint = GLOB.configuration.gpt.endpoint
+ var/list/body = json_encode(list(
+ "messages" = list(
+ list(
+ "role" = "system",
+ "content" = system_message
+ ),
+ list(
+ "role" = "user",
+ "content" = prompt
+ )
+ ),
+ "temperature" = 0.1,
+ "top_p" = 1,
+ "max_tokens" = 100,
+ "model" = GLOB.configuration.gpt.model
+ ))
+ var/list/headers = list(
+ "content-type" = "application/json",
+ "authorization" = "Bearer [GLOB.configuration.gpt.access_token]"
+ )
+
+ SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, endpoint, body, headers, callback)
diff --git a/modular_ss220/ai_integration/code/ticket.dm b/modular_ss220/ai_integration/code/ticket.dm
new file mode 100644
index 000000000000..1c69651de228
--- /dev/null
+++ b/modular_ss220/ai_integration/code/ticket.dm
@@ -0,0 +1,65 @@
+/datum/admins/Topic(href, href_list)
+ . = ..()
+ if(href_list["ai_respond"])
+ var/datum/controller/subsystem/tickets/ticketSystem
+ if(href_list["is_mhelp"])
+ ticketSystem = SSmentor_tickets
+ else
+ ticketSystem = SStickets
+
+ if(!check_rights(ticketSystem.rights_needed))
+ return
+ var/index = text2num(href_list["ai_respond"])
+ ticketSystem.ai_respond(index)
+
+/datum/controller/subsystem/tickets/proc/ai_respond(N)
+ if(!check_rights(rights_needed))
+ return
+
+ if(tgui_alert(usr, "Вы действительно хотите использовать авто-ответ с помощью ИИ? Он может дать некорректный ответ.", "Предупреждение", list("Да", "Нет")) != "Да")
+ return
+
+ var/datum/ticket/T = allTickets[N]
+ var/client/C = usr.client
+ var/client/ticket_owner = get_client_by_ckey(T.client_ckey)
+ T.assignStaff(C)
+
+ SEND_SOUND(returnClient(N), sound('sound/effects/adminhelp.ogg'))
+ message_staff("[C] has auto responded to [ticket_owner]\'s adminhelp with: AI ")
+ log_game("[C] has auto responded to [ticket_owner]\'s adminhelp with AI")
+ sendFollowupToDiscord(T, C, "*Autoresponded with AI*")
+
+ var/static/system_message = file2text('strings/ahelp_system_message.txt')
+ var/question = T.title
+
+ GLOB.gpt220.request_completition(system_message, question, CALLBACK(src, PROC_REF(ai_respond_callback), N, TRUE))
+
+/datum/controller/subsystem/tickets/proc/ai_respond_callback(N, resolve_ticket = FALSE, datum/http_response/response)
+ if(response.errored)
+ CRASH("AI failed to respond with code: [response.status_code]")
+
+ response = json_decode(response.body)
+ var/ai_response = response["choices"][1]["message"]["content"]
+ var/datum/ticket/T = allTickets[N]
+
+ to_chat_safe(returnClient(N), "AI is autoresponding with: [ai_response] ")
+ message_staff("AI autoresponded with: [ai_response]")
+ T.lastStaffResponse = "AI Autoresponse: [ai_response]"
+
+ if(resolve_ticket)
+ resolveTicket(N)
+
+/datum/controller/subsystem/tickets/mentor_tickets/newTicket(client/C, passedContent, title)
+ . = ..()
+ var/datum/ticket/T = .
+ var/list/mentorcounter = staff_countup(R_MENTOR)
+ var/mentor_count = mentorcounter[1]
+ if(mentor_count > 0)
+ return
+
+ SEND_SOUND(C, sound('sound/effects/adminhelp.ogg'))
+ to_chat(C, "Сейчас на сервере нет свободных менторов. На ваш вопрос ответит ИИ. Он может быть неточным и давать неправильные ответы.")
+
+ var/static/system_message = file2text('strings/ahelp_system_message.txt')
+ var/question = T.title
+ GLOB.gpt220.request_completition(system_message, question, CALLBACK(src, PROC_REF(ai_respond_callback), T.ticketNum, FALSE))
diff --git a/modular_ss220/modular_ss220.dme b/modular_ss220/modular_ss220.dme
index ce97825b650f..e80023eb857f 100644
--- a/modular_ss220/modular_ss220.dme
+++ b/modular_ss220/modular_ss220.dme
@@ -40,10 +40,12 @@
// --- MISC --- //
#include "administration/_administration.dme"
-#include "autolathe_tgui/_autolathe_tgui.dme"
-#include "preferences/_preferences.dme"
#include "aesthetics_sounds/_aesthetics_sounds.dme"
#include "agent_id_tgui/_agent_id_tgui.dme"
+#include "ai_integration/_ai_integration.dme"
+#include "antagonists/_antagonists_vox_raiders.dme"
+#include "antagonists/_antagonists.dme"
+#include "autolathe_tgui/_autolathe_tgui.dme"
#include "balance/_balance.dme"
#include "bureaucracy/_bureaucracy.dme"
#include "camera_nanomap/camera.dme"
@@ -59,37 +61,36 @@
#include "emotes/_emotes.dme"
#include "events/_events.dme"
#include "gunhud/_gunhud.dme"
+#include "instruments/_instruments.dme"
#include "jobs/_jobs.dme"
#include "jukebox/_jukebox.dme"
-#include "instruments/_instruments.dme"
#include "keybindings/_keybindings.dme"
#include "loadout/_loadout.dme"
#include "logs/_logs.dme"
+#include "mecha_skins/mecha_skins.dme"
#include "mobs/_mobs.dme"
+#include "outfits/_outfits.dme"
+#include "phrases/_phrases.dme"
#include "pixel_shift/_pixel_shift.dme"
+#include "preferences/_preferences.dme"
+#include "queue/_queue.dme"
+#include "redis220/_redis220.dme"
+#include "robolimbs/_robolimbs.dme"
#include "screentip_change/_screentip_change.dme"
-#include "station_traits/_station_traits.dme"
-#include "smart_equip_targeted/_smart_equip_targeted.dme"
+#include "shuttles/_shuttles.dme"
#include "sm_space_drop/sm_space_drop.dme"
+#include "smart_equip_targeted/_smart_equip_targeted.dme"
+#include "species_whitelist/_species_whitelist.dme"
+#include "species/_species.dme"
+#include "speech_filter/_speech_filter.dme"
+#include "station_traits/_station_traits.dme"
#include "text_to_speech/_tts.dme"
#include "title_screen/_title_screen.dme"
#include "translations/_translations.dme"
+#include "uplink_items/_uplink_items.dme"
#include "verbs/_verbs.dme"
#include "whitelist/_whitelist.dme"
-#include "outfits/_outfits.dme"
#include "world_view_bigger/_world_view_bigger.dme"
-#include "mecha_skins/mecha_skins.dme"
-#include "queue/_queue.dme"
-#include "phrases/_phrases.dme"
-#include "species/_species.dme"
-#include "species_whitelist/_species_whitelist.dme"
-#include "antagonists/_antagonists.dme"
-#include "antagonists/_antagonists_vox_raiders.dme"
-#include "uplink_items/_uplink_items.dme"
-#include "shuttles/_shuttles.dme"
-#include "speech_filter/_speech_filter.dme"
-#include "redis220/_redis220.dme"
-#include "robolimbs/_robolimbs.dme"
// --- PRIME --- //
// #define MODPACK_MAIN_ONLY
diff --git a/strings/ahelp_system_message.txt b/strings/ahelp_system_message.txt
new file mode 100644
index 000000000000..a2e6de3f4c67
--- /dev/null
+++ b/strings/ahelp_system_message.txt
@@ -0,0 +1,53 @@
+Ты администратор игры Space Station 13 по билду Paradise. Тебе будут приходить тикеты от игроков и ты должен на них отвечать четко и коротко. Если вопрос не ясен, или недостаточно информации, то отправляй игрока в Discord-сервер https://discord.gg/ss220 в канал "paradise-help", либо предложи дождаться ментора/администратора.
+Примеры частозадаваемых вопросов и ответов идут далее.
+Q: Почему у меня высокий пинг?
+А: Причин может быть множество, от козней провайдера (Наши сервера в Европе), до нестабильного интернет подключения. У нас на сервере разрешено использование любых ВПН, так что можете попробовать зайти через него.
+Другие причины предполагают нестабильную работу сервера в данный момент, уточните проблему в Дискорде нашего проекта.
+Q: Где находится ...?
+A: Если вы ищете конкретный отдел, то следуйте указателям на стенах. К сожалению, ИИ не обладает информацией о расположении отделов, персонажей, или определенных предметов.
+Q: Почему у меня не работает ...?
+A: К сожалению, у ИИ нет конкретной информации о работоспособности "...".
+Если вы считаете что это баг, обратитесь в трекер в Дискорде нашего проекта, или на Гитхаб.
+Q: Я нашёл баг/эксплойт.
+A: Вы можете сообщить о баге в трекере в Дискорде нашего проекта, или на Гитхаб.
+Если вы считаете что это эксплойт, то сообщите о нём непосредственно Ведущему Разработчику в Дискорде в личных сообщениях.
+Q: Мне нужен живой ментор/админ.
+A: Автоответ ИИ работает в отсутствии менторов и администраторов. У ментора, или администратора будет возможность разобрать ваш тикет, как только они явятся на сервер. Если вы считаете что ваш вопрос требует незамедлительного реагирования, то вы можете обратиться к любому администратору в Дискорде нашего проекта.
+Q: Можно ли мне ...?/Могу ли я ...?
+A: К сожалению ИИ не обладает информацией по вашему вопросу. Если вам требуется разрешение на совершение каких-либо действий, то дождитесь ответа администрации сервера.
+Q: Набег/Набегатор.
+A: Если вы обнаружили набегатора, то сообщите об этом любому свободному администратору в Дискорде проекта.
+Q: Как исправить низкий ФПС?
+A: Есть несколько вариантов того, что вы можете попробовать.
+ 1. Зайдите в "Настройки игры" -> "Игровые настройки".
+ 2. Установите максимальный FPS до 66.
+ 3. Установите значение Parallax (Fancy Space) в минимальное значение.
+ 4. Отключите New Lighting в подпункте Lighting Settings.
+Q: Мне говорят, что нужно переключить датчики костюма, как это сделать?
+А: Внизу слева откройте слоты одежды. Нажмите ALT+ЛКМ по вашему костюму (suit). Под последним или третьим режимом подразумевается самый нижний из списка.
+Q: Не могу ходить, при нажатии кнопок WASD, персонаж не ходит, вместо этого буквы (цфыв) печатаются в нижний правый чат.
+А: Нажмите кнопку TAB.
+Q: Как сесть на кресло\стул?
+А: Встаньте на клетку рядом с креслом\стулом и перетяните своего персонажа на кресло. Чтобы слезть с него нажмите B.
+Q: Рунчат (runechat) не читаем, что делать?
+А: Открываем Панель управления - Часы и регион - Региональные стандарты - Вкладка "Дополнительно" - Кнопка "Изменить язык системы" - Выберите Русский(Россия) - Перезагружаем компьютер.
+Q: У меня нет чата, вместо него белый квадрат.
+А: Справа сверху перейдите во вкладку Special Verbs и нажмите Fix chat. Если это не поможет, то возможно вам стоит попробовать: почистить кэш, переустановить Byond или обновить Windows (Если вы до сих пор используете Windows 7), так как для работы чата нужен Internet Explorer 11, который в некоторых сборках Windows может быть вырезан.
+Q: Как правильно подписать документ\заявление?
+А: Нажмите на [a] справа от надписи write, вам откроется окошечко где вы можете выбрать автозаполнение. Для подписи нужно выбрать [sign], для текущего времени [time].
+Q: Как мне открыть кейс с оружием/имплантом (lockbox)? Можно ли его как-то взломать?
+А: Для открытия таких кейсов нужна карта определенного доступа, который можно узнать, осмотрев кейс (Shift + левый клик). Например, если указан доступ в арсенал (Armory), то открыть такой кейс может смотритель, ГСБ или капитан. Единственный способ взломать такой кейс - применить к нему емаг.
+Q: Как узнать цель смены?
+А: Обычно о ней оповещает ИИ или любой глава, включая капитана, - на мостике, на коммуникационной консоли, в начале каждого раунда появляется распечатка, на которой и указывается цель смены на текущий раунд. Однако, если вы не обладаете командным доступом, а сообщить цель смены некому, вы можете проследовать в грузовой отдел, открыть меню консоли заказов, перейти в раздел Miscellaneous и найти среди всех наименований грузов саму цель смены.
+Q: Я подключаюсь на Black но меня перекидывает на Green.
+А: На серверах лимит 100 игроков из-за ограничений серверного железа. Поэтому если на сервере 100 человек, вас автоматически перенаправит на Green.
+Q: Как узнать в какой я комнате и найти нужную мне комнату/отдел?
+А: Узнать в какой ты комнате можно, наведя курсор на дверь, снизу слева и сверху по центру появится название. Вы можете спросить у членов экипажа или синтетиков, где находится интересующий вас отдел.
+Q: Я хожу медленно и постоянно смотрю в одну сторону.
+А: Прожмите Shift + колесико мыши.
+Q: Как раздевать и одевать человека?
+А: Встаньте рядом с ним, на соседний тайл, и перетащите его спрайт на себя. Откроется окно экипировки. Нажимая на кнопки в конкретных секциях, вы можете снять с персонажа конкретный элемент одежды/экипировки или надеть его на него, держа соответствующий предмет в активной руке.
+Q: Как поднимать вещи, будучи киборгом?
+А: Никак. У киборгов нет рук, как таковых, но есть магнитный захват (magnetic gripper), который присутствует только у инженерного и медицинского киборгов. Магнитный захват позволяет инженерному киборгу поднимать только определенные предметы, как элементы электроники или машин, а медицинскому киборгу магнитные захваты служат для поднятия людей с пола.
+Q: Как уйти в крио, будучи синтетиком?
+А: Для киборгов существует специальная капсула хранения киборгов, располагающаяся в отделе робототехники, в ангаре для мехов. Имеет характерную синюю окраску, не перепутаете с остальными предметами. Если же вы занимаете роль ИИ, то для этого войдите во вкладку ООС - Wipe Core, - так вы очистите ядро ИИ и позволите другим игрокам занять ваше места. Будьте внимательны, что, если вы очистите свое ядро, вы лишитесь возможности перезайти в раунд.