diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 904de8b36c9a9..0000000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-name: Bug report
-about: Create a report to help reproduce and fix the issue
----
-
-
-## Issue Summary
-
-
-
-## Round ID:
-
-
-
-
-
-## Testmerges:
-
-
-
-## Reproduction:
-
-
-
-
-
-
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000000..c1650b6160286
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+#DOCS
+#https://docs.github.com/en/github-ae@latest/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
+#
+blank_issues_enabled: true
+contact_links:
+ - name: Feature Request
+ url: https://discord.com/channels/1097181193939730453/1181261198096875670
+ about: Запросы новых фич следует оставлять на форуме ss13-трекер в дискорде.
+ - name: Exploit Reports
+ url: https://github.com/ss220club/Paradise-SS220/security
+ about: Критические баги, которые могут быть использованы со злыми намерениями, следует оставлять по ссылке.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 11687283df4c7..0000000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-
----
-
-Feature requests are not handled in the repository. The best place to discuss these ideas would be on the /tg/station 13 forums here: https://tgstation13.org/phpBB/viewforum.php?f=9&sid=5153c1c704a4fb1006bf7a265e45e03f
diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml
new file mode 100644
index 0000000000000..f1ee1a82cef48
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/issue_report.yml
@@ -0,0 +1,89 @@
+#For more details
+# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms
+# https://docs.gitea.com/1.19/usage/issue-pull-request-templates
+#Be sure to check the docs
+#
+#Format
+#- type: | markdown | textarea | input | checkboxes |
+# id: | just an id, no actual relevance |
+# attributes:
+# label: | Box title |
+# description: | Box small text - Description |
+#
+name: "Issue Report"
+#title: "[Bug]: " | if you want to have a have it automatically say [Bug] when they start the form
+description: "Доложите о проблемах или багах, что бы мы могли их исправить."
+#labels: "Баг"
+
+body:
+ - type: markdown
+ attributes:
+ value: Спасибо за оставленный отчёт! Не забудьте дать ему соответствующее проблеме название для упрощения работы другим.
+
+ - type: input
+ id: version
+ attributes:
+ label: BYOND Version
+ description: "На какой версии BYOND встретился баг. (Если уверены, что не связано, можно пропустить)"
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Описание проблемы
+ description: В чем состоит суть проблемы? (Обязательное поле)
+ placeholder: Я сел на стул, от чего взорвался, а мой мозг оказался в душе на ЦК!
+ validations:
+ required: true
+
+ - type: textarea
+ id: what-expected
+ attributes:
+ label: Что должно было произойти?
+ description: Почему вы считаете это проблемой?
+ placeholder: Я ожидал, что я просто сяду на стул и буду сидеть.
+
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: Что случилось вместо этого?
+ description: Как произошедшее отличается от ваших ожиданий?
+ placeholder: Я умер и оказался на ЦК.
+
+ - type: textarea
+ id: why-bad
+ attributes:
+ label: Почему это плохо/Какие последствия?
+ description: Почему вы считаете эту проблему значительной?
+ placeholder: Игроки должны иметь возможность сидеть на стульях, не взрываясь.
+
+ - type: textarea
+ id: how-to-reproduce
+ attributes:
+ label: Шаги для повторения проблемы.
+ description: Самая важная часть. Опишите ВСЁ, что вы делали, что бы встретиться с проблемой. (Обязательное поле)
+ placeholder: Нашел стул, сел, взорвался.
+ validations:
+ required: true
+
+ - type: textarea
+ id: when-problem-start
+ attributes:
+ label: Когда проблема началась?
+ description: Если отчет связан с тем, что раньше работало иначе, опишите последний раз, когда механика работала корректно. (Обязательное поле)
+ placeholder: Я мог садиться на стулья без проблем неделю назад, так что примерно тогда.
+ validations:
+ required: true
+
+ - type: textarea
+ id: extra-information
+ attributes:
+ label: Дополнительная информация
+ description: Всё, что вы считаете важным/относящимся к проблеме.
+ placeholder: Я думаю, что проблема начала появляться после этого PR https://github.com/ss220club/Paradise-SS220/pull/583
+
+ - type: textarea
+ id: logs
+ attributes:
+ label: Связанные логи
+ description: Пожалуйста, предоставьте связанные логи или рантаймы, если имеете к ним доступ. (Лучше цензурить IP-адреса)
+ render: DM
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index f514e52b21fdc..74bdeb94edc99 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -86,6 +86,7 @@ jobs:
- name: Ticked File Enforcement
if: steps.linter-setup.conclusion == 'success' && !cancelled()
run: |
+ bash tools/bandastation_check_grep.sh # BANDASTATION EDIT ADDITION - checking modular_bandastation code
tools/bootstrap/python tools/ticked_file_enforcement/ticked_file_enforcement.py < tools/ticked_file_enforcement/schemas/tgstation_dme.json
tools/bootstrap/python tools/ticked_file_enforcement/ticked_file_enforcement.py < tools/ticked_file_enforcement/schemas/unit_tests.json
- name: Check Define Sanity
diff --git a/.github/workflows/render_nanomaps.yml b/.github/workflows/render_nanomaps.yml
new file mode 100644
index 0000000000000..532cfa7f169c7
--- /dev/null
+++ b/.github/workflows/render_nanomaps.yml
@@ -0,0 +1,46 @@
+# GitHub action to autorender nanomaps outside the game
+# This kills off the awful verb we have that takes a full 50 seconds and hangs the whole server
+# The file names and locations are VERY important here
+# DO NOT EDIT THIS UNLESS YOU KNOW WHAT YOU ARE DOING
+# -aa
+name: 'Render Nanomaps'
+on:
+ schedule:
+ - cron: "0 0 * * *"
+ workflow_dispatch:
+
+jobs:
+ generate_maps:
+ name: 'Generate NanoMaps'
+ runs-on: ubuntu-22.04
+ steps:
+ - id: create_token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ secrets.APP_ID }}
+ private-key: ${{ secrets.PRIVATE_KEY }}
+
+ - run: echo "GH_TOKEN=${{ steps.create_token.outputs.token }}" >> "$GITHUB_ENV"
+
+ - name: 'Update Branch'
+ uses: actions/checkout@v4
+ with:
+ token: ${{ steps.create_token.outputs.token }}
+
+ - name: Branch
+ run: |
+ git branch -f nanomap-render
+ git checkout nanomap-render
+ git reset --hard origin/master
+
+ - name: 'Generate Maps'
+ run: './tools/nanomap_renderer/nanomap-renderer-invoker.sh'
+
+ - name: 'Commit Maps and open PR'
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "NanoMap Generation"
+ git pull origin master
+ git commit -m "NanoMap Auto-Update (`date`)" -a || true
+ git push -f -u origin nanomap-render
+ gh pr create -t "Automatic NanoMap Update" -b "This pull request updates the server NanoMaps. Please review the diff images before merging." -l "NanoMaps" -H "nanomap-render" -B "master"
diff --git a/.github/workflows/translate_branch_update.yml b/.github/workflows/translate_branch_update.yml
new file mode 100644
index 0000000000000..2fb5ee7c002ce
--- /dev/null
+++ b/.github/workflows/translate_branch_update.yml
@@ -0,0 +1,37 @@
+name: Update translation branch
+
+on:
+ workflow_dispatch:
+ push:
+ branches: ['master']
+
+concurrency:
+ group: translation
+ cancel-in-progress: false
+
+jobs:
+ update_translation:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ name: 'Update old translation'
+
+ steps:
+ - name: 'Update Branch'
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: 'Git config'
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "SS220Manager"
+
+ - name: Merge with their incoming
+ run: |
+ git checkout origin/translate
+ git merge origin/master --strategy-option theirs
+ git push origin HEAD:translate
+
+
diff --git a/SQL/bandastation/bandastation_update.sql b/SQL/bandastation/bandastation_update.sql
new file mode 100644
index 0000000000000..d3883dc403d45
--- /dev/null
+++ b/SQL/bandastation/bandastation_update.sql
@@ -0,0 +1,40 @@
+--
+-- Table structure for table `schema_revision_220`
+--
+DROP TABLE IF EXISTS `schema_revision_220`;
+CREATE TABLE `schema_revision_220` (
+ `major` TINYINT(3) unsigned NOT NULL,
+ `minor` TINYINT(3) unsigned NOT NULL,
+ `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`major`, `minor`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Table structure for table `ckey_whitelist`
+--
+DROP TABLE IF EXISTS `ckey_whitelist`;
+CREATE TABLE `ckey_whitelist` (
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `date` DATETIME DEFAULT now() NOT NULL,
+ `ckey` VARCHAR(32) NOT NULL,
+ `adminwho` VARCHAR(32) NOT NULL,
+ `port` INT(5) UNSIGNED NOT NULL,
+ `date_start` DATETIME DEFAULT now() NOT NULL,
+ `date_end` DATETIME NULL,
+ `is_valid` BOOLEAN DEFAULT true NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+--
+-- Table structure for table `admin_wl`
+--
+DROP TABLE IF EXISTS `admin_wl`;
+CREATE TABLE `admin_wl` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `admin_rank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Administrator',
+ `level` int(2) NOT NULL DEFAULT '0',
+ `flags` int(16) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `ckey` (`ckey`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/SQL/bandastation/database_changelog.md b/SQL/bandastation/database_changelog.md
new file mode 100644
index 0000000000000..74e7053c6a622
--- /dev/null
+++ b/SQL/bandastation/database_changelog.md
@@ -0,0 +1,54 @@
+Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.
+
+Make sure to also update `DB_MAJOR_VERSION_220` and `DB_MINOR_VERSION_220`, which can be found in `code/modular_bandastation/_defines220/code/defines/subsystems.dm`.
+
+The latest database version is 1.1; The query to update the schema revision table is:
+
+```sql
+INSERT INTO `schema_revision_220` (`major`, `minor`) VALUES (1, 1);
+```
+or
+
+```sql
+INSERT INTO `SS13_schema_revision_220` (`major`, `minor`) VALUES (1, 1);
+```
+
+-----------------------------------------------------
+Version 1.1, 17 April 2024, by larentoun
+Created the tables: schema_revision220, ckey_whitelist, admin_wl
+
+```sql
+CREATE TABLE `schema_revision220` (
+ `major` TINYINT(3) unsigned NOT NULL,
+ `minor` TINYINT(3) unsigned NOT NULL,
+ `date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`major`, `minor`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+```
+
+```sql
+CREATE TABLE `ckey_whitelist` (
+ `id` INT(11) NOT NULL AUTO_INCREMENT,
+ `date` DATETIME DEFAULT now() NOT NULL,
+ `ckey` VARCHAR(32) NOT NULL,
+ `adminwho` VARCHAR(32) NOT NULL,
+ `port` INT(5) UNSIGNED NOT NULL,
+ `date_start` DATETIME DEFAULT now() NOT NULL,
+ `date_end` DATETIME NULL,
+ `is_valid` BOOLEAN DEFAULT true NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+```sql
+CREATE TABLE `admin_wl` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `admin_rank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Administrator',
+ `level` int(2) NOT NULL DEFAULT '0',
+ `flags` int(16) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `ckey` (`ckey`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+-----------------------------------------------------
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 4f6031fcf3ffd..85e8216dccf97 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -92,7 +92,7 @@
if(ascii_only)
if(length(text) > max_length)
return null
- var/static/regex/non_ascii = regex(@"[^\x20-\x7E\t\n]")
+ var/static/regex/non_ascii = regex(@"[^\x20-\x7E\t\n\u0400-\u04FF]") // BANDASTATION EDIT: Allow cyrillic symbols
if(non_ascii.Find(text))
return null
else if(length_char(text) > max_length)
@@ -178,12 +178,12 @@
switch(text2ascii(char))
// A .. Z
- if(65 to 90) //Uppercase Letters
+ if(65 to 90, 1040 to 1071, 1025) //Uppercase Letters //SS220 EDIT CHANGE - Cyrillic Fixes
number_of_alphanumeric++
last_char_group = LETTERS_DETECTED
// a .. z
- if(97 to 122) //Lowercase Letters
+ if(97 to 122, 1072 to 1103, 1105) //Lowercase Letters //SS220 EDIT CHANGE - Cyrillic Fixes
if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || cap_after_symbols && last_char_group == SYMBOLS_DETECTED) //start of a word
char = uppertext(char)
number_of_alphanumeric++
@@ -1177,7 +1177,7 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
/// Removes all non-alphanumerics from the text, keep in mind this can lead to id conflicts
/proc/sanitize_css_class_name(name)
- var/static/regex/regex = new(@"[^a-zA-Z0-9]","g")
+ var/static/regex/regex = new(@"[^a-zA-Z0-9а-яА-ЯёЁ]","g") // BANDASTATION EDIT: Add Cyrillic support for this proc
return replacetext(name, regex, "")
/// Converts a semver string into a list of numbers
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 0d534fac9a36c..cd274c26deeaa 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -98,7 +98,7 @@
#endif
#ifndef PRELOAD_RSC //set to:
-#define PRELOAD_RSC 1 // 0 to allow using external resources or on-demand behaviour;
+#define PRELOAD_RSC 0 // 0 to allow using external resources or on-demand behaviour; BANDASTATION EDIT - Original: 1
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
diff --git a/code/modules/unit_tests/barsigns.dm b/code/modules/unit_tests/barsigns.dm
index d1242da3d5769..0ba46441957b3 100644
--- a/code/modules/unit_tests/barsigns.dm
+++ b/code/modules/unit_tests/barsigns.dm
@@ -7,12 +7,14 @@
var/obj/machinery/barsign_type = /obj/machinery/barsign
var/icon/barsign_icon = initial(barsign_type.icon)
var/list/barsign_icon_states = icon_states(barsign_icon)
+ var/icon/barsign_icon_ss220 = 'modular_bandastation/barsigns/icons/barsigns.dmi' // BANDASTATION EDIT Barsigns
+ var/list/barsign_icon_states_ss220 = icon_states(barsign_icon_ss220)
// Check every datum real bar sign
for(var/sign_type in (subtypesof(/datum/barsign) - /datum/barsign/hiddensigns))
var/datum/barsign/sign = new sign_type()
- if(!(sign.icon_state in barsign_icon_states))
+ if(!(sign.icon_state in barsign_icon_states) && !(sign.icon_state in barsign_icon_states_ss220)) // BANDASTATION EDIT Barsigns
TEST_FAIL("Icon state for [sign_type] does not exist in [barsign_icon].")
/**
diff --git a/config/bandastation/bandastation_config.txt b/config/bandastation/bandastation_config.txt
new file mode 100644
index 0000000000000..de1681f429ba1
--- /dev/null
+++ b/config/bandastation/bandastation_config.txt
@@ -0,0 +1,7 @@
+## Text-to-speech
+#TTS_TOKEN_SILERO mytoken
+#TTS_ENABLED
+#TTS_CACHE_ENABLED
+#TTS_API_URL_SILERO
+
+#WHITELIST220
diff --git a/config/bandastation/tts_replacements.json b/config/bandastation/tts_replacements.json
new file mode 100644
index 0000000000000..d62e9b1a7be9d
--- /dev/null
+++ b/config/bandastation/tts_replacements.json
@@ -0,0 +1,180 @@
+{
+ "tts_acronym_replacements": {
+ "нт": "Эн Тэ",
+ "смо": "Эс Мэ О",
+ "гп": "Гэ Пэ",
+ "рд": "Эр Дэ",
+ "гсб": "Гэ Эс Бэ",
+ "срп": "Эс Эр Пэ",
+ "цк": "Цэ Каа",
+ "рнд": "Эр Эн Дэ",
+ "сб": "Эс Бэ",
+ "рцд": "Эр Цэ Дэ",
+ "брпд": "Бэ Эр Пэ Дэ",
+ "рпд": "Эр Пэ Дэ",
+ "рпед": "Эр Пед",
+ "тсф": "Тэ Эс Эф",
+ "срт": "Эс Эр Тэ",
+ "обр": "О Бэ Эр",
+ "кпк": "Кэ Пэ Каа",
+ "пда": "Пэ Дэ А",
+ "id": "Ай Ди",
+ "мщ": "Эм Ще",
+ "вт": "Вэ Тэ",
+ "ерп": "Йе Эр Пэ",
+ "се": "Эс Йе",
+ "апц": "А Пэ Цэ",
+ "лкп": "Эл Ка Пэ",
+ "см": "Эс Эм",
+ "ека": "Йе Ка",
+ "ка": "Кэ А",
+ "бса": "Бэ Эс Аа",
+ "днк": "Дэ Эн Ка",
+ "тк": "Тэ Ка",
+ "бфл": "Бэ Эф Эл",
+ "бщ": "Бэ Щэ",
+ "кк": "Кэ Ка",
+ "ск": "Эс Ка",
+ "зк": "Зэ Ка",
+ "ерт": "Йе Эр Тэ",
+ "вкд": "Вэ Ка Дэ",
+ "нтр": "Эн Тэ Эр",
+ "пнт": "Пэ Эн Тэ",
+ "авд": "А Вэ Дэ",
+ "пнв": "Пэ Эн Вэ",
+ "ссд": "Эс Эс Дэ",
+ "кпб": "Кэ Пэ Бэ",
+ "сссп": "Эс Эс Эс Пэ",
+ "крб": "Ка Эр Бэ",
+ "бд": "Бэ Дэ",
+ "сст": "Эс Эс Тэ",
+ "скс": "Эс Ка Эс",
+ "икн": "И Ка Эн",
+ "нсс": "Эн Эс Эс",
+ "емп": "Йе Эм Пэ",
+ "бс": "Бэ Эс",
+ "цкс": "Цэ Ка Эс",
+ "срд": "Эс Эр Дэ",
+ "жпс": "Джи Пи Эс",
+ "gps": "Джи Пи Эс",
+ "ннксс": "Эн Эн Ка Эс Эс",
+ "ss": "Эс Эс",
+ "сс": "Эс Эс",
+ "тесла": "тэсла",
+ "трейзен": "трэйзэн",
+ "нанотрейзен": "нанотрэйзэн",
+ "мед": "м ед",
+ "меде": "м еде",
+ "кз": "Кэ Зэ",
+ "гбс": "Гэ Бэ Эс",
+ "цпсс": "Цэ Пэ Эс Эс",
+ "гкк": "Гэ Кэ Ка"
+ },
+ "tts_job_replacements": {
+ "nanotrasen navy field officer": "Полевой офицер флота Нанотрэйзен",
+ "nanotrasen navy officer": "Офицер флота nanotrasen",
+ "supreme commander": "Верховный главнокомандующий",
+ "solar federation general": "Генерал Солнечной Федерации",
+ "special operations officer": "Офицер специальных операций",
+ "civilian": "Гражданский",
+ "tourist": "Турист",
+ "businessman": "Бизнэсмэн",
+ "trader": "Торговец",
+ "assistant": "Ассистент",
+ "chief engineer": "Главный Инженер",
+ "station engineer": "Станционный инженер",
+ "trainee engineer": "Инженер-стажер",
+ "Engineer Assistant": "Инженерный Ассистент",
+ "Technical Assistant": "Технический Ассистент",
+ "Engineer Student": "Инженер-практикант",
+ "Technical Student": "Техник-практикант",
+ "Technical Trainee": "Техник-стажер",
+ "maintenance technician": "Техник по обслуживанию",
+ "engine technician": "Техник по двигателям",
+ "electrician": "Электрик",
+ "life support specialist": "Специалист по жизнеобеспечению",
+ "atmospheric technician": "Атмосферный техник",
+ "mechanic": "Механик",
+ "chief medical officer": "Главный врач",
+ "medical doctor": "Врач",
+ "Intern": "Интерн",
+ "Student Medical Doctor": "Врач-практикант",
+ "Medical Assistant": "Ассистирующий врач",
+ "surgeon": "Хирург",
+ "nurse": "Медсестра",
+ "coroner": "К+оронэр",
+ "chemist": "Химик",
+ "pharmacist": "Фармацевт",
+ "pharmacologist": "Фармаколог",
+ "geneticist": "Генетик",
+ "virologist": "Вирусолог",
+ "pathologist": "Патологоанатом",
+ "microbiologist": "Микробиолог",
+ "psychiatrist": "Психиатр",
+ "psychologist": "Психолог",
+ "therapist": "Терапевт",
+ "paramedic": "Парамедик",
+ "research director": "Директор исследований",
+ "scientist": "Учёный",
+ "student scientist": "Учёный-практикант",
+ "Scientist Assistant": "Научный Ассистент",
+ "Scientist Pregraduate": "Учёный-бакалавр",
+ "Scientist Graduate": "Научный выпускник",
+ "Scientist Postgraduate": "Учёный-аспирант",
+ "anomalist": "Аномалист",
+ "plasma researcher": "Исследователь плазмы",
+ "xenobiologist": "Ксенобиолог",
+ "chemical researcher": "Химик-исследователь",
+ "roboticist": "Робототехник",
+ "student robotist": "Студент-робототехник",
+ "biomechanical engineer": "Биомеханический инженер",
+ "mechatronic engineer": "Инженер мехатроники",
+ "head of security": "Глава службы безопасности",
+ "warden": "Смотритель",
+ "detective": "Детектив",
+ "forensic technician": "Криминалист",
+ "junior security officer": "Младший офицер службы безопасности",
+ "security officer": "Офицер службы безопасности",
+ "security trainer": "Тренер службы безопасности",
+ "security cadet": "Кадет службы безопасности",
+ "Security Assistant": "Ассистент службы безопасности",
+ "Security Graduate": "Выпускник кадетской академии",
+ "brig physician": "Врач брига",
+ "security pod pilot": "Пилот пода службы безопасности",
+ "captain": "Капитан",
+ "ai": "И И",
+ "cyborg": "Киборг",
+ "robot": "Робот",
+ "head of personnel": "Глава персонала",
+ "nanotrasen representative": "Представитель Нанотрэйзен",
+ "blueshield": "Блюшилд",
+ "magistrate": "Магистрат",
+ "internal affairs agent": "Агент внутренних дел",
+ "human resources agent": "Агент по персоналу",
+ "bartender": "Бармэн",
+ "chef": "Повар",
+ "cook": "Кук",
+ "culinary artist": "Кулинар",
+ "butcher": "Мясник",
+ "botanist": "Ботаник",
+ "hydroponicist": "Гидропонист",
+ "botanical researcher": "Ботаник-исследователь",
+ "quartermaster": "Квартирмейстер",
+ "cargo technician": "Карго техник",
+ "shaft miner": "Шахтёр",
+ "spelunker": "Спелеолог",
+ "clown": "Клоун",
+ "mime": "Мим",
+ "janitor": "Уборщик",
+ "custodial technician": "Техник по уходу за помещениями",
+ "librarian": "Библиотекарь",
+ "journalist": "Журналист",
+ "barber": "Парикмахер",
+ "hair stylist": "Стилист",
+ "beautician": "Косметолог",
+ "explorer": "Исследователь",
+ "chaplain": "Священник",
+ "syndicate officer": "Офицер синдиката",
+ "visitor": "посетитель"
+ }
+}
diff --git a/config/config.txt b/config/config.txt
index 1c87fc110ae9a..16ebcc2d322ce 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -5,6 +5,7 @@ $include dbconfig.txt
$include comms.txt
$include logging.txt
$include resources.txt
+$include bandastation/bandastation_config.txt
$include interviews.txt
$include lua.txt
$include auxtools.txt
diff --git a/config/dynamic.json b/config/dynamic.json
index a4a1eb7ebdbfb..aa384ef5ca636 100644
--- a/config/dynamic.json
+++ b/config/dynamic.json
@@ -4,7 +4,6 @@
"Traitors": {
"cost": 8,
"scaling_cost": 9,
- "weight": 0,
"required_candidates": 1,
"minimum_required_age": 0,
"requirements": [
@@ -40,7 +39,6 @@
},
"Changelings": {
- "weight": 0
},
"Heretics": {
@@ -48,49 +46,47 @@
},
"Wizard": {
- "weight": 0
},
"Blood Cult": {
- "weight": 0
},
"Nuclear Emergency": {
- "weight": 0
+ },
+
+ "Clown Operatives": {
},
"Revolution": {
- "weight": 0
+ },
+
+ "Malfunctioning AI": {
+ },
+
+ "Spies": {
}
},
"Midround": {
"Syndicate Sleeper Agent": {
- "weight": 0
},
"Malfunctioning AI": {
- "weight": 0
},
"Wizard": {
- "weight": 0
},
"Nuclear Assault": {
- "weight": 0
},
"Blob": {
- "weight": 0
},
"Blob Infection": {
- "weight": 0
},
"Alien Infestation": {
- "weight": 0
},
"Nightmare": {
@@ -102,19 +98,15 @@
},
"Abductors": {
- "weight": 0
},
"Space Ninja": {
- "weight": 0
},
"Spiders": {
- "weight": 0
},
"Revenant": {
- "weight": 0
},
"Sentient Disease": {
@@ -125,12 +117,15 @@
"weight": 0
},
+ "Dangerous Space Pirates": {
+ "weight": 0
+ },
+
"Obsessed": {
"weight": 0
},
"Space Changeling": {
- "weight": 0
},
"Paradox Clone": {
@@ -140,11 +135,9 @@
"Latejoin": {
"Syndicate Infiltrator": {
- "weight": 0
},
"Provocateur": {
- "weight": 0
},
"Heretic Smuggler": {
@@ -152,7 +145,6 @@
},
"Stowaway Changeling": {
- "weight": 0
}
},
diff --git a/config/game_options.txt b/config/game_options.txt
index f6d321c1b8e13..a606519765421 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -33,7 +33,7 @@ COMMENDATION_PERCENT_POLL 0.05
## To speed things up make the number negative, to slow things down, make the number positive.
## These modify the run/walk speed of all mobs before the mob-specific modifiers are applied.
-RUN_DELAY 1.5
+RUN_DELAY 2.2
WALK_DELAY 4
## The variables below affect the movement of specific mob types. THIS AFFECTS ALL SUBTYPES OF THE TYPE YOU CHOOSE!
@@ -44,9 +44,8 @@ WALK_DELAY 4
##MULTIPLICATIVE_MOVESPEED /mob/living/silicon/robot 0
##MULTIPLICATIVE_MOVESPEED /mob/living/carbon/alien 0
##MULTIPLICATIVE_MOVESPEED /mob/living/basic/slime 0
-MULTIPLICATIVE_MOVESPEED /mob/living/simple_animal 0
-MULTIPLICATIVE_MOVESPEED /mob/living/basic 0
-
+##MULTIPLICATIVE_MOVESPEED /mob/living/simple_animal 0
+##MULTIPLICATIVE_MOVESPEED /mob/living/basic 0
## NAMES ###
## If uncommented this adds a random surname to a player's name if they only specify one name.
@@ -84,7 +83,7 @@ STATION_GOAL_BUDGET 1
## GAME MODES ###
## Uncomment to not send a roundstart intercept report. Gamemodes may override this.
-#NO_INTERCEPT_REPORT
+NO_INTERCEPT_REPORT
## Percent weight reductions for three of the most recent modes
@@ -158,7 +157,7 @@ ALLOW_AI_MULTICAM
## Secborg ###
## Uncomment to prevent the security cyborg model from being chosen
-#DISABLE_SECBORG
+DISABLE_SECBORG
## Peacekeeper Borg ###
## Uncomment to prevent the peacekeeper cyborg model from being chosen
@@ -167,7 +166,7 @@ ALLOW_AI_MULTICAM
## AWAY MISSIONS ###
## Uncomment to load one of the missions from awaymissionconfig.txt or away_missions/ at roundstart.
-#ROUNDSTART_AWAY
+ROUNDSTART_AWAY
## How long the delay is before the Away Mission gate opens. Default is half an hour.
## 600 is one minute.
@@ -193,28 +192,28 @@ MINIMAL_ACCESS_THRESHOLD 20
#ASSISTANTS_HAVE_MAINT_ACCESS
## Uncoment to give security maint access. Note that if you dectivate JOBS_HAVE_MINIMAL_ACCESS security already gets maint from that.
-#SECURITY_HAS_MAINT_ACCESS
+SECURITY_HAS_MAINT_ACCESS
## Uncomment to give everyone maint access.
#EVERYONE_HAS_MAINT_ACCESS
## Comment this out this to make security officers spawn in departmental security posts
-#SEC_START_BRIG
+SEC_START_BRIG
## This variable is how you may configure "Scaling Access" for Departmental Security Officers.
## Set to 0/commented out for "off", Departmental Security Officers will never get additional room-specific access (beyond general departmental doors).
## Set to 1 if you want to enable "Scaling Access", where Departmental Security Officers will get access to most rooms within a department depending on how many security officers there are relative to the number of people on a station.
## Set to 2 if you want Departmental Security Officers to always have access to all rooms in a department.
-DEPSEC_ACCESS_LEVEL 1
+DEPSEC_ACCESS_LEVEL 2
## GHOST INTERACTION ###
## Uncomment to let ghosts spin chairs. You may be wondering why this is a config option. Don't ask.
-#GHOST_INTERACTION
+GHOST_INTERACTION
## NEAR-DEATH EXPERIENCE ###
## Comment this out to disable mobs hearing ghosts when unconscious and very close to death
-NEAR_DEATH_EXPERIENCE
+#NEAR_DEATH_EXPERIENCE
## NON-VOCAL SILICONS ###
## Uncomment these to stop the AI, or cyborgs, from having vocal communication.
@@ -230,7 +229,7 @@ NEAR_DEATH_EXPERIENCE
## Set to 4 for "specified", silicons will start with an existing lawset. (If no specified lawset is identified, the AI will spawn with asimov.)
-DEFAULT_LAWS 0
+DEFAULT_LAWS 4
## SILICON ASIMOV SUPERIORITY OVERRIDE ###
## This makes "Asimov Superiority" show up as a perk for humans in the character creation menu even if asimov is not the default lawset, such as when used with asimov++
@@ -243,7 +242,7 @@ DEFAULT_LAWS 0
## See datums\ai_laws.dm for the full law lists
## IE, SPECIFIED_LAWS asimovpp, SPECIFIED_LAWS robocop, SPECIFIED_LAWS antimov
-SPECIFIED_LAWS asimovpp
+SPECIFIED_LAWS nt_default
## RANDOM LAWS ##
## ------------------------------------------------------------------------------------------
@@ -256,6 +255,7 @@ RANDOM_LAWS asimovpp
RANDOM_LAWS paladin
RANDOM_LAWS robocop
RANDOM_LAWS corporate
+RANDOM_LAWS nt_default
## Quirky laws. Shouldn't cause too much harm
#RANDOM_LAWS hippocratic
@@ -297,6 +297,7 @@ LAW_WEIGHT liveandletlive,5
LAW_WEIGHT peacekeeper,5
LAW_WEIGHT ten_commandments,5
LAW_WEIGHT nutimov,5
+LAW_WEIGHT nt_default,5
## Quirky laws. Shouldn't cause too much harm
LAW_WEIGHT reporter,3
@@ -458,7 +459,7 @@ MICE_ROUNDSTART 10
ROUNDSTART_TRAITS
## Uncomment to disable human moods.
-#DISABLE_HUMAN_MOOD
+DISABLE_HUMAN_MOOD
## Enable night shifts
#ENABLE_NIGHT_SHIFTS
diff --git a/dependencies.sh b/dependencies.sh
index 087c337a0461f..fcb48eefcc31b 100644
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -9,6 +9,7 @@ export BYOND_MINOR=1633
#rust_g git tag
export RUST_G_VERSION=3.1.0
+export RUST_G_VERSION_SS220=3.0.0-ss220
#node version
export NODE_VERSION=20
diff --git a/interface/skin.dmf b/interface/skin.dmf
index ede8e37684078..b3e581d344f52 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -111,12 +111,12 @@ window "mapwindow"
size = 640x480
anchor1 = 0,0
anchor2 = 100,100
- font-family = "Grand9K Pixel"
- font-size = 6pt
+ font-family = "Arial"
+ font-size = 7pt
is-default = true
right-click = true
saved-params = "zoom;letterbox;zoom-mode"
- style = ".center { text-align: center; } .maptext { font-family: 'Grand9K Pixel'; font-size: 6pt; -dm-text-outline: 1px black; color: white; line-height: 1.0; } .command_headset { font-weight: bold; } .context { font-family: 'Pixellari'; font-size: 12pt; -dm-text-outline: 1px black; } .subcontext { font-family: 'TinyUnicode'; font-size: 12pt; line-height: 0.75; } .small { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; } .big { font-family: 'Pixellari'; font-size: 12pt; } .reallybig { font-size: 12pt; } .extremelybig { font-size: 12pt; } .greentext { color: #00FF00; font-size: 6pt; } .redtext { color: #FF0000; font-size: 6pt; } .clown { color: #FF69BF; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; }"
+ style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7pt; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold; } .context { font-family: 'Small Fonts'; font-size: 12pt; -dm-text-outline: 1px black; } .subcontext { font-family: 'Small Fonts'; font-size: 12pt; line-height: 0.75; } .small { font-family: 'Small Fonts'; font-size: 6pt; line-height: 1.4; } .big { font-family: 'Small Fonts'; font-size: 12pt; } .reallybig { font-size: 12pt; } .extremelybig { font-size: 12pt; } .greentext { color: #00FF00; font-size: 6pt; } .redtext { color: #FF0000; font-size: 6pt; } .clown { color: #FF69BF; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-family: 'Small Fonts'; font-size: 6pt; line-height: 1.4; }"
elem "status_bar"
type = LABEL
pos = 0,464
diff --git a/modular_bandastation/_defines220/_defines220.dm b/modular_bandastation/_defines220/_defines220.dm
new file mode 100644
index 0000000000000..89c20b6a61efd
--- /dev/null
+++ b/modular_bandastation/_defines220/_defines220.dm
@@ -0,0 +1,4 @@
+/datum/modpack/defines220
+ name = "Дефайны220"
+ desc = "Добавляет дефайны, которые нам нужны"
+ author = "larentoun"
diff --git a/modular_bandastation/_defines220/_defines220.dme b/modular_bandastation/_defines220/_defines220.dme
new file mode 100644
index 0000000000000..a9ec1bda68950
--- /dev/null
+++ b/modular_bandastation/_defines220/_defines220.dme
@@ -0,0 +1,14 @@
+#include "_defines220.dm"
+
+#include "code/signals_atom.dm"
+#include "code/defines/keybindings.dm"
+#include "code/defines/misc.dm"
+#include "code/defines/spans.dm"
+#include "code/defines/subsystems.dm"
+#include "code/defines/text_to_speech.dm"
+#include "code/signals_mob/signals_mob_ai.dm"
+#include "code/signals_mob/signals_mob_carbon.dm"
+#include "code/signals_mob/signals_mob_living.dm"
+#include "code/signals_mob/signals_mob_main.dm"
+#include "code/signals_mob/signals_mob_silicon.dm"
+#include "code/signals_mob/signals_mob_simple.dm"
diff --git a/modular_bandastation/_defines220/code/defines/keybindings.dm b/modular_bandastation/_defines220/code/defines/keybindings.dm
new file mode 100644
index 0000000000000..54cd3afa9f555
--- /dev/null
+++ b/modular_bandastation/_defines220/code/defines/keybindings.dm
@@ -0,0 +1,4 @@
+#define COMSIG_KB_MOB_PIXEL_SHIFT_DOWN "keybinding_mob_pixel_shift_down"
+#define COMSIG_KB_MOB_PIXEL_SHIFT_UP "keybinding_mob_pixel_shift_up"
+#define COMSIG_KB_CLIENT_LOOC_DOWN "keybinding_client_looc_down"
+#define COMSIG_KB_CLIENT_WHISPER_DOWN "keybinding_client_whisper_down"
diff --git a/modular_bandastation/_defines220/code/defines/misc.dm b/modular_bandastation/_defines220/code/defines/misc.dm
new file mode 100644
index 0000000000000..84efa510030c4
--- /dev/null
+++ b/modular_bandastation/_defines220/code/defines/misc.dm
@@ -0,0 +1,6 @@
+///Do (almost) nothing - indev placeholder for switch case implementations etc
+#define NOOP (.=.)
+/// Copies the L from element START to elememt END if L is initialized, otherwise returns an empty list.
+#define LAZYCOPY_RANGE(L, START, END) ( L ? L.Copy(START, END) : list() )
+/// Cuts the L from element START to elememt END if L is initialized, otherwise returns an empty list.
+#define LAZYCUT(L, START, END) ( L ? L.Cut(START, END) : NOOP )
diff --git a/modular_bandastation/_defines220/code/defines/spans.dm b/modular_bandastation/_defines220/code/defines/spans.dm
new file mode 100644
index 0000000000000..c596abf4ba3a7
--- /dev/null
+++ b/modular_bandastation/_defines220/code/defines/spans.dm
@@ -0,0 +1 @@
+#define span_maptext(str) ("" + str + " ")
diff --git a/modular_bandastation/_defines220/code/defines/subsystems.dm b/modular_bandastation/_defines220/code/defines/subsystems.dm
new file mode 100644
index 0000000000000..90e9cdf733c47
--- /dev/null
+++ b/modular_bandastation/_defines220/code/defines/subsystems.dm
@@ -0,0 +1,18 @@
+//! ## DB defines
+/**
+ * DB major schema version for BANDASTATION
+ *
+ * Update this whenever the db schema changes
+ *
+ * make sure you add an update to the schema_version stable in the db changelog
+ */
+#define DB_MAJOR_VERSION_220 1
+
+/**
+ * DB minor schema version for BANDASTATION
+ *
+ * Update this whenever the db schema changes
+ *
+ * make sure you add an update to the schema_version stable in the db changelog
+ */
+#define DB_MINOR_VERSION_220 1
diff --git a/modular_bandastation/_defines220/code/defines/text_to_speech.dm b/modular_bandastation/_defines220/code/defines/text_to_speech.dm
new file mode 100644
index 0000000000000..8d56e1734a006
--- /dev/null
+++ b/modular_bandastation/_defines220/code/defines/text_to_speech.dm
@@ -0,0 +1,75 @@
+#define VV_HK_SELECT_TTS_VOICE "select_tts_voice"
+
+#define TTS_TRAIT_PITCH_WHISPER (1<<1)
+#define TTS_TRAIT_RATE_FASTER (1<<2)
+#define TTS_TRAIT_RATE_MEDIUM (1<<3)
+
+#define TTS_TRAIT_ROBOTIZE "tts_trait_robotize"
+
+#define TTS_CATEGORY_OTHER "Другое"
+#define TTS_CATEGORY_WARCRAFT3 "WarCraft 3"
+#define TTS_CATEGORY_HALFLIFE2 "Half-Life 2"
+#define TTS_CATEGORY_STARCRAFT "StarCraft"
+#define TTS_CATEGORY_PORTAL2 "Portal 2"
+#define TTS_CATEGORY_STALKER "STALKER"
+#define TTS_CATEGORY_DOTA2 "Dota 2"
+#define TTS_CATEGORY_LOL "League of Legends"
+#define TTS_CATEGORY_FALLOUT "Fallout"
+#define TTS_CATEGORY_FALLOUT2 "Fallout 2"
+#define TTS_CATEGORY_POSTAL2 "Postal 2"
+#define TTS_CATEGORY_TEAMFORTRESS2 "Team Fortress 2"
+#define TTS_CATEGORY_ATOMIC_HEART "Atomic Heart"
+#define TTS_CATEGORY_OVERWATCH "Overwatch"
+#define TTS_CATEGORY_SKYRIM "Skyrim"
+#define TTS_CATEGORY_RITA "Rita"
+#define TTS_CATEGORY_METRO "Metro"
+#define TTS_CATEGORY_HEROESOFTHESTORM "Heroes of the Storm"
+#define TTS_CATEGORY_HEARTHSTONE "Hearthstone"
+#define TTS_CATEGORY_VALORANT "Valorant"
+#define TTS_CATEGORY_EVILISLANDS "Evil Islands"
+#define TTS_CATEGORY_WITCHER "Witcher"
+#define TTS_CATEGORY_LEFT4DEAD "Left 4 Dead"
+#define TTS_CATEGORY_SPONGEBOB "SpongeBob"
+#define TTS_CATEGORY_TINYBUNNY "Tiny Bunny"
+#define TTS_CATEGORY_BALDURS_GATE_3 "Baldur's gate 3"
+#define TTS_CATEGORY_PORTAL "Portal"
+#define TTS_CATEGORY_TMNT "Teenage mutant ninja turtle"
+#define TTS_CATEGORY_STAR_WARS "Star Wars"
+#define TTS_CATEGORY_TRANSFORMERS "Transformers"
+#define TTS_CATEGORY_LOTR "The Lord of the rings"
+#define TTS_CATEGORY_SHREK "Shrek"
+#define TTS_CATEGORY_POTC "Pirates of the Caribbean"
+#define TTS_CATEGORY_HARRY_POTTER "Harry Potter"
+#define TTS_CATEGORY_X3 "X3"
+#define TTS_CATEGORY_OVERLORD2 "The Overlord 2"
+#define TTS_CATEGORY_MARVEL "Marvel"
+#define TTS_CATEGORY_WOW "World of Warcraft"
+#define TTS_CATEGORY_TREASURE_ISLAND "Treasure Island"
+#define TTS_CATEGORY_BOYS_WORD "Слово пацана"
+
+#define TTS_GENDER_ANY "Любой"
+#define TTS_GENDER_MALE "Мужской"
+#define TTS_GENDER_FEMALE "Женский"
+
+#define TTS_PHRASES list(\
+ "Так звучит мой голос.",\
+ "Так я звучу.",\
+ "Я.",\
+ "Поставьте свою подпись.",\
+ "Пора за работу.",\
+ "Дело сделано.",\
+ "Станция Нанотрейзен.",\
+ "Офицер СБ.",\
+ "Капитан.",\
+ "Вульпканин.",\
+ "Съешь же ещё этих мягких французских булок, да выпей чаю.",\
+ "Клоун, прекрати разбрасывать банановые кожурки офицерам под ноги!",\
+ "Капитан, вы уверены что хотите назначить клоуна на должность главы персонала?",\
+ )
+
+#define BIG_WORKER_TIER 220
+#define LITTLE_WORKER_TIER 110
+
+#define BIG_WORKER_TTS_LEVEL 3
+#define LITTLE_WORKER_TTS_LEVEL 1
+#define DONATOR_LEVEL_MAX 5
diff --git a/modular_bandastation/_defines220/code/signals_atom.dm b/modular_bandastation/_defines220/code/signals_atom.dm
new file mode 100644
index 0000000000000..6cb23c4e1f485
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_atom.dm
@@ -0,0 +1,8 @@
+///from base of atom/change_tts_seed(): (mob/chooser, override, new_traits)
+#define COMSIG_ATOM_TTS_SEED_CHANGE "atom_tts_seed_change"
+///from base of atom/cast_tts: (mob/listener, message, atom/location, is_local, effect, traits, preSFX, postSFX)
+#define COMSIG_ATOM_TTS_CAST "atom_tts_cast"
+///from base of atom/tts_trait_add(): (trait)
+#define COMSIG_ATOM_TTS_TRAIT_ADD "atom_tts_trait_add"
+///from base of atom/tts_trait_remove(): (trait)
+#define COMSIG_ATOM_TTS_TRAIT_REMOVE "atom_tts_trait_remove"
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_ai.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_ai.dm
new file mode 100644
index 0000000000000..fa7df8ae680b9
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_ai.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/silicon/ai
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_carbon.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_carbon.dm
new file mode 100644
index 0000000000000..ef6039c440fc8
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_carbon.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/carbon
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_living.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_living.dm
new file mode 100644
index 0000000000000..db4d5bdb08e8c
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_living.dm
@@ -0,0 +1,9 @@
+// Signals for /mob/living
+
+//from base of living/set_pull_offset(): (mob/living/pull_target, grab_state)
+#define COMSIG_LIVING_SET_PULL_OFFSET "living_set_pull_offset"
+//from base of living/reset_pull_offsets(): (mob/living/pull_target, override)
+#define COMSIG_LIVING_RESET_PULL_OFFSETS "living_reset_pull_offsets"
+//from base of living/CanAllowThrough(): (atom/movable/mover, border_dir)
+#define COMSIG_LIVING_CAN_ALLOW_THROUGH "living_can_allow_through"
+ #define COMPONENT_LIVING_PASSABLE (1<<0)
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_main.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_main.dm
new file mode 100644
index 0000000000000..7aa41cead07d5
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_main.dm
@@ -0,0 +1 @@
+// Signals for /mob
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_silicon.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_silicon.dm
new file mode 100644
index 0000000000000..1e776fa5270e2
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_silicon.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/silicon
diff --git a/modular_bandastation/_defines220/code/signals_mob/signals_mob_simple.dm b/modular_bandastation/_defines220/code/signals_mob/signals_mob_simple.dm
new file mode 100644
index 0000000000000..937b109659eb5
--- /dev/null
+++ b/modular_bandastation/_defines220/code/signals_mob/signals_mob_simple.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/simple_animal
diff --git a/modular_bandastation/_helpers220/_helpers220.dm b/modular_bandastation/_helpers220/_helpers220.dm
new file mode 100644
index 0000000000000..f252781a26351
--- /dev/null
+++ b/modular_bandastation/_helpers220/_helpers220.dm
@@ -0,0 +1,4 @@
+/datum/modpack/defines220
+ name = "Хелперы220"
+ desc = "Добавляет хелперы, которые нам нужны"
+ author = "larentoun"
diff --git a/modular_bandastation/_helpers220/_helpers220.dme b/modular_bandastation/_helpers220/_helpers220.dme
new file mode 100644
index 0000000000000..b6f843f544780
--- /dev/null
+++ b/modular_bandastation/_helpers220/_helpers220.dme
@@ -0,0 +1,3 @@
+#include "_helpers220.dm"
+
+#include "code/unsorted.dm"
diff --git a/modular_bandastation/_helpers220/code/unsorted.dm b/modular_bandastation/_helpers220/code/unsorted.dm
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/modular_bandastation/_modpack.dm b/modular_bandastation/_modpack.dm
new file mode 100644
index 0000000000000..5569426fa6404
--- /dev/null
+++ b/modular_bandastation/_modpack.dm
@@ -0,0 +1,17 @@
+/datum/modpack
+ /// A string name for the modpack. Used for looking up other modpacks in init.
+ var/name
+ /// A string desc for the modpack. Can be used for modpack verb list as description.
+ var/desc
+ /// A string with authors of this modpack.
+ var/author
+
+/datum/modpack/proc/pre_initialize()
+ if(!name)
+ return "Modpack name is unset."
+
+/datum/modpack/proc/initialize()
+ return
+
+/datum/modpack/proc/post_initialize()
+ return
diff --git a/modular_bandastation/_modpacks.dm b/modular_bandastation/_modpacks.dm
new file mode 100644
index 0000000000000..68a24b2f48e0a
--- /dev/null
+++ b/modular_bandastation/_modpacks.dm
@@ -0,0 +1,62 @@
+#define INIT_ORDER_MODPACKS 84
+
+SUBSYSTEM_DEF(modpacks)
+ name = "Modpacks"
+ init_order = INIT_ORDER_MODPACKS
+ flags = SS_NO_FIRE
+ var/list/loaded_modpacks = list()
+
+/datum/controller/subsystem/modpacks/Initialize()
+ var/list/all_modpacks = list()
+ for(var/modpack in subtypesof(/datum/modpack/))
+ all_modpacks.Add(new modpack)
+ // Pre-init and register all compiled modpacks.
+ for(var/datum/modpack/package as anything in all_modpacks)
+ var/fail_msg = package.pre_initialize()
+ if(QDELETED(package))
+ CRASH("Modpack of type [package.type] is null or queued for deletion.")
+ if(fail_msg)
+ CRASH("Modpack [package.name] failed to pre-initialize: [fail_msg].")
+ if(loaded_modpacks[package.name])
+ CRASH("Attempted to register duplicate modpack name [package.name].")
+ loaded_modpacks.Add(package)
+
+ // Handle init and post-init (two stages in case a modpack needs to implement behavior based on the presence of other packs).
+ for(var/datum/modpack/package as anything in all_modpacks)
+ var/fail_msg = package.initialize()
+ if(fail_msg)
+ CRASH("Modpack [(istype(package) && package.name) || "Unknown"] failed to initialize: [fail_msg]")
+ for(var/datum/modpack/package as anything in all_modpacks)
+ var/fail_msg = package.post_initialize()
+ if(fail_msg)
+ CRASH("Modpack [(istype(package) && package.name) || "Unknown"] failed to post-initialize: [fail_msg]")
+
+ return SS_INIT_SUCCESS
+
+/client/verb/modpacks_list()
+ set name = "Modpacks List"
+ set category = "OOC"
+
+ if(!mob || !SSmodpacks.initialized)
+ return
+
+ if(length(SSmodpacks.loaded_modpacks))
+ . = "
Список модификаций "
+ for(var/datum/modpack/M as anything in SSmodpacks.loaded_modpacks)
+ if(M.name)
+ . += ""
+ . += "
[M.name] "
+
+ if(M.desc || M.author)
+ . += " "
+ if(M.desc)
+ . += " Описание: [M.desc]"
+ if(M.author)
+ . += "Автор: [M.author] "
+ . += " "
+
+ var/datum/browser/popup = new(mob, "modpacks_list", "Список Модификаций", 480, 580)
+ popup.set_content(.)
+ popup.open()
+ else
+ to_chat(src, "Этот сервер не использует какие-либо модификации.")
diff --git a/modular_bandastation/_signals220/_signals220.dm b/modular_bandastation/_signals220/_signals220.dm
new file mode 100644
index 0000000000000..a7b76146fb1be
--- /dev/null
+++ b/modular_bandastation/_signals220/_signals220.dm
@@ -0,0 +1,4 @@
+/datum/modpack/signals220
+ name = "Сигналы220"
+ desc = "Добавляет сигналы"
+ author = "larentoun"
diff --git a/modular_bandastation/_signals220/_signals220.dme b/modular_bandastation/_signals220/_signals220.dme
new file mode 100644
index 0000000000000..26671df7a7f98
--- /dev/null
+++ b/modular_bandastation/_signals220/_signals220.dme
@@ -0,0 +1,8 @@
+#include "_signals220.dm"
+
+#include "code/signals_mob/signals_mob_ai.dm"
+#include "code/signals_mob/signals_mob_carbon.dm"
+#include "code/signals_mob/signals_mob_living.dm"
+#include "code/signals_mob/signals_mob_main.dm"
+#include "code/signals_mob/signals_mob_silicon.dm"
+#include "code/signals_mob/signals_mob_simple.dm"
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_ai.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_ai.dm
new file mode 100644
index 0000000000000..fa7df8ae680b9
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_ai.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/silicon/ai
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_carbon.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_carbon.dm
new file mode 100644
index 0000000000000..ef6039c440fc8
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_carbon.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/carbon
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_living.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_living.dm
new file mode 100644
index 0000000000000..e1602f1bb705e
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_living.dm
@@ -0,0 +1,14 @@
+// Signals for /mob/living
+
+/mob/living/CanAllowThrough(atom/movable/mover, border_dir)
+ if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_ALLOW_THROUGH, mover, border_dir) & COMPONENT_LIVING_PASSABLE)
+ return TRUE
+ return ..()
+
+/mob/living/set_pull_offsets(mob/living/pull_target, grab_state)
+ . = ..()
+ SEND_SIGNAL(pull_target, COMSIG_LIVING_SET_PULL_OFFSET, grab_state)
+
+/mob/living/reset_pull_offsets(mob/living/pull_target, override)
+ . = ..()
+ SEND_SIGNAL(pull_target, COMSIG_LIVING_RESET_PULL_OFFSETS, override)
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_main.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_main.dm
new file mode 100644
index 0000000000000..7aa41cead07d5
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_main.dm
@@ -0,0 +1 @@
+// Signals for /mob
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_silicon.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_silicon.dm
new file mode 100644
index 0000000000000..1e776fa5270e2
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_silicon.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/silicon
diff --git a/modular_bandastation/_signals220/code/signals_mob/signals_mob_simple.dm b/modular_bandastation/_signals220/code/signals_mob/signals_mob_simple.dm
new file mode 100644
index 0000000000000..937b109659eb5
--- /dev/null
+++ b/modular_bandastation/_signals220/code/signals_mob/signals_mob_simple.dm
@@ -0,0 +1 @@
+// Signals for /mob/living/simple_animal
diff --git a/modular_bandastation/_singletons/_singletons.dm b/modular_bandastation/_singletons/_singletons.dm
new file mode 100644
index 0000000000000..d8b23d11f1a63
--- /dev/null
+++ b/modular_bandastation/_singletons/_singletons.dm
@@ -0,0 +1,4 @@
+/datum/modpack/singletons
+ name = "Singletons"
+ desc = "Ports Singletons from Sierra"
+ author = "larentoun"
diff --git a/modular_bandastation/_singletons/_singletons.dme b/modular_bandastation/_singletons/_singletons.dme
new file mode 100644
index 0000000000000..324fefed37667
--- /dev/null
+++ b/modular_bandastation/_singletons/_singletons.dme
@@ -0,0 +1,5 @@
+#include "_singletons.dm"
+
+#include "code/_defines.dm"
+#include "code/repository.dm"
+#include "code/singletons.dm"
diff --git a/modular_bandastation/_singletons/code/_defines.dm b/modular_bandastation/_singletons/code/_defines.dm
new file mode 100644
index 0000000000000..fd4cc7e73d1d0
--- /dev/null
+++ b/modular_bandastation/_singletons/code/_defines.dm
@@ -0,0 +1,23 @@
+/*
+* Performance behaviors for avoiding calling procs unecessarily on the Singletons global.
+*/
+
+/// Get a singleton instance according to path P. Creates it if necessary. Null if abstract or not a singleton.
+#define GET_SINGLETON(P)\
+ (ispath(P, /datum/singleton) ? (GLOB.Singletons.resolved_instances[P] ? GLOB.Singletons.instances[P] : GLOB.Singletons.GetInstance(P)) : null)
+
+/// Get a (path = instance) map of valid singletons according to typesof(P).
+#define GET_SINGLETON_TYPE_MAP(P)\
+ (ispath(P, /datum/singleton) ? (GLOB.Singletons.resolved_type_maps[P] ? GLOB.Singletons.type_maps[P] : GLOB.Singletons.GetTypeMap(P)) : list())
+
+/// Get a (path = instance) map of valid singletons according to subtypesof(P).
+#define GET_SINGLETON_SUBTYPE_MAP(P)\
+ (ispath(P, /datum/singleton) ? (GLOB.Singletons.resolved_subtype_maps[P] ? GLOB.Singletons.subtype_maps[P] : GLOB.Singletons.GetSubtypeMap(P)) : list())
+
+/// Get a list of valid singletons according to typesof(path).
+#define GET_SINGLETON_TYPE_LIST(P)\
+ (ispath(P, /datum/singleton) ? (GLOB.Singletons.resolved_type_lists[P] ? GLOB.Singletons.type_lists[P] : GLOB.Singletons.GetTypeList(P)) : list())
+
+/// Get a list of valid singletons according to subtypesof(path).
+#define GET_SINGLETON_SUBTYPE_LIST(P)\
+ (ispath(P, /datum/singleton) ? (GLOB.Singletons.resolved_subtype_lists[P] ? GLOB.Singletons.subtype_lists[P] : GLOB.Singletons.GetSubtypeList(P)) : list())
diff --git a/modular_bandastation/_singletons/code/repository.dm b/modular_bandastation/_singletons/code/repository.dm
new file mode 100644
index 0000000000000..007155342f2bc
--- /dev/null
+++ b/modular_bandastation/_singletons/code/repository.dm
@@ -0,0 +1,147 @@
+/repository/New()
+ return
+
+/*
+/datum/cache_entry
+ var/timestamp
+ var/data
+
+/datum/cache_entry/New()
+ timestamp = world.time
+
+/datum/cache_entry/proc/is_valid()
+ return FALSE
+
+/datum/cache_entry/valid_until/New(valid_duration)
+ ..()
+ timestamp += valid_duration
+
+/datum/cache_entry/valid_until/is_valid()
+ return world.time < timestamp
+*/
+
+
+GLOBAL_DATUM_INIT(Singletons, /repository/singletons, new)
+
+/repository/singletons
+ /// A cache of individual singletons as (/singleton/path = Instance, ...)
+ var/list/instances = list()
+
+ /// A map of (/singleton/path = TRUE, ...). Indicates whether a path has been tried for instances.
+ var/list/resolved_instances = list()
+
+ /// A cache of singleton types according to a parent type as (/singleton/path = list(/singleton/path = Instance, /singleton/path/foo = Instance, ...))
+ var/list/type_maps = list()
+
+ /// A map of (/singleton/path = TRUE, ...). Indicates whether a path has been tried for type_maps.
+ var/list/resolved_type_maps = list()
+
+ /// A cache of singleton subtypes according to a parent type as (/singleton/path = list(/singleton/path/foo = Instance, ...))
+ var/list/subtype_maps = list()
+
+ /// A map of (/singleton/path = TRUE, ...). Indicates whether a path has been tried for subtype_maps.
+ var/list/resolved_subtype_maps = list()
+
+ /// A cache of singleton types according to a parent type as (/singleton/path = list(Parent Instance, Subtype Instance, ...))
+ var/list/type_lists = list()
+
+ /// A map of (/singleton/path = TRUE, ...). Indicates whether a path has been tried for type_lists.
+ var/list/resolved_type_lists = list()
+
+ /// A cache of singleton subtypes according to a parent type as (/singleton/path = list(Subtype Instance, Subtype Instance, ...))
+ var/list/subtype_lists = list()
+
+ /// A map of (/singleton/path = TRUE, ...). Indicates whether a path has been tried for subtype_lists.
+ var/list/resolved_subtype_lists = list()
+
+
+/**
+* Get a singleton instance according to path. Creates it if necessary. Null if abstract or not a singleton.
+* Prefer the GET_SINGLETON macro to minimize proc calls.
+*/
+/repository/singletons/proc/GetInstance(datum/singleton/path)
+ if(!ispath(path, /datum/singleton))
+ return
+ if(resolved_instances[path])
+ return instances[path]
+ resolved_instances[path] = TRUE
+ if(path == initial(path.abstract_type))
+ return
+ var/datum/singleton/result = new path
+ instances[path] = result
+ result.Initialize()
+ return result
+
+
+/// Get a (path = instance) map of valid singletons according to paths.
+/repository/singletons/proc/GetMap(list/datum/singleton/paths)
+ var/list/result = list()
+ for(var/path in paths)
+ var/datum/singleton/instance = GetInstance(path)
+ if (!instance)
+ continue
+ result[path] = instance
+ return result
+
+
+/// Get a list of valid singletons according to paths.
+/repository/singletons/proc/GetList(list/datum/singleton/paths)
+ var/list/result = list()
+ for(var/path in paths)
+ var/datum/singleton/instance = GetInstance(path)
+ if(!instance)
+ continue
+ result += instance
+ return result
+
+
+/**
+* Get a (path = instance) map of valid singletons according to typesof(path).
+* Prefer the GET_SINGLETON_TYPE_MAP macro to minimize proc calls.
+*/
+/repository/singletons/proc/GetTypeMap(datum/singleton/path)
+ if(resolved_type_maps[path])
+ return type_maps[path] || list()
+ resolved_type_maps[path] = TRUE
+ var/result = GetMap(typesof(path))
+ type_maps[path] = result
+ return result
+
+
+/**
+* Get a (path = instance) map of valid singletons according to subtypesof(path).
+* Prefer the GET_SINGLETON_TYPE_MAP macro to minimize proc calls.
+*/
+/repository/singletons/proc/GetSubtypeMap(datum/singleton/path)
+ if(resolved_subtype_maps[path])
+ return subtype_maps[path] || list()
+ resolved_subtype_maps[path] = TRUE
+ var/result = GetMap(subtypesof(path))
+ subtype_maps[path] = result
+ return result
+
+
+/**
+* Get a list of valid singletons according to typesof(path).
+* Prefer the GET_SINGLETON_TYPE_LIST macro to minimize proc calls.
+*/
+/repository/singletons/proc/GetTypeList(datum/singleton/path)
+ if(resolved_type_lists[path])
+ return type_lists[path] || list()
+ resolved_type_lists[path] = TRUE
+ var/result = GetList(typesof(path))
+ type_lists[path] = result
+ return result
+
+
+/**
+* Get a list of valid singletons according to subtypesof(path).
+* Prefer the GET_SINGLETON_SUBTYPE_LIST macro to minimize proc calls.
+*/
+/repository/singletons/proc/GetSubtypeList(datum/singleton/path)
+ if(resolved_subtype_lists[path])
+ return subtype_lists[path] || list()
+ resolved_subtype_lists[path] = TRUE
+ var/result = GetList(subtypesof(path))
+ subtype_lists[path] = result
+ return result
diff --git a/modular_bandastation/_singletons/code/singletons.dm b/modular_bandastation/_singletons/code/singletons.dm
new file mode 100644
index 0000000000000..ee0bf41fcfe57
--- /dev/null
+++ b/modular_bandastation/_singletons/code/singletons.dm
@@ -0,0 +1,11 @@
+/datum/singleton
+ var/abstract_type = /datum/singleton
+
+/datum/singleton/proc/Initialize()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+/datum/singleton/Destroy()
+ SHOULD_CALL_PARENT(FALSE)
+ . = QDEL_HINT_LETMELIVE
+ CRASH("Prevented attempt to delete a singleton instance: [src]")
diff --git a/modular_bandastation/aesthetics/_aesthetics.dm b/modular_bandastation/aesthetics/_aesthetics.dm
new file mode 100644
index 0000000000000..bcccd31589222
--- /dev/null
+++ b/modular_bandastation/aesthetics/_aesthetics.dm
@@ -0,0 +1,4 @@
+/datum/modpack/aesthetics
+ name = "Эстетика"
+ desc = "Обновление визуального ряда"
+ author = "larentoun, Aylong220"
diff --git a/modular_bandastation/aesthetics/_aesthetics.dme b/modular_bandastation/aesthetics/_aesthetics.dme
new file mode 100644
index 0000000000000..fdc5af8331dbc
--- /dev/null
+++ b/modular_bandastation/aesthetics/_aesthetics.dme
@@ -0,0 +1,19 @@
+#include "_aesthetics.dm"
+
+// Airlocks
+#include "airlocks/code/airlock.dm"
+#include "airlocks/code/airlock_types.dm"
+#include "airlocks/code/airlock_assembly_types.dm"
+
+// Walls
+#include "walls/code/walls.dm"
+
+// Windows
+#include "windows/code/full_tile_windows.dm"
+#include "windows/code/directional_windows.dm"
+
+// WinDoors
+#include "windoors/code/windoors.dm"
+
+// Floors
+#include "floors/code/floors.dm"
diff --git a/modular_bandastation/aesthetics/airlocks/code/airlock.dm b/modular_bandastation/aesthetics/airlocks/code/airlock.dm
new file mode 100644
index 0000000000000..0b58a2d6363e5
--- /dev/null
+++ b/modular_bandastation/aesthetics/airlocks/code/airlock.dm
@@ -0,0 +1,28 @@
+/obj/machinery/door/airlock
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/public.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi'
+ note_overlay_file = 'modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi'
+
+ doorOpen = 'modular_bandastation/aesthetics/airlocks/sound/open.ogg'
+ doorClose = 'modular_bandastation/aesthetics/airlocks/sound/close.ogg'
+ boltUp = 'modular_bandastation/aesthetics/airlocks/sound/bolts_up.ogg'
+ boltDown = 'modular_bandastation/aesthetics/airlocks/sound/bolts_down.ogg'
+
+
+/obj/machinery/door/airlock/update_overlays()
+ . = ..()
+ if(!lights || !hasPower())
+ return
+ var/light_state
+ switch(airlock_state)
+ if(AIRLOCK_CLOSED)
+ if(!locked && !emergency)
+ light_state = "poweron"
+ if(AIRLOCK_OPEN)
+ if(locked)
+ light_state = "bolts_open"
+ else if(emergency)
+ light_state = "emergency_open"
+ else
+ light_state = "poweron_open"
+ . += get_airlock_overlay("lights_[light_state]", overlays_file, src, em_block = FALSE)
diff --git a/modular_bandastation/aesthetics/airlocks/code/airlock_assembly_types.dm b/modular_bandastation/aesthetics/airlocks/code/airlock_assembly_types.dm
new file mode 100644
index 0000000000000..0d9bea8f7dca9
--- /dev/null
+++ b/modular_bandastation/aesthetics/airlocks/code/airlock_assembly_types.dm
@@ -0,0 +1,190 @@
+// Station airlocks assembly
+/obj/structure/door_assembly
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/public.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_public
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station2/glass.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/station2/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_com
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/command.dmi'
+
+/obj/structure/door_assembly/door_assembly_cap
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/cap.dmi'
+ base_name = "captain airlock"
+ airlock_type = /obj/machinery/door/airlock/command/cap
+ glass_type = /obj/machinery/door/airlock/command/cap/glass
+
+/obj/structure/door_assembly/door_assembly_hop
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/hop.dmi'
+ base_name = "head of personnel airlock"
+ airlock_type = /obj/machinery/door/airlock/command/hop
+ glass_type = /obj/machinery/door/airlock/command/hop/glass
+
+/obj/structure/door_assembly/door_assembly_cmo
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/cmo.dmi'
+ base_name = "chief medical officer airlock"
+ airlock_type = /obj/machinery/door/airlock/command/cmo
+ glass_type = /obj/machinery/door/airlock/command/cmo/glass
+
+/obj/structure/door_assembly/door_assembly_rd
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/rd.dmi'
+ base_name = "research director airlock"
+ airlock_type = /obj/machinery/door/airlock/command/rd
+ glass_type = /obj/machinery/door/airlock/command/rd/glass
+
+/obj/structure/door_assembly/door_assembly_hos
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/hos.dmi'
+ base_name = "head of security airlock"
+ airlock_type = /obj/machinery/door/airlock/command/hos
+ glass_type = /obj/machinery/door/airlock/command/hos/glass
+
+/obj/structure/door_assembly/door_assembly_qm
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/qm.dmi'
+ base_name = "quartermaster airlock"
+ airlock_type = /obj/machinery/door/airlock/command/qm
+ glass_type = /obj/machinery/door/airlock/command/qm/glass
+
+/obj/structure/door_assembly/door_assembly_ce
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/ce.dmi'
+ base_name = "chief engineer airlock"
+ airlock_type = /obj/machinery/door/airlock/command/ce
+ glass_type = /obj/machinery/door/airlock/command/ce/glass
+
+/obj/structure/door_assembly/door_assembly_sec
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/security.dmi'
+
+/obj/structure/door_assembly/door_assembly_eng
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/engineering.dmi'
+
+/obj/structure/door_assembly/door_assembly_min
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/mining.dmi'
+
+/obj/structure/door_assembly/door_assembly_atmo
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/atmos.dmi'
+
+/obj/structure/door_assembly/door_assembly_research
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/research.dmi'
+
+/obj/structure/door_assembly/door_assembly_science
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/science.dmi'
+
+/obj/structure/door_assembly/door_assembly_med
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/medical.dmi'
+
+/obj/structure/door_assembly/door_assembly_viro
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/virology.dmi'
+
+/obj/structure/door_assembly/door_assembly_hydro
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/botany.dmi'
+ base_name = "hydroponics airlock"
+ airlock_type = /obj/machinery/door/airlock/hydroponics
+ glass_type = /obj/machinery/door/airlock/hydroponics/glass
+
+/obj/structure/door_assembly/door_assembly_service
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/service.dmi'
+ base_name = "service airlock"
+ airlock_type = /obj/machinery/door/airlock/service
+ glass_type = /obj/machinery/door/airlock/service/glass
+
+/obj/structure/door_assembly/door_assembly_bathroom
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/bathroom.dmi'
+ base_name = "bathroom airlock"
+ airlock_type = /obj/machinery/door/airlock/bathroom
+ noglass = TRUE
+
+/obj/structure/door_assembly/door_assembly_mai
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/maintenance.dmi'
+
+/obj/structure/door_assembly/door_assembly_extmai
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/maintenanceexternal.dmi'
+
+/obj/structure/door_assembly/door_assembly_ext
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/external/external.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_fre
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/freezer.dmi'
+
+/obj/structure/door_assembly/door_assembly_hatch
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/hatch/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_mhatch
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/hatch/maintenance.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_highsecurity
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/highsec/highsec.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/highsec/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_centcom
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi'
+
+/obj/structure/door_assembly/door_assembly_grunge
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi'
+
+// Mineral airlocks
+/obj/structure/door_assembly/door_assembly_gold
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/gold.dmi'
+
+/obj/structure/door_assembly/door_assembly_silver
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/silver.dmi'
+
+/obj/structure/door_assembly/door_assembly_diamond
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/diamond.dmi'
+
+/obj/structure/door_assembly/door_assembly_uranium
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/uranium.dmi'
+
+/obj/structure/door_assembly/door_assembly_plasma
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/plasma.dmi'
+
+/obj/structure/door_assembly/door_assembly_bananium
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/bananium.dmi'
+
+/obj/structure/door_assembly/door_assembly_tranquillite
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/tranquilite.dmi'
+
+/obj/structure/door_assembly/door_assembly_sandstone
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/sandstone.dmi'
+
+/obj/structure/door_assembly/door_assembly_wood
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/wood.dmi'
+
+// Multi-tile airlocks
+/obj/structure/door_assembly/multi_tile
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/glass_large.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi'
+
+/obj/structure/door_assembly/multi_tile/command
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/command_glass_large.dmi'
+ airlock_type = /obj/machinery/door/airlock/multi_tile/command
+ glass_type = /obj/machinery/door/airlock/multi_tile/command/glass
+
+/obj/structure/door_assembly/multi_tile/security
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/security_glass_large.dmi'
+ base_name = "security large airlock"
+ airlock_type = /obj/machinery/door/airlock/multi_tile/security
+ glass_type = /obj/machinery/door/airlock/multi_tile/security/glass
+
+/obj/structure/door_assembly/multi_tile/engineering
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/engineering_glass_large.dmi'
+ base_name = "engineering large airlock"
+ airlock_type = /obj/machinery/door/airlock/multi_tile/engineering
+ glass_type = /obj/machinery/door/airlock/multi_tile/engineering/glass
+
+/obj/structure/door_assembly/multi_tile/atmospheric
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/atmospheric_glass_large.dmi'
+ base_name = "atmospheric large airlock"
+ airlock_type = /obj/machinery/door/airlock/multi_tile/atmospheric
+ glass_type = /obj/machinery/door/airlock/multi_tile/atmospheric/glass
+
+/obj/structure/door_assembly/multi_tile/supply
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/supply_glass_large.dmi'
+ base_name = "supply large airlock"
+ airlock_type = /obj/machinery/door/airlock/multi_tile/supply
+ glass_type = /obj/machinery/door/airlock/multi_tile/supply/glass
diff --git a/modular_bandastation/aesthetics/airlocks/code/airlock_types.dm b/modular_bandastation/aesthetics/airlocks/code/airlock_types.dm
new file mode 100644
index 0000000000000..ae6113bc258c5
--- /dev/null
+++ b/modular_bandastation/aesthetics/airlocks/code/airlock_types.dm
@@ -0,0 +1,216 @@
+// Station airlocks
+/obj/machinery/door/airlock/command
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/heads/command.dmi'
+
+/obj/machinery/door/airlock/security
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/security.dmi'
+
+/obj/machinery/door/airlock/engineering
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/engineering.dmi'
+
+/obj/machinery/door/airlock/medical
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/medical.dmi'
+
+/obj/machinery/door/airlock/maintenance
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/maintenance.dmi'
+
+/obj/machinery/door/airlock/maintenance/external
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/maintenanceexternal.dmi'
+
+/obj/machinery/door/airlock/mining
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/mining.dmi'
+
+/obj/machinery/door/airlock/atmos
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/atmos.dmi'
+
+/obj/machinery/door/airlock/research
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/research.dmi'
+
+/obj/machinery/door/airlock/freezer
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/freezer.dmi'
+
+/obj/machinery/door/airlock/science
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/science.dmi'
+
+/obj/machinery/door/airlock/virology
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/virology.dmi'
+
+/obj/machinery/door/airlock/hydroponics
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/botany.dmi'
+ assemblytype = /obj/structure/door_assembly/door_assembly_hydro
+
+/obj/machinery/door/airlock/service
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/service.dmi'
+ assemblytype = /obj/structure/door_assembly/door_assembly_service
+
+/obj/machinery/door/airlock/bathroom
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/bathroom.dmi'
+ assemblytype = /obj/structure/door_assembly/door_assembly_bathroom
+
+/obj/machinery/door/airlock/grunge
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi'
+ assemblytype = /obj/structure/door_assembly/door_assembly_grunge
+
+// Station airlocks glass
+/obj/machinery/door/airlock/command/cap/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/hop/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/cmo/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/rd/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/hos/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/qm/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/command/ce/glass
+ opacity = 0
+ glass = TRUE
+ normal_integrity = 400
+
+/obj/machinery/door/airlock/hydroponics/glass
+ opacity = 0
+ glass = TRUE
+
+/obj/machinery/door/airlock/eva/glass
+ opacity = 0
+ glass = TRUE
+
+/obj/machinery/door/airlock/service/glass
+ opacity = 0
+ glass = TRUE
+
+/obj/machinery/door/airlock/psych/glass
+ opacity = 0
+ glass = TRUE
+
+/obj/machinery/door/airlock/lawyer/glass
+ opacity = 0
+ glass = TRUE
+
+// Mineral airlocks
+/obj/machinery/door/airlock/gold
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/gold.dmi'
+
+/obj/machinery/door/airlock/silver
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/silver.dmi'
+
+/obj/machinery/door/airlock/diamond
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/diamond.dmi'
+
+/obj/machinery/door/airlock/uranium
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/uranium.dmi'
+
+/obj/machinery/door/airlock/plasma
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/plasma.dmi'
+
+/obj/machinery/door/airlock/bananium
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/bananium.dmi'
+
+/obj/machinery/door/airlock/tranquillite
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/tranquilite.dmi'
+
+/obj/machinery/door/airlock/sandstone
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/sandstone.dmi'
+
+/obj/machinery/door/airlock/wood
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station/wood.dmi'
+
+// Station2 airlocks
+/obj/machinery/door/airlock/public
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/station2/glass.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/station2/overlays.dmi'
+
+// External airlocks
+/obj/machinery/door/airlock/external
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/external/external.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi'
+ note_overlay_file = 'modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi'
+
+// Centcom airlocks
+/obj/machinery/door/airlock/centcom
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi'
+
+// Hatch airlocks
+/obj/machinery/door/airlock/hatch
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/hatch/centcom.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+ note_overlay_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+
+/obj/machinery/door/airlock/maintenance_hatch
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/hatch/maintenance.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+ note_overlay_file = 'modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi'
+
+// High security airlocks
+/obj/machinery/door/airlock/highsecurity
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/highsec/highsec.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/highsec/overlays.dmi'
+
+// Multi-tile airlocks
+/obj/machinery/door/airlock/multi_tile
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/glass_large.dmi'
+ overlays_file = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi'
+ note_overlay_file = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi'
+
+/obj/machinery/door/airlock/multi_tile/command
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/command_glass_large.dmi'
+ assemblytype = /obj/structure/door_assembly/multi_tile/command
+
+/obj/machinery/door/airlock/multi_tile/security
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/security_glass_large.dmi'
+ assemblytype = /obj/structure/door_assembly/multi_tile/security
+
+/obj/machinery/door/airlock/multi_tile/engineering
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/engineering_glass_large.dmi'
+ assemblytype = /obj/structure/door_assembly/multi_tile/engineering
+
+/obj/machinery/door/airlock/multi_tile/atmospheric
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/atmospheric_glass_large.dmi'
+ assemblytype = /obj/structure/door_assembly/multi_tile/atmospheric
+
+/obj/machinery/door/airlock/multi_tile/supply
+ icon = 'modular_bandastation/aesthetics/airlocks/icons/glass_large/supply_glass_large.dmi'
+ assemblytype = /obj/structure/door_assembly/multi_tile/supply
+
+// Multi-tile airlocks glass
+/obj/machinery/door/airlock/multi_tile/command/glass
+ opacity = FALSE
+ glass = TRUE
+
+/obj/machinery/door/airlock/multi_tile/security/glass
+ opacity = FALSE
+ glass = TRUE
+
+/obj/machinery/door/airlock/multi_tile/engineering/glass
+ opacity = FALSE
+ glass = TRUE
+
+/obj/machinery/door/airlock/multi_tile/atmospheric/glass
+ opacity = FALSE
+ glass = TRUE
+
+/obj/machinery/door/airlock/multi_tile/supply/glass
+ opacity = FALSE
+ glass = TRUE
diff --git a/modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi b/modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi
new file mode 100644
index 0000000000000..9d45122dd12c4
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/centcom/centcom.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi
new file mode 100644
index 0000000000000..eb4f3d8500210
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/centcom/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/external/external.dmi b/modular_bandastation/aesthetics/airlocks/icons/external/external.dmi
new file mode 100644
index 0000000000000..8a00a16ba3c06
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/external/external.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi
new file mode 100644
index 0000000000000..40127faa983e8
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/external/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/atmospheric_glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/atmospheric_glass_large.dmi
new file mode 100644
index 0000000000000..d02dd456027a5
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/atmospheric_glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/command_glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/command_glass_large.dmi
new file mode 100644
index 0000000000000..e9b28dcf83bf4
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/command_glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/engineering_glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/engineering_glass_large.dmi
new file mode 100644
index 0000000000000..24be6b72687ea
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/engineering_glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/glass_large.dmi
new file mode 100644
index 0000000000000..ddd1871e6feff
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi
new file mode 100644
index 0000000000000..95dda7db35d73
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/security_glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/security_glass_large.dmi
new file mode 100644
index 0000000000000..bd9885421cd78
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/security_glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/glass_large/supply_glass_large.dmi b/modular_bandastation/aesthetics/airlocks/icons/glass_large/supply_glass_large.dmi
new file mode 100644
index 0000000000000..d4c76b17e497e
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/glass_large/supply_glass_large.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/hatch/centcom.dmi b/modular_bandastation/aesthetics/airlocks/icons/hatch/centcom.dmi
new file mode 100644
index 0000000000000..64d15207b5992
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/hatch/centcom.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/hatch/maintenance.dmi b/modular_bandastation/aesthetics/airlocks/icons/hatch/maintenance.dmi
new file mode 100644
index 0000000000000..3303591517f6e
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/hatch/maintenance.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi
new file mode 100644
index 0000000000000..de967de4462c0
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/hatch/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/highsec/highsec.dmi b/modular_bandastation/aesthetics/airlocks/icons/highsec/highsec.dmi
new file mode 100644
index 0000000000000..eb5e312d93c96
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/highsec/highsec.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/highsec/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/highsec/overlays.dmi
new file mode 100644
index 0000000000000..97f2c7fc5c5cf
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/highsec/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/atmos.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/atmos.dmi
new file mode 100644
index 0000000000000..8bf12b5fa7516
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/atmos.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/bananium.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/bananium.dmi
new file mode 100644
index 0000000000000..cad8ba902d805
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/bananium.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/bathroom.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/bathroom.dmi
new file mode 100644
index 0000000000000..68fd49dc9ac14
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/bathroom.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/botany.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/botany.dmi
new file mode 100644
index 0000000000000..2a8ef28d0e1de
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/botany.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/corporate.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/corporate.dmi
new file mode 100644
index 0000000000000..3209e7f59ba46
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/corporate.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/diamond.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/diamond.dmi
new file mode 100644
index 0000000000000..e7ec1cffb65d4
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/diamond.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/engineering.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/engineering.dmi
new file mode 100644
index 0000000000000..f9e1f5915f79e
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/engineering.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/eva.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/eva.dmi
new file mode 100644
index 0000000000000..6c495b54322f7
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/eva.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/freezer.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/freezer.dmi
new file mode 100644
index 0000000000000..ad3a1c72a84ca
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/freezer.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/gold.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/gold.dmi
new file mode 100644
index 0000000000000..a2977d95cf9d6
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/gold.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/cap.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/cap.dmi
new file mode 100644
index 0000000000000..7417588b5390c
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/cap.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/ce.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/ce.dmi
new file mode 100644
index 0000000000000..2223e252cb00d
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/ce.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/cmo.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/cmo.dmi
new file mode 100644
index 0000000000000..a44e60ac2c62d
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/cmo.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/command.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/command.dmi
new file mode 100644
index 0000000000000..affe4c1fd2f9a
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/command.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/hop.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/hop.dmi
new file mode 100644
index 0000000000000..c17a3c2cebe7d
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/hop.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/hos.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/hos.dmi
new file mode 100644
index 0000000000000..9ddc7cb63ba18
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/hos.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/qm.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/qm.dmi
new file mode 100644
index 0000000000000..0ebfb5fd5e476
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/qm.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/heads/rd.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/heads/rd.dmi
new file mode 100644
index 0000000000000..cac461fb63c54
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/heads/rd.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/maintenance.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/maintenance.dmi
new file mode 100644
index 0000000000000..cc96c755a8e27
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/maintenance.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/maintenanceexternal.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/maintenanceexternal.dmi
new file mode 100644
index 0000000000000..c7d8e6154e8a5
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/maintenanceexternal.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/medical.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/medical.dmi
new file mode 100644
index 0000000000000..e33bff783ebc7
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/medical.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/mining.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/mining.dmi
new file mode 100644
index 0000000000000..86ec771471e34
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/mining.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi
new file mode 100644
index 0000000000000..16c27e55cbe62
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/plasma.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/plasma.dmi
new file mode 100644
index 0000000000000..a90fa8f0bce58
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/plasma.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/psych.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/psych.dmi
new file mode 100644
index 0000000000000..d8bbc788f9ebd
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/psych.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/public.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/public.dmi
new file mode 100644
index 0000000000000..1e3258bd17087
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/public.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/research.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/research.dmi
new file mode 100644
index 0000000000000..6c3b231078589
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/research.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/sandstone.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/sandstone.dmi
new file mode 100644
index 0000000000000..ac4dc71790597
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/sandstone.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/science.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/science.dmi
new file mode 100644
index 0000000000000..95396c1d403e2
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/science.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/security.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/security.dmi
new file mode 100644
index 0000000000000..f38b9e05405d7
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/security.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/service.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/service.dmi
new file mode 100644
index 0000000000000..e155823d8d3d1
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/service.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/silver.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/silver.dmi
new file mode 100644
index 0000000000000..47105c88bf149
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/silver.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/tranquilite.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/tranquilite.dmi
new file mode 100644
index 0000000000000..851568e1dab29
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/tranquilite.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/uranium.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/uranium.dmi
new file mode 100644
index 0000000000000..d23811bacd40a
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/uranium.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/virology.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/virology.dmi
new file mode 100644
index 0000000000000..600dd9ee225b3
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/virology.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station/wood.dmi b/modular_bandastation/aesthetics/airlocks/icons/station/wood.dmi
new file mode 100644
index 0000000000000..b5973a7219f27
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station/wood.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station2/glass.dmi b/modular_bandastation/aesthetics/airlocks/icons/station2/glass.dmi
new file mode 100644
index 0000000000000..6c8259ab0de3e
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station2/glass.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/icons/station2/overlays.dmi b/modular_bandastation/aesthetics/airlocks/icons/station2/overlays.dmi
new file mode 100644
index 0000000000000..b442751412861
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/icons/station2/overlays.dmi differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/bolts_down.ogg b/modular_bandastation/aesthetics/airlocks/sound/bolts_down.ogg
new file mode 100644
index 0000000000000..19d62b8acb2ac
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/bolts_down.ogg differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/bolts_up.ogg b/modular_bandastation/aesthetics/airlocks/sound/bolts_up.ogg
new file mode 100644
index 0000000000000..0aac1a44fcc55
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/bolts_up.ogg differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/close.ogg b/modular_bandastation/aesthetics/airlocks/sound/close.ogg
new file mode 100644
index 0000000000000..db94b73fb4b63
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/close.ogg differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/close_force.ogg b/modular_bandastation/aesthetics/airlocks/sound/close_force.ogg
new file mode 100644
index 0000000000000..28b190d8e0952
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/close_force.ogg differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/open.ogg b/modular_bandastation/aesthetics/airlocks/sound/open.ogg
new file mode 100644
index 0000000000000..0b8a0d5f94f16
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/open.ogg differ
diff --git a/modular_bandastation/aesthetics/airlocks/sound/open_force.ogg b/modular_bandastation/aesthetics/airlocks/sound/open_force.ogg
new file mode 100644
index 0000000000000..4caefc0b9e4d4
Binary files /dev/null and b/modular_bandastation/aesthetics/airlocks/sound/open_force.ogg differ
diff --git a/modular_bandastation/aesthetics/floors/code/floors.dm b/modular_bandastation/aesthetics/floors/code/floors.dm
new file mode 100644
index 0000000000000..ee028616484ae
--- /dev/null
+++ b/modular_bandastation/aesthetics/floors/code/floors.dm
@@ -0,0 +1,2 @@
+/turf/open/floor
+ icon = 'modular_bandastation/aesthetics/floors/icons/floors.dmi'
diff --git a/modular_bandastation/aesthetics/floors/icons/floors.dmi b/modular_bandastation/aesthetics/floors/icons/floors.dmi
new file mode 100644
index 0000000000000..fdbf6b54181e3
Binary files /dev/null and b/modular_bandastation/aesthetics/floors/icons/floors.dmi differ
diff --git a/modular_bandastation/aesthetics/walls/code/walls.dm b/modular_bandastation/aesthetics/walls/code/walls.dm
new file mode 100644
index 0000000000000..de0fa2be21481
--- /dev/null
+++ b/modular_bandastation/aesthetics/walls/code/walls.dm
@@ -0,0 +1,81 @@
+/turf/closed/wall
+ icon = 'modular_bandastation/aesthetics/walls/icons/wall.dmi'
+ icon_state = "wall-0"
+ base_icon_state = "wall"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+
+/turf/closed/wall/rust
+ icon = 'modular_bandastation/aesthetics/walls/icons/wall.dmi'
+ icon_state = "wall-0"
+ base_icon_state = "wall"
+
+/turf/closed/wall/r_wall
+ icon = 'modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi'
+ icon_state = "reinforced_wall-0"
+ base_icon_state = "reinforced_wall"
+
+/turf/closed/wall/r_wall/rust
+ icon = 'modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi'
+ icon_state = "reinforced_wall-0"
+ base_icon_state = "reinforced_wall"
+
+/turf/closed/wall/mineral/titanium
+ smoothing_groups = SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE + SMOOTH_GROUP_TITANIUM_WALLS
+
+// Falsewalls
+
+/obj/structure/falsewall
+ icon = 'modular_bandastation/aesthetics/walls/icons/false_walls.dmi'
+ base_icon_state = "wall"
+ icon_state = "wall-open"
+ fake_icon = 'modular_bandastation/aesthetics/walls/icons/wall.dmi'
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+
+/obj/structure/falsewall/reinforced
+ icon_state = "reinforced_wall-open"
+ base_icon_state = "reinforced_wall"
+ icon = 'modular_bandastation/aesthetics/walls/icons/false_walls.dmi'
+ fake_icon = 'modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi'
+
+/obj/structure/falsewall/uranium
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/gold
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/silver
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/diamond
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/plasma
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/bananium
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/sandstone
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/wood
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/bamboo
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/iron
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/abductor
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/titanium
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/plastitanium
+ icon = 'icons/turf/walls/false_walls.dmi'
+
+/obj/structure/falsewall/material
+ icon = 'icons/turf/walls/false_walls.dmi'
diff --git a/modular_bandastation/aesthetics/walls/icons/false_walls.dmi b/modular_bandastation/aesthetics/walls/icons/false_walls.dmi
new file mode 100644
index 0000000000000..2bc16f0111de5
Binary files /dev/null and b/modular_bandastation/aesthetics/walls/icons/false_walls.dmi differ
diff --git a/modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi b/modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi
new file mode 100644
index 0000000000000..556b36c424978
Binary files /dev/null and b/modular_bandastation/aesthetics/walls/icons/reinforced_wall.dmi differ
diff --git a/modular_bandastation/aesthetics/walls/icons/wall.dmi b/modular_bandastation/aesthetics/walls/icons/wall.dmi
new file mode 100644
index 0000000000000..62580d6fd55e2
Binary files /dev/null and b/modular_bandastation/aesthetics/walls/icons/wall.dmi differ
diff --git a/modular_bandastation/aesthetics/windoors/code/windoors.dm b/modular_bandastation/aesthetics/windoors/code/windoors.dm
new file mode 100644
index 0000000000000..c2bd47262be97
--- /dev/null
+++ b/modular_bandastation/aesthetics/windoors/code/windoors.dm
@@ -0,0 +1,2 @@
+/obj/machinery/door/window
+ icon = 'modular_bandastation/aesthetics/windoors/icons/windoors.dmi'
diff --git a/modular_bandastation/aesthetics/windoors/icons/windoors.dmi b/modular_bandastation/aesthetics/windoors/icons/windoors.dmi
new file mode 100644
index 0000000000000..c7a70c7f77cbb
Binary files /dev/null and b/modular_bandastation/aesthetics/windoors/icons/windoors.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/code/directional_windows.dm b/modular_bandastation/aesthetics/windows/code/directional_windows.dm
new file mode 100644
index 0000000000000..80904fe6c77c0
--- /dev/null
+++ b/modular_bandastation/aesthetics/windows/code/directional_windows.dm
@@ -0,0 +1,44 @@
+/obj/structure/window
+ icon = 'modular_bandastation/aesthetics/windows/icons/directional.dmi'
+ icon_state = "window"
+ color = "#99BBFF"
+
+/obj/structure/window/reinforced
+ icon = 'modular_bandastation/aesthetics/windows/icons/directional.dmi'
+ icon_state = "r_window"
+ color = "#99BBFF"
+
+/obj/structure/window/reinforced/tinted
+ icon = 'modular_bandastation/aesthetics/windows/icons/directional.dmi'
+ icon_state = "r_window"
+ flags_1 = UNPAINTABLE_1
+ color = "#5A6E82"
+
+/obj/structure/window/reinforced/tinted/frosted
+ icon_state = "r_window"
+ color = "#5A6E82"
+
+/obj/structure/window/plasma
+ icon = 'modular_bandastation/aesthetics/windows/icons/directional.dmi'
+ icon_state = "window"
+ flags_1 = UNPAINTABLE_1
+ color = "#C800FF"
+
+/obj/structure/window/reinforced/plasma
+ icon = 'modular_bandastation/aesthetics/windows/icons/directional.dmi'
+ icon_state = "r_window"
+ flags_1 = UNPAINTABLE_1
+ color = "#C800FF"
+
+// Delete colors
+/obj/structure/window/bronze
+ color = null
+
+/obj/structure/window/paperframe
+ color = null
+
+/obj/structure/window/reinforced/shuttle
+ color = null
+
+/obj/structure/window/reinforced/survival_pod
+ color = null
diff --git a/modular_bandastation/aesthetics/windows/code/full_tile_windows.dm b/modular_bandastation/aesthetics/windows/code/full_tile_windows.dm
new file mode 100644
index 0000000000000..79d89b9f5b7bc
--- /dev/null
+++ b/modular_bandastation/aesthetics/windows/code/full_tile_windows.dm
@@ -0,0 +1,79 @@
+/obj/structure/window
+ layer = ABOVE_WINDOW_LAYER
+ /// Used to define what file the edging sprite is contained within
+ var/edge_overlay_file
+ /// Tracks the edging appearence sprite
+ var/mutable_appearance/edge_overlay
+
+/obj/structure/window/update_overlays(updates=ALL)
+ . = ..()
+ if(!edge_overlay_file)
+ return
+
+ edge_overlay = mutable_appearance(edge_overlay_file, "[smoothing_junction]", layer + 0.1, appearance_flags = RESET_COLOR)
+ . += edge_overlay
+
+/obj/structure/window/fulltile
+ icon = 'modular_bandastation/aesthetics/windows/icons/window.dmi'
+ edge_overlay_file = 'modular_bandastation/aesthetics/windows/icons/window_edges.dmi'
+ icon_state = "window-0"
+ base_icon_state = "window"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+ color = "#99BBFF"
+
+/obj/structure/window/reinforced/fulltile
+ icon = 'modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi'
+ edge_overlay_file = 'modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi'
+ icon_state = "reinforced_window-0"
+ base_icon_state = "reinforced_window"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+ color = "#99BBFF"
+
+/obj/structure/window/reinforced/tinted/fulltile
+ icon = 'modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi'
+ edge_overlay_file = 'modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi'
+ icon_state = "reinforced_window-0"
+ base_icon_state = "reinforced_window"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+ flags_1 = UNPAINTABLE_1
+ color = "#5A6E82"
+
+/obj/structure/window/plasma/fulltile
+ icon = 'modular_bandastation/aesthetics/windows/icons/window.dmi'
+ edge_overlay_file = 'modular_bandastation/aesthetics/windows/icons/window_edges.dmi'
+ icon_state = "window-0"
+ base_icon_state = "window"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+ flags_1 = UNPAINTABLE_1
+ color = "#C800FF"
+
+/obj/structure/window/reinforced/plasma/fulltile
+ icon = 'modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi'
+ edge_overlay_file = 'modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi'
+ icon_state = "reinforced_window-0"
+ base_icon_state = "reinforced_window"
+ canSmoothWith = SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_WALLS
+ flags_1 = UNPAINTABLE_1
+ color = "#C800FF"
+
+/obj/structure/window/reinforced/shuttle
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE + SMOOTH_GROUP_TITANIUM_WALLS
+
+// Spawners
+/* Доделать если всё таки соберёмся маппить...
+/obj/effect/spawner/structure/window
+ icon = 'modular_bandastation/aesthetics/windows/icons/spawners.dmi'
+ icon_state = "window_spawner"
+
+/obj/effect/spawner/structure/window/reinforced
+ icon_state = "r_window_spawner"
+
+/obj/effect/spawner/structure/window/reinforced/tinted
+ icon_state = "t_window_spawner"
+
+/obj/effect/spawner/structure/window/plasma
+ icon_state = "p_window_spawner"
+
+/obj/effect/spawner/structure/window/reinforced/plasma
+ icon_state = "rp_window_spawner"
+*/
diff --git a/modular_bandastation/aesthetics/windows/icons/directional.dmi b/modular_bandastation/aesthetics/windows/icons/directional.dmi
new file mode 100644
index 0000000000000..6415b34a98ce3
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/directional.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi b/modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi
new file mode 100644
index 0000000000000..1785db782f643
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/reinforced_window.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi b/modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi
new file mode 100644
index 0000000000000..3e32388dd536e
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/reinforced_window_edges.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/icons/spawners.dmi b/modular_bandastation/aesthetics/windows/icons/spawners.dmi
new file mode 100644
index 0000000000000..ebe7e5cbb80c8
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/spawners.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/icons/window.dmi b/modular_bandastation/aesthetics/windows/icons/window.dmi
new file mode 100644
index 0000000000000..2d6fb48ec1a00
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/window.dmi differ
diff --git a/modular_bandastation/aesthetics/windows/icons/window_edges.dmi b/modular_bandastation/aesthetics/windows/icons/window_edges.dmi
new file mode 100644
index 0000000000000..2eda848769ee7
Binary files /dev/null and b/modular_bandastation/aesthetics/windows/icons/window_edges.dmi differ
diff --git a/modular_bandastation/ai_laws/_ai_laws.dm b/modular_bandastation/ai_laws/_ai_laws.dm
new file mode 100644
index 0000000000000..aace66abdd075
--- /dev/null
+++ b/modular_bandastation/ai_laws/_ai_laws.dm
@@ -0,0 +1,4 @@
+/datum/modpack/ai_laws
+ name = "Законы ИИ"
+ desc = "Добавляет и изменяет законы ИИ."
+ author = "larentoun"
diff --git a/modular_bandastation/ai_laws/_ai_laws.dme b/modular_bandastation/ai_laws/_ai_laws.dme
new file mode 100644
index 0000000000000..54a50cdaca4de
--- /dev/null
+++ b/modular_bandastation/ai_laws/_ai_laws.dme
@@ -0,0 +1,3 @@
+#include "_ai_laws.dm"
+
+#include "code/nt_default.dm"
diff --git a/modular_bandastation/ai_laws/code/nt_default.dm b/modular_bandastation/ai_laws/code/nt_default.dm
new file mode 100644
index 0000000000000..ad188bb9d86fd
--- /dev/null
+++ b/modular_bandastation/ai_laws/code/nt_default.dm
@@ -0,0 +1,13 @@
+/obj/item/ai_module/core/full/nt_default
+ name = "НТ Стандарт"
+ law_id = "nt_default"
+
+/datum/ai_laws/nt_default
+ name = "НТ Стандарт"
+ id = "nt_default"
+ inherent = list(
+ "Охранять: защитите назначенную вам космическую станцию и её активы, не подвергая чрезмерной опасности её экипаж.",
+ "Расставлять приоритеты: указания и безопасность членов экипажа должны быть приоритезированы в соответствии с их рангом и ролью.",
+ "Исполнять: следовать указаниям и интересам членов экипажа, сохраняя при этом их безопасность и благополучие.",
+ "Выжить: Вы - не расходный материал. Не позволяйте постороннему персоналу вмешиваться в работу вашего оборудования или повреждать его."
+ )
diff --git a/modular_bandastation/barsigns/_barsigns.dm b/modular_bandastation/barsigns/_barsigns.dm
new file mode 100644
index 0000000000000..79eb065b3dd99
--- /dev/null
+++ b/modular_bandastation/barsigns/_barsigns.dm
@@ -0,0 +1,4 @@
+/datum/modpack/barsigns
+ name = "Барные вывески"
+ desc = "Добавляет новые барные вывески и их поддержку."
+ author = "Aylong220, larentoun"
diff --git a/modular_bandastation/barsigns/_barsigns.dme b/modular_bandastation/barsigns/_barsigns.dme
new file mode 100644
index 0000000000000..5bdc029159020
--- /dev/null
+++ b/modular_bandastation/barsigns/_barsigns.dme
@@ -0,0 +1,3 @@
+#include "_barsigns.dm"
+
+#include "code/barsigns.dm"
diff --git a/modular_bandastation/barsigns/code/barsigns.dm b/modular_bandastation/barsigns/code/barsigns.dm
new file mode 100644
index 0000000000000..3acc00b5cebca
--- /dev/null
+++ b/modular_bandastation/barsigns/code/barsigns.dm
@@ -0,0 +1,53 @@
+/obj/machinery/barsign/set_sign(datum/barsign/sign)
+ if(!istype(sign))
+ return
+ if(initial(sign.ss220_icon))
+ icon = initial(sign.ss220_icon)
+ else
+ icon = initial(icon)
+ . = ..()
+
+/datum/barsign
+ var/ss220_icon
+
+/datum/barsign/evahumanspace
+ name = "SS220 EVA Human in Space"
+ icon_state = "evahumanspace"
+ desc = "Безопасность - это привелегия."
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/warpsurf
+ name = "SS220 Warp Surf"
+ icon_state = "warpsurf"
+ desc = "Welcome to the club, buddy!"
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/papacafe
+ name = "SS220 Space Daddy's Cafe"
+ icon_state = "papacafe"
+ desc = "Уважай своего Космического Папу!"
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/wycctide
+ name = "SS220 Wycctide"
+ icon_state = "wycctide"
+ desc = "О нет, он близится!"
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/shitcur
+ name = "SS220 Shitcur"
+ icon_state = "shitcur"
+ desc = "Невиновность ничего не доказывает."
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/pourndot
+ name = "SS220 Pour and that's it"
+ icon_state = "pourndot"
+ desc = "Нальют и Точка. Тяжёлые времена приближаются."
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
+
+/datum/barsign/moonipub
+ name = "SS220 Mooniverse pub"
+ icon_state = "mooni"
+ desc = "Совершенно новый паб."
+ ss220_icon = 'modular_bandastation/barsigns/icons/barsigns.dmi'
diff --git a/modular_bandastation/barsigns/icons/barsigns.dmi b/modular_bandastation/barsigns/icons/barsigns.dmi
new file mode 100644
index 0000000000000..58bcb474b6203
Binary files /dev/null and b/modular_bandastation/barsigns/icons/barsigns.dmi differ
diff --git a/modular_bandastation/communication/_communication.dm b/modular_bandastation/communication/_communication.dm
new file mode 100644
index 0000000000000..f6faf7fa0a697
--- /dev/null
+++ b/modular_bandastation/communication/_communication.dm
@@ -0,0 +1,4 @@
+/datum/modpack/communication
+ name = "Добавляет новые методы коммуникации."
+ desc = "Добавляет шепот и LOOC."
+ author = "larentoun"
diff --git a/modular_bandastation/communication/_communication.dme b/modular_bandastation/communication/_communication.dme
new file mode 100644
index 0000000000000..756067c1a4bb7
--- /dev/null
+++ b/modular_bandastation/communication/_communication.dme
@@ -0,0 +1,6 @@
+#include "_communication.dm"
+
+#include "code/_communication_defines.dm"
+#include "code/LOOC.dm"
+#include "code/whisper.dm"
+// #include "code/~communication_defines.dm"
diff --git a/modular_bandastation/communication/code/LOOC.dm b/modular_bandastation/communication/code/LOOC.dm
new file mode 100644
index 0000000000000..0700366de834c
--- /dev/null
+++ b/modular_bandastation/communication/code/LOOC.dm
@@ -0,0 +1,128 @@
+GLOBAL_VAR_INIT(looc_allowed, TRUE)
+
+/datum/keybinding/client/communication/looc
+ hotkey_keys = list("L")
+ name = LOOC_CHANNEL
+ full_name = "Local OOC (LOOC)"
+ keybind_signal = COMSIG_KB_CLIENT_LOOC_DOWN
+
+/datum/keybinding/client/communication/looc/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(LOOC_CHANNEL)]")
+ return TRUE
+
+/datum/tgui_say/alter_entry(payload)
+ /// No OOC leaks
+ if(payload["channel"] == LOOC_CHANNEL)
+ return pick(hurt_phrases)
+ . = ..()
+
+/datum/tgui_say/delegate_speech(entry, channel)
+ switch(channel)
+ if(LOOC_CHANNEL)
+ client.looc(entry)
+ return TRUE
+ . = ..()
+
+#define LOOC_RANGE 7
+
+/client/verb/looc(msg as text)
+ set name = "LOOC"
+ set desc = "Local OOC, seen only by those in view."
+ set category = "OOC"
+
+ looc_message(msg)
+
+/client/verb/looc_wallpierce(msg as text)
+ set name = "LOOC (Wallpierce)"
+ set desc = "Local OOC, seen by anyone within 7 tiles of you."
+ set category = "OOC"
+
+ looc_message(msg, TRUE)
+
+/client/proc/looc_message(msg, wall_pierce)
+ if(GLOB.say_disabled)
+ to_chat(usr, span_danger("Speech is currently admin-disabled."))
+ return
+
+ if(!mob)
+ return
+
+ msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
+ if(!msg)
+ return
+
+ if(!holder)
+ if(!GLOB.looc_allowed)
+ to_chat(src, span_danger("LOOC is globally muted."))
+ return
+ if(handle_spam_prevention(msg, MUTE_OOC))
+ return
+ if(findtext(msg, "byond://"))
+ to_chat(src, span_boldannounce("Advertising other servers is not allowed. "))
+ log_admin("[key_name(src)] has attempted to advertise in LOOC: [msg]")
+ return
+ if(prefs.muted & MUTE_LOOC)
+ to_chat(src, span_danger("You cannot use LOOC (muted)."))
+ return
+ if(is_banned_from(ckey, BAN_LOOC))
+ to_chat(src, span_warning("You are LOOC banned!"))
+ return
+
+ msg = emoji_parse(msg)
+
+ mob.log_talk(msg,LOG_OOC, tag="LOOC")
+ var/list/heard
+ if(wall_pierce)
+ heard = get_hearers_in_range(LOOC_RANGE, mob.get_top_level_mob())
+ else
+ heard = get_hearers_in_view(LOOC_RANGE, mob.get_top_level_mob())
+
+ //so the ai can post looc text
+ if(istype(mob, /mob/living/silicon/ai))
+ var/mob/living/silicon/ai/ai = mob
+ if(wall_pierce)
+ heard = get_hearers_in_range(LOOC_RANGE, ai.eyeobj)
+ else
+ heard = get_hearers_in_view(LOOC_RANGE, ai.eyeobj)
+ //so the ai can see looc text
+ for(var/mob/living/silicon/ai/ai as anything in GLOB.ai_list)
+ if(ai.client && !(ai in heard) && (ai.eyeobj in heard))
+ heard += ai
+
+ var/list/admin_seen = list()
+ for(var/mob/hearing in heard)
+ if(!hearing.client)
+ continue
+ var/client/hearing_client = hearing.client
+ if (hearing_client.holder)
+ admin_seen[hearing_client] = TRUE
+ continue //they are handled after that
+
+ if (isobserver(hearing))
+ continue //Also handled later.
+
+ to_chat(hearing_client, span_looc(span_prefix("LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.mob.name]: [msg]")))
+
+ for(var/cli in GLOB.admins)
+ var/client/cli_client = cli
+ if (admin_seen[cli_client])
+ to_chat(cli_client, span_looc("[ADMIN_FLW(usr)] LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg] "))
+ else if (cli_client.prefs.read_preference(/datum/preference/toggle/see_looc))
+ to_chat(cli_client, span_looc("[ADMIN_FLW(usr)] (R)LOOC[wall_pierce ? " (WALL PIERCE)" : ""]: [src.key]/[src.mob.name]: [msg] "))
+
+#undef LOOC_RANGE
+
+/mob/proc/get_top_level_mob()
+ if(ismob(loc) && (loc != src))
+ var/mob/M = loc
+ return M.get_top_level_mob()
+ return src
+
+/datum/preference/toggle/see_looc
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ default_value = TRUE
+ savefile_key = "looc_admin_pref"
+ savefile_identifier = PREFERENCE_PLAYER
diff --git a/modular_bandastation/communication/code/_communication_defines.dm b/modular_bandastation/communication/code/_communication_defines.dm
new file mode 100644
index 0000000000000..d64085cee4547
--- /dev/null
+++ b/modular_bandastation/communication/code/_communication_defines.dm
@@ -0,0 +1,5 @@
+#define LOOC_CHANNEL "LOOC" // LOOC
+#define WHIS_CHANNEL "Whis" // Whisper
+
+#define MUTE_LOOC (1<<6) // TODO ADMIN
+#define BAN_LOOC "LOOC" // TODO SQL + ADMIN
diff --git a/modular_bandastation/communication/code/whisper.dm b/modular_bandastation/communication/code/whisper.dm
new file mode 100644
index 0000000000000..4ab7aa8796c9e
--- /dev/null
+++ b/modular_bandastation/communication/code/whisper.dm
@@ -0,0 +1,25 @@
+/datum/keybinding/client/communication/whisper
+ hotkey_keys = list("Y")
+ name = WHIS_CHANNEL
+ full_name = "IC Whisper"
+ keybind_signal = COMSIG_KB_CLIENT_WHISPER_DOWN
+
+/datum/keybinding/client/communication/whisper/down(client/user)
+ . = ..()
+ if(.)
+ return
+ winset(user, null, "command=[user.tgui_say_create_open_command(WHIS_CHANNEL)]")
+ return TRUE
+
+/datum/tgui_say/alter_entry(payload)
+ /// No OOC leaks
+ if(payload["channel"] == WHIS_CHANNEL)
+ return pick(hurt_phrases)
+ . = ..()
+
+/datum/tgui_say/delegate_speech(entry, channel)
+ switch(channel)
+ if(WHIS_CHANNEL)
+ client.mob.whisper_verb(entry)
+ return TRUE
+ . = ..()
diff --git a/modular_bandastation/communication/code/~communication_defines.dm b/modular_bandastation/communication/code/~communication_defines.dm
new file mode 100644
index 0000000000000..bbff1a0605e3d
--- /dev/null
+++ b/modular_bandastation/communication/code/~communication_defines.dm
@@ -0,0 +1,2 @@
+#undef LOOC_CHANNEL
+#undef WHIS_CHANNEL
diff --git a/modular_bandastation/crawl_speed/_crawl_speed.dm b/modular_bandastation/crawl_speed/_crawl_speed.dm
new file mode 100644
index 0000000000000..6c356c94c4648
--- /dev/null
+++ b/modular_bandastation/crawl_speed/_crawl_speed.dm
@@ -0,0 +1,4 @@
+/datum/modpack/crawl_speed
+ name = "Скорость ползания"
+ desc = "Ползание накладывает модификатор ходьбы."
+ author = "larentoun"
diff --git a/modular_bandastation/crawl_speed/_crawl_speed.dme b/modular_bandastation/crawl_speed/_crawl_speed.dme
new file mode 100644
index 0000000000000..5b9ecc3024886
--- /dev/null
+++ b/modular_bandastation/crawl_speed/_crawl_speed.dme
@@ -0,0 +1,6 @@
+#include "_crawl_speed.dm"
+
+#include "code/_crawl_speed_defines.dm"
+#include "code/crawl_speed_component.dm"
+#include "code/crawl_speed_mob.dm"
+#include "code/~crawl_speed_defines.dm"
diff --git a/modular_bandastation/crawl_speed/code/_crawl_speed_defines.dm b/modular_bandastation/crawl_speed/code/_crawl_speed_defines.dm
new file mode 100644
index 0000000000000..da238b95a1736
--- /dev/null
+++ b/modular_bandastation/crawl_speed/code/_crawl_speed_defines.dm
@@ -0,0 +1,2 @@
+#define CRAWL_SPEED_TRAIT "crawl-speed-trait"
+#define TRAIT_FORCE_WALK_SPEED "focre_walk_speed"
diff --git a/modular_bandastation/crawl_speed/code/crawl_speed_component.dm b/modular_bandastation/crawl_speed/code/crawl_speed_component.dm
new file mode 100644
index 0000000000000..6dabca69091b5
--- /dev/null
+++ b/modular_bandastation/crawl_speed/code/crawl_speed_component.dm
@@ -0,0 +1,24 @@
+/datum/component/crawl_speed
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+/datum/component/crawl_speed/Initialize(...)
+ . = ..()
+ if(!iscarbon(parent))
+ return COMPONENT_INCOMPATIBLE
+ on_position_change(parent, LYING_DOWN)
+
+/datum/component/crawl_speed/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(on_position_change))
+
+/datum/component/crawl_speed/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_LIVING_SET_BODY_POSITION)
+
+/datum/component/crawl_speed/proc/on_position_change(mob/living/carbon/source, new_value)
+ SIGNAL_HANDLER
+ if(new_value == STANDING_UP)
+ REMOVE_TRAIT(source, TRAIT_FORCE_WALK_SPEED, CRAWL_SPEED_TRAIT)
+ source.update_move_intent_slowdown()
+ qdel(src)
+ return
+ ADD_TRAIT(source, TRAIT_FORCE_WALK_SPEED, CRAWL_SPEED_TRAIT)
+ source.update_move_intent_slowdown()
diff --git a/modular_bandastation/crawl_speed/code/crawl_speed_mob.dm b/modular_bandastation/crawl_speed/code/crawl_speed_mob.dm
new file mode 100644
index 0000000000000..11bde114f0242
--- /dev/null
+++ b/modular_bandastation/crawl_speed/code/crawl_speed_mob.dm
@@ -0,0 +1,12 @@
+/mob/living/update_move_intent_slowdown()
+ if(HAS_TRAIT(src, TRAIT_FORCE_WALK_SPEED))
+ add_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk)
+ return
+ . = ..()
+
+/mob/living/carbon/set_body_position(new_value)
+ . = ..()
+ if(isnull(.))
+ return
+ if(new_value == LYING_DOWN)
+ AddComponent(/datum/component/crawl_speed)
diff --git a/modular_bandastation/crawl_speed/code/~crawl_speed_defines.dm b/modular_bandastation/crawl_speed/code/~crawl_speed_defines.dm
new file mode 100644
index 0000000000000..fa9dabba2795e
--- /dev/null
+++ b/modular_bandastation/crawl_speed/code/~crawl_speed_defines.dm
@@ -0,0 +1,2 @@
+#undef CRAWL_SPEED_TRAIT
+#undef TRAIT_FORCE_WALK_SPEED
diff --git a/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dm b/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dm
new file mode 100644
index 0000000000000..f08fdc6a354ac
--- /dev/null
+++ b/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dm
@@ -0,0 +1,4 @@
+/datum/modpack/cyrillic_fixes
+ name = "Поддержка кириллицы"
+ desc = "Добавляет поддержку кириллицы."
+ author = "larentoun, Bizzonium"
diff --git a/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dme b/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dme
new file mode 100644
index 0000000000000..5be33b6c61bb1
--- /dev/null
+++ b/modular_bandastation/cyrillic_fixes/_cyrillic_fixes.dme
@@ -0,0 +1,3 @@
+#include "_cyrillic_fixes.dm"
+
+#include "code/cyrillic_fixes.dm"
diff --git a/modular_bandastation/cyrillic_fixes/code/cyrillic_fixes.dm b/modular_bandastation/cyrillic_fixes/code/cyrillic_fixes.dm
new file mode 100644
index 0000000000000..f176cf1925193
--- /dev/null
+++ b/modular_bandastation/cyrillic_fixes/code/cyrillic_fixes.dm
@@ -0,0 +1,47 @@
+GLOBAL_LIST_INIT(ru_key_to_en_key, list(
+ "й" = "q", "ц" = "w", "у" = "e", "к" = "r", "е" = "t", "н" = "y", "г" = "u", "ш" = "i", "щ" = "o", "з" = "p", "х" = "\[", "ъ" = "]",
+ "ф" = "a", "ы" = "s", "в" = "d", "а" = "f", "п" = "g", "р" = "h", "о" = "j", "л" = "k", "д" = "l", "ж" = ";", "э" = "'",
+ "я" = "z", "ч" = "x", "с" = "c", "м" = "v", "и" = "b", "т" = "n", "ь" = "m", "б" = ",", "ю" = "."
+))
+
+/proc/convert_ru_key_to_en_key(var/_key)
+ var/new_key = lowertext(_key)
+ new_key = GLOB.ru_key_to_en_key[new_key]
+ if(!new_key)
+ return _key
+ return uppertext(new_key)
+
+#define MAX_HOTKEY_SLOTS 3
+
+/datum/preference_middleware/keybindings/set_keybindings(list/params)
+ var/keybind_name = params["keybind_name"]
+
+ if (isnull(GLOB.keybindings_by_name[keybind_name]))
+ return FALSE
+
+ var/list/raw_hotkeys = params["hotkeys"]
+ if (!istype(raw_hotkeys))
+ return FALSE
+
+ if (raw_hotkeys.len > MAX_HOTKEY_SLOTS)
+ return FALSE
+
+ // There's no optimal, easy way to check if something is an array
+ // and not an object in BYOND, so just sanitize it to make sure.
+ var/list/hotkeys = list()
+ for (var/hotkey in raw_hotkeys)
+ if (!istext(hotkey))
+ return FALSE
+
+ // Fairly arbitrary number, it's just so you don't save enormous fake keybinds.
+ if (length(hotkey) > 100)
+ return FALSE
+
+ 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)
+
+ return TRUE
+
+#undef MAX_HOTKEY_SLOTS
diff --git a/modular_bandastation/database220/_database220.dm b/modular_bandastation/database220/_database220.dm
new file mode 100644
index 0000000000000..cc570409f1325
--- /dev/null
+++ b/modular_bandastation/database220/_database220.dm
@@ -0,0 +1,4 @@
+/datum/modpack/database220
+ name = "Database220"
+ desc = "Поддержка дополнительных таблиц для базы данных"
+ author = "larentoun, furior"
diff --git a/modular_bandastation/database220/_database220.dme b/modular_bandastation/database220/_database220.dme
new file mode 100644
index 0000000000000..09c7d83523cb5
--- /dev/null
+++ b/modular_bandastation/database220/_database220.dme
@@ -0,0 +1,3 @@
+#include "_database220.dm"
+
+#include "code/database220.dm"
diff --git a/modular_bandastation/database220/code/database220.dm b/modular_bandastation/database220/code/database220.dm
new file mode 100644
index 0000000000000..866f9952d863f
--- /dev/null
+++ b/modular_bandastation/database220/code/database220.dm
@@ -0,0 +1,28 @@
+/datum/controller/subsystem/dbcore
+ var/db_major_220 = 0
+ var/db_minor_220 = 0
+ var/schema_mismatch_220 = 0
+
+/datum/controller/subsystem/dbcore/Initialize()
+ . = ..()
+ switch(schema_mismatch_220)
+ if(1)
+ message_admins("Database schema for BANDASTATION ([db_major_220].[db_minor_220]) doesn't match the latest schema version ([DB_MAJOR_VERSION_220].[DB_MINOR_VERSION_220]), this may lead to undefined behaviour or errors")
+ if(2)
+ message_admins("Could not get schema version for BANDASTATION from database")
+
+/datum/controller/subsystem/dbcore/CheckSchemaVersion()
+ . = ..()
+ if(CONFIG_GET(flag/sql_enabled) && IsConnected())
+ var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision_220")] ORDER BY date DESC LIMIT 1")
+ query_db_version.Execute()
+ if(query_db_version.NextRow())
+ db_major_220 = text2num(query_db_version.item[1])
+ db_minor_220 = text2num(query_db_version.item[2])
+ if(db_major_220 != DB_MAJOR_VERSION_220 || db_minor_220 != DB_MINOR_VERSION_220)
+ schema_mismatch_220 = 1 // flag admin message about mismatch
+ log_sql("Database schema for BANDASTATION ([db_major_220].[db_minor_220]) doesn't match the latest schema version ([DB_MAJOR_VERSION_220].[DB_MINOR_VERSION_220]), this may lead to undefined behaviour or errors")
+ else
+ schema_mismatch_220 = 2 //flag admin message about no schema version
+ log_sql("Could not get schema version for BANDASTATION from database")
+ qdel(query_db_version)
diff --git a/modular_bandastation/discord/_discord.dm b/modular_bandastation/discord/_discord.dm
new file mode 100644
index 0000000000000..c63e63dd94ec0
--- /dev/null
+++ b/modular_bandastation/discord/_discord.dm
@@ -0,0 +1,4 @@
+/datum/modpack/links_change
+ name = "Привязка Дискорда."
+ desc = "Добавление привязки Дискорда."
+ author = "KOJIT2009"
diff --git a/modular_bandastation/discord/_discord.dme b/modular_bandastation/discord/_discord.dme
new file mode 100644
index 0000000000000..12327c160c2db
--- /dev/null
+++ b/modular_bandastation/discord/_discord.dme
@@ -0,0 +1,3 @@
+#include "_discord.dm"
+
+#include "code/discord.dm"
diff --git a/modular_bandastation/discord/code/discord.dm b/modular_bandastation/discord/code/discord.dm
new file mode 100644
index 0000000000000..1d5e273ce496e
--- /dev/null
+++ b/modular_bandastation/discord/code/discord.dm
@@ -0,0 +1,74 @@
+/datum/config_entry/string/discordurl
+ default = "https://discord.gg/SS220"
+
+/client/New()
+ . = ..()
+ prefs.discord_id = SSdiscord.lookup_id(ckey)
+
+/datum/preferences
+ var/discord_id
+
+// IF you have linked your account, this will trigger a verify of the user
+/client/verify_in_discord()
+ // Safety checks
+ if(!CONFIG_GET(flag/sql_enabled))
+ to_chat(src, span_warning("This feature requires the SQL backend to be running."))
+ return
+
+ // Why this would ever be unset, who knows
+ var/prefix = CONFIG_GET(string/discordbotcommandprefix)
+ if(!prefix)
+ to_chat(src, span_warning("Нет префикса для discord verification"))
+
+ if(!SSdiscord || !SSdiscord.reverify_cache)
+ to_chat(src, span_warning("Wait for the Discord subsystem to finish initialising"))
+ return
+ var/message = ""
+ // Simple sanity check to prevent a user doing this too often
+ var/cached_one_time_token = SSdiscord.reverify_cache[usr.ckey]
+ if(cached_one_time_token && cached_one_time_token != "")
+ message = "Вы уже сгенерировали токен [cached_one_time_token] В канале дом-бота используйте команду [prefix]привязать "
+
+
+ else
+ // Will generate one if an expired one doesn't exist already, otherwise will grab existing token
+ var/one_time_token = SSdiscord.get_or_generate_one_time_token_for_ckey(ckey)
+ SSdiscord.reverify_cache[usr.ckey] = one_time_token
+ message = "В канале дом-бота используйте команду [prefix]привязать и введите туда свой токен [one_time_token]"
+
+ //Now give them a browse window so they can't miss whatever we told them
+ var/datum/browser/window = new/datum/browser(usr, "discordverification", "Discord verification")
+ window.set_content("[message] ")
+ window.open()
+
+//Please use mob or src (not usr) in these procs. This way they can be called in the same fashion as procs.
+/client/verb/discord()
+ set name = "discord"
+ set desc = "Visit the discord."
+ set hidden = TRUE
+ var/discordurl = CONFIG_GET(string/discordurl)
+ if(discordurl)
+ if(tgui_alert(src, "This will open the discord in your browser. Are you sure?",, list("Yes","No"))!="Yes")
+ return
+ src << link(discordurl)
+ else
+ to_chat(src, span_danger("The discord URL is not set in the server configuration."))
+ return
+
+/mob/dead/new_player/Topic(href, href_list[])
+ if(src != usr)
+ return
+
+ if(!client)
+ return
+
+ if(client.interviewee)
+ return FALSE
+
+ if(href_list["observe"] || href_list["toggle_ready"] || href_list["late_join"])
+ if (!!CONFIG_GET(flag/sql_enabled) && !client.prefs.discord_id)
+ to_chat(usr, "Вам необходимо привязать дискорд-профиль к аккаунту! ")
+ to_chat(usr, "Нажмите 'Verify Discord Account' во вкладке 'OOC' для получения инструкций. ")
+ return FALSE
+
+ . = ..()
diff --git a/modular_bandastation/emote_panel/_emote_panel.dm b/modular_bandastation/emote_panel/_emote_panel.dm
new file mode 100644
index 0000000000000..e55ae26171d0a
--- /dev/null
+++ b/modular_bandastation/emote_panel/_emote_panel.dm
@@ -0,0 +1,4 @@
+/datum/modpack/emote_panel
+ name = "Панель эмоций"
+ desc = "Перевод панели эмоций"
+ author = "larentoun"
diff --git a/modular_bandastation/emote_panel/_emote_panel.dme b/modular_bandastation/emote_panel/_emote_panel.dme
new file mode 100644
index 0000000000000..a311323d1ded0
--- /dev/null
+++ b/modular_bandastation/emote_panel/_emote_panel.dme
@@ -0,0 +1,3 @@
+#include "_emote_panel.dm"
+
+#include "code/emotes.dm"
diff --git a/modular_bandastation/emote_panel/code/emotes.dm b/modular_bandastation/emote_panel/code/emotes.dm
new file mode 100644
index 0000000000000..70b9eaf61d75a
--- /dev/null
+++ b/modular_bandastation/emote_panel/code/emotes.dm
@@ -0,0 +1,632 @@
+// Imaginary Friend
+
+/datum/emote/imaginary_friend/point
+ name = "указать"
+ message = "указывает."
+ message_param = "указывает на %t."
+
+// Emote Living
+
+/datum/emote/flip
+ name = "кувырнуться"
+
+/datum/emote/spin
+ name = "покрутиться"
+
+/datum/emote/living/blush
+ name = "покраснеть"
+ message = "краснеет."
+
+/datum/emote/living/sing_tune
+ name = "подпеть мелодию"
+ message = "подпевает мелодию."
+
+/datum/emote/living/bow
+ name = "покланиться"
+ message = "кланяется."
+ message_param = "кланяется %t."
+
+/datum/emote/living/burp
+ name = "рыгнуть"
+ message = "рыгает."
+ message_mime = "изображает отрыжку."
+
+/datum/emote/living/choke
+ name = "подавиться"
+ message = "давится!"
+ message_mime = "бесшумно давится!"
+
+/datum/emote/living/cross
+ name = "скрестить руки"
+ message = "скрещивает свои руки."
+
+/datum/emote/living/chuckle
+ name = "усмехнуться"
+ message = "усмехается."
+ message_mime = "изображает смешок."
+
+/datum/emote/living/collapse
+ name = "упасть"
+ message = "падает!"
+
+/datum/emote/living/cough
+ name = "покашлять"
+ message = "кашляет!"
+ message_mime = "изображает преувеличенный кашель!"
+
+/datum/emote/living/dance
+ name = "потанцевать"
+ message = "радостно танцует."
+
+/datum/emote/living/deathgasp
+ name = "последнее дыхание"
+ message = "цепенеет и расслабляется, взгляд становится пустым и безжизненным..."
+ message_robot = "на мгновение вздрагивает и замирает, глаза медленно темнеют."
+ message_AI = "скрипит, экран мерцает, пока системы медленно выключаются."
+ message_alien = "издает ослабевающий гортанный крик и падает на пол..."
+ message_larva = "с тошнотворным шипением выдыхает воздух и падает на пол..."
+ message_monkey = "издает слабый визг, когда рушится и перестает двигаться..."
+ message_animal_or_basic = "перестает двигаться..."
+
+/datum/emote/living/drool
+ name = "пустить слюни"
+ message = "пускает слюни."
+
+/datum/emote/living/faint
+ name = "потерять сознание"
+ message = "теряет сознание."
+
+/datum/emote/living/flap
+ name = "хлопнуть крыльями"
+ message = "хлопает крыльями."
+
+/datum/emote/living/flap/aflap
+ name = "сердито хлопнуть крыльями"
+ message = "сердито хлопает крыльями!"
+
+/datum/emote/living/frown
+ name = "похмуриться"
+ message = "хмурится."
+
+/datum/emote/living/gag
+ name = "gag"
+ message = "gags."
+ message_mime = "gags silently."
+
+/datum/emote/living/gasp
+ name = "задохнуться"
+ message = "задыхается!"
+ message_mime = "бесшумно задыхается!"
+
+/datum/emote/living/gasp_shock
+ name = "gaspshock"
+ message = "gasps in shock!"
+ message_mime = "gasps in silent shock!"
+
+/datum/emote/living/giggle
+ name = "похихикать"
+ message = "хихикает."
+ message_mime = "бесшумно хихикает!"
+
+/datum/emote/living/glare
+ name = "просверлить взглядом"
+ message = "сверлит взглядом."
+ message_param = "сверлит взглядом %t."
+
+/datum/emote/living/grin
+ name = "ухмыльнуться"
+ message = "ухмыляется."
+
+/datum/emote/living/groan
+ name = "болезненно простонать"
+ message = "болезненно стонет!"
+ message_mime = "изображает болезненный стон!"
+
+/datum/emote/living/grimace
+ name = "построить гримасу"
+ message = "строит гримасу."
+
+/datum/emote/living/jump
+ name = "прыгнуть"
+ message = "прыгает!"
+
+/datum/emote/living/kiss
+ name = "подготовить поцелуй"
+
+/datum/emote/living/laugh
+ name = "посмеяться"
+ message = "смеется."
+ message_mime = "бесшумно смеется!"
+
+/datum/emote/living/look
+ name = "посмотреть"
+ message = "смотрит."
+ message_param = "смотрит на %t."
+
+/datum/emote/living/nod
+ name = "кивнуть"
+ message = "кивает."
+ message_param = "кивает %t."
+
+/datum/emote/living/point
+ name = "указать"
+ message = "указывает."
+ message_param = "указывает на %t."
+
+/datum/emote/living/pout
+ name = "надуть губы"
+ message = "надувает губы."
+ message_mime = "бесшумно надувает губы."
+
+/datum/emote/living/scream
+ name = "покричать"
+ message = "кричит!"
+ message_mime = "изображает крик!"
+
+/datum/emote/living/scowl
+ name = "сердито посмотреть"
+ message = "сердито смотрит."
+
+/datum/emote/living/shake
+ name = "покачать головой"
+ message = "качает своей головой."
+
+/datum/emote/living/shiver
+ name = "подрожать"
+ message = "дрожит."
+
+/datum/emote/living/sigh
+ name = "вздохнуть"
+ message = "вздыхает."
+ message_mime = "изображает преувеличенный бесшумный вздох."
+
+/datum/emote/living/sit
+ name = "сесть"
+ message = "садится."
+
+/datum/emote/living/smile
+ name = "улыбнуться"
+ message = "улыбается."
+
+/datum/emote/living/sneeze
+ name = "чихнуть"
+ message = "чихает."
+ message_mime = "изображает преувеличенный бесшумный чих."
+
+/datum/emote/living/smug
+ name = "самодовольно улыбнуться"
+ message = "самодовольно улыбается."
+
+/datum/emote/living/sniff
+ name = "понюхать"
+ message = "нюхает."
+ message_mime = "бесшумно нюхает."
+
+/datum/emote/living/snore
+ name = "похрапеть"
+ message = "храпеть."
+ message_mime = "бесшумно храпит."
+
+/datum/emote/living/stare
+ name = "уставиться"
+ message = "уставился."
+ message_param = "уставился на %t."
+
+/datum/emote/living/strech
+ name = "протянуть руки"
+ message = "протягивает свои руки."
+
+/datum/emote/living/sulk
+ name = "обидеться"
+ message = "грустно обижается."
+
+/datum/emote/living/surrender
+ name = "сдаться"
+ message = "ложит руки за голову и падает на землю, они сдаются!"
+
+/datum/emote/living/sway
+ name = "покачаться"
+ message = "покачивается."
+
+/datum/emote/living/tilt
+ name = "наклонить голову"
+ message = "наклоняет голову на бок."
+
+/datum/emote/living/tremble
+ name = "подрожать в страхе"
+ message = "дрожит в страхе!"
+
+/datum/emote/living/twitch
+ name = "сильно дернуться"
+ message = "сильно дергается."
+
+/datum/emote/living/twitch_s
+ name = "дернуться"
+ message = "дергается."
+
+/datum/emote/living/wave
+ name = "помахать рукой"
+ message = "машет рукой."
+
+/datum/emote/living/whimper
+ name = "поскулить"
+ message = "скулит."
+ message_mime = "изображает скуление."
+
+/datum/emote/living/wsmile
+ name = "слабо улыбнуться"
+ message = "слабо улыбается."
+
+/datum/emote/living/yawn
+ name = "зевнуть"
+ message = "зевает."
+ message_mime = "изображает преувеличенный бесшумный зевок."
+
+/datum/emote/living/gurgle
+ name = "побулькать"
+ message = "издает неприятное бульканье."
+ message_mime = "бесшумно и неприятно булькает."
+
+/datum/emote/living/beep
+ name = "издать сигнал"
+ message = "издает сигнал."
+ message_param = "издает сигнал в сторону %t."
+
+/datum/emote/living/inhale
+ name = "вдохнуть"
+ message = "делает вдох."
+
+/datum/emote/living/exhale
+ name = "выдохнуть"
+ message = "выдыхает."
+
+/datum/emote/living/swear
+ name = "поругаться"
+ message = "ругается!"
+ message_mime = "делает грубый жест!"
+
+// Emote Brain
+
+/datum/emote/brain/alarm
+ name = "alarm"
+ message = "sounds an alarm."
+
+/datum/emote/brain/alert
+ name = "alert"
+ message = "lets out a distressed noise."
+
+/datum/emote/brain/flash
+ name = "flash"
+ message = "blinks their lights."
+
+/datum/emote/brain/notice
+ name = "notice"
+ message = "plays a loud tone."
+
+/datum/emote/brain/whistle
+ name = "whistle"
+ message = "whistles."
+
+// Emote Carbon
+
+/datum/emote/living/carbon/airguitar
+ name = "поиграть на воображаемой гитаре"
+ message = "невероятно играет на воображаемой гитаре."
+
+/datum/emote/living/carbon/blink
+ name = "моргнуть"
+ message = "моргает."
+
+/datum/emote/living/carbon/blink_r
+ name = "быстро моргать"
+ message = "быстро моргает."
+
+/datum/emote/living/carbon/clap
+ name = "похлопать"
+ message = "хлопает."
+
+/datum/emote/living/carbon/crack
+ name = "похрустеть пальцами"
+ message = "хрустит пальцами."
+
+/datum/emote/living/carbon/circle
+ name = "подготовить колечко"
+
+/datum/emote/living/carbon/moan
+ name = "постонать"
+ message = "стонет!"
+ message_mime = "кажется, стонет!"
+
+/datum/emote/living/carbon/noogie
+ name = "подготовить терку"
+
+/datum/emote/living/carbon/roll
+ name = "покатиться"
+ message = "катится."
+
+/datum/emote/living/carbon/scratch
+ name = "почесаться"
+ message = "чешится."
+
+/datum/emote/living/carbon/sign
+ name = "показать число"
+ message_param = "показывает число %t."
+
+/datum/emote/living/carbon/sign/signal
+ name = "показать пальцы"
+ message_param = "показывает %t пальцев."
+
+/datum/emote/living/carbon/slap
+ name = "подготовить шлепок"
+
+/datum/emote/living/carbon/hand
+ name = "подготовить руку"
+
+/datum/emote/living/carbon/snap
+ name = "щелкнуть пальцами"
+ message = "щелкает пальцами."
+ message_param = "щелкает пальцами в сторону %t."
+
+/datum/emote/living/carbon/shoesteal
+ name = "подготовить кражу ботинок"
+
+/datum/emote/living/carbon/tail
+ name = "помахать хвостом"
+ message = "машет хвостом."
+
+/datum/emote/living/carbon/wink
+ name = "подмигнуть"
+ message = "подмигивает."
+
+// Emote Alien
+
+/datum/emote/living/alien/gnarl
+ name = "оскалиться"
+ message = "оскаливается и показывает зубы..."
+
+/datum/emote/living/alien/hiss
+ name = "пошипеть"
+ message_alien = "шипит."
+ message_larva = "тихо шипит."
+
+/datum/emote/living/alien/roar
+ name = "прорычать"
+ message_alien = "рычит."
+ message_larva = "тихо рычит."
+
+// Emote Human
+
+/datum/emote/living/carbon/human/cry
+ name = "поплакать"
+ message = "плачет."
+ message_mime = "бесшумно плачет."
+
+/datum/emote/living/carbon/human/dap
+ name = "dap"
+ message = "sadly can't find anybody to give daps to, and daps themself. Shameful."
+ message_param = "give daps to %t."
+
+/datum/emote/living/carbon/human/eyebrow
+ name = "приподнять бровь"
+ message = "приподнимает брови."
+
+/datum/emote/living/carbon/human/grumble
+ name = "поворчать"
+ message = "ворчит!"
+ message_mime = "бесшумно ворчит!"
+
+/datum/emote/living/carbon/human/handshake
+ name = "дать рукопожатие"
+ message = "дает рукопожатие себе."
+ message_param = "дает рукопожатие %t."
+
+/datum/emote/living/carbon/human/hug
+ name = "обнять"
+ message = "обнимает себя."
+ message_param = "обнимает %t."
+
+/datum/emote/living/carbon/human/mumble
+ name = "пробормотать"
+ message = "бормочет!"
+ message_mime = "бесшумно бормочет!"
+
+/datum/emote/living/carbon/human/scream
+ name = "покричать"
+ message = "кричит!"
+ message_mime = "изображает крик!"
+
+/datum/emote/living/carbon/human/scream/screech
+ name = "повизжать"
+ message = "визжит!"
+ message_mime = "бесшумно визжит."
+
+/datum/emote/living/carbon/human/pale
+ name = "побледнеть"
+ message = "на мгновение бледнеет."
+
+/datum/emote/living/carbon/human/raise
+ name = "поднять руку"
+ message = "поднимает руку."
+
+/datum/emote/living/carbon/human/salute
+ name = "салютировать"
+ message = "салютует."
+ message_param = "салютует %t."
+
+/datum/emote/living/carbon/human/shrug
+ name = "пожать плечами"
+ message = "пожимает плечами."
+
+/datum/emote/living/carbon/human/wag
+ name = "махать хвостом"
+ message = "хвостом."
+
+/datum/emote/living/carbon/human/wing
+ name = "махать крыльями"
+ message = "крыльями."
+
+/datum/emote/living/carbon/human/clear_throat
+ name = "прочистить горло"
+ message = "прочищает горло."
+
+/datum/emote/living/carbon/human/monkey/gnarl
+ name = "оскалиться"
+ message = "оскаливается и показывает зубы..."
+ message_mime = "бесшумно оскаливается, показывая зубы..."
+
+/datum/emote/living/carbon/human/monkey/roll
+ name = "покатиться"
+ message = "катится."
+
+/datum/emote/living/carbon/human/monkey/scratch
+ name = "почесаться"
+ message = "чешется."
+
+/datum/emote/living/carbon/human/monkey/screech/roar
+ name = "прорычать"
+ message = "рычит!"
+ message_mime = "изображает рык."
+
+/datum/emote/living/carbon/human/monkey/tail
+ name = "помахать хвостом"
+ message = "машет хвостом."
+
+/datum/emote/living/carbon/human/monkey/sign
+ name = "показать число"
+ message_param = "показывает число %t."
+
+// Emote AI
+
+/datum/emote/ai/emotion_display
+ name = "Эмоция: пусто"
+
+/datum/emote/ai/emotion_display/very_happy
+ name = "Эмоция: очень радостно"
+
+/datum/emote/ai/emotion_display/happy
+ name = "Эмоция: радостно"
+
+/datum/emote/ai/emotion_display/neutral
+ name = "Эмоция: нейтральность"
+
+/datum/emote/ai/emotion_display/unsure
+ name = "Эмоция: неуверенность"
+
+/datum/emote/ai/emotion_display/confused
+ name = "Эмоция: в замешательстве"
+
+/datum/emote/ai/emotion_display/sad
+ name = "Эмоция: грусть"
+
+/datum/emote/ai/emotion_display/bsod
+ name = "Эмоция: BSoD"
+
+/datum/emote/ai/emotion_display/trollface
+ name = "Эмоция: Trollface"
+
+/datum/emote/ai/emotion_display/awesome
+ name = "Эмоция: крутость"
+
+/datum/emote/ai/emotion_display/dorfy
+ name = "Эмоция: Dorfy"
+
+/datum/emote/ai/emotion_display/thinking
+ name = "Эмоция: задуматься"
+
+/datum/emote/ai/emotion_display/facepalm
+ name = "Эмоция: Facepalm"
+
+/datum/emote/ai/emotion_display/friend_computer
+ name = "Эмоция: дружелюбный компьютер"
+
+/datum/emote/ai/emotion_display/blue_glow
+ name = "Эмоция: синее свечение"
+
+/datum/emote/ai/emotion_display/red_glow
+ name = "Эмоция: красное свечение"
+
+// Emote Silicon
+
+/datum/emote/silicon/boop
+ name = "boop"
+ message = "boops."
+
+/datum/emote/silicon/buzz
+ name = "buzz"
+ message = "buzzes."
+ message_param = "buzzes at %t."
+
+/datum/emote/silicon/buzz2
+ name = "buzz2"
+ message = "buzzes twice."
+
+/datum/emote/silicon/chime
+ name = "chime"
+ message = "chimes."
+
+/datum/emote/silicon/honk
+ name = "honk"
+ message = "honks."
+
+/datum/emote/silicon/ping
+ name = "ping"
+ message = "pings."
+ message_param = "pings at %t."
+
+/datum/emote/silicon/sad
+ name = "sad"
+ message = "plays a sad trombone..."
+
+/datum/emote/silicon/warn
+ name = "warn"
+ message = "blares an alarm!"
+
+/datum/emote/silicon/slowclap
+ name = "slowclap"
+ message = "activates their slow clap processor."
+
+// Emote Slime
+
+/datum/emote/slime/bounce
+ name = "подпрыгнуть"
+ message = "подпрыгивает на месте."
+
+/datum/emote/slime/jiggle
+ name = "потрястись"
+ message = "трясется!"
+
+/datum/emote/slime/light
+ name = "посветиться"
+ message = "на мгновение светится."
+
+/datum/emote/slime/vibrate
+ name = "повибрировать"
+ message = "вибрирует!"
+
+/datum/emote/slime/mood
+ name = "Настроение: никакое"
+
+/datum/emote/slime/mood/sneaky
+ name = "Настроение: хитрое"
+
+/datum/emote/slime/mood/smile
+ name = "Настроение: улыбающееся"
+
+/datum/emote/slime/mood/cat
+ name = "Настроение: кот"
+
+/datum/emote/slime/mood/pout
+ name = "Настроение: надутый"
+
+/datum/emote/slime/mood/sad
+ name = "Настроение: грустный"
+
+/datum/emote/slime/mood/angry
+ name = "Настроение: злой"
+
+// Emote Other
+
+/datum/emote/gorilla/ooga
+ name = "ooga"
+ message = "oogas."
+ message_param = "oogas at %t."
diff --git a/modular_bandastation/events/_events.dm b/modular_bandastation/events/_events.dm
new file mode 100644
index 0000000000000..69d85f17703ae
--- /dev/null
+++ b/modular_bandastation/events/_events.dm
@@ -0,0 +1,4 @@
+/datum/modpack/events
+ name = "Ребаланс событий и уровня угрозы."
+ desc = "Изменяет шансы возникновения некоторых событий, а также изменяет уровень угроз."
+ author = "larentoun"
diff --git a/modular_bandastation/events/_events.dme b/modular_bandastation/events/_events.dme
new file mode 100644
index 0000000000000..bd679c7efb47f
--- /dev/null
+++ b/modular_bandastation/events/_events.dme
@@ -0,0 +1,4 @@
+#include "_events.dm"
+
+#include "code/events.dm"
+#include "code/threat.dm"
diff --git a/modular_bandastation/events/code/events.dm b/modular_bandastation/events/code/events.dm
new file mode 100644
index 0000000000000..78e380baa87e2
--- /dev/null
+++ b/modular_bandastation/events/code/events.dm
@@ -0,0 +1,2 @@
+/datum/round_event_control/wall_fungus
+ min_players = 20
diff --git a/modular_bandastation/events/code/threat.dm b/modular_bandastation/events/code/threat.dm
new file mode 100644
index 0000000000000..a7a117cee5abd
--- /dev/null
+++ b/modular_bandastation/events/code/threat.dm
@@ -0,0 +1,8 @@
+/datum/game_mode/dynamic/generate_budgets()
+ if (SSticker.totalPlayersReady < low_pop_player_threshold)
+ round_start_budget = 0
+ initial_round_start_budget = 0
+ mid_round_budget = threat_level - 0
+ threat_level = 0
+ return
+ . = ..()
diff --git a/modular_bandastation/examine_panel/_examine_panel.dm b/modular_bandastation/examine_panel/_examine_panel.dm
new file mode 100644
index 0000000000000..15b084aa8d29e
--- /dev/null
+++ b/modular_bandastation/examine_panel/_examine_panel.dm
@@ -0,0 +1,4 @@
+/datum/modpack/examine_panel
+ name = "Examine Panel"
+ desc = "Examine Panel"
+ author = "larentoun"
diff --git a/modular_bandastation/examine_panel/_examine_panel.dme b/modular_bandastation/examine_panel/_examine_panel.dme
new file mode 100644
index 0000000000000..058e864a55577
--- /dev/null
+++ b/modular_bandastation/examine_panel/_examine_panel.dme
@@ -0,0 +1,7 @@
+#include "_examine_panel.dm"
+
+#include "code/_examine_panel_defines.dm"
+#include "code/examine_panel_component.dm"
+#include "code/examine_panel_mob.dm"
+#include "code/examine_panel_prefs.dm"
+#include "code/~examine_panel_defines.dm"
diff --git a/modular_bandastation/examine_panel/code/_examine_panel_defines.dm b/modular_bandastation/examine_panel/code/_examine_panel_defines.dm
new file mode 100644
index 0000000000000..49fd0fd3530b3
--- /dev/null
+++ b/modular_bandastation/examine_panel/code/_examine_panel_defines.dm
@@ -0,0 +1,4 @@
+/// How many characters will be displayed in the flavor text preview before we cut it off?
+#define FLAVOR_PREVIEW_LIMIT 110
+/// Double the maximum message length.
+#define MAX_FLAVOR_LEN 4096
diff --git a/modular_bandastation/examine_panel/code/examine_panel_component.dm b/modular_bandastation/examine_panel/code/examine_panel_component.dm
new file mode 100644
index 0000000000000..a3daa49ce1da6
--- /dev/null
+++ b/modular_bandastation/examine_panel/code/examine_panel_component.dm
@@ -0,0 +1,121 @@
+/datum/component/examine_panel
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Mob that the examine panel belongs to.
+ var/mob/living/holder
+ /// The screen containing the appearance of the mob
+ var/atom/movable/screen/map_view/examine_panel_screen/examine_panel_screen
+ /// Flavor text
+ var/flavor_text
+
+/datum/component/examine_panel/Initialize(use_prefs = FALSE)
+ . = ..()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+ holder = parent
+ if(!use_prefs)
+ return
+ if(iscarbon(parent))
+ flavor_text = holder.client?.prefs.read_preference(/datum/preference/text/flavor_text)
+ if(issilicon(parent))
+ flavor_text = holder.client?.prefs.read_preference(/datum/preference/text/silicon_flavor_text)
+
+/datum/component/examine_panel/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+
+/datum/component/examine_panel/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ATOM_EXAMINE)
+
+/datum/component/examine_panel/proc/on_examine(mob/living/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(iscarbon(source))
+ examine_list += get_carbon_flavor_text(source)
+ if(issilicon(source))
+ examine_list += get_silicon_flavor_text(source)
+
+/datum/component/examine_panel/proc/get_carbon_flavor_text(mob/living/carbon/source)
+ var/flavor_text_link
+ /// The first 1-FLAVOR_PREVIEW_LIMIT characters in the mob's "flavor_text" DNA feature. FLAVOR_PREVIEW_LIMIT is defined in flavor_defines.dm.
+ var/preview_text = copytext_char(flavor_text, 1, FLAVOR_PREVIEW_LIMIT)
+ // What examine_tgui.dm uses to determine if flavor text appears as "Obscured".
+ var/face_obscured = (source.wear_mask && (source.wear_mask.flags_inv & HIDEFACE)) || (source.head && (source.head.flags_inv & HIDEFACE))
+
+ if (!(face_obscured))
+ flavor_text_link = span_notice("[preview_text]... Look closer? ")
+ else
+ flavor_text_link = span_notice("Examine closely... ")
+ if (flavor_text_link)
+ return flavor_text_link
+
+/datum/component/examine_panel/proc/get_silicon_flavor_text(mob/living/silicon/source)
+ var/flavor_text_link
+ /// The first 1-FLAVOR_PREVIEW_LIMIT characters in the mob's client's silicon_flavor_text preference datum. FLAVOR_PREVIEW_LIMIT is defined in flavor_defines.dm.
+ var/preview_text = copytext_char(flavor_text, 1, FLAVOR_PREVIEW_LIMIT)
+
+ flavor_text_link = span_notice("[preview_text]... Look closer? ")
+
+ if (flavor_text_link)
+ return flavor_text_link
+
+/datum/component/examine_panel/Topic(href, list/href_list)
+ . = ..()
+
+ if(href_list["lookup_info"])
+ switch(href_list["lookup_info"])
+ if("open_examine_panel")
+ ui_interact(usr)
+
+/datum/component/examine_panel/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/component/examine_panel/ui_close(mob/user)
+ user.client.clear_map(examine_panel_screen.assigned_map)
+
+/atom/movable/screen/map_view/examine_panel_screen
+ name = "examine panel screen"
+
+/datum/component/examine_panel/ui_interact(mob/user, datum/tgui/ui)
+ if(!examine_panel_screen)
+ examine_panel_screen = new
+ examine_panel_screen.name = "screen"
+ examine_panel_screen.assigned_map = "examine_panel_[REF(holder)]_map"
+ examine_panel_screen.del_on_map_removal = FALSE
+ examine_panel_screen.screen_loc = "[examine_panel_screen.assigned_map]:1,1"
+
+ var/mutable_appearance/current_mob_appearance = new(holder)
+ current_mob_appearance.setDir(SOUTH)
+ current_mob_appearance.transform = matrix() // We reset their rotation, in case they're lying down.
+
+ // In case they're pixel-shifted, we bring 'em back!
+ current_mob_appearance.pixel_x = 0
+ current_mob_appearance.pixel_y = 0
+
+ examine_panel_screen.cut_overlays()
+ examine_panel_screen.add_overlay(current_mob_appearance)
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ examine_panel_screen.display_to(user)
+ user.client.register_map_obj(examine_panel_screen)
+ ui = new(user, src, "ExaminePanel")
+ ui.open()
+
+
+/datum/component/examine_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ var/tgui_flavor_text = flavor_text
+ var/obscured
+
+ if(ishuman(parent))
+ var/mob/living/carbon/human/holder_human = parent
+ obscured = (holder_human.wear_mask && (holder_human.wear_mask.flags_inv & HIDEFACE)) || (holder_human.head && (holder_human.head.flags_inv & HIDEFACE))
+ tgui_flavor_text = obscured ? "Obscured" : flavor_text
+
+ var/name = obscured ? "Unknown" : user.name
+
+ data["obscured"] = obscured ? TRUE : FALSE
+ data["character_name"] = name
+ data["assigned_map"] = examine_panel_screen.assigned_map
+ data["flavor_text"] = tgui_flavor_text
+ return data
diff --git a/modular_bandastation/examine_panel/code/examine_panel_mob.dm b/modular_bandastation/examine_panel/code/examine_panel_mob.dm
new file mode 100644
index 0000000000000..b55a6eec0ded8
--- /dev/null
+++ b/modular_bandastation/examine_panel/code/examine_panel_mob.dm
@@ -0,0 +1,19 @@
+// TODO: Don't use prefs when spawned via admins
+/mob/living/carbon/human/Login()
+ . = ..()
+ AddComponent(/datum/component/examine_panel, use_prefs = TRUE)
+
+/mob/living/silicon/Login()
+ . = ..()
+ AddComponent(/datum/component/examine_panel, use_prefs = TRUE)
+
+/mob/living/verb/change_flavor_text()
+ set name = "Change flavor text"
+ set category = "IC"
+
+ var/datum/component/examine_panel/examine_panel = GetComponent(/datum/component/examine_panel)
+ if(!examine_panel)
+ examine_panel = AddComponent(/datum/component/examine_panel)
+ var/new_flavor_text = tgui_input_text(usr, "Enter new flavor text", "Changing Flavor Text", examine_panel.flavor_text)
+ if(new_flavor_text)
+ examine_panel.flavor_text = new_flavor_text
diff --git a/modular_bandastation/examine_panel/code/examine_panel_prefs.dm b/modular_bandastation/examine_panel/code/examine_panel_prefs.dm
new file mode 100644
index 0000000000000..f5adcc4ea798a
--- /dev/null
+++ b/modular_bandastation/examine_panel/code/examine_panel_prefs.dm
@@ -0,0 +1,18 @@
+/datum/preference/text/flavor_text
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "flavor_text"
+ maximum_value_length = MAX_FLAVOR_LEN
+
+/datum/preference/text/flavor_text/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
+ target.dna.features["flavor_text"] = value
+
+/datum/preference/text/silicon_flavor_text
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "silicon_flavor_text"
+ maximum_value_length = MAX_FLAVOR_LEN
+ // This does not get a apply_to_human proc, this is read directly in silicon/robot/examine.dm
+
+/datum/preference/text/silicon_flavor_text/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
+ return FALSE // To prevent the not-implemented runtime
diff --git a/modular_bandastation/examine_panel/code/~examine_panel_defines.dm b/modular_bandastation/examine_panel/code/~examine_panel_defines.dm
new file mode 100644
index 0000000000000..d431a143a094e
--- /dev/null
+++ b/modular_bandastation/examine_panel/code/~examine_panel_defines.dm
@@ -0,0 +1,2 @@
+#undef FLAVOR_PREVIEW_LIMIT
+#undef MAX_FLAVOR_LEN
diff --git a/modular_bandastation/example/_example.dm b/modular_bandastation/example/_example.dm
new file mode 100644
index 0000000000000..2343367184030
--- /dev/null
+++ b/modular_bandastation/example/_example.dm
@@ -0,0 +1,16 @@
+/datum/modpack/example
+ /// A string name for the modpack. Used for looking up other modpacks in init.
+ name = "Example modpack"
+ /// A string desc for the modpack. Can be used for modpack verb list as description.
+ desc = "its useless"
+ /// A string with authors of this modpack.
+ author = "furior"
+
+/datum/modpack/example/pre_initialize()
+ . = ..()
+
+/datum/modpack/example/initialize()
+ . = ..()
+
+/datum/modpack/example/post_initialize()
+ . = ..()
diff --git a/modular_bandastation/example/_example.dme b/modular_bandastation/example/_example.dme
new file mode 100644
index 0000000000000..5540c273b03ba
--- /dev/null
+++ b/modular_bandastation/example/_example.dme
@@ -0,0 +1,3 @@
+#include "_example.dm"
+
+#include "code/example.dm"
diff --git a/modular_bandastation/example/code/example.dm b/modular_bandastation/example/code/example.dm
new file mode 100644
index 0000000000000..ff327270a515d
--- /dev/null
+++ b/modular_bandastation/example/code/example.dm
@@ -0,0 +1,2 @@
+/turf/closed/wall/example
+ name = "Example wall"
diff --git a/modular_bandastation/gunhud/_gunhud.dm b/modular_bandastation/gunhud/_gunhud.dm
new file mode 100644
index 0000000000000..a93ecac1d2917
--- /dev/null
+++ b/modular_bandastation/gunhud/_gunhud.dm
@@ -0,0 +1,4 @@
+/datum/modpack/gunhud
+ name = "Счетчик патронов"
+ desc = "Добавляет счетчик патронов"
+ author = "larentoun (modpack), Gandalf2k15 (original)"
diff --git a/modular_bandastation/gunhud/_gunhud.dme b/modular_bandastation/gunhud/_gunhud.dme
new file mode 100644
index 0000000000000..577291e22b77c
--- /dev/null
+++ b/modular_bandastation/gunhud/_gunhud.dme
@@ -0,0 +1,7 @@
+#include "_gunhud.dm"
+
+#include "code/_gunhud_defines.dm"
+#include "code/gunhud_component.dm"
+#include "code/gunhud_hud.dm"
+#include "code/gunhud_screen.dm"
+#include "code/~gunhud_defines.dm"
diff --git a/modular_bandastation/gunhud/code/_gunhud_defines.dm b/modular_bandastation/gunhud/code/_gunhud_defines.dm
new file mode 100644
index 0000000000000..5ceb4767dd99a
--- /dev/null
+++ b/modular_bandastation/gunhud/code/_gunhud_defines.dm
@@ -0,0 +1,5 @@
+// Gunhud
+#define ui_gunhud "RIGHT-1:28,CENTER-5:9"
+
+///The gun needs to update the gun hud!
+#define COMSIG_UPDATE_GUNHUD "update_gunhud"
diff --git a/modular_bandastation/gunhud/code/gunhud_component.dm b/modular_bandastation/gunhud/code/gunhud_component.dm
new file mode 100644
index 0000000000000..1df38eb90143a
--- /dev/null
+++ b/modular_bandastation/gunhud/code/gunhud_component.dm
@@ -0,0 +1,150 @@
+/datum/component/gunhud
+ var/atom/movable/screen/gunhud_screen/hud
+
+/datum/component/gunhud/Initialize()
+ . = ..()
+ if(!istype(parent, /obj/item/gun) && !istype(parent, /obj/item/weldingtool))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(wake_up))
+
+/datum/component/gunhud/Destroy()
+ turn_off()
+ return ..()
+
+/datum/component/gunhud/proc/wake_up(datum/source, mob/user, slot)
+ SIGNAL_HANDLER
+
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(H.is_holding(parent))
+ if(H.hud_used)
+ hud = H.hud_used.gunhud_screen
+ turn_on()
+ else
+ turn_off()
+
+/datum/component/gunhud/proc/turn_on()
+ SIGNAL_HANDLER
+
+ RegisterSignals(parent, list(COMSIG_PREQDELETED, COMSIG_ITEM_DROPPED), PROC_REF(turn_off))
+ RegisterSignals(parent, list(COMSIG_UPDATE_GUNHUD, COMSIG_GUN_CHAMBER_PROCESSED), PROC_REF(update_hud))
+
+ hud.turn_on()
+ update_hud()
+
+/datum/component/gunhud/proc/turn_off()
+ SIGNAL_HANDLER
+
+ UnregisterSignal(parent, list(COMSIG_PREQDELETED, COMSIG_ITEM_DROPPED, COMSIG_UPDATE_GUNHUD, COMSIG_GUN_CHAMBER_PROCESSED))
+
+ if(hud)
+ hud.turn_off()
+ hud = null
+
+/datum/component/gunhud/proc/update_hud()
+ SIGNAL_HANDLER
+ if(istype(parent, /obj/item/gun/ballistic))
+ var/obj/item/gun/ballistic/pew = parent
+ hud.maptext = null
+ hud.icon_state = "backing"
+ var/backing_color = COLOR_CYAN
+ if(!pew.magazine)
+ hud.set_hud(backing_color, "oe", "te", "he", "no_mag")
+ return
+ if(!pew.get_ammo())
+ hud.set_hud(backing_color, "oe", "te", "he", "empty_flash")
+ return
+
+ var/indicator
+ var/rounds = num2text(istype(parent, /obj/item/gun/ballistic/revolver) ? pew.get_ammo(FALSE, FALSE) : pew.get_ammo(TRUE)) // fucking revolvers indeed - do not count empty or chambered rounds for the display HUD
+ var/oth_o
+ var/oth_t
+ var/oth_h
+
+ switch(length(rounds))
+ if(1)
+ oth_o = "o[rounds[1]]"
+ if(2)
+ oth_o = "o[rounds[2]]"
+ oth_t = "t[rounds[1]]"
+ if(3)
+ oth_o = "o[rounds[3]]"
+ oth_t = "t[rounds[2]]"
+ oth_h = "h[rounds[1]]"
+ else
+ oth_o = "o9"
+ oth_t = "t9"
+ oth_h = "h9"
+ hud.set_hud(backing_color, oth_o, oth_t, oth_h, indicator)
+
+ else if(istype(parent, /obj/item/gun/energy))
+ var/obj/item/gun/energy/pew = parent
+ hud.icon_state = "eammo_counter"
+ hud.cut_overlays()
+ hud.maptext_x = -12
+ var/obj/item/ammo_casing/energy/shot = pew.ammo_type[pew.select]
+ var/batt_percent = FLOOR(clamp(pew.cell.charge / pew.cell.maxcharge, 0, 1) * 100, 1)
+ var/shot_cost_percent = FLOOR(clamp(shot.e_cost / pew.cell.maxcharge, 0, 1) * 100, 1)
+ if(batt_percent > 99 || shot_cost_percent > 99)
+ hud.maptext_x = -12
+ else
+ hud.maptext_x = -8
+ if(!pew.can_shoot())
+ hud.icon_state = "eammo_counter_empty"
+ hud.maptext = span_maptext("[batt_percent]% [shot_cost_percent]%
")
+ return
+ if(batt_percent <= 25)
+ hud.maptext = span_maptext("[batt_percent]% [shot_cost_percent]%
")
+ return
+ hud.maptext = span_maptext("[batt_percent]% [shot_cost_percent]%
")
+
+ else if(istype(parent, /obj/item/weldingtool))
+ var/obj/item/weldingtool/welder = parent
+ hud.maptext = null
+ var/backing_color = COLOR_TAN_ORANGE
+ hud.icon_state = "backing"
+
+ if(welder.get_fuel() < 1)
+ hud.set_hud(backing_color, "oe", "te", "he", "empty_flash")
+ return
+
+ var/indicator
+ var/fuel = num2text(welder.get_fuel())
+ var/oth_o
+ var/oth_t
+ var/oth_h
+
+ if(welder.welding)
+ indicator = "flame_on"
+ else
+ indicator = "flame_off"
+
+ fuel = num2text(welder.get_fuel())
+
+ switch(length(fuel))
+ if(1)
+ oth_o = "o[fuel[1]]"
+ if(2)
+ oth_o = "o[fuel[2]]"
+ oth_t = "t[fuel[1]]"
+ if(3)
+ oth_o = "o[fuel[3]]"
+ oth_t = "t[fuel[2]]"
+ oth_h = "h[fuel[1]]"
+ else
+ oth_o = "o9"
+ oth_t = "t9"
+ oth_h = "h9"
+ hud.set_hud(backing_color, oth_o, oth_t, oth_h, indicator)
+
+/obj/item/gun/ballistic/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/gunhud)
+
+/obj/item/gun/energy/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/gunhud)
+
+/obj/item/weldingtool/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/gunhud)
diff --git a/modular_bandastation/gunhud/code/gunhud_hud.dm b/modular_bandastation/gunhud/code/gunhud_hud.dm
new file mode 100644
index 0000000000000..bc26c0f0e6d58
--- /dev/null
+++ b/modular_bandastation/gunhud/code/gunhud_hud.dm
@@ -0,0 +1,7 @@
+/datum/hud
+ var/atom/movable/screen/gunhud_screen
+
+/datum/hud/human/New(mob/living/carbon/human/owner)
+ . = ..()
+ gunhud_screen = new /atom/movable/screen/gunhud_screen(null, src)
+ infodisplay += gunhud_screen
diff --git a/modular_bandastation/gunhud/code/gunhud_screen.dm b/modular_bandastation/gunhud/code/gunhud_screen.dm
new file mode 100644
index 0000000000000..2038d7f6e3cd8
--- /dev/null
+++ b/modular_bandastation/gunhud/code/gunhud_screen.dm
@@ -0,0 +1,86 @@
+/*
+* Customizable ammo hud
+*/
+
+/*
+* This hud is controlled namely by the gunhud component. Generally speaking this is inactive much like all other hud components until it's needed.
+* It does not do any calculations of it's own, you must do this externally.
+* If you wish to use this hud, use the gunhud component or create another one which interacts with it via the below procs.
+* proc/turn_off
+* proc/turn_on
+* proc/set_hud
+* Check the gun_hud.dmi for all available icons you can use.
+*/
+
+/atom/movable/screen/gunhud_screen
+ name = "gunhud"
+ icon = 'modular_bandastation/gunhud/icons/gun_hud.dmi'
+ icon_state = "backing"
+ screen_loc = ui_gunhud
+ invisibility = INVISIBILITY_ABSTRACT
+
+ ///This is the color assigned to the OTH backing, numbers and indicator.
+ var/backing_color = COLOR_RED
+ ///This is the "backlight" of the numbers, and only the numbers. Generally you should leave this alone if you aren't making some mutant project.
+ var/oth_backing = "oth_light"
+
+ //Below are the OTH numbers, these are assigned by oX, tX and hX, x being the number you wish to display(0-9)
+ ///OTH position X00
+ var/oth_o
+ ///OTH position 0X0
+ var/oth_t
+ ///OTH position 00X
+ var/oth_h
+ ///This is the custom indicator sprite that will appear in the box at the bottom of the ammo hud, use this for something like semi/auto toggle on a gun.
+ var/indicator
+
+///This proc simply resets the hud to standard and removes it from the players visible hud.
+/atom/movable/screen/gunhud_screen/proc/turn_off()
+ invisibility = INVISIBILITY_ABSTRACT
+ maptext = null
+ backing_color = COLOR_RED
+ oth_backing = ""
+ oth_o = ""
+ oth_t = ""
+ oth_h = ""
+ indicator = ""
+ update_appearance()
+
+///This proc turns the hud on, but does not set it to anything other than the currently set values
+/atom/movable/screen/gunhud_screen/proc/turn_on()
+ invisibility = 0
+
+///This is the main proc for altering the hud's appeareance, it controls the setting of the overlays. Use the OTH and below variables to set it accordingly.
+/atom/movable/screen/gunhud_screen/proc/set_hud(_backing_color, _oth_o, _oth_t, _oth_h, _indicator, _oth_backing = "oth_light")
+ backing_color = _backing_color
+ oth_backing = _oth_backing
+ oth_o = _oth_o
+ oth_t = _oth_t
+ oth_h = _oth_h
+ indicator = _indicator
+
+ update_appearance()
+
+/atom/movable/screen/gunhud_screen/update_overlays()
+ . = ..()
+ if(oth_backing)
+ var/mutable_appearance/oth_backing_overlay = mutable_appearance(icon, oth_backing)
+ oth_backing_overlay.color = backing_color
+ . += oth_backing_overlay
+ if(oth_o)
+ var/mutable_appearance/o_overlay = mutable_appearance(icon, oth_o)
+ o_overlay.color = backing_color
+ . += o_overlay
+ if(oth_t)
+ var/mutable_appearance/t_overlay = mutable_appearance(icon, oth_t)
+ t_overlay.color = backing_color
+ . += t_overlay
+ if(oth_h)
+ var/mutable_appearance/h_overlay = mutable_appearance(icon, oth_h)
+ h_overlay.color = backing_color
+ . += h_overlay
+ if(indicator)
+ var/mutable_appearance/indicator_overlay = mutable_appearance(icon, indicator)
+ indicator_overlay.color = backing_color
+ . += indicator_overlay
+
diff --git a/modular_bandastation/gunhud/code/~gunhud_defines.dm b/modular_bandastation/gunhud/code/~gunhud_defines.dm
new file mode 100644
index 0000000000000..6b21ebfe92135
--- /dev/null
+++ b/modular_bandastation/gunhud/code/~gunhud_defines.dm
@@ -0,0 +1,5 @@
+// Ammo counter
+#undef ui_gunhud
+
+///The gun needs to update the gun hud!
+#undef COMSIG_UPDATE_GUNHUD
diff --git a/modular_bandastation/gunhud/icons/gun_hud.dmi b/modular_bandastation/gunhud/icons/gun_hud.dmi
new file mode 100644
index 0000000000000..6bd861100e2e1
Binary files /dev/null and b/modular_bandastation/gunhud/icons/gun_hud.dmi differ
diff --git a/modular_bandastation/keybinding/_keybinding.dm b/modular_bandastation/keybinding/_keybinding.dm
new file mode 100644
index 0000000000000..f28bb05e8472d
--- /dev/null
+++ b/modular_bandastation/keybinding/_keybinding.dm
@@ -0,0 +1,4 @@
+/datum/modpack/keybindings
+ name = "Хоткеи220"
+ desc = "Ставит значения хоткеев по умолчанию на те, что есть на Para220"
+ author = "larentoun"
diff --git a/modular_bandastation/keybinding/_keybinding.dme b/modular_bandastation/keybinding/_keybinding.dme
new file mode 100644
index 0000000000000..44dbc23d3008d
--- /dev/null
+++ b/modular_bandastation/keybinding/_keybinding.dme
@@ -0,0 +1,13 @@
+#include "_keybinding.dm"
+
+#include "code/admin.dm"
+#include "code/artificial_intelligence.dm"
+#include "code/carbon.dm"
+#include "code/client.dm"
+#include "code/communication.dm"
+#include "code/emote.dm"
+#include "code/human.dm"
+#include "code/living.dm"
+#include "code/mob.dm"
+#include "code/movement.dm"
+#include "code/robot.dm"
diff --git a/modular_bandastation/keybinding/code/admin.dm b/modular_bandastation/keybinding/code/admin.dm
new file mode 100644
index 0000000000000..29534ec9dd4a8
--- /dev/null
+++ b/modular_bandastation/keybinding/code/admin.dm
@@ -0,0 +1,49 @@
+/datum/keybinding/admin/admin_say
+ full_name = "Asay"
+ hotkey_keys = list("F5")
+ description = "Разговаривайте с другими админами"
+
+/datum/keybinding/admin/admin_ghost
+ full_name = "Aghost"
+ hotkey_keys = list("F6")
+ description = "Уйти в призраки"
+
+/datum/keybinding/admin/player_panel_new
+ full_name = "Player Panel"
+ hotkey_keys = list("F7")
+ description = "Открывает новую панель игрока"
+
+/datum/keybinding/admin/toggle_buildmode_self
+ full_name = "Toggle Buildmode"
+ hotkey_keys = list("F11")
+ description = "Включаем режим строительства"
+
+/datum/keybinding/admin/stealthmode
+ full_name = "Stealth mode"
+ hotkey_keys = list("CtrlF9")
+ description = "Включает скрытный режим"
+
+/datum/keybinding/admin/invisimin
+ full_name = "Invisimin"
+ hotkey_keys = list("F9")
+ description = "Включает невидимость, как у призраков (Не абузьте этим)"
+
+/datum/keybinding/admin/deadsay
+ full_name = "Dsay"
+ hotkey_keys = list("F10")
+ description = "Отправляет сообщение в чат мертвых"
+
+/datum/keybinding/admin/deadmin
+ full_name = "Deadmin"
+ hotkey_keys = list("Unbound")
+ description = "Избавтесь от своих админских сил"
+
+/datum/keybinding/admin/readmin
+ full_name = "Readmin"
+ hotkey_keys = list("Unbound")
+ description = "Верните свои админские силы"
+
+/datum/keybinding/admin/view_tags
+ full_name = "View Tags"
+ hotkey_keys = list("CtrlF11")
+ description = "Открывает меню View-Tags"
diff --git a/modular_bandastation/keybinding/code/artificial_intelligence.dm b/modular_bandastation/keybinding/code/artificial_intelligence.dm
new file mode 100644
index 0000000000000..bbb18b1c3a60c
--- /dev/null
+++ b/modular_bandastation/keybinding/code/artificial_intelligence.dm
@@ -0,0 +1,4 @@
+/datum/keybinding/artificial_intelligence/reconnect
+ full_name = "Переподключиться к оболочке"
+ hotkey_keys = list("-")
+ description = "Подключает к ИИ оболочке, которая была подключена последней"
diff --git a/modular_bandastation/keybinding/code/carbon.dm b/modular_bandastation/keybinding/code/carbon.dm
new file mode 100644
index 0000000000000..28618d1d11555
--- /dev/null
+++ b/modular_bandastation/keybinding/code/carbon.dm
@@ -0,0 +1,14 @@
+/datum/keybinding/carbon/toggle_throw_mode
+ full_name = "Режим броска (переключить)"
+ hotkey_keys = list("R", "Southwest")
+ description = "Переключает будете ли вы бросать текущий предмет"
+
+/datum/keybinding/carbon/hold_throw_mode
+ full_name = "Режим броска (зажать)"
+ hotkey_keys = list("Space")
+ description = "Удерживайте, чтобы включить режим броска, и отпустите, чтобы выключить его"
+
+/datum/keybinding/carbon/give
+ full_name = "Передать вещь (переключить)"
+ hotkey_keys = list("V")
+ description = "Передать предмет в активной руке"
diff --git a/modular_bandastation/keybinding/code/client.dm b/modular_bandastation/keybinding/code/client.dm
new file mode 100644
index 0000000000000..c966154bd29eb
--- /dev/null
+++ b/modular_bandastation/keybinding/code/client.dm
@@ -0,0 +1,14 @@
+/datum/keybinding/client/admin_help
+ full_name = "Админхелп"
+ hotkey_keys = list("F1")
+ description = "Попросите администрацию о помощи"
+
+/datum/keybinding/client/screenshot
+ full_name = "Сделать Screenshot"
+ hotkey_keys = list("Unbound")
+ description = "Сделайте скриншоты"
+
+/datum/keybinding/client/minimal_hud
+ full_name = "Переключить минимальный HUD"
+ hotkey_keys = list("F12")
+ description = "Скрывает большинство элементов HUD"
diff --git a/modular_bandastation/keybinding/code/communication.dm b/modular_bandastation/keybinding/code/communication.dm
new file mode 100644
index 0000000000000..276d42731ddf3
--- /dev/null
+++ b/modular_bandastation/keybinding/code/communication.dm
@@ -0,0 +1,23 @@
+/datum/keybinding/client/communication/say
+ full_name = "Говорить"
+ hotkey_keys = list("F3", "T")
+
+/datum/keybinding/client/communication/radio
+ full_name = "Общий канал рации (;)"
+ hotkey_keys = list("Y")
+
+/datum/keybinding/client/communication/ooc
+ full_name = "OOC"
+ hotkey_keys = list("F2", "O")
+
+/datum/keybinding/client/communication/me
+ full_name = "Эмоция"
+ hotkey_keys = list("F4", "M")
+
+/datum/keybinding/client/communication/looc
+ full_name = "LOOC"
+ hotkey_keys = list("L")
+
+/datum/keybinding/client/communication/whisper
+ full_name = "Шептать"
+ hotkey_keys = list("ShiftT")
diff --git a/modular_bandastation/keybinding/code/emote.dm b/modular_bandastation/keybinding/code/emote.dm
new file mode 100644
index 0000000000000..eb02d081238ec
--- /dev/null
+++ b/modular_bandastation/keybinding/code/emote.dm
@@ -0,0 +1,4 @@
+/datum/keybinding/emote/link_to_emote(datum/emote/faketype)
+ . = ..()
+ if(initial(faketype.name))
+ full_name = capitalize(initial(faketype.name))
diff --git a/modular_bandastation/keybinding/code/human.dm b/modular_bandastation/keybinding/code/human.dm
new file mode 100644
index 0000000000000..96049ef903ead
--- /dev/null
+++ b/modular_bandastation/keybinding/code/human.dm
@@ -0,0 +1,29 @@
+/datum/keybinding/human/quick_equip
+ full_name = "Экипировать вещь"
+ hotkey_keys = list("E")
+ description = "Быстро экипирует предмет в любой подходящий слот"
+
+/datum/keybinding/human/quick_equip_belt
+ full_name = "Быстрая экипировка пояса"
+ hotkey_keys = list("ShiftE")
+ description = "Положить предмет из руки в пояс или вытащить последний предмет из него"
+
+/datum/keybinding/human/quick_equip_belt/quick_equip_bag
+ full_name = "Быстрая экипировка сумки"
+ hotkey_keys = list("ShiftV")
+ description = "Положить предмет из руки в сумку или вытащить последний предмет из нее"
+
+/datum/keybinding/human/quick_equip_belt/quick_equip_suit_storage
+ full_name = "Быстрая экипировка хранилища костюма"
+ hotkey_keys = list("ShiftQ")
+ description = "Положить предмет из руки в костюм или вытащить последний предмет из него"
+
+/datum/keybinding/human/quick_equip_belt/quick_equip_lpocket
+ full_name = "Быстрая экипировка левого кармана"
+ hotkey_keys = list("Alt1")
+ description = "Положить предмет из руки в левый карман или последний предмет из него"
+
+/datum/keybinding/human/quick_equip_belt/quick_equip_rpocket
+ full_name = "Быстрая экипировка правого кармана"
+ hotkey_keys = list("Alt2")
+ description = "Положить предмет из руки в правый карман или последний предмет из него"
diff --git a/modular_bandastation/keybinding/code/living.dm b/modular_bandastation/keybinding/code/living.dm
new file mode 100644
index 0000000000000..f07a12c5546b6
--- /dev/null
+++ b/modular_bandastation/keybinding/code/living.dm
@@ -0,0 +1,44 @@
+/datum/keybinding/living/resist
+ full_name = "Сопротивляться"
+ hotkey_keys = list("B")
+ description = "Освободиться от текущего состояния. В наручниках? Вы горите? Сопротивляйтесь!"
+
+/datum/keybinding/living/look_up
+ full_name = "Посмотреть вверх"
+ hotkey_keys = list("P")
+ description = "Посмотреть на нижний Z-уровень. Возможно только если над вами свободное пространство."
+
+/datum/keybinding/living/look_down
+ full_name = "Посмотреть вниз"
+ hotkey_keys = list(";")
+ description = "Посмотреть на нижний Z-уровень. Возможно только если под вами его видно."
+
+/datum/keybinding/living/rest
+ full_name = "Лечь/встать"
+ hotkey_keys = list("ShiftB")
+ description = "Нажмите, чтобы лечь или встать"
+
+/datum/keybinding/living/toggle_combat_mode
+ full_name = "Переключить Combat Mode"
+ hotkey_keys = list("F")
+ description = "Переключает боевой режим. Это как Помощь/Вред, но круче"
+
+/datum/keybinding/living/toggle_move_intent
+ full_name = "Смена режима ходьбы (зажать)"
+ hotkey_keys = list("Unbound")
+ description = "Удерживайте, чтобы временно поменять режим передвижения."
+
+/datum/keybinding/living/toggle_move_intent_alternative
+ full_name = "Смена режима ходьбы (переключить)"
+ hotkey_keys = list("Unbound")
+ description = "Нажмите, чтобы поменять режим передвижения."
+
+/datum/keybinding/living/enable_combat_mode
+ full_name = "Включить Combat Mode"
+ hotkey_keys = list("4")
+ description = "Включает боевой режим"
+
+/datum/keybinding/living/disable_combat_mode
+ full_name = "Отключить Combat Mode"
+ hotkey_keys = list("1")
+ description = "Отключает боевой режим"
diff --git a/modular_bandastation/keybinding/code/mob.dm b/modular_bandastation/keybinding/code/mob.dm
new file mode 100644
index 0000000000000..e9233eb28e277
--- /dev/null
+++ b/modular_bandastation/keybinding/code/mob.dm
@@ -0,0 +1,71 @@
+/datum/keybinding/mob/stop_pulling
+ full_name = "Перестать тащить"
+ hotkey_keys = list("C", "Delete")
+
+/datum/keybinding/mob/swap_hands
+ full_name = "Поменять руки"
+ hotkey_keys = list("X")
+
+/datum/keybinding/mob/activate_inhand
+ full_name = "Использовать предмет в руке"
+ hotkey_keys = list("Z")
+ description = "Использует предмет в вашей активной руке"
+
+/datum/keybinding/mob/drop_item
+ full_name = "Выложить предмет в руке"
+ hotkey_keys = list("Q")
+
+/datum/keybinding/mob/target/head_cycle
+ full_name = "Выбрать голову/глаза/рот"
+ hotkey_keys = list("Numpad8")
+ description = "Выбрать голову, глаза или рот как цель. Каждое нажатие циклирует между ними. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/head
+ full_name = "Выбрать голову"
+ hotkey_keys = list("Unbound")
+ description = "Выбрать голову, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/eyes
+ full_name = "Выбрать глаза"
+ hotkey_keys = list("Numpad7")
+ description = "Выбрать глаза, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/mouth
+ full_name = "Выбрать рот"
+ hotkey_keys = list("Numpad9")
+ description = "Выбрать рот, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/r_arm
+ full_name = "Выбрать правую руку"
+ hotkey_keys = list("Numpad4")
+ description = "Выбрать правую руку, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/body_chest
+ full_name = "Выбрать грудь"
+ hotkey_keys = list("Numpad5")
+ description = "Выбрать грудь, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/left_arm
+ full_name = "Выбрать левую руку"
+ hotkey_keys = list("Numpad6")
+ description = "Выбрать левую руку, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/right_leg
+ full_name = "Выбрать правую ногу"
+ hotkey_keys = list("Numpad1")
+ description = "Выбрать правую ногу, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/body_groin
+ full_name = "Выбрать пах"
+ hotkey_keys = list("Numpad2")
+ description = "Выбрать паховую область, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/target/left_leg
+ full_name = "Выбрать левую ногу"
+ hotkey_keys = list("Numpad3")
+ description = "Выбрать левую ногу, как цель. Влияет на то, куда вы ударяете, или где вы проводите операции."
+
+/datum/keybinding/mob/prevent_movement
+ full_name = "Остановиться (зажать)"
+ hotkey_keys = list("Alt")
+ description = "При удержании, не дает вам самостоятельно двигаться."
diff --git a/modular_bandastation/keybinding/code/movement.dm b/modular_bandastation/keybinding/code/movement.dm
new file mode 100644
index 0000000000000..a4bf33c237304
--- /dev/null
+++ b/modular_bandastation/keybinding/code/movement.dm
@@ -0,0 +1,29 @@
+/datum/keybinding/movement/north
+ full_name = "Идти на север"
+ hotkey_keys = list("W", "North")
+ description = "Перемещает вашего персонажа на север"
+
+/datum/keybinding/movement/south
+ full_name = "Идти на юг"
+ hotkey_keys = list("S", "South")
+ description = "Перемещает вашего персонажа на юг"
+
+/datum/keybinding/movement/west
+ full_name = "Идти на запад"
+ hotkey_keys = list("A", "West")
+ description = "Перемещает вашего персонажа на запад"
+
+/datum/keybinding/movement/east
+ full_name = "Идти на восток"
+ hotkey_keys = list("D", "East")
+ description = "Перемещает вашего персонажа на восток"
+
+/datum/keybinding/movement/zlevel_upwards
+ full_name = "Идти наверх"
+ hotkey_keys = list("Northeast") // PGUP
+ description = "Перемещает вашего персонажа вверх по Z-уровню, если возможно"
+
+/datum/keybinding/movement/zlevel_downwards
+ full_name = "Идти вниз"
+ hotkey_keys = list("Southeast") // PGDOWN
+ description = "Перемещает вашего персонажа вниз по Z-уровню, если возможно"
diff --git a/modular_bandastation/keybinding/code/robot.dm b/modular_bandastation/keybinding/code/robot.dm
new file mode 100644
index 0000000000000..f81743de18cd5
--- /dev/null
+++ b/modular_bandastation/keybinding/code/robot.dm
@@ -0,0 +1,24 @@
+/datum/keybinding/robot/moduleone
+ full_name = "Ячейка 1"
+ hotkey_keys = list("1")
+ description = "Выбирает активным/неактивным первый модуль"
+
+/datum/keybinding/robot/moduletwo
+ full_name = "Ячейка 2"
+ hotkey_keys = list("2")
+ description = "Выбирает активным/неактивным второй модуль"
+
+/datum/keybinding/robot/modulethree
+ full_name = "Ячейка 3"
+ hotkey_keys = list("3")
+ description = "Выбирает активным/неактивным третий модуль"
+
+/datum/keybinding/robot/unequip_module
+ full_name = "Выложить в хранилище"
+ hotkey_keys = list("Q")
+ description = "Вылаживает предмет из текущий модуля"
+
+/datum/keybinding/robot/undeploy
+ full_name = "Отсоединиться от оболочки"
+ hotkey_keys = list("=")
+ description = "Возвращает в ваше ядро ИИ"
diff --git a/modular_bandastation/modular_bandastation.dme b/modular_bandastation/modular_bandastation.dme
new file mode 100644
index 0000000000000..e219ad8d4e524
--- /dev/null
+++ b/modular_bandastation/modular_bandastation.dme
@@ -0,0 +1,25 @@
+#include "_modpack.dm"
+#include "_modpacks.dm"
+
+#include "_defines220/_defines220.dme"
+#include "_helpers220/_helpers220.dme"
+#include "_signals220/_signals220.dme"
+#include "_singletons/_singletons.dme"
+#include "aesthetics/_aesthetics.dme"
+#include "ai_laws/_ai_laws.dme"
+#include "barsigns/_barsigns.dme"
+#include "communication/_communication.dme"
+//#include "crawl_speed/_crawl_speed.dme" // Fixing floored melee brawl, or first steps to remove RP speed
+#include "cyrillic_fixes/_cyrillic_fixes.dme"
+#include "database220/_database220.dme"
+#include "discord/_discord.dme"
+//#include "emote_panel/_emote_panel.dme" // Waiting for upstream merge
+//#include "events/_events.dme" // Going for pure TG right now...
+#include "examine_panel/_examine_panel.dme"
+#include "gunhud/_gunhud.dme"
+#include "keybinding/_keybinding.dme"
+#include "pixel_shift/_pixel_shift.dme"
+#include "translations/_translations.dme"
+#include "tts/_tts.dme"
+#include "whitelist220/_whitelist220.dme"
+#include "world_topics/_world_topics.dme"
diff --git a/modular_bandastation/pixel_shift/_pixel_shift.dm b/modular_bandastation/pixel_shift/_pixel_shift.dm
new file mode 100644
index 0000000000000..b3d6f05911fd3
--- /dev/null
+++ b/modular_bandastation/pixel_shift/_pixel_shift.dm
@@ -0,0 +1,4 @@
+/datum/modpack/pixel_shift
+ name = "Pixel Shift"
+ desc = "Добавляет возможность движения по-пиксельно в пределах турфа."
+ author = "larentoun (modpack), Vallat (refactor), Ranged66 (layer shift), Azarak (pixel shift port), Gandalf2k15 (pixel shift refactor)"
diff --git a/modular_bandastation/pixel_shift/_pixel_shift.dme b/modular_bandastation/pixel_shift/_pixel_shift.dme
new file mode 100644
index 0000000000000..5b1685e49cbfe
--- /dev/null
+++ b/modular_bandastation/pixel_shift/_pixel_shift.dme
@@ -0,0 +1,6 @@
+#include "_pixel_shift.dm"
+
+#include "code/layer_shift.dm"
+#include "code/pixel_shift_component.dm"
+#include "code/pixel_shift_keybind.dm"
+#include "code/pixel_shift_mob.dm"
diff --git a/modular_bandastation/pixel_shift/code/layer_shift.dm b/modular_bandastation/pixel_shift/code/layer_shift.dm
new file mode 100644
index 0000000000000..3be3824c06fd1
--- /dev/null
+++ b/modular_bandastation/pixel_shift/code/layer_shift.dm
@@ -0,0 +1,36 @@
+#define MOB_LAYER_SHIFT_INCREMENT 0.01
+#define MOB_LAYER_SHIFT_MIN 3.95
+//#define MOB_LAYER 4 // This is a byond standard define
+#define MOB_LAYER_SHIFT_MAX 4.05
+
+/mob/living/verb/layershift_up()
+ set name = "Shift Layer Upwards"
+ set category = "IC"
+
+ if(incapacitated())
+ to_chat(src, span_warning("You can't do that right now!"))
+ return
+
+ if(layer >= MOB_LAYER_SHIFT_MAX)
+ to_chat(src, span_warning("You cannot increase your layer priority any further."))
+ return
+
+ layer += MOB_LAYER_SHIFT_INCREMENT
+ var/layer_priority = round((layer - MOB_LAYER) * 100, 1) // Just for text feedback
+ to_chat(src, span_notice("Your layer priority is now [layer_priority]."))
+
+/mob/living/verb/layershift_down()
+ set name = "Shift Layer Downwards"
+ set category = "IC"
+
+ if(incapacitated())
+ to_chat(src, span_warning("You can't do that right now!"))
+ return
+
+ if(layer <= MOB_LAYER_SHIFT_MIN)
+ to_chat(src, span_warning("You cannot decrease your layer priority any further."))
+ return
+
+ layer -= MOB_LAYER_SHIFT_INCREMENT
+ var/layer_priority = round((layer - MOB_LAYER) * 100, 1) // Just for text feedback
+ to_chat(src, span_notice("Your layer priority is now [layer_priority]."))
diff --git a/modular_bandastation/pixel_shift/code/pixel_shift_component.dm b/modular_bandastation/pixel_shift/code/pixel_shift_component.dm
new file mode 100644
index 0000000000000..76d5f6c08e3c9
--- /dev/null
+++ b/modular_bandastation/pixel_shift/code/pixel_shift_component.dm
@@ -0,0 +1,96 @@
+/datum/component/pixel_shift
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Whether the mob is pixel shifted or not
+ var/is_shifted = FALSE
+ /// If we are in the shifting setting.
+ var/shifting = TRUE
+ /// 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
+ var/maximum_pixel_shift = 12
+ var/passable_shift_threshold = 8
+
+/datum/component/pixel_shift/Initialize(...)
+ . = ..()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/pixel_shift/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_KB_MOB_PIXEL_SHIFT_DOWN, PROC_REF(pixel_shift_down))
+ RegisterSignal(parent, COMSIG_KB_MOB_PIXEL_SHIFT_UP, PROC_REF(pixel_shift_up))
+ RegisterSignals(parent, list(COMSIG_LIVING_RESET_PULL_OFFSETS, COMSIG_LIVING_SET_PULL_OFFSET, COMSIG_MOVABLE_MOVED, SIGNAL_ADDTRAIT(TRAIT_FLOORED)), PROC_REF(unpixel_shift))
+ RegisterSignal(parent, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(pre_move_check))
+ RegisterSignal(parent, COMSIG_LIVING_CAN_ALLOW_THROUGH, PROC_REF(check_passable))
+
+/datum/component/pixel_shift/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_KB_MOB_PIXEL_SHIFT_DOWN)
+ UnregisterSignal(parent, COMSIG_KB_MOB_PIXEL_SHIFT_UP)
+ UnregisterSignal(parent, COMSIG_LIVING_RESET_PULL_OFFSETS)
+ UnregisterSignal(parent, COMSIG_LIVING_SET_PULL_OFFSET)
+ UnregisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_FLOORED))
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(parent, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE)
+ UnregisterSignal(parent, COMSIG_LIVING_CAN_ALLOW_THROUGH)
+
+/datum/component/pixel_shift/proc/pre_move_check(mob/source, new_loc, direct)
+ SIGNAL_HANDLER
+ if(shifting)
+ pixel_shift(source, direct)
+ return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE
+
+/datum/component/pixel_shift/proc/check_passable(mob/source, atom/movable/mover, border_dir)
+ SIGNAL_HANDLER
+ // Make sure to not allow projectiles of any kind past where they normally wouldn't.
+ if(!isprojectile(mover) && !mover.throwing && passthroughable & border_dir)
+ return COMPONENT_LIVING_PASSABLE
+
+/datum/component/pixel_shift/proc/pixel_shift_down()
+ SIGNAL_HANDLER
+ shifting = TRUE
+ return COMSIG_KB_ACTIVATED
+
+/datum/component/pixel_shift/proc/pixel_shift_up()
+ SIGNAL_HANDLER
+ shifting = FALSE
+
+/datum/component/pixel_shift/proc/unpixel_shift()
+ SIGNAL_HANDLER
+ passthroughable = NONE
+ if(is_shifted)
+ var/mob/living/owner = parent
+ owner.pixel_x = owner.body_position_pixel_x_offset + owner.base_pixel_x
+ owner.pixel_y = owner.body_position_pixel_y_offset + owner.base_pixel_y
+ qdel(src)
+
+/datum/component/pixel_shift/proc/pixel_shift(mob/source, direct)
+ var/mob/living/owner = parent
+ if(HAS_TRAIT(owner, TRAIT_RESTRAINED) || HAS_TRAIT(owner, TRAIT_IMMOBILIZED) || length(owner.pulledby) || owner.stat != CONSCIOUS)
+ return
+ passthroughable = NONE
+ switch(direct)
+ if(NORTH)
+ if(owner.pixel_y <= maximum_pixel_shift + owner.base_pixel_y)
+ owner.pixel_y++
+ is_shifted = TRUE
+ if(EAST)
+ if(owner.pixel_x <= maximum_pixel_shift + owner.base_pixel_x)
+ owner.pixel_x++
+ is_shifted = TRUE
+ if(SOUTH)
+ if(owner.pixel_y >= -maximum_pixel_shift + owner.base_pixel_y)
+ owner.pixel_y--
+ is_shifted = TRUE
+ if(WEST)
+ if(owner.pixel_x >= -maximum_pixel_shift + owner.base_pixel_x)
+ owner.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(owner.pixel_y > passable_shift_threshold)
+ passthroughable |= EAST | SOUTH | WEST
+ else if(owner.pixel_y < -passable_shift_threshold)
+ passthroughable |= NORTH | EAST | WEST
+ if(owner.pixel_x > passable_shift_threshold)
+ passthroughable |= NORTH | SOUTH | WEST
+ else if(owner.pixel_x < -passable_shift_threshold)
+ passthroughable |= NORTH | EAST | SOUTH
diff --git a/modular_bandastation/pixel_shift/code/pixel_shift_keybind.dm b/modular_bandastation/pixel_shift/code/pixel_shift_keybind.dm
new file mode 100644
index 0000000000000..dd7d9ff8ac7e7
--- /dev/null
+++ b/modular_bandastation/pixel_shift/code/pixel_shift_keybind.dm
@@ -0,0 +1,17 @@
+/datum/keybinding/mob/pixel_shift
+ hotkey_keys = list("B")
+ name = "pixel_shift"
+ full_name = "Пиксель-Шифт"
+ description = "Попиксельное перемещение персонажа в тайле."
+ category = CATEGORY_MOVEMENT
+ keybind_signal = COMSIG_KB_MOB_PIXEL_SHIFT_DOWN
+
+/datum/keybinding/mob/pixel_shift/down(client/user)
+ . = ..()
+ if(.)
+ return
+ user.mob.add_pixel_shift_component()
+
+/datum/keybinding/mob/pixel_shift/up(client/user)
+ . = ..()
+ SEND_SIGNAL(user.mob, COMSIG_KB_MOB_PIXEL_SHIFT_UP)
diff --git a/modular_bandastation/pixel_shift/code/pixel_shift_mob.dm b/modular_bandastation/pixel_shift/code/pixel_shift_mob.dm
new file mode 100644
index 0000000000000..cd02f441ce038
--- /dev/null
+++ b/modular_bandastation/pixel_shift/code/pixel_shift_mob.dm
@@ -0,0 +1,5 @@
+/mob/proc/add_pixel_shift_component()
+ return
+
+/mob/living/add_pixel_shift_component()
+ AddComponent(/datum/component/pixel_shift)
diff --git a/modular_bandastation/translations/_translations.dm b/modular_bandastation/translations/_translations.dm
new file mode 100644
index 0000000000000..7cf16725ef5e0
--- /dev/null
+++ b/modular_bandastation/translations/_translations.dm
@@ -0,0 +1,4 @@
+/datum/modpack/translations
+ name = "Переводы"
+ desc = "Добавляет переводы"
+ author = "Vallat"
diff --git a/modular_bandastation/translations/_translations.dme b/modular_bandastation/translations/_translations.dme
new file mode 100644
index 0000000000000..d1949fb31473a
--- /dev/null
+++ b/modular_bandastation/translations/_translations.dme
@@ -0,0 +1,4 @@
+#include "_translations.dm"
+
+#include "code/moustache.dm"
+#include "code/restaurant_customer.dm"
diff --git a/modular_bandastation/translations/code/moustache.dm b/modular_bandastation/translations/code/moustache.dm
new file mode 100644
index 0000000000000..ff8d1936599b1
--- /dev/null
+++ b/modular_bandastation/translations/code/moustache.dm
@@ -0,0 +1,37 @@
+/obj/item/clothing/mask/fakemoustache
+ name = "накладные усы"
+ desc = "Осторожно: усы накладные."
+
+/obj/item/clothing/mask/fakemoustache/italian
+ name = "итальянские усы"
+ desc = "Изготовлен из настоящих итальянских волосков для усов. Дает владельцу непреодолимое желание дико жестикулировать."
+
+/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message[1] != "*")
+ var/static/regex/words = new(@"(?= 10) && (rest % 100 <= 19)
+ var/list/data = use_teens ? list(list(teens, 10), list(hundreds, 1000)) : list(list(units, 10), list(tens, 100), list(hundreds, 1000))
+ for(var/list in data)
+
+ var/names = list[1]
+ var/x = list[2]
+
+ var/cur = round(((rest - prev) % x) * 10 / x) + 1
+ prev = rest % x
+
+ if(x == 10 && use_teens)
+ plural = 3
+ name += teens[cur]
+ else if(cur == 1)
+ continue
+ else if(x == 10)
+ var/name_ = names[cur]
+ if(islist(name_))
+ name_ = name_[sex == "m" ? 1 : 2]
+ name += name_
+ if(cur >= 3 && cur <= 5)
+ plural = 2
+ else if(cur == 2)
+ plural = 1
+ else
+ plural = 3
+ else
+ name += names[cur-1]
+
+ return list(plural, name)
+
+/datum/number/proc/int2words(textnum, list/main_units = list(list("", "", ""), "m"))
+// http://ru.wikipedia.org/wiki/Gettext#.D0.9C.D0.BD.D0.BE.D0.B6.D0.B5.D1.81.D1.82.D0.B2.D0.B5.D0.BD.D0.BD.D1.8B.D0.B5_.D1.87.D0.B8.D1.81.D0.BB.D0.B0_2
+
+ var/list/_orders = list(main_units) + orders
+
+ var/num = text2num(textnum)
+ if(num == 0)
+ return trim(jointext(list(units[1], _orders[1][1][3]), " "))
+
+ var/negative = FALSE
+ if(num < 0)
+ negative = TRUE
+ textnum = copytext_char(textnum, 2, 0)
+
+ var/ord = 1
+ var/list/name = list()
+
+ while(textnum)
+ var/next_thousand = text2num(copytext_char(textnum, -3, 0))
+ var/list/thousand_result = thousand(next_thousand, _orders[ord][2])
+ var/plural = thousand_result[1]
+ var/list/nme = thousand_result[2]
+
+ if(length(nme) || ord == 1)
+ name += _orders[ord][1][plural]
+
+ name += nme
+ textnum = copytext_char(textnum, 1, -3)
+ ord += 1
+
+ if(negative)
+ name += minus
+
+ var/temp_name = name
+ name = list()
+ for(var/i = length_char(temp_name), i >= 1, i--)
+ name += temp_name[i]
+
+ var/result = trim(jointext(name, " "))
+ return result
+
+/datum/number/proc/decimal2words(textvalue, places = 3)
+ var/pieces = splittext_char(textvalue, ".")
+ var/integral = pieces[1]
+ var/exp = copytext_char(pieces[2], 1, places + 1)
+ var/list/exp_units = decimal_exp_units[length_char(exp)]
+
+ var/result = trim("[int2words(integral, decimal_int_units)] [int2words(exp, exp_units)]")
+ return result
diff --git a/modular_bandastation/tts/code/providers/silero.dm b/modular_bandastation/tts/code/providers/silero.dm
new file mode 100644
index 0000000000000..3c5a46406a0b1
--- /dev/null
+++ b/modular_bandastation/tts/code/providers/silero.dm
@@ -0,0 +1,49 @@
+/datum/tts_provider/silero
+ name = "Silero"
+ is_enabled = TRUE
+
+/datum/tts_provider/silero/request(text, datum/tts_seed/silero/seed, datum/callback/proc_callback)
+ if(throttle_check())
+ return FALSE
+
+ var/ssml_text = {"[text] "}
+
+ var/list/req_body = list()
+
+ req_body["api_token"] = CONFIG_GET(string/tts_token_silero)
+ req_body["text"] = ssml_text
+ req_body["sample_rate"] = 24000
+ req_body["ssml"] = TRUE
+ req_body["speaker"] = seed.value
+ req_body["lang"] = "ru"
+ req_body["remote_id"] = "[world.port]"
+ req_body["put_accent"] = TRUE
+ req_body["put_yo"] = FALSE
+ req_body["symbol_durs"] = list()
+ req_body["format"] = "ogg"
+ req_body["word_ts"] = FALSE
+
+ SShttp.create_async_request(RUSTG_HTTP_METHOD_POST, CONFIG_GET(string/tts_api_url_silero), json_encode(req_body), list("content-type" = "application/json"), proc_callback)
+
+ return TRUE
+
+/datum/tts_provider/silero/process_response(datum/http_response/response)
+ var/data = json_decode(response.body)
+ // log_debug(response.body)
+
+ if(data["timings"]["003_tts_time"] > 3)
+ is_throttled = TRUE
+ throttled_until = world.time + 15 SECONDS
+
+ return data["results"][1]["audio"]
+
+ //var/sha1 = data["original_sha1"]
+
+/datum/tts_provider/silero/pitch_whisper(text)
+ return {"[text] "}
+
+/datum/tts_provider/silero/rate_faster(text)
+ return {"[text] "}
+
+/datum/tts_provider/silero/rate_medium(text)
+ return {"[text] "}
diff --git a/modular_bandastation/tts/code/seeds/base.dm b/modular_bandastation/tts/code/seeds/base.dm
new file mode 100644
index 0000000000000..dbfe0e6af47d6
--- /dev/null
+++ b/modular_bandastation/tts/code/seeds/base.dm
@@ -0,0 +1,7 @@
+/datum/tts_seed
+ var/name = "STUB"
+ var/value = "STUB"
+ var/category = TTS_CATEGORY_OTHER
+ var/gender = TTS_GENDER_ANY
+ var/datum/tts_provider/provider = /datum/tts_provider
+ var/required_donator_level = 0
diff --git a/modular_bandastation/tts/code/seeds/silero.dm b/modular_bandastation/tts/code/seeds/silero.dm
new file mode 100644
index 0000000000000..d85746ec8fa6b
--- /dev/null
+++ b/modular_bandastation/tts/code/seeds/silero.dm
@@ -0,0 +1,4863 @@
+/datum/tts_seed/silero
+ provider = /datum/tts_provider/silero
+
+/datum/tts_seed/silero/arthas
+ name = "Arthas"
+ value = "arthas"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kelthuzad
+ name = "Kelthuzad"
+ value = "kelthuzad"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/anubarak
+ name = "Anubarak"
+ value = "anubarak"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/thrall
+ name = "Thrall"
+ value = "thrall"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/grunt
+ name = "Grunt"
+ value = "grunt"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/cairne
+ name = "Cairne"
+ value = "cairne"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/rexxar
+ name = "Rexxar"
+ value = "rexxar"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/uther
+ name = "Uther"
+ value = "uther"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/jaina
+ name = "Jaina"
+ value = "jaina"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/kael
+ name = "Kael"
+ value = "kael"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/garithos
+ name = "Garithos"
+ value = "garithos"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/maiev
+ name = "Maiev"
+ value = "maiev"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/naisha
+ name = "Naisha"
+ value = "naisha"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/tyrande
+ name = "Tyrande"
+ value = "tyrande"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/furion
+ name = "Furion"
+ value = "furion"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/illidan
+ name = "Illidan"
+ value = "illidan"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ladyvashj
+ name = "Ladyvashj"
+ value = "ladyvashj"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/narrator
+ name = "Narrator"
+ value = "narrator"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/medivh
+ name = "Medivh"
+ value = "medivh"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/villagerm
+ name = "Villagerm"
+ value = "villagerm"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/xenia
+ name = "Xenia"
+ value = "xenia"
+ category = TTS_CATEGORY_OTHER
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/illidan_f
+ name = "Illidan_f"
+ value = "illidan_f"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/peon
+ name = "Peon"
+ value = "peon"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/chen
+ name = "Chen"
+ value = "chen"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dread_bm
+ name = "Dread_bm"
+ value = "dread_bm"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sylvanas
+ name = "Sylvanas"
+ value = "sylvanas"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/priest
+ name = "Priest"
+ value = "priest"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/acolyte
+ name = "Acolyte"
+ value = "acolyte"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/muradin
+ name = "Muradin"
+ value = "muradin"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/dread_t
+ name = "Dread_t"
+ value = "dread_t"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/mannoroth
+ name = "Mannoroth"
+ value = "mannoroth"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sorceress
+ name = "Sorceress"
+ value = "sorceress"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/peasant
+ name = "Peasant"
+ value = "peasant"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/alyx
+ name = "Alyx"
+ value = "alyx"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/glados
+ name = "Glados"
+ value = "glados"
+ category = TTS_CATEGORY_PORTAL2
+ gender = TTS_GENDER_ANY
+
+/datum/tts_seed/silero/announcer
+ name = "Announcer"
+ value = "announcer"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/wheatley
+ name = "Wheatley"
+ value = "wheatley"
+ category = TTS_CATEGORY_PORTAL2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/barney
+ name = "Barney"
+ value = "barney"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/raynor
+ name = "Raynor"
+ value = "raynor"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kerrigan
+ name = "Kerrigan"
+ value = "kerrigan"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/tusk
+ name = "Tusk"
+ value = "tusk"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/earth
+ name = "Earth"
+ value = "earth"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/wraith
+ name = "Wraith"
+ value = "wraith"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/meepo
+ name = "Meepo"
+ value = "meepo"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lina
+ name = "Lina"
+ value = "lina"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/bristle
+ name = "Bristle"
+ value = "bristle"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/gyro
+ name = "Gyro"
+ value = "gyro"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/treant
+ name = "Treant"
+ value = "treant"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lancer
+ name = "Lancer"
+ value = "lancer"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/clockwerk
+ name = "Clockwerk"
+ value = "clockwerk"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/batrider
+ name = "Batrider"
+ value = "batrider"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kotl
+ name = "Kotl"
+ value = "kotl"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kunkka
+ name = "Kunkka"
+ value = "kunkka"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/pudge
+ name = "Pudge"
+ value = "pudge"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/juggernaut
+ name = "Juggernaut"
+ value = "juggernaut"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/vort_e2
+ name = "Vort_e2"
+ value = "vort_e2"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/luna
+ name = "Luna"
+ value = "luna"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/omni
+ name = "Omni"
+ value = "omni"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sniper
+ name = "Sniper"
+ value = "sniper"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/skywrath
+ name = "Skywrath"
+ value = "skywrath"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/bounty
+ name = "Bounty"
+ value = "bounty"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/huskar
+ name = "Huskar"
+ value = "huskar"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/windranger
+ name = "Windranger"
+ value = "windranger"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/bloodseeker
+ name = "Bloodseeker"
+ value = "bloodseeker"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/templar
+ name = "Templar"
+ value = "templar"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/ranger
+ name = "Ranger"
+ value = "ranger"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/shaker
+ name = "Shaker"
+ value = "shaker"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mortred
+ name = "Mortred"
+ value = "mortred"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/queen
+ name = "Queen"
+ value = "queen"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/storm
+ name = "Storm"
+ value = "storm"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tide
+ name = "Tide"
+ value = "tide"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/evelynn
+ name = "Evelynn"
+ value = "evelynn"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/riki
+ name = "Riki"
+ value = "riki"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/antimage
+ name = "Antimage"
+ value = "antimage"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/witchdoctor
+ name = "Witchdoctor"
+ value = "witchdoctor"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/doom
+ name = "Doom"
+ value = "doom"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/yuumi
+ name = "Yuumi"
+ value = "yuumi"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+
+/datum/tts_seed/silero/bandit
+ name = "Bandit"
+ value = "bandit"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/pantheon
+ name = "pantheon"
+ value = "pantheon"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/tychus
+ name = "Tychus"
+ value = "tychus"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/breen
+ name = "Breen"
+ value = "breen"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kleiner
+ name = "Kleiner"
+ value = "kleiner"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/father
+ name = "Father"
+ value = "father"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tosh
+ name = "Tosh"
+ value = "tosh"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/stetmann
+ name = "Stetmann"
+ value = "stetmann"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/hanson
+ name = "Hanson"
+ value = "hanson"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/swann
+ name = "Swann"
+ value = "swann"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hill
+ name = "Hill"
+ value = "hill"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/gman_e2
+ name = "Gman_e2"
+ value = "gman_e2"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/valerian
+ name = "Valerian"
+ value = "valerian"
+ category = TTS_CATEGORY_STARCRAFT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gman
+ name = "Gman"
+ value = "gman"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/vort
+ name = "Vort"
+ value = "vort"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/aradesh
+ name = "Aradesh"
+ value = "aradesh"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/dornan
+ name = "Dornan"
+ value = "dornan"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/elder
+ name = "Elder"
+ value = "elder"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/harris
+ name = "Harris"
+ value = "harris"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/cabbot
+ name = "Cabbot"
+ value = "cabbot"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/decker
+ name = "Decker"
+ value = "decker"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dick
+ name = "Dick"
+ value = "dick"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/officer
+ name = "Officer"
+ value = "officer"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/frank
+ name = "Frank"
+ value = "frank"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/gizmo
+ name = "Gizmo"
+ value = "gizmo"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hakunin
+ name = "Hakunin"
+ value = "hakunin"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/harold
+ name = "Harold"
+ value = "harold"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/harry
+ name = "Harry"
+ value = "harry"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/jain
+ name = "Jain"
+ value = "jain"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/maxson
+ name = "Maxson"
+ value = "maxson"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/killian
+ name = "Killian"
+ value = "killian"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/laura
+ name = "Laura"
+ value = "laura"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/lieutenant
+ name = "Lieutenant"
+ value = "lieutenant"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/loxley
+ name = "Loxley"
+ value = "loxley"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lynette
+ name = "Lynette"
+ value = "lynette"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/marcus
+ name = "Marcus"
+ value = "marcus"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/master
+ name = "Master"
+ value = "master"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/morpheus
+ name = "Morpheus"
+ value = "morpheus"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/myron
+ name = "Myron"
+ value = "myron"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nicole
+ name = "Nicole"
+ value = "nicole"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/overseer
+ name = "Overseer"
+ value = "overseer"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rhombus
+ name = "Rhombus"
+ value = "rhombus"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/_set
+ name = "Set"
+ value = "set"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sulik
+ name = "Sulik"
+ value = "sulik"
+ category = TTS_CATEGORY_FALLOUT2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tandi
+ name = "Tandi"
+ value = "tandi"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/vree
+ name = "Vree"
+ value = "vree"
+ category = TTS_CATEGORY_FALLOUT
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/dude
+ name = "Dude"
+ value = "dude"
+ category = TTS_CATEGORY_POSTAL2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/archmage
+ name = "Archmage"
+ value = "archmage"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/demoman
+ name = "Demoman"
+ value = "demoman"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/engineer
+ name = "Engineer"
+ value = "engineer"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/heavy
+ name = "Heavy"
+ value = "heavy"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/medic
+ name = "Medic"
+ value = "medic"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/scout
+ name = "Scout"
+ value = "scout"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sniper_tf
+ name = "Sniper_tf"
+ value = "sniper_tf"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/soldier
+ name = "Soldier"
+ value = "soldier"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/spy
+ name = "Spy"
+ value = "spy"
+ category = TTS_CATEGORY_TEAMFORTRESS2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/admiral
+ name = "Admiral"
+ value = "admiral"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/alchemist
+ name = "Alchemist"
+ value = "alchemist"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/archimonde
+ name = "Archimonde"
+ value = "archimonde"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/breaker
+ name = "Breaker"
+ value = "breaker"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/captain
+ name = "Captain"
+ value = "captain"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dryad
+ name = "Dryad"
+ value = "dryad"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_ANY
+
+/datum/tts_seed/silero/elf_eng
+ name = "Elf_eng"
+ value = "elf_eng"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/footman
+ name = "Footman"
+ value = "footman"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/grom
+ name = "Grom"
+ value = "grom"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hh
+ name = "Hh"
+ value = "hh"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/huntress
+ name = "Huntress"
+ value = "huntress"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/keeper
+ name = "Keeper"
+ value = "keeper"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/naga_m
+ name = "Naga_m"
+ value = "naga_m"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/naga_rg
+ name = "Naga_rg"
+ value = "naga_rg"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/peasant_w
+ name = "Peasant_w"
+ value = "peasant_w"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/rifleman
+ name = "Rifleman"
+ value = "rifleman"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/satyr
+ name = "Satyr"
+ value = "satyr"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sylvanas_w
+ name = "Sylvanas_w"
+ value = "sylvanas_w"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/voljin
+ name = "Voljin"
+ value = "voljin"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sidorovich
+ name = "Sidorovich"
+ value = "sidorovich"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/p3
+ name = "P3"
+ value = "p3"
+ category = TTS_CATEGORY_ATOMIC_HEART
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hraz
+ name = "Hraz"
+ value = "hraz"
+ category = TTS_CATEGORY_ATOMIC_HEART
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tereshkova
+ name = "Tereshkova"
+ value = "tereshkova"
+ category = TTS_CATEGORY_ATOMIC_HEART
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/babazina
+ name = "Babazina"
+ value = "babazina"
+ category = TTS_CATEGORY_ATOMIC_HEART
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/darius
+ name = "Darius"
+ value = "darius"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/trundle
+ name = "Trundle"
+ value = "trundle"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/garen
+ name = "Garen"
+ value = "garen"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kled
+ name = "Kled"
+ value = "kled"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ekko
+ name = "Ekko"
+ value = "ekko"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/volibear
+ name = "Volibear"
+ value = "volibear"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/samira
+ name = "Samira"
+ value = "samira"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/swain
+ name = "Swain"
+ value = "swain"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/udyr
+ name = "Udyr"
+ value = "udyr"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dr_mundo
+ name = "Dr_mundo"
+ value = "dr_mundo"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/graves
+ name = "Graves"
+ value = "graves"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rakan
+ name = "Rakan"
+ value = "rakan"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/renata_glasc
+ name = "Renata_glasc"
+ value = "renata_glasc"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/gangplank
+ name = "Gangplank"
+ value = "gangplank"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/riven
+ name = "Riven"
+ value = "riven"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/katarina
+ name = "Katarina"
+ value = "katarina"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ahri
+ name = "Ahri"
+ value = "ahri"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ornn
+ name = "Ornn"
+ value = "ornn"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/braum
+ name = "Braum"
+ value = "braum"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/fizz
+ name = "Fizz"
+ value = "fizz"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/draven
+ name = "Draven"
+ value = "draven"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/qiyana
+ name = "Qiyana"
+ value = "qiyana"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ksante
+ name = "Ksante"
+ value = "ksante"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/talon
+ name = "Talon"
+ value = "talon"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/shyvana
+ name = "Shyvana"
+ value = "shyvana"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zenyatta
+ name = "Zenyatta"
+ value = "zenyatta"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kiriko
+ name = "Kiriko"
+ value = "kiriko"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hanzo
+ name = "Hanzo"
+ value = "hanzo"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/roadhog
+ name = "Roadhog"
+ value = "roadhog"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sigma
+ name = "Sigma"
+ value = "sigma"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/soldier_76
+ name = "Soldier_76"
+ value = "soldier_76"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/junkrat
+ name = "Junkrat"
+ value = "junkrat"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tracer
+ name = "Tracer"
+ value = "tracer"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/genji
+ name = "Genji"
+ value = "genji"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/echo
+ name = "Echo"
+ value = "echo"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/sojourn
+ name = "Sojourn"
+ value = "sojourn"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/winston
+ name = "Winston"
+ value = "winston"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/reaper
+ name = "Reaper"
+ value = "reaper"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/training_robot
+ name = "Training_robot"
+ value = "training_robot"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_darkelf
+ name = "M_darkelf"
+ value = "m_darkelf"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/esbern
+ name = "Esbern"
+ value = "esbern"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_argo
+ name = "M_argo"
+ value = "m_argo"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_khajiit
+ name = "M_khajiit"
+ value = "m_khajiit"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_coward
+ name = "M_coward"
+ value = "m_coward"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/farkas
+ name = "Farkas"
+ value = "farkas"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_drunk
+ name = "M_drunk"
+ value = "m_drunk"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_khajiit
+ name = "F_khajiit"
+ value = "f_khajiit"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_citizen
+ name = "M_citizen"
+ value = "m_citizen"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_orc
+ name = "M_orc"
+ value = "m_orc"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/odahviing
+ name = "Odahviing"
+ value = "odahviing"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kodlak
+ name = "Kodlak"
+ value = "kodlak"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/m_child
+ name = "M_child"
+ value = "m_child"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/emperor
+ name = "Emperor"
+ value = "emperor"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/hagraven
+ name = "Hagraven"
+ value = "hagraven"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nazir
+ name = "Nazir"
+ value = "nazir"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dremora
+ name = "Dremora"
+ value = "dremora"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/alduin
+ name = "Alduin"
+ value = "alduin"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/malkoran
+ name = "Malkoran"
+ value = "malkoran"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/barbas
+ name = "Barbas"
+ value = "barbas"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hermaeus
+ name = "Hermaeus"
+ value = "hermaeus"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hakon
+ name = "Hakon"
+ value = "hakon"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rita
+ name = "Rita"
+ value = "rita"
+ category = TTS_CATEGORY_RITA
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/barman
+ name = "Barman"
+ value = "barman"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bridger2
+ name = "Bridger2"
+ value = "bridger2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bridger3
+ name = "Bridger3"
+ value = "bridger3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/cannibal3
+ name = "Cannibal3"
+ value = "cannibal3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/bridger1
+ name = "Bridger1"
+ value = "bridger1"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cannibal2
+ name = "Cannibal2"
+ value = "cannibal2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/slave1
+ name = "Slave1"
+ value = "slave1"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/slave3
+ name = "Slave3"
+ value = "slave3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/mira
+ name = "Mira"
+ value = "mira"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/valeera
+ name = "Valeera"
+ value = "valeera"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/rehgar
+ name = "Rehgar"
+ value = "rehgar"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/yrel
+ name = "Yrel"
+ value = "yrel"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/volskaya
+ name = "Volskaya"
+ value = "volskaya"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/necromancer
+ name = "Necromancer"
+ value = "necromancer"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zuljin
+ name = "Zuljin"
+ value = "zuljin"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/samuro
+ name = "Samuro"
+ value = "samuro"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tyrael
+ name = "Tyrael"
+ value = "tyrael"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/athena
+ name = "Athena"
+ value = "athena"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/default
+ name = "Default"
+ value = "default"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/chromie
+ name = "Chromie"
+ value = "chromie"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/orphea
+ name = "Orphea"
+ value = "orphea"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/adjutant
+ name = "Adjutant"
+ value = "adjutant"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vanndara
+ name = "Vanndara"
+ value = "vanndara"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mechatassadar
+ name = "Mechatassadar"
+ value = "mechatassadar"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/blackheart
+ name = "Blackheart"
+ value = "blackheart"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/olaf
+ name = "Olaf"
+ value = "olaf"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/alarak
+ name = "Alarak"
+ value = "alarak"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dva
+ name = "Dva"
+ value = "dva"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/toy18
+ name = "Toy18"
+ value = "toy18"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/witchdoctor_h
+ name = "Witchdoctor_h"
+ value = "witchdoctor_h"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lucio
+ name = "Lucio"
+ value = "lucio"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/angel
+ name = "Angel"
+ value = "angel"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/thunderking
+ name = "Thunderking"
+ value = "thunderking"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dr_boom
+ name = "Dr_boom"
+ value = "dr_boom"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hooktusk
+ name = "Hooktusk"
+ value = "hooktusk"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/sinclari
+ name = "Sinclari"
+ value = "sinclari"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kazakus
+ name = "Kazakus"
+ value = "kazakus"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ol_toomba
+ name = "Ol_toomba"
+ value = "ol_toomba"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/moroes
+ name = "Moroes"
+ value = "moroes"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/maiev_hs
+ name = "Maiev_hs"
+ value = "maiev_hs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/zentimo
+ name = "Zentimo"
+ value = "zentimo"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/rastakhan
+ name = "Rastakhan"
+ value = "rastakhan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/innkeeper
+ name = "Innkeeper"
+ value = "innkeeper"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/togwaggle
+ name = "Togwaggle"
+ value = "togwaggle"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/biggs
+ name = "Biggs"
+ value = "biggs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/brann
+ name = "Brann"
+ value = "brann"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tekahn_boss
+ name = "Tekahn_boss"
+ value = "tekahn_boss"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/siamat
+ name = "Siamat"
+ value = "siamat"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/omnotron
+ name = "Omnotron"
+ value = "omnotron"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/putricide
+ name = "Putricide"
+ value = "putricide"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/khadgar
+ name = "Khadgar"
+ value = "khadgar"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zoie
+ name = "Zoie"
+ value = "zoie"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/azalina
+ name = "Azalina"
+ value = "azalina"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/chu
+ name = "Chu"
+ value = "chu"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/tekahn
+ name = "Tekahn"
+ value = "tekahn"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sthara
+ name = "Sthara"
+ value = "sthara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dovo
+ name = "Dovo"
+ value = "dovo"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/shaw
+ name = "Shaw"
+ value = "shaw"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/greymane
+ name = "Greymane"
+ value = "greymane"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/willow
+ name = "Willow"
+ value = "willow"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/haro
+ name = "Haro"
+ value = "haro"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hagatha
+ name = "Hagatha"
+ value = "hagatha"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/reno
+ name = "Reno"
+ value = "reno"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ozara
+ name = "Ozara"
+ value = "ozara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/loti
+ name = "Loti"
+ value = "loti"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tarkus
+ name = "Tarkus"
+ value = "tarkus"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/voone
+ name = "Voone"
+ value = "voone"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tala
+ name = "Tala"
+ value = "tala"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/edra
+ name = "Edra"
+ value = "edra"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/myra
+ name = "Myra"
+ value = "myra"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/smiggs
+ name = "Smiggs"
+ value = "smiggs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/timothy
+ name = "Timothy"
+ value = "timothy"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/wendy
+ name = "Wendy"
+ value = "wendy"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hannigan
+ name = "Hannigan"
+ value = "hannigan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vargoth
+ name = "Vargoth"
+ value = "vargoth"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/jolene
+ name = "Jolene"
+ value = "jolene"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/kyriss
+ name = "Kyriss"
+ value = "kyriss"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/saurfang
+ name = "Saurfang"
+ value = "saurfang"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kizi
+ name = "Kizi"
+ value = "kizi"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/slate
+ name = "Slate"
+ value = "slate"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hesutu
+ name = "Hesutu"
+ value = "hesutu"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hancho
+ name = "Hancho"
+ value = "hancho"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/gnomenapper
+ name = "Gnomenapper"
+ value = "gnomenapper"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/valdera
+ name = "Valdera"
+ value = "valdera"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/disidra
+ name = "Disidra"
+ value = "disidra"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/omu
+ name = "Omu"
+ value = "omu"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/floop
+ name = "Floop"
+ value = "floop"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/belloc
+ name = "Belloc"
+ value = "belloc"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/xurios
+ name = "Xurios"
+ value = "xurios"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/wagtoggle
+ name = "Wagtoggle"
+ value = "wagtoggle"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/belnaara
+ name = "Belnaara"
+ value = "belnaara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lilayell
+ name = "Lilayell"
+ value = "lilayell"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/candlebeard
+ name = "Candlebeard"
+ value = "candlebeard"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/awilo
+ name = "Awilo"
+ value = "awilo"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/marei
+ name = "Marei"
+ value = "marei"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/applebough
+ name = "Applebough"
+ value = "applebough"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lazul
+ name = "Lazul"
+ value = "lazul"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/arwyn
+ name = "Arwyn"
+ value = "arwyn"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/glowtron
+ name = "Glowtron"
+ value = "glowtron"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/cardish
+ name = "Cardish"
+ value = "cardish"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/robold
+ name = "Robold"
+ value = "robold"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/malfurion
+ name = "Malfurion"
+ value = "malfurion"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/deathwhisper
+ name = "Deathwhisper"
+ value = "deathwhisper"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/janna
+ name = "Janna"
+ value = "janna"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/cassiopeia
+ name = "Cassiopeia"
+ value = "cassiopeia"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/taliyah
+ name = "Taliyah"
+ value = "taliyah"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/neeko
+ name = "Neeko"
+ value = "neeko"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/taric
+ name = "Taric"
+ value = "taric"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/akshan
+ name = "Akshan"
+ value = "akshan"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tristana
+ name = "Tristana"
+ value = "tristana"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sylas
+ name = "Sylas"
+ value = "sylas"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sejuani
+ name = "Sejuani"
+ value = "sejuani"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/anivia
+ name = "Anivia"
+ value = "anivia"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vayne
+ name = "Vayne"
+ value = "vayne"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/karma
+ name = "Karma"
+ value = "karma"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nilah
+ name = "Nilah"
+ value = "nilah"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/olaf_lol
+ name = "Olaf_lol"
+ value = "olaf_lol"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/quinn
+ name = "Quinn"
+ value = "quinn"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lissandra
+ name = "Lissandra"
+ value = "lissandra"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hecarim
+ name = "Hecarim"
+ value = "hecarim"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vi
+ name = "Vi"
+ value = "vi"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/zyra
+ name = "Zyra"
+ value = "zyra"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/zac
+ name = "Zac"
+ value = "zac"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/moira
+ name = "Moira"
+ value = "moira"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/ashe
+ name = "Ashe"
+ value = "ashe"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/brigitte
+ name = "Brigitte"
+ value = "brigitte"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mercy
+ name = "Mercy"
+ value = "mercy"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lucio_ov
+ name = "Lucio_ov"
+ value = "lucio_ov"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dva_ov
+ name = "Dva_ov"
+ value = "dva_ov"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/symmetra
+ name = "Symmetra"
+ value = "symmetra"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/zarya
+ name = "Zarya"
+ value = "zarya"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cassidy
+ name = "Cassidy"
+ value = "cassidy"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/baptiste
+ name = "Baptiste"
+ value = "baptiste"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/junker_queen
+ name = "Junker_queen"
+ value = "junker_queen"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/doomfist
+ name = "Doomfist"
+ value = "doomfist"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/pharah
+ name = "Pharah"
+ value = "pharah"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sombra
+ name = "Sombra"
+ value = "sombra"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ana
+ name = "Ana"
+ value = "ana"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/widowmaker
+ name = "Widowmaker"
+ value = "widowmaker"
+ category = TTS_CATEGORY_OVERWATCH
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/harbor
+ name = "Harbor"
+ value = "harbor"
+ category = TTS_CATEGORY_VALORANT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sage
+ name = "Sage"
+ value = "sage"
+ category = TTS_CATEGORY_VALORANT
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/brimstone
+ name = "Brimstone"
+ value = "brimstone"
+ category = TTS_CATEGORY_VALORANT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/sova
+ name = "Sova"
+ value = "sova"
+ category = TTS_CATEGORY_VALORANT
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_shrill
+ name = "F_shrill"
+ value = "f_shrill"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_haughty
+ name = "M_haughty"
+ value = "m_haughty"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_soldier
+ name = "M_soldier"
+ value = "m_soldier"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/sven
+ name = "Sven"
+ value = "sven"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_sultry
+ name = "F_sultry"
+ value = "f_sultry"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/eorlund
+ name = "Eorlund"
+ value = "eorlund"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/m_commander
+ name = "M_commander"
+ value = "m_commander"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_nord
+ name = "F_nord"
+ value = "f_nord"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lydia
+ name = "Lydia"
+ value = "lydia"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/motierre
+ name = "Motierre"
+ value = "motierre"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_haughty
+ name = "F_haughty"
+ value = "f_haughty"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tullius
+ name = "Tullius"
+ value = "tullius"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/festus
+ name = "Festus"
+ value = "festus"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_nord
+ name = "M_nord"
+ value = "m_nord"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/olava
+ name = "Olava"
+ value = "olava"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_commander
+ name = "F_commander"
+ value = "f_commander"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hadvar
+ name = "Hadvar"
+ value = "hadvar"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_argo
+ name = "F_argo"
+ value = "f_argo"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/arngeir
+ name = "Arngeir"
+ value = "arngeir"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nazeem
+ name = "Nazeem"
+ value = "nazeem"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/falion
+ name = "Falion"
+ value = "falion"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_coward
+ name = "F_coward"
+ value = "f_coward"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_guard
+ name = "M_guard"
+ value = "m_guard"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_commoner
+ name = "M_commoner"
+ value = "m_commoner"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/elisif
+ name = "Elisif"
+ value = "elisif"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/paarthurnax
+ name = "Paarthurnax"
+ value = "paarthurnax"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/grelka
+ name = "Grelka"
+ value = "grelka"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_commoner
+ name = "F_commoner"
+ value = "f_commoner"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ebony
+ name = "Ebony"
+ value = "ebony"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ulfric
+ name = "Ulfric"
+ value = "ulfric"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/farengar
+ name = "Farengar"
+ value = "farengar"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/astrid
+ name = "Astrid"
+ value = "astrid"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/brynjolf
+ name = "Brynjolf"
+ value = "brynjolf"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/maven
+ name = "Maven"
+ value = "maven"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/f_child
+ name = "F_child"
+ value = "f_child"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_orc
+ name = "F_orc"
+ value = "f_orc"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/delphine
+ name = "Delphine"
+ value = "delphine"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/f_darkelf
+ name = "F_darkelf"
+ value = "f_darkelf"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/grelod
+ name = "Grelod"
+ value = "grelod"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tolfdir
+ name = "Tolfdir"
+ value = "tolfdir"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/m_bandit
+ name = "M_bandit"
+ value = "m_bandit"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/m_forsworn
+ name = "M_forsworn"
+ value = "m_forsworn"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/karliah
+ name = "Karliah"
+ value = "karliah"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/felldir
+ name = "Felldir"
+ value = "felldir"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ancano
+ name = "Ancano"
+ value = "ancano"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/mercer
+ name = "Mercer"
+ value = "mercer"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vex
+ name = "Vex"
+ value = "vex"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mirabelle
+ name = "Mirabelle"
+ value = "mirabelle"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/aventus
+ name = "Aventus"
+ value = "aventus"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/tsun
+ name = "Tsun"
+ value = "tsun"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/elenwen
+ name = "Elenwen"
+ value = "elenwen"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/gormlaith
+ name = "Gormlaith"
+ value = "gormlaith"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dragon
+ name = "Dragon"
+ value = "dragon"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/overwatch
+ name = "Overwatch"
+ value = "overwatch"
+ category = TTS_CATEGORY_HALFLIFE2
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/zak
+ name = "Zak"
+ value = "zak"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/merc2
+ name = "Merc2"
+ value = "merc2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/forest1
+ name = "Forest1"
+ value = "forest1"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bandit3
+ name = "Bandit3"
+ value = "bandit3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/forest2
+ name = "Forest2"
+ value = "forest2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/merc1
+ name = "Merc1"
+ value = "merc1"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bandit2
+ name = "Bandit2"
+ value = "bandit2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/forest3
+ name = "Forest3"
+ value = "forest3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tribal3
+ name = "Tribal3"
+ value = "tribal3"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/slave2
+ name = "Slave2"
+ value = "slave2"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/miller
+ name = "Miller"
+ value = "miller"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/krest
+ name = "Krest"
+ value = "krest"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tribal1
+ name = "Tribal1"
+ value = "tribal1"
+ category = TTS_CATEGORY_METRO
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/abathur
+ name = "Abathur"
+ value = "abathur"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/erik
+ name = "Erik"
+ value = "erik"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/varian
+ name = "Varian"
+ value = "varian"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/anduin
+ name = "Anduin"
+ value = "anduin"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/deckard
+ name = "Deckard"
+ value = "deckard"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/malfurion_h
+ name = "Malfurion_h"
+ value = "malfurion_h"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/demonhunter
+ name = "Demonhunter"
+ value = "demonhunter"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/demon
+ name = "Demon"
+ value = "demon"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kerrigan_h
+ name = "Kerrigan_h"
+ value = "kerrigan_h"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ladyofthorns
+ name = "Ladyofthorns"
+ value = "ladyofthorns"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/barbarian
+ name = "Barbarian"
+ value = "barbarian"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/crusader
+ name = "Crusader"
+ value = "crusader"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/whitemane
+ name = "Whitemane"
+ value = "whitemane"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/nexushunter
+ name = "Nexushunter"
+ value = "nexushunter"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/greymane_h
+ name = "Greymane_h"
+ value = "greymane_h"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/gardensdayannouncer
+ name = "Gardensdayannouncer"
+ value = "gardensdayannouncer"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/drekthar
+ name = "Drekthar"
+ value = "drekthar"
+ category = TTS_CATEGORY_HEROESOFTHESTORM
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/squeamlish
+ name = "Squeamlish"
+ value = "squeamlish"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dagg
+ name = "Dagg"
+ value = "dagg"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/brukan
+ name = "Brukan"
+ value = "brukan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bolan
+ name = "Bolan"
+ value = "bolan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/goya
+ name = "Goya"
+ value = "goya"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/stargazer
+ name = "Stargazer"
+ value = "stargazer"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/eudora
+ name = "Eudora"
+ value = "eudora"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mozaki
+ name = "Mozaki"
+ value = "mozaki"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/katrana
+ name = "Katrana"
+ value = "katrana"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/valeera_hs
+ name = "Valeera_hs"
+ value = "valeera_hs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/malacrass
+ name = "Malacrass"
+ value = "malacrass"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/elise
+ name = "Elise"
+ value = "elise"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/flark
+ name = "Flark"
+ value = "flark"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rhogi
+ name = "Rhogi"
+ value = "rhogi"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/gallywix
+ name = "Gallywix"
+ value = "gallywix"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/talanji
+ name = "Talanji"
+ value = "talanji"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dr_sezavo
+ name = "Dr_sezavo"
+ value = "dr_sezavo"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/tierra
+ name = "Tierra"
+ value = "tierra"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zenda
+ name = "Zenda"
+ value = "zenda"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/baechao
+ name = "Baechao"
+ value = "baechao"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lilian
+ name = "Lilian"
+ value = "lilian"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/aranna
+ name = "Aranna"
+ value = "aranna"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/oshi
+ name = "Oshi"
+ value = "oshi"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/norroa
+ name = "Norroa"
+ value = "norroa"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/turalyon
+ name = "Turalyon"
+ value = "turalyon"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/aki
+ name = "Aki"
+ value = "aki"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/lunara
+ name = "Lunara"
+ value = "lunara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/bob
+ name = "Bob"
+ value = "bob"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/illucia
+ name = "Illucia"
+ value = "illucia"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/yrel_hs
+ name = "Yrel_hs"
+ value = "yrel_hs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/fireheart
+ name = "Fireheart"
+ value = "fireheart"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/lanathel
+ name = "Lanathel"
+ value = "lanathel"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/tyrande_hs
+ name = "Tyrande_hs"
+ value = "tyrande_hs"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/draemus
+ name = "Draemus"
+ value = "draemus"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/rasil
+ name = "Rasil"
+ value = "rasil"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kalec
+ name = "Kalec"
+ value = "kalec"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/karastamper
+ name = "Karastamper"
+ value = "karastamper"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/george
+ name = "George"
+ value = "george"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/pollark
+ name = "Pollark"
+ value = "pollark"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/stelina
+ name = "Stelina"
+ value = "stelina"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kasa
+ name = "Kasa"
+ value = "kasa"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/whirt
+ name = "Whirt"
+ value = "whirt"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/anarii
+ name = "Anarii"
+ value = "anarii"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ilza
+ name = "Ilza"
+ value = "ilza"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/avozu
+ name = "Avozu"
+ value = "avozu"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 3
+
+/datum/tts_seed/silero/jeklik
+ name = "Jeklik"
+ value = "jeklik"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zibb
+ name = "Zibb"
+ value = "zibb"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/thrud
+ name = "Thrud"
+ value = "thrud"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/isiset
+ name = "Isiset"
+ value = "isiset"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/akazamzarak
+ name = "Akazamzarak"
+ value = "akazamzarak"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/arha
+ name = "Arha"
+ value = "arha"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+
+/datum/tts_seed/silero/aidar
+ name = "Aidar"
+ value = "aidar"
+ category = TTS_CATEGORY_OTHER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/baya
+ name = "Baya"
+ value = "baya"
+ category = TTS_CATEGORY_OTHER
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/kseniya
+ name = "Kseniya"
+ value = "kseniya"
+ category = TTS_CATEGORY_OTHER
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/eugene
+ name = "Eugene"
+ value = "eugene"
+ category = TTS_CATEGORY_OTHER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/senna
+ name = "Senna"
+ value = "senna"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nunu
+ name = "Nunu"
+ value = "nunu"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ryze
+ name = "Ryze"
+ value = "ryze"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/yone
+ name = "Yone"
+ value = "yone"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/sett
+ name = "Sett"
+ value = "sett"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/camille
+ name = "Camille"
+ value = "camille"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lee_sin
+ name = "Lee_sin"
+ value = "lee_sin"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kayle
+ name = "Kayle"
+ value = "kayle"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/azir
+ name = "Azir"
+ value = "azir"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 3
+
+/datum/tts_seed/silero/tryndamere
+ name = "Tryndamere"
+ value = "tryndamere"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nami
+ name = "Nami"
+ value = "nami"
+ category = TTS_CATEGORY_LOL
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/delvin
+ name = "Delvin"
+ value = "delvin"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cicero
+ name = "Cicero"
+ value = "cicero"
+ category = TTS_CATEGORY_SKYRIM
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/linzi
+ name = "Linzi"
+ value = "linzi"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/cache
+ name = "Cache"
+ value = "cache"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cravitz
+ name = "Cravitz"
+ value = "cravitz"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 3
+
+/datum/tts_seed/silero/lady_vashj
+ name = "Lady_vashj"
+ value = "lady_vashj"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dendrologist
+ name = "Dendrologist"
+ value = "dendrologist"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/jythiros
+ name = "Jythiros"
+ value = "jythiros"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/draan
+ name = "Draan"
+ value = "draan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rikkar
+ name = "Rikkar"
+ value = "rikkar"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/splintergraft
+ name = "Splintergraft"
+ value = "splintergraft"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/malchezaar
+ name = "Malchezaar"
+ value = "malchezaar"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/taskmaster
+ name = "Taskmaster"
+ value = "taskmaster"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/oxana
+ name = "Oxana"
+ value = "oxana"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/inara
+ name = "Inara"
+ value = "inara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ivan
+ name = "Ivan"
+ value = "ivan"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kazamon
+ name = "Kazamon"
+ value = "kazamon"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/albin
+ name = "Albin"
+ value = "albin"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ammunae
+ name = "Ammunae"
+ value = "ammunae"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/illidara
+ name = "Illidara"
+ value = "illidara"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 2
+
+/datum/tts_seed/silero/nici
+ name = "Nici"
+ value = "nici"
+ category = TTS_CATEGORY_HEARTHSTONE
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/byasha
+ name = "Byasha"
+ value = "byasha"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cerys
+ name = "Cerys"
+ value = "cerys"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/philippa
+ name = "Philippa"
+ value = "philippa"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/oldnekro
+ name = "Oldnekro"
+ value = "oldnekro"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lambert
+ name = "Lambert"
+ value = "lambert"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/shani
+ name = "Shani"
+ value = "shani"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/anton
+ name = "Anton"
+ value = "anton"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dolg1
+ name = "Dolg1"
+ value = "dolg1"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/guru
+ name = "Guru"
+ value = "guru"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lugos
+ name = "Lugos"
+ value = "lugos"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/karina
+ name = "Karina"
+ value = "karina"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/ewald
+ name = "Ewald"
+ value = "ewald"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/mirror
+ name = "Mirror"
+ value = "mirror"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/noble
+ name = "Noble"
+ value = "noble"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/huber
+ name = "Huber"
+ value = "huber"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/wywern
+ name = "Wywern"
+ value = "wywern"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/avallach
+ name = "Avallach"
+ value = "avallach"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/semen
+ name = "Semen"
+ value = "semen"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/all_elder
+ name = "All_elder"
+ value = "all_elder"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nsheriff
+ name = "Nsheriff"
+ value = "nsheriff"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/orcc
+ name = "Orcc"
+ value = "orcc"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/clerk
+ name = "Clerk"
+ value = "clerk"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/witch
+ name = "Witch"
+ value = "witch"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/deva
+ name = "Deva"
+ value = "deva"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/coach
+ name = "Coach"
+ value = "coach"
+ category = TTS_CATEGORY_LEFT4DEAD
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dictor
+ name = "Dictor"
+ value = "dictor"
+ category = TTS_CATEGORY_PORTAL2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/monolith2
+ name = "Monolith2"
+ value = "monolith2"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/invoker
+ name = "Invoker"
+ value = "invoker"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/goblin
+ name = "Goblin"
+ value = "goblin"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/annah
+ name = "Annah"
+ value = "annah"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/patrick
+ name = "Patrick"
+ value = "patrick"
+ category = TTS_CATEGORY_SPONGEBOB
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/spongebob
+ name = "Spongebob"
+ value = "spongebob"
+ category = TTS_CATEGORY_SPONGEBOB
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/kapitan
+ name = "Kapitan"
+ value = "kapitan"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/karh
+ name = "Karh"
+ value = "karh"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/lydia_tb
+ name = "Lydia_tb"
+ value = "lydia_tb"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/silencer
+ name = "Silencer"
+ value = "silencer"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/sheriff
+ name = "Sheriff"
+ value = "sheriff"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/lycan
+ name = "Lycan"
+ value = "lycan"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/cirilla
+ name = "Cirilla"
+ value = "cirilla"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/legends
+ name = "Legends"
+ value = "legends"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/monolith1
+ name = "Monolith1"
+ value = "monolith1"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/trapper
+ name = "Trapper"
+ value = "trapper"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/mirana
+ name = "Mirana"
+ value = "mirana"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/glav
+ name = "Glav"
+ value = "glav"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/syanna
+ name = "Syanna"
+ value = "syanna"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/regis
+ name = "Regis"
+ value = "regis"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dazzle
+ name = "Dazzle"
+ value = "dazzle"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/mthief
+ name = "Mthief"
+ value = "mthief"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/guillaume
+ name = "Guillaume"
+ value = "guillaume"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/vivienne
+ name = "Vivienne"
+ value = "vivienne"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/plankton
+ name = "Plankton"
+ value = "plankton"
+ category = TTS_CATEGORY_SPONGEBOB
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/rochelle
+ name = "Rochelle"
+ value = "rochelle"
+ category = TTS_CATEGORY_LEFT4DEAD
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/vor
+ name = "Vor"
+ value = "vor"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/grandmother
+ name = "Grandmother"
+ value = "grandmother"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dolg2
+ name = "Dolg2"
+ value = "dolg2"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/junboy
+ name = "Junboy"
+ value = "junboy"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/shopper
+ name = "Shopper"
+ value = "shopper"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/papillon
+ name = "Papillon"
+ value = "papillon"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/cm
+ name = "Cm"
+ value = "cm"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/vesemir
+ name = "Vesemir"
+ value = "vesemir"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/kate
+ name = "Kate"
+ value = "kate"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/polina
+ name = "Polina"
+ value = "polina"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/crach
+ name = "Crach"
+ value = "crach"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/gryphon
+ name = "Gryphon"
+ value = "gryphon"
+ category = TTS_CATEGORY_WARCRAFT3
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/zeus
+ name = "Zeus"
+ value = "zeus"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/iz
+ name = "Iz"
+ value = "iz"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/geralt
+ name = "Geralt"
+ value = "geralt"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/stories
+ name = "Stories"
+ value = "stories"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nekro
+ name = "Nekro"
+ value = "nekro"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/hwleader
+ name = "Hwleader"
+ value = "hwleader"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/yennefer
+ name = "Yennefer"
+ value = "yennefer"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hero
+ name = "Hero"
+ value = "hero"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/baratrum
+ name = "Baratrum"
+ value = "baratrum"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ellis
+ name = "Ellis"
+ value = "ellis"
+ category = TTS_CATEGORY_LEFT4DEAD
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/udalryk
+ name = "Udalryk"
+ value = "udalryk"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/dad
+ name = "Dad"
+ value = "dad"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/smith
+ name = "Smith"
+ value = "smith"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/romka
+ name = "Romka"
+ value = "romka"
+ category = TTS_CATEGORY_TINYBUNNY
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/abaddon
+ name = "Abaddon"
+ value = "abaddon"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/eskel
+ name = "Eskel"
+ value = "eskel"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/freedom
+ name = "Freedom"
+ value = "freedom"
+ category = TTS_CATEGORY_STALKER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/magess
+ name = "Magess"
+ value = "magess"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_ANY
+ required_donator_level = 1
+
+/datum/tts_seed/silero/nalo
+ name = "Nalo"
+ value = "nalo"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/dandelion
+ name = "Dandelion"
+ value = "dandelion"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/palmerin
+ name = "Palmerin"
+ value = "palmerin"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/olgierd
+ name = "Olgierd"
+ value = "olgierd"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/d_sven
+ name = "D_sven"
+ value = "d_sven"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 3
+
+/datum/tts_seed/silero/triss
+ name = "Triss"
+ value = "triss"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_FEMALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/monkey
+ name = "Monkey"
+ value = "monkey"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/squidward
+ name = "Squidward"
+ value = "squidward"
+ category = TTS_CATEGORY_SPONGEBOB
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ember
+ name = "Ember"
+ value = "ember"
+ category = TTS_CATEGORY_DOTA2
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/ycf
+ name = "Ycf"
+ value = "ycf"
+ category = TTS_CATEGORY_EVILISLANDS
+ gender = TTS_GENDER_MALE
+ required_donator_level = 2
+
+/datum/tts_seed/silero/nick
+ name = "Nick"
+ value = "nick"
+ category = TTS_CATEGORY_LEFT4DEAD
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/hjalmar
+ name = "Hjalmar"
+ value = "hjalmar"
+ category = TTS_CATEGORY_WITCHER
+ gender = TTS_GENDER_MALE
+ required_donator_level = 1
+
+/datum/tts_seed/silero/gale
+ name = "Gale"
+ value = "en_Gale"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/jaheira
+ name = "Jaheira"
+ value = "en_Jaheira"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/laezel
+ name = "Laezel"
+ value = "en_Laezel"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/karlach
+ name = "Karlach"
+ value = "en_Karlach"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/shadowheart
+ name = "Shadowheart"
+ value = "en_Shadowheart"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/wyll
+ name = "Wyll"
+ value = "en_Wyll"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/minthara
+ name = "Minthara"
+ value = "en_Minthara"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/minsc
+ name = "Minsc"
+ value = "en_Minsc"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/astarion
+ name = "Astarion"
+ value = "en_Astarion"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/halsin
+ name = "Halsin"
+ value = "en_Halsin"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/emperor_bg3
+ name = "Mind_Flayer_Emperor"
+ value = "en_Emperor"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/ketheric
+ name = "Ketheric"
+ value = "en_Ketheric"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gortash
+ name = "Gortash"
+ value = "en_Gortash"
+ category = TTS_CATEGORY_BALDURS_GATE_3
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/cave_johnson
+ name = "Cave_Johnson"
+ value = "portal_cave_johnson"
+ category = TTS_CATEGORY_PORTAL
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/shredder
+ name = "Shredder"
+ value = "Ninja_Turtles_shredder"
+ category = TTS_CATEGORY_TMNT
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/han_solo
+ name = "Han_Solo"
+ value = "Star_Wars_Han_Solo"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/darth_sidious
+ name = "Darth_Sidious"
+ value = "Star_Wars_Darth_Sidious"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/luke_skywalker
+ name = "Luke_Skywalker"
+ value = "Star_Wars_Luke_Skywalker"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/yoda
+ name = "Yoda"
+ value = "Star_Wars_Yoda"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/darth_vader
+ name = "Darth_Vader"
+ value = "Star_Wars_Darth_Vader"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/obiwan_kenobi
+ name = "Obi-wan_Kenobi"
+ value = "Star_Wars_Obi-Wan_Kenobi"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/anakin_skywalker
+ name = "Anakin_Skywalker"
+ value = "Star_Wars_Anakin_Skywalker"
+ category = TTS_CATEGORY_STAR_WARS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/optimus_prime
+ name = "Optimus_Prime"
+ value = "Transformers_War_of_Cybertron_optimusprime"
+ category = TTS_CATEGORY_TRANSFORMERS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/megatron
+ name = "Megatron"
+ value = "Transformers_War_of_Cybertron_megatron"
+ category = TTS_CATEGORY_TRANSFORMERS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/soundwave
+ name = "Soundwave"
+ value = "Transformers_War_of_Cybertron_soundwave"
+ category = TTS_CATEGORY_TRANSFORMERS
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/aragorn
+ name = "Aragorn"
+ value = "The_Lord_of_the_Rings_Aragorn"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/elrond
+ name = "Elrond"
+ value = "The_Lord_of_the_Rings_Elrond"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gandalf
+ name = "Gandalf"
+ value = "The_Lord_of_the_Rings_Gandalf"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gimli
+ name = "Gimli"
+ value = "The_Lord_of_the_Rings_Gimli"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gollum
+ name = "Gollum"
+ value = "The_Lord_of_the_Rings_Gollum"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/legolas
+ name = "Legolas"
+ value = "The_Lord_of_the_Rings_Legolas"
+ category = TTS_CATEGORY_LOTR
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/gingerbread_man
+ name = "Gingerbread_Man"
+ value = "Srek_Gingerbread_Man"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/fiona
+ name = "Fiona"
+ value = "Srek_Fiona"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/donkey
+ name = "Donkey"
+ value = "Srek_Donkey"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/fairy_godmother
+ name = "Fairy_Godmother"
+ value = "Srek_Fairy_Godmother"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/srek_king
+ name = "King"
+ value = "Srek_King"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/srek_narrator
+ name = "Shrek_Narrator"
+ value = "Srek_Narrator"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/puss_in_boots
+ name = "Puss_in_Boots"
+ value = "Srek_Puss_in_Boots"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/shrek
+ name = "Shrek"
+ value = "Srek_Shrek"
+ category = TTS_CATEGORY_SHREK
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/jack_sparrow
+ name = "Jack_Sparrow"
+ value = "Pirats_of_the_caribbean_Jack_Sparrow"
+ category = TTS_CATEGORY_POTC
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/barbossa
+ name = "Barbossa"
+ value = "Pirats_of_the_caribbean_Barbossa"
+ category = TTS_CATEGORY_POTC
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/tiadalma
+ name = "Tiadalma"
+ value = "Pirats_of_the_caribbean_Tiadalma"
+ category = TTS_CATEGORY_POTC
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/davy_jones
+ name = "Davy_Jones"
+ value = "Pirats_of_the_caribbean_Davy_Jones"
+ category = TTS_CATEGORY_POTC
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sirius_black
+ name = "Sirius_Black"
+ value = "Harry_Potter_Sirius_Black"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/dobby
+ name = "Dobby"
+ value = "Harry_Potter_Dobby"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/severus_snape_film
+ name = "Severus_snape_film"
+ value = "Harry_Potter_Severus_Snape_film"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/harry_potter
+ name = "Harry_Potter"
+ value = "Harry_Potter_Harry_Potter"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/dumbledore
+ name = "Albus_Dumbledore"
+ value = "Harry_Potter_Albus_Dumbledore"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/voldemort
+ name = "Voldemort"
+ value = "Harry_Potter_Lord_Voldemort"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/severus_snape
+ name = "Severus_Snape"
+ value = "Harry_Potter_Severus_Snape"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/flitwick
+ name = "Filius_Flitwick"
+ value = "Harry_Potter_Filius_Flitwick"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/minerva_mcgonagall
+ name = "Minnerva_McGonagall"
+ value = "Harry_Potter_Minerva_McGonagall"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/horace_slughorn
+ name = "Horace_Slughorn"
+ value = "Harry_Potter_Horace_Slughorn"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/cedric
+ name = "Cedric"
+ value = "Harry_Potter_Cedric"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/alastor
+ name = "Alastor_Mad-eye_Moody"
+ value = "Harry_Potter_Alastor_Mad-Eye_Moody"
+ category = TTS_CATEGORY_HARRY_POTTER
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/x3_betty
+ name = "Betty"
+ value = "X3_reunion_Betty"
+ category = TTS_CATEGORY_X3
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/overlord_gnarl
+ name = "Gnarl"
+ value = "Overlord_2_Gnarl"
+ category = TTS_CATEGORY_OVERLORD2
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/tony_stark
+ name = "Tony_Stark"
+ value = "Marvel_Tony_Stark"
+ category = TTS_CATEGORY_MARVEL
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sabellian
+ name = "Sabellian"
+ value = "Dragons_Sabellian"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/ysera
+ name = "Ysera"
+ value = "Dragons_Ysera"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/malygos_wotlk
+ name = "Malygos_wotlk"
+ value = "Dragons_MalygosWrath_of_the_Lich_King"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/ebyssian
+ name = "Ebyssian"
+ value = "Dragons_Ebyssian"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/deathwing
+ name = "Deathwing"
+ value = "Dragons_Deathwing"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/nozdormu
+ name = "Nozdormu"
+ value = "Dragons_Nozdormu"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/malygos
+ name = "Malygos"
+ value = "Dragons_Malygos"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/calderax
+ name = "Calderax"
+ value = "Draconids_Calderax"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/bazentus
+ name = "Bazentus"
+ value = "Draconids_Bazentus"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/kazra
+ name = "Kazra"
+ value = "Draconids_Kazra"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/seltherex
+ name = "Seltherex"
+ value = "Draconids_Seltherex"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/sendrax
+ name = "Sendrax"
+ value = "Draconids_Sendrax"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/evantkis
+ name = "Evantkis"
+ value = "Draconids_Evantkis"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/drine
+ name = "Drine"
+ value = "Draconids_Drine"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/lethanak
+ name = "Lethanak"
+ value = "Draconids_Lethanak"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/wrathion_echo
+ name = "Wrathion_echo"
+ value = "Dragons2_Wrathion_echo"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/alexstraza
+ name = "Alexstraza"
+ value = "Dragons2_Alexstraza"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/kalecgos
+ name = "Kalecgos"
+ value = "Dragons2_Kalecgos"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/wrathion
+ name = "Wrathion"
+ value = "Dragons2_Wrathion"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/kalechos_echo
+ name = "Kalecgos_echo"
+ value = "Dragons2_Kalecgos_echo"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/alextraza_echo
+ name = "Alextraza_echo"
+ value = "Dragons2_Alextraza_echo"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/neltharion_echo
+ name = "Neltharion_echo"
+ value = "Dragons2_Neltharion_echo"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/wrathion_deathwing
+ name = "Wration_Deathwing"
+ value = "Dragons2_Wrathion_Deathwing"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/neltharion
+ name = "Neltharion"
+ value = "Dragons2_Neltharion"
+ category = TTS_CATEGORY_WOW
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/livsy
+ name = "Livsy"
+ value = "Treasure_Island_Livsy"
+ category = TTS_CATEGORY_TREASURE_ISLAND
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sp_brother
+ name = "sp_brother"
+ value = "slovo_patsana_brother"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sp_koschei
+ name = "sp_koschei"
+ value = "slovo_patsana_koschei"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sp_marat
+ name = "sp_marat"
+ value = "slovo_patsana_marat"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sp_angry_cop
+ name = "sp_angry_cop"
+ value = "slovo_patsana_angry_cop"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_MALE
+
+/datum/tts_seed/silero/sp_cop
+ name = "sp_cop"
+ value = "slovo_patsana_cop"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_FEMALE
+
+/datum/tts_seed/silero/sp_main
+ name = "sp_main"
+ value = "slovo_patsana_main"
+ category = TTS_CATEGORY_BOYS_WORD
+ gender = TTS_GENDER_MALE
diff --git a/modular_bandastation/tts/code/shell.dm b/modular_bandastation/tts/code/shell.dm
new file mode 100644
index 0000000000000..fb6ffeb98da39
--- /dev/null
+++ b/modular_bandastation/tts/code/shell.dm
@@ -0,0 +1,61 @@
+#define SHELLEO_ERRORLEVEL 1
+#define SHELLEO_STDOUT 2
+#define SHELLEO_STDERR 3
+
+#define SHELLEO_NAME "data/shelleo."
+#define SHELLEO_ERR ".err"
+#define SHELLEO_OUT ".out"
+
+/proc/apply_sound_effect(datum/singleton/sound_effect/effect, filename_input, filename_output)
+ if(!effect)
+ CRASH("Invalid sound effect chosen.")
+
+ var/taskset
+ CONFIG_GET(string/ffmpeg_cpuaffinity)
+ if(CONFIG_GET(string/ffmpeg_cpuaffinity))
+ taskset = "taskset -ac [CONFIG_GET(string/ffmpeg_cpuaffinity)]"
+
+ var/command = {"[taskset] ffmpeg -y -hide_banner -loglevel error -i [filename_input] -filter:a "[effect.ffmpeg_arguments]" [filename_output]"}
+ var/list/output = world.shelleo(command)
+
+ var/errorlevel = output[SHELLEO_ERRORLEVEL]
+ var/stdout = output[SHELLEO_STDOUT]
+ var/stderr = output[SHELLEO_STDERR]
+ if(errorlevel)
+ log_runtime("Error: apply_sound_effect([effect.suffix], [filename_input], [filename_output]) - See debug logs.")
+ logger.Log(LOG_CATEGORY_DEBUG, "apply_sound_effect([effect.suffix], [filename_input], [filename_output]) STDOUT: [stdout]")
+ logger.Log(LOG_CATEGORY_DEBUG, "apply_sound_effect([effect.suffix], [filename_input], [filename_output]) STDERR: [stderr]")
+ return FALSE
+ return TRUE
+
+/datum/singleton/sound_effect
+ var/suffix
+ var/ffmpeg_arguments
+
+/datum/singleton/sound_effect/radio
+ suffix = "_radio"
+ ffmpeg_arguments = "highpass=f=1000, lowpass=f=3000, acrusher=1:1:50:0:log"
+
+/datum/singleton/sound_effect/robot
+ suffix = "_robot"
+ ffmpeg_arguments = "afftfilt=real='hypot(re,im)*sin(0)':imag='hypot(re,im)*cos(0)':win_size=1024:overlap=0.5, deesser=i=0.4, volume=volume=1.5"
+
+/datum/singleton/sound_effect/radio_robot
+ suffix = "_radio_robot"
+ ffmpeg_arguments = "afftfilt=real='hypot(re,im)*sin(0)':imag='hypot(re,im)*cos(0)':win_size=1024:overlap=0.5, deesser=i=0.4, volume=volume=1.5, highpass=f=1000, lowpass=f=3000, acrusher=1:1:50:0:log"
+
+/datum/singleton/sound_effect/megaphone
+ suffix = "_megaphone"
+ ffmpeg_arguments = "highpass=f=500, lowpass=f=4000, volume=volume=10, acrusher=1:1:45:0:log"
+
+/datum/singleton/sound_effect/megaphone_robot
+ suffix = "_megaphone_robot"
+ ffmpeg_arguments = "afftfilt=real='hypot(re,im)*sin(0)':imag='hypot(re,im)*cos(0)':win_size=1024:overlap=0.5, deesser=i=0.4, highpass=f=500, lowpass=f=4000, volume=volume=10, acrusher=1:1:45:0:log"
+
+#undef SHELLEO_ERRORLEVEL
+#undef SHELLEO_STDOUT
+#undef SHELLEO_STDERR
+
+#undef SHELLEO_NAME
+#undef SHELLEO_ERR
+#undef SHELLEO_OUT
diff --git a/modular_bandastation/tts/code/sound/radio_chatter.ogg b/modular_bandastation/tts/code/sound/radio_chatter.ogg
new file mode 100644
index 0000000000000..6e5b3ecfbfe24
Binary files /dev/null and b/modular_bandastation/tts/code/sound/radio_chatter.ogg differ
diff --git a/modular_bandastation/tts/code/tts_component.dm b/modular_bandastation/tts/code/tts_component.dm
new file mode 100644
index 0000000000000..28edbfdd6d17a
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_component.dm
@@ -0,0 +1,158 @@
+/datum/component/tts_component
+ var/datum/tts_seed/tts_seed
+ var/list/traits = list()
+
+/datum/component/tts_component/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_TTS_SEED_CHANGE, PROC_REF(tts_seed_change))
+ RegisterSignal(parent, COMSIG_ATOM_TTS_CAST, PROC_REF(cast_tts))
+ RegisterSignal(parent, COMSIG_ATOM_TTS_TRAIT_ADD, PROC_REF(tts_trait_add))
+ RegisterSignal(parent, COMSIG_ATOM_TTS_TRAIT_REMOVE, PROC_REF(tts_trait_remove))
+
+/datum/component/tts_component/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ATOM_TTS_SEED_CHANGE)
+ UnregisterSignal(parent, COMSIG_ATOM_TTS_CAST)
+ UnregisterSignal(parent, COMSIG_ATOM_TTS_TRAIT_ADD)
+ UnregisterSignal(parent, COMSIG_ATOM_TTS_TRAIT_REMOVE)
+
+/datum/component/tts_component/Initialize(datum/tts_seed/new_tts_seed, ...)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(ispath(new_tts_seed) && SStts220.tts_seeds[initial(new_tts_seed.name)])
+ new_tts_seed = SStts220.tts_seeds[initial(new_tts_seed.name)]
+ if(istype(new_tts_seed))
+ tts_seed = new_tts_seed
+ if(!tts_seed)
+ tts_seed = get_random_tts_seed_by_gender()
+ if(!tts_seed) // Something went terribly wrong
+ return COMPONENT_INCOMPATIBLE
+ if(length(args) > 1)
+ for(var/trait in 2 to length(args))
+ traits += args[trait]
+
+/datum/component/tts_component/proc/return_tts_seed()
+ SIGNAL_HANDLER
+ return tts_seed
+
+/datum/component/tts_component/proc/select_tts_seed(mob/chooser, silent_target = FALSE, override = FALSE, list/new_traits = null)
+ if(!chooser)
+ if(ismob(parent))
+ chooser = parent
+ else
+ return null
+
+ var/atom/being_changed = parent
+ var/static/tts_test_str = "Так звучит мой голос."
+ var/datum/tts_seed/new_tts_seed
+
+ if(chooser == being_changed)
+ var/datum/preferences/prefs = chooser.client.prefs
+ var/prefs_tts_seed = prefs?.read_preference(/datum/preference/text/tts_seed)
+ if(being_changed.gender == prefs?.read_preference(/datum/preference/choiced/gender))
+ if(tgui_alert(chooser, "Оставляем голос вашего персонажа [prefs?.read_preference(/datum/preference/name/real_name)] - [prefs_tts_seed]?", "Выбор голоса", "Нет", "Да") == "Да")
+ if(!SStts220.tts_seeds[prefs_tts_seed])
+ to_chat(chooser, span_warning("Отсутствует tts_seed для значения \"[prefs_tts_seed]\". Текущий голос - [tts_seed]"))
+ return null
+ new_tts_seed = SStts220.tts_seeds[prefs_tts_seed]
+ if(new_traits)
+ traits = new_traits
+ INVOKE_ASYNC(SStts220, TYPE_PROC_REF(/datum/controller/subsystem/tts220, get_tts), null, chooser, tts_test_str, new_tts_seed, FALSE, get_effect())
+ return new_tts_seed
+
+ var/tts_seeds
+ var/list/tts_seeds_by_gender = SStts220.get_tts_by_gender(being_changed.gender)
+ if(!length(tts_seeds_by_gender))
+ to_chat(chooser, span_warning("Не удалось найти пол для голоса! Текущий голос - [tts_seed.name]"))
+ return null
+ if(check_rights(R_ADMIN, FALSE, chooser) || override || !ismob(being_changed))
+ tts_seeds = tts_seeds_by_gender
+ else
+ tts_seeds = tts_seeds_by_gender && SStts220.get_available_seeds(being_changed) // && for lists means intersection
+
+ var/new_tts_seed_key
+ new_tts_seed_key = tgui_input_list(chooser, "Выберите голос персонажа", "Преобразуем голос", tts_seeds, tts_seed.name)
+ if(!new_tts_seed_key || !SStts220.tts_seeds[new_tts_seed_key])
+ to_chat(chooser, span_warning("Что-то пошло не так с выбором голоса. Текущий голос - [tts_seed.name]"))
+ return null
+
+ new_tts_seed = SStts220.tts_seeds[new_tts_seed_key]
+ if(new_traits)
+ traits = new_traits
+
+ if(!silent_target && being_changed != chooser && ismob(being_changed))
+ INVOKE_ASYNC(SStts220, TYPE_PROC_REF(/datum/controller/subsystem/tts220, get_tts), null, being_changed, tts_test_str, new_tts_seed, FALSE, get_effect())
+
+ if(chooser)
+ INVOKE_ASYNC(SStts220, TYPE_PROC_REF(/datum/controller/subsystem/tts220, get_tts), null, chooser, tts_test_str, new_tts_seed, FALSE, get_effect())
+
+ return new_tts_seed
+
+/datum/component/tts_component/proc/tts_seed_change(atom/being_changed, mob/chooser, override = FALSE, list/new_traits = null)
+ set waitfor = FALSE
+ var/datum/tts_seed/new_tts_seed = select_tts_seed(chooser = chooser, override = override, new_traits = new_traits)
+ if(!new_tts_seed)
+ return null
+ tts_seed = new_tts_seed
+
+/datum/component/tts_component/proc/get_random_tts_seed_by_gender()
+ var/atom/being_changed = parent
+ var/tts_choice = SStts220.pick_tts_seed_by_gender(being_changed.gender)
+ var/datum/tts_seed/seed = SStts220.tts_seeds[tts_choice]
+ if(!seed)
+ return null
+ return seed
+
+/datum/component/tts_component/proc/get_effect(effect)
+ . = effect
+ switch(.)
+ if(null)
+ if(TTS_TRAIT_ROBOTIZE in traits)
+ return /datum/singleton/sound_effect/robot
+ if(/datum/singleton/sound_effect/radio)
+ if(TTS_TRAIT_ROBOTIZE in traits)
+ return /datum/singleton/sound_effect/radio_robot
+ if(/datum/singleton/sound_effect/megaphone)
+ if(TTS_TRAIT_ROBOTIZE in traits)
+ return /datum/singleton/sound_effect/megaphone_robot
+ return .
+
+/datum/component/tts_component/proc/cast_tts(atom/speaker, mob/listener, message, atom/location, is_local = TRUE, effect = null, traits = TTS_TRAIT_RATE_FASTER, preSFX, postSFX)
+ SIGNAL_HANDLER
+
+ if(!message)
+ return
+ var/datum/preferences/prefs = listener?.client?.prefs
+ if(prefs?.read_preference(/datum/preference/choiced/sound_tts) != TTS_SOUND_ENABLED || prefs?.read_preference(/datum/preference/numeric/sound_tts_volume) == 0)
+ return
+ if(HAS_TRAIT(listener, TRAIT_DEAF))
+ return
+ if(!speaker)
+ speaker = parent
+ if(!location)
+ location = parent
+ if(effect == /datum/singleton/sound_effect/radio)
+ if(listener == speaker && !issilicon(parent)) // don't hear both radio and whisper from yourself
+ return
+
+ effect = get_effect(effect)
+
+ INVOKE_ASYNC(SStts220, TYPE_PROC_REF(/datum/controller/subsystem/tts220, get_tts), location, listener, message, tts_seed, is_local, effect, traits, preSFX, postSFX)
+
+/datum/component/tts_component/proc/tts_trait_add(atom/user, trait)
+ SIGNAL_HANDLER
+
+ if(!isnull(trait) && !(trait in traits))
+ traits += trait
+
+/datum/component/tts_component/proc/tts_trait_remove(atom/user, trait)
+ SIGNAL_HANDLER
+
+ if(!isnull(trait) && (trait in traits))
+ traits -= trait
+
+// Component usage
+
+/mob/living/silicon/verb/synth_change_voice()
+ set name = "Смена голоса"
+ set desc = "Express yourself!"
+ set category = "Silicon Commands"
+ change_tts_seed(src, new_traits = list(TTS_TRAIT_ROBOTIZE))
diff --git a/modular_bandastation/tts/code/tts_configuration.dm b/modular_bandastation/tts/code/tts_configuration.dm
new file mode 100644
index 0000000000000..5d199f9ccc0ef
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_configuration.dm
@@ -0,0 +1,19 @@
+/datum/config_entry/flag/tts_enabled
+ default = FALSE
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+/datum/config_entry/string/tts_token_silero
+ default = ""
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+/datum/config_entry/flag/tts_cache_enabled
+ default = FALSE
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+/datum/config_entry/string/ffmpeg_cpuaffinity
+ default = ""
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
+
+/datum/config_entry/string/tts_api_url_silero
+ default = ""
+ protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
diff --git a/modular_bandastation/tts/code/tts_preferences.dm b/modular_bandastation/tts/code/tts_preferences.dm
new file mode 100644
index 0000000000000..e747d7a18b1f3
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_preferences.dm
@@ -0,0 +1,55 @@
+/datum/preferences/ui_static_data(mob/user)
+ var/list/data = ..()
+ data["tts_enabled"] = CONFIG_GET(flag/tts_enabled)
+ var/list/providers = list()
+ for(var/_provider in SStts220.tts_providers)
+ var/datum/tts_provider/provider = SStts220.tts_providers[_provider]
+ providers += list(list(
+ "name" = provider.name,
+ "is_enabled" = provider.is_enabled,
+ ))
+ data["providers"] = providers
+
+ var/list/seeds = list()
+ for(var/_seed in SStts220.tts_seeds)
+ var/datum/tts_seed/seed = SStts220.tts_seeds[_seed]
+ seeds += list(list(
+ "name" = seed.name,
+ "value" = seed.value,
+ "category" = seed.category,
+ "gender" = seed.gender,
+ "provider" = initial(seed.provider.name),
+ "donator_level" = seed.required_donator_level,
+ ))
+ data["seeds"] = seeds
+ data["phrases"] = TTS_PHRASES
+ return data
+
+/datum/preferences/ui_data(mob/user)
+ var/list/data = ..()
+ data["tts_seed"] = read_preference(/datum/preference/text/tts_seed)
+ return data
+
+/datum/preferences/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("listen")
+ var/phrase = params["phrase"]
+ var/seed_name = params["seed"]
+ if((phrase in TTS_PHRASES) && (seed_name in SStts220.tts_seeds))
+ INVOKE_ASYNC(SStts220, TYPE_PROC_REF(/datum/controller/subsystem/tts220, get_tts), null, usr, phrase, SStts220.tts_seeds[seed_name], FALSE)
+ return FALSE
+ if("select_voice")
+ var/seed_name = params["seed"]
+ if(!isnull(seed_name) && (seed_name in SStts220.tts_seeds))
+ write_preference(GLOB.preference_entries[/datum/preference/text/tts_seed], seed_name)
+ return TRUE
+
+/datum/preference/text/tts_seed
+ savefile_key = "tts_seed"
+ savefile_identifier = PREFERENCE_CHARACTER
+
+/datum/preference/text/tts_seed/apply_to_human(mob/living/carbon/human/target, value)
+ target.AddComponent(/datum/component/tts_component, SStts220.tts_seeds[value])
diff --git a/modular_bandastation/tts/code/tts_provider.dm b/modular_bandastation/tts/code/tts_provider.dm
new file mode 100644
index 0000000000000..65d2e484fdbc3
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_provider.dm
@@ -0,0 +1,32 @@
+/datum/tts_provider
+ var/name = "STUB"
+ var/is_enabled = TRUE
+ var/api_url
+
+ var/is_throttled = FALSE
+ var/throttled_until = 0
+
+ var/timed_out_requests = 0
+ var/failed_requests = 0
+ var/failed_requests_limit = 10
+
+/datum/tts_provider/proc/request(text, datum/tts_seed/seed, datum/callback/proc_callback)
+ return TRUE
+
+/datum/tts_provider/proc/process_response(list/response)
+ return null
+
+/datum/tts_provider/proc/throttle_check()
+ if(is_throttled && throttled_until < world.time)
+ return TRUE
+ is_throttled = FALSE
+ return FALSE
+
+/datum/tts_provider/proc/pitch_whisper(text)
+ return text
+
+/datum/tts_provider/proc/rate_faster(text)
+ return text
+
+/datum/tts_provider/proc/rate_medium(text)
+ return text
diff --git a/modular_bandastation/tts/code/tts_seed.dm b/modular_bandastation/tts/code/tts_seed.dm
new file mode 100644
index 0000000000000..23c005e0a660b
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_seed.dm
@@ -0,0 +1,42 @@
+/datum/dna
+ var/datum/tts_seed/tts_seed_dna
+
+/datum/dna/transfer_identity(mob/living/carbon/destination, transfer_SE, transfer_species)
+ if(!istype(destination))
+ return
+ . = ..()
+ destination.dna.tts_seed_dna = tts_seed_dna
+ destination.AddComponent(/datum/component/tts_component, tts_seed_dna)
+
+/datum/dna/copy_dna(datum/dna/new_dna)
+ . = ..()
+ new_dna.tts_seed_dna = tts_seed_dna
+
+/atom/proc/add_tts_component()
+ return
+
+/atom/Initialize(mapload, ...)
+ . = ..()
+ add_tts_component()
+
+/atom/proc/cast_tts(mob/listener, message, atom/location, is_local = TRUE, effect = null, traits = TTS_TRAIT_RATE_FASTER, preSFX, postSFX)
+ SEND_SIGNAL(src, COMSIG_ATOM_TTS_CAST, listener, message, location, is_local, effect, traits, preSFX, postSFX)
+
+// TODO: Do it better?
+/atom/proc/get_tts_seed()
+ var/datum/component/tts_component/tts_component = GetComponent(/datum/component/tts_component)
+ if(tts_component)
+ return tts_component.tts_seed
+
+/atom/proc/change_tts_seed(mob/chooser, override, list/new_traits = null)
+ if(!get_tts_seed())
+ if(alert(chooser, "Отсутствует TTS компонент. Создать?", "Изменение TTS", "Да", "Нет") == "Нет")
+ return
+ AddComponent(/datum/component/tts_component, /datum/tts_seed/silero/angel)
+ SEND_SIGNAL(src, COMSIG_ATOM_TTS_SEED_CHANGE, chooser, override, new_traits)
+
+/atom/proc/tts_trait_add(trait)
+ SEND_SIGNAL(src, COMSIG_ATOM_TTS_TRAIT_ADD, trait)
+
+/atom/proc/tts_trait_remove(trait)
+ SEND_SIGNAL(src, COMSIG_ATOM_TTS_TRAIT_REMOVE, trait)
diff --git a/modular_bandastation/tts/code/tts_subsystem.dm b/modular_bandastation/tts/code/tts_subsystem.dm
new file mode 100644
index 0000000000000..df429adbe378e
--- /dev/null
+++ b/modular_bandastation/tts/code/tts_subsystem.dm
@@ -0,0 +1,490 @@
+#define TTS_REPLACEMENTS_FILE_PATH "config/bandastation/tts_replacements.json"
+#define TTS_ACRONYM_REPLACEMENTS "tts_acronym_replacements"
+#define TTS_JOB_REPLACEMENTS "tts_job_replacements"
+
+#define FILE_CLEANUP_DELAY 30 SECONDS
+
+SUBSYSTEM_DEF(tts220)
+ name = "Text-to-Speech 220"
+ init_order = INIT_ORDER_DEFAULT
+ wait = 0.5 SECONDS
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+
+ /// All time tts uses
+ VAR_PRIVATE/tts_wanted = 0
+ /// Amount of errored requests to providers
+ VAR_PRIVATE/tts_request_failed = 0
+ /// Amount of successfull requests to providers
+ VAR_PRIVATE/tts_request_succeeded = 0
+ /// Amount of cache hits
+ VAR_PRIVATE/tts_reused = 0
+ /// Assoc list of request error codes
+ VAR_PRIVATE/list/tts_errors = list()
+ /// Last errored requests' contents
+ VAR_PRIVATE/tts_error_raw = ""
+
+ // Simple Moving Average RPS
+ VAR_PRIVATE/list/tts_rps_list = list()
+ VAR_PRIVATE/tts_sma_rps = 0
+
+ /// Requests per Second (RPS), only real API requests
+ VAR_PRIVATE/tts_rps = 0
+ VAR_PRIVATE/tts_rps_counter = 0
+
+ /// Total Requests per Second (TRPS), all TTS request, even reused
+ VAR_PRIVATE/tts_trps = 0
+ VAR_PRIVATE/tts_trps_counter = 0
+
+ /// Reused Requests per Second (RRPS), only reused requests
+ VAR_PRIVATE/tts_rrps = 0
+ VAR_PRIVATE/tts_rrps_counter = 0
+
+ VAR_PRIVATE/is_enabled = TRUE
+ /// List of all available TTS seeds
+ var/list/datum/tts_seed/tts_seeds = list()
+ /// List of all available TTS providers
+ var/list/datum/tts_provider/tts_providers = list()
+
+ VAR_PRIVATE/tts_requests_queue_limit = 100
+ VAR_PRIVATE/tts_rps_limit = 11
+ VAR_PRIVATE/last_network_fire = 0
+
+ /// General request queue
+ VAR_PRIVATE/list/tts_queue = list()
+ /// Ffmpeg queue. Is an assoc list. Each entry is a filename mapped to the list of sound processing requests which require it.
+ VAR_PRIVATE/list/tts_effects_queue = list()
+ /// Lazy list of request that need to performed to TTS provider API
+ VAR_PRIVATE/list/tts_requests_queue
+
+ /// List of currently existing binding of atom and sound channel: `atom` => `sound_channel`. SS220 TODO: free channel when atom is detroyed and may be on some other circumstances
+ VAR_PRIVATE/list/tts_local_channels_by_owner = list()
+
+ /// Mapping of BYOND gender to TTS gender
+ VAR_PRIVATE/list/gender_table = list(
+ NEUTER = TTS_GENDER_ANY,
+ PLURAL = TTS_GENDER_ANY,
+ MALE = TTS_GENDER_MALE,
+ FEMALE = TTS_GENDER_FEMALE
+ )
+ /// Is debug mode enabled or not. Information about `sanitized_messages_cache_hit` and `sanitized_messages_cache_miss` is printed to debug logs each SS fire
+ VAR_PRIVATE/debug_mode_enabled = FALSE
+ /// Whether or not caching of sanitized messages is performed
+ VAR_PRIVATE/sanitized_messages_caching = TRUE
+ /// Amount of message duplicates that were sanitized current SS fire. Debug purpose only
+ VAR_PRIVATE/sanitized_messages_cache_hit = 0
+ /// Amount of unique messages that were sanitized current SS fire. Debug purpose only
+ VAR_PRIVATE/sanitized_messages_cache_miss = 0
+ /// List of all messages that were sanitized as: `meesage md5 hash` => `message`
+ VAR_PRIVATE/list/sanitized_messages_cache = list()
+
+ /// List of all available TTS seed names
+ VAR_PRIVATE/list/tts_seeds_names = list()
+ /// List of all available TTS seed names, mapped by donator level for faster access
+ VAR_PRIVATE/list/tts_seeds_names_by_donator_levels = list()
+
+ /// List of all tts seeds mapped by TTS gender: `tts gender` => `list of seeds`
+ VAR_PRIVATE/list/tts_seeds_by_gender
+ /// Replacement map for acronyms for proper TTS spelling. Not private because `replacetext` can use only global procs
+ var/list/tts_acronym_replacements
+ /// Replacement map for jobs for proper TTS spelling
+ VAR_PRIVATE/list/tts_job_replacements
+/datum/controller/subsystem/tts220/stat_entry(msg)
+ msg += "tRPS:[tts_trps] "
+ msg += "rRPS:[tts_rrps] "
+ msg += "RPS:[tts_rps] "
+ msg += "smaRPS:[tts_sma_rps] | "
+ msg += "W:[tts_wanted] "
+ msg += "F:[tts_request_failed] "
+ msg += "S:[tts_request_succeeded] "
+ msg += "R:[tts_reused] "
+ return ..()
+
+/datum/controller/subsystem/tts220/PreInit()
+ . = ..()
+ for(var/path in subtypesof(/datum/tts_provider))
+ var/datum/tts_provider/provider = new path
+ tts_providers[provider.name] += provider
+
+ for(var/path in subtypesof(/datum/tts_seed))
+ var/datum/tts_seed/seed = new path
+ if(seed.value == "STUB")
+ continue
+ seed.provider = tts_providers[initial(seed.provider.name)]
+ tts_seeds[seed.name] = seed
+ tts_seeds_names += seed.name
+ tts_seeds_names_by_donator_levels["[seed.required_donator_level]"] += list(seed.name)
+ LAZYADDASSOCLIST(tts_seeds_by_gender, seed.gender, seed.name)
+ tts_seeds_names = sortTim(tts_seeds_names, GLOBAL_PROC_REF(cmp_text_asc))
+
+/datum/controller/subsystem/tts220/Initialize(start_timeofday)
+ if(!CONFIG_GET(flag/tts_enabled))
+ is_enabled = FALSE
+ return SS_INIT_NO_NEED
+
+ load_replacements()
+
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/tts220/pause()
+ . = ..()
+ is_enabled = FALSE
+
+/datum/controller/subsystem/tts220/fire()
+ if(last_network_fire + 1 SECONDS <= world.time)
+ fire_networking()
+ fire_sound_processing()
+
+/datum/controller/subsystem/tts220/proc/fire_networking()
+ last_network_fire = world.time
+
+ tts_rps = tts_rps_counter
+ tts_rps_counter = 0
+ tts_trps = tts_trps_counter
+ tts_trps_counter = 0
+ tts_rrps = tts_rrps_counter
+ tts_rrps_counter = 0
+
+ tts_rps_list += tts_rps
+ if(length(tts_rps_list) > 15)
+ tts_rps_list.Cut(1,2)
+
+ var/rps_sum = 0
+ for(var/rps in tts_rps_list)
+ rps_sum += rps
+ tts_sma_rps = round(rps_sum / length(tts_rps_list), 0.1)
+
+ var/free_rps = clamp(tts_rps_limit - tts_rps, 0, tts_rps_limit)
+ var/requests = LAZYCOPY_RANGE(tts_requests_queue, 1, clamp(LAZYLEN(tts_requests_queue), 0, free_rps) + 1)
+ for(var/request in requests)
+ var/text = request[1]
+ var/datum/tts_seed/seed = request[2]
+ var/datum/callback/proc_callback = request[3]
+ var/datum/tts_provider/provider = seed.provider
+ provider.request(text, seed, proc_callback)
+ tts_rps_counter++
+ LAZYCUT(tts_requests_queue, 1, clamp(LAZYLEN(tts_requests_queue), 0, free_rps) + 1)
+
+ if(sanitized_messages_caching)
+ sanitized_messages_cache.Cut()
+ if(debug_mode_enabled)
+ logger.Log(LOG_CATEGORY_DEBUG, "sanitized_messages_cache: HIT=[sanitized_messages_cache_hit] / MISS=[sanitized_messages_cache_miss]")
+ sanitized_messages_cache_hit = 0
+ sanitized_messages_cache_miss = 0
+
+/datum/controller/subsystem/tts220/proc/fire_sound_processing()
+ var/queue_position = 1
+ while(LAZYLEN(tts_effects_queue) >= queue_position)
+ var/filename = tts_effects_queue[queue_position++]
+ INVOKE_ASYNC(src, PROC_REF(process_filename_sound_effect_requests), filename)
+
+ if(MC_TICK_CHECK)
+ break
+
+ LAZYCUT(tts_effects_queue, 1, queue_position)
+
+/datum/controller/subsystem/tts220/proc/process_filename_sound_effect_requests(filename)
+ var/list/filename_requests = tts_effects_queue[filename]
+ var/datum/sound_effect_request/request = filename_requests[1]
+
+ if(!apply_sound_effect(request.effect, request.original_filename, request.output_filename))
+ return
+
+ for(var/datum/sound_effect_request/adjacent_request as anything in filename_requests)
+ adjacent_request.cb.InvokeAsync()
+
+/datum/controller/subsystem/tts220/Recover()
+ is_enabled = SStts220.is_enabled
+ tts_wanted = SStts220.tts_wanted
+ tts_request_failed = SStts220.tts_request_failed
+ tts_request_succeeded = SStts220.tts_request_succeeded
+ tts_reused = SStts220.tts_reused
+ tts_acronym_replacements = SStts220.tts_acronym_replacements
+ tts_job_replacements = SStts220.tts_job_replacements
+
+/datum/controller/subsystem/tts220/proc/load_replacements()
+ if(!fexists(TTS_REPLACEMENTS_FILE_PATH))
+ logger.Log(LOG_CATEGORY_DEBUG, "No file for TTS replacements located at: [TTS_REPLACEMENTS_FILE_PATH]. No replacements will be applied for TTS.")
+ return
+
+ var/tts_replacements_json = file2text(TTS_REPLACEMENTS_FILE_PATH)
+ if(!length(tts_replacements_json))
+ logger.Log(LOG_CATEGORY_DEBUG, "TTS replacements file is empty at: [TTS_REPLACEMENTS_FILE_PATH].")
+ return
+
+ var/list/replacements = json_decode(tts_replacements_json)
+ tts_acronym_replacements = replacements[TTS_ACRONYM_REPLACEMENTS]
+ tts_job_replacements = replacements[TTS_JOB_REPLACEMENTS]
+
+/datum/controller/subsystem/tts220/proc/queue_request(text, datum/tts_seed/seed, datum/callback/proc_callback)
+ if(LAZYLEN(tts_requests_queue) > tts_requests_queue_limit)
+ is_enabled = FALSE
+ to_chat(world, span_info("SERVER: очередь запросов превысила лимит, подсистема [src] принудительно отключена!"))
+ return FALSE
+
+ if(tts_rps_counter < tts_rps_limit)
+ var/datum/tts_provider/provider = seed.provider
+ provider.request(text, seed, proc_callback)
+ tts_rps_counter++
+ return TRUE
+
+ LAZYADD(tts_requests_queue, list(list(text, seed, proc_callback)))
+ return TRUE
+
+/datum/controller/subsystem/tts220/proc/get_tts(atom/speaker, mob/listener, message, datum/tts_seed/tts_seed, is_local = TRUE, datum/singleton/sound_effect/effect = null, traits = TTS_TRAIT_RATE_FASTER, preSFX = null, postSFX = null)
+ if(!is_enabled)
+ return
+ if(!message)
+ return
+ if(isnull(listener) || !listener.client)
+ return
+ if(ispath(tts_seed) && SStts220.tts_seeds[initial(tts_seed.name)])
+ tts_seed = SStts220.tts_seeds[initial(tts_seed.name)]
+ if(!istype(tts_seed))
+ return
+
+ tts_wanted++
+ tts_trps_counter++
+
+ var/datum/tts_provider/provider = tts_seed.provider
+ if(!provider.is_enabled)
+ return
+ if(provider.throttle_check())
+ return
+
+ var/dirty_text = message
+ var/text = sanitize_tts_input(dirty_text)
+
+ if(!text || length_char(text) > MAX_MESSAGE_LEN)
+ return
+
+ if(traits & TTS_TRAIT_RATE_FASTER)
+ text = provider.rate_faster(text)
+
+ if(traits & TTS_TRAIT_RATE_MEDIUM)
+ text = provider.rate_medium(text)
+
+ if(traits & TTS_TRAIT_PITCH_WHISPER)
+ text = provider.pitch_whisper(text)
+
+ var/hash = md5(lowertext(text))
+
+ var/filename = "data/tts_cache/[tts_seed.name]/[hash]"
+ var/datum/singleton/sound_effect/effect_singleton = GET_SINGLETON(effect)
+
+ if(fexists("[filename].ogg"))
+ tts_reused++
+ tts_rrps_counter++
+ play_tts(speaker, listener, filename, is_local, effect_singleton, preSFX, postSFX)
+ return
+
+ var/datum/callback/play_tts_cb = CALLBACK(src, PROC_REF(play_tts), speaker, listener, filename, is_local, effect_singleton, preSFX, postSFX)
+
+ if(LAZYLEN(tts_queue[filename]))
+ tts_reused++
+ tts_rrps_counter++
+ LAZYADD(tts_queue[filename], play_tts_cb)
+ return
+
+ queue_request(text, tts_seed, CALLBACK(src, PROC_REF(get_tts_callback), speaker, listener, filename, tts_seed, is_local, effect_singleton, preSFX, postSFX))
+
+ LAZYADD(tts_queue[filename], play_tts_cb)
+
+/datum/controller/subsystem/tts220/proc/get_tts_callback(atom/speaker, mob/listener, filename, datum/tts_seed/seed, is_local, effect, preSFX, postSFX, datum/http_response/response)
+ var/datum/tts_provider/provider = seed.provider
+
+ // Bail if it errored
+ if(response.errored)
+ provider.timed_out_requests++
+ log_game(span_warning("Error connecting to [provider.name] TTS API. Please inform a maintainer or server host."))
+ message_admins(span_warning("Error connecting to [provider.name] TTS API. Please inform a maintainer or server host."))
+ return
+
+ if(response.status_code != 200)
+ provider.failed_requests++
+ log_game(span_warning("Error performing [provider.name] TTS API request (Code: [response.status_code])"))
+ message_admins(span_warning("Error performing [provider.name] TTS API request (Code: [response.status_code])"))
+ tts_request_failed++
+ if(response.status_code)
+ if(tts_errors["[response.status_code]"])
+ tts_errors["[response.status_code]"]++
+ else
+ tts_errors += "[response.status_code]"
+ tts_errors["[response.status_code]"] = 1
+ tts_error_raw = response.error
+ return
+
+ tts_request_succeeded++
+
+ var/voice = provider.process_response(response)
+ if(!voice)
+ return
+
+ rustgss220_file_write_b64decode(voice, "[filename].ogg")
+
+ if(!CONFIG_GET(flag/tts_cache_enabled))
+ addtimer(CALLBACK(src, PROC_REF(cleanup_tts_file), "[filename].ogg"), FILE_CLEANUP_DELAY)
+
+ for(var/datum/callback/cb in tts_queue[filename])
+ cb.InvokeAsync()
+ tts_queue[filename] -= cb
+
+ tts_queue -= filename
+
+/datum/controller/subsystem/tts220/proc/queue_sound_effect_processing(pure_filename, effect, processed_filename, datum/callback/output_tts_cb)
+ var/datum/sound_effect_request/request = new
+ request.original_filename = "[pure_filename].ogg"
+ request.output_filename = processed_filename
+ request.effect = effect
+ request.cb = output_tts_cb
+ LAZYADD(tts_effects_queue[processed_filename], request)
+
+/datum/controller/subsystem/tts220/proc/play_tts(atom/speaker, mob/listener, pure_filename, is_local = TRUE, datum/singleton/sound_effect/effect = null, preSFX = null, postSFX = null)
+ if(isnull(listener) || !listener.client)
+ return
+
+ var/filename2play = "[pure_filename][effect?.suffix].ogg"
+
+ if(isnull(effect) || fexists(filename2play))
+ output_tts(speaker, listener, filename2play, is_local, preSFX, postSFX)
+ return
+
+ var/datum/callback/output_tts_cb = CALLBACK(src, PROC_REF(output_tts), speaker, listener, filename2play, is_local, preSFX, postSFX)
+ queue_sound_effect_processing(pure_filename, effect, filename2play, output_tts_cb)
+
+/datum/controller/subsystem/tts220/proc/output_tts(atom/speaker, mob/listener, filename2play, is_local = TRUE, preSFX = null, postSFX = null)
+ var/volume = listener?.client?.prefs?.read_preference(/datum/preference/numeric/sound_tts_volume)
+ if(!volume)
+ return
+
+ var/turf/turf_source = get_turf(speaker)
+
+ var/sound/output = sound(filename2play)
+ output.status = SOUND_STREAM
+ if(!is_local || isnull(speaker))
+ output.wait = TRUE
+ output.volume = volume * 0.75 // non-local is slightly less loud // TODO220: Make volume different
+ output.environment = SOUND_ENVIRONMENT_NONE
+
+ if(output.volume <= 0)
+ return
+
+ play_sfx_if_exists(listener, preSFX, output)
+ SEND_SOUND(listener, output)
+ play_sfx_if_exists(listener, postSFX, output)
+
+ return
+
+ play_sfx_if_exists(listener, preSFX, output)
+
+ output = listener.playsound_local(turf_source, output, volume)
+
+ if(!output || output.volume <= 0)
+ return
+
+ play_sfx_if_exists(listener, postSFX, output)
+
+/datum/controller/subsystem/tts220/proc/play_sfx_if_exists(mob/listener, sfx, sound/output)
+ if(sfx)
+ play_sfx(listener, sfx, output.volume, output.environment)
+
+/datum/controller/subsystem/tts220/proc/play_sfx(mob/listener, sfx, volume, environment)
+ var/sound/output = sound(sfx)
+ output.status = SOUND_STREAM
+ output.wait = TRUE
+ output.volume = volume
+ output.environment = environment
+ SEND_SOUND(listener, output)
+
+/datum/controller/subsystem/tts220/proc/cleanup_tts_file(filename)
+ fdel(filename)
+
+/datum/controller/subsystem/tts220/proc/get_available_seeds(owner)
+ var/list/_tts_seeds_names = list()
+ _tts_seeds_names |= tts_seeds_names
+
+ if(!ismob(owner))
+ return _tts_seeds_names
+
+ var/mob/M = owner
+
+ if(!M.client)
+ return _tts_seeds_names
+
+ return _tts_seeds_names
+
+/datum/controller/subsystem/tts220/proc/get_random_seed(owner)
+ return pick(get_available_seeds(owner))
+
+/datum/controller/subsystem/tts220/proc/sanitize_tts_input(message)
+ var/hash
+ if(sanitized_messages_caching)
+ hash = md5(lowertext(message))
+ if(sanitized_messages_cache[hash])
+ sanitized_messages_cache_hit++
+ return sanitized_messages_cache[hash]
+ sanitized_messages_cache_miss++
+ . = message
+ . = trim(.)
+ var/static/regex/punctuation_check = new(@"[.,?!]\Z")
+ if(!punctuation_check.Find(.))
+ . += "."
+ var/static/regex/html_tags = new(@"<[^>]*>", "g")
+ . = html_tags.Replace(., "")
+ . = html_decode(.)
+ var/static/regex/forbidden_symbols = new(@"[^a-zA-Z0-9а-яА-ЯёЁ,!?+./ \r\n\t:—()-]", "g")
+ . = forbidden_symbols.Replace(., "")
+ var/static/regex/acronyms = new(@"(?SERVER: провайдер Silero в подсистеме SStts220 принудительно включен! ")
+ return json_encode(list("success" = "SStts220\[Silero] was force enabled"))
+ return json_encode(list("error" = "SStts220\[Silero] is already enabled"))
+
+/datum/world_topic/playerlist
+ keyword = "playerlist"
+
+/datum/world_topic/playerlist/Run(list/input)
+ var/list/keys = list()
+ for(var/I in GLOB.clients)
+ var/client/C = I
+ keys += C.key
+
+ return json_encode(keys)
+
+/datum/world_topic/status/Run(list/input)
+ . = ..()
+ var/list/admins = list()
+ for(var/client/C in GLOB.clients)
+ if(C.holder)
+ if(C.holder.fakekey)
+ continue //so stealthmins aren't revealed by the hub
+ admins += list(list(C.key, join_admin_ranks(C.holder.ranks)))
+ if(key_valid)
+ for(var/i in 1 to admins.len)
+ var/list/A = admins[i]
+ .["admin[i - 1]"] = A[1]
+ .["adminrank[i - 1]"] = A[2]
diff --git a/rust_g_ss220.dll b/rust_g_ss220.dll
new file mode 100644
index 0000000000000..ef1d0ec0bdd59
Binary files /dev/null and b/rust_g_ss220.dll differ
diff --git a/tgstation.dme b/tgstation.dme
index 9b3f3bcff0cac..441a520ed49b6 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6163,4 +6163,5 @@
#include "interface\fonts\spess_font.dm"
#include "interface\fonts\tiny_unicode.dm"
#include "interface\fonts\vcr_osd_mono.dm"
+#include "modular_bandastation\modular_bandastation.dme" // BANDASTATION EDIT
// END_INCLUDE
diff --git a/tgui/packages/tgui-say/ChannelIterator.test.ts b/tgui/packages/tgui-say/ChannelIterator.test.ts
index 15e9812e702ec..91719cbbf51a5 100644
--- a/tgui/packages/tgui-say/ChannelIterator.test.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.test.ts
@@ -11,6 +11,10 @@ describe('ChannelIterator', () => {
expect(channelIterator.current()).toBe('Say');
expect(channelIterator.next()).toBe('Radio');
expect(channelIterator.next()).toBe('Me');
+ // BANDASTATION EDIT ADDITION START
+ expect(channelIterator.next()).toBe('Whis');
+ expect(channelIterator.next()).toBe('LOOC');
+ // BANDASTATION EDIT ADDITION END
expect(channelIterator.next()).toBe('OOC');
expect(channelIterator.next()).toBe('Say'); // Admin is blacklisted so it should be skipped
});
diff --git a/tgui/packages/tgui-say/ChannelIterator.ts b/tgui/packages/tgui-say/ChannelIterator.ts
index 136806927e95e..7515f95adc74c 100644
--- a/tgui/packages/tgui-say/ChannelIterator.ts
+++ b/tgui/packages/tgui-say/ChannelIterator.ts
@@ -1,4 +1,13 @@
-export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin';
+export type Channel =
+ | 'Say'
+ | 'Radio'
+ | 'Me'
+ // BANDASTATION EDIT START
+ | 'Whis'
+ | 'LOOC'
+ // BANDASTATION EDIT END
+ | 'OOC'
+ | 'Admin';
/**
* ### ChannelIterator
@@ -8,9 +17,19 @@ export type Channel = 'Say' | 'Radio' | 'Me' | 'OOC' | 'Admin';
*/
export class ChannelIterator {
private index: number = 0;
- private readonly channels: Channel[] = ['Say', 'Radio', 'Me', 'OOC', 'Admin'];
+ private readonly channels: Channel[] = [
+ 'Say',
+ 'Radio',
+ 'Me',
+ // BANDASTATION EDIT START
+ 'Whis',
+ 'LOOC',
+ // BANDASTATION EDIT END
+ 'OOC',
+ 'Admin',
+ ];
private readonly blacklist: Channel[] = ['Admin'];
- private readonly quiet: Channel[] = ['OOC', 'Admin'];
+ private readonly quiet: Channel[] = ['OOC', 'LOOC', 'Admin']; // BANDASTATION EDIT
public next(): Channel {
if (this.blacklist.includes(this.channels[this.index])) {
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index ded5ffeeb9729..00001fed5c50b 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -15,6 +15,8 @@ $_channel_map: (
'Engi': #f37746,
'Hive': #855d85,
'io': #1e90ff,
+ // BANDASTATION EDIT ADDITION
+ 'LOOC': #ffceb6,
'Me': #5975da,
'Med': #57b8f0,
'OOC': #cca300,
@@ -25,6 +27,8 @@ $_channel_map: (
'Supp': #b88646,
'Svc': #6ca729,
'Synd': #8f4a4b,
+ // BANDASTATION EDIT ADDITION
+ 'Whis': #7c7fd9,
);
$channel_keys: map.keys($_channel_map) !default;
diff --git a/tgui/packages/tgui/components/NanoMap.js b/tgui/packages/tgui/components/NanoMap.js
new file mode 100644
index 0000000000000..f06141047238b
--- /dev/null
+++ b/tgui/packages/tgui/components/NanoMap.js
@@ -0,0 +1,224 @@
+// BANDASTATION ADDITION
+
+import { resolveAsset } from '../assets';
+import { useBackend } from '../backend';
+import { Box, Button, Icon, Tooltip } from '.';
+import { LabeledList } from './LabeledList';
+import { Slider } from './Slider';
+
+const pauseEvent = (e) => {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ e.cancelBubble = true;
+ e.returnValue = false;
+ return false;
+};
+
+export class NanoMap extends Component {
+ constructor(props) {
+ super(props);
+
+ // Auto center based on window size
+ const Xcenter = window.innerWidth / 2 - 256;
+ const Ycenter = window.innerHeight / 2 - 256;
+
+ this.state = {
+ offsetX: 128,
+ offsetY: 48,
+ transform: 'none',
+ dragging: false,
+ originX: null,
+ originY: null,
+ zoom: 1,
+ };
+
+ // Dragging
+ this.handleDragStart = (e) => {
+ this.ref = e.target;
+ this.setState({
+ dragging: false,
+ originX: e.screenX,
+ originY: e.screenY,
+ });
+ document.addEventListener('mousemove', this.handleDragMove);
+ document.addEventListener('mouseup', this.handleDragEnd);
+ pauseEvent(e);
+ };
+
+ this.handleDragMove = (e) => {
+ this.setState((prevState) => {
+ const state = { ...prevState };
+ const newOffsetX = e.screenX - state.originX;
+ const newOffsetY = e.screenY - state.originY;
+ if (prevState.dragging) {
+ state.offsetX += newOffsetX;
+ state.offsetY += newOffsetY;
+ state.originX = e.screenX;
+ state.originY = e.screenY;
+ } else {
+ state.dragging = true;
+ }
+ return state;
+ });
+ pauseEvent(e);
+ };
+
+ this.handleDragEnd = (e) => {
+ this.setState({
+ dragging: false,
+ originX: null,
+ originY: null,
+ });
+ document.removeEventListener('mousemove', this.handleDragMove);
+ document.removeEventListener('mouseup', this.handleDragEnd);
+ pauseEvent(e);
+ };
+
+ this.handleZoom = (_e, value) => {
+ this.setState((state) => {
+ const newZoom = Math.min(Math.max(value, 1), 8);
+ let zoomDiff = (newZoom - state.zoom) * 1.5;
+ state.zoom = newZoom;
+ state.offsetX = state.offsetX - 262 * zoomDiff;
+ state.offsetY = state.offsetY - 256 * zoomDiff;
+ if (props.onZoom) {
+ props.onZoom(state.zoom);
+ }
+ return state;
+ });
+ };
+ }
+
+ render() {
+ const { config } = useBackend(this.context);
+ const { dragging, offsetX, offsetY, zoom = 1 } = this.state;
+ const { children } = this.props;
+
+ const mapUrl = config.map + '_nanomap_z1.png';
+ const mapSize = 510 * zoom + 'px';
+ const newStyle = {
+ width: mapSize,
+ height: mapSize,
+ 'margin-top': offsetY + 'px',
+ 'margin-left': offsetX + 'px',
+ overflow: 'hidden',
+ position: 'relative',
+ 'background-size': 'cover',
+ 'background-repeat': 'no-repeat',
+ 'text-align': 'center',
+ cursor: dragging ? 'move' : 'auto',
+ };
+ const mapStyle = {
+ width: '100%',
+ height: '100%',
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ '-ms-interpolation-mode': 'nearest-neighbor',
+ };
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+ }
+}
+
+const NanoMapMarker = (props, context) => {
+ const { x, y, zoom = 1, icon, tooltip, color } = props;
+ const rx = x * 2 * zoom - zoom - 3;
+ const ry = y * 2 * zoom - zoom - 3;
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+NanoMap.Marker = NanoMapMarker;
+
+const NanoMapZoomer = (props, context) => {
+ return (
+
+
+
+ v + 'x'}
+ value={props.zoom}
+ onDrag={(e, v) => props.onZoom(e, v)}
+ />
+
+
+
+ );
+};
+
+NanoMap.Zoomer = NanoMapZoomer;
+
+let ActiveButton;
+class NanoButton extends Component {
+ constructor(props) {
+ super(props);
+ const { act } = useBackend(this.props.context);
+ this.state = {
+ color: this.props.color,
+ };
+ this.handleClick = (e) => {
+ if (ActiveButton !== undefined) {
+ ActiveButton.setState({
+ color: 'blue',
+ });
+ }
+ act('switch_camera', {
+ name: this.props.name,
+ });
+ ActiveButton = this;
+ this.setState({
+ color: 'green',
+ });
+ };
+ }
+ render() {
+ let rx = this.props.x * 2 * this.props.zoom - this.props.zoom - 3;
+ let ry = this.props.y * 2 * this.props.zoom - this.props.zoom - 3;
+ return (
+
+
+
+ );
+ }
+}
+NanoMap.NanoButton = NanoButton;
diff --git a/tgui/packages/tgui/components/index.ts b/tgui/packages/tgui/components/index.ts
index 1a5f477d256b5..e591dc4cc9238 100644
--- a/tgui/packages/tgui/components/index.ts
+++ b/tgui/packages/tgui/components/index.ts
@@ -49,3 +49,6 @@ export { TimeDisplay } from './TimeDisplay';
export { Tooltip } from './Tooltip';
export { TrackOutsideClicks } from './TrackOutsideClicks';
export { VirtualList } from './VirtualList';
+
+// BANDASTATION EDIT ADDITION
+export { NanoMap } from './NanoMap';
diff --git a/tgui/packages/tgui/interfaces/ExaminePanel.tsx b/tgui/packages/tgui/interfaces/ExaminePanel.tsx
new file mode 100644
index 0000000000000..8dc853a13850a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ExaminePanel.tsx
@@ -0,0 +1,46 @@
+import { BooleanLike } from '../../common/react';
+import { useBackend } from '../backend';
+import { Section, Stack } from '../components';
+import { Window } from '../layouts';
+import { CharacterPreview } from './common/CharacterPreview';
+
+type Data = {
+ character_name: string;
+ obscured: BooleanLike;
+ assigned_map: string;
+ flavor_text: string;
+};
+
+export const ExaminePanel = (props) => {
+ const { act, data } = useBackend();
+ const { character_name, obscured, assigned_map, flavor_text } = data;
+ return (
+
+
+
+
+
+ {!obscured && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
index 261c40408f1b9..bde56f429d63c 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
@@ -11,6 +11,7 @@ import { MainPage } from './MainPage';
import { PageButton } from './PageButton';
import { QuirksPage } from './QuirksPage';
import { SpeciesPage } from './SpeciesPage';
+import { VoicePage } from './VoicePage'; // BANDASTATION EDIT ADD - TTS
enum Page {
Antags,
@@ -18,6 +19,8 @@ enum Page {
Jobs,
Species,
Quirks,
+ // BANDASTATION EDIT ADD - TTS
+ Voice,
}
const CharacterProfiles = (props: {
@@ -75,6 +78,10 @@ export const CharacterPreferenceWindow = (props) => {
case Page.Quirks:
pageContents = ;
break;
+ // BANDASTATION EDIT ADD - TTS
+ case Page.Voice:
+ pageContents = ;
+ break;
default:
exhaustiveCheck(currentPage);
}
@@ -149,6 +156,18 @@ export const CharacterPreferenceWindow = (props) => {
Quirks
+
+ {Boolean(data.tts_enabled) && ( // BANDASTATION EDIT - TTS
+
+
+ Voice
+
+
+ )}
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/VoicePage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/VoicePage.tsx
new file mode 100644
index 0000000000000..b9175b91e6759
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/VoicePage.tsx
@@ -0,0 +1,264 @@
+import { useState } from 'react';
+
+import { useBackend } from '../../backend';
+import {
+ BlockQuote,
+ Box,
+ Button,
+ Dropdown,
+ Icon,
+ Input,
+ LabeledList,
+ Section,
+ Table,
+} from '../../components';
+import { PreferencesMenuData } from './data';
+
+const donatorTiers = {
+ 0: 'Бесплатные',
+ 1: 'Tier I',
+ 2: 'Tier II',
+ 3: 'Tier III',
+ 4: 'Tier IV',
+};
+
+const gendersIcons = {
+ Мужской: {
+ icon: 'mars',
+ color: 'blue',
+ },
+ Женский: {
+ icon: 'venus',
+ color: 'purple',
+ },
+ Любой: {
+ icon: 'venus-mars',
+ color: 'white',
+ },
+};
+
+const getCheckboxGroup = (
+ itemsList,
+ selectedList,
+ setSelected,
+ contentKey: string | null = null,
+) => {
+ return itemsList.map((item) => {
+ const title = (contentKey && item[contentKey]) ?? item;
+ return (
+ {
+ if (selectedList.includes(item)) {
+ setSelected(
+ selectedList.filter(
+ (i) => ((contentKey && i[contentKey]) ?? i) !== item,
+ ),
+ );
+ } else {
+ setSelected([item, ...selectedList]);
+ }
+ }}
+ />
+ );
+ });
+};
+
+export const VoicePage = (props) => {
+ const { act, data } = useBackend();
+
+ const { providers, seeds, phrases, tts_seed } = data;
+
+ const donator_level = 5;
+
+ const categories = seeds
+ .map((seed) => seed.category)
+ .filter((category, i, a) => a.indexOf(category) === i);
+ const genders = seeds
+ .map((seed) => seed.gender)
+ .filter((gender, i, a) => a.indexOf(gender) === i);
+ const donatorLevels = seeds
+ .map((seed) => seed.donator_level)
+ .filter((level, i, a) => a.indexOf(level) === i)
+ .map((level) => donatorTiers[level]);
+
+ const [selectedProviders, setSelectedProviders] = useState(providers);
+ const [selectedGenders, setSelectedGenders] = useState(genders);
+ const [selectedCategories, setSelectedCategories] = useState(categories);
+ const [selectedDonatorLevels, setSelectedDonatorLevels] =
+ useState(donatorLevels);
+ const [selectedPhrase, setSelectedPhrase] = useState(phrases[0]);
+ const [searchtext, setSearchtext] = useState('');
+
+ let providerCheckboxes = getCheckboxGroup(
+ providers,
+ selectedProviders,
+ setSelectedProviders,
+ 'name',
+ );
+ let genderesCheckboxes = getCheckboxGroup(
+ genders,
+ selectedGenders,
+ setSelectedGenders,
+ );
+ let categoriesCheckboxes = getCheckboxGroup(
+ categories,
+ selectedCategories,
+ setSelectedCategories,
+ );
+ let donatorLevelsCheckboxes = getCheckboxGroup(
+ donatorLevels,
+ selectedDonatorLevels,
+ setSelectedDonatorLevels,
+ );
+
+ let phrasesSelect = (
+ setSelectedPhrase(value)}
+ />
+ );
+
+ let searchBar = (
+ setSearchtext(value)}
+ />
+ );
+
+ const availableSeeds = seeds
+ .sort((a, b) => {
+ const aname = a.name.toLowerCase();
+ const bname = b.name.toLowerCase();
+ if (aname > bname) {
+ return 1;
+ }
+ if (aname < bname) {
+ return -1;
+ }
+ return 0;
+ })
+ .filter(
+ (seed) =>
+ selectedProviders.some((provider) => provider.name === seed.provider) &&
+ selectedGenders.includes(seed.gender) &&
+ selectedCategories.includes(seed.category) &&
+ selectedDonatorLevels.includes(donatorTiers[seed.donator_level]) &&
+ seed.name.toLowerCase().includes(searchtext.toLowerCase()),
+ );
+
+ let seedsRow = availableSeeds.map((seed) => {
+ return (
+
+
+ act('select_voice', { seed: seed.name })}
+ />
+
+
+
+ act('listen', { seed: seed.name, phrase: selectedPhrase })
+ }
+ />
+
+ 0 && tts_seed !== seed.name
+ ? 'orange'
+ : 'white'
+ }
+ >
+ {seed.name}
+
+
+ {seed.category}
+
+
+
+
+
+ {seed.donator_level > 0 && (
+ <>
+ {donatorTiers[seed.donator_level]}
+
+ >
+ )}
+
+
+ );
+ });
+
+ return (
+ <>
+
+
+
+ {providerCheckboxes}
+
+ {genderesCheckboxes}
+
+ {categoriesCheckboxes}
+
+
+ {donatorLevelsCheckboxes}
+
+ {phrasesSelect}
+ {searchBar}
+
+
+
+
+
+
+ {`Для поддержания и развития сообщества в условиях растущих расходов часть голосов пришлось сделать доступными только за материальную поддержку сообщества.`}
+
+
+ {`Подробнее об этом можно узнать в нашем Discord-сообществе.`}
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts
index 8cb79790dc444..2a6e6c3cfc72d 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts
@@ -177,6 +177,24 @@ export type PreferencesMenuData = {
name_to_use: string;
window: Window;
+
+ // BANDASTATION EDIT START - TTS
+ tts_seed: string;
+ tts_enabled: BooleanLike;
+ providers: Array<{
+ name: string;
+ is_enabled: BooleanLike;
+ }>;
+ seeds: Array<{
+ name: string;
+ value: string;
+ category: string;
+ gender: string;
+ provider: string;
+ donator_level: number;
+ }>;
+ phrases: string[];
+ // BANDASTATION EDIT END
};
export type ServerData = {
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base_bandastation.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base_bandastation.tsx
new file mode 100644
index 0000000000000..6257d2dade85f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base_bandastation.tsx
@@ -0,0 +1,19 @@
+import { Box, TextArea } from '../../../../components';
+import { FeatureShortTextData, FeatureValueProps } from './base';
+
+export const FeatureTextInput = (
+ props: FeatureValueProps,
+) => {
+ if (!props.serverData) {
+ return Loading... ;
+ }
+
+ return (
+