diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index a28b9732104b4..4ba7d9442b680 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -26,3 +26,9 @@
bf996203dfc4b09f8dc4dd73b532f9ee49691776
bfa20cdc17d1794969331c4272c4a8d7ad523a44
bca4aa1184eca550a6d9543a93d720ba6dc10b20
+
+# obj/effect/ -> obj/
+8cd28ed954d5873c1b20f35ce58aa5820803ec4c
+
+# datum/effect/effect/system & datum/effect/system -> datum/effect
+96f09a4736ccdc33d9651aa9f162d27e3263b127
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index de8371f9e640c..4ef640edbb517 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,17 +1 @@
-# Require reviews Core for changes to configuration, license, etc
-
-.github/ @Baystation12/Core
-.vscode/ @Baystation12/Core
-
-.codebeatignore @Baystation12/Core
-.editorconfig @Baystation12/Core
-.git-blame-ignore-revs @Baystation12/Core
-.gitattributes @Baystation12/Core
-.gitignore @Baystation12/Core
-SpacemanDMM.toml @Baystation12/Core
-
-/docs/ @Baystation12/Core
-/tools/ @Baystation12/Core
-
-/LICENSE @Baystation12/Core
-/README.md @Baystation12/Core
+* @SierraBay/reviewers
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b184b097bcbef..afb409f8b476b 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,7 +1,557 @@
-All users are expected to review [/docs/CODE_OF_CONDUCT.md](/docs/CODE_OF_CONDUCT.md) before interacting with the repository or other users.
+# Contributing
-Baystation12 is licensed under the GNU Affero General Public License version 3, which can be found in full in LICENSE-AGPL3.txt.
+
+ Содержание
-Commits with a git authorship date prior to `1420675200 +0000` (2015/01/08 00:00 GMT) are licensed under the GNU General Public License version 3, which can be found in full in LICENSE-GPL3.txt.
+- [Основное](#основное)
+- [Создание PR](#создание-pull-request)
+- [Continuous Integration](#continuous-integration)
+- [Процесс прохождения Code Review](#процесс-прохождения-code-review)
+- [Договоренности по написанию кода](#договоренности-по-написанию-кода)
+ - [Правила именования переменных и функций](#правила-именования-переменных-и-функций)
+ - [Написание функций](#написание-функций)
+ - [Общие правила](#общие-правила)
+- [Разработка безопасного кода](#разработка-безопасного-кода)
+- [Файлы](#файлы)
+- [Фишки и лайфхаки Dream Maker](#фишки-и-лайфхаки-dream-maker)
-All commits whose authorship dates on or after `1420675200 +0000` are assumed to be licensed under AGPL v3, if you wish to license under GPL v3 please make this clear in the commit message and any added files.
\ No newline at end of file
+
+
+# Основное
+
+- Официальный язык нашего сообщества - русский. Коммиты, пулл реквесты и ишью стоит писать на нём, либо на английском. Однако в исходных кодах русский язык запрещен в связи с проблемами, связанными с кодировками.
+- Мы ожидаем, что вы поможете нам поддерживать код, который вы добавили. Скорее всего мы к вам обратимся в случае возникновения каких-то проблем, связанных с вашими изменениями, в том числе рантаймами или багами.
+
+# Создание Pull Request
+
+Здесь приведен небольшой чек-лист важных вещей, на которые обязательно нужно обращать внимание при открытии PR'а. Пренебрежение этим разделом может увеличить время проверки ваших изменений и принятия в сборку вплоть до бесконечности!
+
+- **Заголовок PR'а - цель его изменений.**
+ У любого PR'а должна быть конкретная цель, которая должна быть указана в заголовке. PR не должен содержать изменений, которые не относятся к указанной цели. Если цель PR'а слишком большая, чтобы уместить её в одном заголовке - подумайте над тем, чтобы разбить свой PR на более мелкие части.
+- **Изменения востребованы.**
+ Перед началом разработки убедитесь, что то, что вы хотите сделать - востребовано и будет принято. Для этого нужно создать предложку в дискорде и дождаться её прохождения. Также изменения очевидно востребованы, если на них есть соответствующий ишью. Чтобы ваши изменения ТОЧНО были приняты - лучше убедиться в том, что у вас есть соответстующий ишью с подтверждением, либо предложка, так как любые договоренности в дискорде могут потеряться или оказаться неактуальными.
+- **Подробное описание.**
+ В описании PR'а должно быть подробное описание того, зачем он нужен и что в нем происходит. PR не должен содержать изменений, о которых ничего не написано в описании.
+- **Привяжите ишью.**
+ Если PR решает какие-то ишью, то они должны быть указаны в описании специальным образом, например, `close #23`, чтобы Github смог их автоматически привязать к вашим изменениям. Подробнее можно почитать тут: https://help.github.com/articles/closing-issues-via-commit-messages.
+- **Проверьте свой код.**
+ Открывайте PR только если вы уверены в его работоспособности. Это означает, что вы проверили каждую строчку своих изменений, а также **проверили свои изменения локально**, запустив сервер на своем компьютере. Если что-то невозможно проверить полностью или вы считаете, что на какую-то часть изменений нужно обратить пристальное внимание проверяющих, то укажите это отдельно в описании. Конечно мы также будем проверять ваши изменения, но вы не должны полагаться на то, что ваши баги заметит кто-то другой и исправит за вас.
+- **Ваш код соответствует принятым договоренностям по написанию кода.**
+ Подробнее смотрите раздел "[Договоренности по написанию кода](#договоренности-по-написанию-кода)" ниже.
+- **Ваш код - модульный.** Изменения в кода игры вне модов очень не приветствуются и скорее всего будут отклонены. Код обязан соотвествовать [руководству по модуляризации](/mods/README.md).
+
+# Continuous Integration
+
+Каждый PR проходит проверку на несколько вещей:
+
+- Наличие и корректность чейнджлога.
+- Наличие ошибок найденные статическим анализатором DreamChecker.
+- Наличие нарушений стиля кода.
+- Соответствие [руководству по модуляризации](/mods/README.md).
+
+Наличие и корректность чейнджлога проверяется при каждом открытии PR'а, редактировании текста в теле PR'а, добавлении и удаления меток на PR.
+
+# Процесс прохождения Code Review
+
+- **Аппрув разработчиков.**
+ Чтобы PR был принят - он должен быть аппрувнут активными разработчиками сервера.
+- **Аппрув администрации и игроков.**
+ Если в PR есть изменения игровой логики, то он должен быть аппрувнут голосованием, либо администрацией. Аппрув не нужен, если PR выполнен в рамках ишью, который был ранее аппрувнут голосованием/администрацией. Также аппрув не нужен для фикса багов и любых изменений, которые не затрагивают игровой опыт игроков (рефакторинг, оптимизации, фичи для администраторов и подобное).
+- **Проверка на локальной сборке.**
+ Принимаются только PR'ы, проверенные локально. Если сам разработчик не проверяет свои изменения, то проверка проходит тщательнее, из-за чего принятие изменений затягивается.
+- **Вливание изменений в основную ветку.**
+ Если коммиты в PR соответствуют правилам именования, при мерже не возникает конфликтов и каждый коммит по отдельности не ломает сборку, то принимать следует с помощью rebase. Если коммиты в PR соответствуют правилам именования, но принять её с помощью rebase невозможно т.к. на каких-то коммитах из PR сборка сломана, или же из-за конфликтов, то принимать нужно с помощью merge. Если хотя бы один коммит в PR не соответствует правилам именования, то весь PR может быть принят только через squash с названием, соответствующим правилам.
+- **Вливание изменений на сервер.**
+ Ветка разработки называется `dev-sierra`. Перед обновлением серверов проверенные изменения из `dev-sierra` вливаются в репозиторий `ss220-space/Baystation12-1`. В обоих репозиториях должна поддерживаться чистая история коммитов, которые соответствуют правилам именования. На сервер изменения должны попадать вместе с чейнжлогом для максимального оповещения игроков о статусе разработки.
+
+# Договоренности по написанию кода
+
+> By ChaoticOnyx
+
+## Правила именования переменных и функций
+
+### Имена должны передавать намерения программиста
+
+Имя переменной, функции или типа должно сообщить, почему эта переменная существует, что она делает и как используется. Если имя требует дополнительных комментариев, значит, оно не передает намерений программиста. Лучше написать, что именно измеряется и в каких именно единицах.
+
+Не бойтесь использовать длинные имена: длинное содержательное имя лучше короткого невразумительного.
+
+Пример хорошего названия переменной: days_since_creation;
+Цель: убрать неочевидность.
+
+### Избегайте неинформативных слов в названиях
+
+Неинформативные слова избыточны. Слово variable никогда не должно встречаться в именах переменных. Слово object никогда не должно встречаться в именах объектов. Чем имя name_string лучше name? Разве имя может быть, скажем, вещественным числом?
+
+### Избегайте сокращений, старайтесь выбирать удобопроизносимые имена
+
+russian_to_utf намного лучше rustoutf.
+
+### Выбирайте имена, удобные для поиска
+
+Чем дольше время жизни переменной - тем длиннее должно быть её название. Однобуквенные имена могут использоваться только для локальных переменных в коротких методах.
+
+### Имена типов
+
+Имена типов должны представлять собой существительные и их комбинации: traitor, profession, id_card и request_parser. Старайтесь не использовать в именах классов такие слова, как manager, processor, data или info. Имя класса не должно быть глаголом.
+
+### Имена функций
+
+Имена методов представляют собой глаголы или глагольные словосочетания: explode_station, delete_ian, save и т. д. Методы чтения/записи образуются из значения и префикса get, set и is.
+
+### Используйте абсолютные пути
+
+DM позволяет писать код блоками:
+
+```DM
+datum
+ datum1
+ var
+ varname1 = 1
+ varname2
+ static
+ varname3
+ varname4
+ proc
+ proc1()
+ code
+ proc2()
+ code
+
+ datum2
+ varname1 = 0
+ proc
+ proc3()
+ code
+ proc2()
+ ..()
+ code
+```
+
+Такой способ написания делает невозможным текстовый поиск функции или типа по коду. Единственное исключение - внутри блока описания типа можно определять переменные.
+
+Тот же код, написанный правильно:
+
+```DM
+/datum/datum1
+ var/varname1
+ var/varname2
+ var/static/varname3
+ var/static/varname4
+
+/datum/datum1/proc/proc1()
+ code
+/datum/datum1/proc/proc2()
+ code
+/datum/datum1/datum2
+ varname1 = 0
+/datum/datum1/datum2/proc/proc3()
+ code
+/datum/datum1/datum2/proc2()
+ ..()
+ code
+```
+
+## Написание функций
+
+### Компактность
+
+- **Первое правило:** функции должны быть компактными.
+- **Второе правило:** функции должны быть еще компактнее.
+
+Серьезные программисты работающие в реальном мире ценой многих проб и ошибок вывели, что функции должны быть очень маленькими. Желательно, чтобы длина фукнции не превышала 20 строк. В условиях разработки SS13 кому-то это может показаться излишним, но даже в наших проектах можно заметить, что чем длиннее функция тем сложнее её поддерживать, тем больше в ней багов и тем меньшее количество людей хочет в неё лезть и с ней разбираться.
+
+### Правило одной операции
+
+Функция должна выполнять только одну операцию. Она должна выполнять ее хорошо. И ничего другого она делать не должна. Если функция выполняет только те действия, которые находятся на одном уровне под объявленным именем функции, то эта функция выполняет одну операцию.
+
+### Функция не должна разбиваться на секции
+
+Функцию, выполняющую только одну операцию, невозможно осмысленно разделить на секции.
+
+### Разделение команд и запросов
+
+Функция должна что-то делать или отвечать на какой-то вопрос, но не одновременно. Либо функция изменяет состояние объекта, либо возвращает информацию об этом объекте. Совмещение двух операций часто создает путаницу.
+
+## Общие правила
+
+### Комментарии
+
+Закомметированный код запрещен.
+
+Запрещены комментарии отвечающие на вопрос "Что делает этот код?". Вместо этого используете методы для увеличения читаемости кода выше, чтобы код объяснял себя сам. Разрешены комментарии отвечающие на вопрос "Зачем этот код нужен?".
+
+Также можно писать комментарии типа "TODO" и "FIXME".
+
+### Не избавляйтесь от проверки типов
+
+Запрещено использовать оператор `:`. Всегда явно преобразуйте переменную к конкретному типу.
+
+Плохой пример:
+
+```DM
+var/something_general_object = ...
+something_general_object:specific_type_func()
+```
+
+Мы узнаем, что у something_general_object нет такой функции только когда запустим сервер. Более того, когда мы это узнаем - будет непонятным что это за тип, почему у него было этой функции и какой тип на самом деле ожидался. С другой стороны, в коде:
+
+```DM
+var/something_general_object
+var/more/specified/type/O = object
+ASSERT(istype(O)) // bad argument
+O.specific_type_funс()
+```
+
+Мы явно указываем какой тип мы ожидаем и статически проверяем, что у этого типа есть такая функция.
+
+### Используйте ASSERT для проверок аргументов функций
+
+Если ваша функция расчитана на то, чтобы работать только с аргументами определенного типа или какими-то конкретными значениями - используйте ASSERT, чтобы явно это указать. Если, вдруг, случится так, что откуда-то в функцию приедет неожиданный аргумент - лучше сразу зарепортить об этом рантаймом и выйти из функции, чем пойти дальше, сделать что-то неожиданное и, может быть, даже не оставить следа о том, что фукнция сработала не так, как нужно.
+
+Имейте в виду, что ASSERT генерирует рантайм, что ведет к выходу из функции с возвратом null. Функция выше по стеку продолжит свое выполнение с полученным null.
+
+Плохой пример:
+
+```DM
+/datum/controller/subsystem/open_space/proc/add_turf(turf/T, ...)
+ if(!isturf(T))
+ return
+ ...
+```
+
+Если мы в функцию `add_turf` передали аргументом не turf - это очевидный баг. С таким вариантом реализации этот баг останется незамеченным и он может привести к неожиданным проблемам. Но если мы напишем так:
+
+```DM
+/datum/controller/subsystem/open_space/proc/add_turf(turf/T, ...)
+ ASSERT(isturf(T))
+ ...
+```
+
+То мы все также выйдем из функции, вернув null, но при этом запишем рантайм, который мы увидим в логах, благодаря чему сможем обнаружить и пофиксить изначальный баг, который приводит к тому, что сюда аргументом передается неверный тип.
+
+### Пути типов всегда должны начинаться с /
+
+Например: `/datum/thing`, вместо `datum/thing`
+
+### Пути типов всегда должны быть в нижнем регистре
+
+Например: `/datum/thing/blue`, вместо `datum/thing/BLUE` или `datum/thing/Blue`
+
+### Использование ключевого слова `var`
+
+Локальные переменные всегда определяйте в формате `var/type/name`, вместо `var type/name`. В аргументах функций оно избыточно, поэтому всегда опускаем и пишем просто `type/name`.
+
+### Табы vs пробелы
+
+Для индентации (отступ до текущего блока кода) всегда используем только табы. После индентации может быть выравнивание пробелами (не выравнивайте табами - иначе разъедется в редакторах с разной длиной таба).
+
+### Избегайте дублирования кода
+
+Когда вы копируете один и тот же код в разные места - появляется необходимость одинаково поддерживать этот код в двух местах. При любом изменении оригинального кода надо помнить о копии этого кода в другом месте и зачастую вносить изменения и туда.
+
+Чтобы избежать подобных проблем используйте наследование объектов друг от друга или же просто вынесите необходимую логику в отдельную функцию.
+
+### Предпочитайте `Initialize()` вместо `New()` для atom (объектов, размещаемых на карте).
+
+Контроллеры, используемые в нашей сборке, должны справляться с длительными операциями и лагами, но они не могут контролировать то, что происходит во время загрузки карты, когда для всех объектов вызывается New. Для любых новых объектов, без явной необходимости, используйте `Initialize`, чтобы сделать все, что вы хотели сделать в `New`. Это уменьшает количество функций вызываемых на этапе загрузки карты. Чтобы узнать больше про то, как работает `Initialize`, смотрите [`code/game/atoms.dm`](https://github.com/SierraBay/SierraBay12/blob/dev-sierra/code/game/atoms.dm)
+
+**NB:** Важно понимать то, как работают Initialize и последовательность их выполнения относительно New, иначе можно [словить приколы](https://github.com/ChaoticOnyx/OnyxBay/issues/3817). Initialize вызывается в /atom/New. Соответственно при создании объекта сначала вызывается New последнего типа в иерархии наследования. Дальше через ..() (если они, конечно, есть) последовательно вызываются New более ранних по иерархии типов вплоть до /atom/New, где вызывается /atom/Initialize, который точно также начинает выполняться с последнего переопределения в иерархии наследования объектов.
+
+Таким образом получается, что в любом New, все что находится после ..() - вызывается после всех Initialize в цепочке наследования, а все что написано до ..() - работает перед всеми Initialize в цепочке наследования. Из этого следует, что если в вашей иерархии типов перемешиваются New и Initialize, то порядок их вызова может быть немного непредсказуемым, что вызывает приколы вроде перезаписи переменной, которая определяется в чайлдовом типе, в Initialize, значением из родителького объекта, из New. Пример и подробный разбор такой баги можно [посмотреть здесь](https://github.com/ChaoticOnyx/OnyxBay/issues/3817).
+
+Если вы не очень поняли что написано выше, то просто следуйте одному простому правилу:
+
+**Старайтесь не перемешивать New и Initialize в иерархиях объектов. Если меняете один New на Initialize - меняйте сразу всей иерархии. Если в иерархии используются New, то проще использовать New в новом объекте этой иерархии**, однако в плане производительности - лучше переписать всю иерархию на Initialize.
+
+### Удаление объектов
+
+Все объекты должны всегда помечаться к удалению с помощью `qdel`.
+
+Использование `del` запрещено, так как эта функция запускает довольно требовательную процедуру поиска и обнуления ссылок на объект по всему коду.
+
+Просто "бросать" объекты без вызова `qdel` не рекомендуется, так как в таком случае не будет вызван Destroy, который, даже если еще не реализован для конкретного объекта, кто-то может захотеть добавить позже.
+
+Отдельно важно помнить про случаи с циклическими зависимостями, когда объекты хранят ссылки друг на друга циклически. Если такие объекты просто пометить к удалению через `qdel`, то объекты никогда не будут удалены, так как сборщик мусора будет ждать, пока ссылки на объекты закончатся (а наши объекты циклически указывают друг на друга - поэтому ссылки никогда не закончатся). В таком случае нужно делать не только помечать объекты к удалению через `qdel`, но и занулять какую-то часть ссылок так, чтобы избавиться от цикла. С помощью макроса `QDEL_NULL(obj)` можно одной строчкой пометить `obj` к удалению и занулить ссылку.
+
+Пример реализации циклических объектов:
+
+```DM
+/mob/living/simple_animal
+ ...
+ var/datum/mob_ai/mob_ai
+
+/mob/living/simple_animal/Initialize()
+ . = ..()
+ mob_ai = new() // simple_mob создает и владеет своим искусственным интеллектом
+ mob_ai.holder = src // искусственному интеллекту нужно знать кем он управляет
+ // получили циклическую ссылку simple_mob <-> mob_ai объектов друг на друга
+
+/mob/living/simple_animal/Destroy()
+ QDEL_NULL(mob_ai) // когда моба удаляют с карты нам нужно разорвать циклическую ссылку. Убиваем ссылку на mob_ai.
+ // в итоге на mob_ai больше никто в мире не ссылается, сборщик мусора его собирает, а следом собирает simple_mob.
+ // the same as:
+ // qdel(mob_ai) // тоже самое, что делает макрос QDEL_NULL в раскрытом виде
+ // mob_ai = null
+
+ return ..()
+```
+
+### Перемещайте объекты с помощью forceMove(newpos)
+
+В отличие от `loc = newpos`, `forceMove` может переопределяться и в нем написано много важной логики, которая нужна при перемещении объектов из одного места в другое.
+
+### Не используйте магические значения
+
+В коде не должно быть "брошенных" числовых/строчных или каких-либо еще значений, смысл которых неясен из контекста или которые могут быть переиспользованы. Определяйте их через макрос, если они нужны в глобальном скоупе, или создавайте локальную переменную с понятным названием.
+
+Пример:
+
+```DM
+/datum/proc/do_the_thing(thing_to_do)
+ switch(thing_to_do)
+ if(1)
+ (...)
+ if(2)
+ (...)
+```
+
+Здесь неясно, что означают "1" и "2"! Вместо этого можно было бы написать:
+
+```DM
+#define DO_THE_THING_REALLY_HARD 1
+#define DO_THE_THING_EFFICIENTLY 2
+/datum/proc/do_the_thing(thing_to_do)
+ switch(thing_to_do)
+ if(DO_THE_THING_REALLY_HARD)
+ (...)
+ if(DO_THE_THING_EFFICIENTLY)
+ (...)
+```
+
+Так получается гораздо понятнее, что увеличивает читаемость вашего кода.
+
+Еще пример:
+
+```DM
+/datun/gamemode/proc/create_events()
+ events.Add(new /datum/event(90, list(/datum/role/hos)))
+ events.Add(new /datum/event(50, list(/datum/role/officer)))
+```
+
+В примере понятно, что мы задаем ивенты для какого-то игрового режима, однако непонятно что за аргументы передаются внутрь - это как раз "магические" значения. Чтобы сделать понятнее, можно, например, явно указать переменные, которые мы присваиваем:
+
+```DM
+/datun/gamemode/proc/create_events()
+ events.Add(new /datum/event(chance=90, requires_roles=list(/datum/role/hos)))
+ events.Add(new /datum/event(chance=50, requires_roles=list(/datum/role/officer)))
+```
+
+### Операторы контроля выполнения
+
+(if, while, for и другие)
+
+- Все операторы контроля выполнения не должны содержать другого кода на той же строчке (`if (blah) return`)
+- Все операторы контроля выполнения, сравнивающие переменную с каким-то значением, должны использовать формулу `переменная` `оператор` `значение`, не наоборот (например: `if (count <= 10)`, вместо `if (10 >= count)`)
+
+### Оператор in
+
+Оператор in имеет наименьший приоритет (это не указано в рефе), поэтому его всегда нужно заключать в скобки: `if(A && (A in foo_list))`.
+
+### Оператор as запрещено использовать в проках, не использующихся как verb
+
+Потому что не имеет смысла
+
+### Используйте макросы оформления текста SPAN_NOTICE, SPAN_WARNING, SPAN_DANGER и другие
+
+Вместо `to_chat(usr, "[text]")` используйте `to_chat(usr, SPAN_WARNING(text))`.
+
+### Используйте ранний return
+
+Не стоит строчить многоуровневые конструкции из блоков if, когда того же результата можно достичь ранним возвратом из функции
+
+Вот так делать не стоит:
+
+```DM
+/datum/datum1/proc/proc1()
+ if (thing1)
+ do stuff
+ if (!thing2)
+ do more stuff
+ if (thing3 == 30)
+ do extra stuff
+```
+
+А так уже лучше:
+
+```DM
+/datum/datum1/proc/proc1()
+ if (!thing1)
+ return
+ do stuff
+ if (thing2)
+ return
+ do more stuff
+ if (thing3 != 30)
+ return
+ do extra stuff
+```
+
+### TRUE/FALSE для логических значений
+
+Во всех случаях, когда нужно логическое значение, вместо 1/0 используйте TRUE/FALSE.
+
+### Global против static
+
+В DM есть ключевое слово global, которое используется в двух случаях:
+
+1. Внутри типа (или внутри функции) мы хотим определить переменную общую для всех объектов этого типа (для всех вызовов этой функции). (http://www.byond.com/docs/ref/#/var/global).
+2. Внутри функции мы хотим использовать переменную из глобального скоупа, когда у нас в локальном скоупе уже есть переменная с таким же названием. (http://www.byond.com/docs/ref/#/proc/var/global) Так использовать global не стоит! См. следующее правило.
+
+Так как в первом случае речь идет не о видимости переменной, а том, что она общая для разных экземпляров типа (вызовов функции), то название ключевого слова global может кого-то запутать. Поэтому вместо него в первом случае мы стараемся использовать ключевое слово static, которое отсутствует в документации, но полностью заменяет global в этом случае и лучше описывает суть происходящего.
+
+Отдельно обратите внимание, что все переменные в BYOND имеют глобальную видимость с точки зрения доступа, поэтому global (и static) в глобальном скоупе не имеет смысла и запрещено.
+
+### Избегайте глобальных переменных
+
+Главная проблема глобальных переменных в том, что чем больше они используются, тем сложнее понять логику их изменения и поведения процессов, которые от них зависят. Поэтому глобальные переменные лучше вовсе не использовать. Однако, если вам все-таки очень нужна глобальная переменная, то, как минимум, создавайте их на контроллере глобальных переменных, что даст чуть больше возможностей для их дебага:
+
+Там все просто - вместо глобальных перменных нужно всегда использовать макросы (ну и конечно же лучше всего вообще не использовать глобальные переменные). Пример с тг:
+
+Вместо:
+
+```DM
+var/X
+var/list/Y
+var/datum/genitalia/Z
+var/A = 42
+var/list/B = list(burn = "witch")
+var/datum/genitalia/C = MakeAPenis()
+var/hub_password
+```
+
+Используйте:
+
+```DM
+GLOBAL_VAR(X)
+GLOBAL_LIST(Y)
+GLOBAL_DATUM(Z, /datum/genitalia)
+GLOBAL_VAR_INIT(A, 42)
+GLOBAL_LIST_INIT(B, list(burn = "witch"))
+GLOBAL_DATUM_INIT(C, /datum/genitalia, MakeAPenis())
+GLOBAL_PROTECT(hub_password)
+```
+
+### Избегайте использования генераторов списков
+
+`range()`, `view()`, `hearers()`, `locate() [in world]`, `for(... [in world])` и подобные операции очень требовательные в плане производительности, поэтому должны импользоваться как можно реже. Зачастую их можно заменить глобальным списком объектов нужного типа.
+
+### alert() и input(): изменение контекста после вызова
+
+`alert()` и `input()` ждут реакции пользователя и пока они ждут, все окружение может поменяться: поля объекта, переменные вокруг и т.д. Учитывайте это и добавляйте дополнительные проверки по необходимости.
+
+### addtimer() вместо spawn()/sleep()
+
+Практика показывает, что `addtimer` лучше оптимизирован, чем встроенные spawn и sleep, плюс код, написанный с его использованием, гораздо проще дебажить.
+
+## Разработка безопасного кода
+
+- Всегда относитель к пользовательскому вводу так, как будто он намеренно пытается все сломать. Проверяйте пользовательский ввод на все случаи, которые не соответсвуют ожиданиям вашего кода. Для чисел проверяйте границы, для строк используйте escape-функции. Обратите внимание на функцию `sanitize`, которой удобно эскейпить вообще любой `input`, чтобы избежать ввод какого-то кода, который выполнится в браузере.
+- Обязательно эскейптьте все команды к базе данных - используйте `sql_query` чтобы обработать весь текст от игроков и админов перед тем как передавать его в базу данных. Для чисел используйте `isnum`.
+- Все вызовы топиков обязательно нужно проверять на их корректность. Такие вызовы могут быть подделаны со стороны клиента, поэтому их содержимым может быть что угодно!
+- Скрывайте от игроков любую информацию, которая может быть использована для метагейма (даже такую простую, как количество игроков, которые нажали Declare, так как даже она может быть использована, чтобы вычислить текущий режим).
+- Когда вы пишите код, который может каким-то образом влиять на раунд и генерировать _ВЕСЕЛЬЕ_, дважды проверьте, что такой функционал будет доступен только админам соответствующего ранга.
+- Не используйте `locate()` для глобального поиска экземпляра типа который может быть удалён в процессе игры. Эта функция может вернуть экземпляр который находится в процессе удаления но не до конца удалён. Вместо этого лучше держать список с ещё не удаленными объектами и искать в нём (`locate() in objects_list`).
+
+## Файлы
+
+- Рантаймы не содержат полного пути до файла - поэтому избегайте одинаковых названий файлов даже в разных папках.
+- Названия файлов не должны содержать пробелов или символов, которые придется эскейпить, указывая uri.
+- Названия файлов и все пути всегда должны быть в нижнем регистре, чтобы избежать проблем, связанных с разным отношением к регистру в разных операционных системах.
+
+## Фишки и лайфхаки Dream Maker
+
+Как и любые другие языки, в BYOND есть свои особенности, которые стоит учитывать, чтобы писать более эффективный код. Тут описаны некоторые из них.
+
+### In-To for-loops
+
+`for(var/i = 1, i <= some_value, i++)` является стандартным способом писать циклы во многих языках программирования, однако в BYOND, внезапно, `for(var/i in 1 to some_value)` оказывается быстрее в плане производительности. (Обратите внимание, что `to` включает и левую, и правую границу).
+
+ОДНАКО, если `some_value` или `i` меняются в течение цикла, или вы итерируете по элементам списка, длина которого изменяется, вы НЕ можете использовать этот тип цикла for.
+
+### "Объединение переменных" с помощью оператора ||
+
+Оператор (A || B) возвращает выражение A или B без изменений их значений. Этот факт можно использовать, чтобы кратким образом подставить B вместо нулевого (или ложного) A.
+
+Пример:
+
+```dm
+obj.name = some_name || "Unknown"
+```
+
+Этот код полностью аналогичен следующему:
+
+```dm
+if (some_name):
+ obj.name = some_name
+else
+ obj.name = "Unknown"
+```
+
+Если some_name - это имеет ложное значение, то obj.name будет присвоено значение "Unknown".
+
+Начиная с версии 514 в BYOND добавили сокращенную версию оператора: A ||= B.
+
+### "Краткое условное выражение" через оператор &&
+
+Аналогичным образом работает и оператор &&, благодаря чему можно писать условия в одну строчку. Такое сокращение не настолько полезно, как предыдущее, но тоже может использоваться в разных ситуациях.
+
+Пример:
+
+```dm
+var/obj/a = some_object
+istype(a) && a.foo()
+```
+
+Этот пример равнозначен коду:
+
+```dm
+var/obj/a = some_object
+if(istype(a))
+ a.foo()
+```
+
+Начиная с версии 514 в BYOND добавили сокращенную версию оператора: A &&= B.
+
+### Циклы с "as anything()" или "as()"
+
+По дефолту BYOND проверяет в рантайме тип элемента при итерации по списку:
+
+```dm
+for(var/obj/item/projectile/O in weapon.content)
+```
+
+В этом примере из списка `content` будут отобраны только объекты типа `/obj/item/projectile` (объекты других типов будут пропущены), но ради такого поведения BYOND будет неявно проверять тип в каждой итерации, что влияет на перфоманс. Обычно это не очень критично, но если цикл вызывается часто или в нем много элементов, то такие циклы есть смысл оптимизировать.
+
+**as anything()** позволяет избавиться от неявной проверки типа:
+
+```dm
+for(var/obj/item/projectile/O as anything() in weapon.content)
+```
+
+Теперь проверки на тип не будет и обрабатываться будут все объекты из списка. Если случайно в списке окажется объект не являющийся `/obj/item/projectile`, то если вы попытаетесь вызывать методы `/obj/item/projectile` у такого объекта, то вы получите runtime ошибку. Таким образом следить за типом объектов в списке нужно самостоятельно, но цикл будет работать быстрее.
+
+Заметим, что следующие циклы равнозначны по своей логике работы:
+
+```dm
+for(var/obj/item/projectile/O in weapon.content)
+ ...
+
+for(var/obj/item/projectile/O as anything() in weapon.content)
+ if(!istype(O)) // "лишняя" проверка, от которой можно избавиться,
+ // если мы знаем, что в weapon.content могут храниться только /obj/item/projectile
+ continue
+ ...
+```
+
+Также вместо **as anything()** можно писать просто **as()**.
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
index 8572bdcfb45b3..3a63329d2ebc2 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -1,67 +1,79 @@
-# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
-name: Bug Report
-description: File a bug report
+# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms
+#
+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: |
- Make sure to [search for an existing issue report](https://github.com/Baystation12/Baystation12/issues) before making a new one.
+ value: Спасибо за оставленный отчёт! Не забудьте дать ему соответствующее проблеме название для упрощения работы другим.
+
+ - type: input
+ id: version
+ attributes:
+ label: BYOND Version
+ description: "На какой версии BYOND встретился баг. (Если уверены, что не связано - можно пропустить.)"
- Your issue will be tagged by a developer with reproduction status and a priority once it has been reviewed and verified.
- type: textarea
- id: desc
+ id: description
attributes:
- label: Description of issue
- placeholder: The macguffin doesn't do anything when used.
+ label: Описание проблемы
+ description: В чем состоит суть проблемы? (Обязательное поле)
+ placeholder: Я сел на стул, от чего взорвался, а мой мозг оказался в душе на ЦК!
validations:
required: true
+
- type: textarea
- id: expected
+ id: what-expected
attributes:
- label: Difference between expected and actual behaviour
- placeholder: The macguffin should kill the big bad when I use it.
+ label: Что должно было произойти?
+ description: Почему вы считаете это проблемой?
+ placeholder: Я ожидал, что я просто сяду на стул и буду сидеть.
+
- type: textarea
- id: repro-steps
+ id: what-happened
attributes:
- label: Steps to reproduce
- description: Please provide a complete step-by-step method of reproducing the bug, including steps you may think are obvious. Also please make sure you have followed and verified these steps reproduce the bug yourself. Being verbose is better than being vague. If reproduction is unreliable, i.e., it only happens sometimes or only happened once, mention that in the reproduction steps.
- placeholder: |
- 1.
- 2.
- 3.
- ...
- validations:
- required: true
+ label: Что случилось вместо этого?
+ description: Как произошедшее отличается от ваших ожиданий?
+ placeholder: Я умер и оказался на ЦК.
+
- type: textarea
- id: paths
+ id: why-bad
attributes:
- label: Specific information for locating
- description: e.g. an object name, specific message outputs, object paths or file names if known, specific maps and/or coordinates, etc
- placeholder: |
- Item name: The MacGuffin
- Big bad name: Very Evil Mob
- Item path: `/obj/item/macguffin`
+ label: Почему это плохо/Какие последствия?
+ description: Почему вы считаете эту проблему значительной?
+ placeholder: Игроки должны иметь возможность сидеть на стульях, не взрываясь.
+
- type: textarea
- id: version
+ id: how-to-reproduce
attributes:
- label: Client version, server revision, & game ID
- description: This is found with the `Show server revision` verb in the OOC tab in game, or typing `show-server-revision` into the chat.
- placeholder: |
- Client Version: 514
- Server Revision: 34bf3ff6a39a09848d5552e0486c0f0fedb58fc9 - dev - 2022-08-06
- Game ID: cji-cImG
- Current map: SEV Torch
+ label: Шаги для повторения проблемы.
+ description: Самая важная часть. Опишите ВСЁ, что вы делали, что бы встретиться с проблемой. (Обязательное поле)
+ placeholder: Найди стул, сядь на него, пойми, что на стуле стояла бутылка. (Тру стори)
validations:
required: true
- - type: checkboxes
- id: issue-bingo
+
+ - 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: Issue bingo
- description: Check all that apply. Try to tick off all of these before you submit the report.
- options:
- - label: Issue could be reproduced at least once
- - label: Issue could be reproduced by different players
- - label: Issue could be reproduced in multiple rounds
- - label: Issue happened in a recent (less than 7 days ago) round
- - label: "[Couldn't find an existing issue about this](https://github.com/Baystation12/Baystation12/issues)"
+ label: Связанные логи
+ description: Пожалуйста, предоставьте связанные логи или рантаймы, если имеете к ним доступ(Лучше цензурить IP адреса).
+ render: DM
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a40ea100ba345..1a3d90e0c33d0 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,16 +1,49 @@
-
\ No newline at end of file
+
+
+
+## Что этот PR делает
+
+
+
+
+
+## Почему это хорошо для игры
+
+
+
+## Изображения изменений
+
+Скриншоты
+
+
+
+
+Видео
+
+
+
+
+
+## Тестирование
+
+
+
+## Changelog
+
+:cl:
+add: Что-то добавил
+del: Что-то удалил
+tweak: Поменял что-то по мелочи
+fix: Что-то починил
+wip: Какие-либо наработки в процессе
+soundadd: Добавил новый звук
+sounddel: Удалил старый звук
+imageadd: Добавил новую картинку
+imagedel: Удалил старую картинку
+spellcheck: Исправил опечатку
+experiment: Добавил эксперементальную функцию
+/:cl:
+
+
+
+
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 3f6e234e60bf2..4ac3104801458 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,27 +1,61 @@
-Repo:
-- .github/**/*
-- .vscode/**/*
-- docs/**/*
-- scripts/**/*
-- test/**/*
-- tools/**/*
-- .codebeatignore
-- .editorconfig
-- .git*
-- LICENSE
-- README.md
-- SpacemanDMM.toml
+':guitar: Инструменты':
+- changed-files:
+ - any-glob-to-any-file:
+ - '.github/**/*'
+ - '.vscode/**/*'
+ - 'docs/**/*'
+ - 'scripts/**/*'
+ - 'test/**/*'
+ - 'tools/**/*'
+ - '.codebeatignore'
+ - '.codebeatsettings'
+ - '.editorconfig'
+ - '.git*'
+ - 'biome.json'
+ - 'libmysql.dll'
+ - 'LICENSE'
+ - 'README.md'
+ - 'SpacemanDMM.toml'
-Map:
-- '**/*.dmm'
+':world_map: Изменение Карты':
+- changed-files:
+ - any-glob-to-any-file:
+ - '**/*.dmm'
-Sprites:
-- '**/*.dmi'
+':rice: Спрайты':
+- changed-files:
+ - any-glob-to-any-file:
+ - '**/*.dmi'
-Sound:
-- '**/*.ogg'
-- '**/*.wav'
-- '**/*.mp3'
+':sound: Звуки':
+- changed-files:
+ - any-glob-to-any-file:
+ - '**/*.aif'
+ - '**/*.aiff'
+ - '**/*.it'
+ - '**/*.mid'
+ - '**/*.midi'
+ - '**/*.mod'
+ - '**/*.mp3'
+ - '**/*.ogg'
+ - '**/*.oxm'
+ - '**/*.raw'
+ - '**/*.s3m'
+ - '**/*.wma'
+ - '**/*.wav'
+ - '**/*.xm'
-Config Update:
-- config/example/**/*
+':page_facing_up: Требуется изменение конфига':
+- changed-files:
+ - any-glob-to-any-file:
+ - 'config/example/**/*'
+
+'SQL':
+- changed-files:
+ - any-glob-to-any-file:
+ - 'sql/**/*'
+
+'TGUI':
+- changed-files:
+ - any-glob-to-any-file:
+ - 'tgui/**'
diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml
index f352979eba9c7..bf11bf4ea04d0 100644
--- a/.github/workflows/cancel.yml
+++ b/.github/workflows/cancel.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- - uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5
+ - uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa
with:
workflow_id: 3545321
access_token: ${{ github.token }}
diff --git a/.github/workflows/changelog_generation.yml b/.github/workflows/changelog_generation.yml
deleted file mode 100644
index 86daaee0b2397..0000000000000
--- a/.github/workflows/changelog_generation.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Compile changelogs
-
-permissions:
- contents: read
-
-on:
- schedule:
- - cron: "0 0 * * *"
- workflow_dispatch: # allows this workflow to be manually triggered
-
-jobs:
- CompileCL:
- permissions:
- contents: write # required to push the updated changelog commit
- runs-on: ubuntu-latest
- if: github.repository == 'Baystation12/Baystation12' # to prevent this running on forks
- steps:
- - name: Checkout
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- with:
- fetch-depth: 0 # Otherwise, we will fail to push refs
- ref: dev
- token: ${{ secrets.BOT_TOKEN }}
- - name: Python setup
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435
- with:
- python-version: '3.x'
- - name: Install depends
- run: |
- python -m pip install --upgrade pip
- pip install pyyaml bs4
- - name: Compile CL
- run: |
- python tools/changelog/ss13_genchangelog.py html/changelog.html html/changelogs
- - name: Commit And Push
- run: |
- git config --global user.email "${{ secrets.BOT_EMAIL }}"
- git config --global user.name "${{ secrets.BOT_NAME }}"
- git diff --quiet --exit-code && echo "No changes found, abort." && exit 0
- git commit -m "Automatic changelog generation [ci skip]" -a
- git push
diff --git a/.github/workflows/check_changelog.yml b/.github/workflows/check_changelog.yml
new file mode 100644
index 0000000000000..09c480e92cfdd
--- /dev/null
+++ b/.github/workflows/check_changelog.yml
@@ -0,0 +1,32 @@
+name: Проверка Ченджлога
+
+permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+
+on:
+ pull_request_target:
+ types: [opened, reopened, edited]
+
+jobs:
+ CheckCL:
+ runs-on: ubuntu-latest
+ if: github.repository == 'ss220club/WyccerraBay220' && github.base_ref == 'master' && github.event.pull_request.draft == false
+ steps:
+ - name: Скачивание файлов
+ run: |
+ wget https://raw.githubusercontent.com/ss220club/WyccerraBay220/master/tools/changelog/sierra_check_changelog.py
+ wget https://raw.githubusercontent.com/ss220club/WyccerraBay220/master/tools/changelog/tags.yml
+ - name: Установка Python
+ uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c
+ with:
+ python-version: '3.x'
+ - name: Установка зависимостей
+ run: |
+ python -m pip install --upgrade pip
+ pip install ruamel.yaml PyGithub
+ - name: Проверка чейнджлога
+ env:
+ BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
+ run: python sierra_check_changelog.py
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
new file mode 100644
index 0000000000000..e7c359aa58260
--- /dev/null
+++ b/.github/workflows/ci_suite.yml
@@ -0,0 +1,160 @@
+name: CI Suite
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+ merge_group:
+ branches:
+ - master
+jobs:
+ run_linters:
+ if: ( !contains(github.event.head_commit.message, '[ci skip]') )
+ name: Run Linters
+ runs-on: ubuntu-22.04
+ concurrency:
+ group: run_linters-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+ steps:
+ - uses: actions/checkout@v4
+ - name: Restore SpacemanDMM cache
+ uses: actions/cache@v4
+ with:
+ path: ~/SpacemanDMM
+ key: ${{ runner.os }}-spacemandmm-${{ hashFiles('dependencies.sh') }}
+ restore-keys: |
+ ${{ runner.os }}-spacemandmm-
+ - name: Restore Yarn cache
+ uses: actions/cache@v4
+ with:
+ path: tgui/.yarn/cache
+ key: ${{ runner.os }}-yarn-${{ hashFiles('tgui/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+ - name: Restore Node cache
+ uses: actions/cache@v4
+ with:
+ path: ~/.nvm
+ key: ${{ runner.os }}-node-${{ hashFiles('dependencies.sh') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+ - name: Restore Bootstrap cache
+ uses: actions/cache@v4
+ with:
+ path: tools/bootstrap/.cache
+ key: ${{ runner.os }}-bootstrap-${{ hashFiles('tools/requirements.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-bootstrap-
+ - name: Restore Rust cache
+ uses: actions/cache@v4
+ with:
+ path: ~/.cargo
+ key: ${{ runner.os }}-rust-${{ hashFiles('tools/ci/ci_dependencies.sh')}}
+ restore-keys: |
+ ${{ runner.os }}-rust-
+
+
+ - name: Install Tools
+ run: |
+ pip3 install setuptools
+ bash tools/ci/install_node.sh
+ bash tools/ci/install_spaceman_dmm.sh dreamchecker
+ bash tools/ci/install_ripgrep.sh
+ tools/bootstrap/python -c ''
+ - name: Give Linters A Go
+ id: linter-setup
+ run: ':'
+ - name: Run Grep Checks
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: bash tools/ci/check_grep.sh
+ - name: Run Old Grep Checks
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: bash tools/ci/check_grep_bay.sh
+ # Enable if we decide to undef everything(we wont)
+ # - name: Check Define Sanity
+ # if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ # run: tools/bootstrap/python -m define_sanity.check
+ # Enable when we have traits
+ # - name: Check Trait Validity
+ # if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ # run: tools/bootstrap/python -m trait_validity.check
+ - name: Run DreamChecker
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ shell: bash
+ run: ~/dreamchecker 2>&1 | bash tools/ci/annotate_dm.sh
+ - name: Run Map Checks
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: |
+ tools/bootstrap/python -m mapmerge2.dmm_test
+ # Enable when our mappers define the rules(in a hour)
+ # tools/bootstrap/python -m tools.maplint.source
+ - name: Run DMI Tests
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: tools/bootstrap/python -m dmi.test
+ - name: Check File Directories
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: bash tools/ci/check_filedirs.sh baystation12.dme
+
+ - name: Check Miscellaneous Files
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: bash tools/ci/check_misc.sh
+ - name: Run TGUI Checks
+ if: steps.linter-setup.conclusion == 'success' && !cancelled()
+ run: tools/build/build --ci lint tgui-test
+
+ compile_all_maps:
+ if: ( !contains(github.event.head_commit.message, '[ci skip]') )
+ name: Compile Maps
+ needs: [collect_data]
+ runs-on: ubuntu-22.04
+ concurrency:
+ group: compile_all_maps-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+ steps:
+ - uses: actions/checkout@v4
+ - name: Restore BYOND cache
+ uses: actions/cache@v4
+ with:
+ path: ~/BYOND
+ key: ${{ runner.os }}-byond
+ - name: Compile All Maps
+ run: |
+ bash tools/ci/install_byond.sh
+ source $HOME/BYOND/byond/bin/byondsetup
+ tools/build/build --ci dm -DCIBUILDING -DCITESTING -DALL_MAPS
+
+ collect_data:
+ if: ( !contains(github.event.head_commit.message, '[ci skip]') )
+ name: Collect data for other tasks
+ runs-on: ubuntu-22.04
+ outputs:
+ maps: ${{ steps.map_finder.outputs.maps }}
+ concurrency:
+ group: find_all_maps-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+ steps:
+ - uses: actions/checkout@v4
+ - name: Find Maps
+ id: map_finder
+ run: |
+ echo "$(ls -mw0 maps/*.json)" > maps_output.txt
+ sed -i -e s+maps/+\"+g -e s+.json+\"+g maps_output.txt
+ echo "Maps: $(cat maps_output.txt)"
+ echo "maps={\"paths\":[$(cat maps_output.txt)]}" >> $GITHUB_OUTPUT
+
+ run_all_tests:
+ if: ( !contains(github.event.head_commit.message, '[ci skip]') )
+ name: Integration Tests
+ needs: [collect_data]
+ strategy:
+ fail-fast: false
+ matrix:
+ map: ${{ fromJSON(needs.collect_data.outputs.maps).paths }}
+ concurrency:
+ group: run_all_tests-${{ github.head_ref || github.run_id }}-${{ matrix.map }}
+ cancel-in-progress: true
+ uses: ./.github/workflows/run_integration_tests.yml
+ with:
+ map: ${{ matrix.map }}
diff --git a/.github/workflows/close_stale.yml b/.github/workflows/close_stale.yml
index 22a63484590f5..44d61bf6ae776 100644
--- a/.github/workflows/close_stale.yml
+++ b/.github/workflows/close_stale.yml
@@ -14,9 +14,9 @@ jobs:
issues: write # required to close stale issues
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v7
+ - uses: actions/stale@v9
with:
- stale-issue-message: 'This issue has not been updated in 30 days, and will close in 5 days from now if no updates are made.'
+ stale-issue-message: 'В этом ишью ничего не происходило ничего в течение 30 дней и он будет закрыт через 5 дней, если обновлений не произойдёт.'
days-before-stale: 30
days-before-close: 5
days-before-pr-stale: -1
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index d3bfe257f79e5..7ec40495ee238 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -16,7 +16,7 @@ permissions:
on:
pull_request:
- branches: [ "dev" ]
+ branches: [ "master" ]
paths: [ "tools/**", "nano/**" ]
schedule:
- cron: '45 15 * * 4'
@@ -39,11 +39,11 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
config-file: ./.github/workflow-config/codeql.yml
languages: ${{ matrix.language }}
@@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -71,4 +71,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml
index 6c4baec885593..951a41a4acb3d 100644
--- a/.github/workflows/generate_documentation.yml
+++ b/.github/workflows/generate_documentation.yml
@@ -6,20 +6,20 @@ permissions:
on:
push:
branches:
- - dev
+ - master
workflow_dispatch: # allows this workflow to be manually triggered
env:
- SPACEMAN_DMM_VERSION: suite-1.7.2
+ SPACEMAN_DMM_VERSION: suite-1.7.3
jobs:
PreFlight:
runs-on: ubuntu-latest
steps:
- name: checkout
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: paths-filter
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50
+ uses: dorny/paths-filter@ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a
id: filter
with:
filters: .github/workflow-config/preflight-filters.yml
@@ -37,11 +37,11 @@ jobs:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
+ uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
with:
- path: $HOME/spaceman_dmm/$SPACEMAN_DMM_VERSION
+ path: ~/spaceman_dmm/${{ env.SPACEMAN_DMM_VERSION }}
key: ${{ runner.os }}-spacemandmm-${{ env.SPACEMAN_DMM_VERSION }}
- name: Install dmdoc
run: scripts/install-spaceman-dmm.sh dmdoc
@@ -49,10 +49,10 @@ jobs:
run: |
~/dmdoc
- name: Deploy
- uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6
+ uses: JamesIves/github-pages-deploy-action@65b5dfd4f5bcd3a7403bbc2959c144256167464e
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
folder: dmdoc
- target-folder: docs/dmdoc
+ target-folder: docs
clean: true
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 9a5741dcce09b..217c9c247a8ff 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -6,7 +6,7 @@ permissions:
on:
pull_request_target:
branches:
- - dev
+ - master
jobs:
triage:
@@ -15,7 +15,7 @@ jobs:
pull-requests: write # required to apply labels to PRs
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v4
+ - uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
diff --git a/.github/workflows/make_changelogs.yml b/.github/workflows/make_changelogs.yml
deleted file mode 100644
index 5f86a30a7e6f6..0000000000000
--- a/.github/workflows/make_changelogs.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: Make changelogs
-
-permissions:
- contents: read
-
-on:
- push:
- branches:
- - dev
-
-jobs:
- MakeCL:
- permissions:
- contents: write # required to push the changelog chunk yml commit
- runs-on: ubuntu-latest
- if: github.repository == 'Baystation12/Baystation12' # to prevent this running on forks
- steps:
- - name: Checkout
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- with:
- fetch-depth: 25
- - name: Python setup
- uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435
- with:
- python-version: '3.x'
- - name: Install depends
- run: |
- python -m pip install --upgrade pip
- pip install ruamel.yaml PyGithub
- - name: Make CL
- env:
- BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
- GIT_EMAIL: "${{ secrets.BOT_EMAIL }}"
- GIT_NAME: "${{ secrets.BOT_NAME }}"
- run: python tools/changelog/generate_cl.py
diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml
new file mode 100644
index 0000000000000..357d93951afc3
--- /dev/null
+++ b/.github/workflows/run_integration_tests.yml
@@ -0,0 +1,59 @@
+# This is a reusable workflow to run integration tests on a single map.
+# This is run for every single map in ci_suite.yml. You might want to edit that instead.
+name: Run Integration Tests
+on:
+ workflow_call:
+ inputs:
+ map:
+ required: true
+ type: string
+ major:
+ required: false
+ type: string
+ minor:
+ required: false
+ type: string
+jobs:
+ run_integration_tests:
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:latest
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v4
+ - name: Restore BYOND cache
+ uses: actions/cache@v4
+ with:
+ path: ~/BYOND
+ key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }}
+ - name: Setup database
+ run: |
+ sudo systemctl start mysql
+ mysql -u root -proot -e 'CREATE DATABASE test;'
+ mysql -u root -proot test < sql/schema.sql
+ - name: Install rust-g
+ run: |
+ bash tools/ci/install_rust_g.sh
+ - name: Install auxlua
+ run: |
+ bash tools/ci/install_auxlua.sh
+ - name: Configure version
+ run: |
+ echo "BYOND_MAJOR=${{ inputs.major }}" >> $GITHUB_ENV
+ echo "BYOND_MINOR=${{ inputs.minor }}" >> $GITHUB_ENV
+ if: ${{ inputs.major }}
+ - name: Compile Tests
+ id: compile_tests
+ run: |
+ bash tools/ci/install_byond.sh
+ source $HOME/BYOND/byond/bin/byondsetup
+ tools/build/build --ci build -DCIBUILDING -DUNIT_TEST_COLOURED -WError -NWTG0001
+ - name: Run Tests
+ run: |
+ source $HOME/BYOND/byond/bin/byondsetup
+ bash tools/ci/run_server.sh ${{ inputs.map }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index e614b044c7641..0000000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,171 +0,0 @@
-name: Run Tests
-
-permissions:
- contents: read
-
-on:
- push:
- branches: [ "dev" ]
- pull_request:
- branches: [ "dev" ]
-env:
- BYOND_MAJOR: "514"
- BYOND_MINOR: "1589"
- SPACEMAN_DMM_VERSION: suite-1.7.2
-
-jobs:
- PreFlight:
- runs-on: ubuntu-latest
- steps:
- - name: checkout
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: paths-filter
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50
- id: filter
- with:
- filters: .github/workflow-config/preflight-filters.yml
- outputs:
- dm: ${{ steps.filter.outputs.dm }}
- DreamChecker:
- runs-on: ubuntu-latest
- needs: PreFlight
- if: needs.PreFlight.outputs.dm == 'true'
- steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
- with:
- path: $HOME/spaceman_dmm/$SPACEMAN_DMM_VERSION
- key: ${{ runner.os }}-spacemandmm-${{ env.SPACEMAN_DMM_VERSION }}
- - name: Install Dreamchecker
- run: scripts/install-spaceman-dmm.sh dreamchecker
- - name: Run Dreamchecker
- run: ~/dreamchecker
- - name: Run Failure Webhook
- env:
- JOB_STATUS: ${{ job.status }}
- WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- HOOK_OS_NAME: ${{ runner.os }}
- WORKFLOW_NAME: ${{ github.workflow }}
- if: ${{ failure() }}
- run: |
- wget https://raw.githubusercontent.com/DiscordHooks/github-actions-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
- Code:
- runs-on: ubuntu-latest
- needs:
- - PreFlight
- - DreamChecker
- if: needs.PreFlight.outputs.dm == 'true'
- steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
- with:
- path: $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
- key: ${{ runner.os }}-byond-${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}
- - name: Install Dependencies
- run: sudo apt-get install -y uchardet
- - name: Run Tests
- env:
- TEST: CODE
- run: test/run-test.sh
- - name: Run Failure Webhook
- env:
- JOB_STATUS: ${{ job.status }}
- WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- HOOK_OS_NAME: ${{ runner.os }}
- WORKFLOW_NAME: ${{ github.workflow }}
- if: ${{ failure() }}
- run: |
- wget https://raw.githubusercontent.com/DiscordHooks/github-actions-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
- ExampleMap:
- runs-on: ubuntu-latest
- needs:
- - PreFlight
- - DreamChecker
- if: needs.PreFlight.outputs.dm == 'true'
- steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
- with:
- path: $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
- key: ${{ runner.os }}-byond-${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}
- - name: Run Tests
- env:
- TEST: MAP
- MAP_PATH: example
- run: test/run-test.sh
- - name: Run Failure Webhook
- env:
- JOB_STATUS: ${{ job.status }}
- WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- HOOK_OS_NAME: ${{ runner.os }}
- WORKFLOW_NAME: ${{ github.workflow }}
- if: ${{ failure() }}
- run: |
- wget https://raw.githubusercontent.com/DiscordHooks/github-actions-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
- TorchMap:
- runs-on: ubuntu-latest
- needs:
- - PreFlight
- - DreamChecker
- if: needs.PreFlight.outputs.dm == 'true'
- steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
- with:
- path: $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
- key: ${{ runner.os }}-byond-${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}
- - name: Run Tests
- env:
- TEST: MAP
- MAP_PATH: torch
- run: test/run-test.sh
- - name: Run Failure Webhook
- env:
- JOB_STATUS: ${{ job.status }}
- WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- HOOK_OS_NAME: ${{ runner.os }}
- WORKFLOW_NAME: ${{ github.workflow }}
- if: ${{ failure() }}
- run: |
- wget https://raw.githubusercontent.com/DiscordHooks/github-actions-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
- AwaySites:
- runs-on: ubuntu-latest
- needs:
- - PreFlight
- - DreamChecker
- if: needs.PreFlight.outputs.dm == 'true'
- steps:
- - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- - name: Setup Cache
- uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812
- with:
- path: $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
- key: ${{ runner.os }}-byond-${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}
- - name: Run Tests
- env:
- TEST: MAP
- MAP_PATH: away_sites_testing
- run: test/run-test.sh
- - name: Run Failure Webhook
- env:
- JOB_STATUS: ${{ job.status }}
- WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- HOOK_OS_NAME: ${{ runner.os }}
- WORKFLOW_NAME: ${{ github.workflow }}
- if: ${{ failure() }}
- run: |
- wget https://raw.githubusercontent.com/DiscordHooks/github-actions-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
diff --git a/.github/workflows/testmerge-blocker.yml b/.github/workflows/testmerge-blocker.yml
new file mode 100644
index 0000000000000..ad49f5e998116
--- /dev/null
+++ b/.github/workflows/testmerge-blocker.yml
@@ -0,0 +1,17 @@
+name: "Test Merge Blocker"
+
+on:
+ pull_request:
+ types: [synchronize, opened, labeled, unlabeled]
+
+jobs:
+ testmerge-blocker:
+ name: Enforce Test Merge Label
+ runs-on: ubuntu-latest
+ steps:
+ - name: Enforce Test Merge Label
+ if: contains(github.event.pull_request.labels.*.name, 'Test Merge') && !contains(github.event.pull_request.labels.*.name, 'Test Merge Passed')
+ run: |
+ echo "Pull request is labeled for Test Merge and has not been flagged as Test Merge Passed."
+ echo "The test merge must pass, or the label removed, before this PR can be merged."
+ exit 1
diff --git a/.gitignore b/.gitignore
index 4023ef50926eb..1781aa64ce3c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,58 +1,58 @@
-# ignore misc BYOND files
-Thumbs.db
-*.log
-*.int
-*.rsc
-*.dmb
-*.lk
-*.backup
-*.before
-data/
-dmdoc/
-cfg/
-build_log.txt
-use_map
-stopserver
-reboot_called
-atupdate
-
-# ignore config, but not subdirs
-!config/*/
-config/*
-sql/test_db
-
-# VisualStudioCode
-.vscode/*
-!.vscode/settings.json
-*.code-workspace
-.history
-
-# swap
-[._]*.s[a-v][a-z]
-[._]*.sw[a-p]
-[._]s[a-v][a-z]
-[._]sw[a-p]
-
-# session
-Session.vim
-
-# temporary
-.netrwhist
-*~
-
-# auto-generated tag files
-tags
-
-# ignore built libs
-lib/*.dll
-lib/*.so
-
-# python
-*.pyc
-__pycache__
-
-node_modules/
-package-lock.json
-
-# ignore local selected map
-/maps/using.dm
+# ignore misc BYOND files
+Thumbs.db
+*.log
+*.int
+*.rsc
+*.dmb
+*.lk
+*.backup
+*.before
+data/
+dmdoc/
+cfg/
+build_log.txt
+use_map
+stopserver
+reboot_called
+atupdate
+
+# ignore config, but not subdirs
+!config/*/
+config/*
+sql/test_db
+
+# VisualStudioCode
+.vscode/*
+!.vscode/settings.json
+*.code-workspace
+.history
+
+# swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-v][a-z]
+[._]sw[a-p]
+
+# session
+Session.vim
+
+# temporary
+.netrwhist
+*~
+
+# auto-generated tag files
+tags
+
+# ignore built libs
+lib/*.dll
+lib/*.so
+
+# python
+*.pyc
+__pycache__
+
+node_modules/
+package-lock.json
+
+# ignore local selected map
+/maps/using.dm
diff --git a/.tgs.yml b/.tgs.yml
new file mode 100644
index 0000000000000..880ca67a61b75
--- /dev/null
+++ b/.tgs.yml
@@ -0,0 +1,25 @@
+# This file is used by TGS (https://github.com/tgstation/tgstation-server) clients to quickly initialize a server instance for the codebase
+# The format isn't documented anywhere but hopefully we never have to change it. If there are questions, contact the TGS maintainer Cyberboss/@Dominion#0444
+version: 1
+# The BYOND version to use (kept in sync with dependencies.sh by the "TGS Test Suite" CI job)
+# Must be interpreted as a string, keep quoted
+byond: "515.1630"
+# Folders to create in "/Configuration/GameStaticFiles/"
+static_files:
+ # Config directory should be static
+ - name: config
+ # This implies the folder should be pre-populated with contents from the repo
+ populate: true
+ # Data directory must be static
+ - name: data
+# String dictionary. The value is the location of the file in the repo to upload to TGS. The key is the name of the file to upload to "/Configuration/EventScripts/"
+# This one is for Linux hosted servers
+linux_scripts:
+ PreCompile.sh: tools/tgs_scripts/PreCompile.sh
+ WatchdogLaunch.sh: tools/tgs_scripts/WatchdogLaunch.sh
+ InstallDeps.sh: tools/tgs_scripts/InstallDeps.sh
+# Same as above for Windows hosted servers
+windows_scripts:
+ PreCompile.bat: tools/tgs_scripts/PreCompile.bat
+# The security level the game should be run at
+security: Trusted
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 1a2a65362d883..e74872710d075 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,8 +1,10 @@
-{
- "recommendations": [
- "gbasood.byond-dm-language-support",
- "platymuus.dm-langclient",
- "rome.rome",
- "eamodio.gitlens"
- ]
-}
+{
+ "recommendations": [
+ "gbasood.byond-dm-language-support",
+ "platymuus.dm-langclient",
+ "eamodio.gitlens",
+ "anturk.dmi-editor",
+ "stylemistake.auto-comment-blocks",
+ "esbenp.prettier-vscode"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000..42a293a333805
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,32 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "byond",
+ "request": "launch",
+ "name": "Launch DreamSeeker",
+ "preLaunchTask": "Build All",
+ "dmb": "${workspaceFolder}/${command:CurrentDMB}"
+ },
+ {
+ "type": "byond",
+ "request": "launch",
+ "name": "Launch DreamDaemon",
+ "preLaunchTask": "Build All",
+ "dmb": "${workspaceFolder}/${command:CurrentDMB}",
+ "dreamDaemon": true
+ },
+ {
+ "name": "Debug External Libraries",
+ "type": "cppvsdbg",
+ "request": "launch",
+ "program": "${command:dreammaker.returnDreamDaemonPath}",
+ "cwd": "${workspaceRoot}",
+ "args": [
+ "${command:dreammaker.getFilenameDmb}",
+ "-trusted"
+ ],
+ "preLaunchTask": "Build All"
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d6d788f7b6dfe..966a809e61786 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,19 @@
{
+ "eslint.nodePath": "./tgui/.yarn/sdks",
+ "eslint.workingDirectories": ["./tgui"],
+ "prettier.prettierPath": "./tgui/.yarn/sdks/prettier/index.cjs",
+ "typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+
+ "search.exclude": {
+ "**/.yarn": true,
+ "**/.pnp.*": true
+ },
+
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+
"files.eol": "\n",
"files.encoding": "utf8",
"files.insertFinalNewline": true,
@@ -6,24 +21,47 @@
"files.trimTrailingWhitespace": true,
"files.associations": {
- "*.{dme,dmf,dmm,dm}": "dm",
+ "*.{dme,dmf,dmm,dm}": "dm"
},
+
"[dm]": {
"files.eol": "\r\n",
"editor.detectIndentation": false,
"editor.insertSpaces": false
},
-
+ "[javascript]": {
+ "editor.rulers": [80],
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[javascriptreact]": {
+ "editor.rulers": [80],
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[typescript]": {
+ "editor.rulers": [80],
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[typescriptreact]": {
+ "editor.rulers": [80],
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[scss]": {
+ "editor.rulers": [80],
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
"[markdown]": {
"files.trimTrailingWhitespace": false
},
-
"[python]": {
"editor.detectIndentation": false,
"editor.insertSpaces": true,
"editor.tabSize": 4
},
-
"[yaml]": {
"editor.detectIndentation": false,
"editor.insertSpaces": true,
@@ -31,7 +69,7 @@
},
"workbench.editorAssociations": {
- "*.dmi": "imagePreview.previewEditor"
+ "*.dmi": "dmiEditor.dmiEditor"
},
"debug.onTaskErrors": "abort",
@@ -41,6 +79,7 @@
"gitlens.advanced.blame.customArguments": [
"--ignore-revs-file", ".git-blame-ignore-revs"
-],
-"diffEditor.ignoreTrimWhitespace": false
+ ],
+
+ "diffEditor.ignoreTrimWhitespace": false
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000000..e3061b165a862
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,107 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "process",
+ "command": "tools/build/build",
+ "windows": {
+ "command": ".\\tools\\build\\build.bat"
+ },
+ "options": {
+ "env": {
+ "DM_EXE": "${config:dreammaker.byondPath}"
+ }
+ },
+ "problemMatcher": [
+ "$dreammaker",
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "dependsOn": "dm: reparse",
+ "label": "Build All"
+ },
+ {
+ "type": "dreammaker",
+ "dme": "baystation12.dme",
+ "problemMatcher": [
+ "$dreammaker"
+ ],
+ "group": "build",
+ "label": "dm: build - baystation12.dme"
+ },
+ {
+ "command": "${command:dreammaker.reparse}",
+ "group": "build",
+ "label": "dm: reparse"
+ },
+ {
+ "type": "shell",
+ "command": "bin/tgui-build",
+ "windows": {
+ "command": ".\\bin\\tgui-build.cmd"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: build"
+ },
+ {
+ "type": "shell",
+ "command": "bin/tgui-dev",
+ "windows": {
+ "command": ".\\bin\\tgui-dev.cmd"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: dev server"
+ },
+ {
+ "type": "shell",
+ "command": "bin/tgui-bench",
+ "windows": {
+ "command": ".\\bin\\tgui-bench.cmd"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: bench"
+ },
+ {
+ "type": "shell",
+ "command": "bin/tgui-sonar",
+ "windows": {
+ "command": ".\\bin\\tgui-sonar.cmd"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: sonar"
+ },
+ {
+ "type": "shell",
+ "command": "bin/tgfont",
+ "windows": {
+ "command": ".\\bin\\tgfont.cmd"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: rebuild tgfont"
+ }
+ ]
+}
diff --git a/BUILD.bat b/BUILD.bat
new file mode 100644
index 0000000000000..b965bcb95bc52
--- /dev/null
+++ b/BUILD.bat
@@ -0,0 +1,2 @@
+@echo off
+"%~dp0\tools\build\build.bat" --wait-on-error build %*
diff --git a/README.md b/README.md
index dd5d959397695..5cf866f6eb276 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,55 @@
-# Baystation [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3.0-orange.svg)](https://opensource.org/licenses/AGPL-3.0) [![CodeQL](https://github.com/baystation12/baystation12/workflows/CodeQL/badge.svg)](https://github.com/Baystation12/Baystation12/actions/workflows/codeql-analysis.yml) [![CI Status](https://github.com/baystation12/baystation12/workflows/Run%20Tests/badge.svg)](https://github.com/Baystation12/Baystation12/actions/workflows/test.yml) [![codebeat badge](https://codebeat.co/badges/8ecb9a34-1bab-4d80-b34d-b16e8b216a03)](https://codebeat.co/projects/github-com-baystation12-baystation12-dev)
-
-[Website](https://bay.ss13.me) - [Discord](https://bay.ss13.me/discord) - [Code](https://bay.ss13.me/github) - [DMDoc](https://baystation.xyz/dmdoc)
-
----
-
-### CONTRIBUTING GUIDELINES
-
-**Please refrain from making non code related comments in pull requests on GitHub.** See the Code of Conduct on where you may provide feedback to users.
-
-All users are expected to review [/docs/CODE_OF_CONDUCT.md](/docs/CODE_OF_CONDUCT.md) before interacting with the repository or other users.
-
----
-
-### SECURITY
-
-Please see [/docs/SECURITY.md](/docs/SECURITY.md) for this repository's security policy, and how to report security issues.
-
----
-
-### LICENSE
-
-The code for Baystation is licensed under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl.html), which can be found in full in [/LICENSE](/LICENSE).
-
-Code with a git authorship date prior to `1420675200 +0000` (2015/01/08 00:00 GMT) is licensed under the GNU General Public License version 3, which can be found in full in [/docs/GPL3.txt](/docs/GPL3.txt)
-
-All code where the authorship dates on or after `1420675200 +0000` is assumed to be licensed under AGPL v3, if you wish to license under GPL v3 please make this clear in the commit message and any added files.
-
-If you wish to develop and host this codebase in a closed source manner you may use all commits prior to `1420675200 +0000`, which are licensed under GPL v3. The major change here is that if you host a server using any code licensed under AGPLv3 you are required to provide full source code for your servers users as well including addons and modifications you have made.
-
-See [here](https://www.gnu.org/licenses/why-affero-gpl.html) for more information.
-
-All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated.
-
----
-
-### GETTING THE CODE AND INSTALLING
-
-Please see [/docs/installation.md](/docs/installation.md) for instructions on obtaining, installing, updating, and running this code.
+
+
+---
+
+### Рекомендации для контрибьюторов
+
+**Пожалуйста, воздержись от написания комментариев, не связанных с кодом в PR на GitHub.** Ознакомься с Правилами Поведения, где сказано о том где следует оставлять отзывы пользователям.
+
+Ожидается, что все пользователи ознакомятся с [`/docs/CODE_OF_CONDUCT.md`](/docs/CODE_OF_CONDUCT.md) перед взаимодействием с репозиторием или другими пользователями.
+
+---
+
+### Безопасность
+
+С Политикой Безопасности этого репозитория, а также с тем, как репортить уязвимости ты можешь ознакомиться в [`/docs/SECURITY.md`](/docs/SECURITY.md).
+
+---
+
+### Лизцензия
+
+Код Baystation, и, следовательно, SierraBay лицензируется в соответствии с [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl.html), полное содержание которой находится в файле [`LICENSE`](/LICENSE).
+
+Код с датой авторства git до `1420675200 +0000` (08.01.2015, 00:00 GMT) распространяется под лицензией [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html), с полной версией которой вы можете ознакомиться в [`/docs/GPL3.txt`](/docs/GPL3.txt).
+
+Предполагается, что весь код, авторство которого датируется `1420675200 +0000` или позже, лицензируется по AGPL v3. Если вы хотите лицензировать по GPL v3, укажите это в сообщении коммита, а также внутри всех добавленных файлов.
+
+Если ты желаешь разрабатывать и хостить эту кодбазу, закрыв исходный код, ты можешь использовать все коммиты до `1420675200 +0000`, которые лицензированы по GPL v3. Главное различие заключается в том, что если ты хостишь сервер, используя любой код, лицензированный в соответствии с AGPLv3, ты должен предоставить полный исходный код для пользователей ваших серверов, включая дополнения и изменения, которые ты сделал.
+
+Статья ["Зачем нужна GPL Афферо"](https://www.gnu.org/licenses/why-affero-gpl.html) рассказывает об этом подробнее.
+
+Все ассеты, включая иконки и звуки, лицензируются по [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/), если не указано иное.
+
+---
+
+### Получение кода и установка
+
+Все инструкции по получению, установке, обновлению и запуску этого кода находятся можно изучить в [`/docs/installation.md`](/docs/installation.md).
diff --git a/SpacemanDMM.toml b/SpacemanDMM.toml
index 6bbcadbee38d0..1ed4fa5c1b27f 100644
--- a/SpacemanDMM.toml
+++ b/SpacemanDMM.toml
@@ -12,6 +12,7 @@ disallow_relative_type_definitions = true
disallow_relative_proc_definitions = true
[diagnostics]
+# Dreamchecker
disabled_directive = "error"
sets_directive_twice = "error"
invalid_lint_directive_value = "error"
@@ -40,10 +41,22 @@ control_condition_static = "error"
if_condition_determinate = "error"
loop_condition_determinate = "error"
+# Lexer
integer_precision_loss = "error"
+# Parser
+var_in_proc_parameter = "error"
+static_in_proc_parameter = "error"
+semicolon_in_proc_parameter = "error"
+in_precedes_as = "error"
+tmp_no_effect = "error"
+final_no_effect = "error"
+as_local_var = "error"
+
+# Preprocessor
duplicate_include = "error"
macro_redefined = "error"
macro_undefined_no_definition = "error"
+# Object Tree
override_precedes_definition = "error"
diff --git a/baystation12.dme b/baystation12.dme
index 9de371b05ee65..4cd4783d255d6 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -20,10 +20,15 @@
#include "code\__datastructures\priority_queue.dm"
#include "code\__datastructures\stack.dm"
#include "code\__defines\__compile_options.dm"
+#include "code\__defines\__dview.dm"
+#include "code\__defines\__icons.dm"
#include "code\__defines\__initialization.dm"
#include "code\__defines\__renderer.dm"
+#include "code\__defines\_click.dm"
+#include "code\__defines\_excom.dm"
#include "code\__defines\_renderer.dm"
#include "code\__defines\admin.dm"
+#include "code\__defines\ai.dm"
#include "code\__defines\antagonists.dm"
#include "code\__defines\ao.dm"
#include "code\__defines\ao_misc.dm"
@@ -31,14 +36,20 @@
#include "code\__defines\armor.dm"
#include "code\__defines\atmos.dm"
#include "code\__defines\atmospherics.dm"
+#include "code\__defines\byond_tracy.dm"
+#include "code\__defines\callback.dm"
+#include "code\__defines\chat.dm"
+#include "code\__defines\chat_boxes.dm"
#include "code\__defines\chemistry.dm"
#include "code\__defines\client.dm"
#include "code\__defines\colors.dm"
+#include "code\__defines\combat.dm"
#include "code\__defines\culture.dm"
#include "code\__defines\damage_organs.dm"
#include "code\__defines\directions.dm"
#include "code\__defines\dna.dm"
#include "code\__defines\doors.dm"
+#include "code\__defines\emissives.dm"
#include "code\__defines\feedback.dm"
#include "code\__defines\flags.dm"
#include "code\__defines\fluids.dm"
@@ -51,6 +62,7 @@
#include "code\__defines\inventory_sizes.dm"
#include "code\__defines\items_clothing.dm"
#include "code\__defines\jobs.dm"
+#include "code\__defines\keybinding.dm"
#include "code\__defines\languages.dm"
#include "code\__defines\lighting.dm"
#include "code\__defines\lists.dm"
@@ -68,21 +80,42 @@
#include "code\__defines\qdel.dm"
#include "code\__defines\research.dm"
#include "code\__defines\ruin_tags.dm"
+#include "code\__defines\rust_g.dm"
#include "code\__defines\shields.dm"
#include "code\__defines\shuttle.dm"
#include "code\__defines\singletons.dm"
#include "code\__defines\skills.dm"
+#include "code\__defines\sound.dm"
#include "code\__defines\species.dm"
#include "code\__defines\subsystem-priority.dm"
#include "code\__defines\subsystems.dm"
#include "code\__defines\targeting.dm"
#include "code\__defines\temperature.dm"
+#include "code\__defines\text.dm"
+#include "code\__defines\tgui.dm"
+#include "code\__defines\tools.dm"
#include "code\__defines\topic.dm"
#include "code\__defines\turfs.dm"
#include "code\__defines\webhooks.dm"
#include "code\__defines\xenoarcheaology.dm"
#include "code\__defines\ZAS.dm"
#include "code\__defines\zmimic.dm"
+#include "code\__defines\dcs\flags.dm"
+#include "code\__defines\dcs\helpers.dm"
+#include "code\__defines\dcs\signals\signals_area.dm"
+#include "code\__defines\dcs\signals\signals_atom.dm"
+#include "code\__defines\dcs\signals\signals_atom_attack.dm"
+#include "code\__defines\dcs\signals\signals_atom_movable.dm"
+#include "code\__defines\dcs\signals\signals_datum.dm"
+#include "code\__defines\dcs\signals\signals_living.dm"
+#include "code\__defines\dcs\signals\signals_mob.dm"
+#include "code\__defines\dcs\signals\signals_object.dm"
+#include "code\__defines\~mods\bloom_light.dm"
+#include "code\__defines\~mods\expanded_culture_descriptor.dm"
+#include "code\__defines\~mods\text_to_speech.dm"
+#include "code\__defines\~mods\tgs.config.dm"
+#include "code\__defines\~mods\tgs.dm"
+#include "code\__defines\~mods\~master_defines.dm"
#include "code\_global_vars\edible.dm"
#include "code\_global_vars\logging.dm"
#include "code\_global_vars\mapping.dm"
@@ -96,14 +129,21 @@
#include "code\_global_vars\lists\mapping.dm"
#include "code\_global_vars\lists\names.dm"
#include "code\_global_vars\lists\objects.dm"
+#include "code\_global_vars\lists\research.dm"
+#include "code\_global_vars\lists\slots.dm"
#include "code\_global_vars\lists\times.dm"
#include "code\_helpers\_global_objects.dm"
#include "code\_helpers\areas.dm"
+#include "code\_helpers\assets.dm"
#include "code\_helpers\atmospherics.dm"
#include "code\_helpers\atom_movables.dm"
+#include "code\_helpers\bicon_procs.dm"
+#include "code\_helpers\bubble_effect.dm"
#include "code\_helpers\builtin_proc_callers.dm"
#include "code\_helpers\client.dm"
#include "code\_helpers\cmp.dm"
+#include "code\_helpers\cyrillic_keys.dm"
+#include "code\_helpers\emissives.dm"
#include "code\_helpers\functional.dm"
#include "code\_helpers\game.dm"
#include "code\_helpers\global_lists.dm"
@@ -124,6 +164,7 @@
#include "code\_helpers\tools.dm"
#include "code\_helpers\turfs.dm"
#include "code\_helpers\type2type.dm"
+#include "code\_helpers\types.dm"
#include "code\_helpers\unsorted.dm"
#include "code\_helpers\vector.dm"
#include "code\_helpers\washing.dm"
@@ -145,6 +186,7 @@
#include "code\_onclick\hud\action.dm"
#include "code\_onclick\hud\animal.dm"
#include "code\_onclick\hud\fullscreen.dm"
+#include "code\_onclick\hud\ghost.dm"
#include "code\_onclick\hud\global_hud.dm"
#include "code\_onclick\hud\gun_mode.dm"
#include "code\_onclick\hud\hud.dm"
@@ -152,11 +194,12 @@
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\other_mobs.dm"
#include "code\_onclick\hud\pai.dm"
+#include "code\_onclick\hud\radial.dm"
+#include "code\_onclick\hud\radial_persistent.dm"
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\skybox.dm"
#include "code\controllers\admin.dm"
-#include "code\controllers\autotransfer.dm"
#include "code\controllers\communications.dm"
#include "code\controllers\configuration.dm"
#include "code\controllers\controller.dm"
@@ -180,11 +223,15 @@
#include "code\controllers\subsystems\air.dm"
#include "code\controllers\subsystems\airflow.dm"
#include "code\controllers\subsystems\alarm.dm"
+#include "code\controllers\subsystems\ambient_lighting.dm"
#include "code\controllers\subsystems\ao.dm"
+#include "code\controllers\subsystems\area_turfs.dm"
+#include "code\controllers\subsystems\assets.dm"
#include "code\controllers\subsystems\atoms.dm"
#include "code\controllers\subsystems\chat.dm"
#include "code\controllers\subsystems\chemistry.dm"
#include "code\controllers\subsystems\circuit_component.dm"
+#include "code\controllers\subsystems\dcs.dm"
#include "code\controllers\subsystems\evac.dm"
#include "code\controllers\subsystems\event.dm"
#include "code\controllers\subsystems\fluids.dm"
@@ -201,13 +248,18 @@
#include "code\controllers\subsystems\misc.dm"
#include "code\controllers\subsystems\misc_slow.dm"
#include "code\controllers\subsystems\mobs.dm"
+#include "code\controllers\subsystems\overlays.dm"
#include "code\controllers\subsystems\ping.dm"
#include "code\controllers\subsystems\plants.dm"
#include "code\controllers\subsystems\presence.dm"
#include "code\controllers\subsystems\radiation.dm"
+#include "code\controllers\subsystems\roundend.dm"
#include "code\controllers\subsystems\shuttle.dm"
#include "code\controllers\subsystems\skybox.dm"
+#include "code\controllers\subsystems\spacedrift.dm"
+#include "code\controllers\subsystems\SSinput.dm"
#include "code\controllers\subsystems\supply.dm"
+#include "code\controllers\subsystems\tgui.dm"
#include "code\controllers\subsystems\throwing.dm"
#include "code\controllers\subsystems\ticker.dm"
#include "code\controllers\subsystems\timer.dm"
@@ -224,9 +276,12 @@
#include "code\controllers\subsystems\initialization\misc.dm"
#include "code\controllers\subsystems\initialization\misc_early.dm"
#include "code\controllers\subsystems\initialization\misc_late.dm"
+#include "code\controllers\subsystems\initialization\modpacks.dm"
#include "code\controllers\subsystems\initialization\persistence.dm"
#include "code\controllers\subsystems\initialization\robots.dm"
#include "code\controllers\subsystems\initialization\webhooks.dm"
+#include "code\controllers\subsystems\initialization\xenoarch.dm"
+#include "code\controllers\subsystems\processing\beach.dm"
#include "code\controllers\subsystems\processing\circuit.dm"
#include "code\controllers\subsystems\processing\disposals.dm"
#include "code\controllers\subsystems\processing\fast_process.dm"
@@ -234,6 +289,7 @@
#include "code\controllers\subsystems\processing\icon_updates.dm"
#include "code\controllers\subsystems\processing\nano.dm"
#include "code\controllers\subsystems\processing\obj.dm"
+#include "code\controllers\subsystems\processing\overmap.dm"
#include "code\controllers\subsystems\processing\processing.dm"
#include "code\controllers\subsystems\processing\psi.dm"
#include "code\controllers\subsystems\processing\temperature.dm"
@@ -252,6 +308,8 @@
#include "code\datums\browser.dm"
#include "code\datums\callbacks.dm"
#include "code\datums\category.dm"
+#include "code\datums\chat_message.dm"
+#include "code\datums\chat_payload.dm"
#include "code\datums\cinematic.dm"
#include "code\datums\datum.dm"
#include "code\datums\footsteps.dm"
@@ -264,10 +322,11 @@
#include "code\datums\ruins.dm"
#include "code\datums\security_state.dm"
#include "code\datums\shackle_law_sets.dm"
+#include "code\datums\signals.dm"
#include "code\datums\sound_player.dm"
#include "code\datums\suit_sensor_jammer_method.dm"
+#include "code\datums\tgs_event_helper.dm"
#include "code\datums\weakref.dm"
-#include "code\datums\ai\ai.dm"
#include "code\datums\ai\ai_holo.dm"
#include "code\datums\appearances\appearance_data.dm"
#include "code\datums\appearances\appearance_manager.dm"
@@ -285,6 +344,9 @@
#include "code\datums\communication\ooc.dm"
#include "code\datums\communication\pray.dm"
#include "code\datums\communication\~defines.dm"
+#include "code\datums\components\_component.dm"
+#include "code\datums\components\pixel_shift.dm"
+#include "code\datums\elements\_element.dm"
#include "code\datums\extensions\_defines.dm"
#include "code\datums\extensions\chameleon.dm"
#include "code\datums\extensions\event_registration.dm"
@@ -310,7 +372,6 @@
#include "code\datums\extensions\multitool\circuitboards\circuitboards.dm"
#include "code\datums\extensions\multitool\circuitboards\shuttle_console.dm"
#include "code\datums\extensions\multitool\circuitboards\stationalert.dm"
-#include "code\datums\extensions\multitool\items\cable.dm"
#include "code\datums\extensions\multitool\items\clothing.dm"
#include "code\datums\extensions\multitool\items\items.dm"
#include "code\datums\extensions\multitool\items\stock_parts_radio.dm"
@@ -330,6 +391,17 @@
#include "code\datums\item_modifiers\item_modifier.dm"
#include "code\datums\item_modifiers\space_suits.dm"
#include "code\datums\item_modifiers\~defines.dm"
+#include "code\datums\keybindings\_defines.dm"
+#include "code\datums\keybindings\_keybinding.dm"
+#include "code\datums\keybindings\admin.dm"
+#include "code\datums\keybindings\carbon.dm"
+#include "code\datums\keybindings\client.dm"
+#include "code\datums\keybindings\communication.dm"
+#include "code\datums\keybindings\human.dm"
+#include "code\datums\keybindings\living.dm"
+#include "code\datums\keybindings\mob.dm"
+#include "code\datums\keybindings\movement.dm"
+#include "code\datums\keybindings\robot.dm"
#include "code\datums\licences\license.dm"
#include "code\datums\mind\memory.dm"
#include "code\datums\mind\mind.dm"
@@ -347,6 +419,7 @@
#include "code\datums\observation\destroyed.dm"
#include "code\datums\observation\dir_set.dm"
#include "code\datums\observation\dismembered.dm"
+#include "code\datums\observation\empd.dm"
#include "code\datums\observation\entered.dm"
#include "code\datums\observation\equipped.dm"
#include "code\datums\observation\exited.dm"
@@ -405,6 +478,7 @@
#include "code\datums\repositories\cameras.dm"
#include "code\datums\repositories\client.dm"
#include "code\datums\repositories\follow.dm"
+#include "code\datums\repositories\icon_states.dm"
#include "code\datums\repositories\images.dm"
#include "code\datums\repositories\mobs.dm"
#include "code\datums\repositories\repository.dm"
@@ -424,7 +498,9 @@
#include "code\datums\singletons\pools\instance_configurator.dm"
#include "code\datums\singletons\pools\instance_configurator_auto.dm"
#include "code\datums\singletons\pools\instance_pool.dm"
-#include "code\datums\singletons\pools\pool_behavior.dm"
+#include "code\datums\singletons\shared_list\_shared_list.dm"
+#include "code\datums\singletons\shared_list\path.dm"
+#include "code\datums\singletons\shared_list\sound.dm"
#include "code\datums\state_machine\paper_fortune_fsm.dm"
#include "code\datums\state_machine\state.dm"
#include "code\datums\state_machine\transition.dm"
@@ -495,6 +571,7 @@
#include "code\datums\uplink\services.dm"
#include "code\datums\uplink\stealth_and_camouflage_items.dm"
#include "code\datums\uplink\stealthy_and_inconspicuous_weapons.dm"
+#include "code\datums\uplink\structures_and_vehicles.dm"
#include "code\datums\uplink\telecrystals.dm"
#include "code\datums\uplink\uplink_categories.dm"
#include "code\datums\uplink\uplink_items.dm"
@@ -522,6 +599,7 @@
#include "code\datums\wires\smes.dm"
#include "code\datums\wires\suit_storage_unit.dm"
#include "code\datums\wires\taperecorder.dm"
+#include "code\datums\wires\transfer_valve.dm"
#include "code\datums\wires\wire_description.dm"
#include "code\datums\wires\wires.dm"
#include "code\game\atoms.dm"
@@ -529,9 +607,9 @@
#include "code\game\atoms_health.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\atoms_temperature.dm"
+#include "code\game\atoms_tool_acts.dm"
#include "code\game\base_turf.dm"
#include "code\game\client_macros.dm"
-#include "code\game\images.dm"
#include "code\game\movietitles.dm"
#include "code\game\response_team.dm"
#include "code\game\sound.dm"
@@ -658,16 +736,15 @@
#include "code\game\jobs\job\security.dm"
#include "code\game\jobs\job\silicon.dm"
#include "code\game\machinery\air_sensor.dm"
-#include "code\game\machinery\alarm.dm"
#include "code\game\machinery\atmo_control.dm"
#include "code\game\machinery\barrier.dm"
#include "code\game\machinery\biogenerator.dm"
#include "code\game\machinery\bioprinter.dm"
#include "code\game\machinery\bluespace_drive.dm"
#include "code\game\machinery\bluespacerelay.dm"
-#include "code\game\machinery\bodyscanner.dm"
-#include "code\game\machinery\bodyscanner_console.dm"
-#include "code\game\machinery\bodyscanner_display.dm"
+#include "code\game\machinery\body_scanner.dm"
+#include "code\game\machinery\body_scanner_console.dm"
+#include "code\game\machinery\body_scanner_display.dm"
#include "code\game\machinery\buttons.dm"
#include "code\game\machinery\CableLayer.dm"
#include "code\game\machinery\cell_charger.dm"
@@ -698,13 +775,13 @@
#include "code\game\machinery\recharger.dm"
#include "code\game\machinery\rechargestation.dm"
#include "code\game\machinery\requests_console.dm"
-#include "code\game\machinery\robot_fabricator.dm"
#include "code\game\machinery\robotics_fabricator.dm"
#include "code\game\machinery\rotating_alarm.dm"
#include "code\game\machinery\seed_extractor.dm"
#include "code\game\machinery\self_destruct.dm"
#include "code\game\machinery\Sleeper.dm"
#include "code\game\machinery\spaceheater.dm"
+#include "code\game\machinery\stasis_cage.dm"
#include "code\game\machinery\status_display.dm"
#include "code\game\machinery\status_display_ai.dm"
#include "code\game\machinery\status_light.dm"
@@ -745,6 +822,9 @@
#include "code\game\machinery\_machines_base\stock_parts\radio\receiver.dm"
#include "code\game\machinery\_machines_base\stock_parts\radio\stock_parts_radio.dm"
#include "code\game\machinery\_machines_base\stock_parts\radio\transmitter.dm"
+#include "code\game\machinery\alarm\alarm.dm"
+#include "code\game\machinery\alarm\firealarm.dm"
+#include "code\game\machinery\alarm\partyalarm.dm"
#include "code\game\machinery\atmoalter\area_atmos_computer.dm"
#include "code\game\machinery\atmoalter\canister.dm"
#include "code\game\machinery\atmoalter\clamp.dm"
@@ -883,6 +963,7 @@
#include "code\game\objects\compass\compass_holder.dm"
#include "code\game\objects\compass\compass_waypoint.dm"
#include "code\game\objects\effects\bump_teleporter.dm"
+#include "code\game\objects\effects\cig_smoke.dm"
#include "code\game\objects\effects\effect_system.dm"
#include "code\game\objects\effects\explosion_particles.dm"
#include "code\game\objects\effects\fake_fire.dm"
@@ -892,7 +973,6 @@
#include "code\game\objects\effects\item_pickup_ghost.dm"
#include "code\game\objects\effects\landmarks.dm"
#include "code\game\objects\effects\manifest.dm"
-#include "code\game\objects\effects\mines.dm"
#include "code\game\objects\effects\misc.dm"
#include "code\game\objects\effects\overlays.dm"
#include "code\game\objects\effects\portals.dm"
@@ -903,6 +983,7 @@
#include "code\game\objects\effects\chem\chemsmoke.dm"
#include "code\game\objects\effects\chem\foam.dm"
#include "code\game\objects\effects\chem\water.dm"
+#include "code\game\objects\effects\decals\bullet_holes.dm"
#include "code\game\objects\effects\decals\cleanable.dm"
#include "code\game\objects\effects\decals\contraband.dm"
#include "code\game\objects\effects\decals\crayon.dm"
@@ -916,7 +997,9 @@
#include "code\game\objects\effects\decals\Cleanable\misc.dm"
#include "code\game\objects\effects\decals\Cleanable\robots.dm"
#include "code\game\objects\effects\decals\Cleanable\tracks.dm"
+#include "code\game\objects\effects\decals\posters\_defines.dm"
#include "code\game\objects\effects\decals\posters\bs12.dm"
+#include "code\game\objects\effects\decals\posters\posters.dm"
#include "code\game\objects\effects\fire\fire.dm"
#include "code\game\objects\effects\particles\particles.dm"
#include "code\game\objects\effects\spawners\bombspawner.dm"
@@ -940,6 +1023,7 @@
#include "code\game\objects\items\passport.dm"
#include "code\game\objects\items\plunger.dm"
#include "code\game\objects\items\rescuebag.dm"
+#include "code\game\objects\items\selection.dm"
#include "code\game\objects\items\spirit_board.dm"
#include "code\game\objects\items\toys.dm"
#include "code\game\objects\items\traitor_plush.dm"
@@ -948,9 +1032,10 @@
#include "code\game\objects\items\devices\auto_cpr.dm"
#include "code\game\objects\items\devices\binoculars.dm"
#include "code\game\objects\items\devices\boombox.dm"
-#include "code\game\objects\items\devices\cable_painter.dm"
#include "code\game\objects\items\devices\chameleonproj.dm"
+#include "code\game\objects\items\devices\cosmetic_surgery_kit.dm"
#include "code\game\objects\items\devices\debugger.dm"
+#include "code\game\objects\items\devices\dna_sampler.dm"
#include "code\game\objects\items\devices\dociler.dm"
#include "code\game\objects\items\devices\flash.dm"
#include "code\game\objects\items\devices\flashlight.dm"
@@ -968,6 +1053,7 @@
#include "code\game\objects\items\devices\paint_sprayer.dm"
#include "code\game\objects\items\devices\personal_shield.dm"
#include "code\game\objects\items\devices\powersink.dm"
+#include "code\game\objects\items\devices\radio_jammer.dm"
#include "code\game\objects\items\devices\slide_projector.dm"
#include "code\game\objects\items\devices\spy_bug.dm"
#include "code\game\objects\items\devices\suit_cooling.dm"
@@ -1023,7 +1109,7 @@
#include "code\game\objects\items\weapons\extinguisher.dm"
#include "code\game\objects\items\weapons\flame.dm"
#include "code\game\objects\items\weapons\flamethrower.dm"
-#include "code\game\objects\items\weapons\gift_wrappaper.dm"
+#include "code\game\objects\items\weapons\gifts.dm"
#include "code\game\objects\items\weapons\handcuffs.dm"
#include "code\game\objects\items\weapons\janitor_sign.dm"
#include "code\game\objects\items\weapons\lighter.dm"
@@ -1052,6 +1138,7 @@
#include "code\game\objects\items\weapons\trays.dm"
#include "code\game\objects\items\weapons\weaponry.dm"
#include "code\game\objects\items\weapons\weldbackpack.dm"
+#include "code\game\objects\items\weapons\wrapping_paper.dm"
#include "code\game\objects\items\weapons\candle\candle.dm"
#include "code\game\objects\items\weapons\candle\incense.dm"
#include "code\game\objects\items\weapons\candle\scent_decls.dm"
@@ -1068,7 +1155,6 @@
#include "code\game\objects\items\weapons\circuitboards\computer\station_alert.dm"
#include "code\game\objects\items\weapons\circuitboards\computer\telecomms.dm"
#include "code\game\objects\items\weapons\circuitboards\machinery\biogenerator.dm"
-#include "code\game\objects\items\weapons\circuitboards\machinery\bluespacedrive.dm"
#include "code\game\objects\items\weapons\circuitboards\machinery\chemistry.dm"
#include "code\game\objects\items\weapons\circuitboards\machinery\cloning.dm"
#include "code\game\objects\items\weapons\circuitboards\machinery\commsantenna.dm"
@@ -1157,6 +1243,7 @@
#include "code\game\objects\items\weapons\storage\fancy\crackers.dm"
#include "code\game\objects\items\weapons\storage\fancy\crayons.dm"
#include "code\game\objects\items\weapons\storage\fancy\egg_box.dm"
+#include "code\game\objects\items\weapons\storage\fancy\matchbox.dm"
#include "code\game\objects\items\weapons\storage\fancy\pencilcase.dm"
#include "code\game\objects\items\weapons\storage\fancy\vials.dm"
#include "code\game\objects\items\weapons\storage\fancy\smokable\_smokable.dm"
@@ -1169,6 +1256,7 @@
#include "code\game\objects\items\weapons\tanks\tanks.dm"
#include "code\game\objects\items\weapons\tools\crowbar.dm"
#include "code\game\objects\items\weapons\tools\screwdriver.dm"
+#include "code\game\objects\items\weapons\tools\swapper.dm"
#include "code\game\objects\items\weapons\tools\weldingtool.dm"
#include "code\game\objects\items\weapons\tools\wirecutter.dm"
#include "code\game\objects\items\weapons\tools\wrench.dm"
@@ -1203,6 +1291,7 @@
#include "code\game\objects\structures\janicart.dm"
#include "code\game\objects\structures\lattice.dm"
#include "code\game\objects\structures\mineral_bath.dm"
+#include "code\game\objects\structures\mines.dm"
#include "code\game\objects\structures\mop_bucket.dm"
#include "code\game\objects\structures\morgue.dm"
#include "code\game\objects\structures\pit.dm"
@@ -1215,7 +1304,6 @@
#include "code\game\objects\structures\showcase.dm"
#include "code\game\objects\structures\signs.dm"
#include "code\game\objects\structures\skele_stand.dm"
-#include "code\game\objects\structures\stasis_cage.dm"
#include "code\game\objects\structures\tank_dispenser.dm"
#include "code\game\objects\structures\transit_tubes.dm"
#include "code\game\objects\structures\under_wardrobe.dm"
@@ -1234,6 +1322,7 @@
#include "code\game\objects\structures\crates_lockers\closets\coffin.dm"
#include "code\game\objects\structures\crates_lockers\closets\crittercrate.dm"
#include "code\game\objects\structures\crates_lockers\closets\fitness.dm"
+#include "code\game\objects\structures\crates_lockers\closets\fridge.dm"
#include "code\game\objects\structures\crates_lockers\closets\gimmick.dm"
#include "code\game\objects\structures\crates_lockers\closets\job_closets.dm"
#include "code\game\objects\structures\crates_lockers\closets\l3closet.dm"
@@ -1246,7 +1335,6 @@
#include "code\game\objects\structures\crates_lockers\closets\secure\bar.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\cargo.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\engineering.dm"
-#include "code\game\objects\structures\crates_lockers\closets\secure\freezer.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\guncabinet.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\hydroponics.dm"
#include "code\game\objects\structures\crates_lockers\closets\secure\medical.dm"
@@ -1341,6 +1429,8 @@
#include "code\modules\admin\buildmode\turret.dm"
#include "code\modules\admin\buildmode\_datums\mob_spawner.dm"
#include "code\modules\admin\callproc\callproc.dm"
+#include "code\modules\admin\connectioncheck\bancheck_functions.dm"
+#include "code\modules\admin\connectioncheck\connectioncheck_functions.dm"
#include "code\modules\admin\DB ban\functions.dm"
#include "code\modules\admin\permissionverbs\permissionedit.dm"
#include "code\modules\admin\secrets\admin_secrets\admin_logs.dm"
@@ -1405,7 +1495,6 @@
#include "code\modules\admin\view_variables\view_variables.dm"
#include "code\modules\admin\view_variables\view_variables_global.dm"
#include "code\modules\admin\view_variables\vv_set_handlers.dm"
-#include "code\modules\ai\_defines.dm"
#include "code\modules\ai\ai_holder.dm"
#include "code\modules\ai\ai_holder_combat.dm"
#include "code\modules\ai\ai_holder_combat_unseen.dm"
@@ -1420,7 +1509,9 @@
#include "code\modules\ai\ai_holder_targeting.dm"
#include "code\modules\ai\interfaces.dm"
#include "code\modules\ai\say_list.dm"
-#include "code\modules\ai\aI_holder_subtypes\simple_mob_ai.dm"
+#include "code\modules\ai\ai_holder_subtypes\alien_ai.dm"
+#include "code\modules\ai\ai_holder_subtypes\human_ai.dm"
+#include "code\modules\ai\ai_holder_subtypes\simple_mob_ai.dm"
#include "code\modules\alarm\alarm.dm"
#include "code\modules\alarm\alarm_handler.dm"
#include "code\modules\alarm\atmosphere_alarm.dm"
@@ -1439,6 +1530,24 @@
#include "code\modules\assembly\signaler.dm"
#include "code\modules\assembly\timer.dm"
#include "code\modules\assembly\voice.dm"
+#include "code\modules\asset_cache\asset_cache_client.dm"
+#include "code\modules\asset_cache\asset_cache_item.dm"
+#include "code\modules\asset_cache\asset_list.dm"
+#include "code\modules\asset_cache\assets\asset_changelog.dm"
+#include "code\modules\asset_cache\assets\asset_character_preview.dm"
+#include "code\modules\asset_cache\assets\asset_chem_master.dm"
+#include "code\modules\asset_cache\assets\asset_fontawesome.dm"
+#include "code\modules\asset_cache\assets\asset_lobby_screen.dm"
+#include "code\modules\asset_cache\assets\asset_nanoui_common.dm"
+#include "code\modules\asset_cache\assets\asset_nanoui_group.dm"
+#include "code\modules\asset_cache\assets\asset_nanoui_templates.dm"
+#include "code\modules\asset_cache\assets\asset_paicard.dm"
+#include "code\modules\asset_cache\assets\asset_paper.dm"
+#include "code\modules\asset_cache\assets\asset_permission_panel.dm"
+#include "code\modules\asset_cache\assets\asset_tgfont.dm"
+#include "code\modules\asset_cache\assets\asset_tgui.dm"
+#include "code\modules\asset_cache\transports\asset_transport.dm"
+#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\atmospherics\_atmos_setup.dm"
#include "code\modules\atmospherics\atmos_primitives.dm"
#include "code\modules\atmospherics\atmospherics.dm"
@@ -1497,21 +1606,19 @@
#include "code\modules\augment\passive\boost\muscle.dm"
#include "code\modules\augment\passive\boost\reflex.dm"
#include "code\modules\augment\passive\boost\shooting.dm"
+#include "code\modules\balloon_alert\balloon_alert.dm"
#include "code\modules\blob\blob.dm"
#include "code\modules\butchery\butchery.dm"
#include "code\modules\butchery\remains.dm"
-#include "code\modules\client\asset_cache.dm"
#include "code\modules\client\client_color.dm"
#include "code\modules\client\client_defines.dm"
#include "code\modules\client\client_helpers.dm"
#include "code\modules\client\client_procs.dm"
-#include "code\modules\client\darkmode.dm"
#include "code\modules\client\movement.dm"
#include "code\modules\client\preferences.dm"
#include "code\modules\client\preferences_persist.dm"
#include "code\modules\client\preferences_spawnpoints.dm"
#include "code\modules\client\preferences_storage.dm"
-#include "code\modules\client\preferences_toggle.dm"
#include "code\modules\client\ui_style.dm"
#include "code\modules\client\preference_setup\_defines.dm"
#include "code\modules\client\preference_setup\preference_setup.dm"
@@ -1532,27 +1639,31 @@
#include "code\modules\client\preference_setup\global\05_settings.dm"
#include "code\modules\client\preference_setup\global\preferences.dm"
#include "code\modules\client\preference_setup\global\prefixes.dm"
+#include "code\modules\client\preference_setup\keybindings\keybindings_pref.dm"
+#include "code\modules\client\preference_setup\keybindings\keybindings_proc.dm"
#include "code\modules\client\preference_setup\laws\laws_pref.dm"
#include "code\modules\client\preference_setup\loadout\_defines.dm"
+#include "code\modules\client\preference_setup\loadout\gear.dm"
#include "code\modules\client\preference_setup\loadout\gear_tweaks.dm"
#include "code\modules\client\preference_setup\loadout\loadout.dm"
+#include "code\modules\client\preference_setup\loadout\loadout_category.dm"
#include "code\modules\client\preference_setup\loadout\lists\accessories.dm"
#include "code\modules\client\preference_setup\loadout\lists\augments.dm"
#include "code\modules\client\preference_setup\loadout\lists\clothing.dm"
+#include "code\modules\client\preference_setup\loadout\lists\documents.dm"
#include "code\modules\client\preference_setup\loadout\lists\earwear.dm"
#include "code\modules\client\preference_setup\loadout\lists\eyegear.dm"
#include "code\modules\client\preference_setup\loadout\lists\footwear.dm"
#include "code\modules\client\preference_setup\loadout\lists\gloves.dm"
#include "code\modules\client\preference_setup\loadout\lists\headwear.dm"
+#include "code\modules\client\preference_setup\loadout\lists\masks.dm"
#include "code\modules\client\preference_setup\loadout\lists\misc.dm"
-#include "code\modules\client\preference_setup\loadout\lists\pet.dm"
#include "code\modules\client\preference_setup\loadout\lists\storage.dm"
#include "code\modules\client\preference_setup\loadout\lists\suits.dm"
#include "code\modules\client\preference_setup\loadout\lists\tactical.dm"
#include "code\modules\client\preference_setup\loadout\lists\trinkets.dm"
#include "code\modules\client\preference_setup\loadout\lists\uniforms.dm"
#include "code\modules\client\preference_setup\loadout\lists\utility.dm"
-#include "code\modules\client\preference_setup\loadout\lists\xenowear.dm"
#include "code\modules\client\preference_setup\occupation\occupation.dm"
#include "code\modules\client\preference_setup\occupation\skill_selection.dm"
#include "code\modules\clothing\_clothing.dm"
@@ -1669,8 +1780,6 @@
#include "code\modules\clothing\under\accessories\neckerchief.dm"
#include "code\modules\clothing\under\accessories\necklace.dm"
#include "code\modules\clothing\under\accessories\pouches.dm"
-#include "code\modules\clothing\under\accessories\pride_pin.dm"
-#include "code\modules\clothing\under\accessories\pronouns.dm"
#include "code\modules\clothing\under\accessories\qipao.dm"
#include "code\modules\clothing\under\accessories\scarf.dm"
#include "code\modules\clothing\under\accessories\sherwani.dm"
@@ -1755,7 +1864,6 @@
#include "code\modules\culture_descriptor\faction\factions_adherent.dm"
#include "code\modules\culture_descriptor\faction\factions_hidden.dm"
#include "code\modules\culture_descriptor\faction\factions_human.dm"
-#include "code\modules\culture_descriptor\faction\factions_ipc.dm"
#include "code\modules\culture_descriptor\faction\factions_skrell.dm"
#include "code\modules\culture_descriptor\faction\factions_unathi.dm"
#include "code\modules\culture_descriptor\faction\factions_vox.dm"
@@ -1763,7 +1871,6 @@
#include "code\modules\culture_descriptor\location\locations_adherent.dm"
#include "code\modules\culture_descriptor\location\locations_diona.dm"
#include "code\modules\culture_descriptor\location\locations_human.dm"
-#include "code\modules\culture_descriptor\location\locations_ipc.dm"
#include "code\modules\culture_descriptor\location\locations_other.dm"
#include "code\modules\culture_descriptor\location\locations_serpentids.dm"
#include "code\modules\culture_descriptor\location\locations_skrell.dm"
@@ -1816,11 +1923,11 @@
#include "code\modules\events\blob.dm"
#include "code\modules\events\brain_expansion.dm"
#include "code\modules\events\brand_intelligence.dm"
+#include "code\modules\events\bsd_instability.dm"
#include "code\modules\events\camera_damage.dm"
#include "code\modules\events\communications_blackout.dm"
#include "code\modules\events\computer_damage.dm"
#include "code\modules\events\computer_update.dm"
-#include "code\modules\events\deepmaint_event.dm"
#include "code\modules\events\disposals_explosion.dm"
#include "code\modules\events\dust.dm"
#include "code\modules\events\electrical_storm.dm"
@@ -1863,7 +1970,6 @@
#include "code\modules\fabrication\fabricator_hacked.dm"
#include "code\modules\fabrication\fabricator_intake.dm"
#include "code\modules\fabrication\fabricator_microlathe.dm"
-#include "code\modules\fabrication\fabricator_topic.dm"
#include "code\modules\fabrication\fabricator_ui.dm"
#include "code\modules\fabrication\designs\_design.dm"
#include "code\modules\fabrication\designs\general\designs_arms_ammo.dm"
@@ -1898,8 +2004,6 @@
#include "code\modules\goals\definitions\personal_achievement.dm"
#include "code\modules\goals\definitions\personal_achievement_movement.dm"
#include "code\modules\goals\definitions\personal_achievement_specific_object.dm"
-#include "code\modules\goonchat\_helpers.dm"
-#include "code\modules\goonchat\browserOutput.dm"
#include "code\modules\holodeck\HolodeckControl.dm"
#include "code\modules\holodeck\HolodeckObjects.dm"
#include "code\modules\holodeck\HolodeckPrograms.dm"
@@ -1925,6 +2029,8 @@
#include "code\modules\hydroponics\trays\tray_soil.dm"
#include "code\modules\hydroponics\trays\tray_tools.dm"
#include "code\modules\hydroponics\trays\tray_update_icons.dm"
+#include "code\modules\input\input.dm"
+#include "code\modules\input\movement.dm"
#include "code\modules\integrated_electronics\_defines.dm"
#include "code\modules\integrated_electronics\core\analyzer.dm"
#include "code\modules\integrated_electronics\core\assemblies.dm"
@@ -1938,7 +2044,6 @@
#include "code\modules\integrated_electronics\core\wirer.dm"
#include "code\modules\integrated_electronics\core\prefab\prefab.dm"
#include "code\modules\integrated_electronics\core\prefab\prefabs.dm"
-#include "code\modules\integrated_electronics\core\prefab\test\testprefabs.dm"
#include "code\modules\integrated_electronics\core\special_pins\boolean_pin.dm"
#include "code\modules\integrated_electronics\core\special_pins\char_pin.dm"
#include "code\modules\integrated_electronics\core\special_pins\color_pin.dm"
@@ -1962,7 +2067,6 @@
#include "code\modules\integrated_electronics\subtypes\memory.dm"
#include "code\modules\integrated_electronics\subtypes\output.dm"
#include "code\modules\integrated_electronics\subtypes\power.dm"
-#include "code\modules\integrated_electronics\subtypes\reagents.dm"
#include "code\modules\integrated_electronics\subtypes\smart.dm"
#include "code\modules\integrated_electronics\subtypes\time.dm"
#include "code\modules\integrated_electronics\subtypes\trig.dm"
@@ -1978,11 +2082,12 @@
#include "code\modules\library\manuals\medical.dm"
#include "code\modules\library\manuals\nanotrasen.dm"
#include "code\modules\library\manuals\union.dm"
+#include "code\modules\lighting\_lighting_defs.dm"
+#include "code\modules\lighting\darksight.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
#include "code\modules\lighting\lighting_corner.dm"
#include "code\modules\lighting\lighting_overlay.dm"
-#include "code\modules\lighting\lighting_planemaster.dm"
#include "code\modules\lighting\lighting_setup.dm"
#include "code\modules\lighting\lighting_source.dm"
#include "code\modules\lighting\lighting_turf.dm"
@@ -2057,7 +2162,8 @@
#include "code\modules\mining\machinery\mineral_unloader.dm"
#include "code\modules\mob\animations.dm"
#include "code\modules\mob\death.dm"
-#include "code\modules\mob\gender.dm"
+#include "code\modules\mob\dview.dm"
+#include "code\modules\mob\examinations.dm"
#include "code\modules\mob\hear_say.dm"
#include "code\modules\mob\holder.dm"
#include "code\modules\mob\inventory.dm"
@@ -2068,9 +2174,11 @@
#include "code\modules\mob\mob_helpers.dm"
#include "code\modules\mob\mob_movement.dm"
#include "code\modules\mob\mob_transformation_simple.dm"
+#include "code\modules\mob\pronouns.dm"
#include "code\modules\mob\say.dm"
#include "code\modules\mob\transform_procs.dm"
#include "code\modules\mob\update_icons.dm"
+#include "code\modules\mob\vv_handlers.dm"
#include "code\modules\mob\grab\grab_datum.dm"
#include "code\modules\mob\grab\grab_object.dm"
#include "code\modules\mob\grab\grab_readme.dm"
@@ -2112,6 +2220,7 @@
#include "code\modules\mob\living\living.dm"
#include "code\modules\mob\living\living_defense.dm"
#include "code\modules\mob\living\living_defines.dm"
+#include "code\modules\mob\living\living_health.dm"
#include "code\modules\mob\living\living_maneuvers.dm"
#include "code\modules\mob\living\living_powers.dm"
#include "code\modules\mob\living\login.dm"
@@ -2202,6 +2311,7 @@
#include "code\modules\mob\living\carbon\human\descriptors\descriptors_nabber.dm"
#include "code\modules\mob\living\carbon\human\descriptors\descriptors_skrell.dm"
#include "code\modules\mob\living\carbon\human\descriptors\descriptors_vox.dm"
+#include "code\modules\mob\living\carbon\human\machine\screen_abilities.dm"
#include "code\modules\mob\living\carbon\xenobiological\death.dm"
#include "code\modules\mob\living\carbon\xenobiological\examine.dm"
#include "code\modules\mob\living\carbon\xenobiological\hud.dm"
@@ -2323,7 +2433,6 @@
#include "code\modules\mob\living\simple_animal\friendly\slime.dm"
#include "code\modules\mob\living\simple_animal\friendly\tomato.dm"
#include "code\modules\mob\living\simple_animal\hostile\antlion.dm"
-#include "code\modules\mob\living\simple_animal\hostile\bad_drone.dm"
#include "code\modules\mob\living\simple_animal\hostile\bat.dm"
#include "code\modules\mob\living\simple_animal\hostile\bear.dm"
#include "code\modules\mob\living\simple_animal\hostile\bluespace.dm"
@@ -2336,7 +2445,6 @@
#include "code\modules\mob\living\simple_animal\hostile\meat_horrors.dm"
#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
-#include "code\modules\mob\living\simple_animal\hostile\roguefleet.dm"
#include "code\modules\mob\living\simple_animal\hostile\russian.dm"
#include "code\modules\mob\living\simple_animal\hostile\skeleton.dm"
#include "code\modules\mob\living\simple_animal\hostile\space_carp.dm"
@@ -2360,9 +2468,6 @@
#include "code\modules\mob\living\simple_animal\hostile\giant_spider\thermic.dm"
#include "code\modules\mob\living\simple_animal\hostile\giant_spider\tunneler.dm"
#include "code\modules\mob\living\simple_animal\hostile\giant_spider\webslinger.dm"
-#include "code\modules\mob\living\simple_animal\hostile\hivebot\hivebot.dm"
-#include "code\modules\mob\living\simple_animal\hostile\hivebot\ranged.dm"
-#include "code\modules\mob\living\simple_animal\hostile\hivebot\tank.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\exoplanet.dm"
@@ -2376,6 +2481,11 @@
#include "code\modules\mob\living\simple_animal\hostile\retaliate\space_whale.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\giant_parrot\giant_parrot.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\giant_parrot\giant_parrot_species.dm"
+#include "code\modules\mob\living\simple_animal\hostile\robotic\bad_drone.dm"
+#include "code\modules\mob\living\simple_animal\hostile\robotic\robots.dm"
+#include "code\modules\mob\living\simple_animal\hostile\robotic\hivebot\hivebot.dm"
+#include "code\modules\mob\living\simple_animal\hostile\robotic\hivebot\ranged.dm"
+#include "code\modules\mob\living\simple_animal\hostile\robotic\hivebot\tank.dm"
#include "code\modules\mob\new_player\login.dm"
#include "code\modules\mob\new_player\logout.dm"
#include "code\modules\mob\new_player\new_player.dm"
@@ -2399,6 +2509,7 @@
#include "code\modules\mob\observer\ghost\ghost.dm"
#include "code\modules\mob\observer\ghost\login.dm"
#include "code\modules\mob\observer\ghost\logout.dm"
+#include "code\modules\mob\observer\ghost\orbit.dm"
#include "code\modules\mob\observer\ghost\say.dm"
#include "code\modules\mob\observer\virtual\_constants.dm"
#include "code\modules\mob\observer\virtual\base.dm"
@@ -2609,6 +2720,9 @@
#include "code\modules\overmap\overmap_shuttle.dm"
#include "code\modules\overmap\sectors.dm"
#include "code\modules\overmap\spacetravel.dm"
+#include "code\modules\overmap\contacts\_contacts.dm"
+#include "code\modules\overmap\contacts\contact_sensors.dm"
+#include "code\modules\overmap\contacts\tracker.dm"
#include "code\modules\overmap\disperser\disperser.dm"
#include "code\modules\overmap\disperser\disperser_charge.dm"
#include "code\modules\overmap\disperser\disperser_circuit.dm"
@@ -2681,15 +2795,16 @@
#include "code\modules\persistence\persistence_datum_paper_sticky.dm"
#include "code\modules\pointdefense\pointdefense.dm"
#include "code\modules\power\apc.dm"
-#include "code\modules\power\batteryrack.dm"
#include "code\modules\power\breaker_box.dm"
#include "code\modules\power\cable_coil.dm"
#include "code\modules\power\cable_structure.dm"
#include "code\modules\power\cell.dm"
+#include "code\modules\power\cell_rack.dm"
#include "code\modules\power\debug_items.dm"
#include "code\modules\power\generator.dm"
#include "code\modules\power\gravitygenerator.dm"
#include "code\modules\power\lighting.dm"
+#include "code\modules\power\nav_light.dm"
#include "code\modules\power\port_gen.dm"
#include "code\modules\power\power.dm"
#include "code\modules\power\powernet.dm"
@@ -2836,15 +2951,12 @@
#include "code\modules\random_map\drop\droppod.dm"
#include "code\modules\random_map\drop\droppod_doors.dm"
#include "code\modules\random_map\drop\supply.dm"
-#include "code\modules\random_map\dungeon\deepmaint.dm"
-#include "code\modules\random_map\dungeon\jp_dungeon_gen.dm"
#include "code\modules\random_map\dungeon\predefined.dm"
#include "code\modules\random_map\dungeon\room.dm"
#include "code\modules\random_map\dungeon\room_generation.dm"
#include "code\modules\random_map\dungeon\room_theme.dm"
#include "code\modules\random_map\dungeon\winding_dungeon.dm"
#include "code\modules\random_map\dungeon\winding_dungeon_verb.dm"
-#include "code\modules\random_map\dungeon\rooms\deepmain_room.dm"
#include "code\modules\random_map\dungeon\rooms\mimic_room.dm"
#include "code\modules\random_map\dungeon\rooms\monster_room.dm"
#include "code\modules\random_map\dungeon\rooms\tomb.dm"
@@ -2882,12 +2994,12 @@
#include "code\modules\reagents\dispenser\_defines.dm"
#include "code\modules\reagents\dispenser\cartridge.dm"
#include "code\modules\reagents\dispenser\cartridge_spawn.dm"
-#include "code\modules\reagents\dispenser\dispenser2.dm"
+#include "code\modules\reagents\dispenser\chemical_dispenser.dm"
#include "code\modules\reagents\dispenser\dispenser_presets.dm"
#include "code\modules\reagents\dispenser\presets\bar.dm"
#include "code\modules\reagents\dispenser\presets\chemistry.dm"
#include "code\modules\reagents\dispenser\presets\medicine.dm"
-#include "code\modules\reagents\heat_sources\_heat_source.dm"
+#include "code\modules\reagents\heat_sources\thermal_regulator.dm"
#include "code\modules\reagents\reagent_containers\borghypo.dm"
#include "code\modules\reagents\reagent_containers\dropper.dm"
#include "code\modules\reagents\reagent_containers\food.dm"
@@ -2937,6 +3049,7 @@
#include "code\modules\research\rdmachines.dm"
#include "code\modules\research\research.dm"
#include "code\modules\research\server.dm"
+#include "code\modules\research\tech.dm"
#include "code\modules\research\designs\_designs.dm"
#include "code\modules\research\designs\design_aimodule.dm"
#include "code\modules\research\designs\designs_beaker.dm"
@@ -3154,6 +3267,38 @@
#include "code\modules\tables\tables.dm"
#include "code\modules\tables\update_triggers.dm"
#include "code\modules\tension\tension.dm"
+#include "code\modules\tgui\external.dm"
+#include "code\modules\tgui\states.dm"
+#include "code\modules\tgui\tgui.dm"
+#include "code\modules\tgui\tgui_window.dm"
+#include "code\modules\tgui\modules\_base.dm"
+#include "code\modules\tgui\modules\crew_monitor.dm"
+#include "code\modules\tgui\states\admin.dm"
+#include "code\modules\tgui\states\always.dm"
+#include "code\modules\tgui\states\conscious.dm"
+#include "code\modules\tgui\states\contained.dm"
+#include "code\modules\tgui\states\deep_inventory.dm"
+#include "code\modules\tgui\states\default.dm"
+#include "code\modules\tgui\states\hands.dm"
+#include "code\modules\tgui\states\human_adjacent.dm"
+#include "code\modules\tgui\states\inventory.dm"
+#include "code\modules\tgui\states\not_incapacitated.dm"
+#include "code\modules\tgui\states\notcontained.dm"
+#include "code\modules\tgui\states\observer.dm"
+#include "code\modules\tgui\states\physical.dm"
+#include "code\modules\tgui\states\self.dm"
+#include "code\modules\tgui\states\zlevel.dm"
+#include "code\modules\tgui\tgui_input\alert_input.dm"
+#include "code\modules\tgui\tgui_input\list_input.dm"
+#include "code\modules\tgui\tgui_input\number_input.dm"
+#include "code\modules\tgui\tgui_input\text_input.dm"
+#include "code\modules\tgui\tgui_panel\audio.dm"
+#include "code\modules\tgui\tgui_panel\telemetry.dm"
+#include "code\modules\tgui\tgui_panel\tgui_panel.dm"
+#include "code\modules\tgui\tgui_panel\tgui_panel_external.dm"
+#include "code\modules\tgui\tgui_panel\tgui_panel_message.dm"
+#include "code\modules\tgui\tgui_panel\to_chat.dm"
+#include "code\modules\tooltip\tooltip.dm"
#include "code\modules\turbolift\_turbolift.dm"
#include "code\modules\turbolift\turbolift.dm"
#include "code\modules\turbolift\turbolift_areas.dm"
@@ -3162,6 +3307,48 @@
#include "code\modules\turbolift\turbolift_floor.dm"
#include "code\modules\turbolift\turbolift_map.dm"
#include "code\modules\turbolift\turbolift_turfs.dm"
+#include "code\modules\unit_tests\_defines.dm"
+#include "code\modules\unit_tests\_includes.dm"
+#include "code\modules\unit_tests\_unit_tests.dm"
+#include "code\modules\unit_tests\alt_appearances_test.dm"
+#include "code\modules\unit_tests\area_tests.dm"
+#include "code\modules\unit_tests\area_turfs_cache.dm"
+#include "code\modules\unit_tests\atmospherics_tests.dm"
+#include "code\modules\unit_tests\cargo_tests.dm"
+#include "code\modules\unit_tests\closets.dm"
+#include "code\modules\unit_tests\component_tests.dm"
+#include "code\modules\unit_tests\culture.dm"
+#include "code\modules\unit_tests\equipment_tests.dm"
+#include "code\modules\unit_tests\food_tests.dm"
+#include "code\modules\unit_tests\foundation_tests.dm"
+#include "code\modules\unit_tests\fusion_plants.dm"
+#include "code\modules\unit_tests\graph_tests.dm"
+#include "code\modules\unit_tests\icon_tests.dm"
+#include "code\modules\unit_tests\integrated_circuits.dm"
+#include "code\modules\unit_tests\job_tests.dm"
+#include "code\modules\unit_tests\loadout_tests.dm"
+#include "code\modules\unit_tests\machine_tests.dm"
+#include "code\modules\unit_tests\map_tests.dm"
+#include "code\modules\unit_tests\mob_tests.dm"
+#include "code\modules\unit_tests\movement_tests.dm"
+#include "code\modules\unit_tests\music_test.dm"
+#include "code\modules\unit_tests\observation_tests.dm"
+#include "code\modules\unit_tests\organ_tests.dm"
+#include "code\modules\unit_tests\overmap_tests.dm"
+#include "code\modules\unit_tests\power_tests.dm"
+#include "code\modules\unit_tests\reagent_color_test.dm"
+#include "code\modules\unit_tests\seed_tests.dm"
+#include "code\modules\unit_tests\shuttle_tests.dm"
+#include "code\modules\unit_tests\submaps.dm"
+#include "code\modules\unit_tests\subsystem_tests.dm"
+#include "code\modules\unit_tests\test_obj.dm"
+#include "code\modules\unit_tests\time_tests.dm"
+#include "code\modules\unit_tests\unique_tests.dm"
+#include "code\modules\unit_tests\uplink_tests.dm"
+#include "code\modules\unit_tests\view_variables_test.dm"
+#include "code\modules\unit_tests\virtual_mob_tests.dm"
+#include "code\modules\unit_tests\zas_tests.dm"
+#include "code\modules\unit_tests\~helpers.dm"
#include "code\modules\ventcrawl\ventcrawl.dm"
#include "code\modules\ventcrawl\ventcrawl_atmospherics.dm"
#include "code\modules\ventcrawl\ventcrawl_multiz.dm"
@@ -3171,6 +3358,12 @@
#include "code\modules\webhooks\webhook_roundend.dm"
#include "code\modules\webhooks\webhook_roundstart.dm"
#include "code\modules\webhooks\webhook_submap.dm"
+#include "code\modules\world_topic\_spam_prevention_handler.dm"
+#include "code\modules\world_topic\_topic_base.dm"
+#include "code\modules\world_topic\adminwho.dm"
+#include "code\modules\world_topic\ping.dm"
+#include "code\modules\world_topic\players.dm"
+#include "code\modules\world_topic\status.dm"
#include "code\modules\xenoarcheaology\anomaly_container.dm"
#include "code\modules\xenoarcheaology\boulder.dm"
#include "code\modules\xenoarcheaology\effect.dm"
@@ -3190,6 +3383,7 @@
#include "code\modules\xenoarcheaology\effects\goodfeeling.dm"
#include "code\modules\xenoarcheaology\effects\heal.dm"
#include "code\modules\xenoarcheaology\effects\heat.dm"
+#include "code\modules\xenoarcheaology\effects\human_ifier.dm"
#include "code\modules\xenoarcheaology\effects\hurt.dm"
#include "code\modules\xenoarcheaology\effects\pushback.dm"
#include "code\modules\xenoarcheaology\effects\radiate.dm"
@@ -3218,6 +3412,7 @@
#include "code\modules\xenoarcheaology\tools\suspension_generator.dm"
#include "code\modules\xenoarcheaology\tools\tools.dm"
#include "code\modules\xenoarcheaology\tools\tools_pickaxe.dm"
+#include "code\modules\xenoarcheaology\tools\transport_drone.dm"
#include "code\modules\xenoarcheaology\triggers\_trigger.dm"
#include "code\modules\xenoarcheaology\triggers\chemical.dm"
#include "code\modules\xenoarcheaology\triggers\energy.dm"
@@ -3246,52 +3441,51 @@
#include "code\procs\dbcore.dm"
#include "code\procs\hud.dm"
#include "code\procs\radio.dm"
-#include "code\unit_tests\_defines.dm"
-#include "code\unit_tests\_includes.dm"
-#include "code\unit_tests\alt_appearances_test.dm"
-#include "code\unit_tests\area_tests.dm"
-#include "code\unit_tests\atmospherics_tests.dm"
-#include "code\unit_tests\cargo_tests.dm"
-#include "code\unit_tests\closets.dm"
-#include "code\unit_tests\culture.dm"
-#include "code\unit_tests\equipment_tests.dm"
-#include "code\unit_tests\food_tests.dm"
-#include "code\unit_tests\foundation_tests.dm"
-#include "code\unit_tests\fusion_plants.dm"
-#include "code\unit_tests\graph_tests.dm"
-#include "code\unit_tests\icon_tests.dm"
-#include "code\unit_tests\integrated_circuits.dm"
-#include "code\unit_tests\job_tests.dm"
-#include "code\unit_tests\loadout_tests.dm"
-#include "code\unit_tests\machine_tests.dm"
-#include "code\unit_tests\map_tests.dm"
-#include "code\unit_tests\mob_tests.dm"
-#include "code\unit_tests\movement_tests.dm"
-#include "code\unit_tests\music_test.dm"
-#include "code\unit_tests\observation_tests.dm"
-#include "code\unit_tests\organ_tests.dm"
-#include "code\unit_tests\overmap_tests.dm"
-#include "code\unit_tests\power_tests.dm"
-#include "code\unit_tests\reagent_color_test.dm"
-#include "code\unit_tests\seed_tests.dm"
-#include "code\unit_tests\shuttle_tests.dm"
-#include "code\unit_tests\submaps.dm"
-#include "code\unit_tests\subsystem_tests.dm"
-#include "code\unit_tests\test_obj.dm"
-#include "code\unit_tests\time_tests.dm"
-#include "code\unit_tests\unique_tests.dm"
-#include "code\unit_tests\unit_test.dm"
-#include "code\unit_tests\uplink_tests.dm"
-#include "code\unit_tests\view_variables_test.dm"
-#include "code\unit_tests\virtual_mob_tests.dm"
-#include "code\unit_tests\zas_tests.dm"
-#include "code\unit_tests\~helpers.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
+#include "maps\_map_include.dm"
#include "maps\_maps.dm"
-#include "maps\deepmaint\deepmaint.dm"
-#include "maps\deepmaint\deepmaint_rooms\template.dm"
-#include "maps\deepmaint\deepmaint_rooms\core\_core_rooms.dm"
-#include "maps\deepmaint\deepmaint_rooms\normal\_normal_rooms.dm"
+#include "maps\sierra\loadout\loadout_documents.dm"
+#include "mods\_modpack.dm"
+#include "mods\global_modpacks.dm"
+#include "mods\_master_files\code\game\world.dm"
+#include "mods\_master_files\code\game\gamemodes\ert.dm"
+#include "mods\_master_files\code\game\objects\effects\decals\contraband.dm"
+#include "mods\_master_files\code\game\objects\structures\crates_lockers\closets\_closet_appearance_definitions.dm"
+#include "mods\_master_files\code\modules\clothing\spacesuits\spacesuits.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\_culture.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_adherent.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_diona.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_human.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_ipc.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_serpentid.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_skrell.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_unathi.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\culture\cultures_vox.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\faction\factions_adherent.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\faction\factions_human.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\faction\factions_skrell.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\faction\factions_unathi.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\faction\factions_vox.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\_location.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_adherent.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_diona.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_human.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_other.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_serpentids.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_skrell.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_unathi.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\location\locations_vox.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\religion\religions_human.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\religion\religions_skrell.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\religion\religions_unathi.dm"
+#include "mods\_master_files\code\modules\culture_descriptor\religion\religions_vox.dm"
+#include "mods\_master_files\code\modules\events\gravity.dm"
+#include "mods\_master_files\code\modules\mob\new_player\new_player.dm"
+#include "mods\_master_files\code\modules\power\gravitygenerator.dm"
+#include "mods\_master_files\code\modules\projectiles\projectile\bullets.dm"
+#include "mods\_master_files\maps\sierra\sierra_ranks.dm"
+#include "mods\_master_files\maps\sierra\items\rigs.dm"
+#include "mods\client_verbs\code\credits_datums.dm"
#include "~code\global_init.dm"
// END_INCLUDE
diff --git a/bin/build.cmd b/bin/build.cmd
new file mode 100644
index 0000000000000..98c2ef45e15ba
--- /dev/null
+++ b/bin/build.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error build %*
diff --git a/bin/clean.cmd b/bin/clean.cmd
new file mode 100644
index 0000000000000..8eacd92ebd7a0
--- /dev/null
+++ b/bin/clean.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error clean-all %*
diff --git a/bin/server.cmd b/bin/server.cmd
new file mode 100644
index 0000000000000..c6e6642baf489
--- /dev/null
+++ b/bin/server.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error server %*
diff --git a/bin/test.cmd b/bin/test.cmd
new file mode 100644
index 0000000000000..a76a9c6745c23
--- /dev/null
+++ b/bin/test.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error dm-test %*
diff --git a/bin/tgfont.cmd b/bin/tgfont.cmd
new file mode 100644
index 0000000000000..b768c81d653ae
--- /dev/null
+++ b/bin/tgfont.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error tg-font %*
diff --git a/bin/tgui-bench.cmd b/bin/tgui-bench.cmd
new file mode 100644
index 0000000000000..333115f79548f
--- /dev/null
+++ b/bin/tgui-bench.cmd
@@ -0,0 +1,3 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-bench %*
+pause
diff --git a/bin/tgui-build.cmd b/bin/tgui-build.cmd
new file mode 100644
index 0000000000000..7804fc6daaadf
--- /dev/null
+++ b/bin/tgui-build.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui tgui-lint tgui-test %*
diff --git a/bin/tgui-dev.cmd b/bin/tgui-dev.cmd
new file mode 100644
index 0000000000000..25ff3495d429b
--- /dev/null
+++ b/bin/tgui-dev.cmd
@@ -0,0 +1,2 @@
+@echo off
+call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-dev %*
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000000000..77aa384963110
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,19 @@
+{
+ "linter": {
+ "rules": {
+ "complexity": {
+ "useOptionalChain": "off"
+ },
+ "performance": {
+ "noDelete": "off"
+ },
+ "style": {
+ "noArguments": "off",
+ "noNegationElse": "off",
+ "noVar": "off",
+ "useConst": "off",
+ "useTemplate": "off"
+ }
+ }
+ }
+}
diff --git a/code/__datastructures/globals.dm b/code/__datastructures/globals.dm
index 4d4f4ca1c506f..a2bc4aef4a04c 100644
--- a/code/__datastructures/globals.dm
+++ b/code/__datastructures/globals.dm
@@ -9,10 +9,10 @@ var/global/datum/globals/GLOB
var/static/atom/movable/clickable_stat/__stat_line
-/datum/globals/Destroy(force)
+/datum/globals/Destroy()
if (!GLOB || GLOB == src)
GLOB = src
- return 1 //QDEL_HINT_LETMELIVE
+ return QDEL_HINT_LETMELIVE
return ..()
@@ -78,7 +78,6 @@ var/global/datum/globals/GLOB
#define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list())
-#if !defined(TESTING)
/// Prevents the GLOB member from being shown in View Variables.
#define GLOBAL_PROTECT(X) \
/datum/globals/proc/GI_P_##X() { \
@@ -90,6 +89,3 @@ var/global/datum/globals/GLOB
done = TRUE; \
VV_hidden(#X); \
}
-#else
-#define GLOBAL_PROTECT(X)
-#endif
diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm
index 70cca5de41344..edd52aa3fb13d 100644
--- a/code/__defines/MC.dm
+++ b/code/__defines/MC.dm
@@ -168,26 +168,10 @@ if(Datum.is_processing) {\
* SStimer
****/
-/// Create a hash from the timer's configuration and don't run it if one already exists.
-#define TIMER_UNIQUE FLAG(0)
+#define addtimer(args...) _addtimer(args, source ="[__FILE__]#[__LINE__]")
-/// For timers with TIMER_UNIQUE, do not include the timer's delay as part of its uniquenes.
-#define TIMER_NO_HASH_WAIT FLAG(4)
-
-/// For timers with TIMER_UNIQUE, replace the old timer instead of discarding the new one.
-#define TIMER_OVERRIDE FLAG(1)
-
-/// Use real world time instead of server time. More expensive and should be reserved for client display like animations and sounds.
-#define TIMER_CLIENT_TIME FLAG(2)
-
-/// The call to addtimer() will return a timer ID which may be passed to deltimer() to stop it if it still exists.
-#define TIMER_STOPPABLE FLAG(3)
-
-/// Repeat the timer until it's deleted or the parent is destroyed.
-#define TIMER_LOOP FLAG(5)
-
-/// The default timer ID.
-#define TIMER_ID_NULL -1
+/****
+ * Helper for waits
+ ****/
-/// Automatically adding filename and line to the timer's arguments for debugging purposes.
-#define addtimer(args...) _addtimer(args, file = __FILE__, line = __LINE__)
+#define UNTIL(X) while(!(X)) stoplag()
diff --git a/code/__defines/__dview.dm b/code/__defines/__dview.dm
new file mode 100644
index 0000000000000..52553ef99784a
--- /dev/null
+++ b/code/__defines/__dview.dm
@@ -0,0 +1,14 @@
+//DVIEW defines
+
+#define FOR_DVIEW(type, range, center, invis_flags) \
+ global.dview_mob.loc = center; \
+ global.dview_mob.see_invisible = invis_flags; \
+ for(type in view(range, dview_mob))
+
+#define END_FOR_DVIEW dview_mob.loc = null
+
+#define DVIEW(output, range, center, invis_flags) \
+ global.dview_mob.loc = center; \
+ global.dview_mob.see_invisible = invis_flags; \
+ output = view(range, dview_mob); \
+ global.dview_mob.loc = null;
diff --git a/code/__defines/__icons.dm b/code/__defines/__icons.dm
new file mode 100644
index 0000000000000..0a4a156af7eda
--- /dev/null
+++ b/code/__defines/__icons.dm
@@ -0,0 +1,13 @@
+/// Simple macro wrapper for BYOND proc `icon_states`, but with use of cache
+/// Checkout `code/datums/repositories/icon_states.dm` for details
+/// Should be used instead of `icon_states`
+#define ICON_STATES(icon) GLOB.icon_states_repository.get_icon_states(icon)
+
+/// Helper to efficiently check if passed `icon_state` is in `icon`
+#define ICON_HAS_STATE(icon, icon_state) GLOB.icon_states_repository.icon_has_state(icon, icon_state)
+
+/// Helper to efficiently check if passed `icon_state` is in list of `icons`
+#define ANY_ICON_HAS_STATE(icons, icon_state) GLOB.icon_states_repository.any_icon_has_state(icons, icon_state)
+
+/// Helper to efficiently check if passed icon does not have any states
+#define ICON_IS_EMPTY(icon) GLOB.icon_states_repository.is_icon_empty(icon)
diff --git a/code/__defines/__initialization.dm b/code/__defines/__initialization.dm
index 83dfb481df8de..7d9c1a57995da 100644
--- a/code/__defines/__initialization.dm
+++ b/code/__defines/__initialization.dm
@@ -3,9 +3,9 @@
#define INITIALIZATION_INNEW_MAPLOAD 2 //New should call Initialize(TRUE)
#define INITIALIZATION_INNEW_REGULAR 3 //New should call Initialize(FALSE)
-#define INITIALIZE_HINT_NORMAL 0 //Nothing happens
-#define INITIALIZE_HINT_LATELOAD 1 //Call LateInitialize
-#define INITIALIZE_HINT_QDEL 2 //Call qdel on the atom
+#define INITIALIZE_HINT_NORMAL 1 //Nothing happens
+#define INITIALIZE_HINT_LATELOAD 2 //Call LateInitialize
+#define INITIALIZE_HINT_QDEL 3 //Call qdel on the atom
#define ATOM_FLAG_INITIALIZED FLAG(0) // The atom has been initialized. Also see flags.dm
diff --git a/code/__defines/__renderer.dm b/code/__defines/__renderer.dm
index a84135b006791..3a3b0e8cafd02 100644
--- a/code/__defines/__renderer.dm
+++ b/code/__defines/__renderer.dm
@@ -12,9 +12,6 @@
EFFECTS_LAYER = 5000
TOPDOWN_LAYER = 10000
BACKGROUND_LAYER = 20000
- EFFECTS_LAYER = 5000
- TOPDOWN_LAYER = 10000
- BACKGROUND_LAYER = 20000
------
FLOAT_PLANE = -32767
@@ -43,6 +40,8 @@
#define HEAT_EFFECT_PLANE -4
#define HEAT_EFFECT_TARGET "*heat"
+#define COLD_EFFECT_TARGET "*cold"
+#define COLD_EFFECT_BACK_TARGET "*coldb"
#define HEAT_COMPOSITE_TARGET "*heatc"
#define WARP_EFFECT_PLANE -3
@@ -134,33 +133,43 @@
#define OBFUSCATION_LAYER 5.2
#define BASE_AREA_LAYER 999
-#define OBSERVER_PLANE 2
+#define OBSERVER_PLANE 2
-#define LIGHTING_PLANE 3 // For Lighting. - The highest plane (ignoring all other even higher planes)
- #define LIGHTBULB_LAYER 0
- #define LIGHTING_LAYER 1
- #define ABOVE_LIGHTING_LAYER 2
+#define LIGHTING_PLANE 3 // For Lighting. - The highest plane (ignoring all other even higher planes)
+ #define LIGHTBULB_LAYER 0
+ #define LIGHTING_LAYER 1
+ #define ABOVE_LIGHTING_LAYER 2
-#define EFFECTS_ABOVE_LIGHTING_PLANE 4 // For glowy eyes, laser beams, etc. that shouldn't be affected by darkness
- #define EYE_GLOW_LAYER 1
- #define BEAM_PROJECTILE_LAYER 2
+#define EFFECTS_ABOVE_LIGHTING_PLANE 7 // For glowy eyes, laser beams, etc. that shouldn't be affected by darkness
+ #define EYE_GLOW_LAYER 1
+ #define BEAM_PROJECTILE_LAYER 2
#define SUPERMATTER_WALL_LAYER 3
#define SPEECH_INDICATOR_LAYER 4
-#define FULLSCREEN_PLANE 5 // for fullscreen overlays that do not cover the hud.
+/// This plane masks out lighting, to create an "emissive" effect for e.g glowing screens in otherwise dark areas.
+#define EMISSIVE_PLANE 8
+#define EMISSIVE_TARGET "*emissive"
+ /// The layer you should use when you -really- don't want an emissive overlay to be blocked.
+ #define EMISSIVE_LAYER_UNBLOCKABLE 9999
- #define FULLSCREEN_LAYER 0
- #define DAMAGE_LAYER 1
- #define IMPAIRED_LAYER 2
- #define BLIND_LAYER 3
- #define CRIT_LAYER 4
+#define FULLSCREEN_PLANE 9 // for fullscreen overlays that do not cover the hud.
-#define HUD_PLANE 6
- #define UNDER_HUD_LAYER 0
- #define HUD_BASE_LAYER 2
- #define HUD_ITEM_LAYER 3
- #define HUD_ABOVE_ITEM_LAYER 4
+ #define FULLSCREEN_LAYER 0
+ #define DAMAGE_LAYER 1
+ #define IMPAIRED_LAYER 2
+ #define BLIND_LAYER 3
+ #define CRIT_LAYER 4
+#define HUD_PLANE 10
+ #define UNDER_HUD_LAYER 0
+ #define HUD_BASE_LAYER 2
+ #define HUD_CLICKABLE_LAYER 3
+ #define HUD_ITEM_LAYER 4
+ #define HUD_ABOVE_ITEM_LAYER 5
+ #define HUD_ABOVE_HUD_LAYER 6
+
+#define RUNECHAT_PLANE 11
+#define BALLOON_CHAT_PLANE 12
//-------------------- Rendering ---------------------
@@ -176,22 +185,13 @@
/// The final render group, for compositing
#define RENDER_GROUP_FINAL 999
-
-/// Causes the atom to ignore clicks, hovers, etc.
-#define MOUSE_OPACITY_UNCLICKABLE 0
-
-/// Causes the atom to catch clicks, hovers, etc.
-#define MOUSE_OPACITY_NORMAL 1
-
-/// Causes the atom to catch clicks, hovers, etc, taking priority over NORMAL for a shared pointer target.
-#define MOUSE_OPACITY_PRIORITY 2
-
-
/// Integer (One of `*_PLANE`). The atom's rendering plane. See `code\__defines\__renderer.dm` for a list of valid planes. Also see the DM Reference for `plane var (atom)`.
/atom/plane = DEFAULT_PLANE
#define DEFAULT_APPEARANCE_FLAGS (PIXEL_SCALE)
+#define DEFAULT_RENDERER_APPEARANCE_FLAGS (PLANE_MASTER | NO_CLIENT_COLOR)
+
/atom/appearance_flags = DEFAULT_APPEARANCE_FLAGS
/atom/movable/appearance_flags = DEFAULT_APPEARANCE_FLAGS | TILE_BOUND // Most AMs are not visibly bigger than a tile.
/image/appearance_flags = DEFAULT_APPEARANCE_FLAGS
diff --git a/code/__defines/_click.dm b/code/__defines/_click.dm
new file mode 100644
index 0000000000000..07d044f8b8961
--- /dev/null
+++ b/code/__defines/_click.dm
@@ -0,0 +1,45 @@
+//Defines file for byond click related parameters
+//this is mostly for ease of use and for finding all the things that use say RIGHT_CLICK rather then just searching "right"
+
+//Mouse buttons held
+#define RIGHT_CLICK "right"
+#define MIDDLE_CLICK "middle"
+#define LEFT_CLICK "left"
+
+///Mouse button that was just clicked/released
+///if(modifiers[BUTTON] == LEFT_CLICK)
+#define BUTTON "button"
+
+//Keys held down during the mouse action
+#define CTRL_CLICK "ctrl"
+#define ALT_CLICK "alt"
+#define SHIFT_CLICK "shift"
+
+//Cells involved if using a Grid control
+#define DRAG_CELL "drag-cell"
+#define DROP_CELL "drop-cell"
+
+//The button used for dragging (only sent for unrelated mouse up/down messages during a drag)
+#define DRAG "drag"
+
+//If the mouse is over a link in maptext, or this event is related to clicking such a link
+#define LINK "link"
+
+//Pixel coordinates relative to the icon's position on screen
+#define VIS_X "vis-x"
+#define VIS_Y "vis-y"
+
+//Pixel coordinates within the icon, in the icon's coordinate space
+#define ICON_X "icon-x"
+#define ICON_Y "icon-y"
+
+//Pixel coordinates in screen_loc format ("[tile_x]:[pixel_x],[tile_y]:[pixel_y]")
+#define SCREEN_LOC "screen-loc"
+
+//https://secure.byond.com/docs/ref/info.html#/atom/var/mouse_opacity
+/// Objects will ignore being clicked on regardless of their transparency (used in parallax, lighting effects, holograms, lasers, etc.)
+#define MOUSE_OPACITY_TRANSPARENT 0
+/// Objects will be clicked on if it is the topmost object and the pixel isn't transparent at the position of the mouse (default behavior for 99.99% of game objects)
+#define MOUSE_OPACITY_ICON 1
+/// Objects will be always be clicked on regardless of pixel transparency or other objects at that location (used in space vines, megafauna, storage containers)
+#define MOUSE_OPACITY_OPAQUE 2
diff --git a/code/__defines/_excom.dm b/code/__defines/_excom.dm
new file mode 100644
index 0000000000000..d8cf8f709272d
--- /dev/null
+++ b/code/__defines/_excom.dm
@@ -0,0 +1,2 @@
+#define EXCOM_MSG_AHELP "ahelp"
+#define EXCOM_MSG_STATUS "status"
diff --git a/code/__defines/_renderer.dm b/code/__defines/_renderer.dm
index 318c454dd76a0..9be51256f3e98 100644
--- a/code/__defines/_renderer.dm
+++ b/code/__defines/_renderer.dm
@@ -10,7 +10,7 @@
/// The base /renderer definition and defaults.
/atom/movable/renderer
abstract_type = /atom/movable/renderer
- appearance_flags = PLANE_MASTER
+ appearance_flags = DEFAULT_RENDERER_APPEARANCE_FLAGS
screen_loc = "CENTER"
plane = LOWEST_PLANE
blend_mode = BLEND_OVERLAY
@@ -127,20 +127,24 @@ INITIALIZE_IMMEDIATE(/atom/movable/renderer)
* Higher plane values are composited over lower. Here, they are ordered from under to over.
*/
- /// Handles byond internal letterboxing. Avoid touching.
+/// Handles byond internal letterboxing. Avoid touching.
/atom/movable/renderer/letterbox
name = "Letterbox"
group = RENDER_GROUP_SCENE
plane = BLACKNESS_PLANE
- appearance_flags = PLANE_MASTER | NO_CLIENT_COLOR
blend_mode = BLEND_MULTIPLY
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/renderer/space
name = "Space"
group = RENDER_GROUP_SCENE
plane = SPACE_PLANE
+/atom/movable/renderer/space_dust
+ name = "Space Dust"
+ group = RENDER_GROUP_SCENE
+ plane = DUST_PLANE
+
/atom/movable/renderer/skybox
name = "Skybox"
group = RENDER_GROUP_SCENE
@@ -166,6 +170,10 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
name = "Zrenderer [plane]"
. = ..()
+/atom/movable/renderer/shared/zmimic/Destroy()
+ GLOB.zmimic_renderers -= src
+ return ..()
+
// Draws the game world; live mobs, items, turfs, etc.
/atom/movable/renderer/game
name = "Game"
@@ -184,16 +192,17 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
name = "Lighting"
group = RENDER_GROUP_SCENE
plane = LIGHTING_PLANE
- appearance_flags = PLANE_MASTER | NO_CLIENT_COLOR
relay_blend_mode = BLEND_MULTIPLY
- color = list(
- -1, 0, 0, 0, // R
- 0, -1, 0, 0, // G
- 0, 0, -1, 0, // B
- 0, 0, 0, 0, // A
- 1, 1, 1, 1 // Mapping
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+
+/atom/movable/renderer/lighting/Initialize()
+ . = ..()
+ filters += filter(
+ type = "alpha",
+ render_source = EMISSIVE_TARGET,
+ flags = MASK_INVERSE
)
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
/// Draws visuals that should not be affected by darkness.
/atom/movable/renderer/above_lighting
@@ -207,7 +216,7 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
name = "Screen Effects"
group = RENDER_GROUP_SCENE
plane = FULLSCREEN_PLANE
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/// Draws user interface elements.
@@ -216,6 +225,15 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
group = RENDER_GROUP_SCREEN
plane = HUD_PLANE
+/atom/movable/renderer/runechat
+ name = "Runechat"
+ group = RENDER_GROUP_SCREEN
+ plane = RUNECHAT_PLANE
+
+/atom/movable/renderer/balloon_chat
+ name = "Balloon chat"
+ group = RENDER_GROUP_SCREEN
+ plane = BALLOON_CHAT_PLANE
/* *
* Group renderers
@@ -233,7 +251,6 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
group = RENDER_GROUP_FINAL
plane = RENDER_GROUP_SCENE
-
/// Render group for stuff OUTSIDE the typical game context - UI, full screen effects, etc.
/atom/movable/renderer/screen_group
name = "Screen Group"
@@ -258,13 +275,13 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
*/
-/// Renders the /obj/effect/effect/warp example effect as well as gravity catapult effects
+/// Renders the /obj/effect/warp example effect as well as gravity catapult effects
/atom/movable/renderer/warp
name = "Warp Effect"
group = RENDER_GROUP_NONE
plane = WARP_EFFECT_PLANE
render_target_name = "*warp"
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
//Similar to warp but not as strong
/atom/movable/renderer/heat
@@ -272,9 +289,10 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
group = RENDER_GROUP_NONE
plane = HEAT_EFFECT_PLANE
render_target_name = HEAT_COMPOSITE_TARGET
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
var/obj/gas_heat_object = null
+ var/obj/gas_cold_object = null // Not strictly a heat effect but similar setup so may as well
/atom/movable/renderer/heat/proc/Setup()
var/mob/M = owner
@@ -285,22 +303,28 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
if(gas_heat_object)
vis_contents -= gas_heat_object
+ if(gas_cold_object)
+ vis_contents -= gas_cold_object
+
if (quality == GLOB.PREF_LOW)
- if(!istype(gas_heat_object, /obj/effect/heat))
- QDEL_NULL(gas_heat_object)
- gas_heat_object = new /obj/effect/heat(null)
+ QDEL_NULL(gas_heat_object)
+ gas_heat_object = new /obj/heat(null)
+
+ QDEL_NULL(gas_cold_object)
+ gas_cold_object = new /obj/effect/cold_mist_gas(null)
else
- if(!istype(gas_heat_object, /obj/particle_emitter/heat))
- QDEL_NULL(gas_heat_object)
- gas_heat_object = new /obj/particle_emitter/heat(null, -1)
+ QDEL_NULL(gas_heat_object)
+ QDEL_NULL(gas_cold_object)
+ gas_cold_object = new /obj/particle_emitter/mist/gas(null)
if (quality == GLOB.PREF_MED)
- gas_heat_object.particles?.count = 250
- gas_heat_object.particles?.spawning = 15
+ gas_heat_object = new /obj/particle_emitter/heat(null)
else if (quality == GLOB.PREF_HIGH)
- gas_heat_object.particles?.count = 600
- gas_heat_object.particles?.spawning = 35
+ gas_heat_object = new /obj/particle_emitter/heat/high(null)
vis_contents += gas_heat_object
+ if(config.enable_cold_mist)
+ vis_contents += gas_cold_object
+
/atom/movable/renderer/heat/Initialize()
. = ..()
@@ -314,3 +338,30 @@ GLOBAL_LIST_EMPTY(zmimic_renderers)
. = ..()
filters += filter(type = "displace", render_source = "*warp", size = 5)
filters += filter(type = "displace", render_source = HEAT_COMPOSITE_TARGET, size = 2.5)
+
+/*!
+ * This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
+ *
+ * Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.
+ * Emissive blockers are pasted with an atom color that converts them to be entirely some different color.
+ * Emissive overlays and emissive blockers are put onto the same plane.
+ * The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.
+ * A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.
+ * This is then used to alpha mask the lighting plane.
+ *
+ * This works best if emissive overlays applied only to objects that emit light,
+ * since luminosity=0 turfs may not be rendered.
+ */
+/atom/movable/renderer/emissive
+ name = "Emissive"
+ group = RENDER_GROUP_NONE
+ plane = EMISSIVE_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target_name = EMISSIVE_TARGET
+
+/atom/movable/renderer/emissive/Initialize()
+ . = ..()
+ filters += filter(
+ type = "color",
+ color = GLOB.em_mask_matrix
+ )
diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm
index 2c0ebdb4b0943..82d32cb4d73c3 100644
--- a/code/__defines/admin.dm
+++ b/code/__defines/admin.dm
@@ -31,7 +31,9 @@
#define R_SOUNDS FLAG(11)
#define R_SPAWN FLAG(12)
#define R_MOD FLAG(13)
-#define R_HOST FLAG(14)
+#define R_XENO FLAG(14)
+#define R_HOST FLAG(15)
+
#define R_INVESTIGATE (R_ADMIN | R_MOD)
#define R_MAXPERMISSION R_HOST
@@ -44,4 +46,4 @@
#define TICKET_ASSIGNED 2 // An admin has assigned themself to the ticket and will respond
#define LAST_CKEY(M) (M.ckey || M.last_ckey)
-#define LAST_KEY(M) (M.key || M.last_ckey)
\ No newline at end of file
+#define LAST_KEY(M) (M.key || M.last_ckey)
diff --git a/code/__defines/ai.dm b/code/__defines/ai.dm
new file mode 100644
index 0000000000000..78a3d87ff688a
--- /dev/null
+++ b/code/__defines/ai.dm
@@ -0,0 +1,54 @@
+// Defines for the ai_intelligence var.
+// Controls if the mob will do 'advanced tactics' like running from grenades.
+#define AI_DUMB 1 // Be dumber than usual.
+#define AI_NORMAL 2 // Default level.
+#define AI_SMART 3 // Will do more processing to be a little smarter, like not walking while confused if it could risk stepping randomly onto a bad tile.
+
+#define ai_log(M,V) if(debug_ai) ai_log_output(M,V)
+
+// Logging level defines.
+#define AI_LOG_OFF 0 // Don't show anything.
+#define AI_LOG_ERROR 1 // Show logs of things likely causing the mob to not be functioning correctly.
+#define AI_LOG_WARNING 2 // Show less serious but still helpful to know issues that might be causing things to work incorrectly.
+#define AI_LOG_INFO 3 // Important regular events, like selecting a target or switching stances.
+#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution.
+#define AI_LOG_TRACE 5 // Even more detailed than the last. Will absolutely flood your chatlog.
+
+// Results of pre-movement checks.
+// Todo: Move outside AI code?
+#define MOVEMENT_ON_COOLDOWN -1 // Recently moved and needs to try again soon.
+#define MOVEMENT_FAILED 0 // Move() returned false for whatever reason and the mob didn't move.
+#define MOVEMENT_SUCCESSFUL 1 // Move() returned true and the mob hopefully moved.
+
+// Results of pre-attack checks.
+#define ATTACK_ON_COOLDOWN -1 // Recently attacked and needs to try again soon.
+#define ATTACK_FAILED 0 // Something else went wrong! Maybe they moved away!
+#define ATTACK_SUCCESSFUL 1 // We attacked (or tried to, misses count too)
+
+// Reasons for targets to not be valid. Based on why, the AI responds differently.
+#define AI_TARGET_VALID 0 // We can fight them.
+#define AI_TARGET_INVIS 1 // They were in field of view but became invisible. Switch to STANCE_BLINDFIGHT if no other viable targets exist.
+#define AI_TARGET_NOSIGHT 2 // No longer in field of view. Go STANCE_REPOSITION to their last known location if no other targets are seen.
+#define AI_TARGET_ALLY 3 // They are an ally. Find a new target.
+#define AI_TARGET_DEAD 4 // They're dead. Find a new target.
+#define AI_TARGET_INVINCIBLE 5 // Target is currently unable to receive damage for whatever reason. Find a new target or wait.
+
+// Stances to determine AI state.
+#define STANCE_SLEEP 0 // Doing (almost) nothing, to save on CPU because nobody is around to notice or the mob died.
+#define STANCE_IDLE 1 // The more or less default state. Wanders around, looks for baddies, and spouts one-liners.
+#define STANCE_ALERT 2 // A baddie is visible but not too close, and essentially we tell them to go away or die.
+#define STANCE_APPROACH 3 // Attempting to get into range to attack them.
+#define STANCE_FIGHT 4 // Actually fighting, with melee or ranged.
+#define STANCE_BLINDFIGHT 5 // Fighting something that cannot be seen by the mob, from invisibility or out of sight.
+#define STANCE_REPOSITION 6 // Relocating to a better position while in combat. Also used when moving away from a danger like grenades.
+#define STANCE_MOVE 7 // Similar to above but for out of combat. If a baddie is seen, they'll cancel and fight them.
+#define STANCE_FOLLOW 8 // Following somone, without trying to murder them.
+#define STANCE_FLEE 9 // Run away from the target because they're too spooky/we're dying/some other reason.
+#define STANCE_DISABLED 10 // Used when the holder is afflicted with certain status effects, such as stuns or confusion.
+
+#define STANCE_ATTACK 11 // Backwards compatability
+#define STANCE_ATTACKING 12 // Ditto
+
+#define STANCES_COMBAT list(STANCE_ALERT, STANCE_APPROACH, STANCE_FIGHT, STANCE_BLINDFIGHT, STANCE_REPOSITION)
+
+#define AUTO_ANNOUNCER_NAME "Автоматическая система оповещений"
diff --git a/code/__defines/ao_misc.dm b/code/__defines/ao_misc.dm
index d4be84dc0662a..f8813b26060e9 100644
--- a/code/__defines/ao_misc.dm
+++ b/code/__defines/ao_misc.dm
@@ -1,9 +1,9 @@
/*
Define for getting a bitfield of adjacent turfs that meet a condition.
- ORIGIN is the object to step from, VAR is the var to write the bitfield to
- TVAR is the temporary turf variable to use, FUNC is the condition to check.
- FUNC generally should reference TVAR.
- example:
+ORIGIN is the object to step from, VAR is the var to write the bitfield to
+TVAR is the temporary turf variable to use, FUNC is the condition to check.
+FUNC generally should reference TVAR.
+example:
var/turf/T
var/result = 0
CALCULATE_NEIGHBORS(src, result, T, isopenturf(T))
diff --git a/code/__defines/atmos.dm b/code/__defines/atmos.dm
index 9419357e4ab51..9dca486ef05d5 100644
--- a/code/__defines/atmos.dm
+++ b/code/__defines/atmos.dm
@@ -22,7 +22,7 @@
#define SOUND_MINIMUM_PRESSURE 10
#define PRESSURE_DAMAGE_COEFFICIENT 4 // The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE.
-#define MAX_HIGH_PRESSURE_DAMAGE 4 // This used to be 20... I got this much random rage for some retarded decision by polymorph?! Polymorph now lies in a pool of blood with a katana jammed in his spleen. ~Errorage --PS: The katana did less than 20 damage to him :(
+#define MAX_HIGH_PRESSURE_DAMAGE 4 // This used to be 20
#define LOW_PRESSURE_DAMAGE 0.6 // The amount of damage someone takes when in a low pressure area. (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value).
#define MINIMUM_PRESSURE_DIFFERENCE_TO_SUSPEND (MINIMUM_AIR_TO_SUSPEND*R_IDEAL_GAS_EQUATION*T20C)/CELL_VOLUME // Minimum pressure difference between zones to suspend
@@ -49,6 +49,9 @@
#define CARBON_LIFEFORM_FIRE_RESISTANCE (T0C + 200)
#define CARBON_LIFEFORM_FIRE_DAMAGE 4
+#define FOGGING_TEMPERATURE (T0C + 5)
+#define MAX_FOG_TEMPERATURE (T0C - 50)
+
// Phoron fire properties.
#define PHORON_MINIMUM_BURN_TEMPERATURE (T0C + 126) //400 K - autoignite temperature in tanks and canisters - enclosed environments I guess
#define PHORON_FLASHPOINT (T0C + 246) //519 K - autoignite temperature in air if that ever gets implemented.
@@ -126,3 +129,4 @@
#define GAS_PHORON "phoron"
#define GAS_BORON "boron"
#define GAS_HEAT "heat" //Not a real gas, used for visual effects
+#define GAS_COLD "cold" //Not a real gas, used for visual effects
diff --git a/code/__defines/atmospherics.dm b/code/__defines/atmospherics.dm
index 90b906449774d..da3db5331c8da 100644
--- a/code/__defines/atmospherics.dm
+++ b/code/__defines/atmospherics.dm
@@ -4,11 +4,11 @@
#define PIPE_ROTATE_ONEDIR 2 // Only has one dir, south
//Connection Type Definitions
-#define CONNECT_TYPE_REGULAR 1
-#define CONNECT_TYPE_SUPPLY 2
-#define CONNECT_TYPE_SCRUBBER 4
-#define CONNECT_TYPE_HE 8
-#define CONNECT_TYPE_FUEL 16
+#define CONNECT_TYPE_REGULAR FLAG(0)
+#define CONNECT_TYPE_SUPPLY FLAG(1)
+#define CONNECT_TYPE_SCRUBBER FLAG(2)
+#define CONNECT_TYPE_HE FLAG(3)
+#define CONNECT_TYPE_FUEL FLAG(4)
#define DISPOSAL_FLIP_NONE 0
#define DISPOSAL_FLIP_FLIP 1
@@ -23,4 +23,4 @@
#define PIPE_CLASS_QUATERNARY 4
#define PIPE_CLASS_OMNI 5
-#define ADIABATIC_EXPONENT 0.667 //Actually adiabatic exponent - 1.
\ No newline at end of file
+#define ADIABATIC_EXPONENT 0.667 //Actually adiabatic exponent - 1.
diff --git a/code/__defines/byond_tracy.dm b/code/__defines/byond_tracy.dm
new file mode 100644
index 0000000000000..457636d295f5f
--- /dev/null
+++ b/code/__defines/byond_tracy.dm
@@ -0,0 +1,31 @@
+// Implements https://github.com/mafemergency/byond-tracy
+// Client https://github.com/wolfpld/tracy
+// As of now, only 0.8.2 is supported as a client, this might change in the future however
+
+// In case you need to start the capture as soon as the server boots, uncomment the following lines and recompile:
+
+// /world/New()
+// prof_init()
+// . = ..()
+
+/client/proc/profiler_start()
+ set name = "Start Tracy Profiler"
+ set category = "Debug"
+ set desc = "Starts the tracy profiler, which will await the client connection."
+ switch(alert("Are you sure? Tracy will remain active until the server restarts.", "Tracy Init", "No", "Yes"))
+ if("Yes")
+ prof_init()
+
+/**
+ * Starts Tracy
+ */
+/proc/prof_init()
+ var/lib
+
+ switch(world.system_type)
+ if(MS_WINDOWS) lib = "prof.dll"
+ if(UNIX) lib = "libprof.so"
+ else CRASH("Tracy initialization failed: unsupported platform or DLL not found.")
+
+ var/init = CALL_EXT(lib, "init")()
+ if("0" != init) CRASH("[lib] init error: [init]")
diff --git a/code/__defines/callback.dm b/code/__defines/callback.dm
new file mode 100644
index 0000000000000..f86e968c98767
--- /dev/null
+++ b/code/__defines/callback.dm
@@ -0,0 +1 @@
+#define CALLBACK new /datum/callback
diff --git a/code/__defines/chat.dm b/code/__defines/chat.dm
new file mode 100644
index 0000000000000..149f5d856988c
--- /dev/null
+++ b/code/__defines/chat.dm
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/// How many chat payloads to keep in history
+#define CHAT_RELIABILITY_HISTORY_SIZE 5
+/// How many resends to allow before giving up
+#define CHAT_RELIABILITY_MAX_RESENDS 3
+
+#define MESSAGE_TYPE_SYSTEM "system"
+#define MESSAGE_TYPE_LOCALCHAT "localchat"
+#define MESSAGE_TYPE_RADIO "radio"
+#define MESSAGE_TYPE_INFO "info"
+#define MESSAGE_TYPE_WARNING "warning"
+#define MESSAGE_TYPE_DEADCHAT "deadchat"
+#define MESSAGE_TYPE_OOC "ooc"
+#define MESSAGE_TYPE_ADMINPM "adminpm"
+#define MESSAGE_TYPE_COMBAT "combat"
+#define MESSAGE_TYPE_ADMINCHAT "adminchat"
+#define MESSAGE_TYPE_MENTORCHAT "mentorchat"
+#define MESSAGE_TYPE_EVENTCHAT "eventchat"
+#define MESSAGE_TYPE_ADMINLOG "adminlog"
+#define MESSAGE_TYPE_ATTACKLOG "attacklog"
+#define MESSAGE_TYPE_DEBUG "debug"
diff --git a/code/__defines/chat_boxes.dm b/code/__defines/chat_boxes.dm
new file mode 100644
index 0000000000000..40a1c491cf37f
--- /dev/null
+++ b/code/__defines/chat_boxes.dm
@@ -0,0 +1,8 @@
+#define chat_box_regular(str) ("
" + str + "
")
+#define chat_box_examine(str) ("
" + str + "
")
+#define chat_box_red(str) ("
" + str + "
")
+#define chat_box_green(str) ("
" + str + "
")
+#define chat_box_purple(str) ("
" + str + "
")
+#define chat_box_notice(str) ("
" + str + "
")
+#define chat_box_healthscan(str) ("
" + str + "
")
+#define chat_box_notice_thick(str) ("
" + str + "
")
diff --git a/code/__defines/client.dm b/code/__defines/client.dm
index 0794c852df6f0..41979280cbb6b 100644
--- a/code/__defines/client.dm
+++ b/code/__defines/client.dm
@@ -1,2 +1,3 @@
-#define CLIENT_MIN_FPS 0
-#define CLIENT_MAX_FPS 1000
+#define CLIENT_MIN_FPS 15
+#define CLIENT_MAX_FPS 120
+#define CLIENT_DEFAULT_FPS 66
diff --git a/code/__defines/colors.dm b/code/__defines/colors.dm
index 289afdbe9010e..6c8d73023e0bf 100644
--- a/code/__defines/colors.dm
+++ b/code/__defines/colors.dm
@@ -84,6 +84,17 @@
#define COLOR_VOX "#244238"
+#define CABLE_COLOR_RED "#800000"
+#define CABLE_COLOR_YELLOW "#ffbf00"
+#define CABLE_COLOR_GREEN "#008000"
+#define CABLE_COLOR_BLUE "#3366cc"
+#define CABLE_COLOR_PINK "#800080"
+#define CABLE_COLOR_ORANGE "#ff9900"
+#define CABLE_COLOR_CYAN "#5ca1cc"
+#define CABLE_COLOR_WHITE "#c0c0c0"
+#define CABLE_COLOR_BLACK "#333333"
+
+
#define PIPE_COLOR_GREY "#808080"
#define PIPE_COLOR_RED "#ff0000"
#define PIPE_COLOR_BLUE "#0000ff"
@@ -109,7 +120,7 @@
#define COMMS_COLOR_SECURITY "#930000"
#define COMMS_COLOR_SECURITY_I "#935050"
#define COMMS_COLOR_COMMAND "#204090"
-#define COMMS_COLOR_CENTCOMM "#5c5c7c"
+#define COMMS_COLOR_CENTCOM "#5c5c7c"
#define COMMS_COLOR_SYNDICATE "#6d3f40"
#define COMMS_COLOR_SKRELL "#7331c4"
#define COMMS_COLOR_VOX "#f32b06"
@@ -127,12 +138,26 @@
#define WOOD_COLOR_YELLOW "#e3994e"
#define GLASS_COLOR "#aaccff"
-#define GLASS_COLOR_PHORON "#7c3a9a"
+#define GLASS_COLOR_BORON "#899ebd"
#define GLASS_COLOR_TINTED "#222222"
#define GLASS_COLOR_FROSTED "#eeeeee"
#define COLOR_BLOOD_HUMAN "#a10808"
+// Color defines used by the crew manifest
+#define MANIFEST_COLOR_COMMAND "#204090"
+#define MANIFEST_COLOR_SUPPORT "#3085b6"
+#define MANIFEST_COLOR_SCIENCE "#993399"
+#define MANIFEST_COLOR_SECURITY "#930000"
+#define MANIFEST_COLOR_MEDICAL "#009190"
+#define MANIFEST_COLOR_ENGINEER "#a66300"
+#define MANIFEST_COLOR_SUPPLY "#7f6539"
+#define MANIFEST_COLOR_EXPLORER "#929820"
+#define MANIFEST_COLOR_SERVICE "#709b00"
+#define MANIFEST_COLOR_CIVILIAN "#408010"
+#define MANIFEST_COLOR_MISC "#353a42"
+#define MANIFEST_COLOR_SILICON "#4c535b"
+
//Color defines used by the assembly detailer.
#define COLOR_ASSEMBLY_BLACK "#545454"
#define COLOR_ASSEMBLY_BGRAY "#9497ab"
@@ -180,10 +205,6 @@
#define COLOR_WEBHOOK_DEFAULT 0x8bbbd5
-#define COLOR_DARKMODE_BACKGROUND "#202020"
-#define COLOR_DARKMODE_DARKBACKGROUND "#171717"
-#define COLOR_DARKMODE_TEXT "#a4bad6"
-
// Medical readout colors
#define COLOR_MEDICAL_BRUTE "#ff0000"
#define COLOR_MEDICAL_BURN "#ff7700"
diff --git a/code/__defines/combat.dm b/code/__defines/combat.dm
new file mode 100644
index 0000000000000..47620a76af9d2
--- /dev/null
+++ b/code/__defines/combat.dm
@@ -0,0 +1,10 @@
+/// Alternate attack defines. Return these at the end of procs like afterattack_secondary.
+/// Calls the normal attack proc. For example, if returned in afterattack_secondary, will call afterattack.
+/// Will continue the chain depending on the return value of the non-alternate proc, like with normal attacks.
+#define SECONDARY_ATTACK_CALL_NORMAL 1
+
+/// Cancels the attack chain entirely.
+#define SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN 2
+
+/// Proceed with the attack chain, but don't call the normal methods.
+#define SECONDARY_ATTACK_CONTINUE_CHAIN 3
diff --git a/code/__defines/culture.dm b/code/__defines/culture.dm
index 2cc1ee3fd5454..0131435a771ac 100644
--- a/code/__defines/culture.dm
+++ b/code/__defines/culture.dm
@@ -3,17 +3,25 @@
#define TAG_FACTION "faction"
#define TAG_RELIGION "religion"
+// [SIERRA-EDIT] - EXPANDED_CULTURE_DESCRIPTOR - Перевод дефайнов
+//#define ALL_CULTURAL_TAGS list( \ // SIERRA-EDIT - ORIGINAL
+// TAG_CULTURE = "Culture", \ // SIERRA-EDIT - ORIGINAL
+// TAG_HOMEWORLD = "Residence", \ // SIERRA-EDIT - ORIGINAL
+// TAG_FACTION = "Faction", \ // SIERRA-EDIT - ORIGINAL
+// TAG_RELIGION = "Beliefs" \ // SIERRA-EDIT - ORIGINAL
+// ) // SIERRA-EDIT - ORIGINAL
#define ALL_CULTURAL_TAGS list( \
- TAG_CULTURE = "Culture", \
- TAG_HOMEWORLD = "Residence", \
- TAG_FACTION = "Faction", \
- TAG_RELIGION = "Beliefs" \
+ TAG_CULTURE = "Культура", \
+ TAG_HOMEWORLD = "Место жительства", \
+ TAG_FACTION = "Фракция", \
+ TAG_RELIGION = "Вера" \
)
+// [SIERRA-EDIT]
// Cultural IDs.
#define FACTION_SOL_CENTRAL "Sol Central Government"
#define FACTION_INDIE_CONFED "Gilgamesh Colonial Confederation"
-#define FACTION_NANOTRASEN "NanoTrasen"
+#define FACTION_NANOTRASEN "Nanotrasen"
#define FACTION_FREETRADE "Free Trade Union"
#define FACTION_HEPHAESTUS "Hephaestus Industries"
#define FACTION_XYNERGY "Xynergy"
@@ -61,10 +69,22 @@
#define CULTURE_HUMAN_BELTER "Belter, Ceres"
#define CULTURE_HUMAN_PLUTO "Plutonian"
#define CULTURE_HUMAN_EARTH "Earther"
-#define CULTURE_HUMAN_CETI "Cetite"
-#define CULTURE_HUMAN_SPACER "Spacer, Core Systems"
-#define CULTURE_HUMAN_SPAFRO "Spacer, Frontier Systems"
-#define CULTURE_HUMAN_CONFED "Terran"
+#define CULTURE_HUMAN_CETIN "Cetite, Northern"
+#define CULTURE_HUMAN_CETIS "Cetite, Southern"
+#define CULTURE_HUMAN_CETII "Cetite, Interstate"
+#define CULTURE_HUMAN_FOSTER "Fostersman"
+#define CULTURE_HUMAN_PIRXL "Pirxish, High"
+#define CULTURE_HUMAN_PIRXB "Pirxish, Bugeater"
+#define CULTURE_HUMAN_PIRXF "Pirxish, Frontier"
+#define CULTURE_HUMAN_TADMOR "Tadmorian"
+#define CULTURE_HUMAN_IOLAUS "Iolan"
+#define CULTURE_HUMAN_BRAHE "Brahite"
+#define CULTURE_HUMAN_EOS "Eosic"
+#define CULTURE_HUMAN_SPACER "Spacer"
+#define CULTURE_HUMAN_OFFWORLD "Offworlder"
+#define CULTURE_HUMAN_SOLCOL "Solar-Colonial"
+#define CULTURE_HUMAN_CONFEDC "Terran, Core Systems"
+#define CULTURE_HUMAN_CONFEDO "Terran, Outer Systems"
#define CULTURE_HUMAN_GAIAN "Gaian"
#define CULTURE_HUMAN_OTHER "Other, Humanity"
#define CULTURE_STARLIGHT "Starlit Realms"
@@ -143,11 +163,9 @@
#define RELIGION_VOX "Auralis Reverence"
// IPC cultures.
-#define CULTURE_POSITRONICS "Union Member"
-
-#define HOME_SYSTEM_ROOT "Root"
-
-#define FACTION_POSITRONICS "Positronic Union"
+#define CULTURE_POSITRONICS_GEN1 "First Generation"
+#define CULTURE_POSITRONICS_GEN2 "Second Generation"
+#define CULTURE_POSITRONICS_GEN3 "Third Generation"
// Diona cultures.
#define CULTURE_DIONA "Diona Chorus"
diff --git a/code/__defines/dcs/flags.dm b/code/__defines/dcs/flags.dm
new file mode 100644
index 0000000000000..192049d83b592
--- /dev/null
+++ b/code/__defines/dcs/flags.dm
@@ -0,0 +1,54 @@
+/// Return this from `/datum/component/Initialize` or `/datum/component/OnTransfer` or `/datum/component/on_source_add` to have the component be deleted if it's applied to an incorrect type.
+/// `parent` must not be modified if this is to be returned.
+/// This will be noted in the runtime logs
+#define COMPONENT_INCOMPATIBLE 1
+/// Returned in PostTransfer to prevent transfer, similar to `COMPONENT_INCOMPATIBLE`
+#define COMPONENT_NOTRANSFER 2
+
+/// Return value to cancel attaching
+#define ELEMENT_INCOMPATIBLE 1
+
+// /datum/element flags
+/// Causes the detach proc to be called when the host object is being deleted.
+/// Should only be used if you need to perform cleanup not related to the host object.
+/// You do not need this if you are only unregistering signals, for instance.
+/// You would need it if you are doing something like removing the target from a processing list.
+#define ELEMENT_DETACH_ON_HOST_DESTROY FLAG(0)
+/**
+ * Only elements created with the same arguments given after `argument_hash_start_idx` share an element instance
+ * The arguments are the same when the text and number values are the same and all other values have the same ref
+ */
+#define ELEMENT_BESPOKE FLAG(1)
+/// Causes all detach arguments to be passed to detach instead of only being used to identify the element
+/// When this is used your Detach proc should have the same signature as your Attach proc
+#define ELEMENT_COMPLEX_DETACH FLAG(2)
+/**
+ * Elements with this flag will have their datum lists arguments compared as is,
+ * without the contents being sorted alpha-numerically first.
+ * This is good for those elements where the position of the keys matter, like in the case of color matrices.
+ */
+#define ELEMENT_DONT_SORT_LIST_ARGS FLAG(3)
+/**
+ * Elements with this flag will be ignored by the dcs_check_list_arguments test.
+ * A good example is connect_loc, for which it's pratically undoable unless we force every signal proc to have a different name.
+ */
+#define ELEMENT_NO_LIST_UNIT_TEST FLAG(4)
+
+// How multiple components of the exact same type are handled in the same datum
+/// old component is deleted (default)
+#define COMPONENT_DUPE_HIGHLANDER 0
+/// duplicates allowed
+#define COMPONENT_DUPE_ALLOWED 1
+/// new component is deleted
+#define COMPONENT_DUPE_UNIQUE 2
+/**
+ * Component uses source tracking to manage adding and removal logic.
+ * Add a source/spawn to/the component by using AddComponentFrom(source, component_type, args...)
+ * Removing the last source will automatically remove the component from the parent.
+ * Arguments will be passed to on_source_add(source, args...); ensure that Initialize and on_source_add have the same signature.
+ */
+#define COMPONENT_DUPE_SOURCES 3
+/// old component is given the initialization args of the new
+#define COMPONENT_DUPE_UNIQUE_PASSARGS 4
+/// each component of the same type is consulted as to whether the duplicate should be allowed
+#define COMPONENT_DUPE_SELECTIVE 5
diff --git a/code/__defines/dcs/helpers.dm b/code/__defines/dcs/helpers.dm
new file mode 100644
index 0000000000000..df6acffd761a8
--- /dev/null
+++ b/code/__defines/dcs/helpers.dm
@@ -0,0 +1,26 @@
+/// Used to trigger signals and call procs registered for that signal
+/// The datum hosting the signal is automaticaly added as the first argument
+/// Returns a bitfield gathered from all registered procs
+/// Arguments given here are packaged in a list and given to _SendSignal
+#define SEND_SIGNAL(target, sigtype, arguments...) ( !target._listen_lookup?[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
+
+#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
+
+/// Signifies that this proc is used to handle signals.
+/// Every proc you pass to RegisterSignal must have this.
+#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
+
+/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
+#define AddElement(arguments...) _AddElement(list(##arguments))
+/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments
+#define RemoveElement(arguments...) _RemoveElement(list(##arguments))
+
+/// A wrapper for _AddComponent that allows us to pretend we're using normal named arguments
+#define AddComponent(arguments...) _AddComponent(list(##arguments))
+
+/// A wrapper for _AddComonent that passes in a source.
+/// Necessary if dupe_mode is set to COMPONENT_DUPE_SOURCES.
+#define AddComponentFrom(source, arguments...) _AddComponent(list(##arguments), source)
+
+/// A wrapper for _LoadComponent that allows us to pretend we're using normal named arguments
+#define LoadComponent(arguments...) _LoadComponent(list(##arguments))
diff --git a/code/__defines/dcs/signals/signals_area.dm b/code/__defines/dcs/signals/signals_area.dm
new file mode 100644
index 0000000000000..7e05ef6a0479f
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_area.dm
@@ -0,0 +1,10 @@
+// Main area signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+///from base of /area/proc/power_change(): (area/apc_area)
+#define COMSIG_AREA_POWER_CHANGE "area_apc_power_change"
+///from base of /area/proc/set_apc(): (area/apc_area)
+#define COMSIG_AREA_APC_ADDED "area_apc_added"
+///from base of /area/proc/remove_apc(): (area/apc_area)
+#define COMSIG_AREA_APC_REMOVED "area_apc_removed"
diff --git a/code/__defines/dcs/signals/signals_atom.dm b/code/__defines/dcs/signals/signals_atom.dm
new file mode 100644
index 0000000000000..bacd94d99b035
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_atom.dm
@@ -0,0 +1,67 @@
+// Main atom signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /atom signals
+
+//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization
+#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success"
+
+///from base of atom/Entered(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+#define COMSIG_ATOM_ENTERED "atom_entered"
+/// Sent from the atom that just Entered src. From base of atom/Entered(): (/atom/destination, atom/old_loc, list/atom/old_locs)
+#define COMSIG_ATOM_ENTERING "atom_entering"
+
+///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"
+
+/// Sent from [atom/proc/item_interaction], when this atom is left-clicked on by a mob with an item
+/// Sent from the very beginning of the click chain, intended for generic atom-item interactions
+/// Args: (mob/living/user, obj/item/tool, list/modifiers)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_ITEM_INTERACTION "atom_item_interaction"
+/// Sent from [atom/proc/item_interaction], when this atom is right-clicked on by a mob with an item
+/// Sent from the very beginning of the click chain, intended for generic atom-item interactions
+/// Args: (mob/living/user, obj/item/tool, list/modifiers)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_ITEM_INTERACTION_SECONDARY "atom_item_interaction_secondary"
+/// Sent from [atom/proc/item_interaction], to an item clicking on an atom
+/// Args: (mob/living/user, atom/interacting_with, list/modifiers)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ITEM_INTERACTING_WITH_ATOM "item_interacting_with_atom"
+/// Sent from [atom/proc/item_interaction], to an item right-clicking on an atom
+/// Args: (mob/living/user, atom/interacting_with, list/modifiers)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ITEM_INTERACTING_WITH_ATOM_SECONDARY "item_interacting_with_atom_secondary"
+/// Sent from [atom/proc/item_interaction], when this atom is left-clicked on by a mob with a tool of a specific tool type
+/// Args: (mob/living/user, obj/item/tool)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_TOOL_ACT(tooltype) "tool_act_[tooltype]"
+/// Sent from [atom/proc/item_interaction], when this atom is right-clicked on by a mob with a tool of a specific tool type
+/// Args: (mob/living/user, obj/item/tool)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_SECONDARY_TOOL_ACT(tooltype) "tool_secondary_act_[tooltype]"
+/// Sent from [atom/proc/item_interaction], when the interaction is complete. Use it as "post-act"
+/// Args: (mob/living/user, obj/item/tool, act_result)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_TOOL_ACT_RESULT(tooltype) "tool_act_result_[tooltype]"
+/// Sent from [atom/proc/item_interaction], when the interaction has failed (no act_result)
+/// Args: (mob/living/user, obj/item/tool)
+/// Return any ITEM_INTERACT_ flags as relevant (see tools.dm)
+#define COMSIG_ATOM_TOOL_ACT_EMPTY "tool_act_empty"
+
+// Notifies tools that something is happening.
+// Sucessful actions against an atom.
+///Called from /atom/proc/tool_act (atom)
+#define COMSIG_TOOL_ATOM_ACTED_PRIMARY(tooltype) "tool_atom_acted_[tooltype]"
+///Called from /atom/proc/tool_act (atom)
+#define COMSIG_TOOL_ATOM_ACTED_SECONDARY(tooltype) "tool_atom_acted_[tooltype]"
+
+///from base of atom/examine(): (/mob, list/examine_text)
+#define COMSIG_ATOM_EXAMINE "atom_examine"
diff --git a/code/__defines/dcs/signals/signals_atom_attack.dm b/code/__defines/dcs/signals/signals_atom_attack.dm
new file mode 100644
index 0000000000000..5a5952d04f249
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_atom_attack.dm
@@ -0,0 +1,33 @@
+// Atom attack signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+///from base of atom/attackby(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_ATTACKBY "atom_attackby"
+///from base of atom/can_use_item(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_CAN_USE_ITEM "atom_can_use_item"
+///from base of atom/use_tool(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_USE_TOOL "atom_use_tool"
+///from base of atom/use_tool_secondary(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_USE_TOOL_SECONDARY "atom_use_tool_secondary"
+///from base of atom/use_weapon(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_USE_WEAPON "atom_use_weapon"
+///from base of atom/use_weapon_secondary(): (/obj/item, /mob/living, params)
+#define COMSIG_ATOM_USE_WEAPON_SECONDARY "atom_use_weapon_secondary"
+///from base of atom/attack_hand(): (mob/user, list/modifiers)
+#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand"
+///from base of atom/attack_hand_secondary(): (mob/user, list/modifiers)
+#define COMSIG_ATOM_ATTACK_HAND_SECONDARY "atom_attack_hand_secondary"
+///from base of atom/animal_attack(): (/mob/user)
+#define COMSIG_ATOM_ATTACK_ANIMAL "attack_animal"
+///from base of atom/attack_robot(): (mob/user)
+#define COMSIG_ATOM_ATTACK_ROBOT "atom_attack_robot"
+///from base of atom/attack_robot_secondary(): (mob/user)
+#define COMSIG_ATOM_ATTACK_ROBOT_SECONDARY "atom_attack_robot_secondary"
+
+/* Attack signals. They should share the returned flags, to standardize the attack chain. */
+/// tool_act -> pre_attack -> target.attackby (item.attack) -> afterattack
+ ///Ends the attack chain. If sent early might cause posterior attacks not to happen.
+ #define COMPONENT_CANCEL_ATTACK_CHAIN FLAG(0)
+ ///Skips the specific attack step, continuing for the next one to happen.
+ #define COMPONENT_SKIP_ATTACK FLAG(1)
diff --git a/code/__defines/dcs/signals/signals_atom_movable.dm b/code/__defines/dcs/signals/signals_atom_movable.dm
new file mode 100644
index 0000000000000..f724b87be9be0
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_atom_movable.dm
@@ -0,0 +1,10 @@
+// Atom movable signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+///from base of atom/movable/Moved(): (/atom)
+#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move"
+ #define COMPONENT_MOVABLE_BLOCK_PRE_MOVE FLAG(0)
+
+///from base of atom/movable/Moved(): (atom/old_loc, forced)
+#define COMSIG_MOVABLE_MOVED "movable_moved"
diff --git a/code/__defines/dcs/signals/signals_datum.dm b/code/__defines/dcs/signals/signals_datum.dm
new file mode 100644
index 0000000000000..0abcff497b297
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_datum.dm
@@ -0,0 +1,47 @@
+// Datum signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /datum signals
+/// when a component is added to a datum: (/datum/component)
+#define COMSIG_COMPONENT_ADDED "component_added"
+/// before a component is removed from a datum because of ClearFromParent: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing"
+
+/// before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
+/// you should only be using this if you want to block deletion
+/// that's the only functional difference between it and COMSIG_QDELETING, outside setting QDELETING to detect
+#define COMSIG_PREQDELETED "parent_preqdeleted"
+/// just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
+#define COMSIG_QDELETING "parent_qdeleting"
+/// generic topic handler (usr, href_list)
+#define COMSIG_TOPIC "handle_topic"
+/// handler for vv_do_topic (usr, href_list)
+#define COMSIG_VV_TOPIC "vv_topic"
+ #define COMPONENT_VV_HANDLED FLAG(0)
+/// from datum ui_act (usr, action)
+#define COMSIG_UI_ACT "COMSIG_UI_ACT"
+
+/// fires on the target datum when an element is attached to it (/datum/element)
+#define COMSIG_ELEMENT_ATTACH "element_attach"
+/// fires on the target datum when an element is attached to it (/datum/element)
+#define COMSIG_ELEMENT_DETACH "element_detach"
+
+// Merger datum signals
+/// Called on the object being added to a merger group: (datum/merger/new_merger)
+#define COMSIG_MERGER_ADDING "comsig_merger_adding"
+/// Called on the object being removed from a merger group: (datum/merger/old_merger)
+#define COMSIG_MERGER_REMOVING "comsig_merger_removing"
+/// Called on the merger after finishing a refresh: (list/leaving_members, list/joining_members)
+#define COMSIG_MERGER_REFRESH_COMPLETE "comsig_merger_refresh_complete"
+
+// Gas mixture signals
+/// From /datum/gas_mixture/proc/merge: ()
+#define COMSIG_GASMIX_MERGED "comsig_gasmix_merged"
+/// From /datum/gas_mixture/proc/remove: ()
+#define COMSIG_GASMIX_REMOVED "comsig_gasmix_removed"
+/// From /datum/gas_mixture/proc/react: ()
+#define COMSIG_GASMIX_REACTED "comsig_gasmix_reacted"
+
+///from /datum/bank_account/pay_debt(), after a portion or all the debt has been paid.
+#define COMSIG_BANK_ACCOUNT_DEBT_PAID "bank_account_debt_paid"
diff --git a/code/__defines/dcs/signals/signals_living.dm b/code/__defines/dcs/signals/signals_living.dm
new file mode 100644
index 0000000000000..b2aa1050fbad1
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_living.dm
@@ -0,0 +1,6 @@
+/// from /mob/living/*/UnarmedAttack(), before sending [COMSIG_LIVING_UNARMED_ATTACK]: (/atom, proximity, modifiers)
+/// The only reason this exists is so hulk can fire before Fists of the North Star.
+/// Note that this is called before [/mob/living/proc/can_unarmed_attack] is called, so be wary of that.
+#define COMSIG_LIVING_EARLY_UNARMED_ATTACK "human_pre_attack_hand"
+/// from mob/living/*/UnarmedAttack(): (/atom, proximity, modifiers)
+#define COMSIG_LIVING_UNARMED_ATTACK "living_unarmed_attack"
diff --git a/code/__defines/dcs/signals/signals_mob.dm b/code/__defines/dcs/signals/signals_mob.dm
new file mode 100644
index 0000000000000..568589b655b5f
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_mob.dm
@@ -0,0 +1,21 @@
+/// From base of /client/Move(): (list/move_args)
+#define COMSIG_MOB_CLIENT_PRE_LIVING_MOVE "mob_client_pre_living_move"
+ /// Should we stop the current living movement attempt
+ #define COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
+/// From base of /mob/UpdateLyingBuckledAndVerbStatus()
+#define COMSIG_MOB_UPDATE_LYING_BUCKLED_VERBSTATUS "mob_update_lying_buckled_verbstatus"
+
+/// from mob/CanPass(): (atom/movable/mover, turf/target, height, air_group)
+#define COMSIG_MOB_CAN_PASS "mob_can_pass"
+ #define COMPONENT_MOB_PASSABLE FLAG(0)
+
+///from base of mob/ranged_attack(): (/atom, modifiers)
+#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged"
+///from base of mob/ranged_attack_secondary(): (/atom, modifiers)
+#define COMSIG_MOB_ATTACK_RANGED_SECONDARY "mob_attack_ranged_secondary"
+
+///from base of proc/examinate(): (/atom, list/examine_strings)
+#define COMSIG_MOB_EXAMINING "mob_examining"
+///from base of proc/examinate(): (/atom)
+#define COMSIG_MOB_EXAMINATE "mob_examinate"
diff --git a/code/__defines/dcs/signals/signals_object.dm b/code/__defines/dcs/signals/signals_object.dm
new file mode 100644
index 0000000000000..a3a8c26a3f322
--- /dev/null
+++ b/code/__defines/dcs/signals/signals_object.dm
@@ -0,0 +1,24 @@
+/// from base of /obj/item/gun/toggle_safety(): (safety_state)
+#define COMSIG_GUN_TOGGLE_SAFETY "gun_toggle_safety"
+
+/// from base of /obj/item/change_tool_behaviour(): (new_tool_behaviour, new_toolspeed, override_sound)
+#define COMSIG_OBJ_CHANGE_TOOL_BEHAVIOUR "obj_change_tool_behaviour"
+
+///from base of [/obj/item/proc/tool_check_callback]: (mob/living/user)
+#define COMSIG_TOOL_IN_USE "tool_in_use"
+///from base of [/obj/item/proc/tool_start_check]: (mob/living/user)
+#define COMSIG_TOOL_START_USE "tool_start_use"
+
+///from base of atom/use_before(): (/atom, /mob/living, params)
+#define COMSIG_ITEM_USE_BEFORE "item_use_before"
+///from base of atom/use_after(): (/atom, /mob/living, params)
+#define COMSIG_ITEM_USE_AFTER "item_use_after"
+///from base of atom/use_after_secondary(): (/atom, /mob/living, params)
+#define COMSIG_ITEM_USE_AFTER_SECONDARY "item_use_after_secondary"
+///from base of atom/afterattack(): (/atom, /mob/living, proximity, params)
+#define COMSIG_ITEM_AFTERATTACK "item_afterattack_secondary"
+///from base of atom/afterattack_secondary(): (/atom, /mob/living, proximity, params)
+#define COMSIG_ITEM_AFTERATTACK_SECONDARY "item_afterattack_secondary"
+ #define COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN FLAG(0)
+ #define COMPONENT_SECONDARY_CONTINUE_ATTACK_CHAIN FLAG(1)
+ #define COMPONENT_SECONDARY_CALL_NORMAL_ATTACK_CHAIN FLAG(2)
diff --git a/code/__defines/directions.dm b/code/__defines/directions.dm
index 29436e32cd9f8..3d4d3278b53bd 100644
--- a/code/__defines/directions.dm
+++ b/code/__defines/directions.dm
@@ -6,4 +6,7 @@
#define N_NORTHEAST 32
#define N_NORTHWEST 512
#define N_SOUTHEAST 64
-#define N_SOUTHWEST 1024
\ No newline at end of file
+#define N_SOUTHWEST 1024
+
+#define IS_DIR_DIAGONAL(dir) (dir & (dir - 1))
+#define DIR_TO_CARDINAL(dir) (IS_DIR_DIAGONAL(dir) ? (dir & ~(dir & dir - 1)) : dir)
diff --git a/code/__defines/dna.dm b/code/__defines/dna.dm
index a10c4705005a6..0ddfba0d13015 100644
--- a/code/__defines/dna.dm
+++ b/code/__defines/dna.dm
@@ -5,15 +5,14 @@
// Generic mutations:
#define MUTATION_COLD_RESISTANCE 1
#define MUTATION_XRAY 2
-#define MUTATION_HULK 3
+#define MUTATION_FERAL 3 // Smash objects instead of using them, and harder to grab.
#define MUTATION_CLUMSY 4
#define MUTATION_FAT 5
#define MUTATION_HUSK 6
-#define MUTATION_LASER 7 // Harm intent - click anywhere to shoot lasers from eyes.
+#define MUTATION_LASER 7 // Harm intent - click anywhere to shoot lasers from eyes.
#define MUTATION_HEAL 8 // Healing people with hands.
#define MUTATION_SPACERES 9 // Can't be harmed via pressure damage.
#define MUTATION_SKELETON 10
-#define MUTATION_FERAL 11 // Smash objects instead of using them, and unable to use items
// Other Mutations:
#define mNobreath 100 // No need to breathe.
@@ -43,11 +42,11 @@
// Too much of a project to handle at the moment, TODO for later.
GLOBAL_VAR_INIT(BLINDBLOCK,0)
GLOBAL_VAR_INIT(DEAFBLOCK,0)
-GLOBAL_VAR_INIT(HULKBLOCK,0)
GLOBAL_VAR_INIT(TELEBLOCK,0)
GLOBAL_VAR_INIT(FIREBLOCK,0)
GLOBAL_VAR_INIT(XRAYBLOCK,0)
GLOBAL_VAR_INIT(CLUMSYBLOCK,0)
+GLOBAL_VAR_INIT(FERALBLOCK, 0)
GLOBAL_VAR_INIT(FAKEBLOCK,0)
GLOBAL_VAR_INIT(COUGHBLOCK,0)
GLOBAL_VAR_INIT(GLASSESBLOCK,0)
diff --git a/code/__defines/emissives.dm b/code/__defines/emissives.dm
new file mode 100644
index 0000000000000..a34cc4089608b
--- /dev/null
+++ b/code/__defines/emissives.dm
@@ -0,0 +1,27 @@
+// Emissive blockers
+/// For anything that shouldn't block emissives. Small objects or translucent objects primarily
+#define EMISSIVE_BLOCK_NONE 0
+/// For anything that doesn't change outline or opaque area much or at all.
+#define EMISSIVE_BLOCK_GENERIC 1
+/// Uses a dedicated render_target object to copy the entire appearance in real time to the blocking layer. For things that can change in appearance a lot from the base state, like humans.
+#define EMISSIVE_BLOCK_UNIQUE 2
+
+#define _EMISSIVE_COLOR(val) list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, val,val,val,0)
+/// The color matrix applied to all emissive overlays. Should be solely dependent on alpha and not have RGB overlap with [EM_BLOCK_COLOR].
+#define EMISSIVE_COLOR _EMISSIVE_COLOR(1)
+/// A globaly cached version of [EMISSIVE_COLOR] for quick access.
+GLOBAL_LIST_INIT(emissive_color, EMISSIVE_COLOR)
+
+#define _EM_BLOCK_COLOR(val) list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,val, 0,0,0,0)
+/// The color matrix applied to all emissive blockers. Should be solely dependent on alpha and not have RGB overlap with [EMISSIVE_COLOR].
+#define EM_BLOCK_COLOR _EM_BLOCK_COLOR(1)
+/// A globaly cached version of [EM_BLOCK_COLOR] for quick access.
+GLOBAL_LIST_INIT(em_block_color, EM_BLOCK_COLOR)
+
+/// The color matrix used to mask out emissive blockers on the emissive plane. Alpha should default to zero, be solely dependent on the RGB value of [EMISSIVE_COLOR], and be independant of the RGB value of [EM_BLOCK_COLOR].
+#define EM_MASK_MATRIX list(0,0,0,1/3, 0,0,0,1/3, 0,0,0,1/3, 0,0,0,0, 1,1,1,0)
+/// A globally cached version of [EM_MASK_MATRIX] for quick access.
+GLOBAL_LIST_INIT(em_mask_matrix, EM_MASK_MATRIX)
+
+/// A set of appearance flags applied to all emissive and emissive blocker overlays.
+#define EMISSIVE_APPEARANCE_FLAGS (KEEP_APART|KEEP_TOGETHER|RESET_COLOR|NO_CLIENT_COLOR)
diff --git a/code/__defines/feedback.dm b/code/__defines/feedback.dm
index cb3c427885854..24527b9dad2cf 100644
--- a/code/__defines/feedback.dm
+++ b/code/__defines/feedback.dm
@@ -1,2 +1,53 @@
-#define FEEDBACK_YOU_LACK_DEXTERITY SPAN_WARNING("You don't have the dexterity to do this!")
-#define FEEDBACK_ACCESS_DENIED SPAN_WARNING("Access Denied!")
+#define FEEDBACK_YOU_LACK_DEXTERITY USE_FEEDBACK_FAILURE("You don't have the dexterity to do this!")
+#define FEEDBACK_ACCESS_DENIED(USER, SRC) FEEDBACK_FAILURE(USER, SPAN_WARNING("\The [SRC] flashes, 'Access Denied!'"))
+
+/// Generic feedback failure message handler.
+#define FEEDBACK_FAILURE(USER, MSG) to_chat(USER, SPAN_WARNING(MSG))
+/// User can't unequip/drop item.
+#define FEEDBACK_UNEQUIP_FAILURE(USER, ITEM) FEEDBACK_FAILURE(USER, "You can't drop \the [ITEM].")
+
+/// Feedback messages intended for use in `use_*` overrides. These assume the presence of the `user` variable.
+#define USE_FEEDBACK_FAILURE(MSG) FEEDBACK_FAILURE(user, MSG)
+/// ID card lacks access
+#define USE_FEEDBACK_ID_CARD_DENIED(REFUSER, ID_CARD) USE_FEEDBACK_FAILURE("\The [REFUSER] refuses [ID_CARD].")
+/// Item stack did not have enough items. `STACK` is assumed to be of type `/obj/item/stack`.
+#define USE_FEEDBACK_STACK_NOT_ENOUGH(STACK, NEEDED_AMT, ACTION) USE_FEEDBACK_FAILURE("You need at least [STACK.get_exact_name(NEEDED_AMT)] [ACTION]")
+
+/// Feedback messages intended for use in `use_grab()` overrides. These assume the presence of the `grab` variable.
+#define USE_FEEDBACK_GRAB_FAILURE(MSG) FEEDBACK_FAILURE(grab.assailant, MSG)
+/// Assailant must upgrade their grab to perform action.
+#define USE_FEEDBACK_GRAB_MUST_UPGRADE(ACTION) USE_FEEDBACK_GRAB_FAILURE("You need a better grip on \the [grab.affecting][ACTION].")
+
+
+/// Feedback message when user needs to unanchor the target
+#define USE_FEEDBACK_NEED_UNANCHOR(user) balloon_alert(user, "нужно открутить от пола!")
+/// Feedback message when user needs to unanchor the target
+#define USE_FEEDBACK_NEED_ANCHOR(user) balloon_alert(user, "нужно прикрутить к полу!")
+/// Feedback message when user starts deconstructing the target
+#define USE_FEEDBACK_DECONSTRUCT_START(user) balloon_alert(user, "разбор")
+/// Feedback message when user starts welding/unwelding the target
+#define USE_FEEDBACK_WELD_UNWELD(user, welded) balloon_alert(user, "[welded ? "отваривание" : "заваривание"]")
+/// Feedback message when user finishes welding/unwelding the target
+#define USE_FEEDBACK_WELD_UNWELD_FINISH(user, welded) balloon_alert_to_viewers("[welded ? "заварено!" : "отварено!"]")
+/// Feedback message when user starts unwelding target from the floor
+#define USE_FEEDBACK_UNWELD_FROM_FLOOR(user) balloon_alert(user, "отваривание от пола")
+
+/// Feedback message when user starts repairing the target
+#define USE_FEEDBACK_REPAIR_START(user) balloon_alert(user, "ремонт")
+/// Feedback message when user finishes the repairs
+#define USE_FEEDBACK_REPAIR_FINISH(user) balloon_alert_to_viewers("ремонт завершен!")
+/// Feedback message when the target doesn't need repairs
+#define USE_FEEDBACK_NOTHING_TO_REPAIR(user) balloon_alert(user, "нет повреждений!")
+
+/// Feedback message when the wiring gets exposed/unexposed by the user
+#define USE_FEEDBACK_WIRING_EXPOSED(user, exposed) balloon_alert(user, "проводка [exposed ? "открыта" : "закрыта"]!")
+/// Feedback message when the target gets anchored/unanchored by the user
+#define USE_FEEDBACK_NEW_ANCHOR_FINISH(user, anchored) balloon_alert(user, "[anchored ? "закреплено на полу!" : "откреплено от пола!"]")
+/// Feedback message when the target gets its panel open/closed by the user
+#define USE_FEEDBACK_NEW_PANEL_OPEN(user, opened) balloon_alert(user, "[opened ? "панель открыта!" : "панель закрыта!"]")
+/// Feedback message if the cell is missing
+#define USE_FEEDBACK_CELL_MISSING(user) balloon_alert(user, "нет батареи!")
+/// Feedback message when the cell is removed by the user
+#define USE_FEEDBACK_CELL_REMOVED(user) balloon_alert_to_viewers("батарея снята")
+/// Feedback message when the target is enabled/active and needs to be disabled
+#define USE_FEEDBACK_NEED_DISABLED(user) balloon_alert(user, "нужно выключить!")
diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm
index 61fbe4ea38008..dc6ae948143a4 100644
--- a/code/__defines/flags.dm
+++ b/code/__defines/flags.dm
@@ -20,6 +20,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define ATOM_FLAG_NO_TEMP_CHANGE FLAG(6) // Reagents do not cool or heat to ambient temperature in this container.
#define ATOM_FLAG_CAN_BE_PAINTED FLAG(7) // Can be painted using a paint sprayer or similar.
#define ATOM_FLAG_ADJACENT_EXCEPTION FLAG(8) // Skips adjacent checks for atoms that should always be reachable in window tiles
+#define ATOM_FLAG_NO_TOOLS FLAG(9) // Blocks tool interactions.
+#define ATOM_AWAITING_OVERLAY_UPDATE FLAG(10)
#define MOVABLE_FLAG_PROXMOVE FLAG(0) // Does this object require proximity checking in Enter()?
#define MOVABLE_FLAG_Z_INTERACT FLAG(1) // Should attackby and attack_hand be relayed through ladders and open spaces?
@@ -29,6 +31,10 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define OBJ_FLAG_CONDUCTIBLE FLAG(1) // Conducts electricity. (metal etc.)
#define OBJ_FLAG_ROTATABLE FLAG(2) // Can be rotated with alt-click
#define OBJ_FLAG_NOFALL FLAG(3) // Will prevent mobs from falling
+/// Can be click+dragged onto a table, rack, etc
+#define OBJ_FLAG_CAN_TABLE FLAG(4)
+/// Can receive objects with the `OBJ_FLAG_CAN_TABLE` flag
+#define OBJ_FLAG_RECEIVE_TABLE FLAG(5)
//Flags for items (equipment)
#define ITEM_FLAG_NO_BLUDGEON FLAG(0) // When an item has this it produces no "X has been hit by Y with Z" message with the default handler.
@@ -46,7 +52,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define ITEM_FLAG_NOCUFFS FLAG(13) // Gloves that have this flag prevent cuffs being applied
#define ITEM_FLAG_CAN_HIDE_IN_SHOES FLAG(14) // Items that can be hidden in shoes that permit it
#define ITEM_FLAG_WASHER_ALLOWED FLAG(15) // Items that can be washed in washing machines
-#define ITEM_FLAG_TRY_ATTACK FLAG(16) // Use the item's attack() when set before trying the receiver's attackby()
+#define ITEM_FLAG_IS_CHAMELEON_ITEM FLAG(16) // Setups the chameleon extension on init. Throws an exception if there is no compatible extension subtype.
// Flags for pass_flags.
#define PASS_FLAG_TABLE FLAG(0)
@@ -58,3 +64,20 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define TANK_FLAG_FORCED FLAG(1)
#define TANK_FLAG_LEAKING FLAG(2)
#define TANK_FLAG_WIRED FLAG(3)
+
+// Flags for beds/chairs
+/// The bed/chair cannot be dismantled with a wrench.
+#define BED_FLAG_CANNOT_BE_DISMANTLED FLAG(1)
+/// The bed/chair cannot be padded with material.
+#define BED_FLAG_CANNOT_BE_PADDED FLAG(2)
+/// The bed/chair cannot be made into an electric chair with a shock kit. Only applies to `/obj/structure/bed/chair` subtypes.
+#define BED_FLAG_CANNOT_BE_ELECTRIFIED FLAG(3)
+
+/// Whether or not this sector is a starting sector. Z levels contained in this sector are added to station_levels
+#define OVERMAP_SECTOR_BASE FLAG(0)
+/// Makes the sector show up on nav computers
+#define OVERMAP_SECTOR_KNOWN FLAG(1)
+/// If the sector can be accessed by drifting off the map edge
+#define OVERMAP_SECTOR_IN_SPACE FLAG(2)
+/// If the sector is untargetable by missiles.
+#define OVERMAP_SECTOR_UNTARGETABLE FLAG(3)
diff --git a/code/__defines/fluids.dm b/code/__defines/fluids.dm
index 65f9e00792acd..9b48d432da82a 100644
--- a/code/__defines/fluids.dm
+++ b/code/__defines/fluids.dm
@@ -10,11 +10,11 @@
#define ADD_ACTIVE_FLUID_SOURCE(T) SSfluids.water_sources[T] = TRUE
#define REMOVE_ACTIVE_FLUID_SOURCE(T) SSfluids.water_sources -= T
-// Expects /obj/effect/fluid for F.
+// Expects /obj/fluid for F.
#define ADD_ACTIVE_FLUID(F) SSfluids.active_fluids[F] = TRUE
#define REMOVE_ACTIVE_FLUID(F) SSfluids.active_fluids -= F
-// Expects /obj/effect/fluid for F, int for amt.
+// Expects /obj/fluid for F, int for amt.
#define LOSE_FLUID(F, amt) \
F:fluid_amount = max(-1, F:fluid_amount - amt); \
ADD_ACTIVE_FLUID(F)
@@ -44,9 +44,9 @@
UPDATE_FLUID_BLOCKED_DIRS(next); \
if((next.fluid_blocked_dirs & GLOB.reverse_dir[spread_dir]) || !next.CanFluidPass(spread_dir)) continue; \
flooded_a_neighbor = TRUE; \
- var/obj/effect/fluid/F = locate() in next; \
+ var/obj/fluid/F = locate() in next; \
if(!F && !dry_run) {\
- F = new /obj/effect/fluid(next); \
+ F = new /obj/fluid(next); \
var/datum/gas_mixture/GM = T:return_air(); \
if(GM) F.temperature = GM.temperature; \
} \
@@ -60,7 +60,7 @@
// We share overlays for all fluid turfs to sync icon animation.
#define APPLY_FLUID_OVERLAY(img_state) \
if(!SSfluids.fluid_images[img_state]) SSfluids.fluid_images[img_state] = image('icons/effects/liquids.dmi',img_state); \
- overlays += SSfluids.fluid_images[img_state];
+ AddOverlays(SSfluids.fluid_images[img_state]);
#define FLUID_MAX_ALPHA 160
#define FLUID_MIN_ALPHA 45
diff --git a/code/__defines/gamemode.dm b/code/__defines/gamemode.dm
index 61a977b5bf2f6..4c27480ce1b9b 100644
--- a/code/__defines/gamemode.dm
+++ b/code/__defines/gamemode.dm
@@ -37,7 +37,7 @@
#define MODE_ERT "ert"
#define MODE_ACTOR "actor"
#define MODE_MERCENARY "mercenary"
-#define MODE_NINJA "ninja"
+#define MODE_NINJA "operatives"
#define MODE_RAIDER "raider"
#define MODE_WIZARD "wizard"
#define MODE_CHANGELING "changeling"
@@ -65,7 +65,7 @@
#define GHOSTCAST FLAG(0) //can a ghost cast it?
#define NEEDSCLOTHES FLAG(1) //does it need the wizard garb to cast? Nonwizard spells should not have this
#define NEEDSHUMAN FLAG(2) //does it require the caster to be human?
-#define Z2NOCAST FLAG(3) //if this is added, the spell can't be cast at centcomm
+#define Z2NOCAST FLAG(3) //if this is added, the spell can't be cast at centcom
#define NO_SOMATIC FLAG(4) //spell will go off if the person is incapacitated or stunned
#define IGNOREPREV FLAG(5) //if set, each new target does not overlap with the previous one
//The following flags only affect different types of spell, and therefore overlap
diff --git a/code/__defines/guns.dm b/code/__defines/guns.dm
index 76d79371d23e7..f6df203f5cdc9 100644
--- a/code/__defines/guns.dm
+++ b/code/__defines/guns.dm
@@ -24,7 +24,9 @@
#define MAGAZINE 4 //The magazine item itself goes inside the gun
+#define GUN_BULK_LIGHT_RIFLE 4
#define GUN_BULK_RIFLE 5
+#define GUN_BULK_HEAVY_RIFLE 6
#define BULLET_IMPACT_NONE "none"
#define BULLET_IMPACT_METAL "metal"
diff --git a/code/__defines/items_clothing.dm b/code/__defines/items_clothing.dm
index 288ffb7cbb433..dad42c43754de 100644
--- a/code/__defines/items_clothing.dm
+++ b/code/__defines/items_clothing.dm
@@ -35,6 +35,8 @@
#define ACCESSORY_SLOT_ARMOR_M "Misc armor"
#define ACCESSORY_SLOT_HELM_C "Helmet cover"
#define ACCESSORY_SLOT_HELM_D "Helmet decor"
+#define ACCESSORY_SLOT_OVER "Over"
+
#define ACCESSORY_REMOVABLE FLAG(0)
@@ -183,8 +185,8 @@
#define FIRESUIT_MAX_PRESSURE 100 * ONE_ATMOSPHERE // Firesuis and atmos voidsuits
#define RIG_MAX_PRESSURE 50 * ONE_ATMOSPHERE // Rigs
#define LIGHT_RIG_MAX_PRESSURE 25 * ONE_ATMOSPHERE // Rigs
-#define ENG_VOIDSUIT_MAX_PRESSURE 50 * ONE_ATMOSPHERE
-#define VOIDSUIT_MAX_PRESSURE 25 * ONE_ATMOSPHERE
+#define ENG_VOIDSUIT_MAX_PRESSURE 50 * ONE_ATMOSPHERE
+#define VOIDSUIT_MAX_PRESSURE 25 * ONE_ATMOSPHERE
#define SPACE_SUIT_MAX_PRESSURE 5 * ONE_ATMOSPHERE
// Fire.
@@ -218,7 +220,9 @@
#define HUD_SECURITY FLAG(1)
#define HUD_MEDICAL FLAG(2)
#define HUD_JANITOR FLAG(3)
-
+// [SIERRA-ADD] - NTNET - NTNet gimmics
+#define HUD_IT FLAG(4)
+// [/SIERRA-ADD]
/**
* flags for /mob/proc/equip_to_slot_if_possible
@@ -275,3 +279,13 @@ GLOBAL_LIST_INIT(default_onmob_icons, list(
slot_s_store_str = 'icons/mob/onmob/onmob_belt_mirror.dmi',\
slot_tie_str = 'icons/mob/onmob/onmob_accessories.dmi'\
))
+
+// Rolldown status defines for `/obj/item/clothing/under`
+
+#define UNDER_ROLLDOWN_STATUS_CANT_BE_ROLLED -1
+#define UNDER_ROLLDOWN_STATUS_UNROLLED 0
+#define UNDER_ROLLDOWN_STATUS_ROLLED 1
+
+#define SLEEVES_ROLLDOWN_STATUS_CANT_BE_ROLLED -1
+#define SLEEVES_ROLLDOWN_STATUS_UNROLLED 0
+#define SLEEVES_ROLLDOWN_STATUS_ROLLED 1
diff --git a/code/__defines/keybinding.dm b/code/__defines/keybinding.dm
new file mode 100644
index 0000000000000..4a8f156a22082
--- /dev/null
+++ b/code/__defines/keybinding.dm
@@ -0,0 +1,8 @@
+//Signals
+
+//General
+#define COMSIG_KB_ACTIVATED FLAG(0)
+
+// Mob
+#define COMSIG_KB_MOB_PIXEL_SHIFT_DOWN "keybinding_mob_pixel_shift_down"
+#define COMSIG_KB_MOB_PIXEL_SHIFT_UP "keybinding_mob_pixel_shift_up"
diff --git a/code/__defines/lighting.dm b/code/__defines/lighting.dm
index 5e35bad8636b1..8a5820f42579d 100644
--- a/code/__defines/lighting.dm
+++ b/code/__defines/lighting.dm
@@ -1,29 +1,42 @@
-#define FOR_DVIEW(type, range, center, invis_flags) \
- GLOB.dview_mob.loc = center; \
- GLOB.dview_mob.see_invisible = invis_flags; \
- for(type in view(range, GLOB.dview_mob))
+#define LIGHTING_INTERVAL 1 // Frequency, in 1/10ths of a second, of the lighting process.
-#define END_FOR_DVIEW GLOB.dview_mob.loc = null
+#define LIGHTING_HEIGHT 1 // height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone
+#define LIGHTING_Z_FACTOR 10 // Z diff is multiplied by this and LIGHTING_HEIGHT to get the final height of a light source. Affects how much darker A Z light gets with each level transitioned.
+#define LIGHTING_ROUND_VALUE (1 / 200) //Value used to round lumcounts, values smaller than 1/255 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY.
#define LIGHTING_ICON 'icons/effects/lighting_overlay.dmi' // icon used for lighting shading effects
-#define LIGHTING_ICON_STATE_DARK "dark" // Change between "soft_dark" and "dark" to swap soft darkvision
+#define LIGHTING_BASE_ICON_STATE "matrix" // icon_state used for normal color-matrix based lighting overlays.
+#define LIGHTING_STATION_ICON_STATE "tubedefault" // icon_state used for lighting overlays that are just displaying standard station lighting.
+#define LIGHTING_DARKNESS_ICON_STATE "black" // icon_state used for lighting overlays with no luminosity.
+#define LIGHTING_TRANSPARENT_ICON_STATE "blank"
-#define LIGHTING_ROUND_VALUE (1 / 64) // Value used to round lumcounts, values smaller than 1/69 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY.
+#define LIGHTING_BLOCKED_FACTOR 0.5 // How much the range of a directional light will be reduced while facing a wall.
-#define LIGHTING_SOFT_THRESHOLD 0 // If the max of the lighting lumcounts of each spectrum drops below this, disable luminosity on the lighting overlays. This also should be the transparancy of the "soft_dark" icon state.
+// If defined, instant updates will be used whenever server load permits. Otherwise queued updates are always used.
+#define USE_INTELLIGENT_LIGHTING_UPDATES
+
+/// Maximum light_range before forced to always queue instead of using sync updates. Setting this too high will cause server stutter with moving large lights.
+#define LIGHTING_MAXIMUM_INSTANT_RANGE 8
+
+// mostly identical to below, but doesn't make sure T is valid first. Should only be used by lighting code.
+#define TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) ((T:dynamic_lighting && T:loc:dynamic_lighting))
+#define TURF_IS_DYNAMICALLY_LIT(T) (isturf(T) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
+
+// Note: this does not imply the above, a turf can have ambient light without being dynamically lit.
+#define TURF_IS_AMBIENT_LIT_UNSAFE(T) (T:ambient_active)
+#define TURF_IS_AMBIENT_LIT(T) (isturf(T) && TURF_IS_AMBIENT_LIT_UNSAFE(T))
-#define LIGHTING_MULT_FACTOR 0.9
// If I were you I'd leave this alone.
#define LIGHTING_BASE_MATRIX \
- list \
- ( \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, LIGHTING_SOFT_THRESHOLD, 0, \
- 0, 0, 0, 1 \
- )
+ list \
+ ( \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 1, 1, 1, 0, \
+ 0, 0, 0, 1 \
+ ) \
// Helpers so we can (more easily) control the colour matrices.
#define CL_MATRIX_RR 1
@@ -47,12 +60,25 @@
#define CL_MATRIX_CB 19
#define CL_MATRIX_CA 20
+// Higher numbers override lower.
+#define LIGHTING_NO_UPDATE 0
+#define LIGHTING_VIS_UPDATE 1
+#define LIGHTING_CHECK_UPDATE 2
+#define LIGHTING_FORCE_UPDATE 3
+
// Lightbulb statuses
#define LIGHT_OK 0 // A light bulb is installed and functioning.
#define LIGHT_EMPTY 1 // There is no light bulb installed.
#define LIGHT_BROKEN 2 // The light bulb is broken/shattered.
#define LIGHT_BURNED 3 // The light bulb is burned out.
+// This color of overlay is very common - most of the station is this color when lit fully.
+// Tube lights are a bluish-white, so we can't just assume 1-1-1 is full-illumination.
+// -- If you want to change these, find them *by checking in-game*, just converting tubes' RGB color into floats will not work!
+#define LIGHTING_DEFAULT_TUBE_R 0.96
+#define LIGHTING_DEFAULT_TUBE_G 1
+#define LIGHTING_DEFAULT_TUBE_B 1
+
// Lighting color presets
#define LIGHT_COLOUR_WHITE "#fefefe" // Clinical white light bulbs
#define LIGHT_COLOUR_WARM "#fffee0" // Warm yellowish light bulbs
@@ -84,3 +110,16 @@
#define AREA_LIGHTING_WARM "warm"
#define AREA_LIGHTING_COOL "cool"
#define AREA_LIGHTING_DEFAULT "default" // For light replacers, defaults to whatever the area is set to. For areas, uses the initial lighting value from the light bulb itself.
+
+// Some angle presets for directional lighting.
+#define LIGHT_OMNI null
+#define LIGHT_SEMI 180
+#define LIGHT_VERY_WIDE 135
+#define LIGHT_WIDE 90
+#define LIGHT_NARROW 45
+
+#define DARKSIGHT_GRADIENT_SIZE 480
+// Max number of ambient groups, amount over this value will simply not be created
+#define AMBIENT_GROUP_MAX_BITS 24
+// Ambient group used for exterior turfs not on planets - Could also replace Space turf legacy starlight implementation
+#define SPACE_AMBIENT_GROUP 1
diff --git a/code/__defines/lists.dm b/code/__defines/lists.dm
index be91ee13ac279..8142ae1b7cac0 100644
--- a/code/__defines/lists.dm
+++ b/code/__defines/lists.dm
@@ -11,6 +11,8 @@
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
// Adds I to L, initalizing L if necessary
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
+///Add an item to the list if not already present, if the list is null it will initialize it
+#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
// Insert I into L at position X, initalizing L if necessary
#define LAZYINSERT(L, I, X) if(!L) { L = list(); } L.Insert(X, I);
// Adds I to L, initalizing L if necessary, if I is not already in L
@@ -24,15 +26,29 @@
// Safely checks if I is in L
#define LAZYISIN(L, I) (L ? (I in L) : FALSE)
// Null-safe List.Cut() and discard.
-#define LAZYCLEARLIST(L) if(L) { L.Cut(); L = null; }
+#define LAZYCLEARLIST(L) if(L) { L.len = 0; L = null; }
// Safely merges L2 into L1 as lazy lists, initializing L1 if necessary.
#define LAZYMERGELIST(L1, L2) if (length(L2)) { if (!L1) { L1 = list() } L1 |= L2 }
+/// 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 )
// Reads L or an empty list if L is not a list. Note: Does NOT assign, L may be an expression.
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
-
+///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
+#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
+// Adds to the item K the value V, if the list is null it will initialize it
+#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
+// Removes the value V from the item K, if the item K is empty will remove it from the list, if the list is empty will set the list to null
+#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
+/// Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made.
+#define LAZYORASSOCLIST(lazy_list, key, value) \
+ LAZYINITLIST(lazy_list); \
+ LAZYINITLIST(lazy_list[key]); \
+ lazy_list[key] |= value;
/****
-* Binary search sorted insert
+* Binary search sorted insert from TG
* INPUT: Object to be inserted
* LIST: List to insert object into
* TYPECONT: The typepath of the contents of the list
diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm
index 39b8106d46e12..90ff11a5895bd 100644
--- a/code/__defines/machinery.dm
+++ b/code/__defines/machinery.dm
@@ -41,16 +41,17 @@
#define AI_CAMERA_LUMINOSITY 6
// Camera networks
-#define NETWORK_CRESCENT "Crescent"
-#define NETWORK_ENGINEERING "Engineering"
-#define NETWORK_ERT "ZeEmergencyResponseTeam"
-#define NETWORK_EXODUS "Exodus"
-#define NETWORK_MEDICAL "Medical"
-#define NETWORK_MERCENARY "MercurialNet"
-#define NETWORK_MINE "Mining"
-#define NETWORK_RESEARCH "Research"
-#define NETWORK_SECURITY "Security"
-#define NETWORK_THUNDER "Thunderdome"
+var/global/const/NETWORK_CRESCENT = "Crescent"
+var/global/const/NETWORK_ENGINEERING = "Engineering"
+var/global/const/NETWORK_ERT = "ERT"
+var/global/const/NETWORK_EXODUS = "Exodus"
+var/global/const/NETWORK_MEDICAL = "Medical"
+var/global/const/NETWORK_MERCENARY = "MercurialNet"
+var/global/const/NETWORK_MINE = "Mining"
+var/global/const/NETWORK_RESEARCH = "Research"
+var/global/const/NETWORK_SECURITY = "Security"
+var/global/const/NETWORK_THUNDER = "Thunderdome"
+var/global/const/NETWORK_HELMETS = "Helmet Cameras"
#define NETWORK_ALARM_ATMOS "Atmosphere Alarms"
#define NETWORK_ALARM_CAMERA "Camera Alarms"
@@ -176,4 +177,4 @@
#define PART_TESLA /obj/item/stock_parts/computer/tesla_link // Tesla Link, Allows remote charging from nearest APC.
#define PART_SCANNER /obj/item/stock_parts/computer/scanner // One of several optional scanner attachments.
-#define CLICKSOUND_INTERVAL (5 SECONDS)
+#define CLICKSOUND_INTERVAL (2.5 SECONDS)
diff --git a/code/__defines/mapping.dm b/code/__defines/mapping.dm
index d75cc6c95453e..ae65c1c99c060 100644
--- a/code/__defines/mapping.dm
+++ b/code/__defines/mapping.dm
@@ -27,3 +27,7 @@
crash_with("Deleting duplicate of [log_info_line(src)]"); \
return INITIALIZE_HINT_QDEL; \
}
+
+/// Just a list of all the area objects in the game
+/// Note, areas can have duplicate types
+GLOBAL_LIST_EMPTY(areas)
diff --git a/code/__defines/materials.dm b/code/__defines/materials.dm
index 40aa76fd1fa3f..7f7d475c40098 100644
--- a/code/__defines/materials.dm
+++ b/code/__defines/materials.dm
@@ -14,8 +14,8 @@
#define MATERIAL_IRON "iron"
#define MATERIAL_PLATINUM "platinum"
#define MATERIAL_BRONZE "bronze"
-#define MATERIAL_PHORON_GLASS "phglass"
-#define MATERIAL_REINFORCED_PHORON_GLASS "rphglass"
+#define MATERIAL_BORAX "borax"
+#define MATERIAL_BORON_GLASS "borosilicate glass"
#define MATERIAL_MARBLE "marble"
#define MATERIAL_CULT "cult"
#define MATERIAL_REINFORCED_CULT "cult2"
@@ -129,6 +129,7 @@
#define USE_MATERIAL_COLOR FLAG(0)
#define USE_MATERIAL_SINGULAR_NAME FLAG(1)
#define USE_MATERIAL_PLURAL_NAME FLAG(2)
+#define USE_MATERIAL_ICON FLAG(3)
//Arbitrary hardness thresholds
#define MATERIAL_SOFT 10
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 0eebc6367ac88..eb9ad919f742c 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -1,4 +1,9 @@
#define DEBUG
+
+// Flags
+#define ALL (~0) //For convenience.
+#define NONE 0
+
// Turf-only flags.
#define TURF_FLAG_NOJAUNT FLAG(0) // This is used in literally one place, turf.dm, to block ethereal jaunt.
#define TURF_FLAG_NORUINS FLAG(1)
@@ -11,6 +16,7 @@
#define INVISIBILITY_LIGHTING 20
#define INVISIBILITY_LEVEL_ONE 35
#define INVISIBILITY_LEVEL_TWO 45
+#define INVISIBILITY_OVERMAP 50
#define INVISIBILITY_OBSERVER 60
#define INVISIBILITY_EYE 61
#define INVISIBILITY_SYSTEM 99
@@ -103,13 +109,13 @@
#define CUSTOM_ITEM_SYNTH_CONFIG "config/custom_sprites.txt"
#endif
#ifndef CUSTOM_ITEM_OBJ
-#define CUSTOM_ITEM_OBJ 'icons/obj/custom_items_obj.dmi'
+#define CUSTOM_ITEM_OBJ 'icons/obj/unused.dmi'
#endif
#ifndef CUSTOM_ITEM_MOB
-#define CUSTOM_ITEM_MOB 'icons/mob/custom_items_mob.dmi'
+#define CUSTOM_ITEM_MOB 'icons/obj/unused.dmi'
#endif
#ifndef CUSTOM_ITEM_SYNTH
-#define CUSTOM_ITEM_SYNTH 'icons/mob/custom_synthetic.dmi'
+#define CUSTOM_ITEM_SYNTH 'icons/obj/unused.dmi'
#endif
#define WALL_CAN_OPEN 1
@@ -134,6 +140,7 @@
#define PROGRAM_TABLET FLAG(2)
#define PROGRAM_TELESCREEN FLAG(3)
#define PROGRAM_PDA FLAG(4)
+#define PROGRAM_NO_KILL FLAG(5) //Not included in PROGRAM_ALL
#define PROGRAM_ALL ( PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_TABLET | PROGRAM_TELESCREEN | PROGRAM_PDA )
#define PROGRAM_STATE_KILLED 0
@@ -257,10 +264,13 @@
//Lying animation
#define ANIM_LYING_TIME 2
-//Planet habitability class
-#define HABITABILITY_IDEAL 1
-#define HABITABILITY_OKAY 2
-#define HABITABILITY_BAD 3
+
+//Planet habitability weight
+#define HABITABILITY_LOCKED 1
+#define HABITABILITY_TYPICAL 2
+#define HABITABILITY_BAD 3
+#define HABITABILITY_EXTREME 4
+
#ifndef WINDOWS_HTTP_POST_DLL_LOCATION
#define WINDOWS_HTTP_POST_DLL_LOCATION "lib/byhttp.dll"
@@ -281,9 +291,7 @@
//-- Masks for /atom/var/init_flags --
//- machinery
-#define INIT_MACHINERY_PROCESS_SELF FLAG(0)
-#define INIT_MACHINERY_PROCESS_COMPONENTS FLAG(1)
-#define INIT_MACHINERY_PROCESS_ALL ( INIT_MACHINERY_PROCESS_SELF | INIT_MACHINERY_PROCESS_COMPONENTS )
+#define INIT_MACHINERY_START_PROCESSING FLAG(0)
//--
@@ -319,3 +327,25 @@
// Helper macro for generating stringified name text for IDs located inside objects, i.e. PDAs or wallets. Used for feedback and interaction messages.
#define GET_ID_NAME(ID, HOLDER) (ID == HOLDER ? "\the [ID]" : "\the [ID] in \the [HOLDER]")
+
+
+// Flags for `use_sanity_check()`
+/// Do not display user feedback messages.
+#define SANITY_CHECK_SILENT FLAG(0)
+/// Verify the tool can be unequipped from user. Ignored if the tool is not an item.
+#define SANITY_CHECK_TOOL_UNEQUIP FLAG(1)
+/// Verify the target can be unequipped from user. Includes `target.loc == src` check to allow items the user isn't holding.
+#define SANITY_CHECK_TARGET_UNEQUIP FLAG(2)
+/// Verify the target and tool are adjacent to eachother. Ignored if there is no tool or if tool is held by user.
+#define SANITY_CHECK_BOTH_ADJACENT FLAG(3)
+/// Verify the tool is in the user's active hand. Ignored if the tool is not an item.
+#define SANITY_CHECK_TOOL_IN_HAND FLAG(4)
+/// Check `CanInteractWith(target, user)`. Only use this for Topic() revalidation. Functionally exclusive with `SANITY_CHECK_TOPIC_PHYSICALLY_INTERACT`.
+#define SANITY_CHECK_TOPIC_INTERACT FLAG(5)
+/// Check `CanPhysicallyInteractWith(target, user)`. Only use this for Topic() revalidation. Functionally exclusive with `SANITY_CHECK_TOPIC_INTERACT`.
+#define SANITY_CHECK_TOPIC_PHYSICALLY_INTERACT FLAG(6)
+
+#define SANITY_CHECK_DEFAULT (SANITY_CHECK_TOOL_IN_HAND | SANITY_CHECK_BOTH_ADJACENT)
+
+#define Z_ALL_TURFS(Z) block(locate(1, 1, Z), locate(world.maxx, world.maxy, Z))
+#define DEFAULT_OVERMAP_RANGE 0 // Makes general computers and devices be able to connect to other overmap z-levels on the same tile. //SIERRA TGUI
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 468478a1998d9..6c9a858d12d27 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -36,24 +36,6 @@
#define BORGXRAY FLAG(2)
#define BORGMATERIAL FLAG(3)
-
-#define STANCE_SLEEP 0 // Doing (almost) nothing, to save on CPU because nobody is around to notice or the mob died.
-#define STANCE_IDLE 1 // The more or less default state. Wanders around, looks for baddies, and spouts one-liners.
-#define STANCE_ALERT 2 // A baddie is visible but not too close, and essentially we tell them to go away or die.
-#define STANCE_APPROACH 3 // Attempting to get into range to attack them.
-#define STANCE_FIGHT 4 // Actually fighting, with melee or ranged.
-#define STANCE_BLINDFIGHT 5 // Fighting something that cannot be seen by the mob, from invisibility or out of sight.
-#define STANCE_REPOSITION 6 // Relocating to a better position while in combat. Also used when moving away from a danger like grenades.
-#define STANCE_MOVE 7 // Similar to above but for out of combat. If a baddie is seen, they'll cancel and fight them.
-#define STANCE_FOLLOW 8 // Following somone, without trying to murder them.
-#define STANCE_FLEE 9 // Run away from the target because they're too spooky/we're dying/some other reason.
-#define STANCE_DISABLED 10 // Used when the holder is afflicted with certain status effects, such as stuns or confusion.
-
-#define STANCE_ATTACK 11 // Backwards compatability
-#define STANCE_ATTACKING 12 // Ditto
-
-#define STANCES_COMBAT list(STANCE_ALERT, STANCE_APPROACH, STANCE_FIGHT, STANCE_BLINDFIGHT, STANCE_REPOSITION)
-
#define LEFT FLAG(0)
#define RIGHT FLAG(1)
#define UNDER FLAG(2)
@@ -104,10 +86,15 @@
#define APPEARANCE_ALL_HAIR (APPEARANCE_HEAD | APPEARANCE_HEAD_COLOR | APPEARANCE_FACE | APPEARANCE_FACE_COLOR)
#define APPEARANCE_EYES FLAG(8)
#define APPEARANCE_LANG FLAG(9)
-#define APPEARANCE_LANG_ANY_NUMBER FLAG(10)
-#define APPEARANCE_LANG_ANY_ORIGIN FLAG(11)
+#define APPEARANCE_PRONOUNS FLAG(10)
+
+#define APPEARANCE_LANG_ANY_NUMBER FLAG(21)
+#define APPEARANCE_LANG_ANY_ORIGIN FLAG(22)
+#define APPEARANCE_SKIP_ALLOW_LIST_CHECK FLAG(23)
+#define APPEARANCE_SKIP_RESTRICTED_CHECK FLAG(24)
-#define APPEARANCE_COMMON (APPEARANCE_DNA2|APPEARANCE_RACE|APPEARANCE_GENDER|APPEARANCE_SKIN|APPEARANCE_ALL_HAIR|APPEARANCE_EYES|APPEARANCE_LANG)
+#define APPEARANCE_BASIC (APPEARANCE_GENDER|APPEARANCE_SKIN|APPEARANCE_ALL_HAIR|APPEARANCE_EYES|APPEARANCE_PRONOUNS)
+#define APPEARANCE_COMMON (APPEARANCE_BASIC|APPEARANCE_DNA2|APPEARANCE_RACE|APPEARANCE_LANG)
// /sprite_accessory flags
@@ -373,7 +360,11 @@
#define SPECIES_GRAVWORLDER "Grav-Adapted Human"
#define SPECIES_MULE "Mule"
#define SPECIES_MONKEY "Monkey"
-#define SPECIES_NABBER "giant armoured serpentid"
+#define SPECIES_NABBER "Giant Armoured Serpentid"
+#define SPECIES_FARWA "Farwa"
+#define SPECIES_NEAERA "Neaera"
+#define SPECIES_STOK "Stok"
+#define SPECIES_FBP "Full Body Prosthesis"
#define UNRESTRICTED_SPECIES list(SPECIES_HUMAN, SPECIES_DIONA, SPECIES_IPC, SPECIES_UNATHI, SPECIES_YEOSA, SPECIES_SKRELL, SPECIES_TRITONIAN, SPECIES_SPACER, SPECIES_VATGROWN, SPECIES_GRAVWORLDER, SPECIES_MULE)
#define RESTRICTED_SPECIES list(SPECIES_VOX, SPECIES_ALIEN, SPECIES_GOLEM)
@@ -388,11 +379,20 @@
#define STASIS_CRYOBAG "cryobag"
#define STASIS_COLD "cold"
-#define AURA_CANCEL 1
-#define AURA_FALSE 2
+// Aura check result flags for `/obj/aura/proc/aura_check_*()`.
+/// Halts further checking of any other auras on the mob.
+#define AURA_CANCEL FLAG(0)
+/// Causes the calling `aura_check()` proc to return `FALSE`.
+#define AURA_FALSE FLAG(1)
+
+// Aura type options for `/mob/living/proc/aura_check()`.
+/// Aura checks for projectile impacts. Generally called by `/obj/item/projectile/proc/attack_mob()`. Results in `/obj/aura/proc/aura_check_bullet()`.
#define AURA_TYPE_BULLET "Bullet"
+/// Aura checks for physical weapon attacks. Generally called by `/obj/item/proc/use_weapon()`. Results in `/obj/aura/proc/aura_check_weapon()`.
#define AURA_TYPE_WEAPON "Weapon"
+/// Aura checks for thrown atom impacts. Generally called by `/mob/living/hitby()`. Results in `/obj/aura/proc/aura_check_thrown()`.
#define AURA_TYPE_THROWN "Thrown"
+/// Aura checks during mob life. Generally called by `/mob/living/Life()`. Results in `/obj/aura/proc/aura_check_life()`.
#define AURA_TYPE_LIFE "Life"
#define SPECIES_BLOOD_DEFAULT 560
@@ -408,6 +408,7 @@
#define MOB_CLIMB_TIME_MEDIUM (5 SECONDS)
#define MOB_FACTION_NEUTRAL "neutral"
+#define MOB_FACTION_CREW "crew"
#define ROBOT_MODULE_TYPE_GROUNDED "grounded"
#define ROBOT_MODULE_TYPE_FLYING "flying"
@@ -482,3 +483,18 @@
#define DO_INCAPACITATED (-3)
#define FAKE_INVIS_ALPHA_THRESHOLD 127 // If something's alpha var is at or below this number, certain things will pretend it is invisible.
+
+#define PRONOUNS_THEY_THEM "they/them"
+#define PRONOUNS_HE_HIM "he/his"
+#define PRONOUNS_SHE_HER "she/her"
+#define PRONOUNS_IT_ITS "it/its"
+#define PRONOUNS_HE_THEY "he/they"
+#define PRONOUNS_SHE_THEY "she/they"
+
+#define PRONOUNS_ALL list(PRONOUNS_THEY_THEM, PRONOUNS_HE_HIM, PRONOUNS_SHE_HER, PRONOUNS_HE_THEY, PRONOUNS_SHE_THEY, PRONOUNS_IT_ITS)
+
+/// Integer (~ticks * SSMobs/wait fire rate). The default maximum value a mob's confused var can be set to.
+#define CONFUSED_MAX 15
+
+/// Get the client from the var
+#define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (istype(I, /client) ? I : (istype(I, /datum/mind) ? I:current?:client : null)))
diff --git a/code/__defines/overmap.dm b/code/__defines/overmap.dm
index a9eee83eb376f..7356c7b0ddaa2 100644
--- a/code/__defines/overmap.dm
+++ b/code/__defines/overmap.dm
@@ -11,4 +11,3 @@
#define OVERMAP_WEAKNESS_EMP 2
#define OVERMAP_WEAKNESS_MINING 4
#define OVERMAP_WEAKNESS_EXPLOSIVE 8
-#define OVERMAP_WEAKNESS_DROPPOD 16
\ No newline at end of file
diff --git a/code/__defines/proc_presets.dm b/code/__defines/proc_presets.dm
index 0577213357e83..8268469f0150d 100644
--- a/code/__defines/proc_presets.dm
+++ b/code/__defines/proc_presets.dm
@@ -1 +1 @@
-#define MERGE_ASSOCS_WITH_NUM_VALUES(a, b) merge_assoc_lists(a, b, /proc/assoc_merge_add, 1)
+#define MERGE_ASSOCS_WITH_NUM_VALUES(a, b) merge_assoc_lists(a, b, GLOBAL_PROC_REF(assoc_merge_add), 1)
diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm
index ffe750f69a6f8..1202fdf3457f0 100644
--- a/code/__defines/qdel.dm
+++ b/code/__defines/qdel.dm
@@ -1,24 +1,3 @@
-//defines that give qdel hints. these can be given as a return in destory() or by calling
-
-#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion.
-#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory.
-#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
-#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
-#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
-#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
- //if TESTING is enabled, qdel will call this object's find_references() verb.
-#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails.
-//defines for the gc_destroyed var
-
-#define GC_QUEUE_PREQUEUE 1
-#define GC_QUEUE_CHECK 2
-#define GC_QUEUE_HARDDELETE 3
-#define GC_QUEUE_COUNT 3 //increase this when adding more steps.
-
-#define GC_QUEUED_FOR_QUEUING -1
-#define GC_QUEUED_FOR_HARD_DEL -2
-#define GC_CURRENTLY_BEING_QDELETED -3
-
#define QDELING(X) (X.gc_destroyed)
#define QDELETED(X) (!X || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
diff --git a/code/__defines/research.dm b/code/__defines/research.dm
index 6862d2355c1ab..560872ef16c6a 100644
--- a/code/__defines/research.dm
+++ b/code/__defines/research.dm
@@ -15,5 +15,3 @@
#define PROTOLATHE FLAG(1) //New stuff. Uses glass/metal/chemicals
#define MECHFAB FLAG(2) //Mechfab
#define CHASSIS FLAG(3) //For protolathe, but differently
-
-#define T_BOARD(name) "circuit board (" + (name) + ")"
\ No newline at end of file
diff --git a/code/__defines/rust_g.dm b/code/__defines/rust_g.dm
new file mode 100644
index 0000000000000..7420f1426b95f
--- /dev/null
+++ b/code/__defines/rust_g.dm
@@ -0,0 +1,211 @@
+// rust_g.dm - DM API for rust_g extension library
+//
+// To configure, create a `rust_g.config.dm` and set what you care about from
+// the following options:
+//
+// #define RUST_G "path/to/rust_g"
+// Override the .dll/.so detection logic with a fixed path or with detection
+// logic of your own.
+
+// Enable replacement rust-g functions for certain builtins. Off by default.
+// #define RUSTG_OVERRIDE_BUILTINS
+
+#ifndef RUST_G
+// Default automatic RUST_G detection.
+// On Windows, looks in the standard places for `rust_g.dll`.
+// On Linux, looks in `.`, `$LD_LIBRARY_PATH`, and `~/.byond/bin` for either of
+// `librust_g.so` (preferred) or `rust_g` (old).
+
+/* This comment bypasses grep checks */ /var/__rust_g
+
+/proc/__detect_rust_g()
+ if (world.system_type == UNIX)
+ if (fexists("./librust_g.so"))
+ // No need for LD_LIBRARY_PATH badness.
+ return __rust_g = "./librust_g.so"
+ else if (fexists("./rust_g"))
+ // Old dumb filename.
+ return __rust_g = "./rust_g"
+ else if (fexists("[world.GetConfig("env", "HOME")]/.byond/bin/rust_g"))
+ // Old dumb filename in `~/.byond/bin`.
+ return __rust_g = "rust_g"
+ else
+ // It's not in the current directory, so try others
+ return __rust_g = "librust_g.so"
+ else
+ return __rust_g = "rust_g"
+
+#define RUST_G (__rust_g || __detect_rust_g())
+#endif
+
+// Handle 515 call() -> call_ext() changes
+#if DM_VERSION >= 515
+#define RUSTG_CALL call_ext
+#else
+#define RUSTG_CALL call
+#endif
+
+
+/**
+ * Sets up the Aho-Corasick automaton with its default options.
+ *
+ * The search patterns list and the replacements must be of the same length when replace is run, but an empty replacements list is allowed if replacements are supplied with the replace call
+ * Arguments:
+ * * key - The key for the automaton, to be used with subsequent rustg_acreplace/rustg_acreplace_with_replacements calls
+ * * patterns - A non-associative list of strings to search for
+ * * replacements - Default replacements for this automaton, used with rustg_acreplace
+ */
+#define rustg_setup_acreplace(key, patterns, replacements) RUSTG_CALL(RUST_G, "setup_acreplace")(key, json_encode(patterns), json_encode(replacements))
+
+/**
+ * Sets up the Aho-Corasick automaton using supplied options.
+ *
+ * The search patterns list and the replacements must be of the same length when replace is run, but an empty replacements list is allowed if replacements are supplied with the replace call
+ * Arguments:
+ * * key - The key for the automaton, to be used with subsequent rustg_acreplace/rustg_acreplace_with_replacements calls
+ * * options - An associative list like list("anchored" = 0, "ascii_case_insensitive" = 0, "match_kind" = "Standard"). The values shown on the example are the defaults, and default values may be omitted. See the identically named methods at https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasickBuilder.html to see what the options do.
+ * * patterns - A non-associative list of strings to search for
+ * * replacements - Default replacements for this automaton, used with rustg_acreplace
+ */
+#define rustg_setup_acreplace_with_options(key, options, patterns, replacements) RUSTG_CALL(RUST_G, "setup_acreplace")(key, json_encode(options), json_encode(patterns), json_encode(replacements))
+
+/**
+ * Run the specified replacement engine with the provided haystack text to replace, returning replaced text.
+ *
+ * Arguments:
+ * * key - The key for the automaton
+ * * text - Text to run replacements on
+ */
+#define rustg_acreplace(key, text) RUSTG_CALL(RUST_G, "acreplace")(key, text)
+
+/**
+ * Run the specified replacement engine with the provided haystack text to replace, returning replaced text.
+ *
+ * Arguments:
+ * * key - The key for the automaton
+ * * text - Text to run replacements on
+ * * replacements - Replacements for this call. Must be the same length as the set-up patterns
+ */
+#define rustg_acreplace_with_replacements(key, text, replacements) RUSTG_CALL(RUST_G, "acreplace_with_replacements")(key, text, json_encode(replacements))
+
+/**
+ * This proc generates a cellular automata noise grid which can be used in procedural generation methods.
+ *
+ * Returns a single string that goes row by row, with values of 1 representing an alive cell, and a value of 0 representing a dead cell.
+ *
+ * Arguments:
+ * * percentage: The chance of a turf starting closed
+ * * smoothing_iterations: The amount of iterations the cellular automata simulates before returning the results
+ * * birth_limit: If the number of neighboring cells is higher than this amount, a cell is born
+ * * death_limit: If the number of neighboring cells is lower than this amount, a cell dies
+ * * width: The width of the grid.
+ * * height: The height of the grid.
+ */
+#define rustg_cnoise_generate(percentage, smoothing_iterations, birth_limit, death_limit, width, height) \
+ RUSTG_CALL(RUST_G, "cnoise_generate")(percentage, smoothing_iterations, birth_limit, death_limit, width, height)
+
+
+// File operations //
+#define rustg_dmi_strip_metadata(fname) RUSTG_CALL(RUST_G, "dmi_strip_metadata")(fname)
+#define rustg_dmi_create_png(path, width, height, data) RUSTG_CALL(RUST_G, "dmi_create_png")(path, width, height, data)
+#define rustg_dmi_resize_png(path, width, height, resizetype) RUSTG_CALL(RUST_G, "dmi_resize_png")(path, width, height, resizetype)
+/**
+ * input: must be a path, not an /icon; you have to do your own handling if it is one, as icon objects can't be directly passed to rustg.
+ *
+ * output: json_encode'd list. json_decode to get a flat list with icon states in the order they're in inside the .dmi
+ */
+#define rustg_dmi_icon_states(fname) RUSTG_CALL(RUST_G, "dmi_icon_states")(fname)
+
+#define rustg_file_read(fname) RUSTG_CALL(RUST_G, "file_read")(fname)
+#define rustg_file_exists(fname) RUSTG_CALL(RUST_G, "file_exists")(fname)
+#define rustg_file_write(text, fname) RUSTG_CALL(RUST_G, "file_write")(text, fname)
+#define rustg_file_append(text, fname) RUSTG_CALL(RUST_G, "file_append")(text, fname)
+#define rustg_file_get_line_count(fname) text2num(RUSTG_CALL(RUST_G, "file_get_line_count")(fname))
+#define rustg_file_seek_line(fname, line) RUSTG_CALL(RUST_G, "file_seek_line")(fname, "[line]")
+#define rustg_file_write_b64decode(text, fname) RUSTG_CALL(RUST_G, "file_write")(text, fname, "true")
+
+#ifdef RUSTG_OVERRIDE_BUILTINS
+ #define file2text(fname) rustg_file_read("[fname]")
+ #define text2file(text, fname) rustg_file_append(text, "[fname]")
+ #define fexists(fname) (rustg_file_exists("[fname]") == "true")
+#endif
+
+#define rustg_git_revparse(rev) RUSTG_CALL(RUST_G, "rg_git_revparse")(rev)
+#define rustg_git_commit_date(rev) RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev)
+
+#define rustg_json_is_valid(text) (RUSTG_CALL(RUST_G, "json_is_valid")(text) == "true")
+
+#define rustg_log_write(fname, text, format) RUSTG_CALL(RUST_G, "log_write")(fname, text, format)
+/proc/rustg_log_close_all() return RUSTG_CALL(RUST_G, "log_close_all")()
+
+#define rustg_log_write_formatted(log, text) rustg_log_write(log, text, "true")
+#define rustg_log_write_no_format(log, text) rustg_log_write(log, text, "false")
+
+#define rustg_raw_read_toml_file(path) json_decode(RUSTG_CALL(RUST_G, "toml_file_to_json")(path) || "null")
+
+/proc/rustg_read_toml_file(path)
+ var/list/output = rustg_raw_read_toml_file(path)
+ if (output["success"])
+ return json_decode(output["content"])
+ else
+ CRASH(output["content"])
+
+#define rustg_raw_toml_encode(value) json_decode(RUSTG_CALL(RUST_G, "toml_encode")(json_encode(value)))
+
+/proc/rustg_toml_encode(value)
+ var/list/output = rustg_raw_toml_encode(value)
+ if (output["success"])
+ return output["content"]
+ else
+ CRASH(output["content"])
+
+// Network operations //
+
+#define RUSTG_HTTP_METHOD_GET "get"
+#define RUSTG_HTTP_METHOD_PUT "put"
+#define RUSTG_HTTP_METHOD_DELETE "delete"
+#define RUSTG_HTTP_METHOD_PATCH "patch"
+#define RUSTG_HTTP_METHOD_HEAD "head"
+#define RUSTG_HTTP_METHOD_POST "post"
+#define rustg_http_request_blocking(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_blocking")(method, url, body, headers, options)
+#define rustg_http_request_async(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_async")(method, url, body, headers, options)
+#define rustg_http_check_request(req_id) RUSTG_CALL(RUST_G, "http_check_request")(req_id)
+/proc/rustg_create_async_http_client() return RUSTG_CALL(RUST_G, "start_http_client")()
+/proc/rustg_close_async_http_client() return RUSTG_CALL(RUST_G, "shutdown_http_client")()
+
+#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
+#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB"
+#define RUSTG_JOB_ERROR "JOB PANICKED"
+
+#define rustg_url_encode(text) RUSTG_CALL(RUST_G, "url_encode")("[text]")
+#define rustg_url_decode(text) RUSTG_CALL(RUST_G, "url_decode")(text)
+
+#define rustg_noise_get_at_coordinates(seed, x, y) RUSTG_CALL(RUST_G, "noise_get_at_coordinates")(seed, x, y)
+
+// SQL //
+
+#define rustg_sql_connect_pool(options) RUSTG_CALL(RUST_G, "sql_connect_pool")(options)
+#define rustg_sql_query_async(handle, query, params) RUSTG_CALL(RUST_G, "sql_query_async")(handle, query, params)
+#define rustg_sql_query_blocking(handle, query, params) RUSTG_CALL(RUST_G, "sql_query_blocking")(handle, query, params)
+#define rustg_sql_connected(handle) RUSTG_CALL(RUST_G, "sql_connected")(handle)
+#define rustg_sql_disconnect_pool(handle) RUSTG_CALL(RUST_G, "sql_disconnect_pool")(handle)
+#define rustg_sql_check_query(job_id) RUSTG_CALL(RUST_G, "sql_check_query")("[job_id]")
+
+// Time //
+
+#define rustg_time_microseconds(id) text2num(RUSTG_CALL(RUST_G, "time_microseconds")(id))
+#define rustg_time_milliseconds(id) text2num(RUSTG_CALL(RUST_G, "time_milliseconds")(id))
+#define rustg_time_reset(id) RUSTG_CALL(RUST_G, "time_reset")(id)
+
+/// Returns the timestamp as a string
+/proc/rustg_unix_timestamp()
+ return RUSTG_CALL(RUST_G, "unix_timestamp")()
+
+// Text Operations //
+#define rustg_cyrillic_to_latin(text) RUSTG_CALL(RUST_G, "cyrillic_to_latin")("[text]")
+#define rustg_latin_to_cyrillic(text) RUSTG_CALL(RUST_G, "latin_to_cyrillic")("[text]")
+
+#ifdef RUSTG_OVERRIDE_BUILTINS
+ #define url_encode(text) rustg_url_encode(text)
+ #define url_decode(text) rustg_url_decode(text)
+#endif
diff --git a/code/__defines/skills.dm b/code/__defines/skills.dm
index d186030ed9403..60e9e46b36b85 100644
--- a/code/__defines/skills.dm
+++ b/code/__defines/skills.dm
@@ -1,17 +1,22 @@
-#define SKILL_NONE 1
-#define SKILL_BASIC 2
-#define SKILL_ADEPT 3
-#define SKILL_EXPERT 4
-#define SKILL_PROF 5
-#define HAS_PERK SKILL_NONE + 1
+#define SKILL_UNSKILLED 1
+#define SKILL_BASIC 2
+#define SKILL_TRAINED 3
+#define SKILL_EXPERIENCED 4
+#define SKILL_MASTER 5
+#define HAS_PERK SKILL_UNSKILLED + 1
-#define SKILL_MIN 1 // Min skill value selectable
-#define SKILL_MAX 5 // Max skill value selectable
-#define SKILL_DEFAULT 4 //most mobs will default to this
+/// Min skill value selectable
+#define SKILL_MIN SKILL_UNSKILLED
+/// Max skill value selectable
+#define SKILL_MAX SKILL_MASTER
+/// Default skill value for mobs
+#define SKILL_DEFAULT SKILL_EXPERIENCED
+/// Baseline skill level used for determining mechanical skill multipliers.
+#define SKILL_BASELINE SKILL_TRAINED
-#define SKILL_EASY 1
-#define SKILL_AVERAGE 2
-#define SKILL_HARD 4
+#define SKILL_EASY SKILL_UNSKILLED
+#define SKILL_AVERAGE SKILL_BASIC
+#define SKILL_HARD SKILL_EXPERIENCED
#define SKILL_BUREAUCRACY /singleton/hierarchy/skill/organizational/bureaucracy
#define SKILL_FINANCE /singleton/hierarchy/skill/organizational/finance
diff --git a/code/__defines/sound.dm b/code/__defines/sound.dm
new file mode 100644
index 0000000000000..5ff044f5aed8d
--- /dev/null
+++ b/code/__defines/sound.dm
@@ -0,0 +1,34 @@
+///Announcer audio keys
+#define ANNOUNCER_ALIENS 'sound/wyccstation/announcements/life_signatures.ogg'
+#define ANNOUNCER_IONSTORM 'sound/ai/ionstorm.ogg'
+#define ANNOUNCER_METEORS 'sound/wyccstation/announcements/meteors_1.ogg'
+#define ANNOUNCER_OUTBREAK5 'sound/wyccstation/announcements/biohazard_level_5.ogg'
+#define ANNOUNCER_OUTBREAK7 'sound/wyccstation/announcements/biohazard_level_7.ogg'
+#define ANNOUNCER_POWEROFF 'sound/wyccstation/announcements/electricity_off.ogg'
+#define ANNOUNCER_POWERON 'sound/wyccstation/announcements/electricity_on.ogg'
+#define ANNOUNCER_RADIATION 'sound/wyccstation/announcements/radiation.ogg'
+#define ANNOUNCER_SHUTTLECALLED 'sound/ai/shuttlecalled.ogg'
+#define ANNOUNCER_SHUTTLEDOCK 'sound/ai/shuttledock.ogg'
+#define ANNOUNCER_SHUTTLERECALLED 'sound/ai/shuttlerecalled.ogg'
+#define ANNOUNCER_SPANOMALIES 'sound/wyccstation/announcements/anomaly_gravity.ogg'
+#define ANNOUNCER_WELCOME 'sound/wyccstation/announcements/welcome_1.ogg'
+#define ANNOUNCER_COMMANDREPORT 'sound/wyccstation/announcements/command_report.ogg'
+#define ANNOUNCER_ABANDONSHIP 'sound/wyccstation/announcements/evacuation.ogg'
+#define ANNOUNCER_ELECTRICALSTORM_MOD 'sound/wyccstation/announcements/electrical_storm_normal.ogg'
+#define ANNOUNCER_ELECTRICALSTORM_MAJ 'sound/wyccstation/announcements/electrical_storm_high.ogg'
+#define ANNOUNCER_BLUESPACEJUMP_INIT 'sound/wyccstation/announcements/bluespacejump_initialization.ogg'
+#define ANNOUNCER_BLUESPACEJUMP_PREP 'sound/wyccstation/announcements/bluespacejump_prepare.ogg'
+#define ANNOUNCER_BLUESPACEJUMP_START 'sound/wyccstation/announcements/bluespacejump_start.ogg'
+
+// Currently unused
+
+//#define ANNOUNCER_ANIMES 'sound/wyccstation/announcements/anime.ogg'
+//#define ANNOUNCER_GRANOMALIES 'sound/wyccstation/announcements/anomaly_gravity.ogg'
+//#define ANNOUNCER_NEWAI 'sound/wyccstation/announcements/new_ai_1.ogg'
+
+///Tools
+#define DEFAULT_CROWBAR_SOUND 'sound/items/Crowbar.ogg'
+#define DEFAULT_SCREWDRIVER_SOUND 'sound/items/Screwdriver.ogg'
+#define DEFAULT_WRENCH_SOUND 'sound/items/Ratchet.ogg'
+#define DEFAULT_WIRECUTTER_SOUND 'sound/items/Wirecutter.ogg'
+#define DEFAULT_WELDER_SOUND 'sound/items/Welder.ogg'
diff --git a/code/__defines/species.dm b/code/__defines/species.dm
index 58dbcc6e359cf..31b74723209b8 100644
--- a/code/__defines/species.dm
+++ b/code/__defines/species.dm
@@ -42,7 +42,7 @@
#define SKIN_THREAT FLAG(0)
-// Darkvision Levels. Inverted - white is darkest, black is full vision
-#define DARKTINT_NONE "#ffffff"
-#define DARKTINT_MODERATE "#f9f9f5"
-#define DARKTINT_GOOD "#ebebe6"
+// Darkvision Levels. White is brightest, darker tints affect vision negatively
+#define DARKTINT_GOOD "#ffffff"
+#define DARKTINT_MODERATE "#f9f9f5"
+#define DARKTINT_NONE "#ebebe6"
diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm
index c43047876537e..888f64cf2a6a8 100644
--- a/code/__defines/subsystem-priority.dm
+++ b/code/__defines/subsystem-priority.dm
@@ -3,49 +3,53 @@
// SS_BACKGROUND handles high server load differently than Normal and SS_TICKER do.
// Higher priority also means a larger share of a given tick before sleep checks.
-#define SS_PRIORITY_DEFAULT 50 // Default priority for all processes levels
+#define FIRE_PRIORITY_INPUT 1000 // Input MUST ALWAYS BE HIGHEST PRIORITY!!!
+#define FIRE_PRIORITY_TIMER 950
+#define FIRE_PRIORITY_OVERLAYS 900
+#define FIRE_PRIORITY_CHAT 850 // Chat
+#define FIRE_PRIORITY_TICKER 800 // Gameticker.
+#define FIRE_PRIORITY_TGUI 750
+#define FIRE_PRIORITY_NANO 749 // Updates to nanoui uis.
+#define FIRE_PRIORITY_MOB 700 // Mob Life().
-// SS_TICKER
-#define SS_PRIORITY_TIMER 20
-#define SS_PRIORITY_ICON_UPDATE 20 // Queued icon updates. Mostly used by APCs and tables.
+#define FIRE_PRIORITY_PROCESSING 650 // Default priority for all processing subsystems
-// Normal
-#define SS_PRIORITY_TICKER 100 // Gameticker.
-#define SS_PRIORITY_MOB 95 // Mob Life().
-#define SS_PRIORITY_MACHINERY 95 // Machinery + powernet ticks.
-#define SS_PRIORITY_AIR 80 // ZAS processing.
-#define SS_PRIORITY_CHEMISTRY 60 // Multi-tick chemical reactions.
-#define SS_PRIORITY_CHAT 40 // Chat
-#define SS_PRIORITY_ALARM 20 // Alarm processing.
-#define SS_PRIORITY_EVENT 20 // Event processing and queue handling.
-#define SS_PRIORITY_SHUTTLE 20 // Shuttle movement.
-#define SS_PRIORITY_CIRCUIT_COMP 20 // Processing circuit component do_work.
-#define SS_PRIORITY_TEMPERATURE 20 // Cooling and heating of atoms.
-#define SS_PRIORITY_RADIATION 20 // Radiation processing and cache updates.
-#define SS_PRIORITY_OPEN_SPACE 20 // Open turf updates.
-#define SS_PRIORITY_AIRFLOW 15 // Object movement from ZAS airflow.
-#define SS_PRIORITY_AI 15 // Mob AI
-#define SS_PRIORITY_PRESENCE 10 // z-level player presence testing
-#define SS_PRIORITY_VOTE 10 // Vote management.
-#define SS_PRIORITY_SUPPLY 10 // Supply point accumulation.
-#define SS_PRIORITY_TRADE 10 // Adds/removes traders.
-#define SS_PRIORITY_GHOST_IMAGES 10 // Updates ghost client images.
-#define SS_PRIORITY_ZCOPY 10 // Builds appearances for Z-Mimic.
+#define FIRE_PRIORITY_MACHINERY 600 // Machinery + powernet ticks.
+#define FIRE_PRIORITY_AIR 500 // ZAS processing.
+#define FIRE_PRIORITY_SPACEDRIFT 450 // Drifting things
+#define FIRE_PRIORITY_PLANTS 400
+#define FIRE_PRIORITY_THROWING 350 // Throwing calculation and constant checks
+#define FIRE_PRIORITY_CHEMISTRY 300 // Multi-tick chemical reactions.
-// SS_BACKGROUND
-#define SS_PRIORITY_OBJECTS 100 // processing_objects processing.
-#define SS_PRIORITY_PROCESSING 95 // Generic datum processor. Replaces objects processor.
-#define SS_PRIORITY_PLANTS 90 // Plant processing, slow ticks.
-#define SS_PRIORITY_VINES 50 // Spreading vine effects.
-#define SS_PRIORITY_PSYCHICS 45 // Psychic complexus processing.
-#define SS_PRIORITY_NANO 40 // Updates to nanoui uis.
-#define SS_PRIORITY_TURF 30 // Radioactive walls/blob.
-#define SS_PRIORITY_EVAC 30 // Processes the evac controller.
-#define SS_PRIORITY_CIRCUIT 30 // Processing Circuit's ticks and all that
-#define SS_PRIORITY_GRAPH 30 // Merging and splitting of graphs
-#define SS_PRIORITY_CHAR_SETUP 25 // Writes player preferences to savefiles.
-#define SS_PRIORITY_GARBAGE 20 // Garbage collection.
-#define SS_PRIORITY_INACTIVITY 10 // Idle kicking.
+#define FIRE_PRIORITY_DEFAULT 50 // Default priority for all subsystems
+
+#define FIRE_PRIORITY_VINES 50 // Spreading vine effects.
+#define FIRE_PRIORITY_PSYCHICS 45 // Psychic complexus processing.
+#define FIRE_PRIORITY_TURF 30 // Radioactive walls/blob.
+#define FIRE_PRIORITY_EVAC 30 // Processes the evac controller.
+#define FIRE_PRIORITY_CIRCUIT 30 // Processing Circuit's ticks and all that
+#define FIRE_PRIORITY_GRAPH 30 // Merging and splitting of graphs
+#define FIRE_PRIORITY_CHAR_SETUP 25 // Writes player preferences to savefiles.
+#define FIRE_PRIORITY_AI 25 // Mob AI
+#define FIRE_PRIORITY_GARBAGE 20 // Garbage collection.
+#define FIRE_PRIORITY_ALARM 20 // Alarm processing.
+#define FIRE_PRIORITY_EVENT 20 // Event processing and queue handling.
+#define FIRE_PRIORITY_SHUTTLE 20 // Shuttle movement.
+#define FIRE_PRIORITY_CIRCUIT_COMP 20 // Processing circuit component do_work.
+#define FIRE_PRIORITY_TEMPERATURE 20 // Cooling and heating of atoms.
+#define FIRE_PRIORITY_RADIATION 20 // Radiation processing and cache updates.
+#define FIRE_PRIORITY_OPEN_SPACE 20 // Open turf updates.
+#define FIRE_PRIORITY_AIRFLOW 15 // Object movement from ZAS airflow.
+#define FIRE_PRIORITY_OVERMAP 12
+#define FIRE_PRIORITY_ICON_UPDATE 10
+#define FIRE_PRIORITY_INACTIVITY 10 // Idle kicking.
+#define FIRE_PRIORITY_PRESENCE 10 // z-level player presence testing
+#define FIRE_PRIORITY_VOTE 10 // Vote management.
+#define FIRE_PRIORITY_SUPPLY 10 // Supply point accumulation.
+#define FIRE_PRIORITY_TRADE 10 // Adds/removes traders.
+#define FIRE_PRIORITY_GHOST_IMAGES 10 // Updates ghost client images.
+#define FIRE_PRIORITY_PING 10
+#define FIRE_PRIORITY_ZCOPY 10 // Builds appearances for Z-Mimic.
// Subsystem fire priority, from lowest to highest priority
// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child)
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index b6298315bc7cd..72579b7f19416 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -8,24 +8,28 @@
#define RUNLEVELS_ALL (~EMPTY_BITFIELD)
#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME)
+#define RUNLEVELS_GAME (RUNLEVEL_GAME | RUNLEVEL_POSTGAME)
+#define RUNLEVELS_PREGAME (RUNLEVEL_LOBBY | RUNLEVEL_SETUP)
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
-#define SS_INIT_EARLY 18
-#define SS_INIT_GARBAGE 17
-#define SS_INIT_CHEMISTRY 16
-#define SS_INIT_MATERIALS 15
-#define SS_INIT_PLANTS 14
-#define SS_INIT_ANTAGS 13
-#define SS_INIT_CULTURE 12
-#define SS_INIT_MISC 11
-#define SS_INIT_SKYBOX 10
-#define SS_INIT_MAPPING 9
-#define SS_INIT_JOBS 8
-#define SS_INIT_CHAR_SETUP 7
+#define SS_INIT_EARLY 20
+#define SS_INIT_GARBAGE 19
+#define SS_INIT_CHEMISTRY 18
+#define SS_INIT_MATERIALS 17
+#define SS_INIT_PLANTS 16
+#define SS_INIT_ANTAGS 15
+#define SS_INIT_CULTURE 14
+#define SS_INIT_MISC 13
+#define SS_INIT_SKYBOX 12
+#define SS_INIT_MAPPING 11
+#define SS_INIT_JOBS 10
+#define SS_INIT_ASSETS 9
+#define SS_INIT_CHAR_SETUP 8
+#define SS_INIT_INPUT 7
#define SS_INIT_CIRCUIT 6
#define SS_INIT_GRAPH 5
#define SS_INIT_OPEN_SPACE 4
@@ -40,12 +44,15 @@
#define SS_INIT_SHUTTLE -5
#define SS_INIT_GOALS -5
#define SS_INIT_LIGHTING -6
-#define SS_INIT_ZCOPY -7
-#define SS_INIT_HOLOMAP -8
-#define SS_INIT_XENOARCH -10
+#define SS_INIT_AMBIENT_LIGHT -7
+#define SS_INIT_ZCOPY -8
+#define SS_INIT_HOLOMAP -9
+#define SS_INIT_OVERLAYS -10
+#define SS_INIT_XENOARCH -11
#define SS_INIT_BAY_LEGACY -12
#define SS_INIT_TICKER -20
#define SS_INIT_AI -21
#define SS_INIT_AIFAST -22
+#define SS_INIT_PING -85
#define SS_INIT_CHAT -90 // Should be lower to ensure chat remains smooth during init.
#define SS_INIT_UNIT_TESTS -100
diff --git a/code/__defines/text.dm b/code/__defines/text.dm
new file mode 100644
index 0000000000000..272ab5965e59a
--- /dev/null
+++ b/code/__defines/text.dm
@@ -0,0 +1,3 @@
+/// Standard maptext
+/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
+#define MAPTEXT(text) {"[##text]"}
diff --git a/code/__defines/tgui.dm b/code/__defines/tgui.dm
new file mode 100644
index 0000000000000..b6b48f01dac63
--- /dev/null
+++ b/code/__defines/tgui.dm
@@ -0,0 +1,48 @@
+/// Max length for Modal Input
+#define TGUI_MODAL_INPUT_MAX_LENGTH 1024
+/// Max length for Modal Input for names
+#define TGUI_MODAL_INPUT_MAX_LENGTH_NAME 64 // Names for generally anything don't go past 32, let alone 64.
+
+#define TGUI_MODAL_OPEN 1
+#define TGUI_MODAL_DELEGATE 2
+#define TGUI_MODAL_ANSWER 3
+#define TGUI_MODAL_CLOSE 4
+
+/// Maximum number of windows that can be suspended/reused
+#define TGUI_WINDOW_SOFT_LIMIT 5
+/// Maximum number of open windows
+#define TGUI_WINDOW_HARD_LIMIT 9
+
+/// Maximum ping timeout allowed to detect zombie windows
+#define TGUI_PING_TIMEOUT (4 SECONDS)
+/// Used for rate-limiting to prevent DoS by excessively refreshing a TGUI window
+#define TGUI_REFRESH_FULL_UPDATE_COOLDOWN (1 SECONDS)
+
+/// Window does not exist
+#define TGUI_WINDOW_CLOSED 0
+/// Window was just opened, but is still not ready to be sent data
+#define TGUI_WINDOW_LOADING 1
+/// Window is free and ready to receive data
+#define TGUI_WINDOW_READY 2
+
+/// Get a window id based on the provided pool index
+#define TGUI_WINDOW_ID(index) "tgui-window-[index]"
+/// Get a pool index of the provided window id
+#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13))
+
+/// Creates a message packet for sending via output()
+// This is {"type":type,"payload":payload}, but pre-encoded. This is much faster
+// than doing it the normal way.
+// To ensure this is correct, this is unit tested in tgui_create_message.
+#define TGUI_CREATE_MESSAGE(type, payload) ( \
+ "%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
+)
+
+/// Standard super() call to make sure that the ui_act is sane
+#define UI_ACT_CHECK \
+ if(..()) {return TRUE}
+
+/// Standard super() call to make sure that the ui_act is sane
+/// Except do not trigger the action in the overriden ui_act of parent
+#define UI_ACT_CHECK_NO_ACTION \
+ if(..(null, null)) {return TRUE}
diff --git a/code/__defines/tools.dm b/code/__defines/tools.dm
new file mode 100644
index 0000000000000..99c3cdb8eb930
--- /dev/null
+++ b/code/__defines/tools.dm
@@ -0,0 +1,38 @@
+// Tool types, if you add new ones please add them to /obj/item/debug/omnitool in code/game/objects/items/debug_items.dm
+#define TOOL_CROWBAR "crowbar"
+#define TOOL_MULTITOOL "multitool"
+#define TOOL_SCREWDRIVER "screwdriver"
+#define TOOL_WIRECUTTER "wirecutter"
+#define TOOL_WRENCH "wrench"
+#define TOOL_WELDER "welder"
+#define TOOL_ANALYZER "analyzer"
+#define TOOL_MINING "mining"
+#define TOOL_SHOVEL "shovel"
+#define TOOL_RETRACTOR "retractor"
+#define TOOL_HEMOSTAT "hemostat"
+#define TOOL_CAUTERY "cautery"
+#define TOOL_DRILL "drill"
+#define TOOL_SCALPEL "scalpel"
+#define TOOL_SAW "saw"
+#define TOOL_BONESET "bonesetter"
+#define TOOL_KNIFE "knife"
+#define TOOL_BLOODFILTER "bloodfilter"
+#define TOOL_ROLLINGPIN "rolling pin"
+/// Can be used to scrape rust off an any atom; which will result in the Rust Component being qdel'd
+#define TOOL_RUSTSCRAPER "rustscraper"
+
+// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY,
+// tool sound is only played when op is started. If not, it's played twice.
+#define MIN_TOOL_SOUND_DELAY 20
+
+/// Return when an item interaction is successful.
+/// This cancels the rest of the chain entirely and indicates success.
+#define ITEM_INTERACT_SUCCESS FLAG(0) // Same as TRUE, as most tool (legacy) tool acts return TRUE on success
+/// Return to prevent the rest of the attack chain from being executed / preventing the item user from thwacking the target.
+/// Similar to [ITEM_INTERACT_SUCCESS], but does not necessarily indicate success.
+#define ITEM_INTERACT_BLOCKING FLAG(1)
+/// Return to skip the rest of the interaction chain, going straight to attack.
+#define ITEM_INTERACT_SKIP_TO_ATTACK FLAG(2)
+
+/// Combination flag for any item interaction that blocks the rest of the attack chain
+#define ITEM_INTERACT_ANY_BLOCKER (ITEM_INTERACT_SUCCESS | ITEM_INTERACT_BLOCKING)
diff --git a/code/__defines/turfs.dm b/code/__defines/turfs.dm
index c6cf13fbb28b8..58f0935456e9d 100644
--- a/code/__defines/turfs.dm
+++ b/code/__defines/turfs.dm
@@ -19,4 +19,10 @@
#define SMOOTH_WHITELIST 2 //Smooth with a whitelist of subtypes
#define SMOOTH_BLACKLIST 3 //Smooth with all but a blacklist of subtypes
+/// Finds turfs block with desired center and radius. Make sure that `center` has valid x,y,z. If it's in loc of another non-turf atom, it's coordinates will be (0,0,0)
#define RANGE_TURFS(CENTER, RADIUS) block(locate(max(CENTER.x-(RADIUS), 1), max(CENTER.y-(RADIUS),1), CENTER.z), locate(min(CENTER.x+(RADIUS), world.maxx), min(CENTER.y+(RADIUS), world.maxy), CENTER.z))
+/// The same as `RANGE_TURFS` but with center excluded
+#define ORANGE_TURFS(CENTER, RADIUS) RANGE_TURFS(CENTER, RADIUS) - CENTER
+
+///Returns all currently loaded turfs
+#define ALL_TURFS(...) block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz))
diff --git a/code/__defines/xenoarcheaology.dm b/code/__defines/xenoarcheaology.dm
index 67f75e72f8439..852f2c0d1d8cd 100644
--- a/code/__defines/xenoarcheaology.dm
+++ b/code/__defines/xenoarcheaology.dm
@@ -54,4 +54,7 @@
#define EFFECT_PARTICLE 4
#define EFFECT_ORGANIC 5
#define EFFECT_BLUESPACE 6
-#define EFFECT_SYNTH 7
\ No newline at end of file
+#define EFFECT_SYNTH 7
+
+#define TRIGGER_SIMPLE 0
+#define TRIGGER_COMPLEX 1
diff --git a/code/__defines/~mods/bloom_light.dm b/code/__defines/~mods/bloom_light.dm
new file mode 100644
index 0000000000000..371247d85841d
--- /dev/null
+++ b/code/__defines/~mods/bloom_light.dm
@@ -0,0 +1,3 @@
+#define LIGHTING_EXPOSURE_PLANE 4 // Light sources "cones"
+#define LIGHTING_LAMPS_PLANE 5 // Light sources themselves (lamps, screens, etc.)
+#define LIGHTING_LAMPS_GLARE 6 // Light glare (optional setting)
diff --git a/code/__defines/~mods/expanded_culture_descriptor.dm b/code/__defines/~mods/expanded_culture_descriptor.dm
new file mode 100644
index 0000000000000..fbe058c80e4d9
--- /dev/null
+++ b/code/__defines/~mods/expanded_culture_descriptor.dm
@@ -0,0 +1,47 @@
+// EXPANDED_CULTURE_DESCRIPTOR - Start
+#define HOME_SYSTEM_UNATHI_TERSTEN "Tersten Clans"
+#define FACTION_UNATHI_TERSTEN "Tersten Hegemony"
+#define CULTURE_UNATHI_TERSTEN "Tersten Unathi"
+
+#define FACTION_ZENG_HU "Zeng-Hu Pharmaceuticals"
+#define FACTION_WARD_TAKAHASHI "Ward-Takahashi GMB"
+#define FACTION_GRAYSON "Grayson Manufactories Ltd."
+#define FACTION_AERTHER "Aether Atmospherics and Recycling"
+#define FACTION_MAJOR_BILL "Major Bill's"
+#define FACTION_FOCAL_POINT "Focal Point Energistics"
+#define FACTION_XION "Xion Industrial"
+#define FACTION_VEY_MED "Vey-Med"
+#define FACTION_BISHOP "Bishop Cybernetics"
+#define FACTION_MORPHEUS "Morpheus Cyberkinetics"
+#define FACTION_ZPCI "Zone Protection Control Inc."
+#define FACTION_SEPTENERGO "SeptEnergo"
+
+#define CULTURE_HUMAN_LORRIMAN "Lorrimanian"
+#define CULTURE_HUMAN_AVANOBLE "Avalon Noble"
+#define CULTURE_HUMAN_AVACOMMON "Avalon Commoner"
+#define CULTURE_HUMAN_LORDUP "Lordanian, Upper"
+#define CULTURE_HUMAN_LORDLOW "Lordanian, Lower"
+#define CULTURE_HUMAN_MIRANIAN "Miranian"
+#define CULTURE_HUMAN_NYXIAN "Nyxian"
+
+#define HOME_SYSTEM_TERSTEN "Tersten"
+#define HOME_SYSTEM_AVALON "Avalon"
+#define HOME_SYSTEM_MIRANIA "Mirania"
+#define HOME_SYSTEM_NYX_BRINKBURN "Brinkburn"
+#define HOME_SYSTEM_NYX_KALDARK "Kaldark"
+#define HOME_SYSTEM_NYX_ROANOK "Roanok"
+#define HOME_SYSTEM_NYX_YUKLIT "Yuklit"
+#define HOME_SYSTEM_NYX_CASSER "Casser"
+
+#define FACTION_SKRELL_MED "Gloa'Morr Corp."
+#define FACTION_SKRELL_AIR "Krri'gli Corp."
+#define FACTION_SKRELL_FOOD "Qorr'moa Inc."
+
+#define FACTION_POSITRONICS "Positronic Union"
+
+#define HOME_SYSTEM_ROOT "Root"
+
+#define LANGUAGE_HUMAN_LORRIMAN "Lirris"
+#define LANGUAGE_HUMAN_AVALON "Alain"
+#define LANGUAGE_HUMAN_MIRANIAN "Miranian"
+// EXPANDED_CULTURE_DESCRIPTOR - End
diff --git a/code/__defines/~mods/text_to_speech.dm b/code/__defines/~mods/text_to_speech.dm
new file mode 100644
index 0000000000000..5758c881b489d
--- /dev/null
+++ b/code/__defines/~mods/text_to_speech.dm
@@ -0,0 +1,73 @@
+#define TTS_TRAIT_PITCH_WHISPER SHIFTL(1, 1)
+#define TTS_TRAIT_RATE_FASTER SHIFTL(1, 2)
+#define TTS_TRAIT_RATE_MEDIUM SHIFTL(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/code/__defines/~mods/tgs.config.dm b/code/__defines/~mods/tgs.config.dm
new file mode 100644
index 0000000000000..000d0b7b979a8
--- /dev/null
+++ b/code/__defines/~mods/tgs.config.dm
@@ -0,0 +1,12 @@
+#define TGS_EXTERNAL_CONFIGURATION
+#define TGS_V3_API
+#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
+#define TGS_READ_GLOBAL(Name) GLOB.##Name
+#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
+#define TGS_WORLD_ANNOUNCE(message) to_world("TGS Notification: [html_encode(##message)]")
+#define TGS_INFO_LOG(message) log_tgs(message, "INF")
+#define TGS_WARNING_LOG(message) log_tgs(message, "WRN")
+#define TGS_ERROR_LOG(message) log_tgs(message, "ERR")
+#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
+#define TGS_CLIENT_COUNT length(GLOB.clients)
+#define TGS_PROTECT_DATUM(Path)
diff --git a/code/__defines/~mods/tgs.dm b/code/__defines/~mods/tgs.dm
new file mode 100644
index 0000000000000..fdfec5e8ca086
--- /dev/null
+++ b/code/__defines/~mods/tgs.dm
@@ -0,0 +1,518 @@
+// tgstation-server DMAPI
+
+#define TGS_DMAPI_VERSION "7.0.2"
+
+// All functions and datums outside this document are subject to change with any version and should not be relied on.
+
+// CONFIGURATION
+
+/// Create this define if you want to do TGS configuration outside of this file.
+#ifndef TGS_EXTERNAL_CONFIGURATION
+
+// Comment this out once you've filled in the below.
+#error TGS API unconfigured
+
+// Uncomment this if you wish to allow the game to interact with TGS 3..
+// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()().
+//#define TGS_V3_API
+
+// Required interfaces (fill in with your codebase equivalent):
+
+/// Create a global variable named `Name` and set it to `Value`.
+#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value)
+
+/// Read the value in the global variable `Name`.
+#define TGS_READ_GLOBAL(Name)
+
+/// Set the value in the global variable `Name` to `Value`.
+#define TGS_WRITE_GLOBAL(Name, Value)
+
+/// Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game use of DD -> TGS capabilities.
+#define TGS_PROTECT_DATUM(Path)
+
+/// Display an announcement `message` from the server to all players.
+#define TGS_WORLD_ANNOUNCE(message)
+
+/// Notify current in-game administrators of a string `event`.
+#define TGS_NOTIFY_ADMINS(event)
+
+/// Write an info `message` to a server log.
+#define TGS_INFO_LOG(message)
+
+/// Write an warning `message` to a server log.
+#define TGS_WARNING_LOG(message)
+
+/// Write an error `message` to a server log.
+#define TGS_ERROR_LOG(message)
+
+/// Get the number of connected /clients.
+#define TGS_CLIENT_COUNT
+
+#endif
+
+// EVENT CODES
+
+/// Before a reboot mode change, extras parameters are the current and new reboot mode enums.
+#define TGS_EVENT_REBOOT_MODE_CHANGE -1
+/// Before a port change is about to happen, extra parameters is new port.
+#define TGS_EVENT_PORT_SWAP -2
+/// Before the instance is renamed, extra parameter is the new name.
+#define TGS_EVENT_INSTANCE_RENAMED -3
+/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server.
+#define TGS_EVENT_WATCHDOG_REATTACH -4
+/// When the watchdog sends a health check to DD. No parameters.
+#define TGS_EVENT_HEALTH_CHECK -5
+
+/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA.
+#define TGS_EVENT_REPO_RESET_ORIGIN 0
+/// When the repository performs a checkout. Parameters: Checkout git object.
+#define TGS_EVENT_REPO_CHECKOUT 1
+/// When the repository performs a fetch operation. No parameters.
+#define TGS_EVENT_REPO_FETCH 2
+/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user.
+#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
+/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path.
+#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
+/// Before a engine install operation begins. Parameters: Version string of the installing engine.
+#define TGS_EVENT_ENGINE_INSTALL_START 5
+/// When a engine install operation fails. Parameters: Error message
+#define TGS_EVENT_ENGINE_INSTALL_FAIL 6
+/// When the active engine version changes. Parameters: (Nullable) Version string of the current engine, version string of the new engine.
+#define TGS_EVENT_ENGINE_ACTIVE_VERSION_CHANGE 7
+/// When the compiler starts running. Parameters: Game directory path, origin commit SHA.
+#define TGS_EVENT_COMPILE_START 8
+/// When a compile is cancelled. No parameters.
+#define TGS_EVENT_COMPILE_CANCELLED 9
+/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation.
+#define TGS_EVENT_COMPILE_FAILURE 10
+/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path.
+#define TGS_EVENT_COMPILE_COMPLETE 11
+/// When an automatic update for the current instance begins. No parameters.
+#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12
+/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference.
+#define TGS_EVENT_REPO_MERGE_CONFLICT 13
+/// When a deployment completes. No Parameters.
+#define TGS_EVENT_DEPLOYMENT_COMPLETE 14
+/// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters.
+#define TGS_EVENT_WATCHDOG_SHUTDOWN 15
+/// Before the watchdog detaches for a TGS update/restart. No parameters.
+#define TGS_EVENT_WATCHDOG_DETACH 16
+// We don't actually implement these 4 events as the DMAPI can never receive them.
+// #define TGS_EVENT_WATCHDOG_LAUNCH 17
+// #define TGS_EVENT_WATCHDOG_CRASH 18
+// #define TGS_EVENT_WORLD_END_PROCESS 19
+// #define TGS_EVENT_WORLD_REBOOT 20
+/// Watchdog event when TgsInitializationComplete() is called. No parameters.
+#define TGS_EVENT_WORLD_PRIME 21
+// DMAPI also doesnt implement this
+// #define TGS_EVENT_DREAM_DAEMON_LAUNCH 22
+/// After a single submodule update is performed. Parameters: Updated submodule name.
+#define TGS_EVENT_REPO_SUBMODULE_UPDATE 23
+/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, version string of the used engine.
+#define TGS_EVENT_PRE_DREAM_MAKER 24
+/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path.
+#define TGS_EVENT_DEPLOYMENT_CLEANUP 25
+
+// OTHER ENUMS
+
+/// The server will reboot normally.
+#define TGS_REBOOT_MODE_NORMAL 0
+/// The server will stop running on reboot.
+#define TGS_REBOOT_MODE_SHUTDOWN 1
+/// The watchdog will restart on reboot.
+#define TGS_REBOOT_MODE_RESTART 2
+
+// Note that security levels are currently meaningless in OpenDream
+/// DreamDaemon Trusted security level.
+#define TGS_SECURITY_TRUSTED 0
+/// DreamDaemon Safe security level.
+#define TGS_SECURITY_SAFE 1
+/// DreamDaemon Ultrasafe security level.
+#define TGS_SECURITY_ULTRASAFE 2
+
+/// DreamDaemon public visibility level.
+#define TGS_VISIBILITY_PUBLIC 0
+/// DreamDaemon private visibility level.
+#define TGS_VISIBILITY_PRIVATE 1
+/// DreamDaemon invisible visibility level.
+#define TGS_VISIBILITY_INVISIBLE 2
+
+/// The Build Your Own Net Dream engine.
+#define TGS_ENGINE_TYPE_BYOND 0
+/// The OpenDream engine.
+#define TGS_ENGINE_TYPE_OPENDREAM 1
+
+//REQUIRED HOOKS
+
+/**
+ * Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
+ *
+ * * event_handler - Optional user defined [/datum/tgs_event_handler].
+ * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
+ */
+/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
+ return
+
+/**
+ * Call this when your initializations are complete and your game is ready to play before any player interactions happen.
+ *
+ * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
+ * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
+ * This function should not be called before ..() in [/world/proc/New].
+ */
+/world/proc/TgsInitializationComplete()
+ return
+
+/// Put this at the start of [/world/proc/Topic].
+#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
+
+/**
+ * Call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
+ */
+/world/proc/TgsReboot()
+ return
+
+// DATUM DEFINITIONS
+// All datums defined here should be considered read-only
+
+/// Represents git revision information.
+/datum/tgs_revision_information
+ /// Full SHA of the commit.
+ var/commit
+ /// ISO 8601 timestamp of when the commit was created.
+ var/timestamp
+ /// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch.
+ var/origin_commit
+
+/// Represents a version.
+/datum/tgs_version
+ /// The suite/major version number.
+ var/suite
+
+ // This group of variables can be null to represent a wild card.
+ /// The minor version number. null for wildcards.
+ var/minor
+ /// The patch version number. null for wildcards.
+ var/patch
+
+ /// Legacy version number. Generally null.
+ var/deprecated_patch
+
+ /// Unparsed string value.
+ var/raw_parameter
+ /// String value minus prefix.
+ var/deprefixed_parameter
+
+/**
+ * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
+ */
+/datum/tgs_version/proc/Wildcard()
+ return
+
+/**
+ * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
+ *
+ * other_version - The [/datum/tgs_version] to compare against.
+ */
+/datum/tgs_version/proc/Equals(datum/tgs_version/other_version)
+ return
+
+/// Represents a merge of a GitHub pull request.
+/datum/tgs_revision_information/test_merge
+ /// The test merge number.
+ var/number
+ /// The test merge source's title when it was merged.
+ var/title
+ /// The test merge source's body when it was merged.
+ var/body
+ /// The Username of the test merge source's author.
+ var/author
+ /// An http URL to the test merge source.
+ var/url
+ /// The SHA of the test merge when that was merged.
+ var/head_commit
+ /// Optional comment left by the TGS user who initiated the merge.
+ var/comment
+
+/// Represents a connected chat channel.
+/datum/tgs_chat_channel
+ /// TGS internal channel ID.
+ var/id
+ /// User friendly name of the channel.
+ var/friendly_name
+ /// Name of the chat connection. This is the IRC server address or the Discord guild.
+ var/connection_name
+ /// [TRUE]/[FALSE] based on if the server operator has marked this channel for game admins only.
+ var/is_admin_channel
+ /// [TRUE]/[FALSE] if the channel is a private message channel for a [/datum/tgs_chat_user].
+ var/is_private_channel
+ /// Tag string associated with the channel in TGS.
+ var/custom_tag
+ /// [TRUE]/[FALSE] if the channel supports embeds.
+ var/embeds_supported
+
+// Represents a chat user
+/datum/tgs_chat_user
+ /// TGS internal user ID.
+ var/id
+ /// The user's display name.
+ var/friendly_name
+ /// The string to use to ping this user in a message.
+ var/mention
+ /// The [/datum/tgs_chat_channel] the user was from.
+ var/datum/tgs_chat_channel/channel
+
+/// User definable handler for TGS events.
+/datum/tgs_event_handler
+ /// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events.
+ var/receive_health_checks = FALSE
+
+/**
+ * User definable callback for handling TGS events.
+ *
+ * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each.
+ */
+/datum/tgs_event_handler/proc/HandleEvent(event_code, ...)
+ set waitfor = FALSE
+ return
+
+/// User definable chat command.
+/datum/tgs_chat_command
+ /// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`.
+ var/name = ""
+ /// The help text displayed for this command.
+ var/help_text = ""
+ /// If this command should be available to game administrators only.
+ var/admin_only = FALSE
+ /// A subtype of [/datum/tgs_chat_command] that is ignored when enumerating available commands. Use this to create shared base /datums for commands.
+ var/ignore_type
+
+/**
+ * Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with.
+ *
+ * sender - The [/datum/tgs_chat_user] who issued the command.
+ * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
+ */
+/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
+ CRASH("[type] has no implementation for Run()")
+
+/// User definable chat message.
+/datum/tgs_message_content
+ /// The tring content of the message. Must be provided in New().
+ var/text
+
+ /// The [/datum/tgs_chat_embed] to embed in the message. Not supported on all chat providers.
+ var/datum/tgs_chat_embed/structure/embed
+
+/datum/tgs_message_content/New(text)
+ if(!istext(text))
+ TGS_ERROR_LOG("[/datum/tgs_message_content] created with no text!")
+ text = null
+
+ src.text = text
+
+/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/channel#embed-object-embed-structure for details.
+/datum/tgs_chat_embed/structure
+ var/title
+ var/description
+ var/url
+
+ /// Timestamp must be encoded as: time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss"). Use the active timezone.
+ var/timestamp
+
+ /// Colour must be #AARRGGBB or #RRGGBB hex string.
+ var/colour
+
+ /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
+ var/datum/tgs_chat_embed/media/image
+
+ /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure for details.
+ var/datum/tgs_chat_embed/media/thumbnail
+
+ /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
+ var/datum/tgs_chat_embed/media/video
+
+ var/datum/tgs_chat_embed/footer/footer
+ var/datum/tgs_chat_embed/provider/provider
+ var/datum/tgs_chat_embed/provider/author/author
+
+ var/list/datum/tgs_chat_embed/field/fields
+
+/// Common datum for similar discord embed medias.
+/datum/tgs_chat_embed/media
+ /// Must be set in New().
+ var/url
+ var/width
+ var/height
+ var/proxy_url
+
+/datum/tgs_chat_embed/media/New(url)
+ if(!istext(url))
+ CRASH("[/datum/tgs_chat_embed/media] created with no url!")
+
+ src.url = url
+
+/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure for details.
+/datum/tgs_chat_embed/footer
+ /// Must be set in New().
+ var/text
+ var/icon_url
+ var/proxy_icon_url
+
+/datum/tgs_chat_embed/footer/New(text)
+ if(!istext(text))
+ CRASH("[/datum/tgs_chat_embed/footer] created with no text!")
+
+ src.text = text
+
+/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure for details.
+/datum/tgs_chat_embed/provider
+ var/name
+ var/url
+
+/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure for details. Must have name set in New().
+/datum/tgs_chat_embed/provider/author
+ var/icon_url
+ var/proxy_icon_url
+
+/datum/tgs_chat_embed/provider/author/New(name)
+ if(!istext(name))
+ CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!")
+
+ src.name = name
+
+/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure for details. Must have name and value set in New().
+/datum/tgs_chat_embed/field
+ var/name
+ var/value
+ var/is_inline
+
+/datum/tgs_chat_embed/field/New(name, value)
+ if(!istext(name))
+ CRASH("[/datum/tgs_chat_embed/field] created with no name!")
+
+ if(!istext(value))
+ CRASH("[/datum/tgs_chat_embed/field] created with no value!")
+
+ src.name = name
+ src.value = value
+
+// API FUNCTIONS
+
+/// Returns the maximum supported [/datum/tgs_version] of the DMAPI.
+/world/proc/TgsMaximumApiVersion()
+ return
+
+/// Returns the minimum supported [/datum/tgs_version] of the DMAPI.
+/world/proc/TgsMinimumApiVersion()
+ return
+
+/**
+ * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
+ */
+/world/proc/TgsAvailable()
+ return
+
+// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
+
+/**
+ * Forces a hard reboot of DreamDaemon by ending the process. This function may sleep!
+ *
+ * Unlike del(world) clients will try to reconnect.
+ * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again.
+ */
+/world/proc/TgsEndProcess()
+ return
+
+/**
+ * Send a message to connected chats. This function may sleep!
+ * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
+ *
+ * message - The [/datum/tgs_message_content] to send.
+ * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
+ */
+/world/proc/TgsTargetedChatBroadcast(datum/tgs_message_content/message, admin_only = FALSE)
+ return
+
+/**
+ * Send a private message to a specific user. This function may sleep!
+ * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
+ *
+ * message - The [/datum/tgs_message_content] to send.
+ * user: The [/datum/tgs_chat_user] to PM.
+ */
+/world/proc/TgsChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user)
+ return
+
+/**
+ * Send a message to connected chats that are flagged as game-related in TGS. This function may sleep!
+ * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
+ *
+ * message - The [/datum/tgs_message_content] to send.
+ * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
+ */
+/world/proc/TgsChatBroadcast(datum/tgs_message_content/message, list/channels = null)
+ return
+
+/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsVersion()
+ return
+
+/// Returns the running engine type
+/world/proc/TgsEngine()
+ return
+
+/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsApiVersion()
+ return
+
+/// Returns the name of the TGS instance running the game if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsInstanceName()
+ return
+
+/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsRevision()
+ return
+
+/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsSecurityLevel()
+ return
+
+/// Returns the current BYOND visibility level as a TGS_VISIBILITY_ define if TGS is present, null otherwise. Requires TGS to be using interop API version 5 or higher otherwise the string "___unimplemented" wil be returned. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsVisibility()
+ return
+
+/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsTestMerges()
+ return
+
+/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
+/world/proc/TgsChatChannelInfo()
+ return
+
+/*
+The MIT License
+
+Copyright (c) 2017-2023 Jordan Brown
+
+Permission is hereby granted, free of charge,
+to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to
+deal in the Software without restriction, including
+without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom
+the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
diff --git a/code/__defines/~mods/~master_defines.dm b/code/__defines/~mods/~master_defines.dm
new file mode 100644
index 0000000000000..767ded56bc693
--- /dev/null
+++ b/code/__defines/~mods/~master_defines.dm
@@ -0,0 +1,40 @@
+/*
+ SierraBay12 - Master defines file
+
+ Руководство по добавлению дефайнов:
+ - Добавь комментарий с ID модуля и указанием, что это начало
+ - Запиши все нужные дефайны
+ - Вновь комментарий с ID модуля и указанием, что это конец
+*/
+
+// TAJARA - Start
+#define SPECIES_TAJARA "Tajara"
+#define LANGUAGE_SIIK_MAAS "Siik'maas"
+#define LANGUAGE_SIIK_TAJR "Siik'tajr"
+// TAJARA - End
+
+// LEGALESE - Start
+#define LANGUAGE_LEGALESE "Legalese"
+// LEGALESE - End
+
+// UTF8 - Start
+#undef show_browser
+#define show_browser(target, content, title) to_target(target, browse(utf_8_html(content), title))
+// UTF8 - End
+
+// DON_LOADOUT - Start
+#define DONATION_TIER_NONE 0
+#define DONATION_TIER_ONE 1
+#define DONATION_TIER_TWO 2
+#define DONATION_TIER_THREE 3
+#define DONATION_TIER_FOUR 4
+#define DONATION_TIER_ADMIN 5
+
+#define DONATION_TIER_ONE_SUM 100
+#define DONATION_TIER_TWO_SUM 300
+#define DONATION_TIER_THREE_SUM 500
+#define DONATION_TIER_FOUR_SUM 1000
+
+#define DELAY2GLIDESIZE(delay) (world.icon_size / max(ceil(delay / world.tick_lag), 1))
+
+#define BP_COOLING "cooling system"
diff --git a/code/__defines/~unit_testing.dm b/code/__defines/~unit_testing.dm
deleted file mode 100644
index 4b2caf048cc22..0000000000000
--- a/code/__defines/~unit_testing.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- *
- * This file is used to indicate that Unit Tests are to be ran.
- * Do not add anything but the UNIT_TEST definition here as it will be overwritten when running tests.
- *
- *
- * Should you wish to edit set UNIT_TEST to 1 like so:
- * #define UNIT_TEST 1
- */
-#define UNIT_TEST 0
diff --git a/code/_global_vars/lists/flavor.dm b/code/_global_vars/lists/flavor.dm
index 8a91dc33dfd46..9a1adfe0b3295 100644
--- a/code/_global_vars/lists/flavor.dm
+++ b/code/_global_vars/lists/flavor.dm
@@ -72,27 +72,3 @@ GLOBAL_LIST_INIT(numbers_as_words, list("One", "Two", "Three", "Four",
"Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve",
"Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen",
"Eighteen", "Nineteen"))
-
-GLOBAL_LIST_INIT(possible_cable_colours, SetupCableColors())
-
-/proc/SetupCableColors()
- . = list()
-
- var/invalid_cable_coils = list(
- /obj/item/stack/cable_coil/single,
- /obj/item/stack/cable_coil/cut,
- /obj/item/stack/cable_coil/cyborg,
- /obj/item/stack/cable_coil/fabricator,
- /obj/item/stack/cable_coil/random
- )
-
- var/special_name_mappings = list(/obj/item/stack/cable_coil = "Red")
-
- for(var/coil_type in (typesof(/obj/item/stack/cable_coil) - invalid_cable_coils))
- var/name = special_name_mappings[coil_type] || capitalize(copytext_after_last("[coil_type]", "/"))
-
- var/obj/item/stack/cable_coil/C = coil_type
- var/color = initial(C.color)
-
- .[name] = color
- . = sortAssoc(.)
diff --git a/code/_global_vars/lists/mapping.dm b/code/_global_vars/lists/mapping.dm
index 47bd677e07777..50e4be515e2d9 100644
--- a/code/_global_vars/lists/mapping.dm
+++ b/code/_global_vars/lists/mapping.dm
@@ -3,30 +3,51 @@ GLOBAL_LIST_INIT(cardinalz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN))
GLOBAL_LIST_INIT(cornerdirs, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
GLOBAL_LIST_INIT(cornerdirsz, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, NORTH|UP, EAST|UP, WEST|UP, SOUTH|UP, NORTH|DOWN, EAST|DOWN, WEST|DOWN, SOUTH|DOWN))
GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
-GLOBAL_LIST_INIT(reverse_dir, list( // reverse_dir[dir] = reverse of dir
+
+/// reverse_dir[dir] = reverse of dir
+GLOBAL_LIST_INIT(reverse_dir, list(
2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15,
32, 34, 33, 35, 40, 42, 41, 43, 36, 38, 37, 39, 44, 46, 45, 47,
16, 18, 17, 19, 24, 26, 25, 27, 20, 22, 21, 23, 28, 30, 29, 31,
48, 50, 49, 51, 56, 58, 57, 59, 52, 54, 53, 55, 60, 62, 61, 63
))
-GLOBAL_LIST_INIT(flip_dir, list( // flip_dir[dir] = 180 degree rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+/// flip_dir[dir] = 180 degree rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+GLOBAL_LIST_INIT(flip_dir, list(
2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15,
16, 18, 17, 19, 24, 26, 25, 27, 20, 22, 21, 23, 28, 30, 29, 31, // UP - Same as first line but +16
32, 34, 33, 35, 40, 42, 41, 43, 36, 38, 37, 39, 44, 46, 45, 47, // DOWN - Same as first line but +32
48, 50, 49, 51, 56, 58, 57, 59, 52, 54, 53, 55, 60, 62, 61, 63 // UP+DOWN - Same as first line but +48
))
-GLOBAL_LIST_INIT(cw_dir, list( // cw_dir[dir] = clockwise rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+/// cw_dir[dir] = clockwise 4-rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+GLOBAL_LIST_INIT(cw_dir, list(
4, 8, 12, 2, 6, 10, 14, 1, 5, 9, 13, 3, 7, 11, 15,
16, 20, 24, 28, 18, 22, 26, 30, 17, 21, 25, 19, 29, 23, 27, 31, // UP - Same as first line but +16
32, 36, 40, 44, 34, 38, 42, 46, 33, 37, 41, 45, 35, 39, 43, 47, // DOWN - Same as first line but +32
48, 52, 56, 40, 50, 54, 58, 62, 49, 53, 57, 61, 51, 55, 59, 63 // UP+DOWN - Same as first line but +48
))
-GLOBAL_LIST_INIT(ccw_dir, list( // ccw_dir[dir] = counter-clockwise rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+/// cw_dir_8[dir] = clockwise 8-rotation of dir. Ignores invalid dir combinations.
+GLOBAL_LIST_INIT(cw_dir_8, list(
+ 5, 10, 0, 6, 4, 2, 0, 9, 1, 8, 0, 0, 0, 0, 0, 0,
+ 21, 26, 0, 22, 20, 18, 0, 25, 17, 24, 0, 0, 0, 0, 0, 0,
+ 37, 42, 0, 38, 36, 34, 0, 41, 33, 40, 0, 0, 0, 0, 0, 0,
+ 53, 58, 0, 54, 52, 50, 0, 57, 49, 56, 0, 0, 0, 0, 0, 0
+))
+
+/// ccw_dir[dir] = counter-clockwise 4-rotation of dir. Unlike reverse_dir, UP remains UP & DOWN remains DOWN.
+GLOBAL_LIST_INIT(ccw_dir, list(
8, 4, 12, 1, 9, 5, 13, 2, 10, 6, 14, 3, 11, 7, 15,
16, 24, 20, 28, 17, 25, 21, 29, 18, 26, 22, 30, 19, 27, 23, 31, // UP - Same as first line but +16
32, 40, 36, 44, 33, 41, 37, 45, 34, 42, 38, 46, 35, 43, 39, 47, // DOWN - Same as first line but +32
48, 56, 52, 60, 49, 57, 53, 61, 50, 58, 54, 62, 51, 59, 55, 63 // UP+DOWN - Same as first line but +48
))
+
+/// ccw_dir_8[dir] = counter-clockwise 8-rotation of dir. Ignores invalid dir combinations.
+GLOBAL_LIST_INIT(ccw_dir_8, list(
+ 9, 6, 0, 5, 1, 4, 0, 10, 8, 2, 0, 0, 0, 0, 0, 0,
+ 25, 22, 0, 21, 17, 20, 0, 26, 24, 18, 0, 0, 0, 0, 0, 0,
+ 41, 38, 0, 37, 33, 36, 0, 42, 40, 34, 0, 0, 0, 0, 0, 0,
+ 57, 54, 0, 53, 49, 52, 0, 58, 56, 50, 0, 0, 0, 0, 0, 0
+))
diff --git a/code/_global_vars/lists/research.dm b/code/_global_vars/lists/research.dm
new file mode 100644
index 0000000000000..705482a2f36f7
--- /dev/null
+++ b/code/_global_vars/lists/research.dm
@@ -0,0 +1,2 @@
+/// List of research ids to corresponding reasearch names
+GLOBAL_LIST_EMPTY(tech_id_to_name)
diff --git a/code/_global_vars/lists/slots.dm b/code/_global_vars/lists/slots.dm
new file mode 100644
index 0000000000000..20223ced271ae
--- /dev/null
+++ b/code/_global_vars/lists/slots.dm
@@ -0,0 +1,20 @@
+GLOBAL_LIST_INIT(slot_descriptions, list(\
+ "[slot_back]" = "Back", \
+ "[slot_r_hand]" = "Right Hand", \
+ "[slot_l_hand]" = "Left Hand", \
+ "[slot_w_uniform]" = "Uniform", \
+ "[slot_head]" = "Head", \
+ "[slot_wear_suit]" = "Suit", \
+ "[slot_l_ear]" = "Left Ear", \
+ "[slot_r_ear]" = "Right Ear", \
+ "[slot_belt]" = "Belt", \
+ "[slot_shoes]" = "Shoes", \
+ "[slot_wear_mask]" = "Mask", \
+ "[slot_handcuffed]" = "Handcuffed", \
+ "[slot_legcuffed]" = "Legcuffed", \
+ "[slot_wear_id]" = "ID", \
+ "[slot_gloves]" = "Gloves", \
+ "[slot_glasses]" = "Glasses", \
+ "[slot_s_store]" = "Suit Store", \
+ "[slot_tie]" = "Accessory" \
+))
diff --git a/code/_global_vars/logging.dm b/code/_global_vars/logging.dm
index 6f6dc2e514545..3ff03e78c2456 100644
--- a/code/_global_vars/logging.dm
+++ b/code/_global_vars/logging.dm
@@ -4,5 +4,3 @@ var/global/diary
GLOBAL_VAR(log_directory)
GLOBAL_PROTECT(log_directory)
-GLOBAL_VAR(world_qdel_log)
-GLOBAL_PROTECT(world_qdel_log)
diff --git a/code/_helpers/areas.dm b/code/_helpers/areas.dm
index dcc8f351a9d30..c357a7411c162 100644
--- a/code/_helpers/areas.dm
+++ b/code/_helpers/areas.dm
@@ -1,42 +1,88 @@
/*
List generation helpers
*/
-/proc/get_filtered_areas(list/predicates = list(/proc/is_area_with_turf))
+/proc/get_filtered_areas(list/predicates = list(GLOBAL_PROC_REF(is_area_with_turf)))
+ RETURN_TYPE(/list)
. = list()
if(!predicates)
return
if(!islist(predicates))
predicates = list(predicates)
- for(var/area/A)
+ for(var/area/A as anything in GLOB.areas)
if(all_predicates_true(list(A), predicates))
. += A
/proc/get_area_turfs(area/A, list/predicates)
- . = new/list()
+ RETURN_TYPE(/list)
A = istype(A) ? A : locate(A)
if(!A)
- return
- for(var/turf/T in A.contents)
- if(!predicates || all_predicates_true(list(T), predicates))
- . += T
+ return list()
+
+ if(!A.has_turfs())
+ return list()
+
+ var/list/all_area_turfs = A.get_turfs_from_all_z()
+ if(!length(predicates))
+ return all_area_turfs
+
+ var/list/area_turfs = list()
+ for(var/turf/T as anything in all_area_turfs)
+ if(all_predicates_true(list(T), predicates))
+ area_turfs += T
+
+ return area_turfs
+
+/proc/get_turfs_in_areas(list/areas, list/predicates)
+ if(!islist(areas))
+ areas = list(areas)
+
+ var/list/turfs = list()
+ for(var/area/current_area as anything in areas)
+ var/list/current_area_turfs = get_area_turfs(current_area, predicates)
+ if(!current_area_turfs)
+ continue
+
+ turfs |= current_area_turfs
+
+ return turfs
+
+/// Returns set of area refs as: area_ref => TRUE
+/// For fast area checking
+/proc/get_area_refs_set(list/areas)
+ var/list/area_refs_set = list()
+ for(var/area/single_area as anything in areas)
+ area_refs_set[ref(single_area)] = TRUE
+
+ return area_refs_set
/proc/get_subarea_turfs(area/A, list/predicates)
- . = new/list()
+ RETURN_TYPE(/list)
+ . = list()
A = istype(A) ? A.type : A
if(!ispath(A))
return
+
for(var/sub_area_type in typesof(A))
var/area/sub_area = locate(sub_area_type)
- for(var/turf/T in sub_area.contents)
- if(!predicates || all_predicates_true(list(T), predicates))
+ if(!sub_area.has_turfs())
+ continue
+
+ var/list/all_area_turfs = sub_area.get_turfs_from_all_z()
+ if(!length(predicates))
+ . += all_area_turfs
+
+ for(var/turf/T as anything in all_area_turfs)
+ if(all_predicates_true(list(T), predicates))
. += T
/proc/group_areas_by_name(list/predicates)
+ RETURN_TYPE(/list)
. = list()
for(var/area/A in get_filtered_areas(predicates))
group_by(., A.name, A)
/proc/group_areas_by_z_level(list/predicates)
+ RETURN_TYPE(/list)
. = list()
for(var/area/A in get_filtered_areas(predicates))
group_by(., pad_left(num2text(A.z), 3, "0"), A)
@@ -45,21 +91,25 @@
Pick helpers
*/
/proc/pick_subarea_turf(areatype, list/predicates)
+ RETURN_TYPE(/turf)
var/list/turfs = get_subarea_turfs(areatype, predicates)
if(LAZYLEN(turfs))
return pick(turfs)
/proc/pick_area_turf(areatype, list/predicates)
+ RETURN_TYPE(/turf)
var/list/turfs = get_area_turfs(areatype, predicates)
- if(turfs && length(turfs))
+ if(length(turfs))
return pick(turfs)
/proc/pick_area(list/predicates)
+ RETURN_TYPE(/area)
var/list/areas = get_filtered_areas(predicates)
if(LAZYLEN(areas))
. = pick(areas)
/proc/pick_area_and_turf(list/area_predicates, list/turf_predicates)
+ RETURN_TYPE(/turf)
var/list/areas = get_filtered_areas(area_predicates)
// We loop over all area candidates, until we finally get a valid turf or run out of areas
while(!. && length(areas))
@@ -67,15 +117,17 @@
. = pick_area_turf(A, turf_predicates)
/proc/pick_area_turf_in_connected_z_levels(list/area_predicates, list/turf_predicates, z_level)
+ RETURN_TYPE(/turf)
area_predicates = area_predicates.Copy()
var/z_levels = GetConnectedZlevels(z_level)
- area_predicates[/proc/area_belongs_to_zlevels] = z_levels
+ area_predicates[GLOBAL_PROC_REF(area_belongs_to_zlevels)] = z_levels
return pick_area_and_turf(area_predicates, turf_predicates)
/proc/pick_area_turf_in_single_z_level(list/area_predicates, list/turf_predicates, z_level)
+ RETURN_TYPE(/turf)
area_predicates = area_predicates.Copy()
- area_predicates[/proc/area_belongs_to_zlevels] = list(z_level)
+ area_predicates[GLOBAL_PROC_REF(area_belongs_to_zlevels)] = list(z_level)
return pick_area_and_turf(area_predicates, turf_predicates)
/*
@@ -114,17 +166,17 @@
/proc/is_coherent_area(area/A)
return !is_type_in_list(A, GLOB.using_map.area_coherency_test_exempt_areas)
-GLOBAL_LIST_INIT(is_station_but_not_space_or_shuttle_area, list(/proc/is_station_area, /proc/is_not_space_area, /proc/is_not_shuttle_area))
+GLOBAL_LIST_INIT(is_station_but_not_space_or_shuttle_area, list(GLOBAL_PROC_REF(is_station_area), GLOBAL_PROC_REF(is_not_space_area), GLOBAL_PROC_REF(is_not_shuttle_area)))
-GLOBAL_LIST_INIT(is_contact_but_not_space_or_shuttle_area, list(/proc/is_contact_area, /proc/is_not_space_area, /proc/is_not_shuttle_area))
+GLOBAL_LIST_INIT(is_contact_but_not_space_or_shuttle_area, list(GLOBAL_PROC_REF(is_contact_area), GLOBAL_PROC_REF(is_not_space_area), GLOBAL_PROC_REF(is_not_shuttle_area)))
-GLOBAL_LIST_INIT(is_player_but_not_space_or_shuttle_area, list(/proc/is_player_area, /proc/is_not_space_area, /proc/is_not_shuttle_area))
+GLOBAL_LIST_INIT(is_player_but_not_space_or_shuttle_area, list(GLOBAL_PROC_REF(is_player_area), GLOBAL_PROC_REF(is_not_space_area), GLOBAL_PROC_REF(is_not_shuttle_area)))
-GLOBAL_LIST_INIT(is_station_area, list(/proc/is_station_area))
+GLOBAL_LIST_INIT(is_station_area, list(GLOBAL_PROC_REF(is_station_area)))
-GLOBAL_LIST_INIT(is_station_and_maint_area, list(/proc/is_station_area, /proc/is_maint_area))
+GLOBAL_LIST_INIT(is_station_and_maint_area, list(GLOBAL_PROC_REF(is_station_area), GLOBAL_PROC_REF(is_maint_area)))
-GLOBAL_LIST_INIT(is_station_but_not_maint_area, list(/proc/is_station_area, /proc/is_not_maint_area))
+GLOBAL_LIST_INIT(is_station_but_not_maint_area, list(GLOBAL_PROC_REF(is_station_area), GLOBAL_PROC_REF(is_not_maint_area)))
/*
Misc Helpers
diff --git a/code/_helpers/assets.dm b/code/_helpers/assets.dm
new file mode 100644
index 0000000000000..a288f7727288b
--- /dev/null
+++ b/code/_helpers/assets.dm
@@ -0,0 +1,5 @@
+/// Generate a filename for this asset
+/// The same asset will always lead to the same asset name
+/// (Generated names do not include file extention.)
+/proc/generate_asset_name(file)
+ return "asset.[md5(fcopy_rsc(file))]"
diff --git a/code/_helpers/atom_movables.dm b/code/_helpers/atom_movables.dm
index 86aafafae9891..7b347f5a29962 100644
--- a/code/_helpers/atom_movables.dm
+++ b/code/_helpers/atom_movables.dm
@@ -1,4 +1,5 @@
/proc/get_turf_pixel(atom/movable/AM)
+ RETURN_TYPE(/turf)
if(!istype(AM))
return
@@ -29,19 +30,25 @@
// Walks up the loc tree until it finds a holder of the given holder_type
/proc/get_holder_of_type(atom/A, holder_type)
+ RETURN_TYPE(/atom)
if(!istype(A)) return
for(A, A && !istype(A, holder_type), A=A.loc);
return A
-/atom/movable/proc/throw_at_random(include_own_turf, maxrange, speed)
- var/list/turfs = trange(maxrange, src)
- if(!maxrange)
+/atom/movable/proc/throw_at_random(include_own_turf = FALSE, maxrange = 1, speed)
+ if(maxrange < 0)
+ stack_trace("Negative `maxrange` passed")
maxrange = 1
- if(!include_own_turf)
- turfs -= get_turf(src)
- if (length(turfs))
- throw_at(pick(turfs), maxrange, speed)
+ var/turf/center = get_turf(src)
+ if(!center)
+ return
+
+ var/list/turfs = include_own_turf ? RANGE_TURFS(center, maxrange) : ORANGE_TURFS(center, maxrange)
+ if(length(turfs))
+ return
+
+ throw_at(pick(turfs), maxrange, speed)
/atom/movable/proc/do_simple_ranged_interaction(mob/user)
return FALSE
@@ -52,6 +59,7 @@
do_simple_ranged_interaction()
/proc/get_atom_closest_to_atom(atom/a, list/possibilities)
+ RETURN_TYPE(/atom)
if(!possibilities || !length(possibilities))
return null
var/closest_distance = get_dist(a, possibilities[1])
diff --git a/code/_helpers/bicon_procs.dm b/code/_helpers/bicon_procs.dm
new file mode 100644
index 0000000000000..121b247265d81
--- /dev/null
+++ b/code/_helpers/bicon_procs.dm
@@ -0,0 +1,181 @@
+GLOBAL_DATUM_INIT(is_http_protocol, /regex, regex("^https?://"))
+
+/// Gets a dummy savefile for usage in icon generation.
+/// Savefiles generated from this proc will be empty.
+/proc/get_dummy_savefile(from_failure = FALSE)
+ var/static/next_id = 0
+ if(next_id++ > 9)
+ next_id = 0
+ var/savefile_path = "data/temp/dummy-save-[next_id].sav"
+ try
+ if(fexists(savefile_path))
+ fdel(savefile_path)
+ return new /savefile(savefile_path)
+ catch(var/exception/error)
+ // if we failed to create a dummy once, try again; maybe someone slept somewhere they shouldnt have
+ if(from_failure) // this *is* the retry, something fucked up
+ CRASH("get_dummy_savefile failed to create a dummy savefile: '[error]'")
+ return get_dummy_savefile(from_failure = TRUE)
+
+/**
+ * Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
+ * exporting it as text, and then parsing the base64 from that.
+ * (This relies on byond automatically storing icons in savefiles as base64)
+ */
+/proc/icon2base64(icon/icon)
+ if (!isicon(icon))
+ return FALSE
+ var/savefile/dummySave = get_dummy_savefile()
+ to_save(dummySave["dummy"], icon)
+ var/iconData = dummySave.ExportText("dummy")
+ var/list/partial = splittext(iconData, "{")
+ return replacetext(copytext_char(partial[2], 3, -5), "\n", "") //if cleanup fails we want to still return the correct base64
+
+/proc/register_icon_asset(icon/thing, icon_state, dir, frame = 1, moving = FALSE, realsize = FALSE, class = null)
+ if (!thing)
+ return null
+
+ if (!isicon(thing))
+ if (isfile(thing))
+ var/name = "[generate_asset_name(thing)].png"
+ SSassets.transport.register_asset(name, thing)
+ return name
+
+ if (ispath(thing))
+ var/atom/A = thing
+ if (isnull(dir))
+ dir = SOUTH
+ if (isnull(icon_state))
+ icon_state = initial(A.icon_state)
+ thing = initial(A.icon)
+
+ else
+ var/atom/A = thing
+ if (isnull(dir))
+ dir = A.dir
+ if (isnull(icon_state))
+ icon_state = A.icon_state
+ thing = A.icon
+ if (ishuman(thing)) // Shitty workaround for a BYOND issue.
+ var/icon/temp = thing
+ thing = icon()
+ thing.Insert(temp, dir = SOUTH)
+ dir = SOUTH
+ else
+ if (isnull(dir))
+ dir = SOUTH
+ if (isnull(icon_state))
+ icon_state = ""
+
+ thing = icon(thing, icon_state, dir, frame, moving)
+
+ var/key = "[generate_asset_name(thing)].png"
+ SSassets.transport.register_asset(key, thing)
+ return key
+
+/proc/icon2html(icon/thing, target, icon_state, dir, frame = 1, moving = FALSE, realsize = FALSE, class = null)
+ if (!thing || !target)
+ return
+
+ var/list/targets
+ if(target == world)
+ targets = GLOB.clients
+
+ else if (islist(target))
+ targets = target
+
+ else
+ targets = list(target)
+
+ if(!length(targets))
+ return
+
+ if (!isicon(thing))
+ if (isfile(thing))
+ var/name = "[generate_asset_name(thing)].png"
+ SSassets.transport.register_asset(name, thing)
+ for (var/thing2 in targets)
+ SSassets.transport.send_assets(thing2, name)
+ return ""
+
+ if (ispath(thing))
+ var/atom/A = thing
+ if (isnull(dir))
+ dir = SOUTH
+ if (isnull(icon_state))
+ icon_state = initial(A.icon_state)
+ thing = initial(A.icon)
+
+ else
+ var/atom/A = thing
+ if (isnull(dir))
+ dir = A.dir
+ if (isnull(icon_state))
+ icon_state = A.icon_state
+ thing = A.icon
+ if (ishuman(thing)) // Shitty workaround for a BYOND issue.
+ var/icon/temp = thing
+ thing = icon()
+ thing.Insert(temp, dir = SOUTH)
+ dir = SOUTH
+ else
+ if (isnull(dir))
+ dir = SOUTH
+ if (isnull(icon_state))
+ icon_state = ""
+
+ thing = icon(thing, icon_state, dir, frame, moving)
+
+ var/key = "[generate_asset_name(thing)].png"
+ SSassets.transport.register_asset(key, thing)
+ for (var/thing2 in targets)
+ SSassets.transport.send_assets(thing2, key)
+
+ if(realsize)
+ return ""
+
+ return ""
+
+/proc/icon2base64html(thing)
+ if (!thing)
+ return
+
+ var/static/list/bicon_cache = list()
+ if (isicon(thing))
+ var/icon/I = thing
+ var/icon_base64 = icon2base64(I)
+
+ if (I.Height() > world.icon_size || I.Width() > world.icon_size)
+ var/icon_md5 = md5(icon_base64)
+ icon_base64 = bicon_cache[icon_md5]
+ if (!icon_base64) // Doesn't exist yet, make it.
+ bicon_cache[icon_md5] = icon_base64 = icon2base64(I)
+
+ return ""
+
+ // Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with.
+ var/atom/A = thing
+ var/key = "[istype(A.icon, /icon) ? "\ref[A.icon]" : A.icon]:[A.icon_state]"
+
+
+ if (!bicon_cache[key]) // Doesn't exist, make it.
+ var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1)
+ if (ishuman(thing)) // Shitty workaround for a BYOND issue.
+ var/icon/temp = I
+ I = icon()
+ I.Insert(temp, dir = SOUTH)
+
+ bicon_cache[key] = icon2base64(I, key)
+
+ return ""
+
+// Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs.
+/proc/costly_icon2html(thing, target)
+ if (!thing)
+ return
+
+ if (isicon(thing))
+ return icon2html(thing, target)
+
+ var/icon/I = getFlatIcon(thing)
+ return icon2html(I, target)
diff --git a/code/_helpers/bubble_effect.dm b/code/_helpers/bubble_effect.dm
new file mode 100644
index 0000000000000..d4f87ef746a53
--- /dev/null
+++ b/code/_helpers/bubble_effect.dm
@@ -0,0 +1,65 @@
+/datum/bubble_effect
+ var/center_x
+ var/center_y
+ var/z
+ var/radius
+ var/soft_radius
+ var/delta
+ var/list/seen
+ var/list/cache
+
+
+/datum/bubble_effect/Destroy()
+ LAZYCLEARLIST(seen)
+ LAZYCLEARLIST(cache)
+ return ..()
+
+
+/datum/bubble_effect/New(center_x, center_y, z, initial_radius, delta, datum/bubble_effect/parent)
+ src.center_x = center_x
+ src.center_y = center_y
+ src.z = z
+ soft_radius = initial_radius
+ src.delta = delta
+ if (istype(parent))
+ if (!parent.cache)
+ parent.cache = list()
+ cache = parent.cache
+ seen = parent.seen
+ else
+ seen = list()
+
+
+/datum/bubble_effect/proc/Tick()
+ soft_radius += delta
+ var/new_radius = round(soft_radius, 1)
+ if (new_radius == radius)
+ return radius
+ radius = new_radius
+ var/list/coords
+ if (cache && length(cache))
+ coords = cache["[radius]"]
+ if (!coords)
+ coords = get_circle_coordinates(radius, center_x, center_y) - seen
+ if (cache)
+ cache["[radius]"] = coords
+ seen += coords
+ var/maxx = world.maxx
+ var/maxy = world.maxy
+ for (var/entry in coords)
+ var/x = entry & 0xFFF
+ var/y = SHIFTR(entry, 12)
+ if (x < 1 || x > maxx)
+ continue
+ if (y < 1 || y > maxy)
+ continue
+ var/turf/turf = locate(x, y, z)
+ if (!turf)
+ continue
+ if (TurfEffect(turf))
+ return radius
+ return radius
+
+
+/datum/bubble_effect/proc/TurfEffect(turf/turf)
+ return
diff --git a/code/_helpers/client.dm b/code/_helpers/client.dm
index 8b855112f7f4e..bdb13dd2559a4 100644
--- a/code/_helpers/client.dm
+++ b/code/_helpers/client.dm
@@ -17,6 +17,7 @@
/// Get the client associated with ckey text if it is currently connected
/proc/ckey2client(text)
+ RETURN_TYPE(/client)
if (valid_ckey(text))
for (var/client/C as anything in GLOB.clients)
if (C.ckey == text)
@@ -25,6 +26,7 @@
/// Get the client associated with key text if it is currently connected
/proc/key2client(text)
+ RETURN_TYPE(/client)
if (valid_key(text))
for (var/client/C as anything in GLOB.clients)
if (C.key == text)
@@ -33,6 +35,7 @@
/// Null, or a client if thing is a client, a mob with a client, a connected ckey, or null
/proc/resolve_client(client/thing)
+ RETURN_TYPE(/client)
if (istype(thing))
return thing
if (!thing)
@@ -45,6 +48,7 @@
/// Null or a client from the list of connected clients, chosen by actor if actor is valid
/proc/select_client(client/actor, message = "Connected clients:", title = "Select Client")
+ RETURN_TYPE(/client)
actor = resolve_client(actor)
if (!actor)
return
diff --git a/code/_helpers/cmp.dm b/code/_helpers/cmp.dm
index dff6cc114af34..92ee7224d820e 100644
--- a/code/_helpers/cmp.dm
+++ b/code/_helpers/cmp.dm
@@ -49,21 +49,16 @@
/proc/cmp_text_dsc(a,b)
return sorttext(a, b)
-/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B)
- . = B.hard_delete_time - A.hard_delete_time
- if (!.)
- . = B.destroy_time - A.destroy_time
- if (!.)
- . = B.failures - A.failures
- if (!.)
- . = B.qdels - A.qdels
+/proc/cmp_embed_text_asc(a,b)
+ if(isdatum(a))
+ a = REF(a)
+ if(isdatum(b))
+ b = REF(b)
+ return sorttext("[b]", "[a]")
/proc/cmp_ruincost_priority(datum/map_template/ruin/A, datum/map_template/ruin/B)
return initial(A.spawn_cost) - initial(B.spawn_cost)
-/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b)
- return a.timeToRun - b.timeToRun
-
/proc/cmp_clientcolor_priority(datum/client_color/A, datum/client_color/B)
return B.priority - A.priority
diff --git a/code/_helpers/cyrillic_keys.dm b/code/_helpers/cyrillic_keys.dm
new file mode 100644
index 0000000000000..2b1b9809e89c3
--- /dev/null
+++ b/code/_helpers/cyrillic_keys.dm
@@ -0,0 +1,14 @@
+/proc/convert_ru_symbols_to_en(text)
+ for(var/i in 1 to length(text))
+ var/letter = lowertext(copytext_char(text, i, i + 1))
+ var/ru_letter = GLOB.ru_symbol_to_en[letter]
+ if(ru_letter)
+ . += ru_letter
+ continue
+ . += letter
+
+GLOBAL_LIST_INIT(ru_symbol_to_en, list(
+ "й" = "i", "ц" = "c", "у" = "u", "к" = "k", "е" = "e", "н" = "n", "г" = "g", "ш" = "sh", "щ" = "sh", "з" = "z", "х" = "h", "ъ" = "",
+ "ф" = "f", "ы" = "y", "в" = "v", "а" = "a", "п" = "p", "р" = "r", "о" = "o", "л" = "l", "д" = "d", "ж" = "zh", "э" = "e",
+ "я" = "ja", "ч" = "ch", "с" = "s", "м" = "m", "и" = "i", "т" = "t", "ь" = "", "б" = "b", "ю" = "iu", "ё" = "e"
+))
diff --git a/code/_helpers/emissives.dm b/code/_helpers/emissives.dm
new file mode 100644
index 0000000000000..66664d1881063
--- /dev/null
+++ b/code/_helpers/emissives.dm
@@ -0,0 +1,42 @@
+/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR].
+/proc/emissive_appearance(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = EMPTY_BITFIELD)
+ var/mutable_appearance/appearance = mutable_appearance(icon = icon, icon_state = icon_state, layer = layer, plane = EMISSIVE_PLANE, flags = appearance_flags|EMISSIVE_APPEARANCE_FLAGS)
+ appearance.alpha = alpha
+ if(alpha == 255)
+ appearance.color = GLOB.emissive_color
+ else
+ var/alpha_ratio = alpha/255
+ appearance.color = _EMISSIVE_COLOR(alpha_ratio)
+ return appearance
+
+/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EM_BLOCK_COLOR].
+/proc/emissive_blocker(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = EMPTY_BITFIELD, source = null)
+ var/mutable_appearance/appearance = mutable_appearance(icon = icon, icon_state = icon_state, layer = layer, plane = EMISSIVE_PLANE, flags = appearance_flags|EMISSIVE_APPEARANCE_FLAGS)
+ appearance.alpha = alpha
+ if(alpha == 255)
+ appearance.color = GLOB.em_block_color
+ else
+ var/alpha_ratio = alpha/255
+ appearance.color = _EM_BLOCK_COLOR(alpha_ratio)
+
+ if(source)
+ appearance.render_source = source
+ // Since only render_target handles transform we don't get any applied transform "stacking"
+ appearance.appearance_flags |= RESET_TRANSFORM
+
+ return appearance
+
+// Designed to be a faster version of the above, for most use-cases
+/proc/fast_emissive_blocker(atom/make_blocker)
+ var/mutable_appearance/blocker = new()
+ blocker.icon = make_blocker.icon
+ blocker.icon_state = make_blocker.icon_state
+ blocker.appearance_flags |= make_blocker.appearance_flags | EMISSIVE_APPEARANCE_FLAGS
+ blocker.dir = make_blocker.dir
+ if(make_blocker.alpha == 255)
+ blocker.color = GLOB.em_block_color
+ else
+ var/alpha_ratio = make_blocker.alpha/255
+ blocker.color = _EM_BLOCK_COLOR(alpha_ratio)
+ blocker.plane = EMISSIVE_PLANE
+ return blocker
diff --git a/code/_helpers/functional.dm b/code/_helpers/functional.dm
index f51ed4187f751..8f81680e382fa 100644
--- a/code/_helpers/functional.dm
+++ b/code/_helpers/functional.dm
@@ -1,14 +1,15 @@
#define PREPARE_INPUT \
- predicates = istype(predicates) ? predicates : list(predicates);\
- input = istype(input) ? input : list(input);
+ predicates = islist(predicates) ? predicates : list(predicates);\
+ input = islist(input) ? input : list(input);
#define PREPARE_ARGUMENTS \
var/extra_arguments = predicates[predicate];\
- var/list/predicate_input = input;\
+ var/list/predicate_input = input.Copy();\
if(LAZYLEN(extra_arguments)) {\
- predicate_input = predicate_input.Copy();\
predicate_input += list(extra_arguments);\
- }
+ } else if(extra_arguments) {\
+ predicate_input += extra_arguments;\
+ }\
/proc/all_predicates_true(list/input, list/predicates)
PREPARE_INPUT
@@ -42,6 +43,11 @@
if(!. && feedback_receiver)
to_chat(feedback_receiver, SPAN_WARNING("Value must be a numeral."))
+/proc/is_int_predicate(value, feedback_receiver)
+ . = value == round(value)
+ if (!. && feedback_receiver)
+ to_chat(feedback_receiver, SPAN_WARNING("Value must be a whole number."))
+
/proc/is_non_zero_predicate(value, feedback_receiver)
. = value != 0
if (!. && feedback_receiver)
@@ -80,6 +86,7 @@
/proc/where(list/list_to_filter, list/predicates, list/extra_predicate_input)
+ RETURN_TYPE(/list)
. = list()
for(var/entry in list_to_filter)
var/predicate_input
@@ -92,6 +99,7 @@
. += entry
/proc/map(list/list_to_map, map_proc)
+ RETURN_TYPE(/list)
. = list()
for(var/entry in list_to_map)
. += call(map_proc)(entry)
diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index 921d90f53a928..aa77bc1ceaa2b 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -1,5 +1,9 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
+#define IS_SUBTYPE(child_type, parent_type) (child_type != parent_type && istype(child_type, parent_type))
+
+#define IS_SUBPATH(child_path, parent_path) (child_path != parent_path && ispath(child_path, parent_path))
+
/proc/is_on_same_plane_or_station(z1, z2)
if(z1 == z2)
return 1
@@ -26,19 +30,18 @@
return TRUE
return FALSE
-/proc/get_area(O)
- var/turf/loc = get_turf(O)
- if(loc)
- var/area/res = loc.loc
- .= res
-
/proc/get_area_name(N) //get area by its name
- for(var/area/A in world)
+ RETURN_TYPE(/area)
+ for(var/area/A as anything in GLOB.areas)
+ /// TODO: Cache areas by name, but check out if area names are updated anywhere
+ /// and add `set_name` for this case, to properly update entry in cache
if(A.name == N)
return A
- return 0
-/proc/get_area_master(const/O)
+ stack_trace("Area name [N] not found")
+
+/proc/get_area_master(O)
+ RETURN_TYPE(/area)
var/area/A = get_area(O)
if (isarea(A))
return A
@@ -52,6 +55,7 @@
// Like view but bypasses luminosity check
/proc/hear(range, atom/source)
+ RETURN_TYPE(/list)
var/lum = source.luminosity
source.luminosity = 6
@@ -83,9 +87,10 @@
return level in GLOB.using_map.escape_levels
/proc/circlerange(center=usr,radius=3)
+ RETURN_TYPE(/list)
var/turf/centerturf = get_turf(center)
- var/list/turfs = new/list()
+ var/list/turfs = list()
var/rsq = radius * (radius+0.5)
for(var/atom/T in range(radius, centerturf))
@@ -98,9 +103,10 @@
return turfs
/proc/circleview(center=usr,radius=3)
+ RETURN_TYPE(/list)
var/turf/centerturf = get_turf(center)
- var/list/atoms = new/list()
+ var/list/atoms = list()
var/rsq = radius * (radius+0.5)
for(var/atom/A in view(radius, centerturf))
@@ -112,14 +118,6 @@
//turfs += centerturf
return atoms
-/proc/trange(rad = 0, turf/centre = null) //alternative to range (ONLY processes turfs and thus less intensive)
- if(!centre)
- return
-
- var/turf/x1y1 = locate(((centre.x-rad)<1 ? 1 : centre.x-rad),((centre.y-rad)<1 ? 1 : centre.y-rad),centre.z)
- var/turf/x2y2 = locate(((centre.x+rad)>world.maxx ? world.maxx : centre.x+rad),((centre.y+rad)>world.maxy ? world.maxy : centre.y+rad),centre.z)
- return block(x1y1,x2y2)
-
/proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj)
var/dx = Loc1.x - Loc2.x
var/dy = Loc1.y - Loc2.y
@@ -128,7 +126,14 @@
return dist
+/proc/get_bearing(atom/source, atom/destination)
+ var/bearing = round(90 - Atan2(destination.x - source.x, destination.y - source.y),5)
+ if(bearing < 0)
+ bearing += 360
+ return bearing
+
/proc/circlerangeturfs(center=usr,radius=3)
+ RETURN_TYPE(/list)
var/turf/centerturf = get_turf(center)
. = list()
if(!centerturf)
@@ -136,16 +141,17 @@
var/rsq = radius * (radius+0.5)
- for(var/turf/T in range(radius, centerturf))
+ for(var/turf/T as anything in RANGE_TURFS(centerturf, radius))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
. += T
/proc/circleviewturfs(center=usr,radius=3) //Is there even a diffrence between this proc and circlerangeturfs()?
+ RETURN_TYPE(/list)
var/turf/centerturf = get_turf(center)
- var/list/turfs = new/list()
+ var/list/turfs = list()
var/rsq = radius * (radius+0.5)
for(var/turf/T in view(radius, centerturf))
@@ -164,6 +170,7 @@
// being unable to hear people due to being in a box within a bag.
/proc/recursive_content_check(atom/O, list/L = list(), recursion_limit = 3, client_check = 1, sight_check = 1, include_mobs = 1, include_objects = 1)
+ RETURN_TYPE(/list)
if(!recursion_limit)
return L
@@ -192,6 +199,7 @@
// Returns a list of mobs and/or objects in range of R from source. Used in radio and say code.
/proc/get_mobs_or_objects_in_view(R, atom/source, include_mobs = 1, include_objects = 1)
+ RETURN_TYPE(/list)
var/turf/T = get_turf(source)
var/list/hear = list()
@@ -217,6 +225,7 @@
/proc/get_mobs_in_radio_ranges(list/obj/item/device/radio/radios)
+ RETURN_TYPE(/list)
set background = 1
@@ -252,7 +261,8 @@
/proc/get_mobs_and_objs_in_view_fast(turf/T, range, list/mobs, list/objs, checkghosts = null)
- var/list/hear = dview(range,T,INVISIBILITY_MAXIMUM)
+ var/list/hear = list()
+ DVIEW(hear, range, T, INVISIBILITY_MAXIMUM)
var/list/hearturfs = list()
for(var/atom/movable/AM in hear)
@@ -321,6 +331,7 @@
return 0
/proc/get_cardinal_step_away(atom/start, atom/finish) //returns the position of a step from start away from finish, in one of the cardinal directions
+ RETURN_TYPE(/turf)
//returns only NORTH, SOUTH, EAST, or WEST
var/dx = finish.x - start.x
var/dy = finish.y - start.y
@@ -336,6 +347,7 @@
return get_step(start, EAST)
/proc/get_mob_by_key(key)
+ RETURN_TYPE(/mob)
for(var/mob/M in SSmobs.mob_list)
if(M.ckey == lowertext(key))
return M
@@ -344,6 +356,7 @@
// Will return a list of active candidates. It increases the buffer 5 times until it finds a candidate which is active within the buffer.
/proc/get_active_candidates(buffer = 1)
+ RETURN_TYPE(/list)
var/list/candidates = list() //List of candidate KEYS to assume control of the new larva ~Carn
var/i = 0
@@ -356,6 +369,7 @@
return candidates
/proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480)
+ RETURN_TYPE(/obj/screen)
if(!isobj(O)) O = new /obj/screen/text()
O.maptext = maptext
O.maptext_height = maptext_height
@@ -380,6 +394,24 @@
for(var/client/C in show_to)
C.images -= I
+/// Adds an image to a client's `.images`. Useful as a callback.
+/proc/add_image_to_client(image/image_to_remove, client/add_to)
+ add_to?.images += image_to_remove
+
+/// Like add_image_to_client, but will add the image from a list of clients
+/proc/add_image_to_clients(image/image_to_remove, list/show_to)
+ for(var/client/add_to as anything in show_to)
+ add_to.images += image_to_remove
+
+/// Removes an image from a client's `.images`. Useful as a callback.
+/proc/remove_image_from_client(image/image_to_remove, client/remove_from)
+ remove_from?.images -= image_to_remove
+
+/// Like remove_image_from_client, but will remove the image from a list of clients
+/proc/remove_image_from_clients(image/image_to_remove, list/hide_from)
+ for(var/client/remove_from as anything in hide_from)
+ remove_from.images -= image_to_remove
+
/datum/projectile_data
var/src_x
var/src_y
@@ -391,7 +423,7 @@
var/dest_y
/datum/projectile_data/New(src_x, src_y, time, distance, \
- var/power_x, var/power_y, var/dest_x, var/dest_y)
+ power_x, power_y, dest_x, dest_y)
src.src_x = src_x
src.src_y = src_y
src.time = time
@@ -402,6 +434,7 @@
src.dest_y = dest_y
/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
+ RETURN_TYPE(/datum/projectile_data)
// returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
// rotated at [rotation] and with the power of [power]
@@ -418,23 +451,24 @@
return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
-/proc/GetRedPart(const/hexa)
+/proc/GetRedPart(hexa)
return hex2num(copytext(hexa,2,4))
-/proc/GetGreenPart(const/hexa)
+/proc/GetGreenPart(hexa)
return hex2num(copytext(hexa,4,6))
-/proc/GetBluePart(const/hexa)
+/proc/GetBluePart(hexa)
return hex2num(copytext(hexa,6,8))
-/proc/GetHexColors(const/hexa)
+/proc/GetHexColors(hexa)
+ RETURN_TYPE(/list)
return list(
GetRedPart(hexa),
GetGreenPart(hexa),
GetBluePart(hexa)
)
-/proc/MixColors(const/list/colors)
+/proc/MixColors(list/colors)
var/list/reds = list()
var/list/blues = list()
var/list/greens = list()
@@ -506,7 +540,8 @@
return ((temp + T0C))
/proc/getCardinalAirInfo(turf/loc, list/stats=list("temperature"))
- var/list/temps = new/list(4)
+ RETURN_TYPE(/list)
+ var/list/temps = new(4)
for(var/dir in GLOB.cardinal)
var/direction
switch(dir)
@@ -519,7 +554,7 @@
if(WEST)
direction = 4
var/turf/simulated/T=get_turf(get_step(loc,dir))
- var/list/rstats = new /list(length(stats))
+ var/list/rstats = new(length(stats))
if(T && istype(T) && T.zone)
var/datum/gas_mixture/environment = T.return_air()
for(var/i=1;i<=length(stats);i++)
@@ -550,6 +585,7 @@
return (length(GLOB.cult.current_antagonists) > spookiness_threshold)
/proc/getviewsize(view)
+ RETURN_TYPE(/list)
var/viewX
var/viewY
if(isnum(view))
@@ -558,6 +594,27 @@
viewY = totalviewrange
else
var/list/viewrangelist = splittext(view,"x")
+ if(length(viewrangelist) != 2)
+ stack_trace("For some reason, client view is not represented as standard values or is null: [view]")
+ viewX = 19
+ viewY = 15
+
viewX = text2num(viewrangelist[1])
viewY = text2num(viewrangelist[2])
return list(viewX, viewY)
+
+/proc/get_view_size_x(view)
+ var/list/view_size = getviewsize(view)
+ return view_size[1]
+
+/proc/get_view_size_y(view)
+ var/list/view_size = getviewsize(view)
+ return view_size[2]
+
+/proc/get_lesser_view_size_component(view)
+ var/list/view_size = getviewsize(view)
+ return min(view_size[1], view_size[2])
+
+/proc/get_greater_view_size_component(view)
+ var/list/view_size = getviewsize(view)
+ return max(view_size[1], view_size[2])
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 625c979902a13..51d7c48274596 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -19,9 +19,10 @@ var/global/list/landmarks_list = list() //list of all landmarks created
var/global/list/all_species[0]
var/global/list/datum/language/all_languages = list()
var/global/list/language_keys[0] // Table of say codes for all languages
-var/global/list/playable_species = list(SPECIES_HUMAN) // A list of ALL playable species, whitelisted, latejoin or otherwise.
+var/global/list/playable_species = list() // A list of ALL playable species, whitelisted, latejoin or otherwise.
+GLOBAL_LIST_EMPTY(all_particles)
// Grabs
var/global/list/all_grabstates[0]
@@ -81,11 +82,51 @@ var/global/list/string_slot_flags = list(
"holster" = SLOT_HOLSTER
)
+GLOBAL_LIST_EMPTY(hotkey_keybinding_list_by_key)
+GLOBAL_LIST_EMPTY(keybindings_by_name)
+
+/// This is a mapping from JS keys to Byond - ref: https://keycode.info/
+GLOBAL_LIST_INIT(keybindings_map, list(
+ "UP" = "North",
+ "RIGHT" = "East",
+ "DOWN" = "South",
+ "LEFT" = "West",
+ "INSERT" = "Insert",
+ "HOME" = "Northwest",
+ "PAGEUP" = "Northeast",
+ "DEL" = "Delete",
+ "END" = "Southwest",
+ "PAGEDOWN" = "Southeast",
+ "SPACEBAR" = "Space",
+ "ALT" = "Alt",
+ "SHIFT" = "Shift",
+ "CONTROL" = "Ctrl",
+ "DIVIDE" = "Divide",
+ "MULTIPLY" = "Multiply",
+ "ADD" = "Add",
+ "SUBTRACT" = "Subtract",
+ "DECIMAL" = "Decimal",
+ "CLEAR" = "Center"
+))
+
+/// Without alt, shift, ctrl and etc because its not necessary
+GLOBAL_LIST_INIT(keybindings_map_reverse, list(
+ "North" = "Up",
+ "East" = "Right",
+ "South" = "Down",
+ "West" = "Left",
+ "Northwest" = "Home",
+ "Northeast" = "PageUp",
+ "Southwest" = "End",
+ "Southeast" = "PageDown",
+))
+
//////////////////////////
/////Initial Building/////
//////////////////////////
/proc/get_mannequin(ckey)
+ RETURN_TYPE(/mob/living/carbon/human/dummy/mannequin)
if (!GLOB.mannequins[ckey])
GLOB.mannequins[ckey] = new /mob/living/carbon/human/dummy/mannequin
return GLOB.mannequins[ckey]
@@ -98,7 +139,7 @@ var/global/list/string_slot_flags = list(
paths = typesof(/datum/sprite_accessory/hair) - /datum/sprite_accessory/hair
for(var/path in paths)
var/datum/sprite_accessory/hair/H = path
- if (!initial(H.name))
+ if (is_abstract(H) || !initial(H.name))
continue
H = new path()
GLOB.hair_styles_list[H.name] = H
@@ -107,7 +148,7 @@ var/global/list/string_slot_flags = list(
paths = typesof(/datum/sprite_accessory/facial_hair) - /datum/sprite_accessory/facial_hair
for(var/path in paths)
var/datum/sprite_accessory/facial_hair/H = path
- if (!initial(H.name))
+ if (is_abstract(H) || !initial(H.name))
continue
H = new path()
GLOB.facial_hair_styles_list[H.name] = H
@@ -164,13 +205,41 @@ var/global/list/string_slot_flags = list(
var/datum/grab/G = all_grabstates[grabstate_name]
G.refresh_updown()
- return 1
+ paths = typesof(/particles)
+ for (var/path in paths)
+ var/particles/P = new path()
+ GLOB.all_particles[P.name] = P
+
+ for(var/datum/tech/tech_type as anything in subtypesof(/datum/tech))
+ GLOB.tech_id_to_name[initial(tech_type.id)] = initial(tech_type.name)
+
+ // Setup world topic handlers
+ for(var/topic_handler_type in subtypesof(/datum/world_topic_handler))
+ var/datum/world_topic_handler/wth = new topic_handler_type()
+ if(!wth.topic_key)
+ stack_trace("[wth.type] has no topic key!")
+ continue
+ if(GLOB.world_topic_handlers[wth.topic_key])
+ stack_trace("[wth.type] has the same topic key as [GLOB.world_topic_handlers[wth.topic_key]]! ([wth.topic_key])")
+ continue
+ GLOB.world_topic_handlers[wth.topic_key] = topic_handler_type
+
+ for(var/datum/keybinding/keybinding as anything in subtypesof(/datum/keybinding))
+ if(!initial(keybinding.name))
+ continue
+ var/datum/keybinding/instance = new keybinding
+ GLOB.keybindings_by_name[instance.name] = instance
+ if(length(instance.hotkey_keys))
+ for(var/bound_key in instance.hotkey_keys)
+ GLOB.hotkey_keybinding_list_by_key[bound_key] += list(instance.name)
+
+ return TRUE
//*** params cache
var/global/list/paramslist_cache = list()
-#define cached_key_number_decode(key_number_data) cached_params_decode(key_number_data, /proc/key_number_decode)
-#define cached_number_list_decode(number_list_data) cached_params_decode(number_list_data, /proc/number_list_decode)
+#define cached_key_number_decode(key_number_data) cached_params_decode(key_number_data, GLOBAL_PROC_REF(key_number_decode))
+#define cached_number_list_decode(number_list_data) cached_params_decode(number_list_data, GLOBAL_PROC_REF(number_list_decode))
/proc/cached_params_decode(params_data, decode_proc)
. = paramslist_cache[params_data]
@@ -179,12 +248,14 @@ var/global/list/paramslist_cache = list()
paramslist_cache[params_data] = .
/proc/key_number_decode(key_number_data)
+ RETURN_TYPE(/list)
var/list/L = params2list(key_number_data)
for(var/key in L)
L[key] = text2num(L[key])
return L
/proc/number_list_decode(number_list_data)
+ RETURN_TYPE(/list)
var/list/L = params2list(number_list_data)
for(var/i in 1 to length(L))
L[i] = text2num(L[i])
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index 275426305e50b..e4ee6fee53928 100644
--- a/code/_helpers/icons.dm
+++ b/code/_helpers/icons.dm
@@ -1,222 +1,5 @@
-/*
-IconProcs README
-
-A BYOND library for manipulating icons and colors
-
-by Lummox JR
-
-version 1.0
-
-The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation
-routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners.
-
-CHANGING ICONS
-
-Several new procs have been added to the /icon datum to simplify working with icons. To use them,
-remember you first need to setup an /icon var like so:
-
- var/icon/my_icon = new('iconfile.dmi')
-
-icon/ChangeOpacity(amount = 1)
- A very common operation in DM is to try to make an icon more or less transparent. Making an icon more
- transparent is usually much easier than making it less so, however. This proc basically is a frontend
- for MapColors() which can change opacity any way you like, in much the same way that SetIntensity()
- can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half.
- If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque.
-icon/GrayScale()
- Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact.
-icon/ColorTone(tone)
- Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an
- RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect.
- See also the global ColorTone() proc.
-icon/MinColors(icon)
- The icon is blended with a second icon where the minimum of each RGB pixel is the result.
- Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon.
-icon/MaxColors(icon)
- The icon is blended with a second icon where the maximum of each RGB pixel is the result.
- Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon.
-icon/Opaque(background = "#000000")
- All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify.
-icon/BecomeAlphaMask()
- You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc.
- The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white.
-icon/AddAlphaMask(mask)
- The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque,
- the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent.
- Where the mask is translucent, the current icon becomes more transparent.
-icon/UseAlphaMask(mask, mode)
- Sometimes you may want to take the alpha values from one icon and use them on a different icon.
- This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change
- so it has the same colors as before but uses the mask for opacity.
-
-COLOR MANAGEMENT AND HSV
-
-RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value.
-
- * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to
- cyan to blue to magenta and back to red.
- * The saturation of a color is how much color is in it. A color with low saturation will be more gray,
- and with no saturation at all it is a shade of gray.
- * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark,
- and no value at all is black.
-
-Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three
-hex digits because it ranges from 0 to 0x5FF.
-
- * 0 to 0xFF - red to yellow
- * 0x100 to 0x1FF - yellow to green
- * 0x200 to 0x2FF - green to cyan
- * 0x300 to 0x3FF - cyan to blue
- * 0x400 to 0x4FF - blue to magenta
- * 0x500 to 0x5FF - magenta to red
-
-Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible),
-value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff".
-
-More than one HSV color can match the same RGB color.
-
-Here are some procs you can use for color management:
-
-ReadRGB(rgb)
- Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used
- that includes alpha, the list will have a fourth item for the alpha value.
-hsv(hue, sat, val, apha)
- Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa"
- format. Alpha is not included in the result if null.
-ReadHSV(rgb)
- Takes an HSV string like "#100ff80" and converts it to a list such as list(256,255,128). If an HSVA format is used that
- includes alpha, the list will have a fourth item for the alpha value.
-RGBtoHSV(rgb)
- Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff".
-HSVtoRGB(hsv)
- Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa".
-BlendRGB(rgb1, rgb2, amount)
- Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result;
- if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well.
- The returned value is an RGB or RGBA color.
-BlendHSV(hsv1, hsv2, amount)
- Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB
- blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1,
- the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well.
- The returned value is an HSV or HSVA color.
-BlendRGBasHSV(rgb1, rgb2, amount)
- Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form.
-HueToAngle(hue)
- Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue.
-AngleToHue(hue)
- Converts an angle to a hue in the valid range.
-RotateHue(hsv, angle)
- Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360.
- (Rotating red by 60 degrees produces yellow.) The result is another HSV or HSVA color with the same saturation and value
- as the original, but a different hue.
-GrayScale(rgb)
- Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string.
-ColorTone(rgb, tone)
- Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of
- using strict shades of gray. The tone value is an RGB color; any alpha value is ignored.
-*/
-
-/*
-Get Flat Icon DEMO by DarkCampainger
-
-This is a test for the get flat icon proc, modified approprietly for icons and their states.
-Probably not a good idea to run this unless you want to see how the proc works in detail.
-mob
- icon = 'old_or_unused.dmi'
- icon_state = "green"
-
- Login()
- // Testing image underlays
- underlays += image(icon='old_or_unused.dmi',icon_state="red")
- underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32)
- underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32)
-
- // Testing image overlays
- overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)
- overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)
- overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)
-
- // Testing icon file overlays (defaults to mob's state)
- overlays += '_flat_demoIcons2.dmi'
-
- // Testing icon_state overlays (defaults to mob's icon)
- overlays += "white"
-
- // Testing dynamic icon overlays
- var/icon/I = icon('old_or_unused.dmi', icon_state="aqua")
- I.Shift(NORTH,16,1)
- overlays+=I
-
- // Testing dynamic image overlays
- I=image(icon=I,pixel_x = -32, pixel_y = 32)
- overlays+=I
-
- // Testing object types (and layers)
- overlays+=/obj/effect/overlayTest
-
- loc = locate (10,10,1)
- verb
- Browse_Icon()
- set name = "1. Browse Icon"
- // Give it a name for the cache
- var/iconName = "[ckey(src.name)]_flattened.dmi"
- // Send the icon to src's local cache
- send_rsc(src, getFlatIcon(src), iconName)
- // Display the icon in their browser
- show_browser(src, "", null)
-
- Output_Icon()
- set name = "2. Output Icon"
- to_chat(src, "Icon is: \icon[getFlatIcon(src)]")
-
- Label_Icon()
- set name = "3. Label Icon"
- // Give it a name for the cache
- var/iconName = "[ckey(src.name)]_flattened.dmi"
- // Copy the file to the rsc manually
- var/icon/I = fcopy_rsc(getFlatIcon(src))
- // Send the icon to src's local cache
- send_rsc(src, I, iconName)
- // Update the label to show it
- winset(src,"imageLabel","image='\ref[I]'");
-
- Add_Overlay()
- set name = "4. Add Overlay"
- overlays += image(icon='old_or_unused.dmi',icon_state="yellow",pixel_x = rand(-64,32), pixel_y = rand(-64,32))
-
- Stress_Test()
- set name = "5. Stress Test"
- for(var/i = 0 to 1000)
- // The third parameter forces it to generate a new one, even if it's already cached
- getFlatIcon(src,0,2)
- if(prob(5))
- Add_Overlay()
- Browse_Icon()
-
- Cache_Test()
- set name = "6. Cache Test"
- for(var/i = 0 to 1000)
- getFlatIcon(src)
- Browse_Icon()
-
-/obj/effect/overlayTest
- icon = 'old_or_unused.dmi'
- icon_state = "blue"
- pixel_x = -24
- pixel_y = 24
- plane = ABOVE_TURF_PLANE
- layer = HOLOMAP_LAYER
-
-world
- view = "7x7"
- maxx = 20
- maxy = 20
- maxz = 1
-*/
-
-#define TO_HEX_DIGIT(n) ascii2text((n&15) + ((n&15)<10 ? 48 : 87))
-
/icon/proc/MakeLying()
+ RETURN_TYPE(/icon)
var/icon/I = new(src,dir=SOUTH)
I.BecomeLying()
return I
@@ -318,6 +101,7 @@ world
*/
/proc/ReadRGB(rgb)
+ RETURN_TYPE(/list)
if(!rgb) return
// interpret the HSV or HSVA value
@@ -368,6 +152,7 @@ world
if(usealpha) . += alpha
/proc/ReadHSV(hsv)
+ RETURN_TYPE(/list)
if(!hsv) return
// interpret the HSV or HSVA value
@@ -634,7 +419,8 @@ as a single icon. Useful for when you want to manipulate an icon via the above a
The _flatIcons list is a cache for generated icon files.
*/
-/proc/getFlatIcon(image/A, defdir=2, deficon=null, defstate="", defblend=BLEND_DEFAULT, always_use_defdir = 0)
+/proc/getFlatIcon(image/A, defdir=2, deficon=null, defstate="", defblend=BLEND_DEFAULT, always_use_defdir = FALSE)
+ RETURN_TYPE(/icon)
// We start with a blank canvas, otherwise some icon procs crash silently
var/icon/flat = icon('icons/effects/effects.dmi', "icon_state"="nothing") // Final flattened icon
if(!A)
@@ -658,8 +444,8 @@ The _flatIcons list is a cache for generated icon files.
else
curstate = defstate
- if(!noIcon && !(curstate in icon_states(curicon)))
- if("" in icon_states(curicon))
+ if(!noIcon && !ICON_HAS_STATE(curicon, curstate))
+ if(ICON_HAS_STATE(curicon, ""))
curstate = ""
else
noIcon = TRUE // Do not render this object.
@@ -681,7 +467,7 @@ The _flatIcons list is a cache for generated icon files.
var/image/copy
// Add the atom's icon itself, without pixel_x/y offsets.
if(!noIcon)
- copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=curdir)
+ copy = image(icon(curicon, curstate, curdir, 1), layer=A.layer)
copy.color = A.color
copy.alpha = A.alpha
copy.blend_mode = curblend
@@ -740,36 +526,24 @@ The _flatIcons list is a cache for generated icon files.
for(var/I in layers)
+ if(I:plane == EMISSIVE_PLANE) //Just replace this with whatever it is TG is doing these days sometime. Getflaticon breaks emissives
+ continue
+
+ if(I:plane == LIGHTING_EXPOSURE_PLANE) // SS220 Bloom-Lighting
+ continue
+
if(I:alpha == 0)
continue
if(I == copy) // 'I' is an /image based on the object being flattened.
curblend = BLEND_OVERLAY
add = icon(I:icon, I:icon_state, I:dir)
- // This checks for a silent failure mode of the icon routine. If the requested dir
- // doesn't exist in this icon state it returns a 32x32 icon with 0 alpha.
- if (I:dir != SOUTH && add.Width() == 32 && add.Height() == 32)
- // Check every pixel for blank (computationally expensive, but the process is limited
- // by the amount of film on the station, only happens when we hit something that's
- // turned, and bails at the very first pixel it sees.
- var/blankpixel;
- for(var/y;y<=32;y++)
- for(var/x;x<32;x++)
- blankpixel = isnull(add.GetPixel(x,y))
- if(!blankpixel)
- break
- if(!blankpixel)
- break
- // If we ALWAYS returned a null (which happens when GetPixel encounters something with alpha 0)
- if (blankpixel)
- // Pull the default direction.
- add = icon(I:icon, I:icon_state)
else // 'I' is an appearance object.
if(istype(A,/obj/machinery/atmospherics) && (I in A.underlays))
var/image/Im = I
- add = getFlatIcon(new/image(I), Im.dir, curicon, curstate, curblend, 1)
+ add = getFlatIcon(image(I), Im.dir, curicon, curstate, curblend, TRUE)
else
- add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend, always_use_defdir)
+ add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, always_use_defdir)
// Find the new dimensions of the flat icon to fit the added overlay
addX1 = min(flatX1, I:pixel_x+1)
@@ -800,6 +574,7 @@ The _flatIcons list is a cache for generated icon files.
return icon(flat, "", SOUTH)
/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N
+ RETURN_TYPE(/icon)
var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A.
for(var/I in A.overlays)//For every image in overlays. var/image/I will not work, don't try it.
if(I:layer>A.layer) continue//If layer is greater than what we need, skip it.
@@ -810,7 +585,7 @@ The _flatIcons list is a cache for generated icon files.
/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay.
var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays.
- //Now we need to culculate overlays+underlays and add them together to form an image for a mask.
+ //Now we need to culculate overlays and underlays and add them together to form an image for a mask.
//var/icon/alpha_mask = getFlatIcon(src)//Accurate but SLOW. Not designed for running each tick. Could have other uses I guess.
var/icon/alpha_mask = getIconMask(src)//Which is why I created that proc. Also a little slow since it's blending a bunch of icons together but good enough.
opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick.
@@ -822,12 +597,13 @@ The _flatIcons list is a cache for generated icon files.
if(2) I.pixel_x++
if(3) I.pixel_y--
if(4) I.pixel_y++
- overlays += I//And finally add the overlay.
+ AddOverlays(overlays) //And finally add the overlay.
#define HOLOPAD_SHORT_RANGE 1 //For determining the color of holopads based on whether they're short or long range.
#define HOLOPAD_LONG_RANGE 2
/proc/getHologramIcon(icon/A, safety=1, noDecolor=FALSE, hologram_color=HOLOPAD_SHORT_RANGE)//If safety is on, a new icon is not created.
+ RETURN_TYPE(/icon)
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
if (noDecolor == FALSE)
if(hologram_color == HOLOPAD_LONG_RANGE)
@@ -841,6 +617,7 @@ The _flatIcons list is a cache for generated icon files.
//For photo camera.
/proc/build_composite_icon(atom/A)
+ RETURN_TYPE(/icon)
var/icon/composite = icon(A.icon, A.icon_state, A.dir, 1)
for(var/O in A.overlays)
var/image/I = O
@@ -858,6 +635,7 @@ The _flatIcons list is a cache for generated icon files.
return rgb(RGB[1],RGB[2],RGB[3])
/proc/sort_atoms_by_layer(list/atoms)
+ RETURN_TYPE(/list)
// Comb sort icons based on levels
var/list/result = atoms.Copy()
var/gap = length(result)
@@ -882,6 +660,7 @@ cap_mode is capturing mode (optional), user is capturing mob (requred only wehen
lighting determines lighting capturing (optional), suppress_errors suppreses errors and continues to capture (optional).
*/
/proc/generate_image(tx as num, ty as num, tz as num, range as num, cap_mode = CAPTURE_MODE_PARTIAL, mob/living/user, lighting = 1, suppress_errors = 1)
+ RETURN_TYPE(/icon)
var/list/turfstocapture = list()
//Lines below determine what tiles will be rendered
for(var/xoff = 0 to range)
diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm
index 32e340533b08d..0aa0d7aaeb234 100644
--- a/code/_helpers/lists.dm
+++ b/code/_helpers/lists.dm
@@ -120,6 +120,7 @@
* If either of arguments is not a list, returns null
*/
/proc/difflist(list/first, list/second, skiprep=0)
+ RETURN_TYPE(/list)
if(!islist(first) || !islist(second))
return
var/list/result = new
@@ -160,6 +161,7 @@ Checks if a list has the same entries and values as an element of big.
* If either of arguments is not a list, returns null
*/
/proc/uniquemergelist(list/first, list/second, skiprep=0)
+ RETURN_TYPE(/list)
if(!islist(first) || !islist(second))
return
var/list/result = new
@@ -170,10 +172,12 @@ Checks if a list has the same entries and values as an element of big.
return result
/proc/assoc_merge_add(value_a, value_b)
+ RETURN_TYPE(/list)
return value_a + value_b
// This proc merges two associative lists
/proc/merge_assoc_lists(list/a, list/b, merge_method, default_if_null_value = null)
+ RETURN_TYPE(/list)
. = list()
for(var/key in a)
var/a_value = a[key]
@@ -233,14 +237,19 @@ Checks if a list has the same entries and values as an element of big.
return len
return null
-//Pick a random element from the list and remove it from the list.
-/proc/pick_n_take(list/listfrom)
- if (length(listfrom) > 0)
- var/picked = pick(listfrom)
- listfrom -= picked
- return picked
- return null
+/// Pick a random element from the list and remove it from the list.
+/proc/pick_n_take(list/list_to_pick)
+ RETURN_TYPE(list_to_pick[_].type)
+
+ var/list_length = length(list_to_pick)
+ if(!list_length)
+ return null
+
+ var/picked_index = rand(1, list_length)
+ var/picked = list_to_pick[picked_index]
+ list_to_pick.Cut(picked_index, picked_index + 1)
+ return picked
/// Remove and return the last element of the list, or null.
/proc/pop(list/list)
@@ -249,6 +258,11 @@ Checks if a list has the same entries and values as an element of big.
. = list[last_index]
LIST_DEC(list)
+/// Returns the first element from the list and removes it from the list
+/proc/popleft(list/L)
+ if (length(L))
+ . = L[1]
+ L.Cut(1,2)
//Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element.
/proc/next_in_list(element, list/L)
@@ -263,6 +277,7 @@ Checks if a list has the same entries and values as an element of big.
//Reverses the order of items in the list
/proc/reverselist(list/L)
+ RETURN_TYPE(/list)
var/list/output = list()
if(L)
for(var/i = length(L); i >= 1; i--)
@@ -271,6 +286,7 @@ Checks if a list has the same entries and values as an element of big.
//Randomize: Return the list in a random order
/proc/shuffle(list/L)
+ RETURN_TYPE(/list)
if(!L)
return
@@ -282,18 +298,21 @@ Checks if a list has the same entries and values as an element of big.
//Return a list with no duplicate entries
/proc/uniquelist(list/L)
+ RETURN_TYPE(/list)
. = list()
for(var/i in L)
. |= i
// Return a list of the values in an assoc list (including null)
/proc/list_values(list/L)
+ RETURN_TYPE(/list)
. = list()
for(var/e in L)
. += L[e]
//Mergesort: divides up the list into halves to begin the sort
/proc/sortKey(list/client/L, order = 1)
+ RETURN_TYPE(/list)
if(isnull(L) || length(L) < 2)
return L
var/middle = length(L) / 2 + 1
@@ -301,6 +320,7 @@ Checks if a list has the same entries and values as an element of big.
//Mergsort: does the actual sorting and returns the results back to sortAtom
/proc/mergeKey(list/client/L, list/client/R, order = 1)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -318,6 +338,7 @@ Checks if a list has the same entries and values as an element of big.
//Mergesort: divides up the list into halves to begin the sort
/proc/sortAtom(list/atom/L, order = 1)
+ RETURN_TYPE(/list)
if(isnull(L) || length(L) < 2)
return L
if(null in L) // Cannot sort lists containing null entries.
@@ -327,6 +348,7 @@ Checks if a list has the same entries and values as an element of big.
//Mergsort: does the actual sorting and returns the results back to sortAtom
/proc/mergeAtoms(list/atom/L, list/atom/R, order = 1)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -345,6 +367,7 @@ Checks if a list has the same entries and values as an element of big.
//Mergesort: any value in a list
/proc/sortList(list/L)
+ RETURN_TYPE(/list)
if(length(L) < 2)
return L
var/middle = length(L) / 2 + 1 // Copy is first,second-1
@@ -352,12 +375,14 @@ Checks if a list has the same entries and values as an element of big.
//Mergsorge: uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sortNames(list/L)
+ RETURN_TYPE(/list)
var/list/Q = new()
for(var/atom/x in L)
Q[x.name] = x
return sortList(Q)
/proc/mergeLists(list/L, list/R)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -374,12 +399,14 @@ Checks if a list has the same entries and values as an element of big.
// List of lists, sorts by element[key] - for things like crew monitoring computer sorting records by name.
/proc/sortByKey(list/L, key)
+ RETURN_TYPE(/list)
if(length(L) < 2)
return L
var/middle = length(L) / 2 + 1
return mergeKeyedLists(sortByKey(L.Copy(0, middle), key), sortByKey(L.Copy(middle), key), key)
/proc/mergeKeyedLists(list/L, list/R, key)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -399,12 +426,14 @@ Checks if a list has the same entries and values as an element of big.
//Mergesort: any value in a list, preserves key=value structure
/proc/sortAssoc(list/L)
+ RETURN_TYPE(/list)
if(length(L) < 2)
return L
var/middle = length(L) / 2 + 1 // Copy is first,second-1
return mergeAssoc(sortAssoc(L.Copy(0,middle)), sortAssoc(L.Copy(middle))) //second parameter null = to end of list
/proc/mergeAssoc(list/L, list/R)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -420,6 +449,7 @@ Checks if a list has the same entries and values as an element of big.
//Converts a bitfield to a list of numbers (or words if a wordlist is provided)
/proc/bitfield2list(bitfield = 0, list/wordlist)
+ RETURN_TYPE(/list)
var/list/r = list()
if(istype(wordlist,/list))
var/max = min(length(wordlist),16)
@@ -459,6 +489,7 @@ Checks if a list has the same entries and values as an element of big.
//Don't use this on lists larger than half a dozen or so
/proc/insertion_sort_numeric_list_ascending(list/L)
+ RETURN_TYPE(/list)
//to_world_log("ascending len input: [length(L)]")
var/list/out = list(pop(L))
for(var/entry in L)
@@ -476,6 +507,7 @@ Checks if a list has the same entries and values as an element of big.
return out
/proc/insertion_sort_numeric_list_descending(list/L)
+ RETURN_TYPE(/list)
//to_world_log("descending len input: [length(L)]")
var/list/out = insertion_sort_numeric_list_ascending(L)
//to_world_log("output: [length(out)]")
@@ -506,12 +538,14 @@ Checks if a list has the same entries and values as an element of big.
/proc/dd_sortedObjectList(list/L, cache=list())
+ RETURN_TYPE(/list)
if(length(L) < 2)
return L
var/middle = length(L) / 2 + 1 // Copy is first,second-1
return dd_mergeObjectList(dd_sortedObjectList(L.Copy(0,middle), cache), dd_sortedObjectList(L.Copy(middle), cache), cache) //second parameter null = to end of list
/proc/dd_mergeObjectList(list/L, list/R, list/cache)
+ RETURN_TYPE(/list)
var/Li=1
var/Ri=1
var/list/result = new()
@@ -537,6 +571,7 @@ Checks if a list has the same entries and values as an element of big.
// Insert an object into a sorted list, preserving sortedness
/proc/dd_insertObjectList(list/L, O)
+ RETURN_TYPE(/list)
var/min = 1
var/max = length(L) + 1
var/Oval = O:dd_SortValue()
@@ -559,6 +594,7 @@ Checks if a list has the same entries and values as an element of big.
min = mid+1
/proc/dd_sortedtextlist(list/incoming, case_sensitive = 0)
+ RETURN_TYPE(/list)
// Returns a new list with the text values sorted.
// Use binary search to order by sortValue.
// This works by going to the half-point of the list, seeing if the node in question is higher or lower cost,
@@ -618,6 +654,7 @@ Checks if a list has the same entries and values as an element of big.
/proc/dd_sortedTextList(list/incoming)
+ RETURN_TYPE(/list)
var/case_sensitive = 1
return dd_sortedtextlist(incoming, case_sensitive)
@@ -637,6 +674,7 @@ Checks if a list has the same entries and values as an element of big.
//creates every subtype of prototype (excluding prototype) and adds it to list L.
//if no list/L is provided, one is created.
/proc/init_subtypes(prototype, list/L)
+ RETURN_TYPE(/list)
if(!istype(L)) L = list()
for(var/path in subtypesof(prototype))
L += new path()
@@ -645,6 +683,7 @@ Checks if a list has the same entries and values as an element of big.
//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair.
//if no list/L is provided, one is created.
/proc/init_subtypes_assoc(prototype, list/L)
+ RETURN_TYPE(/list)
if(!istype(L)) L = list()
for(var/path in subtypesof(prototype))
L[path] = new path()
@@ -653,6 +692,7 @@ Checks if a list has the same entries and values as an element of big.
#define listequal(A, B) (length(A) == length(B) && !length(A^B))
/proc/filter_list(list/L, type)
+ RETURN_TYPE(/list)
. = list()
for(var/entry in L)
if(istype(entry, type))
@@ -667,6 +707,7 @@ Checks if a list has the same entries and values as an element of big.
values += value
/proc/duplicates(list/L)
+ RETURN_TYPE(/list)
. = list()
var/list/checked = list()
for(var/value in L)
@@ -675,10 +716,6 @@ Checks if a list has the same entries and values as an element of big.
else
checked += value
-/proc/assoc_by_proc(list/plain_list, get_initial_value)
- . = list()
- for(var/entry in plain_list)
- .[call(get_initial_value)(entry)] = entry
/proc/get_initial_name(atom/atom_type)
var/atom/A = atom_type
@@ -725,6 +762,7 @@ Checks if a list has the same entries and values as an element of big.
//replaces reverseList ~Carnie
/proc/reverseRange(list/L, start=1, end=0)
+ RETURN_TYPE(/list)
if(length(L))
start = start % length(L)
end = end % (length(L)+1)
@@ -742,6 +780,7 @@ Checks if a list has the same entries and values as an element of big.
//Copies a list, and all lists inside it recusively
//Does not copy any other reference type
/proc/deepCopyList(list/l)
+ RETURN_TYPE(/list)
if(!islist(l))
return l
. = l.Copy()
@@ -759,6 +798,7 @@ Checks if a list has the same entries and values as an element of big.
// Gets the first instance that is of the given type (strictly)
/proc/get_instance_of_strict_type(list/L, T)
+ RETURN_TYPE(/atom)
for(var/key in L)
var/atom/A = key
if(A.type == T)
@@ -769,6 +809,7 @@ Checks if a list has the same entries and values as an element of big.
*
*/
/proc/typecache_filter_list(list/atoms, list/typecache)
+ RETURN_TYPE(/list)
. = list()
for(var/thing in atoms)
var/atom/A = thing
@@ -779,6 +820,7 @@ Checks if a list has the same entries and values as an element of big.
* Like typesof() or subtypesof(), but returns a typecache instead of a list
*/
/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE)
+ RETURN_TYPE(/list)
if(ispath(path))
var/list/types = list()
if(only_root_path)
@@ -804,3 +846,54 @@ Checks if a list has the same entries and values as an element of big.
for(var/T in typesof(P))
L[T] = TRUE
return L
+
+/proc/is_type_in_typecache(atom/A, list/L)
+ if(!A || !length(L))
+ return FALSE
+
+ return L[A.type]
+
+/// Convert list to a map by calling handler per entry. Map may be supplied as a reference. Handlers should implement a no-params clear.
+/proc/list_to_map(list/list, handler, list/map)
+ RETURN_TYPE(/list)
+ call(handler)()
+ if (!islist(map))
+ map = list()
+ for (var/entry in list)
+ call(handler)(map, entry)
+ call(handler)()
+ return map
+
+
+/// Entry handler for list_to_map. Produces a "name"=ref map, overwriting duplicate names in encounter order.
+/proc/ltm_by_atom_name(list/map, atom/entry)
+ if (!map)
+ return
+ map[entry.name] = entry
+
+
+/// Entry handler for list_to_map. Produces a "name"=ref map, suffixing a count to name for duplicate names.
+/proc/ltm_by_atom_name_numbered(list/map, atom/entry)
+ var/static/list/names_seen
+ if (!map)
+ names_seen = null
+ return
+ if (!names_seen)
+ names_seen = list()
+ var/index = ++names_seen[entry.name]
+ if (index > 1)
+ map["[entry.name] [index]"] = entry
+ else
+ map[entry.name] = entry
+
+/// Takes an input_key, as text, and the list of keys already used, outputting a replacement key in the format of "[input_key] ([number_of_duplicates])" if it finds a duplicate
+/// use this for lists of things that might have the same name, like mobs or objects, that you plan on giving to a player as input
+/proc/avoid_assoc_duplicate_keys(input_key, list/used_key_list)
+ if(!input_key || !istype(used_key_list))
+ return
+ if(used_key_list[input_key])
+ used_key_list[input_key]++
+ input_key = "[input_key] ([used_key_list[input_key]])"
+ else
+ used_key_list[input_key] = 1
+ return input_key
diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm
index 8756fe6e1c34d..0d000c767a4f1 100644
--- a/code/_helpers/logging.dm
+++ b/code/_helpers/logging.dm
@@ -20,7 +20,13 @@ var/global/log_end= world.system_type == UNIX ? ascii2text(13) : ""
/proc/log_ss_init(text)
game_log("SS", "[text]")
+//wrapper macros for easier grepping
+#define DIRECT_OUTPUT(A, B) A << B
+#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
+#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
+
#define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [src] usr: [usr].")
+
//print a warning message to world.log
/proc/warning(msg)
to_world_log("## WARNING: [msg][log_end]")
@@ -30,7 +36,7 @@ var/global/log_end= world.system_type == UNIX ? ascii2text(13) : ""
to_world_log("## TESTING: [msg][log_end]")
/proc/game_log(category, text)
- to_file(global.diary, "\[[time_stamp()]] [game_id] [category]: [text][log_end]")
+ rustg_log_write_formatted("[GLOB.log_directory]/game.log", "[category]: [text]")
/proc/log_admin(text)
GLOB.admin_log.Add(text)
@@ -44,9 +50,13 @@ var/global/log_end= world.system_type == UNIX ? ascii2text(13) : ""
/proc/log_runtime(text)
for (var/client/C as anything in GLOB.admins)
- if (C.get_preference_value(/datum/client_preference/staff/show_runtime_logs) == GLOB.PREF_SHOW)
+ if (!C.prefs || C.get_preference_value(/datum/client_preference/staff/show_runtime_logs) == GLOB.PREF_SHOW)
to_chat(C, append_admin_tools(SPAN_DEBUG("RUNTIME: [text]"), usr, usr?.loc))
+/proc/log_signal(text)
+ if(config.log_signals)
+ game_log("SIGNALS", text)
+
/proc/log_error(text)
error(text)
to_debug_listeners(text, "ERROR")
@@ -57,13 +67,17 @@ var/global/log_end= world.system_type == UNIX ? ascii2text(13) : ""
/proc/to_debug_listeners(text, prefix = "DEBUG")
for(var/client/C as anything in GLOB.admins)
- if(C.get_preference_value(/datum/client_preference/staff/show_debug_logs) == GLOB.PREF_SHOW)
- to_chat(C, SPAN_DEBUG("[prefix]: [text]"))
+ if (!C.prefs || C.get_preference_value(/datum/client_preference/staff/show_debug_logs) == GLOB.PREF_SHOW)
+ to_chat(C, MESSAGE_TYPE_DEBUG, SPAN_DEBUG("[prefix]: [text]"), TRUE)
/proc/log_game(text)
if (config.log_game)
game_log("GAME", text)
+/proc/log_tool(text)
+ if (config.log_tool)
+ game_log("TOOL", text)
+
/proc/log_vote(text)
if (config.log_vote)
game_log("VOTE", text)
@@ -103,12 +117,28 @@ var/global/log_end= world.system_type == UNIX ? ascii2text(13) : ""
/proc/log_misc(text)
game_log("MISC", text)
+/proc/log_tgs(text, level)
+ to_world_log("TGS [text]")
+
+/proc/log_tgui(user_or_client, text)
+ if(!text)
+ stack_trace("Pointless log_tgui message")
+ return
+ var/entry = ""
+ if(!user_or_client)
+ entry += "no user"
+ else if(istype(user_or_client, /mob))
+ var/mob/user = user_or_client
+ entry += "[user.ckey] (as [user])"
+ else if(istype(user_or_client, /client))
+ var/client/client = user_or_client
+ entry += "[client.ckey]"
+ entry += ":\n[text]"
+
/proc/log_unit_test(text)
to_world_log("## UNIT_TEST ##: [text]")
log_debug(text)
-/proc/log_qdel(text)
- to_file(GLOB.world_qdel_log, "\[[time_stamp()]]QDEL: [text]")
//This replaces world.log so it displays both in DD and the file
/proc/log_world(text)
diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm
index 4deaddb73b4f2..c0a102af8f91c 100644
--- a/code/_helpers/maths.dm
+++ b/code/_helpers/maths.dm
@@ -2,10 +2,6 @@
#define Frand(low, high) ( rand() * ((high) - (low)) + (low) )
-/// Value or the next integer in a positive direction: Ceil(-1.5) = -1 , Ceil(1.5) = 2
-#define Ceil(value) ( -round(-(value)) )
-
-
/// Value or the next multiple of divisor in a positive direction. Ceilm(-1.5, 0.3) = -1.5 , Ceilm(-1.5, 0.4) = -1.2
#define Ceilm(value, divisor) ( -round(-(value) / (divisor)) * (divisor) )
@@ -14,10 +10,6 @@
#define Ceilp(value, power) ( (power) ** -round(-log((power), (value))) )
-/// Value or the next integer in a negative direction: Floor(-1.5) = -2 , Floor(1.5) = 1
-#define Floor(value) round(value)
-
-
/// Value or the next multiple of divisor in a negative direction: Floorm(-1.5, 0.3) = -1.5 , Floorm(-1.5, 0.4) = -1.6
#define Floorm(value, divisor) ( round((value) / (divisor)) * (divisor) )
@@ -118,7 +110,7 @@
/proc/Drand(x, y, normalize)
var/sum = 0
for (var/i = 1 to x)
- sum += Floor(rand() * y)
+ sum += floor(rand() * y)
if (normalize)
return sum / ((x * y) - x)
return sum + x
@@ -130,6 +122,7 @@
/// A circular random coordinate pair from 0, unit by default, scaled by radius, then rounded if round.
/proc/CircularRandomCoordinate(radius = 1, round)
+ RETURN_TYPE(/list)
var/angle = rand(0, 359)
var/x = cos(angle) * radius
var/y = sin(angle) * radius
@@ -152,6 +145,7 @@
* radius outside the scope of the proc, eg as BoundedCircularRandomCoordinate(Frand(1, 3), ...)
*/
/proc/BoundedCircularRandomCoordinate(radius, center_x, center_y, low_x, low_y, high_x, high_y, round)
+ RETURN_TYPE(/list)
var/list/xy = CircularRandomCoordinate(radius, round)
var/dx = xy[1]
var/dy = xy[2]
@@ -169,12 +163,14 @@
/// Pick a random turf using BoundedCircularRandomCoordinate about x,y on level z
/proc/CircularRandomTurf(radius, z, center_x, center_y, low_x = 1, low_y = 1, high_x = world.maxx, high_y = world.maxy)
+ RETURN_TYPE(/turf)
var/list/xy = BoundedCircularRandomCoordinate(radius, center_x, center_y, low_x, low_y, high_x, high_y, TRUE)
return locate(xy[1], xy[2], z)
/// Pick a random turf using BoundedCircularRandomCoordinate around the turf of target
/proc/CircularRandomTurfAround(atom/target, radius, low_x = 1, low_y = 1, high_x = world.maxx, high_y = world.maxy)
+ RETURN_TYPE(/turf)
var/turf/turf = get_turf(target)
return CircularRandomTurf(radius, turf.z, turf.x, turf.y, low_x, low_y, high_x, high_y)
@@ -200,3 +196,60 @@
/proc/grand(min = 0, max = 1)
var/static/generator/gauss = generator("num", 0, 1, NORMAL_RAND)
return min + gauss.Rand() * (max - min)
+
+/proc/gaussian(mean=0, stddev=1)
+ var u1 = rand()
+ var u2 = rand()
+ var z0 = sqrt(-2 * log(u1)) * cos(2 * PI * u2)
+ return z0 * stddev + mean
+
+
+/proc/rangedGaussian(min=0, max=1, mean=0, stddev=1)
+ var/final_temp = min + rand() * (max - min)
+
+ for (var/runs = 1 to 10)
+ var temp = gaussian(mean, stddev)
+
+ if (temp < min || temp > max)
+ continue
+ final_temp = temp
+ break
+
+ return final_temp
+
+/proc/skewedGaussian(min=0, max=1, skew=1)
+ var/final_temp = min + rand() * (max - min)
+
+ for (var/runs = 1 to 10)
+ var temp = gaussian() ** skew
+ temp = temp * (max - min) + min
+
+ if (temp < min || temp > max)
+ continue
+
+ final_temp = temp
+ break
+
+ return final_temp
+
+/proc/Wrap(val, min, max)
+ var/d = max - min
+ var/t = round((val - min) / d)
+ return val - (t * d)
+
+
+/proc/MakeGenerator(g_type, g_min, g_max, g_rand = UNIFORM_RAND)
+ switch (g_rand)
+ if (1)
+ g_rand = NORMAL_RAND
+ if (2)
+ g_rand = LINEAR_RAND
+ if (3)
+ g_rand = SQUARE_RAND
+ else
+ g_rand = UNIFORM_RAND
+
+ if (!isnum(g_min) || !isnum(g_max))
+ return null
+
+ return generator(g_type, g_min, g_max, g_rand)
diff --git a/code/_helpers/matrices.dm b/code/_helpers/matrices.dm
index adb136b5297cc..d17460cbbe1b2 100644
--- a/code/_helpers/matrices.dm
+++ b/code/_helpers/matrices.dm
@@ -48,11 +48,13 @@
//Returns an identity color matrix which does nothing
/proc/color_identity()
+ RETURN_TYPE(/list)
return list(1,0,0, 0,1,0, 0,0,1)
//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites
//TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone)
/proc/color_rotation(angle)
+ RETURN_TYPE(/list)
if(angle == 0)
return color_identity()
angle = clamp(angle, -180, 180)
@@ -70,6 +72,7 @@
//Makes everything brighter or darker without regard to existing color or brightness
/proc/color_brightness(power)
+ RETURN_TYPE(/list)
power = clamp(power, -255, 255)
power = power/255
@@ -90,6 +93,7 @@ var/global/list/delta_index = list(
//Exxagerates or removes brightness
/proc/color_contrast(value)
+ RETURN_TYPE(/list)
value = round(clamp(value, -100, 100))
if(value == 0)
return color_identity()
@@ -111,6 +115,7 @@ var/global/list/delta_index = list(
//Exxagerates or removes colors
/proc/color_saturation(value as num)
+ RETURN_TYPE(/list)
if(value == 0)
return color_identity()
value = clamp(value, -100, 100)
@@ -132,6 +137,7 @@ var/global/list/delta_index = list(
//Given 2 matrices mxn and nxp (row major) it multiplies their members and return an mxp matrix
//Do make sure your lists actually have this many elements
/proc/multiply_matrices(list/A, list/B, m, n, p)
+ RETURN_TYPE(/list)
var/list/result = new (m * p)
if(length(A) == m*n && length(B) == n*p)
diff --git a/code/_helpers/medical_scans.dm b/code/_helpers/medical_scans.dm
index 54a012f7eba36..23c28d1ff400f 100644
--- a/code/_helpers/medical_scans.dm
+++ b/code/_helpers/medical_scans.dm
@@ -1,4 +1,5 @@
/mob/living/carbon/human/proc/get_raw_medical_data(tag = FALSE)
+ RETURN_TYPE(/list)
var/mob/living/carbon/human/H = src
var/list/scan = list()
@@ -107,6 +108,7 @@
return scan
/proc/display_medical_data_header(list/scan, skill_level = SKILL_DEFAULT)
+ RETURN_TYPE(/list)
//In case of problems, abort.
var/dat = list()
@@ -128,6 +130,7 @@
return dat
/proc/display_medical_data_health(list/scan, skill_level = SKILL_DEFAULT)
+ RETURN_TYPE(/list)
//In case of problems, abort.
if(!scan["name"])
return "
"
+
+ var/datum/browser/popup = new(mob, "modpacks_list", "Список Модификаций", 480, 580)
+ popup.set_content(.)
+ popup.open()
+ else
+ to_chat(src, SPAN_WARNING("Этот сервер не использует какие-либо модификации."))
diff --git a/code/controllers/subsystems/initialization/robots.dm b/code/controllers/subsystems/initialization/robots.dm
index 44dee66a51fc3..050b7a4931870 100644
--- a/code/controllers/subsystems/initialization/robots.dm
+++ b/code/controllers/subsystems/initialization/robots.dm
@@ -15,6 +15,9 @@ SUBSYSTEM_DEF(robots)
)
var/list/mmi_types_by_title = list(
+ // [SIERRA-ADD] - Allow to join as cyborg,
+ "cyborg" = /obj/item/device/mmi,
+ // [/SIERRA-ADD] ,
"robot" = /obj/item/organ/internal/posibrain,
"drone" = /obj/item/device/mmi/digital/robot,
"robot, flying" = /obj/item/organ/internal/posibrain,
@@ -31,7 +34,7 @@ SUBSYSTEM_DEF(robots)
// This is done via loop instead of just assignment in order to trim associations.
for(var/title in (mob_types_by_title|mmi_types_by_title))
robot_alt_titles |= capitalize(title)
- sortTim(robot_alt_titles, /proc/cmp_text_asc)
+ sortTim(robot_alt_titles, GLOBAL_PROC_REF(cmp_text_asc))
for(var/module_type in subtypesof(/obj/item/robot_module))
var/obj/item/robot_module/module = module_type
@@ -48,7 +51,7 @@ SUBSYSTEM_DEF(robots)
LAZYINITLIST(modules_by_category[module_category])
LAZYSET(modules_by_category[module_category], module_name, module)
all_module_names |= module_name
- all_module_names = sortTim(all_module_names, /proc/cmp_text_asc)
+ all_module_names = sortTim(all_module_names, GLOBAL_PROC_REF(cmp_text_asc))
/datum/controller/subsystem/robots/proc/get_available_modules(module_category, crisis_mode, include_override)
. = list()
@@ -62,7 +65,7 @@ SUBSYSTEM_DEF(robots)
.[include_override] = modules[include_override]
/datum/controller/subsystem/robots/proc/get_mmi_type_by_title(check_title)
- . = mmi_types_by_title[lowertext(trim(check_title))] || /obj/item/device/mmi
+ . = mmi_types_by_title[lowertext(trimtext(check_title))] || /obj/item/device/mmi
/datum/controller/subsystem/robots/proc/get_mob_type_by_title(check_title)
- . = mob_types_by_title[lowertext(trim(check_title))] || /mob/living/silicon/robot
+ . = mob_types_by_title[lowertext(trimtext(check_title))] || /mob/living/silicon/robot
diff --git a/code/controllers/subsystems/initialization/xenoarch.dm b/code/controllers/subsystems/initialization/xenoarch.dm
new file mode 100644
index 0000000000000..06f97013ec5a8
--- /dev/null
+++ b/code/controllers/subsystems/initialization/xenoarch.dm
@@ -0,0 +1,85 @@
+SUBSYSTEM_DEF(xenoarch)
+ name = "Xenoarcheology"
+ flags = SS_NO_FIRE
+ init_order = SS_INIT_XENOARCH
+ var/static/list/xeno_artifact_turfs = list()
+ var/static/list/xeno_digsite_turfs = list()
+
+/datum/controller/subsystem/xenoarch/Initialize(start_uptime)
+ var/datum/map/map = GLOB.using_map
+ if (!map)
+ return
+
+ var/list/artifact_turfs = list()
+ var/static/excavation_turf_chance = 0.5
+ var/list/banned_levels = map.admin_levels + map.escape_levels
+ for(var/z_level_index in mining_walls)
+ if(text2num(z_level_index) in banned_levels)
+ continue
+
+ var/list/mining_turfs = mining_walls[z_level_index]
+ if(!length(mining_turfs))
+ continue
+
+ for(var/turf/simulated/mineral/mineral_turf as anything in mining_turfs)
+ if (!mineral_turf.density)
+ continue
+
+ if (!mineral_turf.geologic_data)
+ mineral_turf.geologic_data = new(mineral_turf)
+
+ if(!prob(excavation_turf_chance))
+ continue
+
+ xeno_digsite_turfs += mineral_turf
+ var/list/possible_site_turfs = list()
+ for(var/turf/simulated/mineral/T in RANGE_TURFS(mineral_turf, 2))
+ if(!T.density)
+ continue
+
+ if(T.finds)
+ continue
+
+ possible_site_turfs += T
+
+ possible_site_turfs = shuffle(possible_site_turfs)
+ LIST_RESIZE(possible_site_turfs, min(rand(4, 12), length(possible_site_turfs)))
+
+ var/site_type = get_random_digsite_type()
+ for(var/turf/simulated/mineral/T as anything in possible_site_turfs)
+ if(!T.finds)
+ var/list/finds = list()
+ if (prob(50))
+ finds += new /datum/find (site_type, rand(10, 190))
+ else if (prob(75))
+ finds += new /datum/find (site_type, rand(10, 90))
+ finds += new /datum/find (site_type, rand(110, 190))
+ else
+ finds += new /datum/find (site_type, rand(10, 50))
+ finds += new /datum/find (site_type, rand(60, 140))
+ finds += new /datum/find (site_type, rand(150, 190))
+ var/datum/find/F = finds[1]
+ if (F.excavation_required <= F.view_range)
+ T.archaeo_overlay = "overlay_archaeo[rand(1, 3)]"
+ T.update_icon()
+ T.finds = finds
+
+ if(site_type == DIGSITE_GARDEN)
+ continue
+
+ if(site_type == DIGSITE_ANIMAL)
+ continue
+
+ artifact_turfs += T
+
+ CHECK_TICK
+
+ var/xeno_artifact_turfs_amount = min(rand(6, 12), length(artifact_turfs))
+ for (var/i = 1 to xeno_artifact_turfs_amount)
+ var/turf/simulated/mineral/selected_mineral = pick_n_take(artifact_turfs)
+ // Failsafe for invalid turf types
+ if (!istype(selected_mineral))
+ continue
+
+ xeno_artifact_turfs += selected_mineral
+ selected_mineral.artifact_find = new
diff --git a/code/controllers/subsystems/jobs.dm b/code/controllers/subsystems/jobs.dm
index 61822f8b2255f..d2ee947d24cff 100644
--- a/code/controllers/subsystems/jobs.dm
+++ b/code/controllers/subsystems/jobs.dm
@@ -38,6 +38,9 @@ SUBSYSTEM_DEF(jobs)
// Create main map jobs.
primary_job_datums.Cut()
for(var/jobtype in (list(DEFAULT_JOB_TYPE) | GLOB.using_map.allowed_jobs))
+ if(!jobtype)
+ stack_trace("`null` jobtype exists in `GLOB.using_map.allowed_jobs` and `DEFAULT_JOB_TYPE`")
+
var/datum/job/job = get_by_path(jobtype)
if(!job)
job = new jobtype
@@ -151,8 +154,7 @@ SUBSYSTEM_DEF(jobs)
/datum/controller/subsystem/jobs/proc/check_latejoin_blockers(mob/new_player/joining, datum/job/job)
if(!check_general_join_blockers(joining, job))
return FALSE
- if(job.minimum_character_age && (joining.client.prefs.age < job.minimum_character_age))
- to_chat(joining, SPAN_WARNING("Your character's in-game age is too low for this job."))
+ if(job.is_restricted(joining.client.prefs, joining))
return FALSE
if(!job.player_old_enough(joining.client))
to_chat(joining, SPAN_WARNING("Your player age (days since first seen on the server) is too low for this job."))
@@ -242,38 +244,35 @@ SUBSYSTEM_DEF(jobs)
///This proc is called before the level loop of divide_occupations() and will try to select a head, ignoring ALL non-head preferences for every level until it locates a head or runs out of levels to check
/datum/controller/subsystem/jobs/proc/fill_head_position(datum/game_mode/mode)
- for(var/level = 1 to 3)
- for(var/command_position in titles_by_department(COM))
+ for (var/level = 1 to 3)
+ for (var/command_position as anything in titles_by_department(COM))
var/datum/job/job = get_by_title(command_position)
- if(!job) continue
+ if (!job)
+ continue
var/list/candidates = find_occupation_candidates(job, level)
- if(!length(candidates)) continue
- // Build a weighted list, weight by age.
+ if (!length(candidates))
+ continue
var/list/weightedCandidates = list()
- for(var/mob/V in candidates)
- // Log-out during round-start? What a bad boy, no head position for you!
- if(!V.client) continue
- var/age = V.client.prefs.age
- if(age < job.minimum_character_age) // Nope.
+ for (var/mob/mob as anything in candidates)
+ if (!mob.client)
continue
- switch(age)
- if(job.minimum_character_age to (job.minimum_character_age+10))
- weightedCandidates[V] = 3 // Still a bit young.
- if((job.minimum_character_age+10) to (job.ideal_character_age-10))
- weightedCandidates[V] = 6 // Better.
- if((job.ideal_character_age-10) to (job.ideal_character_age+10))
- weightedCandidates[V] = 10 // Great.
- if((job.ideal_character_age+10) to (job.ideal_character_age+20))
- weightedCandidates[V] = 6 // Still good.
- if((job.ideal_character_age+20) to INFINITY)
- weightedCandidates[V] = 3 // Geezer.
- else
- // If there's ABSOLUTELY NOBODY ELSE
- if(length(candidates) == 1) weightedCandidates[V] = 1
+ var/age = mob.client.prefs.age
+ if (age < job.minimum_character_age)
+ continue
+ if (age < job.minimum_character_age + 10)
+ weightedCandidates[mob] = 3
+ else if (age < job.ideal_character_age - 10)
+ weightedCandidates[mob] = 6
+ else if (age < job.ideal_character_age + 10)
+ weightedCandidates[mob] = 10
+ else if (age < job.ideal_character_age + 20)
+ weightedCandidates[mob] = 6
+ else
+ weightedCandidates[mob] = 3
var/mob/new_player/candidate = pickweight(weightedCandidates)
- if(assign_role(candidate, command_position, mode = mode))
- return 1
- return 0
+ if (assign_role(candidate, command_position, mode = mode))
+ return TRUE
+ return FALSE
///This proc is called at the start of the level loop of divide_occupations() and will cause head jobs to be checked before any other jobs of the same level
/datum/controller/subsystem/jobs/proc/CheckHeadPositions(level, datum/game_mode/mode)
@@ -288,7 +287,7 @@ SUBSYSTEM_DEF(jobs)
/** Proc divide_occupations
* fills var "assigned_role" for all ready players.
* This proc must not have any side effect besides of modifying "assigned_role".
- **/
+ */
/datum/controller/subsystem/jobs/proc/divide_occupations(datum/game_mode/mode)
//Get the players who are ready
for(var/mob/new_player/player in GLOB.player_list)
@@ -361,73 +360,61 @@ SUBSYSTEM_DEF(jobs)
/datum/controller/subsystem/jobs/proc/attempt_role_assignment(mob/new_player/player, datum/job/job, level, datum/game_mode/mode)
if(!jobban_isbanned(player, job.title) && \
- job.player_old_enough(player.client) && \
- player.client.prefs.CorrectLevel(job, level) && \
- job.is_position_available())
+ job.player_old_enough(player.client) && \
+ player.client.prefs.CorrectLevel(job, level) && \
+ job.is_position_available())
assign_role(player, job.title, mode = mode)
return TRUE
return FALSE
-/datum/controller/subsystem/jobs/proc/equip_custom_loadout(mob/living/carbon/human/H, datum/job/job)
-
- if(!H || !H.client)
+/datum/controller/subsystem/jobs/proc/equip_custom_loadout(mob/living/carbon/human/human_to_equip, datum/job/job)
+ if(!human_to_equip?.client)
return
// Equip custom gear loadout, replacing any job items
- var/list/spawn_in_storage = list()
+ var/list/spawn_in_storage = equip_gear(human_to_equip, job)
+
+ // do accessories last so they don't attach to a suit that will be replaced
+ equip_accessories(human_to_equip)
+ return spawn_in_storage
+
+/// Equips all the loadout gear to `human_to_equip`.
+/// Returns the list of gear, that couldn't be equipped to any inventory slot.
+/datum/controller/subsystem/jobs/proc/equip_gear(mob/living/carbon/human/human_to_equip, datum/job/job)
+ if(!job.loadout_allowed)
+ return list()
+
+ var/list/gear_to_equip = human_to_equip.client.prefs.Gear()
+ if(!length(gear_to_equip))
+ return list()
+
var/list/loadout_taken_slots = list()
- if(H.client.prefs.Gear() && job.loadout_allowed)
- for(var/thing in H.client.prefs.Gear())
- var/datum/gear/G = gear_datums[thing]
- if(G)
- var/permitted = 0
- if(G.allowed_branches)
- if(H.char_branch && (H.char_branch.type in G.allowed_branches))
- permitted = 1
- else
- permitted = 1
-
- if(permitted)
- if(G.allowed_roles)
- if(job.type in G.allowed_roles)
- permitted = 1
- else
- permitted = 0
- else
- permitted = 1
-
- if(permitted && G.allowed_skills)
- for(var/required in G.allowed_skills)
- if(!H.skill_check(required,G.allowed_skills[required]))
- permitted = 0
-
- if(G.whitelisted && (!(H.species.name in G.whitelisted)))
- permitted = 0
-
- if(!permitted)
- to_chat(H, SPAN_WARNING("Your current species, job, branch, skills or whitelist status does not permit you to spawn with [thing]!"))
- continue
+ var/list/failed_to_equip_gear = list()
+ for(var/gear_name as anything in gear_to_equip)
+ var/datum/gear/gear_item = gear_datums[gear_name]
+ if(!gear_item)
+ stack_trace("Non-existing gear: `gear_name`")
+ continue
- if(!G.slot || G.slot == slot_tie || (G.slot in loadout_taken_slots) || !G.spawn_on_mob(H, H.client.prefs.Gear()[G.display_name]))
- spawn_in_storage.Add(G)
- else
- loadout_taken_slots.Add(G.slot)
+ if(!gear_item.is_permitted_to_equip(human_to_equip, job))
+ to_chat(human_to_equip, SPAN_WARNING("Your current species, job, branch, skills or whitelist status does not permit you to spawn with [gear_name]!"))
+ continue
- // do accessories last so they don't attach to a suit that will be replaced
- if(H.char_rank && H.char_rank.accessory)
- for(var/accessory_path in H.char_rank.accessory)
- var/list/accessory_data = H.char_rank.accessory[accessory_path]
- if(islist(accessory_data))
- var/amt = accessory_data[1]
- var/list/accessory_args = accessory_data.Copy()
- accessory_args[1] = src
- for(var/i in 1 to amt)
- H.equip_to_slot_or_del(new accessory_path(arglist(accessory_args)), slot_tie)
- else
- for(var/i in 1 to (isnull(accessory_data)? 1 : accessory_data))
- H.equip_to_slot_or_del(new accessory_path(src), slot_tie)
+ var/failed_to_equip = !gear_item.slot || gear_item.slot == slot_tie || loadout_taken_slots["[gear_item.slot]"] || !gear_item.spawn_on_mob(human_to_equip, gear_to_equip[gear_item.display_name])
+ if(failed_to_equip)
+ failed_to_equip_gear += gear_item
+ continue
- return spawn_in_storage
+ loadout_taken_slots["[gear_item.slot]"] = TRUE
+
+ return failed_to_equip_gear
+
+/datum/controller/subsystem/jobs/proc/equip_accessories(mob/living/carbon/human/human_to_equip)
+ if(!human_to_equip.char_rank || !length(human_to_equip.char_rank.accessory))
+ return
+
+ for(var/accessory_path in human_to_equip.char_rank.accessory)
+ human_to_equip.equip_to_slot_or_del(new accessory_path, slot_tie)
/datum/controller/subsystem/jobs/proc/equip_rank(mob/living/carbon/human/H, rank, joined_late = 0)
if(!H)
@@ -484,7 +471,7 @@ SUBSYSTEM_DEF(jobs)
if(!joined_late || job.latejoin_at_spawnpoints)
var/obj/S = job.get_roundstart_spawnpoint()
- if(istype(S, /obj/effect/landmark/start) && istype(S.loc, /turf))
+ if(istype(S, /obj/landmark/start) && istype(S.loc, /turf))
H.forceMove(S.loc)
else
var/datum/spawnpoint/spawnpoint = job.get_spawnpoint(H.client)
@@ -520,15 +507,15 @@ SUBSYSTEM_DEF(jobs)
return other_mob
if(spawn_in_storage)
- for(var/datum/gear/G in spawn_in_storage)
- G.spawn_in_storage_or_drop(H, H.client.prefs.Gear()[G.display_name])
+ var/datum/gear_slot/picked_gear_slot = H.client.prefs.get_picked_gear_slot()
+ for(var/datum/gear/gear_to_spawn as anything in spawn_in_storage)
+ gear_to_spawn.spawn_in_storage_or_drop(H, picked_gear_slot.get_gear_tweaks(gear_to_spawn.display_name))
if(istype(H)) //give humans wheelchairs, if they need them.
var/obj/item/organ/external/l_foot = H.get_organ(BP_L_FOOT)
var/obj/item/organ/external/r_foot = H.get_organ(BP_R_FOOT)
if(!l_foot || !r_foot)
var/obj/structure/bed/chair/wheelchair/W = new /obj/structure/bed/chair/wheelchair(H.loc)
- H.buckled = W
H.UpdateLyingBuckledAndVerbStatus()
W.set_dir(H.dir)
W.buckle_mob(H)
@@ -559,7 +546,7 @@ SUBSYSTEM_DEF(jobs)
return positions_by_department["[dept]"] || list()
/datum/controller/subsystem/jobs/proc/spawn_empty_ai()
- for(var/obj/effect/landmark/start/S in landmarks_list)
+ for(var/obj/landmark/start/S in landmarks_list)
if(S.name != "AI")
continue
if(locate(/mob/living) in S.loc)
diff --git a/code/controllers/subsystems/lighting.dm b/code/controllers/subsystems/lighting.dm
index 775bb83b47ba5..353c4bf9b53d4 100644
--- a/code/controllers/subsystems/lighting.dm
+++ b/code/controllers/subsystems/lighting.dm
@@ -1,112 +1,138 @@
-var/global/lighting_overlays_initialised = FALSE
-
SUBSYSTEM_DEF(lighting)
name = "Lighting"
- wait = 1
+ wait = LIGHTING_INTERVAL
init_order = SS_INIT_LIGHTING
+ runlevels = RUNLEVELS_PREGAME | RUNLEVELS_GAME
- // Queues of update counts, waiting to be rolled into stats lists
- var/list/stats_queues = list(
- "Source" = list(),
- "Corner" = list(),
- "Overlay" = list()
- )
- // Stats lists
- var/list/stats_lists = list(
- "Source" = list(),
- "Corner" = list(),
- "Overlay" = list()
- )
- var/update_stats_every = 1 SECOND
- var/next_stats_update = 0
- var/stat_updates_to_keep = 5
+ var/total_lighting_overlays = 0
+ var/total_lighting_sources = 0
+ var/total_ambient_turfs = 0
+ var/total_lighting_corners = 0
- var/list/light_queue = list() // lighting sources queued for update.
+ /// lighting sources queued for update.
+ var/list/light_queue = list()
var/lq_idex = 1
- var/list/corner_queue = list() // lighting corners queued for update.
+ /// lighting corners queued for update.
+ var/list/corner_queue = list()
var/cq_idex = 1
- var/list/overlay_queue = list() // lighting overlays queued for update.
+ /// lighting overlays queued for update.
+ var/list/overlay_queue = list()
var/oq_idex = 1
+ // - Performance and analytics data
var/processed_lights = 0
var/processed_corners = 0
var/processed_overlays = 0
+ var/total_ss_updates = 0
+ var/total_instant_updates = 0
+
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+ var/force_queued = TRUE
+ var/force_override = FALSE // For admins.
+#endif
+
/datum/controller/subsystem/lighting/UpdateStat(time)
- if (PreventUpdateStat(time))
- return ..()
- ..({"\
- Queues: \
- Source [length(light_queue)] \
- Corner [length(corner_queue)] \
- Overlay [length(overlay_queue)]\n\
- Source Updates [length(stats_lists["Source"])]\n\
- Corner Updates [length(stats_lists["Corner"])]\n\
- Overlay Updates [length(stats_lists["Overlay"])]\
- "})
-
-
-/datum/controller/subsystem/lighting/Initialize(start_uptime)
- InitializeTurfs()
- lighting_overlays_initialised = TRUE
+ var/list/out = list(
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+ "IUR: [total_ss_updates ? round(total_instant_updates/(total_instant_updates+total_ss_updates)*100, 0.1) : "NaN"]%\n",
+#endif
+ "\tT:{L:[total_lighting_sources] C:[total_lighting_corners] O:[total_lighting_overlays] A:[total_ambient_turfs]}\n",
+ "\tP:{L:[length(light_queue) - (lq_idex - 1)]|C:[length(corner_queue) - (cq_idex - 1)]|O:[length(overlay_queue) - (oq_idex - 1)]}\n",
+ "\tL:{L:[processed_lights]|C:[processed_corners]|O:[processed_overlays]}\n"
+ )
+ ..(out.Join())
+
+#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
+
+/hook/roundstart/proc/lighting_init_roundstart()
+ SSlighting.handle_roundstart()
+ return TRUE
+
+/datum/controller/subsystem/lighting/proc/handle_roundstart()
+ force_queued = FALSE
+ total_ss_updates = 0
+ total_instant_updates = 0
+
+#endif
+/// Generate overlays for all Zlevels and then fire normally
+/datum/controller/subsystem/lighting/Initialize(timeofday)
+ var/overlaycount = 0
+ var/starttime = REALTIMEOFDAY
+
+ // Generate overlays.
+ for (var/zlevel = 1 to world.maxz)
+ overlaycount += InitializeZlev(zlevel)
+
+ admin_notice(SPAN_DANGER("Created [overlaycount] lighting overlays in [(REALTIMEOFDAY - starttime)/10] seconds."), R_DEBUG)
+
+ starttime = REALTIMEOFDAY
+ // Tick once to clear most lights.
fire(FALSE, TRUE)
+ admin_notice(SPAN_DANGER("Processed [processed_lights] light sources."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Processed [processed_corners] light corners."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Processed [processed_overlays] light overlays."), R_DEBUG)
+ admin_notice(SPAN_DANGER("Lighting pre-bake completed in [(REALTIMEOFDAY - starttime)/10] seconds."), R_DEBUG)
+
+ log_ss("lighting", "NOv:[overlaycount] L:[processed_lights] C:[processed_corners] O:[processed_overlays]")
+
+ ..()
+
+/**
+ * Go over turfs thay may be dynamically lit and add a lighting overlay if they don't have one. Then do the same for turfs that may be ambient lit.
+ *
+ * **Parameters**:
+ * - `zlev` int - z-level index
+ */
+/datum/controller/subsystem/lighting/proc/InitializeZlev(zlev)
+ for (var/turf/T as anything in Z_ALL_TURFS(zlev))
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) && !T.lighting_overlay) // Can't assume that one hasn't already been created on bay/neb.
+ new /atom/movable/lighting_overlay(T)
+ . += 1
+ if(TURF_IS_AMBIENT_LIT_UNSAFE(T))
+ T.generate_missing_corners() // Forcibly generate corners.
-// It's safe to pass a list of non-turfs to this list - it'll only check turfs.
+ CHECK_TICK
+
+/// Initialize a set of turfs (for example as part of loading a map template) It's safe to pass a list of non-turfs to this list - it'll only check turfs.
/datum/controller/subsystem/lighting/proc/InitializeTurfs(list/targets)
- for (var/turf/T in (targets || world))
- if (T.dynamic_lighting && T.loc:dynamic_lighting)
+ for (var/turf/T in targets)
+ if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T))
T.lighting_build_overlay()
// If this isn't here, BYOND will set-background us.
CHECK_TICK
+/**
+ * Go over light queue and update corners as needed
+ * Go over light corner queue and update overlays as needed
+ * Go over overlay queue and update as needed
+ */
/datum/controller/subsystem/lighting/fire(resumed = FALSE, no_mc_tick = FALSE)
if (!resumed)
- stats_queues["Source"] += processed_lights
- stats_queues["Corner"] += processed_corners
- stats_queues["Overlay"] += processed_overlays
-
processed_lights = 0
processed_corners = 0
processed_overlays = 0
- if(next_stats_update <= world.time)
- next_stats_update = world.time + update_stats_every
- for(var/stat_name in stats_queues)
- var/stat_sum = 0
- var/list/stats_queue = stats_queues[stat_name]
- for(var/count in stats_queue)
- stat_sum += count
- stats_queue.Cut()
-
- var/list/stats_list = stats_lists[stat_name]
- stats_list.Insert(1, stat_sum)
- if(length(stats_list) > stat_updates_to_keep)
- stats_list.Cut(length(stats_list))
-
MC_SPLIT_TICK_INIT(3)
if (!no_mc_tick)
MC_SPLIT_TICK
- // Sources.
- while (lq_idex <= length(light_queue))
- var/datum/light_source/L = light_queue[lq_idex]
- lq_idex += 1
+ var/list/curr_lights = light_queue
+ var/list/curr_corners = corner_queue
+ var/list/curr_overlays = overlay_queue
- if(L.check() || L.destroyed || L.force_update)
- L.remove_lum()
- if(!L.destroyed)
- L.apply_lum()
+ while (lq_idex <= length(curr_lights))
+ var/datum/light_source/L = curr_lights[lq_idex++]
- else if(L.vis_update) //We smartly update only tiles that became (in) visible to use.
- L.smart_vis_update()
+ if (L.needs_update != LIGHTING_NO_UPDATE)
+ total_ss_updates += 1
+ L.update_corners()
- L.vis_update = FALSE
- L.force_update = FALSE
- L.needs_update = FALSE
+ L.needs_update = LIGHTING_NO_UPDATE
- processed_lights += 1
+ processed_lights++
if (no_mc_tick)
CHECK_TICK
@@ -114,22 +140,21 @@ SUBSYSTEM_DEF(lighting)
break
if (lq_idex > 1)
- light_queue.Cut(1, lq_idex)
+ curr_lights.Cut(1, lq_idex)
lq_idex = 1
if (!no_mc_tick)
MC_SPLIT_TICK
- // Corners.
- while (cq_idex <= length(corner_queue))
- var/datum/lighting_corner/C = corner_queue[cq_idex]
- cq_idex += 1
+ while (cq_idex <= length(curr_corners))
+ var/datum/lighting_corner/C = curr_corners[cq_idex++]
- C.update_overlays()
+ if (C.needs_update)
+ C.update_overlays()
- C.needs_update = FALSE
+ C.needs_update = FALSE
- processed_corners += 1
+ processed_corners++
if (no_mc_tick)
CHECK_TICK
@@ -137,27 +162,51 @@ SUBSYSTEM_DEF(lighting)
break
if (cq_idex > 1)
- corner_queue.Cut(1, cq_idex)
+ curr_corners.Cut(1, cq_idex)
cq_idex = 1
if (!no_mc_tick)
MC_SPLIT_TICK
- // Objects.
- while (oq_idex <= length(overlay_queue))
- var/atom/movable/lighting_overlay/O = overlay_queue[oq_idex]
- oq_idex += 1
+ while (oq_idex <= length(curr_overlays))
+ var/atom/movable/lighting_overlay/O = curr_overlays[oq_idex++]
- O.update_overlay()
- O.needs_update = 0
+ if (!QDELETED(O) && O.needs_update)
+ O.update_overlay()
+ O.needs_update = FALSE
- processed_overlays += 1
+ processed_overlays++
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
break
+ if (oq_idex > 1)
+ curr_overlays.Cut(1, oq_idex)
+ oq_idex = 1
+
+/datum/controller/subsystem/lighting/Recover()
+ total_lighting_corners = SSlighting.total_lighting_corners
+ total_lighting_overlays = SSlighting.total_lighting_overlays
+ total_lighting_sources = SSlighting.total_lighting_sources
+
+ light_queue = SSlighting.light_queue
+ corner_queue = SSlighting.corner_queue
+ overlay_queue = SSlighting.overlay_queue
+
+ lq_idex = SSlighting.lq_idex
+ cq_idex = SSlighting.cq_idex
+ oq_idex = SSlighting.oq_idex
+
+ if (lq_idex > 1)
+ light_queue.Cut(1, lq_idex)
+ lq_idex = 1
+
+ if (cq_idex > 1)
+ corner_queue.Cut(1, cq_idex)
+ cq_idex = 1
+
if (oq_idex > 1)
overlay_queue.Cut(1, oq_idex)
oq_idex = 1
diff --git a/code/controllers/subsystems/machines.dm b/code/controllers/subsystems/machines.dm
index dff1ccf741ddb..0c7d0eb8d0740 100644
--- a/code/controllers/subsystems/machines.dm
+++ b/code/controllers/subsystems/machines.dm
@@ -37,27 +37,25 @@ if(Datum.is_processing) {\
SUBSYSTEM_DEF(machines)
name = "Machines"
init_order = SS_INIT_MACHINES
- priority = SS_PRIORITY_MACHINERY
+ priority = FIRE_PRIORITY_MACHINERY
flags = SS_KEEP_TIMING
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/static/current_step = SSMACHINES_PIPENETS
var/static/cost_pipenets = 0
var/static/cost_machinery = 0
var/static/cost_powernets = 0
var/static/cost_power_objects = 0
var/static/list/pipenets = list()
- var/static/list/machinery = list()
var/static/list/powernets = list()
var/static/list/power_objects = list()
var/static/list/processing = list()
var/static/list/queue = list()
-
+ var/static/list/machinery = list()
+ var/static/list/machinery_by_type = list()
/datum/controller/subsystem/machines/Recover()
current_step = SSMACHINES_PIPENETS
queue.Cut()
-
/datum/controller/subsystem/machines/Initialize(start_uptime)
makepowernets()
setup_atmos_machinery(machinery)
@@ -66,7 +64,9 @@ SUBSYSTEM_DEF(machines)
/datum/controller/subsystem/machines/fire(resumed, no_mc_tick)
var/timer
- if (!resumed || current_step == SSMACHINES_PIPENETS)
+ if (!resumed)
+ current_step = SSMACHINES_PIPENETS
+ if (current_step == SSMACHINES_PIPENETS)
timer = world.tick_usage
process_pipenets(resumed, no_mc_tick)
cost_pipenets = MC_AVERAGE(cost_pipenets, (world.tick_usage - timer) * world.tick_lag)
@@ -98,6 +98,51 @@ SUBSYSTEM_DEF(machines)
return
current_step = SSMACHINES_PIPENETS
+/datum/controller/subsystem/machines/proc/register_machinery(obj/machinery/machine)
+ if(!machine)
+ CRASH("Null machinery was tried to be registered")
+
+ machinery += machine
+ LAZYADDASSOCLIST(machinery_by_type, machine.type, machine)
+ var/area/A = get_area(machine)
+ if(A)
+ LAZYADD(A.machinery_list, machine)
+
+/datum/controller/subsystem/machines/proc/unregister_machinery(obj/machinery/machine)
+ if(!machine)
+ CRASH("Null machinery was tried to be unregistered")
+
+ machinery -= machine
+ LAZYREMOVEASSOC(machinery_by_type, machine.type, machine)
+
+ var/area/A = get_area(machine)
+ if(A)
+ LAZYREMOVE(A.machinery_list, machine)
+
+/datum/controller/subsystem/machines/proc/get_machinery_of_type(obj/machinery/machinery_type)
+ if(istype(machinery_type))
+ var/obj/machinery/passed_machinery = machinery_type
+ machinery_type = passed_machinery.type
+
+ if(!ispath(machinery_type))
+ stack_trace("Non-machinery type passed in `/datum/controller/subsystem/machines/proc/get_machinery_of_type`")
+ return list()
+
+ if(machinery_type == /obj/machinery)
+ return get_all_machinery()
+
+ var/list/machinery = list()
+ for(var/type in typesof(machinery_type))
+ var/list/machinery_of_type = machinery_by_type[type]
+ if(!length(machinery_of_type))
+ continue
+
+ machinery += machinery_of_type
+
+ return machinery
+
+/datum/controller/subsystem/machines/proc/get_all_machinery()
+ return machinery.Copy()
/// Rebuilds power networks from scratch. Called by world initialization and elevators.
/datum/controller/subsystem/machines/proc/makepowernets()
@@ -117,7 +162,6 @@ SUBSYSTEM_DEF(machines)
/datum/controller/subsystem/machines/proc/setup_atmos_machinery(list/machines)
- set background = TRUE
var/list/atmos_machines = list()
for (var/obj/machinery/atmospherics/machine in machines)
atmos_machines += machine
@@ -179,8 +223,15 @@ SUBSYSTEM_DEF(machines)
machine.is_processing = null
processing -= machine
continue
- if (machine.ProcessAll(wait) == PROCESS_KILL)
- processing -= machine
+
+ if(machine.processing_flags & MACHINERY_PROCESS_COMPONENTS)
+ for(var/obj/item/stock_parts/part as anything in machine.processing_parts)
+ if(part.machine_process(machine) == PROCESS_KILL)
+ part.stop_processing()
+
+ if((machine.processing_flags & MACHINERY_PROCESS_SELF) && machine.Process(wait) == PROCESS_KILL)
+ STOP_PROCESSING_MACHINE(machine, MACHINERY_PROCESS_SELF)
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
diff --git a/code/controllers/subsystems/mapping.dm b/code/controllers/subsystems/mapping.dm
index c0569570ea6cb..bbe5086a092c7 100644
--- a/code/controllers/subsystems/mapping.dm
+++ b/code/controllers/subsystems/mapping.dm
@@ -21,13 +21,14 @@ SUBSYSTEM_DEF(mapping)
for(var/atype in subtypesof(/singleton/submap_archetype))
submap_archetypes[atype] = new atype
-
/datum/controller/subsystem/mapping/Recover()
flags |= SS_NO_INIT
map_templates = SSmapping.map_templates
space_ruins_templates = SSmapping.space_ruins_templates
exoplanet_ruins_templates = SSmapping.exoplanet_ruins_templates
away_sites_templates = SSmapping.away_sites_templates
+ submaps = SSmapping.submaps
+ submap_archetypes = SSmapping.submap_archetypes
/datum/controller/subsystem/mapping/proc/preloadTemplates(path = "maps/templates/") //see master controller setup
var/list/filelist = flist(path)
@@ -47,7 +48,7 @@ SUBSYSTEM_DEF(mapping)
var/list/banned_maps = list() + banned_exoplanet_dmms + banned_space_dmms + banned_away_site_dmms
- for(var/item in sortList(subtypesof(/datum/map_template), /proc/cmp_ruincost_priority))
+ for(var/item in sortList(subtypesof(/datum/map_template), GLOBAL_PROC_REF(cmp_ruincost_priority)))
var/datum/map_template/map_template_type = item
// screen out the abstract subtypes
if(!initial(map_template_type.id))
@@ -74,6 +75,7 @@ SUBSYSTEM_DEF(mapping)
away_sites_templates[MT.name] = MT
/proc/generateMapList(filename)
+ RETURN_TYPE(/list)
var/list/potentialMaps = list()
var/list/Lines = world.file2list(filename)
if(!length(Lines))
@@ -81,7 +83,7 @@ SUBSYSTEM_DEF(mapping)
for (var/t in Lines)
if (!t)
continue
- t = trim(t)
+ t = trimtext(t)
if (length(t) == 0)
continue
else if (copytext(t, 1, 2) == "#")
diff --git a/code/controllers/subsystems/misc.dm b/code/controllers/subsystems/misc.dm
index 80a726262baaf..0c81cd1117560 100644
--- a/code/controllers/subsystems/misc.dm
+++ b/code/controllers/subsystems/misc.dm
@@ -1,7 +1,6 @@
SUBSYSTEM_DEF(misc)
name = "Misc Updates"
wait = 30 SECONDS
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/static/list/queue = list()
var/static/stage = STAGE_TRADERS
var/static/cost_traders = 0
diff --git a/code/controllers/subsystems/misc_slow.dm b/code/controllers/subsystems/misc_slow.dm
index b80cc69d79496..f148f52bc7fad 100644
--- a/code/controllers/subsystems/misc_slow.dm
+++ b/code/controllers/subsystems/misc_slow.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(misc_slow)
name = "Misc Updates (Slow)"
flags = SS_NO_INIT
- runlevels = RUNLEVEL_LOBBY | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_GAME
wait = 5 MINUTES
/// The number of times dbcon can fail in a row before being considered dead
diff --git a/code/controllers/subsystems/mobs.dm b/code/controllers/subsystems/mobs.dm
index 50083d78fd274..2f721a00a25e2 100644
--- a/code/controllers/subsystems/mobs.dm
+++ b/code/controllers/subsystems/mobs.dm
@@ -1,9 +1,12 @@
SUBSYSTEM_DEF(mobs)
name = "Mobs"
- priority = SS_PRIORITY_MOB
+ priority = FIRE_PRIORITY_MOB
flags = SS_NO_INIT | SS_KEEP_TIMING
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 2 SECONDS
+ /// List of all mobs currently present in world
+ var/static/list/mob/all_mobs
+ /// List of all mobs currently present in world by type
+ var/static/list/mob/mobs_by_type
var/static/list/mob/mob_list = list()
var/static/list/mob/queue = list()
@@ -39,6 +42,44 @@ SUBSYSTEM_DEF(mobs)
return
queue.Cut()
+/datum/controller/subsystem/mobs/proc/register_mob(mob/mob_to_register)
+ if(!istype(mob_to_register))
+ CRASH("Invalid mob being registered: [mob_to_register]")
+
+ LAZYADD(all_mobs, mob_to_register)
+ LAZYADDASSOCLIST(mobs_by_type, mob_to_register.type, mob_to_register)
+
+/datum/controller/subsystem/mobs/proc/unregister_mob(mob/mob_to_unregister)
+ if(!istype(mob_to_unregister))
+ CRASH("Invalid mob being unregistered: [mob_to_unregister]")
+
+ LAZYREMOVE(all_mobs, mob_to_unregister)
+ LAZYREMOVEASSOC(mobs_by_type, mob_to_unregister.type, mob_to_unregister)
+
+/datum/controller/subsystem/mobs/proc/get_all_mobs()
+ return all_mobs.Copy()
+
+/datum/controller/subsystem/mobs/proc/get_mobs_of_type(mob/type)
+ if(istype(type))
+ var/mob/passed_mob = type
+ type = passed_mob.type
+
+ if(!ispath(type))
+ stack_trace("Non-mob type passed in `/datum/controller/subsystem/mobs/proc/get_mobs_of_type`")
+ return list()
+
+ if(type == /mob)
+ return get_all_mobs()
+
+ var/list/desired_mobs = list()
+ for(var/mob/mob_type as anything in typesof(type))
+ var/list/mobs_of_type = mobs_by_type[mob_type]
+ if(!length(mobs_of_type))
+ continue
+
+ desired_mobs += mobs_of_type
+
+ return desired_mobs
#define START_PROCESSING_MOB(MOB) \
if (MOB.is_processing) {\
@@ -56,12 +97,8 @@ else {\
if(MOB.is_processing == SSmobs) {\
MOB.is_processing = null;\
SSmobs.mob_list -= MOB;\
+ SSmobs.queue -= MOB;\
}\
else if (MOB.is_processing) {\
crash_with("Failed to stop processing mob. Being processed by [MOB.is_processing] instead.")\
}
-
-
-/mob/dview/Initialize()
- . = ..()
- STOP_PROCESSING_MOB(src)
diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm
new file mode 100644
index 0000000000000..baf3141f9a0b2
--- /dev/null
+++ b/code/controllers/subsystems/overlays.dm
@@ -0,0 +1,331 @@
+/// SSoverlays. Target the normal overlay cache.
+var/global/const/ATOM_ICON_CACHE_NORMAL = FLAG(0)
+
+/// SSoverlays. Target the protected overlay cache.
+var/global/const/ATOM_ICON_CACHE_PROTECTED = FLAG(1)
+
+/// SSoverlays. Target both normal and protected overlay caches.
+var/global/const/ATOM_ICON_CACHE_ALL = (ATOM_ICON_CACHE_NORMAL | ATOM_ICON_CACHE_PROTECTED)
+
+
+SUBSYSTEM_DEF(overlays)
+ name = "Overlays"
+ flags = SS_TICKER
+ wait = 1
+ priority = FIRE_PRIORITY_OVERLAYS
+ init_order = SS_INIT_OVERLAYS
+ /// The queue of atoms that need under/overlay updates.
+ VAR_PRIVATE/static/list/atom/queue = list()
+ /// A list([icon] = list([state] = [appearance], ...), ...) cache of appearances.
+ VAR_PRIVATE/static/list/state_cache = list()
+ /// A list([icon] = [appearance], ...) cache of appearances.
+ VAR_PRIVATE/static/list/icon_cache = list()
+ /// The number of appearances currently cached.
+ VAR_PRIVATE/static/cache_size = 0
+
+
+/datum/controller/subsystem/overlays/Recover()
+ LIST_RESIZE(queue, 0)
+ LIST_RESIZE(state_cache, 0)
+ LIST_RESIZE(icon_cache, 0)
+ cache_size = 0
+ for(var/atom/atom as anything in world)
+ if(atom.atom_flags & ATOM_AWAITING_OVERLAY_UPDATE)
+ SSoverlays.queue += atom
+
+ CHECK_TICK
+
+
+/datum/controller/subsystem/overlays/Initialize(start_uptime)
+ flush_queue()
+
+
+/datum/controller/subsystem/overlays/UpdateStat(time)
+ if (PreventUpdateStat(time))
+ return ..()
+ ..({"Queued Atoms: [length(queue)], Cache Size: [cache_size]"})
+
+
+/datum/controller/subsystem/overlays/fire(resumed)
+ var/queue_position = 1
+ while(length(queue) >= queue_position)
+ var/atom/atom_to_update = queue[queue_position]
+ if(!QDELETED(atom_to_update) && atom_to_update.atom_flags & ATOM_AWAITING_OVERLAY_UPDATE)
+ atom_to_update.UpdateOverlays()
+
+ queue_position++
+ if(MC_TICK_CHECK)
+ break
+
+ queue.Cut(1, queue_position)
+
+/datum/controller/subsystem/overlays/proc/flush_queue()
+ var/queue_position = 1
+ while(length(queue) >= queue_position)
+ process_atom_overlays_update(queue[queue_position])
+ queue_position++
+ CHECK_TICK
+
+ LIST_RESIZE(queue, 0)
+
+/datum/controller/subsystem/overlays/proc/process_atom_overlays_update(atom/atom_to_update)
+ if(!QDELETED(atom_to_update) && atom_to_update.atom_flags & ATOM_AWAITING_OVERLAY_UPDATE)
+ atom_to_update.UpdateOverlays()
+
+
+/datum/controller/subsystem/overlays/proc/GetStateAppearance(icon, state)
+ var/list/state_to_appearance = state_cache[icon]
+ if(!state_to_appearance)
+ state_to_appearance = list()
+ state_cache[icon] = state_to_appearance
+
+ var/state_appearance = state_to_appearance[state]
+ if(!state_appearance)
+ var/image/state_image = image(icon, null, state)
+ state_appearance = state_image.appearance
+ state_to_appearance[state] = state_appearance
+ cache_size++
+
+ return state_appearance
+
+
+/datum/controller/subsystem/overlays/proc/GetIconAppearance(icon)
+ var/icon_appearance = icon_cache[icon]
+ if (!icon_appearance)
+ var/image/icon_image = image(icon)
+ icon_appearance = icon_image.appearance
+ icon_cache[icon] = icon_appearance
+ cache_size++
+
+ return icon_appearance
+
+
+/datum/controller/subsystem/overlays/proc/getAppearanceList(atom/subject, list/sources)
+ if (!sources)
+ return list()
+
+ if (!islist(sources))
+ sources = list(sources)
+
+ var/list/result = list()
+ for (var/atom/source as anything in sources)
+ if(!source)
+ continue
+
+ if(istext(source))
+ result += GetStateAppearance(subject.icon, source)
+
+ else if(isicon(source))
+ result += GetIconAppearance(source)
+
+ else
+ if(isatom(source) && source.atom_flags & ATOM_AWAITING_OVERLAY_UPDATE)
+ source.UpdateOverlays()
+
+ if(!ispath(source))
+ result += source.appearance
+ else
+ var/image/image = source
+ result += image.appearance
+
+ return result
+
+/datum/controller/subsystem/overlays/proc/enque_atom_overlay_update(atom/atom_to_update)
+ if(!atom_to_update)
+ return
+
+ if(atom_to_update.atom_flags & ATOM_AWAITING_OVERLAY_UPDATE)
+ return
+
+ atom_to_update.atom_flags |= ATOM_AWAITING_OVERLAY_UPDATE
+ SSoverlays.queue += atom_to_update
+
+
+/// Immediately runs an overlay update.
+/atom/proc/ImmediateOverlayUpdate()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ UpdateOverlays()
+
+
+/**
+* Shared behavior for CutOverlays & CutUnderlays. Do not use directly.
+* null: nothing changed, do nothing
+* FALSE: update should be queued
+* TRUE: update should be queued, cache should be nulled
+*/
+/atom/proc/CutCacheBehavior(sources, cache)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ var/initial_length = length(cache)
+ if (!initial_length)
+ return
+ cache -= sources
+ var/after_length = length(cache)
+ if (!after_length)
+ return TRUE
+ if (initial_length > after_length)
+ return FALSE
+
+
+/// Enqueues the atom for an overlay update if not already queued
+/atom/proc/QueueOverlayUpdate()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SSoverlays.enque_atom_overlay_update(src)
+
+/// Builds the atom's overlay state from caches
+/atom/proc/UpdateOverlays()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ atom_flags &= ~ATOM_AWAITING_OVERLAY_UPDATE
+ if (QDELING(src))
+ LIST_RESIZE(overlays, 0)
+ return
+
+ if (length(atom_protected_overlay_cache))
+ if (length(atom_overlay_cache))
+ overlays = atom_protected_overlay_cache + atom_overlay_cache
+ else
+ overlays = atom_protected_overlay_cache
+ else if (length(atom_overlay_cache))
+ overlays = atom_overlay_cache
+ else
+ LIST_RESIZE(overlays, 0)
+
+
+/// Clears the atom's overlay cache(s) and queues an update if needed. Use CLEAR_TARGET_* flags.
+/atom/proc/ClearOverlays(cache_target = ATOM_ICON_CACHE_NORMAL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if (cache_target & ATOM_ICON_CACHE_PROTECTED)
+ if (!atom_protected_overlay_cache)
+ return
+ LAZYCLEARLIST(atom_protected_overlay_cache)
+ QueueOverlayUpdate()
+ if (cache_target & ATOM_ICON_CACHE_NORMAL)
+ if (!atom_overlay_cache)
+ return
+ LAZYCLEARLIST(atom_overlay_cache)
+ QueueOverlayUpdate()
+
+
+/**
+ * Adds specific overlay(s) to the atom.
+ * It is designed so any of the types allowed to be added to /atom/overlays can be added here too. More details below.
+ *
+ * @param sources The overlay(s) to add. These may be
+ * - A string: In which case it is treated as an icon_state of the atom's icon.
+ * - An icon: It is treated as an icon.
+ * - An atom: Its own overlays are compiled and then it's appearance is added. (Meaning its current apperance is frozen).
+ * - An image: Image's apperance is added (i.e. subsequently editing the image will not edit the overlay)
+ * - A type path: Added to overlays as is. Does whatever it is BYOND does when you add paths to overlays.
+ * - Or a list containing any of the above.
+ * @param cache_target If ATOM_ICON_CACHE_PROTECTED, add to the protected cache instead of normal.
+ */
+/atom/proc/AddOverlays(sources, cache_target = ATOM_ICON_CACHE_NORMAL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if (!sources)
+ return
+ sources = SSoverlays.getAppearanceList(src, sources)
+ if (!length(sources))
+ return
+ if (cache_target & ATOM_ICON_CACHE_PROTECTED)
+ if (atom_protected_overlay_cache)
+ atom_protected_overlay_cache += sources
+ else
+ atom_protected_overlay_cache = sources
+ else if (atom_overlay_cache)
+ atom_overlay_cache += sources
+ else
+ atom_overlay_cache = sources
+ QueueOverlayUpdate()
+
+
+/**
+ * Removes specific overlay(s) from the atom's normal or protected overlay cache and queue an update.
+ *
+ * @param overlays The overlays to removed. See AddOverlays for legal source types.
+ * @param cache_target A mask of ICON_CACHE_TARGET_*.
+ */
+/atom/proc/CutOverlays(sources, cache_target = ATOM_ICON_CACHE_NORMAL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if (!sources)
+ return
+
+ sources = SSoverlays.getAppearanceList(src, sources)
+ if (!length(sources))
+ return
+
+ var/update
+ if (cache_target & ATOM_ICON_CACHE_PROTECTED)
+ var/outcome = CutCacheBehavior(sources, atom_protected_overlay_cache)
+ if (!isnull(outcome))
+ update = TRUE
+ if (outcome == TRUE)
+ atom_protected_overlay_cache = null
+
+ if (cache_target & ATOM_ICON_CACHE_NORMAL)
+ var/outcome = CutCacheBehavior(sources, atom_overlay_cache)
+ if (!isnull(outcome))
+ update = TRUE
+ if (outcome == TRUE)
+ atom_overlay_cache = null
+
+ if (update)
+ QueueOverlayUpdate()
+
+
+/// AddOverlays with ClearOverlays first. See AddOverlays for behavior.
+/atom/proc/SetOverlays(sources, cache_target = ATOM_ICON_CACHE_NORMAL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ ClearOverlays(cache_target)
+ AddOverlays(sources, cache_target)
+
+
+/**
+ * Copy the overlays from another atom.
+ *
+ * @param other The atom to copy overlays from.
+ * @param clear If TRUE, clear before adding other's overlays.
+ * @param cache_target A mask of ICON_CACHE_TARGET_* indicating what to copy.
+ */
+/atom/proc/CopyOverlays(atom/other, clear, cache_target = ATOM_ICON_CACHE_NORMAL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if (clear)
+ ClearOverlays(cache_target)
+ if (!istype(other))
+ return
+ if (cache_target & ATOM_ICON_CACHE_PROTECTED)
+ AddOverlays(other.atom_protected_overlay_cache, ATOM_ICON_CACHE_PROTECTED)
+ if (cache_target & ATOM_ICON_CACHE_NORMAL)
+ AddOverlays(other.atom_overlay_cache, ATOM_ICON_CACHE_NORMAL)
+
+
+// Skin-deep API parity for images.
+// Reference for permitted types.
+
+/// Adds sources to the image's overlays.
+/image/proc/AddOverlays(sources)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ overlays += sources
+
+
+/// Removes sources from the image's overlays.
+/image/proc/CutOverlays(sources)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ overlays -= sources
+
+
+/// Removes all of the image's overlays.
+/image/proc/ClearOverlays()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ LIST_RESIZE(overlays, 0)
+
+
+/// Copies the overlays from the atom other, clearing first if set, and using the caches indicated.
+/image/proc/CopyOverlays(atom/other, clear, cache_target = ATOM_ICON_CACHE_ALL)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if (clear)
+ LIST_RESIZE(overlays, 0)
+ if (!istype(other))
+ return
+ if (cache_target & ATOM_ICON_CACHE_PROTECTED)
+ overlays |= other.atom_protected_overlay_cache
+ if (cache_target & ATOM_ICON_CACHE_NORMAL)
+ overlays |= other.atom_overlay_cache
diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm
index ee9ec06eeccba..114143e6e47b9 100644
--- a/code/controllers/subsystems/ping.dm
+++ b/code/controllers/subsystems/ping.dm
@@ -1,27 +1,40 @@
+/*!
+ * Copyright (c) 2022 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
SUBSYSTEM_DEF(ping)
name = "Ping"
+ priority = FIRE_PRIORITY_PING
+ init_order = SS_INIT_PING
+ wait = 4 SECONDS
flags = SS_NO_INIT
- runlevels = RUNLEVELS_ALL
- wait = 30 SECONDS
- var/static/list/datum/chatOutput/chats = list()
- var/static/list/datum/chatOutput/queue = list()
+ runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ var/list/currentrun = list()
-/datum/controller/subsystem/ping/fire(resumed, no_mc_tick)
- if (!resumed)
- if (!length(chats))
- return
- queue = chats.Copy()
- var/cut_until = 1
- for (var/datum/chatOutput/chat as anything in chats)
- ++cut_until
- if (QDELETED(chat))
- continue
- if (chat.loaded && !chat.broken)
- chat.updatePing()
- if (no_mc_tick)
- CHECK_TICK
- else if (MC_TICK_CHECK)
- queue.Cut(1, cut_until)
+/datum/controller/subsystem/ping/StartLoadingMap()
+ ..("P:[length(GLOB.clients)]")
+
+/datum/controller/subsystem/ping/fire(resumed = FALSE)
+ // Prepare the new batch of clients
+ if(!resumed)
+ src.currentrun = GLOB.clients.Copy()
+
+ // De-reference the list for sanic speeds
+ var/list/currentrun = src.currentrun
+
+ while(length(currentrun))
+ var/client/client = currentrun[length(currentrun)]
+ LIST_DEC(currentrun)
+
+ if(client?.tgui_panel?.is_ready())
+ // Send a soft ping
+ client.tgui_panel.window.send_message("ping/soft", list(
+ // Slightly less than the subsystem timer (somewhat arbitrary)
+ // to prevent incoming pings from resetting the afk state
+ "afk" = client.is_afk(3.5 SECONDS),
+ ))
+
+ if(MC_TICK_CHECK)
return
- queue.Cut()
diff --git a/code/controllers/subsystems/plants.dm b/code/controllers/subsystems/plants.dm
index e15ffbd158f25..b13bfe292cda9 100644
--- a/code/controllers/subsystems/plants.dm
+++ b/code/controllers/subsystems/plants.dm
@@ -1,7 +1,6 @@
SUBSYSTEM_DEF(plants)
name = "Plants"
- priority = SS_PRIORITY_PLANTS
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ priority = FIRE_PRIORITY_PLANTS
flags = SS_BACKGROUND | SS_POST_FIRE_TIMING
init_order = SS_INIT_PLANTS
wait = 5 SECONDS
@@ -74,7 +73,7 @@ SUBSYSTEM_DEF(plants)
/datum/controller/subsystem/plants/Initialize(start_uptime)
- for (var/state in icon_states('icons/obj/hydroponics_growing.dmi'))
+ for (var/state in ICON_STATES('icons/obj/flora/hydroponics_growing.dmi'))
var/split = findtext_char(state, "-")
if (!split)
continue
@@ -86,7 +85,7 @@ SUBSYSTEM_DEF(plants)
if (!plant_sprites[plant] || plant_sprites[plant] < growth_level)
plant_sprites[plant] = growth_level
- for (var/state in icon_states('icons/obj/hydroponics_products.dmi'))
+ for (var/state in ICON_STATES('icons/obj/flora/hydroponics_products.dmi'))
var/split = findtext_char(state, "-")
if (!split)
continue
@@ -103,8 +102,9 @@ SUBSYSTEM_DEF(plants)
var/list/gene_datums = GET_SINGLETON_SUBTYPE_MAP(/singleton/plantgene)
var/list/used_masks = list()
+ var/list/plant_genes = shuffle(ALL_GENES)
- for (var/tag in ALL_GENES)
+ for (var/tag in plant_genes)
var/mask = uppertext(num2hex(rand(0, 0xFF)))
while (mask in used_masks)
mask = uppertext(num2hex(rand(0, 0xFF)))
diff --git a/code/controllers/subsystems/presence.dm b/code/controllers/subsystems/presence.dm
index 5077464c6b004..abe9b7199ee05 100644
--- a/code/controllers/subsystems/presence.dm
+++ b/code/controllers/subsystems/presence.dm
@@ -3,37 +3,37 @@
/// Builds a list of z-level populations to allow for easier pauses on processing when nobody is around to care
SUBSYSTEM_DEF(presence)
name = "Player Presence"
- priority = SS_PRIORITY_PRESENCE
- runlevels = RUNLEVEL_GAME
+ priority = FIRE_PRIORITY_PRESENCE
wait = 2 SECONDS
var/static/list/levels = list()
var/static/list/queue = list()
- var/static/list/build
-
+ var/static/list/build = list()
/datum/controller/subsystem/presence/Recover()
queue.Cut()
build.Cut()
+/datum/controller/subsystem/presence/UpdateStat(text)
+ return ..("Queue: [length(queue)]")
/datum/controller/subsystem/presence/fire(resume, no_mc_tick)
- if (!resume)
+ if(!resume)
queue = GLOB.player_list.Copy()
build = list()
- var/cut_until = 1
- for (var/mob/living/player as anything in GLOB.living_players)
- ++cut_until
- if (QDELETED(player) || player.stat == DEAD)
- continue
- ++build["[get_z(player)]"]
- if (no_mc_tick)
+
+ while(length(queue))
+ var/mob/living/player_to_check = queue[length(queue)]
+ if(!QDELETED(player_to_check) && player_to_check.stat < DEAD)
+ build["[get_z(player_to_check)]"]++
+
+ LIST_DEC(queue)
+
+ if(no_mc_tick)
CHECK_TICK
- else if (MC_TICK_CHECK)
- queue.Cut(1, cut_until)
+ else if(MC_TICK_CHECK)
return
- levels = build
- queue.Cut()
+ levels = build
#ifndef UNIT_TEST
diff --git a/code/controllers/subsystems/processing/beach.dm b/code/controllers/subsystems/processing/beach.dm
new file mode 100644
index 0000000000000..e0bd25bf94333
--- /dev/null
+++ b/code/controllers/subsystems/processing/beach.dm
@@ -0,0 +1,6 @@
+/// TODO: Create component for playing ambience
+/// And probably replace it with looping sound or create generic subsystem
+/// Subsystem that process `/area/beach`
+PROCESSING_SUBSYSTEM_DEF(beach)
+ name = "Beach"
+ wait = 6 SECONDS
diff --git a/code/controllers/subsystems/processing/circuit.dm b/code/controllers/subsystems/processing/circuit.dm
index eb074d4a51157..ed6222760f2ca 100644
--- a/code/controllers/subsystems/processing/circuit.dm
+++ b/code/controllers/subsystems/processing/circuit.dm
@@ -2,7 +2,7 @@
PROCESSING_SUBSYSTEM_DEF(circuit)
name = "Circuit"
- priority = SS_PRIORITY_CIRCUIT
+ priority = FIRE_PRIORITY_CIRCUIT
init_order = SS_INIT_CIRCUIT
flags = SS_BACKGROUND
diff --git a/code/controllers/subsystems/processing/graphs.dm b/code/controllers/subsystems/processing/graphs.dm
index 85e8c00fca6f5..f65eba6471e1b 100644
--- a/code/controllers/subsystems/processing/graphs.dm
+++ b/code/controllers/subsystems/processing/graphs.dm
@@ -1,3 +1,3 @@
PROCESSING_SUBSYSTEM_DEF(graphs_process)
name = "Graphs (Process)"
- priority = SS_PRIORITY_GRAPH
+ priority = FIRE_PRIORITY_GRAPH
diff --git a/code/controllers/subsystems/processing/icon_updates.dm b/code/controllers/subsystems/processing/icon_updates.dm
index c328b05ed02df..5ce611a23d7f2 100644
--- a/code/controllers/subsystems/processing/icon_updates.dm
+++ b/code/controllers/subsystems/processing/icon_updates.dm
@@ -1,42 +1,65 @@
-PROCESSING_SUBSYSTEM_DEF(icon_update)
+SUBSYSTEM_DEF(icon_update)
name = "Icon Updates"
wait = 1 // ticks
flags = SS_TICKER
- priority = SS_PRIORITY_ICON_UPDATE
+ priority = FIRE_PRIORITY_ICON_UPDATE
init_order = SS_INIT_ICON_UPDATE
- var/list/queue = list()
+ VAR_PRIVATE/static/list/queue = list()
-/datum/controller/subsystem/processing/icon_update/Initialize(start_uptime)
- fire(FALSE, TRUE)
+/datum/controller/subsystem/icon_update/Recover()
+ LIST_RESIZE(queue, 0)
+ queue = list()
-/datum/controller/subsystem/processing/icon_update/fire(resumed = FALSE, no_mc_tick = FALSE)
- var/list/curr = queue
+/datum/controller/subsystem/icon_update/UpdateStat(time)
+ if (PreventUpdateStat(time))
+ return ..()
+ ..("queue: [length(queue)]")
- if (!length(curr))
- suspend()
- return
- while (length(curr))
- var/atom/A = curr[length(curr)]
- var/list/argv = curr[A]
- LIST_DEC(curr)
+/datum/controller/subsystem/icon_update/Initialize(start_uptime)
+ flush_queue()
+
+/datum/controller/subsystem/icon_update/fire(resumed)
+ var/queue_position = 1
+ while(length(queue) >= queue_position)
+ process_atom_icon_update(queue[queue_position])
+ queue_position++
+ if(MC_TICK_CHECK)
+ break
+
+ queue.Cut(1, queue_position)
+
+/datum/controller/subsystem/icon_update/proc/flush_queue()
+ var/queue_position = 1
+ while(length(queue) >= queue_position)
+ process_atom_icon_update(queue[queue_position])
+ queue_position++
+ CHECK_TICK
- if (islist(argv))
- A.update_icon(arglist(argv))
- else
- A.update_icon()
+ LIST_RESIZE(queue, 0)
- if (no_mc_tick)
- CHECK_TICK
- else if (MC_TICK_CHECK)
- return
+/datum/controller/subsystem/icon_update/proc/process_atom_icon_update(atom/atom_to_update)
+ if(QDELETED(atom_to_update))
+ return
+
+ var/list/params = queue[atom_to_update]
+ if (islist(params))
+ atom_to_update.update_icon(arglist(params))
+ else
+ atom_to_update.update_icon()
+
+/datum/controller/subsystem/icon_update/proc/enque_atom_icon_update(atom/atom_to_update, arguments)
+ SSicon_update.queue[atom_to_update] = arguments
/**
* Adds the atom to the icon_update subsystem to be queued for icon updates. Use this if you're going to be pushing a
* lot of icon updates at once.
*/
/atom/proc/queue_icon_update(...)
- SSicon_update.queue[src] = length(args) ? args : TRUE
- SSicon_update.wake()
+ SSicon_update.enque_atom_icon_update(src, length(args) ? args : TRUE)
+
+/hook/game_ready/proc/flush_icon_update_queue()
+ SSicon_update.flush_queue()
+ return TRUE
diff --git a/code/controllers/subsystems/processing/nano.dm b/code/controllers/subsystems/processing/nano.dm
index 0f6ebe49feb53..3b760d136903b 100644
--- a/code/controllers/subsystems/processing/nano.dm
+++ b/code/controllers/subsystems/processing/nano.dm
@@ -1,23 +1,23 @@
PROCESSING_SUBSYSTEM_DEF(nano)
name = "NanoUI"
- priority = SS_PRIORITY_NANO
+ priority = FIRE_PRIORITY_NANO
wait = 2 SECONDS
// a list of current open /nanoui UIs, grouped by src_object and ui_key
var/list/open_uis = list()
- /**
- * Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data
- *
- * @param user /mob The mob who opened/owns the ui
- * @param src_object /obj|/mob The obj or mob which the ui belongs to
- * @param ui_key string A string key used for the ui
- * @param ui /datum/nanoui An existing instance of the ui (can be null)
- * @param data list The data to be passed to the ui, if it exists
- * @param force_open boolean The ui is being forced to (re)open, so close ui if it exists (instead of updating)
- *
- * @return /nanoui Returns the found ui, for null if none exists
- */
+/**
+ * Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data
+ *
+ * @param user /mob The mob who opened/owns the ui
+ * @param src_object /obj|/mob The obj or mob which the ui belongs to
+ * @param ui_key string A string key used for the ui
+ * @param ui /datum/nanoui An existing instance of the ui (can be null)
+ * @param data list The data to be passed to the ui, if it exists
+ * @param force_open boolean The ui is being forced to (re)open, so close ui if it exists (instead of updating)
+ *
+ * @return /nanoui Returns the found ui, for null if none exists
+ */
/datum/controller/subsystem/processing/nano/proc/try_update_ui(mob/user, src_object, ui_key, datum/nanoui/ui, data, force_open = 0)
if (!ui) // no ui has been passed, so we'll search for one
ui = get_open_ui(user, src_object, ui_key)
@@ -27,15 +27,15 @@ PROCESSING_SUBSYSTEM_DEF(nano)
force_open ? ui.reinitialise(new_initial_data=data) : ui.push_data(data)
return ui
- /**
- * Get an open /nanoui ui for the current user, src_object and ui_key
- *
- * @param user /mob The mob who opened/owns the ui
- * @param src_object /obj|/mob The obj or mob which the ui belongs to
- * @param ui_key string A string key used for the ui
- *
- * @return /nanoui Returns the found ui, or null if none exists
- */
+/**
+ * Get an open /nanoui ui for the current user, src_object and ui_key
+ *
+ * @param user /mob The mob who opened/owns the ui
+ * @param src_object /obj|/mob The obj or mob which the ui belongs to
+ * @param ui_key string A string key used for the ui
+ *
+ * @return /nanoui Returns the found ui, or null if none exists
+ */
/datum/controller/subsystem/processing/nano/proc/get_open_ui(mob/user, src_object, ui_key)
var/src_object_key = "\ref[src_object]"
if (!open_uis[src_object_key] || !open_uis[src_object_key][ui_key])
@@ -45,13 +45,13 @@ PROCESSING_SUBSYSTEM_DEF(nano)
if (ui.user == user)
return ui
- /**
- * Update all /nanoui uis attached to src_object
- *
- * @param src_object /obj|/mob The obj or mob which the uis are attached to
- *
- * @return int The number of uis updated
- */
+/**
+ * Update all /nanoui uis attached to src_object
+ *
+ * @param src_object /obj|/mob The obj or mob which the uis are attached to
+ *
+ * @return int The number of uis updated
+ */
/datum/controller/subsystem/processing/nano/proc/update_uis(src_object)
. = 0
var/src_object_key = "\ref[src_object]"
@@ -66,13 +66,13 @@ PROCESSING_SUBSYSTEM_DEF(nano)
else
ui.close()
- /**
- * Close all /nanoui uis attached to src_object
- *
- * @param src_object /obj|/mob The obj or mob which the uis are attached to
- *
- * @return int The number of uis close
- */
+/**
+ * Close all /nanoui uis attached to src_object
+ *
+ * @param src_object /obj|/mob The obj or mob which the uis are attached to
+ *
+ * @return int The number of uis close
+ */
/datum/controller/subsystem/processing/nano/proc/close_uis(src_object)
. = 0
var/src_object_key = "\ref[src_object]"
@@ -84,15 +84,15 @@ PROCESSING_SUBSYSTEM_DEF(nano)
ui.close() // If it's missing src_object or user, we want to close it even more.
.++
- /**
- * Update /nanoui uis belonging to user
- *
- * @param user /mob The mob who owns the uis
- * @param src_object /obj|/mob If src_object is provided, only update uis which are attached to src_object (optional)
- * @param ui_key string If ui_key is provided, only update uis with a matching ui_key (optional)
- *
- * @return int The number of uis updated
- */
+/**
+ * Update /nanoui uis belonging to user
+ *
+ * @param user /mob The mob who owns the uis
+ * @param src_object /obj|/mob If src_object is provided, only update uis which are attached to src_object (optional)
+ * @param ui_key string If ui_key is provided, only update uis with a matching ui_key (optional)
+ *
+ * @return int The number of uis updated
+ */
/datum/controller/subsystem/processing/nano/proc/update_user_uis(mob/user, src_object, ui_key)
. = 0
if (!length(user.open_uis))
@@ -103,15 +103,15 @@ PROCESSING_SUBSYSTEM_DEF(nano)
ui.try_update(1)
.++
- /**
- * Close /nanoui uis belonging to user
- *
- * @param user /mob The mob who owns the uis
- * @param src_object /obj|/mob If src_object is provided, only close uis which are attached to src_object (optional)
- * @param ui_key string If ui_key is provided, only close uis with a matching ui_key (optional)
- *
- * @return int The number of uis closed
- */
+/**
+ * Close /nanoui uis belonging to user
+ *
+ * @param user /mob The mob who owns the uis
+ * @param src_object /obj|/mob If src_object is provided, only close uis which are attached to src_object (optional)
+ * @param ui_key string If ui_key is provided, only close uis with a matching ui_key (optional)
+ *
+ * @return int The number of uis closed
+ */
/datum/controller/subsystem/processing/nano/proc/close_user_uis(mob/user, src_object, ui_key)
. = 0
if (!length(user.open_uis))
@@ -122,14 +122,14 @@ PROCESSING_SUBSYSTEM_DEF(nano)
ui.close()
.++
- /**
- * Add a /nanoui ui to the list of open uis
- * This is called by the /nanoui open() proc
- *
- * @param ui /nanoui The ui to add
- *
- * @return nothing
- */
+/**
+ * Add a /nanoui ui to the list of open uis
+ * This is called by the /nanoui open() proc
+ *
+ * @param ui /nanoui The ui to add
+ *
+ * @return nothing
+ */
/datum/controller/subsystem/processing/nano/proc/ui_opened(datum/nanoui/ui)
var/src_object_key = "\ref[ui.src_object]"
LAZYINITLIST(open_uis[src_object_key])
@@ -137,14 +137,14 @@ PROCESSING_SUBSYSTEM_DEF(nano)
LAZYDISTINCTADD(ui.user.open_uis, ui)
START_PROCESSING(SSnano, ui)
- /**
- * Remove a /nanoui ui from the list of open uis
- * This is called by the /nanoui close() proc
- *
- * @param ui /nanoui The ui to remove
- *
- * @return int 0 if no ui was removed, 1 if removed successfully
- */
+/**
+ * Remove a /nanoui ui from the list of open uis
+ * This is called by the /nanoui close() proc
+ *
+ * @param ui /nanoui The ui to remove
+ *
+ * @return int 0 if no ui was removed, 1 if removed successfully
+ */
/datum/controller/subsystem/processing/nano/proc/ui_closed(datum/nanoui/ui)
var/src_object_key = "\ref[ui.src_object]"
if (!open_uis[src_object_key] || !open_uis[src_object_key][ui.ui_key])
@@ -160,26 +160,26 @@ PROCESSING_SUBSYSTEM_DEF(nano)
open_uis -= src_object_key
return 1
- /**
- * This is called on user logout
- * Closes/clears all uis attached to the user's /mob
- *
- * @param user /mob The user's mob
- *
- * @return nothing
- */
+/**
+ * This is called on user logout
+ * Closes/clears all uis attached to the user's /mob
+ *
+ * @param user /mob The user's mob
+ *
+ * @return nothing
+ */
/datum/controller/subsystem/processing/nano/proc/user_logout(mob/user)
return close_user_uis(user)
- /**
- * This is called when a player transfers from one mob to another
- * Transfers all open UIs to the new mob
- *
- * @param oldMob /mob The user's old mob
- * @param newMob /mob The user's new mob
- *
- * @return nothing
- */
+/**
+ * This is called when a player transfers from one mob to another
+ * Transfers all open UIs to the new mob
+ *
+ * @param oldMob /mob The user's old mob
+ * @param newMob /mob The user's new mob
+ *
+ * @return nothing
+ */
/datum/controller/subsystem/processing/nano/proc/user_transferred(mob/oldMob, mob/newMob)
if (!oldMob || !oldMob.open_uis)
return 0 // has no open uis
diff --git a/code/controllers/subsystems/processing/obj.dm b/code/controllers/subsystems/processing/obj.dm
index e0b54fc0c8b53..c6576fadde7c4 100644
--- a/code/controllers/subsystems/processing/obj.dm
+++ b/code/controllers/subsystems/processing/obj.dm
@@ -1,3 +1,2 @@
PROCESSING_SUBSYSTEM_DEF(obj)
name = "Objs"
- priority = SS_PRIORITY_OBJECTS
diff --git a/code/controllers/subsystems/processing/overmap.dm b/code/controllers/subsystems/processing/overmap.dm
new file mode 100644
index 0000000000000..7bccf77dba6f3
--- /dev/null
+++ b/code/controllers/subsystems/processing/overmap.dm
@@ -0,0 +1,7 @@
+PROCESSING_SUBSYSTEM_DEF(overmap)
+
+/datum/controller/subsystem/processing/overmap
+ name = "Overmap"
+ priority = FIRE_PRIORITY_OVERMAP
+ flags = SS_TICKER|SS_NO_INIT
+ wait = 7
diff --git a/code/controllers/subsystems/processing/processing.dm b/code/controllers/subsystems/processing/processing.dm
index f78dcd6f454d1..0a3847b2f2307 100644
--- a/code/controllers/subsystems/processing/processing.dm
+++ b/code/controllers/subsystems/processing/processing.dm
@@ -2,13 +2,13 @@
SUBSYSTEM_DEF(processing)
name = "Processing"
- priority = SS_PRIORITY_PROCESSING
+ priority = FIRE_PRIORITY_PROCESSING
flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
wait = 1 SECOND
var/list/processing = list()
var/list/current_run = list()
- var/process_proc = /datum/proc/Process
+ var/process_proc = TYPE_PROC_REF(/datum, Process)
var/debug_last_thing
var/debug_original_process_proc // initial() does not work with procs
@@ -47,7 +47,7 @@ SUBSYSTEM_DEF(processing)
debug_original_process_proc = null
else
debug_original_process_proc = process_proc
- process_proc = /datum/proc/DebugSubsystemProcess
+ process_proc = TYPE_PROC_REF(/datum, DebugSubsystemProcess)
to_chat(usr, "[name] - Debug mode [debug_original_process_proc ? "en" : "dis"]abled")
diff --git a/code/controllers/subsystems/processing/psi.dm b/code/controllers/subsystems/processing/psi.dm
index 94396a6fbd5bd..88c8fadd79167 100644
--- a/code/controllers/subsystems/processing/psi.dm
+++ b/code/controllers/subsystems/processing/psi.dm
@@ -2,7 +2,7 @@ GLOBAL_LIST_INIT(psychic_ranks_to_strings, list("Latent", "Operant", "Masterclas
PROCESSING_SUBSYSTEM_DEF(psi)
name = "Psychics"
- priority = SS_PRIORITY_PSYCHICS
+ priority = FIRE_PRIORITY_PSYCHICS
flags = SS_POST_FIRE_TIMING | SS_BACKGROUND
var/list/faculties_by_id = list()
diff --git a/code/controllers/subsystems/processing/temperature.dm b/code/controllers/subsystems/processing/temperature.dm
index 377c828366020..dfd2325ca518f 100644
--- a/code/controllers/subsystems/processing/temperature.dm
+++ b/code/controllers/subsystems/processing/temperature.dm
@@ -1,5 +1,5 @@
PROCESSING_SUBSYSTEM_DEF(temperature)
name = "Temperature"
- priority = SS_PRIORITY_TEMPERATURE
+ priority = FIRE_PRIORITY_TEMPERATURE
wait = 5 SECONDS
- process_proc = /atom/proc/ProcessAtomTemperature
+ process_proc = TYPE_PROC_REF(/atom, ProcessAtomTemperature)
diff --git a/code/controllers/subsystems/processing/turf.dm b/code/controllers/subsystems/processing/turf.dm
index 2988f40311c5e..ce8a23321c9b4 100644
--- a/code/controllers/subsystems/processing/turf.dm
+++ b/code/controllers/subsystems/processing/turf.dm
@@ -2,4 +2,4 @@
PROCESSING_SUBSYSTEM_DEF(turf)
name = "Turfs"
- priority = SS_PRIORITY_TURF
\ No newline at end of file
+ priority = FIRE_PRIORITY_TURF
diff --git a/code/controllers/subsystems/processing/vines.dm b/code/controllers/subsystems/processing/vines.dm
index 56e24d30f2778..14d850c96239f 100644
--- a/code/controllers/subsystems/processing/vines.dm
+++ b/code/controllers/subsystems/processing/vines.dm
@@ -1,13 +1,5 @@
// This does NOT process the type of plant that's in a tray. It only does the spreading vines like kudzu.
PROCESSING_SUBSYSTEM_DEF(vines)
name = "Vines"
- priority = SS_PRIORITY_VINES
- runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
- wait = 80
-
- process_proc = /obj/effect/vine/Process
-
- var/list/vine_list
-
-/datum/controller/subsystem/processing/vines/PreInit()
- vine_list = processing // Simply setups a more recognizable var name than "processing"
+ priority = FIRE_PRIORITY_VINES
+ wait = 8 SECONDS
diff --git a/code/controllers/subsystems/radiation.dm b/code/controllers/subsystems/radiation.dm
index cb039c29dcdf1..97829ca6391e0 100644
--- a/code/controllers/subsystems/radiation.dm
+++ b/code/controllers/subsystems/radiation.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(radiation)
name = "Radiation"
wait = 2 SECONDS
- priority = SS_PRIORITY_RADIATION
+ priority = FIRE_PRIORITY_RADIATION
flags = SS_NO_INIT
var/list/sources = list() // all radiation source datums
diff --git a/code/controllers/subsystems/roundend.dm b/code/controllers/subsystems/roundend.dm
new file mode 100644
index 0000000000000..a692ff318c11b
--- /dev/null
+++ b/code/controllers/subsystems/roundend.dm
@@ -0,0 +1,68 @@
+SUBSYSTEM_DEF(roundend)
+ name = "Round End"
+ wait = 30 SECONDS
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVEL_GAME
+
+ /// The time in minutes when the round will be ended.
+ var/static/max_length
+
+ /// The next time in minutes to check whether a round is empty.
+ var/static/empty_check
+
+ /// The next time in minutes to start a round end vote.
+ var/static/vote_check
+
+ /// The cached duration to the next vote.
+ var/static/vote_cache
+
+
+/datum/controller/subsystem/roundend/UpdateStat(time)
+ if (PreventUpdateStat(time))
+ return ..()
+ if (GAME_STATE < RUNLEVEL_POSTGAME)
+ var/round_time = round_duration_in_ticks / 600
+ var/show_max
+ if (max_length)
+ show_max = max(round(max_length - round_time, 0.1), 0)
+ var/show_empty
+ if (empty_check)
+ show_empty = max(round(empty_check - round_time, 0.1), 0)
+ var/show_vote
+ if (vote_check)
+ show_vote = max(round(vote_check - round_time, 0.1), 0)
+ ..({"\n\
+ Max Time: [isnull(show_max) ? "Off" : "[show_max]m"]\n\
+ Empty End: [isnull(show_empty) ? "Off" : "[show_empty]m"]\n\
+ Next Vote: [isnull(show_vote) ? "Off" : "[show_vote]m"]\
+ "})
+ else
+ ..("Game Finished")
+
+
+/datum/controller/subsystem/roundend/Initialize(start_uptime)
+ max_length = config.maximum_round_length
+ empty_check = config.empty_round_check_interval
+ vote_check = config.vote_autotransfer_initial
+
+
+/datum/controller/subsystem/roundend/fire(resumed, no_mc_tick)
+ var/time = round_duration_in_ticks / 600
+ if (max_length && time > max_length)
+ if (evacuation_controller.is_idle())
+ init_autotransfer()
+ return
+ if (empty_check && time > empty_check)
+ empty_check += config.empty_round_check_interval
+ if (!length(GLOB.living_players))
+ SSticker.forced_end = TRUE
+ return
+ if (vote_check)
+ vote_cache = round(max(vote_check - time, 0), 0.1)
+ if (vote_cache > 0)
+ return
+ SSvote.initiate_vote(/datum/vote/transfer, null, TRUE)
+ if (config.vote_autotransfer_interval)
+ vote_check += config.vote_autotransfer_interval
+ else
+ vote_check = 0
diff --git a/code/controllers/subsystems/shuttle.dm b/code/controllers/subsystems/shuttle.dm
index 9bc61f97332c3..a372783d5c0da 100644
--- a/code/controllers/subsystems/shuttle.dm
+++ b/code/controllers/subsystems/shuttle.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(shuttle)
name = "Shuttle"
wait = 2 SECONDS
- priority = SS_PRIORITY_SHUTTLE
+ priority = FIRE_PRIORITY_SHUTTLE
init_order = SS_INIT_SHUTTLE //Should be initialized after all maploading is over and atoms are initialized, to ensure that landmarks have been initialized.
var/overmap_halted = FALSE //Whether ships can move on the overmap; used for adminbus.
@@ -66,18 +66,18 @@ SUBSYSTEM_DEF(shuttle)
initialize_sector(sector)
sectors_to_initialize = null
-/datum/controller/subsystem/shuttle/proc/register_landmark(shuttle_landmark_tag, obj/effect/shuttle_landmark/shuttle_landmark)
+/datum/controller/subsystem/shuttle/proc/register_landmark(shuttle_landmark_tag, obj/shuttle_landmark/shuttle_landmark)
if (registered_shuttle_landmarks[shuttle_landmark_tag])
CRASH("Attempted to register shuttle landmark with tag [shuttle_landmark_tag], but it is already registered!")
if (istype(shuttle_landmark))
registered_shuttle_landmarks[shuttle_landmark_tag] = shuttle_landmark
last_landmark_registration_time = world.time
- var/obj/effect/overmap/visitable/O = landmarks_still_needed[shuttle_landmark_tag]
+ var/obj/overmap/visitable/O = landmarks_still_needed[shuttle_landmark_tag]
if(O) //These need to be added to sectors, which we handle.
try_add_landmark_tag(shuttle_landmark_tag, O)
landmarks_still_needed -= shuttle_landmark_tag
- else if(istype(shuttle_landmark, /obj/effect/shuttle_landmark/automatic)) //These find their sector automatically
+ else if(istype(shuttle_landmark, /obj/shuttle_landmark/automatic)) //These find their sector automatically
O = map_sectors["[shuttle_landmark.z]"]
O ? O.add_landmark(shuttle_landmark, shuttle_landmark.shuttle_restricted) : (landmarks_awaiting_sector += shuttle_landmark)
@@ -86,7 +86,7 @@ SUBSYSTEM_DEF(shuttle)
//Checks if the given sector's landmarks have initialized; if so, registers them with the sector, if not, marks them for assignment after they come in.
//Also adds automatic landmarks that were waiting on their sector to spawn.
-/datum/controller/subsystem/shuttle/proc/initialize_sector(obj/effect/overmap/visitable/given_sector)
+/datum/controller/subsystem/shuttle/proc/initialize_sector(obj/overmap/visitable/given_sector)
given_sector.populate_sector_objects() // This is a late init operation that sets up the sector's map_z and does non-overmap-related init tasks.
for(var/landmark_tag in given_sector.initial_generic_waypoints)
@@ -100,13 +100,13 @@ SUBSYSTEM_DEF(shuttle)
var/landmarks_to_check = landmarks_awaiting_sector.Copy()
for(var/thing in landmarks_to_check)
- var/obj/effect/shuttle_landmark/automatic/landmark = thing
+ var/obj/shuttle_landmark/automatic/landmark = thing
if(landmark.z in given_sector.map_z)
given_sector.add_landmark(landmark, landmark.shuttle_restricted)
landmarks_awaiting_sector -= landmark
-/datum/controller/subsystem/shuttle/proc/try_add_landmark_tag(landmark_tag, obj/effect/overmap/visitable/given_sector)
- var/obj/effect/shuttle_landmark/landmark = get_landmark(landmark_tag)
+/datum/controller/subsystem/shuttle/proc/try_add_landmark_tag(landmark_tag, obj/overmap/visitable/given_sector)
+ var/obj/shuttle_landmark/landmark = get_landmark(landmark_tag)
if(!landmark)
return
@@ -140,17 +140,17 @@ SUBSYSTEM_DEF(shuttle)
return
overmap_halted = !overmap_halted
for(var/ship in ships)
- var/obj/effect/overmap/visitable/ship/ship_effect = ship
+ var/obj/overmap/visitable/ship/ship_effect = ship
overmap_halted ? ship_effect.halt() : ship_effect.unhalt()
/datum/controller/subsystem/shuttle/proc/ship_by_name(name)
- for (var/obj/effect/overmap/visitable/ship/ship in ships)
+ for (var/obj/overmap/visitable/ship/ship in ships)
if (ship.name == name)
return ship
return null
/datum/controller/subsystem/shuttle/proc/ship_by_type(type)
- for (var/obj/effect/overmap/visitable/ship/ship in ships)
+ for (var/obj/overmap/visitable/ship/ship in ships)
if (ship.type == type)
return ship
return null
diff --git a/code/controllers/subsystems/skybox.dm b/code/controllers/subsystems/skybox.dm
index 905eba2b54ff4..28f3bda99c865 100644
--- a/code/controllers/subsystems/skybox.dm
+++ b/code/controllers/subsystems/skybox.dm
@@ -18,9 +18,6 @@ SUBSYSTEM_DEF(skybox)
/// whether the current skybox collected an extra appearance from an overmap feature
var/static/use_overmap_details = TRUE
- /// The skybox icon file to use for stars. Expects to be 736x736
- var/static/star_path = 'icons/skybox/skybox.dmi'
-
/// The skybox icon state to use for stars
var/static/star_state = "stars"
@@ -40,10 +37,13 @@ SUBSYSTEM_DEF(skybox)
dust.plane = DUST_PLANE
dust.alpha = 80
dust.blend_mode = BLEND_ADD
+
var/mutable_appearance/space = new /mutable_appearance(/turf/space)
+ space.plane = SPACE_PLANE
space.icon_state = "white"
- space.overlays += dust
+ space.AddOverlays(dust)
space_appearance_cache[index] = space.appearance
+
background_color = RANDOM_RGB
@@ -51,7 +51,7 @@ SUBSYSTEM_DEF(skybox)
if (!skybox_cache["[z]"])
skybox_cache["[z]"] = generate_skybox(z)
if (GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/O = map_sectors["[z]"]
+ var/obj/overmap/visitable/O = map_sectors["[z]"]
if (istype(O))
for (var/zlevel in O.map_z)
skybox_cache["[zlevel]"] = skybox_cache["[z]"]
@@ -63,21 +63,21 @@ SUBSYSTEM_DEF(skybox)
var/image/base = overlay_image(skybox_icon, background_icon, background_color)
if (use_stars)
var/image/stars = overlay_image(skybox_icon, star_state, flags = RESET_COLOR)
- base.overlays += stars
- res.overlays += base
+ base.AddOverlays(stars)
+ res.AddOverlays(base)
if (GLOB.using_map.use_overmap && use_overmap_details)
- var/obj/effect/overmap/visitable/O = map_sectors["[z]"]
+ var/obj/overmap/visitable/O = map_sectors["[z]"]
if (istype(O))
var/image/overmap = image(skybox_icon)
- overmap.overlays += O.generate_skybox()
- for (var/obj/effect/overmap/visitable/other in O.loc)
+ overmap.AddOverlays(O.generate_skybox())
+ for (var/obj/overmap/visitable/other in O.loc)
if (other != O)
- overmap.overlays += other.get_skybox_representation()
+ overmap.AddOverlays(other.get_skybox_representation())
overmap.appearance_flags |= RESET_COLOR
- res.overlays += overmap
+ res.AddOverlays(overmap)
for (var/datum/event/event as anything in SSevent.active_events)
if(event.has_skybox_image && event.isRunning && (z in event.affecting_z))
- res.overlays += event.get_skybox_image()
+ res.AddOverlays(event.get_skybox_image())
return res
diff --git a/code/controllers/subsystems/spacedrift.dm b/code/controllers/subsystems/spacedrift.dm
new file mode 100644
index 0000000000000..9f4500c6f19ab
--- /dev/null
+++ b/code/controllers/subsystems/spacedrift.dm
@@ -0,0 +1,62 @@
+//ported from TG 30/03/2020
+SUBSYSTEM_DEF(spacedrift)
+ name = "Space Drift"
+ priority = FIRE_PRIORITY_SPACEDRIFT
+ wait = 5
+ flags = SS_NO_INIT|SS_KEEP_TIMING
+
+ var/list/currentrun = list()
+ var/list/processing = list()
+
+/datum/controller/subsystem/spacedrift/UpdateStat(time)
+ if (PreventUpdateStat(time))
+ return ..()
+ ..("P:[length(processing)]")
+
+
+/datum/controller/subsystem/spacedrift/fire(resumed = 0)
+ if (!resumed)
+ src.currentrun = processing.Copy()
+
+ //cache for sanic speed (lists are references anyways)
+ var/list/currentrun = src.currentrun
+
+ while (length(currentrun))
+ var/atom/movable/AM = currentrun[length(currentrun)]
+ LIST_DEC(currentrun)
+ if (!AM)
+ processing -= AM
+ if (MC_TICK_CHECK)
+ return
+ continue
+
+ if (AM.inertia_next_move > world.time)
+ if (MC_TICK_CHECK)
+ return
+ continue
+
+ if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0))
+ AM.inertia_dir = 0
+
+ AM.inertia_ignore = null
+
+ if (!AM.inertia_dir)
+ AM.inertia_last_loc = null
+ processing -= AM
+ if (MC_TICK_CHECK)
+ return
+ continue
+
+ var/old_dir = AM.dir
+ var/old_loc = AM.loc
+ AM.inertia_moving = TRUE
+ step(AM, AM.inertia_dir)
+ AM.inertia_moving = FALSE
+ AM.inertia_next_move = world.time + AM.inertia_move_delay
+ if (AM.loc == old_loc)
+ AM.inertia_dir = 0
+
+ AM.set_dir(old_dir)
+ AM.inertia_last_loc = AM.loc
+ if (MC_TICK_CHECK)
+ return
diff --git a/code/controllers/subsystems/supply.dm b/code/controllers/subsystems/supply.dm
index 0e8e2f14fc7e2..7ade4ca38bdd8 100644
--- a/code/controllers/subsystems/supply.dm
+++ b/code/controllers/subsystems/supply.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(supply)
name = "Supply"
wait = 20 SECONDS
- priority = SS_PRIORITY_SUPPLY
+ priority = FIRE_PRIORITY_SUPPLY
//Initializes at default time
flags = SS_NO_TICK_CHECK
@@ -26,6 +26,7 @@ SUBSYSTEM_DEF(supply)
"manifest" = "From exported manifests",
"crate" = "From exported crates",
"gep" = "From uploaded good explorer points",
+ "anomaly" = "From scanned and categorized anomalies",
"total" = "Total" // If you're adding additional point sources, add it here in a new line. Don't forget to put a comma after the old last line.
)
@@ -34,13 +35,13 @@ SUBSYSTEM_DEF(supply)
//Build master supply list
var/singleton/hierarchy/supply_pack/root = GET_SINGLETON(/singleton/hierarchy/supply_pack)
- for(var/singleton/hierarchy/supply_pack/sp in root.children)
+ for (var/singleton/hierarchy/supply_pack/sp in root.children)
if(sp.is_category())
for(var/singleton/hierarchy/supply_pack/spc in sp.get_descendents())
spc.setup()
master_supply_list += spc
- for(var/material/mat in SSmaterials.materials)
+ for (var/material/mat in SSmaterials.materials)
if(mat.sale_price > 0)
point_source_descriptions[mat.display_name] = "From exported [mat.display_name]"
@@ -62,21 +63,23 @@ SUBSYSTEM_DEF(supply)
point_sources[source] += amount
point_sources["total"] += amount
- //To stop things being sent to centcomm which should not be sent to centcomm. Recursively checks for these types.
+ //To stop things being sent to centcom which should not be sent to centcom. Recursively checks for these types.
/datum/controller/subsystem/supply/proc/forbidden_atoms_check(atom/A)
- if(istype(A,/mob/living))
- return 1
- if(istype(A,/obj/item/disk/nuclear))
- return 1
- if(istype(A,/obj/machinery/nuclearbomb))
- return 1
- if(istype(A,/obj/machinery/tele_beacon))
- return 1
+ if (istype(A, /mob/living))
+ var/mob/living/mob = A
+ if (istype(mob, /mob/living/simple_animal/hostile/human) || mob.mind)
+ return TRUE
+ if (istype(A, /obj/item/disk/nuclear))
+ return TRUE
+ if (istype(A, /obj/machinery/nuclearbomb))
+ return TRUE
+ if (istype(A, /obj/machinery/tele_beacon))
+ return TRUE
for(var/i=1, i<=length(A.contents), i++)
var/atom/B = A.contents[i]
if(.(B))
- return 1
+ return TRUE
/datum/controller/subsystem/supply/proc/sell()
var/list/material_count = list()
@@ -113,7 +116,68 @@ SUBSYSTEM_DEF(supply)
// Must sell ore detector disks in crates
if(istype(A, /obj/item/disk/survey))
var/obj/item/disk/survey/D = A
- add_points_from_source(round(D.Value() * 0.005), "gep")
+ add_points_from_source(round(D.Value() * 0.05), "gep")
+
+ // Sell artefacts (in anomaly cages)
+ if (istype(AM, /obj/machinery/anomaly_container))
+ var/obj/machinery/anomaly_container/AC = AM
+ callHook("sell_anomalycage", list(AC, subarea))
+ if (AC.contained)
+ var/obj/machinery/artifact/C = AC.contained
+ var/list/my_effects
+ if (C.my_effect)
+ var/datum/artifact_effect/eone = C.my_effect
+ my_effects += eone
+ if (C.secondary_effect)
+ var/datum/artifact_effect/etwo = C.secondary_effect
+ my_effects += etwo
+ //Different effects and trigger combos give different rewards
+
+ if (AC.attached_paper) //Needs to have a scan sheet of the anomaly to the container.
+ if (istype(AC.attached_paper, /obj/item/paper/anomaly_scan))
+ var/obj/item/paper/anomaly_scan/P = AC.attached_paper
+ if (!P.is_copy)
+ for (var/datum/artifact_effect/E in my_effects)
+ switch (E.effect_type)
+ if (EFFECT_UNKNOWN, EFFECT_PSIONIC)
+ points += 20
+ if (EFFECT_ENERGY, EFFECT_ELECTRO)
+ points += 30
+ if (EFFECT_ORGANIC, EFFECT_SYNTH)
+ points += 40
+ if (EFFECT_BLUESPACE, EFFECT_PARTICLE)
+ points += 50
+ else
+ points += 10
+ //In case there's ever a broken artifact, it's still worth SOMETHING
+ switch (E.trigger.trigger_type)
+ if (TRIGGER_SIMPLE)
+ points += 5
+ if (TRIGGER_COMPLEX)
+ points += 10
+ else
+ points += 2
+
+ add_points_from_source(points, "anomaly")
+
+ //Only for animals in stasis cages.
+ if (istype(AM, /obj/machinery/stasis_cage))
+ var/obj/machinery/stasis_cage/SC = AM
+ var/points_per_animal = 10
+ callHook("sell_animal", list(SC, subarea))
+ if (SC.contained)
+ var/mob/living/simple_animal/CA = SC.contained
+ if (istype(CA, /mob/living/simple_animal/hostile/human))
+ return
+ if (istype(CA, /mob/living/simple_animal/passive))
+ add_points_from_source(points_per_animal, "animal")
+ if (istype(CA, /mob/living/simple_animal/hostile/retaliate/beast))
+ add_points_from_source((points_per_animal * 2), "animal")
+ return //So that it doesn't give points twice for beasts
+ if (istype(CA, /mob/living/simple_animal/hostile))
+ add_points_from_source((points_per_animal * 4), "animal")
+ if (CA.stat != DEAD) //Alive gives more.
+ add_points_from_source((point_sources["animal"] * 2), "animal")
qdel(AM)
@@ -170,7 +234,7 @@ SUBSYSTEM_DEF(supply)
info +="CONTENTS:
"
slip = new /obj/item/paper/manifest(A, JOINTEXT(info))
- slip.is_copy = 0
+ slip.is_copy = FALSE
//spawn the stuff, finish generating the manifest while you're at it
if(SP.access)
diff --git a/code/controllers/subsystems/tgui.dm b/code/controllers/subsystems/tgui.dm
new file mode 100644
index 0000000000000..91f0b9be8b7af
--- /dev/null
+++ b/code/controllers/subsystems/tgui.dm
@@ -0,0 +1,356 @@
+/**
+ * tgui subsystem
+ *
+ * Contains all tgui state and subsystem code.
+ */
+
+
+SUBSYSTEM_DEF(tgui)
+ name = "TGUI"
+ wait = 9
+ flags = SS_NO_INIT
+ priority = FIRE_PRIORITY_TGUI
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+
+ /// A list of UIs scheduled to process
+ var/list/current_run = list()
+ /// A list of open UIs
+ var/list/open_uis = list()
+ /// A list of open UIs, grouped by src_object and ui_key.
+ var/list/open_uis_by_src = list()
+ /// The HTML base used for all UIs.
+ var/basehtml
+
+/datum/controller/subsystem/tgui/PreInit()
+ basehtml = file2text('tgui/public/tgui.html')
+ // Inject inline polyfills
+ var/polyfill = file2text('tgui/public/tgui-polyfill.min.js')
+ polyfill = ""
+ basehtml = replacetextEx(basehtml, "", polyfill)
+
+/datum/controller/subsystem/tgui/Shutdown()
+ close_all_uis()
+
+/datum/controller/subsystem/tgui/UpdateStat(time)
+ if(PreventUpdateStat(time))
+ return ..()
+ ..("P:[length(open_uis)]")
+
+/datum/controller/subsystem/tgui/fire(resumed = FALSE)
+ if(!resumed)
+ src.current_run = open_uis.Copy()
+ // Cache for sanic speed (lists are references anyways)
+ var/list/current_run = src.current_run
+ while(length(current_run))
+ var/datum/tgui/ui = current_run[length(current_run)]
+ LIST_DEC(current_run)
+ // TODO: Move user/src_object check to process()
+ if(ui && ui.user && ui.src_object)
+ ui.process()
+ else
+ open_uis.Remove(ui)
+ if(MC_TICK_CHECK)
+ return
+
+/**
+ * public
+ *
+ * Requests a usable tgui window from the pool.
+ * Returns null if pool was exhausted.
+ *
+ * required user mob
+ * return datum/tgui
+ */
+/datum/controller/subsystem/tgui/proc/request_pooled_window(mob/user)
+ if(!user.client)
+ return null
+ var/list/windows = user.client.tgui_windows
+ var/window_id
+ var/datum/tgui_window/window
+ var/window_found = FALSE
+ // Find a usable window
+ for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT)
+ window_id = TGUI_WINDOW_ID(i)
+ window = windows[window_id]
+ // As we are looping, create missing window datums
+ if(!window)
+ window = new(user.client, window_id, pooled = TRUE)
+ // Skip windows with acquired locks
+ if(window.locked)
+ continue
+ if(window.status == TGUI_WINDOW_READY)
+ return window
+ if(window.status == TGUI_WINDOW_CLOSED)
+ window.status = TGUI_WINDOW_LOADING
+ window_found = TRUE
+ break
+ if(!window_found)
+ #ifdef TGUI_DEBUGGING
+ log_tgui(user, "Error: Pool exhausted")
+ #endif
+ return
+ return window
+
+/**
+ * public
+ *
+ * Force closes all tgui windows.
+ *
+ * required user mob
+ */
+/datum/controller/subsystem/tgui/proc/force_close_all_windows(mob/user)
+ if(user.client)
+ user.client.tgui_windows = list()
+ for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT)
+ var/window_id = TGUI_WINDOW_ID(i)
+ close_browser(user, "window=[window_id]")
+
+/**
+ * public
+ *
+ * Force closes the tgui window by window_id.
+ *
+ * required user mob
+ * required window_id string
+ */
+/datum/controller/subsystem/tgui/proc/force_close_window(mob/user, window_id)
+ // Close all tgui datums based on window_id.
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(ui.window && ui.window.id == window_id)
+ ui.close(can_be_suspended = FALSE)
+ // Unset machine just to be sure.
+ user.unset_machine()
+ // Close window directly just to be sure.
+ close_browser(user, "window=[window_id]")
+
+/**
+ * public
+ *
+ * Try to find an instance of a UI, and push an update to it.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required src_object datum The object/datum which owns the UI.
+ * optional ui datum/tgui The UI to be updated, if it exists.
+ * optional force_open bool If the UI should be re-opened instead of updated.
+ *
+ * return datum/tgui The found UI.
+ */
+/datum/controller/subsystem/tgui/proc/try_update_ui(mob/user, datum/src_object, datum/tgui/ui, force_open = FALSE)
+ // Look up a UI if it wasn't passed
+ if(isnull(ui))
+ ui = get_open_ui(user, src_object)
+ // Couldn't find a UI.
+ if(isnull(ui))
+ return null
+ var/data = src_object.ui_data(user) // Get data from the src_object.
+ if(force_open) // UI is already open; update it.
+ ui.send_full_update(data, TRUE)
+ return ui // We found the UI, return it
+ ui.process_status()
+ // UI ended up with the closed status
+ // or is actively trying to close itself.
+ // FIXME: Doesn't actually fix the paper bug.
+ if(ui.status <= STATUS_CLOSE)
+ ui.close()
+ return null
+ ui.send_update()
+ return ui
+
+/**
+ * public
+ *
+ * Get a open UI given a user and src_object.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required src_object datum The object/datum which owns the UI.
+ *
+ * return datum/tgui The found UI.
+ */
+/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object)
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !islist(open_uis_by_src[key]))
+ return
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Make sure we have the right user
+ if(ui.user == user)
+ return ui
+ return
+
+/**
+ * public
+ *
+ * Update all UIs attached to src_object.
+ *
+ * required src_object datum The object/datum which owns the UIs.
+ *
+ * return int The number of UIs updated.
+ */
+/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object)
+ var/count = 0
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !islist(open_uis_by_src[key]))
+ return count
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.tgui_host(ui.user))
+ ui.process(force = TRUE) // Update the UI.
+ count++ // Count each UI we update.
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs attached to src_object.
+ *
+ * required src_object datum The object/datum which owns the UIs.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
+ var/count = 0
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !islist(open_uis_by_src[key]))
+ return count
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.tgui_host(ui.user))
+ ui.close()
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs regardless of their attachment to src_object.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_all_uis()
+ var/count = 0
+ for(var/key in open_uis_by_src)
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.tgui_host(ui.user))
+ ui.close()
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Update all UIs belonging to a user.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * optional src_object datum If provided, only update UIs belonging this src_object.
+ *
+ * return int The number of UIs updated.
+ */
+/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object)
+ var/count = 0
+ if(length(user?.tgui_open_uis) == 0)
+ return count
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(isnull(src_object) || ui.src_object == src_object)
+ ui.process(force = TRUE)
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs belonging to a user.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * optional src_object datum If provided, only close UIs belonging this src_object.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object, logout = FALSE)
+ var/count = 0
+ if(length(user?.tgui_open_uis) == 0)
+ return count
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(isnull(src_object) || ui.src_object == src_object)
+ ui.close(logout = logout)
+ count++
+ return count
+
+/**
+ * private
+ *
+ * Add a UI to the list of open UIs.
+ *
+ * required ui datum/tgui The UI to be added.
+ */
+/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui)
+ var/key = "[REF(ui.src_object)]"
+ if(isnull(open_uis_by_src[key]) || !islist(open_uis_by_src[key]))
+ open_uis_by_src[key] = list()
+ ui.user.tgui_open_uis |= ui
+ var/list/uis = open_uis_by_src[key]
+ uis |= ui
+ open_uis |= ui
+
+/**
+ * private
+ *
+ * Remove a UI from the list of open UIs.
+ *
+ * required ui datum/tgui The UI to be removed.
+ *
+ * return bool If the UI was removed or not.
+ */
+/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui)
+ var/key = "[REF(ui.src_object)]"
+ if(isnull(open_uis_by_src[key]) || !islist(open_uis_by_src[key]))
+ return FALSE
+ // Remove it from the list of processing UIs.
+ open_uis.Remove(ui)
+ // If the user exists, remove it from them too.
+ if(ui.user)
+ ui.user.tgui_open_uis.Remove(ui)
+ var/list/uis = open_uis_by_src[key]
+ uis.Remove(ui)
+ if(length(uis) == 0)
+ open_uis_by_src.Remove(key)
+ return TRUE
+
+/**
+ * private
+ *
+ * Handle client logout, by closing all their UIs.
+ *
+ * required user mob The mob which logged out.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/on_logout(mob/user)
+ return close_user_uis(user, logout = TRUE)
+
+/**
+ * private
+ *
+ * Handle clients switching mobs, by transferring their UIs.
+ *
+ * required user source The client's original mob.
+ * required user target The client's new mob.
+ *
+ * return bool If the UIs were transferred.
+ */
+/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
+ // The old mob had no open UIs.
+ if(length(source?.tgui_open_uis) == 0)
+ return FALSE
+ if(isnull(target.tgui_open_uis) || !istype(target.tgui_open_uis, /list))
+ target.tgui_open_uis = list()
+ // Transfer all the UIs.
+ for(var/datum/tgui/ui in source.tgui_open_uis)
+ // Inform the UIs of their new owner.
+ ui.user = target
+ target.tgui_open_uis.Add(ui)
+ // Clear the old list.
+ source.tgui_open_uis.Cut()
+ return TRUE
diff --git a/code/controllers/subsystems/throwing.dm b/code/controllers/subsystems/throwing.dm
index d0d1a893967b6..931f62c1579e7 100644
--- a/code/controllers/subsystems/throwing.dm
+++ b/code/controllers/subsystems/throwing.dm
@@ -1,10 +1,10 @@
-#define MAX_THROWING_DIST 1280 // 5 z-levels on default width
#define MAX_TICKS_TO_MAKE_UP 3 //how many missed ticks will we attempt to make up for this run.
SUBSYSTEM_DEF(throwing)
name = "Throwing"
wait = 1
+ priority = FIRE_PRIORITY_THROWING
flags = SS_NO_INIT | SS_KEEP_TIMING
/// An atom => thrownthing map of current throws
@@ -113,16 +113,15 @@ SUBSYSTEM_DEF(throwing)
if (dist_travelled && hitcheck(get_turf(thrownthing)))
finalize()
return
- var/area/A = get_area(AM.loc)
var/atom/step
last_move = world.time
var/scaled_wait = world.tick_lag * SSthrowing.wait
var/target_travel = (delayed_time + (world.time + world.tick_lag) - start_time) * speed
var/prior_travel = dist_travelled ? dist_travelled : -1
var/max_travel = speed * MAX_TICKS_TO_MAKE_UP
- var/tilestomove = Ceil(min(target_travel - prior_travel, max_travel) * scaled_wait)
+ var/tilestomove = ceil(min(target_travel - prior_travel, max_travel) * scaled_wait)
while (tilestomove-- > 0)
- if ((dist_travelled >= maxrange || AM.loc == target_turf) && (A && A.has_gravity()))
+ if (dist_travelled >= maxrange || AM.loc == target_turf)
finalize()
return
if (dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction
@@ -141,14 +140,8 @@ SUBSYSTEM_DEF(throwing)
return
AM.Move(step, get_dir(AM, step))
if (!AM.throwing) // we hit something during our move
- finalize(hit = TRUE)
return
dist_travelled++
- if (dist_travelled > MAX_THROWING_DIST)
- finalize()
- return
- sleep(-1)
- A = get_area(AM.loc)
/datum/thrownthing/proc/finalize(hit = FALSE, t_target = null)
@@ -164,9 +157,8 @@ SUBSYSTEM_DEF(throwing)
break
if (!hit)
thrownthing.throw_impact(get_turf(thrownthing), src)
- if(ismob(thrownthing))
- var/mob/M = thrownthing
- M.inertia_dir = init_dir
+ thrownthing.space_drift(init_dir)
+
if(t_target && !QDELETED(thrownthing))
thrownthing.throw_impact(t_target, src)
if (callback)
diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm
index bde90ddbd5cc1..7e417f932d42c 100644
--- a/code/controllers/subsystems/ticker.dm
+++ b/code/controllers/subsystems/ticker.dm
@@ -1,17 +1,17 @@
SUBSYSTEM_DEF(ticker)
name = "Ticker"
wait = 10
- priority = SS_PRIORITY_TICKER
+ priority = FIRE_PRIORITY_TICKER
init_order = SS_INIT_TICKER
flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING
- runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+ runlevels = RUNLEVELS_PREGAME | RUNLEVELS_GAME
var/pregame_timeleft
var/start_ASAP = FALSE //the game will start as soon as possible, bypassing all pre-game nonsense
var/list/gamemode_vote_results //Will be a list, in order of preference, of form list(config_tag = number of votes).
- var/bypass_gamemode_vote = 0 //Intended for use with admin tools. Will avoid voting and ignore any results.
+ var/bypass_gamemode_vote = TRUE //Intended for use with admin tools. Will avoid voting and ignore any results.
- var/master_mode = "extended" //The underlying game mode (so "secret" or the voted mode). Saved to default back to previous round's mode in case the vote failed. This is a config_tag.
+ var/master_mode = "secret" //The underlying game mode (so "secret" or the voted mode). Saved to default back to previous round's mode in case the vote failed. This is a config_tag.
var/datum/game_mode/mode //The actual gamemode, if selected.
var/round_progressing = 1 //Whether the lobby clock is ticking down.
@@ -32,6 +32,8 @@ SUBSYSTEM_DEF(ticker)
///Set to TRUE when an admin forcefully ends the round.
var/forced_end = FALSE
+ var/skip_requirement_checks = FALSE
+
var/static/list/mode_tags = list()
var/static/list/mode_names = list()
@@ -48,6 +50,7 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/Initialize(start_uptime)
pregame_timeleft = config.pre_game_time SECONDS
+ bypass_gamemode_vote = config.bypass_gamemode_vote
build_mode_cache()
to_world(SPAN_INFO("Welcome to the pre-game lobby!"))
to_world("Please, setup your character and select ready. Game will start in [round(pregame_timeleft/10)] seconds")
@@ -110,8 +113,10 @@ SUBSYSTEM_DEF(ticker)
if(start_ASAP)
start_now()
return
+
if(round_progressing && last_fire)
pregame_timeleft -= world.time - last_fire
+
if(pregame_timeleft <= 0)
Master.SetRunLevel(RUNLEVEL_SETUP)
return
@@ -181,7 +186,7 @@ SUBSYSTEM_DEF(ticker)
player.new_player_panel()
if(!length(GLOB.admins))
- send2adminirc("Round has started with no admins online.")
+ send_to_admin_discord(EXCOM_MSG_AHELP, "Round has started with no admins online.")
/datum/controller/subsystem/ticker/proc/playing_tick()
mode.process()
@@ -190,7 +195,7 @@ SUBSYSTEM_DEF(ticker)
if(mode_finished && game_finished())
Master.SetRunLevel(RUNLEVEL_POSTGAME)
end_game_state = END_GAME_READY_TO_END
- invoke_async(src, .proc/declare_completion)
+ invoke_async(src, PROC_REF(declare_completion))
if(config.allow_map_switching && config.auto_map_vote && length(GLOB.all_maps) > 1)
SSvote.initiate_vote(/datum/vote/map/end_game, automatic = 1)
@@ -332,7 +337,11 @@ Helpers
mode_datum.pre_setup() // Makes lists of viable candidates; performs candidate draft for job-override roles; stores the draft result both internally and on the draftee.
SSjobs.divide_occupations(mode_datum) // Gives out jobs to everyone who was not selected to antag.
var/list/lobby_players = SSticker.lobby_players()
- var/result = mode_datum.check_startable(lobby_players)
+
+ var/result = FALSE
+ if (!skip_requirement_checks)
+ result = mode_datum.check_startable(lobby_players)
+
if(result)
mode_datum.fail_setup()
SSjobs.reset_occupations()
@@ -398,18 +407,11 @@ Helpers
minds += player.mind
/datum/controller/subsystem/ticker/proc/equip_characters()
- var/captainless=1
for(var/mob/living/carbon/human/player in GLOB.player_list)
if(player && player.mind && player.mind.assigned_role)
- if(player.mind.assigned_role == "Captain")
- captainless=0
if(!player_is_antag(player.mind, only_offstation_roles = 1))
SSjobs.equip_rank(player, player.mind.assigned_role, 0)
SScustomitems.equip_custom_items(player)
- if(captainless)
- for(var/mob/M in GLOB.player_list)
- if(!istype(M,/mob/new_player))
- to_chat(M, "Captainship not forced on anyone.")
/datum/controller/subsystem/ticker/proc/attempt_late_antag_spawn(list/antag_choices)
var/datum/antagonist/antag = antag_choices[1]
@@ -492,9 +494,14 @@ Helpers
/datum/controller/subsystem/ticker/proc/declare_completion()
to_world("
A round of [mode.name] has ended!
")
- for(var/client/C)
- if(!C.credits)
- C.RollCredits()
+ for(var/client/C as anything in GLOB.clients)
+ if(!C)
+ continue
+
+ if(C.credits)
+ continue
+
+ C.RollCredits()
GLOB.using_map.roundend_player_status()
@@ -571,9 +578,9 @@ Helpers
return
if(istype(SSvote.active_vote, /datum/vote/gamemode))
SSvote.cancel_vote(user)
- bypass_gamemode_vote = 1
+ bypass_gamemode_vote = TRUE
Master.SetRunLevel(RUNLEVEL_SETUP)
- return 1
+ return TRUE
/hook/roundstart/proc/PlayWelcomeSound()
diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm
index fa0fb585156f9..0b46ddee94820 100644
--- a/code/controllers/subsystems/timer.dm
+++ b/code/controllers/subsystems/timer.dm
@@ -1,661 +1,103 @@
-/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth)
-#define BUCKET_LEN (world.fps * 60)
-/// Helper for getting the correct bucket for a given timer
-#define BUCKET_POS(timer) (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN)
-/// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue
-#define TIMER_MAX(timer_ss) (world.time + (min(BUCKET_LEN - (timer_ss.practical_offset - (world.time - timer_ss.head_offset) / world.tick_lag)-1, BUCKET_LEN - 1)) * world.tick_lag)
-/// Max float with integer precision
-#define TIMER_ID_MAX (2**24)
+/// Looping timers automatically re-queue themselves after firing, assuming they are still valid
+var/global/const/TIMER_LOOP = FLAG(0)
-/**
- * # Timer Subsystem
- *
- * Handles creation, callbacks, and destruction of timed events.
- *
- * It is important to understand the buckets used in the timer subsystem are just a series of doubly-linked
- * lists. The object at a given index in bucket_list is a /datum/timedevent, the head of a list, which has prev
- * and next references for the respective elements in that bucket's list.
- */
-SUBSYSTEM_DEF(timer)
- name = "Timer"
- wait = 1 //SS_TICKER subsystem, so wait is in ticks
- priority = SS_PRIORITY_TIMER
-
- flags = SS_NO_INIT | SS_TICKER
-
- /// Queue used for storing timers that do not fit into the current buckets
- var/list/datum/timedevent/second_queue = list()
- /// A hashlist dictionary used for storing unique timers
- var/list/hashes = list()
- /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets
- var/head_offset = 0
- /// Index of the wrap around pivot for buckets. buckets before this are later running buckets wrapped around from the end of the bucket list.
- var/practical_offset = 1
- /// world.tick_lag the bucket was designed for
- var/bucket_resolution = 0
- /// How many timers are in the buckets
- var/bucket_count = 0
- /// List of buckets, each bucket holds every timer that has to run that byond tick
- var/list/bucket_list = list()
- /// List of all active timers associated to their timer ID (for easy lookup)
- var/list/timer_id_dict = list()
- /// Special timers that run in real-time, not BYOND time; these are more expensive to run and maintain
- var/list/clienttime_timers = list()
- /// Contains the last time that a timer's callback was invoked, or the last tick the SS fired if no timers are being processed
- var/last_invoke_tick = 0
- /// Keeps track of the next index to work on for client timers
- var/next_clienttime_timer_index = 0
- /// Contains the last time that a warning was issued for not invoking callbacks
- var/static/last_invoke_warning = 0
- /// Boolean operator controlling if the timer SS will automatically reset buckets if it fails to invoke callbacks for an extended period of time
- var/static/bucket_auto_reset = TRUE
- /// How many times bucket was reset
- var/bucket_reset_count = 0
-
-
-/datum/controller/subsystem/timer/PreInit()
- LIST_RESIZE(bucket_list, BUCKET_LEN)
- head_offset = world.time
- bucket_resolution = world.tick_lag
-
-
-/datum/controller/subsystem/timer/UpdateStat(time)
- if (PreventUpdateStat(time))
- return ..()
- ..({"\
- Buckets [bucket_count] \
- Queue2 [length(second_queue)] \
- Hashes [length(hashes)] \
- Client Timers [length(clienttime_timers)] \
- Size [length(timer_id_dict)] \
- Resets [bucket_reset_count]\
- "})
-
-
-/datum/controller/subsystem/timer/proc/dump_timer_buckets(full = TRUE)
- var/list/to_log = list("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
- if (full)
- for (var/i in 1 to length(bucket_list))
- var/datum/timedevent/bucket_head = bucket_list[i]
- if (!bucket_head)
- continue
-
- to_log += "Active timers at index [i]:"
- var/datum/timedevent/bucket_node = bucket_head
- var/anti_loop_check = 1
- do
- to_log += get_timer_debug_string(bucket_node)
- bucket_node = bucket_node.next
- anti_loop_check--
- while(bucket_node && bucket_node != bucket_head && anti_loop_check)
-
- to_log += "Active timers in the second_queue queue:"
- for(var/I in second_queue)
- to_log += get_timer_debug_string(I)
-
- // Dump all the logged data to the world log
- log_ss(name, to_log.Join("\n"))
-
-/datum/controller/subsystem/timer/fire(resumed = FALSE)
- // Store local references to datum vars as it is faster to access them
- var/lit = last_invoke_tick
- var/list/bucket_list = src.bucket_list
- var/last_check = world.time - BUCKET_LEN * 1.5 * world.tick_lag
-
- // If there are no timers being tracked, then consider now to be the last invoked time
- if(!bucket_count)
- last_invoke_tick = world.time
-
- // Check that we have invoked a callback in the last 1.5 minutes of BYOND time,
- // and throw a warning and reset buckets if this is true
- if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check)
- last_invoke_warning = world.time
- var/msg = "No regular timers processed in the last [BUCKET_LEN * 1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
- message_admins(msg)
- WARNING(msg)
- if(bucket_auto_reset)
- bucket_resolution = 0
- dump_timer_buckets(config.log_timers_on_bucket_reset)
-
- // Process client-time timers
- if (next_clienttime_timer_index)
- clienttime_timers.Cut(1, next_clienttime_timer_index+1)
- next_clienttime_timer_index = 0
- for (next_clienttime_timer_index in 1 to length(clienttime_timers))
- if (MC_TICK_CHECK)
- next_clienttime_timer_index--
- break
- var/datum/timedevent/ctime_timer = clienttime_timers[next_clienttime_timer_index]
- if (ctime_timer.timeToRun > Uptime())
- next_clienttime_timer_index--
- break
-
- var/datum/callback/callBack = ctime_timer.callBack
- if (!callBack)
- CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], \
- head_offset: [head_offset], practical_offset: [practical_offset], Uptime(): [Uptime()]")
-
- ctime_timer.spent = Uptime()
- invoke_async(callBack)
-
- if(ctime_timer.flags & TIMER_LOOP)
- ctime_timer.spent = 0
- ctime_timer.timeToRun = Uptime() + ctime_timer.wait
- BINARY_INSERT(ctime_timer, clienttime_timers, /datum/timedevent, ctime_timer, timeToRun, COMPARE_KEY)
- else
- qdel(ctime_timer)
-
- // Remove invoked client-time timers
- if (next_clienttime_timer_index)
- clienttime_timers.Cut(1, next_clienttime_timer_index+1)
- next_clienttime_timer_index = 0
-
- // Check for when we need to loop the buckets, this occurs when
- // the head_offset is approaching BUCKET_LEN ticks in the past
- if (practical_offset > BUCKET_LEN)
- head_offset += BUCKET_LEN * world.tick_lag
- practical_offset = 1
- resumed = FALSE
-
- // Check for when we have to reset buckets, typically from auto-reset
- if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution))
- reset_buckets()
- bucket_list = src.bucket_list
- resumed = FALSE
-
-
- // Iterate through each bucket starting from the practical offset
- while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time)
- var/datum/timedevent/timer
- while ((timer = bucket_list[practical_offset]))
- var/datum/callback/callBack = timer.callBack
- if (!callBack)
- stack_trace("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \
- head_offset: [head_offset], practical_offset: [practical_offset], bucket_joined: [timer.bucket_joined]")
- if (!timer.spent)
- bucket_resolution = null // force bucket recreation
- return
-
- timer.bucketEject() //pop the timer off of the bucket list.
-
- // Invoke callback if possible
- if (!timer.spent)
- timer.spent = world.time
- invoke_async(callBack)
- last_invoke_tick = world.time
-
- if (timer.flags & TIMER_LOOP) // Prepare looping timers to re-enter the queue
- timer.spent = 0
- timer.timeToRun = world.time + timer.wait
- timer.bucketJoin()
- else
- qdel(timer)
-
- if (MC_TICK_CHECK)
- break
-
- if (!bucket_list[practical_offset])
- // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket
- bucket_list[practical_offset] = null // Just in case
- practical_offset++
- var/i = 0
- for (i in 1 to length(second_queue))
- timer = second_queue[i]
- if (timer.timeToRun >= TIMER_MAX(src))
- i--
- break
-
- // Check for timers that are scheduled to run in the past
- if (timer.timeToRun < head_offset)
- bucket_resolution = null // force bucket recreation
- stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. \
- [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
- break
-
- // Check for timers that are not capable of being scheduled to run without rebuilding buckets
- if (timer.timeToRun < head_offset + (practical_offset - 1) * world.tick_lag)
- bucket_resolution = null // force bucket recreation
- stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to \
- short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
- break
-
- timer.bucketJoin()
- if (i)
- second_queue.Cut(1, i+1)
- if (MC_TICK_CHECK)
- break
-
-/**
- * Generates a string with details about the timed event for debugging purposes
- */
-/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
- . = "Timer: [TE]"
- . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
- if(TE.spent)
- . += ", SPENT([TE.spent])"
- if(QDELETED(TE))
- . += ", QDELETED"
- if(!TE.callBack)
- . += ", NO CALLBACK"
-
-/**
- * Destroys the existing buckets and creates new buckets from the existing timed events
- */
-/datum/controller/subsystem/timer/proc/reset_buckets()
- log_debug("Timer buckets has been reset, this may cause timer to lag")
- bucket_reset_count++
-
- var/list/bucket_list = src.bucket_list // Store local reference to datum var, this is faster
- var/list/alltimers = list()
-
- // Get all timers currently in the buckets
- for (var/bucket_head in bucket_list)
- if (!bucket_head) // if bucket is empty for this tick
- continue
- var/datum/timedevent/bucket_node = bucket_head
- do
- alltimers += bucket_node
- bucket_node = bucket_node.next
- while(bucket_node && bucket_node != bucket_head)
-
- // Empty the list by zeroing and re-assigning the length
- bucket_list.Cut()
- LIST_RESIZE(bucket_list, BUCKET_LEN)
-
- // Reset values for the subsystem to their initial values
- practical_offset = 1
- bucket_count = 0
- head_offset = world.time
- bucket_resolution = world.tick_lag
-
- // Add all timed events from the secondary queue as well
- alltimers += second_queue
-
- for (var/datum/timedevent/t as anything in alltimers)
- t.timer_subsystem = src // Recovered timers need to be reparented
- t.bucket_joined = FALSE
- t.bucket_pos = -1
- t.prev = null
- t.next = null
-
- // If there are no timers being tracked by the subsystem,
- // there is no need to do any further rebuilding
- if (!length(alltimers))
- return
-
- // Sort all timers by time to run
- sortTim(alltimers, .proc/cmp_timer)
-
- // Get the earliest timer, and if the TTR is earlier than the current world.time,
- // then set the head offset appropriately to be the earliest time tracked by the
- // current set of buckets
- var/datum/timedevent/head = alltimers[1]
- if (head.timeToRun < head_offset)
- head_offset = head.timeToRun
+/// Stoppable timers produce a hash that can be given to deltimer() to unqueue them
+var/global/const/TIMER_STOPPABLE = FLAG(1)
- // Iterate through each timed event and insert it into an appropriate bucket,
- // up unto the point that we can no longer insert into buckets as the TTR
- // is outside the range we are tracking, then insert the remainder into the
- // secondary queue
- var/new_bucket_count
- var/i = 1
- for (i in 1 to length(alltimers))
- var/datum/timedevent/timer = alltimers[i]
- if (!timer)
- continue
+/// Two of the same timer signature cannot be queued at once when they are unique
+var/global/const/TIMER_UNIQUE = FLAG(2)
- // Check that the TTR is within the range covered by buckets, when exceeded we've finished
- if (timer.timeToRun >= TIMER_MAX(src))
- i--
- break
+/// Attempting to add a unique timer will re-queue the event instead of being ignored
+var/global/const/TIMER_OVERRIDE = FLAG(3)
- // Check that timer has a valid callback and hasn't been invoked
- if (!timer.callBack || timer.spent)
- WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \
- head_offset: [head_offset], practical_offset: [practical_offset]")
- if (timer.callBack)
- qdel(timer)
- continue
+/// Skips adding the wait to the timer hash, allowing for uniques with variable wait times
+var/global/const/TIMER_NO_HASH_WAIT = FLAG(4)
- // Insert the timer into the bucket, and perform necessary doubly-linked list operations
- new_bucket_count++
- var/bucket_pos = BUCKET_POS(timer)
- timer.bucket_pos = bucket_pos
- timer.bucket_joined = TRUE
- var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
- if (!bucket_head)
- bucket_list[bucket_pos] = timer
- timer.next = null
- timer.prev = null
- continue
-
- bucket_head.prev = timer
- timer.next = bucket_head
- timer.prev = null
- bucket_list[bucket_pos] = timer
-
- // Cut the timers that are tracked by the buckets from the secondary queue
- if (i)
- alltimers.Cut(1, i + 1)
- second_queue = alltimers
- bucket_count = new_bucket_count
-
-
-/datum/controller/subsystem/timer/Recover()
- // Find the current timer sub-subsystem in global and recover its buckets etc
- var/datum/controller/subsystem/timer/timerSS = null
- for(var/global_var in global.vars)
- if (istype(global.vars[global_var],src.type))
- timerSS = global.vars[global_var]
-
- hashes = timerSS.hashes
- timer_id_dict = timerSS.timer_id_dict
-
- bucket_list = timerSS.bucket_list
- second_queue = timerSS.second_queue
-
- // The buckets are FUBAR
- reset_buckets()
-
-/**
- * # Timed Event
- *
- * This is the actual timer, it contains the callback and necessary data to maintain
- * the timer.
- *
- * See the documentation for the timer subsystem for an explanation of the buckets referenced
- * below in next and prev
- */
-/datum/timedevent
- /// ID used for timers when the TIMER_STOPPABLE flag is present
- var/id
- /// The callback to invoke after the timer completes
- var/datum/callback/callBack
- /// The time at which the callback should be invoked at
- var/timeToRun
- /// The length of the timer
+/datum/timer
+ var/datum/callback/callback
var/wait
- /// Unique hash generated when TIMER_UNIQUE flag is present
- var/hash
- /// The source of the timedevent, whatever called addtimer
+ var/flags
var/source
- /// Flags associated with the timer, see _DEFINES/subsystems.dm
- var/list/flags
- /// Time at which the timer was invoked or destroyed
- var/spent = 0
- /// An informative name generated for the timer as its representation in strings, useful for debugging
- var/name
- /// Next timed event in the bucket
- var/datum/timedevent/next
- /// Previous timed event in the bucket
- var/datum/timedevent/prev
- /// The timer subsystem this event is associated with
- var/datum/controller/subsystem/timer/timer_subsystem
- /// Boolean indicating if timer joined into bucket
- var/bucket_joined = FALSE
- /// Initial bucket position
- var/bucket_pos = -1
-
-/datum/timedevent/New(datum/callback/callBack, wait, flags, datum/controller/subsystem/timer/timer_subsystem, hash, source)
- var/static/nextid = 1
- id = TIMER_ID_NULL
- src.callBack = callBack
- src.wait = wait
- src.flags = flags
- src.hash = hash
- src.source = source
- src.timer_subsystem = timer_subsystem || SStimer
-
- // Determine time at which the timer's callback should be invoked
- timeToRun = (flags & TIMER_CLIENT_TIME ? Uptime() : world.time) + wait
-
- // Include the timer in the hash table if the timer is unique
- if (flags & TIMER_UNIQUE)
- timer_subsystem.hashes[hash] = src
-
- // Generate ID for the timer if the timer is stoppable, include in the timer id dictionary
- if (flags & TIMER_STOPPABLE)
- id = num2text(nextid, 100)
- if (nextid >= SHORT_REAL_LIMIT)
- nextid += min(1, 2 ** round(nextid / SHORT_REAL_LIMIT))
- else
- nextid++
- timer_subsystem.timer_id_dict[id] = src
-
- if ((timeToRun < world.time || timeToRun < timer_subsystem.head_offset) && !(flags & TIMER_CLIENT_TIME))
- CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
-
- if (callBack.target != FALSE && !QDESTROYING(callBack.target))
- LAZYADD(callBack.target.active_timers, src)
-
- bucketJoin()
-
-/datum/timedevent/Destroy()
- ..()
- if (flags & TIMER_UNIQUE && hash)
- timer_subsystem.hashes -= hash
-
- if (callBack && callBack.target && callBack.target != FALSE && callBack.target.active_timers)
- callBack.target.active_timers -= src
- UNSETEMPTY(callBack.target.active_timers)
-
- callBack = null
-
- if (flags & TIMER_STOPPABLE)
- timer_subsystem.timer_id_dict -= id
-
- if (flags & TIMER_CLIENT_TIME)
- if (!spent)
- spent = world.time
- timer_subsystem.clienttime_timers -= src
- return QDEL_HINT_IWILLGC
-
- if (!spent)
- spent = world.time
- bucketEject()
- else
- if (prev && prev.next == src)
- prev.next = next
- if (next && next.prev == src)
- next.prev = prev
- next = null
- prev = null
- return QDEL_HINT_IWILLGC
+ var/hash
+ var/fire_time
-/**
- * Removes this timed event from any relevant buckets, or the secondary queue
- */
-/datum/timedevent/proc/bucketEject()
- // Store local references for the bucket list and secondary queue
- // This is faster than referencing them from the datum itself
- var/list/bucket_list = timer_subsystem.bucket_list
- var/list/second_queue = timer_subsystem.second_queue
- // Attempt to get the head of the bucket
- var/datum/timedevent/buckethead
- if(bucket_pos > 0)
- buckethead = bucket_list[bucket_pos]
+SUBSYSTEM_DEF(timer)
+ name = "Timer"
+ flags = SS_NO_INIT | SS_TICKER
+ priority = FIRE_PRIORITY_TIMER
+ wait = 1
- // Decrement the number of timers in buckets if the timed event is
- // the head of the bucket, or has a TTR less than TIMER_MAX implying it fits
- // into an existing bucket, or is otherwise not present in the secondary queue
- if(buckethead == src)
- bucket_list[bucket_pos] = next
- timer_subsystem.bucket_count--
- else if(bucket_joined)
- timer_subsystem.bucket_count--
- else
- var/l = length(second_queue)
- second_queue -= src
- if(l == length(second_queue))
- timer_subsystem.bucket_count--
+ var/list/datum/timer/queue = list()
- // Remove the timed event from the bucket, ensuring to maintain
- // the integrity of the bucket's list if relevant
- if (prev && prev.next == src)
- prev.next = next
- if (next && next.prev == src)
- next.prev = prev
- prev = next = null
- bucket_pos = -1
- bucket_joined = FALSE
+ var/list/datum/timer/timers_by_hash = list()
-/**
- * Attempts to add this timed event to a bucket, will enter the secondary queue
- * if there are no appropriate buckets at this time.
- *
- * Secondary queueing of timed events will occur when the timespan covered by the existing
- * buckets is exceeded by the time at which this timed event is scheduled to be invoked.
- * If the timed event is tracking client time, it will be added to a special bucket.
- */
-/datum/timedevent/proc/bucketJoin()
- var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")
- name = "Timer: [id] (\ref[src]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield2list(flags, bitfield_flags), ", ")], \
- callBack: \ref[callBack], target: [callBack.identity], callable:[callBack.callable]([callBack.params ? callBack.params.Join(", ") : ""]), source: [source]"
+/datum/controller/subsystem/timer/UpdateStat(time)
+ if (PreventUpdateStat(time))
+ return ..()
+ ..("Queue: [length(queue)]")
- if (bucket_joined)
- stack_trace("Bucket already joined! [name]")
- // Check if this timed event should be diverted to the client time bucket, or the secondary queue
- var/list/L
- if (flags & TIMER_CLIENT_TIME)
- L = timer_subsystem.clienttime_timers
- else if (timeToRun >= TIMER_MAX(timer_subsystem))
- L = timer_subsystem.second_queue
- if(L)
- BINARY_INSERT(src, L, /datum/timedevent, src, timeToRun, COMPARE_KEY)
+/datum/controller/subsystem/timer/fire(resume, no_mc_tick)
+ if (!length(queue))
return
-
- // Get a local reference to the bucket list, this is faster than referencing the datum
- var/list/bucket_list = timer_subsystem.bucket_list
-
- // Find the correct bucket for this timed event
- bucket_pos = BUCKET_POS(src)
-
- if (bucket_pos < timer_subsystem.practical_offset && timeToRun < (timer_subsystem.head_offset + BUCKET_LEN * world.tick_lag))
- WARNING("Bucket pos in past: bucket_pos = [bucket_pos] < practical_offset = [timer_subsystem.practical_offset] \
- && timeToRun = [timeToRun] < [timer_subsystem.head_offset + BUCKET_LEN * world.tick_lag], Timer: [name]")
- bucket_pos = timer_subsystem.practical_offset // Recover bucket_pos to avoid timer blocking queue
-
- var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
- timer_subsystem.bucket_count++
-
- // If there is no timed event at this position, then the bucket is 'empty'
- // and we can just set this event to that position
- if (!bucket_head)
- bucket_joined = TRUE
- bucket_list[bucket_pos] = src
+ var/datum/timer/timer
+ var/datum/target
+ var/size = length(queue)
+ for (var/i = 1 to size)
+ timer = queue[i]
+ if (world.time < timer.fire_time)
+ if (i > 1)
+ queue.Cut(1, i)
+ return
+ target = timer.callback.target
+ if (target == GLOBAL_PROC || !QDELETED(target))
+ invoke_async(timer.callback)
+ if (timer.flags & TIMER_LOOP)
+ _addtimer(timer, subsystem = src)
+ else if (timer.hash)
+ timers_by_hash -= timer.hash
+ if (no_mc_tick)
+ CHECK_TICK
+ else if (MC_TICK_CHECK)
+ queue.Cut(1, i + 1)
+ return
+ queue.Cut(1, size + 1)
+
+
+/proc/deltimer(datum/timer/timer, datum/controller/subsystem/timer/subsystem = SStimer)
+ if (istext(timer))
+ timer = subsystem.timers_by_hash[timer]
+ if (!timer)
return
-
- // Otherwise, we merely add this timed event into the bucket, which is a
- // doubly-linked list
- bucket_joined = TRUE
- bucket_head.prev = src
- next = bucket_head
- prev = null
- bucket_list[bucket_pos] = src
-
-
-/**
- * Create a new timer and insert it in the queue.
- * You should not call this directly, and should instead use the addtimer macro, which includes source information.
- *
- * Arguments:
- * * callback the callback to call on timer finish
- * * wait deciseconds to run the timer for
- * * flags flags for this timer, see: code\__DEFINES\subsystems.dm
- */
-/proc/_addtimer(datum/callback/callback, wait = 0, flags = 0, datum/controller/subsystem/timer/timer_subsystem, file, line)
- if (!callback)
- CRASH("addtimer called without a callback")
-
- if (wait < 0)
- stack_trace("addtimer called with a negative wait. Converting to [world.tick_lag]")
-
- if (callback.target != FALSE && QDELETED(callback.target) && !QDESTROYING(callback.target))
- stack_trace("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not \
- be supported and may refuse to run or run with a 0 wait")
-
- wait = max(Ceilm(wait, world.tick_lag), world.tick_lag)
-
- if(wait >= INFINITY)
- CRASH("Attempted to create timer with INFINITY delay")
-
- timer_subsystem = timer_subsystem || SStimer
-
- // Generate hash if relevant for timed events with the TIMER_UNIQUE flag
- var/hash
- if (flags & TIMER_UNIQUE)
- var/list/hashlist = list(callback.target, "(\ref[callback.target])", callback.callable, flags & TIMER_CLIENT_TIME)
- if(!(flags & TIMER_NO_HASH_WAIT))
- hashlist += wait
- hashlist += callback.params
- hash = hashlist.Join("|||||||")
-
- var/datum/timedevent/hash_timer = timer_subsystem.hashes[hash]
- if(hash_timer)
- if (hash_timer.spent) // it's pending deletion, pretend it doesn't exist.
- hash_timer.hash = null // but keep it from accidentally deleting us
- else
- if (flags & TIMER_OVERRIDE)
- hash_timer.hash = null // no need having it delete it's hash if we are going to replace it
- qdel(hash_timer)
- else
- if (hash_timer.flags & TIMER_STOPPABLE)
- . = hash_timer.id
- return
- else if(flags & TIMER_OVERRIDE)
- stack_trace("TIMER_OVERRIDE used without TIMER_UNIQUE")
-
- var/datum/timedevent/timer = new(callback, wait, flags, timer_subsystem, hash, file && "[file]:[line]")
- return timer.id
-
-/**
- * Delete a timer
- *
- * Arguments:
- * * id a timerid or a /datum/timedevent
- */
-/proc/deltimer(id, datum/controller/subsystem/timer/timer_subsystem)
- if (!id)
- return FALSE
- if (id == TIMER_ID_NULL)
- CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag")
- if (istype(id, /datum/timedevent))
- qdel(id)
- return TRUE
- timer_subsystem = timer_subsystem || SStimer
- //id is string
- var/datum/timedevent/timer = timer_subsystem.timer_id_dict[id]
- if (timer && !timer.spent)
- qdel(timer)
- return TRUE
- return FALSE
-
-/**
- * Get the remaining deciseconds on a timer
- *
- * Arguments:
- * * id a timerid or a /datum/timedevent
- */
-/proc/timeleft(id, datum/controller/subsystem/timer/timer_subsystem)
- if (!id)
- return null
- if (id == TIMER_ID_NULL)
- CRASH("Tried to get timeleft of a null timerid. Use TIMER_STOPPABLE flag")
- if (istype(id, /datum/timedevent))
- var/datum/timedevent/timer = id
- return timer.timeToRun - world.time
- timer_subsystem = timer_subsystem || SStimer
- //id is string
- var/datum/timedevent/timer = timer_subsystem.timer_id_dict[id]
- if(!timer || timer.spent)
- return null
- return timer.timeToRun - (timer.flags & TIMER_CLIENT_TIME ? Uptime() : world.time)
-
-#undef BUCKET_LEN
-#undef BUCKET_POS
-#undef TIMER_MAX
-#undef TIMER_ID_MAX
+ if (timer.hash)
+ subsystem.timers_by_hash -= timer.hash
+ subsystem.queue -= timer
+
+
+/proc/_addtimer(datum/callback/callback, wait, flags, datum/controller/subsystem/timer/subsystem = SStimer, source)
+ var/datum/timer/timer = callback
+ if (!istype(timer))
+ timer = new
+ timer.callback = callback
+ timer.wait = wait
+ timer.flags = flags
+ timer.source = source
+ if (flags & (TIMER_STOPPABLE | TIMER_UNIQUE))
+ var/hash = "[flags][callback.identity]"
+ if (!(flags & TIMER_NO_HASH_WAIT))
+ hash = "[hash][wait]"
+ hash = sha1(hash)
+ if (flags & TIMER_UNIQUE)
+ var/datum/timer/match = subsystem.timers_by_hash[hash]
+ if (match)
+ if (!(flags & TIMER_OVERRIDE))
+ return
+ subsystem.timers_by_hash[hash] = timer
+ subsystem.queue -= match
+ timer.hash = hash
+ timer.fire_time = timer.wait + world.time
+ BINARY_INSERT(timer, subsystem.queue, /datum/timer, timer, fire_time, COMPARE_KEY)
+ return timer.hash
diff --git a/code/controllers/subsystems/turf_fire.dm b/code/controllers/subsystems/turf_fire.dm
index daa37177d7290..23cdaf1dd43a5 100644
--- a/code/controllers/subsystems/turf_fire.dm
+++ b/code/controllers/subsystems/turf_fire.dm
@@ -1,12 +1,11 @@
SUBSYSTEM_DEF(turf_fire)
name = "Turf Fire"
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 2 SECONDS
flags = SS_NO_INIT
var/list/fires = list()
/datum/controller/subsystem/turf_fire/fire()
- for(var/obj/effect/turf_fire/fire as anything in fires)
+ for(var/obj/turf_fire/fire as anything in fires)
fire.Process()
if(MC_TICK_CHECK)
return
diff --git a/code/controllers/subsystems/typing.dm b/code/controllers/subsystems/typing.dm
index 8912b3fdf8183..bcf6c033a61f0 100644
--- a/code/controllers/subsystems/typing.dm
+++ b/code/controllers/subsystems/typing.dm
@@ -1,7 +1,6 @@
SUBSYSTEM_DEF(typing)
name = "Typing"
flags = SS_BACKGROUND | SS_NO_INIT
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 0.5 SECONDS
/// The skin control to poll for TYPING_STATE_INPUT status.
@@ -35,7 +34,7 @@ SUBSYSTEM_DEF(typing)
istyping_input = 0|1,
istyping_hotkey = 0|1
), ...)
- See .proc/GetEntry for details.
+ See PROC_REF(GetEntry for details.
*/
var/static/list/status = list()
@@ -234,3 +233,12 @@ SUBSYSTEM_DEF(typing)
SStyping.UpdateVerbState(client, FALSE)
if (message)
me_verb(message)
+
+/mob/verb/whisper_wrapper()
+ set name = ".Whisper"
+ set hidden = TRUE
+ SStyping.UpdateVerbState(client, TRUE)
+ var/message = input("","whisper (text)") as null | text
+ SStyping.UpdateVerbState(client, FALSE)
+ if (message)
+ whisper(message)
diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm
index 29217fe03db90..1682434dd8895 100644
--- a/code/controllers/subsystems/vote.dm
+++ b/code/controllers/subsystems/vote.dm
@@ -1,9 +1,9 @@
SUBSYSTEM_DEF(vote)
name = "Voting"
wait = 1 SECOND
- priority = SS_PRIORITY_VOTE
+ priority = FIRE_PRIORITY_VOTE
flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING
- runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ runlevels = RUNLEVELS_PREGAME | RUNLEVELS_GAME
var/last_started_time //To enforce delay between votes.
var/antag_added //Enforces a maximum of one added antag per round.
@@ -89,7 +89,6 @@ SUBSYSTEM_DEF(vote)
voting |= C
. = list()
- . += "Voting Panel"
if(active_vote)
. += active_vote.interface(C.mob)
if(admin)
@@ -110,7 +109,7 @@ SUBSYSTEM_DEF(vote)
. += ""
. += "
"
- . += "Close"
+ . += "Close"
return JOINTEXT(.)
/datum/controller/subsystem/vote/proc/show_panel(mob/user)
@@ -119,8 +118,9 @@ SUBSYSTEM_DEF(vote)
if(active_vote)
win_x = active_vote.win_x
win_y = active_vote.win_y
- show_browser(user, interface(user.client),"window=vote;size=[win_x]x[win_y]")
- onclose(user, "vote", src)
+ var/datum/browser/popup = new(user, "vote", "Voting Panel", win_x, win_y)
+ popup.set_content(interface(user.client))
+ popup.open()
/datum/controller/subsystem/vote/proc/close_panel(mob/user)
show_browser(user, null, "window=vote")
diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm
index 6a910d394476a..d50762273e911 100644
--- a/code/controllers/subsystems/zcopy.dm
+++ b/code/controllers/subsystems/zcopy.dm
@@ -1,12 +1,13 @@
-#define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this.
-#define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs.
+#define SHADOWER_DARKENING_FACTOR 0.8 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this.
+#define SHADOWER_DARKENING_COLOR "#00000033" // The above, but as an RGB string for lighting-less turfs.
+//Bay can't do multiplicative lighting for zmimic currently so we change alpha, this does mean full lit turfs need a different colour. TODO: Take another look at zmimic render setup
SUBSYSTEM_DEF(zcopy)
name = "Z-Copy"
wait = 1
init_order = SS_INIT_ZCOPY
- priority = SS_PRIORITY_ZCOPY
- runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ priority = FIRE_PRIORITY_ZCOPY
+ runlevels = RUNLEVELS_PREGAME | RUNLEVELS_GAME
var/list/queued_turfs = list()
var/qt_idex = 1
@@ -181,6 +182,9 @@ SUBSYSTEM_DEF(zcopy)
T.z_generation += 1
T.z_queued -= 1
+ if (T.above)
+ T.above.update_mimic()
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -257,7 +261,7 @@ SUBSYSTEM_DEF(zcopy)
TO.plane = t_target
TO.mouse_opacity = initial(TO.mouse_opacity)
- T.queue_ao(T.ao_neighbors_mimic == null) // If ao_neighbors hasn't been set yet, we need to do a rebuild
+ T.queue_ao(isnull(T.ao_neighbors_mimic)) // If ao_neighbors hasn't been set yet, we need to do a rebuild
// Explicitly copy turf delegates so they show up properly on below levels.
// I think it's possible to get this to work without discrete delegate copy objects, but I'd rather this just work.
@@ -268,7 +272,7 @@ SUBSYSTEM_DEF(zcopy)
var/atom/movable/openspace/turf_mimic/DC = T.below.mimic_above_copy
DC.appearance = T.below
DC.mouse_opacity = initial(DC.mouse_opacity)
- DC.plane = OPENTURF_MAX_PLANE
+ DC.plane = OPENTURF_MAX_PLANE - turf_depth - 1
else if (T.below.mimic_above_copy)
QDEL_NULL(T.below.mimic_above_copy)
@@ -284,7 +288,7 @@ SUBSYSTEM_DEF(zcopy)
// Special case: these are merged into the shadower to reduce memory usage.
if (object.type == /atom/movable/lighting_overlay)
- //T.shadower.copy_lighting(object)
+ T.shadower.copy_lighting(object)
continue
if (!object.bound_overlay) // Generate a new overlay if the atom doesn't already have one.
@@ -297,6 +301,7 @@ SUBSYSTEM_DEF(zcopy)
var/have_performed_fixup = FALSE
switch (object.type)
+ // Layering for recursive mimic needs to be inherited.
if (/atom/movable/openspace/mimic)
var/atom/movable/openspace/mimic/OOO = object
original_type = OOO.mimiced_type
@@ -490,7 +495,7 @@ SUBSYSTEM_DEF(zcopy)
if (mutated)
for (var/i in 1 to length(fixed_overlays))
- if (fixed_overlays[i] == null)
+ if (isnull(fixed_overlays[i]))
fixed_overlays[i] = appearance:overlays[i]
// Scan & fix underlays
@@ -507,7 +512,7 @@ SUBSYSTEM_DEF(zcopy)
if (mutated)
for (var/i in 1 to length(fixed_overlays))
- if (fixed_underlays[i] == null)
+ if (isnull(fixed_underlays[i]))
fixed_underlays[i] = appearance:underlays[i]
// If we did nothing (no violations), don't bother creating a new appearance
@@ -524,7 +529,7 @@ SUBSYSTEM_DEF(zcopy)
MA.layer = FLY_LAYER // probably fine
if (fixed_overlays)
- MA.overlays = fixed_overlays
+ MA.AddOverlays(fixed_overlays)
if (fixed_underlays)
MA.underlays = fixed_underlays
@@ -533,7 +538,7 @@ SUBSYSTEM_DEF(zcopy)
return MA
-#define FMT_DEPTH(X) (X == null ? "(null)" : X)
+#define FMT_DEPTH(X) (isnull(X) ? "(null)" : X)
// This is a dummy object used so overlays can be shown in the analyzer.
/atom/movable/openspace/debug
@@ -607,7 +612,7 @@ SUBSYSTEM_DEF(zcopy)
found_oo += D
temp_objects += D
- sortTim(found_oo, /proc/cmp_planelayer)
+ sortTim(found_oo, GLOBAL_PROC_REF(cmp_planelayer))
var/list/atoms_list_list = list()
for (var/thing in found_oo)
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index e804a2796e54d..8f2a0c85aa9f9 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -33,8 +33,6 @@
debug_variables(paiController)
if("Cameras")
debug_variables(cameranet)
- if("Transfer Controller")
- debug_variables(transfer_controller)
if("Gas Data")
debug_variables(gas_data)
if("Alt Appearance Manager")
diff --git a/code/core/math/math.dm b/code/core/math/math.dm
index 94aaac224219e..b100a2a421399 100644
--- a/code/core/math/math.dm
+++ b/code/core/math/math.dm
@@ -24,4 +24,92 @@ var/global/const/HALF_PI = 1.5707963268
/// True if number is a number that is not nan or an infinity.
/proc/isfinite(number)
- return isnum(number) && number == number && number != POSITIVE_INFINITY && number != NEGATIVE_INFINITY
+ return isnum(number) && !isnan(number) && !isinf(number)
+
+
+/**
+Sample t(0..1) into a quadratic binomial polynomial.
+Generally this is useful for shaping rand() distribution.
+see tools/polyvis.html for a parameter picker.
+*/
+/proc/poly_interp2(t, p0, p1, p2)
+ var/mt = 1 - t
+ return p0 * mt * mt +\
+ 2 * p1 * mt * t +\
+ p2 * t * t
+
+/**
+Sample t(0..1) into a cubic binomial polynomial.
+Generally this is useful for shaping rand() distribution.
+see tools/polyvis.html for a parameter picker.
+More expensive than poly_interp2.
+*/
+/proc/poly_interp3(t, p0, p1, p2, p3)
+ var/t2 = t * t
+ var/mt = 1 - t
+ var/mt2 = mt * mt
+ return p0 * mt2 * mt +\
+ 3 * p1 * mt2 * t +\
+ 3 * p2 * mt * t2 +\
+ p3 * t2 * t
+
+/**
+Sample t(0..1) into a quartic binomial polynomial.
+Generally this is useful for shaping rand() distribution.
+see tools/polyvis.html for a parameter picker.
+More expensive than poly_interp3.
+*/
+/proc/poly_interp4(t, p0, p1, p2, p3, p4)
+ var/t2 = t * t
+ var/t3 = t2 * t
+ var/mt = 1 - t
+ var/mt2 = mt * mt
+ var/mt3 = mt2 * mt
+ return p0 * mt3 * mt +\
+ 4 * p1 * mt3 * t +\
+ 6 * p2 * mt2 * t2 +\
+ 4 * p3 * mt * t3 +\
+ p4 * t3 * t
+
+
+/**
+Get the coordinates that make up a circle of radius on center, packed as (x | y left shift 12).
+These coordinates are able to be outside the world: check (v < 1 || v > world.maxV) for safety.
+Implements the Bresenham Circle Drawing Algorithm for the actual point picking.
+*/
+/proc/get_circle_coordinates(radius, center_x, center_y)
+ var/static/list/cache = list()
+ radius = round(radius, 1)
+ if (radius < 1)
+ return list(center_x | SHIFTL(center_y, 12))
+ center_x = round(center_x, 1)
+ center_y = round(center_y, 1)
+ var/list/points = length(cache) ? cache["[radius]"] : null
+ if (!points)
+ points = list()
+ var/y = radius
+ var/gradient = 3 - 2 * radius
+ for (var/x = 0 to radius)
+ points |= list(
+ radius + x | SHIFTL(radius + y, 12),
+ radius + x | SHIFTL(radius - y, 12),
+ radius - x | SHIFTL(radius + y, 12),
+ radius - x | SHIFTL(radius - y, 12),
+ radius + y | SHIFTL(radius + x, 12),
+ radius + y | SHIFTL(radius - x, 12),
+ radius - y | SHIFTL(radius + x, 12),
+ radius - y | SHIFTL(radius - x, 12)
+ )
+ if (x >= y)
+ break
+ if (gradient < 0)
+ gradient = gradient + 4 * x + 6
+ else
+ gradient = gradient + 4 * (x - y) + 10
+ --y
+ cache["[radius]"] = points
+ var/list/result = points.Copy()
+ var/center = center_x - radius | SHIFTL(center_y - radius, 12)
+ for (var/i = 1 to length(result))
+ result[i] += center
+ return result
diff --git a/code/datums/ai/ai.dm b/code/datums/ai/ai.dm
deleted file mode 100644
index 13985849ac62f..0000000000000
--- a/code/datums/ai/ai.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-/datum/ai
- var/name
-
-/datum/ai/proc/process()
- return PROCESS_KILL
diff --git a/code/datums/appearances/appearance_manager.dm b/code/datums/appearances/appearance_manager.dm
index e83a1d94f772f..78909268ab109 100644
--- a/code/datums/appearances/appearance_manager.dm
+++ b/code/datums/appearances/appearance_manager.dm
@@ -17,10 +17,10 @@ var/global/singleton/appearance_manager/appearance_manager = new()
/singleton/appearance_manager/proc/add_appearance(mob/viewer, datum/appearance_data/ad)
var/PriorityQueue/pq = appearances_[viewer]
if(!pq)
- pq = new/PriorityQueue(/proc/cmp_appearance_data)
+ pq = new/PriorityQueue(GLOBAL_PROC_REF(cmp_appearance_data))
appearances_[viewer] = pq
- GLOB.logged_in_event.register(viewer, src, /singleton/appearance_manager/proc/apply_appearance_images)
- GLOB.destroyed_event.register(viewer, src, /singleton/appearance_manager/proc/remove_appearances)
+ GLOB.logged_in_event.register(viewer, src, TYPE_PROC_REF(/singleton/appearance_manager, apply_appearance_images))
+ GLOB.destroyed_event.register(viewer, src, TYPE_PROC_REF(/singleton/appearance_manager, remove_appearances))
pq.Enqueue(ad)
reset_appearance_images(viewer)
@@ -30,8 +30,8 @@ var/global/singleton/appearance_manager/appearance_manager = new()
if(viewer.client)
viewer.client.images -= ad.images
if(!pq.Length())
- GLOB.logged_in_event.unregister(viewer, src, /singleton/appearance_manager/proc/apply_appearance_images)
- GLOB.destroyed_event.register(viewer, src, /singleton/appearance_manager/proc/remove_appearances)
+ GLOB.logged_in_event.unregister(viewer, src, TYPE_PROC_REF(/singleton/appearance_manager, apply_appearance_images))
+ GLOB.destroyed_event.register(viewer, src, TYPE_PROC_REF(/singleton/appearance_manager, remove_appearances))
appearances_ -= viewer
/singleton/appearance_manager/proc/remove_appearances(mob/viewer)
diff --git a/code/datums/appearances/automatic/_base.dm b/code/datums/appearances/automatic/_base.dm
index 79ee7eab33d06..f65b768320fe1 100644
--- a/code/datums/appearances/automatic/_base.dm
+++ b/code/datums/appearances/automatic/_base.dm
@@ -10,7 +10,7 @@
if(source in appearance_sources)
return FALSE
appearance_sources[source] = new/datum/appearance_data(images, viewers, priority)
- GLOB.destroyed_event.register(source, src, /singleton/appearance_handler/proc/RemoveAltAppearance)
+ GLOB.destroyed_event.register(source, src, TYPE_PROC_REF(/singleton/appearance_handler, RemoveAltAppearance))
/singleton/appearance_handler/proc/RemoveAltAppearance(source)
var/datum/appearance_data/ad = appearance_sources[source]
diff --git a/code/datums/appearances/automatic/cardborg.dm b/code/datums/appearances/automatic/cardborg.dm
index 71a188a471435..6d24a748d473a 100644
--- a/code/datums/appearances/automatic/cardborg.dm
+++ b/code/datums/appearances/automatic/cardborg.dm
@@ -17,7 +17,7 @@
var/image/I = get_image_from_backpack(H)
AddAltAppearance(H, I, GLOB.silicon_mobs+H) //you look like a robot to robots! (including yourself because you're totally a robot)
- GLOB.logged_in_event.register_global(src, /singleton/appearance_handler/cardborg/proc/mob_joined) // Duplicate registration request are handled for us
+ GLOB.logged_in_event.register_global(src, TYPE_PROC_REF(/singleton/appearance_handler/cardborg, mob_joined)) // Duplicate registration request are handled for us
/singleton/appearance_handler/cardborg/proc/item_removed(obj/item/item, mob/user)
if((istype(item, /obj/item/clothing/suit/cardborg) || istype(item, /obj/item/clothing/head/cardborg)) || istype(item, /obj/item/storage/backpack))
@@ -36,7 +36,7 @@
var/image/I = image(icon = 'icons/mob/robots.dmi', icon_state = ca.icon_state, loc = H)
I.override = 1
- I.overlays += image(icon = 'icons/mob/robots.dmi', icon_state = "eyes-[ca.icon_state]") //gotta look realistic
+ I.AddOverlays(image(icon = 'icons/mob/robots.dmi', icon_state = "eyes-[ca.icon_state]"))
return I
/singleton/appearance_handler/cardborg/proc/init_appearances()
@@ -75,10 +75,10 @@
/singleton/cardborg_appearance/science
icon_state = "droid-science"
- backpack_type = /obj/item/storage/backpack/toxins
+ backpack_type = /obj/item/storage/backpack/corpsci
/singleton/cardborg_appearance/science/satchel
- backpack_type = /obj/item/storage/backpack/satchel/tox
+ backpack_type = /obj/item/storage/backpack/satchel/corpsci
/singleton/cardborg_appearance/security
icon_state = "securityrobot"
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index 5e2591f36d777..1c57d865874d6 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -24,11 +24,11 @@
///The beam will qdel if it's longer than this many tiles.
var/max_distance = 0
///the objects placed in the elements list
- var/beam_type = /obj/effect/ebeam
+ var/beam_type = /obj/ebeam
///This is used as the visual_contents of beams, so you can apply one effect to this and the whole beam will look like that. never gets deleted on redrawing.
- var/obj/effect/ebeam/visuals
+ var/obj/ebeam/visuals
-/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=INFINITY,maxdistance=INFINITY,btype = /obj/effect/ebeam)
+/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=INFINITY,maxdistance=INFINITY,btype = /obj/ebeam)
origin = beam_origin
target = beam_target
max_distance = maxdistance
@@ -47,10 +47,12 @@
visuals.icon_state = icon_state
Draw()
//Register for movement events
- GLOB.moved_event.register(origin, src, .proc/redrawing)
- GLOB.moved_event.register(target, src, .proc/redrawing)
- GLOB.destroyed_event.register(origin, src, .proc/redrawing)
- GLOB.destroyed_event.register(target, src, .proc/redrawing)
+ if(istype(origin, /atom/movable))
+ GLOB.moved_event.register(origin, src, PROC_REF(redrawing))
+ if(istype(target, /atom/movable))
+ GLOB.moved_event.register(target, src, PROC_REF(redrawing))
+ GLOB.destroyed_event.register(origin, src, PROC_REF(redrawing))
+ GLOB.destroyed_event.register(target, src, PROC_REF(redrawing))
/**
* Triggered by events set up when the beam is set up. If it's still sane to create a beam, it removes the old beam, creates a new one. Otherwise it kills the beam.
@@ -63,17 +65,17 @@
/datum/beam/proc/redrawing(atom/movable/mover, atom/oldloc, new_loc)
if(!QDELETED(origin) && !QDELETED(target) && get_dist(origin,target)32)
a = Pixel_x > 0 ? round(Pixel_x/32) : Ceilm(Pixel_x/32, 1)
@@ -135,20 +137,27 @@
X.pixel_y = Pixel_y
CHECK_TICK
-/obj/effect/ebeam
- mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
+/obj/ebeam
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
var/datum/beam/owner
-/obj/effect/ebeam/Destroy()
+/obj/ebeam/Destroy()
owner = null
return ..()
-/obj/effect/ebeam/singularity_pull()
+/obj/ebeam/singularity_pull()
return
-/obj/effect/ebeam/singularity_act()
+/obj/ebeam/singularity_act()
return
+//Equivalent to /obj/ebeam, except it also adds an emissive layer
+/obj/ebeam/emissive/on_update_icon()
+ . = ..()
+ var/mutable_appearance/emissive_overlay = emissive_appearance(icon, icon_state, src)
+ var/mutable_appearance/glow_overlay = overlay_image(icon, icon_state, plane = LIGHTING_LAMPS_PLANE) // SS220 Bloom-Light
+ AddOverlays(list(emissive_overlay, glow_overlay)) // SS220 Bloom-Light
+
/**
* This is what you use to start a beam. Example: origin.Beam(target, args). **Store the return of this proc if you don't set maxdist or time, you need it to delete the beam.**
*
@@ -161,7 +170,7 @@
* - maxdistance: how far the beam will go before stopping itself. Used mainly for two things: preventing lag if the beam may go in that direction and setting a range to abilities that use beams.
* - beam_type: The type of your custom beam. This is for adding other wacky stuff for your beam only. Most likely, you won't (and shouldn't) change it.
*/
-/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=INFINITY,maxdistance=INFINITY,beam_type=/obj/effect/ebeam)
+/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=INFINITY,maxdistance=INFINITY,beam_type=/obj/ebeam)
var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type)
- invoke_async(newbeam, /datum/beam/.proc/Start)
+ invoke_async(newbeam, TYPE_PROC_REF(/datum/beam, Start))
return newbeam
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index 03d7cbaeeb204..730d729410db1 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -4,10 +4,10 @@
var/window_id // window_id is used as the window name for browse and onclose
var/width = 0
var/height = 0
- var/atom/ref = null
+ var/weakref/ref = null
var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
- var/stylesheets[0]
- var/scripts[0]
+ var/list/stylesheets = list()
+ var/list/scripts = list()
var/title_image
var/head_elements
var/body_elements
@@ -16,7 +16,15 @@
var/title_buttons = ""
-/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
+/datum/browser/Destroy()
+ if (user)
+ user.unset_machine()
+ user = null
+ ref = null
+ return ..()
+
+
+/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, datum/nref = null)
user = nuser
window_id = nwindow_id
@@ -27,10 +35,7 @@
if (nheight)
height = nheight
if (nref)
- ref = nref
- // If a client exists, but they have disabled fancy windowing, disable it!
- if(user && user.client && user.client.get_preference_value(/datum/client_preference/browser_style) == GLOB.PREF_PLAIN)
- return
+ ref = weakref(nref)
add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
/datum/browser/proc/set_title(ntitle)
@@ -45,9 +50,6 @@
/datum/browser/proc/set_window_options(nwindow_options)
window_options = nwindow_options
-/datum/browser/proc/set_title_image(ntitle_image)
- //title_image = ntitle_image
-
/datum/browser/proc/add_stylesheet(name, file)
stylesheets[name] = file
@@ -79,7 +81,7 @@
return {"
-
+
[head_content]
@@ -104,15 +106,15 @@
[get_footer()]
"}
-/datum/browser/proc/open(use_onclose = 1)
+/datum/browser/proc/open(use_onclose = TRUE)
var/window_size = ""
if (width && height)
window_size = "size=[width]x[height];"
show_browser(user, get_content(), "window=[window_id];[window_size][window_options]")
if (use_onclose)
- onclose(user, window_id, ref)
+ onclose(user, window_id, ref ? ref.resolve() : null)
-/datum/browser/proc/update(force_open = 0, use_onclose = 1)
+/datum/browser/proc/update(force_open = FALSE, use_onclose = TRUE)
if(force_open)
open(use_onclose)
else
@@ -121,24 +123,6 @@
/datum/browser/proc/close()
close_browser(user, "window=[window_id]")
-// This will allow you to show an icon in the browse window
-// This is added to mob so that it can be used without a reference to the browser object
-// There is probably a better place for this...
-/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1)
- /*
- var/icon/I
- if (dir >= 0)
- I = new /icon(icon, icon_state, dir)
- else
- I = new /icon(icon, icon_state)
- dir = "default"
-
- var/filename = "[ckey("[icon]_[icon_state]_[dir]")].png"
- send_rsc(src, I, filename)
- return filename
- */
-
-
// Registers the on-close verb for a browse window (client/verb/.windowclose)
// this will be called when the close-button of a window is pressed.
//
@@ -149,48 +133,40 @@
// e.g. code is : show_browser(user, text, "window=fred")
// then use : onclose(user, "fred")
//
-// Optionally, specify the "ref" parameter as the controlled atom (usually src)
-// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
+// Optionally, specify the "ref" parameter as the controlled datum (usually src)
+// to pass a "close=1" parameter to the datum's Topic() proc for special handling.
// Otherwise, the user mob's machine var will be reset directly.
//
-/proc/onclose(mob/user, windowid, atom/ref=null)
- if(!user || !user.client) return
+/proc/onclose(mob/user, windowid, datum/ref = null)
+ if(!user || !user.client)
+ return
+
var/param = "null"
if(ref)
- param = "\ref[ref]"
+ param = ref(ref)
spawn(2)
- if(!user.client) return
- winset(user, windowid, "on-close=\".windowclose [param]\"")
-
-// log_debug("OnClose [user]: [windowid] : ["on-close=\".windowclose [param]\""]")
-
+ if(!user.client)
+ return
+ winset(user, windowid, "on-close=\".windowclose [param]\"")
// the on-close client verb
// called when a browser popup window is closed after registering with proc/onclose()
-// if a valid atom reference is supplied, call the atom's Topic() with "close=1"
+// if a valid datum reference is supplied, call the datum's Topic() with "close=1"
// otherwise, just reset the client mob's machine var.
-//
-/client/verb/windowclose(atomref as text)
- set hidden = 1 // hide this verb from the user's panel
+/client/verb/windowclose(datum_ref as text)
+ set hidden = TRUE // hide this verb from the user's panel
set name = ".windowclose" // no autocomplete on cmd line
-// log_debug("windowclose: [atomref]")
-
- if(atomref!="null") // if passed a real atomref
- var/hsrc = locate(atomref) // find the reffed atom
+ if(datum_ref != "null") // if passed a real datum
+ var/hsrc = locate(datum_ref) // find the reffed datum
if(hsrc)
-// log_debug("[src] Topic [href] [hsrc]")
-
usr = src.mob
- src.Topic("close=1", list("close"="1"), hsrc) // this will direct to the atom's
- return // Topic() proc via client.Topic()
+ src.Topic("close=1", list("close"="1"), hsrc) // this will direct to the datum's
+ return
// no atomref specified (or not found)
// so just reset the user mob's machine var
if(src && src.mob)
-// log_debug("[src] was [src.mob.machine], setting to null")
-
src.mob.unset_machine()
- return
diff --git a/code/datums/callbacks.dm b/code/datums/callbacks.dm
index bea75c1658d2a..1d29e5d6cd2cf 100644
--- a/code/datums/callbacks.dm
+++ b/code/datums/callbacks.dm
@@ -3,7 +3,7 @@
Callbacks wrap a target, callable, and arguments to pass. See the dm reference for call().
When the target is GLOBAL_PROC, the callable is global - otherwise it is a datum (or dead) reference.
Callbacks are created with the new keyword via a global alias like:
-- var/datum/callback/instance = new Callback(GLOBAL_PROC, /proc/get_area, someObject)
+- var/datum/callback/instance = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_area), someObject)
Callbacks are thin - they should be used with invoke or invoke_async.
** Invocation
@@ -17,18 +17,18 @@ on the first sleep, and so should be used only where results are not required.
Callables are proc names or proc references, with references preferred for safety (in most cases).
These vary between 515 and older major versions:
Before 515:
-- .proc/name refers to the last override of name on target, OR the global proc name.
+- PROC_REF(name refers to the last override of name on target, OR the global proc name.
After 515:
- src::name() must be used for the last override, or ::name() for the global.
- nameof() is available at compile time to resolve safe proc names like nameof(/datum::fooBehavior()).
- This can be preferable to direct refs in complex cases.
+This can be preferable to direct refs in complex cases.
A specific version of a proc may be called by fully specifying its type depth, like
-invoke(myLivingMob, /mob/living/proc/handle_vision)
+invoke(myLivingMob, TYPE_PROC_REF(/mob/living, handle_vision)
** Timers
Timers accept callbacks as their first argument. For full timer documentation, see the timedevent
datum. For example:
-addTimer(new Callback(myMob, myMob::drop_l_hand()), 10 SECONDS)
+addTimer(CALLBACK(myMob, proc_ref(drop_l_hand())), 10 SECONDS)
*/
var/global/const/GLOBAL_PROC = FALSE
diff --git a/code/datums/category.dm b/code/datums/category.dm
index ba088b19affaa..f73fa5179a6ca 100644
--- a/code/datums/category.dm
+++ b/code/datums/category.dm
@@ -2,26 +2,30 @@
* Category Collection *
**********************/
/datum/category_collection
- var/category_group_type // Type of categories to initialize
- var/list/datum/category_group/categories // List of initialized categories
- var/list/datum/category_group/categories_by_name // Associative list of initialized categories, keyed by name
+ /// Type of categories to initialize
+ var/category_group_type
+ /// Lazy list of initialized categories
+ var/list/datum/category_group/categories
+ /// Lazy associative list of initialized categories, keyed by name
+ var/list/datum/category_group/categories_by_name
/datum/category_collection/New()
..()
- categories = new()
- categories_by_name = new()
- for(var/category_type in typesof(category_group_type))
- var/datum/category_group/category = category_type
- if(initial(category.name))
- category = new category(src)
- categories += category
- categories_by_name[category.name] = category
- categories = dd_sortedObjectList(categories)
+
+ for(var/datum/category_group/category_type as anything in typesof(category_group_type))
+ if(!initial(category_type.name))
+ continue
+
+ var/datum/category_group/category = new category_type(src)
+ LAZYADD(categories, category)
+ LAZYADDASSOC(categories_by_name, category.name, category)
+
+ if(LAZYLEN(categories))
+ categories = dd_sortedObjectList(categories)
/datum/category_collection/Destroy()
- for(var/category in categories)
- qdel(category)
- categories.Cut()
+ QDEL_NULL_LIST(categories)
+ LAZYCLEARLIST(categories_by_name)
return ..()
/******************
@@ -37,24 +41,21 @@
/datum/category_group/New(datum/category_collection/cc)
..()
collection = cc
- items = new()
- items_by_name = new()
+ for(var/datum/category_item/item_type as anything in typesof(category_item_type))
+ if(!initial(item_type.name))
+ continue
- for(var/item_type in typesof(category_item_type))
- var/datum/category_item/item = item_type
- if(initial(item.name))
- item = new item(src)
- items += item
- items_by_name[item.name] = item
+ var/datum/category_item/item = new item_type(src)
+ LAZYADD(items, item)
+ LAZYSET(items_by_name, item.name, item)
// For whatever reason dd_insertObjectList(items, item) doesn't insert in the correct order
// If you change this, confirm that character setup doesn't become completely unordered.
- items = dd_sortedObjectList(items)
+ if(LAZYLEN(items))
+ items = dd_sortedObjectList(items)
/datum/category_group/Destroy()
- for(var/item in items)
- qdel(item)
- items.Cut()
+ QDEL_NULL_LIST(items)
collection = null
return ..()
diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm
new file mode 100644
index 0000000000000..9dd56c24d35b1
--- /dev/null
+++ b/code/datums/chat_message.dm
@@ -0,0 +1,394 @@
+/// How long the chat message's spawn-in animation will occur for
+#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS
+/// How long the chat message will exist prior to any exponential decay
+#define CHAT_MESSAGE_LIFESPAN 5 SECONDS
+/// How long the chat message's end of life fading animation will occur for
+#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS
+/// Grace period for fade before we actually delete the chat message
+#define CHAT_MESSAGE_GRACE_PERIOD 0.2 SECONDS
+
+/// Factor of how much the message index (number of messages) will account to exponential decay
+#define CHAT_MESSAGE_EXP_DECAY 0.7
+/// Factor of how much height will account to exponential decay
+#define CHAT_MESSAGE_HEIGHT_DECAY 0.9
+/// Approximate height in pixels of an 'average' line, used for height decay
+#define CHAT_MESSAGE_APPROX_LHEIGHT 11
+
+/// Max default runechat message length in characters
+#define CHAT_MESSAGE_LENGTH 68
+/// Max extended runechat message length in characters
+#define CHAT_MESSAGE_EXT_LENGTH 150
+/// Max default runechat message width in pixels
+#define CHAT_MESSAGE_WIDTH 96
+/// Max extended runechat message width in pixels
+#define CHAT_MESSAGE_EXT_WIDTH 128
+
+// Tweak these defines to change the available color ranges
+#define CM_COLOR_SAT_MIN 0.6
+#define CM_COLOR_SAT_MAX 0.7
+#define CM_COLOR_LUM_MIN 0.65
+#define CM_COLOR_LUM_MAX 0.8
+
+/// Macro from Lummox used to get height from a MeasureText proc.
+/// resolves the MeasureText() return value once, then resolves the height, then sets return_var to that.
+#define WXH_TO_HEIGHT(measurement, return_var) \
+ do { \
+ var/_measurement = measurement; \
+ return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \
+ } while(FALSE);
+
+// Cached runechat icon
+GLOBAL_LIST_EMPTY(runechat_image_cache)
+
+/hook/startup/proc/runechat_images()
+ var/image/radio_image = image('icons/chaticons.dmi', icon_state = "radio")
+ GLOB.runechat_image_cache["radio"] = radio_image
+
+ var/image/emote_image = image('icons/chaticons.dmi', icon_state = "emote")
+ GLOB.runechat_image_cache["emote"] = emote_image
+
+ return TRUE
+
+/**
+ * # Chat Message Overlay
+ *
+ * Datum for generating a message overlay on the map
+ * Ported from TGStation; https://github.com/tgstation/tgstation/pull/50608, author: bobbahbrown
+ */
+/datum/chatmessage
+ /// The visual element of the chat message
+ var/image/message
+ /// The location in which the message is appearing
+ var/atom/message_loc
+ /// The client who heard this message
+ var/client/owned_by
+ /// Contains the scheduled destruction time, used for scheduling EOL
+ var/scheduled_destruction
+ /// Contains the time that the EOL for the message will be complete, used for qdel scheduling
+ var/eol_complete
+ /// Contains the approximate amount of lines for height decay
+ var/approx_lines
+ /// Contains the reference to the next chatmessage in the bucket, used by runechat subsystem
+ var/datum/chatmessage/next
+ /// Contains the reference to the previous chatmessage in the bucket, used by runechat subsystem
+ var/datum/chatmessage/prev
+ /// The current index used for adjusting the layer of each sequential chat message such that recent messages will overlay older ones
+ var/static/current_z_idx = 0
+ /// When we started animating the message
+ var/animate_start = 0
+ /// Our animation lifespan, how long this message will last
+ var/animate_lifespan = 0
+
+/**
+ * Constructs a chat message overlay
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
+/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN)
+ . = ..()
+ if (!istype(target))
+ crash_with("Invalid target given for chatmessage")
+ qdel(src)
+ return
+ if(QDELETED(owner) || !istype(owner) || !owner.client)
+ crash_with("[src.type] created with [isnull(owner) ? "null" : "invalid"] mob owner")
+ qdel(src)
+ return
+ invoke_async(src, PROC_REF(generate_image), text, target, owner, extra_classes, lifespan)
+
+/datum/chatmessage/Destroy()
+ if (!QDELING(owned_by))
+ if(world.timeofday < animate_start + animate_lifespan)
+ crash_with("Del'd before we finished fading, with [(animate_start + animate_lifespan) - world.timeofday] time left")
+
+ if (owned_by.seen_messages)
+ LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src)
+ owned_by.images.Remove(message)
+
+ owned_by = null
+ message_loc = null
+ message = null
+ return ..()
+
+/datum/chatmessage/proc/unregister_and_qdel_self()
+ GLOB.destroyed_event.unregister(owned_by, src, PROC_REF(unregister_and_qdel_self))
+ qdel_self()
+
+
+/**
+ * Generates a chat message image representation
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
+/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan)
+ // Register client who owns this message
+ owned_by = owner.client
+
+ // Remove spans in the message from things like the recorder
+ var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi")
+ text = replacetext(text, span_check, "")
+
+ // Clip message
+ var/extra_length = owned_by.get_preference_value(/datum/client_preference/runechat_messages_length) == GLOB.PREF_LONG
+ var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH
+ var/msgwidth = extra_length ? CHAT_MESSAGE_EXT_WIDTH : CHAT_MESSAGE_WIDTH
+ if (length_char(text) > maxlen)
+ text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment
+
+ // Calculate target color if not already present
+ if (!target.chat_color || target.chat_color_name != target.name)
+ target.chat_color = colorize_string(target.name)
+ target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85)
+ target.chat_color_name = target.name
+
+ // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag
+ var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g")
+ text = replacetext(text, url_scheme, "")
+
+ // Reject whitespace
+ var/static/regex/whitespace = new(@"^\s*$")
+ if (whitespace.Find(text))
+ qdel(src)
+ return
+
+ // If haven't been deleted, watch for the owner
+ GLOB.destroyed_event.register(owned_by, src, PROC_REF(unregister_and_qdel_self))
+
+ // Non mobs speakers can be small
+ if (!ismob(target))
+ extra_classes |= "small"
+
+ // Why are you yelling?
+ if(copytext_char(text, -2) == "!!")
+ extra_classes |= "yell"
+
+ // Append radio icon if from a virtual speaker
+ if (extra_classes.Find("virtual-speaker"))
+ var/image/r_icon = image('icons/chaticons.dmi', icon_state = "radio")
+ text = "\icon[r_icon] " + text
+ else if (extra_classes.Find("emote"))
+ var/image/r_icon = image('icons/chaticons.dmi', icon_state = "emote")
+ text = "\icon[r_icon] " + text
+
+ // We dim italicized text to make it more distinguishable from regular text
+ var/tgt_color = target.chat_color
+ if (extra_classes.Find("italics") || extra_classes.Find("emote"))
+ tgt_color = target.chat_color_darkened
+
+ // Approximate text height
+ // Note we have to replace HTML encoded metacharacters otherwise MeasureText will return a zero height
+ // BYOND Bug #2563917
+ // Construct text
+ var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g")
+ var/complete_text = "[text]"
+ var/mheight
+ WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, msgwidth), mheight)
+
+ invoke_async(src, PROC_REF(finish_image_generation), mheight, target, owner, complete_text, lifespan)
+
+/// Finishes the image generation after the MeasureText() call in generate_image().
+/// Necessary because after that call the proc can resume at the end of the tick and cause overtime.
+/datum/chatmessage/proc/finish_image_generation(mheight, atom/target, mob/owner, complete_text, lifespan)
+ var/rough_time = world.timeofday
+ approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
+
+ // Translate any existing messages upwards, apply exponential decay factors to timers
+ message_loc = isturf(target) ? target : get_atom_on_turf(target)
+ if (owned_by.seen_messages)
+ var/idx = 1
+ var/combined_height = approx_lines
+ for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc])
+ combined_height += m.approx_lines
+
+ var/time_spent = rough_time - m.animate_start
+ var/time_before_fade = m.animate_lifespan - CHAT_MESSAGE_EOL_FADE
+
+ // When choosing to update the remaining time we have to be careful not to update the
+ // scheduled time once the EOL has been executed.
+ if (time_spent >= time_before_fade)
+ animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
+ continue
+
+ var/remaining_time = time_before_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
+ // Ensure we don't accidentially spike alpha up or something silly like that
+ m.message.alpha = m.get_current_alpha(time_spent)
+ if (remaining_time > 0)
+ // Stay faded in for a while, then
+ animate(m.message, alpha = 255, remaining_time)
+ // Fade out
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
+ m.animate_lifespan = remaining_time + CHAT_MESSAGE_EOL_FADE
+ else
+ // Your time has come my son
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
+ // We run this after the alpha animate, because we don't want to interrup it, but also don't want to block it by running first
+ // Sooo instead we do this. bit messy but it fuckin works
+ animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
+
+ // Build message image
+ message = image(loc = message_loc, layer = ABOVE_HUMAN_LAYER)
+ message.plane = RUNECHAT_PLANE
+ message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
+ message.alpha = 0
+ message.pixel_y = target.maptext_height
+ message.maptext_width = CHAT_MESSAGE_WIDTH
+ message.maptext_height = mheight * 1.25
+ message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
+ message.maptext = complete_text
+
+ // View the message
+ LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
+ owned_by.images |= message
+
+ // Fade in
+ animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
+ var/time_before_fade = lifespan - CHAT_MESSAGE_SPAWN_TIME - CHAT_MESSAGE_EOL_FADE
+
+ // Stay faded in
+ animate(alpha = 255, time = time_before_fade)
+
+ // Fade out
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
+
+ // Desctruct yourself
+ addtimer(CALLBACK(src, PROC_REF(unregister_and_qdel_self)), lifespan + CHAT_MESSAGE_GRACE_PERIOD, TIMER_UNIQUE|TIMER_OVERRIDE)
+
+/datum/chatmessage/proc/get_current_alpha(time_spent)
+ if(time_spent < CHAT_MESSAGE_SPAWN_TIME)
+ return (time_spent / CHAT_MESSAGE_SPAWN_TIME) * 255
+
+ var/time_before_fade = animate_lifespan - CHAT_MESSAGE_EOL_FADE
+ if(time_spent <= time_before_fade)
+ return 255
+
+ return (1 - ((time_spent - time_before_fade) / CHAT_MESSAGE_EOL_FADE)) * 255
+
+/**
+ * Creates a message overlay at a defined location for a given speaker
+ *
+ * Arguments:
+ * * speaker - The atom who is saying this message
+ * * message - The text content of the message
+ * * italics - Decides if this should be small or not, as generally italics text are for whisper/radio overhear
+ * * existing_extra_classes - Additional classes to add to the message
+ */
+/mob/proc/create_chat_message(atom/movable/speaker, message, italics, list/existing_extra_classes, audible = TRUE)
+ if(!client)
+ return
+
+ if (!config.runechat_enabled)
+ return
+
+ // Doesn't want to hear
+ if(ismob(speaker) && client.get_preference_value(/datum/client_preference/runechat_mob) != GLOB.PREF_YES)
+ return
+ if(isobj(speaker) && client.get_preference_value(/datum/client_preference/runechat_obj) != GLOB.PREF_YES)
+ return
+
+ // Incapable of receiving
+ if((audible && is_deaf()) || (!audible && is_blind()))
+ return
+
+ // Check for virtual speakers (aka hearing a message through a radio)
+ if(existing_extra_classes.Find("radio"))
+ return
+
+ /* Not currently necessary
+ message = strip_html_properly(message)
+ if(!message)
+ return
+ */
+
+ var/list/extra_classes = list()
+ extra_classes += existing_extra_classes
+
+ if(italics)
+ extra_classes |= "italics"
+
+ // Display visual above source
+ new /datum/chatmessage(message, speaker, src, extra_classes)
+
+/**
+ * Gets a color for a name, will return the same color for a given string consistently within a round.atom
+ *
+ * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
+ *
+ * Arguments:
+ * * name - The name to generate a color for
+ * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
+ * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
+ */
+/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1)
+ // seed to help randomness
+ var/static/rseed = rand(1,26)
+
+ // get hsl using the selected 6 characters of the md5 hash
+ var/hash = copytext(md5(name), rseed, rseed + 6)
+ var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255)
+ var/s = SHIFTR(hex2num(copytext(hash, 3, 5)), 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN
+ var/l = SHIFTR(hex2num(copytext(hash, 5, 7)), 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN
+
+ // adjust for shifts
+ s *= clamp(sat_shift, 0, 1)
+ l *= clamp(lum_shift, 0, 1)
+
+ // convert to rgba
+ var/h_int = round(h/60) // mapping each section of H to 60 degree sections
+ var/c = (1 - abs(2 * l - 1)) * s
+ var/x = c * (1 - abs((h / 60) % 2 - 1))
+ var/m = l - c * 0.5
+ x = (x + m) * 255
+ c = (c + m) * 255
+ m *= 255
+ switch(h_int)
+ if(0)
+ return rgb(c,x,m)
+ if(1)
+ return rgb(x,c,m)
+ if(2)
+ return rgb(m,c,x)
+ if(3)
+ return rgb(m,x,c)
+ if(4)
+ return rgb(x,m,c)
+ if(5)
+ return rgb(c,m,x)
+
+/atom/proc/runechat_message(message, range = world.view, italics, list/classes = list(), audible = TRUE)
+ var/list/hearing_mobs = list()
+ var/list/objs = list()
+ get_mobs_and_objs_in_view_fast(get_turf(src), range, hearing_mobs, objs, checkghosts = FALSE)
+
+ for(var/mob in hearing_mobs)
+ var/mob/M = mob
+ if(!M.client)
+ continue
+ M.create_chat_message(src, message, italics, classes, audible)
+
+
+#undef CHAT_MESSAGE_SPAWN_TIME
+#undef CHAT_MESSAGE_LIFESPAN
+#undef CHAT_MESSAGE_EOL_FADE
+#undef CHAT_MESSAGE_GRACE_PERIOD
+#undef CHAT_MESSAGE_EXP_DECAY
+#undef CHAT_MESSAGE_HEIGHT_DECAY
+#undef CHAT_MESSAGE_APPROX_LHEIGHT
+#undef CHAT_MESSAGE_LENGTH
+#undef CHAT_MESSAGE_EXT_LENGTH
+#undef CHAT_MESSAGE_WIDTH
+#undef CHAT_MESSAGE_EXT_WIDTH
+
+#undef CM_COLOR_SAT_MIN
+#undef CM_COLOR_SAT_MAX
+#undef CM_COLOR_LUM_MIN
+#undef CM_COLOR_LUM_MAX
diff --git a/code/datums/chat_payload.dm b/code/datums/chat_payload.dm
new file mode 100644
index 0000000000000..fd35bbc4eecf6
--- /dev/null
+++ b/code/datums/chat_payload.dm
@@ -0,0 +1,16 @@
+/// Stores information about a chat payload
+/datum/chat_payload
+ /// Sequence number of this payload
+ var/sequence = 0
+ /// Message we are sending
+ var/list/content
+ /// Resend count
+ var/resends = 0
+
+/// Converts the chat payload into a JSON string
+/datum/chat_payload/proc/into_message()
+ return "{\"sequence\":[sequence],\"content\":[json_encode(content)]}"
+
+/// Returns an HTML-encoded message from our contents.
+/datum/chat_payload/proc/get_content_as_html()
+ return message_to_html(content)
diff --git a/code/datums/communication/dsay.dm b/code/datums/communication/dsay.dm
index 464a31c8cf947..647b3aec07d8d 100644
--- a/code/datums/communication/dsay.dm
+++ b/code/datums/communication/dsay.dm
@@ -47,7 +47,7 @@
return FALSE
if(istype(C) && M.is_key_ignored(C.key))
return FALSE
- if (M.client.holder)
+ if (M.client && M.client.holder)
return TRUE
if(M.stat != DEAD)
return FALSE
diff --git a/code/datums/communication/looc.dm b/code/datums/communication/looc.dm
index 24dda1415f433..af87c8015046d 100644
--- a/code/datums/communication/looc.dm
+++ b/code/datums/communication/looc.dm
@@ -18,7 +18,7 @@
/singleton/communication_channel/ooc/looc/do_communicate(client/C, message)
var/mob/M = C.mob ? C.mob.get_looc_mob() : null
- var/list/listening_hosts = hosts_in_view_range(M)
+ var/list/listening_hosts = hearers_in_range(M)
var/list/listening_clients = list()
var/key = C.key
diff --git a/code/datums/communication/ooc.dm b/code/datums/communication/ooc.dm
index 426b0ff12fac1..7eb32f36b5044 100644
--- a/code/datums/communication/ooc.dm
+++ b/code/datums/communication/ooc.dm
@@ -33,11 +33,12 @@
var/can_badmin = !is_stealthed && can_select_ooc_color(C) && (C.prefs.ooccolor != initial(C.prefs.ooccolor))
var/ooc_color = C.prefs.ooccolor
+ var/ckey_prefix = C.donator_info.get_decorated_message(C, "[C.key]:")
for(var/client/target in GLOB.clients)
if(target.is_key_ignored(C.key)) // If we're ignored by this person, then do nothing.
continue
- var/sent_message = "[create_text_tag("ooc", "OOC:", target)] [C.key]: [SPAN_CLASS("message linkify", "[message]")]"
+ var/sent_message = "[create_text_tag("ooc", "OOC:", target)] [ckey_prefix] [SPAN_CLASS("message linkify", "[message]")]"
if(can_badmin)
receive_communication(C, target, SPAN_COLOR(ooc_color, SPAN_CLASS("ooc", sent_message)))
else
diff --git a/code/datums/communication/pray.dm b/code/datums/communication/pray.dm
index be382d0fd7a3f..c1856a9b1c158 100644
--- a/code/datums/communication/pray.dm
+++ b/code/datums/communication/pray.dm
@@ -6,7 +6,7 @@
mute_setting = MUTE_PRAY
/singleton/communication_channel/pray/do_communicate(mob/communicator, message, speech_method_type)
- var/image/cross = image('icons/obj/storage.dmi',"bible")
+ var/image/cross = image('icons/obj/books.dmi',"bible")
for(var/mob/M in GLOB.player_list)
if(!M.client)
continue
diff --git a/code/datums/components/COMPONENT_TEMPLATE.md b/code/datums/components/COMPONENT_TEMPLATE.md
new file mode 100644
index 0000000000000..7b08205888522
--- /dev/null
+++ b/code/datums/components/COMPONENT_TEMPLATE.md
@@ -0,0 +1,55 @@
+
+# Template file for your new component
+
+See _component.dm for detailed explanations
+
+```dm
+/datum/component/mycomponent
+ //can_transfer = TRUE // Must have PostTransfer
+ //dupe_mode = COMPONENT_DUPE_ALLOWED // code/__DEFINES/dcs/flags.dm
+ var/myvar
+
+/datum/component/mycomponent/Initialize(myargone, myargtwo)
+ if(myargone)
+ myvar = myargone
+ if(myargtwo)
+ send_to_playing_players(myargtwo)
+
+/datum/component/mycomponent/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_NOT_REAL, PROC_REF(signalproc)) // RegisterSignal can take a signal name by itself,
+ RegisterSignal(parent, list(COMSIG_NOT_REAL_EITHER, COMSIG_ALMOST_REAL), PROC_REF(otherproc)) // or a list of them to assign to the same proc
+
+/datum/component/mycomponent/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_NOT_REAL) // UnregisterSignal has similar behavior
+ UnregisterSignal(parent, list( // But you can just include all registered signals in one call
+ COMSIG_NOT_REAL,
+ COMSIG_NOT_REAL_EITHER,
+ COMSIG_ALMOST_REAL,
+ ))
+
+/datum/component/mycomponent/proc/signalproc(datum/source)
+ SIGNAL_HANDLER
+ send_to_playing_players("[source] signaled [src]!")
+
+/*
+/datum/component/mycomponent/InheritComponent(datum/component/mycomponent/old, i_am_original, list/arguments)
+ myvar = old.myvar
+
+ if(i_am_original)
+ send_to_playing_players("No parent should have to bury their child")
+*/
+
+/*
+/datum/component/mycomponent/PreTransfer()
+ send_to_playing_players("Goodbye [parent], I'm getting adopted")
+
+/datum/component/mycomponent/PostTransfer()
+ send_to_playing_players("Hello my new parent, [parent]! It's nice to meet you!")
+*/
+
+/*
+/datum/component/mycomponent/CheckDupeComponent(datum/mycomponent/new, myargone, myargtwo)
+ if(myargone == myvar)
+ return TRUE
+*/
+```
diff --git a/code/datums/components/README.md b/code/datums/components/README.md
new file mode 100644
index 0000000000000..db8bf10a327f6
--- /dev/null
+++ b/code/datums/components/README.md
@@ -0,0 +1,9 @@
+# Datum Component System (DCS)
+
+## Concept
+
+Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
+
+### [HackMD page for an introduction to the system as a whole.](https://hackmd.io/@tgstation/SignalsComponentsElements)
+
+### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm)
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
new file mode 100644
index 0000000000000..07b749fd92493
--- /dev/null
+++ b/code/datums/components/_component.dm
@@ -0,0 +1,497 @@
+/**
+ * # Component
+ *
+ * The component datum
+ *
+ * A component should be a single standalone unit
+ * of functionality, that works by receiving signals from it's parent
+ * object to provide some single functionality (i.e a slippery component)
+ * that makes the object it's attached to cause people to slip over.
+ * Useful when you want shared behaviour independent of type inheritance
+ */
+/datum/component
+ /**
+ * Defines how duplicate existing components are handled when added to a datum
+ *
+ * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options
+ */
+ var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
+
+ /**
+ * The type to check for duplication
+ *
+ * `null` means exact match on `type` (default)
+ *
+ * Any other type means that and all subtypes
+ */
+ var/dupe_type
+
+ /// The datum this components belongs to
+ var/datum/parent
+
+ /**
+ * Only set to true if you are able to properly transfer this component
+ *
+ * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used
+ *
+ * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling
+ */
+ var/can_transfer = FALSE
+
+ /// A lazy list of the sources for this component
+ var/list/sources
+
+/**
+ * Create a new component.
+ *
+ * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize]
+ *
+ * Arguments:
+ * * datum/P the parent datum this component reacts to signals from
+ */
+/datum/component/New(list/raw_args)
+ parent = raw_args[1]
+ var/list/arguments = raw_args.Copy(2)
+ if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
+ stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
+ qdel(src, TRUE, TRUE)
+ return
+
+ _JoinParent(parent)
+
+/**
+ * Called during component creation with the same arguments as in new excluding parent.
+ *
+ * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
+ */
+/datum/component/proc/Initialize(...)
+ return
+
+/**
+ * Properly removes the component from `parent` and cleans up references
+ *
+ * Arguments:
+ * * force - makes it not check for and remove the component from the parent
+ */
+/datum/component/Destroy(force = FALSE)
+ if(!parent)
+ return ..()
+ if(!force)
+ _RemoveFromParent()
+ SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
+ parent = null
+ return ..()
+
+/**
+ * Internal proc to handle behaviour of components when joining a parent
+ */
+/datum/component/proc/_JoinParent()
+ var/datum/P = parent
+ //lazy init the parent's dc list
+ var/list/dc = P._datum_components
+ if(!dc)
+ P._datum_components = dc = list()
+
+ //set up the typecache
+ var/our_type = type
+ for(var/I in _GetInverseTypeList(our_type))
+ var/test = dc[I]
+ if(test) //already another component of this type here
+ var/list/components_of_type
+ if(!length(test))
+ components_of_type = list(test)
+ dc[I] = components_of_type
+ else
+ components_of_type = test
+ if(I == our_type) //exact match, take priority
+ var/inserted = FALSE
+ for(var/J in 1 to length(components_of_type))
+ var/datum/component/C = components_of_type[J]
+ if(C.type != our_type) //but not over other exact matches
+ components_of_type.Insert(J, I)
+ inserted = TRUE
+ break
+ if(!inserted)
+ components_of_type += src
+ else //indirect match, back of the line with ya
+ components_of_type += src
+ else //only component of this type, no list
+ dc[I] = src
+
+ RegisterWithParent()
+
+/**
+ * Internal proc to handle behaviour when being removed from a parent
+ */
+/datum/component/proc/_RemoveFromParent()
+ var/datum/parent = src.parent
+ var/list/parents_components = parent._datum_components
+ for(var/I in _GetInverseTypeList())
+ var/list/components_of_type = parents_components[I]
+
+ if(length(components_of_type)) //
+ var/list/subtracted = components_of_type - src
+
+ if(length(subtracted) == 1) //only 1 guy left
+ parents_components[I] = subtracted[1] //make him special
+ else
+ parents_components[I] = subtracted
+
+ else //just us
+ parents_components -= I
+
+ if(!length(parents_components))
+ parent._datum_components = null
+
+ UnregisterFromParent()
+
+/**
+ * Register the component with the parent object
+ *
+ * Use this proc to register with your parent object
+ *
+ * Overridable proc that's called when added to a new parent
+ */
+/datum/component/proc/RegisterWithParent()
+ return
+
+/**
+ * Unregister from our parent object
+ *
+ * Use this proc to unregister from your parent object
+ *
+ * Overridable proc that's called when removed from a parent
+ * *
+ */
+/datum/component/proc/UnregisterFromParent()
+ return
+
+/**
+ * Called when the component has a new source registered.
+ * Return COMPONENT_INCOMPATIBLE to signal that the source is incompatible and should not be added
+ */
+/datum/component/proc/on_source_add(source, ...)
+ SHOULD_CALL_PARENT(TRUE)
+ if(dupe_mode != COMPONENT_DUPE_SOURCES)
+ return COMPONENT_INCOMPATIBLE
+ LAZYOR(sources, source)
+
+/**
+ * Called when the component has a source removed.
+ * You probably want to call parent after you do your logic because at the end of this we qdel if we have no sources remaining!
+ */
+/datum/component/proc/on_source_remove(source)
+ SHOULD_CALL_PARENT(TRUE)
+ if(dupe_mode != COMPONENT_DUPE_SOURCES)
+ CRASH("Component '[type]' does not use sources but is trying to remove a source")
+ LAZYREMOVE(sources, source)
+ if(!LAZYLEN(sources))
+ qdel(src)
+
+/**
+ * Called on a component when a component of the same type was added to the same parent
+ *
+ * See [/datum/component/var/dupe_mode]
+ *
+ * `C`'s type will always be the same of the called component
+ */
+/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
+ return
+
+
+/**
+ * Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE]
+ *
+ * See [/datum/component/var/dupe_mode]
+ *
+ * `C`'s type will always be the same of the called component
+ *
+ * return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component
+ */
+/datum/component/proc/CheckDupeComponent(datum/component/C, ...)
+ return
+
+
+/**
+ * Callback Just before this component is transferred
+ *
+ * Use this to do any special cleanup you might need to do before being deregged from an object
+ */
+/datum/component/proc/PreTransfer()
+ return
+
+/**
+ * Callback Just after a component is transferred
+ *
+ * Use this to do any special setup you need to do after being moved to a new object
+ *
+ * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
+ */
+/datum/component/proc/PostTransfer()
+ return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it
+
+/**
+ * Internal proc to create a list of our type and all parent types
+ */
+/datum/component/proc/_GetInverseTypeList(our_type = type)
+ //we can do this one simple trick
+ . = list(our_type)
+ var/current_type = parent_type
+ //and since most components are root level + 1, this won't even have to run
+ while (current_type != /datum/component)
+ . += current_type
+ current_type = type2parent(current_type)
+
+// The type arg is casted so initial works, you shouldn't be passing a real instance into this
+/**
+ * Return any component assigned to this datum of the given type
+ *
+ * This will throw an error if it's possible to have more than one component of that type on the parent
+ *
+ * Arguments:
+ * * datum/component/c_type The typepath of the component you want to get a reference to
+ */
+/datum/proc/GetComponent(datum/component/c_type)
+ RETURN_TYPE(c_type)
+ if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
+ stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
+ var/list/dc = _datum_components
+ if(!dc)
+ return null
+ . = dc[c_type]
+ if(length(.))
+ return .[1]
+
+// The type arg is casted so initial works, you shouldn't be passing a real instance into this
+/**
+ * Return any component assigned to this datum of the exact given type
+ *
+ * This will throw an error if it's possible to have more than one component of that type on the parent
+ *
+ * Arguments:
+ * * datum/component/c_type The typepath of the component you want to get a reference to
+ */
+/datum/proc/GetExactComponent(datum/component/c_type)
+ RETURN_TYPE(c_type)
+ if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
+ stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
+ var/list/dc = _datum_components
+ if(!dc)
+ return null
+ var/datum/component/C = dc[c_type]
+ if(C)
+ if(length(C))
+ C = C[1]
+ if(C.type == c_type)
+ return C
+ return null
+
+/**
+ * Get all components of a given type that are attached to this datum
+ *
+ * Arguments:
+ * * c_type The component type path
+ */
+/datum/proc/GetComponents(c_type)
+ var/list/components = _datum_components?[c_type]
+ if(!components)
+ return list()
+ return islist(components) ? components : list(components)
+
+/**
+ * Creates an instance of `new_type` in the datum and attaches to it as parent
+ *
+ * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum
+ *
+ * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set
+ *
+ * If this tries to add a component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
+ *
+ * Properly handles duplicate situations based on the `dupe_mode` var
+ */
+/datum/proc/_AddComponent(list/raw_args, source)
+ var/original_type = raw_args[1]
+ var/datum/component/component_type = original_type
+
+ if(QDELING(src))
+ CRASH("Attempted to add a new component of type \[[component_type]\] to a qdeleting parent of type \[[type]\]!")
+
+ var/datum/component/new_component
+
+ if(!ispath(component_type, /datum/component))
+ if(!istype(component_type, /datum/component))
+ CRASH("Attempted to instantiate \[[component_type]\] as a component added to parent of type \[[type]\]!")
+ else
+ new_component = component_type
+ component_type = new_component.type
+ else if(component_type == /datum/component)
+ CRASH("[component_type] attempted instantiation!")
+
+ var/dupe_mode = initial(component_type.dupe_mode)
+ var/dupe_type = initial(component_type.dupe_type)
+ var/uses_sources = (dupe_mode == COMPONENT_DUPE_SOURCES)
+ if(uses_sources && !source)
+ CRASH("Attempted to add a sourced component of type '[component_type]' to '[type]' without a source!")
+ else if(!uses_sources && source)
+ CRASH("Attempted to add a normal component of type '[component_type]' to '[type]' with a source!")
+
+ var/datum/component/old_component
+
+ raw_args[1] = src
+ if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE && dupe_mode != COMPONENT_DUPE_SOURCES)
+ if(!dupe_type)
+ old_component = GetExactComponent(component_type)
+ else
+ old_component = GetComponent(dupe_type)
+
+ if(old_component)
+ switch(dupe_mode)
+ if(COMPONENT_DUPE_UNIQUE)
+ if(!new_component)
+ new_component = new component_type(raw_args)
+ if(!QDELETED(new_component))
+ old_component.InheritComponent(new_component, TRUE)
+ QDEL_NULL(new_component)
+
+ if(COMPONENT_DUPE_HIGHLANDER)
+ if(!new_component)
+ new_component = new component_type(raw_args)
+ if(!QDELETED(new_component))
+ new_component.InheritComponent(old_component, FALSE)
+ QDEL_NULL(old_component)
+
+ if(COMPONENT_DUPE_UNIQUE_PASSARGS)
+ if(!new_component)
+ var/list/arguments = raw_args.Copy(2)
+ arguments.Insert(1, null, TRUE)
+ old_component.InheritComponent(arglist(arguments))
+ else
+ old_component.InheritComponent(new_component, TRUE)
+
+ if(COMPONENT_DUPE_SOURCES)
+ if(source in old_component.sources)
+ return old_component // source already registered, no work to do
+
+ if(old_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE)
+ stack_trace("incompatible source added to a [old_component.type]. Args: [json_encode(raw_args)]")
+ return null
+
+ else if(!new_component)
+ new_component = new component_type(raw_args) // There's a valid dupe mode but there's no old component, act like normal
+
+ else if(dupe_mode == COMPONENT_DUPE_SELECTIVE)
+ var/list/arguments = raw_args.Copy()
+ arguments[1] = new_component
+ var/make_new_component = TRUE
+ for(var/datum/component/existing_component as anything in GetComponents(original_type))
+ if(existing_component.CheckDupeComponent(arglist(arguments)))
+ make_new_component = FALSE
+ QDEL_NULL(new_component)
+ break
+ if(!new_component && make_new_component)
+ new_component = new component_type(raw_args)
+
+ else if(dupe_mode == COMPONENT_DUPE_SOURCES)
+ new_component = new component_type(raw_args)
+ if(new_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE)
+ stack_trace("incompatible source added to a [new_component.type]. Args: [json_encode(raw_args)]")
+ return null
+
+ else if(!new_component)
+ new_component = new component_type(raw_args) // Dupes are allowed, act like normal
+
+ if(!old_component && !QDELETED(new_component)) // Nothing related to duplicate components happened and the new component is healthy
+ SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_component)
+ return new_component
+
+ return old_component
+
+/**
+ * Removes a component source from this datum
+ */
+/datum/proc/RemoveComponentSource(source, datum/component/component_type)
+ if(ispath(component_type))
+ component_type = GetExactComponent(component_type)
+ if(!component_type)
+ return
+ component_type.on_source_remove(source)
+
+/**
+ * Get existing component of type, or create it and return a reference to it
+ *
+ * Use this if the item needs to exist at the time of this call, but may not have been created before now
+ *
+ * Arguments:
+ * * component_type The typepath of the component to create or return
+ * * ... additional arguments to be passed when creating the component if it does not exist
+ */
+/datum/proc/_LoadComponent(list/arguments)
+ . = GetComponent(arguments[1])
+ if(!.)
+ return _AddComponent(arguments)
+
+/**
+ * Removes the component from parent, ends up with a null parent
+ * Used as a helper proc by the component transfer proc, does not clean up the component like Destroy does
+ */
+/datum/component/proc/ClearFromParent()
+ if(!parent)
+ return
+ var/datum/old_parent = parent
+ PreTransfer()
+ _RemoveFromParent()
+ parent = null
+ SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src)
+
+/**
+ * Transfer this component to another parent
+ *
+ * Component is taken from source datum
+ *
+ * Arguments:
+ * * datum/component/target Target datum to transfer to
+ */
+/datum/proc/TakeComponent(datum/component/target)
+ if(!target || target.parent == src)
+ return
+ if(target.parent)
+ target.ClearFromParent()
+ target.parent = src
+ var/result = target.PostTransfer()
+ switch(result)
+ if(COMPONENT_INCOMPATIBLE)
+ var/c_type = target.type
+ qdel(target)
+ CRASH("Incompatible [c_type] transfer attempt to a [type]!")
+
+ if(target == AddComponent(target))
+ target._JoinParent()
+
+/**
+ * Transfer all components to target
+ *
+ * All components from source datum are taken
+ *
+ * Arguments:
+ * * /datum/target the target to move the components to
+ */
+/datum/proc/TransferComponents(datum/target)
+ var/list/dc = _datum_components
+ if(!dc)
+ return
+ for(var/component_key in dc)
+ var/component_or_list = dc[component_key]
+ if(islist(component_or_list))
+ for(var/datum/component/I in component_or_list)
+ if(I.can_transfer)
+ target.TakeComponent(I)
+ else
+ var/datum/component/C = component_or_list
+ if(C.can_transfer)
+ target.TakeComponent(C)
+
+/**
+ * Return the object that is the host of any UI's that this component has
+ */
+/datum/component/tgui_host(mob/user)
+ return parent
diff --git a/code/datums/components/pixel_shift.dm b/code/datums/components/pixel_shift.dm
new file mode 100644
index 0000000000000..21349d5b5be11
--- /dev/null
+++ b/code/datums/components/pixel_shift.dm
@@ -0,0 +1,94 @@
+/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_MOVABLE_MOVED, COMSIG_MOB_UPDATE_LYING_BUCKLED_VERBSTATUS), PROC_REF(unpixel_shift))
+ RegisterSignal(parent, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(pre_move_check))
+ RegisterSignal(parent, COMSIG_MOB_CAN_PASS, 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_MOB_UPDATE_LYING_BUCKLED_VERBSTATUS)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(parent, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE)
+ UnregisterSignal(parent, COMSIG_MOB_CAN_PASS)
+
+/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(!istype(mover, /obj/item/projectile) && !mover.throwing && passthroughable & get_dir(parent, mover))
+ return COMPONENT_MOB_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.default_pixel_x
+ owner.pixel_y = owner.default_pixel_y
+ qdel(src)
+
+/datum/component/pixel_shift/proc/pixel_shift(mob/source, direct)
+ var/mob/living/owner = parent
+ if(owner.incapacitated(INCAPACITATION_ALL) || length(owner.pulledby) || length(owner.grabbed_by))
+ return
+ passthroughable = NONE
+ switch(direct)
+ if(NORTH)
+ if(owner.pixel_y <= maximum_pixel_shift + owner.default_pixel_y)
+ owner.pixel_y++
+ is_shifted = TRUE
+ if(EAST)
+ if(owner.pixel_x <= maximum_pixel_shift + owner.default_pixel_x)
+ owner.pixel_x++
+ is_shifted = TRUE
+ if(SOUTH)
+ if(owner.pixel_y >= -maximum_pixel_shift + owner.default_pixel_y)
+ owner.pixel_y--
+ is_shifted = TRUE
+ if(WEST)
+ if(owner.pixel_x >= -maximum_pixel_shift + owner.default_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/code/datums/datum.dm b/code/datums/datum.dm
index b0a9aec5a411f..f7cb7eeaac96c 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -1,54 +1,93 @@
/datum
var/gc_destroyed //Time when this object was destroyed.
var/is_processing = FALSE
- var/list/active_timers //for SStimer
-#ifdef TESTING
- var/running_find_references
- var/last_find_references = 0
-#endif
+ /// If this datum is pooled, the pool it belongs to.
+ var/singleton/instance_pool/instance_pool
+
+ /// If this datum is pooled, the last configurator applied (if any).
+ var/singleton/instance_configurator/instance_configurator
+
+ /**
+ * Components attached to this datum
+ *
+ * Lazy associated list in the structure of `type -> component/list of components`
+ */
+ var/list/_datum_components
+ /**
+ * Any datum registered to receive signals from this datum is in this list
+ *
+ * Lazy associated list in the structure of `signal -> registree/list of registrees`
+ */
+ var/list/_listen_lookup
+ /// Lazy associated list in the structure of `target -> list(signal -> proctype)` that are run when the datum receives that signal
+ var/list/list/_signal_procs
// Default implementation of clean-up code.
// This should be overridden to remove all references pointing to the object being destroyed.
// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
-/datum/proc/Destroy(force=FALSE)
+/datum/proc/Destroy()
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
tag = null
- weakref = null // Clear this reference to ensure it's kept for as brief duration as possible.
-
SSnano && SSnano.close_uis(src)
-
- var/list/timers = active_timers
- active_timers = null
- for(var/datum/timedevent/timer as anything in timers)
- if (timer.spent)
- continue
- qdel(timer)
-
- if(extensions)
- for(var/expansion_key in extensions)
+ if (extensions)
+ for (var/expansion_key in extensions)
var/list/extension = extensions[expansion_key]
- if(islist(extension))
+ if (islist(extension))
extension.Cut()
else
qdel(extension)
extensions = null
+ //BEGIN: ECS SHIT
+ var/list/dc = _datum_components
+ if(dc)
+ for(var/component_key in dc)
+ var/component_or_list = dc[component_key]
+ if(islist(component_or_list))
+ for(var/datum/component/component as anything in component_or_list)
+ qdel(component, FALSE)
+ else
+ var/datum/component/C = component_or_list
+ qdel(C, FALSE)
+ dc.Cut()
+ _clear_signal_refs()
+ //END: ECS SHIT
GLOB.destroyed_event && GLOB.destroyed_event.raise_event(src)
-
- if (!isturf(src)) // Not great, but the 'correct' way to do it would add overhead for little benefit.
- cleanup_events(src)
-
+ cleanup_events(src)
var/list/machines = global.state_machines["\ref[src]"]
- if(length(machines))
- for(var/base_type in machines)
+ if (length(machines))
+ for (var/base_type in machines)
qdel(machines[base_type])
global.state_machines -= "\ref[src]"
-
+ if (instance_pool?.ReturnInstance(src))
+ return QDEL_HINT_IWILLGC
+ instance_configurator = null
+ instance_pool = null
+ weakref = null
return QDEL_HINT_QUEUE
+///Only override this if you know what you're doing. You do not know what you're doing
+///This is a threat
+/datum/proc/_clear_signal_refs()
+ var/list/lookup = _listen_lookup
+ if(lookup)
+ for(var/sig in lookup)
+ var/list/comps = lookup[sig]
+ if(length(comps))
+ for(var/datum/component/comp as anything in comps)
+ comp.UnregisterSignal(src, sig)
+ else
+ var/datum/component/comp = comps
+ comp.UnregisterSignal(src, sig)
+ _listen_lookup = lookup = null
+
+ for(var/target in _signal_procs)
+ UnregisterSignal(target, _signal_procs[target])
+
+
/datum/proc/Process()
set waitfor = 0
return PROCESS_KILL
diff --git a/code/datums/elements/ELEMENT_TEMPLATE.md b/code/datums/elements/ELEMENT_TEMPLATE.md
new file mode 100644
index 0000000000000..4bc1f72f2dc84
--- /dev/null
+++ b/code/datums/elements/ELEMENT_TEMPLATE.md
@@ -0,0 +1,25 @@
+
+# Template file for your new element
+
+See _element.dm for detailed explanations
+
+```dm
+/datum/element/myelement
+ element_flags = ELEMENT_BESPOKE | ELEMENT_COMPLEX_DETACH | ELEMENT_DETACH_ON_HOST_DESTROY | ELEMENT_NOTAREALFLAG // code/__DEFINES/dcs/flags.dm
+ //argument_hash_start_idx = 2 // Use with ELEMENT_BESPOKE
+ var/list/myvar = list()
+
+/datum/element/myelement/Attach(datum/target)
+ if(!ismovable(target))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, myproc)
+ to_chat(target, "Hey, you're in your element.")
+
+/datum/element/myelement/Detach(datum/source)
+ UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
+ to_chat(source, "You feel way out of your element.")
+
+/datum/element/myelement/proc/myproc(datum/source)
+ SIGNAL_HANDLER
+ playsound(source, 'sound/effects/gong.ogg', 50, TRUE)
+```
diff --git a/code/datums/elements/_element.dm b/code/datums/elements/_element.dm
new file mode 100644
index 0000000000000..bcafc83497cfc
--- /dev/null
+++ b/code/datums/elements/_element.dm
@@ -0,0 +1,77 @@
+/**
+ * A holder for simple behaviour that can be attached to many different types
+ *
+ * Only one element of each type is instanced during game init.
+ * Otherwise acts basically like a lightweight component.
+ */
+/datum/element
+ /// Option flags for element behaviour
+ var/element_flags = NONE
+ /**
+ * The index of the first attach argument to consider for duplicate elements
+ *
+ * All arguments from this index onwards (1 based) are hashed into the key to determine
+ * if this is a new unique element or one already exists
+ *
+ * Is only used when flags contains [ELEMENT_BESPOKE]
+ *
+ * This is infinity so you must explicitly set this
+ */
+ var/argument_hash_start_idx = INFINITY
+
+/// Activates the functionality defined by the element on the given target datum
+/datum/element/proc/Attach(datum/target)
+ SHOULD_CALL_PARENT(TRUE)
+ if(type == /datum/element)
+ return ELEMENT_INCOMPATIBLE
+ SEND_SIGNAL(target, COMSIG_ELEMENT_ATTACH, src)
+ if(element_flags & ELEMENT_DETACH_ON_HOST_DESTROY)
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(OnTargetDelete), override = TRUE)
+
+/datum/element/proc/OnTargetDelete(datum/source, force)
+ SIGNAL_HANDLER
+ Detach(source)
+
+/// Deactivates the functionality defines by the element on the given datum
+/datum/element/proc/Detach(datum/source, ...)
+ SIGNAL_HANDLER
+ SHOULD_CALL_PARENT(TRUE)
+
+ SEND_SIGNAL(source, COMSIG_ELEMENT_DETACH, src)
+ UnregisterSignal(source, COMSIG_QDELETING)
+
+/datum/element/Destroy(force)
+ if(!force)
+ return QDEL_HINT_LETMELIVE
+ SSdcs.elements_by_type -= type
+ return ..()
+
+//DATUM PROCS
+
+/// Finds the singleton for the element type given and attaches it to src
+/datum/proc/_AddElement(list/arguments)
+ if(QDELING(src))
+ var/datum/element/element_type = arguments[1]
+ stack_trace("We just tried to add the element [element_type] to a qdeleted datum, something is fucked")
+ return
+
+ var/datum/element/ele = SSdcs.GetElement(arguments)
+ if(!ele) // We couldn't fetch the element, likely because it was not an element.
+ return // the crash message has already been sent
+ arguments[1] = src
+ if(ele.Attach(arglist(arguments)) == ELEMENT_INCOMPATIBLE)
+ CRASH("Incompatible element [ele.type] was assigned to a [type]! args: [json_encode(args)]")
+
+/**
+ * Finds the singleton for the element type given and detaches it from src
+ * You only need additional arguments beyond the type if you're using [ELEMENT_BESPOKE]
+ */
+/datum/proc/_RemoveElement(list/arguments)
+ var/datum/element/ele = SSdcs.GetElement(arguments, FALSE)
+ if(!ele) // We couldn't fetch the element, likely because it didn't exist.
+ return
+ if(ele.element_flags & ELEMENT_COMPLEX_DETACH)
+ arguments[1] = src
+ ele.Detach(arglist(arguments))
+ else
+ ele.Detach(src)
diff --git a/code/datums/extensions/appearance/cardborg.dm b/code/datums/extensions/appearance/cardborg.dm
index d16a60591b0c0..6aecb14c9b5b9 100644
--- a/code/datums/extensions/appearance/cardborg.dm
+++ b/code/datums/extensions/appearance/cardborg.dm
@@ -1,5 +1,5 @@
/datum/extension/appearance/cardborg
expected_type = /obj/item
appearance_handler_type = /singleton/appearance_handler/cardborg
- item_equipment_proc = /singleton/appearance_handler/cardborg/proc/item_equipped
- item_removal_proc = /singleton/appearance_handler/cardborg/proc/item_removed
+ item_equipment_proc = TYPE_PROC_REF(/singleton/appearance_handler/cardborg, item_equipped)
+ item_removal_proc = TYPE_PROC_REF(/singleton/appearance_handler/cardborg, item_removed)
diff --git a/code/datums/extensions/chameleon.dm b/code/datums/extensions/chameleon.dm
index 358554ee66438..ffacbddc2423e 100644
--- a/code/datums/extensions/chameleon.dm
+++ b/code/datums/extensions/chameleon.dm
@@ -2,80 +2,80 @@
base_type = /datum/extension/chameleon
expected_type = /obj/item
flags = EXTENSION_FLAG_IMMEDIATE
- var/list/chameleon_choices
+ var/emp_amount = 0
var/static/list/chameleon_choices_by_type
- var/atom/atom_holder
- var/chameleon_verb
+ var/chameleon_choices
+ var/obj/item/item_holder
+ var/static/chameleon_verbs = list(
+ /obj/item/proc/ChameleonFlexibleAppearance,
+ /obj/item/proc/ChameleonOutfitAppearanceSingle,
+ /obj/item/proc/ChameleonOutfitAppearanceAll)
-/datum/extension/chameleon/New(datum/holder, base_type)
+/**
+ * **Parameters**:
+ * - `holder` - The instance which is granted the chameleon verbs.
+ * - `chameleon_base_type` - The base type from which to generate the list of valid chameleon options. Defaults to the holder's `parent_type` if unset.
+ * - `exclude_outfits` - Whether to exclude the chameleon outfit verbs.
+ */
+/datum/extension/chameleon/New(datum/holder, chameleon_base_type, exclude_outfits)
..()
if (!chameleon_choices)
- var/chameleon_type = base_type || holder.parent_type
+ var/chameleon_type = chameleon_base_type || holder.parent_type
chameleon_choices = LAZYACCESS(chameleon_choices_by_type, chameleon_type)
- if(!chameleon_choices)
- chameleon_choices = generate_chameleon_choices(chameleon_type)
+ if (!chameleon_choices)
+ chameleon_choices = GenerateChameleonChoices(chameleon_type)
LAZYSET(chameleon_choices_by_type, chameleon_type, chameleon_choices)
- else
- var/list/choices = list()
- for(var/path in chameleon_choices)
- add_chameleon_choice(choices, path)
- chameleon_choices = sortAssoc(choices)
- atom_holder = holder
- chameleon_verb += new/atom/proc/chameleon_appearance(atom_holder,"Change [atom_holder.name] Appearance")
+ item_holder = holder
+ if (exclude_outfits)
+ item_holder.verbs += /obj/item/proc/ChameleonFlexibleAppearance
+ else
+ item_holder.verbs += chameleon_verbs
+ GLOB.empd_event.register(item_holder, src, TYPE_PROC_REF(/datum/extension/chameleon, OnEMP))
/datum/extension/chameleon/Destroy()
- . = ..()
- atom_holder.verbs -= chameleon_verb
- atom_holder = null
-
-/datum/extension/chameleon/proc/disguise(newtype, mob/user)
- var/obj/item/copy = new newtype(null) //initial() does not handle lists well
- var/obj/item/C = atom_holder
-
- C.name = copy.name
- C.desc = copy.desc
- C.icon = copy.icon
- C.color = copy.color
- C.icon_state = copy.icon_state
- C.flags_inv = copy.flags_inv
- C.item_state = copy.item_state
- C.body_parts_covered = copy.body_parts_covered
-
- if (copy.item_icons)
- C.item_icons = copy.item_icons.Copy()
- if (copy.item_state_slots)
- C.item_state_slots = copy.item_state_slots.Copy()
- if (copy.sprite_sheets)
- C.sprite_sheets = copy.sprite_sheets.Copy()
-
- OnDisguise(copy)
+ if (emp_amount)
+ STOP_PROCESSING(SSobj, src)
+ GLOB.empd_event.unregister(item_holder, src)
+ item_holder.verbs -= chameleon_verbs // We don't complicate things, remove all the verbs every time no matter the initial setup
+ item_holder = null
+ return ..()
+
+/datum/extension/chameleon/proc/Disguise(newtype, newname, newdesc)
+ SHOULD_NOT_OVERRIDE(TRUE) // Subtypes should override OnDisguise
+
+ var/obj/item/copy = new newtype(null) // initial() does not handle lists well
+ item_holder.name = newname || copy.name
+ item_holder.desc = newdesc || copy.desc
+ item_holder.icon = copy.icon
+ item_holder.color = copy.color
+ item_holder.icon_state = copy.icon_state
+ item_holder.flags_inv = copy.flags_inv
+ item_holder.item_state = copy.item_state
+ item_holder.body_parts_covered = copy.body_parts_covered
+
+ item_holder.item_icons = copy.item_icons
+ item_holder.item_state_slots = copy.item_state_slots
+ item_holder.sprite_sheets = copy.sprite_sheets
+
+ OnDisguise(item_holder, copy)
qdel(copy)
-/datum/extension/chameleon/proc/OnDisguise(obj/item/copy)
+/datum/extension/chameleon/proc/OnDisguise(obj/item/holder, obj/item/copy)
+ return
-/datum/extension/chameleon/clothing
- expected_type = /obj/item/clothing
+/datum/extension/chameleon/proc/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ return null
-/datum/extension/chameleon/clothing/accessory
- expected_type = /obj/item/clothing/accessory
+/datum/extension/chameleon/proc/GenerateChameleonChoices(basetype)
+ var/choices = list()
+ var/types = islist(basetype) ? basetype : typesof(basetype)
+ for (var/path in types)
+ AddChameleonChoice(choices, path)
+ return sortAssoc(choices)
-/datum/extension/chameleon/clothing/accessory/OnDisguise(obj/item/clothing/accessory/copy)
- ..()
- var/obj/item/clothing/accessory/A = holder
-
- A.slot = copy.slot
- A.parent = copy.parent
- A.inv_overlay = copy.inv_overlay
- A.mob_overlay = copy.mob_overlay
- A.overlay_state = copy.overlay_state
- A.accessory_icons = copy.accessory_icons
- A.on_rolled_down = copy.on_rolled_down
- A.on_rolled_sleeves = copy.on_rolled_sleeves
- A.accessory_flags = copy.accessory_flags
-
-/datum/extension/chameleon/proc/add_chameleon_choice(list/target, path)
+/datum/extension/chameleon/proc/AddChameleonChoice(list/target, path)
var/obj/item/I = path
if (initial(I.icon) && initial(I.icon_state) && !(initial(I.item_flags) & ITEM_FLAG_INVALID_FOR_CHAMELEON))
var/name = initial(I.name)
@@ -89,76 +89,221 @@
else
target[name] = path
-/datum/extension/chameleon/proc/generate_chameleon_choices(basetype)
- var/choices = list()
- var/types = islist(basetype) ? basetype : typesof(basetype)
- for (var/path in types)
- add_chameleon_choice(choices, path)
- return sortAssoc(choices)
+/datum/extension/chameleon/proc/OnEMP(holder, severity)
+ if(!prob(50/severity))
+ return
+
+ if (emp_amount == 0)
+ START_PROCESSING(SSobj, src)
+
+ emp_amount += rand((30 SECONDS)/severity, (1 MINUTE)/severity)
+ emp_amount = min(2 MINUTES, emp_amount) // Cap EMP duration to 2 minutes
+ Malfunction()
+
+/datum/extension/chameleon/Process(wait)
+ var/trigger = FALSE
+ // For each second, check if a malfunction is triggered
+ for (var/i = 1 to ceil(wait / (1 SECOND)))
+ // There's a (EMP seconds left / 2) probability of another malfunction triggering
+ if (!trigger && prob(emp_amount / 2 / 1 SECOND))
+ trigger = TRUE
+ emp_amount -= 10 SECONDS // If a malfunction did trigger, we're kind and reduce the remaining time by 10 seconds
+ else // Otherwise we only reduce it by 1 second
+ emp_amount -= 1 SECOND
+
+ if (trigger)
+ Malfunction()
+
+ if (emp_amount <= 0)
+ emp_amount = 0
+ STOP_PROCESSING(SSobj, src)
+
+/datum/extension/chameleon/proc/Malfunction()
+ playsound(item_holder.loc, "sparks", 75, 1, -1)
+ Disguise(chameleon_choices[pick(chameleon_choices)])
/**
- * Verb to handle changing the appearance of atoms that have the chameleon extension.
+ * Verbs to handle changing the appearance of atoms that have the chameleon extension.
*/
-/atom/proc/chameleon_appearance()
- set name = "Change Appearance"
+/obj/item/proc/ChameleonFlexibleAppearance()
+ set name = "Change Appearance - Flexible"
set desc = "Activate the holographic appearance changing module."
set category = "Object"
- if (!CanPhysicallyInteract(usr))
+ if (!CanPhysicallyInteractWith(usr, src))
return
- if (has_extension(src,/datum/extension/chameleon))
- var/datum/extension/chameleon/C = get_extension(src, /datum/extension/chameleon)
- C.change(usr)
+
+ var/datum/extension/chameleon/C = get_extension(src, /datum/extension/chameleon)
+ if (C)
+ C.ChangeGeneral(usr)
else
- src.verbs -= /atom/proc/chameleon_appearance
+ src.verbs -= C.chameleon_verbs
-/datum/extension/chameleon/proc/change(mob/user)
+/datum/extension/chameleon/proc/ChangeGeneral(mob/user)
var/choice = input(user, "Select a new appearance", "Select appearance") as null|anything in chameleon_choices
- if (choice)
- if (QDELETED(user) || QDELETED(holder))
- return
- if(user.incapacitated() || !(holder in user))
- to_chat(user, SPAN_WARNING("You can't reach \the [holder]."))
- return
- disguise(chameleon_choices[choice], user)
- OnChange(user,holder)
-
-/datum/extension/chameleon/proc/OnChange(mob/user, obj/item/clothing/C) //contains icon updates
- if (istype(C))
- C.update_clothing_icon()
+ if (!choice)
+ return
+
+ var/newname = input(user, "Choose a new name, or leave blank to use the default", "Choose item name") as null|text
+ var/newdesc = input(user, "Choose a new description, or leave blank to use the default", "Choose item description") as null|text
+ if(!CanPhysicallyInteractWith(user, holder))
+ to_chat(user, SPAN_WARNING("You can't reach \the [holder]."))
+ return
+ Disguise(chameleon_choices[choice], newname, newdesc)
+
+/obj/item/proc/ChameleonOutfitAppearanceSingle()
+ set name = "Change Appearance - Outfit (Selected Only)"
+ set desc = "Activate the holographic appearance changing module."
+ set category = "Object"
+
+ if (!CanPhysicallyInteractWith(usr, src))
+ return
+
+ var/datum/extension/chameleon/C = get_extension(src, /datum/extension/chameleon)
+ if (C)
+ C.ChangeOutfitSingle(usr)
+ else
+ src.verbs -= C.chameleon_verbs
+
+/datum/extension/chameleon/proc/ChangeOutfitSingle(mob/user)
+ var/choice = input(user, "Select a new appearance for the selected chameleon item", "Select appearance") as null|anything in outfits()
+ if (!choice)
+ return
+ if(!CanPhysicallyInteractWith(user, holder))
+ to_chat(user, SPAN_WARNING("You can't reach \the [holder]."))
+ return
+ SetOutfitAppearance(user, list(src), choice)
+
+/obj/item/proc/ChameleonOutfitAppearanceAll()
+ set name = "Change Appearance - Outfit (All Equipped)"
+ set desc = "Activate the holographic appearance changing module."
+ set category = "Object"
+
+ if (!CanPhysicallyInteractWith(usr, src))
+ return
+
+ var/datum/extension/chameleon/C = get_extension(src, /datum/extension/chameleon)
+ if (C)
+ C.ChangeOutfitAll(usr)
+ else
+ src.verbs -= C.chameleon_verbs
+
+/datum/extension/chameleon/proc/ChangeOutfitAll(mob/user)
+ var/choice = input(usr, "Select a new appearance for the selected chameleon item", "Select appearance") as null|anything in outfits()
+ if (!choice)
+ return
+ if(!CanPhysicallyInteractWith(user, holder))
+ to_chat(usr, SPAN_WARNING("You can't reach \the [holder]."))
+ return
+
+ var/list/extensions = list()
+ for (var/obj/item/I as anything in user.get_equipped_items(TRUE))
+ var/extension = get_extension(I, /datum/extension/chameleon)
+ if (extension)
+ extensions += extension
+ extensions |= src
+ SetOutfitAppearance(user, extensions, choice)
+/datum/extension/chameleon/proc/SetOutfitAppearance(mob/user, list/chameleon_extensions, singleton/hierarchy/outfit/outfit)
+ for (var/datum/extension/chameleon/chameleon_extension as anything in chameleon_extensions)
+ var/outfit_type = chameleon_extension.GetItemDisguiseType(outfit)
+ if (outfit_type)
+ to_chat(user, SPAN_NOTICE("The outfit '[outfit]' appearance was applied to \the [chameleon_extension.holder]."));
+ chameleon_extension.Disguise(outfit_type)
+ else
+ to_chat(user, SPAN_WARNING("The outfit '[outfit]' had no suitable appearance for \the [chameleon_extension.holder]."));
+
+/********************
+* Subtype overrides *
+********************/
/datum/extension/chameleon/backpack
expected_type = /obj/item/storage/backpack
-/datum/extension/chameleon/backpack/OnChange(mob/user, obj/item/storage/backpack/C)
- if (ismob(C.loc))
- var/mob/M = C.loc
+/datum/extension/chameleon/backpack/OnDisguise(obj/item/storage/backpack/holder, obj/item/copy)
+ if (ismob(holder.loc))
+ var/mob/M = holder.loc
M.update_inv_back()
-/datum/extension/chameleon/headset
- expected_type = /obj/item/device/radio/headset
+/datum/extension/chameleon/backpack/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.back, expected_type))
+ return outfit.back
+ for (var/potential_backpack_type in list_values(outfit.backpack_overrides))
+ if (ispath(potential_backpack_type, expected_type))
+ return potential_backpack_type
-/datum/extension/chameleon/headset/OnChange(mob/user, obj/item/device/radio/headset/C)
- if (ismob(C.loc))
- var/mob/M = C.loc
- M.update_inv_ears()
+/datum/extension/chameleon/clothing
+ expected_type = /obj/item/clothing
-/datum/extension/chameleon/gun
- expected_type = /obj/item/gun
+/datum/extension/chameleon/clothing/OnDisguise(obj/item/clothing/holder, obj/item/copy)
+ SHOULD_CALL_PARENT(TRUE)
+ ..()
+ if (istype(holder))
+ holder.update_clothing_icon()
-/datum/extension/chameleon/gun/OnChange(mob/user, obj/item/gun/C)
- if (ismob(C.loc))
- var/mob/M = C.loc
- M.update_inv_r_hand()
- M.update_inv_l_hand()
+/datum/extension/chameleon/clothing/accessory
+ expected_type = /obj/item/clothing/accessory
+
+/datum/extension/chameleon/clothing/accessory/OnDisguise(obj/item/clothing/accessory/holder, obj/item/clothing/accessory/copy)
+ holder.slot = copy.slot
+ holder.parent = copy.parent
+ holder.inv_overlay = copy.inv_overlay
+ holder.mob_overlay = copy.mob_overlay
+ holder.overlay_state = copy.overlay_state
+ holder.accessory_icons = copy.accessory_icons
+ holder.on_rolled_down = copy.on_rolled_down
+ holder.on_rolled_sleeves = copy.on_rolled_sleeves
+ holder.accessory_flags = copy.accessory_flags
+ ..()
+
+/datum/extension/chameleon/clothing/glasses
+ expected_type = /obj/item/clothing/glasses
+
+/datum/extension/chameleon/clothing/glasses/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.glasses, expected_type))
+ return outfit.glasses
+
+/datum/extension/chameleon/clothing/gloves
+ expected_type = /obj/item/clothing/gloves
+
+/datum/extension/chameleon/clothing/gloves/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.gloves, expected_type))
+ return outfit.gloves
+
+/datum/extension/chameleon/clothing/head
+ expected_type = /obj/item/clothing/head
+
+/datum/extension/chameleon/clothing/head/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.head, expected_type))
+ return outfit.head
+
+/datum/extension/chameleon/clothing/mask
+ expected_type = /obj/item/clothing/mask
+
+/datum/extension/chameleon/clothing/mask/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.mask, expected_type))
+ return outfit.mask
+
+/datum/extension/chameleon/clothing/shoes
+ expected_type = /obj/item/clothing/shoes
+
+/datum/extension/chameleon/clothing/shoes/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ ..()
+ if (ispath(outfit.shoes, expected_type))
+ return outfit.shoes
+
+/datum/extension/chameleon/clothing/suit
+ expected_type = /obj/item/clothing/suit
-/datum/extension/chameleon/gun/OnDisguise(obj/item/gun/copy)
- var/obj/item/gun/G = atom_holder
+/datum/extension/chameleon/clothing/suit/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.suit, expected_type))
+ return outfit.suit
- G.flags_inv = copy.flags_inv
- G.fire_sound = copy.fire_sound
- G.fire_sound_text = copy.fire_sound_text
- G.icon = copy.icon
+/datum/extension/chameleon/clothing/under
+ expected_type = /obj/item/clothing/under
+
+/datum/extension/chameleon/clothing/under/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.uniform, expected_type))
+ return outfit.uniform
/datum/extension/chameleon/emag
expected_type = /obj/item/card
@@ -170,3 +315,88 @@
/obj/item/card/data/disk,
/obj/item/card/id
)
+
+/datum/extension/chameleon/emag/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (length(outfit.id_types) > 0)
+ var/id_path = outfit.id_types[0]
+ if (ispath(id_path, expected_type))
+ return id_path
+
+/datum/extension/chameleon/gun
+ expected_type = /obj/item/gun
+
+/datum/extension/chameleon/gun/OnDisguise(obj/item/gun/holder, obj/item/gun/copy)
+ holder.flags_inv = copy.flags_inv
+ holder.fire_sound = copy.fire_sound
+ holder.fire_sound_text = copy.fire_sound_text
+
+ if (ismob(holder.loc))
+ var/mob/M = holder.loc
+ M.update_inv_r_hand()
+ M.update_inv_l_hand()
+
+/datum/extension/chameleon/headset
+ expected_type = /obj/item/device/radio/headset
+
+/datum/extension/chameleon/headset/OnDisguise(obj/item/holder, obj/item/copy)
+ if (ismob(holder.loc))
+ var/mob/M = holder.loc
+ M.update_inv_ears()
+
+/datum/extension/chameleon/headset/GetItemDisguiseType(singleton/hierarchy/outfit/outfit)
+ if (ispath(outfit.l_ear, expected_type))
+ return outfit.l_ear
+ if (ispath(outfit.r_ear, expected_type))
+ return outfit.r_ear
+
+/// Grants the full set of chameleon selection options available to the extension.
+var/global/const/CHAMELEON_FLEXIBLE_OPTIONS_EXTENSION = 1 // Not flags
+/// Grants a (potential) subset of chameleon options available to the extension, based on the instance's `parent_type`. Falls back to `type` if not a valid type for the extension.
+var/global/const/CHAMELEON_FLEXIBLE_OPTIONS_PARENT_TYPE = 2
+/// Grants a (potential) subset of chameleon options available to the extension, based on the instance's `type`.
+var/global/const/CHAMELEON_FLEXIBLE_OPTIONS_TYPE = 3
+
+/**
+ * Call this proc to automatically setup the best suited chameleon extension for the instance, if one exists.
+ *
+ * Exceptions:
+ * - If the instance only matches the base /datum/extension/chameleon type it is not set for performance reasons. For these `set_extension()` has to be called explicitly.
+ * - If the instance already has the /datum/extension/chameleon extension it is not overriden, but the proc still returns `TRUE`.
+ *
+ * **Parameters**:
+ * - `chamelon_options` - Based on the relevant CHAMELEON_FLEXIBLE_OPTION_* argument
+ * - `exclude_outfits` - Whether to exclude the chameleon outfit verbs.
+ * - `throw_runtime` - Whether to throw a runtime exception if no matching extension was found. This includes cases when /datum/extension/chameleon would've been a match had it not been for its exclusion.
+ *
+ * Returns boolean - Whether or not a matching extension was found
+ */
+/obj/proc/SetupChameleonExtension(chamelon_options, exclude_outfits, throw_runtime)
+ if (has_extension(src, /datum/extension/chameleon))
+ return TRUE
+
+ var/best_found_expected_type
+ var/best_found_extension
+
+ // Most items matching only /obj/item have a tendency to generate huge cache lists (and also lag spikes), hence the exclusion of the base extension type
+ for (var/datum/extension/chameleon/chameleon_extension_type as anything in subtypesof(/datum/extension/chameleon))
+ var/expected_type = initial(chameleon_extension_type.expected_type)
+
+ if (istype(src, expected_type)) // If the type of src is a type expected by the extension then..
+ // Check if the expected type is a better match than the previously found best expected type (if any)
+ if (!best_found_expected_type || IS_SUBPATH(expected_type, best_found_expected_type))
+ best_found_expected_type = expected_type
+ best_found_extension = chameleon_extension_type
+
+ var/chameleon_base_type
+ switch (chamelon_options)
+ if (CHAMELEON_FLEXIBLE_OPTIONS_EXTENSION) chameleon_base_type = best_found_expected_type
+ if (CHAMELEON_FLEXIBLE_OPTIONS_PARENT_TYPE) chameleon_base_type = ispath(parent_type, best_found_expected_type) ? parent_type : type
+ if (CHAMELEON_FLEXIBLE_OPTIONS_TYPE) chameleon_base_type = type
+ else CRASH("Invalid chameleon flexible option: [chamelon_options]")
+
+ if (best_found_extension)
+ set_extension(src, best_found_extension, chameleon_base_type, exclude_outfits)
+ return TRUE
+ else if (throw_runtime)
+ CRASH("The type [type] does not have a compatible chameleon extension.")
+ return FALSE
diff --git a/code/datums/extensions/event_registration.dm b/code/datums/extensions/event_registration.dm
index c8a2c4a443bef..767f52039c81a 100644
--- a/code/datums/extensions/event_registration.dm
+++ b/code/datums/extensions/event_registration.dm
@@ -10,8 +10,8 @@
/datum/extension/event_registration/New(datum/holder, singleton/observ/event, datum/target, callproc)
..()
- event.register(target, src, .proc/trigger)
- GLOB.destroyed_event.register(target, src, .proc/qdel_self)
+ event.register(target, src, PROC_REF(trigger))
+ GLOB.destroyed_event.register(target, src, PROC_REF(qdel_self))
src.event = event
src.target = target
src.callproc = callproc
@@ -36,15 +36,15 @@
..()
src.given_area = given_area
register_shuttles()
- GLOB.shuttle_added.register_global(src, .proc/shuttle_added)
+ GLOB.shuttle_added.register_global(src, PROC_REF(shuttle_added))
/datum/extension/event_registration/shuttle_stationary/proc/register_shuttles()
if(given_area in SSshuttle.shuttle_areas)
for(var/shuttle_name in SSshuttle.shuttles)
var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle_name]
if(given_area in shuttle_datum.shuttle_area)
- GLOB.shuttle_moved_event.register(shuttle_datum, src, .proc/shuttle_moved)
- GLOB.shuttle_pre_move_event.register(shuttle_datum, src, .proc/shuttle_pre_move)
+ GLOB.shuttle_moved_event.register(shuttle_datum, src, PROC_REF(shuttle_moved))
+ GLOB.shuttle_pre_move_event.register(shuttle_datum, src, PROC_REF(shuttle_pre_move))
LAZYADD(shuttles_registered, shuttle_datum)
/datum/extension/event_registration/shuttle_stationary/proc/unregister_shuttles()
@@ -55,8 +55,8 @@
/datum/extension/event_registration/shuttle_stationary/proc/shuttle_added(datum/shuttle/shuttle)
if(given_area in shuttle.shuttle_area)
- GLOB.shuttle_moved_event.register(shuttle, src, .proc/shuttle_moved)
- GLOB.shuttle_pre_move_event.register(shuttle, src, .proc/shuttle_pre_move)
+ GLOB.shuttle_moved_event.register(shuttle, src, PROC_REF(shuttle_moved))
+ GLOB.shuttle_pre_move_event.register(shuttle, src, PROC_REF(shuttle_pre_move))
LAZYADD(shuttles_registered, shuttle)
/datum/extension/event_registration/shuttle_stationary/Destroy()
diff --git a/code/datums/extensions/extensions.dm b/code/datums/extensions/extensions.dm
index 670b375144543..4f55fa7cc880f 100644
--- a/code/datums/extensions/extensions.dm
+++ b/code/datums/extensions/extensions.dm
@@ -39,12 +39,14 @@
source.extensions[extension_base_type] = extension_data
/proc/get_or_create_extension(datum/source, datum/extension/extension_type)
+ RETURN_TYPE(/datum/extension)
var/base_type = initial(extension_type.base_type)
if(!has_extension(source, base_type))
set_extension(arglist(args))
return get_extension(source, base_type)
/proc/get_extension(datum/source, base_type)
+ RETURN_TYPE(/datum/extension)
if(!source.extensions)
return
. = source.extensions[base_type]
@@ -60,6 +62,7 @@
return !!(source.extensions && source.extensions[base_type])
/proc/construct_extension_instance(extension_type, datum/source, list/arguments)
+ RETURN_TYPE(/datum/extension)
arguments = list(source) + arguments
return new extension_type(arglist(arguments))
diff --git a/code/datums/extensions/holster/holster.dm b/code/datums/extensions/holster/holster.dm
index 8267be6597955..d292e7a4ed6cc 100644
--- a/code/datums/extensions/holster/holster.dm
+++ b/code/datums/extensions/holster/holster.dm
@@ -15,12 +15,6 @@
src.sound_out = sound_out || src.sound_out
src.can_holster = can_holster
- atom_holder.verbs += /atom/proc/holster_verb
-
-/datum/extension/holster/Destroy()
- . = ..()
- atom_holder.verbs -= /atom/proc/holster_verb
-
/datum/extension/holster/proc/can_holster(obj/item/I)
if(can_holster)
if(is_type_in_list(I,can_holster))
@@ -45,6 +39,11 @@
playsound(get_turf(atom_holder), sound_in, 50)
if(istype(user))
user.stop_aiming(no_message=1)
+ if(istype(I, /obj/item/gun))
+ var/obj/item/gun/G = I
+ G.check_accidents(user)
+ if(user.a_intent == I_HELP && G.has_safety && !G.safety_state && user.skill_check(SKILL_WEAPONS, SKILL_EXPERIENCED))
+ G.toggle_safety(user)
holstered = I
storage.handle_item_insertion(holstered, 1)
holstered.add_fingerprint(user)
@@ -52,14 +51,14 @@
user.visible_message(SPAN_NOTICE("\The [user] holsters \the [holstered]."), SPAN_NOTICE("You holster \the [holstered]."))
atom_holder.SetName("occupied [initial(atom_holder.name)]")
atom_holder.update_icon()
- GLOB.moved_event.register(holstered, src, .proc/check_holster)
- GLOB.destroyed_event.register(holstered, src, .proc/clear_holster)
+ GLOB.moved_event.register(holstered, src, PROC_REF(check_holster))
+ GLOB.destroyed_event.register(holstered, src, PROC_REF(clear_holster))
return 1
return 0
/datum/extension/holster/proc/clear_holster()
- GLOB.moved_event.unregister(holstered, src, .proc/check_holster)
- GLOB.destroyed_event.unregister(holstered, src, .proc/clear_holster)
+ GLOB.moved_event.unregister(holstered, src, PROC_REF(check_holster))
+ GLOB.destroyed_event.unregister(holstered, src, PROC_REF(clear_holster))
holstered = null
atom_holder.SetName(initial(atom_holder.name))
@@ -77,7 +76,7 @@
if(istype(holstered, /obj/item/gun))
var/obj/item/gun/G = holstered
G.check_accidents(user)
- if(G.safety() && !user.skill_fail_prob(SKILL_WEAPONS, 100, SKILL_EXPERT, 0.5)) //Experienced shooter will disable safety before shooting.
+ if(G.safety() && user.skill_check(SKILL_WEAPONS, SKILL_EXPERIENCED)) // Experienced shooter will disable safety before shooting.
G.toggle_safety(user)
usr.visible_message(
SPAN_DANGER("\The [user] draws \the [holstered], ready to go!"),
@@ -99,10 +98,11 @@
return 0
/datum/extension/holster/proc/examine_holster(mob/user)
+ . = list()
if (holstered)
- to_chat(user, "\A [holstered] is holstered here.")
+ . += SPAN_NOTICE("[holstered] is holstered here.")
else
- to_chat(user, "It is empty.")
+ . += SPAN_NOTICE("It is empty.")
/datum/extension/holster/proc/check_holster()
if(holstered.loc != storage)
@@ -112,16 +112,37 @@
* Verb to handle quick-holstering an item in the mob's active hand, or retrieving an item from this atom's holster
* extension.
*/
-/atom/proc/holster_verb(holster_name in get_holsters())
+/mob/living/verb/holster_verb()
set name = "Holster"
set category = "Object"
- set src in usr
if(usr.incapacitated())
return
- var/datum/extension/holster/H = get_holsters()[holster_name]
- if(!H)
+ var/list/holsters = list()
+ for (var/obj/item/item in contents)
+ holsters += item.get_holsters()
+ continue
+
+ if (!length(holsters))
+ return
+
+ var/holster_name
+ if (length(holsters) > 1)
+ var/list/options = list()
+ for (var/holster in holsters)
+ var/datum/extension/holster/H = holsters[holster]
+ var/atom/holder = H.atom_holder
+ options[holster] = mutable_appearance(holder.icon, holder.icon_state)
+
+ holster_name = show_radial_menu(usr, usr, options, tooltips = TRUE, use_labels = TRUE)
+
+ if (!holster_name)
+ return
+ else
+ holster_name = holsters[1]
+ var/datum/extension/holster/H = holsters[holster_name]
+ if (!H || !usr.use_sanity_check(H.atom_holder))
return
if(!H.holstered)
diff --git a/code/datums/extensions/label.dm b/code/datums/extensions/label.dm
index 0f6a293e28672..645d961bed021 100644
--- a/code/datums/extensions/label.dm
+++ b/code/datums/extensions/label.dm
@@ -72,6 +72,7 @@
/proc/get_attached_labels(atom/source)
+ RETURN_TYPE(/list)
if (has_extension(source, /datum/extension/labels))
var/datum/extension/labels/labels = get_extension(source, /datum/extension/labels)
if (length(labels.labels))
diff --git a/code/datums/extensions/multitool/circuitboards/buildtype_select.dm b/code/datums/extensions/multitool/circuitboards/buildtype_select.dm
index e12c38d3cf436..eb4e979ecfbdb 100644
--- a/code/datums/extensions/multitool/circuitboards/buildtype_select.dm
+++ b/code/datums/extensions/multitool/circuitboards/buildtype_select.dm
@@ -24,6 +24,6 @@
if(path && (path in board.get_buildable_types()))
board.build_path = path
var/obj/thing = path
- board.SetName(T_BOARD(initial(thing.name)))
+ board.SetName("circuit board ([initial(thing.name)])")
return MT_REFRESH
return ..()
diff --git a/code/datums/extensions/multitool/items/cable.dm b/code/datums/extensions/multitool/items/cable.dm
deleted file mode 100644
index 7f922271afd00..0000000000000
--- a/code/datums/extensions/multitool/items/cable.dm
+++ /dev/null
@@ -1,25 +0,0 @@
-/obj/item/stack/cable_coil/Initialize(mapload, _amount, _color)
- . = ..()
- set_extension(src, /datum/extension/interactive/multitool/items/cable)
-
-/datum/extension/interactive/multitool/items/cable/get_interact_window(obj/item/device/multitool/M, mob/user)
- var/obj/item/stack/cable_coil/cable_coil = holder
- . += "Available Colors "
- . += "
"
- for(var/cable_color in GLOB.possible_cable_colours)
- . += "
Entries matching the current query are highlighted.
+ [unique_ckeys_table]
+
+
+
All Matching Bans
+
Entries matching the current query are highlighted.
+ [all_bans_table]
+ "}
+ var/datum/browser/popup = new(user, "associatedbans", "Associated Bans ([target_ckey ? target_ckey : "NO CKEY"])", 700, 480)
+ popup.set_content(final_body)
+ popup.open()
+
+
+/**
+ * Aliases to `_show_associated_bans()` using this client's `fetch_bans()` result, ckey, IP address, and CID.
+ *
+ * Has no return value.
+ */
+/client/proc/show_associated_bans(mob/user, list/bans)
+ if (isnull(bans))
+ bans = fetch_bans()
+ _show_associated_bans(user, bans, ckey, address, computer_id)
+
+
+/**
+ * Aliases to `_show_associated_bans()` using this mob's `fetch_bans()` result, ckey, IP address, and CID.
+ *
+ * Has no return value.
+ */
+/mob/proc/show_associated_bans(mob/user, list/bans)
+ if (client)
+ client.show_associated_bans(user, bans)
+ return
+ if (isnull(bans))
+ bans = fetch_bans()
+ _show_associated_bans(user, bans, ckey ? ckey : last_ckey, lastKnownIP, computer_id)
diff --git a/code/modules/admin/connectioncheck/connectioncheck_functions.dm b/code/modules/admin/connectioncheck/connectioncheck_functions.dm
new file mode 100644
index 0000000000000..0f2b96cd90c9f
--- /dev/null
+++ b/code/modules/admin/connectioncheck/connectioncheck_functions.dm
@@ -0,0 +1,278 @@
+/**
+ * Checks for prior connections matching ckey, ip, or cid.
+ *
+ * Returns list of lists.
+ */
+/proc/_fetch_connections(ckey, ip, cid)
+ RETURN_TYPE(/list)
+ . = list()
+ ckey = sql_sanitize_text(ckey)
+ ip = sql_sanitize_text(ip)
+ cid = sql_sanitize_text(cid)
+ if (!ckey && !ip && !cid)
+ return
+ establish_db_connection()
+ if (!dbcon.IsConnected())
+ crash_with("Database connection failed.")
+ return
+ var/selection = list()
+ if (ckey)
+ selection += "`ckey` = '[ckey]'"
+ if (ip)
+ selection += "`ip` = '[ip]'"
+ if (cid)
+ selection += "`computerid` = '[cid]'"
+ selection = english_list(selection, "", "", " OR ", " OR ")
+ var/DBQuery/query = dbcon.NewQuery("\
+ SELECT `datetime`, `ckey`, `ip`, `computerid`\
+ FROM `erro_connection_log`\
+ WHERE [selection]\
+ GROUP BY `ckey`, `ip`, `computerid`\
+ ORDER BY `datetime`\
+ ")
+ query.Execute()
+ while (query.NextRow())
+ var/row = list(
+ "datetime" = "[query.item[1]]",
+ "ckey" = "[query.item[2]]",
+ "ip" = "[query.item[3]]",
+ "computerid" = "[query.item[4]]"
+ )
+ . += list(row)
+
+
+/**
+ * Returns a sorted list containing only each unique ckey present in a list of connections provided by `_fetch_connections()`.
+ */
+/proc/_unique_ckeys_from_connections(list/connections)
+ RETURN_TYPE(/list)
+ . = list()
+ for (var/list/connection in connections)
+ . |= connection["ckey"]
+ return sortList(.)
+
+
+/**
+ * Returns a sorted list containing only each unique CID present in a list of connections provided by `_fetch_connections()`.
+ */
+/proc/_unique_cids_from_connections(list/connections)
+ RETURN_TYPE(/list)
+ . = list()
+ for (var/list/connection in connections)
+ . |= connection["computerid"]
+ return sortList(.)
+
+
+/**
+ * Returns a sorted list containing only each unique IP present in a list of connections provided by `_fetch_connections()`.
+ */
+/proc/_unique_ips_from_connections(list/connections)
+ RETURN_TYPE(/list)
+ . = list()
+ for (var/list/connection in connections)
+ . |= connection["ip"]
+ return sortList(.)
+
+
+/**
+ * Aliases to `_fetch_connections()` with this client's ckey, address, and CID.
+ *
+ * Returns list of lists.
+ */
+/client/proc/fetch_connections()
+ RETURN_TYPE(/list)
+ return _fetch_connections(ckey, address, computer_id)
+
+
+/**
+ * Aliases to `_fetch_connections()` with this mob's client, if present, or this mob's ckey/last ckey, last IP, and last CID.
+ *
+ * Returns list of lists.
+ */
+/mob/proc/fetch_connections()
+ RETURN_TYPE(/list)
+ if (client)
+ return client.fetch_connections()
+ return _fetch_connections(ckey ? ckey : last_ckey, lastKnownIP, computer_id)
+
+
+/**
+ * Generates and displays an HTML window, displaying data from a `_fetch_connections()` call with the provided
+ * parameters.
+ *
+ * **WARNING: This proc makes no validation or access checks. Ensure `user` is a valid candidate to receive this
+ * information before calling.**
+ *
+ * Used by the `Check Connections` button in the player panel.
+ *
+ * **Parameters**:
+ * - `user` - The mob requesing that the window is displayed to.
+ * - `connections` - List generated from a `_fetch_connections()` call.
+ * - `target_ckey` - If provided, highlights ckeys in the window that match this value.
+ * - `target_ip` - If provided, highlights IP addresses in the window that match this value.
+ * - `target_cid` - If provided, highlights CIDs in the window that match this value.
+ *
+ * Has no return value.
+ */
+/proc/_show_associated_connections(mob/user, list/connections, target_ckey, target_ip, target_cid)
+ // Unique Ckeys
+ var/list/unique_ckeys = _unique_ckeys_from_connections(connections)
+ var/unique_ckeys_table = {"
+
+
+ "}
+ var/stripe = FALSE
+ for (var/ckey in unique_ckeys)
+ unique_ckeys_table += {"
+
NOTE: Rows in this table are not necessarily associated with eachother. This is simply a list of each category's entries for ease of information.
+ Entries matching the current query are highlighted.
+
+
+
+
Ckeys
+
IP Addresses
+
Computer IDs
+
+
+
+
+
[unique_ckeys_table]
+
[unique_ips_table]
+
[unique_cids_table]
+
+
+
+
+
+
All Unique Connections
+
NOTE: This table does not list every single connection ever made, only the first connection seen for each unique combination of ckey, IP, and CID.
+ Entries matching the current query are highlighted.
"
. = jointext(.,null)
-/datum/category_item/player_setup_item/loadout/proc/get_gear_metadata(datum/gear/G, readonly)
- var/list/gear = pref.gear_list[pref.gear_slot]
- . = gear[G.display_name]
- if(!.)
- . = list()
- if(!readonly)
- gear[G.display_name] = .
+/datum/category_item/player_setup_item/loadout/proc/get_gear_metadata(datum/gear/our_gear)
+ if(!our_gear)
+ stack_trace("`our_gear` should not be null")
+ return list()
+
+ var/datum/gear_slot/picked_gear_slot = pref.get_picked_gear_slot()
+ return picked_gear_slot.get_gear_tweaks(our_gear.display_name)
/datum/category_item/player_setup_item/loadout/proc/get_tweak_metadata(datum/gear/G, datum/gear_tweak/tweak)
var/list/metadata = get_gear_metadata(G)
@@ -269,140 +473,256 @@ var/global/list/gear_datums = list()
var/list/metadata = get_gear_metadata(G)
metadata["[tweak]"] = new_metadata
-/datum/category_item/player_setup_item/loadout/OnTopic(href, href_list, user)
+/datum/category_item/player_setup_item/loadout/OnTopic(href, list/href_list, mob/user)
+ ASSERT(istype(user))
+
if(href_list["toggle_gear"])
- var/datum/gear/TG = locate(href_list["toggle_gear"])
- if(!istype(TG) || gear_datums[TG.display_name] != TG)
- return TOPIC_REFRESH
- if(TG.display_name in pref.gear_list[pref.gear_slot])
- pref.gear_list[pref.gear_slot] -= TG.display_name
- else
- var/total_cost = 0
- for(var/gear_name in pref.gear_list[pref.gear_slot])
- var/datum/gear/G = gear_datums[gear_name]
- if(istype(G)) total_cost += G.cost
- if((total_cost+TG.cost) <= config.max_gear_cost)
- pref.gear_list[pref.gear_slot] += TG.display_name
- return TOPIC_REFRESH_UPDATE_PREVIEW
- if(href_list["gear"] && href_list["tweak"])
- var/datum/gear/gear = locate(href_list["gear"])
- var/datum/gear_tweak/tweak = locate(href_list["tweak"])
- if(!tweak || !istype(gear) || !(tweak in gear.gear_tweaks) || gear_datums[gear.display_name] != gear)
- return TOPIC_NOACTION
- var/metadata = tweak.get_metadata(user, get_tweak_metadata(gear, tweak))
- if(!metadata || !CanUseTopic(user))
- return TOPIC_NOACTION
- set_tweak_metadata(gear, tweak, metadata)
- return TOPIC_REFRESH_UPDATE_PREVIEW
+ if(toggle_gear(gear_datums[href_list["toggle_gear"]], user))
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ return TOPIC_NOACTION
+
if(href_list["next_slot"])
- pref.gear_slot = pref.gear_slot+1
- if(pref.gear_slot > config.loadout_slots)
- pref.gear_slot = 1
- return TOPIC_REFRESH_UPDATE_PREVIEW
+ if(pref.gear_container.cycle_slot_right())
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ return TOPIC_NOACTION
+
if(href_list["prev_slot"])
- pref.gear_slot = pref.gear_slot-1
- if(pref.gear_slot < 1)
- pref.gear_slot = config.loadout_slots
- return TOPIC_REFRESH_UPDATE_PREVIEW
+ if(pref.gear_container.cycle_slot_left())
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ return TOPIC_NOACTION
+
if(href_list["select_category"])
- current_tab = href_list["select_category"]
+ var/new_tab = href_list["select_category"]
+ if(new_tab == current_tab)
+ return TOPIC_NOACTION
+
+ current_tab = new_tab
return TOPIC_REFRESH
+
if(href_list["clear_loadout"])
- var/list/gear = pref.gear_list[pref.gear_slot]
- gear.Cut()
+ var/datum/gear_slot/picked_gear_slot = pref.get_picked_gear_slot()
+ if(!picked_gear_slot.size())
+ return TOPIC_NOACTION
+
+ picked_gear_slot.clear()
return TOPIC_REFRESH_UPDATE_PREVIEW
+
if(href_list["toggle_hiding"])
hide_unavailable_gear = !hide_unavailable_gear
return TOPIC_REFRESH
+
+ if(href_list["select_gear"])
+ var/datum/gear/gear_to_select = gear_datums[href_list["select_gear"]]
+ if(!gear_to_select)
+ return TOPIC_NOACTION
+
+ selected_gear = gear_to_select
+ var/datum/gear_slot/picked_gear_slot = pref.get_picked_gear_slot()
+
+ selected_tweaks = picked_gear_slot.get_gear_tweaks(selected_gear.display_name)
+ if(!length(selected_tweaks))
+ for(var/datum/gear_tweak/tweak as anything in selected_gear.gear_tweaks)
+ selected_tweaks["[tweak]"] = tweak.get_default()
+
+ pref.trying_on_gear = null
+ pref.trying_on_tweaks.Cut()
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ if(href_list["tweak"])
+ var/datum/gear_tweak/tweak = locate(href_list["tweak"])
+ if(!tweak || !istype(selected_gear) || !(tweak in selected_gear.gear_tweaks))
+ return TOPIC_NOACTION
+
+ var/metadata = tweak.get_metadata(user, get_tweak_metadata(selected_gear, tweak))
+ if(!metadata || !CanUseTopic(user))
+ return TOPIC_NOACTION
+
+ selected_tweaks["[tweak]"] = metadata
+
+ var/datum/gear_slot/picked_slot = pref.get_picked_gear_slot()
+ var/ticked = picked_slot.contains(selected_gear.display_name)
+ if(ticked)
+ set_tweak_metadata(selected_gear, tweak, metadata)
+
+ var/trying_on = (selected_gear.display_name == pref.trying_on_gear)
+ if(trying_on)
+ pref.trying_on_tweaks["[tweak]"] = metadata
+
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ if(href_list["try_on"])
+ if(!istype(selected_gear))
+ return TOPIC_NOACTION
+
+ if(selected_gear.display_name == pref.trying_on_gear)
+ pref.trying_on_gear = null
+ pref.trying_on_tweaks.Cut()
+ else
+ pref.trying_on_gear = selected_gear.display_name
+ pref.trying_on_tweaks = selected_tweaks.Copy()
+
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ if(href_list["random_loadout"])
+ randomize(user)
+ return TOPIC_REFRESH_UPDATE_PREVIEW
+
+ if(href_list["toggle_donate"])
+ hide_donate_gear = !hide_donate_gear
+ return TOPIC_REFRESH
+
+ if(href_list["donate"])
+ var/singleton/modpack/don_loadout/donations = GET_SINGLETON(/singleton/modpack/don_loadout)
+ donations.show_donations_info(user)
+ return TOPIC_NOACTION
+
return ..()
-/datum/gear
- var/display_name //Name/index. Must be unique.
- var/description //Description of this gear. If left blank will default to the description of the pathed item.
- var/path //Path to item.
- var/cost = 1 //Number of points used. Items in general cost 1 point, storage/armor/gloves/special use costs 2 points.
- var/slot //Slot to equip to.
- var/list/allowed_roles //Roles that can spawn with this item.
- var/list/allowed_branches //Service branches that can spawn with it.
- var/list/allowed_skills //Skills required to spawn with this item.
- var/whitelisted //Term to check the whitelist for..
- var/sort_category = "General"
- var/flags //Special tweaks in New
- var/custom_setup_proc //Special tweak in New
- var/category
- var/list/gear_tweaks = list() //List of datums which will alter the item after it has been spawned.
-
-/datum/gear/New()
- if(HAS_FLAGS(flags, GEAR_HAS_TYPE_SELECTION|GEAR_HAS_SUBTYPE_SELECTION))
- CRASH("May not have both type and subtype selection tweaks")
- if(!description)
- var/obj/O = path
- description = initial(O.desc)
- if(flags & GEAR_HAS_COLOR_SELECTION)
- gear_tweaks += gear_tweak_free_color_choice()
- if(!(flags & GEAR_HAS_NO_CUSTOMIZATION))
- gear_tweaks += gear_tweak_free_name(display_name)
- gear_tweaks += gear_tweak_free_desc(description)
- if(flags & GEAR_HAS_TYPE_SELECTION)
- gear_tweaks += new/datum/gear_tweak/path/type(path)
- if(flags & GEAR_HAS_SUBTYPE_SELECTION)
- gear_tweaks += new/datum/gear_tweak/path/subtype(path)
- if(custom_setup_proc)
- gear_tweaks += new/datum/gear_tweak/custom_setup(custom_setup_proc)
-
-/datum/gear/proc/get_description(metadata)
- . = description
- for(var/datum/gear_tweak/gt in gear_tweaks)
- . = gt.tweak_description(., metadata["[gt]"])
-
-/datum/gear_data
- var/path
- var/location
-
-/datum/gear_data/New(path, location)
- src.path = path
- src.location = location
-
-/datum/gear/proc/spawn_item(user, location, metadata)
- var/datum/gear_data/gd = new(path, location)
- for(var/datum/gear_tweak/gt in gear_tweaks)
- gt.tweak_gear_data(metadata && metadata["[gt]"], gd)
- var/item = new gd.path(gd.location)
- for(var/datum/gear_tweak/gt in gear_tweaks)
- gt.tweak_item(user, item, metadata && metadata["[gt]"])
- return item
-
-/datum/gear/proc/spawn_on_mob(mob/living/carbon/human/H, metadata)
- var/obj/item/item = spawn_item(H, H, metadata)
- if(H.equip_to_slot_if_possible(item, slot, TRYEQUIP_REDRAW | TRYEQUIP_DESTROY | TRYEQUIP_FORCE))
- . = item
-
-
-/datum/gear/proc/spawn_in_storage_or_drop(mob/living/carbon/human/subject, metadata)
- var/obj/item/item = spawn_item(subject, subject, metadata)
- item.add_fingerprint(subject)
- if (istype(item, /obj/item/organ/internal/augment))
- var/obj/item/organ/internal/augment/augment = item
- var/obj/item/organ/external/parent = augment.get_valid_parent_organ(subject)
- if (!parent)
- to_chat(subject, SPAN_WARNING("Failed to find a valid organ to install \the [augment] into!"))
- qdel(augment)
- return
- var/surgery_step = GET_SINGLETON(/singleton/surgery_step/internal/replace_organ)
- if (augment.surgery_configure(subject, subject, parent, null, surgery_step))
- to_chat(subject, SPAN_WARNING("Failed to set up \the [augment] for installation in your [parent.name]!"))
- qdel(augment)
- return
- augment.forceMove(subject)
- augment.replaced(subject, parent)
- augment.onRoundstart()
- return
- var/atom/container = subject.equip_to_storage(item)
- if (container)
- to_chat(subject, SPAN_NOTICE("Placing \the [item] in your [container.name]!"))
- else if (subject.equip_to_appropriate_slot(item))
- to_chat(subject, SPAN_NOTICE("Placing \the [item] in your inventory!"))
- else if (subject.put_in_hands(item))
- to_chat(subject, SPAN_NOTICE("Placing \the [item] in your hands!"))
+/datum/category_item/player_setup_item/loadout/proc/randomize(mob/user)
+ ASSERT(user)
+
+ pref.trying_on_gear = null
+ pref.trying_on_tweaks.Cut()
+
+ var/datum/gear_slot/current_slot = pref.get_picked_gear_slot()
+ current_slot.clear()
+
+ var/list/pool = list()
+ for(var/gear_name in gear_datums)
+ var/datum/gear/gear_datum = gear_datums[gear_name]
+ if(gear_allowed_to_see(gear_datum) && is_gear_valid(gear_datum))
+ pool += gear_datum
+
+ var/points_left = config.max_gear_cost
+
+ while (points_left > 0 && length(pool))
+ var/datum/gear/chosen = pick(pool)
+ var/list/chosen_tweaks = list()
+
+ for(var/datum/gear_tweak/tweak in chosen.gear_tweaks)
+ chosen_tweaks["[tweak]"] = tweak.get_random()
+
+ current_slot.add_gear(chosen.display_name, chosen_tweaks)
+ points_left -= chosen.cost
+
+ for(var/datum/gear/gear_datum as anything in pool)
+ if(gear_datum.cost <= points_left && gear_datum.slot != chosen.slot)
+ continue
+
+ pool -= gear_datum
+
+/datum/category_item/player_setup_item/loadout/proc/gear_allowed_to_see(datum/gear/G)
+ ASSERT(G)
+ if(!G.path)
+ return FALSE
+
+ if(length(G.allowed_roles) || length(G.required_skills) || length(G.allowed_branches))
+ // Branches are dependent on jobs so here it is
+ ASSERT(SSjobs.initialized)
+ var/list/jobs = new
+ for(var/job_title in (pref.job_medium|pref.job_low|pref.job_high))
+ if(SSjobs.get_by_title(job_title))
+ jobs += SSjobs.get_by_title(job_title)
+
+ // No jobs = Fail
+ // No jobs = No skills = No branches = Fail
+ if(!jobs || !length(jobs))
+ return FALSE
+
+ if (length(G.allowed_roles))
+ var/job_ok = FALSE
+ for(var/datum/job/J in jobs)
+ if(J.type in G.allowed_roles)
+ job_ok = TRUE
+ break
+ if(!job_ok)
+ return FALSE
+
+ if (length(G.required_skills))
+ var/list/skills_required = list()
+ for(var/skill in G.required_skills)
+ var/singleton/hierarchy/skill/instance = GET_SINGLETON(skill)
+ skills_required[instance] = G.required_skills[skill]
+ if (!skill_check(jobs, skills_required))
+ return FALSE
+
+ // It is nesting hell, but it should work fine
+ if (length(G.allowed_branches))
+ var/list/branches = list()
+ for(var/datum/job/J in jobs)
+ if(pref.branches[J.title])
+ branches |= pref.branches[J.title]
+ if (!branches || !length(branches))
+ return FALSE
+ var/branch_ok = FALSE
+ for(var/branch in branches)
+ var/datum/mil_branch/player_branch = GLOB.mil_branches.get_branch(branch)
+ if(player_branch.type in G.allowed_branches)
+ branch_ok = TRUE
+ break
+
+ if (!branch_ok)
+ return FALSE
+
+ if (length(G.required_factions))
+ var/singleton/cultural_info/faction = SSculture.get_culture(pref.cultural_info[TAG_FACTION])
+ var/facname = faction ? faction.name : "Unset"
+ if(!(facname in G.required_factions))
+ return FALSE
+
+
+ if(G.whitelisted && !(pref.species in G.whitelisted))
+ return FALSE
+
+ return TRUE
+
+/// Adds or removes gear from picked gear slot.
+/// Returns `true` if gear successfully added or removed. `False` otherwise
+/datum/category_item/player_setup_item/loadout/proc/toggle_gear(datum/gear/gear_to_toggle, mob/user)
+ // Check if someone trying to tricking us. However, it's may be just a bug
+ ASSERT(gear_to_toggle?.allowed_donation_tier(user))
+
+ var/datum/gear_slot/picked_slot = pref.get_picked_gear_slot()
+
+ var/gear_name = gear_to_toggle.display_name
+ if(picked_slot.contains(gear_name))
+ picked_slot.remove_gear(gear_name)
+ return TRUE
+
+ var/total_gear_cost = picked_slot.get_total_gear_cost()
+ if(total_gear_cost + gear_to_toggle.cost > config.max_gear_cost)
+ return FALSE
+
+ picked_slot.add_gear(gear_name, selected_tweaks)
+ return TRUE
+
+
+/datum/category_item/player_setup_item/loadout/proc/get_gear_image(atom/movable/gear_item_prototype, mob/user)
+ ASSERT(istype(gear_item_prototype))
+ ASSERT(user)
+
+ var/list/icon_cache_key_components = list("[gear_item_prototype.icon]", "[gear_item_prototype.icon_state]")
+ if(islist(gear_item_prototype.color))
+ for(var/color in gear_item_prototype.color)
+ icon_cache_key_components += color
else
- to_chat(subject, SPAN_WARNING("Dropping \the [item] on the ground!"))
+ icon_cache_key_components += gear_item_prototype.color
+
+ var/cache_key = icon_cache_key_components.Join(":")
+ var/asset_name = LAZYACCESS(gear_icons_cache, cache_key)
+ if(!asset_name)
+ var/icon/gear_icon = icon(gear_item_prototype.icon, gear_item_prototype.icon_state)
+ if(gear_item_prototype.color)
+ if(islist(gear_item_prototype.color))
+ gear_icon.MapColors(arglist(gear_item_prototype.color))
+ else
+ gear_icon.Blend(gear_item_prototype.color, ICON_MULTIPLY)
+
+ asset_name = register_icon_asset(gear_icon)
+ LAZYSET(gear_icons_cache, cache_key, asset_name)
+
+ SSassets.transport.send_assets(user, asset_name)
+ return ""
diff --git a/code/modules/client/preference_setup/loadout/loadout_category.dm b/code/modules/client/preference_setup/loadout/loadout_category.dm
new file mode 100644
index 0000000000000..16d62782cc312
--- /dev/null
+++ b/code/modules/client/preference_setup/loadout/loadout_category.dm
@@ -0,0 +1,23 @@
+/datum/gear_category
+ /// Name of the category. Used for sorting
+ var/category_name = ""
+ /// Assoc list of gear instances as
+ VAR_PRIVATE/list/gear_items = list()
+
+/datum/gear_category/New(category_name)
+ src.category_name = category_name
+
+/datum/gear_category/proc/add_gear(datum/gear/gear_to_add)
+ ASSERT(istype(gear_to_add))
+ gear_items[gear_to_add.display_name] = gear_to_add
+
+/datum/gear_category/proc/sort_gear()
+ gear_items = sortAssoc(gear_items)
+
+/// Returns copy of gear `gear_items`
+/datum/gear_category/proc/get_gear_items()
+ if(!gear_items)
+ stack_trace("`gear_items` should not be null")
+ gear_items = list()
+
+ return gear_items.Copy()
diff --git a/code/modules/client/preference_setup/occupation/occupation.dm b/code/modules/client/preference_setup/occupation/occupation.dm
index f4f0b37effdcd..d96faf34fc15c 100644
--- a/code/modules/client/preference_setup/occupation/occupation.dm
+++ b/code/modules/client/preference_setup/occupation/occupation.dm
@@ -391,7 +391,6 @@
show_browser(user, jointext(HTML, null), "window=\ref[user]skillinfo")
else if(href_list["job_info"])
-
var/rank = href_list["job_info"]
var/datum/job/job = SSjobs.get_by_title(rank)
diff --git a/code/modules/client/preference_setup/preference_setup.dm b/code/modules/client/preference_setup/preference_setup.dm
index 09344a28fcf70..1d29e3f0a4cb0 100644
--- a/code/modules/client/preference_setup/preference_setup.dm
+++ b/code/modules/client/preference_setup/preference_setup.dm
@@ -1,6 +1,8 @@
-#define TOPIC_UPDATE_PREVIEW 4
-#define TOPIC_HARD_REFRESH 8 // use to force a browse() call, unblocking some rsc operations
+#define TOPIC_UPDATE_PREVIEW FLAG(2)
+#define TOPIC_HARD_REFRESH FLAG(3) // use to force a browse() call, unblocking some rsc operations
+#define TOPIC_UPDATE_PREVIEW_BACKGROUND_ICON FLAG(4)
#define TOPIC_REFRESH_UPDATE_PREVIEW (TOPIC_HARD_REFRESH|TOPIC_UPDATE_PREVIEW)
+#define TOPIC_REFRESH_UPDATE_PREVIEW_BACKGROUND_ICON (TOPIC_HARD_REFRESH|TOPIC_UPDATE_PREVIEW_BACKGROUND_ICON)
var/global/const/CHARACTER_PREFERENCE_INPUT_TITLE = "Character Preference"
@@ -32,17 +34,17 @@ var/global/const/CHARACTER_PREFERENCE_INPUT_TITLE = "Character Preference"
/datum/category_group/player_setup_category/loadout_preferences
name = "Loadout"
- sort_order = 6
+ sort_order = 5
category_item_type = /datum/category_item/player_setup_item/loadout
/datum/category_group/player_setup_category/global_preferences
name = "Global"
- sort_order = 7
+ sort_order = 6
category_item_type = /datum/category_item/player_setup_item/player_global
/datum/category_group/player_setup_category/law_pref
name = "Laws"
- sort_order = 8
+ sort_order = 7
category_item_type = /datum/category_item/player_setup_item/law_pref
@@ -51,13 +53,13 @@ var/global/const/CHARACTER_PREFERENCE_INPUT_TITLE = "Character Preference"
****************************/
/datum/category_collection/player_setup_collection
category_group_type = /datum/category_group/player_setup_category
- var/datum/preferences/preferences
+ var/datum/preferences/preferences = null
var/datum/category_group/player_setup_category/selected_category = null
/datum/category_collection/player_setup_collection/New(datum/preferences/preferences)
src.preferences = preferences
..()
- selected_category = categories[1]
+ selected_category = LAZYACCESS(categories, 1)
/datum/category_collection/player_setup_collection/Destroy()
preferences = null
@@ -65,50 +67,72 @@ var/global/const/CHARACTER_PREFERENCE_INPUT_TITLE = "Character Preference"
return ..()
/datum/category_collection/player_setup_collection/proc/sanitize_setup()
+ if(!LAZYLEN(categories))
+ return
+
for(var/datum/category_group/player_setup_category/PS in categories)
PS.sanitize_setup()
/datum/category_collection/player_setup_collection/proc/load_character(datum/pref_record_reader/R)
+ if(!LAZYLEN(categories))
+ return
+
for(var/datum/category_group/player_setup_category/PS in categories)
PS.load_character(R)
/datum/category_collection/player_setup_collection/proc/save_character(datum/pref_record_writer/W)
+ if(!LAZYLEN(categories))
+ return
+
for(var/datum/category_group/player_setup_category/PS in categories)
PS.save_character(W)
/datum/category_collection/player_setup_collection/proc/load_preferences(datum/pref_record_reader/R)
+ if(!LAZYLEN(categories))
+ return
+
for(var/datum/category_group/player_setup_category/PS in categories)
PS.load_preferences(R)
/datum/category_collection/player_setup_collection/proc/save_preferences(datum/pref_record_writer/W)
+ if(!LAZYLEN(categories))
+ return
+
for(var/datum/category_group/player_setup_category/PS in categories)
PS.save_preferences(W)
/datum/category_collection/player_setup_collection/proc/header()
- var/dat = ""
- for(var/datum/category_group/player_setup_category/PS in categories)
+ if(!LAZYLEN(categories))
+ return ""
+
+ var/list/dat = list()
+ for(var/datum/category_group/player_setup_category/PS as anything in categories)
if(PS == selected_category)
dat += "[PS.name] " // TODO: Check how to properly mark a href/button selected in a classic browser window
else
dat += "[PS.name] "
- return dat
+
+ return dat.Join()
/datum/category_collection/player_setup_collection/proc/content(mob/user)
- if(selected_category)
- return selected_category.content(user)
+ if(!selected_category)
+ return null
+
+ return selected_category.content(user)
-/datum/category_collection/player_setup_collection/Topic(href,list/href_list)
+/datum/category_collection/player_setup_collection/Topic(href, list/href_list)
if(..())
- return 1
+ return TRUE
+
var/mob/user = usr
if(!user.client)
- return 1
+ return TRUE
if(href_list["category"])
var/category = locate(href_list["category"])
if(category && (category in categories))
selected_category = category
- . = 1
+ . = TRUE
if(.)
user.client.prefs.update_setup_window(user)
@@ -217,45 +241,53 @@ var/global/const/CHARACTER_PREFERENCE_INPUT_TITLE = "Character Preference"
/datum/category_item/player_setup_item/proc/sanitize_preferences()
return
-/datum/category_item/player_setup_item/Topic(href,list/href_list)
+/datum/category_item/player_setup_item/Topic(href, list/href_list)
if(..())
- return 1
+ return TOPIC_HANDLED
+
var/mob/pref_mob = preference_mob()
if(!pref_mob || !pref_mob.client)
- return 1
+ return TOPIC_HANDLED
+
// If the usr isn't trying to alter their own mob then they must instead be an admin
if(usr != pref_mob && !check_rights(R_ADMIN, 0, usr))
- return 1
+ return TOPIC_HANDLED
- . = OnTopic(href, href_list, usr)
+ var/topic_result = OnTopic(href, href_list, usr)
// The user might have joined the game or otherwise had a change of mob while tweaking their preferences.
pref_mob = preference_mob()
if(!pref_mob || !pref_mob.client)
- return 1
+ return TOPIC_HANDLED
- if (. & TOPIC_UPDATE_PREVIEW)
- pref_mob.client.prefs.preview_icon = null
- if (. & TOPIC_HARD_REFRESH)
+ if(topic_result & TOPIC_UPDATE_PREVIEW_BACKGROUND_ICON)
+ pref_mob.client.prefs.update_preview_background_icon()
+
+ if (topic_result & TOPIC_UPDATE_PREVIEW)
+ pref_mob.client.prefs.update_preview_icon()
+
+ if (topic_result & TOPIC_HARD_REFRESH)
pref_mob.client.prefs.open_setup_window(usr)
- else if (. & TOPIC_REFRESH)
+
+ else if (topic_result & TOPIC_REFRESH)
pref_mob.client.prefs.update_setup_window(usr)
+ return topic_result
+
/datum/category_item/player_setup_item/CanUseTopic(mob/user)
- return 1
+ return TRUE
/datum/category_item/player_setup_item/proc/OnTopic(href,list/href_list, mob/user)
return TOPIC_NOACTION
/datum/category_item/player_setup_item/proc/preference_mob()
if(!pref.client)
- for(var/client/C)
- if(C.ckey == pref.client_ckey)
- pref.client = C
- break
+ pref.client = GLOB.ckey_directory[pref.client_ckey]
if(pref.client)
return pref.client.mob
+ return null
+
/datum/category_item/player_setup_item/proc/preference_species()
return all_species[pref.species] || all_species[SPECIES_HUMAN]
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index e1af628cb5636..8155ebf502f12 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -12,7 +12,7 @@
//doohickeys for savefiles
var/is_guest = FALSE
var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
-
+ var/character_slots_count = 0
// Cache, mapping slot record ids to character names
// Saves reading all the slot records when listing
var/list/slot_names = null
@@ -33,9 +33,10 @@
var/client_ckey = null
var/datum/browser/popup
-
var/datum/category_collection/player_setup_collection/player_setup
var/datum/browser/panel
+ var/datum/gear/trying_on_gear
+ var/list/trying_on_tweaks = list()
/datum/preferences/New(client/C)
if(istype(C))
@@ -49,6 +50,7 @@
..()
/datum/preferences/proc/setup()
+ key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key)
if(!length(GLOB.skills))
GET_SINGLETON(/singleton/hierarchy/skill)
player_setup = new(src)
@@ -57,6 +59,7 @@
b_type = RANDOM_BLOOD_TYPE
if(client)
+ client.set_macros()
if(IsGuestKey(client.key))
is_guest = TRUE
else
@@ -136,10 +139,10 @@
dat += "Loading your savefile failed. Please adminhelp for assistance."
else
dat += "Slot - "
- dat += "Load slot - "
- dat += "Save slot - "
- dat += "Reset slot - "
- dat += "Reload slot"
+ dat += "Load slot - "
+ dat += "Save slot - "
+ dat += "Reset slot - "
+ dat += "Reload slot"
dat += " "
dat += player_setup.header()
@@ -150,7 +153,8 @@
/datum/preferences/proc/open_setup_window(mob/user)
if (!SScharacter_setup.initialized)
return
- popup = new (user, "preferences_browser", "Character Setup", 1200, 800, src)
+
+ popup = new(user, "preference_window", "Character Setup", 1000, 800, src)
var/content = {"
-
- Chat
-
-
-
-
-
- Loading...
- If this takes longer than 30 seconds, it will automatically reload a maximum of 5 times.
- If it still doesn't work, use the bug report button at the top right of the window.
-
").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
diff --git a/code/modules/goonchat/browserassets/js/json2.min.js b/code/modules/goonchat/browserassets/js/json2.min.js
deleted file mode 100644
index d867407f265eb..0000000000000
--- a/code/modules/goonchat/browserassets/js/json2.min.js
+++ /dev/null
@@ -1 +0,0 @@
-"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function this_value(){return this.valueOf()}function quote(t){return rx_escapable.lastIndex=0,rx_escapable.test(t)?'"'+t.replace(rx_escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var r,n,o,u,f,a=gap,i=e[t];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(t)),"function"==typeof rep&&(i=rep.call(e,t,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,f=[],"[object Array]"===Object.prototype.toString.apply(i)){for(u=i.length,r=0;u>r;r+=1)f[r]=str(r,i)||"null";return o=0===f.length?"[]":gap?"[\n"+gap+f.join(",\n"+gap)+"\n"+a+"]":"["+f.join(",")+"]",gap=a,o}if(rep&&"object"==typeof rep)for(u=rep.length,r=0;u>r;r+=1)"string"==typeof rep[r]&&(n=rep[r],o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));else for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));return o=0===f.length?"{}":gap?"{\n"+gap+f.join(",\n"+gap)+"\n"+a+"}":"{"+f.join(",")+"}",gap=a,o}}var rx_one=/^[\],:{}\s]*$/,rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,rx_four=/(?:^|:|,)(?:\s*\[)+/g,rx_escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,rx_dangerous=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},Boolean.prototype.toJSON=this_value,Number.prototype.toJSON=this_value,String.prototype.toJSON=this_value);var gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,r){var n;if(gap="",indent="","number"==typeof r)for(n=0;r>n;n+=1)indent+=" ";else"string"==typeof r&&(indent=r);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw new Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(JSON.parse=function(text,reviver){function walk(t,e){var r,n,o=t[e];if(o&&"object"==typeof o)for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(n=walk(o,r),void 0!==n?o[r]=n:delete o[r]);return reviver.call(t,e,o)}var j;if(text=String(text),rx_dangerous.lastIndex=0,rx_dangerous.test(text)&&(text=text.replace(rx_dangerous,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),rx_one.test(text.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
\ No newline at end of file
diff --git a/code/modules/holodeck/HolodeckControl.dm b/code/modules/holodeck/HolodeckControl.dm
index 98094592b7c09..e2139248bc803 100644
--- a/code/modules/holodeck/HolodeckControl.dm
+++ b/code/modules/holodeck/HolodeckControl.dm
@@ -209,7 +209,7 @@
for(var/turf/T in linkedholodeck)
if(prob(30))
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ var/datum/effect/spark_spread/s = new /datum/effect/spark_spread
s.set_up(2, 1, T)
s.start()
T.ex_act(EX_ACT_LIGHT)
@@ -218,7 +218,7 @@
/obj/machinery/computer/HolodeckControl/proc/derez(obj/obj , silent = 1)
holographic_objs.Remove(obj)
- if(obj == null)
+ if(isnull(obj))
return
if(!silent)
@@ -274,17 +274,14 @@
holographic_mobs -= C
C.death()
- for(var/obj/effect/decal/cleanable/blood/B in linkedholodeck)
+ for(var/obj/decal/cleanable/blood/B in linkedholodeck)
qdel(B)
holographic_objs = A.copy_contents_to(linkedholodeck , 1)
for(var/obj/holo_obj in holographic_objs)
holo_obj.alpha *= 0.8 //give holodeck objs a slight transparency
holo_obj.holographic = TRUE
- if(istype(holo_obj,/obj/item/storage))
- set_extension(holo_obj,/datum/extension/chameleon/backpack)
- if(istype(holo_obj,/obj/item/clothing))
- set_extension(holo_obj,/datum/extension/chameleon/clothing)
+ holo_obj.SetupChameleonExtension(CHAMELEON_FLEXIBLE_OPTIONS_PARENT_TYPE, TRUE, FALSE)
if(HP.ambience)
linkedholodeck.forced_ambience = HP.ambience.Copy()
@@ -298,11 +295,11 @@
linkedholodeck.sound_env = A.sound_env
spawn(30)
- for(var/obj/effect/landmark/L in linkedholodeck)
+ for(var/obj/landmark/L in linkedholodeck)
if(L.name=="Atmospheric Test Start")
spawn(20)
var/turf/T = get_turf(L)
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ var/datum/effect/spark_spread/s = new /datum/effect/spark_spread
s.set_up(2, 1, T)
s.start()
if(T)
diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm
index f43a411db2bf2..6c6b09055a477 100644
--- a/code/modules/holodeck/HolodeckObjects.dm
+++ b/code/modules/holodeck/HolodeckObjects.dm
@@ -5,15 +5,12 @@
/turf/simulated/floor/holofloor
thermal_conductivity = 0
+ atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_NO_TOOLS
// the new Diona Death Prevention Feature: gives an average amount of lumination
/turf/simulated/floor/holofloor/get_lumcount(minlum = 0, maxlum = 1)
return 0.8
-/turf/simulated/floor/holofloor/attackby(obj/item/W as obj, mob/user as mob)
- return
- // HOLOFLOOR DOES NOT GIVE A FUCK
-
/turf/simulated/floor/holofloor/set_flooring()
return
@@ -89,7 +86,7 @@
name = "reinforced holofloor"
icon_state = "reinforced"
-/turf/simulated/floor/holofloor/space/Initialize()
+/turf/simulated/floor/holofloor/space/Initialize(mapload, added_to_area_cache)
. = ..()
icon_state = "[((x + y) ^ ~(x * y) + z) % 25]"
@@ -127,15 +124,15 @@
base_icon = 'icons/turf/flooring/asteroid.dmi'
initial_flooring = null
-/turf/simulated/floor/holofloor/desert/New()
- ..()
+/turf/simulated/floor/holofloor/desert/Initialize(mapload, added_to_area_cache)
+ . = ..()
if(prob(10))
- overlays += "asteroid[rand(0,9)]"
+ AddOverlays("asteroid[rand(0,9)]")
/obj/structure/holostool
name = "stool"
desc = "Apply butt."
- icon = 'icons/obj/furniture.dmi'
+ icon = 'icons/obj/structures/furniture.dmi'
icon_state = "stool_padded_preview"
anchored = TRUE
@@ -145,6 +142,9 @@
icon_state = "boxing"
item_state = "boxing"
+/obj/structure/window/holowindow
+ atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_NO_TOOLS
+
/obj/structure/window/holowindow/full
dir = 5
icon_state = "window_full"
@@ -155,20 +155,6 @@
/obj/structure/window/reinforced/holowindow/Destroy()
..()
-/obj/structure/window/reinforced/holowindow/attackby(obj/item/W as obj, mob/user as mob)
-
- if(!istype(W) || W.item_flags & ITEM_FLAG_NO_BLUDGEON) return
-
- if(isScrewdriver(W) || isCrowbar(W) || isWrench(W))
- to_chat(user, (SPAN_NOTICE("It's a holowindow, you can't dismantle it!")))
- else
- if (W.damtype == DAMAGE_BRUTE || W.damtype == DAMAGE_BURN)
- hit(W.force, user, W)
- else
- playsound(loc, 'sound/effects/Glasshit.ogg', 75, 1)
- ..()
- return
-
/obj/structure/window/reinforced/holowindow/shatter(display_message = 1)
playsound(src, "shatter", 70, 1)
if(display_message)
@@ -212,14 +198,12 @@
visible_message("[src] fades away as it shatters!")
qdel(src)
+/obj/structure/bed/chair/holochair
+ bed_flags = BED_FLAG_CANNOT_BE_DISMANTLED | BED_FLAG_CANNOT_BE_ELECTRIFIED | BED_FLAG_CANNOT_BE_PADDED
+
/obj/structure/bed/chair/holochair/Destroy()
..()
-/obj/structure/bed/chair/holochair/attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/wrench))
- to_chat(user, (SPAN_NOTICE("It's a holochair, you can't dismantle it!")))
- return
-
/obj/item/holo
damtype = DAMAGE_PAIN
no_attack_log = TRUE
@@ -248,12 +232,12 @@
/obj/item/holo/esword/handle_shield(mob/user, damage, atom/damage_source = null, mob/attacker = null, def_zone = null, attack_text = "the attack")
. = ..()
if(.)
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
+ var/datum/effect/spark_spread/spark_system = new /datum/effect/spark_spread()
spark_system.set_up(5, 0, user.loc)
spark_system.start()
playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1)
-/obj/item/holo/esword/get_parry_chance(mob/user)
+/obj/item/holo/esword/get_parry_chance(mob/user, mob/attacker)
return active ? ..() : 0
/obj/item/holo/esword/Initialize()
@@ -283,7 +267,7 @@
//BASKETBALL OBJECTS
/obj/item/beach_ball/holoball
- icon = 'icons/obj/basketball.dmi'
+ icon = 'icons/obj/structures/basketball.dmi'
icon_state = "basketball"
name = "basketball"
item_state = "basketball"
@@ -293,7 +277,7 @@
/obj/structure/holohoop
name = "basketball hoop"
desc = "Boom, Shakalaka!"
- icon = 'icons/obj/basketball.dmi'
+ icon = 'icons/obj/structures/basketball.dmi'
icon_state = "hoop"
anchored = TRUE
density = TRUE
@@ -316,7 +300,7 @@
//VOLLEYBALL OBJECTS
/obj/item/beach_ball/holovolleyball
- icon = 'icons/obj/basketball.dmi'
+ icon = 'icons/obj/structures/basketball.dmi'
icon_state = "volleyball"
name = "volleyball"
item_state = "volleyball"
@@ -326,7 +310,7 @@
/obj/structure/holonet
name = "net"
desc = "Bullshit, you can be mine!"
- icon = 'icons/obj/basketball.dmi'
+ icon = 'icons/obj/structures/basketball.dmi'
icon_state = "volleynet_mid"
density = TRUE
anchored = TRUE
@@ -354,7 +338,7 @@
/obj/machinery/readybutton
name = "Ready Declaration Device"
desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!"
- icon = 'icons/obj/monitors.dmi'
+ icon = 'icons/obj/structures/keycard_authenticator.dmi'
icon_state = "auth_off"
var/ready = 0
var/area/currentarea = null
@@ -372,10 +356,6 @@
/obj/machinery/readybutton/New()
..()
-
-/obj/machinery/readybutton/attackby(obj/item/W as obj, mob/user as mob)
- to_chat(user, "The device is a solid button, there's nothing you can do with it!")
-
/obj/machinery/readybutton/physical_attack_hand(mob/user)
currentarea = get_area(src)
if(!currentarea)
@@ -435,9 +415,10 @@
/mob/living/simple_animal/hostile/carp/holodeck/on_update_icon()
return
-/mob/living/simple_animal/hostile/carp/holodeck/New()
- ..()
- set_light(0.5, 0.1, 2) //hologram lighting
+/mob/living/simple_animal/hostile/carp/holodeck/Initialize(mapload, ...)
+ . = ..()
+ set_light(2, 0.5) //hologram lighting
+
/mob/living/simple_animal/hostile/carp/holodeck/proc/set_safety(safe)
var/obj/item/NW = get_natural_weapon()
diff --git a/code/modules/holomap/ship_holomap.dm b/code/modules/holomap/ship_holomap.dm
index 27aa80a1d9f14..52bfc2709a3a6 100644
--- a/code/modules/holomap/ship_holomap.dm
+++ b/code/modules/holomap/ship_holomap.dm
@@ -112,8 +112,8 @@
user.client.images |= holomap_datum.station_map
watching_mob = user
- GLOB.moved_event.register(watching_mob, src, /obj/machinery/ship_map/proc/checkPosition)
- GLOB.destroyed_event.register(watching_mob, src, /obj/machinery/ship_map/proc/stopWatching)
+ GLOB.moved_event.register(watching_mob, src, TYPE_PROC_REF(/obj/machinery/ship_map, checkPosition))
+ GLOB.destroyed_event.register(watching_mob, src, TYPE_PROC_REF(/obj/machinery/ship_map, stopWatching))
update_use_power(POWER_USE_ACTIVE)
if(bogus)
@@ -137,7 +137,7 @@
if(watching_mob.client)
animate(holomap_datum.station_map, alpha = 0, time = 5, easing = LINEAR_EASING)
var/mob/M = watching_mob
- addtimer(new Callback(src, .proc/clear_image, M, holomap_datum.station_map), 5, TIMER_CLIENT_TIME)//we give it time to fade out
+ addtimer(CALLBACK(src, PROC_REF(clear_image), M, holomap_datum.station_map), 0.5 SECONDS)//we give it time to fade out
GLOB.moved_event.unregister(watching_mob, src)
GLOB.destroyed_event.unregister(watching_mob, src)
watching_mob = null
@@ -151,7 +151,7 @@
/obj/machinery/ship_map/on_update_icon()
. = ..()
- overlays.Cut()
+ ClearOverlays()
if(MACHINE_IS_BROKEN(src))
icon_state = "station_mapb"
set_light(0)
@@ -160,20 +160,20 @@
set_light(0)
else
icon_state = "station_map"
- set_light(0.8, 0.1, 2, 2, "#1dbe17")
+ set_light(2, 0.8, "#1dbe17")
// Put the little "map" overlay down where it looks nice
if(small_station_map)
- overlays.Add(small_station_map)
+ AddOverlays(small_station_map)
if(floor_markings)
floor_markings.dir = src.dir
floor_markings.pixel_x = -src.pixel_x
floor_markings.pixel_y = -src.pixel_y
- src.overlays.Add(floor_markings)
+ AddOverlays(floor_markings)
if(panel_open)
- overlays.Add("station_map-panel")
+ AddOverlays("station_map-panel")
/obj/machinery/ship_map/ex_act(severity)
switch(severity)
@@ -250,13 +250,13 @@
/obj/screen/legend/proc/Setup(z_level)
has_areas = FALSE
//Get the areas for this z level and mark if we're empty
- overlays.Cut()
+ ClearOverlays()
for(var/area/A in SSminimap.holomaps[z_level].holomap_areas)
if(A.holomap_color == saved_color)
var/image/area = image(SSminimap.holomaps[z_level].holomap_areas[A])
area.pixel_x = ((HOLOMAP_ICON_SIZE / 2) - world.maxx / 2) - pixel_x
area.pixel_y = ((HOLOMAP_ICON_SIZE / 2) - world.maxy / 2) - pixel_y
- overlays += area
+ AddOverlays(area)
has_areas = TRUE
//What happens when we are clicked on / when another is clicked on
@@ -288,7 +288,7 @@
var/z = -1
var/displayed_level = 1 //Index of level to display
-/datum/station_holomap/Destroy(force)
+/datum/station_holomap/Destroy()
QDEL_NULL(station_map)
QDEL_NULL(cursor)
QDEL_NULL_LIST(legend)
@@ -296,7 +296,7 @@
QDEL_NULL_LIST(lbuttons)
QDEL_NULL_LIST(maptexts)
QDEL_NULL_LIST(z_levels)
- . = ..()
+ return ..()
/datum/station_holomap/proc/initialize_holomap(turf/T, isAI = null, mob/user = null, reinit = FALSE)
z = T.z
@@ -331,7 +331,7 @@
//This is where the fun begins
if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/O = map_sectors["[z]"]
+ var/obj/overmap/visitable/O = map_sectors["[z]"]
var/current_z_offset_x = (HOLOMAP_ICON_SIZE / 2) - world.maxx / 2
var/current_z_offset_y = (HOLOMAP_ICON_SIZE / 2) - world.maxy / 2
@@ -394,13 +394,13 @@
displayed_level = level
- station_map.overlays.Cut()
+ station_map.ClearOverlays()
station_map.vis_contents.Cut()
if(z == z_levels[displayed_level])
- station_map.overlays += cursor
+ station_map.AddOverlays(cursor)
- station_map.overlays += levels["[z_levels[displayed_level]]"]
+ station_map.AddOverlays(levels["[z_levels[displayed_level]]"])
station_map.vis_contents += maptexts["[z_levels[displayed_level]]"]
//Fix legend position
@@ -429,6 +429,7 @@
/datum/station_holomap/proc/initialize_holomap_bogus()
station_map = image('icons/480x480.dmi', "stationmap")
- station_map.overlays |= image('icons/effects/64x64.dmi', "notfound", pixel_x = 7 * WORLD_ICON_SIZE, pixel_y = 7 * WORLD_ICON_SIZE)
+ var/notfound = image('icons/effects/64x64.dmi', "notfound", pixel_x = 7 * WORLD_ICON_SIZE, pixel_y = 7 * WORLD_ICON_SIZE)
+ station_map.AddOverlays(notfound)
#undef HOLOMAP_LEGEND_STYLING
diff --git a/code/modules/hydroponics/beekeeping/beehive.dm b/code/modules/hydroponics/beekeeping/beehive.dm
index 0c4f4f81d7b0d..c7bae89aa8706 100644
--- a/code/modules/hydroponics/beekeeping/beehive.dm
+++ b/code/modules/hydroponics/beekeeping/beehive.dm
@@ -6,6 +6,7 @@
density = TRUE
anchored = TRUE
layer = BELOW_OBJ_LAYER
+ obj_flags = OBJ_FLAG_ANCHORABLE
var/closed = 0
var/bee_count = 0 // Percent
@@ -19,98 +20,121 @@
update_icon()
/obj/machinery/beehive/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
icon_state = "beehive-[closed]"
if(closed)
- overlays += "lid"
+ AddOverlays("lid")
if(frames)
- overlays += "empty[frames]"
+ AddOverlays("empty[frames]")
if(honeycombs >= 100)
- overlays += "full[round(honeycombs / 100)]"
+ AddOverlays("full[round(honeycombs / 100)]")
if(!smoked)
switch(bee_count)
if(1 to 20)
- overlays += "bees1"
+ AddOverlays("bees1")
if(21 to 40)
- overlays += "bees2"
+ AddOverlays("bees2")
if(41 to 60)
- overlays += "bees3"
+ AddOverlays("bees3")
if(61 to 80)
- overlays += "bees4"
+ AddOverlays("bees4")
if(81 to 100)
- overlays += "bees5"
+ AddOverlays("bees5")
/obj/machinery/beehive/examine(mob/user)
. = ..()
if(!closed)
- to_chat(user, "The lid is open.")
+ . += SPAN_NOTICE("The lid is open.")
-/obj/machinery/beehive/attackby(obj/item/I, mob/user)
- if(isCrowbar(I))
- closed = !closed
- user.visible_message(
- SPAN_NOTICE("\The [user] [closed ? "closes" : "opens"] \the [src]."),
- SPAN_NOTICE("You [closed ? "close" : "open"] \the [src].")
- )
- update_icon()
+/obj/machinery/beehive/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
- else if(isWrench(I))
- anchored = !anchored
- user.visible_message(
- SPAN_NOTICE("\The [user] [anchored ? "wrenches" : "unwrenches"] \the [src]."),
- SPAN_NOTICE("You [anchored ? "wrench" : "unwrench"] \the [src].")
- )
+ closed = !closed
+ user.visible_message(
+ SPAN_NOTICE("[user] [closed ? "closes" : "opens"] [src]."),
+ SPAN_NOTICE("You [closed ? "close" : "open"] [src].")
+ )
+ update_icon()
+
+/obj/machinery/beehive/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(bee_count)
+ balloon_alert(user, "внутри пчёлы!")
+ return
+ USE_FEEDBACK_DECONSTRUCT_START(user)
+ if(!tool.use_as_tool(src, user, 3 SECONDS, volume = 50, skill_path = SKILL_CONSTRUCTION, do_flags = DO_PUBLIC_UNIQUE))
return
- else if(istype(I, /obj/item/bee_smoker))
+ user.visible_message(SPAN_NOTICE("[user] dismantles [src]."), SPAN_NOTICE("You dismantle [src]."))
+ new /obj/item/beehive_assembly(loc)
+ qdel(src)
+
+/obj/machinery/beehive/use_tool(obj/item/I, mob/living/user, list/click_params)
+ if (istype(I, /obj/item/bee_smoker))
if(closed)
- to_chat(user, SPAN_NOTICE("You need to open \the [src] with a crowbar before smoking the bees."))
- return
- user.visible_message(SPAN_NOTICE("\The [user] smokes the bees in \the [src]."), SPAN_NOTICE("You smoke the bees in \the [src]."))
+ to_chat(user, SPAN_NOTICE("You need to open [src] with a crowbar before smoking the bees."))
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("[user] smokes the bees in [src]."),
+ SPAN_NOTICE("You smoke the bees in [src].")
+ )
smoked = 30
update_icon()
- return
- else if(istype(I, /obj/item/honey_frame))
+ return TRUE
+
+ if (istype(I, /obj/item/honey_frame))
if(closed)
- to_chat(user, SPAN_NOTICE("You need to open \the [src] with a crowbar before inserting \the [I]."))
- return
+ to_chat(user, SPAN_NOTICE("You need to open [src] with a crowbar before inserting [I]."))
+ return TRUE
if(frames >= maxFrames)
to_chat(user, SPAN_NOTICE("There is no place for an another frame."))
- return
+ return TRUE
var/obj/item/honey_frame/H = I
if(H.honey)
- to_chat(user, SPAN_NOTICE("\The [I] is full with beeswax and honey, empty it in the extractor first."))
- return
+ to_chat(user, SPAN_NOTICE("[I] is full with beeswax and honey, empty it in the extractor first."))
+ return TRUE
++frames
- user.visible_message(SPAN_NOTICE("\The [user] loads \the [I] into \the [src]."), SPAN_NOTICE("You load \the [I] into \the [src]."))
+ user.visible_message(
+ SPAN_NOTICE("[user] loads [I] into [src]."),
+ SPAN_NOTICE("You load [I] into [src].")
+ )
update_icon()
qdel(I)
- return
- else if(istype(I, /obj/item/bee_pack))
+ return TRUE
+
+ if (istype(I, /obj/item/bee_pack))
var/obj/item/bee_pack/B = I
if(B.full && bee_count)
- to_chat(user, SPAN_NOTICE("\The [src] already has bees inside."))
- return
+ to_chat(user, SPAN_NOTICE("[src] already has bees inside."))
+ return TRUE
if(!B.full && bee_count < 90)
- to_chat(user, SPAN_NOTICE("\The [src] is not ready to split."))
- return
+ to_chat(user, SPAN_NOTICE("[src] is not ready to split."))
+ return TRUE
if(!B.full && !smoked)
- to_chat(user, SPAN_NOTICE("Smoke \the [src] first!"))
- return
+ to_chat(user, SPAN_NOTICE("Smoke [src] first!"))
+ return TRUE
if(closed)
- to_chat(user, SPAN_NOTICE("You need to open \the [src] with a crowbar before moving the bees."))
- return
+ to_chat(user, SPAN_NOTICE("You need to open [src] with a crowbar before moving the bees."))
+ return TRUE
if(B.full)
- user.visible_message(SPAN_NOTICE("\The [user] puts the queen and the bees from \the [I] into \the [src]."), SPAN_NOTICE("You put the queen and the bees from \the [I] into \the [src]."))
+ user.visible_message(
+ SPAN_NOTICE("[user] puts the queen and the bees from [I] into [src]."),
+ SPAN_NOTICE("You put the queen and the bees from [I] into [src].")
+ )
bee_count = 20
B.empty()
else
- user.visible_message(SPAN_NOTICE("\The [user] puts bees and larvae from \the [src] into \the [I]."), SPAN_NOTICE("You put bees and larvae from \the [src] into \the [I]."))
+ user.visible_message(
+ SPAN_NOTICE("[user] puts bees and larvae from [src] into [I]."),
+ SPAN_NOTICE("You put bees and larvae from [src] into [I].")
+ )
bee_count /= 2
B.fill()
update_icon()
- return
- else if(istype(I, /obj/item/device/scanner/plant))
- to_chat(user, SPAN_NOTICE("Scan result of \the [src]..."))
+ return TRUE
+
+ if (istype(I, /obj/item/device/scanner/plant))
+ to_chat(user, SPAN_NOTICE("Scan result of [src]..."))
to_chat(user, "Beehive is [bee_count ? "[round(bee_count)]% full" : "empty"].[bee_count > 90 ? " Colony is ready to split." : ""]")
if(frames)
to_chat(user, "[frames] frames installed, [round(honeycombs / 100)] filled.")
@@ -120,18 +144,9 @@
to_chat(user, "No frames installed.")
if(smoked)
to_chat(user, "The hive is smoked.")
- return 1
- else if(isScrewdriver(I))
- if(bee_count)
- to_chat(user, SPAN_NOTICE("You can't dismantle \the [src] with these bees inside."))
- return
- to_chat(user, SPAN_NOTICE("You start dismantling \the [src]..."))
- playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1)
- if (do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE))
- user.visible_message(SPAN_NOTICE("\The [user] dismantles \the [src]."), SPAN_NOTICE("You dismantle \the [src]."))
- new /obj/item/beehive_assembly(loc)
- qdel(src)
- return
+ return TRUE
+
+ . = ..()
/obj/machinery/beehive/physical_attack_hand(mob/user)
if(!closed)
@@ -142,7 +157,7 @@
if(!smoked && bee_count)
to_chat(user, SPAN_NOTICE("The bees won't let you take the honeycombs out like this, smoke them first."))
return
- user.visible_message(SPAN_NOTICE("\The [user] starts taking the honeycombs out of \the [src]."), SPAN_NOTICE("You start taking the honeycombs out of \the [src]..."))
+ user.visible_message(SPAN_NOTICE("[user] starts taking the honeycombs out of [src]."), SPAN_NOTICE("You start taking the honeycombs out of [src]..."))
while (honeycombs >= 100 && do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE))
new /obj/item/honey_frame/filled(loc)
honeycombs -= 100
@@ -172,7 +187,7 @@
/obj/machinery/honey_extractor
name = "honey extractor"
desc = "A machine used to extract honey and wax from a beehive frame."
- icon = 'icons/obj/virology.dmi'
+ icon = 'icons/obj/machines/research/virology.dmi'
icon_state = "centrifuge"
anchored = TRUE
density = TRUE
@@ -191,21 +206,20 @@
/obj/machinery/honey_extractor/cannot_transition_to(state_path, mob/user)
if(processing)
- return SPAN_NOTICE("You must wait for \the [src] to finish first!")
+ return SPAN_NOTICE("You must wait for [src] to finish first!")
return ..()
-/obj/machinery/honey_extractor/attackby(obj/item/I, mob/user)
+/obj/machinery/honey_extractor/use_tool(obj/item/I, mob/living/user, list/click_params)
if(processing)
- to_chat(user, SPAN_NOTICE("\The [src] is currently spinning, wait until it's finished."))
- return
- if((. = component_attackby(I, user)))
- return
+ to_chat(user, SPAN_NOTICE("[src] is currently spinning, wait until it's finished."))
+ return TRUE
+
if(istype(I, /obj/item/honey_frame))
var/obj/item/honey_frame/H = I
if(!H.honey)
- to_chat(user, SPAN_NOTICE("\The [H] is empty, put it into a beehive."))
- return
- user.visible_message(SPAN_NOTICE("\The [user] loads \the [H] into \the [src] and turns it on."), SPAN_NOTICE("You load \the [H] into \the [src] and turn it on."))
+ to_chat(user, SPAN_NOTICE("[H] is empty, put it into a beehive."))
+ return TRUE
+ user.visible_message(SPAN_NOTICE("[user] loads [H] into [src] and turns it on."), SPAN_NOTICE("You load [H] into [src] and turn it on."))
processing = H.honey
icon_state = "centrifuge_moving"
qdel(H)
@@ -215,21 +229,25 @@
honey += processing
processing = 0
icon_state = "centrifuge"
- else if(istype(I, /obj/item/reagent_containers/glass))
+ return TRUE
+
+ if (istype(I, /obj/item/reagent_containers/glass))
if(!honey)
- to_chat(user, SPAN_NOTICE("There is no honey in \the [src]."))
- return
+ to_chat(user, SPAN_NOTICE("There is no honey in [src]."))
+ return TRUE
var/obj/item/reagent_containers/glass/G = I
var/transferred = min(G.reagents.maximum_volume - G.reagents.total_volume, honey)
G.reagents.add_reagent(/datum/reagent/nutriment/honey, transferred)
honey -= transferred
- user.visible_message(SPAN_NOTICE("\The [user] collects honey from \the [src] into \the [G]."), SPAN_NOTICE("You collect [transferred] units of honey from \the [src] into \the [G]."))
- return 1
+ user.visible_message(SPAN_NOTICE("[user] collects honey from [src] into [G]."), SPAN_NOTICE("You collect [transferred] units of honey from [src] into [G]."))
+ return TRUE
+
+ return ..()
/obj/item/bee_smoker
name = "bee smoker"
desc = "A device used to calm down bees before harvesting honey."
- icon = 'icons/obj/batterer.dmi'
+ icon = 'icons/obj/tools/batterer.dmi'
icon_state = "batterer"
w_class = ITEM_SIZE_SMALL
@@ -249,18 +267,18 @@
/obj/item/honey_frame/filled/New()
..()
- overlays += "honeycomb"
+ AddOverlays("honeycomb")
/obj/item/beehive_assembly
name = "beehive assembly"
desc = "Contains everything you need to build a beehive."
- icon = 'icons/obj/apiary_bees_etc.dmi'
+ icon = 'icons/obj/beekeeping.dmi'
icon_state = "apiary"
/obj/item/beehive_assembly/attack_self(mob/user)
- to_chat(user, SPAN_NOTICE("You start assembling \the [src]..."))
+ to_chat(user, SPAN_NOTICE("You start assembling [src]..."))
if (do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE))
- user.visible_message(SPAN_NOTICE("\The [user] constructs a beehive."), SPAN_NOTICE("You construct a beehive."))
+ user.visible_message(SPAN_NOTICE("[user] constructs a beehive."), SPAN_NOTICE("You construct a beehive."))
new /obj/machinery/beehive(get_turf(user))
qdel(src)
@@ -288,21 +306,21 @@ var/global/list/datum/stack_recipe/wax_recipes = list(
/obj/item/bee_pack/New()
..()
- overlays += "beepack-full"
+ AddOverlays("beepack-full")
/obj/item/bee_pack/proc/empty()
full = 0
name = "empty bee pack"
desc = "A stasis pack for moving bees. It's empty."
- overlays.Cut()
- overlays += "beepack-empty"
+ ClearOverlays()
+ AddOverlays("beepack-empty")
/obj/item/bee_pack/proc/fill()
full = initial(full)
SetName(initial(name))
desc = initial(desc)
- overlays.Cut()
- overlays += "beepack-full"
+ ClearOverlays()
+ AddOverlays("beepack-full")
/obj/structure/closet/crate/hydroponics/beekeeping
name = "beekeeping crate"
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index 1212b4e8fa605..2da0cc57992a2 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -1,7 +1,7 @@
//Grown foods.
/obj/item/reagent_containers/food/snacks/grown
name = "fruit"
- icon = 'icons/obj/hydroponics_products.dmi'
+ icon = 'icons/obj/flora/hydroponics_products.dmi'
icon_state = "blank"
randpixel = 5
desc = "Nutritious! Probably."
@@ -134,13 +134,13 @@
/obj/item/reagent_containers/food/snacks/grown/on_update_icon()
if(!seed)
return
- overlays.Cut()
+ ClearOverlays()
icon_state = "[seed.get_trait(TRAIT_PRODUCT_ICON)]-product"
color = seed.get_trait(TRAIT_PRODUCT_COLOUR)
- if("[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf" in icon_states('icons/obj/hydroponics_products.dmi'))
- var/image/fruit_leaves = image('icons/obj/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf")
+ if(ICON_HAS_STATE('icons/obj/flora/hydroponics_products.dmi', "[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf"))
+ var/image/fruit_leaves = image('icons/obj/flora/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf")
fruit_leaves.color = seed.get_trait(TRAIT_PLANT_COLOUR)
- overlays |= fruit_leaves
+ AddOverlays(fruit_leaves)
/obj/item/reagent_containers/food/snacks/grown/Crossed(mob/living/M)
if(seed && seed.get_trait(TRAIT_JUICY) == 2)
@@ -169,14 +169,32 @@
/obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/W, mob/user)
if(seed)
- if(seed.get_trait(TRAIT_PRODUCES_POWER) && isCoil(W))
+ if(isCoil(W))
var/obj/item/stack/cable_coil/C = W
- if(C.use(5))
- //TODO: generalize this.
+ if(seed.get_trait(TRAIT_PRODUCT_ICON) in list("flower2","flower3","flower4","flower5","flower6"))
+ if(!C.can_use(1))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(C, 1, "to make a pin out of \the [src.name].")
+ return
+ C.use(1)
+ to_chat(user, SPAN_NOTICE("You add some wire to the [src.name] and make a pin."))
+ var/obj/item/clothing/head/hairflower/pin = new /obj/item/clothing/head/hairflower(get_turf(src))
+ pin.name = "[src.name] pin"
+ pin.icon = 'icons/obj/flora/hydroponics_products.dmi'
+ pin.icon_state = "[seed.get_trait(TRAIT_PRODUCT_ICON)]-product"
+ if(ICON_HAS_STATE('icons/obj/flora/hydroponics_products.dmi', "[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf"))
+ var/image/fruit_leaves = image('icons/obj/flora/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf")
+ fruit_leaves.color = seed.get_trait(TRAIT_PLANT_COLOUR)
+ pin.AddOverlays(fruit_leaves)
+ pin.item_state = "hairflower"
+ pin.color = src.color
+ qdel(src)
+ return
+ else if(seed.get_trait(TRAIT_PRODUCES_POWER))
+ if(!C.can_use(5))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(C, 5, "to wire \the [src.name].")
+ return
to_chat(user, SPAN_NOTICE("You add some cable to the [src.name] and slide it inside the battery casing."))
- var/obj/item/cell/potato/pocell = new /obj/item/cell/potato(get_turf(user))
- if(src.loc == user && user.HasFreeHand() && istype(user,/mob/living/carbon/human))
- user.put_in_hands(pocell)
+ var/obj/item/cell/potato/pocell = new /obj/item/cell/potato(get_turf(src))
pocell.maxcharge = src.potency * 10
pocell.charge = pocell.maxcharge
qdel(src)
@@ -271,7 +289,7 @@
continue
if(NG.amount>=NG.max_amount)
continue
- NG.attackby(G, user)
+ G.resolve_attackby(NG, user)
to_chat(user, "You add the newly-formed grass to the stack. It now contains [G.amount] tiles.")
qdel(src)
return
@@ -308,7 +326,7 @@
/obj/item/reagent_containers/food/snacks/fruit_slice
name = "fruit slice"
desc = "A slice of some tasty fruit."
- icon = 'icons/obj/hydroponics_misc.dmi'
+ icon = 'icons/obj/flora/hydroponics_misc.dmi'
icon_state = ""
var/global/list/fruit_icon_cache = list()
@@ -330,9 +348,9 @@ var/global/list/fruit_icon_cache = list()
var/image/I = image(icon,"fruit_rind")
I.color = rind_colour
fruit_icon_cache["rind-[rind_colour]"] = I
- overlays |= fruit_icon_cache["rind-[rind_colour]"]
+ AddOverlays(fruit_icon_cache["rind-[rind_colour]"])
if(!fruit_icon_cache["slice-[rind_colour]"])
var/image/I = image(icon,"fruit_slice")
I.color = flesh_colour
fruit_icon_cache["slice-[rind_colour]"] = I
- overlays |= fruit_icon_cache["slice-[rind_colour]"]
+ AddOverlays(fruit_icon_cache["slice-[rind_colour]"])
diff --git a/code/modules/hydroponics/grown_inedible.dm b/code/modules/hydroponics/grown_inedible.dm
index 661ededc08b1e..1d1c588b342cd 100644
--- a/code/modules/hydroponics/grown_inedible.dm
+++ b/code/modules/hydroponics/grown_inedible.dm
@@ -5,7 +5,7 @@
/obj/item/bananapeel
name = "banana peel"
desc = "A peel from a banana."
- icon = 'icons/obj/items.dmi'
+ icon = 'icons/obj/flora/hydroponics_products.dmi'
icon_state = "banana_peel"
item_state = "banana_peel"
w_class = ITEM_SIZE_SMALL
diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm
index fc96e90aa53fe..977c6cd465718 100644
--- a/code/modules/hydroponics/seed.dm
+++ b/code/modules/hydroponics/seed.dm
@@ -21,7 +21,7 @@
var/list/exude_gasses // The plant will exude these gasses during its life.
var/kitchen_tag // Used by the reagent grinder.
var/trash_type // Garbage item produced when eaten.
- var/splat_type = /obj/effect/decal/cleanable/fruit_smudge // Graffiti decal
+ var/splat_type = /obj/decal/cleanable/fruit_smudge // Graffiti decal
/// Used when product isn't under /food/snacks/grown (e.g. simplemobs, aquaculture).
var/product_type
/// If mob, whether players can possess
@@ -102,7 +102,7 @@
var/injecting = min(5,max(1,get_trait(TRAIT_POTENCY)/3))
R.add_reagent(rid,injecting)
- var/datum/effect/effect/system/smoke_spread/chem/spores/S = new(name)
+ var/datum/effect/smoke_spread/chem/spores/S = new(name)
S.attach(T)
S.set_up(R, round(get_trait(TRAIT_POTENCY)/4), 0, T)
S.start()
@@ -196,15 +196,15 @@
//Splatter a turf.
/datum/seed/proc/splatter(turf/T,obj/item/thrown)
- if(splat_type && !(locate(/obj/effect/vine) in T))
- var/obj/effect/vine/splat = new splat_type(T, src)
+ if(splat_type && !(locate(/obj/vine) in T))
+ var/obj/vine/splat = new splat_type(T, src)
if(!istype(splat)) // Plants handle their own stuff.
splat.SetName("[thrown.name] [pick("smear","smudge","splatter")]")
if(get_trait(TRAIT_BIOLUM))
var/clr
if(get_trait(TRAIT_BIOLUM_COLOUR))
clr = get_trait(TRAIT_BIOLUM_COLOUR)
- splat.set_light(0.5, 0.1, 3, l_color = clr)
+ splat.set_light(3, 0.5, l_color = clr)
var/flesh_colour = get_trait(TRAIT_FLESH_COLOUR)
if(!flesh_colour) flesh_colour = get_trait(TRAIT_PRODUCT_COLOUR)
if(flesh_colour) splat.color = get_trait(TRAIT_PRODUCT_COLOUR)
@@ -303,7 +303,7 @@
var/missing_gas = 0
for(var/gas in consume_gasses)
if(environment && environment.gas && environment.gas[gas] && \
- environment.gas[gas] >= consume_gasses[gas])
+ environment.gas[gas] >= consume_gasses[gas])
if(!check_only)
environment.adjust_gas(gas,-consume_gasses[gas],1)
else
@@ -343,7 +343,7 @@
if(abs(light_supplied - get_trait(TRAIT_IDEAL_LIGHT)) > get_trait(TRAIT_LIGHT_TOLERANCE))
health_change += rand(1,3) * HYDRO_SPEED_MULTIPLIER
- for(var/obj/effect/effect/smoke/chem/smoke in range(1, current_turf))
+ for(var/obj/effect/smoke/chem/smoke in range(1, current_turf))
if(smoke.reagents.has_reagent(/datum/reagent/toxin/plantbgone))
return 100
@@ -370,10 +370,10 @@
var/turf/T = get_random_turf_in_range(target, outer_teleport_radius, inner_teleport_radius)
if(T)
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ var/datum/effect/spark_spread/s = new /datum/effect/spark_spread
s.set_up(3, 1, get_turf(target))
s.start()
- new/obj/effect/decal/cleanable/molten_item(get_turf(target)) // Leave a pile of goo behind for dramatic effect...
+ new/obj/decal/cleanable/molten_item(get_turf(target)) // Leave a pile of goo behind for dramatic effect...
target.forceMove(T) // And teleport them to the chosen location.
impact = 1
@@ -481,7 +481,7 @@
/datum/reagent/nanites,
/datum/reagent/water/holywater,
/datum/reagent/toxin/plantbgone,
- /datum/reagent/chloralhydrate/beer2,
+ /datum/reagent/chloralhydrate/beer,
/datum/reagent/zombie
)
banned_chems += subtypesof(/datum/reagent/ethanol)
@@ -787,7 +787,7 @@
var/clr
if(get_trait(TRAIT_BIOLUM_COLOUR))
clr = get_trait(TRAIT_BIOLUM_COLOUR)
- product.set_light(0.5, 0.1, 3, l_color = clr)
+ product.set_light(3, 0.5, l_color = clr)
//Handle spawning in living, mobile products (like dionaea).
if(istype(product,/mob/living))
@@ -851,19 +851,19 @@
/datum/seed/proc/get_icon(growth_stage)
var/plant_icon = get_trait(TRAIT_PLANT_ICON)
- var/image/res = image('icons/obj/hydroponics_growing.dmi', "[plant_icon]-[growth_stage]")
+ var/image/res = image('icons/obj/flora/hydroponics_growing.dmi', "[plant_icon]-[growth_stage]")
if(get_growth_type())
res.icon_state = "[get_growth_type()]-[growth_stage]"
else
res.icon_state = "[plant_icon]-[growth_stage]"
if(get_growth_type())
- res.icon = 'icons/obj/hydroponics_vines.dmi'
+ res.icon = 'icons/obj/flora/hydroponics_vines.dmi'
res.color = get_trait(TRAIT_PLANT_COLOUR)
if(get_trait(TRAIT_LARGE))
- res.icon = 'icons/obj/hydroponics_large.dmi'
+ res.icon = 'icons/obj/flora/hydroponics_large.dmi'
res.pixel_x = -8
res.pixel_y = -16
@@ -872,6 +872,6 @@
var/image/I = image(res.icon, "[plant_icon]-[growth_stage]-leaves")
I.color = leaves
I.appearance_flags = DEFAULT_APPEARANCE_FLAGS | RESET_COLOR
- res.overlays += I
+ res.AddOverlays(I)
return res
diff --git a/code/modules/hydroponics/seed_datums.dm b/code/modules/hydroponics/seed_datums.dm
index b8c3e8c66736a..871ab4f9d5b3b 100644
--- a/code/modules/hydroponics/seed_datums.dm
+++ b/code/modules/hydroponics/seed_datums.dm
@@ -190,7 +190,7 @@
display_name = "blood tomato plant"
mutants = list("killer")
chems = list(/datum/reagent/nutriment = list(1,10), /datum/reagent/blood = list(1,5))
- splat_type = /obj/effect/decal/cleanable/blood/splatter
+ splat_type = /obj/decal/cleanable/blood/splatter
/datum/seed/tomato/blood/New()
..()
@@ -345,7 +345,7 @@
display_name = "chanterelle cluster"
mutants = list("reishi","amanita","plumphelmet")
chems = list(/datum/reagent/nutriment = list(1,25))
- splat_type = /obj/effect/vine
+ splat_type = /obj/vine
kitchen_tag = "mushroom"
/datum/seed/mushroom/New()
@@ -649,6 +649,7 @@
seed_name = "peanut"
display_name = "peanut plant"
chems = list(/datum/reagent/nutriment = list(1,10))
+ kitchen_tag = "peanut"
/datum/seed/peanuts/New()
..()
@@ -1278,7 +1279,7 @@
set_trait(TRAIT_PRODUCTION,5)
set_trait(TRAIT_YIELD,4)
set_trait(TRAIT_POTENCY,10)
- set_trait(TRAIT_PRODUCT_ICON,"algae")
+ set_trait(TRAIT_PRODUCT_ICON,"grass2")
set_trait(TRAIT_PRODUCT_COLOUR,"#84bd82")
set_trait(TRAIT_PLANT_COLOUR,"#6d9c6b")
set_trait(TRAIT_PLANT_ICON,"algae")
@@ -1547,7 +1548,7 @@
set_trait(TRAIT_PRODUCTION,5)
set_trait(TRAIT_YIELD,4)
set_trait(TRAIT_POTENCY,10)
- set_trait(TRAIT_PRODUCT_ICON,"algae")
+ set_trait(TRAIT_PRODUCT_ICON,"vine3")
set_trait(TRAIT_PRODUCT_COLOUR,"#e93e1c")
set_trait(TRAIT_PLANT_COLOUR,"#6d9c6b")
set_trait(TRAIT_PLANT_ICON,"algae")
@@ -1581,7 +1582,7 @@
seed_noun = SEED_NOUN_SPORES
display_name = "o'krri cluster"
chems = list(/datum/reagent/nutriment = list(1,25), /datum/reagent/drugs/psilocybin = list(1,3))
- splat_type = /obj/effect/vine
+ splat_type = /obj/vine
kitchen_tag = "mushroom"
/datum/seed/okrri/New()
@@ -1600,7 +1601,7 @@
/datum/seed/ximikoa
name = "ximikoa"
- seed_name = "ximi'koa stalks"
+ seed_name = "ximi'koa"
display_name = "ximi'koa patch"
chems = list(/datum/reagent/nutriment = list(1,2), /datum/reagent/sugar = list(4,5))
fruit_size = ITEM_SIZE_TINY
@@ -1816,4 +1817,23 @@
set_trait(TRAIT_YIELD,3)
set_trait(TRAIT_IDEAL_LIGHT,5)
set_trait(TRAIT_PRODUCT_COLOUR,"#ef5f32")
- set_trait(TRAIT_PLANT_COLOUR,"#ef5f32")
\ No newline at end of file
+ set_trait(TRAIT_PLANT_COLOUR,"#ef5f32")
+
+/datum/seed/almond
+ name = "almond"
+ seed_name = "almond"
+ display_name = "almond plant"
+ chems = list(/datum/reagent/nutriment = list(1,10))
+ kitchen_tag = "almond"
+
+/datum/seed/almond/New()
+ ..()
+ set_trait(TRAIT_HARVEST_REPEAT,1)
+ set_trait(TRAIT_MATURATION,6)
+ set_trait(TRAIT_PRODUCTION,6)
+ set_trait(TRAIT_YIELD,6)
+ set_trait(TRAIT_POTENCY,10)
+ set_trait(TRAIT_PRODUCT_ICON,"nuts")
+ set_trait(TRAIT_PRODUCT_COLOUR,"#eddcc8")
+ set_trait(TRAIT_PLANT_ICON,"bush2")
+ set_trait(TRAIT_IDEAL_LIGHT, 6)
diff --git a/code/modules/hydroponics/seed_machines.dm b/code/modules/hydroponics/seed_machines.dm
index 36b6091e32c5f..40d33c3bc7a08 100644
--- a/code/modules/hydroponics/seed_machines.dm
+++ b/code/modules/hydroponics/seed_machines.dm
@@ -1,7 +1,7 @@
/obj/item/disk/botany
name = "flora data disk"
desc = "A small disk used for carrying data on plant genetics."
- icon = 'icons/obj/hydroponics_machines.dmi'
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
icon_state = "disk"
w_class = ITEM_SIZE_TINY
@@ -24,7 +24,7 @@
startswith = list(/obj/item/disk/botany = 14)
/obj/machinery/botany
- icon = 'icons/obj/hydroponics_machines.dmi'
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
icon_state = "hydrotray3"
density = TRUE
anchored = TRUE
@@ -65,51 +65,56 @@
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and spits out [loaded_disk].")
loaded_disk = null
-/obj/machinery/botany/attackby(obj/item/W as obj, mob/user as mob)
+/obj/machinery/botany/crowbar_act(mob/living/user, obj/item/tool)
+ if(!open)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ dismantle()
+
+/obj/machinery/botany/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ open = !open
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, open)
+
+/obj/machinery/botany/use_tool(obj/item/W, mob/living/user, list/click_params)
if(istype(W,/obj/item/seeds))
if(seed)
to_chat(user, "There is already a seed loaded.")
- return
+ return TRUE
var/obj/item/seeds/S =W
if(S.seed && S.seed.get_trait(TRAIT_IMMUTABLE) > 0)
to_chat(user, "That seed is not compatible with our genetics technology.")
else if(user.unEquip(W, src))
seed = W
to_chat(user, "You load [W] into [src].")
- return
-
- if(isScrewdriver(W))
- open = !open
- to_chat(user, SPAN_NOTICE("You [open ? "open" : "close"] the maintenance panel."))
- return
-
- if(open)
- if(isCrowbar(W))
- dismantle()
- return
+ return TRUE
if(istype(W,/obj/item/disk/botany))
if(loaded_disk)
to_chat(user, "There is already a data disk loaded.")
- return
+ return TRUE
+
+ var/obj/item/disk/botany/B = W
+ if (B.genes && length(B.genes))
+ if (!disk_needs_genes)
+ to_chat(user, "That disk already has gene data loaded.")
+ return TRUE
else
- var/obj/item/disk/botany/B = W
+ if(disk_needs_genes)
+ to_chat(user, "That disk does not have any gene data loaded.")
+ return TRUE
- if(B.genes && length(B.genes))
- if(!disk_needs_genes)
- to_chat(user, "That disk already has gene data loaded.")
- return
- else
- if(disk_needs_genes)
- to_chat(user, "That disk does not have any gene data loaded.")
- return
- if(!user.unEquip(W, src))
- return
- loaded_disk = W
- to_chat(user, "You load [W] into [src].")
+ if(!user.unEquip(W, src))
+ return TRUE
+ loaded_disk = W
+ to_chat(user, "You load \the [W] into \the [src].")
+ return TRUE
- return
- ..()
+ return ..()
// Allows for a trait to be extracted from a seed packet, destroying that seed.
/obj/machinery/botany/extractor
@@ -203,7 +208,7 @@
active = 1
if(seed && seed.seed)
- if(prob(user.skill_fail_chance(SKILL_BOTANY, 100, SKILL_ADEPT)))
+ if(prob(user.skill_fail_chance(SKILL_BOTANY, 100, SKILL_TRAINED)))
failed_task = 1
else
genetics = seed.seed
@@ -231,8 +236,8 @@
loaded_disk.desc += " The label reads \'gene [SSplants.gene_tag_masks[href_list["get_gene"]]], sampled from [genetics.display_name]\'."
eject_disk = 1
- degradation += rand(20,60) + user.skill_fail_chance(SKILL_BOTANY, 100, SKILL_ADEPT)
- var/expertise = max(0, user.get_skill_value(SKILL_BOTANY) - SKILL_ADEPT)
+ degradation += rand(20,60) + user.skill_fail_chance(SKILL_BOTANY, 100, SKILL_TRAINED)
+ var/expertise = max(0, user.get_skill_value(SKILL_BOTANY) - SKILL_TRAINED)
degradation = max(0, degradation - 10*expertise)
if(degradation >= 100)
@@ -318,7 +323,7 @@
for(var/datum/plantgene/gene in loaded_disk.genes)
seed.seed.apply_gene(gene)
- var/expertise = max(user.get_skill_value(SKILL_BOTANY) - SKILL_ADEPT)
+ var/expertise = max(user.get_skill_value(SKILL_BOTANY) - SKILL_TRAINED)
seed.modified += rand(5,10) + min(-5, 30 * expertise)
usr.set_machine(src)
diff --git a/code/modules/hydroponics/seed_packets.dm b/code/modules/hydroponics/seed_packets.dm
index 399ce22306fec..81c507f172ce7 100644
--- a/code/modules/hydroponics/seed_packets.dm
+++ b/code/modules/hydroponics/seed_packets.dm
@@ -1,39 +1,42 @@
-var/global/list/plant_seed_sprites = list()
-
//Seed packet object/procs.
/obj/item/seeds
name = "packet of seeds"
- icon = 'icons/obj/seeds.dmi'
+ icon = 'icons/obj/flora/seeds.dmi'
icon_state = "seedy"
w_class = ITEM_SIZE_SMALL
var/seed_type
- var/datum/seed/seed
var/modified = 0
+ var/datum/seed/seed
+ var/static/list/plant_seed_sprites = list()
/obj/item/seeds/Initialize()
- update_seed()
. = ..()
+ update_seed()
//Grabs the appropriate seed datum from the global list.
/obj/item/seeds/proc/update_seed()
- if(!seed && seed_type && !isnull(SSplants.seeds) && SSplants.seeds[seed_type])
- seed = SSplants.seeds[seed_type]
+ if(!seed && seed_type && length(SSplants.seeds))
+ var/datum/seed/existing_seed = SSplants.seeds[seed_type]
+ if(existing_seed)
+ seed = existing_seed
+
update_appearance()
//Updates strings and icon appropriately based on seed datum.
/obj/item/seeds/proc/update_appearance()
- if(!seed) return
+ if(!seed)
+ return
// Update icon.
- overlays.Cut()
+ ClearOverlays()
var/is_seeds = ((seed.seed_noun in list(SEED_NOUN_SEEDS, SEED_NOUN_PITS, SEED_NOUN_NODES)) ? 1 : 0)
var/image/seed_mask
var/seed_base_key = "base-[is_seeds ? seed.get_trait(TRAIT_PLANT_COLOUR) : "spores"]"
if(plant_seed_sprites[seed_base_key])
seed_mask = plant_seed_sprites[seed_base_key]
else
- seed_mask = image('icons/obj/seeds.dmi',"[is_seeds ? "seed" : "spore"]-mask")
+ seed_mask = image('icons/obj/flora/seeds.dmi',"[is_seeds ? "seed" : "spore"]-mask")
if(is_seeds) // Spore glass bits aren't coloured.
seed_mask.color = seed.get_trait(TRAIT_PLANT_COLOUR)
plant_seed_sprites[seed_base_key] = seed_mask
@@ -43,24 +46,24 @@ var/global/list/plant_seed_sprites = list()
if(plant_seed_sprites[seed_overlay_key])
seed_overlay = plant_seed_sprites[seed_overlay_key]
else
- seed_overlay = image('icons/obj/seeds.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]")
+ seed_overlay = image('icons/obj/flora/seeds.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]")
seed_overlay.color = seed.get_trait(TRAIT_PRODUCT_COLOUR)
plant_seed_sprites[seed_overlay_key] = seed_overlay
- overlays |= seed_mask
- overlays |= seed_overlay
+ AddOverlays(seed_mask)
+ AddOverlays(seed_overlay)
if(is_seeds)
- src.SetName("packet of [seed.seed_name] [seed.seed_noun]")
- src.desc = "It has a picture of \a [seed.display_name] on the front."
+ SetName("packet of [seed.seed_name] [seed.seed_noun]")
+ desc = "It has a picture of \a [seed.display_name] on the front."
else
- src.SetName("sample of [seed.seed_name] [seed.seed_noun]")
- src.desc = "It's labelled as coming from \a [seed.seed_name]."
+ SetName("sample of [seed.seed_name] [seed.seed_noun]")
+ desc = "It's labelled as coming from \a [seed.seed_name]."
/obj/item/seeds/examine(mob/user)
. = ..()
if(seed && !seed.roundstart)
- to_chat(user, "It's tagged as variety #[seed.uid].")
+ . += SPAN_NOTICE("It's tagged as variety #[seed.uid].")
/obj/item/seeds/cutting
name = "cuttings"
@@ -68,7 +71,7 @@ var/global/list/plant_seed_sprites = list()
/obj/item/seeds/cutting/update_appearance()
..()
- src.SetName("packet of [seed.seed_name] cuttings")
+ SetName("packet of [seed.seed_name] cuttings")
/obj/item/seeds/random
seed_type = null
@@ -291,9 +294,11 @@ var/global/list/plant_seed_sprites = list()
/obj/item/seeds/bamboo
seed_type = "bamboo"
-/obj/item/seeds/breather/seed_type = "breather"
+/obj/item/seeds/breather
+ seed_type = "breather"
-/obj/item/seeds/resin/seed_type = "resinplant"
+/obj/item/seeds/resin
+ seed_type = "resinplant"
// fruit expansion
@@ -367,4 +372,7 @@ var/global/list/plant_seed_sprites = list()
seed_type = "shrimp"
/obj/item/seeds/crab
- seed_type = "crab"
\ No newline at end of file
+ seed_type = "crab"
+
+/obj/item/seeds/almondseed
+ seed_type = "almond"
diff --git a/code/modules/hydroponics/seed_storage.dm b/code/modules/hydroponics/seed_storage.dm
index 58f609390b13c..bbb1396061b90 100644
--- a/code/modules/hydroponics/seed_storage.dm
+++ b/code/modules/hydroponics/seed_storage.dm
@@ -20,17 +20,19 @@
/obj/machinery/seed_storage
name = "Seed storage"
desc = "It stores, sorts, and dispenses seeds."
- icon = 'icons/obj/vending.dmi'
+ icon = 'icons/obj/machines/vending.dmi'
icon_state = "seeds"
density = TRUE
anchored = TRUE
idle_power_usage = 100
+ obj_flags = OBJ_FLAG_ANCHORABLE
var/list/datum/seed_pile/piles = list()
var/list/starting_seeds = list(
/obj/item/seeds/affelerin = 15,
/obj/item/seeds/aghrassh = 15,
/obj/item/seeds/algaeseed = 15,
+ /obj/item/seeds/almondseed = 15,
/obj/item/seeds/ambrosiavulgarisseed = 15,
/obj/item/seeds/appleseed = 15,
/obj/item/seeds/bamboo = 15,
@@ -292,13 +294,14 @@
break
updateUsrDialog()
-/obj/machinery/seed_storage/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/seed_storage/use_tool(obj/item/O, mob/living/user, list/click_params)
if (istype(O, /obj/item/seeds))
add(O)
sort_piles()
user.visible_message("[user] puts \the [O.name] into \the [src].", "You put \the [O] into \the [src].")
- return
- else if (istype(O, /obj/item/storage/plants))
+ return TRUE
+
+ if (istype(O, /obj/item/storage/plants))
var/obj/item/storage/P = O
var/loaded = 0
for(var/obj/item/seeds/G in P.contents)
@@ -311,11 +314,8 @@
user.visible_message("[user] puts the seeds from \the [O.name] into \the [src].", "You put the seeds from \the [O.name] into \the [src].")
else
to_chat(user, SPAN_NOTICE("There are no seeds in \the [O.name]."))
- return
- else if(isWrench(O))
- playsound(loc, 'sound/items/Ratchet.ogg', 50, 1)
- anchored = !anchored
- to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].")
+ return TRUE
+ return ..()
/obj/machinery/seed_storage/proc/add(obj/item/seeds/O, bypass_removal = 0)
if(!bypass_removal)
diff --git a/code/modules/hydroponics/spreading/spreading.dm b/code/modules/hydroponics/spreading/spreading.dm
index 40f45791d688d..f52427bb3d060 100644
--- a/code/modules/hydroponics/spreading/spreading.dm
+++ b/code/modules/hydroponics/spreading/spreading.dm
@@ -3,7 +3,7 @@
/proc/spacevine_infestation(potency_min=70, potency_max=100, maturation_min=5, maturation_max=15)
spawn() //to stop the secrets panel hanging
- var/turf/T = pick_subarea_turf(/area/hallway , list(/proc/is_station_turf, /proc/not_turf_contains_dense_objects))
+ var/turf/T = pick_subarea_turf(/area/hallway , list(GLOBAL_PROC_REF(is_station_turf), GLOBAL_PROC_REF(not_turf_contains_dense_objects)))
if(T)
var/datum/seed/seed = SSplants.create_random_seed(1)
seed.set_trait(TRAIT_SPREAD,2) // So it will function properly as vines.
@@ -16,29 +16,36 @@
seed.display_name = "strange plants" //more thematic for the vine infestation event
//make vine zero start off fully matured
- new /obj/effect/vine(T,seed, start_matured = 1)
+ new /obj/vine(T,seed, start_matured = 1)
log_and_message_admins("Spacevines spawned in \the [get_area(T)]", location = T)
return
log_and_message_admins(SPAN_NOTICE("Event: Spacevines failed to find a viable turf."))
-/obj/effect/dead_plant
+/obj/dead_plant
anchored = TRUE
opacity = 0
density = FALSE
color = DEAD_PLANT_COLOUR
-/obj/effect/dead_plant/attack_hand()
+/obj/dead_plant/attack_hand()
qdel(src)
-/obj/effect/dead_plant/attackby()
- ..()
+
+/obj/dead_plant/use_tool(obj/item/weapon, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE)
+ user.visible_message(
+ SPAN_WARNING("\The [user] hits \the [src] with \a [weapon], and it falls to pieces!"),
+ SPAN_WARNING("You hit \the [src] with \the [weapon], and it falls to pieces!")
+ )
qdel(src)
+ return TRUE
+
-/obj/effect/vine
+/obj/vine
name = "vine"
anchored = TRUE
- icon = 'icons/obj/hydroponics_growing.dmi'
+ icon = 'icons/obj/flora/hydroponics_growing.dmi'
icon_state = ""
pass_flags = PASS_FLAG_TABLE
buckle_sound = null
@@ -46,7 +53,7 @@
var/growth_threshold = 0
var/growth_type = 0
var/max_growth = 0
- var/obj/effect/vine/parent
+ var/obj/vine/parent
var/datum/seed/seed
var/floor = 0
var/possible_children = 20
@@ -56,10 +63,10 @@
var/mature_time //minimum maturation time
var/obj/machinery/portable_atmospherics/hydroponics/soil/invisible/plant
-/obj/effect/vine/single
+/obj/vine/single
spread_chance = 0
-/obj/effect/vine/New(newloc, datum/seed/newseed, obj/effect/vine/newparent, start_matured = 0)
+/obj/vine/New(newloc, datum/seed/newseed, obj/vine/newparent, start_matured = 0)
if(!newparent)
parent = src
else
@@ -70,7 +77,7 @@
mature_time = 0
..()
-/obj/effect/vine/Initialize(mapload, datum/seed/newseed, obj/effect/vine/newparent, start_matured = 0)
+/obj/vine/Initialize(mapload, datum/seed/newseed, obj/vine/newparent, start_matured = 0)
. = ..()
if(!SSplants)
@@ -105,13 +112,13 @@
START_PROCESSING(SSvines, src)
-/obj/effect/vine/Destroy()
+/obj/vine/Destroy()
wake_neighbors()
STOP_PROCESSING(SSvines, src)
return ..()
-/obj/effect/vine/on_update_icon()
- overlays.Cut()
+/obj/vine/on_update_icon()
+ ClearOverlays()
var/growth = growth_threshold ? min(max_growth, round(get_current_health() / growth_threshold)) : 1
var/at_fringe = get_dist(src,parent)
if(spread_distance > 5)
@@ -125,7 +132,7 @@
var/ikey = "\ref[seed]-plant-[growth]"
if(!SSplants.plant_icon_cache[ikey])
SSplants.plant_icon_cache[ikey] = seed.get_icon(growth)
- overlays += SSplants.plant_icon_cache[ikey]
+ AddOverlays(SSplants.plant_icon_cache[ikey])
if(growth > 2 && growth == max_growth)
layer = (seed && seed.force_layer) ? seed.force_layer : ABOVE_OBJ_LAYER
@@ -150,11 +157,11 @@
// Apply colour and light from seed datum.
if(seed.get_trait(TRAIT_BIOLUM))
- set_light(0.5, 0.1, 3, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
+ set_light(3, 0.5, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
else
set_light(0)
-/obj/effect/vine/proc/calc_dir()
+/obj/vine/proc/calc_dir()
set background = 1
var/turf/T = get_turf(src)
if(!istype(T)) return
@@ -166,7 +173,7 @@
if(newTurf && newTurf.density)
direction |= wallDir
- for(var/obj/effect/vine/shroom in T.contents)
+ for(var/obj/vine/shroom in T.contents)
if(shroom == src)
continue
if(shroom.floor) //special
@@ -190,43 +197,64 @@
floor = 1
return 1
-/obj/effect/vine/attackby(obj/item/W, mob/user)
- START_PROCESSING(SSvines, src)
+/obj/vine/can_use_item(obj/item/tool, mob/living/user, click_params)
+ . = ..()
+ if(.)
+ START_PROCESSING(SSvines, src)
- if(W.edge && W.w_class < ITEM_SIZE_NORMAL && user.a_intent != I_HURT)
- if(!is_mature())
- to_chat(user, SPAN_WARNING("\The [src] is not mature enough to yield a sample yet."))
+
+/obj/vine/use_weapon(obj/item/weapon, mob/user, list/click_params)
+ // Edged Items - Chop down vine
+ if (has_edge(weapon))
+ if (weapon.w_class < ITEM_SIZE_NORMAL)
+ USE_FEEDBACK_FAILURE("\The [weapon] is too small to chop down \the [src].")
return TRUE
- if(!seed)
- to_chat(user, SPAN_WARNING("There is nothing to take a sample from."))
+ user.visible_message(
+ SPAN_WARNING("\The [user] starts chopping down \the [src] with \a [weapon]!"),
+ SPAN_WARNING("You start chopping down \the [src] with \the [weapon]!")
+ )
+ playsound(src, weapon.hitsound, 50, TRUE)
+ var/chop_time = (get_current_health() / weapon.force) * 0.5 SECONDS
+ if (user.skill_check(SKILL_BOTANY, SKILL_TRAINED))
+ chop_time *= 0.5
+ if (!do_after(user, round(chop_time), src, DO_PUBLIC_UNIQUE) || !user.use_sanity_check(src, weapon))
return TRUE
- var/needed_skill = seed.mysterious ? SKILL_ADEPT : SKILL_BASIC
- if(prob(user.skill_fail_chance(SKILL_BOTANY, 90, needed_skill)))
- to_chat(user, SPAN_WARNING("You failed to get a usable sample."))
- else
- seed.harvest(user,0,1)
- damage_health(rand(15, 25), W.damtype)
+ user.visible_message(
+ SPAN_WARNING("\The [user] chops down \the [src] with \a [weapon]!"),
+ SPAN_WARNING("You chop down \the [src] with \the [weapon]!")
+ )
+ playsound(src, weapon.hitsound, 50, TRUE)
+ kill_health()
+ return TRUE
+
+ return ..()
+
+
+/obj/vine/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Edged Items - Take sample
+ if (has_edge(tool))
+ if (tool.w_class < ITEM_SIZE_NORMAL)
+ USE_FEEDBACK_FAILURE("\The [tool] is too small to cut a sample from \the [src].")
+ return TRUE
+ if (!is_mature())
+ USE_FEEDBACK_FAILURE("\The [src] is not mature enough to yield a sample yet.")
+ return TRUE
+ if (!seed)
+ USE_FEEDBACK_FAILURE("There is nothing on \the [src] to take a sample from.")
+ return TRUE
+ var/needed_skill = seed.mysterious ? SKILL_TRAINED : SKILL_BASIC
+ if (prob(user.skill_fail_chance(SKILL_BOTANY, 90, needed_skill)))
+ USE_FEEDBACK_FAILURE("You failed to get a usable sample from \the [src], and damage it in the process.")
+ damage_health(rand(15, 25), tool.damtype)
+ return TRUE
+ seed.harvest(user, 0, 1)
return TRUE
return ..()
-/obj/effect/vine/AltClick(mob/user)
- if(!CanPhysicallyInteract(user) || user.incapacitated())
- return ..()
- var/obj/item/W = user.get_active_hand()
- if(istype(W) && W.edge && W.w_class >= ITEM_SIZE_NORMAL)
- visible_message(SPAN_NOTICE("[user] starts chopping down \the [src]."))
- playsound(, W.hitsound, 100, 1)
- var/chop_time = (get_current_health() / W.force) * 0.5 SECONDS
- if(user.skill_check(SKILL_BOTANY, SKILL_ADEPT))
- chop_time *= 0.5
- if (do_after(user, chop_time, src, DO_PUBLIC_UNIQUE))
- visible_message(SPAN_NOTICE("[user] chops down \the [src]."))
- playsound(get_turf(src), W.hitsound, 100, 1)
- kill_health()
//handles being overrun by vines - note that attacker_parent may be null in some cases
-/obj/effect/vine/proc/vine_overrun(datum/seed/attacker_seed, obj/effect/vine/attacker_parent)
+/obj/vine/proc/vine_overrun(datum/seed/attacker_seed, obj/vine/attacker_parent)
var/aggression = 0
aggression += (attacker_seed.get_trait(TRAIT_CARNIVOROUS) - seed.get_trait(TRAIT_CARNIVOROUS))
aggression += (attacker_seed.get_trait(TRAIT_SPREAD) - seed.get_trait(TRAIT_SPREAD))
@@ -250,18 +278,18 @@
if(aggression > 0)
damage_health(aggression * 5)
-/obj/effect/vine/on_death()
+/obj/vine/on_death()
if(plant)
plant.die()
wake_neighbors()
qdel(src)
-/obj/effect/vine/post_health_change(health_mod, prior_health, damage_type)
+/obj/vine/post_health_change(health_mod, prior_health, damage_type)
..()
queue_icon_update()
-/obj/effect/vine/proc/is_mature()
+/obj/vine/proc/is_mature()
return (get_damage_percentage() < 66 && world.time > mature_time)
-/obj/effect/vine/is_burnable()
+/obj/vine/is_burnable()
return seed.get_trait(TRAIT_HEAT_TOLERANCE) < 1000
diff --git a/code/modules/hydroponics/spreading/spreading_growth.dm b/code/modules/hydroponics/spreading/spreading_growth.dm
index 9302ad3092a72..ac15b691e8daa 100644
--- a/code/modules/hydroponics/spreading/spreading_growth.dm
+++ b/code/modules/hydroponics/spreading/spreading_growth.dm
@@ -1,6 +1,6 @@
#define NEIGHBOR_REFRESH_TIME 100
-/obj/effect/vine/proc/get_cardinal_neighbors()
+/obj/vine/proc/get_cardinal_neighbors()
var/list/cardinal_neighbors = list()
for(var/check_dir in GLOB.cardinal)
var/turf/simulated/T = get_step(get_turf(src), check_dir)
@@ -8,7 +8,7 @@
cardinal_neighbors |= T
return cardinal_neighbors
-/obj/effect/vine/proc/get_zlevel_neighbors()
+/obj/vine/proc/get_zlevel_neighbors()
var/list/zlevel_neighbors = list()
var/turf/start = loc
@@ -22,7 +22,7 @@
return zlevel_neighbors
-/obj/effect/vine/proc/get_neighbors()
+/obj/vine/proc/get_neighbors()
var/list/neighbors = list()
for(var/turf/simulated/floor in get_cardinal_neighbors())
@@ -30,7 +30,7 @@
continue
var/blocked = 0
- for(var/obj/effect/vine/other in floor.contents)
+ for(var/obj/vine/other in floor.contents)
if(other.seed == src.seed)
blocked = 1
break
@@ -50,18 +50,18 @@
neighbors |= get_zlevel_neighbors()
return neighbors
-/obj/effect/vine/Process()
+/obj/vine/Process()
var/turf/simulated/T = get_turf(src)
if(!istype(T))
return
//Take damage from bad environment if any
damage_health(seed.handle_environment(T, T.return_air(), null, 1))
- if(health_dead)
+ if(health_dead())
return
//Vine fight!
- for(var/obj/effect/vine/other in T)
+ for(var/obj/vine/other in T)
if(other.seed != seed)
other.vine_overrun(seed, src)
@@ -105,11 +105,11 @@
if(should_sleep())
STOP_PROCESSING(SSvines, src)
-/obj/effect/vine/proc/can_spawn_plant()
+/obj/vine/proc/can_spawn_plant()
var/turf/simulated/T = get_turf(src)
return parent == src && !health_damaged() && !plant && istype(T) && !T.CanZPass(src, DOWN)
-/obj/effect/vine/proc/should_sleep()
+/obj/vine/proc/should_sleep()
if(buckled_mob) //got a victim to fondle
return FALSE
if(length(get_neighbors())) //got places to spread to
@@ -124,8 +124,8 @@
//spreading vines aren't created on their final turf.
//Instead, they are created at their parent and then move to their destination.
-/obj/effect/vine/proc/spread_to(turf/target_turf)
- var/obj/effect/vine/child = new(get_turf(src),seed,parent) // This should do a little bit of animation.
+/obj/vine/proc/spread_to(turf/target_turf)
+ var/obj/vine/child = new(get_turf(src),seed,parent) // This should do a little bit of animation.
//move out to the destination
if(child.forceMove(target_turf))
child.update_icon()
@@ -137,21 +137,21 @@
else
qdel(child)
-/obj/effect/vine/proc/wake_neighbors()
+/obj/vine/proc/wake_neighbors()
// This turf is clear now, let our buddies know.
for(var/turf/simulated/check_turf in (get_cardinal_neighbors() | get_zlevel_neighbors()))
if(!istype(check_turf))
continue
- for(var/obj/effect/vine/neighbor in check_turf.contents)
+ for(var/obj/vine/neighbor in check_turf.contents)
START_PROCESSING(SSvines, neighbor)
-/obj/effect/vine/proc/targets_in_range()
+/obj/vine/proc/targets_in_range()
var/list/mob/targets = list()
for(var/turf/simulated/check_turf in (get_cardinal_neighbors() | get_zlevel_neighbors() | list(loc)))
if(!istype(check_turf))
continue
for(var/mob/living/M in check_turf.contents)
- if(prob(5) || !M.skill_check(SKILL_BOTANY, SKILL_PROF))
+ if(prob(5) || !M.skill_check(SKILL_BOTANY, SKILL_MASTER))
targets |= M
if(length(targets))
return targets
diff --git a/code/modules/hydroponics/spreading/spreading_response.dm b/code/modules/hydroponics/spreading/spreading_response.dm
index cbe2a0a7fa60e..5aec8f143f84f 100644
--- a/code/modules/hydroponics/spreading/spreading_response.dm
+++ b/code/modules/hydroponics/spreading/spreading_response.dm
@@ -1,4 +1,4 @@
-/obj/effect/vine/HasProximity(atom/movable/AM)
+/obj/vine/HasProximity(atom/movable/AM)
if(!is_mature() || seed.get_trait(TRAIT_SPREAD) != 2)
return
@@ -13,20 +13,20 @@
if(prob(seed.get_trait(((TRAIT_POTENCY)/2)*3)))
entangle(M)
-/obj/effect/vine/attack_hand(mob/user)
+/obj/vine/attack_hand(mob/user)
manual_unbuckle(user)
-/obj/effect/vine/attack_generic(mob/user)
+/obj/vine/attack_generic(mob/user)
if (buckled_mob == user)
manual_unbuckle(user)
return
..()
-/obj/effect/vine/Crossed(atom/movable/O)
+/obj/vine/Crossed(atom/movable/O)
if(isliving(O))
trodden_on(O)
-/obj/effect/vine/proc/trodden_on(mob/living/victim)
+/obj/vine/proc/trodden_on(mob/living/victim)
wake_neighbors()
if(!is_mature())
return
@@ -38,8 +38,8 @@
seed.do_thorns(victim,src)
seed.do_sting(victim,src,pick(BP_R_FOOT,BP_L_FOOT,BP_R_LEG,BP_L_LEG))
-/obj/effect/vine/proc/manual_unbuckle(mob/user)
- if(!buckled_mob)
+/obj/vine/proc/manual_unbuckle(mob/user)
+ if (!can_unbuckle(user))
return
if(buckled_mob != user)
to_chat(user, SPAN_NOTICE("You try to free \the [buckled_mob] from \the [src]."))
@@ -53,21 +53,21 @@
unbuckle_mob()
else
user.setClickCooldown(100)
- var/breakouttime = rand(600, 1200) //1 to 2 minutes.
+ var/escapetime = rand(600, 1200) //1 to 2 minutes.
user.visible_message(
"\The [user] attempts to get free from [src]!",
SPAN_NOTICE("You attempt to get free from [src].")
)
- if (do_after(user, breakouttime, src, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS, INCAPACITATION_DEFAULT & ~INCAPACITATION_RESTRAINED))
+ if (do_after(user, escapetime, src, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS, INCAPACITATION_DEFAULT & ~INCAPACITATION_RESTRAINED) && can_unbuckle(user))
if(unbuckle_mob())
user.visible_message(
"\The [user] manages to escape [src]!",
SPAN_NOTICE("You successfully escape [src]!")
)
-/obj/effect/vine/proc/entangle(mob/living/victim)
+/obj/vine/proc/entangle(mob/living/victim)
if(buckled_mob)
return
@@ -99,10 +99,12 @@
SPAN_DANGER("Tendrils lash out from \the [src] and drag \the [victim] in!"),
SPAN_DANGER("Tendrils lash out from \the [src] and drag you in!"))
victim.forceMove(loc)
- if(buckle_mob(victim))
+ if (victim.buckled)
+ victim.buckled.unbuckle_mob()
+ if (can_buckle(victim) && buckle_mob(victim))
victim.set_dir(pick(GLOB.cardinal))
to_chat(victim, SPAN_DANGER("The tendrils [pick("wind", "tangle", "tighten", "coil", "knot", "snag", "twist", "constrict", "squeeze", "clench", "tense")] around you!"))
-/obj/effect/vine/buckle_mob()
+/obj/vine/buckle_mob()
. = ..()
if(.) START_PROCESSING(SSvines, src)
diff --git a/code/modules/hydroponics/trays/tray.dm b/code/modules/hydroponics/trays/tray.dm
index 72a5ab4c0ef1c..f499846a32f41 100644
--- a/code/modules/hydroponics/trays/tray.dm
+++ b/code/modules/hydroponics/trays/tray.dm
@@ -1,7 +1,7 @@
/obj/machinery/portable_atmospherics/hydroponics
name = "hydroponics tray"
desc = "A mechanical basin designed to nurture plants. It has various useful sensors."
- icon = 'icons/obj/hydroponics_machines.dmi'
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
icon_state = "hydrotray3"
density = TRUE
anchored = TRUE
@@ -76,7 +76,8 @@
/datum/reagent/adminordrazine = 1,
/datum/reagent/toxin/fertilizer/eznutrient = 1,
/datum/reagent/toxin/fertilizer/robustharvest = 1,
- /datum/reagent/toxin/fertilizer/left4zed = 1
+ /datum/reagent/toxin/fertilizer/left4zed = 1,
+ /datum/reagent/toxin/fertilizer/potash = 1
)
var/static/list/weedkiller_reagents = list(
/datum/reagent/hydrazine = -4,
@@ -124,7 +125,8 @@
/datum/reagent/adminordrazine = list( 1, 1, 1 ),
/datum/reagent/toxin/fertilizer/robustharvest = list( 0, 0.2, 0 ),
/datum/reagent/toxin/fertilizer/left4zed = list( 0, 0, 0.2),
- /datum/reagent/drugs/three_eye = list( -1 , 0, 0.5)
+ /datum/reagent/drugs/three_eye = list( -1 , 0, 0.5),
+ /datum/reagent/toxin/fertilizer/potash = list( 0,5, 0.5, 0)
)
// Mutagen list specifies minimum value for the mutation to take place, rather
@@ -137,7 +139,7 @@
/obj/machinery/portable_atmospherics/hydroponics/AltClick()
if(mechanical && !usr.incapacitated() && Adjacent(usr))
close_lid(usr)
- return 1
+ return TRUE
return ..()
/obj/machinery/portable_atmospherics/hydroponics/attack_ghost(mob/observer/ghost/user)
@@ -169,7 +171,7 @@
SSplants.active_plants -= src
return ..()
-/obj/machinery/portable_atmospherics/hydroponics/LateInitialize()
+/obj/machinery/portable_atmospherics/hydroponics/LateInitialize(mapload)
..()
if(locate(/obj/item/seeds) in get_turf(src))
plant()
@@ -412,26 +414,38 @@
return
-/obj/machinery/portable_atmospherics/hydroponics/attackby(obj/item/O, mob/user)
+/obj/machinery/portable_atmospherics/hydroponics/wrench_act(mob/living/user, obj/item/tool)
+ if(!mechanical)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ //If there's a connector here, the portable_atmospherics setup can handle it.
+ if(locate(/obj/machinery/atmospherics/portables_connector) in loc) // TODO: what's this
+ var/obj/machinery/atmospherics/portables_connector/connector = locate(/obj/machinery/atmospherics/portables_connector)
+ return connector.wrench_act(user, tool)
+
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ anchored = !anchored
+ to_chat(user, "You [anchored ? "wrench" : "unwrench"] [src].")
+/obj/machinery/portable_atmospherics/hydroponics/use_tool(obj/item/O, mob/living/user, list/click_params)
if (O.is_open_container())
- return 0
+ return FALSE
if(O.edge && O.w_class < ITEM_SIZE_NORMAL && user.a_intent != I_HURT)
-
if(!seed)
- to_chat(user, SPAN_WARNING("There is nothing to take a sample from in \the [src]."))
- return
+ to_chat(user, SPAN_WARNING("There is nothing to take a sample from in [src]."))
+ return TRUE
if(sampled)
to_chat(user, SPAN_WARNING("There's no bits that can be used for a sampling left."))
- return
+ return TRUE
if(dead)
to_chat(user, SPAN_WARNING("The plant is dead."))
- return
+ return TRUE
- var/needed_skill = seed.mysterious ? SKILL_ADEPT : SKILL_BASIC
+ var/needed_skill = seed.mysterious ? SKILL_TRAINED : SKILL_BASIC
if(prob(user.skill_fail_chance(SKILL_BOTANY, 90, needed_skill)))
to_chat(user, SPAN_WARNING("You failed to get a usable sample."))
else
@@ -447,59 +461,56 @@
force_update = 1
Process()
- return
-
- else if(istype(O, /obj/item/reagent_containers/syringe))
+ return TRUE
+ if (istype(O, /obj/item/reagent_containers/syringe))
var/obj/item/reagent_containers/syringe/S = O
-
if (S.mode == 1)
if(seed)
return ..()
else
to_chat(user, "There's no plant to inject.")
- return 1
+ return TRUE
else
if(seed)
//Leaving this in in case we want to extract from plants later.
to_chat(user, "You can't get any extract out of this plant.")
else
to_chat(user, "There's nothing to draw something from.")
- return 1
-
- else if (istype(O, /obj/item/seeds))
+ return TRUE
+ if (istype(O, /obj/item/seeds))
plant_seed(user, O)
+ return TRUE
- else if (istype(O, /obj/item/material/minihoe)) // The minihoe
-
+ if (istype(O, /obj/item/material/minihoe))
if(weedlevel > 0)
user.visible_message(SPAN_NOTICE("[user] starts uprooting the weeds."), SPAN_NOTICE("You remove the weeds from the [src]."))
weedlevel = 0
if(seed)
- var/needed_skill = seed.mysterious ? SKILL_ADEPT : SKILL_BASIC
+ var/needed_skill = seed.mysterious ? SKILL_TRAINED : SKILL_BASIC
if(!user.skill_check(SKILL_BOTANY, needed_skill))
health -= rand(40,60)
check_health(1)
+ return TRUE
else
to_chat(user, SPAN_NOTICE("This plot is completely devoid of weeds. It doesn't need uprooting."))
+ return TRUE
- else if (istype(O, /obj/item/storage/plants))
-
+ if (istype(O, /obj/item/storage/plants))
attack_hand(user)
-
var/obj/item/storage/plants/S = O
for (var/obj/item/reagent_containers/food/snacks/grown/G in locate(user.x,user.y,user.z))
if(!S.can_be_inserted(G, user))
- return
+ return TRUE
S.handle_item_insertion(G, 1)
for (var/obj/item/shellfish/G in locate(user.x,user.y,user.z))
if(!S.can_be_inserted(G, user))
- return
+ return TRUE
S.handle_item_insertion(G, 1)
+ return TRUE
- else if ( istype(O, /obj/item/plantspray) )
-
+ if (istype(O, /obj/item/plantspray))
var/obj/item/plantspray/spray = O
toxins += spray.toxicity
pestlevel -= spray.pest_kill_str
@@ -508,31 +519,29 @@
playsound(loc, 'sound/effects/spray3.ogg', 50, 1, -6)
qdel(O)
check_health()
+ return TRUE
- else if(mechanical && isWrench(O))
-
- //If there's a connector here, the portable_atmospherics setup can handle it.
- if(locate(/obj/machinery/atmospherics/portables_connector) in loc)
- return ..()
+ if (mechanical)
+ return component_attackby(O, user)
- playsound(loc, 'sound/items/Ratchet.ogg', 50, 1)
- anchored = !anchored
- to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].")
+ return ..()
- else if(O.force && seed)
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
- user.visible_message(SPAN_DANGER("\The [seed.display_name] has been attacked by [user] with \the [O]!"))
- playsound(get_turf(src), O.hitsound, 100, 1)
+/obj/machinery/portable_atmospherics/hydroponics/use_weapon(obj/item/weapon, mob/living/user, list/click_params)
+ if (weapon.force && seed)
+ user.setClickCooldown(user.get_attack_speed(weapon))
+ user.do_attack_animation(src)
+ user.visible_message(SPAN_DANGER("[seed.display_name] has been attacked by [user] with [weapon]!"))
+ playsound(get_turf(src), weapon.hitsound, 100, 1)
if(!dead)
- health -= O.force
+ health -= weapon.force
check_health()
- else if(mechanical)
- return component_attackby(O, user)
+ return TRUE
+ return ..()
/obj/machinery/portable_atmospherics/hydroponics/proc/plant_seed(mob/user, obj/item/seeds/S)
if(seed)
- to_chat(user, SPAN_WARNING("\The [src] already has seeds in it!"))
+ to_chat(user, SPAN_WARNING("[src] already has seeds in it!"))
return
if(!S.seed)
@@ -550,7 +559,7 @@
health = (istype(S, /obj/item/seeds/cutting) ? round(seed.get_trait(TRAIT_ENDURANCE)/rand(2,5)) : seed.get_trait(TRAIT_ENDURANCE))
lastcycle = world.time
- var/needed_skill = seed.mysterious ? SKILL_ADEPT : SKILL_BASIC
+ var/needed_skill = seed.mysterious ? SKILL_TRAINED : SKILL_BASIC
if(prob(user.skill_fail_chance(SKILL_BOTANY, 40, needed_skill)))
dead = 1
health = 0
@@ -572,21 +581,21 @@
/obj/machinery/portable_atmospherics/hydroponics/examine(mob/user)
. = ..(user)
if(!seed)
- to_chat(user, "\The [src] is empty.")
+ . += SPAN_NOTICE("[src] is empty.")
return
- to_chat(user, SPAN_NOTICE("\An [seed.display_name] is growing here."))
+ . += SPAN_NOTICE("\An [seed.display_name] is growing here.")
if(user.skill_check(SKILL_BOTANY, SKILL_BASIC))
if(weedlevel >= 5)
- to_chat(user, "\The [src] is [SPAN_DANGER("infested with weeds")]!")
+ . += SPAN_NOTICE("[src] is [SPAN_DANGER("infested with weeds")]!")
if(pestlevel >= 5)
- to_chat(user, "\The [src] is [SPAN_DANGER("infested with tiny worms")]!")
+ . += SPAN_NOTICE("[src] is [SPAN_DANGER("infested with tiny worms")]!")
if(dead)
- to_chat(user, SPAN_DANGER("The [seed.display_name] is dead."))
+ . += SPAN_DANGER("The [seed.display_name] is dead.")
else if(health <= (seed.get_trait(TRAIT_ENDURANCE)/ 2))
- to_chat(user, "The [seed.display_name] looks [SPAN_DANGER("unhealthy")].")
+ . += SPAN_NOTICE("The [seed.display_name] looks [SPAN_DANGER("unhealthy")].")
if(mechanical && Adjacent(user))
var/turf/T = loc
@@ -609,9 +618,9 @@
var/light_available = T.get_lumcount() * 5
light_string = "a light level of [light_available] lumens"
- to_chat(user, "Water: [round(waterlevel,0.1)]/100")
- to_chat(user, "Nutrient: [round(nutrilevel,0.1)]/10")
- to_chat(user, "The tray's sensor suite is reporting [light_string] and a temperature of [environment.temperature]K.")
+ . += SPAN_NOTICE("Water: [round(waterlevel,0.1)]/100")
+ . += SPAN_NOTICE("Nutrient: [round(nutrilevel,0.1)]/10")
+ . += SPAN_NOTICE("The tray's sensor suite is reporting [light_string] and a temperature of [environment.temperature]K.")
/obj/machinery/portable_atmospherics/hydroponics/verb/close_lid_verb()
set name = "Toggle Tray Lid"
diff --git a/code/modules/hydroponics/trays/tray_process.dm b/code/modules/hydroponics/trays/tray_process.dm
index 7f1648be88e98..2398ab325670b 100644
--- a/code/modules/hydroponics/trays/tray_process.dm
+++ b/code/modules/hydroponics/trays/tray_process.dm
@@ -1,7 +1,7 @@
/obj/machinery/portable_atmospherics/hydroponics/Process()
// Handle nearby smoke if any.
- for(var/obj/effect/effect/smoke/chem/smoke in view(1, src))
+ for(var/obj/effect/smoke/chem/smoke in view(1, src))
if(smoke.reagents.total_volume)
smoke.reagents.trans_to_obj(src, 5, copy = 1)
@@ -35,13 +35,13 @@
// If there is no seed data (and hence nothing planted),
// or the plant is dead, process nothing further.
if(!seed || dead)
- if(mechanical)
+ if(mechanical)
update_icon() //Harvesting would fail to set alert icons properly.
return
// Advance plant age.
var/cur_stage = get_overlay_stage()
- if(prob(30))
+ if(prob(30))
age += 1 * HYDRO_SPEED_MULTIPLIER
if(get_overlay_stage() != cur_stage)
needs_icon_update |= 1
@@ -123,19 +123,19 @@
// If enough time (in cycles, not ticks) has passed since the plant was harvested, we're ready to harvest again.
if((age > seed.get_trait(TRAIT_MATURATION)) && \
- ((age - lastproduce) > seed.get_trait(TRAIT_PRODUCTION)) && \
- (!harvest && !dead))
+ ((age - lastproduce) > seed.get_trait(TRAIT_PRODUCTION)) && \
+ (!harvest && !dead))
harvest = 1
lastproduce = age
needs_icon_update |= 1
// If we're a vine which is not in a closed tray and is at least half mature, and there's no vine currently on our turf: make one (maybe)
if(!closed_system && \
- seed.get_trait(TRAIT_SPREAD) == 2 && \
- 2 * age >= seed.get_trait(TRAIT_MATURATION) && \
- !(locate(/obj/effect/vine) in get_turf(src)) && \
- prob(2 * seed.get_trait(TRAIT_POTENCY)))
- new /obj/effect/vine(get_turf(src), seed)
+ seed.get_trait(TRAIT_SPREAD) == 2 && \
+ 2 * age >= seed.get_trait(TRAIT_MATURATION) && \
+ !(locate(/obj/vine) in get_turf(src)) && \
+ prob(2 * seed.get_trait(TRAIT_POTENCY)))
+ new /obj/vine(get_turf(src), seed)
if(prob(3)) // On each tick, there's a chance the pest population will increase
pestlevel += 0.1 * HYDRO_SPEED_MULTIPLIER
diff --git a/code/modules/hydroponics/trays/tray_reagents.dm b/code/modules/hydroponics/trays/tray_reagents.dm
index 78c0c6576fbf3..7552bbbb64d9a 100644
--- a/code/modules/hydroponics/trays/tray_reagents.dm
+++ b/code/modules/hydroponics/trays/tray_reagents.dm
@@ -1,7 +1,7 @@
/obj/item/plantspray
- icon = 'icons/obj/hydroponics_machines.dmi'
- item_state = "spray"
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
+ item_state = "nolabelspray"
item_flags = ITEM_FLAG_NO_BLUDGEON
slot_flags = SLOT_BELT
throwforce = 4
@@ -27,24 +27,19 @@
/obj/item/plantspray/pests/old
name = "bottle of pestkiller"
- icon = 'icons/obj/chemical.dmi'
- icon_state = "bottle16"
/obj/item/plantspray/pests/old/carbaryl
name = "bottle of carbaryl"
- icon_state = "bottle16"
toxicity = 4
pest_kill_str = 2
/obj/item/plantspray/pests/old/lindane
name = "bottle of lindane"
- icon_state = "bottle18"
toxicity = 6
pest_kill_str = 4
/obj/item/plantspray/pests/old/phosmet
name = "bottle of phosmet"
- icon_state = "bottle15"
toxicity = 8
pest_kill_str = 7
@@ -54,28 +49,22 @@
/obj/item/weedkiller
name = "bottle of weedkiller"
- icon = 'icons/obj/chemical.dmi'
- icon_state = "bottle16"
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
+ item_state = "nolabelspray"
var/toxicity = 0
var/weed_kill_str = 0
/obj/item/weedkiller/triclopyr
name = "bottle of glyphosate"
- icon = 'icons/obj/chemical.dmi'
- icon_state = "bottle16"
toxicity = 4
weed_kill_str = 2
/obj/item/weedkiller/lindane
name = "bottle of triclopyr"
- icon = 'icons/obj/chemical.dmi'
- icon_state = "bottle18"
toxicity = 6
weed_kill_str = 4
/obj/item/weedkiller/D24
name = "bottle of 2,4-D"
- icon = 'icons/obj/chemical.dmi'
- icon_state = "bottle15"
toxicity = 8
- weed_kill_str = 7
\ No newline at end of file
+ weed_kill_str = 7
diff --git a/code/modules/hydroponics/trays/tray_soil.dm b/code/modules/hydroponics/trays/tray_soil.dm
index a69afb0c43f03..4815c5e660ff9 100644
--- a/code/modules/hydroponics/trays/tray_soil.dm
+++ b/code/modules/hydroponics/trays/tray_soil.dm
@@ -8,11 +8,11 @@
mechanical = 0
tray_light = 0
-/obj/machinery/portable_atmospherics/hydroponics/soil/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/portable_atmospherics/hydroponics/soil/use_tool(obj/item/O, mob/living/user, list/click_params)
if(istype(O,/obj/item/tank))
- return
+ return FALSE
else
- ..()
+ return ..()
/obj/machinery/portable_atmospherics/hydroponics/soil/New()
..()
@@ -29,7 +29,7 @@
/obj/machinery/portable_atmospherics/hydroponics/soil/invisible
name = "plant"
desc = null
- icon = 'icons/obj/seeds.dmi'
+ icon = 'icons/obj/flora/seeds.dmi'
icon_state = "blank"
machine_desc = null
var/list/connected_zlevels //cached for checking if we someone is obseving us so we should process
@@ -80,7 +80,7 @@
/obj/machinery/portable_atmospherics/hydroponics/soil/invisible/Destroy()
// Check if we're masking a decal that needs to be visible again.
- for(var/obj/effect/vine/plant in get_turf(src))
+ for(var/obj/vine/plant in get_turf(src))
if(plant.invisibility == INVISIBILITY_MAXIMUM)
plant.set_invisibility(initial(plant.invisibility))
. = ..()
diff --git a/code/modules/hydroponics/trays/tray_update_icons.dm b/code/modules/hydroponics/trays/tray_update_icons.dm
index 614d12e5eee4b..60fb1f53159ea 100644
--- a/code/modules/hydroponics/trays/tray_update_icons.dm
+++ b/code/modules/hydroponics/trays/tray_update_icons.dm
@@ -9,7 +9,7 @@
else
SetName(initial(name))
- overlays.Cut()
+ ClearOverlays()
var/new_overlays = list()
// Updates the plant overlay.
if(seed)
@@ -17,7 +17,7 @@
var/ikey = "[seed.get_trait(TRAIT_PLANT_ICON)]-dead"
var/image/dead_overlay = SSplants.plant_icon_cache["[ikey]"]
if(!dead_overlay)
- dead_overlay = image('icons/obj/hydroponics_growing.dmi', "[ikey]")
+ dead_overlay = image('icons/obj/flora/hydroponics_growing.dmi', "[ikey]")
dead_overlay.color = DEAD_PLANT_COLOUR
new_overlays |= dead_overlay
else
@@ -37,7 +37,7 @@
ikey = "[seed.get_trait(TRAIT_PRODUCT_ICON)]"
var/image/harvest_overlay = SSplants.plant_icon_cache["product-[ikey]-[seed.get_trait(TRAIT_PLANT_COLOUR)]"]
if(!harvest_overlay)
- harvest_overlay = image('icons/obj/hydroponics_products.dmi', "[ikey]")
+ harvest_overlay = image('icons/obj/flora/hydroponics_products.dmi', "[ikey]")
harvest_overlay.color = seed.get_trait(TRAIT_PRODUCT_COLOUR)
SSplants.plant_icon_cache["product-[ikey]-[harvest_overlay.color]"] = harvest_overlay
new_overlays |= harvest_overlay
@@ -67,11 +67,11 @@
set_density(0)
set_opacity(0)
- overlays |= new_overlays
+ AddOverlays(new_overlays)
// Update bioluminescence.
if(seed && seed.get_trait(TRAIT_BIOLUM))
- set_light(0.5, 0.1, 3, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
+ set_light(3, 0.5, l_color = seed.get_trait(TRAIT_BIOLUM_COLOUR))
else
set_light(0)
diff --git a/code/modules/input/input.dm b/code/modules/input/input.dm
new file mode 100644
index 0000000000000..28a27c46732d6
--- /dev/null
+++ b/code/modules/input/input.dm
@@ -0,0 +1,165 @@
+/// Set a client's focus to an object and override these procs on that object to let it handle keypresses
+/// Called when a key is pressed down initially
+/datum/proc/key_down(key, client/user)
+ return
+
+/// Called when a key is released
+/datum/proc/key_up(key, client/user)
+ return
+
+/// Called once every frame
+/datum/proc/keyLoop(client/user)
+ return
+
+/// Called every game tick
+/client/keyLoop()
+ holder?.keyLoop(src)
+ mob.keyLoop(src)
+
+/// Clients aren't datums so we have to define these procs indpendently.
+/// These verbs are called for all key press and release events
+/client/verb/keyDown(_key as text)
+ set hidden = TRUE
+
+ // So here's some eplaination why we use `instant`.
+ // Due of verbs nature, you can use the same verb limited time per tick.
+ // This means, you unable to perform combinated keypresses at the same time.
+ // (i.e multiple movement key press for diagonal direction move)
+ // (or ShiftF5 where we press two different keys at the same time)
+ // In current case, we should make this verb instant.
+ set instant = TRUE
+
+ if(!user_acted(src, "was just autokicked for flooding keysends; likely abuse but potentially lagspike."))
+ return
+
+ ///Check if the key is short enough to even be a real key
+ if(LAZYLEN(_key) > MAX_KEYPRESS_COMMANDLENGTH)
+ to_chat(src, SPAN_DANGER("Invalid KeyDown detected! You have been disconnected from the server automatically."))
+ log_admin("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ message_admins("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.")
+ qdel(src)
+ return
+
+ //Focus Chat failsafe. Overrides movement checks to prevent WASD.
+ if(!prefs.hotkeys && length(_key) == 1 && _key != "Alt" && _key != "Ctrl" && _key != "Shift")
+ var/current_text = winget(src, "input", "text")
+ winset(src, "outputwindow.input", "focus=true;text=[current_text + url_encode(_key)]")
+ return
+
+ if(length(keys_held) >= HELD_KEY_BUFFER_LENGTH && !keys_held[_key])
+ keyUp(keys_held[1]) //We are going over the number of possible held keys, so let's remove the first one.
+
+ //the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
+ keys_held[_key] = world.time
+ if(!movement_locked)
+ var/movement = movement_keys[_key]
+ if(!(next_move_dir_sub & movement))
+ next_move_dir_add |= movement
+
+ if(movement)
+ last_move_dir_pressed = movement
+
+ // Client-level keybindings are ones anyone should be able to do at any time
+ // Things like taking screenshots, hitting tab, and adminhelps.
+ var/AltMod = keys_held["Alt"] ? "Alt" : ""
+ var/CtrlMod = keys_held["Ctrl"] ? "Ctrl" : ""
+ var/ShiftMod = keys_held["Shift"] ? "Shift" : ""
+ var/full_key
+ switch(_key)
+ if("Alt", "Ctrl", "Shift")
+ full_key = "[AltMod][CtrlMod][ShiftMod]"
+ else
+ if(AltMod || CtrlMod || ShiftMod)
+ full_key = "[AltMod][CtrlMod][ShiftMod][_key]"
+ key_combos_held[_key] = full_key
+ else
+ _key = capitalize(_key)
+ full_key = _key
+ var/keycount = 0
+ for(var/kb_name in prefs.key_bindings[full_key])
+ keycount++
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
+ if(kb.can_use(src) && kb.down(src) && keycount >= MAX_COMMANDS_PER_KEY)
+ break
+
+ holder?.key_down(full_key, src)
+ mob.key_down(full_key, src)
+
+/client/verb/keyUp(_key as text)
+ set instant = TRUE
+ set hidden = TRUE
+
+ var/key_combo = key_combos_held[_key]
+ if(key_combo)
+ key_combos_held -= _key
+ keyUp(key_combo)
+
+ if(!keys_held[_key])
+ return
+
+ keys_held -= _key
+
+ if(!movement_locked)
+ var/movement = movement_keys[_key]
+ if(!(next_move_dir_add & movement))
+ next_move_dir_sub |= movement
+
+ // We don't do full key for release, because for mod keys you
+ // can hold different keys and releasing any should be handled by the key binding specifically
+ for (var/kb_name in prefs.key_bindings[_key])
+ var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
+ if(kb.can_use(src) && kb.up(src))
+ break
+ holder?.key_up(_key, src)
+ mob.key_up(_key, src)
+
+// removes all the existing macros
+/client/proc/erase_all_macros()
+ var/erase_output = ""
+ var/list/macro_set = params2list(winget(src, "default.*", "command")) // The third arg doesnt matter here as we're just removing them all
+ for(var/k in 1 to length(macro_set))
+ var/list/split_name = splittext(macro_set[k], ".")
+ var/macro_name = "[split_name[1]].[split_name[2]]" // [3] is "command"
+ erase_output = "[erase_output];[macro_name].parent=null"
+ winset(src, null, erase_output)
+
+
+/client/proc/set_macros()
+ set waitfor = FALSE
+
+ //Reset the buffer
+ reset_held_keys()
+ erase_all_macros()
+
+ var/list/macro_set = SSinput.macro_set
+ for(var/k in 1 to length(macro_set))
+ var/key = macro_set[k]
+ var/command = macro_set[key]
+ winset(src, "default-\ref[key]", "parent=default;name=[key];command=[command]")
+
+ update_special_keybinds()
+
+/client/proc/reset_macros(skip_alert = FALSE)
+ var/answer
+ if(!skip_alert)
+ answer = tgui_alert(src, "Change your keyboard language to ENG", "Reset macros")
+
+ if(skip_alert || answer)
+ set_macros()
+ to_chat(src, SPAN_NOTICE("Keybindings were fixed.")) // not yet but set_macros works fast enough
+
+/**
+ * Manually clears any held keys, in case due to lag or other undefined behavior a key gets stuck.
+ *
+ * Hardcoded to the ESC key.
+ */
+/client/verb/reset_held_keys()
+ set name = "Reset Held Keys"
+ set hidden = TRUE
+
+ for(var/key in keys_held)
+ keyUp(key)
+
+ //In case one got stuck and the previous loop didn't clean it, somehow.
+ for(var/key in key_combos_held)
+ keyUp(key_combos_held[key])
diff --git a/code/modules/input/movement.dm b/code/modules/input/movement.dm
new file mode 100644
index 0000000000000..94f7827d44cc9
--- /dev/null
+++ b/code/modules/input/movement.dm
@@ -0,0 +1,40 @@
+/atom/movable
+ appearance_flags = TILE_BOUND | PIXEL_SCALE | LONG_GLIDE
+
+// You might be wondering why this isn't client level. If focus is null, we don't want you to move.
+// Only way to do that is to tie the behavior into the focus's keyLoop().
+/atom/movable/keyLoop(client/user)
+ var/movement_dir = 0
+ for(var/_key in user.keys_held)
+ movement_dir = movement_dir | user.movement_keys[_key]
+ if(user.next_move_dir_add)
+ movement_dir |= user.next_move_dir_add
+ if(user.next_move_dir_sub)
+ movement_dir &= ~user.next_move_dir_sub
+ // Sanity checks in case you hold left and right and up to make sure you only go up
+ if((movement_dir & NORTH) && (movement_dir & SOUTH))
+ movement_dir &= ~(NORTH|SOUTH)
+ if((movement_dir & EAST) && (movement_dir & WEST))
+ movement_dir &= ~(EAST|WEST)
+
+ if(movement_dir) //If we're not moving, don't compensate, as byond will auto-fill dir otherwise
+ movement_dir = turn(movement_dir, -dir2angle(user.dir)) //By doing this we ensure that our input direction is offset by the client (camera) direction
+
+ if(user.movement_locked)
+ keybind_face_direction(movement_dir)
+ else
+ user.Move(get_step(src, movement_dir), movement_dir)
+
+ user.next_move_dir_add = 0
+ user.next_move_dir_sub = 0
+
+/**
+* A wrapper for setDir that should only be able to fail by living mobs.
+*
+* Called from [/atom/movable/proc/keyLoop], this exists to be overwritten by living mobs with a check to see if we're actually alive enough to change directions
+*/
+/atom/movable/proc/keybind_face_direction(direction)
+ return
+
+/mob/keybind_face_direction(direction)
+ facedir(direction)
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
index 056a9b2d3ad33..7eaad292555ff 100644
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ b/code/modules/integrated_electronics/core/assemblies.dm
@@ -27,7 +27,7 @@
var/interact_page = 0
var/components_per_page = 10
/// Spark system used for creating sparks while the assembly is damaged and destroyed.
- var/datum/effect/effect/system/spark_spread/spark_system
+ var/datum/effect/spark_spread/spark_system
var/adrone = FALSE
pass_flags = 0
anchored = FALSE
@@ -55,22 +55,22 @@
/obj/item/device/electronic_assembly/examine(mob/user)
. = ..()
if(IC_FLAG_ANCHORABLE & circuit_flags)
- to_chat(user, SPAN_NOTICE("The anchoring bolts [anchored ? "are" : "can be"] wrenched in place and the maintenance panel [opened ? "can be" : "is"] screwed in place."))
+ . += SPAN_NOTICE("The anchoring bolts [anchored ? "are" : "can be"] wrenched in place and the maintenance panel [opened ? "can be" : "is"] screwed in place.")
else
- to_chat(user, SPAN_NOTICE("The maintenance panel [opened ? "can be" : "is"] screwed in place."))
+ . += SPAN_NOTICE("The maintenance panel [opened ? "can be" : "is"] screwed in place.")
if((isobserver(user) && ckeys_allowed_to_scan[user.ckey]) || check_rights(R_ADMIN, 0, user))
- to_chat(user, "You can scan this circuit.");
+ . += SPAN_NOTICE("You can scan this circuit.");
/obj/item/device/electronic_assembly/on_death()
- visible_message(SPAN_WARNING("\The [src] falls to pieces!"))
+ visible_message(SPAN_WARNING("[src] falls to pieces!"))
if(w_class == ITEM_SIZE_HUGE)
if(adrone)
- new /obj/effect/decal/cleanable/blood/gibs/robot(loc)
+ new /obj/decal/cleanable/blood/gibs/robot(loc)
new /obj/item/stack/material/steel(loc, rand(7, 10))
else if(w_class == ITEM_SIZE_LARGE)
if(adrone)
- new /obj/effect/decal/cleanable/blood/gibs/robot(loc)
+ new /obj/decal/cleanable/blood/gibs/robot(loc)
new /obj/item/stack/material/steel(loc, rand(3, 6))
else if(w_class == ITEM_SIZE_NORMAL)
new /obj/item/stack/material/steel(loc, rand(1, 3))
@@ -80,13 +80,13 @@
spark_system.start()
playsound(loc, 'sound/items/electronic_assembly_empty.ogg', 100, 1)
icon = 0
- addtimer(new Callback(src, .proc/fall_apart), 5.1)
+ addtimer(CALLBACK(src, PROC_REF(fall_apart)), 5.1)
/obj/item/device/electronic_assembly/post_health_change(health_mod, prior_health, damage_type)
..()
if (get_damage_percentage() >= 75)
if(battery && battery.charge > 0)
- visible_message(SPAN_WARNING("\The [src] sputters and sparks!"))
+ visible_message(SPAN_WARNING("[src] sputters and sparks!"))
spark_system.start()
opened = TRUE
queue_icon_update()
@@ -112,7 +112,7 @@
.=..()
START_PROCESSING(SScircuit, src)
matter[MATERIAL_STEEL] = round((max_complexity + max_components) / 4) * SScircuit.cost_multiplier
- spark_system = new /datum/effect/effect/system/spark_spread
+ spark_system = new /datum/effect/spark_spread
spark_system.set_up(7, 0, src)
spark_system.attach(src)
@@ -135,7 +135,7 @@
var/power_failure = FALSE
if(get_damage_percentage() >= 75 && prob(1))
if(battery && battery.charge > 0)
- visible_message(SPAN_WARNING("\The [src] sparks violently!"))
+ visible_message(SPAN_WARNING("[src] sparks violently!"))
spark_system.start()
power_failure = TRUE
// Now spend it.
@@ -157,7 +157,8 @@
if(opened)
open_interact(user)
- closed_interact(user)
+ else
+ closed_interact(user)
/obj/item/device/electronic_assembly/proc/closed_interact(mob/user)
var/HTML = list()
@@ -210,17 +211,17 @@
for(var/i = start_index to min(length(assembly_components), start_index + (components_per_page - 1)))
var/obj/item/integrated_circuit/circuit = assembly_components[i]
HTML += "\[ [i] \] | "
- HTML += "\[R\] | "
+ HTML += "\[R\] | "
if(circuit.removable)
HTML += "\[-\] | "
else
HTML += "\[-\] | "
- HTML += "[circuit.displayed_name]"
+ HTML += "[circuit.displayed_name]"
HTML += " "
if(length(assembly_components) > components_per_page)
HTML += " \["
- for(var/i = 1 to Ceil(length(assembly_components)/components_per_page))
+ for(var/i = 1 to ceil(length(assembly_components)/components_per_page))
if((i-1) == interact_page)
HTML += " [i]"
else
@@ -254,11 +255,11 @@
if(href_list["remove_cell"])
if(!battery)
- to_chat(usr, SPAN_DANGER("There's no power cell to remove from \the [src]."))
+ to_chat(usr, SPAN_DANGER("There's no power cell to remove from [src]."))
else
battery.dropInto(loc)
playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
- to_chat(usr, SPAN_NOTICE("You pull \the [battery] out of \the [src]'s power supplier."))
+ to_chat(usr, SPAN_NOTICE("You pull [battery] out of [src]'s power supplier."))
battery = null
if(href_list["component"])
@@ -275,17 +276,22 @@
if(href_list["remove"])
try_remove_component(component, usr)
- else
- // Adjust the position
- if(href_list["set_slot"])
- var/selected_slot = input("Select a new slot", "Select slot", current_pos) as null|num
- if(!check_interactivity(usr))
- return 0
- if(selected_slot < 1 || selected_slot > length(assembly_components))
- return 0
+ else if (href_list["rename_component"])
+ component.rename_component()
+
+ else if(href_list["set_slot"])
+ var/selected_slot = input("Select a new slot", "Select slot", current_pos) as null|num
+ if(!check_interactivity(usr))
+ return 0
+ if(selected_slot < 1 || selected_slot > length(assembly_components))
+ return 0
- assembly_components.Remove(component)
- assembly_components.Insert(selected_slot, component)
+ assembly_components.Remove(component)
+ assembly_components.Insert(selected_slot, component)
+
+ else if (href_list["examine_component"])
+ component.interact(usr)
+ return
interact(usr) // To refresh the UI.
@@ -313,20 +319,20 @@
icon_state = initial(icon_state) + "-open"
else
icon_state = initial(icon_state)
- overlays.Cut()
+ ClearOverlays()
if(detail_color == COLOR_ASSEMBLY_BLACK) //Black colored overlay looks almost but not exactly like the base sprite, so just cut the overlay and avoid it looking kinda off.
return
var/image/detail_overlay = image('icons/obj/assemblies/electronic_setups.dmi', src,"[icon_state]-color")
detail_overlay.color = detail_color
- overlays += detail_overlay
+ AddOverlays(detail_overlay)
/obj/item/device/electronic_assembly/examine(mob/user)
. = ..()
for(var/I in assembly_components)
var/obj/item/integrated_circuit/IC = I
- IC.external_examine(user)
+ . += IC.external_examine(user)
if(opened)
- IC.internal_examine(user)
+ . += IC.internal_examine(user)
if(opened)
interact(user)
@@ -353,11 +359,11 @@
// Returns true if the circuit made it inside.
/obj/item/device/electronic_assembly/proc/try_add_component(obj/item/integrated_circuit/IC, mob/user)
if(!opened)
- to_chat(user, SPAN_DANGER("\The [src]'s hatch is closed, you can't put anything inside."))
+ to_chat(user, SPAN_DANGER("[src]'s hatch is closed, you can't put anything inside."))
return FALSE
if(IC.w_class > w_class)
- to_chat(user, SPAN_DANGER("\The [IC] is way too big to fit into \the [src]."))
+ to_chat(user, SPAN_DANGER("[IC] is way too big to fit into [src]."))
return FALSE
var/total_part_size = return_total_size()
@@ -394,23 +400,23 @@
/obj/item/device/electronic_assembly/proc/try_remove_component(obj/item/integrated_circuit/IC, mob/user, silent)
if(!opened)
if(!silent)
- to_chat(user, SPAN_DANGER("\The [src]'s hatch is closed, so you can't fiddle with the internal components."))
+ to_chat(user, SPAN_DANGER("[src]'s hatch is closed, so you can't fiddle with the internal components."))
return FALSE
if(!IC.removable)
if(!silent)
- to_chat(user, SPAN_DANGER("\The [src] is permanently attached to the case."))
+ to_chat(user, SPAN_DANGER("[src] is permanently attached to the case."))
return FALSE
remove_component(IC)
if(!silent)
- to_chat(user, SPAN_NOTICE("You pop \the [IC] out of the case, and slide it out."))
- playsound(src, 'sound/items/crowbar.ogg', 50, 1)
+ to_chat(user, SPAN_NOTICE("You pop [IC] out of the case, and slide it out."))
+ playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
user.put_in_hands(IC)
add_allowed_scanner(user.ckey)
// Make sure we're not on an invalid page
- interact_page = clamp(interact_page, 0, Ceil(length(assembly_components)/components_per_page)-1)
+ interact_page = clamp(interact_page, 0, ceil(length(assembly_components)/components_per_page)-1)
return TRUE
@@ -427,99 +433,135 @@
for(var/obj/item/integrated_circuit/input/S in assembly_components)
if(S.sense(target,user,proximity))
if(proximity)
- visible_message(SPAN_NOTICE("\The [user] waves \the [src] around \the [target]."))
+ visible_message(SPAN_NOTICE("[user] waves [src] around [target]."))
else
- visible_message(SPAN_NOTICE("\The [user] points \the [src] towards \the [target]."))
-
+ visible_message(SPAN_NOTICE("[user] points [src] towards [target]."))
-/obj/item/device/electronic_assembly/attackby(obj/item/I, mob/living/user)
- if (user.a_intent == I_HURT)
- ..()
+/obj/item/device/electronic_assembly/multitool_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if (!opened)
+ USE_FEEDBACK_FAILURE("[src]'s hatch needs to be opened before you can access the internal components.")
return
+ interact(user)
- if(istype(I, /obj/item/wrench))
- if(istype(loc, /turf) && (IC_FLAG_ANCHORABLE & circuit_flags))
- user.visible_message(SPAN_NOTICE("\The [user] wrenches \the [src]'s anchoring bolts [anchored ? "back" : "into position"]."))
- playsound(get_turf(user), 'sound/items/Ratchet.ogg',50)
- if(user.do_skilled(5 SECONDS, SKILL_CONSTRUCTION, src, do_flags = DO_REPAIR_CONSTRUCT))
- anchored = !anchored
+/obj/item/device/electronic_assembly/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ for(var/obj/item/integrated_circuit/manipulation/hatchlock/hatchlock in assembly_components)
+ if(hatchlock.lock_enabled)
+ balloon_alert(user, "[hatchlock] закрыт!")
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ opened = !opened
+ update_icon()
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, opened)
+
+/obj/item/device/electronic_assembly/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!HAS_FLAGS(circuit_flags, IC_FLAG_ANCHORABLE))
+ USE_FEEDBACK_FAILURE("[src] can't be anchored.")
+ return
+ if(!isturf(loc))
+ USE_FEEDBACK_FAILURE("[src] needs to be on the floor to be anchored.")
return
+ user.visible_message(
+ SPAN_NOTICE("[user] starts wrenching [src] [anchored ? "from" : "to"] the floor with [tool]."),
+ SPAN_NOTICE("You start wrenching [src] [anchored ? "from" : "to"] the floor with [tool].")
+ )
+ if(!tool.use_as_tool(src, user, 1 SECONDS, volume = 50, skill_path = SKILL_DEVICES, do_flags = DO_REPAIR_CONSTRUCT) || !HAS_FLAGS(circuit_flags, IC_FLAG_ANCHORABLE) || !isturf(loc))
+ return
+ user.visible_message(
+ SPAN_NOTICE("[user] wrenches [src] [anchored ? "from" : "to"] the floor with [tool]."),
+ SPAN_NOTICE("You wrenches [src] [anchored ? "from" : "to"] the floor with [tool].")
+ )
+ anchored = !anchored
+
+/obj/item/device/electronic_assembly/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Assembly Detailer - Set color
+ if (istype(tool, /obj/item/device/integrated_electronics/detailer))
+ var/obj/item/device/integrated_electronics/detailer/detailer = tool
+ detail_color = detailer.detail_color
+ update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] re-colors [src] with [tool]."),
+ SPAN_NOTICE("You re-color [src] with [tool].")
+ )
+ return TRUE
- if(istype(I, /obj/item/integrated_circuit))
- if(!user.canUnEquip(I))
- return FALSE
- if(try_add_component(I, user))
+ // Integrated Circuit - Install circuit
+ if (istype(tool, /obj/item/integrated_circuit))
+ if (!user.canUnEquip(tool))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
return TRUE
- else
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
+ if (try_add_component(tool, user))
+ return TRUE
+ return ..()
- if(istype(I, /obj/item/device/multitool) || istype(I, /obj/item/device/integrated_electronics/wirer) || istype(I, /obj/item/device/integrated_electronics/debugger))
- if(opened)
- interact(user)
+ // Wirer, debugger - Interact
+ if (istype(tool, /obj/item/device/integrated_electronics/wirer) || istype(tool, /obj/item/device/integrated_electronics/debugger))
+ if (!opened)
+ USE_FEEDBACK_FAILURE("[src]'s hatch needs to be opened before you can access the internal components.")
return TRUE
- else
- to_chat(user, SPAN_DANGER("\The [src]'s hatch is closed, so you can't fiddle with the internal components."))
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
-
- if(istype(I, /obj/item/cell))
- if(!opened)
- to_chat(user, SPAN_DANGER("\The [src]'s hatch is closed, so you can't access \the [src]'s power supply."))
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
- if(battery)
- to_chat(user, SPAN_DANGER("\The [src] already has \a [battery] installed. Remove it first if you want to replace it."))
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
- var/obj/item/cell/cell = I
- if(user.unEquip(I,loc))
- user.drop_from_inventory(I, loc)
- cell.forceMove(src)
- battery = cell
- playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1)
- to_chat(user, SPAN_NOTICE("You slot \the [cell] inside \the [src]."))
+ interact(user)
+ return TRUE
+
+ // Power Cell - Install battery
+ if (istype(tool, /obj/item/cell))
+ if (!opened)
+ USE_FEEDBACK_FAILURE("[src]'s hatch needs to be opened before you can install [tool].")
return TRUE
- return FALSE
+ if (battery)
+ USE_FEEDBACK_FAILURE("[src] already has [battery] installed.")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ battery = tool
+ playsound(src, 'sound/items/Deconstruct.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("[user] installs [tool] into [src]."),
+ SPAN_NOTICE("You install [tool] into [src].")
+ )
+ return TRUE
- if(istype(I, /obj/item/device/integrated_electronics/detailer))
- var/obj/item/device/integrated_electronics/detailer/D = I
- detail_color = D.detail_color
- update_icon()
- return
+ // Cable Coil - Repair damage
+ if (isCoil(tool))
+ if (!health_damaged())
+ USE_FEEDBACK_FAILURE("[src] doesn't need repair.")
+ return TRUE
+ var/obj/item/stack/cable_coil/cable = tool
+ if (!cable.can_use(5))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(cable, 5, "to repair [src].")
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("[user] starts repairing some of [src]'s damage with [cable.get_vague_name(TRUE)]."),
+ SPAN_NOTICE("You start repairing some of [src]'s damage with [cable.get_exact_name(5)].")
+ )
+ if (!user.do_skilled(1 SECOND, SKILL_DEVICES, src) || !user.use_sanity_check(src, tool))
+ return TRUE
+ if (!health_damaged())
+ USE_FEEDBACK_FAILURE("[src] doesn't need repair.")
+ return TRUE
+ if (!cable.use(5))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(cable, 5, "to repair [src].")
+ return TRUE
+ restore_health(5)
+ user.visible_message(
+ SPAN_NOTICE("[user] repairs some of [src]'s damage with [cable.get_vague_name(TRUE)]."),
+ SPAN_NOTICE("You repair some of [src]'s damage with [cable.get_exact_name(5)].")
+ )
+ return TRUE
- if(istype(I, /obj/item/screwdriver))
- var/hatch_locked = FALSE
- for(var/obj/item/integrated_circuit/manipulation/hatchlock/H in assembly_components)
- // If there's more than one hatch lock, only one needs to be enabled for the assembly to be locked
- if(H.lock_enabled)
- hatch_locked = TRUE
- break
-
- if(hatch_locked)
- to_chat(user, SPAN_NOTICE("The screws are covered by a locking mechanism!"))
- return FALSE
-
- playsound(src, 'sound/items/Screwdriver.ogg', 25)
- opened = !opened
- to_chat(user, SPAN_NOTICE("You [opened ? "open" : "close"] the maintenance hatch of \the [src]."))
- update_icon()
- return
+ // Everything else - Handle component reactions
+ var/result = FALSE
+ for (var/obj/item/integrated_circuit/component in assembly_components)
+ if (component.attackby_react(tool, user, user.a_intent))
+ result = TRUE
+ if (result)
+ return TRUE
- if(isCoil(I))
- var/obj/item/stack/cable_coil/C = I
- if(health_damaged() && do_after(user, 1 SECOND, src, DO_PUBLIC_UNIQUE) && C.use(1))
- user.visible_message(SPAN_NOTICE("\The [user] patches up \the [src]."))
- restore_health(5)
- return
+ return ..()
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- ..()
/obj/item/device/electronic_assembly/attack_self(mob/user)
interact(user)
@@ -562,7 +604,7 @@
..()
/obj/item/device/electronic_assembly/default //The /default electronic_assemblys are to allow the introduction of the new naming scheme without breaking old saves.
- name = "type-a electronic assembly"
+ name = "type-a electronic assembly"
/obj/item/device/electronic_assembly/calc
name = "type-b electronic assembly"
@@ -738,9 +780,10 @@
max_complexity = IC_COMPLEXITY_BASE * 2
health_max = 40
-/obj/item/device/electronic_assembly/wallmount/afterattack(atom/a, mob/user, proximity)
- if(proximity && istype(a ,/turf) && a.density)
- mount_assembly(a,user)
+/obj/item/device/electronic_assembly/wallmount/use_after(atom/target, mob/living/user, click_parameters)
+ if(istype(target ,/turf) && target.density)
+ mount_assembly(target,user)
+ return TRUE
/obj/item/device/electronic_assembly/wallmount/heavy
name = "heavy wall-mounted electronic assembly"
@@ -768,7 +811,7 @@
return
var/turf/T = get_turf(user)
if(T.density)
- to_chat(user, SPAN_DANGER("You cannot place \the [src] on this spot!"))
+ to_chat(user, SPAN_DANGER("You cannot place [src] on this spot!"))
return
if(gotwallitem(T, ndir))
to_chat(user, SPAN_DANGER("There's already an item on this wall!"))
diff --git a/code/modules/integrated_electronics/core/debugger.dm b/code/modules/integrated_electronics/core/debugger.dm
index 778fbc6fbc506..691e22939f636 100644
--- a/code/modules/integrated_electronics/core/debugger.dm
+++ b/code/modules/integrated_electronics/core/debugger.dm
@@ -37,14 +37,14 @@
data_to_write = null
to_chat(user, SPAN_NOTICE("You set \the [src]'s memory to absolutely nothing."))
-/obj/item/device/integrated_electronics/debugger/afterattack(atom/target, mob/living/user, proximity)
- . = ..()
- if(accepting_refs && proximity)
+/obj/item/device/integrated_electronics/debugger/use_after(atom/target, mob/living/user, click_parameters)
+ if (accepting_refs)
data_to_write = weakref(target)
visible_message(SPAN_NOTICE("[user] slides \a [src]'s over \the [target]."))
to_chat(user, SPAN_NOTICE("You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \
now off."))
accepting_refs = FALSE
+ return TRUE
/obj/item/device/integrated_electronics/debugger/proc/write_data(datum/integrated_io/io, mob/user)
if(io.io_type == DATA_CHANNEL)
diff --git a/code/modules/integrated_electronics/core/detailer.dm b/code/modules/integrated_electronics/core/detailer.dm
index 6cc45bddac9f3..19a44427477bc 100644
--- a/code/modules/integrated_electronics/core/detailer.dm
+++ b/code/modules/integrated_electronics/core/detailer.dm
@@ -33,10 +33,10 @@
update_icon()
/obj/item/device/integrated_electronics/detailer/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
var/image/detail_overlay = image('icons/obj/assemblies/electronic_tools.dmi',src, "detailer-color")
detail_overlay.color = detail_color
- overlays += detail_overlay
+ AddOverlays(detail_overlay)
/obj/item/device/integrated_electronics/detailer/attack_self(mob/user)
var/color_choice = input(user, "Select color.", "Assembly Detailer") as null|anything in color_list
diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm
index efc03f9e60faf..b542a364d3955 100644
--- a/code/modules/integrated_electronics/core/integrated_circuit.dm
+++ b/code/modules/integrated_electronics/core/integrated_circuit.dm
@@ -32,22 +32,23 @@ a creative player the means to solve many problems. Circuits are held inside an
/obj/item/integrated_circuit/examine(mob/user)
. = ..()
- external_examine(user)
+ . += external_examine(user)
-/obj/item/integrated_circuit/ShiftClick(mob/living/user)
- if(istype(user))
+/obj/item/integrated_circuit/ShiftClick(mob/user)
+ if (isliving(user))
interact(user)
- else
- ..()
+ return TRUE
+ return ..()
// This should be used when someone is examining while the case is opened.
/obj/item/integrated_circuit/proc/internal_examine(mob/user)
- any_examine(user)
+ . = list()
+ . += any_examine(user)
interact(user)
// This should be used when someone is examining from an 'outside' perspective, e.g. reading a screen or LED.
/obj/item/integrated_circuit/proc/external_examine(mob/user)
- any_examine(user)
+ return any_examine(user)
/obj/item/integrated_circuit/proc/any_examine(mob/user)
return
@@ -244,7 +245,7 @@ a creative player the means to solve many problems. Circuits are held inside an
return 1
. = IC_TOPIC_HANDLED
- var/obj/held_item = usr.get_active_hand()
+ var/obj/item/held_item = usr.get_active_hand()
if(href_list["pin"] && assembly)
var/datum/integrated_io/pin = locate(href_list["pin"]) in inputs + outputs + activators
if(pin)
@@ -266,7 +267,7 @@ a creative player the means to solve many problems. Circuits are held inside an
if(istype(held_item, /obj/item/device/integrated_electronics/debugger))
var/obj/item/device/integrated_electronics/debugger/D = held_item
if(D.accepting_refs)
- D.afterattack(src, usr, TRUE)
+ D.use_after(src, usr)
. = IC_TOPIC_REFRESH
else
to_chat(usr, SPAN_WARNING("The debugger's 'ref scanner' needs to be on."))
@@ -285,13 +286,14 @@ a creative player the means to solve many problems. Circuits are held inside an
. = IC_TOPIC_REFRESH
else if(href_list["remove"] && assembly)
- if(istype(held_item, /obj/item/screwdriver))
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ if(!held_item.use_as_tool(src, usr, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
disconnect_all()
dropInto(loc)
- playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
- to_chat(usr, SPAN_NOTICE("You pop \the [src] out of the case, and slide it out."))
+ to_chat(usr, SPAN_NOTICE("You pop [src] out of the case, and slide it out."))
else
- to_chat(usr, SPAN_WARNING("You need a screwdriver to remove components."))
+ balloon_alert(usr, "нужна отвертка!")
interact_with_assembly(usr)
. = IC_TOPIC_REFRESH
diff --git a/code/modules/integrated_electronics/core/pins.dm b/code/modules/integrated_electronics/core/pins.dm
index abd144c0a3a69..89ae06d33470f 100644
--- a/code/modules/integrated_electronics/core/pins.dm
+++ b/code/modules/integrated_electronics/core/pins.dm
@@ -13,7 +13,7 @@ B [1]-\|++|/
C [4]-/|++|
D [1]/ ||
||
- Activator
+ Activator
diff --git a/code/modules/integrated_electronics/core/prefab/test/testprefabs.dm b/code/modules/integrated_electronics/core/prefab/test/testprefabs.dm
deleted file mode 100644
index a37b44be2b162..0000000000000
--- a/code/modules/integrated_electronics/core/prefab/test/testprefabs.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/singleton/prefab/ic_assembly/test_heatercooler
- assembly_name = "heating-cooling-test"
- data = {"{'assembly':{'type':'type-c electronic machine'},'components':\[{'type':'starter'},{'type':'reagent funnel'},{'type':'big reagent storage'},{'type':'reagent pump','name':'Hot Pump','inputs':\[\[3,0,5]]},{'type':'reagent pump','name':'Cool Pump','inputs':\[\[3,0,5]]},{'type':'reagent heater','name':'Heater','inputs':\[\[1,0,80]]},{'type':'reagent cooler','name':'Cooler','inputs':\[\[1,0,-50]]},{'type':'button','name':'Heat And Cool'},{'type':'and gate','name':'Heater Active Check','inputs':\[\[1,0,0],\[2,0,1]]},{'type':'and gate','name':'Cooler Active Check','inputs':\[\[1,0,0],\[2,0,1]]},{'type':'custom delay circuit','name':'Heater Delay','inputs':\[\[1,0,100]]},{'type':'custom delay circuit','name':'Cooler Delay','inputs':\[\[1,0,100]]}],'wires':\[\[\[1,'A',1],\[3,'A',1]],\[\[1,'A',1],\[6,'A',3]],\[\[1,'A',1],\[7,'A',3]],\[\[2,'I',1],\[3,'O',2]],\[\[3,'O',2],\[4,'I',1]],\[\[3,'O',2],\[5,'I',1]],\[\[4,'I',2],\[6,'O',4]],\[\[4,'A',1],\[8,'A',1]],\[\[4,'A',2],\[6,'A',1]],\[\[5,'I',2],\[7,'O',4]],\[\[5,'A',1],\[8,'A',1]],\[\[5,'A',2],\[7,'A',1]],\[\[6,'O',3],\[9,'I',1]],\[\[6,'A',1],\[11,'A',2]],\[\[6,'A',2],\[9,'A',1]],\[\[7,'O',3],\[10,'I',1]],\[\[7,'A',1],\[12,'A',2]],\[\[7,'A',2],\[10,'A',1]],\[\[9,'A',2],\[11,'A',1]],\[\[10,'A',2],\[12,'A',1]]]}"}
- power_cell_type = /obj/item/cell/hyper
-
-/obj/prefab/test_heatcool
- name = "heating-cooling test"
- prefab_type = /singleton/prefab/ic_assembly/test_heatercooler
diff --git a/code/modules/integrated_electronics/core/printer.dm b/code/modules/integrated_electronics/core/printer.dm
index 37507116a81bf..dc92f3fa7e5c5 100644
--- a/code/modules/integrated_electronics/core/printer.dm
+++ b/code/modules/integrated_electronics/core/printer.dm
@@ -61,72 +61,125 @@
qdel(O)
return TRUE
-/obj/item/device/integrated_circuit_printer/attackby(obj/item/O, mob/user)
- if(istype(O, /obj/item/stack/material))
- var/obj/item/stack/material/M = O
- var/amt = M.amount
- if(amt * SHEET_MATERIAL_AMOUNT + materials[M.material.name] > metal_max)
- amt = -round(-(metal_max - materials[M.material.name]) / SHEET_MATERIAL_AMOUNT) //round up
- if(M.use(amt))
- materials[M.material.name] = min(metal_max, materials[M.material.name] + amt * SHEET_MATERIAL_AMOUNT)
- to_chat(user, SPAN_WARNING("You insert [M.material.display_name] into \the [src]."))
- if(user)
- attack_self(user) // We're really bad at refreshing the UI, so this is the best we've got.
- if(istype(O, /obj/item/disk/integrated_circuit/upgrade/advanced))
- if(upgraded)
- to_chat(user, SPAN_WARNING("[src] already has this upgrade. "))
+
+/obj/item/device/integrated_circuit_printer/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Electronic Assembly - Recycle assembly
+ if (istype(tool, /obj/item/device/electronic_assembly))
+ var/obj/item/device/electronic_assembly/assembly = tool
+ if (assembly.battery)
+ USE_FEEDBACK_FAILURE("You must remove \the [tool]'s power cell before it can be recycled.")
+ return TRUE
+ if (recycling)
+ USE_FEEDBACK_FAILURE("\The [src] is already busy recycling.")
+ return TRUE
+ if (!length(assembly.assembly_components))
+ recycle(assembly)
+ return TRUE
+ if (!assembly.opened)
+ USE_FEEDBACK_FAILURE("\The [tool]'s panel needs to be open before you can recycle the components.")
+ return TRUE
+ for (var/obj/item/integrated_circuit/component as anything in assembly.assembly_components)
+ if (!component.removable)
+ USE_FEEDBACK_FAILURE(SPAN_WARNING("\The [tool] has an unremovable [component.name] in the casing, preventing you from recycling it."))
+ return TRUE
+ playsound(src, 'sound/items/electronic_assembly_emptying.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] starts recycling \a [tool] with \a [src]."),
+ SPAN_NOTICE("You start recycling \the [tool] with \the [src].")
+ )
+ if (!do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE) || !user.use_sanity_check(src, tool))
+ return TRUE
+ if (assembly.battery)
+ USE_FEEDBACK_FAILURE("You must remove \the [tool]'s power cell before it can be recycled.")
+ return TRUE
+ if (recycling)
+ USE_FEEDBACK_FAILURE("\The [src] is already busy recycling.")
+ return TRUE
+ if (!length(assembly.assembly_components))
+ USE_FEEDBACK_FAILURE("\The [src] no longer has components to recycle.")
+ return TRUE
+ if (!assembly.opened)
+ USE_FEEDBACK_FAILURE("\The [tool]'s panel needs to be open before you can recycle the components.")
+ return TRUE
+ for (var/obj/item/integrated_circuit/component as anything in assembly.assembly_components)
+ if (!component.removable)
+ USE_FEEDBACK_FAILURE(SPAN_WARNING("\The [tool] has an unremovable [component.name] in the casing, preventing you from recycling it."))
+ return TRUE
+ recycling = TRUE
+ for (var/component as anything in assembly.assembly_components)
+ recycle(component, null, assembly)
+ recycling = FALSE
+ playsound(src, 'sound/items/electronic_assembly_empty.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] recycles \a [tool]'s components with \a [src]."),
+ SPAN_NOTICE("You recycle \the [tool]'s components with \the [src].")
+ )
+ return TRUE
+
+ // Integrated Circuit - Recycle circuit
+ if (istype(tool, /obj/item/integrated_circuit))
+ recycle(tool, user)
+ return TRUE
+
+ // Integrated Circuit Printer Advanced Upgrade Disk - Upgrade printer
+ if (istype(tool, /obj/item/disk/integrated_circuit/upgrade/advanced))
+ if (upgraded)
+ USE_FEEDBACK_FAILURE("\The [src] already has the advanced designs upgrade.")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
return TRUE
- to_chat(user, SPAN_NOTICE("You install [O] into [src]. "))
upgraded = TRUE
- if(user)
- attack_self(user)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] installs \a [tool] into \a [src]."),
+ SPAN_NOTICE("You install \the [tool] into \the [src].")
+ )
+ attack_self(user)
+ qdel(tool)
return TRUE
- if(istype(O, /obj/item/disk/integrated_circuit/upgrade/clone))
- if(fast_clone)
- to_chat(user, SPAN_WARNING("[src] already has this upgrade. "))
+ // Integrated Circuit Printer Clone Upgrade Disk - Upgrade printer
+ if (istype(tool, /obj/item/disk/integrated_circuit/upgrade/clone))
+ if (fast_clone)
+ USE_FEEDBACK_FAILURE("\The [src] already has the instant cloner upgrade.")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
return TRUE
- to_chat(user, SPAN_NOTICE("You install [O] into [src]. Circuit cloning will now be instant. "))
fast_clone = TRUE
- if(user)
- attack_self(user)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] installs \a [tool] into \a [src]."),
+ SPAN_NOTICE("You install \the [tool] into \the [src].")
+ )
+ attack_self(user)
+ qdel(tool)
return TRUE
- if(istype(O, /obj/item/device/electronic_assembly))
- var/obj/item/device/electronic_assembly/EA = O //microtransactions not included
- if(EA.battery)
- to_chat(user, SPAN_WARNING("Remove [EA]'s power cell first!"))
- return
- if(length(EA.assembly_components))
- if(recycling)
- return
- if(!EA.opened)
- to_chat(user, SPAN_WARNING("You can't reach [EA]'s components to remove them!"))
- return
- for(var/V in EA.assembly_components)
- var/obj/item/integrated_circuit/IC = V
- if(!IC.removable)
- to_chat(user, SPAN_WARNING("[EA] has irremovable components in the casing, preventing you from emptying it."))
- return
- to_chat(user, SPAN_NOTICE("You begin recycling [EA]'s components..."))
- playsound(src, 'sound/items/electronic_assembly_emptying.ogg', 50, TRUE)
- if (!do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE) || recycling) //short channel so you don't accidentally start emptying out a complex assembly
- return
- recycling = TRUE
- for(var/V in EA.assembly_components)
- recycle(V, null, EA)
- to_chat(user, SPAN_NOTICE("You recycle all the components[length(EA.assembly_components) ? " you could " : " "]from [EA]!"))
- playsound(src, 'sound/items/electronic_assembly_empty.ogg', 50, TRUE)
- recycling = FALSE
+ // Material Stack - Add material
+ if (istype(tool, /obj/item/stack/material))
+ var/obj/item/stack/material/material_stack = tool
+ var/stack_amount = material_stack.amount
+ var/material_amount = stack_amount * SHEET_MATERIAL_AMOUNT
+ var/remaining_space = metal_max - materials[material_stack.get_material_name()]
+ // Limit inserted material to max or below
+ if (material_amount > remaining_space)
+ stack_amount = floor(remaining_space / SHEET_MATERIAL_AMOUNT)
+ material_amount = stack_amount * SHEET_MATERIAL_AMOUNT
+ // Insert material
+ if (!material_stack.use(stack_amount))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(material_stack, stack_amount, "to load \the [src].")
return TRUE
- else
- return recycle(EA, user)
-
- if(istype(O, /obj/item/integrated_circuit))
- return recycle(O, user)
+ materials[material_stack.get_material_name()] += material_amount
+ user.visible_message(
+ SPAN_NOTICE("\The [user] loads [material_stack.get_vague_name(stack_amount > 1)] into \a [src]."),
+ SPAN_NOTICE("You load [material_stack.get_exact_name(stack_amount)] into \the [src].")
+ )
+ attack_self(user)
+ return TRUE
return ..()
+
/obj/item/device/integrated_circuit_printer/attack_self(mob/user)
interact(user)
@@ -305,7 +358,7 @@
to_chat(usr, SPAN_NOTICE("You begin printing a custom assembly. This will take approximately [round(cloning_time/10)] seconds. You can still print \
off normal parts during this time."))
playsound(src, 'sound/items/poster_being_created.ogg', 50, TRUE)
- addtimer(new Callback(src, .proc/print_program, usr), cloning_time)
+ addtimer(CALLBACK(src, PROC_REF(print_program), usr), cloning_time)
if("cancel")
if(!cloning || !program)
diff --git a/code/modules/integrated_electronics/core/wirer.dm b/code/modules/integrated_electronics/core/wirer.dm
index b2cce9bc41a6e..eb1f998d21464 100644
--- a/code/modules/integrated_electronics/core/wirer.dm
+++ b/code/modules/integrated_electronics/core/wirer.dm
@@ -81,7 +81,7 @@
if(selected_io)
unselect_io(selected_io)
selected_io = io
- GLOB.destroyed_event.register(selected_io, src, .proc/unselect_io)
+ GLOB.destroyed_event.register(selected_io, src, PROC_REF(unselect_io))
switch(mode)
if(UNWIRE)
mode = UNWIRING
diff --git a/code/modules/integrated_electronics/passive/power.dm b/code/modules/integrated_electronics/passive/power.dm
index 09e06b001e9e5..8c56eb6910736 100644
--- a/code/modules/integrated_electronics/passive/power.dm
+++ b/code/modules/integrated_electronics/passive/power.dm
@@ -76,7 +76,7 @@
desc = "A seemingly enigmatic device which connects to nearby APCs wirelessly and draws power from them, now in industrial size!"
w_class = ITEM_SIZE_LARGE
extended_desc = "The siphon drains 2 kW of power from an APC in the same room as it as long as it has charge remaining. It will always drain \
- from the 'equipment' power channel."
+ from the 'equipment' power channel."
icon_state = "power_relay"
complexity = 15
spawn_flags = IC_SPAWN_RESEARCH
@@ -89,7 +89,7 @@
desc = "Produces electricity from chemicals."
icon_state = "chemical_cell"
extended_desc = "This is effectively an internal beaker. It will consume and produce power from phoron, welding fuel, carbon,\
- ethanol, nutriment, and blood in order of decreasing efficiency. It will consume fuel only if the battery can take more energy."
+ ethanol, nutriment, and blood in order of decreasing efficiency. It will consume fuel only if the battery can take more energy."
atom_flags = ATOM_FLAG_OPEN_CONTAINER
complexity = 4
inputs = list()
@@ -129,4 +129,4 @@
/obj/item/integrated_circuit/passive/power/chemical_cell/do_work()
set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
\ No newline at end of file
+ push_data()
diff --git a/code/modules/integrated_electronics/subtypes/converters.dm b/code/modules/integrated_electronics/subtypes/converters.dm
index 4974ff57ec967..173a6737f7397 100644
--- a/code/modules/integrated_electronics/subtypes/converters.dm
+++ b/code/modules/integrated_electronics/subtypes/converters.dm
@@ -315,7 +315,7 @@
/obj/item/integrated_circuit/converter/exploders/do_work()
var/strin = get_pin_data(IC_INPUT, 1)
var/delimiter = get_pin_data(IC_INPUT, 2)
- if(delimiter == null)
+ if(isnull(delimiter))
set_pin_data(IC_OUTPUT, 1, splittext(strin,null))
else
set_pin_data(IC_OUTPUT, 1, splittext(strin, delimiter))
diff --git a/code/modules/integrated_electronics/subtypes/filter.dm b/code/modules/integrated_electronics/subtypes/filter.dm
index 9069be935981b..8e11976fa9ad1 100644
--- a/code/modules/integrated_electronics/subtypes/filter.dm
+++ b/code/modules/integrated_electronics/subtypes/filter.dm
@@ -4,7 +4,7 @@
complexity = 2
activators = list("compare" = IC_PINTYPE_PULSE_IN, "if valid" = IC_PINTYPE_PULSE_OUT, "if not valid" = IC_PINTYPE_PULSE_OUT)
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
- icon = 'icons/obj/electronic_assemblies.dmi'
+ icon = 'icons/obj/assemblies/electronic_assemblies.dmi'
/obj/item/integrated_circuit/filter/proc/may_pass(input)
return FALSE
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index a68ca207843ba..d343598b79e10 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -9,7 +9,7 @@
message = "There is \a [src]."
else
message = "There is \a ["\improper[initial_name]"] labeled '[name]'."
- to_chat(user, message)
+ return SPAN_NOTICE("[message]")
/obj/item/integrated_circuit/input/button
@@ -41,8 +41,10 @@
activators = list("on toggle" = IC_PINTYPE_PULSE_OUT)
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
-/obj/item/integrated_circuit/input/toggle_button/emp_act()
- return // This is a mainly physical thing, not affected by electricity
+// This is a mainly physical thing, not affected by electricity
+/obj/item/integrated_circuit/input/toggle_button/emp_act(severity)
+ SHOULD_CALL_PARENT(FALSE)
+ return
/obj/item/integrated_circuit/input/toggle_button/get_topic_data(mob/user)
return list("Toggle [get_pin_data(IC_OUTPUT, 1) ? "Off" : "On"]" = "toggle=1")
@@ -474,7 +476,7 @@
set_pin_data(IC_OUTPUT, 1, turf_contents)
set_pin_data(IC_OUTPUT, 3, area_name)
var/list/St = new()
- for(var/obj/effect/decal/cleanable/crayon/I in scanned_turf)
+ for(var/obj/decal/cleanable/crayon/I in scanned_turf)
St.Add(I.icon_state)
if(length(St))
set_pin_data(IC_OUTPUT, 2, jointext(St, ",", 1, 0))
@@ -806,7 +808,7 @@
. = list()
. += "Current selection: [(current_console && current_console.id) || "None"]"
. += "Please select a teleporter to lock in on:"
- for (var/obj/machinery/computer/teleporter/computer in SSmachines.machinery)
+ for (var/obj/machinery/computer/teleporter/computer as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/teleporter))
if (computer.target && computer.operable() && AreConnectedZLevels(get_z(src), get_z(computer)))
.["[computer.id] ([computer.active ? "Active" : "Inactive"])"] = "tport=[any2ref(computer)]"
.["None (Dangerous)"] = "tport=random"
@@ -1185,8 +1187,8 @@
name = "data card reader"
desc = "A circuit that can read from and write to data cards."
extended_desc = "Setting the \"write mode\" boolean to true will cause any data cards that are used on the assembly to replace\
- their existing function and data strings with the given strings, if it is set to false then using a data card on the assembly will cause\
- the function and data strings stored on the card to be written to the output pins."
+their existing function and data strings with the given strings, if it is set to false then using a data card on the assembly will cause\
+the function and data strings stored on the card to be written to the output pins."
icon_state = "card_reader"
complexity = 4
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm
index 5472bec8d95fb..2f08f41a18645 100644
--- a/code/modules/integrated_electronics/subtypes/manipulation.dm
+++ b/code/modules/integrated_electronics/subtypes/manipulation.dm
@@ -207,7 +207,7 @@
dt = clamp(detonation_time.data, 1, 12)*10
else
dt = 15
- addtimer(new Callback(attached_grenade, /obj/item/grenade.proc/activate), dt)
+ addtimer(CALLBACK(attached_grenade, TYPE_PROC_REF(/obj/item/grenade, activate)), dt)
var/atom/holder = loc
log_and_message_admins("activated a grenade assembly. Last touches: Assembly: [holder.fingerprintslast] Circuit: [fingerprintslast] Grenade: [attached_grenade.fingerprintslast]")
@@ -250,7 +250,7 @@
activate_pin(2)
return
- if(istype(OM,/obj/effect/vine) && check_target(OM) && get_pin_data(IC_INPUT, 2) == 2)
+ if(istype(OM,/obj/vine) && check_target(OM) && get_pin_data(IC_INPUT, 2) == 2)
qdel(OM)
push_data()
activate_pin(2)
@@ -420,9 +420,9 @@
set_pin_data(IC_OUTPUT, 1, TRUE)
pulling = to_pull
acting_object.visible_message("\The [acting_object] starts pulling \the [to_pull] around.")
- GLOB.moved_event.register(to_pull, src, .proc/check_pull) //Whenever the target moves, make sure we can still pull it!
- GLOB.destroyed_event.register(to_pull, src, .proc/stop_pulling) //Stop pulling if it gets destroyed
- GLOB.moved_event.register(acting_object, src, .proc/pull) //Make sure we actually pull it.
+ GLOB.moved_event.register(to_pull, src, PROC_REF(check_pull)) //Whenever the target moves, make sure we can still pull it!
+ GLOB.destroyed_event.register(to_pull, src, PROC_REF(stop_pulling)) //Stop pulling if it gets destroyed
+ GLOB.moved_event.register(acting_object, src, PROC_REF(pull)) //Make sure we actually pull it.
push_data()
if(3)
if(pulling)
@@ -582,7 +582,7 @@
step_dir = assembly.dir
depart = get_step(depart, step_dir) || depart
- new /obj/effect/portal(depart, arrive, 30 SECONDS, 33)
+ new /obj/portal(depart, arrive, 30 SECONDS, 33)
playsound(src, 'sound/effects/sparks2.ogg', 50, 1)
diff --git a/code/modules/integrated_electronics/subtypes/memory.dm b/code/modules/integrated_electronics/subtypes/memory.dm
index b847f51e552f2..4479af07c9f35 100644
--- a/code/modules/integrated_electronics/subtypes/memory.dm
+++ b/code/modules/integrated_electronics/subtypes/memory.dm
@@ -30,7 +30,7 @@
data = "[d]"
else if(!isnull(O.data))
data = O.data
- to_chat(user, "\The [src] has [data] saved to address [i].")
+ . += SPAN_NOTICE("[src] has [data] saved to address [i].")
/obj/item/integrated_circuit/memory/do_work()
for(var/i = 1 to length(inputs))
@@ -129,12 +129,12 @@
O.data = null
to_chat(user, SPAN_NOTICE("You set \the [src]'s memory to absolutely nothing."))
-/obj/item/integrated_circuit/memory/constant/afterattack(atom/target, mob/living/user, proximity)
- . = ..()
- if(accepting_refs && proximity)
+/obj/item/integrated_circuit/memory/constant/use_after(atom/target, mob/living/user, click_parameters)
+ if (accepting_refs)
var/datum/integrated_io/O = outputs[1]
O.data = weakref(target)
visible_message(SPAN_NOTICE("[user] slides \a [src]'s over \the [target]."))
to_chat(user, SPAN_NOTICE("You set \the [src]'s memory to a reference to [O.display_data(O.data)]. The ref scanner is \
now off."))
accepting_refs = FALSE
+ return TRUE
diff --git a/code/modules/integrated_electronics/subtypes/output.dm b/code/modules/integrated_electronics/subtypes/output.dm
index 7722ef377c973..e2fb3e893f8f1 100644
--- a/code/modules/integrated_electronics/subtypes/output.dm
+++ b/code/modules/integrated_electronics/subtypes/output.dm
@@ -23,7 +23,7 @@
if(displayed_name && displayed_name != name)
shown_label = " labeled '[displayed_name]'"
- to_chat(user, "There is \a [src][shown_label], which displays [!isnull(stuff_to_display) ? "'[stuff_to_display]'" : "nothing"].")
+ return SPAN_NOTICE("There is [src][shown_label], which displays [!isnull(stuff_to_display) ? "'[stuff_to_display]'" : "nothing"].")
/obj/item/integrated_circuit/output/screen/get_topic_data()
return stuff_to_display ? list(stuff_to_display) : list()
@@ -83,7 +83,7 @@
/obj/item/integrated_circuit/output/light/proc/update_lighting()
if(light_toggled)
if(assembly)
- assembly.set_light(light_brightness, 1, 4, 2, light_rgb)
+ assembly.set_light(4, light_brightness, light_rgb)
else
if(assembly)
assembly.set_light(0)
@@ -265,6 +265,7 @@
/obj/item/integrated_circuit/output/video_camera/Initialize()
. = ..()
camera = new(src)
+ camera.set_stat_immunity(MACHINE_STAT_NOPOWER, TRUE)
camera.replace_networks(list())
on_data_written()
@@ -335,4 +336,4 @@
else
text_output += "\an ["\improper[name]"] labeled '[displayed_name]'"
text_output += " which is currently [get_pin_data(IC_INPUT, 1) ? "lit [SPAN_COLOR(led_color, "*")]" : "unlit"]."
- to_chat(user, text_output)
+ return ("[text_output]")
diff --git a/code/modules/integrated_electronics/subtypes/power.dm b/code/modules/integrated_electronics/subtypes/power.dm
index 6a9e5136a8a16..d472504e51a18 100644
--- a/code/modules/integrated_electronics/subtypes/power.dm
+++ b/code/modules/integrated_electronics/subtypes/power.dm
@@ -83,7 +83,7 @@
if(..()) // If the above code succeeds, do this below.
var/atom/movable/acting_object = get_object()
if(prob(20))
- var/datum/effect/effect/system/spark_spread/s = new()
+ var/datum/effect/spark_spread/s = new()
s.set_up(12, 1, src)
s.start()
acting_object.visible_message(SPAN_WARNING("\The [acting_object] makes some sparks!"))
diff --git a/code/modules/integrated_electronics/subtypes/reagents.dm b/code/modules/integrated_electronics/subtypes/reagents.dm
deleted file mode 100644
index 2626a6af72d04..0000000000000
--- a/code/modules/integrated_electronics/subtypes/reagents.dm
+++ /dev/null
@@ -1,648 +0,0 @@
-#define IC_SMOKE_REAGENTS_MINIMUM_UNITS 10
-#define IC_REAGENTS_DRAW 0
-#define IC_REAGENTS_INJECT 1
-#define IC_HEATER_MODE_HEAT "heat"
-#define IC_HEATER_MODE_COOL "cool"
-
-/obj/item/integrated_circuit/reagent
- category_text = "Reagent"
- unacidable = TRUE
- cooldown_per_use = 10
- var/volume = 0
-
-/obj/item/integrated_circuit/reagent/Initialize()
- . = ..()
- if(volume)
- create_reagents(volume)
- push_vol()
-
-/obj/item/integrated_circuit/reagent/proc/push_vol()
- set_pin_data(IC_OUTPUT, 1, reagents.total_volume)
- push_data()
-
-/obj/item/integrated_circuit/reagent/smoke
- name = "smoke generator"
- desc = "Unlike most electronics, creating smoke is completely intentional."
- icon_state = "smoke"
- extended_desc = "This smoke generator creates clouds of smoke on command. It can also hold liquids inside, which will go \
- into the smoke clouds when activated. The reagents are consumed when the smoke is made."
- ext_cooldown = 1
- atom_flags = ATOM_FLAG_OPEN_CONTAINER
- volume = 100
-
- complexity = 20
- cooldown_per_use = 1 SECONDS
- inputs = list()
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "self reference" = IC_PINTYPE_REF
- )
- activators = list(
- "create smoke" = IC_PINTYPE_PULSE_IN,
- "on smoked" = IC_PINTYPE_PULSE_OUT,
- "push ref" = IC_PINTYPE_PULSE_IN
- )
- spawn_flags = IC_SPAWN_RESEARCH
- power_draw_per_use = 20
- var/smoke_radius = 5
- var/notified = FALSE
-
-/obj/item/integrated_circuit/reagent/smoke/on_reagent_change()
- push_vol()
-
-/obj/item/integrated_circuit/reagent/smoke/do_work(ord)
- switch(ord)
- if(1)
- if(!reagents || (reagents.total_volume < IC_SMOKE_REAGENTS_MINIMUM_UNITS))
- return
- var/location = get_turf(src)
- var/datum/effect/effect/system/smoke_spread/chem/S = new
- S.attach(location)
- playsound(location, 'sound/effects/smoke.ogg', 50, 1, -3)
- if(S)
- S.set_up(reagents, smoke_radius, 0, location)
- if(!notified)
- notified = TRUE
- S.start()
- reagents.clear_reagents()
- activate_pin(2)
- if(3)
- set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/injector
- name = "integrated hypo-injector"
- desc = "This scary looking thing is able to pump liquids into, or suck liquids out of, whatever it's pointed at."
- icon_state = "injector"
- extended_desc = "This autoinjector can push up to 30 units of reagents into another container or someone else outside of the machine. The target \
- must be adjacent to the machine, and if it is a person, they cannot be wearing thick clothing. Negative given amounts makes the injector suck out reagents instead."
-
- atom_flags = ATOM_FLAG_OPEN_CONTAINER
- volume = 30
-
- complexity = 20
- cooldown_per_use = 6 SECONDS
- inputs = list(
- "target" = IC_PINTYPE_REF,
- "injection amount" = IC_PINTYPE_NUMBER
- )
- inputs_default = list(
- "2" = 5
- )
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "self reference" = IC_PINTYPE_REF
- )
- activators = list(
- "inject" = IC_PINTYPE_PULSE_IN,
- "on injected" = IC_PINTYPE_PULSE_OUT,
- "on fail" = IC_PINTYPE_PULSE_OUT,
- "push ref" = IC_PINTYPE_PULSE_IN
-
- )
- spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
- power_draw_per_use = 15
- var/direction_mode = IC_REAGENTS_INJECT
- var/transfer_amount = 10
- var/busy = FALSE
-
-/obj/item/integrated_circuit/reagent/injector/on_reagent_change(changetype)
- push_vol()
-
-/obj/item/integrated_circuit/reagent/injector/on_data_written()
- var/new_amount = get_pin_data(IC_INPUT, 2)
- if(new_amount < 0)
- new_amount = -new_amount
- direction_mode = IC_REAGENTS_DRAW
- else
- direction_mode = IC_REAGENTS_INJECT
- if(isnum(new_amount))
- new_amount = clamp(new_amount, 0, volume)
- transfer_amount = new_amount
-
-
-/obj/item/integrated_circuit/reagent/injector/do_work(ord)
- switch(ord)
- if(1)
- inject()
- if(4)
- set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/injector/proc/target_nearby(weakref/target)
- var/mob/living/L = target.resolve()
- if(!L || get_dist(src,L) > 1)
- return
- return L
-
-/obj/item/integrated_circuit/reagent/injector/proc/inject_after(weakref/target)
- busy = FALSE
- var/mob/living/L = target_nearby(target)
- if(!L)
- activate_pin(3)
- return
- var/atom/movable/acting_object = get_object()
- log_admin("[key_name(L)] was successfully injected with " + reagents.get_reagents() + " by \the [acting_object]")
- L.visible_message(SPAN_WARNING("\The [acting_object] injects [L] with its needle!"), \
- SPAN_WARNING("\The [acting_object] injects you with its needle!"))
- reagents.trans_to_mob(L, transfer_amount, CHEM_BLOOD)
- activate_pin(2)
-
-/obj/item/integrated_circuit/reagent/injector/proc/draw_after(weakref/target, amount)
- busy = FALSE
- var/mob/living/carbon/C = target_nearby(target)
- if(!C)
- activate_pin(3)
- return
- var/atom/movable/acting_object = get_object()
-
- C.visible_message(SPAN_WARNING("\The [acting_object] draws blood from \the [C]"),
- SPAN_WARNING("\The [acting_object] draws blood from you.")
- )
- C.take_blood(src, amount)
- activate_pin(2)
-
-
-/obj/item/integrated_circuit/reagent/injector/proc/inject()
- set waitfor = FALSE // Don't sleep in a proc that is called by a processor without this set, otherwise it'll delay the entire thing
- var/atom/movable/AM = get_pin_data_as_type(IC_INPUT, 1, /atom/movable)
- var/atom/movable/acting_object = get_object()
-
- if(busy || !check_target(AM))
- activate_pin(3)
- return
-
- if(!AM.reagents)
- activate_pin(3)
- return
-
- if(direction_mode == IC_REAGENTS_INJECT)
- if(!reagents.total_volume || !AM.reagents || !AM.reagents.get_free_space())
- activate_pin(3)
- return
-
- if(isliving(AM))
- var/mob/living/L = AM
- var/injection_status = L.can_inject(null, BP_CHEST)
- log_world("Injection status? [injection_status]")
- var/injection_delay = 3 SECONDS
- if(injection_status == INJECTION_PORT)
- injection_delay += INJECTION_PORT_DELAY
- if(!injection_status)
- activate_pin(3)
- return
- //Always log attemped injections for admins
- log_admin("[key_name(L)] is getting injected with " + reagents.get_reagents() + " by \the [acting_object]")
- L.visible_message(SPAN_DANGER("\The [acting_object] is trying to inject [L]!"), \
- SPAN_DANGER("\The [acting_object] is trying to inject you!"))
- busy = TRUE
- addtimer(new Callback(src, .proc/inject_after, weakref(L)), injection_delay)
- return
- else
- if(!AM.is_open_container())
- activate_pin(3)
- return
-
-
- reagents.trans_to(AM, transfer_amount)
-
- else if(direction_mode == IC_REAGENTS_DRAW)
- if(reagents.total_volume >= reagents.maximum_volume)
- acting_object.visible_message("\The [acting_object] tries to draw from [AM], but the injector is full.")
- activate_pin(3)
- return
-
- var/tramount = abs(transfer_amount)
-
- if(istype(AM, /mob/living/carbon))
- var/mob/living/carbon/C = AM
- var/injection_status = C.can_inject(null, BP_CHEST)
- var/injection_delay = 3 SECONDS
- if(injection_status == INJECTION_PORT)
- injection_delay += INJECTION_PORT_DELAY
- if(istype(C, /mob/living/carbon/slime) || !C.dna || !injection_status)
- activate_pin(3)
- return
- C.visible_message(SPAN_DANGER("\The [acting_object] is trying to take a blood sample from [C]!"), \
- SPAN_DANGER("\The [acting_object] is trying to take a blood sample from you!"))
- busy = TRUE
- addtimer(new Callback(src, .proc/draw_after, weakref(C), tramount), injection_delay)
- return
-
- else
- if(!AM.reagents.total_volume)
- acting_object.visible_message(SPAN_NOTICE("\The [acting_object] tries to draw from [AM], but it is empty!"))
- activate_pin(3)
- return
-
- if(!AM.is_open_container())
- activate_pin(3)
- return
- tramount = min(tramount, AM.reagents.total_volume)
- AM.reagents.trans_to(src, tramount)
- activate_pin(2)
-
-
-
-/obj/item/integrated_circuit/reagent/pump
- name = "reagent pump"
- desc = "Moves liquids safely inside a machine, or even nearby it."
- icon_state = "reagent_pump"
- extended_desc = "This is a pump which will move liquids from the source ref to the target ref. The third pin determines \
- how much liquid is moved per pulse, between 0 and 50. The pump can move reagents to any open container inside the machine, or \
- outside the machine if it is adjacent to the machine."
-
- complexity = 8
- inputs = list("source" = IC_PINTYPE_REF, "target" = IC_PINTYPE_REF, "injection amount" = IC_PINTYPE_NUMBER)
- inputs_default = list("3" = 5)
- outputs = list()
- activators = list("transfer reagents" = IC_PINTYPE_PULSE_IN, "on transfer" = IC_PINTYPE_PULSE_OUT)
- spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
- var/transfer_amount = 10
- var/direction_mode = IC_REAGENTS_INJECT
- power_draw_per_use = 10
-
-/obj/item/integrated_circuit/reagent/pump/on_data_written()
- var/new_amount = get_pin_data(IC_INPUT, 3)
- if(new_amount < 0)
- new_amount = -new_amount
- direction_mode = IC_REAGENTS_DRAW
- else
- direction_mode = IC_REAGENTS_INJECT
- if(isnum(new_amount))
- new_amount = clamp(new_amount, 0, 50)
- transfer_amount = new_amount
-
-/obj/item/integrated_circuit/reagent/pump/do_work()
- var/atom/movable/source = get_pin_data_as_type(IC_INPUT, 1, /atom/movable)
- var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 2, /atom/movable)
-
- // Check for invalid input.
- if(!check_target(source) || !check_target(target))
- return
-
- // If the pump is pumping backwards, swap target and source.
- if(!direction_mode)
- var/temp_source = source
- source = target
- target = temp_source
-
- if(!source.reagents)
- return
-
- if(!source.is_open_container())
- return
-
- source.reagents.trans_to(target, transfer_amount)
- activate_pin(2)
-
-/obj/item/integrated_circuit/reagent/storage
- cooldown_per_use = 1
- name = "reagent storage"
- desc = "Stores liquid inside the device away from electrical components. It can store up to 60u."
- icon_state = "reagent_storage"
- extended_desc = "This is effectively an internal beaker."
-
- atom_flags = ATOM_FLAG_OPEN_CONTAINER
- volume = 60
-
- complexity = 4
- inputs = list()
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "self reference" = IC_PINTYPE_REF
- )
- activators = list("push ref" = IC_PINTYPE_PULSE_IN)
- spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
-
-
-
-/obj/item/integrated_circuit/reagent/storage/do_work()
- set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/storage/on_reagent_change(changetype)
- push_vol()
-
-/obj/item/integrated_circuit/reagent/storage/big
- name = "big reagent storage"
- icon_state = "reagent_storage_big"
- desc = "Stores liquid inside the device away from electrical components. Can store up to 180u."
-
- volume = 180
-
- complexity = 16
- spawn_flags = IC_SPAWN_RESEARCH
-
-/obj/item/integrated_circuit/reagent/storage/cryo
- name = "cryo reagent storage"
- desc = "Stores liquid inside the device away from electrical components. It can store up to 60u. This will also prevent reactions."
- icon_state = "reagent_storage_cryo"
- extended_desc = "This is effectively an internal cryo beaker."
-
- atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_OPEN_CONTAINER | ATOM_FLAG_NO_REACT
- complexity = 8
- spawn_flags = IC_SPAWN_RESEARCH
-
-/obj/item/integrated_circuit/reagent/storage/grinder
- name = "reagent grinder"
- desc = "This is a reagent grinder. It accepts a ref to something, and refines it into reagents. It cannot grind materials. It can store up to 100u."
- icon_state = "blender"
- extended_desc = ""
- inputs = list(
- "target" = IC_PINTYPE_REF,
- )
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "self reference" = IC_PINTYPE_REF
- )
- activators = list(
- "grind" = IC_PINTYPE_PULSE_IN,
- "on grind" = IC_PINTYPE_PULSE_OUT,
- "on fail" = IC_PINTYPE_PULSE_OUT,
- "push ref" = IC_PINTYPE_PULSE_IN
- )
- volume = 100
- power_draw_per_use = 150
- complexity = 16
- spawn_flags = IC_SPAWN_RESEARCH
-
-
-/obj/item/integrated_circuit/reagent/storage/grinder/do_work(ord)
- switch(ord)
- if(1)
- grind()
- if(4)
- set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/storage/grinder/proc/grind()
- if(reagents.total_volume >= reagents.maximum_volume)
- activate_pin(3)
- return FALSE
- var/obj/item/I = get_pin_data_as_type(IC_INPUT, 1, /obj/item)
-
- if(isnull(I))
- return FALSE
-
- if(!I.reagents || !I.reagents.total_volume)
- activate_pin(3)
- return FALSE
-
- I.reagents.trans_to(src,I.reagents.total_volume)
- if(!I.reagents.total_volume)
- qdel(I)
-
- activate_pin(2)
- return FALSE
-
-
-
-/obj/item/integrated_circuit/reagent/storage/scan
- name = "reagent scanner"
- desc = "Stores liquid inside the device away from electrical components. It can store up to 60u. On pulse this beaker will send list of contained reagents."
- icon_state = "reagent_scan"
- extended_desc = "Mostly useful for filtering reagents."
-
- complexity = 8
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "self reference" = IC_PINTYPE_REF,
- "list of reagents" = IC_PINTYPE_LIST
- )
- activators = list(
- "scan" = IC_PINTYPE_PULSE_IN,
- "push ref" = IC_PINTYPE_PULSE_IN
- )
- spawn_flags = IC_SPAWN_RESEARCH
-
-/obj/item/integrated_circuit/reagent/storage/scan/do_work(ord)
- switch(ord)
- if(1)
- var/cont[0]
- for(var/datum/reagent/RE in reagents.reagent_list)
- cont += RE.name
- set_pin_data(IC_OUTPUT, 3, cont)
- push_data()
- if(2)
- set_pin_data(IC_OUTPUT, 2, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/filter
- name = "reagent filter"
- desc = "Filters liquids by list of desired or unwanted reagents."
- icon_state = "reagent_filter"
- extended_desc = "This is a filter which will move liquids from the source to its target. \
- If the amount in the fourth pin is positive, it will move all reagents except those in the unwanted list. \
- If the amount in the fourth pin is negative, it will only move the reagents in the wanted list. \
- The third pin determines how many reagents are moved per pulse, between 0 and 50. Amount is given for each separate reagent."
-
- complexity = 8
- inputs = list(
- "source" = IC_PINTYPE_REF,
- "target" = IC_PINTYPE_REF,
- "injection amount" = IC_PINTYPE_NUMBER,
- "list of reagents" = IC_PINTYPE_LIST
- )
- inputs_default = list(
- "3" = 5
- )
- outputs = list()
- activators = list(
- "transfer reagents" = IC_PINTYPE_PULSE_IN,
- "on transfer" = IC_PINTYPE_PULSE_OUT
- )
- spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
- var/transfer_amount = 10
- var/direction_mode = IC_REAGENTS_INJECT
- power_draw_per_use = 10
-
-/obj/item/integrated_circuit/reagent/filter/on_data_written()
- var/new_amount = get_pin_data(IC_INPUT, 3)
- if(new_amount < 0)
- new_amount = -new_amount
- direction_mode = IC_REAGENTS_DRAW
- else
- direction_mode = IC_REAGENTS_INJECT
- if(isnum(new_amount))
- new_amount = clamp(new_amount, 0, 50)
- transfer_amount = new_amount
-
-/obj/item/integrated_circuit/reagent/filter/do_work()
- var/atom/movable/source = get_pin_data_as_type(IC_INPUT, 1, /atom/movable)
- var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 2, /atom/movable)
- var/list/demand = get_pin_data(IC_INPUT, 4)
-
- // Check for invalid input.
- if(!check_target(source) || !check_target(target))
- return
-
- if(!source.reagents || !target.reagents)
- return
-
- if(!source.is_open_container() || istype(source, /mob))
- return
-
- if(target.reagents.maximum_volume - target.reagents.total_volume <= 0)
- return
-
- for(var/datum/reagent/G in source.reagents.reagent_list)
- if(!direction_mode)
- if(G.name in demand)
- source.reagents.trans_type_to(target, G.type, transfer_amount)
- else
- if(!(G.name in demand))
- source.reagents.trans_type_to(target, G.type, transfer_amount)
- activate_pin(2)
- push_data()
-
-// This is an input circuit because attackby_react is only called for input circuits
-/obj/item/integrated_circuit/input/funnel
- category_text = "Reagent"
- name = "reagent funnel"
- desc = "A funnel with a small pump that lets you refill an internal reagent storage."
- icon_state = "reagent_funnel"
-
- inputs = list(
- "target" = IC_PINTYPE_REF
- )
- activators = list(
- "on transfer" = IC_PINTYPE_PULSE_OUT
- )
-
- unacidable = TRUE
- spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
- complexity = 4
- power_draw_per_use = 5
-
-/obj/item/integrated_circuit/input/funnel/attackby_react(obj/item/I, mob/living/user, intent)
- var/atom/movable/target = get_pin_data_as_type(IC_INPUT, 1, /atom/movable)
- var/obj/item/reagent_containers/container = I
-
- if(!check_target(target))
- return FALSE
-
- if(!istype(container))
- return FALSE
-
- // Messages are provided by standard_pour_into
- if(container.standard_pour_into(user, target))
- activate_pin(1)
- return TRUE
-
- return FALSE
-
-// Most of this is just chemical heater code refitted for ICs
-/obj/item/integrated_circuit/reagent/temp
- inputs = list(
- "target temperature" = IC_PINTYPE_NUMBER
- )
- outputs = list(
- "volume used" = IC_PINTYPE_NUMBER,
- "temperature" = IC_PINTYPE_NUMBER,
- "enabled" = IC_PINTYPE_BOOLEAN,
- "self reference" = IC_PINTYPE_REF
- )
- activators = list(
- "toggle" = IC_PINTYPE_PULSE_IN,
- "on toggle" = IC_PINTYPE_PULSE_OUT,
- "push ref" = IC_PINTYPE_PULSE_IN
- )
-
- atom_flags = ATOM_FLAG_OPEN_CONTAINER
- complexity = 12
- cooldown_per_use = 1
- power_draw_per_use = 50
- volume = 30
-
- var/active = 0
- var/min_temp = 40 CELSIUS
- var/max_temp = 200 CELSIUS
- var/heating_power = 5
- var/target_temp = T20C
- var/last_temperature = 0
- var/mode = IC_HEATER_MODE_HEAT
-
-/obj/item/integrated_circuit/reagent/temp/Initialize()
- . = ..()
-
- set_pin_data(IC_OUTPUT, 2, temperature - T0C)
- push_data()
-
-/obj/item/integrated_circuit/reagent/temp/do_work(ord)
- switch(ord)
- if(1)
- target_temp = get_pin_data(IC_INPUT, 1)
- if(isnull(target_temp))
- return
-
- // +/- T0C to convert to/from kelvin
- target_temp = clamp(target_temp + T0C, min_temp, max_temp)
- set_pin_data(IC_INPUT, 1, target_temp - T0C)
-
- active = !active
- set_pin_data(IC_OUTPUT, 3, active)
- push_data()
- activate_pin(2)
-
- // begin processing temperature
- if(active)
- QUEUE_TEMPERATURE_ATOMS(src)
- if(3)
- set_pin_data(IC_OUTPUT, 4, weakref(src))
- push_data()
-
-/obj/item/integrated_circuit/reagent/temp/on_reagent_change()
- push_vol()
-
-/obj/item/integrated_circuit/reagent/temp/power_fail()
- active = 0
-
-/obj/item/integrated_circuit/reagent/temp/ProcessAtomTemperature()
- if(!active)
- return PROCESS_KILL
-
- last_temperature = temperature
-
- if(mode == IC_HEATER_MODE_HEAT && temperature < target_temp)
- temperature = min(temperature + heating_power, max_temp)
- else if(mode == IC_HEATER_MODE_COOL && temperature > target_temp)
- temperature = max(temperature - heating_power, min_temp)
-
- if(temperature != last_temperature)
- // Lost power
- if(!check_power())
- power_fail()
- return ..()
-
- set_pin_data(IC_OUTPUT, 2, temperature - T0C)
- push_data()
-
- return TRUE
-
-/obj/item/integrated_circuit/reagent/temp/heater
- name = "reagent heater"
- desc = "A small reagent container capable of heating reagents. It can hold up to 30u."
- icon_state = "reagent_heater"
- extended_desc = "This is effectively an internal beaker. It has a heating coil wrapped around it, which allows it to heat the contents of the beaker. Temperature is given in celsius."
-
- spawn_flags = IC_SPAWN_RESEARCH
-
-/obj/item/integrated_circuit/reagent/temp/cooler
- name = "reagent cooler"
- desc = "A small reagent container capable of cooling reagents. It can hold up to 30u."
- icon_state = "reagent_cooler"
- extended_desc = "This is effectively an internal beaker. It has a cooling mechanism wrapped around it, which allows it to cool the contents of the beaker. Temperature is given in celsius."
-
- spawn_flags = IC_SPAWN_RESEARCH
-
- min_temp = -80 CELSIUS
- max_temp = 30 CELSIUS
- mode = IC_HEATER_MODE_COOL
-
-//undefs
-
-#undef IC_HEATER_MODE_HEAT
-#undef IC_HEATER_MODE_COOL
-#undef IC_REAGENTS_DRAW
-#undef IC_REAGENTS_INJECT
diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm
index 0c97a81575cdb..773e3ec4fe2e8 100644
--- a/code/modules/integrated_electronics/subtypes/smart.dm
+++ b/code/modules/integrated_electronics/subtypes/smart.dm
@@ -119,14 +119,14 @@
if(Pl&&islist(Pl))
idc.access = Pl
var/turf/a_loc = get_turf(assembly)
- var/list/P = AStar(a_loc, locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 200, id=idc, exclude=get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom)))
+ var/list/P = AStar(a_loc, locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, 200, id=idc, exclude=get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom)))
if(!P)
activate_pin(3)
return
else
- var/list/Xn = new/list(length(P))
- var/list/Yn = new/list(length(P))
+ var/list/Xn = new(length(P))
+ var/list/Yn = new(length(P))
var/turf/T
for(var/i =1 to length(P))
T=P[i]
diff --git a/code/modules/integrated_electronics/subtypes/time.dm b/code/modules/integrated_electronics/subtypes/time.dm
index 8f621e9d375ba..2d4558a9e67fb 100644
--- a/code/modules/integrated_electronics/subtypes/time.dm
+++ b/code/modules/integrated_electronics/subtypes/time.dm
@@ -17,7 +17,7 @@
power_draw_per_use = 2
/obj/item/integrated_circuit/time/delay/do_work()
- addtimer(new Callback(src, .proc/activate_pin, 2), delay)
+ addtimer(CALLBACK(src, PROC_REF(activate_pin), 2), delay)
/obj/item/integrated_circuit/time/delay/five_sec
name = "five-sec delay circuit"
@@ -98,7 +98,7 @@
/obj/item/integrated_circuit/time/ticker/proc/tick()
if(is_running)
- addtimer(new Callback(src, .proc/tick), delay)
+ addtimer(CALLBACK(src, PROC_REF(tick)), delay)
if(world.time > next_fire)
next_fire = world.time + delay
activate_pin(1)
diff --git a/code/modules/item_worth/worths_list.dm b/code/modules/item_worth/worths_list.dm
index 6300832abb36b..c2cd676b6be00 100644
--- a/code/modules/item_worth/worths_list.dm
+++ b/code/modules/item_worth/worths_list.dm
@@ -78,8 +78,8 @@ var/global/list/worths = list(
/obj/item/stack/material/cloth = -3,
/obj/item/stack/material/cardboard = -1,
/obj/item/stack/material/glass/reinforced = -12,
- /obj/item/stack/material/glass/phoronglass = -35,
- /obj/item/stack/material/glass/phoronrglass = -65,
+ /obj/item/stack/material/glass/boron = -35,
+ /obj/item/stack/material/glass/boron_reinforced = -65,
/obj/item/stack/material = -5,
//STACKS,
/obj/item/stack/medical/advanced/bruise_pack = -30,
@@ -99,12 +99,12 @@ var/global/list/worths = list(
/obj/item/ore = -10,
/obj/item/device/scanner/mining = 130,
//PICK AXES,
- /obj/item/pickaxe/silver = 300,
+ /obj/item/pickaxe/hand/silver = 300,
/obj/item/pickaxe/drill = 100,
/obj/item/pickaxe/jackhammer = 90,
- /obj/item/pickaxe/gold = 400,
+ /obj/item/pickaxe/hand/gold = 400,
/obj/item/gun/energy/plasmacutter = 100,
- /obj/item/pickaxe/diamond = 600,
+ /obj/item/pickaxe/hand/diamond = 600,
/obj/item/pickaxe/diamonddrill = 700,
/obj/item/pickaxe = 30,
/obj/item/shovel/spade = 20,
@@ -228,10 +228,8 @@ var/global/list/worths = list(
/obj/item/hemostat = 90,
/obj/item/cautery = 110,
/obj/item/surgicaldrill = 130,
- /obj/item/scalpel/laser1 = 300,
- /obj/item/scalpel/laser2 = 600,
- /obj/item/scalpel/laser3 = 900,
- /obj/item/scalpel/manager = 1200,
+ /obj/item/scalpel/laser = 900,
+ /obj/item/scalpel/ims = 1200,
/obj/item/scalpel = 60,
/obj/item/circular_saw = 140,
/obj/item/bonegel = 300,
@@ -361,10 +359,6 @@ var/global/list/worths = list(
/obj/item/clothing/mask/surgical = 20,
/obj/item/clothing/mask/fakemoustache = 5,
/obj/item/clothing/mask/snorkel = 5,
- /obj/item/clothing/mask/bluescarf = 10,
- /obj/item/clothing/mask/redscarf = 15,
- /obj/item/clothing/mask/greenscarf = 15,
- /obj/item/clothing/mask/ninjascarf = 15,
/obj/item/clothing/mask/ai = 3000,
/obj/item/clothing/mask/smokable/cigarette = 2,
/obj/item/clothing/mask/smokable/ecig/util = 100,
@@ -388,7 +382,7 @@ var/global/list/worths = list(
/obj/item/clothing/suit/armor/bulletproof = 500,
/obj/item/clothing/suit/armor/laserproof = 600,
/obj/item/clothing/suit/armor/reactive = 1000,
- /obj/item/clothing/suit/armor/centcomm = 2000,
+ /obj/item/clothing/suit/armor/centcom = 2000,
/obj/item/clothing/suit/armor/heavy = 1000,
/obj/item/clothing/suit/armor/captain = 2000,
/obj/item/clothing/suit/armor = 500,
@@ -424,7 +418,6 @@ var/global/list/worths = list(
/obj/item/clothing/head/centhat = 600,
/obj/item/clothing/head/hairflower = 5,
/obj/item/clothing/head/powdered_wig = 80,
- /obj/item/clothing/head/justice = 30,
/obj/item/clothing/head/philosopher_wig = 70,
/obj/item/clothing/head/welding = 150,
/obj/item/clothing/head/bomb_hood = 200,
@@ -443,7 +436,7 @@ var/global/list/worths = list(
/obj/item/clothing/head/helmet/space = 450,
/obj/item/clothing/head/caretakerhood = 10,
/obj/item/clothing/head/champhelm = 3200,
- /obj/item/clothing/head/bandana/familiarband = 5,
+ /obj/item/clothing/head/familiarband = 5,
/obj/item/clothing/head/fiendhood = 100,
/obj/item/clothing/head/infilhat = 80,
/obj/item/clothing/head/overseerhood = 50,
@@ -530,9 +523,9 @@ var/global/list/worths = list(
/obj/item/inflatable = 30,
/obj/item/roller_bed = 80,
/obj/item/rig_module/grenade_launcher = 1500,
- /obj/item/rig_module/mounted/egun = 2100,
- /obj/item/rig_module/mounted/energy_blade = 2200,
- /obj/item/rig_module/mounted/lcannon = 4100,
+ /obj/item/rig_module/mounted/energy/egun = 2100,
+ /obj/item/rig_module/mounted/energy/energy_blade = 2200,
+ /obj/item/rig_module/mounted/energy/lcannon = 4100,
/obj/item/rig_module/stealth_field = 2500,
/obj/item/rig_module/teleporter = 3000,
/obj/item/rig_module/fabricator/energy_net = 1200,
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index da5cf4b7d7468..8f82250570290 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -26,30 +26,45 @@
update_icon()
. = ..()
-/obj/structure/bookcase/attackby(obj/O as obj, mob/user as mob)
- if(istype(O, /obj/item/book))
- if(!user.unEquip(O, src))
- return
+/obj/structure/bookcase/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ USE_FEEDBACK_DECONSTRUCT_START(user)
+ if(!tool.use_as_tool(src, user, 2.5 SECONDS, volume = 50, skill_path = SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/item/stack/material/wood/wood = new (loc, 5)
+ transfer_fingerprints_to(wood)
+ for(var/obj/item/book/book in contents)
+ book.dropInto(loc)
+ user.visible_message(
+ SPAN_NOTICE("[user] dismantles [src] with [tool]."),
+ SPAN_NOTICE("You dismantle [src] with [tool].")
+ )
+ qdel(src)
+
+/obj/structure/bookcase/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Book - Add book to shelf
+ if (istype(tool, /obj/item/book))
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(tool, src)
+ return TRUE
update_icon()
- else if(istype(O, /obj/item/pen))
- var/newname = sanitizeSafe(input("What would you like to title this bookshelf?"), MAX_NAME_LEN)
- if(!newname)
- return
- else
- SetName("bookcase ([newname])")
- else if(isScrewdriver(O))
- playsound(loc, 'sound/items/Screwdriver.ogg', 75, 1)
- to_chat(user, SPAN_NOTICE("You begin dismantling \the [src]."))
- if(do_after(user, 2.5 SECONDS, src, DO_PUBLIC_UNIQUE))
- to_chat(user, SPAN_NOTICE("You dismantle \the [src]."))
- new/obj/item/stack/material/wood(get_turf(src), 5)
- for(var/obj/item/book/b in contents)
- b.dropInto(loc)
- qdel(src)
+ return TRUE
+
+ // Pen - Title bookshelf
+ if (istype(tool, /obj/item/pen))
+ var/input = input(user, "What would you like to title this bookshelf?", "Bookshelf Title") as null|text
+ input = sanitizeSafe(input, MAX_NAME_LEN)
+ if (!input || !user.use_sanity_check(src, tool))
+ return TRUE
+ SetName("[initial(name)] ([input])")
+ user.visible_message(
+ SPAN_NOTICE("[user] re-labels [src] with [tool]."),
+ SPAN_NOTICE("You re-label [src] with [tool].")
+ )
+ return TRUE
+
+ . = ..()
- else
- ..()
- return
/obj/structure/bookcase/attack_hand(mob/user as mob)
if(length(contents))
@@ -211,7 +226,7 @@
src.author = newauthor
else
return
- else if(istype(W, /obj/item/material/knife) || isWirecutter(W))
+ else if(istype(W, /obj/item/material/knife) || W.tool_behaviour == TOOL_WIRECUTTER)
if(carved) return
to_chat(user, SPAN_NOTICE("You begin to carve out [title]."))
if(do_after(user, 3 SECONDS, src, DO_PUBLIC_UNIQUE))
@@ -221,12 +236,14 @@
else
..()
-/obj/item/book/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
- if(user.zone_sel.selecting == BP_EYES)
+/obj/item/book/use_before(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
+ . = FALSE
+ if (istype(M) && user.a_intent == I_HELP && user.zone_sel.selecting == BP_EYES)
user.visible_message(SPAN_NOTICE("You open up the book and show it to [M]. "), \
SPAN_NOTICE(" [user] opens up a book and shows it to [M]. "))
show_browser(M, "Author: [author].
" + "[dat]", "window=book;size=1000x550")
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN) //to prevent spam
+ return TRUE
/*
* Manual Base Object
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 3fa35243357b6..9065abf3b367e 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -16,10 +16,12 @@
density = TRUE
var/obj/item/book/cache // Last scanned book
-/obj/machinery/libraryscanner/attackby(obj/O as obj, mob/user as mob)
+/obj/machinery/libraryscanner/use_tool(obj/item/O, mob/living/user, list/click_params)
if(istype(O, /obj/item/book))
- if(!user.unEquip(O, src))
- return
+ user.unEquip(O, src)
+ return TRUE
+
+ return ..()
/obj/machinery/libraryscanner/interface_interact(mob/user)
interact(user)
@@ -67,19 +69,30 @@
icon_state = "binder"
anchored = TRUE
density = TRUE
+ var/binding
-/obj/machinery/bookbinder/attackby(obj/O as obj, mob/user as mob)
+/obj/machinery/bookbinder/use_tool(obj/item/O, mob/living/user, list/click_params)
if(istype(O, /obj/item/paper))
if(!user.unEquip(O, src))
- return
- user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].")
- src.visible_message("[src] begins to hum as it warms up its printing drums.")
+ return TRUE
+ if (binding)
+ to_chat(user, SPAN_WARNING("\The [src] is currently busy printing a book."))
+ return TRUE
+
+ user.visible_message(
+ SPAN_NOTICE("\The [user] loads some paper into \the [src]."),
+ SPAN_NOTICE("You load some paper into \the [src].")
+ )
+ visible_message(SPAN_NOTICE("\The [src] begins to hum as it warms up its printing drums."))
+ binding = TRUE
sleep(rand(200,400))
- src.visible_message("[src] whirs as it prints and binds a new book.")
- var/obj/item/book/b = new(src.loc)
+ visible_message(SPAN_NOTICE("\The [src] whirs as it prints and binds a new book."))
+ binding = FALSE
+ var/obj/item/book/b = new(loc)
b.dat = O:info
b.SetName("Print Job #" + "[rand(100, 999)]")
b.icon_state = "book[rand(1,7)]"
qdel(O)
- else
- ..()
+ return TRUE
+
+ return ..()
diff --git a/code/modules/library/lib_readme.dm b/code/modules/library/lib_readme.dm
index 0150ed6ba8a7a..c9de443571c90 100644
--- a/code/modules/library/lib_readme.dm
+++ b/code/modules/library/lib_readme.dm
@@ -18,18 +18,18 @@
/*
- The Library
- ------------
- A place for the crew to go, relax, and enjoy a good book.
- Aspiring authors can even self publish and, if they're lucky
- convince the on-staff Librarian to submit it to the Archives
- to be chronicled in history forever - some say even persisting
- through alternate dimensions.
-
-
- Written by TLE for /tg/station 13
- Feel free to use this as you like. Some credit would be cool.
- Check us out at http://nanotrasen.com/ if you're so inclined.
+The Library
+------------
+A place for the crew to go, relax, and enjoy a good book.
+Aspiring authors can even self publish and, if they're lucky
+convince the on-staff Librarian to submit it to the Archives
+to be chronicled in history forever - some say even persisting
+through alternate dimensions.
+
+
+Written by TLE for /tg/station 13
+Feel free to use this as you like. Some credit would be cool.
+Check us out at http://nanotrasen.com/ if you're so inclined.
*/
// CONTAINS:
diff --git a/code/modules/library/manuals/engineering.dm b/code/modules/library/manuals/engineering.dm
index 5f549c28ff983..fda2dd3267dc0 100644
--- a/code/modules/library/manuals/engineering.dm
+++ b/code/modules/library/manuals/engineering.dm
@@ -630,7 +630,7 @@
EVA gear. Wonderful to use. It's useful for mining, engineering, and occasionally just surviving, if things are that bad. Most people have EVA training,
but apparently there are some people out in space who don't. This guide should give you a basic idea of how to use this gear, safely. It's split into two sections:
- Civilian suits and hardsuits.
"
// END TORCH JOBS
// SUBMAP JOBS
- for(var/thing in SSmapping.submaps)
- var/datum/submap/submap = thing
- if(submap && submap.available())
- dat += "
"
+ for(var/thing in SSmapping.submaps)
+ var/datum/submap/submap = thing
+ if(submap && submap.available())
+ var/color = "ffffff"
+ dat += " "
else
- for(var/raisin in job.get_unavailable_reasons(client))
- hidden_reasons[raisin] = TRUE
-
- if(LAZYLEN(job_summaries))
- dat += job_summaries
- else
- dat += "No available positions."
+ dat += "
No available positions.
"
+ dat += "
"
+ dat += " "
+ dat += "
"
// END SUBMAP JOBS
dat += "
"
@@ -355,10 +465,17 @@
additional_dat += " "
dat = additional_dat + dat
dat = header + dat
- show_browser(src, jointext(dat, null), "window=latechoices;size=450x640;can_close=1")
+ var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 950, 900)
+ popup.set_content(jointext(dat, null))
+ popup.open(0) // 0 is passed to open so that it doesn't use the onclose() proc
/mob/new_player/proc/create_character(turf/spawn_turf)
spawning = 1
+ if(client.prefs.organ_data[BP_CHEST] == "cyborg")
+ if(!is_any_alien_whitelisted(src, SPECIES_FBP) && client.prefs.species != SPECIES_IPC)
+ to_chat(src, "Нельзя зайти за ППТ без вайтлиста.")
+ spawning = 0
+ return null
close_spawn_windows()
var/mob/living/carbon/human/new_character
@@ -428,14 +545,16 @@
/mob/new_player/proc/close_spawn_windows()
close_browser(src, "window=latechoices") //closes late choices window
- panel.close()
+ close_browser(src, "window=preference_window")
+ if(panel)
+ panel.close()
/mob/new_player/proc/check_species_allowed(datum/species/S, show_alert=1)
if(!S.is_available_for_join() && !has_admin_rights())
if(show_alert)
to_chat(src, alert("Your current species, [client.prefs.species], is not available for play."))
return 0
- if(!is_alien_whitelisted(src, S))
+ if(!is_any_alien_whitelisted(src, S))
if(show_alert)
to_chat(src, alert("You are currently not whitelisted to play [client.prefs.species]."))
return 0
@@ -451,10 +570,6 @@
return chosen_species.name
-/mob/new_player/get_gender()
- if(!client || !client.prefs) ..()
- return client.prefs.gender
-
/mob/new_player/is_ready()
return ready && ..()
@@ -485,3 +600,18 @@
var/singleton/audio/track/track = GLOB.using_map.get_lobby_track(GLOB.using_map.lobby_track.type)
sound_to(src, track.get_sound())
to_chat(src, track.get_info())
+
+/mob/new_player/proc/check_occupation_set(datum/job/job)
+ if (!job)
+ return FALSE
+
+ if (job.title == client.prefs.job_high)
+ return TRUE
+
+ if (job.title in client.prefs.job_medium)
+ return TRUE
+
+ if (job.title in client.prefs.job_low)
+ return TRUE
+
+ return FALSE
diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm
index d6a6ccce92ca4..72d1d0b7905b5 100644
--- a/code/modules/mob/new_player/preferences_setup.dm
+++ b/code/modules/mob/new_player/preferences_setup.dm
@@ -30,9 +30,11 @@
if(current_species.appearance_flags & SPECIES_APPEARANCE_HAS_UNDERWEAR)
if(all_underwear)
all_underwear.Cut()
- for(var/datum/category_group/underwear/WRC in GLOB.underwear.categories)
- var/datum/category_item/underwear/WRI = pick(WRC.items)
- all_underwear[WRC.name] = WRI.name
+
+ if(LAZYLEN(GLOB.underwear.categories))
+ for(var/datum/category_group/underwear/WRC in GLOB.underwear.categories)
+ var/datum/category_item/underwear/WRI = pick(WRC.items)
+ all_underwear[WRC.name] = WRI.name
backpack = GET_SINGLETON(pick(subtypesof(/singleton/backpack_outfit)))
age = rand(current_species.min_age, current_species.max_age)
diff --git a/code/modules/mob/observer/following.dm b/code/modules/mob/observer/following.dm
index 6d1637977a8c6..1ade3539ef1e2 100644
--- a/code/modules/mob/observer/following.dm
+++ b/code/modules/mob/observer/following.dm
@@ -13,13 +13,14 @@
GLOB.dir_set_event.unregister(following, src)
following = null
-/mob/observer/proc/start_following(atom/a)
- stop_following()
- following = a
- GLOB.destroyed_event.register(a, src, .proc/stop_following)
- GLOB.moved_event.register(a, src, .proc/keep_following)
- GLOB.dir_set_event.register(a, src, /atom/proc/recursive_dir_set)
- keep_following(new_loc = get_turf(following))
+/mob/observer/proc/start_following(atom/Atom)
+ if(!istype(Atom, /obj/screen))
+ stop_following()
+ following = Atom
+ GLOB.destroyed_event.register(Atom, src, PROC_REF(stop_following))
+ GLOB.moved_event.register(Atom, src, PROC_REF(keep_following))
+ GLOB.dir_set_event.register(Atom, src, TYPE_PROC_REF(/atom, recursive_dir_set))
+ keep_following(new_loc = get_turf(following))
/mob/observer/proc/keep_following(atom/movable/moving_instance, atom/old_loc, atom/new_loc)
forceMove(get_turf(new_loc))
diff --git a/code/modules/mob/observer/freelook/ai/cameranet.dm b/code/modules/mob/observer/freelook/ai/cameranet.dm
index bde34a77b7a8e..9887f7941c510 100644
--- a/code/modules/mob/observer/freelook/ai/cameranet.dm
+++ b/code/modules/mob/observer/freelook/ai/cameranet.dm
@@ -22,7 +22,7 @@
return FALSE
. = ..(c, c.can_use())
if(.)
- ADD_SORTED(cameras, c, /proc/cmp_camera_ctag_asc)
+ ADD_SORTED(cameras, c, GLOBAL_PROC_REF(cmp_camera_ctag_asc))
else if(isAI(c))
var/mob/living/silicon/AI = c
return ..(AI, AI.stat != DEAD)
diff --git a/code/modules/mob/observer/freelook/ai/eye.dm b/code/modules/mob/observer/freelook/ai/eye.dm
index 588dfb4991206..766a9c8d5e905 100644
--- a/code/modules/mob/observer/freelook/ai/eye.dm
+++ b/code/modules/mob/observer/freelook/ai/eye.dm
@@ -7,19 +7,23 @@
name = "Inactive Camera Eye"
name_sufix = "Camera Eye"
-/mob/observer/eye/cameranet/New()
- ..()
+
+/mob/observer/eye/cameranet/Initialize(mapload)
+ . = ..()
visualnet = cameranet
+
/mob/observer/eye/aiEye
name = "Inactive AI Eye"
name_sufix = "AI Eye"
icon_state = "AI-eye"
-/mob/observer/eye/aiEye/New()
- ..()
+
+/mob/observer/eye/aiEye/Initialize(mapload)
+ . = ..()
visualnet = cameranet
+
/mob/observer/eye/aiEye/setLoc(T, cancel_tracking = 1)
. = ..()
if(. && isAI(owner))
@@ -65,10 +69,11 @@
eyeobj.possess(src)
// Intiliaze the eye by assigning it's "ai" variable to us. Then set it's loc to us.
-/mob/living/silicon/ai/New()
- ..()
+/mob/living/silicon/ai/Initialize(mapload)
+ . = ..()
create_eyeobj()
+
/mob/living/silicon/ai/Destroy()
destroy_eyeobj()
. = ..()
diff --git a/code/modules/mob/observer/freelook/ai/update_triggers.dm b/code/modules/mob/observer/freelook/ai/update_triggers.dm
index a947e0a27f4e4..cebb7ee5555b9 100644
--- a/code/modules/mob/observer/freelook/ai/update_triggers.dm
+++ b/code/modules/mob/observer/freelook/ai/update_triggers.dm
@@ -37,10 +37,11 @@
invalidateCameraCache()
// Mobs
-/mob/living/silicon/ai/New()
- ..()
+/mob/living/silicon/ai/Initialize(mapload)
+ . = ..()
cameranet.add_source(src)
+
/mob/living/silicon/ai/Destroy()
cameranet.remove_source(src)
. = ..()
diff --git a/code/modules/mob/observer/freelook/chunk.dm b/code/modules/mob/observer/freelook/chunk.dm
index 8bec8137873a2..1418933e0d248 100644
--- a/code/modules/mob/observer/freelook/chunk.dm
+++ b/code/modules/mob/observer/freelook/chunk.dm
@@ -23,6 +23,9 @@
var/image/obfuscation = obfuscation_images[T]
if(!obfuscation)
obfuscation = image(icon, T, icon_state)
+ // [SIERRA-ADD] - AI
+ obfuscation.plane = EFFECTS_ABOVE_LIGHTING_PLANE
+ // [/SIERRA-ADD]
obfuscation.layer = OBFUSCATION_LAYER
if(!obfuscation_underlay)
// Creating a new icon of a fairly common icon state, adding some random color to prevent address searching, and hoping being static kills memory locality
@@ -60,7 +63,9 @@
src.y = y
src.z = z
- for(var/turf/t in range(10, locate(x + 8, y + 8, z)))
+ var/turf/center = locate(src.x + 8, src.y + 8, src.z)
+ for(var/turf/t as anything in RANGE_TURFS(center, 10))
+ /// Todo: remove this unnecessary check
if(t.x >= x && t.y >= y && t.x < x + 16 && t.y < y + 16)
turfs[t] = t
@@ -176,6 +181,7 @@
return
/proc/seen_turfs_in_range(source, range)
+ RETURN_TYPE(/list)
var/turf/pos = get_turf(source)
if(pos)
. = hear(range, pos)
diff --git a/code/modules/mob/observer/freelook/cult/mask.dm b/code/modules/mob/observer/freelook/cult/mask.dm
index b889b6edd8622..147cd62be1f28 100644
--- a/code/modules/mob/observer/freelook/cult/mask.dm
+++ b/code/modules/mob/observer/freelook/cult/mask.dm
@@ -2,10 +2,12 @@
name = "Mask of God"
desc = "A terrible fracture of reality coinciding into a mirror to another world."
-/mob/observer/eye/cult/New(loc, net)
- ..()
+
+/mob/observer/eye/cult/Initialize(mapload, net)
+ . = ..()
visualnet = net
+
/mob/observer/eye/cult/Destroy()
visualnet = null
return ..()
diff --git a/code/modules/mob/observer/freelook/eye.dm b/code/modules/mob/observer/freelook/eye.dm
index 78383f97fad64..e36bb74aeaeaf 100644
--- a/code/modules/mob/observer/freelook/eye.dm
+++ b/code/modules/mob/observer/freelook/eye.dm
@@ -40,7 +40,7 @@
set_dir(ndir)
return 1
-/mob/observer/eye/examinate()
+/mob/observer/eye/ExaminateVerb()
set popup_menu = 0
set src = usr.contents
return 0
diff --git a/code/modules/mob/observer/freelook/read_me.dm b/code/modules/mob/observer/freelook/read_me.dm
index 380a3e0171eeb..2795333e112b4 100644
--- a/code/modules/mob/observer/freelook/read_me.dm
+++ b/code/modules/mob/observer/freelook/read_me.dm
@@ -1,51 +1,51 @@
// CREDITS
/*
- Initial code credit for this goes to Uristqwerty.
- Debugging, functionality, all comments and porting by Giacom.
+Initial code credit for this goes to Uristqwerty.
+Debugging, functionality, all comments and porting by Giacom.
- Everything about freelook (or what we can put in here) will be stored here.
+Everything about freelook (or what we can put in here) will be stored here.
- WHAT IS THIS?
+WHAT IS THIS?
- This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could
- only see what the cameras could see. Not only this but the cameras could see through walls, which created problems.
- With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players.
- The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and
- cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it.
- This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking,
- the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more.
+This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could
+only see what the cameras could see. Not only this but the cameras could see through walls, which created problems.
+With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players.
+The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and
+cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it.
+This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking,
+the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more.
- HOW IT WORKS
+HOW IT WORKS
- It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be
- explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Destroy().
+It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be
+explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Destroy().
- Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk.
- These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside
- the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it.
+Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk.
+These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside
+the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it.
- HOW IT UPDATES
+HOW IT UPDATES
- The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing,
- turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick.
+The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing,
+turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick.
- The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created.
- One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area.
- We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk
- that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead
- flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing
- measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking
- sight; for example, we don't update glass airlocks or floors.
+The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created.
+One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area.
+We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk
+that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead
+flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing
+measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking
+sight; for example, we don't update glass airlocks or floors.
- WHERE IS EVERYTHING?
+WHERE IS EVERYTHING?
- cameranet.dm = Everything about the cameranet datum.
- chunk.dm = Everything about the chunk datum.
- eye.dm = Everything about the AI and the AIEye.
- updating.dm = Everything about triggers that will update chunks.
+cameranet.dm = Everything about the cameranet datum.
+chunk.dm = Everything about the chunk datum.
+eye.dm = Everything about the AI and the AIEye.
+updating.dm = Everything about triggers that will update chunks.
-*/
\ No newline at end of file
+*/
diff --git a/code/modules/mob/observer/freelook/visualnet.dm b/code/modules/mob/observer/freelook/visualnet.dm
index 7016c7fb88c47..f5e7df9b94f15 100644
--- a/code/modules/mob/observer/freelook/visualnet.dm
+++ b/code/modules/mob/observer/freelook/visualnet.dm
@@ -109,7 +109,7 @@
// Never access this proc directly!!!!
// This will update the chunk and all the surrounding chunks.
/datum/visualnet/proc/major_chunk_change(atom/source)
- for_all_chunks_in_range(source, /datum/chunk/proc/visibility_changed, list())
+ for_all_chunks_in_range(source, TYPE_PROC_REF(/datum/chunk, visibility_changed), list())
/datum/visualnet/proc/add_source(atom/source, update_visibility = TRUE, opacity_check = FALSE)
if(!(source && is_type_in_list(source, valid_source_types)))
@@ -118,9 +118,9 @@
if(source in sources)
return FALSE
sources += source
- GLOB.moved_event.register(source, src, /datum/visualnet/proc/source_moved)
- GLOB.destroyed_event.register(source, src, /datum/visualnet/proc/remove_source)
- for_all_chunks_in_range(source, /datum/chunk/proc/add_source, list(source))
+ GLOB.moved_event.register(source, src, TYPE_PROC_REF(/datum/visualnet, source_moved))
+ GLOB.destroyed_event.register(source, src, TYPE_PROC_REF(/datum/visualnet, remove_source))
+ for_all_chunks_in_range(source, TYPE_PROC_REF(/datum/chunk, add_source), list(source))
if(update_visibility)
update_visibility(source, opacity_check)
return TRUE
@@ -129,9 +129,9 @@
if(!sources.Remove(source))
return FALSE
- GLOB.moved_event.unregister(source, src, /datum/visualnet/proc/source_moved)
- GLOB.destroyed_event.unregister(source, src, /datum/visualnet/proc/remove_source)
- for_all_chunks_in_range(source, /datum/chunk/proc/remove_source, list(source))
+ GLOB.moved_event.unregister(source, src, TYPE_PROC_REF(/datum/visualnet, source_moved))
+ GLOB.destroyed_event.unregister(source, src, TYPE_PROC_REF(/datum/visualnet, remove_source))
+ for_all_chunks_in_range(source, TYPE_PROC_REF(/datum/chunk, remove_source), list(source))
if(update_visibility)
update_visibility(source, opacity_check)
return TRUE
@@ -146,9 +146,9 @@
// A more proper way would be to figure out which chunks have gone out of range, and which have come into range
// and only remove/add to those.
if(old_turf)
- for_all_chunks_in_range(source, /datum/chunk/proc/remove_source, list(source), old_turf)
+ for_all_chunks_in_range(source, TYPE_PROC_REF(/datum/chunk, remove_source), list(source), old_turf)
if(new_turf)
- for_all_chunks_in_range(source, /datum/chunk/proc/add_source, list(source), new_turf)
+ for_all_chunks_in_range(source, TYPE_PROC_REF(/datum/chunk, add_source), list(source), new_turf)
/datum/visualnet/proc/for_all_chunks_in_range(atom/source, proc_call, list/proc_args, turf/T)
T = T ? T : get_turf(source)
diff --git a/code/modules/mob/observer/ghost/ghost.dm b/code/modules/mob/observer/ghost/ghost.dm
index e132ee4fa49b4..807129bf7ca17 100644
--- a/code/modules/mob/observer/ghost/ghost.dm
+++ b/code/modules/mob/observer/ghost/ghost.dm
@@ -6,17 +6,20 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
desc = "It's a g-g-g-g-ghooooost!" //jinkies!
icon = 'icons/mob/mob.dmi'
icon_state = "ghost"
- appearance_flags = DEFAULT_APPEARANCE_FLAGS | KEEP_TOGETHER
+ // [SIERRA-EDIT] - SSINPUT
+ // appearance_flags = DEFAULT_APPEARANCE_FLAGS | KEEP_TOGETHER // SIERRA-EDIT - ORIGINAL
+ appearance_flags = DEFAULT_APPEARANCE_FLAGS | KEEP_TOGETHER | LONG_GLIDE
+ // [/SIERRA-EDIT]
blinded = 0
anchored = TRUE // don't get pushed around
universal_speak = TRUE
-
mob_flags = MOB_FLAG_HOLY_BAD
movement_handlers = list(/datum/movement_handler/mob/multiz_connected, /datum/movement_handler/mob/incorporeal)
var/is_manifest = FALSE
var/next_visibility_toggle = 0
var/can_reenter_corpse
+ var/thearea
var/bootime = 0
var/started_as_observer //This variable is set to 1 when you enter the game as an observer.
//If you died in the game and are a ghost - this will remain as null.
@@ -32,12 +35,13 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
var/obj/item/device/multitool/ghost_multitool
var/list/hud_images // A list of hud images
-/mob/observer/ghost/New(mob/body)
+/mob/observer/ghost/Initialize(mapload)
see_in_dark = 100
verbs += /mob/proc/toggle_antag_pool
var/turf/T
- if(ismob(body))
+ if(ismob(loc))
+ var/mob/body = loc
T = get_turf(body) //Where is the body located?
attack_logs_ = body.attack_logs_ //preserve our attack logs by copying them to our ghost
@@ -72,7 +76,7 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
GLOB.ghost_mobs += src
- ..()
+ . = ..()
/mob/observer/ghost/Destroy()
GLOB.ghost_mobs -= src
@@ -150,6 +154,7 @@ Works together with spawning an observer, noted above.
ghost.key = key
if(ghost.client && !ghost.client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed.
ghost.verbs -= /mob/observer/ghost/verb/toggle_antagHUD // Poor guys, don't know what they are missing!
+ ghost.add_ghost_buttons()
return ghost
/mob/observer/ghostize() // Do not create ghosts of ghosts.
@@ -199,6 +204,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/eta_status = evacuation_controller.get_status_panel_eta()
if(eta_status)
stat(null, eta_status)
+ stat("Local Time:", "[stationtime2text()]")
+ stat("Local Date:", "[stationdate2text()]")
/mob/observer/ghost/verb/reenter_corpse()
set category = "Ghost"
@@ -216,7 +223,16 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
mind.current.reload_fullscreen()
if(!admin_ghosted)
announce_ghost_joinleave(mind, 0, "They now occupy their body again.")
- return 1
+ return TRUE
+
+/mob/observer/ghost/proc/jumptomob()
+ var/mob/M = tgui_input_list(usr, "К кому вы хотите телепортироваться?", "Выбрать моба", SSmobs.mob_list)
+ log_and_message_admins("jumped to [key_name(M)]")
+ var/turf/T = get_turf(M)
+ if(T && isturf(T))
+ jumpTo(T)
+ else
+ to_chat(usr, "Этот моб не находится в игровом мире.")
/mob/observer/ghost/verb/toggle_medHUD()
set category = "Ghost"
@@ -225,10 +241,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(!client)
return
if(medHUD)
- medHUD = 0
+ medHUD = FALSE
to_chat(src, SPAN_NOTICE("Medical HUD Disabled"))
else
- medHUD = 1
+ medHUD = TRUE
to_chat(src, SPAN_NOTICE("Medical HUD Enabled"))
/mob/observer/ghost/verb/toggle_antagHUD()
@@ -258,17 +274,18 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
M.antagHUD = 1
to_chat(src, SPAN_NOTICE("AntagHUD Enabled"))
-/mob/observer/ghost/verb/dead_tele(A in area_repository.get_areas_by_z_level())
+/mob/observer/ghost/verb/dead_tele()
set category = "Ghost"
set name = "Teleport"
set desc= "Teleport to a location"
+ var/A = tgui_input_list(usr, "Выберите зону.", "Выбор зоны", area_repository.get_areas_by_z_level())
var/area/thearea = area_repository.get_areas_by_z_level()[A]
if(!thearea)
to_chat(src, "No area available.")
return
- var/list/area_turfs = get_area_turfs(thearea, shall_check_if_holy() ? list(/proc/is_not_holy_turf) : list())
+ var/list/area_turfs = get_area_turfs(thearea, shall_check_if_holy() ? list(GLOBAL_PROC_REF(is_not_holy_turf)) : list())
if(!length(area_turfs))
to_chat(src, SPAN_WARNING("This area has been entirely made into sacred grounds, you cannot enter it while you are in this plane of existence!"))
return
@@ -285,13 +302,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
ghost_to_turf(T)
else
to_chat(src, SPAN_WARNING("Invalid coordinates."))
-/mob/observer/ghost/verb/follow(datum/follow_holder/fh in get_follow_targets())
+/mob/observer/ghost/verb/follow()
set category = "Ghost"
set name = "Follow"
set desc = "Follow and haunt a mob."
- if(!fh.show_entry()) return
- start_following(fh.followed_instance)
+ GLOB.orbit_menu.show(src)
/mob/observer/ghost/proc/ghost_to_turf(turf/target_turf)
if(check_is_holy_turf(target_turf))
@@ -309,7 +325,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(src, SPAN_NOTICE("No longer following \the [following]"))
..()
-/mob/observer/ghost/keep_following(atom/movable/am, old_loc, new_loc)
+/mob/observer/ghost/keep_following(obj/AM, old_loc, new_loc)
var/turf/T = get_turf(new_loc)
if(check_is_holy_turf(T))
to_chat(src, SPAN_WARNING("You cannot follow something standing on holy grounds!"))
@@ -377,7 +393,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/mob/living/simple_animal/passive/mouse/host
var/obj/machinery/atmospherics/unary/vent_pump/vent_found
var/list/found_vents = list()
- for(var/obj/machinery/atmospherics/unary/vent_pump/v in SSmachines.machinery)
+ for(var/obj/machinery/atmospherics/unary/vent_pump/v as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics/unary/vent_pump))
if(!v.welded && v.z == T.z)
found_vents.Add(v)
if(length(found_vents))
@@ -392,6 +408,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
host.ckey = src.ckey
host.status_flags |= NO_ANTAG
to_chat(host, SPAN_INFO("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent."))
+
/mob/observer/ghost/verb/view_manfiest()
set name = "Show Crew Manifest"
set category = "Ghost"
@@ -400,7 +417,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
dat += "
Crew Manifest
"
dat += html_crew_manifest()
- show_browser(src, dat, "window=manifest;size=370x420;can_close=1")
+ var/datum/browser/popup = new(src, "Crew Manifest", "Crew Manifest", 370, 420, src)
+ popup.set_content(dat)
+ popup.open()
//This is called when a ghost is drag clicked to something.
/mob/observer/ghost/MouseDrop(atom/over)
@@ -419,13 +438,17 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return ..()
-/mob/observer/ghost/proc/try_possession(mob/living/M)
- if(!config.ghosts_can_possess_animals)
+/mob/observer/ghost/proc/try_possession(mob/living/target)
+ if(target.is_zombie())
+ if(!config.ghosts_can_possess_zombies)
+ to_chat(src, SPAN_WARNING("Ghosts are not permitted to possess zombies."))
+ return 0
+ else if(!config.ghosts_can_possess_animals)
to_chat(src, SPAN_WARNING("Ghosts are not permitted to possess animals."))
return 0
- if(!M.can_be_possessed_by(src))
+ if(!target.can_be_possessed_by(src))
return 0
- return M.do_possession(src)
+ return target.do_possession(src)
/mob/observer/ghost/pointed(atom/A as mob|obj|turf in view())
if(!..())
@@ -463,6 +486,13 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
updateghostsight()
to_chat(src, "You [(ghostvision?"now":"no longer")] have ghost vision.")
+/mob/observer/ghost/verb/set_ghost_alpha()
+ set name = "Set Ghost Alpha"
+ set desc = "Giving you option to enter value for custom ghost transparency"
+ set category = "Ghost"
+ alpha = alpha == 127 ? 0 : 127
+ mouse_opacity = alpha ? 1 : 0
+
/mob/observer/ghost/verb/toggle_darkness()
set name = "Toggle Darkness"
set category = "Ghost"
@@ -492,23 +522,28 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/observer/ghost/MayRespawn(feedback = 0, respawn_time = 0)
if(!client)
- return 0
+ return FALSE
+
if(mind && mind.current && mind.current.stat != DEAD && can_reenter_corpse == CORPSE_CAN_REENTER)
if(feedback)
to_chat(src, SPAN_WARNING("Your non-dead body prevents you from respawning."))
- return 0
+ return FALSE
+
+ if(!config.respawn_delay)
+ return TRUE
+
if(config.antag_hud_restricted && has_enabled_antagHUD == 1)
if(feedback)
to_chat(src, SPAN_WARNING("antagHUD restrictions prevent you from respawning."))
- return 0
+ return FALSE
var/timedifference = world.time - timeofdeath
if(!client.holder && respawn_time && timeofdeath && timedifference < respawn_time MINUTES)
var/timedifference_text = time2text(respawn_time MINUTES - timedifference,"mm:ss")
to_chat(src, SPAN_WARNING("You must have been dead for [respawn_time] minute\s to respawn. You have [timedifference_text] left."))
- return 0
+ return FALSE
- return 1
+ return TRUE
/proc/isghostmind(datum/mind/player)
return player && !isnewplayer(player.current) && (!player.current || isghost(player.current) || (isliving(player.current) && player.current.stat == DEAD) || !player.current.client)
@@ -528,18 +563,13 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return TRUE
/mob/observer/ghost/proc/set_appearance(mob/target)
- var/pre_alpha = alpha
- var/pre_plane = plane
- var/pre_layer = layer
- var/pre_invis = invisibility
-
- appearance = target
- appearance_flags |= initial(appearance_flags)
- alpha = pre_alpha
- plane = pre_plane
- layer = pre_layer
- set_invisibility(pre_invis)
ClearTransform()
+ ClearOverlays()
+ if (!target)
+ icon = initial(icon)
+ return
+ icon = null
+ CopyOverlays(target)
/mob/observer/ghost/verb/respawn()
@@ -566,3 +596,15 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
M.respawned_time = world.time
M.key = key
log_and_message_admins("has respawned.", M)
+
+/mob/observer/ghost/proc/add_ghost_buttons()
+ var/jumptomob = new /obj/screen/ghost/jumptomob()
+ var/orbit = new /obj/screen/ghost/orbit()
+ var/reenter_corpse = new /obj/screen/ghost/reenter_corpse()
+ var/teleport = new /obj/screen/ghost/teleport()
+ var/toggle_darkness = new /obj/screen/ghost/toggle_darkness()
+ client.screen.Add(jumptomob)
+ client.screen.Add(orbit)
+ client.screen.Add(reenter_corpse)
+ client.screen.Add(teleport)
+ client.screen.Add(toggle_darkness)
diff --git a/code/modules/mob/observer/ghost/orbit.dm b/code/modules/mob/observer/ghost/orbit.dm
new file mode 100644
index 0000000000000..17dbdf096f7ff
--- /dev/null
+++ b/code/modules/mob/observer/ghost/orbit.dm
@@ -0,0 +1,75 @@
+GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new)
+
+/datum/orbit_menu
+
+/datum/orbit_menu/tgui_state(mob/user)
+ return GLOB.tgui_observer_state
+
+/datum/orbit_menu/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Orbit")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/orbit_menu/tgui_act(action, list/params)
+ if(..())
+ return
+ . = TRUE
+
+ switch(action)
+ if("orbit")
+ var/datum/follow_holder/follow_holder = locate(params["ref"]) in get_follow_targets()
+ var/atom/movable/atom = follow_holder.followed_instance
+ var/mob/observer/ghost/ghost = usr
+ if(atom != usr)
+ ghost.start_following(atom)
+ return TRUE
+ if("refresh")
+ update_tgui_static_data()
+ return TRUE
+
+/datum/orbit_menu/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["misc"] = list()
+ data["ghosts"] = list()
+ data["dead"] = list()
+ data["npcs"] = list()
+ data["alive"] = list()
+ data["antagonists"] = list()
+ for(var/datum/follow_holder/follow_holder in get_follow_targets())
+ var/atom/movable/follow_instance = follow_holder.followed_instance
+ var/list/serialized = list()
+ serialized["name"] = follow_instance.name
+ serialized["ref"] = "[REF(follow_holder)]"
+
+ if(!istype(follow_instance, /mob))
+ data["misc"] += list(serialized)
+ continue
+ var/mob/mob = follow_instance
+ if(isobserver(mob))
+ data["ghosts"] += list(serialized)
+ continue
+
+ if(mob.stat == DEAD)
+ data["dead"] += list(serialized)
+ continue
+
+ if(isnull(mob.mind))
+ data["npcs"] += list(serialized)
+ continue
+
+ data["alive"] += list(serialized)
+
+ var/mob/observer/ghost/observer = user
+ if(observer.antagHUD && mob.get_antag_info())
+ var/antag_serialized = serialized.Copy()
+ for(var/antag_category in mob.get_antag_info())
+ antag_serialized["antag"] += list(antag_category)
+ data["antagonists"] += list(antag_serialized)
+
+ return data
+
+/// Shows the UI to the specified user.
+/datum/orbit_menu/proc/show(mob/user)
+ tgui_interact(user)
diff --git a/code/modules/mob/observer/observer.dm b/code/modules/mob/observer/observer.dm
index 7c301c7905a80..5ce3da827f9e5 100644
--- a/code/modules/mob/observer/observer.dm
+++ b/code/modules/mob/observer/observer.dm
@@ -13,11 +13,12 @@ var/global/const/GHOST_IMAGE_ALL = ~GHOST_IMAGE_NONE
simulated = FALSE
stat = DEAD
status_flags = GODMODE
+ shift_to_open_context_menu = FALSE
var/ghost_image_flag = GHOST_IMAGE_DARKNESS
var/image/ghost_image = null //this mobs ghost image, for deleting and stuff
-/mob/observer/New()
- ..()
+/mob/observer/Initialize(mapload)
+ . = ..()
ghost_image = image(src.icon,src)
ghost_image.plane = plane
ghost_image.layer = layer
@@ -78,7 +79,6 @@ var/global/const/GHOST_IMAGE_ALL = ~GHOST_IMAGE_NONE
var/turf/T = locate(new_x, new_y, z)
if(T)
forceMove(T)
- inertia_dir = 0
throwing = null
to_chat(src, SPAN_NOTICE("You cannot move further in this direction."))
diff --git a/code/modules/mob/observer/virtual/base.dm b/code/modules/mob/observer/virtual/base.dm
index d9db6a6a5fd19..5d223fc43d806 100644
--- a/code/modules/mob/observer/virtual/base.dm
+++ b/code/modules/mob/observer/virtual/base.dm
@@ -17,23 +17,22 @@ var/global/list/all_virtual_listeners = list()
var/static/list/overlay_icons
-/mob/observer/virtual/New(location, atom/movable/host)
- ..()
+/mob/observer/virtual/Initialize(mapload, atom/movable/host)
+ . = ..()
+
if(!istype(host, host_type))
CRASH("Received an unexpected host type. Expected [host_type], was [log_info_line(host)].")
src.host = host
- GLOB.moved_event.register(host, src, /atom/movable/proc/move_to_turf_or_null)
+ GLOB.moved_event.register(host, src, TYPE_PROC_REF(/atom/movable, move_to_turf_or_null))
all_virtual_listeners += src
update_icon()
-/mob/observer/virtual/Initialize()
- . = ..()
STOP_PROCESSING_MOB(src)
/mob/observer/virtual/Destroy()
- GLOB.moved_event.unregister(host, src, /atom/movable/proc/move_to_turf_or_null)
+ GLOB.moved_event.unregister(host, src, TYPE_PROC_REF(/atom/movable, move_to_turf_or_null))
all_virtual_listeners -= src
host = null
return ..()
@@ -41,14 +40,14 @@ var/global/list/all_virtual_listeners = list()
/mob/observer/virtual/on_update_icon()
if(!overlay_icons)
overlay_icons = list()
- for(var/i_state in icon_states(icon))
+ for(var/i_state in ICON_STATES(icon))
overlay_icons[i_state] = image(icon = icon, icon_state = i_state)
- overlays.Cut()
+ ClearOverlays()
if(abilities & VIRTUAL_ABILITY_HEAR)
- overlays += overlay_icons["hear"]
+ AddOverlays(overlay_icons["hear"])
if(abilities & VIRTUAL_ABILITY_SEE)
- overlays += overlay_icons["see"]
+ AddOverlays(overlay_icons["see"])
/***********************
* Virtual Mob Creation *
diff --git a/code/modules/mob/observer/virtual/helpers.dm b/code/modules/mob/observer/virtual/helpers.dm
index 903469f896e54..66c54243d4c99 100644
--- a/code/modules/mob/observer/virtual/helpers.dm
+++ b/code/modules/mob/observer/virtual/helpers.dm
@@ -14,6 +14,7 @@
* Range Helpers *
****************/
/proc/clients_in_range(atom/movable/center_vmob)
+ RETURN_TYPE(/list)
. = list()
ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
@@ -23,6 +24,7 @@
. |= C
/proc/hearers_in_range(atom/movable/center_vmob, hearing_range = world.view)
+ RETURN_TYPE(/list)
. = list()
ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
@@ -31,6 +33,7 @@
. |= v_mob.host
/proc/viewers_in_range(atom/movable/center_vmob)
+ RETURN_TYPE(/list)
. = list()
ACQUIRE_VIRTUAL_OR_TURF(center_vmob)
@@ -47,6 +50,7 @@
// Gets the hosts of all the virtual mobs that can hear the given movable atom (or rather, it's virtual mob or turf in that existence order)
/proc/all_hearers(atom/movable/heard_vmob, range = world.view)
+ RETURN_TYPE(/list)
. = list()
ACQUIRE_VIRTUAL_OR_TURF(heard_vmob)
@@ -67,6 +71,7 @@
// Gets the hosts of all virtual mobs that can see the given atom movable as well as its turf
/proc/all_viewers(mob/observer/virtual/viewed_atom)
+ RETURN_TYPE(/list)
. = list()
viewed_atom = istype(viewed_atom) ? viewed_atom.host : viewed_atom
@@ -84,6 +89,7 @@
// This proc returns all hosts of virtual mobs in the given atom's view range (using its turf), ignoring invisibility, VIRUAL_ABILITY_SEE, and most other restrictions.
// In most cases you actually want the all_* procs above. This helper was designed with LOOC in mind.
/proc/hosts_in_view_range(atom/movable/viewing_atom)
+ RETURN_TYPE(/list)
. = list()
ACQUIRE_VIRTUAL_OR_TURF(viewing_atom)
diff --git a/code/modules/mob/observer/virtual/mob.dm b/code/modules/mob/observer/virtual/mob.dm
index 604ee58b07ef0..89bca627b2ecc 100644
--- a/code/modules/mob/observer/virtual/mob.dm
+++ b/code/modules/mob/observer/virtual/mob.dm
@@ -1,19 +1,21 @@
/mob/observer/virtual/mob
host_type = /mob
-/mob/observer/virtual/mob/New(location, mob/host)
- ..()
- GLOB.sight_set_event.register(host, src, /mob/observer/virtual/mob/proc/sync_sight)
- GLOB.see_invisible_set_event.register(host, src, /mob/observer/virtual/mob/proc/sync_sight)
- GLOB.see_in_dark_set_event.register(host, src, /mob/observer/virtual/mob/proc/sync_sight)
+/mob/observer/virtual/mob/Initialize(mapload, mob/host)
+ . = ..()
+
+ GLOB.sight_set_event.register(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
+ GLOB.see_invisible_set_event.register(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
+ GLOB.see_in_dark_set_event.register(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
sync_sight(host)
+
/mob/observer/virtual/mob/Destroy()
- GLOB.sight_set_event.unregister(host, src, /mob/observer/virtual/mob/proc/sync_sight)
- GLOB.see_invisible_set_event.unregister(host, src, /mob/observer/virtual/mob/proc/sync_sight)
- GLOB.see_in_dark_set_event.unregister(host, src, /mob/observer/virtual/mob/proc/sync_sight)
+ GLOB.sight_set_event.unregister(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
+ GLOB.see_invisible_set_event.unregister(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
+ GLOB.see_in_dark_set_event.unregister(host, src, TYPE_PROC_REF(/mob/observer/virtual/mob, sync_sight))
. = ..()
/mob/observer/virtual/mob/proc/sync_sight(mob/mob_host)
diff --git a/code/modules/mob/pronouns.dm b/code/modules/mob/pronouns.dm
new file mode 100644
index 0000000000000..6836dcdb68077
--- /dev/null
+++ b/code/modules/mob/pronouns.dm
@@ -0,0 +1,408 @@
+//pronoun procs, for getting pronouns without using the text macros that only work in certain positions
+//datums don't have gender, but most of their subtypes do!
+/datum/proc/p_they(temp_gender)
+ return "it"
+
+/datum/proc/p_They(temp_gender)
+ return capitalize(p_they(temp_gender))
+
+/datum/proc/p_their(temp_gender)
+ return "its"
+
+/datum/proc/p_Their(temp_gender)
+ return capitalize(p_their(temp_gender))
+
+/datum/proc/p_theirs(temp_gender)
+ return "its"
+
+/datum/proc/p_Theirs(temp_gender)
+ return capitalize(p_theirs(temp_gender))
+
+/datum/proc/p_them(temp_gender)
+ return "it"
+
+/datum/proc/p_Them(temp_gender)
+ return capitalize(p_them(temp_gender))
+
+/datum/proc/p_have(temp_gender)
+ return "has"
+
+/datum/proc/p_are(temp_gender)
+ return "is"
+
+/datum/proc/p_were(temp_gender)
+ return "was"
+
+/datum/proc/p_do(temp_gender)
+ return "does"
+
+/datum/proc/p_theyve(temp_gender)
+ return p_they(temp_gender) + "'" + copytext_char(p_have(temp_gender), 3)
+
+/datum/proc/p_Theyve(temp_gender)
+ return p_They(temp_gender) + "'" + copytext_char(p_have(temp_gender), 3)
+
+/datum/proc/p_theyre(temp_gender)
+ return p_they(temp_gender) + "'" + copytext_char(p_are(temp_gender), 2)
+
+/datum/proc/p_Theyre(temp_gender)
+ return p_They(temp_gender) + "'" + copytext_char(p_are(temp_gender), 2)
+
+/datum/proc/p_s(temp_gender) //is this a descriptive proc name, or what?
+ return "s"
+
+/datum/proc/p_es(temp_gender)
+ return "es"
+
+/datum/proc/plural_s(pluralize)
+ switch(copytext_char(pluralize, -2))
+ if ("ss")
+ return "es"
+ if ("sh")
+ return "es"
+ if ("ch")
+ return "es"
+ else
+ switch(copytext_char(pluralize, -1))
+ if("s", "x", "z")
+ return "es"
+ else
+ return "s"
+
+//like clients, which do have gender.
+/client/p_they(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "she"
+ if(MALE)
+ return "he"
+ else
+ return "they"
+
+/client/p_their(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "her"
+ if(MALE)
+ return "his"
+ else
+ return "their"
+
+/client/p_theirs(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "hers"
+ if(MALE)
+ return "his"
+ else
+ return "theirs"
+
+/client/p_them(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "her"
+ if(MALE)
+ return "him"
+ else
+ return "them"
+
+/client/p_have(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL || temp_gender == NEUTER)
+ return "have"
+ return "has"
+
+/client/p_are(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL || temp_gender == NEUTER)
+ return "are"
+ return "is"
+
+/client/p_were(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL || temp_gender == NEUTER)
+ return "were"
+ return "was"
+
+/client/p_do(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL || temp_gender == NEUTER)
+ return "do"
+ return "does"
+
+/client/p_s(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL && temp_gender != NEUTER)
+ return "s"
+
+/client/p_es(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL && temp_gender != NEUTER)
+ return "es"
+
+//mobs(and atoms but atoms don't really matter write your own proc overrides) also have gender!
+/mob/p_they(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "she"
+ if(MALE)
+ return "he"
+ if(PLURAL)
+ return "they"
+ else
+ return "it"
+
+/mob/p_their(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "her"
+ if(MALE)
+ return "his"
+ if(PLURAL)
+ return "their"
+ else
+ return "its"
+
+/mob/p_theirs(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "hers"
+ if(MALE)
+ return "his"
+ if(PLURAL)
+ return "theirs"
+ else
+ return "its"
+
+/mob/p_them(capitalized, temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ switch(temp_gender)
+ if(FEMALE)
+ return "her"
+ if(MALE)
+ return "him"
+ if(PLURAL)
+ return "them"
+ else
+ return "it"
+
+/mob/p_have(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "have"
+ return "has"
+
+/mob/p_are(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "are"
+ return "is"
+
+/mob/p_were(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "were"
+ return "was"
+
+/mob/p_do(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "do"
+ return "does"
+
+/mob/p_s(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL)
+ return "s"
+
+/mob/p_es(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL)
+ return "es"
+
+//humans need special handling, because they can have their gender hidden
+/mob/living/carbon/human/p_they(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_their(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_theirs(capitalized, temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_them(capitalized, temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_have(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_are(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_were(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_do(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_s(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+/mob/living/carbon/human/p_es(temp_gender)
+ var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
+ if(skipface)
+ temp_gender = PLURAL
+ return ..()
+
+//clothing need special handling due to pairs of items, ie gloves vs a singular glove, shoes, ect.
+/obj/item/clothing/p_they(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "they"
+ return "it"
+
+/obj/item/clothing/p_their(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "their"
+ return "its"
+
+/obj/item/clothing/p_theirs(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "theirs"
+ return "its"
+
+/obj/item/clothing/p_them(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "them"
+ return "it"
+
+/obj/item/clothing/p_have(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "have"
+ return "has"
+
+/obj/item/clothing/p_are(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "are"
+ return "is"
+
+/obj/item/clothing/p_were(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "were"
+ return "was"
+
+/obj/item/clothing/p_do(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender == PLURAL)
+ return "do"
+ return "does"
+
+/obj/item/clothing/p_s(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL)
+ return "s"
+
+/obj/item/clothing/p_es(temp_gender)
+ if(!temp_gender)
+ temp_gender = gender
+ if(temp_gender != PLURAL)
+ return "es"
+
+/datum/mind/p_they(temp_gender)
+ return current?.p_they(temp_gender) || ..()
+
+/datum/mind/p_their(temp_gender)
+ return current?.p_their(temp_gender) || ..()
+
+/datum/mind/p_theirs(temp_gender)
+ return current?.p_theirs(temp_gender) || ..()
+
+/datum/mind/p_them(capitalized, temp_gender)
+ return current?.p_them(capitalized, temp_gender) || ..()
+
+/datum/mind/p_have(temp_gender)
+ return current?.p_have(temp_gender) || ..()
+
+/datum/mind/p_are(temp_gender)
+ return current?.p_are(temp_gender) || ..()
+
+/datum/mind/p_were(temp_gender)
+ return current?.p_were(temp_gender) || ..()
+
+/datum/mind/p_do(temp_gender)
+ return current?.p_do(temp_gender) || ..()
+
+/datum/mind/p_s(temp_gender)
+ return current?.p_s(temp_gender) || ..()
+
+/datum/mind/p_es(temp_gender)
+ return current?.p_es(temp_gender) || ..()
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 54f2609cb24bf..811bc47d4a891 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -1,7 +1,7 @@
/mob/proc/say()
return
-/mob/verb/whisper()
+/mob/verb/whisper(message as text)
set name = "Whisper"
set category = "IC"
return
diff --git a/code/modules/mob/skills/antag_skill_setter.dm b/code/modules/mob/skills/antag_skill_setter.dm
index 598b1d1c3ce89..f27cd8476db5f 100644
--- a/code/modules/mob/skills/antag_skill_setter.dm
+++ b/code/modules/mob/skills/antag_skill_setter.dm
@@ -36,9 +36,12 @@
skillset.obtain_from_client(job, my_client, 1)
skillset.open_ui()
+/datum/antag_skill_setter/station/renegade
+ nm_type = /datum/nano_module/skill_ui/antag/rene
+
//This will obtain skills from the job selection before giving additional buffs.
/datum/antag_skill_setter/station/offstation
nm_type = /datum/nano_module/skill_ui/antag/station/offstation
//Placeholder for ai; defaults to experienced in everything like usual.
-/datum/antag_skill_setter/ai
\ No newline at end of file
+/datum/antag_skill_setter/ai
diff --git a/code/modules/mob/skills/skill.dm b/code/modules/mob/skills/skill.dm
index 3b15f3b29c222..1721d830ac46e 100644
--- a/code/modules/mob/skills/skill.dm
+++ b/code/modules/mob/skills/skill.dm
@@ -6,21 +6,21 @@ GLOBAL_LIST_EMPTY(skills)
hierarchy_type = /singleton/hierarchy/skill // Don't mess with this without changing how Initialize works.
var/desc = "Placeholder skill" // Generic description of this skill.
- // Names for different skill values, in order from 1 up.
+ // Names for different skill values, in order from 1 up.
var/levels = list( "Unskilled" = "Unskilled Description",
"Basic" = "Basic Description",
"Trained" = "Trained Description",
"Experienced" = "Experienced Description",
"Master" = "Professional Description")
var/difficulty = SKILL_AVERAGE //Used to compute how expensive the skill is
- var/default_max = SKILL_ADEPT //Makes the skill capped at this value in selection unless overriden at job level.
+ var/default_max = SKILL_TRAINED //Makes the skill capped at this value in selection unless overriden at job level.
var/prerequisites // A list of skill prerequisites, if needed.
/singleton/hierarchy/skill/proc/get_cost(level)
switch(level)
- if(SKILL_BASIC, SKILL_ADEPT)
+ if(SKILL_BASIC, SKILL_TRAINED)
return difficulty
- if(SKILL_EXPERT, SKILL_PROF)
+ if(SKILL_EXPERIENCED, SKILL_MASTER)
return 2*difficulty
else
return 0
@@ -115,7 +115,7 @@ GLOBAL_LIST_EMPTY(skills)
desc = "Allows you to operate exosuits well."
levels = list("Untrained" = "You are unfamiliar with exosuit controls, and if you attempt to use them you are liable to make mistakes.",
"Trained" = "You are proficient in exosuit operation and safety, and can use them without penalties.")
- prerequisites = list(SKILL_EVA = SKILL_ADEPT)
+ prerequisites = list(SKILL_EVA = SKILL_TRAINED)
default_max = SKILL_BASIC
difficulty = SKILL_AVERAGE
@@ -129,7 +129,7 @@ GLOBAL_LIST_EMPTY(skills)
"Experienced" = "You are an experienced pilot, and can safely take the helm of many types of craft. You could probably live in a spacecraft, and you're very well versed in essentially everything related to space-faring vessels. Not only can you fly a ship, but you can perform difficult maneuvers, and make most calculations related to piloting a spacecraft. You can maintain a ship. Skills of this level are typical for very experienced pilots. You have received formal piloting training. - You can somewhat avoid meteors on normal speed while using tiny shuttlecrafts.",
"Master" = "Not only are you an exceptional pilot, but you have mastered peripheral functions such as stellar navigation and bluespace jump plotting. You have experience performing complex maneuvers, managing squadrons of small craft, and operating in hostile environments. - You can mostly avoid meteors on normal speed using any shuttlecrafts. - Less meteors will hit the ship while passing through meteor fields.")
difficulty = SKILL_AVERAGE
- default_max = SKILL_ADEPT
+ default_max = SKILL_TRAINED
/singleton/hierarchy/skill/general/hauling
ID = "hauling"
@@ -189,9 +189,9 @@ GLOBAL_LIST_EMPTY(skills)
switch(level)
if(SKILL_BASIC)
return difficulty
- if(SKILL_ADEPT, SKILL_EXPERT)
+ if(SKILL_TRAINED, SKILL_EXPERIENCED)
return 2*difficulty
- if(SKILL_PROF)
+ if(SKILL_MASTER)
return 4*difficulty
else
return 0
@@ -210,11 +210,11 @@ GLOBAL_LIST_EMPTY(skills)
switch(level)
if(SKILL_BASIC)
return difficulty
- if(SKILL_ADEPT)
+ if(SKILL_TRAINED)
return 2*difficulty
- if(SKILL_EXPERT)
+ if(SKILL_EXPERIENCED)
return 3*difficulty
- if(SKILL_PROF)
+ if(SKILL_MASTER)
return 4*difficulty
else
return 0
@@ -232,9 +232,9 @@ GLOBAL_LIST_EMPTY(skills)
/singleton/hierarchy/skill/security/forensics/get_cost(level)
switch(level)
- if(SKILL_BASIC, SKILL_ADEPT, SKILL_EXPERT)
+ if(SKILL_BASIC, SKILL_TRAINED, SKILL_EXPERIENCED)
return difficulty * 2
- if(SKILL_PROF)
+ if(SKILL_MASTER)
return 3 * difficulty
else
return 0
diff --git a/code/modules/mob/skills/skill_buffs.dm b/code/modules/mob/skills/skill_buffs.dm
index 8cb586e791bb1..d3b6cf3d427e1 100644
--- a/code/modules/mob/skills/skill_buffs.dm
+++ b/code/modules/mob/skills/skill_buffs.dm
@@ -71,7 +71,7 @@
buff.skillset = skillset
skillset.on_levels_change()
if(duration)
- addtimer(new Callback(buff, /datum/skill_buff/proc/remove), duration)
+ addtimer(CALLBACK(buff, TYPE_PROC_REF(/datum/skill_buff, remove)), duration)
return buff
//Takes a buff type or datum; typing is false here.
diff --git a/code/modules/mob/skills/skill_ui.dm b/code/modules/mob/skills/skill_ui.dm
index 5e15b149cf050..c9048cbcd7e2d 100644
--- a/code/modules/mob/skills/skill_ui.dm
+++ b/code/modules/mob/skills/skill_ui.dm
@@ -229,6 +229,9 @@ Similar, but for station antags that have jobs.
*/
/datum/nano_module/skill_ui/antag/station
max_choices = list(0, 0, 2, 1, 1)
+
+/datum/nano_module/skill_ui/antag/rene
+ max_choices = list(0, 1, 1, 0, 0)
/*
Similar, but for off-station jobs (Bearcat, Verne, survivor etc.).
*/
diff --git a/code/modules/mob/skills/skill_verbs.dm b/code/modules/mob/skills/skill_verbs.dm
index 4d5ef7726eac5..edd831c202425 100644
--- a/code/modules/mob/skills/skill_verbs.dm
+++ b/code/modules/mob/skills/skill_verbs.dm
@@ -51,7 +51,7 @@ GLOBAL_LIST_INIT(skill_verbs, init_subtypes(/datum/skill_verb))
return
cooling_down = 1
update_verb()
- addtimer(new Callback(src, .proc/remove_cooldown), cooldown)
+ addtimer(CALLBACK(src, PROC_REF(remove_cooldown)), cooldown)
/*
The Instruct verb. buffs untrained -> basic and requires skill in the skill training as well as leadership.
Robots and antags can instruct.
@@ -71,7 +71,7 @@ Robots and antags can instruct.
if(!..())
return
for(var/singleton/hierarchy/skill/S in GLOB.skills)
- if(skillset.owner.skill_check(S.type, SKILL_EXPERT))
+ if(skillset.owner.skill_check(S.type, SKILL_EXPERIENCED))
return 1
/mob/proc/instruct(mob/living/carbon/human/target as mob in oview(2))
@@ -95,7 +95,7 @@ Robots and antags can instruct.
var/options = list()
for(var/singleton/hierarchy/skill/S in GLOB.skills)
- if(!target.skill_check(S.type, SKILL_BASIC) && skill_check(S.type, SKILL_EXPERT))
+ if(!target.skill_check(S.type, SKILL_BASIC) && skill_check(S.type, SKILL_EXPERIENCED))
options[S.name] = S
if(!length(options))
to_chat(src, SPAN_NOTICE("There is nothing you can teach \the [target]."))
@@ -115,7 +115,7 @@ Robots and antags can instruct.
if(target.skill_check(skill.type, SKILL_BASIC))
to_chat(src, SPAN_NOTICE("\The [target] is too skilled to gain any benefit from a short lesson."))
return
- if(!skill_check(skill.type, SKILL_EXPERT))
+ if(!skill_check(skill.type, SKILL_EXPERIENCED))
return
target.buff_skill(list(skill.type = 1), buff_type = /datum/skill_buff/instruct)
@@ -181,9 +181,9 @@ The Appraise verb. Used on objects to estimate their value.
switch(skill)
if(SKILL_MAX)
return 5
- if(SKILL_EXPERT)
+ if(SKILL_EXPERIENCED)
return 10
- if(SKILL_ADEPT)
+ if(SKILL_TRAINED)
return 20
else
return 50
@@ -201,7 +201,7 @@ The Appraise verb. Used on objects to estimate their value.
/datum/skill_verb/noirvision/should_see_verb()
if(!..())
return
- if(!skillset.owner.skill_check(SKILL_FORENSICS, SKILL_PROF))
+ if(!skillset.owner.skill_check(SKILL_FORENSICS, SKILL_MASTER))
return
return 1
diff --git a/code/modules/mob/skills/skillset.dm b/code/modules/mob/skills/skillset.dm
index 5b69b69123e53..b00170a9e64e3 100644
--- a/code/modules/mob/skills/skillset.dm
+++ b/code/modules/mob/skills/skillset.dm
@@ -115,18 +115,39 @@
// A generic way of modifying times via skill values
/mob/proc/skill_delay_mult(skill_path, factor = 0.3)
var/points = get_skill_value(skill_path)
- switch(points)
- if(SKILL_BASIC)
- return max(0, 1 + 3*factor)
- if(SKILL_NONE)
- return max(0, 1 + 6*factor)
- else
- return max(0, 1 + (SKILL_DEFAULT - points) * factor)
-
-/mob/proc/do_skilled(base_delay, skill_path , atom/target = null, factor = 0.3, do_flags = DO_PUBLIC_UNIQUE)
- return do_after(src, base_delay * skill_delay_mult(skill_path, factor), target, do_flags)
-
-// A generic way of modifying success probabilities via skill values. Higher factor means skills have more effect. fail_chance is the chance at SKILL_NONE.
+ return max(0, 1 + (SKILL_BASELINE - points) * factor)
+
+
+/**
+ * Performs a do_after timer, with a modifier applied to the delay time based on the user's skill in the given skill path.
+ *
+ * **Parameters**
+ * - `base_delay` Integer (Seconds) - Base delay for the timer. This is added to the final delay time.
+ * - `skill_path` Path or list of paths (Subtypes of `/singleton/hierarchy/skill`; Any of `SKILL_*`) - The skill used to modify the delay time. If provided as a list, the skill that produces the smallest timer is used. Passed to `skill_delay_mult()`.
+ * - `target` (Default `null`) - The atom being interacted with. Passed directly to `do_after()`.
+ * - `factor` Float (Default `0.3`) - Modifier factor used to modify the delay applied to the action. Passed to `skill_delay_mult()`.
+ * - `do_flags` Bitflag (Any of `DO_*`; Default `DO_PUBLIC_UNIQUE`) - Flags passed on to `do_after()`.
+ *
+ * Returns boolean. Whether or not the do after check passed.
+ */
+
+/mob/proc/do_skilled(base_delay, skill_path, atom/target = null, factor = 0.3, do_flags = DO_PUBLIC_UNIQUE)
+ var/delay_multiplier = skill_delay_total(skill_path, factor)
+ base_delay *= delay_multiplier
+ return do_after(src, base_delay, target, do_flags)
+
+/mob/proc/skill_delay_total(skill_path, factor = 0.3)
+ if(isnull(skill_path))
+ return 1
+ if(islist(skill_path))
+ for(var/path as anything in skill_path)
+ var/check_delay = skill_delay_mult(path, factor)
+ if (check_delay < . || isnull(.))
+ . = check_delay
+ else
+ . = skill_delay_mult(skill_path, factor)
+
+// A generic way of modifying success probabilities via skill values. Higher factor means skills have more effect. fail_chance is the chance at SKILL_UNSKILLED.
/mob/proc/skill_fail_chance(skill_path, fail_chance, no_more_fail = SKILL_MAX, factor = 1)
var/points = get_skill_value(skill_path)
if(points >= no_more_fail)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 8d771154fa935..7b24f892e788c 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -9,10 +9,10 @@
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
stunned = 1
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in organs)
qdel(t)
- var/atom/movable/overlay/animation = new /atom/movable/overlay(src)
+ var/atom/movable/fake_overlay/animation = new /atom/movable/fake_overlay(src)
animation.icon_state = "blank"
animation.icon = 'icons/mob/mob.dmi'
flick("h2monkey", animation)
@@ -58,7 +58,7 @@
drop_from_inventory(W)
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
return ..()
/mob/proc/AIize(move=1)
@@ -79,21 +79,21 @@
if(move)
var/obj/loc_landmark
- for(var/obj/effect/landmark/start/sloc in landmarks_list)
+ for(var/obj/landmark/start/sloc in landmarks_list)
if (sloc.name != "AI")
continue
if (locate(/mob/living) in sloc.loc)
continue
loc_landmark = sloc
if (!loc_landmark)
- for(var/obj/effect/landmark/tripai in landmarks_list)
+ for(var/obj/landmark/tripai in landmarks_list)
if (tripai.name == "tripai")
if((locate(/mob/living) in tripai.loc) || (locate(/obj/structure/AIcore) in tripai.loc))
continue
loc_landmark = tripai
if (!loc_landmark)
to_chat(O, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.")
- for(var/obj/effect/landmark/start/sloc in landmarks_list)
+ for(var/obj/landmark/start/sloc in landmarks_list)
if (sloc.name == "AI")
loc_landmark = sloc
if (!loc_landmark)
@@ -119,7 +119,7 @@
regenerate_icons()
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in organs)
qdel(t)
@@ -136,6 +136,11 @@
if(mmi_type)
O.mmi = new mmi_type(O)
O.mmi.transfer_identity(src)
+ if(O.mind.assigned_job && O.mind.assigned_job.faction)
+ O.faction = O.mind.assigned_job.faction
+ O.mind.faction = O.mind.assigned_job.faction
+ else
+ O.mind.faction = faction
else
O.key = key
@@ -155,7 +160,7 @@
regenerate_icons()
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in organs)
qdel(t)
@@ -188,7 +193,7 @@
regenerate_icons()
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in organs) //this really should not be necessary
qdel(t)
@@ -217,7 +222,7 @@
regenerate_icons()
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
icon = null
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in organs)
qdel(t)
diff --git a/code/modules/mob/vv_handlers.dm b/code/modules/mob/vv_handlers.dm
new file mode 100644
index 0000000000000..211b17c8f2475
--- /dev/null
+++ b/code/modules/mob/vv_handlers.dm
@@ -0,0 +1,9 @@
+/singleton/vv_set_handler/mob_confused
+ handled_type = /mob
+ predicates = list(GLOBAL_PROC_REF(is_num_predicate), GLOBAL_PROC_REF(is_non_negative_predicate), GLOBAL_PROC_REF(is_int_predicate))
+ handled_vars = list("confused")
+
+
+/singleton/vv_set_handler/mob_confused/handle_set_var(datum/O, variable, var_value, client)
+ var/mob/mob = O
+ mob.set_confused(var_value, var_value)
diff --git a/code/modules/modular_computers/NTNet/NTNet.dm b/code/modules/modular_computers/NTNet/NTNet.dm
index ade254663f500..3ea698d855146 100644
--- a/code/modules/modular_computers/NTNet/NTNet.dm
+++ b/code/modules/modular_computers/NTNet/NTNet.dm
@@ -42,9 +42,8 @@ var/global/datum/ntnet/ntnet_global = new()
/datum/ntnet/New()
if(ntnet_global && (ntnet_global != src))
ntnet_global = src // There can be only one.
- for(var/obj/machinery/ntnet_relay/R in SSmachines.machinery)
- relays.Add(R)
- R.NTNet = src
+ for(var/obj/machinery/ntnet_relay/R as anything in SSmachines.get_machinery_of_type(/obj/machinery/ntnet_relay))
+ relays += R
build_software_lists()
build_emails_list()
build_reports_list()
@@ -144,10 +143,10 @@ var/global/datum/ntnet/ntnet_global = new()
if(!category_list)
category_list = list()
available_software_by_category[prog.category] = category_list
- ADD_SORTED(available_station_software, prog, /proc/cmp_program)
- ADD_SORTED(category_list, prog, /proc/cmp_program)
+ ADD_SORTED(available_station_software, prog, GLOBAL_PROC_REF(cmp_program))
+ ADD_SORTED(category_list, prog, GLOBAL_PROC_REF(cmp_program))
if(prog.available_on_syndinet)
- ADD_SORTED(available_antag_software, prog, /proc/cmp_program)
+ ADD_SORTED(available_antag_software, prog, GLOBAL_PROC_REF(cmp_program))
/// Generates service email list.
/datum/ntnet/proc/build_emails_list()
diff --git a/code/modules/modular_computers/NTNet/NTNet_relay.dm b/code/modules/modular_computers/NTNet/NTNet_relay.dm
index 9abc8732c6065..0bea7c8091a0d 100644
--- a/code/modules/modular_computers/NTNet/NTNet_relay.dm
+++ b/code/modules/modular_computers/NTNet/NTNet_relay.dm
@@ -1,11 +1,12 @@
-// Relays don't handle any actual communication. Global NTNet datum does that, relays only tell the datum if it should or shouldn't work.
/obj/machinery/ntnet_relay
- name = "NTNet Quantum Relay"
+ name = "\improper NTNet quantum relay"
desc = "A very complex router and transmitter capable of connecting electronic devices together. Looks fragile."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "relay"
use_power = POWER_USE_ACTIVE
active_power_usage = 20000 //20kW, apropriate for machine that keeps massive cross-Zlevel wireless network operational.
idle_power_usage = 100
- icon_state = "bus"
+ icon_state = "relay"
anchored = TRUE
density = TRUE
construct_state = /singleton/machine_construction/default/panel_closed
@@ -13,57 +14,84 @@
stat_immune = 0
machine_name = "\improper NTNet quantum relay"
machine_desc = "Maintains a copy of proprietary software used to provide NTNet service to all valid devices in the region. Essentially a huge router."
- /// Reference to NTNet. This is mostly for backwards reference and to allow varedit modifications from ingame.
- var/datum/ntnet/NTNet = null
+
/// Set to FALSE if the relay was turned off
var/enabled = TRUE
+
/// Set to TRUE if the relay failed due to (D)DoS attack
var/dos_failure = FALSE
+
/// List of backwards reference for qdel() stuff
var/list/dos_sources = list()
/// Amount of DoS "packets" in this relay's buffer.
var/dos_overload = 0
+
/// Amount of DoS "packets" in buffer required to crash the relay.
var/dos_capacity = 500
+
/// Amount of DoS "packets" dissipated over time.
var/dos_dissipate = 1
-// TODO: Implement more logic here. For now it's only a placeholder.
+
+/obj/machinery/ntnet_relay/Destroy()
+ if (ntnet_global)
+ ntnet_global.relays -= src
+ ntnet_global.add_log("Quantum Relay ([uid]) connection severed. Current amount of linked relays: [length(ntnet_global.relays)]")
+ for (var/datum/computer_file/program/ntnet_dos/D in dos_sources)
+ D.target = null
+ D.error = "Connection to quantum relay severed"
+ LAZYCLEARLIST(dos_sources)
+ return ..()
+
+
+/obj/machinery/ntnet_relay/Initialize()
+ . = ..()
+ uid = gl_uid
+ gl_uid++
+ if (ntnet_global)
+ ntnet_global.relays += src
+ ntnet_global.add_log("New Quantum Relay ([uid]) activated. Current amount of linked relays: [length(ntnet_global.relays)]")
+ update_icon()
+
+
/obj/machinery/ntnet_relay/operable()
- if(!..(MACHINE_STAT_EMPED))
+ if (!enabled)
return FALSE
- if(dos_failure)
+ if (dos_failure)
return FALSE
- if(!enabled)
+ if (inoperable(MACHINE_STAT_EMPED))
return FALSE
return TRUE
+
/obj/machinery/ntnet_relay/on_update_icon()
- if(operable())
- icon_state = "bus"
- else
- icon_state = "bus_off"
+ ClearOverlays()
+ if (operable())
+ AddOverlays(list(
+ "relay_lights_working",
+ emissive_appearance(icon, "relay_lights_working")
+ ))
+ if (panel_open)
+ AddOverlays("relay_panel")
+
/obj/machinery/ntnet_relay/Process()
- if(operable())
+ if (dos_overload)
+ dos_overload = max(0, dos_overload - dos_dissipate)
+ if (dos_failure)
+ if (!dos_overload)
+ dos_failure = FALSE
+ ntnet_global.add_log("Quantum relay ([uid]) switched from overload recovery mode to normal operation mode.")
+ else if (dos_overload > dos_capacity)
+ dos_failure = TRUE
+ ntnet_global.add_log("Quantum relay ([uid]) switched from normal operation mode to overload recovery mode.")
+ if (operable())
update_use_power(POWER_USE_ACTIVE)
else
update_use_power(POWER_USE_IDLE)
+ update_icon()
- if(dos_overload)
- dos_overload = max(0, dos_overload - dos_dissipate)
-
- // If DoS traffic exceeded capacity, crash.
- if((dos_overload > dos_capacity) && !dos_failure)
- dos_failure = TRUE
- update_icon()
- ntnet_global.add_log("Quantum relay ([uid]) switched from normal operation mode to overload recovery mode.")
- // If the DoS buffer reaches 0 again, restart.
- if((dos_overload == 0) && dos_failure)
- dos_failure = FALSE
- update_icon()
- ntnet_global.add_log("Quantum relay ([uid]) switched from overload recovery mode to normal operation mode.")
/obj/machinery/ntnet_relay/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/topic_state/state = GLOB.default_state)
var/list/data = list()
@@ -80,46 +108,30 @@
ui.open()
ui.set_auto_update(1)
+
/obj/machinery/ntnet_relay/interface_interact(mob/living/user)
ui_interact(user)
return TRUE
+
/obj/machinery/ntnet_relay/Topic(href, href_list)
- if(..())
+ if (..())
return TOPIC_HANDLED
- if(href_list["restart"])
+ if (href_list["restart"])
dos_overload = 0
dos_failure = FALSE
update_icon()
ntnet_global.add_log("Quantum relay ([uid]) manually restarted from overload recovery mode to normal operation mode.")
return TOPIC_HANDLED
- else if(href_list["toggle"])
+ else if (href_list["toggle"])
enabled = !enabled
ntnet_global.add_log("Quantum relay ([uid]) manually [enabled ? "enabled" : "disabled"].")
update_icon()
return TOPIC_HANDLED
- else if(href_list["purge"])
+ else if (href_list["purge"])
ntnet_global.banned_nids.Cut()
ntnet_global.add_log("Override: Network blacklist manually cleared from Quantum relay ([uid]).")
return TOPIC_HANDLED
- else if(href_list["eject_drive"] && uninstall_component(/obj/item/stock_parts/computer/hard_drive/portable))
+ else if (href_list["eject_drive"] && uninstall_component(/obj/item/stock_parts/computer/hard_drive/portable))
visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps and ejects its portable disk.")
-
-/obj/machinery/ntnet_relay/New()
- uid = gl_uid
- gl_uid++
- if(ntnet_global)
- ntnet_global.relays.Add(src)
- NTNet = ntnet_global
- ntnet_global.add_log("New Quantum Relay ([uid]) activated. Current amount of linked relays: [length(NTNet.relays)]")
- ..()
-
-/obj/machinery/ntnet_relay/Destroy()
- if(ntnet_global)
- ntnet_global.relays.Remove(src)
- ntnet_global.add_log("Quantum Relay ([uid]) connection severed. Current amount of linked relays: [length(NTNet.relays)]")
- NTNet = null
- for(var/datum/computer_file/program/ntnet_dos/D in dos_sources)
- D.target = null
- D.error = "Connection to quantum relay severed"
- ..()
+ return TOPIC_HANDLED
diff --git a/code/modules/modular_computers/NTNet/emails/email_account.dm b/code/modules/modular_computers/NTNet/emails/email_account.dm
index 3bbd8feb8dd8e..b13a0333f94c3 100644
--- a/code/modules/modular_computers/NTNet/emails/email_account.dm
+++ b/code/modules/modular_computers/NTNet/emails/email_account.dm
@@ -32,7 +32,7 @@
fullname = _fullname
if(_assignment)
assignment = _assignment
- ADD_SORTED(ntnet_global.email_accounts, src, /proc/cmp_emails_asc)
+ ADD_SORTED(ntnet_global.email_accounts, src, GLOBAL_PROC_REF(cmp_emails_asc))
..()
/datum/computer_file/data/email_account/Destroy()
diff --git a/code/modules/modular_computers/computers/modular_computer/core.dm b/code/modules/modular_computers/computers/modular_computer/core.dm
index 8a70e7bc57f1a..71907054b189e 100644
--- a/code/modules/modular_computers/computers/modular_computer/core.dm
+++ b/code/modules/modular_computers/computers/modular_computer/core.dm
@@ -79,14 +79,14 @@
/obj/item/modular_computer/on_update_icon()
icon_state = icon_state_unpowered
- overlays.Cut()
+ ClearOverlays()
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
- overlays += os.get_screen_overlay()
- overlays += os.get_keyboard_overlay()
+ AddOverlays(os.get_screen_overlay())
+ AddOverlays(os.get_keyboard_overlay())
if(enabled)
- set_light(0.2, 0.1, light_strength)
+ set_light(light_strength, 0.2)
else
set_light(0)
diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm
index dd04567fd1fab..11baefb5affa3 100644
--- a/code/modules/modular_computers/computers/modular_computer/interaction.dm
+++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm
@@ -31,7 +31,7 @@
if(enabled)
bsod = 1
update_icon()
- to_chat(usr, "You press a hard-reset button on \the [src]. It displays a brief debug screen before shutting down.")
+ to_chat(usr, "You press a hard-reset button on [src]. It displays a brief debug screen before shutting down.")
shutdown_computer(FALSE)
spawn(2 SECONDS)
bsod = 0
@@ -82,7 +82,7 @@
user = usr
if(!portable_drive)
- to_chat(user, "There is no portable device connected to \the [src].")
+ to_chat(user, "There is no portable device connected to [src].")
return
uninstall_component(user, portable_drive)
@@ -113,6 +113,54 @@
else if(!enabled && screen_on)
turn_on(user)
+/obj/item/modular_computer/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/list/all_components = get_all_components()
+ if(!length(all_components))
+ balloon_alert(user, "нет компонентов!")
+ return
+ var/list/component_names = list()
+ for(var/obj/item/stock_parts/computer/H in all_components)
+ component_names.Add(H.name)
+ var/choice = input(usr, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names
+ if(!choice)
+ return
+ if(!Adjacent(usr))
+ return
+ var/obj/item/stock_parts/computer/H = find_hardware_by_name(choice)
+ if(!H)
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ uninstall_component(user, H)
+
+/obj/item/modular_computer/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/list/components = get_all_components()
+ if(length(components))
+ to_chat(user, "Remove all components from [src] before disassembling it.")
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ new /obj/item/stack/material/steel(get_turf(src.loc), steel_sheet_cost)
+ src.visible_message("[src] has been disassembled by [user].")
+ qdel(src)
+
+/obj/item/modular_computer/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!get_damage_value())
+ USE_FEEDBACK_NOTHING_TO_REPAIR(user)
+ return
+ var/damage = get_damage_value()
+ var/amount = round(damage/75)
+ if(!tool.tool_start_check(amount, 1))
+ return
+ USE_FEEDBACK_REPAIR_START(user)
+ if(!tool.use_as_tool(src, user, damage / (1 SECONDS), amount, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ USE_FEEDBACK_REPAIR_FINISH(user)
+ revive_health()
+
/obj/item/modular_computer/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/card/id)) // ID Card, try to insert it.
var/obj/item/card/id/I = W
@@ -141,11 +189,11 @@
return
if(istype(W, /obj/item/paper) || istype(W, /obj/item/paper_bundle))
if(nano_printer)
- nano_printer.attackby(W, user)
+ W.resolve_attackby(nano_printer, user)
if(istype(W, /obj/item/aicard))
if(!ai_slot)
return
- ai_slot.attackby(W, user)
+ W.resolve_attackby(ai_slot, user)
if(!modifiable)
return ..()
@@ -155,69 +203,17 @@
if(C.hardware_size <= max_hardware_size)
try_install_component(user, C)
else
- to_chat(user, "This component is too large for \the [src].")
- if(isWrench(W))
- var/list/components = get_all_components()
- if(length(components))
- to_chat(user, "Remove all components from \the [src] before disassembling it.")
- return
- new /obj/item/stack/material/steel( get_turf(src.loc), steel_sheet_cost )
- src.visible_message("\The [src] has been disassembled by [user].")
- qdel(src)
- return
- if(isWelder(W))
- var/obj/item/weldingtool/WT = W
- if(!WT.isOn())
- to_chat(user, "\The [W] is off.")
- return
-
- if(!get_damage_value())
- to_chat(user, "\The [src] does not require repairs.")
- return
-
- to_chat(user, "You begin repairing damage to \the [src]...")
- var/damage = get_damage_value()
- if(WT.remove_fuel(round(damage / 75)) && do_after(user, damage / 10, src, DO_REPAIR_CONSTRUCT))
- revive_health()
- to_chat(user, "You repair \the [src].")
- return
-
- if(isScrewdriver(W))
- var/list/all_components = get_all_components()
- if(!length(all_components))
- to_chat(user, "This device doesn't have any components installed.")
- return
- var/list/component_names = list()
- for(var/obj/item/stock_parts/computer/H in all_components)
- component_names.Add(H.name)
-
- var/choice = input(usr, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names
-
- if(!choice)
- return
-
- if(!Adjacent(usr))
- return
-
- var/obj/item/stock_parts/computer/H = find_hardware_by_name(choice)
-
- if(!H)
- return
-
- uninstall_component(user, H)
-
- return
-
- ..()
+ to_chat(user, "This component is too large for [src].")
+ . = ..()
/obj/item/modular_computer/examine(mob/user)
. = ..()
if(enabled)
- to_chat(user, "The time [stationtime2text()] is displayed in the corner of the screen.")
+ . += SPAN_NOTICE("The time [stationtime2text()] is displayed in the corner of the screen.")
if(card_slot && card_slot.stored_card)
- to_chat(user, "[card_slot.stored_card] is inserted into it.")
+ . += SPAN_NOTICE("[card_slot.stored_card] is inserted into it.")
/obj/item/modular_computer/MouseDrop(atom/over_object)
var/mob/M = usr
@@ -231,11 +227,12 @@
/obj/item/modular_computer/CtrlAltClick(mob/user)
if(!CanPhysicallyInteract(user))
- return 0
+ return FALSE
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
os.open_terminal(user)
- return 1
+ return TRUE
+ return FALSE
/obj/item/modular_computer/CouldUseTopic(mob/user)
..()
diff --git a/code/modules/modular_computers/computers/subtypes/dev_console.dm b/code/modules/modular_computers/computers/subtypes/dev_console.dm
index 73fdc616a996a..96f6a4a18b63c 100644
--- a/code/modules/modular_computers/computers/subtypes/dev_console.dm
+++ b/code/modules/modular_computers/computers/subtypes/dev_console.dm
@@ -48,7 +48,7 @@
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
if(os.on)
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_range_on, light_power_on, light_color)
else
set_light(0)
@@ -60,6 +60,7 @@
/obj/machinery/computer/modular/get_keyboard_overlay()
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
+ icon_keyboard = os.get_keyboard_state()
return os.get_keyboard_overlay()
/obj/machinery/computer/modular/emag_act(remaining_charges, mob/user)
@@ -75,7 +76,7 @@
var/obj/item/stock_parts/computer/P = path
return initial(P.external_slot)
-/obj/machinery/computer/modular/attackby(obj/item/I, mob/user)
+/obj/machinery/computer/modular/use_tool(obj/item/I, mob/living/user, list/click_params)
if(istype(I, /obj/item/stock_parts/computer/hard_drive/portable))
if(portable_drive)
to_chat(user, SPAN_WARNING("There's already \a [portable_drive] plugged in."))
@@ -120,11 +121,12 @@
/obj/machinery/computer/modular/CtrlAltClick(mob/user)
if(!CanPhysicallyInteract(user))
- return 0
+ return FALSE
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
os.open_terminal(user)
- return 1
+ return TRUE
+ return FALSE
/obj/machinery/computer/modular/verb/emergency_shutdown()
set name = "Forced Shutdown"
diff --git a/code/modules/modular_computers/computers/subtypes/dev_laptop.dm b/code/modules/modular_computers/computers/subtypes/dev_laptop.dm
index c3df17ea782a1..5a52c129d4ee8 100644
--- a/code/modules/modular_computers/computers/subtypes/dev_laptop.dm
+++ b/code/modules/modular_computers/computers/subtypes/dev_laptop.dm
@@ -26,19 +26,20 @@
// Prevents carrying of open laptops inhand.
// While they work inhand, i feel it'd make tablets lose some of their high-mobility advantage they have over laptops now.
if(!CanPhysicallyInteract(user))
- return
+ return FALSE
if(!isturf(loc))
to_chat(usr, "\The [src] has to be on a stable surface first!")
- return
+ return TRUE
anchored = !anchored
screen_on = anchored
update_icon()
+ return TRUE
/obj/item/modular_computer/laptop/on_update_icon()
if(anchored)
..()
else
- overlays.Cut()
+ ClearOverlays()
icon_state = icon_state_closed
/obj/item/modular_computer/laptop/preset
diff --git a/code/modules/modular_computers/computers/subtypes/dev_pda.dm b/code/modules/modular_computers/computers/subtypes/dev_pda.dm
index d72577e3e8615..1923330792cae 100644
--- a/code/modules/modular_computers/computers/subtypes/dev_pda.dm
+++ b/code/modules/modular_computers/computers/subtypes/dev_pda.dm
@@ -21,16 +21,14 @@
/obj/item/modular_computer/pda/CtrlClick(mob/user)
if(!isturf(loc)) ///If we are dragging the PDA across the ground we don't want to remove the pen
remove_pen(user)
- else
- . = ..()
+ return TRUE
+ return ..()
/obj/item/modular_computer/pda/AltClick(mob/user)
- if(!CanPhysicallyInteract(user))
- return
- if(card_slot && istype(card_slot.stored_card))
+ if (CanPhysicallyInteract(user) && card_slot && istype(card_slot.stored_card))
card_slot.eject_id(user)
- else
- ..()
+ return TRUE
+ return ..()
/obj/item/modular_computer/pda/proc/receive_notification(message = null)
if (!enabled || bsod)
@@ -55,8 +53,8 @@
/obj/item/storage/box/PDAs
name = "box of spare PDAs"
desc = "A box of spare PDA microcomputers."
- icon = 'icons/obj/pda.dmi'
- icon_state = "pdabox"
+ icon = 'icons/obj/boxes.dmi'
+ icon_state = "pda"
/obj/item/storage/box/PDAs/Initialize()
. = ..()
diff --git a/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm b/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm
index 3b2181146b4b0..347305b8f7fc4 100644
--- a/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm
+++ b/code/modules/modular_computers/computers/subtypes/dev_telescreen.dm
@@ -1,7 +1,7 @@
/obj/item/modular_computer/telescreen
name = "telescreen"
desc = "A wall-mounted touchscreen computer."
- icon = 'icons/obj/modular_telescreen.dmi'
+ icon = 'icons/obj/machines/modular_telescreen.dmi'
icon_state = "telescreen"
icon_state_unpowered = "telescreen"
hardware_flag = PROGRAM_TELESCREEN
@@ -21,37 +21,37 @@
// Allows us to create "north bump" "south bump" etc. named objects, for more comfortable mapping.
name = "telescreen"
-/obj/item/modular_computer/telescreen/attackby(obj/item/W as obj, mob/user as mob)
- if(isCrowbar(W))
- if(anchored)
- shutdown_computer()
- anchored = FALSE
- screen_on = FALSE
- pixel_x = 0
- pixel_y = 0
- to_chat(user, "You unsecure \the [src].")
- else
- var/choice = input(user, "Where do you want to place \the [src]?", "Offset selection") in list("North", "South", "West", "East", "This tile", "Cancel")
- var/valid = FALSE
- switch(choice)
- if("North")
- valid = TRUE
- pixel_y = 32
- if("South")
- valid = TRUE
- pixel_y = -32
- if("West")
- valid = TRUE
- pixel_x = -32
- if("East")
- valid = TRUE
- pixel_x = 32
- if("This tile")
- valid = TRUE
-
- if(valid)
- anchored = TRUE
- screen_on = TRUE
- to_chat(user, "You secure \the [src].")
+/obj/item/modular_computer/telescreen/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(anchored)
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
- ..()
+ shutdown_computer()
+ anchored = FALSE
+ screen_on = FALSE
+ pixel_x = 0
+ pixel_y = 0
+ to_chat(user, "You unsecure [src].")
+ return
+
+ var/choice = input(user, "Where do you want to place [src]?", "Offset selection") in list("North", "South", "West", "East", "This tile", "Cancel")
+ var/valid = FALSE
+ switch(choice)
+ if("North")
+ valid = TRUE
+ pixel_y = 32
+ if("South")
+ valid = TRUE
+ pixel_y = -32
+ if("West")
+ valid = TRUE
+ pixel_x = -32
+ if("East")
+ valid = TRUE
+ pixel_x = 32
+ if("This tile")
+ valid = TRUE
+ if(valid)
+ anchored = TRUE
+ screen_on = TRUE
+ to_chat(user, "You secure [src].")
diff --git a/code/modules/modular_computers/computers/subtypes/preset_pda.dm b/code/modules/modular_computers/computers/subtypes/preset_pda.dm
index 3e324a10357eb..cf49eef7d8879 100644
--- a/code/modules/modular_computers/computers/subtypes/preset_pda.dm
+++ b/code/modules/modular_computers/computers/subtypes/preset_pda.dm
@@ -13,6 +13,7 @@
var/datum/extension/interactive/ntos/os = get_extension(src, /datum/extension/interactive/ntos)
if(os)
os.create_file(new/datum/computer_file/program/email_client())
+ os.create_file(new/datum/computer_file/program/chatclient())
os.create_file(new/datum/computer_file/program/crew_manifest())
os.create_file(new/datum/computer_file/program/wordprocessor())
os.create_file(new/datum/computer_file/program/records())
diff --git a/code/modules/modular_computers/file_system/data.dm b/code/modules/modular_computers/file_system/data.dm
index 8787609c7e8ae..08ecf311baaa6 100644
--- a/code/modules/modular_computers/file_system/data.dm
+++ b/code/modules/modular_computers/file_system/data.dm
@@ -39,7 +39,7 @@
/// Use this to do things like automatic records and blackboxes. Alternative for paper records.
/// Values can be in the editor for each map or as a subtype.
/// This is an obj because raw atoms can't be placed in DM or third-party mapping tools.
-/obj/effect/computer_file_creator
+/obj/computer_file_creator
name = "computer file creator"
desc = "This is a mapping tool used for installing text files onto a modular device when it's mapped on top of them. If you see it, it's bugged."
icon = 'icons/effects/landmarks.dmi'
@@ -47,17 +47,17 @@
anchored = TRUE
unacidable = TRUE
simulated = FALSE
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
/// The name that the file will have once it's created.
var/file_name = "helloworld"
/// The contents of this file. Uses paper formatting.
var/file_info = "Hello World!"
-/obj/effect/computer_file_creator/Initialize()
+/obj/computer_file_creator/Initialize()
. = ..()
return INITIALIZE_HINT_LATELOAD
-/obj/effect/computer_file_creator/LateInitialize()
+/obj/computer_file_creator/LateInitialize(mapload)
var/turf/T = get_turf(src)
for (var/obj/O in T)
if (!istype(O, /obj/machinery/computer/modular) && !istype(O, /obj/item/modular_computer))
diff --git a/code/modules/modular_computers/file_system/manifest.dm b/code/modules/modular_computers/file_system/manifest.dm
index 068eeb2fc852d..4feea9cebaad2 100644
--- a/code/modules/modular_computers/file_system/manifest.dm
+++ b/code/modules/modular_computers/file_system/manifest.dm
@@ -1,18 +1,18 @@
// Generates a simple HTML crew manifest for use in various places
/proc/html_crew_manifest(monochrome, OOC)
var/list/dept_data = list(
- list("names" = list(), "header" = "Heads of Staff", "flag" = COM),
- list("names" = list(), "header" = "Command Support", "flag" = SPT),
- list("names" = list(), "header" = "Research", "flag" = SCI),
- list("names" = list(), "header" = "Security", "flag" = SEC),
- list("names" = list(), "header" = "Medical", "flag" = MED),
- list("names" = list(), "header" = "Engineering", "flag" = ENG),
- list("names" = list(), "header" = "Supply", "flag" = SUP),
- list("names" = list(), "header" = "Exploration", "flag" = EXP),
- list("names" = list(), "header" = "Service", "flag" = SRV),
- list("names" = list(), "header" = "Civilian", "flag" = CIV),
- list("names" = list(), "header" = "Miscellaneous", "flag" = MSC),
- list("names" = list(), "header" = "Silicon")
+ list("names" = list(), "header" = "Heads of Staff", "flag" = COM, "color" = MANIFEST_COLOR_COMMAND),
+ list("names" = list(), "header" = "Command Support", "flag" = SPT, "color" = MANIFEST_COLOR_SUPPORT),
+ list("names" = list(), "header" = "Research", "flag" = SCI, "color" = MANIFEST_COLOR_SCIENCE),
+ list("names" = list(), "header" = "Security", "flag" = SEC, "color" = MANIFEST_COLOR_SECURITY),
+ list("names" = list(), "header" = "Medical", "flag" = MED, "color" = MANIFEST_COLOR_MEDICAL),
+ list("names" = list(), "header" = "Engineering", "flag" = ENG, "color" = MANIFEST_COLOR_ENGINEER),
+ list("names" = list(), "header" = "Supply", "flag" = SUP, "color" = MANIFEST_COLOR_SUPPLY),
+ list("names" = list(), "header" = "Exploration", "flag" = EXP, "color" = MANIFEST_COLOR_EXPLORER),
+ list("names" = list(), "header" = "Service", "flag" = SRV, "color" = MANIFEST_COLOR_SERVICE),
+ list("names" = list(), "header" = "Civilian", "flag" = CIV, "color" = MANIFEST_COLOR_CIVILIAN),
+ list("names" = list(), "header" = "Miscellaneous", "flag" = MSC, "color" = MANIFEST_COLOR_MISC),
+ list("names" = list(), "header" = "Silicon", "color" = MANIFEST_COLOR_SILICON),
)
var/list/misc //Special departments for easier access
var/list/bot
@@ -27,17 +27,20 @@
var/dat = {"
"
dat = replacetext(dat, "\n", "") // so it can be placed on paper correctly
@@ -114,8 +110,12 @@
return filtered_entries
/proc/filtered_nano_crew_manifest(list/filter, blacklist = FALSE)
+ RETURN_TYPE(/list)
var/list/filtered_entries = list()
for(var/datum/computer_file/report/crew_record/CR in department_crew_manifest(filter, blacklist))
+ if (CR.get_status() == "Stored")
+ continue
+
filtered_entries.Add(list(list(
"name" = CR.get_name(),
"rank" = CR.get_job(),
@@ -141,6 +141,7 @@
)
/proc/flat_nano_crew_manifest()
+ RETURN_TYPE(/list)
. = list()
. += filtered_nano_crew_manifest(null, TRUE)
. += silicon_nano_crew_manifest(SSjobs.titles_by_department(MSC))
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index 426c79677d583..b05a491582625 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -51,6 +51,9 @@
/// Holder for skill value of current/recent operator for programs that tick.
var/operator_skill = SKILL_MIN
+ /// How much processing size the program should take up.
+ var/processing_size = 1
+
/datum/computer_file/program/Destroy()
if(computer && computer.active_program == src)
computer.kill_program(src)
@@ -160,13 +163,13 @@
return TRUE
/**
- * CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
- * Topic calls are automatically forwarded from NanoModule this program contains.
- * Calls beginning with "PRG_" are reserved for programs handling.
- * Calls beginning with "PC_" are reserved for computer handling (by whatever runs the program)
- * ALWAYS INCLUDE PARENT CALL ..() AT THE TOP OF THE PROC OR DIE IN FIRE.
- * Return values should be one of TOPIC_*
- */
+ * CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
+ * Topic calls are automatically forwarded from NanoModule this program contains.
+ * Calls beginning with "PRG_" are reserved for programs handling.
+ * Calls beginning with "PC_" are reserved for computer handling (by whatever runs the program)
+ * ALWAYS INCLUDE PARENT CALL ..() AT THE TOP OF THE PROC OR DIE IN FIRE.
+ * Return values should be one of TOPIC_*
+ */
/datum/computer_file/program/Topic(href, href_list)
if(..())
return TOPIC_HANDLED
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/access_decrypter.dm b/code/modules/modular_computers/file_system/programs/antagonist/access_decrypter.dm
index f55a2a4dc54c2..e540bdca09afb 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/access_decrypter.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/access_decrypter.dm
@@ -17,8 +17,8 @@
var/datum/access/target_access = null
var/list/restricted_access_codes = list(access_change_ids) // access codes that are not hackable due to balance reasons
var/list/skill_restricted_access_codes = list(
- access_network = SKILL_EXPERT,
- access_network_admin = SKILL_PROF
+ access_network = SKILL_EXPERIENCED,
+ access_network_admin = SKILL_MASTER
)
/datum/computer_file/program/access_decrypter/on_shutdown(forced)
@@ -46,7 +46,7 @@
progress += get_speed()
if(progress >= target_progress)
- if(prob(20 * max(SKILL_ADEPT - operator_skill, 0))) // Oops
+ if(prob(20 * max(SKILL_TRAINED - operator_skill, 0))) // Oops
var/list/valid_access_values = get_all_station_access()
valid_access_values -= restricted_access_codes
valid_access_values -= RFID.stored_card.access
@@ -100,10 +100,10 @@
return TOPIC_HANDLED
/datum/computer_file/program/access_decrypter/proc/get_sneak_chance()
- return max(operator_skill - SKILL_ADEPT, 0) * 30
+ return max(operator_skill - SKILL_TRAINED, 0) * 30
/datum/computer_file/program/access_decrypter/proc/get_speed()
- var/skill_speed_modifier = 1 + (operator_skill - SKILL_ADEPT)/(SKILL_MAX - SKILL_MIN)
+ var/skill_speed_modifier = 1 + (operator_skill - SKILL_TRAINED)/(SKILL_MAX - SKILL_MIN)
var/obj/item/stock_parts/computer/processor_unit/CPU = computer.get_component(PART_CPU)
return CPU?.processing_power * skill_speed_modifier
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
index fc107c6dd6ffc..82539577f8fd0 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
@@ -99,7 +99,7 @@
operator_skill = usr.get_skill_value(SKILL_COMPUTER)
var/list/sources_to_show = list(computer.get_network_tag())
- var/extra_to_show = 2 * max(operator_skill - SKILL_ADEPT, 0)
+ var/extra_to_show = 2 * max(operator_skill - SKILL_TRAINED, 0)
if(extra_to_show)
for(var/i = 1, i <= extra_to_show, i++)
var/nid = pick(ntnet_global.registered_nids)
diff --git a/code/modules/modular_computers/file_system/programs/command/card.dm b/code/modules/modular_computers/file_system/programs/command/card.dm
index 373773b1c95b4..d814a34b70037 100644
--- a/code/modules/modular_computers/file_system/programs/command/card.dm
+++ b/code/modules/modular_computers/file_system/programs/command/card.dm
@@ -1,6 +1,6 @@
/datum/computer_file/program/card_mod
filename = "cardmod"
- filedesc = "ID card modification program"
+ filedesc = "ID Card Modification Program"
nanomodule_path = /datum/nano_module/program/card_mod
program_icon_state = "id"
program_key_state = "id_key"
diff --git a/code/modules/modular_computers/file_system/programs/command/comm.dm b/code/modules/modular_computers/file_system/programs/command/comm.dm
index a258b85dc80ea..65e8662ed3e4b 100644
--- a/code/modules/modular_computers/file_system/programs/command/comm.dm
+++ b/code/modules/modular_computers/file_system/programs/command/comm.dm
@@ -30,7 +30,7 @@
var/current_status = STATE_DEFAULT
var/msg_line1 = ""
var/msg_line2 = ""
- var/centcomm_message_cooldown = 0
+ var/centcom_message_cooldown = 0
var/announcment_cooldown = 0
var/datum/announcement/priority/crew_announcement = new
var/current_viewing_message_id = 0
@@ -154,7 +154,7 @@
if(href_list["target"] == "emagged")
if(program)
if(is_authenticated(user) && program.computer.emagged() && !issilicon(usr) && ntn_comm)
- if(centcomm_message_cooldown)
+ if(centcom_message_cooldown)
to_chat(usr, SPAN_WARNING("Arrays recycling. Please stand by."))
SSnano.update_uis(src)
return
@@ -164,12 +164,12 @@
Syndicate_announce(input, usr)
to_chat(usr, SPAN_NOTICE("Message transmitted."))
log_say("[key_name(usr)] has made an illegal announcement: [input]")
- centcomm_message_cooldown = 1
+ centcom_message_cooldown = 1
spawn(300)//30 second cooldown
- centcomm_message_cooldown = 0
+ centcom_message_cooldown = 0
else if(href_list["target"] == "regular")
if(is_authenticated(user) && !issilicon(usr) && ntn_comm)
- if(centcomm_message_cooldown)
+ if(centcom_message_cooldown)
to_chat(usr, SPAN_WARNING("Arrays recycling. Please stand by."))
SSnano.update_uis(src)
return
@@ -179,12 +179,12 @@
var/input = sanitize(input("Please choose a message to transmit to [GLOB.using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "") as null|text)
if(!input || !can_still_topic())
return
- Centcomm_announce(input, usr)
+ Centcom_announce(input, usr)
to_chat(usr, SPAN_NOTICE("Message transmitted."))
log_say("[key_name(usr)] has made an IA [GLOB.using_map.boss_short] announcement: [input]")
- centcomm_message_cooldown = 1
+ centcom_message_cooldown = 1
spawn(300) //30 second cooldown
- centcomm_message_cooldown = 0
+ centcom_message_cooldown = 0
if("evac")
. = TOPIC_HANDLED
if(is_authenticated(user))
@@ -330,7 +330,7 @@ var/global/last_message_id = 0
/proc/is_relay_online()
- for(var/obj/machinery/bluespacerelay/M in SSmachines.machinery)
+ for(var/obj/machinery/bluespacerelay/M as anything in SSmachines.get_machinery_of_type(/obj/machinery/bluespacerelay))
if(M.stat == EMPTY_BITFIELD)
return 1
return 0
diff --git a/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm
index 0db1837b81f0a..3004301c1cf95 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm
@@ -123,7 +123,7 @@
categories[length(categories)]["alarms"] += list(list(
"name" = sanitize(A.alarm_name()),
- "origin_lost" = A.origin == null,
+ "origin_lost" = isnull(A.origin),
"has_cameras" = length(cameras),
"cameras" = cameras,
"lost_sources" = length(lost_sources) ? sanitize(english_list(lost_sources, nothing_text = "", and_text = ", ")) : ""))
diff --git a/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm b/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm
index 855e0d0bb615a..5188945b8b6ce 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm
@@ -30,7 +30,7 @@
log_debug("\The [src] was given an unexpected req_access: [req_access]")
if(monitored_alarm_ids)
- for(var/obj/machinery/alarm/alarm in SSmachines.machinery)
+ for(var/obj/machinery/alarm/alarm as anything in SSmachines.get_machinery_of_type(/obj/machinery/alarm))
if(alarm.alarm_id && (alarm.alarm_id in monitored_alarm_ids))
monitored_alarms += alarm
// machines may not yet be ordered at this point
diff --git a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm
index 50dc8c5709e87..49edaeed30c0e 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm
@@ -87,13 +87,13 @@
/datum/nano_module/power_monitor/proc/refresh_sensors()
grid_sensors = list()
var/connected_z_levels = GetConnectedZlevels(get_host_z())
- for(var/obj/machinery/power/sensor/S in SSmachines.machinery)
+ for(var/obj/machinery/power/sensor/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/sensor))
if((S.long_range) || (S.loc.z in connected_z_levels)) // Consoles have range on their Z-Level. Sensors with long_range var will work between Z levels.
if(S.name_tag == "#UNKN#") // Default name. Shouldn't happen!
warning("Powernet sensor with unset ID Tag! [S.x]X [S.y]Y [S.z]Z")
else
grid_sensors += S
- GLOB.destroyed_event.register(S, src, /datum/nano_module/power_monitor/proc/remove_sensor)
+ GLOB.destroyed_event.register(S, src, TYPE_PROC_REF(/datum/nano_module/power_monitor, remove_sensor))
/datum/nano_module/power_monitor/proc/remove_sensor(removed_sensor, update_ui = TRUE)
if(active_sensor == removed_sensor)
@@ -101,7 +101,7 @@
if(update_ui)
SSnano.update_uis(src)
grid_sensors -= removed_sensor
- GLOB.destroyed_event.unregister(removed_sensor, src, /datum/nano_module/power_monitor/proc/remove_sensor)
+ GLOB.destroyed_event.unregister(removed_sensor, src, TYPE_PROC_REF(/datum/nano_module/power_monitor, remove_sensor))
// Allows us to process UI clicks, which are relayed in form of hrefs.
/datum/nano_module/power_monitor/Topic(href, href_list)
diff --git a/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm b/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm
index dc0c1584ec3f3..f149fafc04763 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm
@@ -123,12 +123,12 @@
// Parameters: None
// Description: Refreshes local list of known devices.
/datum/nano_module/rcon/proc/FindDevices()
- known_SMESs = new /list()
- for(var/obj/machinery/power/smes/buildable/SMES in SSmachines.machinery)
+ known_SMESs = list()
+ for(var/obj/machinery/power/smes/buildable/SMES as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/smes/buildable))
if(AreConnectedZLevels(get_host_z(), get_z(SMES)) && SMES.RCon_tag && (SMES.RCon_tag != "NO_TAG") && SMES.RCon)
known_SMESs.Add(SMES)
- known_breakers = new /list()
- for(var/obj/machinery/power/breakerbox/breaker in SSmachines.machinery)
+ known_breakers = list()
+ for(var/obj/machinery/power/breakerbox/breaker as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/breakerbox))
if(AreConnectedZLevels(get_host_z(), get_z(breaker)) && breaker.RCon_tag != "NO_TAG")
known_breakers.Add(breaker)
diff --git a/code/modules/modular_computers/file_system/programs/engineering/shields_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/shields_monitor.dm
index 383283cd7695e..cd52777f43619 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/shields_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/shields_monitor.dm
@@ -23,7 +23,7 @@
/datum/nano_module/shields_monitor/proc/get_shields()
var/list/shields = list()
var/connected_z_levels = GetConnectedZlevels(get_host_z())
- for(var/obj/machinery/power/shield_generator/S in SSmachines.machinery)
+ for(var/obj/machinery/power/shield_generator/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/shield_generator))
if(!(S.z in connected_z_levels))
continue
shields.Add(S)
@@ -98,7 +98,7 @@
var/obj/machinery/power/shield_generator/S = locate(href_list["ref"]) in shields
if(S)
deselect_shield()
- GLOB.destroyed_event.register(S, src, /datum/nano_module/shields_monitor/proc/deselect_shield)
+ GLOB.destroyed_event.register(S, src, TYPE_PROC_REF(/datum/nano_module/shields_monitor, deselect_shield))
active = S
return 1
diff --git a/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm
index b0921f7b02bbc..cb7fa435f6414 100644
--- a/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm
@@ -47,7 +47,7 @@
/datum/nano_module/supermatter_monitor/proc/refresh()
supermatters = list()
var/valid_z_levels = GetConnectedZlevels(get_host_z())
- for(var/obj/machinery/power/supermatter/S in SSmachines.machinery)
+ for(var/obj/machinery/power/supermatter/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/supermatter))
// Delaminating, not within coverage, not on a tile.
if(S.grav_pulling || S.exploded || !(S.z in valid_z_levels) || !isturf(S.loc))
continue
@@ -64,7 +64,7 @@
/datum/nano_module/supermatter_monitor/proc/process_data_output(skill, value)
switch(skill)
- if(SKILL_NONE)
+ if(SKILL_UNSKILLED)
return (0.6 + 0.8 * rand()) * value
if(SKILL_BASIC)
return (0.8 + 0.4 * rand()) * value
diff --git a/code/modules/modular_computers/file_system/programs/generic/camera.dm b/code/modules/modular_computers/file_system/programs/generic/camera.dm
index b18afb73894c2..e75feb8252f65 100644
--- a/code/modules/modular_computers/file_system/programs/generic/camera.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/camera.dm
@@ -20,6 +20,8 @@
return access_research
if(NETWORK_THUNDER)
return 0
+ if(NETWORK_HELMETS)
+ return access_eva
return access_security // Default for all other networks
@@ -41,6 +43,12 @@
var/obj/machinery/camera/current_camera = null
var/current_network = null
+
+/datum/nano_module/camera_monitor/Destroy()
+ reset_current()
+ . = ..()
+
+
/datum/nano_module/camera_monitor/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, state = GLOB.default_state)
var/list/data = host.initial_data()
@@ -102,6 +110,9 @@
if (!os?.get_ntnet_status() && !C.is_helmet_cam)
to_chat(usr, "Unable to establish a connection.")
return
+ if (C.inoperable(MACHINE_STAT_EMPED))
+ to_chat(usr, "Unable to establish a connection.")
+ return
switch_to_camera(usr, C)
return 1
@@ -143,12 +154,21 @@
current_camera = C
if(current_camera)
+ GLOB.destroyed_event.register(current_camera, src, PROC_REF(reset_current))
+ GLOB.moved_event.register(current_camera, src, PROC_REF(camera_moved))
var/mob/living/L = current_camera.loc
if(istype(L))
L.tracking_initiated()
+/datum/nano_module/camera_monitor/proc/camera_moved(atom/movable/moved_atom, atom/old_loc, atom/new_loc)
+ if (AreConnectedZLevels(get_z(old_loc), get_z(new_loc)))
+ return
+ reset_current()
+
/datum/nano_module/camera_monitor/proc/reset_current()
if(current_camera)
+ GLOB.destroyed_event.unregister(current_camera, src, PROC_REF(reset_current))
+ GLOB.moved_event.unregister(current_camera, src, PROC_REF(camera_moved))
var/mob/living/L = current_camera.loc
if(istype(L))
L.tracking_cancelled()
diff --git a/code/modules/modular_computers/file_system/programs/generic/configurator.dm b/code/modules/modular_computers/file_system/programs/generic/configurator.dm
index 50b7abf49b11d..abebf5762a4cf 100644
--- a/code/modules/modular_computers/file_system/programs/generic/configurator.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/configurator.dm
@@ -12,6 +12,7 @@
unsendable = 1
undeletable = 1
size = 4
+ processing_size = 0.5
available_on_ntnet = FALSE
requires_ntnet = FALSE
nanomodule_path = /datum/nano_module/program/computer_configurator
diff --git a/code/modules/modular_computers/file_system/programs/generic/docks.dm b/code/modules/modular_computers/file_system/programs/generic/docks.dm
index 2e30293e9752a..c5ff538cf8756 100644
--- a/code/modules/modular_computers/file_system/programs/generic/docks.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/docks.dm
@@ -30,7 +30,7 @@
/datum/nano_module/docking/proc/refresh_docks()
docking_controllers.Cut()
var/list/zlevels = GetConnectedZlevels(get_host_z())
- for(var/obj/machinery/embedded_controller/radio/airlock/docking_port/D in SSmachines.machinery)
+ for(var/obj/machinery/embedded_controller/radio/airlock/docking_port/D as anything in SSmachines.get_machinery_of_type(/obj/machinery/embedded_controller/radio/airlock/docking_port))
if(D.z in zlevels)
var/shuttleside = 0
for(var/sname in SSshuttle.shuttles) //do not touch shuttle-side ones
diff --git a/code/modules/modular_computers/file_system/programs/generic/email_client.dm b/code/modules/modular_computers/file_system/programs/generic/email_client.dm
index 58fa0c26b308b..d6904ca8a7fa5 100644
--- a/code/modules/modular_computers/file_system/programs/generic/email_client.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/email_client.dm
@@ -5,12 +5,13 @@
program_icon_state = "generic"
program_key_state = "generic_key"
program_menu_icon = "mail-closed"
- size = 7
+ size = 1
+ processing_size = 0 //It's the only way to ensure people actually have it on.
requires_ntnet = TRUE
available_on_ntnet = TRUE
var/stored_login = ""
var/stored_password = ""
- usage_flags = PROGRAM_ALL
+ usage_flags = PROGRAM_ALL|PROGRAM_NO_KILL
category = PROG_OFFICE
nanomodule_path = /datum/nano_module/email_client
@@ -91,15 +92,8 @@
/datum/nano_module/email_client/proc/mail_received(datum/computer_file/data/email_message/received_message)
var/mob/living/L = get_holder_of_type(host, /mob/living)
if(L)
- var/list/msg = list()
- msg += "*--*\n"
- msg += "[SPAN_NOTICE("New mail received from [received_message.source]:")]\n"
- msg += "Subject: [received_message.title]\nMessage:\n[digitalPencode2html(received_message.stored_data)]\n"
- if(received_message.attachment)
- msg += "Attachment: [received_message.attachment.filename].[received_message.attachment.filetype] ([received_message.attachment.size]GQ)\n"
- msg += "Reply\n"
- msg += "*--*"
- to_chat(L, jointext(msg, null))
+ var/msg = "[SPAN_NOTICE("New mail received from [received_message.source]:")] Subject: [received_message.title]"
+ to_chat(L, msg)
/datum/nano_module/email_client/Destroy()
log_out()
diff --git a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
index aac088537fdca..eb3395399f5a4 100644
--- a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
@@ -6,6 +6,7 @@
program_key_state = "generic_key"
program_menu_icon = "folder-collapsed"
size = 8
+ processing_size = 0.5
requires_ntnet = FALSE
available_on_ntnet = FALSE
undeletable = TRUE
diff --git a/code/modules/modular_computers/file_system/programs/generic/game.dm b/code/modules/modular_computers/file_system/programs/generic/game.dm
index 0efec0cf2da3e..129f16b052acd 100644
--- a/code/modules/modular_computers/file_system/programs/generic/game.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/game.dm
@@ -11,7 +11,7 @@
size = 5 // Size in GQ. Integers only. Smaller sizes should be used for utility/low use programs (like this one), while large sizes are for important programs.
requires_ntnet = FALSE // This particular program does not require NTNet network conectivity...
available_on_ntnet = TRUE // ... but we want it to be available for download.
- nanomodule_path = /datum/nano_module/arcade_classic/ // Path of relevant nano module. The nano module is defined further in the file.
+ nanomodule_path = /datum/nano_module/arcade_classic // Path of relevant nano module. The nano module is defined further in the file.
var/picked_enemy_name
usage_flags = PROGRAM_ALL
diff --git a/code/modules/modular_computers/file_system/programs/generic/news.dm b/code/modules/modular_computers/file_system/programs/generic/news.dm
index 834d182a28f96..7795d709c61ca 100644
--- a/code/modules/modular_computers/file_system/programs/generic/news.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/news.dm
@@ -120,7 +120,7 @@
story["has_photo"] = message.img != null
if (user && message.img) // user check here to avoid runtimes
var/resource_name = "newscaster_photo_[sanitize(feed.channel_name)]_[i].png"
- send_asset(user.client, resource_name)
+ SSassets.transport.send_assets(user.client, resource_name)
story["photo_dat"] = " "
story["story_ref"] = "\ref[message]"
data["active_stories"] += list(story)
diff --git a/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm
index 4928723295b11..79de2b0d5316c 100644
--- a/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm
@@ -8,6 +8,7 @@
unsendable = TRUE
undeletable = TRUE
size = 4
+ processing_size = 0.5
requires_ntnet = TRUE
requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD
available_on_ntnet = FALSE
diff --git a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm
index ee86f5b4831cf..5d49717319c1f 100644
--- a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm
@@ -5,7 +5,8 @@
program_key_state = "med_key"
program_menu_icon = "comment"
extended_desc = "This program allows communication over NTNRC network"
- size = 8
+ size = 2
+ processing_size = 0
requires_ntnet = TRUE
requires_ntnet_feature = NTNET_COMMUNICATION
network_destination = "NTNRC server"
@@ -20,6 +21,8 @@
var/operator_mode = FALSE
/// Administrator mode (invisible to other users + bypasses passwords)
var/netadmin_mode = FALSE
+ /// Mutes notifications
+ var/muted = FALSE
usage_flags = PROGRAM_ALL
/datum/computer_file/program/chatclient/New()
@@ -38,6 +41,7 @@
if(!message || !channel)
return
channel.add_message(message, username)
+ ntnrc_alert("[username] sent an NTNRC message.")
if(href_list["PRG_joinchannel"])
. = TOPIC_HANDLED
@@ -60,13 +64,16 @@
if(C && (password == C.password))
C.add_client(src)
channel = C
+ ntnrc_alert("A user has joined your channel.")
return TOPIC_HANDLED
C.add_client(src)
channel = C
+ ntnrc_alert("A user has joined your channel.")
if(href_list["PRG_leavechannel"])
. = TOPIC_HANDLED
if(channel)
channel.remove_client(src)
+ ntnrc_alert("A user has left your channel.")
channel = null
if(href_list["PRG_newchannel"])
. = TOPIC_HANDLED
@@ -74,12 +81,12 @@
var/channel_title = sanitizeSafe(input(user,"Enter channel name or leave blank to cancel:"), 64)
if(!channel_title)
return
- var/atom/A = computer.get_physical_host()
- var/datum/ntnet_conversation/C = new/datum/ntnet_conversation(A.z)
- C.add_client(src)
- C.operator = src
- channel = C
- C.title = channel_title
+ var/turf/turf = get_turf(computer.get_physical_host())
+ var/datum/ntnet_conversation/conversation = new/datum/ntnet_conversation(turf.z)
+ conversation.add_client(src)
+ conversation.operator = src
+ channel = conversation
+ conversation.title = channel_title
if(href_list["PRG_toggleadmin"])
. = TOPIC_HANDLED
if(netadmin_mode)
@@ -95,6 +102,7 @@
if(response == "Yes")
if(channel)
channel.remove_client(src)
+ ntnrc_alert("A user has left your channel.")
channel = null
else
return
@@ -109,6 +117,14 @@
channel.add_status_message("[username] is now known as [newname].")
username = newname
+ if (href_list["PRG_mutenotif"])
+ . = TOPIC_HANDLED
+ muted = !muted
+ if (muted)
+ computer.visible_notification(SPAN_NOTICE("Channel notifications have been disabled."))
+ return
+ computer.visible_notification(SPAN_NOTICE("Channel notifications have been enabled."))
+
if(href_list["PRG_savelog"])
. = TOPIC_HANDLED
if(!channel)
@@ -157,9 +173,10 @@
/datum/computer_file/program/chatclient/process_tick()
..()
- var/atom/A = computer.get_physical_host()
- if(channel && !(channel.source_z in GetConnectedZlevels(A.z)))
+ var/turf/turf = get_turf(computer.get_physical_host())
+ if (channel && !(channel.source_z in GetConnectedZlevels(turf.z)))
channel.remove_client(src)
+ ntnrc_alert("A user has left your channel.")
channel = null
if(program_state != PROGRAM_STATE_KILLED)
@@ -179,9 +196,19 @@
/datum/computer_file/program/chatclient/on_shutdown(forced = FALSE)
if(channel)
channel.remove_client(src)
+ src.ntnrc_alert("A user has left your channel.")
channel = null
..(forced)
+/datum/computer_file/program/chatclient/proc/ntnrc_alert(message)
+ if (!istype(src))
+ return
+ for (var/datum/computer_file/program/chatclient/client in channel.clients)
+ if (client == src || client.muted)
+ continue
+ client.computer.visible_notification(SPAN_NOTICE(message))
+ client.computer.audible_notification("sound/machines/ping.ogg")
+
/datum/nano_module/program/computer_chatclient
name = "NTNet Relay Chat Client"
@@ -217,8 +244,8 @@
else // Channel selection screen
var/list/all_channels[0]
- var/atom/A = C.computer.get_physical_host()
- var/list/connected_zs = GetConnectedZlevels(A.z)
+ var/turf/turf = get_turf(C.computer.get_physical_host())
+ var/list/connected_zs = GetConnectedZlevels(turf.z)
for(var/datum/ntnet_conversation/conv in ntnet_global.chat_channels)
if(conv && conv.title && (conv.source_z in connected_zs))
all_channels.Add(list(list(
diff --git a/code/modules/modular_computers/file_system/programs/generic/reports.dm b/code/modules/modular_computers/file_system/programs/generic/reports.dm
index 5662f05972b72..eab89b91e8f8f 100644
--- a/code/modules/modular_computers/file_system/programs/generic/reports.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/reports.dm
@@ -6,6 +6,8 @@
filedesc = "Report Editor"
nanomodule_path = /datum/nano_module/program/reports
extended_desc = "A general paperwork viewing and editing utility."
+ program_icon_state = "word"
+ program_key_state = "atmos_key"
size = 2
available_on_ntnet = TRUE
requires_ntnet = FALSE
diff --git a/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm
index 9b7c470502349..e6a642772c68d 100644
--- a/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm
+++ b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm
@@ -9,10 +9,11 @@
extended_desc = "This program connects to life signs monitoring system to provide basic information on crew health."
required_access = access_medical
requires_ntnet = TRUE
- network_destination = "crew lifesigns monitoring system"
+ network_destination = "crew life signs monitoring system"
size = 11
category = PROG_MONITOR
var/has_alert = FALSE
+ var/beeping = FALSE
/datum/computer_file/program/suit_sensors/process_tick()
..()
@@ -22,9 +23,14 @@
if(!has_alert)
program_icon_state = "crew-red"
ui_header = "crew_red.gif"
+ if(!beeping)
+ computer.visible_notification(SPAN_WARNING("Warning: vital signs beyond acceptable parameters."))
+ computer.audible_notification("sound/machines/twobeep.ogg")
+ beeping = TRUE //For medical sanity purposes, it'll only beep once per emergency.
else
program_icon_state = "crew"
ui_header = "crew_green.gif"
+ beeping = FALSE
update_computer_icon()
has_alert = !has_alert
diff --git a/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm b/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm
index 80195b186e361..8e13173afadb3 100644
--- a/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm
+++ b/code/modules/modular_computers/file_system/programs/research/ai_restorer.dm
@@ -23,7 +23,7 @@
if(..())
return 1
- if(!usr.skill_check(SKILL_COMPUTER, SKILL_ADEPT))
+ if(!usr.skill_check(SKILL_COMPUTER, SKILL_TRAINED))
return 1
var/mob/living/silicon/ai/A = get_ai()
@@ -92,7 +92,7 @@
var/list/data = host.initial_data()
data += "skill_fail"
- if(!user.skill_check(SKILL_COMPUTER, SKILL_ADEPT))
+ if(!user.skill_check(SKILL_COMPUTER, SKILL_TRAINED))
var/datum/extension/fake_data/fake_data = get_or_create_extension(src, /datum/extension/fake_data, 25)
data["skill_fail"] = fake_data.update_and_return_data()
data["terminal"] = !!program
diff --git a/code/modules/modular_computers/file_system/programs/security/forceauthorization.dm b/code/modules/modular_computers/file_system/programs/security/forceauthorization.dm
index fa3c48b797bd1..74708a8363ba4 100644
--- a/code/modules/modular_computers/file_system/programs/security/forceauthorization.dm
+++ b/code/modules/modular_computers/file_system/programs/security/forceauthorization.dm
@@ -5,6 +5,7 @@
size = 4
usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP
program_icon_state = "security"
+ program_key_state = "security_key"
program_menu_icon = "locked"
requires_ntnet = TRUE
available_on_ntnet = TRUE
diff --git a/code/modules/modular_computers/file_system/reports/crew_record.dm b/code/modules/modular_computers/file_system/reports/crew_record.dm
index ad2cad40f477c..43292c5a7b88a 100644
--- a/code/modules/modular_computers/file_system/reports/crew_record.dm
+++ b/code/modules/modular_computers/file_system/reports/crew_record.dm
@@ -1,6 +1,6 @@
GLOBAL_LIST_EMPTY(all_crew_records)
GLOBAL_LIST_INIT(blood_types, list("A-", "A+", "B-", "B+", "AB-", "AB+", "O-", "O+"))
-GLOBAL_LIST_INIT(physical_statuses, list("Active", "Disabled", "SSD", "Deceased", "MIA"))
+GLOBAL_LIST_INIT(physical_statuses, list("Active", "Disabled", "SSD", "Deceased", "MIA", "Stored"))
GLOBAL_VAR_INIT(default_physical_status, "Active")
GLOBAL_LIST_INIT(security_statuses, list("None", "Released", "Parolled", "Incarcerated", "Arrest"))
GLOBAL_VAR_INIT(default_security_status, "None")
@@ -24,12 +24,13 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
/datum/computer_file/report/crew_record/proc/load_from_mob(mob/living/carbon/human/H)
if(istype(H))
- photo_front = getFlatIcon(H, SOUTH, always_use_defdir = 1)
- photo_side = getFlatIcon(H, WEST, always_use_defdir = 1)
+ H.ImmediateOverlayUpdate()
+ photo_front = getFlatIcon(H, SOUTH, always_use_defdir = TRUE)
+ photo_side = getFlatIcon(H, WEST, always_use_defdir = TRUE)
else
var/mob/living/carbon/human/dummy = new()
- photo_front = getFlatIcon(dummy, SOUTH, always_use_defdir = 1)
- photo_side = getFlatIcon(dummy, WEST, always_use_defdir = 1)
+ photo_front = getFlatIcon(dummy, SOUTH, always_use_defdir = TRUE)
+ photo_side = getFlatIcon(dummy, WEST, always_use_defdir = TRUE)
qdel(dummy)
// Add honorifics, etc.
@@ -48,12 +49,7 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
set_name(H ? H.real_name : "Unset")
set_formal_name(formal_name)
set_job(H ? GetAssignment(H) : "Unset")
- var/gender_term = "Unset"
- if(H)
- var/datum/gender/G = gender_datums[H.get_sex()]
- if(G)
- gender_term = gender2text(G.formal_term)
- set_sex(gender_term)
+ set_sex(H ? H.p_They() : "Unset")
set_age(H ? H.age : 30)
set_status(GLOB.default_physical_status)
set_species(H ? H.get_species() : SPECIES_HUMAN)
@@ -117,7 +113,7 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
var/skills = list()
for(var/singleton/hierarchy/skill/S in GLOB.skills)
var/level = H.get_skill_value(S.type)
- if(level > SKILL_NONE)
+ if(level > SKILL_UNSKILLED)
skills += "[S.name], [S.levels[level]]"
set_skillset(jointext(skills,"\n"))
@@ -130,12 +126,19 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
// Used by character creation to create a record for new arrivals.
/proc/CreateModularRecord(mob/living/carbon/human/H)
var/datum/computer_file/report/crew_record/CR = new/datum/computer_file/report/crew_record()
- GLOB.all_crew_records.Add(CR)
CR.load_from_mob(H)
+
+ //ensure we don't get duplicated records
+ for (var/datum/computer_file/report/crew_record/record as anything in GLOB.all_crew_records)
+ if ((CR.get_name() == record.get_name()))
+ qdel(record)
+
+ GLOB.all_crew_records.Add(CR)
return CR
// Gets crew records filtered by set of positions
/proc/department_crew_manifest(list/filter_positions, blacklist = FALSE)
+ RETURN_TYPE(/list)
var/list/matches = list()
for(var/datum/computer_file/report/crew_record/CR in GLOB.all_crew_records)
var/rank = CR.get_job()
@@ -161,12 +164,14 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
dat += ""
return dat
+
/proc/get_crewmember_record(name)
- name = sanitize(name)
- for(var/datum/computer_file/report/crew_record/CR in GLOB.all_crew_records)
- if(CR.get_name() == name)
- return CR
- return null
+ RETURN_TYPE(/datum/computer_file/report/crew_record)
+ for (var/datum/computer_file/report/crew_record/record as anything in GLOB.all_crew_records)
+ var/record_name = record?.get_name()
+ if (record_name && html_decode(record_name) == name)
+ return record
+
/proc/GetAssignment(mob/living/carbon/human/H)
if(!H)
@@ -177,10 +182,22 @@ GLOBAL_VAR_INIT(arrest_security_status, "Arrest")
return H.mind.role_alt_title
return H.mind.assigned_role
-#define GETTER_SETTER(PATH, KEY) /datum/computer_file/report/crew_record/proc/get_##KEY(){var/datum/report_field/F = locate(/datum/report_field/##PATH/##KEY) in fields; if(F) return F.get_value()} \
-/datum/computer_file/report/crew_record/proc/set_##KEY(given_value){var/datum/report_field/F = locate(/datum/report_field/##PATH/##KEY) in fields; if(F) F.set_value(given_value)}
-#define SETUP_FIELD(NAME, KEY, PATH, ACCESS, ACCESS_EDIT) GETTER_SETTER(PATH, KEY); /datum/report_field/##PATH/##KEY;\
-/datum/computer_file/report/crew_record/generate_fields(){..(); var/datum/report_field/##KEY = add_field(/datum/report_field/##PATH/##KEY, ##NAME);\
+#define GETTER_SETTER(PATH, KEY) \
+/datum/computer_file/report/crew_record/proc/get_##KEY(){ \
+var/datum/report_field/F = locate(/datum/report_field/##PATH/##KEY) in fields; \
+if(F) \
+ return F.get_value() \
+} \
+/datum/computer_file/report/crew_record/proc/set_##KEY(given_value){ \
+var/datum/report_field/F = locate(/datum/report_field/##PATH/##KEY) in fields; \
+if(F) \
+ F.set_value(given_value) \
+}
+#define SETUP_FIELD(NAME, KEY, PATH, ACCESS, ACCESS_EDIT) \
+GETTER_SETTER(PATH, KEY); /datum/report_field/##PATH/##KEY; \
+/datum/computer_file/report/crew_record/generate_fields(){ \
+..(); \
+var/datum/report_field/##KEY = add_field(/datum/report_field/##PATH/##KEY, ##NAME); \
KEY.set_access(ACCESS, ACCESS_EDIT || ACCESS || access_bridge)}
// Fear not the preprocessor, for it is a friend. To add a field, use one of these, depending on value type and if you need special access to see it.
@@ -197,7 +214,7 @@ KEY.set_access(ACCESS, ACCESS_EDIT || ACCESS || access_bridge)}
FIELD_SHORT("Name", name, null, access_change_ids)
FIELD_SHORT("Formal Name", formal_name, null, access_change_ids)
FIELD_SHORT("Job", job, null, access_change_ids)
-FIELD_LIST("Sex", sex, record_genders(), null, access_change_ids)
+FIELD_LIST("Pronouns", sex, record_pronouns(), null, access_change_ids)
FIELD_NUM("Age", age, null, access_change_ids)
FIELD_LIST_EDIT("Status", status, GLOB.physical_statuses, null, access_medical)
@@ -240,12 +257,10 @@ FIELD_LONG("Exploitable Information", antagRecord, access_syndicate, access_synd
var/datum/mil_rank/RA = branch.ranks[rank]
. |= RA.name
-/datum/report_field/options/crew_record/sex/proc/record_genders()
+/datum/report_field/options/crew_record/sex/proc/record_pronouns()
. = list()
. |= "Unset"
- for(var/thing in gender_datums)
- var/datum/gender/G = gender_datums[thing]
- . |= gender2text(G.formal_term)
+ . |= list(FEMALE, MALE, NEUTER, PLURAL)
/datum/report_field/options/crew_record/branch/proc/record_branches()
. = list()
diff --git a/code/modules/modular_computers/file_system/reports/report.dm b/code/modules/modular_computers/file_system/reports/report.dm
index 055e33d1f23e3..66bcf00fc6bb9 100644
--- a/code/modules/modular_computers/file_system/reports/report.dm
+++ b/code/modules/modular_computers/file_system/reports/report.dm
@@ -30,19 +30,19 @@
. = ..()
/**
- * Access stuff. The report's access/access_edit should control whether it can be opened/submitted.
- * For field editing or viewing, use the field's access/access_edit permission instead.
- * The access system is based on "access patterns", lists of access values.
- * A user needs all access values in a pattern to be granted access.
- * A user needs to only match one of the potentially several stored access patterns to be granted access.
- * You must have access to have edit access.
- *
- * This proc resets the access to the report, resulting in just one access pattern for access/edit.
- * Arguments can be access values (numbers) or lists of access values.
- * If null is passed to one of the arguments, that access type is left alone. Pass list() to reset to no access needed instead.
- * The recursive option resets access to all fields in the report as well.
- * If the override option is set to FALSE, the access supplied will instead be added as another access pattern, rather than resetting the access.
- */
+ * Access stuff. The report's access/access_edit should control whether it can be opened/submitted.
+ * For field editing or viewing, use the field's access/access_edit permission instead.
+ * The access system is based on "access patterns", lists of access values.
+ * A user needs all access values in a pattern to be granted access.
+ * A user needs to only match one of the potentially several stored access patterns to be granted access.
+ * You must have access to have edit access.
+ *
+ * This proc resets the access to the report, resulting in just one access pattern for access/edit.
+ * Arguments can be access values (numbers) or lists of access values.
+ * If null is passed to one of the arguments, that access type is left alone. Pass list() to reset to no access needed instead.
+ * The recursive option resets access to all fields in the report as well.
+ * If the override option is set to FALSE, the access supplied will instead be added as another access pattern, rather than resetting the access.
+ */
/datum/computer_file/report/proc/set_access(access, access_edit, recursive = 1, override = 1)
if(access)
if(!islist(access))
@@ -142,10 +142,10 @@
for(var/datum/report_field/field in fields)
.["fields"] += list(field.generate_nano_data(given_access))
/**
- * This formats the report into pencode for use with paper and printing. Setting access to null will bypass access checks.
- * with_fields will include a field link after the field value (useful to print fillable forms).
- * no_html will strip any html, possibly killing useful formatting in the process.
- */
+ * This formats the report into pencode for use with paper and printing. Setting access to null will bypass access checks.
+ * with_fields will include a field link after the field value (useful to print fillable forms).
+ * no_html will strip any html, possibly killing useful formatting in the process.
+ */
/datum/computer_file/report/proc/generate_pencode(access, with_fields, no_html)
. = list()
. += "\[center\][logo]\[/center\]"
diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm
index b0f7d4b0c9c50..0528a1426f479 100644
--- a/code/modules/modular_computers/hardware/_hardware.dm
+++ b/code/modules/modular_computers/hardware/_hardware.dm
@@ -24,13 +24,13 @@
/// Whether attackby will be passed on it even with a closed panel
var/external_slot
+/obj/item/stock_parts/computer/multitool_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ to_chat(user, "***** DIAGNOSTICS REPORT *****")
+ to_chat(user, jointext(diagnostics(), "\n"))
+ to_chat(user, "******************************")
+
/obj/item/stock_parts/computer/attackby(obj/item/W as obj, mob/living/user as mob)
- // Multitool. Runs diagnostics
- if(isMultitool(W))
- to_chat(user, "***** DIAGNOSTICS REPORT *****")
- to_chat(user, jointext(diagnostics(), "\n"))
- to_chat(user, "******************************")
- return TRUE
// Nanopaste. Repair all damage if present for a single unit.
var/obj/item/stack/S = W
if(istype(S, /obj/item/stack/nanopaste))
@@ -52,7 +52,6 @@
return TRUE
return ..()
-
/// Returns a list of lines containing diagnostic information for display.
/obj/item/stock_parts/computer/proc/diagnostics()
return list("Hardware Integrity Test... (Corruption: [get_damage_percentage()]%) [is_failing() ? "FAIL" : is_malfunctioning() ? "WARN" : "PASS"]")
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 9cb8eb6a7d9a0..713bb2975b649 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -19,21 +19,26 @@
power_usage = power_usage_occupied
..()
+/obj/item/stock_parts/computer/ai_slot/screwdriver_act(mob/living/user, obj/item/tool)
+ if(!stored_card)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ to_chat(user, "You manually remove [stored_card] from [src].")
+ do_eject_ai(user)
+
/obj/item/stock_parts/computer/ai_slot/attackby(obj/item/W, mob/user)
if(..())
return TRUE
if(istype(W, /obj/item/aicard))
if(stored_card)
- to_chat(user, "\The [src] is already occupied.")
+ to_chat(user, "[src] is already occupied.")
return
if(!user.unEquip(W, src))
return
do_insert_ai(user, W)
return TRUE
- if(isScrewdriver(W) && stored_card)
- to_chat(user, "You manually remove \the [stored_card] from \the [src].")
- do_eject_ai(user)
- return TRUE
/obj/item/stock_parts/computer/ai_slot/Destroy()
if(stored_card)
@@ -57,7 +62,7 @@
device = locate() in src
if(!device.stored_card)
- to_chat(user, "There is no intellicard connected to \the [src].")
+ to_chat(user, "There is no intellicard connected to [src].")
return
device.do_eject_ai(user)
diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm
index 71b9e7c26841c..9f43f8fe6eda4 100644
--- a/code/modules/modular_computers/hardware/battery_module.dm
+++ b/code/modules/modular_computers/hardware/battery_module.dm
@@ -1,9 +1,9 @@
/**
- * This device is wrapper for actual power cell. I have decided to not use power
- * cells directly as even low-end cells available on station have tremendeous capacity
- * in comparsion. Higher tier cells would provide your device with nearly infinite
- * battery life, which is something i want to avoid.
- */
+ * This device is wrapper for actual power cell. I have decided to not use power
+ * cells directly as even low-end cells available on station have tremendeous capacity
+ * in comparsion. Higher tier cells would provide your device with nearly infinite
+ * battery life, which is something i want to avoid.
+ */
/obj/item/stock_parts/computer/battery_module
name = "standard battery"
desc = "A standard power cell, commonly seen in high-end portable microcomputers or low-end laptops. It's rating is 120 Wh."
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index a08444a2958f1..1813a7bca80ce 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -1,5 +1,5 @@
/obj/item/stock_parts/computer/card_slot
- name = "RFID card slot"
+ name = "\improper RFID card slot"
desc = "Slot that allows this computer to write data on RFID cards. Necessary for some programs to run properly."
power_usage = 10 //W
critical = FALSE
@@ -108,7 +108,7 @@
return TRUE
/obj/item/stock_parts/computer/card_slot/broadcaster // read only
- name = "RFID card broadcaster"
+ name = "\improper RFID card broadcaster"
desc = "Reads and broadcasts the RFID signal of an inserted card."
can_write = FALSE
can_broadcast = TRUE
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index 5c91352239b29..39fac6ef69a4b 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -1,10 +1,10 @@
/**
- * Important! Avoid editing the content of file objects already saved on a disk,
- * as this bypasses checks for anything that might prevent saving. Instead,
- * clone the file, make the changes to the clone, and attempt to save the clone
- * with the same filename using save_file(). Additional useful procs for data
- * files in particular are also available.
- */
+ * Important! Avoid editing the content of file objects already saved on a disk,
+ * as this bypasses checks for anything that might prevent saving. Instead,
+ * clone the file, make the changes to the clone, and attempt to save the clone
+ * with the same filename using save_file(). Additional useful procs for data
+ * files in particular are also available.
+ */
/obj/item/stock_parts/computer/hard_drive
name = "basic hard drive"
desc = "A small power efficient solid state drive, with 128GQ of storage capacity for use in basic computers where power efficiency is desired."
diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm
index ea37d70c65b31..b4ded48bc44a7 100644
--- a/code/modules/modular_computers/hardware/network_card.dm
+++ b/code/modules/modular_computers/hardware/network_card.dm
@@ -1,5 +1,3 @@
-var/global/ntnet_card_uid = 1
-
/obj/item/stock_parts/computer/network_card
name = "basic NTNet network card"
desc = "A basic network card for usage with standard NTNet frequencies."
@@ -53,8 +51,7 @@ var/global/ntnet_card_uid = 1
/obj/item/stock_parts/computer/network_card/Initialize()
. = ..()
- identification_id = ntnet_card_uid
- ntnet_card_uid++
+ identification_id = random_id("network_card_id", 1, 999)
/obj/item/stock_parts/computer/network_card/Destroy()
ntnet_global.unregister(identification_id)
diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
index 6f1465c09f740..6f66fdde8fc62 100644
--- a/code/modules/modular_computers/laptop_vendor.dm
+++ b/code/modules/modular_computers/laptop_vendor.dm
@@ -3,7 +3,7 @@
/obj/machinery/lapvend
name = "computer vendor"
desc = "A vending machine with a built-in microfabricator, capable of dispensing various computers."
- icon = 'icons/obj/vending.dmi'
+ icon = 'icons/obj/machines/vending.dmi'
icon_state = "laptop"
layer = BELOW_OBJ_LAYER
anchored = TRUE
@@ -269,8 +269,10 @@
ui.set_auto_update(1)
-/obj/machinery/lapvend/attackby(obj/item/W as obj, mob/user as mob)
+/obj/machinery/lapvend/use_tool(obj/item/W, mob/living/user, list/click_params)
var/obj/item/card/id/I = W.GetIdCard()
+ if (!I)
+ return ..()
// Awaiting payment state
if(state == 2)
if(process_payment(I,W))
@@ -293,14 +295,14 @@
fabricated_tablet = null
ping("Enjoy your new product!")
state = 3
- return 1
- return 0
+ return TRUE
+
return ..()
// Simplified payment processing, returns 1 on success.
/obj/machinery/lapvend/proc/process_payment(obj/item/card/id/I, obj/item/ID_container)
- if(I==ID_container || ID_container == null)
+ if(I==ID_container || isnull(ID_container))
visible_message(SPAN_INFO("\The [usr] swipes \the [I] through \the [src]."))
else
visible_message(SPAN_INFO("\The [usr] swipes \the [ID_container] through \the [src]."))
diff --git a/code/modules/modular_computers/ntos/components.dm b/code/modules/modular_computers/ntos/components.dm
index 4ae6a08944da0..0fdb95be1cb14 100644
--- a/code/modules/modular_computers/ntos/components.dm
+++ b/code/modules/modular_computers/ntos/components.dm
@@ -49,6 +49,10 @@
/datum/extension/interactive/ntos/proc/get_ntnet_status()
if(!on) // No signal if the computer isn't on.
return 0
+
+ if (isAdminLevel(get_z(holder)))
+ return 3
+
if(isnull(ntnet_status))
var/obj/item/stock_parts/computer/network_card/network_card = get_component(PART_NETWORK)
if(network_card)
@@ -101,7 +105,7 @@
/datum/extension/interactive/ntos/proc/voltage_overload()
var/atom/A = holder
if(istype(A))
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ var/datum/effect/spark_spread/s = new /datum/effect/spark_spread
s.set_up(10, 1, A.loc)
s.start()
diff --git a/code/modules/modular_computers/ntos/ntos.dm b/code/modules/modular_computers/ntos/ntos.dm
index 324fae51512cb..119b2ba403a01 100644
--- a/code/modules/modular_computers/ntos/ntos.dm
+++ b/code/modules/modular_computers/ntos/ntos.dm
@@ -7,8 +7,10 @@
var/on = FALSE
/// A currently active program running on the computer.
var/datum/computer_file/program/active_program = null
- /// All programms currently running, background or active.
+ /// All programs currently running, background or active, including small programs.
var/list/running_programs = list()
+ /// The processing size being used by all programs.
+ var/processing_size = 0
/// dmi where the screen overlays are kept, defaults to holder's icon if unset
var/screen_icon_file
@@ -113,6 +115,11 @@
/datum/extension/interactive/ntos/proc/kill_program_remote(datum/computer_file/program/P, forced = FALSE, mob/user = null)
if(!P)
return
+
+ if (!forced && GET_FLAGS(P.usage_flags, PROGRAM_NO_KILL))
+ minimize_program(user)
+ return
+
P.on_shutdown(forced)
running_programs -= P
if(active_program == P)
@@ -135,22 +142,26 @@
/datum/extension/interactive/ntos/proc/run_program_remote(filename, mob/user = null, loud = 0)
var/datum/computer_file/program/P = get_file(filename)
- if(!istype(P))
+ if (!istype(P))
loud && show_error(user, "I/O ERROR - Unable to run [filename]")
return
- if(!P.is_supported_by_hardware(get_hardware_flag()))
+ if (!P.is_supported_by_hardware(get_hardware_flag()))
loud && show_error(user, "Hardware Error - Incompatible software")
return
- if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature))
+ if (P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature))
loud && show_error(user, "Unable to establish a working network connection. Please try again later. If problem persists, please contact your system administrator.")
return
- if(P in running_programs)
+ if (P in running_programs)
return P
- if(length(running_programs) >= get_program_capacity())
+
+ var/processing_total = 0
+ for (var/datum/computer_file/program/program in running_programs)
+ processing_total += program.processing_size
+ if ((processing_total + P.processing_size) > get_program_capacity() && P.processing_size)
loud && show_error(user, "Kernel Error - Insufficient CPU resources available to allocate.")
return
- if(!P.can_run(user, loud))
+ if (!P.can_run(user, loud))
return
P.on_startup(user, src)
diff --git a/code/modules/modular_computers/ntos/subtypes/console.dm b/code/modules/modular_computers/ntos/subtypes/console.dm
index 6c48df2256ae6..7ef18215708e1 100644
--- a/code/modules/modular_computers/ntos/subtypes/console.dm
+++ b/code/modules/modular_computers/ntos/subtypes/console.dm
@@ -1,6 +1,6 @@
/datum/extension/interactive/ntos/console
expected_type = /obj/machinery
- screen_icon_file = 'icons/obj/modular_console.dmi'
+ screen_icon_file = 'icons/obj/machines/modular_console.dmi'
/datum/extension/interactive/ntos/console/get_hardware_flag()
return PROGRAM_CONSOLE
diff --git a/code/modules/modular_computers/ntos/visuals.dm b/code/modules/modular_computers/ntos/visuals.dm
index d88ee08049e7f..7c0aed165a163 100644
--- a/code/modules/modular_computers/ntos/visuals.dm
+++ b/code/modules/modular_computers/ntos/visuals.dm
@@ -26,6 +26,12 @@
if(active_program && active_program.program_key_state)
return image(screen_icon_file, active_program.program_key_state)
+/datum/extension/interactive/ntos/proc/get_keyboard_state()
+ if(!on)
+ return
+ if(active_program && active_program.program_key_state)
+ return active_program.program_key_state
+
/datum/extension/interactive/ntos/proc/visible_error(message)
var/atom/A = holder
if(istype(A))
diff --git a/code/modules/modular_computers/terminal/commands/ban.dm b/code/modules/modular_computers/terminal/commands/ban.dm
index 759c5b26fb699..ace4eb239ede5 100644
--- a/code/modules/modular_computers/terminal/commands/ban.dm
+++ b/code/modules/modular_computers/terminal/commands/ban.dm
@@ -10,7 +10,7 @@
)
pattern = "^banned"
req_access = list(list(access_network, access_network_admin))
- skill_needed = SKILL_ADEPT
+ skill_needed = SKILL_TRAINED
/datum/terminal_command/banned/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/list/arguments = get_arguments(text)
diff --git a/code/modules/modular_computers/terminal/commands/hwinfo.dm b/code/modules/modular_computers/terminal/commands/hwinfo.dm
index a769101e26865..2393a6788dbb7 100644
--- a/code/modules/modular_computers/terminal/commands/hwinfo.dm
+++ b/code/modules/modular_computers/terminal/commands/hwinfo.dm
@@ -7,7 +7,7 @@
"If device is specified by name, runs diagnostic tests."
)
pattern = "^hwinfo"
- skill_needed = SKILL_ADEPT
+ skill_needed = SKILL_TRAINED
/datum/terminal_command/hwinfo/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/command = copytext(text, 1, length(name) + 2)
diff --git a/code/modules/modular_computers/terminal/commands/ifconfig.dm b/code/modules/modular_computers/terminal/commands/ifconfig.dm
index c8af374d55b39..2336389d1d859 100644
--- a/code/modules/modular_computers/terminal/commands/ifconfig.dm
+++ b/code/modules/modular_computers/terminal/commands/ifconfig.dm
@@ -8,7 +8,7 @@
"Returns network adaptor information."
)
pattern = "^ifconfig"
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/ifconfig/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/command = copytext(text, 1, length(name) + 2)
diff --git a/code/modules/modular_computers/terminal/commands/locate.dm b/code/modules/modular_computers/terminal/commands/locate.dm
index 50dda7cc22205..444a572519f63 100644
--- a/code/modules/modular_computers/terminal/commands/locate.dm
+++ b/code/modules/modular_computers/terminal/commands/locate.dm
@@ -8,7 +8,7 @@
)
pattern = "locate"
req_access = list(list(access_network, access_network_admin))
- skill_needed = SKILL_PROF
+ skill_needed = SKILL_MASTER
/datum/terminal_command/locate/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/list/arguments = get_arguments(text)
diff --git a/code/modules/modular_computers/terminal/commands/lock.dm b/code/modules/modular_computers/terminal/commands/lock.dm
index bde99a64c4a72..ad062ee7708ef 100644
--- a/code/modules/modular_computers/terminal/commands/lock.dm
+++ b/code/modules/modular_computers/terminal/commands/lock.dm
@@ -8,7 +8,7 @@
"WARNING: All files on affected device will be read-only, and new files cannot be created."
)
pattern = "^lock"
- skill_needed = SKILL_ADEPT
+ skill_needed = SKILL_TRAINED
/datum/terminal_command/lock/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/list/arguments = get_arguments(text)
diff --git a/code/modules/modular_computers/terminal/commands/log.dm b/code/modules/modular_computers/terminal/commands/log.dm
index 3372b64569b5d..f314884bd7e47 100644
--- a/code/modules/modular_computers/terminal/commands/log.dm
+++ b/code/modules/modular_computers/terminal/commands/log.dm
@@ -8,7 +8,7 @@
)
pattern = "^log"
req_access = list(list(access_network, access_network_admin))
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/log/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/argument = copytext(text, length(name) + 2, 0)
diff --git a/code/modules/modular_computers/terminal/commands/netscan.dm b/code/modules/modular_computers/terminal/commands/netscan.dm
index a7870fe1f3206..5bfdb1d75a094 100644
--- a/code/modules/modular_computers/terminal/commands/netscan.dm
+++ b/code/modules/modular_computers/terminal/commands/netscan.dm
@@ -6,7 +6,7 @@
"Scans the local network for devices and returns their NIDs."
)
pattern = "^netscan$"
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/netscan/proper_input_entered(text, mob/user, datum/terminal/terminal)
if (!ntnet_global || !terminal.computer.get_ntnet_status())
diff --git a/code/modules/modular_computers/terminal/commands/ping.dm b/code/modules/modular_computers/terminal/commands/ping.dm
index 4df2b2eaae71c..a56d1f9922987 100644
--- a/code/modules/modular_computers/terminal/commands/ping.dm
+++ b/code/modules/modular_computers/terminal/commands/ping.dm
@@ -6,7 +6,7 @@
"Checks connection to the given nid (number), or directly to NTNet relay if no nid is given."
)
pattern = "^ping"
- skill_needed = SKILL_ADEPT
+ skill_needed = SKILL_TRAINED
/datum/terminal_command/ping/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/list/arguments = get_arguments(text)
diff --git a/code/modules/modular_computers/terminal/commands/prog.dm b/code/modules/modular_computers/terminal/commands/prog.dm
index 6fc71fe03b559..945833c02311f 100644
--- a/code/modules/modular_computers/terminal/commands/prog.dm
+++ b/code/modules/modular_computers/terminal/commands/prog.dm
@@ -13,7 +13,7 @@
"NOTICE: Programs are executed using access credentials of the original terminal session."
)
pattern = "^prog"
- skill_needed = SKILL_ADEPT
+ skill_needed = SKILL_TRAINED
/datum/terminal_command/prog/proper_input_entered(text, mob/user, datum/terminal/terminal)
. = syntax_error()
diff --git a/code/modules/modular_computers/terminal/commands/proxy.dm b/code/modules/modular_computers/terminal/commands/proxy.dm
index c61cda723cca7..7ce71d8522d42 100644
--- a/code/modules/modular_computers/terminal/commands/proxy.dm
+++ b/code/modules/modular_computers/terminal/commands/proxy.dm
@@ -10,7 +10,7 @@
"It is recommended that the user ensure that the target device is accessible."
)
pattern = "^proxy"
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/proxy/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/list/arguments = get_arguments(text)
diff --git a/code/modules/modular_computers/terminal/commands/purge.dm b/code/modules/modular_computers/terminal/commands/purge.dm
index 943e2420f5f01..54e1a3cf76a1f 100644
--- a/code/modules/modular_computers/terminal/commands/purge.dm
+++ b/code/modules/modular_computers/terminal/commands/purge.dm
@@ -8,7 +8,7 @@
)
pattern = "^purge$"
req_access = list(access_network_admin)
- skill_needed = SKILL_PROF
+ skill_needed = SKILL_MASTER
/datum/terminal_command/purge/proper_input_entered(text, mob/user, datum/terminal/terminal)
if(!ntnet_global || !terminal.computer.get_ntnet_status())
diff --git a/code/modules/modular_computers/terminal/commands/relays.dm b/code/modules/modular_computers/terminal/commands/relays.dm
index 6337bb3195718..546936d2f2110 100644
--- a/code/modules/modular_computers/terminal/commands/relays.dm
+++ b/code/modules/modular_computers/terminal/commands/relays.dm
@@ -8,7 +8,7 @@
)
pattern = "^relays$"
req_access = list(list(access_network, access_network_admin))
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/relays/proper_input_entered(text, mob/user, datum/terminal/terminal)
. = list("[name]: Number of relays found: [length(ntnet_global.relays)]")
diff --git a/code/modules/modular_computers/terminal/commands/ssh.dm b/code/modules/modular_computers/terminal/commands/ssh.dm
index 4bed540bc8a65..ac524415a8dd3 100644
--- a/code/modules/modular_computers/terminal/commands/ssh.dm
+++ b/code/modules/modular_computers/terminal/commands/ssh.dm
@@ -8,7 +8,7 @@
)
pattern = "^ssh"
req_access = list(access_network_admin)
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/check_access(mob/user, datum/terminal/terminal)
if(terminal.computer.emagged() && !istype(terminal, /datum/terminal/remote))
diff --git a/code/modules/modular_computers/terminal/commands/status.dm b/code/modules/modular_computers/terminal/commands/status.dm
index c96e630626f56..ceb6829def952 100644
--- a/code/modules/modular_computers/terminal/commands/status.dm
+++ b/code/modules/modular_computers/terminal/commands/status.dm
@@ -8,7 +8,7 @@
)
pattern = "^status$"
req_access = list(list(access_network, access_network_admin))
- skill_needed = SKILL_EXPERT
+ skill_needed = SKILL_EXPERIENCED
/datum/terminal_command/status/proper_input_entered(text, mob/user, datum/terminal/terminal)
if(!terminal.computer.get_ntnet_status())
diff --git a/code/modules/modular_computers/terminal/commands/sysnotify.dm b/code/modules/modular_computers/terminal/commands/sysnotify.dm
index 4f3ba9b76c425..daf4a7ec90432 100644
--- a/code/modules/modular_computers/terminal/commands/sysnotify.dm
+++ b/code/modules/modular_computers/terminal/commands/sysnotify.dm
@@ -6,7 +6,7 @@
"Triggers a system notification showing the specified message, along with an audio alert."
)
pattern = "^sysnotify"
- skill_needed = SKILL_PROF
+ skill_needed = SKILL_MASTER
/datum/terminal_command/sysnotify/proper_input_entered(text, mob/user, datum/terminal/terminal)
var/argument = copytext(text, length(name) + 2, 0)
diff --git a/code/modules/modular_computers/terminal/terminal_skill_fail.dm b/code/modules/modular_computers/terminal/terminal_skill_fail.dm
index 779416b74ad9c..ef1769e87dc26 100644
--- a/code/modules/modular_computers/terminal/terminal_skill_fail.dm
+++ b/code/modules/modular_computers/terminal/terminal_skill_fail.dm
@@ -49,47 +49,47 @@ GLOBAL_LIST_INIT(terminal_fails, init_subtypes(/datum/terminal_skill_fail))
terminal.computer.visible_notification(message)
return list()
-/datum/terminal_skill_fail/operator
+/datum/terminal_skill_fail/antag
require_ntnet = TRUE
message = "Accessing network operator resources!"
-/datum/terminal_skill_fail/operator/can_run(mob/user, datum/terminal/terminal)
+/datum/terminal_skill_fail/antag/can_run(mob/user, datum/terminal/terminal)
if(!has_access(list(list(access_network, access_network_admin)), user.GetAccess()))
return
return ..()
-/datum/terminal_skill_fail/operator/access
+/datum/terminal_skill_fail/antag/access
message = "Attempting to brute force network admin credentials... unsuccesful. Attempt logged."
-/datum/terminal_skill_fail/operator/access/execute(datum/terminal/terminal)
+/datum/terminal_skill_fail/antag/access/execute(datum/terminal/terminal)
ntnet_global.add_log_with_ids_check("Unauthorised access attempt to primary keycode database.", terminal.computer.get_component(PART_NETWORK))
return ..()
-/datum/terminal_skill_fail/operator/dos
+/datum/terminal_skill_fail/antag/dos
message = "Sending content of dev/random to relay... Connection denied due to excess traffic."
-/datum/terminal_skill_fail/operator/dos/execute(datum/terminal/terminal)
+/datum/terminal_skill_fail/antag/dos/execute(datum/terminal/terminal)
var/obj/machinery/ntnet_relay/R = pick(ntnet_global.relays)
if (!istype(R))
return "Unable to locate Quantum Relay to attack."
ntnet_global.add_log_with_ids_check("Excess traffic flood targeting Quantum Relay ([R.uid]) detected from 1 device\s: [terminal.computer.get_network_tag()]")
return ..()
-/datum/terminal_skill_fail/operator/camera
+/datum/terminal_skill_fail/antag/camera
message = "Accessing raw camera feed... unsuccesful. Attempt logged."
-/datum/terminal_skill_fail/operator/camera/execute(datum/terminal/terminal)
+/datum/terminal_skill_fail/antag/camera/execute(datum/terminal/terminal)
var/network = pick(GLOB.using_map.station_networks)
if (!network)
return "Unable to locate camera network to access."
ntnet_global.add_log_with_ids_check("Unauthorised access detected to camera network [network].", terminal.computer.get_component(PART_NETWORK))
return ..()
-/datum/terminal_skill_fail/operator/email_logs
+/datum/terminal_skill_fail/antag/email_logs
weight = 2
message = "System log backup successful. Chosen method: email attachment. Recipients: all."
-/datum/terminal_skill_fail/operator/email_logs/execute(datum/terminal/terminal)
+/datum/terminal_skill_fail/antag/email_logs/execute(datum/terminal/terminal)
var/datum/computer_file/data/email_account/S = ntnet_global.find_email_by_name(EMAIL_SYSADMIN)
if(!istype(S))
return list()
@@ -145,4 +145,4 @@ GLOBAL_LIST_INIT(terminal_fails, init_subtypes(/datum/terminal_skill_fail))
/datum/terminal_skill_fail/admin/alarm_reset/execute(datum/terminal/terminal)
terminal.computer.add_log("Network packet sent to NTNet Statistics & Configuration")
ntnet_global.resetIDS()
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/modules/multiz/_stubs.dm b/code/modules/multiz/_stubs.dm
index ce60b5c53972b..5eb83f03d6bab 100644
--- a/code/modules/multiz/_stubs.dm
+++ b/code/modules/multiz/_stubs.dm
@@ -1,7 +1,7 @@
-/obj/effect/landmark/map_data
+/obj/landmark/map_data
name = "Map Data"
desc = "An unknown location."
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
var/height = 1 ///< The number of Z-Levels in the map.
var/turf/edge_type ///< What the map edge should be formed with. (null = world.turf)
diff --git a/code/modules/multiz/basic.dm b/code/modules/multiz/basic.dm
index ec456e9a3dce4..54e3b368da1ec 100644
--- a/code/modules/multiz/basic.dm
+++ b/code/modules/multiz/basic.dm
@@ -2,7 +2,7 @@
var/global/list/z_levels = list()// Each bit re... haha just kidding this is a list of bools now
// If the height is more than 1, we mark all contained levels as connected.
-/obj/effect/landmark/map_data/New(turf/loc, _height)
+/obj/landmark/map_data/New(turf/loc, _height)
..()
if(!istype(loc)) // Using loc.z is safer when using the maploader and New.
return
@@ -13,7 +13,7 @@ var/global/list/z_levels = list()// Each bit re... haha just kidding this is a l
LIST_RESIZE(z_levels, i)
z_levels[i] = TRUE
-/obj/effect/landmark/map_data/Initialize()
+/obj/landmark/map_data/Initialize()
..()
return INITIALIZE_HINT_QDEL
@@ -29,28 +29,40 @@ var/global/list/z_levels = list()// Each bit re... haha just kidding this is a l
// Thankfully, no bitwise magic is needed here.
/proc/GetAbove(atom/atom)
+ RETURN_TYPE(/turf)
var/turf/turf = get_turf(atom)
if(!turf)
return null
return HasAbove(turf.z) ? get_step(turf, UP) : null
/proc/GetBelow(atom/atom)
+ RETURN_TYPE(/turf)
var/turf/turf = get_turf(atom)
if(!turf)
return null
return HasBelow(turf.z) ? get_step(turf, DOWN) : null
/proc/GetConnectedZlevels(z)
+ RETURN_TYPE(/list)
. = list(z)
for(var/level = z, HasBelow(level), level--)
. |= level-1
for(var/level = z, HasAbove(level), level++)
. |= level+1
+/proc/GetConnectedZlevelsSet(z)
+ RETURN_TYPE(/list)
+ . = list("[z]" = TRUE)
+ for(var/level = z, HasBelow(level), level--)
+ .["[level-1]"] = TRUE
+ for(var/level = z, HasAbove(level), level++)
+ .["[level+1]"] = TRUE
+
/proc/AreConnectedZLevels(zA, zB)
return zA == zB || (zB in GetConnectedZlevels(zA))
/proc/get_zstep(ref, dir)
+ RETURN_TYPE(/turf)
if(dir == UP)
. = GetAbove(ref)
else if (dir == DOWN)
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index ada9f77086bdf..ba71e8da588fb 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -22,7 +22,7 @@
var/turf/simulated/open/O = GetAbove(src)
var/atom/climb_target
if(istype(O))
- for(var/turf/T in trange(1,O))
+ for(var/turf/T in RANGE_TURFS(O, 1))
if(!isopenspace(T) && T.is_floor())
climb_target = T
else
@@ -110,19 +110,19 @@
return 0
/mob/living/carbon/human/can_ztravel()
- if(Allow_Spacemove())
+ if(Process_Spacemove())
return 1
if(Check_Shoegrip()) //scaling hull with magboots
- for(var/turf/simulated/T in trange(1,src))
+ for(var/turf/simulated/T in RANGE_TURFS(src, 1))
if(T.density)
return 1
/mob/living/silicon/robot/can_ztravel()
- if(Allow_Spacemove()) //Checks for active jetpack
+ if(Process_Spacemove()) //Checks for active jetpack
return 1
- for(var/turf/simulated/T in trange(1,src)) //Robots get "magboots"
+ for(var/turf/simulated/T in RANGE_TURFS(src, 1)) //Robots get "magboots"
if(T.density)
return 1
@@ -142,8 +142,7 @@
return
// No gravity in space, apparently.
- var/area/area = get_area(src)
- if(!area.has_gravity())
+ if(!has_gravity())
return
if(throwing)
@@ -158,7 +157,7 @@
/atom/movable/proc/begin_falling(lastloc, below)
if (QDELETED(src))
return
- addtimer(new Callback(src, /atom/movable/proc/fall_callback, below), 0)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, fall_callback), below), 0)
/atom/movable/proc/fall_callback(turf/below)
var/mob/M = src
@@ -200,7 +199,7 @@
/obj/effect/can_fall(anchor_bypass = FALSE, turf/location_override = loc)
return FALSE
-/obj/effect/decal/cleanable/can_fall(anchor_bypass = FALSE, turf/location_override = loc)
+/obj/decal/cleanable/can_fall(anchor_bypass = FALSE, turf/location_override = loc)
return TRUE
/obj/item/pipe/can_fall(anchor_bypass = FALSE, turf/location_override = loc)
@@ -235,13 +234,22 @@
visible_message("\The [src] falls through \the [landing]!", "You hear a whoosh of displaced air.")
else
visible_message("\The [src] slams into \the [landing]!", "You hear something slam into the deck.")
- if(fall_damage())
- for(var/mob/living/M in landing.contents)
- if(M == src)
+ var/obj/item/rig/rig = get_rig()
+ if (istype(rig))
+ for (var/obj/item/rig_module/actuators/A in rig.installed_modules)
+ if (A.active)
+ visible_message(
+ SPAN_NOTICE("\The [src]'s suit whirrs loudly as \the [rig] absorbs the fall!"),
+ SPAN_NOTICE("You hear an electric *whirr* right after the slam!")
+ )
+ if (fall_damage())
+ for (var/mob/living/M in landing.contents)
+ if (M == src)
continue
visible_message("\The [src] hits \the [M.name]!")
M.take_overall_damage(fall_damage())
+
/atom/movable/proc/fall_damage()
return 0
@@ -256,7 +264,14 @@
if(species && species.handle_fall_special(src, landing))
return
+ var/obj/item/rig/rig = get_rig()
+ if (istype(rig))
+ for (var/obj/item/rig_module/actuators/A in rig.installed_modules)
+ if (A.active && rig.check_power_cost(src, 50 KILOWATTS, A, 0))
+ return
+
..()
+
var/min_damage = 7
var/max_damage = 14
apply_damage(rand(min_damage, max_damage), DAMAGE_BRUTE, BP_HEAD, armor_pen = 50)
@@ -269,7 +284,7 @@
apply_damage(rand(min_damage, max_damage), DAMAGE_BRUTE, BP_L_ARM, armor_pen = 75)
apply_damage(rand(min_damage, max_damage), DAMAGE_BRUTE, BP_R_ARM, armor_pen = 75)
weakened = max(weakened, 3)
- if(prob(skill_fail_chance(SKILL_HAULING, 40, SKILL_EXPERT, 2)))
+ if(prob(skill_fail_chance(SKILL_HAULING, 40, SKILL_EXPERIENCED, 2)))
var/list/victims = list()
for(var/tag in list(BP_L_FOOT, BP_R_FOOT, BP_L_ARM, BP_R_ARM))
var/obj/item/organ/external/E = get_organ(tag)
@@ -288,9 +303,28 @@
var/turf/T = get_turf(A)
var/turf/above = GetAbove(src)
+ var/obj/item/rig/rig = get_rig()
if(above && T.Adjacent(bound_overlay) && above.CanZPass(src, UP)) //Certain structures will block passage from below, others not
- var/area/location = get_area(loc)
- if(location.has_gravity && !can_overcome_gravity())
+
+ if (istype(rig)) ///RIG actuator jumps overcome gravity.
+ for (var/obj/item/rig_module/actuators/R in rig.installed_modules)
+ if (R.active && rig.check_power_cost(src, 50 KILOWATTS, A, 0))
+ visible_message(
+ SPAN_NOTICE("\The [src] prepares to leap upwards onto \the [A]!"),
+ SPAN_NOTICE("You crouch, preparing to leap upwards onto \the [A]!")
+ )
+ if (do_after(src, 2 SECONDS, A, DO_PUBLIC_UNIQUE))
+ if(src.incapacitated() || src.restrained())
+ to_chat(src, SPAN_WARNING("You are in no condition to activate your suit."))
+ return TRUE
+ visible_message(
+ SPAN_NOTICE("\The [src]'s suit whirrs aggressively as they leap up to \the [A]!"),
+ SPAN_NOTICE("You leap to \the [A]!")
+ )
+ src.Move(T)
+ return TRUE
+
+ if(loc.has_gravity() && !can_overcome_gravity())
return FALSE
visible_message(SPAN_NOTICE("[src] starts climbing onto \the [A]!"), SPAN_NOTICE("You start climbing onto \the [A]!"))
@@ -330,7 +364,7 @@
. = ..()
owner = user
follow()
- GLOB.moved_event.register(owner, src, /atom/movable/z_observer/proc/follow)
+ GLOB.moved_event.register(owner, src, TYPE_PROC_REF(/atom/movable/z_observer, follow))
/atom/movable/z_observer/proc/follow()
@@ -354,7 +388,7 @@
qdel(src)
/atom/movable/z_observer/Destroy()
- GLOB.moved_event.unregister(owner, src, /atom/movable/z_observer/proc/follow)
+ GLOB.moved_event.unregister(owner, src, TYPE_PROC_REF(/atom/movable/z_observer, follow))
owner = null
. = ..()
diff --git a/code/modules/multiz/pipes.dm b/code/modules/multiz/pipes.dm
index 50c59c1ed6ac0..8f82b3dab16b3 100644
--- a/code/modules/multiz/pipes.dm
+++ b/code/modules/multiz/pipes.dm
@@ -2,7 +2,7 @@
// parent class for pipes //
////////////////////////////
/obj/machinery/atmospherics/pipe/zpipe
- icon = 'icons/obj/structures.dmi'
+ icon = 'icons/obj/structures/structures.dmi'
icon_state = "up"
name = "upwards pipe"
@@ -20,7 +20,7 @@
/obj/machinery/atmospherics/pipe/zpipe/hide(i)
if(istype(loc, /turf/simulated))
- set_invisibility(i ? 101 : 0)
+ set_invisibility(i ? INVISIBILITY_ABSTRACT : 0)
update_icon()
/obj/machinery/atmospherics/pipe/zpipe/Process()
@@ -47,7 +47,7 @@
/obj/machinery/atmospherics/pipe/zpipe/proc/burst()
src.visible_message(SPAN_WARNING("\The [src] bursts!"));
playsound(src.loc, 'sound/effects/bang.ogg', 25, 1)
- var/datum/effect/effect/system/smoke_spread/smoke = new
+ var/datum/effect/smoke_spread/smoke = new
smoke.set_up(1,0, src.loc, 0)
smoke.start()
qdel(src) // NOT qdel.
@@ -81,7 +81,7 @@
// the elusive up pipe //
/////////////////////////
/obj/machinery/atmospherics/pipe/zpipe/up
- icon = 'icons/obj/structures.dmi'
+ icon = 'icons/obj/structures/structures.dmi'
icon_state = "up"
name = "upwards pipe"
@@ -119,7 +119,7 @@
///////////////////////
/obj/machinery/atmospherics/pipe/zpipe/down
- icon = 'icons/obj/structures.dmi'
+ icon = 'icons/obj/structures/structures.dmi'
icon_state = "down"
name = "downwards pipe"
diff --git a/code/modules/multiz/structures.dm b/code/modules/multiz/structures.dm
index a50d8af0fa34c..ee18d79f9a0bd 100644
--- a/code/modules/multiz/structures.dm
+++ b/code/modules/multiz/structures.dm
@@ -6,35 +6,47 @@
name = "ladder"
desc = "A ladder. You can climb it up and down."
icon_state = "ladder01"
- icon = 'icons/obj/structures.dmi'
+ icon = 'icons/obj/structures/structures.dmi'
density = FALSE
opacity = 0
anchored = TRUE
obj_flags = OBJ_FLAG_NOFALL
+ var/const/climb_time = 2 SECONDS
+
+ var/static/list/climbsounds = list('sound/effects/ladder.ogg','sound/effects/ladder2.ogg','sound/effects/ladder3.ogg','sound/effects/ladder4.ogg')
+
var/allowed_directions = DOWN
var/obj/structure/ladder/target_up
var/obj/structure/ladder/target_down
- var/const/climb_time = 2 SECONDS
- var/static/list/climbsounds = list('sound/effects/ladder.ogg','sound/effects/ladder2.ogg','sound/effects/ladder3.ogg','sound/effects/ladder4.ogg')
+ /// Used by the BSD Instability event. If TRUE, it may cause the user to be teleported to a random other ladder.
+ var/bluespace_affected = FALSE
+
+ ///Chance for a person climbing the ladder to be teleported to a random other ladder while bluespace affected.
+ var/displacement_chance = 15
+
/obj/structure/ladder/Initialize()
. = ..()
- // the upper will connect to the lower
- if(allowed_directions & DOWN) //we only want to do the top one, as it will initialize the ones before it.
- for(var/obj/structure/ladder/L in GetBelow(src))
- if(L.allowed_directions & UP)
- target_down = L
- L.target_up = src
- var/turf/T = get_turf(src)
- T.ReplaceWithLattice()
- return
- update_icon()
+ set_extension(src, /datum/extension/turf_hand)
+ queue_icon_update()
+ return INITIALIZE_HINT_LATELOAD
+/obj/structure/ladder/LateInitialize(mapload, ...)
+ . = ..()
+ // the upper will connect to the lower
+ if(!(allowed_directions & DOWN)) //we only want to do the top one, as it will initialize the ones before it.
+ return
- set_extension(src, /datum/extension/turf_hand)
+ var/obj/structure/ladder/ladder_below = locate() in GetBelow(src)
+ if(!ladder_below || !(ladder_below.allowed_directions & UP))
+ return
+ target_down = ladder_below
+ ladder_below.target_up = src
+ var/turf/turf_ladder_on = get_turf(src)
+ turf_ladder_on.ReplaceWithLattice()
/obj/structure/ladder/Destroy()
if(target_down)
@@ -45,8 +57,11 @@
target_up = null
return ..()
-/obj/structure/ladder/attackby(obj/item/I, mob/user)
- climb(user, I)
+
+/obj/structure/ladder/use_tool(obj/item/tool, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE)
+ climb(user, tool)
+
/turf/hitby(atom/movable/AM)
if(isobj(AM))
@@ -59,9 +74,10 @@
/obj/structure/ladder/hitby(obj/item/I)
if (istype(src, /obj/structure/ladder/up))
return
- var/area/room = get_area(src)
- if(!room.has_gravity())
+
+ if(!has_gravity())
return
+
var/atom/blocker
var/turf/landing = get_turf(target_down)
for(var/atom/A in landing)
@@ -76,7 +92,14 @@
landing.visible_message(SPAN_WARNING("\The [I] falls from the top of \the [target_down]!"))
/obj/structure/ladder/attack_hand(mob/M)
- climb(M)
+ climb(M, direction = "up")
+
+/obj/structure/ladder/attack_hand_secondary(mob/living/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+ . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ climb(user, direction = "down")
/obj/structure/ladder/attack_ai(mob/M)
var/mob/living/silicon/ai/ai = M
@@ -87,21 +110,35 @@
instant_climb(AIeye)
/obj/structure/ladder/attack_robot(mob/M)
- climb(M)
+ climb(M, direction = "up")
+
+/obj/structure/ladder/attack_robot_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+ . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ climb(user, direction = "down")
/obj/structure/ladder/proc/instant_climb(mob/M)
var/atom/target_ladder = getTargetLadder(M)
if(target_ladder)
M.dropInto(target_ladder.loc)
-/obj/structure/ladder/proc/climb(mob/M, obj/item/I = null)
+/obj/structure/ladder/proc/climb(mob/M, obj/item/I = null, direction)
if(!M.may_climb_ladders(src))
return
add_fingerprint(M)
- var/obj/structure/ladder/target_ladder = getTargetLadder(M)
+ var/obj/structure/ladder/target_ladder = getTargetLadder(M, direction)
if(!target_ladder)
return
+ if (bluespace_affected && prob(displacement_chance))
+ var/list/obj/structure/ladder/other_ladders= list()
+ var/list/zlevels = GetConnectedZlevels(z)
+ for (var/obj/structure/ladder/ladder)
+ if (src != ladder && (ladder.z in zlevels))
+ other_ladders += ladder
+ target_ladder = pick(other_ladders)
if(!M.Move(get_turf(src)))
to_chat(M, SPAN_NOTICE("You fail to reach \the [src]."))
return
@@ -109,28 +146,41 @@
for (var/obj/item/grab/G in M)
G.adjust_position()
- var/direction = target_ladder == target_up ? "up" : "down"
+ direction = target_ladder == target_up ? "up" : "down"
- M.visible_message(SPAN_NOTICE("\The [M] begins climbing [direction] \the [src]!"),
- "You begin climbing [direction] \the [src]!",
- "You hear the grunting and clanging of a metal ladder being used.")
+ balloon_alert_to_viewers("[direction == "up" ? "поднимается" : "спускается"]")
target_ladder.audible_message(SPAN_NOTICE("You hear something coming [direction] \the [src]"))
if(do_after(M, climb_time, src, DO_PUBLIC_UNIQUE))
climbLadder(M, target_ladder, I)
+ if (bluespace_affected && prob(20))
+ to_chat(M, SPAN_WARNING("You feel like you didn't end up where you were supposed to..."))
+
for (var/obj/item/grab/G in M)
G.adjust_position(force = 1)
/obj/structure/ladder/attack_ghost(mob/M)
instant_climb(M)
-/obj/structure/ladder/proc/getTargetLadder(mob/M)
+/obj/structure/ladder/proc/getTargetLadder(mob/M, direction)
if((!target_up && !target_down) || (target_up && !istype(target_up.loc, /turf/simulated/open) || (target_down && !istype(target_down.loc, /turf))))
to_chat(M, SPAN_NOTICE("\The [src] is incomplete and can't be climbed."))
return
+
+ if(direction == "up")
+ if(!target_up)
+ balloon_alert(M, "сверху пусто!")
+ return
+ return target_up
+ if(direction == "down")
+ if(!target_down)
+ balloon_alert(M, "внизу пусто!")
+ return
+ return target_down
+
if(target_down && target_up)
- var/direction = alert(M,"Do you want to go up or down?", "Ladder", "Up", "Down", "Cancel")
+ direction = alert(M,"Do you want to go up or down?", "Ladder", "Up", "Down", "Cancel")
if(direction == "Cancel")
return
@@ -207,12 +257,20 @@
/obj/structure/stairs
name = "stairs"
desc = "Stairs leading to another deck. Not too useful if the gravity goes out."
- icon = 'icons/obj/stairs.dmi'
+ icon = 'icons/obj/structures/stairs.dmi'
+ icon_state = "above"
density = FALSE
opacity = 0
anchored = TRUE
layer = RUNE_LAYER
+ ///Used by the BSD instability event. Causes users to sometimes randomly appear on the wrong stairs
+ var/bluespace_affected = FALSE
+
+ /// Chance of a user being displaced to a random set of stairs while its bluespace affected.
+ var/displacement_chance = 15
+
+
/obj/structure/stairs/Initialize()
for(var/turf/turf in locs)
var/turf/simulated/open/above = GetAbove(turf)
@@ -234,6 +292,16 @@
var/turf/target = get_step(above, dir)
var/turf/source = A.loc
if(above.CanZPass(source, UP) && target.Enter(A, src))
+ if (bluespace_affected)
+ var/list/obj/structure/other_stairs= list()
+ for (var/obj/structure/stairs/stair)
+ if (src != stair && (stair.z in GetConnectedZlevels(above.z)))
+ other_stairs += stair
+ var/obj/structure/stairs/other_stair = pick(other_stairs)
+ if (prob(displacement_chance))
+ target = get_turf(other_stair)
+ if (prob(20))
+ to_chat(A, SPAN_WARNING("You feel turned around..."))
A.forceMove(target)
if(isliving(A))
var/mob/living/L = A
diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm
index 9d30f234c8d45..7cb08ba9e536e 100644
--- a/code/modules/multiz/turf.dm
+++ b/code/modules/multiz/turf.dm
@@ -58,45 +58,52 @@
var/depth = 1
for(var/T = GetBelow(src); isopenspace(T); T = GetBelow(T))
depth += 1
- to_chat(user, "It is about [depth] level\s deep.")
+ . += SPAN_NOTICE("It is about [depth] level\s deep.")
/turf/simulated/open/is_open()
return TRUE
-/turf/simulated/open/attackby(obj/item/C, mob/user)
+/turf/simulated/open/use_tool(obj/item/C, mob/living/user, list/click_params)
if (istype(C, /obj/item/stack/material/rods))
var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
if(L)
- return L.attackby(C, user)
+ return L.use_tool(C, user)
var/obj/item/stack/material/rods/R = C
- if (R.use(1))
- to_chat(user, SPAN_NOTICE("You lay down the support lattice."))
- playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
- new /obj/structure/lattice(locate(src.x, src.y, src.z), R.material.name)
- return
+ if (!R.can_use(1))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(R, 1, "to lay down support lattice.")
+ return TRUE
+ to_chat(user, SPAN_NOTICE("You lay down the support lattice."))
+ playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
+ new /obj/structure/lattice(locate(src.x, src.y, src.z), R.material.name)
+ return TRUE
if (istype(C, /obj/item/stack/tile))
var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- if(L)
- var/obj/item/stack/tile/floor/S = C
- if (!S.use(1))
- return
- qdel(L)
- playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
- ChangeTurf(/turf/simulated/floor/plating, keep_air = TRUE)
- return
- else
+ if(!L)
to_chat(user, SPAN_WARNING("The plating is going to need some support."))
+ return TRUE
+
+ var/obj/item/stack/tile/floor/S = C
+ if (!S.can_use(1))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(S, 1, "to plate the lattice.")
+ return TRUE
+
+ qdel(L)
+ playsound(src, 'sound/weapons/Genhit.ogg', 50, 1)
+ ChangeTurf(/turf/simulated/floor/plating, keep_air = TRUE)
+ return TRUE
//To lay cable.
if(isCoil(C))
var/obj/item/stack/cable_coil/coil = C
coil.PlaceCableOnTurf(src, user)
- return
+ return TRUE
for(var/atom/movable/M in below)
if(M.movable_flags & MOVABLE_FLAG_Z_INTERACT)
- return M.attackby(C, user)
+ return C.resolve_attackby(M, user)
+
+ return ..()
/turf/simulated/open/attack_hand(mob/user)
for(var/atom/movable/M in below)
diff --git a/code/modules/multiz/zmimic/mimic_movable.dm b/code/modules/multiz/zmimic/mimic_movable.dm
index 0be2d62da358c..fe780df31e673 100644
--- a/code/modules/multiz/zmimic/mimic_movable.dm
+++ b/code/modules/multiz/zmimic/mimic_movable.dm
@@ -79,10 +79,11 @@
name = "openspace multiplier"
desc = "You shouldn't see this."
icon = 'icons/effects/lighting_overlay.dmi'
- icon_state = "dark"
+ icon_state = LIGHTING_TRANSPARENT_ICON_STATE
plane = OPENTURF_MAX_PLANE
layer = MIMICED_LIGHTING_LAYER
- color = "#0000004b"
+ color = SHADOWER_DARKENING_COLOR
+ //blend_mode = BLEND_MULTIPLY
/atom/movable/openspace/multiplier/Destroy()
var/turf/myturf = loc
@@ -96,38 +97,36 @@
layer = MIMICED_LIGHTING_LAYER
plane = OPENTURF_MAX_PLANE
invisibility = 0
- blend_mode = BLEND_MULTIPLY
- if (icon_state == null)
+
+ if (icon_state == LIGHTING_BASE_ICON_STATE)
+ blend_mode = BLEND_MULTIPLY
// We're using a color matrix, so just darken the colors across the board.
- // Bay stores lights as inverted so the lighting PM can invert it for darksight, but
- // we don't have a plane master, so invert it again.
var/list/c_list = color
- c_list[CL_MATRIX_RR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BB] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AR] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AG] *= -SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AB] *= -SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AB] *= SHADOWER_DARKENING_FACTOR
color = c_list
+
+ //Hackfix until I look into planes a bit more, we copy the plane of turf
+ var/turf/myturf = loc
+ if (istype(myturf))
+ plane = myturf.plane
else
- // Not a color matrix, so we just ignore the lighting values.
- icon_state = "dark" // this is actually just a white sprite, which is what this blending needs
- color = list(
- SHADOWER_DARKENING_FACTOR, 0, 0,
- 0, SHADOWER_DARKENING_FACTOR, 0,
- 0, 0, SHADOWER_DARKENING_FACTOR
- )
+ // Not a color matrix, so we can just use the color var ourselves.
+ color = (icon_state == LIGHTING_DARKNESS_ICON_STATE) ? COLOR_WHITE : SHADOWER_DARKENING_COLOR
var/turf/parent = loc
ASSERT(isturf(parent))
- if (LAZYLEN(parent.ao_overlays_mimic))
- overlays += parent.ao_overlays_mimic
+ UpdateOverlays()
if (bound_overlay)
update_above()
@@ -164,8 +163,9 @@
return ..()
-/atom/movable/openspace/mimic/attackby(obj/item/W, mob/user)
- to_chat(user, SPAN_NOTICE("\The [src] is too far away."))
+/atom/movable/openspace/mimic/can_use_item(obj/item/tool, mob/user, click_params)
+ USE_FEEDBACK_FAILURE("\The [src] is too far away.")
+ return FALSE
/atom/movable/openspace/mimic/attack_hand(mob/user)
to_chat(user, SPAN_NOTICE("You cannot reach \the [src] from here."))
@@ -196,8 +196,9 @@
mouse_opacity = 0
z_flags = ZMM_IGNORE // Only one of these should ever be visible at a time, the mimic logic will handle that.
-/atom/movable/openspace/turf_proxy/attackby(obj/item/W, mob/user)
- loc.attackby(W, user)
+/atom/movable/openspace/turf_proxy/use_tool(obj/item/tool, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE)
+ return tool.resolve_attackby(loc, user, click_params)
/atom/movable/openspace/turf_proxy/attack_hand(mob/user as mob)
loc.attack_hand(user)
@@ -205,9 +206,9 @@
/atom/movable/openspace/turf_proxy/attack_generic(mob/user as mob)
loc.attack_generic(user)
-/atom/movable/openspace/turf_proxy/examine(mob/examiner)
+/atom/movable/openspace/turf_proxy/examine(...)
SHOULD_CALL_PARENT(FALSE)
- . = loc.examine(examiner)
+ . = loc.examine(arglist(args))
// -- TURF MIMIC --
@@ -223,8 +224,9 @@
ASSERT(isturf(loc))
delegate = loc:below
-/atom/movable/openspace/turf_mimic/attackby(obj/item/W, mob/user)
- loc.attackby(W, user)
+/atom/movable/openspace/turf_mimic/use_tool(obj/item/tool, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE)
+ return tool.resolve_attackby(loc, user, click_params)
/atom/movable/openspace/turf_mimic/attack_hand(mob/user as mob)
to_chat(user, SPAN_NOTICE("You cannot reach \the [src] from here."))
@@ -232,6 +234,6 @@
/atom/movable/openspace/turf_mimic/attack_generic(mob/user as mob)
to_chat(user, SPAN_NOTICE("You cannot reach \the [src] from here."))
-/atom/movable/openspace/turf_mimic/examine(mob/examiner)
+/atom/movable/openspace/turf_mimic/examine(...)
SHOULD_CALL_PARENT(FALSE)
- . = delegate.examine(examiner)
+ . = delegate.examine(arglist(args))
diff --git a/code/modules/multiz/zmimic/mimic_turf.dm b/code/modules/multiz/zmimic/mimic_turf.dm
index f708119d10c84..400170e114378 100644
--- a/code/modules/multiz/zmimic/mimic_turf.dm
+++ b/code/modules/multiz/zmimic/mimic_turf.dm
@@ -70,6 +70,23 @@
update_mimic(!mapload) // Only recursively update if the map isn't loading.
+ //Update lights if mapload, else if we're changing turf this will be overriden by corner copy step
+ if(mapload)
+ rebuild_zbleed()
+
+//Force reconsider zbleed
+/turf/proc/rebuild_zbleed()
+ //Only relevant if dynamically lit
+ var/turf/under = GetBelow(src)
+ if(TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) && under)
+ //We need to force recalculation of corners regardless, clear first
+ if(corners && length(corners))
+ for (var/datum/lighting_corner/C in corners)
+ C.clear_below_lumcount()
+ if (under.corners && length(under.corners))
+ for (var/datum/lighting_corner/C in under.corners)
+ C.rebuild_above_below_lumcount()
+
/// Cleans up Z-mimic objects for this turf. You shouldn't call this directly 99% of the time.
/turf/proc/cleanup_zmimic()
SSzcopy.openspace_turfs -= 1
diff --git a/code/modules/nano/interaction/default.dm b/code/modules/nano/interaction/default.dm
index 8ce44be74a9d2..90850591b5852 100644
--- a/code/modules/nano/interaction/default.dm
+++ b/code/modules/nano/interaction/default.dm
@@ -12,7 +12,8 @@ GLOBAL_DATUM_INIT(default_state, /datum/topic_state/default, new)
/mob/observer/ghost/default_can_use_topic(src_object)
if(can_admin_interact())
return STATUS_INTERACTIVE // Admins are more equal
- if(!client || get_dist(src_object, src) > client.view) // Preventing ghosts from having a million windows open by limiting to objects in range
+
+ if(!client || get_dist(src_object, src) > get_lesser_view_size_component(client.view)) // Preventing ghosts from having a million windows open by limiting to objects in range
return STATUS_CLOSE
return STATUS_UPDATE // Ghosts can view updates
@@ -28,7 +29,7 @@ GLOBAL_DATUM_INIT(default_state, /datum/topic_state/default, new)
return
// robots can interact with things they can see within their view range
- if((src_object in view(src)) && get_dist(src_object, src) <= src.client.view)
+ if(get_dist(src_object, src) <= get_lesser_view_size_component(client.view) && (src_object in view(src)))
return STATUS_INTERACTIVE // interactive (green visibility)
return STATUS_DISABLED // no updates, completely disabled (red visibility)
@@ -54,7 +55,7 @@ GLOBAL_DATUM_INIT(default_state, /datum/topic_state/default, new)
if(cameranet && !cameranet.is_turf_visible(get_turf(src_object)))
return STATUS_CLOSE
return STATUS_INTERACTIVE
- else if(get_dist(src_object, src) <= client.view) // View does not return what one would expect while installed in an inteliCard
+ else if(get_dist(src_object, src) <= get_lesser_view_size_component(client.view)) // View does not return what one would expect while installed in an inteliCard
return STATUS_INTERACTIVE
return STATUS_CLOSE
diff --git a/code/modules/nano/modules/human_appearance.dm b/code/modules/nano/modules/human_appearance.dm
index cad5c8a091512..6dc22098809cb 100644
--- a/code/modules/nano/modules/human_appearance.dm
+++ b/code/modules/nano/modules/human_appearance.dm
@@ -18,15 +18,12 @@
. = ..()
-/datum/nano_module/appearance_changer/New(mob/living/carbon/human/_owner, _flags, _races)
- ..(_owner, null)
- owner = _owner
- flags = _flags
+/datum/nano_module/appearance_changer/New(mob/living/carbon/human/owner, flags)
+ ..(owner, null)
+ src.owner = owner
+ src.flags = flags
if (flags & APPEARANCE_RACE)
- if (islist(_races))
- races = _races
- else
- races = owner.generate_valid_species(_races)
+ races = owner.generate_valid_species(flags)
generate_data()
diff --git a/code/modules/nano/nanoexternal.dm b/code/modules/nano/nanoexternal.dm
index 47b35b969aa87..357f2f0be2ec4 100644
--- a/code/modules/nano/nanoexternal.dm
+++ b/code/modules/nano/nanoexternal.dm
@@ -1,12 +1,12 @@
- // This file contains all Nano procs/definitions for external classes/objects
-
- /**
- * Called when a Nano UI window is closed
- * This is how Nano handles closed windows
- * It must be a verb so that it can be called using winset
- *
- * @return nothing
- */
+// This file contains all Nano procs/definitions for external classes/objects
+
+/**
+ * Called when a Nano UI window is closed
+ * This is how Nano handles closed windows
+ * It must be a verb so that it can be called using winset
+ *
+ * @return nothing
+ */
/client/verb/nanoclose(uiref as text)
set hidden = 1 // hide this verb from the user's panel
set name = "nanoclose"
@@ -25,30 +25,30 @@
if(src && src.mob)
src.mob.unset_machine()
- /**
- * The ui_interact proc is used to open and update Nano UIs
- * If ui_interact is not used then the UI will not update correctly
- * ui_interact is currently defined for /atom/movable
- *
- * @param user /mob The mob who is interacting with this UI
- * @param ui_key string A string key to use for this UI. Allows for multiple unique UIs on one obj/mob (defaut value "main")
- * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open UI
- * @param force_open boolean Force the UI to (re)open, even if it's already open
- *
- * @return nothing
- */
+/**
+ * The ui_interact proc is used to open and update Nano UIs
+ * If ui_interact is not used then the UI will not update correctly
+ * ui_interact is currently defined for /atom/movable
+ *
+ * @param user /mob The mob who is interacting with this UI
+ * @param ui_key string A string key to use for this UI. Allows for multiple unique UIs on one obj/mob (defaut value "main")
+ * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open UI
+ * @param force_open boolean Force the UI to (re)open, even if it's already open
+ *
+ * @return nothing
+ */
/datum/proc/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/nanoui/master_ui = null, datum/topic_state/state = GLOB.default_state)
return
- /**
- * Data to be sent to the UI.
- * This must be implemented for a UI to work.
- *
- * @param user /mob The mob who interacting with the UI
- * @param ui_key string A string key to use for this UI. Allows for multiple unique UIs on one obj/mob (defaut value "main")
- *
- * @return data /list Data to be sent to the UI
- **/
+/**
+ * Data to be sent to the UI.
+ * This must be implemented for a UI to work.
+ *
+ * @param user /mob The mob who interacting with the UI
+ * @param ui_key string A string key to use for this UI. Allows for multiple unique UIs on one obj/mob (defaut value "main")
+ *
+ * @return data /list Data to be sent to the UI
+ */
/datum/proc/ui_data(mob/user, ui_key = "main")
return list() // Not implemented.
diff --git a/code/modules/nano/nanomapgen.dm b/code/modules/nano/nanomapgen.dm
index 5ef3d60696a4b..aeef7e20acce7 100644
--- a/code/modules/nano/nanomapgen.dm
+++ b/code/modules/nano/nanomapgen.dm
@@ -51,7 +51,7 @@
sleep(3)
return NANOMAP_TERMINALERR
- var/icon/Tile = icon(file("nano/mapbase1024.png"))
+ var/icon/Tile = icon('nano/mapbase1024.png')
if (Tile.Width() != NANOMAP_MAX_ICON_DIMENSION || Tile.Height() != NANOMAP_MAX_ICON_DIMENSION)
to_world_log("NanoMapGen: ERROR: BASE IMAGE DIMENSIONS ARE NOT [NANOMAP_MAX_ICON_DIMENSION]x[NANOMAP_MAX_ICON_DIMENSION]")
sleep(3)
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index cf9a7bfb2805a..55d4dacb76493 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -59,20 +59,20 @@ nanoui is used to open and update nano browser uis
var/list/datum/nanoui/children = list()
var/datum/topic_state/state = null
- /**
- * Create a new nanoui instance.
- *
- * @param nuser /mob The mob who has opened/owns this ui
- * @param nsrc_object /obj|/mob The obj or mob which this ui belongs to
- * @param nui_key string A string key to use for this ui. Allows for multiple unique uis on one src_oject
- * @param ntemplate string The filename of the template file from /nano/templates (e.g. "my_template.tmpl")
- * @param ntitle string The title of this ui
- * @param nwidth int the width of the ui window
- * @param nheight int the height of the ui window
- * @param nref /atom A custom ref to use if "on_close_logic" is set to 1
- *
- * @return /nanoui new nanoui object
- */
+/**
+ * Create a new nanoui instance.
+ *
+ * @param nuser /mob The mob who has opened/owns this ui
+ * @param nsrc_object /obj|/mob The obj or mob which this ui belongs to
+ * @param nui_key string A string key to use for this ui. Allows for multiple unique uis on one src_oject
+ * @param ntemplate string The filename of the template file from /nano/templates (e.g. "my_template.tmpl")
+ * @param ntitle string The title of this ui
+ * @param nwidth int the width of the ui window
+ * @param nheight int the height of the ui window
+ * @param nref /atom A custom ref to use if "on_close_logic" is set to 1
+ *
+ * @return /nanoui new nanoui object
+ */
/datum/nanoui/New(nuser, nsrc_object, nui_key, ntemplate_filename, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, datum/nanoui/master_ui = null, datum/topic_state/state = GLOB.default_state)
user = nuser
src_object = nsrc_object
@@ -97,8 +97,8 @@ nanoui is used to open and update nano browser uis
ref = nref
add_common_assets()
- var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui)
- assets.send(user, ntemplate_filename)
+ var/datum/asset/asset = get_asset_datum(/datum/asset/group/nanoui)
+ asset.send(user)
//Do not qdel nanouis. Use close() instead.
/datum/nanoui/Destroy()
@@ -107,11 +107,11 @@ nanoui is used to open and update nano browser uis
state = null
. = ..()
- /**
- * Use this proc to add assets which are common to (and required by) all nano uis
- *
- * @return nothing
- */
+/**
+ * Use this proc to add assets which are common to (and required by) all nano uis
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/add_common_assets()
add_script("libraries.min.js") // A JS file comprising of jQuery, doT.js and jQuery Timer libraries (compressed together)
add_script("nano_utility.js") // The NanoUtility JS, this is used to store utility functions.
@@ -124,14 +124,14 @@ nanoui is used to open and update nano browser uis
add_stylesheet("shared.css") // this CSS sheet is common to all UIs
add_stylesheet("icons.css") // this CSS sheet is common to all UIs
- /**
- * Set the current status (also known as visibility) of this ui.
- *
- * @param state int The status to set, see the defines at the top of this file
- * @param push_update int (bool) Push an update to the ui to update it's status (an update is always sent if the status has changed to red (0))
- *
- * @return nothing
- */
+/**
+ * Set the current status (also known as visibility) of this ui.
+ *
+ * @param state int The status to set, see the defines at the top of this file
+ * @param push_update int (bool) Push an update to the ui to update it's status (an update is always sent if the status has changed to red (0))
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_status(state, push_update)
if (state != status) // Only update if it is different
if (status == STATUS_DISABLED)
@@ -143,13 +143,13 @@ nanoui is used to open and update nano browser uis
if (push_update || status == 0)
push_data(null, 1) // Update the UI, force the update in case the status is 0, data is null so that previous data is used
- /**
- * Update the status (visibility) of this ui based on the user's status
- *
- * @param push_update int (bool) Push an update to the ui to update it's status. This is set to 0/false if an update is going to be pushed anyway (to avoid unnessary updates)
- *
- * @return 1 if closed, null otherwise.
- */
+/**
+ * Update the status (visibility) of this ui based on the user's status
+ *
+ * @param push_update int (bool) Push an update to the ui to update it's status. This is set to 0/false if an update is going to be pushed anyway (to avoid unnessary updates)
+ *
+ * @return 1 if closed, null otherwise.
+ */
/datum/nanoui/proc/update_status(push_update = 0)
var/atom/host = src_object && src_object.nano_host()
if(!host)
@@ -164,31 +164,31 @@ nanoui is used to open and update nano browser uis
return 1
set_status(new_status, push_update)
- /**
- * Set the ui to auto update (every master_controller tick)
- *
- * @param state int (bool) Set auto update to 1 or 0 (true/false)
- *
- * @return nothing
- */
+/**
+ * Set the ui to auto update (every master_controller tick)
+ *
+ * @param state int (bool) Set auto update to 1 or 0 (true/false)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_auto_update(nstate = 1)
is_auto_updating = nstate
- /**
- * Set the initial data for the ui. This is vital as the data structure set here cannot be changed when pushing new updates.
- *
- * @param data /list The list of data for this ui
- *
- * @return nothing
- */
+/**
+ * Set the initial data for the ui. This is vital as the data structure set here cannot be changed when pushing new updates.
+ *
+ * @param data /list The list of data for this ui
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_initial_data(list/data)
initial_data = data
- /**
- * Get config data to sent to the ui.
- *
- * @return /list config data
- */
+/**
+ * Get config data to sent to the ui.
+ *
+ * @return /list config data
+ */
/datum/nanoui/proc/get_config_data()
var/name = "[src_object]"
name = sanitize(name)
@@ -208,13 +208,13 @@ nanoui is used to open and update nano browser uis
)
return config_data
- /**
- * Get data to sent to the ui.
- *
- * @param data /list The list of general data for this ui (can be null to use previous data sent)
- *
- * @return /list data to send to the ui
- */
+/**
+ * Get data to sent to the ui.
+ *
+ * @param data /list The list of general data for this ui (can be null to use previous data sent)
+ *
+ * @return /list data to send to the ui
+ */
/datum/nanoui/proc/get_send_data(list/data)
var/list/config_data = get_config_data()
@@ -225,130 +225,130 @@ nanoui is used to open and update nano browser uis
return send_data
- /**
- * Set the browser window options for this ui
- *
- * @param nwindow_options string The new window options
- *
- * @return nothing
- */
+/**
+ * Set the browser window options for this ui
+ *
+ * @param nwindow_options string The new window options
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_window_options(nwindow_options)
window_options = nwindow_options
- /**
- * Add a CSS stylesheet to this UI
- * These must be added before the UI has been opened, adding after that will have no effect
- *
- * @param file string The name of the CSS file from /nano/css (e.g. "my_style.css")
- *
- * @return nothing
- */
+/**
+ * Add a CSS stylesheet to this UI
+ * These must be added before the UI has been opened, adding after that will have no effect
+ *
+ * @param file string The name of the CSS file from /nano/css (e.g. "my_style.css")
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/add_stylesheet(file)
stylesheets.Add(file)
- /**
- * Add a JavsScript script to this UI
- * These must be added before the UI has been opened, adding after that will have no effect
- *
- * @param file string The name of the JavaScript file from /nano/js (e.g. "my_script.js")
- *
- * @return nothing
- */
+/**
+ * Add a JavsScript script to this UI
+ * These must be added before the UI has been opened, adding after that will have no effect
+ *
+ * @param file string The name of the JavaScript file from /nano/js (e.g. "my_script.js")
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/add_script(file)
scripts.Add(file)
- /**
- * Add a template for this UI
- * Templates are combined with the data sent to the UI to create the rendered view
- * These must be added before the UI has been opened, adding after that will have no effect
- *
- * @param key string The key which is used to reference this template in the frontend
- * @param filename string The name of the template file from /nano/templates (e.g. "my_template.tmpl")
- *
- * @return nothing
- */
+/**
+ * Add a template for this UI
+ * Templates are combined with the data sent to the UI to create the rendered view
+ * These must be added before the UI has been opened, adding after that will have no effect
+ *
+ * @param key string The key which is used to reference this template in the frontend
+ * @param filename string The name of the template file from /nano/templates (e.g. "my_template.tmpl")
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/add_template(key, filename)
templates[key] = filename
- /**
- * Set the layout key for use in the frontend Javascript
- * The layout key is the basic layout key for the page
- * Two files are loaded on the client based on the layout key varable:
- * -> a template in /nano/templates with the filename "layout_.tmpl
- * -> a CSS stylesheet in /nano/css with the filename "layout_.css
- *
- * @param nlayout string The layout key to use
- *
- * @return nothing
- */
+/**
+ * Set the layout key for use in the frontend Javascript
+ * The layout key is the basic layout key for the page
+ * Two files are loaded on the client based on the layout key varable:
+ * -> a template in /nano/templates with the filename "layout_.tmpl
+ * -> a CSS stylesheet in /nano/css with the filename "layout_.css
+ *
+ * @param nlayout string The layout key to use
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_layout_key(nlayout_key)
layout_key = lowertext(nlayout_key)
- /**
- * Set the ui to update the layout (re-render it) on each update, turning this on will break the map ui (if it's being used)
- *
- * @param state int (bool) Set update to 1 or 0 (true/false) (default 0)
- *
- * @return nothing
- */
+/**
+ * Set the ui to update the layout (re-render it) on each update, turning this on will break the map ui (if it's being used)
+ *
+ * @param state int (bool) Set update to 1 or 0 (true/false) (default 0)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_auto_update_layout(nstate)
auto_update_layout = nstate
- /**
- * Set the ui to update the main content (re-render it) on each update
- *
- * @param state int (bool) Set update to 1 or 0 (true/false) (default 1)
- *
- * @return nothing
- */
+/**
+ * Set the ui to update the main content (re-render it) on each update
+ *
+ * @param state int (bool) Set update to 1 or 0 (true/false) (default 1)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_auto_update_content(nstate)
auto_update_content = nstate
- /**
- * Set the state key for use in the frontend Javascript
- *
- * @param nstate_key string The key of the state to use
- *
- * @return nothing
- */
+/**
+ * Set the state key for use in the frontend Javascript
+ *
+ * @param nstate_key string The key of the state to use
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_state_key(nstate_key)
state_key = nstate_key
- /**
- * Toggle showing the map ui
- *
- * @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
- *
- * @return nothing
- */
+/**
+ * Toggle showing the map ui
+ *
+ * @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_show_map(nstate)
show_map = nstate
- /**
- * Toggle showing the map ui
- *
- * @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
- *
- * @return nothing
- */
+/**
+ * Toggle showing the map ui
+ *
+ * @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/set_map_z_level(nz)
map_z_level = nz
- /**
- * Set whether or not to use the "old" on close logic (mainly unset_machine())
- *
- * @param state int (bool) Set on_close_logic to 1 or 0 (true/false)
- *
- * @return nothing
- */
+/**
+ * Set whether or not to use the "old" on close logic (mainly unset_machine())
+ *
+ * @param state int (bool) Set on_close_logic to 1 or 0 (true/false)
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/use_on_close_logic(state)
on_close_logic = state
- /**
- * Return the HTML for this UI
- *
- * @return string HTML for the UI
- */
+/**
+ * Return the HTML for this UI
+ *
+ * @return string HTML for the UI
+ */
/datum/nanoui/proc/get_html()
// before the UI opens, add the layout files based on the layout key
@@ -412,12 +412,16 @@ nanoui is used to open and update nano browser uis
"}
- /**
- * Open this UI
- *
- * @return nothing
- */
+/**
+ * Open this UI
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/open()
+ if(!istype(user))
+ stack_trace("Wrong type of nanoui user passed: [user], [user.type]")
+ return
+
if(!user?.client)
return
@@ -436,11 +440,11 @@ nanoui is used to open and update nano browser uis
//onclose(user, window_id)
SSnano.ui_opened(src)
- /**
- * Reinitialise this UI, potentially with a different template and/or initial data
- *
- * @return nothing
- */
+/**
+ * Reinitialise this UI, potentially with a different template and/or initial data
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/reinitialise(template, new_initial_data)
if(template)
add_template("main", template)
@@ -448,11 +452,11 @@ nanoui is used to open and update nano browser uis
set_initial_data(new_initial_data)
open()
- /**
- * Close this UI
- *
- * @return nothing
- */
+/**
+ * Close this UI
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/close()
is_auto_updating = 0
SSnano.ui_closed(src)
@@ -464,12 +468,12 @@ nanoui is used to open and update nano browser uis
master_ui = null
qdel(src)
- /**
- * Set the UI window to call the nanoclose verb when the window is closed
- * This allows Nano to handle closed windows
- *
- * @return nothing
- */
+/**
+ * Set the UI window to call the nanoclose verb when the window is closed
+ * This allows Nano to handle closed windows
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/on_close_winset()
if(!user.client)
return
@@ -480,11 +484,11 @@ nanoui is used to open and update nano browser uis
return
winset(user, window_id, "on-close=\"nanoclose [params]\"")
- /**
- * Push data to an already open UI window
- *
- * @return nothing
- */
+/**
+ * Push data to an already open UI window
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/push_data(data, force_push = 0)
if(update_status(0))
return // Closed
@@ -497,13 +501,13 @@ nanoui is used to open and update nano browser uis
to_target(user, output(list2params(list(strip_improper(json_encode(send_data)))),"[window_id].browser:receiveUpdateData"))
- /**
- * This Topic() proc is called whenever a user clicks on a link within a Nano UI
- * If the UI status is currently STATUS_INTERACTIVE then call the src_object Topic()
- * If the src_object Topic() returns 1 (true) then update all UIs attached to src_object
- *
- * @return nothing
- */
+/**
+ * This Topic() proc is called whenever a user clicks on a link within a Nano UI
+ * If the UI status is currently STATUS_INTERACTIVE then call the src_object Topic()
+ * If the src_object Topic() returns 1 (true) then update all UIs attached to src_object
+ *
+ * @return nothing
+ */
/datum/nanoui/Topic(href, href_list)
update_status(0) // update the status
if (status != STATUS_INTERACTIVE || user != usr) // If UI is not interactive or usr calling Topic is not the UI user
@@ -524,13 +528,13 @@ nanoui is used to open and update nano browser uis
if ((src_object && src_object.Topic(href, href_list, state)) || map_update)
SSnano.update_uis(src_object) // update all UIs attached to src_object
- /**
- * Process this UI, updating the entire UI or just the status (aka visibility)
- *
- * @param update string For this UI to update
- *
- * @return nothing
- */
+/**
+ * Process this UI, updating the entire UI or just the status (aka visibility)
+ *
+ * @param update string For this UI to update
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/try_update(update = 0)
if (!src_object || !user)
close()
@@ -541,17 +545,17 @@ nanoui is used to open and update nano browser uis
else
update_status(1) // Not updating UI, so lets check here if status has changed
- /**
- * This Process proc is called by SSnano.
- * Use try_update() to make manual updates.
- */
+/**
+ * This Process proc is called by SSnano.
+ * Use try_update() to make manual updates.
+ */
/datum/nanoui/Process()
try_update(0)
- /**
- * Update the UI
- *
- * @return nothing
- */
+/**
+ * Update the UI
+ *
+ * @return nothing
+ */
/datum/nanoui/proc/update(force_open = 0)
src_object.ui_interact(user, ui_key, src, force_open, master_ui, state)
diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm
index 5d84664b65bfb..9e528a43e7d2e 100644
--- a/code/modules/organs/blood.dm
+++ b/code/modules/organs/blood.dm
@@ -47,7 +47,7 @@
if(amt <= 0 || !istype(sprayloc))
return
var/spraydir = pick(GLOB.alldirs)
- amt = Ceil(amt/BLOOD_SPRAY_DISTANCE)
+ amt = ceil(amt/BLOOD_SPRAY_DISTANCE)
var/bled = 0
spawn(0)
for(var/i = 1 to BLOOD_SPRAY_DISTANCE)
@@ -211,9 +211,10 @@
return data
/proc/blood_splatter(target,datum/reagent/blood/source,large,spray_dir)
+ RETURN_TYPE(/obj/decal/cleanable/blood)
- var/obj/effect/decal/cleanable/blood/B
- var/decal_type = /obj/effect/decal/cleanable/blood/splatter
+ var/obj/decal/cleanable/blood/B
+ var/decal_type = /obj/decal/cleanable/blood/splatter
var/turf/T = get_turf(target)
if(istype(source,/mob/living/carbon))
@@ -225,11 +226,11 @@
// Are we dripping or splattering?
var/list/drips = list()
// Only a certain number of drips (or one large splatter) can be on a given turf.
- for(var/obj/effect/decal/cleanable/blood/drip/drop in T)
+ for(var/obj/decal/cleanable/blood/drip/drop in T)
drips |= drop.drips
qdel(drop)
if(!large && length(drips) < 3)
- decal_type = /obj/effect/decal/cleanable/blood/drip
+ decal_type = /obj/decal/cleanable/blood/drip
// Find a blood decal or create a new one.
if(T)
@@ -239,9 +240,9 @@
if(!B)
B = new decal_type(T)
- var/obj/effect/decal/cleanable/blood/drip/drop = B
+ var/obj/decal/cleanable/blood/drip/drop = B
if(istype(drop) && drips && length(drips) && !large)
- drop.overlays |= drips
+ drop.AddOverlays(drips)
drop.drips |= drips
// If there's no data to copy, call it quits here.
diff --git a/code/modules/organs/external/_external.dm b/code/modules/organs/external/_external.dm
index aec4434a87154..5466d58d69708 100644
--- a/code/modules/organs/external/_external.dm
+++ b/code/modules/organs/external/_external.dm
@@ -33,7 +33,7 @@
var/icon_position = 0 // Used in mob overlay layering calculations.
var/model // Used when caching robolimb icons.
var/force_icon // Used to force override of species-specific limb icons (for prosthetics).
- var/icon/mob_icon // Cached icon for use in mob overlays.
+ var/list/mob_overlays // Cached limb overlays
var/skin_tone // Skin tone.
var/base_skin = "" // Skin base.
var/list/s_col // skin colour
@@ -87,9 +87,8 @@
if(print)
return print
-/obj/item/organ/external/afterattack(atom/A, mob/user, proximity)
- ..()
- if(proximity && get_fingerprint())
+/obj/item/organ/external/use_after(atom/A, mob/living/user, click_parameters)
+ if(get_fingerprint())
A.add_partial_print(get_fingerprint())
/obj/item/organ/external/New(mob/living/carbon/holder)
@@ -99,7 +98,7 @@
if(owner)
replaced(owner)
sync_colour_to_human(owner)
- get_icon()
+ get_overlays()
slowdown = species.get_slowdown(owner)
@@ -127,8 +126,12 @@
splinted = null
if(owner)
- if(limb_flags & ORGAN_FLAG_CAN_GRASP) owner.grasp_limbs -= src
- if(limb_flags & ORGAN_FLAG_CAN_STAND) owner.stance_limbs -= src
+ if(limb_flags & ORGAN_FLAG_CAN_GRASP)
+ LAZYREMOVE(owner.grasp_limbs, src)
+
+ if(limb_flags & ORGAN_FLAG_CAN_STAND)
+ LAZYREMOVE(owner.stance_limbs, src)
+
owner.organs -= src
owner.organs_by_name[organ_tag] = null
owner.organs_by_name -= organ_tag
@@ -151,15 +154,20 @@
if(owner && BP_IS_CRYSTAL(src)) // Crystalline robotics == piezoelectrics.
owner.Weaken(4 - severity)
- owner.confused = max(owner.confused, 6 - (severity * 2))
+ owner.set_confused(6 - (severity * 2))
return
var/burn_damage = 0
+ var/rand_modifier = rand(1,3)
switch (severity)
if (EMP_ACT_HEAVY)
- burn_damage = 30
+ burn_damage = 10 * rand_modifier
if (EMP_ACT_LIGHT)
- burn_damage = 15
+ burn_damage = 4 * rand_modifier
+
+ /// Ions can't be aimed like conventional weaponry. This way damage is more even between center mass and limbs based on their total health relative to each other.
+ if(!(src.body_part & FULL_TORSO))
+ burn_damage *= 0.5
var/mult = 1 + !!(BP_IS_ASSISTED(src)) // This macro returns (large) bitflags.
burn_damage *= mult/species.get_burn_mod(owner) //ignore burn mod for EMP damage
@@ -176,9 +184,6 @@
if(owner && limb_flags & ORGAN_FLAG_CAN_GRASP)
owner.grasp_damage_disarm(src)
- if(owner && limb_flags & ORGAN_FLAG_CAN_STAND)
- owner.stance_damage_prone(src)
-
..()
/obj/item/organ/external/attack_self(mob/user)
@@ -208,10 +213,10 @@
for(var/obj/item/I in contents)
if(istype(I, /obj/item/organ))
continue
- to_chat(user, SPAN_DANGER("There is \a [I] sticking out of it."))
+ . += SPAN_DANGER("There is [I] sticking out of it.")
var/ouchies = get_wounds_desc()
if(ouchies != "nothing")
- to_chat(user, SPAN_NOTICE("There is [ouchies] visible on it."))
+ . += SPAN_NOTICE("There is [ouchies] visible on it.")
return
@@ -233,14 +238,15 @@
stage++
return
if(2)
- if(W.sharp || istype(W,/obj/item/hemostat) || isWirecutter(W))
+ if(W.sharp || istype(W,/obj/item/hemostat) || W.tool_behaviour == TOOL_WIRECUTTER)
var/list/organs = get_contents_recursive()
if(length(organs))
var/obj/item/removing = pick(organs)
var/obj/item/organ/external/current_child = removing.loc
- current_child.implants.Remove(removing)
- current_child.internal_organs.Remove(removing)
+ if (istype(current_child))
+ current_child.implants.Remove(removing)
+ current_child.internal_organs.Remove(removing)
status |= ORGAN_CUT_AWAY
if(istype(removing, /obj/item/organ/internal/mmi_holder))
@@ -320,8 +326,12 @@
if(istype(owner))
- if(limb_flags & ORGAN_FLAG_CAN_GRASP) owner.grasp_limbs[src] = TRUE
- if(limb_flags & ORGAN_FLAG_CAN_STAND) owner.stance_limbs[src] = TRUE
+ if(limb_flags & ORGAN_FLAG_CAN_GRASP)
+ LAZYSET(owner.grasp_limbs, src, TRUE)
+
+ if(limb_flags & ORGAN_FLAG_CAN_STAND)
+ LAZYSET(owner.stance_limbs, src, TRUE)
+
owner.organs_by_name[organ_tag] = src
owner.organs |= src
@@ -477,7 +487,7 @@ This function completely restores a damaged organ to perfect condition.
var/internal_damage
if(prob(damage) && sever_artery())
internal_damage = TRUE
- if(prob(Ceil(damage/4)) && sever_tendon())
+ if(prob(ceil(damage/4)) && sever_tendon())
internal_damage = TRUE
if(internal_damage)
owner.custom_pain("You feel something rip in your [name]!", 50, affecting = src)
@@ -702,7 +712,7 @@ Note that amputating the affected organ does in fact remove the infection from t
// slow healing
var/heal_amt = 0
// if damage >= 50 AFTER treatment then it's probably too severe to heal within the timeframe of a round.
- if (!owner.chem_effects[CE_TOXIN] && W.can_autoheal() && W.wound_damage() && brute_ratio < 0.5 && burn_ratio < 0.5)
+ if (!owner.chem_effects[CE_TOXIN] && W.can_autoheal() && W.wound_damage() && brute_ratio < 50 && burn_ratio < 50)
heal_amt += 0.5
//we only update wounds once in [wound_update_accuracy] ticks so have to emulate realtime
@@ -763,9 +773,9 @@ Note that amputating the affected organ does in fact remove the infection from t
update_damage_ratios()
/obj/item/organ/external/proc/update_damage_ratios()
- var/limb_loss_threshold = max_damage
- brute_ratio = brute_dam / (limb_loss_threshold * 2)
- burn_ratio = burn_dam / (limb_loss_threshold * 2)
+ var/limb_loss_threshold = max_damage * 2
+ brute_ratio = Percent(brute_dam, limb_loss_threshold, 0)
+ burn_ratio = Percent(burn_dam, limb_loss_threshold, 0)
//Returns 1 if damage_state changed
/obj/item/organ/external/proc/update_damstate()
@@ -831,7 +841,7 @@ Note that amputating the affected organ does in fact remove the infection from t
"You hear a crackling sound[gore]."
)
if(DROPLIMB_BLUNT)
- var/gore = "[BP_IS_ROBOTIC(src) ? "": " in shower of gore"]"
+ var/gore = "[BP_IS_ROBOTIC(src) ? "": " in a shower of gore"]"
var/gore_sound = "[BP_IS_ROBOTIC(src) ? "rending sound of tortured metal" : "sickening splatter of gore"]"
return list(
"\The [owner]'s [src.name] explodes[gore]!",
@@ -864,6 +874,14 @@ Note that amputating the affected organ does in fact remove the infection from t
if(!clean)
victim.shock_stage += min_broken_damage
+ // Handle any internal organ removal before removing the limb itself
+ if (disintegrate == DROPLIMB_BLUNT || disintegrate == DROPLIMB_BURN)
+ for(var/obj/item/organ/I in internal_organs)
+ I.removed()
+ if(!QDELETED(I) && isturf(I.loc))
+ I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
+ I.take_general_damage(I.max_damage * Frand(0.5, 1.0))
+
removed(null, ignore_children)
if(QDELETED(src))
return
@@ -899,7 +917,7 @@ Note that amputating the affected organ does in fact remove the infection from t
throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
dir = 2
if(DROPLIMB_BURN)
- new /obj/effect/decal/cleanable/ash(get_turf(victim))
+ new /obj/decal/cleanable/ash(get_turf(victim))
for(var/obj/item/I in src)
if(I.w_class > ITEM_SIZE_SMALL && !istype(I,/obj/item/organ))
I.dropInto(loc)
@@ -909,22 +927,17 @@ Note that amputating the affected organ does in fact remove the infection from t
if(BP_IS_CRYSTAL(src))
gore = new /obj/item/material/shard(get_turf(victim), MATERIAL_CRYSTAL)
else if(BP_IS_ROBOTIC(src))
- gore = new /obj/effect/decal/cleanable/blood/gibs/robot(get_turf(victim))
+ gore = new /obj/decal/cleanable/blood/gibs/robot(get_turf(victim))
else
- gore = new /obj/effect/decal/cleanable/blood/gibs(get_turf(victim))
+ gore = new /obj/decal/cleanable/blood/gibs(get_turf(victim))
if(species)
- var/obj/effect/decal/cleanable/blood/gibs/G = gore
+ var/obj/decal/cleanable/blood/gibs/G = gore
G.fleshcolor = use_flesh_colour
G.basecolor = use_blood_colour
G.update_icon()
gore.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
- for(var/obj/item/organ/I in internal_organs)
- I.removed()
- if(!QDELETED(I) && isturf(loc))
- I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
-
for(var/obj/item/I in src)
I.dropInto(loc)
I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
@@ -1095,8 +1108,8 @@ Note that amputating the affected organ does in fact remove the infection from t
if(company)
var/datum/robolimb/R = all_robolimbs[company]
if(!istype(R) || (species && (species.name in R.species_cannot_use)) || \
- (species && !(species.get_bodytype(owner) in R.allowed_bodytypes)) || \
- (length(R.applies_to_part) && !(organ_tag in R.applies_to_part)))
+ (species && !(species.get_bodytype(owner) in R.allowed_bodytypes)) || \
+ (length(R.applies_to_part) && !(organ_tag in R.applies_to_part)))
R = basic_robolimb
else
model = company
@@ -1147,7 +1160,7 @@ Note that amputating the affected organ does in fact remove the infection from t
return 0
/obj/item/organ/external/is_usable()
- return ..() && !is_stump() && !(status & ORGAN_TENDON_CUT) && (!can_feel_pain() || get_pain() < pain_disability_threshold) && brute_ratio < 1 && burn_ratio < 1
+ return ..() && !is_stump() && !(status & ORGAN_TENDON_CUT) && (!can_feel_pain() || get_pain() < pain_disability_threshold) && brute_ratio < 100 && burn_ratio < 100
/obj/item/organ/external/proc/is_malfunctioning()
return (BP_IS_ROBOTIC(src) && (brute_dam + burn_dam) >= 10 && prob(brute_dam + burn_dam))
@@ -1189,8 +1202,11 @@ Note that amputating the affected organ does in fact remove the infection from t
if(!owner)
return
- if(limb_flags & ORGAN_FLAG_CAN_GRASP) owner.grasp_limbs -= src
- if(limb_flags & ORGAN_FLAG_CAN_STAND) owner.stance_limbs -= src
+ if(limb_flags & ORGAN_FLAG_CAN_GRASP)
+ LAZYREMOVE(owner.grasp_limbs, src)
+
+ if(limb_flags & ORGAN_FLAG_CAN_STAND)
+ LAZYREMOVE(owner.stance_limbs, src)
switch(body_part)
if(FOOT_LEFT, FOOT_RIGHT)
@@ -1269,7 +1285,7 @@ Note that amputating the affected organ does in fact remove the infection from t
SPAN_DANGER("Your [src.name] explodes!"),\
SPAN_DANGER("You hear an explosion!"))
explosion(get_turf(owner), 2, EX_ACT_LIGHT)
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
+ var/datum/effect/spark_spread/spark_system = new /datum/effect/spark_spread()
spark_system.set_up(5, 0, victim)
spark_system.attach(owner)
spark_system.start()
diff --git a/code/modules/organs/external/_external_damage.dm b/code/modules/organs/external/_external_damage.dm
index cb0f323592ad2..6e41895811d9e 100644
--- a/code/modules/organs/external/_external_damage.dm
+++ b/code/modules/organs/external/_external_damage.dm
@@ -296,7 +296,7 @@
return 1
else if(agony_amount > 0.5 * max_damage)
- owner.visible_message(SPAN_WARNING("[owner] reels in pain!"))
+ owner.balloon_alert_to_viewers("корчится от боли!")
if(agony_amount > max_damage)
owner.Weaken(4)
else
@@ -323,29 +323,33 @@
return FALSE
/obj/item/organ/external/proc/get_brute_mod(damage_flags)
- var/obj/item/organ/internal/augment/armor/A = owner && owner.internal_organs_by_name["[BP_CHEST]_aug_armor"]
- var/B = 1
- if(A && istype(A))
- B = A.brute_mult
+ var/reduction = 1
+ for(var/obj/item/organ/internal/augment/armor/armor_aug in owner.internal_organs)
+ reduction = reduction * armor_aug.brute_mult
+ var/base_armor = 1
+ if(reduction != 1)
+ base_armor = reduction
if(!BP_IS_ROBOTIC(src))
- B *= species.get_brute_mod(owner)
+ base_armor *= species.get_brute_mod(owner)
var/blunt = !(damage_flags & DAMAGE_FLAG_EDGE|DAMAGE_FLAG_SHARP)
if(blunt && BP_IS_BRITTLE(src))
- B *= 1.5
+ base_armor *= 1.5
if(BP_IS_CRYSTAL(src))
- B *= 0.8
- return B + (0.2 * burn_dam/max_damage) //burns make you take more brute damage
+ base_armor *= 0.8
+ return base_armor + (0.2 * burn_dam/max_damage) //burns make you take more brute damage
/obj/item/organ/external/proc/get_burn_mod(damage_flags)
- var/obj/item/organ/internal/augment/armor/A = owner && owner.internal_organs_by_name["[BP_CHEST]_aug_armor"]
- var/B = 1
- if(A && istype(A))
- B = A.burn_mult
+ var/reduction = 1
+ for(var/obj/item/organ/internal/augment/armor/armor_aug in owner.internal_organs)
+ reduction = reduction * armor_aug.burn_mult
+ var/base_armor = 1
+ if(reduction != 1)
+ base_armor = reduction
if(!BP_IS_ROBOTIC(src))
- B *= species.get_burn_mod(owner)
+ base_armor *= species.get_burn_mod(owner)
if(BP_IS_CRYSTAL(src))
- B *= 0.1
- return B
+ base_armor *= 0.1
+ return base_armor
//organs can come off in three cases
//1. If the damage source is edge_eligible and the brute damage dealt exceeds the edge threshold, then the organ is cut off.
diff --git a/code/modules/organs/external/_external_icons.dm b/code/modules/organs/external/_external_icons.dm
index 14762a6b715c0..639f96a0d6f3c 100644
--- a/code/modules/organs/external/_external_icons.dm
+++ b/code/modules/organs/external/_external_icons.dm
@@ -1,16 +1,14 @@
var/global/list/limb_icon_cache = list()
+/// Layer for bodyparts that should appear behind every other bodypart - Mostly, legs when facing WEST or EAST
+#define BODYPARTS_LOW_LAYER -2
+
/obj/item/organ/external/set_dir()
return
/obj/item/organ/external/proc/compile_icon()
- overlays.Cut()
- // This is a kludge, only one icon has more than one generation of children though.
- for(var/obj/item/organ/external/organ in contents)
- if(organ.children && length(organ.children))
- for(var/obj/item/organ/external/child in organ.children)
- overlays += child.mob_icon
- overlays += organ.mob_icon
+ ClearOverlays()
+ update_icon()
/obj/item/organ/external/proc/sync_colour_to_human(mob/living/carbon/human/human)
skin_tone = null
@@ -53,27 +51,73 @@ var/global/list/limb_icon_cache = list()
update_icon(1)
if(owner)
SetName("[owner.real_name]'s head")
- addtimer(new Callback(owner, /mob/living/carbon/human/proc/update_hair), 1, TIMER_UNIQUE)
+ addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon/human, update_hair)), 1, TIMER_UNIQUE)
..()
- var/list/sorted = list()
+/obj/item/organ/external/proc/get_icon_key()
+ RETURN_TYPE(/list)
+ . = list()
+
+ var/gender = "_m"
+ if(!(limb_flags & ORGAN_FLAG_GENDERED_ICON))
+ gender = null
+ else if (dna && dna.GetUIState(DNA_UI_GENDER))
+ gender = "_f"
+ else if(owner && owner.gender == FEMALE)
+ gender = "_f"
+
+ . += "[gender]-"
+ . += "[organ_tag]-"
+ . += "[species.get_race_key(owner)]"
+
+ if(species.base_skin_colours && !isnull(species.base_skin_colours[base_skin]))
+ . += "-[species.base_skin_colours[base_skin]]"
+
+ if(force_icon)
+ . += "[force_icon]"
+ else if (BP_IS_ROBOTIC(src))
+ . += "robot"
+ else if (status & ORGAN_MUTATED)
+ . += "deformed"
+ else if (owner && (MUTATION_SKELETON in owner.mutations))
+ . += "skeleton"
+ else if (owner && (MUTATION_HUSK in owner.mutations))
+ . += "husk"
+
+ //Colour, maybe simplify this one day and actually calculate it once
+ if(status & ORGAN_DEAD)
+ . += "_dead"
+
+ if(skin_tone)
+ . += "_tone_[skin_tone]"
+
+ if(species.appearance_flags & SPECIES_APPEARANCE_HAS_SKIN_COLOR)
+ if(s_col && length(s_col) >= 3)
+ . += "_color_[s_col[1]]_[s_col[2]]_[s_col[3]]_[s_col_blend]"
+
for(var/E in markings)
var/datum/sprite_accessory/marking/M = E
if (M.draw_target == MARKING_TARGET_SKIN)
- var/color = markings[E]
- var/state = M.icon_state
- if (M.use_organ_tag)
- state = "[state]-[organ_tag]"
- var/icon/I = icon(M.icon, state)
- I.Blend(color, M.blend)
- icon_cache_key += "[M.name][color]"
- ADD_SORTED(sorted, list(list(M.draw_order, I, M)), /proc/cmp_marking_order)
- for (var/entry in sorted)
- overlays |= entry[2]
- mob_icon.Blend(entry[2], entry[3]["layer_blend"])
+ . += "-[M.name][markings[E]]"
+
+ if(body_hair && islist(h_col) && length(h_col) >= 3)
+ . += "[body_hair]-[icon_name]-[h_col[1]][h_col[2]][h_col[3]]"
+
+ if(model)
+ . += "_model_[model]"
+
+ if(is_stump())
+ . += "-stump"
+
+ return .
-/obj/item/organ/external/var/icon_cache_key
/obj/item/organ/external/on_update_icon(regenerate = 0)
+ ClearOverlays()
+ mob_overlays = list()
+
+ var/husk_color_mod = rgb(96,88,80)
+ var/husk = owner && (MUTATION_HUSK in owner.mutations)
+
var/gender = "_m"
if(!(limb_flags & ORGAN_FLAG_GENDERED_ICON))
gender = null
@@ -82,26 +126,43 @@ var/global/list/limb_icon_cache = list()
else if(owner && owner.gender == FEMALE)
gender = "_f"
- icon_state = "[icon_name][gender]"
- if(species.base_skin_colours && !isnull(species.base_skin_colours[base_skin]))
- icon_state += species.base_skin_colours[base_skin]
- icon_cache_key = "[icon_state]_[species ? species.name : SPECIES_HUMAN]"
+ var/chosen_icon = ""
+ var/chosen_icon_state = ""
+
+ chosen_icon_state = "[icon_name][gender]"
+ if(species.base_skin_colours && !isnull(species.base_skin_colours[base_skin]))
+ chosen_icon_state += species.base_skin_colours[base_skin]
if(force_icon)
- icon = force_icon
+ chosen_icon = force_icon
else if (BP_IS_ROBOTIC(src))
- icon = 'icons/mob/human_races/cyberlimbs/robotic.dmi'
+ chosen_icon = 'icons/mob/human_races/cyberlimbs/robotic.dmi'
else if (!dna)
- icon = 'icons/mob/human_races/species/human/body.dmi'
+ chosen_icon = 'icons/mob/human_races/species/human/body.dmi'
else if (status & ORGAN_MUTATED)
- icon = species.deform
+ chosen_icon = species.deform
else if (owner && (MUTATION_SKELETON in owner.mutations))
- icon = 'icons/mob/human_races/species/human/skeleton.dmi'
+ chosen_icon = 'icons/mob/human_races/species/human/skeleton.dmi'
else
- icon = species.get_icobase(owner)
+ chosen_icon = species.get_icobase(owner)
+
+ var/icon/mob_icon = apply_colouration(new/icon(chosen_icon, chosen_icon_state))
- mob_icon = apply_colouration(new/icon(icon, icon_state))
+ if (husk)
+ mob_icon.ColorTone(husk_color_mod)
+
+ //Handle husk overlay.
+ if(husk)
+ var/husk_icon = species.get_husk_icon(src)
+ if(husk_icon)
+ var/icon/mask = new/icon(chosen_icon)
+ var/blood = species.get_blood_colour(owner)
+ var/icon/husk_over = new(species.husk_icon,"")
+ mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0)
+ husk_over.Blend(mask, ICON_ADD)
+ husk_over.Blend(blood, ICON_MULTIPLY)
+ mob_icon.Blend(husk_over, ICON_OVERLAY)
var/list/sorted = list()
for(var/E in markings)
@@ -113,10 +174,9 @@ var/global/list/limb_icon_cache = list()
state = "[state]-[organ_tag]"
var/icon/I = icon(M.icon, state)
I.Blend(color, M.blend)
- icon_cache_key += "[M.name][color]"
- ADD_SORTED(sorted, list(list(M.draw_order, I, M)), /proc/cmp_marking_order)
- for (var/entry in sorted)
- overlays |= entry[2]
+ ADD_SORTED(sorted, list(list(M.draw_order, I, M)), GLOBAL_PROC_REF(cmp_marking_order))
+
+ for (var/entry in sorted) //Revisit this with blendmodes
mob_icon.Blend(entry[2], entry[3]["layer_blend"])
if(body_hair && islist(h_col) && length(h_col) >= 3)
@@ -124,17 +184,52 @@ var/global/list/limb_icon_cache = list()
if(!limb_icon_cache[cache_key])
var/icon/I = icon(species.get_icobase(owner), "[icon_name]_[body_hair]")
I.Blend(rgb(h_col[1],h_col[2],h_col[3]), ICON_ADD)
- limb_icon_cache[cache_key] = I
mob_icon.Blend(limb_icon_cache[cache_key], ICON_OVERLAY)
- if(model)
- icon_cache_key += "_model_[model]"
+ //Fix leg layering here
+ //Alternatively you could use masks but it's about same amount of work
+ //Note: This really only works because everything up until now was icon ops to build an icon we can work with
+ // If we ever move to pure overlays for body hair / modifiers / cosmetic changes to a limb, look up daedalusdock's implementation
+ if(icon_position & (LEFT | RIGHT))
+ var/icon/under_icon = new('icons/mob/human.dmi',"blank")
+ under_icon.Insert(new/icon(mob_icon,dir=NORTH),dir=NORTH)
+ under_icon.Insert(new/icon(mob_icon,dir=SOUTH),dir=SOUTH)
+ if(!(icon_position & LEFT))
+ under_icon.Insert(new/icon(mob_icon,dir=EAST),dir=EAST)
+ if(!(icon_position & RIGHT))
+ under_icon.Insert(new/icon(mob_icon,dir=WEST),dir=WEST)
+ //At this point, the icon has all the valid states for both left and right leg overlays
+ var/mutable_appearance/upper_appearance = mutable_appearance(under_icon, chosen_icon_state, flags = DEFAULT_APPEARANCE_FLAGS)
+ upper_appearance.layer = FLOAT_LAYER
+ mob_overlays += upper_appearance
+
+ if(icon_position & LEFT)
+ under_icon.Insert(new/icon(mob_icon,dir=EAST),dir=EAST)
+ if(icon_position & RIGHT)
+ under_icon.Insert(new/icon(mob_icon,dir=WEST),dir=WEST)
+
+ var/mutable_appearance/under_appearance = mutable_appearance(under_icon, chosen_icon_state, flags = DEFAULT_APPEARANCE_FLAGS)
+ upper_appearance.layer = BODYPARTS_LOW_LAYER
+ mob_overlays += under_appearance
+ else
+ var/mutable_appearance/limb_appearance = mutable_appearance(mob_icon, chosen_icon_state, flags = DEFAULT_APPEARANCE_FLAGS)
+ if(icon_position & UNDER)
+ limb_appearance.layer = BODYPARTS_LOW_LAYER
+ mob_overlays += limb_appearance
+
+ if(blocks_emissive)
+ var/mutable_appearance/limb_em_block = emissive_blocker(chosen_icon, chosen_icon_state, FLOAT_LAYER)
+ limb_em_block.dir = dir
+ mob_overlays += limb_em_block
+
+ AddOverlays(mob_overlays)
+
dir = EAST
- icon = mob_icon
+ icon = null
-/obj/item/organ/external/proc/get_icon()
+/obj/item/organ/external/proc/get_overlays()
update_icon()
- return mob_icon
+ return mob_overlays
// Returns an image for use by the human health dolly HUD element.
// If the limb is in pain, it will be used as a minimum damage
@@ -150,20 +245,24 @@ var/global/list/robot_hud_colours = list("#ffffff","#cccccc","#aaaaaa","#888888"
// icon_cache_key is set by any get_icon() calls that are made.
// This looks convoluted, but it's this way to avoid icon proc calls.
if(!hud_damage_image)
- var/cache_key = "dambase-[icon_cache_key]"
- if(!icon_cache_key || !limb_icon_cache[cache_key])
- limb_icon_cache[cache_key] = icon(get_icon(), null, SOUTH)
- var/image/temp = image(limb_icon_cache[cache_key])
- if(species)
- // Calculate the required colour matrix.
- var/r = 0.30 * species.health_hud_intensity
- var/g = 0.59 * species.health_hud_intensity
- var/b = 0.11 * species.health_hud_intensity
- temp.color = list(r, r, r, g, g, g, b, b, b)
- temp.pixel_x = owner.default_pixel_x
- temp.pixel_y = owner.default_pixel_y
+ var/cache_key = "dambase-[json_encode(get_icon_key())]"
+ if(!cache_key || !limb_icon_cache[cache_key])
+ var/list/appearances = get_overlays()
+ for(var/image/I as anything in appearances) //Mutable appearances are a type of image
+ I.dir = SOUTH
+ I.icon_state = null
+ if(species)
+ // Calculate the required colour matrix.
+ var/r = 0.30 * species.health_hud_intensity
+ var/g = 0.59 * species.health_hud_intensity
+ var/b = 0.11 * species.health_hud_intensity
+ I.color = list(r, r, r, g, g, g, b, b, b)
+ I.pixel_x = owner.default_pixel_x
+ I.pixel_y = owner.default_pixel_y
+ limb_icon_cache[cache_key] = appearances
+ var/list/appearances = limb_icon_cache[cache_key]
hud_damage_image = image(null)
- hud_damage_image.overlays += temp
+ hud_damage_image.AddOverlays(appearances)
// Calculate the required color index.
var/dam_state = min(1,((brute_dam+burn_dam)/max(1,max_damage)))
@@ -172,7 +271,7 @@ var/global/list/robot_hud_colours = list("#ffffff","#cccccc","#aaaaaa","#888888"
dam_state = min_dam_state
// Apply colour and return product.
var/list/hud_colours = !BP_IS_ROBOTIC(src) ? flesh_hud_colours : robot_hud_colours
- hud_damage_image.color = hud_colours[max(1,min(Ceil(dam_state*length(hud_colours)),length(hud_colours)))]
+ hud_damage_image.color = hud_colours[max(1,min(ceil(dam_state*length(hud_colours)),length(hud_colours)))]
return hud_damage_image
/obj/item/organ/external/proc/apply_colouration(icon/applying)
@@ -186,7 +285,6 @@ var/global/list/robot_hud_colours = list("#ffffff","#cccccc","#aaaaaa","#888888"
applying += rgb(,,,180) // Makes the icon translucent, SO INTUITIVE TY BYOND
else if(status & ORGAN_DEAD)
- icon_cache_key += "_dead"
applying.ColorTone(rgb(10,50,0))
applying.SetIntensity(0.7)
@@ -195,11 +293,9 @@ var/global/list/robot_hud_colours = list("#ffffff","#cccccc","#aaaaaa","#888888"
applying.Blend(rgb(skin_tone, skin_tone, skin_tone), ICON_ADD)
else
applying.Blend(rgb(-skin_tone, -skin_tone, -skin_tone), ICON_SUBTRACT)
- icon_cache_key += "_tone_[skin_tone]"
if(species.appearance_flags & SPECIES_APPEARANCE_HAS_SKIN_COLOR)
if(s_col && length(s_col) >= 3)
applying.Blend(rgb(s_col[1], s_col[2], s_col[3]), s_col_blend)
- icon_cache_key += "_color_[s_col[1]]_[s_col[2]]_[s_col[3]]_[s_col_blend]"
return applying
diff --git a/code/modules/organs/external/head.dm b/code/modules/organs/external/head.dm
index a7e0c47559178..36d6f4a15f50e 100644
--- a/code/modules/organs/external/head.dm
+++ b/code/modules/organs/external/head.dm
@@ -39,7 +39,7 @@
. = ..()
if(forehead_graffiti && graffiti_style)
- to_chat(user, SPAN_NOTICE("It has \"[forehead_graffiti]\" written on it in [graffiti_style]!"))
+ . += SPAN_NOTICE("It has \"[forehead_graffiti]\" written on it in [graffiti_style]!")
/obj/item/organ/external/head/proc/write_on(mob/penman, style)
var/head_name = name
@@ -94,8 +94,19 @@
if (burn_dam > 40)
disfigure(INJURY_TYPE_BURN)
-/obj/item/organ/external/head/on_update_icon()
+/obj/item/organ/external/head/get_icon_key()
+ . = ..()
+
+ if(owner?.makeup_style && !BP_IS_ROBOTIC(src) && (species && (species.appearance_flags & SPECIES_APPEARANCE_HAS_LIPS)))
+ . += "[owner.makeup_style]"
+ else
+ . += "nolips"
+
+ var/obj/item/organ/internal/eyes/eyes = owner.internal_organs_by_name[owner.species.vision_organ ? owner.species.vision_organ : BP_EYES]
+ if(eyes)
+ . += "[rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])]"
+/obj/item/organ/external/head/on_update_icon()
..()
if(owner)
@@ -103,25 +114,25 @@
if(draw_eyes)
var/icon/I = get_eyes()
if(I)
- overlays |= I
- mob_icon.Blend(I, ICON_OVERLAY)
+ var/mutable_appearance/eye_appearance = mutable_appearance(I, flags = DEFAULT_APPEARANCE_FLAGS)
+ mob_overlays |= eye_appearance
// Floating eyes or other effects.
var/image/eye_glow = get_eye_overlay()
- if(eye_glow) overlays |= eye_glow
+ if(eye_glow)
+ AddOverlays(eye_glow)
if(owner.makeup_style && !BP_IS_ROBOTIC(src) && (species && (species.appearance_flags & SPECIES_APPEARANCE_HAS_LIPS)))
- var/icon/lip_icon = new/icon('icons/mob/human_races/species/human/lips.dmi', "lips_[owner.makeup_style]_s")
- overlays |= lip_icon
- mob_icon.Blend(lip_icon, ICON_OVERLAY)
-
- overlays |= get_hair_icon()
+ var/mutable_appearance/lip_appearance = mutable_appearance('icons/mob/human_races/species/human/lips.dmi', "lips_[owner.makeup_style]_s",flags = DEFAULT_APPEARANCE_FLAGS)
+ mob_overlays |= lip_appearance
- return mob_icon
+ SetOverlays(mob_overlays)
+ var/hair_icon = get_hair_icon()
+ AddOverlays(hair_icon)
/obj/item/organ/external/head/proc/get_hair_icon()
var/image/res = image(species.icon_template,"")
- if(owner.facial_hair_style)
+ if(owner?.facial_hair_style)
var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[owner.facial_hair_style]
if(facial_hair_style)
if(!facial_hair_style.species_allowed || (species.get_bodytype(owner) in facial_hair_style.species_allowed))
@@ -129,9 +140,9 @@
var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
if(facial_hair_style.do_coloration & DO_COLORATION_USER)
facial_s.Blend(owner.facial_hair_color, facial_hair_style.blend)
- res.overlays |= facial_s
+ res.AddOverlays(facial_s)
- if (owner.head_hair_style)
+ if (owner?.head_hair_style)
var/icon/HI
var/datum/sprite_accessory/hair/H = GLOB.hair_styles_list[owner.head_hair_style]
if ((owner.head?.flags_inv & BLOCKHEADHAIR) && !(H.flags & VERY_SHORT))
@@ -151,10 +162,11 @@
var/icon/I = icon(M.icon, M.icon_state)
I.Blend(HI, ICON_AND)
I.Blend(color, ICON_MULTIPLY)
- ADD_SORTED(sorted_hair_markings, list(list(M.draw_order, I)), /proc/cmp_marking_order)
+ ADD_SORTED(sorted_hair_markings, list(list(M.draw_order, I)), GLOBAL_PROC_REF(cmp_marking_order))
for (var/entry in sorted_hair_markings)
HI.Blend(entry[2], ICON_OVERLAY)
- res.overlays |= HI
+ //TODO : Add emissive blocker here if hair should block it. Else, leave as is
+ res.AddOverlays(HI)
var/list/sorted_head_markings = list()
for (var/E in markings)
@@ -189,10 +201,9 @@
0,0,0,1,
rgb[1] / 255, rgb[2] / 255, rgb[3] / 255, 0
)
- icon_cache_key += "[M.name][color]"
- ADD_SORTED(sorted_head_markings, list(list(M.draw_order, I)), /proc/cmp_marking_order)
+ ADD_SORTED(sorted_head_markings, list(list(M.draw_order, I)), GLOBAL_PROC_REF(cmp_marking_order))
for (var/entry in sorted_head_markings)
- res.overlays |= entry[2]
+ res.AddOverlays(entry[2])
return res
diff --git a/code/modules/organs/external/species/diona.dm b/code/modules/organs/external/species/diona.dm
index 77f6c86b1b7b0..204a5dfc56aa8 100644
--- a/code/modules/organs/external/species/diona.dm
+++ b/code/modules/organs/external/species/diona.dm
@@ -130,8 +130,9 @@
var/icon/I = get_eyes()
if(glowing_eyes)
var/image/eye_glow = image(I)
- eye_glow.layer = EYE_GLOW_LAYER
- eye_glow.plane = EFFECTS_ABOVE_LIGHTING_PLANE
+ eye_glow.AddOverlays(emissive_appearance(eye_icon_location, ""))
+ eye_glow.layer = FLOAT_LAYER
+ //eye_glow.plane = EFFECTS_ABOVE_LIGHTING_PLANE
return eye_glow
/obj/item/organ/external/head/diona/get_eyes()
@@ -146,4 +147,4 @@
H.death()
if(prob(25))
spawn_diona_nymph(get_turf(src))
- qdel(src)
\ No newline at end of file
+ qdel(src)
diff --git a/code/modules/organs/external/species/nabber_threat.dm b/code/modules/organs/external/species/nabber_threat.dm
index eee5023c5f8ee..630423e0e7219 100644
--- a/code/modules/organs/external/species/nabber_threat.dm
+++ b/code/modules/organs/external/species/nabber_threat.dm
@@ -28,6 +28,6 @@
playsound(owner.loc, 'sound/effects/angrybug.ogg', 60, 0)
owner.skin_state = SKIN_THREAT
owner.update_skin()
- addtimer(new Callback(owner, /mob/living/carbon/human/proc/reset_skin), 10 SECONDS, TIMER_UNIQUE)
+ addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon/human, reset_skin)), 10 SECONDS, TIMER_UNIQUE)
else if(owner.skin_state == SKIN_THREAT)
owner.reset_skin()
diff --git a/code/modules/organs/external/wounds/wound.dm b/code/modules/organs/external/wounds/wound.dm
index a34faa0b3973b..d602ddb821113 100644
--- a/code/modules/organs/external/wounds/wound.dm
+++ b/code/modules/organs/external/wounds/wound.dm
@@ -149,9 +149,9 @@
if(LAZYLEN(embedded_objects))
return amount // heal nothing
if(parent_organ)
- if (damage_type == INJURY_TYPE_BURN && !(parent_organ.burn_ratio < 1 || (parent_organ.limb_flags & ORGAN_FLAG_HEALS_OVERKILL)))
+ if (damage_type == INJURY_TYPE_BURN && !(parent_organ.burn_ratio < 100 || (parent_organ.limb_flags & ORGAN_FLAG_HEALS_OVERKILL)))
return amount //We don't want to heal wounds on irreparable organs.
- else if(!(parent_organ.brute_ratio < 1 || (parent_organ.limb_flags & ORGAN_FLAG_HEALS_OVERKILL)))
+ else if(!(parent_organ.brute_ratio < 100 || (parent_organ.limb_flags & ORGAN_FLAG_HEALS_OVERKILL)))
return amount
var/healed_damage = min(src.damage, amount)
diff --git a/code/modules/organs/internal/_internal.dm b/code/modules/organs/internal/_internal.dm
index 8ba6dda15cc2c..748cd4326d8bb 100644
--- a/code/modules/organs/internal/_internal.dm
+++ b/code/modules/organs/internal/_internal.dm
@@ -10,7 +10,7 @@
/obj/item/organ/internal/New(mob/living/carbon/holder)
if(max_damage)
- min_bruised_damage = Floor(max_damage / 4)
+ min_bruised_damage = floor(max_damage / 4)
..()
if(istype(holder))
holder.internal_organs |= src
@@ -119,9 +119,9 @@
return damage >= min_bruised_damage
/obj/item/organ/internal/proc/set_max_damage(ndamage)
- max_damage = Floor(ndamage)
- min_broken_damage = Floor(0.75 * max_damage)
- min_bruised_damage = Floor(0.25 * max_damage)
+ max_damage = floor(ndamage)
+ min_broken_damage = floor(0.75 * max_damage)
+ min_bruised_damage = floor(0.25 * max_damage)
/obj/item/organ/internal/take_general_damage(amount, silent = FALSE)
take_internal_damage(amount, silent)
@@ -173,7 +173,7 @@
if(damage > min_broken_damage)
var/scarring = damage/max_damage
scarring = 1 - 0.3 * scarring ** 2 // Between ~15 and 30 percent loss
- var/new_max_dam = Floor(scarring * max_damage)
+ var/new_max_dam = floor(scarring * max_damage)
if(new_max_dam < max_damage)
to_chat(user, SPAN_WARNING("Not every part of [src] could be saved, some dead tissue had to be removed, making it more suspectable to damage in the future."))
set_max_damage(new_max_dam)
@@ -191,9 +191,10 @@
/obj/item/organ/internal/emp_act(severity)
if(!BP_IS_ROBOTIC(src))
return
+ var/rand_modifier = rand(1, 3)
switch (severity)
if (EMP_ACT_HEAVY)
- take_internal_damage(16)
+ take_internal_damage(5 * rand_modifier)
if (EMP_ACT_LIGHT)
- take_internal_damage(9)
+ take_internal_damage(2 * rand_modifier)
..()
diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm
index 7d5d0c5b3acd1..f593b862d7405 100644
--- a/code/modules/organs/internal/brain.dm
+++ b/code/modules/organs/internal/brain.dm
@@ -80,9 +80,9 @@
/obj/item/organ/internal/brain/examine(mob/user)
. = ..()
if(brainmob && brainmob.client)//if thar be a brain inside... the brain.
- to_chat(user, "You can feel the small spark of life still left in this one.")
+ . += SPAN_NOTICE("You can feel the small spark of life still left in this one.")
else
- to_chat(user, "This one seems particularly lifeless. Perhaps it will regain some of its luster later..")
+ . += SPAN_NOTICE("This one seems particularly lifeless. Perhaps it will regain some of its luster later...")
/obj/item/organ/internal/brain/removed(mob/living/user)
if(!istype(owner))
@@ -210,18 +210,18 @@
if (owner)
owner.flash_eyes()
owner.eye_blurry += damage_secondary
- owner.confused += damage_secondary * 2
+ owner.mod_confused(damage_secondary * 2)
owner.Paralyse(damage_secondary)
owner.Weaken(round(damageTaken, 1))
if (prob(30))
- addtimer(new Callback(src, .proc/brain_damage_callback, damage), rand(6, 20) SECONDS, TIMER_UNIQUE)
+ addtimer(CALLBACK(src, PROC_REF(brain_damage_callback), damage), rand(6, 20) SECONDS, TIMER_UNIQUE)
/obj/item/organ/internal/brain/proc/brain_damage_callback(damage) //Confuse them as a somewhat uncommon aftershock. Side note: Only here so a spawn isn't used. Also, for the sake of a unique timer.
if (!owner || owner.stat == DEAD || (status & ORGAN_DEAD))
return
to_chat(owner, SPAN_NOTICE(SPAN_STYLE("font-size: 10", "I can't remember which way is forward...")))
- owner.confused += damage
+ owner.mod_confused(damage)
/obj/item/organ/internal/brain/proc/handle_disabilities()
if(owner.stat)
@@ -253,7 +253,7 @@
var/blood_volume = owner.get_blood_oxygenation()
if(blood_volume < BLOOD_VOLUME_BAD)
to_chat(user, SPAN_DANGER("Parts of [src] didn't survive the procedure due to lack of air supply!"))
- set_max_damage(Floor(max_damage - 0.25*damage))
+ set_max_damage(floor(max_damage - 0.25*damage))
heal_damage(damage)
/obj/item/organ/internal/brain/get_scarring_level()
diff --git a/code/modules/organs/internal/heart.dm b/code/modules/organs/internal/heart.dm
index a6368d8aad4cd..98c3576101bb0 100644
--- a/code/modules/organs/internal/heart.dm
+++ b/code/modules/organs/internal/heart.dm
@@ -153,7 +153,7 @@
blood_max += W.damage / 40
if(temp.status & ORGAN_ARTERY_CUT)
- var/bleed_amount = Floor((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 400 : 250))*temp.arterial_bleed_severity)
+ var/bleed_amount = floor((owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 400 : 250))*temp.arterial_bleed_severity)
if(bleed_amount)
if(open_wound)
blood_max += bleed_amount
@@ -178,13 +178,11 @@
SPAN_DANGER("Blood sprays out from \the [owner]'s [spray_organ]!"),
FONT_HUGE(SPAN_DANGER("Blood sprays out from your [spray_organ]!"))
)
- owner.Stun(1)
- owner.eye_blurry = 2
//AB occurs every heartbeat, this only throttles the visible effect
next_blood_squirt = world.time + 80
var/turf/sprayloc = get_turf(owner)
- blood_max -= owner.drip(Ceil(blood_max/3), sprayloc)
+ blood_max -= owner.drip(ceil(blood_max/3), sprayloc)
if(blood_max > 0)
blood_max -= owner.blood_squirt(blood_max, sprayloc)
if(blood_max > 0)
diff --git a/code/modules/organs/internal/lungs.dm b/code/modules/organs/internal/lungs.dm
index b4da28827dea8..ada511ebb6b8e 100644
--- a/code/modules/organs/internal/lungs.dm
+++ b/code/modules/organs/internal/lungs.dm
@@ -196,7 +196,7 @@
var/breathed_product = gas_data.breathed_product[gasname]
if(breathed_product)
var/reagent_amount = breath.gas[gasname] * REAGENT_GAS_EXCHANGE_FACTOR * ratio
- // Little bit of sanity so we aren't trying to add 0.0000000001 units of CO2, and so we don't end up with 99999 units of CO2.
+ // Little bit of sanity so we aren't trying to add 0.0000000001 units of CO2, and so we don't end up with 99999 units of CO2.
if(reagent_amount >= 0.05)
owner.reagents.add_reagent(breathed_product, reagent_amount)
breath.adjust_gas(gasname, -breath.gas[gasname], update = 0) //update after
@@ -241,61 +241,47 @@
last_int_pressure = 0
/obj/item/organ/internal/lungs/proc/handle_temperature_effects(datum/gas_mixture/breath)
- // Hot air hurts :(
- if((breath.temperature < species.cold_level_1 || breath.temperature > species.heat_level_1) && !(MUTATION_COLD_RESISTANCE in owner.mutations))
- var/damage = 0
- if(breath.temperature <= species.cold_level_1)
- if(prob(20))
+ if ((breath.temperature < species.cold_level_1 || breath.temperature > species.heat_level_1) && !(MUTATION_COLD_RESISTANCE in owner.mutations))
+ var/breath_damage = 0
+ if (breath.temperature < species.cold_level_1)
+ if (prob(20))
to_chat(owner, SPAN_DANGER("You feel your face freezing and icicles forming in your lungs!"))
- switch(breath.temperature)
- if(species.cold_level_3 to species.cold_level_2)
- damage = COLD_GAS_DAMAGE_LEVEL_3
- if(species.cold_level_2 to species.cold_level_1)
- damage = COLD_GAS_DAMAGE_LEVEL_2
- else
- damage = COLD_GAS_DAMAGE_LEVEL_1
-
- if(prob(20))
- owner.apply_damage(damage, DAMAGE_BURN, BP_HEAD, used_weapon = "Excessive Cold")
+ if (breath.temperature < species.cold_level_3)
+ breath_damage = COLD_GAS_DAMAGE_LEVEL_3
+ else if (breath.temperature < species.cold_level_2)
+ breath_damage = COLD_GAS_DAMAGE_LEVEL_2
else
- src.damage += damage
+ breath_damage = COLD_GAS_DAMAGE_LEVEL_1
+ if (prob(20))
+ owner.apply_damage(breath_damage, DAMAGE_BURN, BP_HEAD, used_weapon = "Excessive Cold")
+ else
+ damage += breath_damage
owner.fire_alert = 1
- else if(breath.temperature >= species.heat_level_1)
- if(prob(20))
+ else if (breath.temperature > species.heat_level_1)
+ if (prob(20))
to_chat(owner, SPAN_DANGER("You feel your face burning and a searing heat in your lungs!"))
-
- switch(breath.temperature)
- if(species.heat_level_1 to species.heat_level_2)
- damage = HEAT_GAS_DAMAGE_LEVEL_1
- if(species.heat_level_2 to species.heat_level_3)
- damage = HEAT_GAS_DAMAGE_LEVEL_2
- else
- damage = HEAT_GAS_DAMAGE_LEVEL_3
-
- if(prob(20))
- owner.apply_damage(damage, DAMAGE_BURN, BP_HEAD, used_weapon = "Excessive Heat")
+ if (breath.temperature > species.heat_level_3)
+ breath_damage = HEAT_GAS_DAMAGE_LEVEL_3
+ else if (breath.temperature > species.heat_level_2)
+ breath_damage = HEAT_GAS_DAMAGE_LEVEL_2
else
- src.damage += damage
+ breath_damage = HEAT_GAS_DAMAGE_LEVEL_1
+ if (prob(20))
+ owner.apply_damage(breath_damage, DAMAGE_BURN, BP_HEAD, used_weapon = "Excessive Heat")
+ else
+ damage += breath_damage
owner.fire_alert = 2
-
- //breathing in hot/cold air also heats/cools you a bit
var/temp_adj = breath.temperature - owner.bodytemperature
- if (temp_adj < 0)
- temp_adj /= (BODYTEMP_COLD_DIVISOR * 5) //don't raise temperature as much as if we were directly exposed
- else
- temp_adj /= (BODYTEMP_HEAT_DIVISOR * 5) //don't raise temperature as much as if we were directly exposed
-
- var/relative_density = breath.total_moles / (MOLES_CELLSTANDARD * breath.volume/CELL_VOLUME)
- temp_adj *= relative_density
-
- if (temp_adj > BODYTEMP_HEATING_MAX) temp_adj = BODYTEMP_HEATING_MAX
- if (temp_adj < BODYTEMP_COOLING_MAX) temp_adj = BODYTEMP_COOLING_MAX
-// log_debug("Breath: [breath.temperature], [src]: [bodytemperature], Adjusting: [temp_adj]")
- owner.bodytemperature += temp_adj
-
- else if(breath.temperature >= species.heat_discomfort_level)
+ if (temp_adj)
+ if (temp_adj < 0)
+ temp_adj /= (BODYTEMP_COLD_DIVISOR * 5)
+ else
+ temp_adj /= (BODYTEMP_HEAT_DIVISOR * 5)
+ temp_adj *= breath.total_moles / (MOLES_CELLSTANDARD * breath.volume / CELL_VOLUME)
+ owner.bodytemperature += clamp(temp_adj, BODYTEMP_COOLING_MAX, BODYTEMP_HEATING_MAX)
+ else if (breath.temperature >= species.heat_discomfort_level)
species.get_environment_discomfort(owner,"heat")
- else if(breath.temperature <= species.cold_discomfort_level)
+ else if (breath.temperature <= species.cold_discomfort_level)
species.get_environment_discomfort(owner,"cold")
/obj/item/organ/internal/lungs/listen()
diff --git a/code/modules/organs/internal/species/borer.dm b/code/modules/organs/internal/species/borer.dm
index bb929d066c890..c7c3805a6abdc 100644
--- a/code/modules/organs/internal/species/borer.dm
+++ b/code/modules/organs/internal/species/borer.dm
@@ -1,8 +1,8 @@
//CORTICAL BORER ORGANS.
/obj/item/organ/internal/borer
name = "cortical borer"
- icon = 'icons/obj/objects.dmi'
- icon_state = "borer"
+ icon = 'icons/mob/simple_animal/animal.dmi'
+ icon_state = "brainslug"
organ_tag = BP_BRAIN
desc = "A disgusting space slug."
parent_organ = BP_HEAD
@@ -25,7 +25,7 @@
var/datum/reagent/blood/B = locate(/datum/reagent/blood) in H.vessel.reagent_list
blood_splatter(H,B,1)
- var/obj/effect/decal/cleanable/blood/splatter/goo = locate() in get_turf(owner)
+ var/obj/decal/cleanable/blood/splatter/goo = locate() in get_turf(owner)
if(goo)
goo.SetName("husk ichor")
goo.desc = "A thick goo that reeks of decay."
diff --git a/code/modules/organs/internal/species/diona.dm b/code/modules/organs/internal/species/diona.dm
index 0d2f366a3640d..416ecd71ee06e 100644
--- a/code/modules/organs/internal/species/diona.dm
+++ b/code/modules/organs/internal/species/diona.dm
@@ -1,6 +1,6 @@
/obj/item/organ/internal/diona
name = "diona nymph"
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/mob/gestalt.dmi'
icon_state = "nymph"
organ_tag = "special" // Turns into a nymph instantly, no transplanting possible.
diff --git a/code/modules/organs/internal/species/fbp.dm b/code/modules/organs/internal/species/fbp.dm
index 76ab45fb51c0e..0e089c86e200d 100644
--- a/code/modules/organs/internal/species/fbp.dm
+++ b/code/modules/organs/internal/species/fbp.dm
@@ -35,7 +35,7 @@
return FALSE
return cell && cell.checked_use(amount)
-/obj/item/organ/internal/cell/proc/use(amount)
+/obj/item/organ/internal/cell/use(amount)
if(!is_usable())
return 0
return cell && cell.use(amount)
@@ -56,6 +56,7 @@
if(!checked_use(cost) && owner.isSynthetic())
if(!owner.lying && !owner.buckled)
to_chat(owner, SPAN_WARNING("You don't have enough energy to function!"))
+ owner.Weaken(3)
owner.Paralyse(3)
if(percent() < 10 && prob(1))
to_chat(owner, SPAN_WARNING("Your internal battery beeps an alert code, it is low on charge!"))
@@ -65,36 +66,40 @@
if(cell)
cell.emp_act(severity)
-/obj/item/organ/internal/cell/attackby(obj/item/W, mob/user)
- if(isScrewdriver(W))
- if(open)
- open = 0
- to_chat(user, SPAN_NOTICE("You screw the battery panel in place."))
- else
- open = 1
- to_chat(user, SPAN_NOTICE("You unscrew the battery panel."))
+/obj/item/organ/internal/cell/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!open || !cell)
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ user.put_in_hands(cell)
+ to_chat(user, SPAN_NOTICE("You remove [cell] from [src]."))
+ cell = null
- if(isCrowbar(W))
- if(open)
- if(cell)
- user.put_in_hands(cell)
- to_chat(user, SPAN_NOTICE("You remove \the [cell] from \the [src]."))
- cell = null
+/obj/item/organ/internal/cell/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ open = !open
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, open)
- if (istype(W, /obj/item/cell))
+/obj/item/organ/internal/cell/attackby(obj/item/W, mob/user)
+ if(istype(W, /obj/item/cell))
if(open)
if(cell)
to_chat(user, SPAN_WARNING("There is a power cell already installed."))
else if(user.unEquip(W, src))
cell = W
- to_chat(user, SPAN_NOTICE("You insert \the [cell]."))
+ to_chat(user, SPAN_NOTICE("You insert [cell]."))
+ return TRUE
+ . = ..()
/obj/item/organ/internal/cell/replaced()
..()
// This is very ghetto way of rebooting an IPC. TODO better way.
if(owner && owner.stat == DEAD)
owner.set_stat(CONSCIOUS)
- owner.visible_message(SPAN_DANGER("\The [owner] twitches visibly!"))
+ owner.visible_message(SPAN_DANGER("[owner] twitches visibly!"))
/obj/item/organ/internal/cell/listen()
if(get_charge())
@@ -134,6 +139,8 @@
return
if(damage < 0.1*max_damage)
heal_damage(0.1)
+ if(stored_mmi.brainobj)
+ stored_mmi.brainobj.heal_damage(0.1)
/obj/item/organ/internal/mmi_holder/proc/update_from_mmi()
@@ -157,7 +164,7 @@
if(owner && owner.stat == DEAD)
owner.set_stat(CONSCIOUS)
owner.switch_from_dead_to_living_mob_list()
- owner.visible_message(SPAN_DANGER("\The [owner] twitches visibly!"))
+ owner.visible_message(SPAN_DANGER("[owner] twitches visibly!"))
/obj/item/organ/internal/mmi_holder/cut_away(mob/living/user)
var/obj/item/organ/external/parent = owner.get_organ(parent_organ)
@@ -183,3 +190,13 @@
if(response == "Yes")
persistantMind.transfer_to(stored_mmi.brainmob)
qdel(src)
+
+/obj/item/organ/internal/mmi_holder/take_internal_damage(amount, silent=FALSE)
+ ..()
+ if(stored_mmi.brainobj)
+ stored_mmi.brainobj.take_internal_damage(amount)
+
+/obj/item/organ/internal/mmi_holder/die()
+ ..()
+ if(stored_mmi.brainobj)
+ stored_mmi.brainobj.die()
diff --git a/code/modules/organs/internal/species/golem.dm b/code/modules/organs/internal/species/golem.dm
index 93e368c2e6467..32b1025f9bbac 100644
--- a/code/modules/organs/internal/species/golem.dm
+++ b/code/modules/organs/internal/species/golem.dm
@@ -1,7 +1,7 @@
/obj/item/organ/internal/brain/golem
name = "chem"
desc = "A tightly furled roll of paper, covered with indecipherable runes."
- icon = 'icons/obj/wizard.dmi'
+ icon = 'icons/obj/cult.dmi'
icon_state = "scroll"
/obj/item/organ/internal/brain/golem/can_recover()
diff --git a/code/modules/organs/internal/species/ipc.dm b/code/modules/organs/internal/species/ipc.dm
index 977bbe2c3d3f9..349692e0f6b9a 100644
--- a/code/modules/organs/internal/species/ipc.dm
+++ b/code/modules/organs/internal/species/ipc.dm
@@ -1,7 +1,7 @@
/obj/item/organ/internal/posibrain
name = "positronic brain"
desc = "A cube of shining metal, four inches to a side and covered in shallow grooves."
- icon = 'icons/obj/assemblies.dmi'
+ icon = 'icons/obj/assemblies/assemblies.dmi'
icon_state = "posibrain"
organ_tag = BP_POSIBRAIN
parent_organ = BP_CHEST
@@ -21,7 +21,7 @@
var/mob/living/silicon/sil_brainmob/brainmob = null
- var/searching = TIMER_ID_NULL
+ var/searching = null
var/last_search = 0
req_access = list(access_robotics)
@@ -58,20 +58,20 @@
/obj/item/organ/internal/posibrain/attack_self(mob/user)
if (!user.IsAdvancedToolUser())
return
- if (user.skill_check(SKILL_DEVICES, SKILL_ADEPT))
+ if (user.skill_check(SKILL_DEVICES, SKILL_TRAINED))
if (status & ORGAN_DEAD || !brainmob)
to_chat(user, SPAN_WARNING("\The [src] is ruined; it will never turn on again."))
return
if (damage)
to_chat(user, SPAN_WARNING("\The [src] is damaged and requires repair first."))
return
- if (searching != TIMER_ID_NULL)
+ if (searching)
visible_message("\The [user] flicks the activation switch on \the [src]. The lights go dark.", range = 3)
cancel_search()
return
start_search(user)
else
- if ((status & ORGAN_DEAD) || !brainmob || damage || (searching != TIMER_ID_NULL))
+ if ((status & ORGAN_DEAD) || !brainmob || damage || searching)
to_chat(user, SPAN_WARNING("\The [src] doesn't respond to your pokes and prods."))
return
start_search(user)
@@ -98,14 +98,14 @@
if (!protected)
var/datum/ghosttrap/T = get_ghost_trap("positronic brain")
T.request_player(brainmob, "Someone is requesting a personality for a positronic brain.", 60 SECONDS)
- searching = addtimer(new Callback(src, .proc/cancel_search), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
+ searching = addtimer(CALLBACK(src, PROC_REF(cancel_search)), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
icon_state = "posibrain-searching"
/obj/item/organ/internal/posibrain/proc/cancel_search()
visible_message(SPAN_ITALIC("\The [src] buzzes quietly and returns to an idle state."), range = 3)
- if (searching != TIMER_ID_NULL)
+ if (searching)
deltimer(searching)
- searching = TIMER_ID_NULL
+ searching = null
if (brainmob && brainmob.key)
if (brainmob.mind && brainmob.mind.special_role)
var/sneaky = sanitizeSafe(input(brainmob, "You're safe. Pick a new name as cover? Leave blank to skip.", "Get Sneaky?", brainmob.real_name) as text, MAX_NAME_LEN)
@@ -120,7 +120,7 @@
update_icon()
/obj/item/organ/internal/posibrain/attack_ghost(mob/observer/ghost/user)
- if (searching == TIMER_ID_NULL)
+ if (!searching)
return
if (!brainmob)
return
@@ -142,45 +142,40 @@
. = ..()
if (distance > 3)
return
- var/msg = ""
- if (isghost(user) || user.skill_check(SKILL_DEVICES, SKILL_ADEPT))
+ if (isghost(user) || user.skill_check(SKILL_DEVICES, SKILL_TRAINED))
if ((status & ORGAN_DEAD) || damage)
if ((status & ORGAN_DEAD))
- msg += SPAN_ITALIC("It is ruined and lifeless, damaged beyond hope of recovery.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It is ruined and lifeless, damaged beyond hope of recovery."))
else if (damage > min_broken_damage)
- msg += SPAN_ITALIC("It is seriously damaged and requires repair to work properly.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It is seriously damaged and requires repair to work properly."))
else if (damage > min_bruised_damage)
- msg += SPAN_ITALIC("It has taken some damage and is in need of repair.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It has taken some damage and is in need of repair."))
else
- msg += SPAN_ITALIC("It has superficial wear and should work normally.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It has superficial wear and should work normally."))
if (!(status & ORGAN_DEAD))
- if (msg)
- msg += "\n"
if (brainmob && brainmob.key)
+ var/msg
msg += SPAN_ITALIC("It blinks with activity.")
if (brainmob.stat || !brainmob.client)
msg += SPAN_ITALIC(" The responsiveness fault indicator is lit.")
+ . += SPAN_NOTICE(msg)
else if (damage)
- msg += SPAN_ITALIC("The red integrity fault indicator pulses slowly.")
+ . += SPAN_NOTICE(SPAN_ITALIC("The red integrity fault indicator pulses slowly."))
else
- msg += SPAN_ITALIC("The golden ready indicator [searching != TIMER_ID_NULL ? "flickers quickly as it tries to generate a personality" : "pulses lazily"].")
+ . += SPAN_NOTICE(SPAN_ITALIC("The golden ready indicator [searching ? "flickers quickly as it tries to generate a personality" : "pulses lazily"]."))
else
if ((status & ORGAN_DEAD) || damage > min_broken_damage)
- msg += SPAN_ITALIC("It looks wrecked.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It looks wrecked."))
else if (damage > min_bruised_damage)
- msg += SPAN_ITALIC("It looks damaged.")
+ . += SPAN_NOTICE(SPAN_ITALIC("It looks damaged."))
if (!(status & ORGAN_DEAD))
- if (msg)
- msg += "\n"
if (brainmob && brainmob.key)
- msg += SPAN_ITALIC("Little lights flicker on its surface.")
+ . += SPAN_NOTICE(SPAN_ITALIC("Little lights flicker on its surface."))
else
if (damage)
- msg += SPAN_ITALIC("A lone red light pulses malevolently on its surface.")
+ . += SPAN_NOTICE(SPAN_ITALIC("A lone red light pulses malevolently on its surface."))
else
- msg += SPAN_ITALIC("A lone golden light [searching != TIMER_ID_NULL ? "flickers quickly" : "pulses lazily"].")
- if (msg)
- to_chat(user, msg)
+ . += SPAN_NOTICE(SPAN_ITALIC("A lone golden light [searching ? "flickers quickly" : "pulses lazily"]."))
/obj/item/organ/internal/posibrain/emp_act(severity)
damage += rand(15 - severity * 5, 20 - severity * 5)
@@ -209,9 +204,9 @@
else
icon_state = "posibrain"
- overlays.Cut()
+ ClearOverlays()
if(shackle)
- overlays |= image('icons/obj/assemblies.dmi', "posibrain-shackles")
+ AddOverlays(image('icons/obj/assemblies/assemblies.dmi', "posibrain-shackles"))
/obj/item/organ/internal/posibrain/proc/transfer_identity(mob/living/carbon/H)
if(H && H.mind)
@@ -235,9 +230,9 @@
if (!owner || owner.stat)
return
if (damage > min_bruised_damage)
- if (prob(1) && owner.confused < 1)
+ if (prob(1) && !owner.is_confused())
to_chat(owner, SPAN_WARNING("Your comprehension of spacial positioning goes temporarily awry."))
- owner.confused += 3
+ owner.set_confused(3)
if (prob(1) && owner.eye_blurry < 1)
to_chat(owner, SPAN_WARNING("Your optical interpretations become transiently erratic."))
owner.eye_blurry += 6
@@ -263,7 +258,7 @@
if (C && C.get_charge() > 25)
C.use(25)
to_chat(owner, SPAN_WARNING("Your chassis power routine fluctuates wildly."))
- var/datum/effect/effect/system/spark_spread/S = new
+ var/datum/effect/spark_spread/S = new
S.set_up(2, 0, loc)
S.start()
diff --git a/code/modules/organs/internal/species/nabber.dm b/code/modules/organs/internal/species/nabber.dm
index bbd6c8249ae4f..0fcbffe720518 100644
--- a/code/modules/organs/internal/species/nabber.dm
+++ b/code/modules/organs/internal/species/nabber.dm
@@ -53,7 +53,7 @@
else
to_chat(owner, SPAN_NOTICE("Your protective lenses retract out of the way."))
innate_flash_protection = FLASH_PROTECTION_VULNERABLE
- addtimer(new Callback(src, .proc/remove_shield), 1 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(remove_shield)), 1 SECONDS)
owner.update_icons()
refresh_action_button()
diff --git a/code/modules/organs/internal/species/starborn.dm b/code/modules/organs/internal/species/starborn.dm
index 2c28f497555e2..54d63c84b17f5 100644
--- a/code/modules/organs/internal/species/starborn.dm
+++ b/code/modules/organs/internal/species/starborn.dm
@@ -1,5 +1,5 @@
/obj/item/organ/internal/brain/starlight
name = "essence of fire"
desc = "A fancy name for ash. Still, it does look a bit different from the regular stuff."
- icon = 'icons/obj/objects.dmi'
- icon_state = "ash"
\ No newline at end of file
+ icon = 'icons/obj/ash.dmi'
+ icon_state = "ash"
diff --git a/code/modules/organs/internal/species/vox.dm b/code/modules/organs/internal/species/vox.dm
index eb140778411a8..95a62b92f6d90 100644
--- a/code/modules/organs/internal/species/vox.dm
+++ b/code/modules/organs/internal/species/vox.dm
@@ -79,7 +79,8 @@
MATERIAL_BAUXITE = TRUE,
MATERIAL_COPPER = TRUE,
MATERIAL_ALUMINIUM = TRUE,
- MATERIAL_RUTILE = TRUE
+ MATERIAL_RUTILE = TRUE,
+ MATERIAL_BORAX = TRUE
)
var/list/stored_matter = list()
@@ -113,7 +114,7 @@
// Process it.
if(can_digest_matter[mat])
- owner.adjust_nutrition(max(1, Floor(digested/100)))
+ owner.adjust_nutrition(max(1, floor(digested/100)))
updated_stacks = TRUE
else if(can_process_matter[mat])
LAZYDISTINCTADD(check_materials, mat)
@@ -125,7 +126,7 @@
if(M && M.stack_type && stored_matter[mat] >= M.units_per_sheet)
// Remove as many sheets as possible from the gizzard.
- var/sheets = Floor(stored_matter[mat]/M.units_per_sheet)
+ var/sheets = floor(stored_matter[mat]/M.units_per_sheet)
stored_matter[mat] -= M.units_per_sheet * sheets
if(stored_matter[mat] <= 0)
stored_matter -= mat
@@ -180,18 +181,18 @@
/obj/item/organ/internal/voxstack/examine(mob/user)
. = ..()
-
var/user_vox = user.is_species(SPECIES_VOX)
if (istype(backup))
var/owner_viable = find_dead_player(ownerckey, TRUE)
if (user_vox)
- to_chat(user, SPAN_NOTICE("The integrity light on [src] blinks [owner_viable ? "rapidly. It can be implanted." : "slowly. It is dormant."]"))
+ . += SPAN_NOTICE("The integrity light on [src] blinks [owner_viable ? "rapidly. It can be implanted." : "slowly. It is dormant."]")
else
- to_chat(user, SPAN_NOTICE("A light on [src] blinks [owner_viable ? "rapidly" : "slowly"]."))
+ . += SPAN_NOTICE("A light on [src] blinks [owner_viable ? "rapidly" : "slowly"].")
else if (user_vox)
- to_chat(user, SPAN_NOTICE("The integrity light on [src] is off. It is empty and lifeless."))
+ . += SPAN_NOTICE("The integrity light on [src] is off. It is empty and lifeless.")
/obj/item/organ/internal/voxstack/emp_act()
+ SHOULD_CALL_PARENT(FALSE)
return
/obj/item/organ/internal/voxstack/getToxLoss()
diff --git a/code/modules/organs/internal/stomach.dm b/code/modules/organs/internal/stomach.dm
index 083971c07ccca..bb4430d443d51 100644
--- a/code/modules/organs/internal/stomach.dm
+++ b/code/modules/organs/internal/stomach.dm
@@ -40,7 +40,7 @@
return !isnull(get_devour_time(food))
/obj/item/organ/internal/stomach/proc/is_full(atom/movable/food)
- var/total = Floor(ingested.total_volume / 10)
+ var/total = floor(ingested.total_volume / 10)
for(var/a in contents + food)
if(ismob(a))
var/mob/M = a
diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm
index 7e7822ed1383b..761fac1cb2560 100644
--- a/code/modules/organs/organ.dm
+++ b/code/modules/organs/organ.dm
@@ -2,7 +2,7 @@ var/global/list/organ_cache = list()
/obj/item/organ
name = "organ"
- icon = 'icons/obj/surgery.dmi'
+ icon = 'icons/obj/organs.dmi'
germ_level = 0
w_class = ITEM_SIZE_TINY
default_action_type = /datum/action/item_action/organ
@@ -56,7 +56,7 @@ var/global/list/organ_cache = list()
given_dna = null
if(max_damage)
- min_broken_damage = Floor(max_damage / 2)
+ min_broken_damage = floor(max_damage / 2)
else
max_damage = min_broken_damage * 2
@@ -153,10 +153,10 @@ var/global/list/organ_cache = list()
/obj/item/organ/proc/show_decay_status(mob/user)
if(BP_IS_ROBOTIC(src))
if(status & ORGAN_DEAD)
- to_chat(user, SPAN_NOTICE("\The [src] looks completely spent."))
+ . += SPAN_NOTICE("[src] looks completely spent.")
else
if(status & ORGAN_DEAD)
- to_chat(user, SPAN_NOTICE("The decay has set into \the [src]."))
+ . += SPAN_NOTICE("The decay has set into [src].")
/obj/item/organ/proc/handle_germ_effects()
//** Handle the effects of infections
@@ -303,16 +303,16 @@ var/global/list/organ_cache = list()
set_dna(owner.dna)
return 1
-/obj/item/organ/attack(mob/target, mob/user)
+/obj/item/organ/use_before(mob/target, mob/user)
+ . = FALSE
+ if (status & ORGAN_ROBOTIC || !istype(target) || !istype(user) || (user != target && user.a_intent == I_HELP))
+ return FALSE
- if(status & ORGAN_ROBOTIC || !istype(target) || !istype(user) || (user != target && user.a_intent == I_HELP))
- return ..()
-
- if(alert("Do you really want to use this organ as food? It will be useless for anything else afterwards.",,"Ew, no.","Bon appetit!") == "Ew, no.")
+ if (alert("Do you really want to use this organ as food? It will be useless for anything else afterwards.",,"Ew, no.","Bon appetit!") == "Ew, no.")
to_chat(user, SPAN_NOTICE("You successfully repress your cannibalistic tendencies."))
- return
- if(!user.unEquip(src))
- return
+ return TRUE
+ if (!user.unEquip(src))
+ return TRUE
var/obj/item/reagent_containers/food/snacks/organ/O = new(get_turf(src))
O.SetName(name)
O.appearance = src
@@ -321,7 +321,8 @@ var/global/list/organ_cache = list()
transfer_fingerprints_to(O)
user.put_in_active_hand(O)
qdel(src)
- target.attackby(O, user)
+ O.resolve_attackby(target, user)
+ return TRUE
/obj/item/organ/proc/can_feel_pain()
return (!BP_IS_ROBOTIC(src) && (!species || !(species.species_flags & SPECIES_FLAG_NO_PAIN)))
diff --git a/code/modules/organs/pain.dm b/code/modules/organs/pain.dm
index b7866453a1c06..a3755278fb9f0 100644
--- a/code/modules/organs/pain.dm
+++ b/code/modules/organs/pain.dm
@@ -18,9 +18,9 @@
// Excessive halloss is horrible, just give them enough to make it visible.
if(!nohalloss && power)
if(affecting)
- affecting.add_pain(Ceil(power/2))
+ affecting.add_pain(ceil(power/2))
else
- adjustHalLoss(Ceil(power/2))
+ adjustHalLoss(ceil(power/2))
flash_pain(min(round(2*power)+55, 255))
diff --git a/code/modules/organs/robolimbs.dm b/code/modules/organs/robolimbs.dm
index 19eb534477b38..82eb1ac24dbfb 100644
--- a/code/modules/organs/robolimbs.dm
+++ b/code/modules/organs/robolimbs.dm
@@ -25,6 +25,8 @@ var/global/datum/robolimb/basic_robolimb
var/list/restricted_to = list()
var/list/applies_to_part = list() //TODO.
var/list/allowed_bodytypes = list(SPECIES_HUMAN, SPECIES_IPC, SPECIES_SKRELL, SPECIES_UNATHI)
+ var/has_screen = FALSE
+ var/display_text
/datum/robolimb/bishop
company = "Bishop"
@@ -50,6 +52,7 @@ var/global/datum/robolimb/basic_robolimb
icon = 'icons/mob/human_races/cyberlimbs/bishop/bishop_monitor.dmi'
allowed_bodytypes = list(SPECIES_IPC)
unavailable_at_fab = 1
+ has_screen = TRUE
/datum/robolimb/hephaestus
company = "Hephaestus Industries"
@@ -76,6 +79,7 @@ var/global/datum/robolimb/basic_robolimb
allowed_bodytypes = list(SPECIES_IPC)
can_eat = null
unavailable_at_fab = 1
+ has_screen = TRUE
/datum/robolimb/zenghu
company = "Zeng-Hu"
@@ -114,9 +118,10 @@ var/global/datum/robolimb/basic_robolimb
allowed_bodytypes = list(SPECIES_IPC)
can_eat = null
unavailable_at_fab = 1
+ has_screen = TRUE
/datum/robolimb/nanotrasen
- company = "NanoTrasen"
+ company = "Nanotrasen"
desc = "This limb is made from a cheap polymer."
icon = 'icons/mob/human_races/cyberlimbs/nanotrasen/nanotrasen_main.dmi'
@@ -144,6 +149,7 @@ var/global/datum/robolimb/basic_robolimb
allowed_bodytypes = list(SPECIES_IPC)
can_eat = null
unavailable_at_fab = 1
+ has_screen = TRUE
/datum/robolimb/morpheus
company = "Morpheus"
@@ -192,6 +198,7 @@ var/global/datum/robolimb/basic_robolimb
unavailable_at_fab = 1
has_eyes = FALSE
allowed_bodytypes = list(SPECIES_IPC)
+ has_screen = TRUE
/datum/robolimb/veymed
company = "Vey-Med"
@@ -219,6 +226,7 @@ var/global/datum/robolimb/basic_robolimb
applies_to_part = list(BP_HEAD)
unavailable_at_fab = 1
allowed_bodytypes = list(SPECIES_IPC)
+ has_screen = TRUE
/datum/robolimb/vox
company = "Arkmade"
diff --git a/code/modules/overmap/README.dm b/code/modules/overmap/README.dm
index 97d8272ccf4bf..ac824bb0ca701 100644
--- a/code/modules/overmap/README.dm
+++ b/code/modules/overmap/README.dm
@@ -8,18 +8,18 @@ Unless stated otherwise, you just need to place any of things below somewhere on
# How to make new sector
*************************************************************
0. Map whatever.
-1. Make /obj/effect/overmap/visitable/sector/[whatever]
+1. Make /obj/overmap/visitable/sector/[whatever]
If you want explorations shuttles be able to dock here, remember to set waypoints lists
-2. Put /obj/effect/overmap/visitable/sector/[whatever] on the map. Even if it's multiz, only one is needed, on any z.
+2. Put /obj/overmap/visitable/sector/[whatever] on the map. Even if it's multiz, only one is needed, on any z.
3. Done.
*************************************************************
# How to make new ship
*************************************************************
0. Map whatever.
-1. Make /obj/effect/overmap/visitable/ship/[whatever]
+1. Make /obj/overmap/visitable/ship/[whatever]
If you want explorations shuttles be able to dock here, remember to set waypoints lists
-2. Put /obj/effect/overmap/visitable/ship/[whatever] on the map. If it's multiz, only one is needed, on any z.
+2. Put /obj/overmap/visitable/ship/[whatever] on the map. If it's multiz, only one is needed, on any z.
3. Put Helm Console anywhere on the map.
4. Put Engines Control Console anywhere on the map.
5. Put some engines hooked up to gas supply anywhere on the map.
@@ -28,7 +28,7 @@ Unless stated otherwise, you just need to place any of things below somewhere on
*************************************************************
# Overmap object
*************************************************************
-/obj/effect/overmap/visitable
+/obj/overmap/visitable
### WHAT IT DOES
Lets overmap know this place should be represented on the map as a sector/ship.
If this zlevel (or any of connected ones for multiz) doesn't have this object, you won't be able to travel there by ovemap means.
@@ -84,4 +84,4 @@ Lets you control shuttles that can change destinations and visit other sectors/s
2. Define a /datum/shuttle/autodock/overmap for your shuttle. Same as normal shuttle, aside from 'range' var - how many squares on overmap it can travel on its own.
3. Place console anywhere on the ship/sector. Set shuttle_tag to shuttle's name.
4. Use. You can select destinations if you're in range (on same tile by defualt) on the map and sector has waypoints lists defined
-*/
\ No newline at end of file
+*/
diff --git a/code/modules/overmap/_defines.dm b/code/modules/overmap/_defines.dm
index a2a128e2237c5..47380ae015f32 100644
--- a/code/modules/overmap/_defines.dm
+++ b/code/modules/overmap/_defines.dm
@@ -8,6 +8,7 @@ var/global/list/map_sectors = list()
icon_state = "start"
requires_power = 0
base_turf = /turf/unsimulated/map
+ dynamic_lighting = 0
/turf/unsimulated/map
icon = 'icons/turf/space.dmi'
@@ -18,8 +19,8 @@ var/global/list/map_sectors = list()
opacity = 1
density = TRUE
-/turf/unsimulated/map/New()
- ..()
+/turf/unsimulated/map/Initialize(mapload, added_to_area_cache)
+ . = ..()
name = "[x]-[y]"
var/list/numbers = list()
@@ -44,7 +45,7 @@ var/global/list/map_sectors = list()
I.pixel_x = 5*i - 2
if(x == GLOB.using_map.overmap_size)
I.pixel_x = 5*i + 2
- overlays += I
+ AddOverlays(I)
//list used to track which zlevels are being 'moved' by the proc below
var/global/list/moving_levels = list()
diff --git a/code/modules/overmap/contacts/_contacts.dm b/code/modules/overmap/contacts/_contacts.dm
new file mode 100644
index 0000000000000..7a9304dbddc0d
--- /dev/null
+++ b/code/modules/overmap/contacts/_contacts.dm
@@ -0,0 +1,119 @@
+#define SENSOR_TIME_DELAY 0.2 SECONDS
+/datum/overmap_contact
+
+ /// Contact name
+ var/name = "Unknown"
+ /// Used to animate overmap effects
+ var/pinged = FALSE
+
+ /// Our list of images to cast to users
+ var/list/images = list()
+
+ /// Image overlay attached to the contact
+ var/image/marker
+ /// Radar image for sonar esque effect
+ var/image/radar
+
+ /// The sensor console holding this data
+ var/obj/machinery/shipsensors/owner
+
+ /// The actual overmap effect associated with this
+ var/obj/overmap/effect
+
+/datum/overmap_contact/New(obj/machinery/computer/ship/sensors/creator, obj/overmap/source)
+ // Update local tracking information
+ owner = creator
+ effect = source
+ name = effect.name
+ owner.contact_datums[effect] = src
+
+ marker = new(loc = effect)
+ marker.appearance = effect
+ marker.alpha = 0 // Marker fades in on detection
+
+ // Needs to be reset to be accurate in position and directions
+ marker.pixel_x = 0
+ marker.pixel_y = 0
+ marker.transform = null
+
+ images += marker
+
+ radar = image(loc = effect.loc, icon = 'icons/obj/overmap.dmi', icon_state = "sensor_range", pixel_x = effect.pixel_x, pixel_y = effect.pixel_y)
+ radar.tag = "radar"
+ radar.filters = filter(type="blur", size = 1)
+ radar.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+
+/datum/overmap_contact/proc/update_marker_icon(range = 0)
+ marker.icon_state = effect.icon_state
+
+ marker.ClearOverlays()
+
+ if (check_effect_shield())
+ var/image/shield_image = image(icon = 'icons/obj/overmap.dmi', icon_state = "shield")
+ shield_image.pixel_x = 8
+ marker.AddOverlays(shield_image)
+
+ if (range > 0)
+ radar.transform = null
+ radar.alpha = 255
+ radar.loc = effect.loc
+ radar.pixel_x = effect.pixel_x
+ radar.pixel_y = effect.pixel_y
+
+ images |= radar
+
+ var/matrix/M = matrix()
+ M.Scale(range*2.6)
+ animate(radar, transform = M, alpha = 0, time = (SENSOR_TIME_DELAY*range), 1, SINE_EASING)
+ addtimer(CALLBACK(src, PROC_REF(reset_radar), radar), (0.25 SECONDS *range+0.1))
+
+/datum/overmap_contact/proc/reset_radar(image/radar)
+ images -= radar
+
+/datum/overmap_contact/proc/show()
+ if (!owner)
+ return
+ var/list/showing = owner.linked?.navigation_viewers
+ if (length(showing))
+ for(var/weakref/W in showing)
+ var/mob/M = W.resolve()
+ if (istype(M) && M.client)
+ M.client.images |= images
+
+/datum/overmap_contact/proc/check_effect_shield()
+ var/obj/overmap/visitable/visitable_effect = effect
+ if (!visitable_effect || !istype(visitable_effect))
+ return FALSE
+ for (var/obj/machinery/power/shield_generator/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/shield_generator))
+ if (S.z in visitable_effect.map_z)
+ if (S.running == SHIELD_RUNNING)
+ return TRUE
+ return FALSE
+
+/datum/overmap_contact/proc/ping()
+ if (pinged)
+ return
+ pinged = TRUE
+ show()
+ animate(marker, alpha=255, 0.5 SECOND, 1, LINEAR_EASING)
+ addtimer(CALLBACK(src, PROC_REF(unping)), 1 SECOND)
+
+/datum/overmap_contact/proc/unping()
+ animate(marker, alpha=75, 2 SECOND, 1, LINEAR_EASING)
+ pinged = FALSE
+
+/datum/overmap_contact/Destroy()
+ if (owner)
+ var/list/showing = owner.linked?.navigation_viewers
+ if (length(showing))
+ for (var/weakref/W in showing)
+ var/mob/M = W.resolve()
+ if (istype(M) && M.client)
+ M.client.images -= images
+ if (effect)
+ owner.contact_datums -= effect
+ owner = null
+ effect = null
+ QDEL_NULL_LIST(images)
+ . = ..()
diff --git a/code/modules/overmap/contacts/contact_sensors.dm b/code/modules/overmap/contacts/contact_sensors.dm
new file mode 100644
index 0000000000000..f98299d90ec52
--- /dev/null
+++ b/code/modules/overmap/contacts/contact_sensors.dm
@@ -0,0 +1,227 @@
+#define SENSORS_DISTANCE_COEFFICIENT 6
+
+/proc/inaccurate_bearing(bearing, variability)
+ var/bearing_estimate = round(rand(bearing - variability, bearing + variability), 5)
+ if (bearing_estimate < 0)
+ bearing_estimate += 360
+ return bearing_estimate
+
+/obj/machinery/shipsensors
+ var/obj/overmap/visitable/ship/linked
+ var/list/linked_consoles = list() // To
+
+ var/list/objects_in_view = list() // Associative list of objects in view -> identification progress
+ var/list/memorized_objects = list() // Like objects in view, but for storing data
+ var/list/contact_datums = list()
+ var/list/trackers = list()
+
+/obj/machinery/shipsensors/Destroy()
+ objects_in_view.Cut()
+ memorized_objects.Cut()
+ trackers.Cut()
+
+ for (var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ qdel(record)
+ contact_datums.Cut()
+ . = ..()
+
+/obj/machinery/shipsensors/proc/link_ship(obj/overmap/visitable/ship/new_ship)
+ if (!isnull(new_ship) && !istype(new_ship))
+ return
+ if (linked == new_ship)
+ return
+
+ LAZYREMOVE(linked?.sensors, src)
+ linked = new_ship
+ if (linked && !contact_datums[linked])
+ LAZYADD(linked.sensors, src)
+ var/datum/overmap_contact/record = new(src, linked)
+ contact_datums[linked] = record
+ record.marker.alpha = 255
+
+/obj/machinery/shipsensors/proc/reveal_contacts(mob/user)
+ if (user && user.client)
+ for (var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if (record)
+ user.client.images |= record.marker
+
+/obj/machinery/shipsensors/proc/hide_contacts(mob/user)
+ if (user && user.client)
+ for (var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if (record)
+ user.client.images -= record.marker
+
+/obj/machinery/shipsensors/proc/alert_unknown_contact(contact_id, bearing, bearing_variability)
+ for (var/obj/machinery/computer/ship/sensors/console in linked_consoles)
+ console.alert_unknown_contact(contact_id, bearing, bearing_variability)
+
+/obj/machinery/shipsensors/proc/alert_contact_identified(contact_name, bearing)
+ for (var/obj/machinery/computer/ship/sensors/console in linked_consoles)
+ console.alert_contact_identified(contact_name, bearing)
+
+/obj/machinery/shipsensors/proc/alert_contact_lost(contact_name)
+ for (var/obj/machinery/computer/ship/sensors/console in linked_consoles)
+ console.alert_contact_lost(contact_name)
+
+/obj/machinery/shipsensors/Process()
+ ..()
+ if (!linked)
+ return
+
+ // Update our 'sensor range' (ie. overmap lighting)
+ var/sensor_range = 0
+ if (use_power)
+ sensor_range = round(range, 1)
+
+ // Update our own marker icon regardless of power or sensor connections.
+ var/datum/overmap_contact/self_record = contact_datums[linked]
+ self_record.update_marker_icon(sensor_range)
+ self_record.show()
+
+ if (!use_power || HAS_FLAGS(stat, MACHINE_STAT_NOPOWER) || MACHINE_IS_BROKEN(src))
+ // Turning off sensors is harmful
+ if (length(contact_datums) > 1 || length(objects_in_view))
+ for (var/key in memorized_objects)
+ memorized_objects[key] = max(memorized_objects[key] - 18, 0)
+
+ for (var/key in contact_datums)
+ var/datum/overmap_contact/record = contact_datums[key]
+ if (record.effect == linked)
+ continue
+ qdel(record) // Immediately cut records if power is lost.
+
+ objects_in_view.Cut()
+ return
+
+ // What can we see?
+ var/list/objects_in_current_view = list()
+
+ // Find all sectors with a tracker on their z-level. Only works on ships when they are in space.
+ for (var/obj/item/ship_tracker/tracker in trackers)
+ if (tracker.enabled)
+ var/obj/overmap/visitable/tracked_effect = map_sectors["[get_z(tracker)]"]
+ if (tracked_effect && istype(tracked_effect) && tracked_effect != linked && tracked_effect.requires_contact)
+ objects_in_current_view.Add(tracked_effect)
+ objects_in_view[tracked_effect] = 100
+
+ var/obj/overmap/overmap_obj = linked
+ if (istype(linked.loc, /obj/overmap/visitable))
+ overmap_obj = linked.loc
+
+ for (var/obj/overmap/contact in view(sensor_range, overmap_obj))
+ if (contact == linked)
+ continue
+ if (!contact.requires_contact) // Only some effects require contact for visibility.
+ continue
+
+ // Made like in proc/circlerangeturfs()
+ // Makes the view round
+ if (overmap_obj.z == contact.z)
+ var/dx = contact.x - overmap_obj.x
+ var/dy = contact.y - overmap_obj.y
+ var/rsq = sensor_range * (sensor_range+1)
+ if (dx**2 + dy**2 > rsq)
+ continue
+
+ objects_in_current_view.Add(contact)
+
+ if (contact.type in linked.known_ships)
+ objects_in_view[contact] = 100 // Instantly identify known ships.
+
+ if (contact.instant_contact) // Instantly identify the object in range.
+ objects_in_view[contact] = 100
+
+ else if (!(contact in objects_in_view))
+ objects_in_view[contact] = -1 // Replaced by 0 after notification
+
+ for (var/obj/overmap/contact in objects_in_view) //Update everything.
+ // Are we already aware of this object?
+ var/datum/overmap_contact/record = contact_datums[contact]
+
+ // Fade out and remove anything that is out of range.
+ if (QDELETED(contact) || !(contact in objects_in_current_view)) // Object has exited sensor range.
+ if (record)
+ record.marker.alpha = 200
+ animate(record.marker, alpha=0, time=1.3 SECONDS, 1, LINEAR_EASING)
+ QDEL_IN(record, 1.3 SECONDS) // Need to restart the search if you've lost contact with the object.
+ if (contact.scannable) // Scannable objects are the only ones that give off notifications to prevent spam
+ alert_contact_lost(record.name)
+ objects_in_view -= contact
+ continue
+
+ // Generate contact information for this overmap object.
+ if (!record) // Begin attempting to identify ship.
+ var/bearing = get_bearing(overmap_obj, contact)
+
+ // The chance of detection decreases with distance to the target ship.
+ if (contact.scannable)
+ if (objects_in_view[contact] < 0)
+
+ // Give the player an idea of where the ship is in relation to the ship
+ var/bearing_variability = round(300/sensor_strength, 5)
+ var/bearing_estimate = inaccurate_bearing(bearing, bearing_variability)
+ alert_unknown_contact(contact.unknown_id, bearing_estimate, bearing_variability)
+
+ objects_in_view[contact] = 0
+ if (contact in memorized_objects) // Restore the contact progress if there is anything to restore
+ objects_in_view[contact] = memorized_objects[contact]
+ else
+ var/progress = sensor_strength * contact.sensor_visibility * SENSORS_DISTANCE_COEFFICIENT
+ progress /= max(get_dist_euclidian(overmap_obj, contact), 0.5)
+ objects_in_view[contact] += round(progress / 100)
+
+ memorized_objects[contact] = min(objects_in_view[contact], 100)
+
+ if (objects_in_view[contact] >= 100) // Identification complete.
+ record = new /datum/overmap_contact(src, contact)
+ contact_datums[contact] = record
+
+ if (contact.scannable)
+ alert_contact_identified(record.name, bearing)
+
+ record.show()
+ animate(record.marker, alpha=255, 2 SECOND, 1, LINEAR_EASING)
+ continue
+
+ // Update identification information for this record.
+ record.update_marker_icon()
+
+ var/time_delay = max((SENSOR_TIME_DELAY * get_dist_euclidian(overmap_obj, contact)),1)
+ addtimer(CALLBACK(record, PROC_REF(ping)), time_delay)
+
+/obj/machinery/shipsensors/multitool_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/obj/item/device/multitool/mtool = tool
+ var/obj/item/ship_tracker/tracker = mtool.get_buffer()
+ if(!istype(tracker))
+ return
+ if(tracker in trackers)
+ trackers -= tracker
+ GLOB.destroyed_event.unregister(tracker, src, PROC_REF(remove_tracker))
+ to_chat(user, SPAN_NOTICE("You unlink the tracker in [mtool]'s buffer from [src]"))
+ return
+ trackers += tracker
+ GLOB.destroyed_event.register(tracker, src, PROC_REF(remove_tracker))
+ to_chat(user, SPAN_NOTICE("You link the tracker in [mtool]'s buffer to [src]"))
+
+/obj/machinery/shipsensors/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/damage = get_damage_value()
+ if(!damage)
+ USE_FEEDBACK_NOTHING_TO_REPAIR(user)
+ return
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_REPAIR_START(user)
+ if(!tool.use_as_tool(src, user, (max(0.5, damage / 50)) SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ USE_FEEDBACK_REPAIR_FINISH(user)
+ revive_health()
+
+/obj/machinery/shipsensors/proc/remove_tracker(obj/item/ship_tracker/tracker)
+ trackers -= tracker
+
+#undef SENSORS_DISTANCE_COEFFICIENT
diff --git a/code/modules/overmap/contacts/tracker.dm b/code/modules/overmap/contacts/tracker.dm
new file mode 100644
index 0000000000000..f407c59f1009f
--- /dev/null
+++ b/code/modules/overmap/contacts/tracker.dm
@@ -0,0 +1,27 @@
+/obj/item/ship_tracker
+ name = "long range tracker"
+ desc = "A complex device that transmits conspicuous signals, easily locked onto by modern sensors hardware."
+ icon = 'icons/obj/machines/ship_tracker.dmi'
+ icon_state = "disabled"
+ w_class = ITEM_SIZE_SMALL
+
+ origin_tech = list(TECH_MATERIAL = 3, TECH_DATA = 2)
+ matter = list(MATERIAL_STEEL = 30, MATERIAL_GLASS = 10, MATERIAL_GOLD = 15)
+ var/enabled = FALSE
+
+/obj/item/ship_tracker/Initialize()
+ . = ..()
+ set_extension(src, /datum/extension/interactive/multitool/store)
+
+/obj/item/ship_tracker/attack_self(mob/user)
+ enabled = !enabled
+ to_chat(user, SPAN_NOTICE("You [enabled ? "enable" : "disable"] \the [src]"))
+ update_icon()
+
+/obj/item/ship_tracker/on_update_icon()
+ . = ..()
+ icon_state = enabled ? "enabled" : "disabled"
+
+/obj/item/ship_tracker/examine(mob/user)
+ . = ..()
+ . += SPAN_NOTICE("It appears to be [enabled ? "enabled" : "disabled"]")
diff --git a/code/modules/overmap/disperser/disperser.dm b/code/modules/overmap/disperser/disperser.dm
index 6fa76f711a6c0..e63530fea43ec 100644
--- a/code/modules/overmap/disperser/disperser.dm
+++ b/code/modules/overmap/disperser/disperser.dm
@@ -2,7 +2,7 @@
//This is just basic construction and deconstruction and the like
/obj/machinery/disperser
- icon = 'icons/obj/disperser.dmi'
+ icon = 'icons/obj/machines/disperser.dmi'
density = TRUE
anchored = TRUE
construct_state = /singleton/machine_construction/default/panel_closed
@@ -10,18 +10,20 @@
/obj/machinery/disperser/examine(mob/user)
. = ..()
if(panel_open)
- to_chat(user, "The maintenance panel is open.")
+ . += SPAN_NOTICE("The maintenance panel is open.")
-/obj/machinery/disperser/attackby(obj/item/I, mob/user)
- if(isWrench(I))
- if(panel_open)
- user.visible_message(SPAN_NOTICE("\The [user] rotates \the [src] with \the [I]."), SPAN_NOTICE("You rotate \the [src] with \the [I]."))
- set_dir(turn(dir, 90))
- playsound(src, 'sound/items/jaws_pry.ogg', 50, 1)
- else
- to_chat(user,SPAN_NOTICE("The maintenance panel must be screwed open for this!"))
- else
- return ..()
+/obj/machinery/disperser/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!panel_open)
+ to_chat(user,SPAN_NOTICE("The maintenance panel must be screwed open for this!"))
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ user.visible_message(
+ SPAN_NOTICE("[user] rotates [src] with [tool]."),
+ SPAN_NOTICE("You rotate [src] with [tool].")
+ )
+ set_dir(turn(dir, 90))
/obj/machinery/disperser/front
name = "obstruction field disperser beam generator"
diff --git a/code/modules/overmap/disperser/disperser_charge.dm b/code/modules/overmap/disperser/disperser_charge.dm
index eea1c28a03ba6..0333663dce106 100644
--- a/code/modules/overmap/disperser/disperser_charge.dm
+++ b/code/modules/overmap/disperser/disperser_charge.dm
@@ -10,25 +10,25 @@
CRASH("OFD charge firing logic not set!")
/obj/structure/ship_munition/disperser_charge/fire
- name = "FR1-ENFER charge"
+ name = "\improper FR1-ENFER charge"
color = "#b95a00"
desc = "A charge to power the obstruction field disperser with. It looks impossibly round and shiny. This charge is designed to release a localised fire on impact."
chargetype = OVERMAP_WEAKNESS_FIRE
chargedesc = "ENFER"
/obj/structure/ship_munition/disperser_charge/fire/fire(turf/target, strength, range)
- for(var/turf/T in range(range, target))
- var/obj/effect/fake_fire/bluespace/disperserf = new(T)
+ for(var/turf/T as anything in RANGE_TURFS(target, range))
+ var/obj/fake_fire/bluespace/disperserf = new(T)
disperserf.lifetime = strength * 20
-/obj/effect/fake_fire/bluespace
+/obj/fake_fire/bluespace
name = "bluespace fire"
color = COLOR_BLUE
pressure = 1500
/obj/structure/ship_munition/disperser_charge/emp
- name = "EM2-QUASAR charge"
+ name = "\improper EM2-QUASAR charge"
color = "#6a97b0"
desc = "A charge to power the obstruction field disperser with. It looks impossibly round and shiny. This charge is designed to release a blast of electromagnetic pulse on impact."
chargetype = OVERMAP_WEAKNESS_EMP
@@ -38,7 +38,7 @@
empulse(target, strength * range / 3, strength * range)
/obj/structure/ship_munition/disperser_charge/mining
- name = "MN3-BERGBAU charge"
+ name = "\improper MN3-BERGBAU charge"
color = "#cfcf55"
desc = "A charge to power the obstruction field disperser with. It looks impossibly round and shiny. This charge is designed to mine ores on impact."
chargetype = OVERMAP_WEAKNESS_MINING
@@ -54,7 +54,7 @@
L.ex_act(EX_ACT_LIGHT) //no artif- I mean idiot/unfortunate bystanders survive this... much
/obj/structure/ship_munition/disperser_charge/explosive
- name = "XP4-INDARRA charge"
+ name = "\improper XP4-INDARRA charge"
color = "#aa5f61"
desc = "A charge to power the obstruction field disperser with. It looks impossibly round and shiny. This charge is designed to explode on impact."
chargetype = OVERMAP_WEAKNESS_EXPLOSIVE
diff --git a/code/modules/overmap/disperser/disperser_circuit.dm b/code/modules/overmap/disperser/disperser_circuit.dm
index 53b4e3d007120..bd6614a72e371 100644
--- a/code/modules/overmap/disperser/disperser_circuit.dm
+++ b/code/modules/overmap/disperser/disperser_circuit.dm
@@ -1,10 +1,10 @@
/obj/item/stock_parts/circuitboard/disperser
- name = T_BOARD("obstruction field disperser control")
+ name = "circuit board (obstruction field disperser control)"
build_path = /obj/machinery/computer/ship/disperser
origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2)
/obj/item/stock_parts/circuitboard/disperserfront
- name = T_BOARD("obstruction field disperser beam generator")
+ name = "circuit board (obstruction field disperser beam generator)"
build_path = /obj/machinery/disperser/front
board_type = "machine"
origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2)
@@ -13,7 +13,7 @@
)
/obj/item/stock_parts/circuitboard/dispersermiddle
- name = T_BOARD("obstruction field disperser fusor")
+ name = "circuit board (obstruction field disperser fusor)"
build_path = /obj/machinery/disperser/middle
board_type = "machine"
origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2)
@@ -22,10 +22,10 @@
)
/obj/item/stock_parts/circuitboard/disperserback
- name = T_BOARD("obstruction field disperser material deconstructor")
+ name = "circuit board (obstruction field disperser material deconstructor)"
build_path = /obj/machinery/disperser/back
board_type = "machine"
origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2)
req_components = list (
/obj/item/stock_parts/capacitor/super = 5
- )
\ No newline at end of file
+ )
diff --git a/code/modules/overmap/disperser/disperser_console.dm b/code/modules/overmap/disperser/disperser_console.dm
index 9b0d18327cba7..878e4fdce5fe3 100644
--- a/code/modules/overmap/disperser/disperser_console.dm
+++ b/code/modules/overmap/disperser/disperser_console.dm
@@ -1,12 +1,11 @@
-//Amazing disperser from Bxil(tm). Some icons, sounds, and some code shamelessly stolen from ParadiseSS13.
-
/obj/machinery/computer/ship/disperser
name = "obstruction field disperser control"
- icon = 'icons/obj/computer.dmi'
+ icon = 'icons/obj/machines/computer.dmi'
icon_state = "computer"
core_skill = SKILL_PILOT
- var/skill_offset = SKILL_ADEPT - 1 //After which skill level it starts to matter. -1, because we have to index from zero
+ /// After which skill level it starts to matter. -1, because we have to index from zero
+ var/skill_offset = SKILL_TRAINED - 1
icon_keyboard = "rd_key"
icon_screen = "teleport"
@@ -14,18 +13,32 @@
var/obj/machinery/disperser/front/front
var/obj/machinery/disperser/middle/middle
var/obj/machinery/disperser/back/back
- var/const/link_range = 10 //How far can the above stuff be maximum before we start complaining
+ /// How far can the disperser's front/mid/back sections be apart from eachother before we start complaining
+ var/const/link_range = 10
var/overmapdir = 0
+ /// Coordinates for target
+ var/tx = 0
+ var/ty = 0
+ var/tz = 0
+
+ var/accuracy = 0
+
+ /// What it is
+ var/list/calibration
+ /// what is should be
+ var/list/calexpected
- var/caldigit = 4 //number of digits that needs calibration
- var/list/calibration //what it is
- var/list/calexpected //what is should be
+ /// Range of the explosion
+ var/range = 1
+ /// Strength of the explosion
+ var/strength = 1
+ /// Round time where the next shot can start from
+ var/next_shot = 0
+ /// Time to wait between safe shots in deciseconds
+ var/const/coolinterval = 2 MINUTES
- var/range = 1 //range of the explosion
- var/strength = 1 //strength of the explosion
- var/next_shot = 0 //round time where the next shot can start from
- var/const/coolinterval = 2 MINUTES //time to wait between safe shots in deciseconds
+ var/const/cal_count = 4
/obj/machinery/computer/ship/disperser/Initialize()
. = ..()
@@ -36,11 +49,15 @@
release_links()
. = ..()
+/**
+ * Used to handle linking of OFD parts.
+ * If the parts are too far apart from eachother, it won't work.
+ */
/obj/machinery/computer/ship/disperser/proc/link_parts()
if(is_valid_setup())
return TRUE
- for(var/obj/machinery/disperser/front/F in SSmachines.machinery)
+ for(var/obj/machinery/disperser/front/F as anything in SSmachines.get_machinery_of_type(/obj/machinery/disperser/front))
if(get_dist(src, F) >= link_range)
continue
var/backwards = turn(F.dir, 180)
@@ -54,12 +71,15 @@
middle = M
back = B
if(is_valid_setup())
- GLOB.destroyed_event.register(F, src, .proc/release_links)
- GLOB.destroyed_event.register(M, src, .proc/release_links)
- GLOB.destroyed_event.register(B, src, .proc/release_links)
+ GLOB.destroyed_event.register(F, src, PROC_REF(release_links))
+ GLOB.destroyed_event.register(M, src, PROC_REF(release_links))
+ GLOB.destroyed_event.register(B, src, PROC_REF(release_links))
return TRUE
return FALSE
+/**
+ * Checks order of parts.
+ */
/obj/machinery/computer/ship/disperser/proc/is_valid_setup()
if(front && middle && back)
var/everything_in_range = (get_dist(src, front) < link_range) && (get_dist(src, middle) < link_range) && (get_dist(src, back) < link_range)
@@ -67,17 +87,23 @@
return everything_in_order && everything_in_range
return FALSE
+/**
+ * Used for destroying links when unlinked.
+ */
/obj/machinery/computer/ship/disperser/proc/release_links()
- GLOB.destroyed_event.unregister(front, src, .proc/release_links)
- GLOB.destroyed_event.unregister(middle, src, .proc/release_links)
- GLOB.destroyed_event.unregister(back, src, .proc/release_links)
+ GLOB.destroyed_event.unregister(front, src, PROC_REF(release_links))
+ GLOB.destroyed_event.unregister(middle, src, PROC_REF(release_links))
+ GLOB.destroyed_event.unregister(back, src, PROC_REF(release_links))
front = null
middle = null
back = null
+/**
+ * Calculating calibration.
+ */
/obj/machinery/computer/ship/disperser/proc/get_calibration()
- var/list/calresult[caldigit]
- for(var/i = 1 to caldigit)
+ var/list/calresult = new(cal_count)
+ for(var/i = 1 to cal_count)
if(calibration[i] == calexpected[i])
calresult[i] = 2
else if(calibration[i] in calexpected)
@@ -86,18 +112,26 @@
calresult[i] = 0
return calresult
+/**
+ * Resetting calibration.
+ */
/obj/machinery/computer/ship/disperser/proc/reset_calibration()
- calexpected = new /list(caldigit)
- calibration = new /list(caldigit)
- for(var/i = 1 to caldigit)
- calexpected[i] = rand(0,9)
+ calexpected = new(cal_count)
+ calibration = new(cal_count)
+ for(var/i = 1 to cal_count)
+ calexpected[i] = rand(0, 9)
calibration[i] = 0
+/**
+ * Calculating accuracy from calibration.
+ */
/obj/machinery/computer/ship/disperser/proc/cal_accuracy()
var/top = 0
- var/divisor = caldigit * 2 //maximum possible value, aka 100% accuracy
+ // Maximum possible value, aka 100% accuracy
+ var/divisor = cal_count * 2
for(var/i in get_calibration())
top += i
+ accuracy = round(top * 100 / divisor)
return round(top * 100 / divisor)
/obj/machinery/computer/ship/disperser/proc/get_next_shot_seconds()
@@ -107,21 +141,13 @@
return get_next_shot_seconds() * 1000 / coolinterval
/obj/machinery/computer/ship/disperser/proc/get_charge_type()
- var/obj/structure/ship_munition/disperser_charge/B = locate() in get_turf(back)
- if(B)
- return B.chargetype
- var/obj/structure/closet/C = locate() in get_turf(back)
- if(C)
- return OVERMAP_WEAKNESS_DROPPOD
+ var/obj/structure/ship_munition/disperser_charge/charge = get_charge()
+ if (charge)
+ return charge.chargetype
return OVERMAP_WEAKNESS_NONE
/obj/machinery/computer/ship/disperser/proc/get_charge()
- var/obj/structure/ship_munition/disperser_charge/B = locate() in get_turf(back)
- if(B)
- return B
-
- var/obj/structure/closet/C = locate() in get_turf(back)
- return C
+ return locate(/obj/structure/ship_munition/disperser_charge) in get_turf(back)
/obj/machinery/computer/ship/disperser/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE)
if(!linked)
@@ -135,6 +161,10 @@
else
data["calibration"] = calibration
data["overmapdir"] = overmapdir
+ data["t_x"] = tx
+ data["t_y"] = ty
+ data["t_z"] = tz
+ data["coordinates"] = tx || ty || tz
data["cal_accuracy"] = cal_accuracy()
data["strength"] = strength
data["range"] = range
@@ -146,8 +176,6 @@
switch(get_charge_type())
if(OVERMAP_WEAKNESS_NONE)
charge = "[SPAN_BOLD("ERROR")]: No valid charge detected."
- if(OVERMAP_WEAKNESS_DROPPOD)
- charge = "HERMES"
else
var/obj/structure/ship_munition/disperser_charge/B = get_charge()
charge = B.chargedesc
@@ -172,14 +200,40 @@
overmapdir = sanitize_integer(text2num(href_list["choose"]), 0, 9, 0)
reset_calibration()
+ if (href_list["setx"])
+ var/newx = input("Input new target x coordinate", "Coordinate input", tx) as num|null
+ if(!CanInteract(user,state))
+ return
+ if (newx)
+ tx = clamp(newx, 1, world.maxx - TRANSITIONEDGE)
+
+ if (href_list["sety"])
+ var/newy = input("Input new target y coordinate", "Coordinate input", ty) as num|null
+ if(!CanInteract(user,state))
+ return
+ if (newy)
+ ty = clamp(newy, 1, world.maxy - TRANSITIONEDGE)
+
+ if (href_list["setz"])
+ var/newz = input("Input new target z coordinate", "Coordinate input", tz) as num|null
+ if(!CanInteract(user,state))
+ return
+ if (newz)
+ tz = clamp(newz, 1, world.maxz)
+
+ if (href_list["reset"])
+ tx = 0
+ ty = 0
+ tz = 0
+
if(href_list["calibration"])
var/input = input("0-9", "disperser calibration", 0) as num|null
- if(!isnull(input)) //can be zero so we explicitly check for null
- var/calnum = sanitize_integer(text2num(href_list["calibration"]), 0, caldigit)//sanitiiiiize
- calibration[calnum + 1] = sanitize_integer(input, 0, 9, 0)//must add 1 because nanoui indexes from 0
+ if(!isnull(input))
+ var/calnum = sanitize_integer(text2num(href_list["calibration"]), 0, cal_count - 1)
+ calibration[calnum + 1] = sanitize_integer(input, 0, 9, 0)
if(href_list["skill_calibration"])
- for(var/i = 1 to min(caldigit, user.get_skill_value(core_skill) - skill_offset))
+ for(var/i = 1 to clamp(user.get_skill_value(core_skill) - skill_offset, 1, cal_count))
calibration[i] = calexpected[i]
if(href_list["strength"])
diff --git a/code/modules/overmap/disperser/disperser_fire.dm b/code/modules/overmap/disperser/disperser_fire.dm
index 9481eebdd4a4d..606060f8cbe0b 100644
--- a/code/modules/overmap/disperser/disperser_fire.dm
+++ b/code/modules/overmap/disperser/disperser_fire.dm
@@ -1,19 +1,28 @@
-#define QUICK_TO_STANDING 5 //How much time after standing still after running we will use STANDING_FALL_PROB instead of RUNNING_FALL_PROB
-#define DELIBERATE_TO_STANDING 3 //How much time after standing still after walking we will use STANDING_FALL_PROB instead of WALKING_FALL_PROB
+// How much time after standing still after running we will use STANDING_FALL_PROB instead of RUNNING_FALL_PROB
+#define QUICK_TO_STANDING 5
+// How much time after standing still after walking we will use STANDING_FALL_PROB instead of WALKING_FALL_PROB
+#define DELIBERATE_TO_STANDING 3
#define RUNNING_FALL_PROB 75
#define WALKING_FALL_PROB 50
#define STANDING_FALL_PROB 20
+/**
+ * Main proc to fire shells.
+ * Returns FALSE if power isn't present, charge isn't present, or fired under cooldown.
+ * Blows up if there's an obstruction.
+ * Also handles stumbling during firing of the OFD on the firing vessel.
+ * Finally checks type of target to fire the specific proc (event, ship, empty).
+ */
/obj/machinery/computer/ship/disperser/proc/fire(mob/user)
log_and_message_admins("attempted to launch a disperser beam.")
if(!link_parts())
- return FALSE //no disperser, no service
+ return FALSE
if(!front.powered() || !middle.powered() || !back.powered())
- return FALSE //no power, no boom boom
+ return FALSE
var/chargetype = get_charge_type()
if(chargetype <= 0)
- return FALSE //no dear, you cannot fire the captain out of a cannon... unless you put him in a box of course
+ return FALSE
var/atom/movable/atomcharge = get_charge()
@@ -37,10 +46,8 @@
else
A.ex_act(EX_ACT_DEVASTATING)
- var/list/relevant_z = GetConnectedZlevels(start.z)
for(var/mob/M in GLOB.player_list)
- var/turf/T = get_turf(M)
- if(!T || !(T.z in relevant_z))
+ if(!AreConnectedZLevels(get_z(M), get_z(start)))
continue
shake_camera(M, 25)
if(!isdeaf(M))
@@ -50,11 +57,11 @@
var/shouldstumble = FALSE
var/sincelastmove = world.time - M.l_move_time
- if(sincelastmove > QUICK_TO_STANDING SECONDS) //We are standing still
+ if(sincelastmove > QUICK_TO_STANDING SECONDS)
shouldstumble = prob(STANDING_FALL_PROB)
- else if(sincelastmove > DELIBERATE_TO_STANDING) //We are either standing still after running or standing still after walking/creeping
+ else if(sincelastmove > DELIBERATE_TO_STANDING)
shouldstumble = MOVING_QUICKLY(M) ? prob(RUNNING_FALL_PROB) : prob(STANDING_FALL_PROB)
- else //We are either currently running or currently walking/creeping
+ else
shouldstumble = MOVING_QUICKLY(M) ? prob(RUNNING_FALL_PROB) : prob(WALKING_FALL_PROB)
if(shouldstumble)
@@ -62,94 +69,93 @@
M.AdjustStunned(2)
M.AdjustWeakened(2)
- if(front) //Meanwhile front might have exploded
- front.layer = ABOVE_OBJ_LAYER //So the beam goes below us. Looks a lot better
+ if(front)
+ front.layer = ABOVE_OBJ_LAYER
playsound(start, 'sound/machines/disperser_fire.ogg', 100, 1)
handle_beam(start, direction)
handle_overbeam()
- if (chargetype != OVERMAP_WEAKNESS_DROPPOD)
- qdel(atomcharge)
+ qdel(atomcharge)
- //Some moron disregarded the cooldown warning. Let's blow in their face.
if(prob(cool_failchance()))
explosion(middle, rand(6, 9))
next_shot = coolinterval + world.time
- //Success, but we missed.
- if(prob(100 - cal_accuracy()))
- if(chargetype == OVERMAP_WEAKNESS_DROPPOD)
- atomcharge.forceMove(locate(rand(1,world.maxx),rand(1,world.maxy), GLOB.using_map.get_empty_zlevel())) //Remove it in case it's a droppod.
- return TRUE
-
reset_calibration()
var/turf/overmaptarget = get_step(linked, overmapdir)
var/list/candidates = list()
- //Prioritize events. Thus you can hide in meteor showers in exchange for protection from the disperser.
- for(var/obj/effect/overmap/event/O in overmaptarget)
+ // Prioritize events. Thus you can hide in meteor showers in exchange for protection from the disperser.
+ for(var/obj/overmap/event/O in overmaptarget)
candidates += O
- //Next we see if there are any ships around. Logically they are between us and the sector if one exists.
+ // Next we see if there are any ships around. Logically they are between us and the sector if one exists.
if(!length(candidates))
- for(var/obj/effect/overmap/visitable/ship/S in overmaptarget)
+ for(var/obj/overmap/visitable/ship/S in overmaptarget)
if(S == linked)
- continue //Why are you shooting yourself?
+ /// Why are you shooting yourself?
+ continue
candidates += S
- //No events, no ships, the last thing to check is a sector.
+ // No events, no ships, the last thing to check is a sector.
if(!length(candidates))
- for(var/obj/effect/overmap/O in overmaptarget)
+ for(var/obj/overmap/O in overmaptarget)
if(O == linked)
- continue //Why are you shooting yourself?
+ // Why are you shooting yourself?
+ continue
candidates += O
- //Way to waste a charge
+ // Way to waste a charge
if(!length(candidates))
return TRUE
- var/obj/effect/overmap/finaltarget = pick(candidates)
+ var/obj/overmap/finaltarget = pick(candidates)
log_and_message_admins("A type [chargetype] disperser beam was launched at [finaltarget].", location=finaltarget)
- //Deletion of the overmap effect and the actual event trigger. Bye bye pesky meteors.
- if(istype(finaltarget, /obj/effect/overmap/event))
+ // Deletion of the overmap effect and the actual event trigger. Bye bye pesky meteors.
+ if(istype(finaltarget, /obj/overmap/event))
fire_at_event(finaltarget, chargetype)
qdel(atomcharge)
- //After this point ships act basically as sectors so we stop taking care of differences
+ // After this point ships act basically as sectors so we stop taking care of differences
else
fire_at_sector(finaltarget, atomcharge, chargetype)
return TRUE
-/obj/machinery/computer/ship/disperser/proc/fire_at_event(obj/effect/overmap/event/finaltarget, chargetype)
+/**
+ * Handles firing at events (storms, meteors, etc).
+ * If the shot type and the event type's weakness match, it kills it.
+ */
+/obj/machinery/computer/ship/disperser/proc/fire_at_event(obj/overmap/event/finaltarget, chargetype)
if(chargetype & finaltarget.weaknesses)
var/turf/T = finaltarget.loc
qdel(finaltarget)
overmap_event_handler.update_hazards(T)
-/obj/machinery/computer/ship/disperser/proc/fire_at_sector(obj/effect/overmap/visitable/finaltarget, obj/structure/ship_munition/disperser_charge/charge, chargetype)
+/**
+ * Handles firing at sector, empty or with a map.
+ * Has code to handle manual coordinate-based aiming, and random-aim.
+ * If coordinates are put in, gun tries to hit at the inputted coordinates, else chooses a random area.
+ */
+/obj/machinery/computer/ship/disperser/proc/fire_at_sector(obj/overmap/visitable/finaltarget, obj/structure/ship_munition/disperser_charge/charge, chargetype)
var/list/targetareas = finaltarget.get_areas()
targetareas -= locate(/area/space)
- if (!length(targetareas))
- qdel(charge)
- return
+
var/area/finalarea = pick(targetareas)
- var/turf/targetturf = pick_area_turf(finalarea.type, list(/proc/is_not_space_turf))
-
- log_and_message_admins("Disperser beam hit sector at [get_area(targetturf)].", location=targetturf)
- if(chargetype == OVERMAP_WEAKNESS_DROPPOD)
- if(targetturf.density)
- targetturf.ex_act(EX_ACT_DEVASTATING)
- for(var/atom/A in targetturf)
- A.ex_act(EX_ACT_LIGHT)
- charge.forceMove(targetturf)
- //The disperser is not a taxi
- for(var/mob/living/L in charge)
- to_chat(L, SPAN_DANGER("Your body shakes violently, immense and agonising forces tearing it apart."))
- L.forceMove(targetturf)
- L.ex_act(EX_ACT_DEVASTATING)
- else
- charge.fire(targetturf, strength, range)
- qdel(charge)
+ var/turf/areaturf = pick_area_turf(finalarea.type)
+ var/turf/targetturf
+
+ if (tx > 0 && ty > 0)
+ if (tz == 0 || !AreConnectedZLevels(tz, areaturf.z))
+ tz = areaturf.z
+
+ targetturf = locate(rand(tx + (100 - accuracy), tx - (100 - accuracy)), rand(ty + (100 - accuracy), ty - (100 - accuracy)), tz)
+
+ else if (tx == 0 && ty == 0 && tz == 0)
+ targetturf = areaturf
+
+ log_and_message_admins("Disperser beam hit sector at [targetturf.loc.name].", location=targetturf)
+ charge.fire(targetturf, strength, range)
+ qdel(charge)
/obj/machinery/computer/ship/disperser/proc/handle_beam(turf/start, direction)
set waitfor = FALSE
diff --git a/code/modules/overmap/events/event.dm b/code/modules/overmap/events/event.dm
index cf28edf0c8fe8..e93a6833f1c66 100644
--- a/code/modules/overmap/events/event.dm
+++ b/code/modules/overmap/events/event.dm
@@ -12,7 +12,7 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
/singleton/overmap_event_handler/proc/create_events(z_level, overmap_size, number_of_events)
// Acquire the list of not-yet utilized overmap turfs on this Z-level
var/list/candidate_turfs = block(locate(OVERMAP_EDGE, OVERMAP_EDGE, z_level),locate(overmap_size - OVERMAP_EDGE, overmap_size - OVERMAP_EDGE,z_level))
- candidate_turfs = where(candidate_turfs, /proc/can_not_locate, /obj/effect/overmap/visitable)
+ candidate_turfs = where(candidate_turfs, GLOBAL_PROC_REF(can_not_locate), /obj/overmap/visitable)
for(var/i = 1 to number_of_events)
if(!length(candidate_turfs))
@@ -57,13 +57,13 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
if(continuous)
fitting_turfs = origin_turf.CardinalTurfs(FALSE)
else
- fitting_turfs = trange(range, origin_turf)
+ fitting_turfs = RANGE_TURFS(origin_turf, range)
fitting_turfs = shuffle(fitting_turfs)
for(var/turf/T in fitting_turfs)
if(T in candidate_turfs)
return T
-/singleton/overmap_event_handler/proc/start_hazard(obj/effect/overmap/visitable/ship/ship, obj/effect/overmap/event/hazard)//make these accept both hazards or events
+/singleton/overmap_event_handler/proc/start_hazard(obj/overmap/visitable/ship/ship, obj/overmap/event/hazard)//make these accept both hazards or events
if(!(ship in ship_events))
ship_events += ship
@@ -79,7 +79,7 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
E.vars["victim"] = ship
LAZYADD(ship_events[ship], E)
-/singleton/overmap_event_handler/proc/stop_hazard(obj/effect/overmap/visitable/ship/ship, obj/effect/overmap/event/hazard)
+/singleton/overmap_event_handler/proc/stop_hazard(obj/overmap/visitable/ship/ship, obj/overmap/event/hazard)
for(var/event_type in hazard.events)
var/datum/event/E = is_event_active(ship,event_type,hazard.difficulty)
if(E)
@@ -92,22 +92,22 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
if(E.type == event_type && E.severity == severity)
return E
-/singleton/overmap_event_handler/proc/on_turf_entered(turf/new_loc, obj/effect/overmap/visitable/ship/ship, old_loc)
+/singleton/overmap_event_handler/proc/on_turf_entered(turf/new_loc, obj/overmap/visitable/ship/ship, old_loc)
if(!istype(ship))
return
if(new_loc == old_loc)
return
- for(var/obj/effect/overmap/event/E in hazard_by_turf[new_loc])
+ for(var/obj/overmap/event/E in hazard_by_turf[new_loc])
start_hazard(ship, E)
-/singleton/overmap_event_handler/proc/on_turf_exited(turf/old_loc, obj/effect/overmap/visitable/ship/ship, new_loc)
+/singleton/overmap_event_handler/proc/on_turf_exited(turf/old_loc, obj/overmap/visitable/ship/ship, new_loc)
if(!istype(ship))
return
if(new_loc == old_loc)
return
- for(var/obj/effect/overmap/event/E in hazard_by_turf[old_loc])
+ for(var/obj/overmap/event/E in hazard_by_turf[old_loc])
if(is_event_included(hazard_by_turf[new_loc],E))
continue
stop_hazard(ship,E)
@@ -117,38 +117,38 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
return
var/list/active_hazards = list()
- for(var/obj/effect/overmap/event/E in T)
+ for(var/obj/overmap/event/E in T)
if(is_event_included(active_hazards, E, TRUE))
continue
active_hazards += E
if(!length(active_hazards))
hazard_by_turf -= T
- GLOB.entered_event.unregister(T, src, /singleton/overmap_event_handler/proc/on_turf_entered)
- GLOB.exited_event.unregister(T, src, /singleton/overmap_event_handler/proc/on_turf_exited)
+ GLOB.entered_event.unregister(T, src, TYPE_PROC_REF(/singleton/overmap_event_handler, on_turf_entered))
+ GLOB.exited_event.unregister(T, src, TYPE_PROC_REF(/singleton/overmap_event_handler, on_turf_exited))
else
hazard_by_turf |= T
hazard_by_turf[T] = active_hazards
- GLOB.entered_event.register(T, src,/singleton/overmap_event_handler/proc/on_turf_entered)
- GLOB.exited_event.register(T, src, /singleton/overmap_event_handler/proc/on_turf_exited)
+ GLOB.entered_event.register(T, src, TYPE_PROC_REF(/singleton/overmap_event_handler, on_turf_entered))
+ GLOB.exited_event.register(T, src, TYPE_PROC_REF(/singleton/overmap_event_handler, on_turf_exited))
- for(var/obj/effect/overmap/visitable/ship/ship in T)
+ for(var/obj/overmap/visitable/ship/ship in T)
for(var/datum/event/E in ship_events[ship])
if(is_event_in_turf(E,T))
continue
E.kill()
LAZYREMOVE(ship_events[ship], E)
- for(var/obj/effect/overmap/event/E in active_hazards)
+ for(var/obj/overmap/event/E in active_hazards)
start_hazard(ship,E)
/singleton/overmap_event_handler/proc/is_event_in_turf(datum/event/E, turf/T)
- for(var/obj/effect/overmap/event/hazard in hazard_by_turf[T])
+ for(var/obj/overmap/event/hazard in hazard_by_turf[T])
if(E in hazard.events && E.severity == hazard.difficulty)
return TRUE
-/singleton/overmap_event_handler/proc/is_event_included(list/hazards, obj/effect/overmap/event/E, equal_or_better)//this proc is only used so it can break out of 2 loops cleanly
- for(var/obj/effect/overmap/event/A in hazards)
+/singleton/overmap_event_handler/proc/is_event_included(list/hazards, obj/overmap/event/E, equal_or_better)//this proc is only used so it can break out of 2 loops cleanly
+ for(var/obj/overmap/event/A in hazards)
if(istype(A,E.type) || istype(E,A.type))
if(same_entries(A.events, E.events))
if(equal_or_better)
@@ -160,99 +160,111 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
if(A.difficulty == E.difficulty)
return TRUE
-// We don't subtype /obj/effect/overmap/visitable because that'll create sections one can travel to
+// We don't subtype /obj/overmap/visitable because that'll create sections one can travel to
// And with them "existing" on the overmap Z-level things quickly get odd.
-/obj/effect/overmap/event
+/obj/overmap/event
name = "event"
icon = 'icons/obj/overmap.dmi'
- icon_state = "event"
+ icon_state = "blank"
opacity = 1
- color = "#880000"
+ color = "#bb0000"
var/list/events
var/list/event_icon_states
var/difficulty = EVENT_LEVEL_MODERATE
var/weaknesses //if the BSA can destroy them and with what
var/list/victims //basically cached events on which Z level
-/obj/effect/overmap/event/Initialize()
+ var/list/colors = list() //Pick a color from this list on init
+
+ // Events must be detected by sensors, but are otherwise instantly visible.
+ requires_contact = TRUE
+ instant_contact = TRUE
+
+
+/obj/overmap/event/Initialize()
. = ..()
icon_state = pick(event_icon_states)
overmap_event_handler.update_hazards(loc)
+ if(length(colors))
+ color = pick(colors)
-/obj/effect/overmap/event/Move()
+/obj/overmap/event/Move()
var/turf/old_loc = loc
. = ..()
if(.)
overmap_event_handler.update_hazards(old_loc)
overmap_event_handler.update_hazards(loc)
-/obj/effect/overmap/event/forceMove(atom/destination)
+/obj/overmap/event/forceMove(atom/destination)
var/old_loc = loc
. = ..()
if(.)
overmap_event_handler.update_hazards(old_loc)
overmap_event_handler.update_hazards(loc)
-/obj/effect/overmap/event/Destroy()//takes a look at this one as well, make sure everything is A-OK
+/obj/overmap/event/Destroy()//takes a look at this one as well, make sure everything is A-OK
var/turf/T = loc
. = ..()
overmap_event_handler.update_hazards(T)
-/obj/effect/overmap/event/meteor
+/obj/overmap/event/meteor
name = "asteroid field"
events = list(/datum/event/meteor_wave/overmap)
event_icon_states = list("meteor1", "meteor2", "meteor3", "meteor4")
difficulty = EVENT_LEVEL_MAJOR
+ opacity = 0
weaknesses = OVERMAP_WEAKNESS_MINING | OVERMAP_WEAKNESS_EXPLOSIVE
- color = "#a08444"
+ colors = list("#fc1100", "#ca3227", "#be1e12")
-/obj/effect/overmap/event/electric
+/obj/overmap/event/electric
name = "electrical storm"
events = list(/datum/event/electrical_storm)
opacity = 0
event_icon_states = list("electrical1", "electrical2", "electrical3", "electrical4")
difficulty = EVENT_LEVEL_MAJOR
weaknesses = OVERMAP_WEAKNESS_EMP
- color = "#e8e85c"
+ colors = list("#f5ed0c", "#f0e935", "#faf450")
-/obj/effect/overmap/event/dust
+/obj/overmap/event/dust
name = "dust cloud"
events = list(/datum/event/dust)
+ opacity = 0
event_icon_states = list("dust1", "dust2", "dust3", "dust4")
weaknesses = OVERMAP_WEAKNESS_MINING | OVERMAP_WEAKNESS_EXPLOSIVE | OVERMAP_WEAKNESS_FIRE
- color = "#6c6c6c"
+ color = "#bdbdbd"
-/obj/effect/overmap/event/ion
+/obj/overmap/event/ion
name = "ion cloud"
events = list(/datum/event/ionstorm, /datum/event/computer_damage)
opacity = 0
event_icon_states = list("ion1", "ion2", "ion3", "ion4")
difficulty = EVENT_LEVEL_MAJOR
weaknesses = OVERMAP_WEAKNESS_EMP
- color = "#7cb4d4"
+ colors = list("#02faee", "#34d1c9", "#1b9ce7")
-/obj/effect/overmap/event/carp
+/obj/overmap/event/carp
name = "carp shoal"
events = list(/datum/event/mob_spawning/carp)
opacity = 0
difficulty = EVENT_LEVEL_MODERATE
event_icon_states = list("carp1", "carp2")
weaknesses = OVERMAP_WEAKNESS_EXPLOSIVE | OVERMAP_WEAKNESS_FIRE
- color = "#783ca4"
+ colors = list("#a960dd", "#cd60d3", "#ea50f2", "#f67efc")
-/obj/effect/overmap/event/carp/major
+/obj/overmap/event/carp/major
name = "carp school"
difficulty = EVENT_LEVEL_MAJOR
event_icon_states = list("carp3", "carp4")
+ colors = list("#a709db", "#c228c7", "#c444e4")
-/obj/effect/overmap/event/gravity
+/obj/overmap/event/gravity
name = "dark matter influx"
weaknesses = OVERMAP_WEAKNESS_EXPLOSIVE
events = list(/datum/event/gravity)
event_icon_states = list("grav1", "grav2", "grav3", "grav4")
opacity = 0
- color = "#321945"
+ colors = list("#9e5bd1", "#9339d8", "#9121e7")
//These now are basically only used to spawn hazards. Will be useful when we need to spawn group of moving hazards
/datum/overmap_event
@@ -268,27 +280,27 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
count = 15
radius = 4
continuous = FALSE
- hazards = /obj/effect/overmap/event/meteor
+ hazards = /obj/overmap/event/meteor
/datum/overmap_event/electric
name = "electrical storm"
count = 11
radius = 3
opacity = 0
- hazards = /obj/effect/overmap/event/electric
+ hazards = /obj/overmap/event/electric
/datum/overmap_event/dust
name = "dust cloud"
count = 16
radius = 4
- hazards = /obj/effect/overmap/event/dust
+ hazards = /obj/overmap/event/dust
/datum/overmap_event/ion
name = "ion cloud"
count = 8
radius = 3
opacity = 0
- hazards = /obj/effect/overmap/event/ion
+ hazards = /obj/overmap/event/ion
/datum/overmap_event/carp
name = "carp shoal"
@@ -296,17 +308,17 @@ var/global/singleton/overmap_event_handler/overmap_event_handler = new()
radius = 3
opacity = 0
continuous = FALSE
- hazards = /obj/effect/overmap/event/carp
+ hazards = /obj/overmap/event/carp
/datum/overmap_event/carp/major
name = "carp school"
count = 5
radius = 4
- hazards = /obj/effect/overmap/event/carp/major
+ hazards = /obj/overmap/event/carp/major
/datum/overmap_event/gravity
name = "dark matter influx"
count = 12
radius = 4
opacity = 0
- hazards = /obj/effect/overmap/event/gravity
+ hazards = /obj/overmap/event/gravity
diff --git a/code/modules/overmap/exoplanets/_exoplanet.dm b/code/modules/overmap/exoplanets/_exoplanet.dm
index a692e124c4156..067d1eff9eb2e 100644
--- a/code/modules/overmap/exoplanets/_exoplanet.dm
+++ b/code/modules/overmap/exoplanets/_exoplanet.dm
@@ -1,9 +1,10 @@
GLOBAL_VAR(planet_repopulation_disabled)
-/obj/effect/overmap/visitable/sector/exoplanet
+/obj/overmap/visitable/sector/exoplanet
name = "exoplanet"
icon_state = "globe"
- in_space = FALSE
+ sector_flags = OVERMAP_SECTOR_KNOWN
+ sensor_visibility = 60
var/area/planetary_area
var/list/seeds = list()
var/list/fauna_types = list() // possible types of mobs to spawn
@@ -14,15 +15,24 @@ GLOBAL_VAR(planet_repopulation_disabled)
var/list/breathgas = list() //list of gases animals/plants require to survive
var/badgas //id of gas that is toxic to life here
- var/lightlevel = 0 //This default makes turfs not generate light. Adjust to have exoplanents be lit.
- var/night = TRUE
- var/daycycle //How often do we change day and night
- var/daycolumn = 0 //Which column's light needs to be updated next?
- var/daycycle_column_delay = 10 SECONDS
+
+ //DAY/NIGHT CYCLE
+ var/daycycle_range = list(15 MINUTES, 30 MINUTES)
+ var/daycycle = 0//How often do we change day and night, at first list, to determine min and max day length
+ var/sun_process_interval = 1.5 MINUTES //How often we update planetary sunlight
+ var/sun_last_process = null // world.time
+
+ /// 0 means midnight, 1 means noon.
+ var/sun_position = 0
+ /// This a multiplier used to apply to the brightness of ambient lighting. 0.3 means 30% of the brightness of the sun.
+ var/sun_brightness_modifier = 0.5
+
+ /// Sun control
+ var/ambient_group_index = -1
var/maxx
var/maxy
- var/landmark_type = /obj/effect/shuttle_landmark/automatic
+ var/landmark_type = /obj/shuttle_landmark/automatic
var/list/rock_colors = list(COLOR_ASTEROID_ROCK)
var/list/plant_colors = list("RANDOM")
@@ -61,20 +71,9 @@ GLOBAL_VAR(planet_repopulation_disabled)
var/list/spawned_features
//Either a type or a list of types and weights. You must include all types if it's a list
- var/list/habitability_distribution = list(
- HABITABILITY_IDEAL = 10,
- HABITABILITY_OKAY = 40,
- HABITABILITY_BAD = 50
- )
- var/habitability_class
+ var/habitability_weight = HABITABILITY_TYPICAL
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_habitability()
- if (isnum(habitability_distribution))
- habitability_class = habitability_distribution
- else
- habitability_class = pickweight_index(habitability_distribution)
-
-/obj/effect/overmap/visitable/sector/exoplanet/New(nloc, max_x, max_y)
+/obj/overmap/visitable/sector/exoplanet/New(nloc, max_x, max_y)
if (!GLOB.using_map.use_overmap)
return
@@ -113,15 +112,16 @@ GLOBAL_VAR(planet_repopulation_disabled)
possible_features += new ruin
..()
-/obj/effect/overmap/visitable/sector/exoplanet/proc/build_level()
- generate_habitability()
+/obj/overmap/visitable/sector/exoplanet/proc/build_level()
generate_atmosphere()
- for (var/datum/exoplanet_theme/T in themes)
+
+ for (var/datum/exoplanet_theme/T as anything in themes)
T.adjust_atmosphere(src)
+
if (atmosphere)
//Set up gases for living things
if (!length(breathgas))
- var/list/goodgases = gas_data.gases.Copy()
+ var/list/goodgases = atmosphere.gas.Copy()
var/gasnum = min(rand(1,3), length(goodgases))
for (var/i = 1 to gasnum)
var/gas = pick(goodgases)
@@ -131,11 +131,14 @@ GLOBAL_VAR(planet_repopulation_disabled)
var/list/badgases = gas_data.gases.Copy()
badgases -= atmosphere.gas
badgas = pick(badgases)
+
generate_flora()
generate_map()
generate_features()
- for (var/datum/exoplanet_theme/T in themes)
+
+ for (var/datum/exoplanet_theme/T as anything in themes)
T.after_map_generation(src)
+
generate_landing(2)
update_biome()
generate_daycycle()
@@ -143,7 +146,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
START_PROCESSING(SSobj, src)
//attempt at more consistent history generation for xenoarch finds.
-/obj/effect/overmap/visitable/sector/exoplanet/proc/get_engravings()
+/obj/overmap/visitable/sector/exoplanet/proc/get_engravings()
if (!length(actors))
actors += pick("alien humanoid","an amorphic blob","a short, hairy being","a rodent-like creature","a robot","a primate","a reptilian alien","an unidentifiable object","a statue","a starship","unusual devices","a structure")
actors += pick("alien humanoids","amorphic blobs","short, hairy beings","rodent-like creatures","robots","primates","reptilian aliens")
@@ -156,7 +159,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
engravings += "."
return engravings
-/obj/effect/overmap/visitable/sector/exoplanet/Process(wait, tick)
+/obj/overmap/visitable/sector/exoplanet/Process(wait, tick)
if (length(animals) < 0.5*max_animal_count && !repopulating)
repopulating = TRUE
max_animal_count = round(max_animal_count * 0.5)
@@ -180,24 +183,86 @@ GLOBAL_VAR(planet_repopulation_disabled)
daddy.group_multiplier = Z.air.group_multiplier
Z.air.equalize(daddy)
- if (daycycle)
- if (tick % round(daycycle / wait) == 0)
- night = !night
- daycolumn = 1
- if (daycolumn && tick % round(daycycle_column_delay / wait) == 0)
- update_daynight()
-
-/obj/effect/overmap/visitable/sector/exoplanet/proc/update_daynight()
- var/light = 0.1
- if (!night)
- light = lightlevel
- for (var/turf/simulated/floor/exoplanet/T in block(locate(daycolumn,1,min(map_z)),locate(daycolumn,maxy,max(map_z))))
- T.set_light(light, 0.1, 2)
- daycolumn++
- if (daycolumn > maxx)
- daycolumn = 0
-
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_map()
+ if(sun_last_process <= (world.time - sun_process_interval))
+ update_sun()
+
+/obj/overmap/visitable/sector/exoplanet/proc/generate_daycycle()
+ daycycle = rand(daycycle_range[1], daycycle_range[2])
+ update_sun()
+
+// This changes the position of the sun on the planet.
+/obj/overmap/visitable/sector/exoplanet/proc/update_sun()
+ if(sun_last_process == world.time) //For now, calling it several times in same frame is not valid. Add a parameter to ignore this if weather is added
+ return
+ sun_last_process = world.time
+
+ var/time_of_day = (world.time % daycycle) / daycycle //0 to 1 range.
+
+ var/distance_from_noon = abs(time_of_day - 0.5)
+ sun_position = distance_from_noon / 0.5 // -1 to 1 range
+ sun_position = abs(sun_position - 1)
+
+ var/low_brightness = null
+ var/high_brightness = null
+
+ var/low_color = null
+ var/high_color = null
+ var/min = 0
+ var/max = 0
+
+ //Now, each planet type may want to do its own thing for light, if so move most of this code into its own function and override it.
+ switch(sun_position)
+ if(0 to 0.40) // Night
+ low_brightness = 0.01
+ low_color = "#000066"
+
+ high_brightness = 0.2
+ high_color = "#66004d"
+ min = 0
+ max = 0.4
+
+ if(0.40 to 0.50) // Twilight
+ low_brightness = 0.2
+ low_color = "#66004d"
+
+ high_brightness = 0.5
+ high_color = "#cc3300"
+ min = 0.40
+ max = 0.50
+
+ if(0.50 to 0.70) // Sunrise/set
+ low_brightness = 0.5
+ low_color = "#cc3300"
+
+ high_brightness = 0.8
+ high_color = "#ff9933"
+ min = 0.50
+ max = 0.70
+
+ if(0.70 to 1.00) // Noon
+ low_brightness = 0.8
+ low_color = "#dddddd"
+
+ high_brightness = 1.0
+ high_color = "#ffffff"
+ min = 0.70
+ max = 1.0
+
+ //var/interpolate_weight = (abs(min - sun_position)) * 4 Cit interpolation, not sure
+ var/interpolate_weight = (sun_position - min) / (max - min)
+
+ var/new_brightness = (Interpolate(low_brightness, high_brightness, interpolate_weight) ) * sun_brightness_modifier
+
+ //We do a gradient instead of linear interpolation because linear interpolations of colours are unintuitive
+ var/new_color = UNLINT(gradient(low_color, high_color, space = COLORSPACE_HSV, index=interpolate_weight))
+
+ if(ambient_group_index > 0)
+ var/datum/ambient_group/A = SSambient_lighting.ambient_groups[ambient_group_index]
+ A.set_color(new_color, new_brightness)
+ else
+ ambient_group_index = SSambient_lighting.create_ambient_group(new_color, new_brightness)
+
+/obj/overmap/visitable/sector/exoplanet/proc/generate_map()
var/list/grasscolors = plant_colors.Copy()
grasscolors -= "RANDOM"
if (length(grasscolors))
@@ -220,36 +285,27 @@ GLOBAL_VAR(planet_repopulation_disabled)
else
new map_type(null,1,1,zlevel,maxx,maxy,0,1,1,planetary_area)
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_features()
+/obj/overmap/visitable/sector/exoplanet/proc/generate_features()
spawned_features = seedRuins(map_z, features_budget, possible_features, /area/exoplanet, maxx, maxy)
-/obj/effect/overmap/visitable/sector/exoplanet/proc/update_biome()
+/obj/overmap/visitable/sector/exoplanet/proc/update_biome()
for (var/datum/seed/S in seeds)
adapt_seed(S)
for (var/mob/living/simple_animal/A in animals)
adapt_animal(A)
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_daycycle()
- if (lightlevel)
- night = FALSE //we start with a day if we have light.
-
- //When you set daycycle ensure that the minimum is larger than [maxx * daycycle_column_delay].
- //Otherwise the right side of the exoplanet can get stuck in a forever day.
- daycycle = rand(10 MINUTES, 40 MINUTES)
-
-/obj/effect/landmark/exoplanet_spawn/Initialize()
+/obj/landmark/exoplanet_spawn/Initialize()
..()
return INITIALIZE_HINT_LATELOAD
-/obj/effect/landmark/exoplanet_spawn/LateInitialize()
- . = ..()
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+/obj/landmark/exoplanet_spawn/LateInitialize(mapload)
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
if (istype(E))
do_spawn(E)
//Tries to generate num landmarks, but avoids repeats.
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_landing(num = 1)
+/obj/overmap/visitable/sector/exoplanet/proc/generate_landing(num = 1)
var/places = list()
var/attempts = 30*num
var/new_type = landmark_type
@@ -269,7 +325,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
if (check_collision(T.loc, block_to_check)) //While we have lots of patience, ensure landability
valid = FALSE
else //Running out of patience, but would rather not clear ruins, so switch to clearing landmarks and bypass landability check
- new_type = /obj/effect/shuttle_landmark/automatic/clearing
+ new_type = /obj/shuttle_landmark/automatic/clearing
if (!valid)
continue
@@ -278,11 +334,11 @@ GLOBAL_VAR(planet_repopulation_disabled)
places += T
new new_type(T)
-/obj/effect/overmap/visitable/sector/exoplanet/get_scan_data(mob/user)
+/obj/overmap/visitable/sector/exoplanet/get_scan_data(mob/user)
. = ..()
- var/list/extra_data = list(" ")
+ var/list/extra_data = list()
if (atmosphere)
- if (user.skill_check(SKILL_SCIENCE, SKILL_ADEPT))
+ if (user.skill_check(SKILL_SCIENCE, SKILL_TRAINED))
var/list/gases = list()
for (var/g in atmosphere.gas)
if (atmosphere.gas[g] > atmosphere.total_moles * 0.05)
@@ -292,7 +348,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
extra_data += "Atmosphere pressure [atmosphere.return_pressure()*inaccuracy] kPa, temperature [atmosphere.temperature*inaccuracy] K"
else if (user.skill_check(SKILL_SCIENCE, SKILL_BASIC))
extra_data += "Atmosphere present"
- extra_data += " "
+ extra_data += ""
if (length(seeds) && user.skill_check(SKILL_SCIENCE, SKILL_BASIC))
extra_data += "Xenoflora detected"
@@ -300,7 +356,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
if (length(animals) && user.skill_check(SKILL_SCIENCE, SKILL_BASIC))
extra_data += "Life traces detected"
- if (LAZYLEN(spawned_features) && user.skill_check(SKILL_SCIENCE, SKILL_ADEPT))
+ if (LAZYLEN(spawned_features) && user.skill_check(SKILL_SCIENCE, SKILL_TRAINED))
var/ruin_num = 0
for (var/datum/map_template/ruin/exoplanet/R in spawned_features)
if (!(R.ruin_tags & RUIN_NATURAL))
@@ -313,7 +369,7 @@ GLOBAL_VAR(planet_repopulation_disabled)
extra_data += T.get_sensor_data()
. += jointext(extra_data, " ")
-/obj/effect/overmap/visitable/sector/exoplanet/proc/get_surface_color()
+/obj/overmap/visitable/sector/exoplanet/proc/get_surface_color()
return surface_color
/area/exoplanet
diff --git a/code/modules/overmap/exoplanets/exoplanet_atmosphere.dm b/code/modules/overmap/exoplanets/exoplanet_atmosphere.dm
index bc6e1a4a08910..f094a20728fe3 100644
--- a/code/modules/overmap/exoplanets/exoplanet_atmosphere.dm
+++ b/code/modules/overmap/exoplanets/exoplanet_atmosphere.dm
@@ -1,9 +1,10 @@
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/proc/generate_atmosphere()
atmosphere = new
- if (habitability_class == HABITABILITY_IDEAL)
+ if (habitability_weight == HABITABILITY_LOCKED)
atmosphere.adjust_gas(GAS_OXYGEN, MOLES_O2STANDARD, 0)
atmosphere.adjust_gas(GAS_NITROGEN, MOLES_N2STANDARD)
else //let the fuckery commence
+ var/habitability
var/list/newgases = gas_data.gases.Copy()
if (prob(90)) //all phoron planet should be rare
newgases -= GAS_PHORON
@@ -11,39 +12,52 @@
newgases -= GAS_ALIEN
newgases -= GAS_STEAM
- var/total_moles = MOLES_CELLSTANDARD * rand(80,120)/100
+ switch(habitability_weight)
+ if (HABITABILITY_TYPICAL)
+ habitability = NORMAL_RAND
+ if (HABITABILITY_BAD)
+ habitability = LINEAR_RAND
+ if (HABITABILITY_EXTREME)
+ habitability = SQUARE_RAND
+ else
+ habitability = UNIFORM_RAND
+
+ var/total_moles = MOLES_CELLSTANDARD * (rand(7, 40) / 10)
+ var/generator/new_moles = generator("num", 0.15 * total_moles, 0.6 * total_moles, habitability)
var/badflag = 0
- //Breathable planet
- if (habitability_class == HABITABILITY_OKAY)
- atmosphere.gas[GAS_OXYGEN] += MOLES_O2STANDARD
- total_moles -= MOLES_O2STANDARD
- badflag = XGM_GAS_FUEL|XGM_GAS_CONTAMINANT
+ var/gasnum = rand(max(habitability_weight - 1, 1), 4)
+ var/i = 0
- var/gasnum = rand(1,4)
- var/i = 1
- var/sanity = prob(99.9)
while (i <= gasnum && total_moles && length(newgases))
- if (badflag && sanity)
+ if (badflag)
for(var/g in newgases)
if (gas_data.flags[g] & badflag)
newgases -= g
+
var/ng = pick_n_take(newgases) //pick a gas
- if (sanity) //make sure atmosphere is not flammable... always
- if (gas_data.flags[ng] & XGM_GAS_OXIDIZER)
- badflag |= XGM_GAS_FUEL
- if (gas_data.flags[ng] & XGM_GAS_FUEL)
+
+ if (gas_data.flags[ng] & XGM_GAS_OXIDIZER)
+ badflag |= XGM_GAS_OXIDIZER
+ if (prob(33))
+ badflag |= (XGM_GAS_FUSION_FUEL | XGM_GAS_FUEL)
+
+ if ((gas_data.flags[ng] & XGM_GAS_FUEL) || (gas_data.flags[ng] & XGM_GAS_FUSION_FUEL))
+ badflag |= (XGM_GAS_FUSION_FUEL | XGM_GAS_FUEL)
+ if (prob(33))
badflag |= XGM_GAS_OXIDIZER
- sanity = 0
- var/part = total_moles * rand(3,80)/100 //allocate percentage to it
+ var/part = new_moles.Rand() //allocate percentage to it
if (i == gasnum || !length(newgases)) //if it's last gas, let it have all remaining moles
part = total_moles
+
atmosphere.gas[ng] += part
total_moles = max(total_moles - part, 0)
i++
+ atmosphere.update_values()
+
-/obj/effect/overmap/visitable/sector/exoplanet/proc/get_atmosphere_color()
+/obj/overmap/visitable/sector/exoplanet/proc/get_atmosphere_color()
var/list/colors = list()
for (var/g in atmosphere.gas)
if (gas_data.tile_overlay_color[g])
diff --git a/code/modules/overmap/exoplanets/exoplanet_fauna.dm b/code/modules/overmap/exoplanets/exoplanet_fauna.dm
index 3cbc5784f8cff..d8a60c9c9d8ed 100644
--- a/code/modules/overmap/exoplanets/exoplanet_fauna.dm
+++ b/code/modules/overmap/exoplanets/exoplanet_fauna.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/proc/adapt_animal(mob/living/simple_animal/A, setname = TRUE)
+/obj/overmap/visitable/sector/exoplanet/proc/adapt_animal(mob/living/simple_animal/A, setname = TRUE)
if (setname)
if (species[A.type])
A.SetName(species[A.type])
@@ -20,16 +20,16 @@
A.min_gas = null
A.max_gas = null
-/obj/effect/overmap/visitable/sector/exoplanet/proc/remove_animal(mob/M)
+/obj/overmap/visitable/sector/exoplanet/proc/remove_animal(mob/M)
animals -= M
GLOB.death_event.unregister(M, src)
GLOB.destroyed_event.unregister(M, src)
repopulate_types |= M.type
-/obj/effect/overmap/visitable/sector/exoplanet/proc/handle_repopulation()
+/obj/overmap/visitable/sector/exoplanet/proc/handle_repopulation()
for (var/i = 1 to round(max_animal_count - length(animals)))
if (prob(10))
- var/turf/simulated/T = pick_area_turf(planetary_area, list(/proc/not_turf_contains_dense_objects))
+ var/turf/simulated/T = pick_area_turf(planetary_area, list(GLOBAL_PROC_REF(not_turf_contains_dense_objects)))
var/mob_type = pick(repopulate_types)
var/mob/S = new mob_type(T)
track_animal(S)
@@ -37,15 +37,15 @@
if (length(animals) >= max_animal_count)
repopulating = 0
-/obj/effect/overmap/visitable/sector/exoplanet/proc/track_animal(mob/A)
+/obj/overmap/visitable/sector/exoplanet/proc/track_animal(mob/A)
animals += A
- GLOB.death_event.register(A, src, /obj/effect/overmap/visitable/sector/exoplanet/proc/remove_animal)
- GLOB.destroyed_event.register(A, src, /obj/effect/overmap/visitable/sector/exoplanet/proc/remove_animal)
+ GLOB.death_event.register(A, src, TYPE_PROC_REF(/obj/overmap/visitable/sector/exoplanet, remove_animal))
+ GLOB.destroyed_event.register(A, src, TYPE_PROC_REF(/obj/overmap/visitable/sector/exoplanet, remove_animal))
-/obj/effect/overmap/visitable/sector/exoplanet/proc/get_random_species_name()
+/obj/overmap/visitable/sector/exoplanet/proc/get_random_species_name()
return pick("nol","shan","can","fel","xor")+pick("a","e","o","t","ar")+pick("ian","oid","ac","ese","inian","rd")
-/obj/effect/overmap/visitable/sector/exoplanet/proc/rename_species(species_type, newname, force = FALSE)
+/obj/overmap/visitable/sector/exoplanet/proc/rename_species(species_type, newname, force = FALSE)
if (species[species_type] && !force)
return FALSE
@@ -58,20 +58,20 @@
A.verbs -= /mob/living/simple_animal/proc/name_species
return TRUE
-/obj/effect/landmark/exoplanet_spawn
+/obj/landmark/exoplanet_spawn
name = "spawn exoplanet animal"
-/obj/effect/landmark/exoplanet_spawn/proc/do_spawn(obj/effect/overmap/visitable/sector/exoplanet/planet)
+/obj/landmark/exoplanet_spawn/proc/do_spawn(obj/overmap/visitable/sector/exoplanet/planet)
if (LAZYLEN(planet.fauna_types))
var/beastie = pick(planet.fauna_types)
var/mob/M = new beastie(get_turf(src))
planet.adapt_animal(M)
planet.track_animal(M)
-/obj/effect/landmark/exoplanet_spawn/megafauna
+/obj/landmark/exoplanet_spawn/megafauna
name = "spawn exoplanet megafauna"
-/obj/effect/landmark/exoplanet_spawn/megafauna/do_spawn(obj/effect/overmap/visitable/sector/exoplanet/planet)
+/obj/landmark/exoplanet_spawn/megafauna/do_spawn(obj/overmap/visitable/sector/exoplanet/planet)
if (LAZYLEN(planet.megafauna_types))
var/beastie = pick(planet.megafauna_types)
var/mob/M = new beastie(get_turf(src))
diff --git a/code/modules/overmap/exoplanets/exoplanet_flora.dm b/code/modules/overmap/exoplanets/exoplanet_flora.dm
index 65880da8eb90f..c99658e42927f 100644
--- a/code/modules/overmap/exoplanets/exoplanet_flora.dm
+++ b/code/modules/overmap/exoplanets/exoplanet_flora.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_flora()
+/obj/overmap/visitable/sector/exoplanet/proc/generate_flora()
for (var/i = 1 to flora_diversity)
var/datum/seed/S = new()
if (atmosphere?.gas)
@@ -36,7 +36,7 @@
S.chems[/datum/reagent/woodpulp] = list(1)
big_flora_types += S
-/obj/effect/overmap/visitable/sector/exoplanet/proc/adapt_seed(datum/seed/S)
+/obj/overmap/visitable/sector/exoplanet/proc/adapt_seed(datum/seed/S)
S.set_trait(TRAIT_IDEAL_HEAT, atmosphere.temperature + rand(-5,5),800,70)
S.set_trait(TRAIT_HEAT_TOLERANCE, S.get_trait(TRAIT_HEAT_TOLERANCE) + rand(-5,5),800,70)
S.set_trait(TRAIT_LOWKPA_TOLERANCE, atmosphere.return_pressure() + rand(-5,-50),80,0)
@@ -53,16 +53,16 @@
S.chems[/datum/reagent/nutriment] = nutriment
S.chems[chem_type] = list(rand(1,10),rand(10,20))
-/obj/effect/landmark/exoplanet_spawn/plant
+/obj/landmark/exoplanet_spawn/plant
name = "spawn exoplanet plant"
-/obj/effect/landmark/exoplanet_spawn/plant/do_spawn(obj/effect/overmap/visitable/sector/exoplanet/planet)
+/obj/landmark/exoplanet_spawn/plant/do_spawn(obj/overmap/visitable/sector/exoplanet/planet)
if (LAZYLEN(planet.small_flora_types))
new /obj/machinery/portable_atmospherics/hydroponics/soil/invisible(get_turf(src), pick(planet.small_flora_types), 1)
-/obj/effect/landmark/exoplanet_spawn/large_plant
+/obj/landmark/exoplanet_spawn/large_plant
name = "spawn exoplanet large plant"
-/obj/effect/landmark/exoplanet_spawn/large_plant/do_spawn(obj/effect/overmap/visitable/sector/exoplanet/planet)
+/obj/landmark/exoplanet_spawn/large_plant/do_spawn(obj/overmap/visitable/sector/exoplanet/planet)
if (LAZYLEN(planet.big_flora_types))
new /obj/machinery/portable_atmospherics/hydroponics/soil/invisible(get_turf(src), pick(planet.big_flora_types), 1)
diff --git a/code/modules/overmap/exoplanets/exoplanet_skybox.dm b/code/modules/overmap/exoplanets/exoplanet_skybox.dm
index bf277f38b25bb..07d310ec0d903 100644
--- a/code/modules/overmap/exoplanets/exoplanet_skybox.dm
+++ b/code/modules/overmap/exoplanets/exoplanet_skybox.dm
@@ -1,25 +1,25 @@
-/obj/effect/overmap/visitable/sector/exoplanet/get_skybox_representation()
+/obj/overmap/visitable/sector/exoplanet/get_skybox_representation()
return skybox_image
-/obj/effect/overmap/visitable/sector/exoplanet/proc/get_base_image()
+/obj/overmap/visitable/sector/exoplanet/proc/get_base_image()
var/image/base = image('icons/skybox/planet.dmi', "base")
base.color = get_surface_color()
return base
-/obj/effect/overmap/visitable/sector/exoplanet/proc/generate_planet_image()
+/obj/overmap/visitable/sector/exoplanet/proc/generate_planet_image()
skybox_image = image('icons/skybox/planet.dmi', "")
- skybox_image.overlays += get_base_image()
+ skybox_image.AddOverlays(get_base_image())
for (var/datum/exoplanet_theme/theme in themes)
- skybox_image.overlays += theme.get_planet_image_extra()
+ skybox_image.AddOverlays(theme.get_planet_image_extra())
if (water_color) //TODO: move water levels out of randommap into exoplanet
var/image/water = image('icons/skybox/planet.dmi', "water")
water.color = water_color
water.appearance_flags = DEFAULT_APPEARANCE_FLAGS | PIXEL_SCALE
water.SetTransform(rotation = rand(0, 360))
- skybox_image.overlays += water
+ skybox_image.AddOverlays(water)
if (atmosphere && atmosphere.return_pressure() > SOUND_MINIMUM_PRESSURE)
@@ -30,20 +30,20 @@
var/image/clouds = image('icons/skybox/planet.dmi', "weak_clouds")
if (water_color)
- clouds.overlays += image('icons/skybox/planet.dmi', "clouds")
+ clouds.AddOverlays(image('icons/skybox/planet.dmi', "clouds"))
clouds.color = atmo_color
- skybox_image.overlays += clouds
+ skybox_image.AddOverlays(clouds)
var/image/atmo = image('icons/skybox/planet.dmi', "atmoring")
skybox_image.underlays += atmo
var/image/shadow = image('icons/skybox/planet.dmi', "shadow")
shadow.blend_mode = BLEND_MULTIPLY
- skybox_image.overlays += shadow
+ skybox_image.AddOverlays(shadow)
var/image/light = image('icons/skybox/planet.dmi', "lightrim")
- skybox_image.overlays += light
+ skybox_image.AddOverlays(light)
if (prob(20))
var/image/rings = image('icons/skybox/planet_rings.dmi')
@@ -51,7 +51,7 @@
rings.color = pick("#f0fcff", "#dcc4ad", "#d1dcad", "#adb8dc")
rings.pixel_x = -128
rings.pixel_y = -128
- skybox_image.overlays += rings
+ skybox_image.AddOverlays(rings)
skybox_image.pixel_x = rand(0,64)
skybox_image.pixel_y = rand(128,256)
diff --git a/code/modules/overmap/exoplanets/planet_themes/_planet_theme.dm b/code/modules/overmap/exoplanets/planet_themes/_planet_theme.dm
index 652ebe6443029..95f03d718bc89 100644
--- a/code/modules/overmap/exoplanets/planet_themes/_planet_theme.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/_planet_theme.dm
@@ -5,16 +5,16 @@
var/list/sub_themes = list() // other themes that work in tandem
// Called by exoplanet datum before applying its map generators
-/datum/exoplanet_theme/proc/before_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/proc/before_map_generation(obj/overmap/visitable/sector/exoplanet/E)
// Called by exoplanet datum after applying its map generators and spawning ruins
-/datum/exoplanet_theme/proc/after_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/proc/after_map_generation(obj/overmap/visitable/sector/exoplanet/E)
-/datum/exoplanet_theme/proc/adjust_atmosphere(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/proc/adjust_atmosphere(obj/overmap/visitable/sector/exoplanet/E)
/datum/exoplanet_theme/proc/get_planet_image_extra()
/datum/exoplanet_theme/proc/get_sensor_data()
return "No significant terrain features detected."
-/datum/exoplanet_theme/proc/adapt_animal(obj/effect/overmap/visitable/sector/exoplanet/E, mob/A)
+/datum/exoplanet_theme/proc/adapt_animal(obj/overmap/visitable/sector/exoplanet/E, mob/A)
diff --git a/code/modules/overmap/exoplanets/planet_themes/mountains.dm b/code/modules/overmap/exoplanets/planet_themes/mountains.dm
index 3e5880e48576c..eb4a18cb42ce4 100644
--- a/code/modules/overmap/exoplanets/planet_themes/mountains.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/mountains.dm
@@ -5,7 +5,7 @@
/datum/exoplanet_theme/mountains/get_sensor_data()
return "Extensive cave systems and mountain regions detected."
-/datum/exoplanet_theme/mountains/before_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/mountains/before_map_generation(obj/overmap/visitable/sector/exoplanet/E)
rock_color = pick(E.rock_colors)
for(var/zlevel in E.map_z)
new /datum/random_map/automata/cave_system/mountains(null,TRANSITIONEDGE,TRANSITIONEDGE,zlevel,E.maxx-TRANSITIONEDGE,E.maxy-TRANSITIONEDGE,0,1,1, E.planetary_area, rock_color)
@@ -27,9 +27,9 @@
/datum/random_map/automata/cave_system/mountains/New(seed, tx, ty, tz, tlx, tly, do_not_apply, do_not_announce, never_be_priority = 0, used_area, _rock_color)
if (_rock_color)
rock_color = _rock_color
- if (target_turf_type == null)
+ if (isnull(target_turf_type))
target_turf_type = world.turf
- if (floor_type == null)
+ if (isnull(floor_type))
floor_type = world.turf
..()
diff --git a/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm b/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
index c6323a782aa0c..2849b278c0708 100644
--- a/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/radiation_bombing.dm
@@ -2,7 +2,7 @@
name = "Radiation Bombardment"
ruin_tags_blacklist = RUIN_HUMAN
-/datum/exoplanet_theme/radiation_bombing/adjust_atmosphere(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/radiation_bombing/adjust_atmosphere(obj/overmap/visitable/sector/exoplanet/E)
if (E.atmosphere)
E.atmosphere.temperature += rand(20, 100)
E.atmosphere.update_values()
@@ -10,18 +10,18 @@
/datum/exoplanet_theme/radiation_bombing/get_sensor_data()
return "Hotspots of radiation detected."
-/datum/exoplanet_theme/radiation_bombing/after_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/radiation_bombing/after_map_generation(obj/overmap/visitable/sector/exoplanet/E)
var/radiation_power = rand(10, 37.5)
var/num_craters = round(min(0.04, rand()) * 0.02 * E.maxx * E.maxy)
for (var/i = 1 to num_craters)
- var/turf/simulated/T = pick_area_turf(E.planetary_area, list(/proc/not_turf_contains_dense_objects))
+ var/turf/simulated/T = pick_area_turf(E.planetary_area, list(GLOBAL_PROC_REF(not_turf_contains_dense_objects)))
if (!T) // ran out of space somehow
return
new/obj/structure/rubble/war(T)
var/datum/radiation_source/S = new(T, radiation_power, FALSE)
S.range = 4
SSradiation.add_source(S)
- T.set_light(0.4, 1, 2, l_color = PIPE_COLOR_GREEN)
+ T.set_light(2, 0.4, l_color = PIPE_COLOR_GREEN)
for (var/turf/simulated/floor/exoplanet/crater in circlerangeturfs(T, 3))
if (prob(10))
new/obj/item/remains/xeno/charred(crater)
diff --git a/code/modules/overmap/exoplanets/planet_themes/robotic_guardians.dm b/code/modules/overmap/exoplanets/planet_themes/robotic_guardians.dm
index 5ef9a3212169f..77d90359f20b0 100644
--- a/code/modules/overmap/exoplanets/planet_themes/robotic_guardians.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/robotic_guardians.dm
@@ -9,15 +9,15 @@
/mob/living/simple_animal/hostile/hivebot/mega
)
-/datum/exoplanet_theme/robotic_guardians/before_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/robotic_guardians/before_map_generation(obj/overmap/visitable/sector/exoplanet/E)
E.ruin_tags_whitelist |= RUIN_ALIEN
E.fauna_types |= guardian_types
E.megafauna_types |= mega_guardian_types
-/datum/exoplanet_theme/robotic_guardians/adapt_animal(obj/effect/overmap/visitable/sector/exoplanet/E, mob/A)
+/datum/exoplanet_theme/robotic_guardians/adapt_animal(obj/overmap/visitable/sector/exoplanet/E, mob/A)
// Stopping robots from fighting each other
if (is_type_in_list(A, guardian_types | mega_guardian_types))
A.faction = "Ancient Guardian"
/datum/exoplanet_theme/robotic_guardians/get_sensor_data()
- return "Movement without corresponding lifesigns detected on the surface."
\ No newline at end of file
+ return "Movement without corresponding lifesigns detected on the surface."
diff --git a/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm b/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
index 99b7b5f13687c..e511184268c20 100644
--- a/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
+++ b/code/modules/overmap/exoplanets/planet_themes/ruined_city.dm
@@ -17,14 +17,11 @@
'sound/ambience/ominous3.ogg'
)
-/datum/exoplanet_theme/ruined_city/before_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/ruined_city/before_map_generation(obj/overmap/visitable/sector/exoplanet/E)
E.ruin_tags_whitelist |= RUIN_ALIEN
for (var/zlevel in E.map_z)
new /datum/random_map/city(null,1,1,zlevel,E.maxx,E.maxy,0,1,1, E.planetary_area)
- if (prob(50))
- E.lightlevel = rand(5,10)/10 //deserts are usually :lit:
-
if (prob(50))
var/datum/exoplanet_theme/robotic_guardians/T = new /datum/exoplanet_theme/robotic_guardians
E.themes += T
@@ -32,7 +29,7 @@
T.before_map_generation(E)
-/datum/exoplanet_theme/ruined_city/after_map_generation(obj/effect/overmap/visitable/sector/exoplanet/E)
+/datum/exoplanet_theme/ruined_city/after_map_generation(obj/overmap/visitable/sector/exoplanet/E)
var/area/A = E.planetary_area
LAZYDISTINCTADD(A.ambience, spooky_ambience)
@@ -136,12 +133,12 @@
/turf/simulated/wall/containment
paint_color = COLOR_GRAY20
-/turf/simulated/wall/containment/New(newloc)
- ..(newloc,MATERIAL_CONCRETE, MATERIAL_ALIENALLOY)
+/turf/simulated/wall/containment/Initialize(mapload, added_to_area_cache)
+ . = ..(mapload, added_to_area_cache, MATERIAL_CONCRETE, MATERIAL_ALIENALLOY)
/datum/random_map/maze/lab
wall_type = /turf/simulated/wall/containment
- floor_type = /turf/simulated/floor/fixed/alium/airless
+ floor_type = /turf/simulated/floor/fixed/alium
preserve_map = 0
var/artifacts_to_spawn = 1
diff --git a/code/modules/overmap/exoplanets/planet_types/barren.dm b/code/modules/overmap/exoplanets/planet_types/barren.dm
index 5d0a48116f850..74465cb3c43b7 100644
--- a/code/modules/overmap/exoplanets/planet_types/barren.dm
+++ b/code/modules/overmap/exoplanets/planet_types/barren.dm
@@ -1,26 +1,22 @@
-/obj/effect/overmap/visitable/sector/exoplanet/barren
+/obj/overmap/visitable/sector/exoplanet/barren
name = "barren exoplanet"
desc = "An exoplanet that couldn't hold its atmosphere."
color = "#6c6c6c"
planetary_area = /area/exoplanet/barren
rock_colors = list(COLOR_BEIGE, COLOR_GRAY80, COLOR_BROWN)
- possible_themes = list(/datum/exoplanet_theme/mountains)
map_generators = list(/datum/random_map/noise/exoplanet/barren, /datum/random_map/noise/ore/rich)
ruin_tags_blacklist = RUIN_HABITAT|RUIN_WATER
features_budget = 6
surface_color = "#807d7a"
water_color = null
- habitability_distribution = HABITABILITY_BAD
+ habitability_weight = HABITABILITY_LOCKED
has_trees = FALSE
-/obj/effect/overmap/visitable/sector/exoplanet/barren/generate_atmosphere()
- ..()
- atmosphere.remove_ratio(0.9)
+/obj/overmap/visitable/sector/exoplanet/barren/generate_atmosphere()
+ atmosphere = new
-/obj/effect/overmap/visitable/sector/exoplanet/barren/generate_flora()
- if(prob(10))
- flora_diversity = 1
- ..()
+/obj/overmap/visitable/sector/exoplanet/barren/generate_flora()
+ return
/datum/random_map/noise/exoplanet/barren
descriptor = "barren exoplanet"
@@ -36,15 +32,15 @@
icon_state = "asteroid"
/turf/simulated/floor/exoplanet/barren/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(prob(20))
- overlays += image('icons/turf/flooring/decals.dmi', "asteroid[rand(0,9)]")
+ AddOverlays(image('icons/turf/flooring/decals.dmi', "asteroid[rand(0,9)]"))
-/turf/simulated/floor/exoplanet/barren/Initialize()
+/turf/simulated/floor/exoplanet/barren/Initialize(mapload, added_to_area_cache)
. = ..()
update_icon()
/area/exoplanet/barren
name = "\improper Planetary surface"
ambience = list('sound/effects/wind/wind_2_1.ogg','sound/effects/wind/wind_2_2.ogg','sound/effects/wind/wind_3_1.ogg','sound/effects/wind/wind_4_1.ogg','sound/effects/wind/wind_4_2.ogg','sound/effects/wind/wind_5_1.ogg')
- base_turf = /turf/simulated/floor/exoplanet/barren
\ No newline at end of file
+ base_turf = /turf/simulated/floor/exoplanet/barren
diff --git a/code/modules/overmap/exoplanets/planet_types/chlorine.dm b/code/modules/overmap/exoplanets/planet_types/chlorine.dm
index 4b15ab2077924..847c9fba17a78 100644
--- a/code/modules/overmap/exoplanets/planet_types/chlorine.dm
+++ b/code/modules/overmap/exoplanets/planet_types/chlorine.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/chlorine
+/obj/overmap/visitable/sector/exoplanet/chlorine
name = "chlorine exoplanet"
desc = "An exoplanet with a chlorine based ecosystem. Large quantities of liquid chlorine are present."
color = "#c9df9f"
@@ -9,28 +9,27 @@
ruin_tags_blacklist = RUIN_HABITAT|RUIN_WATER
surface_color = "#a3b879"
water_color = COLOR_BOTTLE_GREEN
- habitability_distribution = HABITABILITY_BAD
+ habitability_weight = HABITABILITY_BAD
has_trees = FALSE
flora_diversity = 5
fauna_types = list(/mob/living/simple_animal/thinbug, /mob/living/simple_animal/hostile/retaliate/beast/samak/alt, /mob/living/simple_animal/yithian, /mob/living/simple_animal/tindalos, /mob/living/simple_animal/hostile/retaliate/jelly)
megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/jelly/mega)
+ sun_brightness_modifier = 0.5 //The dense atmosphere makes it all dark
-/obj/effect/overmap/visitable/sector/exoplanet/chlorine/get_atmosphere_color()
- return "#e5f2bd"
+/obj/overmap/visitable/sector/exoplanet/chlorine/get_atmosphere_color()
+ var/air_color = ..()
+ return MixColors("#e5f2bd", air_color)
-/obj/effect/overmap/visitable/sector/exoplanet/chlorine/generate_map()
- if(prob(50))
- lightlevel = rand(7,10)/10 //It could be night.
- else
- lightlevel = 0.1
+/obj/overmap/visitable/sector/exoplanet/chlorine/generate_atmosphere()
..()
+ var/chlor_moles = (rand(1, 6) / 10) * (atmosphere.total_moles)
+ atmosphere = atmosphere.remove(chlor_moles )
+ atmosphere.adjust_gas(GAS_CHLORINE, chlor_moles )
-/obj/effect/overmap/visitable/sector/exoplanet/chlorine/generate_atmosphere()
- ..()
- if(atmosphere)
- atmosphere.adjust_gas(GAS_CHLORINE, MOLES_O2STANDARD)
- atmosphere.temperature = T100C - rand(0, 100)
- atmosphere.update_values()
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/generator/new_temp = generator("num", H.cold_level_1 + 40, H.heat_level_1 + 10, UNIFORM_RAND)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
/datum/random_map/noise/exoplanet/chlorine
descriptor = "chlorine exoplanet"
@@ -63,6 +62,6 @@
dirt_color = "#d2e0b7"
footstep_type = /singleton/footsteps/sand
-/turf/simulated/floor/exoplanet/chlorine_sand/New()
+/turf/simulated/floor/exoplanet/chlorine_sand/Initialize(mapload, added_to_area_cache)
+ . = ..()
icon_state = "chlorine_sand[rand(0,11)]"
- ..()
diff --git a/code/modules/overmap/exoplanets/planet_types/desert.dm b/code/modules/overmap/exoplanets/planet_types/desert.dm
index c05254711fc24..c370bf7c9d0e8 100644
--- a/code/modules/overmap/exoplanets/planet_types/desert.dm
+++ b/code/modules/overmap/exoplanets/planet_types/desert.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/desert
+/obj/overmap/visitable/sector/exoplanet/desert
name = "desert exoplanet"
desc = "An arid exoplanet with sparse biological resources but rich mineral deposits underground."
color = "#a08444"
@@ -8,28 +8,23 @@
map_generators = list(/datum/random_map/noise/exoplanet/desert, /datum/random_map/noise/ore/rich)
surface_color = "#d6cca4"
water_color = null
- habitability_distribution = list(HABITABILITY_IDEAL = 30, HABITABILITY_OKAY = 50, HABITABILITY_BAD = 10)
has_trees = FALSE
- flora_diversity = 4
fauna_types = list(/mob/living/simple_animal/thinbug, /mob/living/simple_animal/tindalos, /mob/living/simple_animal/hostile/voxslug, /mob/living/simple_animal/hostile/retaliate/beast/antlion)
megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/beast/antlion/mega)
-/obj/effect/overmap/visitable/sector/exoplanet/desert/generate_map()
+/obj/overmap/visitable/sector/exoplanet/desert/generate_map()
if(prob(70))
- lightlevel = rand(5,10)/10 //deserts are usually :lit:
+ sun_brightness_modifier = rand(4,8)/10 //deserts are usually :lit:
..()
-/obj/effect/overmap/visitable/sector/exoplanet/desert/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/desert/generate_atmosphere()
..()
- if(atmosphere)
- var/limit = 1000
- if(habitability_class <= HABITABILITY_OKAY)
- var/datum/species/human/H = /datum/species/human
- limit = initial(H.heat_level_1) - rand(1,10)
- atmosphere.temperature = min(T20C + rand(20, 100), limit)
- atmosphere.update_values()
-
-/obj/effect/overmap/visitable/sector/exoplanet/desert/adapt_seed(datum/seed/S)
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/generator/new_temp = generator("num", H.heat_level_1, 2 * H.heat_level_1, NORMAL_RAND)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
+
+/obj/overmap/visitable/sector/exoplanet/desert/adapt_seed(datum/seed/S)
..()
if(prob(90))
S.set_trait(TRAIT_REQUIRES_WATER,0)
@@ -54,16 +49,16 @@
..()
var/v = noise2value(value)
if(v > 6 && prob(2))
- new/obj/effect/quicksand(T)
+ new/obj/quicksand(T)
/area/exoplanet/desert
ambience = list('sound/effects/wind/desert0.ogg','sound/effects/wind/desert1.ogg','sound/effects/wind/desert2.ogg','sound/effects/wind/desert3.ogg','sound/effects/wind/desert4.ogg','sound/effects/wind/desert5.ogg')
base_turf = /turf/simulated/floor/exoplanet/desert
-/obj/effect/quicksand
+/obj/quicksand
name = "quicksand"
desc = "There is no candy at the bottom."
- icon = 'icons/obj/quicksand.dmi'
+ icon = 'icons/obj/structures/quicksand.dmi'
icon_state = "intact0"
density = FALSE
anchored = TRUE
@@ -72,13 +67,15 @@
var/exposed = FALSE
var/busy
-/obj/effect/quicksand/Initialize()
+/obj/quicksand/Initialize()
. = ..()
var/turf/T = get_turf(src)
appearance = T.appearance
-/obj/effect/quicksand/user_unbuckle_mob(mob/user)
- if(buckled_mob && !user.stat && !user.restrained())
+/obj/quicksand/user_unbuckle_mob(mob/user)
+ if (!can_unbuckle(user))
+ return
+ if (!user.stat && !user.restrained())
if(busy)
to_chat(user, SPAN_NOTICE("\The [buckled_mob] is already getting out, be patient."))
return
@@ -97,7 +94,7 @@
SPAN_NOTICE("You hear water sloshing.")
)
busy = TRUE
- if(do_after(user, delay, src, DO_PUBLIC_UNIQUE))
+ if(do_after(user, delay, src, DO_PUBLIC_UNIQUE) && can_unbuckle(user))
busy = FALSE
if(user == buckled_mob)
if(prob(80))
@@ -112,23 +109,23 @@
to_chat(user, SPAN_WARNING("You slip and fail to get out!"))
return
-/obj/effect/quicksand/unbuckle_mob()
+/obj/quicksand/unbuckle_mob()
..()
update_icon()
-/obj/effect/quicksand/buckle_mob(mob/L)
+/obj/quicksand/buckle_mob(mob/L)
..()
update_icon()
-/obj/effect/quicksand/on_update_icon()
+/obj/quicksand/on_update_icon()
if(!exposed)
return
icon_state = "open"
- overlays.Cut()
+ ClearOverlays()
if(buckled_mob)
- overlays += image(icon,icon_state="overlay",layer=ABOVE_HUMAN_LAYER)
+ AddOverlays(image(icon,icon_state="overlay",layer=ABOVE_HUMAN_LAYER))
-/obj/effect/quicksand/proc/expose()
+/obj/quicksand/proc/expose()
if(exposed)
return
visible_message(SPAN_WARNING("The upper crust breaks, exposing the treacherous quicksand underneath!"))
@@ -138,16 +135,20 @@
exposed = 1
update_icon()
-/obj/effect/quicksand/attackby(obj/item/W, mob/user)
- if(!exposed && W.force)
+
+/obj/quicksand/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Any object - Expose the quicksand
+ if (!exposed && tool.force)
expose()
- else
- ..()
+ return TRUE
+
+ return ..()
+
-/obj/effect/quicksand/Crossed(atom/movable/AM)
+/obj/quicksand/Crossed(atom/movable/AM)
if(isliving(AM))
var/mob/living/L = AM
- if(L.throwing || L.can_overcome_gravity())
+ if(L.throwing || L.can_overcome_gravity() || !can_buckle(L))
return
buckle_mob(L)
if(!exposed)
diff --git a/code/modules/overmap/exoplanets/planet_types/grass.dm b/code/modules/overmap/exoplanets/planet_types/grass.dm
index a548a8089a8d4..7b5c44956f03c 100644
--- a/code/modules/overmap/exoplanets/planet_types/grass.dm
+++ b/code/modules/overmap/exoplanets/planet_types/grass.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/grass
+/obj/overmap/visitable/sector/exoplanet/grass
name = "lush exoplanet"
desc = "Planet with abundant flora and fauna."
color = "#407c40"
@@ -6,27 +6,28 @@
rock_colors = list(COLOR_ASTEROID_ROCK, COLOR_GRAY80, COLOR_BROWN)
plant_colors = list("#0e1e14","#1a3e38","#5a7467","#9eab88","#6e7248", "RANDOM")
map_generators = list(/datum/random_map/noise/exoplanet/grass)
- habitability_distribution = list(HABITABILITY_IDEAL = 70, HABITABILITY_OKAY = 20, HABITABILITY_BAD = 5)
- has_trees = TRUE
flora_diversity = 7
- fauna_types = list(/mob/living/simple_animal/yithian, /mob/living/simple_animal/tindalos, /mob/living/simple_animal/hostile/retaliate/jelly)
- megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/parrot/space/megafauna, /mob/living/simple_animal/hostile/retaliate/goose/dire)
-
-/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_map()
- if(prob(40))
- lightlevel = rand(1,7)/10 //give a chance of twilight jungle
- ..()
+ fauna_types = list(
+ /mob/living/simple_animal/yithian,
+ /mob/living/simple_animal/tindalos,
+ /mob/living/simple_animal/hostile/retaliate/jelly
+ )
+ megafauna_types = list(
+ /mob/living/simple_animal/hostile/retaliate/parrot/space/megafauna,
+ /mob/living/simple_animal/hostile/retaliate/goose/dire
+ )
-/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/grass/generate_atmosphere()
..()
- if(atmosphere)
- atmosphere.temperature = T20C + rand(10, 30)
- atmosphere.update_values()
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/generator/new_temp = generator("num", T0C, H.heat_level_1 - 10, UNIFORM_RAND)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
-/obj/effect/overmap/visitable/sector/exoplanet/grass/get_surface_color()
+/obj/overmap/visitable/sector/exoplanet/grass/get_surface_color()
return grass_color
-/obj/effect/overmap/visitable/sector/exoplanet/grass/adapt_seed(datum/seed/S)
+/obj/overmap/visitable/sector/exoplanet/grass/adapt_seed(datum/seed/S)
..()
var/carnivore_prob = rand(100)
if(carnivore_prob < 30)
@@ -68,7 +69,7 @@
grass_prob = 50
large_flora_prob = 30
-/obj/effect/overmap/visitable/sector/exoplanet/grass/terraformed
+/obj/overmap/visitable/sector/exoplanet/grass/terraformed
name = "life seeded exoplanet"
desc = "Planet with abundant flora and fauna. Shows signs of human terraformation."
color = "#58aa8b"
@@ -76,11 +77,23 @@
rock_colors = list(COLOR_ASTEROID_ROCK, COLOR_GRAY80, COLOR_BROWN)
plant_colors = list("#2f573e","#24574e","#6e9280","#9eab88","#868b58", "#84be7c", "RANDOM")
map_generators = list(/datum/random_map/noise/exoplanet/grass/terraformed)
- lightlevel = 0.5
+ sun_brightness_modifier = 0.8 //Fairly bright
has_trees = TRUE
flora_diversity = 8
- fauna_types = list(/mob/living/simple_animal/passive/cat, /mob/living/simple_animal/passive/chicken, /mob/living/simple_animal/passive/mouse, /mob/living/simple_animal/passive/opossum, /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/hostile/retaliate/goose, /mob/living/simple_animal/passive/cow)
- megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/parrot/space/megafauna, /mob/living/simple_animal/hostile/retaliate/goose/dire)
+ fauna_types = list(
+ /mob/living/simple_animal/passive/cat,
+ /mob/living/simple_animal/passive/chicken,
+ /mob/living/simple_animal/passive/mouse,
+ /mob/living/simple_animal/passive/opossum,
+ /mob/living/simple_animal/hostile/retaliate/goat,
+ /mob/living/simple_animal/hostile/retaliate/goose,
+ /mob/living/simple_animal/passive/cow
+ )
+ megafauna_types = list(
+ /mob/living/simple_animal/hostile/retaliate/parrot/space/megafauna,
+ /mob/living/simple_animal/hostile/retaliate/goose/dire
+ )
+ habitability_weight = HABITABILITY_LOCKED
//Animals being named alien creature is a bit odd as these would just be earth transplants
species = list( /mob/living/simple_animal/passive/cat = "wild cat",
@@ -91,18 +104,12 @@
/mob/living/simple_animal/hostile/retaliate/goose = "goose",
/mob/living/simple_animal/passive/cow = "wild cow")
-/obj/effect/overmap/visitable/sector/exoplanet/grass/terraformed/generate_habitability()
- habitability_class = HABITABILITY_IDEAL
-
-/obj/effect/overmap/visitable/sector/exoplanet/grass/terraformed/generate_atmosphere()
- ..()
- if(atmosphere)
- atmosphere.temperature = T0C + rand(0, 50)
- atmosphere.update_values()
-
-/obj/effect/overmap/visitable/sector/exoplanet/grass/generate_map()
- lightlevel = rand(0.7,0.9)/10
+/obj/overmap/visitable/sector/exoplanet/grass/terraformed/generate_atmosphere()
..()
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/generator/new_temp = generator("num", T20C, H.heat_level_1 - 15)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
/datum/random_map/noise/exoplanet/grass/terraformed
descriptor = "terraformed grass exoplanet"
diff --git a/code/modules/overmap/exoplanets/planet_types/shrouded.dm b/code/modules/overmap/exoplanets/planet_types/shrouded.dm
index 7c1b939fbeb32..ea20d63bb9153 100644
--- a/code/modules/overmap/exoplanets/planet_types/shrouded.dm
+++ b/code/modules/overmap/exoplanets/planet_types/shrouded.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/shrouded
+/obj/overmap/visitable/sector/exoplanet/shrouded
name = "shrouded exoplanet"
desc = "An exoplanet shrouded in a perpetual storm of bizzare, light absorbing particles."
color = "#783ca4"
@@ -7,25 +7,26 @@
plant_colors = list("#3c5434", "#2f6655", "#0e703f", "#495139", "#394c66", "#1a3b77", "#3e3166", "#52457c", "#402d56", "#580d6d")
map_generators = list(/datum/random_map/noise/exoplanet/shrouded, /datum/random_map/noise/ore/poor)
ruin_tags_blacklist = RUIN_HABITAT
- lightlevel = -0.15
+ sun_brightness_modifier = -0.5
surface_color = "#3e3960"
water_color = "#2b2840"
- has_trees = TRUE
- flora_diversity = 4
- fauna_types = list(/mob/living/simple_animal/hostile/retaliate/royalcrab,
- /mob/living/simple_animal/hostile/retaliate/jelly/alt,
- /mob/living/simple_animal/hostile/retaliate/beast/shantak/alt,
- /mob/living/simple_animal/hostile/leech)
+ fauna_types = list(
+ /mob/living/simple_animal/hostile/retaliate/royalcrab,
+ /mob/living/simple_animal/hostile/retaliate/jelly/alt,
+ /mob/living/simple_animal/hostile/retaliate/beast/shantak/alt,
+ /mob/living/simple_animal/hostile/leech
+ )
-/obj/effect/overmap/visitable/sector/exoplanet/shrouded/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/shrouded/generate_atmosphere()
..()
- if(atmosphere)
- atmosphere.temperature = T20C - rand(10, 20)
+ if (atmosphere)
+ atmosphere.temperature = rand(T0C, T20C)
atmosphere.update_values()
-/obj/effect/overmap/visitable/sector/exoplanet/shrouded/get_atmosphere_color()
- return COLOR_BLACK
+/obj/overmap/visitable/sector/exoplanet/shrouded/get_atmosphere_color()
+ var/air_color = ..()
+ return MixColors(COLOR_BLACK, air_color)
/datum/random_map/noise/exoplanet/shrouded
descriptor = "shrouded exoplanet"
@@ -41,7 +42,7 @@
/datum/random_map/noise/exoplanet/shrouded/get_additional_spawns(value, turf/T)
..()
- if(prob(0.1))
+ if (prob(0.1))
new/obj/structure/leech_spawner(T)
/area/exoplanet/shrouded
@@ -68,6 +69,6 @@
desc = "Sand that has been packed in to solid earth."
dirt_color = "#3e3960"
-/turf/simulated/floor/exoplanet/shrouded/New()
+/turf/simulated/floor/exoplanet/shrouded/Initialize(mapload, added_to_area_cache)
+ . = ..()
icon_state = "shrouded[rand(0,8)]"
- ..()
diff --git a/code/modules/overmap/exoplanets/planet_types/snow.dm b/code/modules/overmap/exoplanets/planet_types/snow.dm
index 0c12acbf0c14c..5ba51855824b0 100644
--- a/code/modules/overmap/exoplanets/planet_types/snow.dm
+++ b/code/modules/overmap/exoplanets/planet_types/snow.dm
@@ -1,4 +1,4 @@
-/obj/effect/overmap/visitable/sector/exoplanet/snow
+/obj/overmap/visitable/sector/exoplanet/snow
name = "snow exoplanet"
desc = "Cold planet with limited plant life."
color = "#dcdcdc"
@@ -8,21 +8,20 @@
map_generators = list(/datum/random_map/noise/exoplanet/snow, /datum/random_map/noise/ore/poor)
surface_color = "#e8faff"
water_color = "#b5dfeb"
- habitability_distribution = list(HABITABILITY_IDEAL = 30, HABITABILITY_OKAY = 50, HABITABILITY_BAD = 10)
- has_trees = TRUE
- flora_diversity = 4
- fauna_types = list(/mob/living/simple_animal/hostile/retaliate/beast/samak, /mob/living/simple_animal/hostile/retaliate/beast/diyaab, /mob/living/simple_animal/hostile/retaliate/beast/shantak)
+ habitability_weight = HABITABILITY_BAD
+ fauna_types = list(
+ /mob/living/simple_animal/hostile/retaliate/beast/samak,
+ /mob/living/simple_animal/hostile/retaliate/beast/diyaab,
+ /mob/living/simple_animal/hostile/retaliate/beast/shantak
+ )
megafauna_types = list(/mob/living/simple_animal/hostile/retaliate/giant_crab)
-/obj/effect/overmap/visitable/sector/exoplanet/snow/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/snow/generate_atmosphere()
..()
- if(atmosphere)
- var/limit = 0
- if(habitability_class <= HABITABILITY_OKAY)
- var/datum/species/human/H = /datum/species/human
- limit = initial(H.cold_level_1) + rand(1,10)
- atmosphere.temperature = max(T0C - rand(10, 100), limit)
- atmosphere.update_values()
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/generator/new_temp = generator("num", H.cold_level_1 - 50, H.cold_level_3, NORMAL_RAND)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
/datum/random_map/noise/exoplanet/snow
descriptor = "snow exoplanet"
diff --git a/code/modules/overmap/exoplanets/planet_types/volcanic.dm b/code/modules/overmap/exoplanets/planet_types/volcanic.dm
index a2adbb188812d..861cb3c473fcf 100644
--- a/code/modules/overmap/exoplanets/planet_types/volcanic.dm
+++ b/code/modules/overmap/exoplanets/planet_types/volcanic.dm
@@ -1,36 +1,38 @@
-/obj/effect/overmap/visitable/sector/exoplanet/volcanic
+/obj/overmap/visitable/sector/exoplanet/volcanic
name = "volcanic exoplanet"
desc = "A tectonically unstable planet, extremely rich in minerals."
color = "#9c2020"
planetary_area = /area/exoplanet/volcanic
rock_colors = list(COLOR_DARK_GRAY)
plant_colors = list("#a23c05","#3f1f0d","#662929","#ba6222","#7a5b3a","#120309")
- possible_themes = list()
map_generators = list(/datum/random_map/automata/cave_system/mountains/volcanic, /datum/random_map/noise/exoplanet/volcanic, /datum/random_map/noise/ore/filthy_rich)
ruin_tags_blacklist = RUIN_HABITAT|RUIN_WATER
surface_color = "#261e19"
water_color = "#c74d00"
- habitability_distribution = HABITABILITY_BAD
+ habitability_weight = HABITABILITY_EXTREME
has_trees = FALSE
flora_diversity = 3
fauna_types = list(/mob/living/simple_animal/thinbug, /mob/living/simple_animal/hostile/retaliate/beast/shantak/lava, /mob/living/simple_animal/hostile/retaliate/beast/charbaby)
megafauna_types = list(/mob/living/simple_animal/hostile/drake)
-/obj/effect/overmap/visitable/sector/exoplanet/volcanic/get_atmosphere_color()
- return COLOR_GRAY20
+/obj/overmap/visitable/sector/exoplanet/volcanic/get_atmosphere_color()
+ var/air_color = ..()
+ return MixColors(COLOR_GRAY20, air_color)
-/obj/effect/overmap/visitable/sector/exoplanet/volcanic/generate_atmosphere()
+/obj/overmap/visitable/sector/exoplanet/volcanic/generate_atmosphere()
..()
- if(atmosphere)
- atmosphere.temperature = T20C + rand(220, 800)
- atmosphere.update_values()
+ var/datum/species/H = all_species[SPECIES_HUMAN]
+ var/xtreme = H.heat_level_2 + (rand(1,3) * H.heat_level_2)
+ var/generator/new_temp = generator("num", H.heat_level_2, xtreme, UNIFORM_RAND)
+ atmosphere.temperature = new_temp.Rand()
+ atmosphere.update_values()
-/obj/effect/overmap/visitable/sector/exoplanet/volcanic/adapt_seed(datum/seed/S)
+/obj/overmap/visitable/sector/exoplanet/volcanic/adapt_seed(datum/seed/S)
..()
S.set_trait(TRAIT_REQUIRES_WATER,0)
S.set_trait(TRAIT_HEAT_TOLERANCE, 1000 + S.get_trait(TRAIT_HEAT_TOLERANCE))
-/obj/effect/overmap/visitable/sector/exoplanet/volcanic/adapt_animal(mob/living/simple_animal/A)
+/obj/overmap/visitable/sector/exoplanet/volcanic/adapt_animal(mob/living/simple_animal/A)
..()
A.heat_damage_per_tick = 0 //animals not hot, no burning in lava
@@ -94,16 +96,18 @@
turf_flags = TURF_DISALLOW_BLOB
var/list/victims
+ ambient_light_multiplier = 1
+
+/turf/simulated/floor/exoplanet/lava/setup_local_ambient()
+ set_ambient_light(COLOR_ORANGE, 1)
+
/turf/simulated/floor/exoplanet/lava/on_update_icon()
return
-/turf/simulated/floor/exoplanet/lava/Initialize()
- . = ..()
- set_light(0.95, 0.5, 2, l_color = COLOR_ORANGE)
-
/turf/simulated/floor/exoplanet/lava/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
+ clear_ambient_light()
/turf/simulated/floor/exoplanet/lava/Entered(atom/movable/AM)
..()
@@ -126,7 +130,7 @@
return PROCESS_KILL
for(var/weakref/W in victims)
var/atom/movable/AM = W.resolve()
- if (AM == null || get_turf(AM) != src || AM.is_burnable() == FALSE)
+ if (isnull(AM) || get_turf(AM) != src || AM.is_burnable() == FALSE)
victims -= W
continue
var/datum/gas_mixture/environment = return_air()
diff --git a/code/modules/overmap/exoplanets/random_map.dm b/code/modules/overmap/exoplanets/random_map.dm
index 217e5ea7c5c30..77b9f07f7db7a 100644
--- a/code/modules/overmap/exoplanets/random_map.dm
+++ b/code/modules/overmap/exoplanets/random_map.dm
@@ -24,7 +24,7 @@
var/list/grass_cache
/datum/random_map/noise/exoplanet/New(seed, tx, ty, tz, tlx, tly, do_not_apply, do_not_announce, never_be_priority = 0, used_area, list/_plant_colors)
- if (target_turf_type == null)
+ if (isnull(target_turf_type))
target_turf_type = world.turf
water_level = rand(water_level_min,water_level_max)
//automagically adjust probs for bigger maps to help with lag
@@ -75,9 +75,9 @@
/datum/random_map/noise/exoplanet/proc/spawn_fauna(turf/T)
if (prob(megafauna_spawn_prob))
- new /obj/effect/landmark/exoplanet_spawn/megafauna(T)
+ new /obj/landmark/exoplanet_spawn/megafauna(T)
else
- new /obj/effect/landmark/exoplanet_spawn(T)
+ new /obj/landmark/exoplanet_spawn(T)
/datum/random_map/noise/exoplanet/proc/get_grass_overlay()
var/grass_num = "[rand(1,6)]"
@@ -92,19 +92,19 @@
/datum/random_map/noise/exoplanet/proc/spawn_flora(turf/T, big)
if (big)
- new /obj/effect/landmark/exoplanet_spawn/large_plant(T)
+ new /obj/landmark/exoplanet_spawn/large_plant(T)
for(var/turf/neighbor in RANGE_TURFS(T, 1))
spawn_grass(neighbor)
else
- new /obj/effect/landmark/exoplanet_spawn/plant(T)
+ new /obj/landmark/exoplanet_spawn/plant(T)
spawn_grass(T)
/datum/random_map/noise/exoplanet/proc/spawn_grass(turf/T)
if (istype(T, water_type))
return
- if (locate(/obj/effect/floor_decal) in T)
+ if (locate(/obj/floor_decal) in T)
return
- new /obj/effect/floor_decal(T, null, null, get_grass_overlay())
+ new /obj/floor_decal(T, null, null, get_grass_overlay())
/datum/random_map/noise/exoplanet/cleanup()
..()
diff --git a/code/modules/overmap/exoplanets/turfs.dm b/code/modules/overmap/exoplanets/turfs.dm
index fa7faffb3be69..00af57a6fec35 100644
--- a/code/modules/overmap/exoplanets/turfs.dm
+++ b/code/modules/overmap/exoplanets/turfs.dm
@@ -10,40 +10,56 @@
/turf/simulated/floor/exoplanet/can_engrave()
return FALSE
-/turf/simulated/floor/exoplanet/New()
- if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
- if(istype(E))
- if(E.atmosphere)
- initial_gas = E.atmosphere.gas.Copy()
- temperature = E.atmosphere.temperature
- else
- initial_gas = list()
- temperature = T0C
- //Must be done here, as light data is not fully carried over by ChangeTurf (but overlays are).
- set_light(E.lightlevel, 0.1, 2)
- if(E.planetary_area && istype(loc, world.area))
- ChangeArea(src, E.planetary_area)
- ..()
-
-/turf/simulated/floor/exoplanet/attackby(obj/item/C, mob/user)
+/turf/simulated/floor/exoplanet/Initialize(mapload, added_to_area_cache)
+ . = ..()
+ if(!GLOB.using_map.use_overmap)
+ return
+
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+ if(!istype(E))
+ return
+
+ if(E.atmosphere)
+ initial_gas = E.atmosphere.gas.Copy()
+ temperature = E.atmosphere.temperature
+ else
+ initial_gas = list()
+ temperature = T0C
+
+ if(E.planetary_area && istype(loc, world.area))
+ change_area(E.planetary_area)
+
+/turf/simulated/floor/exoplanet/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+
+/turf/simulated/floor/exoplanet/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+
+/turf/simulated/floor/exoplanet/use_tool(obj/item/C, mob/living/user, list/click_params)
if(diggable && istype(C,/obj/item/shovel))
- visible_message(SPAN_NOTICE("\The [user] starts digging \the [src]"))
+ visible_message(SPAN_NOTICE("[user] starts digging [src]"))
if(do_after(user, 5 SECONDS, src, DO_PUBLIC_UNIQUE))
to_chat(user,SPAN_NOTICE("You dig a deep pit."))
new /obj/structure/pit(src)
diggable = 0
else
to_chat(user,SPAN_NOTICE("You stop shoveling."))
+ return TRUE
+
else if(istype(C, /obj/item/stack/tile))
var/obj/item/stack/tile/T = C
- if(T.use(1))
- playsound(src, 'sound/items/Deconstruct.ogg', 80, 1)
- ChangeTurf(/turf/simulated/floor, FALSE, FALSE, TRUE)
- else if (isCrowbar(C) || isWelder(C) || istype(C, /obj/item/gun/energy/plasmacutter))
+ if(!T.can_use(1))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(T, 1, "to place a tile.")
+ return TRUE
+ T.use(1)
+ playsound(src, 'sound/items/Deconstruct.ogg', 80, 1)
+ ChangeTurf(/turf/simulated/floor, FALSE, FALSE, TRUE)
+ return TRUE
+
+ else if (istype(C, /obj/item/gun/energy/plasmacutter))
return
else
- ..()
+ return ..()
/turf/simulated/floor/exoplanet/ex_act(severity)
switch(severity)
@@ -53,14 +69,14 @@
if(prob(40))
ChangeTurf(get_base_turf_by_area(src))
-/turf/simulated/floor/exoplanet/Initialize()
+/turf/simulated/floor/exoplanet/Initialize(mapload, added_to_area_cache)
. = ..()
update_icon(1)
/turf/simulated/floor/exoplanet/on_update_icon(update_neighbors)
- overlays.Cut()
+ ClearOverlays()
if(LAZYLEN(decals))
- overlays += decals
+ AddOverlays(decals)
for(var/direction in GLOB.cardinal)
var/turf/turf_to_check = get_step(src,direction)
if(!istype(turf_to_check, type))
@@ -75,7 +91,7 @@
rock_side.pixel_x += world.icon_size
if(WEST)
rock_side.pixel_x -= world.icon_size
- overlays += rock_side
+ AddOverlays(rock_side)
else if(update_neighbors)
turf_to_check.update_icon()
@@ -94,11 +110,12 @@
footstep_type = /singleton/footsteps/water
var/reagent_type = /datum/reagent/water
-/turf/simulated/floor/exoplanet/water/shallow/attackby(obj/item/O, mob/living/user)
+/turf/simulated/floor/exoplanet/water/shallow/use_tool(obj/item/O, mob/living/user, list/click_params)
var/obj/item/reagent_containers/RG = O
if (reagent_type && istype(RG) && RG.is_open_container() && RG.reagents)
RG.reagents.add_reagent(reagent_type, min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this))
- user.visible_message(SPAN_NOTICE("[user] fills \the [RG] from \the [src]."),SPAN_NOTICE("You fill \the [RG] from \the [src]."))
+ user.visible_message(SPAN_NOTICE("[user] fills [RG] from [src]."),SPAN_NOTICE("You fill [RG] from [src]."))
+ return TRUE
else
return ..()
@@ -122,7 +139,7 @@
dirt_color = "#e3e7e8"
footstep_type = /singleton/footsteps/snow
-/turf/simulated/floor/exoplanet/snow/Initialize()
+/turf/simulated/floor/exoplanet/snow/Initialize(mapload, added_to_area_cache)
. = ..()
icon_state = pick("snow[rand(1,12)]","snow0")
@@ -142,10 +159,10 @@
color = "#799c4b"
footstep_type = /singleton/footsteps/grass
-/turf/simulated/floor/exoplanet/grass/Initialize()
+/turf/simulated/floor/exoplanet/grass/Initialize(mapload, added_to_area_cache)
. = ..()
if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
if(istype(E) && E.grass_color)
color = E.grass_color
if(!resources)
@@ -174,7 +191,7 @@
dirt_color = "#ae9e66"
footstep_type = /singleton/footsteps/sand
-/turf/simulated/floor/exoplanet/desert/Initialize()
+/turf/simulated/floor/exoplanet/desert/Initialize(mapload, added_to_area_cache)
. = ..()
icon_state = "desert[rand(0,5)]"
@@ -196,11 +213,11 @@
icon_state = "concrete"
/turf/simulated/floor/exoplanet/concrete/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(burnt)
- overlays |= get_damage_overlay("burned[(x + y) % 3]", BLEND_MULTIPLY)
+ AddOverlays(get_damage_overlay("burned[(x + y) % 3]", BLEND_MULTIPLY))
if(broken)
- overlays |= get_damage_overlay("broken[(x + y) % 5]", BLEND_MULTIPLY)
+ AddOverlays(get_damage_overlay("broken[(x + y) % 5]", BLEND_MULTIPLY))
/turf/simulated/floor/exoplanet/concrete/melt()
burnt = TRUE
@@ -216,10 +233,11 @@
dynamic_lighting = FALSE
icon = null
icon_state = null
+ permit_ao = FALSE
-/turf/simulated/planet_edge/Initialize()
+/turf/simulated/planet_edge/Initialize(mapload, added_to_area_cache)
. = ..()
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
if(!istype(E))
return
var/nx = x
@@ -239,18 +257,20 @@
vis_contents = list(NT)
//Need to put a mouse-opaque overlay there to prevent people turning/shooting towards ACTUAL location of vis_content things
- var/obj/effect/overlay/O = new(src)
+ var/obj/overlay/O = new(src)
O.mouse_opacity = 2
O.name = "distant terrain"
O.desc = "You need to come over there to take a better look."
/turf/simulated/planet_edge/Bumped(atom/movable/A)
. = ..()
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
if(!istype(E))
return
+
if(E.planetary_area && istype(loc, world.area))
- ChangeArea(src, E.planetary_area)
+ change_area(E.planetary_area)
+
var/new_x = A.x
var/new_y = A.y
if(x <= TRANSITIONEDGE)
diff --git a/code/modules/overmap/overmap_object.dm b/code/modules/overmap/overmap_object.dm
index 0b12570569c3b..d649007c06397 100644
--- a/code/modules/overmap/overmap_object.dm
+++ b/code/modules/overmap/overmap_object.dm
@@ -1,56 +1,123 @@
-/obj/effect/overmap
+/obj/overmap
name = "map object"
icon = 'icons/obj/overmap.dmi'
icon_state = "object"
color = "#fffffe"
- var/known = TRUE //shows up on nav computers automatically
- var/scannable //if set to TRUE will show up on ship sensors for detailed scans
+ var/scannable // If set to TRUE will show up on ship sensors for detailed scans, and will ping when detected by scanners.
+
+ var/unknown_id // A unique identifier used when this entity is scanned. Assigned in Initialize().
+
+ var/requires_contact = FALSE // Whether or not the effect must be identified by ship sensors before being seen.
+ var/instant_contact = FALSE // Do we instantly identify ourselves to any ship in sensors range?
+ var/sensor_visibility = 20 // How much it increases identification process each scan.
+
+ var/list/known_ships = list() //List of ships known at roundstart - put types here.
+
+ /// The list of scans that can be performed on this overmap effect. See /datum/sector_scan for more info.
+ var/list/scans = list()
+ ///Used for generating unique keys for the associated list 'scans'
+ var/next_id = 0
//Overlay of how this object should look on other skyboxes
-/obj/effect/overmap/proc/get_skybox_representation()
+/obj/overmap/proc/get_skybox_representation()
return
-/obj/effect/overmap/proc/get_scan_data(mob/user)
- return desc
+/obj/overmap/proc/get_scan_data(mob/user)
+ var/temp_data = list()
+ for(var/id in scans)
+ var/datum/sector_scan/scan = scans[id]
+ if (!scan.required_skill || user.skill_check(scan.required_skill, scan.required_skill_level))
+ temp_data += scan.description
+ else if (scan.low_skill_description)
+ temp_data += scan.low_skill_description
+
+ return temp_data
-/obj/effect/overmap/Initialize()
+/obj/overmap/Initialize()
. = ..()
+ add_scan_data("base_scan", desc)
+
if(!GLOB.using_map.use_overmap)
return INITIALIZE_HINT_QDEL
- if(known)
- layer = ABOVE_LIGHTING_LAYER
- plane = EFFECTS_ABOVE_LIGHTING_PLANE
+ if(scannable)
+ unknown_id = "[pick(GLOB.phonetic_alphabet)]-[random_id(/obj/overmap, 100, 999)]"
+
+ if(requires_contact)
+ invisibility = INVISIBILITY_OVERMAP // Effects that require identification have their images cast to the client via sensors.
update_icon()
-/obj/effect/overmap/Crossed(obj/effect/overmap/visitable/other)
+/obj/overmap/Crossed(obj/overmap/visitable/other)
if(istype(other))
- for(var/obj/effect/overmap/visitable/O in loc)
+ for(var/obj/overmap/visitable/O in loc)
SSskybox.rebuild_skyboxes(O.map_z)
-/obj/effect/overmap/Uncrossed(obj/effect/overmap/visitable/other)
+/obj/overmap/Uncrossed(obj/overmap/visitable/other)
if(istype(other))
SSskybox.rebuild_skyboxes(other.map_z)
- for(var/obj/effect/overmap/visitable/O in loc)
+ for(var/obj/overmap/visitable/O in loc)
SSskybox.rebuild_skyboxes(O.map_z)
-/obj/effect/overmap/on_update_icon()
+/obj/overmap/on_update_icon()
filters = filter(type="drop_shadow", color = color + "F0", size = 2, offset = 1,x = 0, y = 0)
/**
* Flags the effect as `known` and runs relevant update procs. Intended for admin event usage.
*/
-/obj/effect/overmap/proc/make_known(notify = FALSE)
- if (!known)
- known = TRUE
+/obj/overmap/visitable/proc/make_known(notify = FALSE)
+ if (!HAS_FLAGS(sector_flags, OVERMAP_SECTOR_KNOWN))
+ sector_flags = OVERMAP_SECTOR_KNOWN
update_known_connections(notify)
/**
* Runs any relevant code needed for updating anything connected to known overmap effects, such as helms.
*/
-/obj/effect/overmap/proc/update_known_connections(notify = FALSE)
+/obj/overmap/proc/update_known_connections(notify = FALSE)
return
+
+/obj/overmap/proc/add_scan_data(id, description, low_skill_description, required_skill, required_skill_level)
+
+ var/datum/sector_scan/new_scan = new()
+ //If id isn't specified, generate unique-ish one
+ if(!id)
+ id = "scan_data_[next_id++]"
+
+ if (scans[id])
+ log_debug("Tried to add a scan with an id that already exists: [id]")
+ return FALSE
+
+ new_scan.id = id
+ new_scan.description = description
+ new_scan.low_skill_description = low_skill_description
+ new_scan.required_skill = required_skill
+ new_scan.required_skill_level = required_skill_level
+
+ scans[id] = new_scan
+
+ return TRUE
+
+/obj/overmap/proc/remove_scan_data(id)
+ if(!scans[id])
+ return FALSE
+
+ var/datum/scan = scans[id]
+ scans -= id
+ qdel(scan)
+
+ return TRUE
+
+/datum/sector_scan
+ /// The id of the scan. Used for referencing the scan in the linked overmap effect's 'scans' list.
+ var/id = "Sector Scan"
+ /// The description of the scan. This is what will be shown to the player when they scan the sector.
+ var/description = "A scan of the sector."
+ /// The description of the scan if the player doesn't have the required skill to see the normal description.
+ var/low_skill_description = "A scan of the sector. You can't make out much."
+ /// The skill required to see the normal description.
+ var/required_skill = SKILL_SCIENCE
+ /// The level of the skill required to see the normal description.
+ var/required_skill_level = SKILL_TRAINED
diff --git a/code/modules/overmap/overmap_shuttle.dm b/code/modules/overmap/overmap_shuttle.dm
index 95265ee141532..7e971cfacf0ba 100644
--- a/code/modules/overmap/overmap_shuttle.dm
+++ b/code/modules/overmap/overmap_shuttle.dm
@@ -1,7 +1,7 @@
#define waypoint_sector(waypoint) map_sectors["[waypoint.z]"]
/datum/shuttle/autodock/overmap
- warmup_time = 10
+ warmup_time = 10 SECONDS
var/range = 0 //how many overmap tiles can shuttle go, for picking destinations and returning.
var/fuel_consumption = 0 //Amount of moles of gas consumed per trip; If zero, then shuttle is magic and does not need fuel
@@ -11,7 +11,7 @@
var/skill_needed = SKILL_BASIC
var/operator_skill = SKILL_MIN
-/datum/shuttle/autodock/overmap/New(_name, obj/effect/shuttle_landmark/start_waypoint)
+/datum/shuttle/autodock/overmap/New(_name, obj/shuttle_landmark/start_waypoint)
..(_name, start_waypoint)
refresh_fuel_ports_list()
@@ -45,8 +45,11 @@
return ..() && can_go()
/datum/shuttle/autodock/overmap/get_travel_time()
- var/distance_mod = get_dist(waypoint_sector(current_location),waypoint_sector(next_location))
- var/skill_mod = 0.2*(skill_needed - operator_skill)
+ var/distance_mod = 0
+ if(current_location != next_location)
+ distance_mod = get_dist(waypoint_sector(current_location), waypoint_sector(next_location))
+
+ var/skill_mod = 0.2 * (skill_needed - operator_skill)
return move_time * (1 + distance_mod + skill_mod)
/datum/shuttle/autodock/overmap/process_launch()
@@ -56,15 +59,15 @@
set_destination(places[place])
..()
-/datum/shuttle/autodock/overmap/proc/set_destination(obj/effect/shuttle_landmark/A)
+/datum/shuttle/autodock/overmap/proc/set_destination(obj/shuttle_landmark/A)
if(A != current_location)
next_location = A
/datum/shuttle/autodock/overmap/proc/get_possible_destinations()
var/list/res = list()
- for (var/obj/effect/overmap/visitable/S in range(get_turf(waypoint_sector(current_location)), range))
+ for (var/obj/overmap/visitable/S in range(get_turf(waypoint_sector(current_location)), range))
var/list/waypoints = S.get_waypoints(name)
- for(var/obj/effect/shuttle_landmark/LZ in waypoints)
+ for(var/obj/shuttle_landmark/LZ in waypoints)
if(LZ.is_valid(src))
res["[waypoints[LZ]] - [LZ.name]"] = LZ
return res
@@ -143,23 +146,40 @@
else
icon_state = icon_closed
-/obj/structure/fuel_port/attackby(obj/item/W as obj, mob/user as mob)
- if(isCrowbar(W))
- if(opened)
- to_chat(user, "You tightly shut \the [src] door.")
- playsound(src.loc, 'sound/effects/locker_close.ogg', 25, 0, -3)
- opened = 0
- else
- to_chat(user, "You open up \the [src] door.")
- playsound(src.loc, 'sound/effects/locker_open.ogg', 15, 1, -3)
- opened = 1
- else if(istype(W,/obj/item/tank))
- if(!opened)
- to_chat(user, "\The [src] door is still closed!")
- return
- if(length(contents) == 0)
- user.unEquip(W, src)
+/obj/structure/fuel_port/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ opened = !opened
+ playsound(src, opened ? 'sound/effects/locker_open.ogg' : 'sound/effects/locker_close.ogg', 50, TRUE)
update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] [opened ? "opens" : "closes"] [src] with [tool]."),
+ SPAN_NOTICE("You [opened ? "open" : "close"] [src] with [tool].")
+ )
+
+/obj/structure/fuel_port/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Tank - Insert tank
+ if (istype(tool, /obj/item/tank))
+ if (!opened)
+ USE_FEEDBACK_FAILURE("[src] needs to be opened before you can insert [tool].")
+ return TRUE
+ var/obj/item/tank/tank = locate() in src
+ if (tank)
+ USE_FEEDBACK_FAILURE("[src] already has [tank] installed.")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] inserts [tool] in [src]."),
+ SPAN_NOTICE("You insert [tool] in [src].")
+ )
+ return TRUE
+
+ return ..()
+
/obj/structure/fuel_port/attack_robot(mob/user)
if (Adjacent(user))
diff --git a/code/modules/overmap/sectors.dm b/code/modules/overmap/sectors.dm
index 74485677f236d..c6b295d299494 100644
--- a/code/modules/overmap/sectors.dm
+++ b/code/modules/overmap/sectors.dm
@@ -1,7 +1,8 @@
+GLOBAL_LIST_EMPTY(known_overmap_sectors)
//===================================================================================
//Overmap object representing zlevel(s)
//===================================================================================
-/obj/effect/overmap/visitable
+/obj/overmap/visitable
name = "map object"
scannable = TRUE
@@ -17,15 +18,18 @@
var/start_x //Coordinates for self placing
var/start_y //will use random values if unset
- var/base = 0 //starting sector, counts as station_levels
- var/in_space = TRUE //can be accessed via lucky EVA
+ var/sector_flags = OVERMAP_SECTOR_IN_SPACE
var/hide_from_reports = FALSE
+ var/randomize_location = TRUE
+
/// null | num | list. If a num or a (num, num) list, the radius or random bounds for placing this sector near the main map's overmap icon.
var/list/place_near_main
-/obj/effect/overmap/visitable/Initialize()
+ var/blob_count = 0
+
+/obj/overmap/visitable/Initialize()
. = ..()
if(. == INITIALIZE_HINT_QDEL)
return
@@ -36,23 +40,31 @@
if(!GLOB.using_map.overmap_z)
build_overmap()
- var/map_low = OVERMAP_EDGE
- var/map_high = GLOB.using_map.overmap_size - OVERMAP_EDGE
- var/turf/home
- if (place_near_main)
- var/obj/effect/overmap/visitable/main = map_sectors["1"]
- if (islist(place_near_main))
- place_near_main = Roundm(Frand(place_near_main[1], place_near_main[2]), 0.1)
- home = CircularRandomTurfAround(main, abs(place_near_main), map_low, map_low, map_high, map_high)
- log_debug("place_near_main moving [src] near [main] ([main.x],[main.y]) with radius [place_near_main], got ([home.x],[home.y])")
- else
- start_x = start_x || rand(map_low, map_high)
- start_y = start_y || rand(map_low, map_high)
- home = locate(start_x, start_y, GLOB.using_map.overmap_z)
- forceMove(home)
+ if (randomize_location)
+ var/map_low = OVERMAP_EDGE
+ var/map_high = GLOB.using_map.overmap_size - OVERMAP_EDGE
+ var/turf/home
+ if (place_near_main)
+ var/obj/overmap/visitable/main = map_sectors["1"]
+ if (islist(place_near_main))
+ place_near_main = Roundm(Frand(place_near_main[1], place_near_main[2]), 0.1)
+ home = CircularRandomTurfAround(main, abs(place_near_main), map_low, map_low, map_high, map_high)
+ log_debug("place_near_main moving [src] near [main] ([main.x],[main.y]) with radius [place_near_main], got ([home.x],[home.y])")
+ else
+ start_x = start_x || rand(map_low, map_high)
+ start_y = start_y || rand(map_low, map_high)
+ home = locate(start_x, start_y, GLOB.using_map.overmap_z)
+ forceMove(home)
- for(var/obj/effect/overmap/event/E in loc)
- qdel(E)
+ for(var/obj/overmap/event/E in loc)
+ qdel(E)
+
+ if(HAS_FLAGS(sector_flags, OVERMAP_SECTOR_KNOWN))
+ LAZYADD(GLOB.known_overmap_sectors, src)
+ layer = ABOVE_LIGHTING_LAYER
+ plane = EFFECTS_ABOVE_LIGHTING_PLANE
+ for(var/obj/machinery/computer/ship/helm/H as anything in GLOB.overmap_helm_computers)
+ H.add_known_sector(src)
docking_codes = "[ascii2text(rand(65,90))][ascii2text(rand(65,90))][ascii2text(rand(65,90))][ascii2text(rand(65,90))]"
@@ -61,50 +73,55 @@
LAZYADD(SSshuttle.sectors_to_initialize, src) //Queued for further init. Will populate the waypoint lists; waypoints not spawned yet will be added in as they spawn.
SSshuttle.clear_init_queue()
+
+/obj/overmap/visitable/Destroy()
+ LAZYREMOVE(GLOB.known_overmap_sectors, src)
+ . = ..()
+
//This is called later in the init order by SSshuttle to populate sector objects. Importantly for subtypes, shuttles will be created by then.
-/obj/effect/overmap/visitable/proc/populate_sector_objects()
+/obj/overmap/visitable/proc/populate_sector_objects()
-/obj/effect/overmap/visitable/proc/get_areas()
- return get_filtered_areas(list(/proc/area_belongs_to_zlevels = map_z))
+/obj/overmap/visitable/proc/get_areas()
+ return get_filtered_areas(list(GLOBAL_PROC_REF(area_belongs_to_zlevels) = map_z))
-/obj/effect/overmap/visitable/proc/find_z_levels()
+/obj/overmap/visitable/proc/find_z_levels()
map_z = GetConnectedZlevels(z)
-/obj/effect/overmap/visitable/proc/register_z_levels()
+/obj/overmap/visitable/proc/register_z_levels()
for(var/zlevel in map_z)
map_sectors["[zlevel]"] = src
GLOB.using_map.player_levels |= map_z
- if(!in_space)
+ if(!HAS_FLAGS(sector_flags, OVERMAP_SECTOR_IN_SPACE))
GLOB.using_map.sealed_levels |= map_z
- if(base)
+ if(HAS_FLAGS(sector_flags, OVERMAP_SECTOR_BASE))
GLOB.using_map.station_levels |= map_z
GLOB.using_map.contact_levels |= map_z
GLOB.using_map.map_levels |= map_z
//Helper for init.
-/obj/effect/overmap/visitable/proc/check_ownership(obj/object)
+/obj/overmap/visitable/proc/check_ownership(obj/object)
if((object.z in map_z) && !(get_area(object) in SSshuttle.shuttle_areas))
return 1
//If shuttle_name is false, will add to generic waypoints; otherwise will add to restricted. Does not do checks.
-/obj/effect/overmap/visitable/proc/add_landmark(obj/effect/shuttle_landmark/landmark, shuttle_name)
+/obj/overmap/visitable/proc/add_landmark(obj/shuttle_landmark/landmark, shuttle_name)
landmark.sector_set(src, shuttle_name)
if(shuttle_name)
LAZYADD(restricted_waypoints[shuttle_name], landmark)
else
generic_waypoints += landmark
-/obj/effect/overmap/visitable/proc/remove_landmark(obj/effect/shuttle_landmark/landmark, shuttle_name)
+/obj/overmap/visitable/proc/remove_landmark(obj/shuttle_landmark/landmark, shuttle_name)
if(shuttle_name)
var/list/shuttles = restricted_waypoints[shuttle_name]
LAZYREMOVE(shuttles, landmark)
else
generic_waypoints -= landmark
-/obj/effect/overmap/visitable/proc/get_waypoints(shuttle_name)
+/obj/overmap/visitable/proc/get_waypoints(shuttle_name)
. = list()
- for(var/obj/effect/overmap/visitable/contained in src)
+ for(var/obj/overmap/visitable/contained in src)
. += contained.get_waypoints(shuttle_name)
for(var/thing in generic_waypoints)
.[thing] = name
@@ -112,33 +129,46 @@
for(var/thing in restricted_waypoints[shuttle_name])
.[thing] = name
-/obj/effect/overmap/visitable/proc/generate_skybox()
+/obj/overmap/visitable/proc/generate_skybox()
return
-/obj/effect/overmap/visitable/sector
+/obj/overmap/visitable/MouseEntered(location, control, params)
+ openToolTip(user = usr, tip_src = src, params = params, title = name)
+ ..()
+
+/obj/overmap/visitable/MouseDown()
+ closeToolTip(usr) //No reason not to, really
+ ..()
+
+/obj/overmap/visitable/MouseExited()
+ closeToolTip(usr) //No reason not to, really
+ ..()
+
+/obj/overmap/visitable/sector
name = "generic sector"
desc = "Sector with some stuff in it."
icon_state = "sector"
+ requires_contact = TRUE
anchored = TRUE
-/obj/effect/overmap/visitable/sector/Initialize()
+/obj/overmap/visitable/sector/Initialize()
. = ..()
-
- if(known)
- update_known_connections(TRUE)
+ if(HAS_FLAGS(sector_flags, OVERMAP_SECTOR_KNOWN))
+ for(var/obj/machinery/computer/ship/helm/H as anything in GLOB.overmap_helm_computers)
+ update_known_connections(TRUE)
-/obj/effect/overmap/visitable/sector/update_known_connections(notify = FALSE)
+/obj/overmap/visitable/sector/update_known_connections(notify = FALSE)
. = ..()
- for(var/obj/machinery/computer/ship/helm/H in SSmachines.machinery)
+ for(var/obj/machinery/computer/ship/helm/H as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/ship/helm))
H.add_known_sector(src, notify)
// Because of the way these are spawned, they will potentially have their invisibility adjusted by the turfs they are mapped on
// prior to being moved to the overmap. This blocks that. Use set_invisibility to adjust invisibility as needed instead.
-/obj/effect/overmap/visitable/sector/hide()
+/obj/overmap/visitable/sector/hide()
/proc/build_overmap()
if(!GLOB.using_map.use_overmap)
@@ -156,7 +186,8 @@
T = T.ChangeTurf(/turf/unsimulated/map/edge)
else
T = T.ChangeTurf(/turf/unsimulated/map)
- ChangeArea(T, A)
+
+ T.change_area(A)
GLOB.using_map.sealed_levels |= GLOB.using_map.overmap_z
diff --git a/code/modules/overmap/ships/beacon.dm b/code/modules/overmap/ships/beacon.dm
index af58824fa3db9..c23d6b79580e9 100644
--- a/code/modules/overmap/ships/beacon.dm
+++ b/code/modules/overmap/ships/beacon.dm
@@ -1,16 +1,16 @@
/obj/machinery/radio_beacon
name = "transmission beacon"
desc = "A bulky hyperspace transmitter, capable of continuously broadcasting a signal that can be picked up by ship sensors."
- icon = 'icons/obj/structures/beacon.dmi'
- icon_state = "inactive"
+ icon = 'icons/obj/machines/beacon.dmi'
+ icon_state = "beacon"
density = TRUE
anchored = TRUE
idle_power_usage = 0
health_max = 100
active_power_usage = 1 KILOWATTS
construct_state = /singleton/machine_construction/default/panel_closed
- var/obj/effect/overmap/radio/signal
- var/obj/effect/overmap/radio/distress/emergency_signal
+ var/obj/overmap/radio/signal
+ var/obj/overmap/radio/distress/emergency_signal
/// Integer. The `world.time` value of the last distress broadcast.
var/last_message_time = 0
/// Integer. The `world.time` of the last activation toggle.
@@ -19,7 +19,7 @@
var/const/activation_frequency = 1 MINUTE
/obj/item/stock_parts/circuitboard/radio_beacon
- name = T_BOARD("transmission beacon")
+ name = "circuit board (transmission beacon)"
board_type = "machine"
icon_state = "mcontroller"
build_path = /obj/machinery/radio_beacon
@@ -45,7 +45,7 @@
to_chat(user, SPAN_WARNING("A small red light flashes on \the [src]."))
return
- var/obj/effect/overmap/visitable/O = map_sectors["[get_z(src)]"]
+ var/obj/overmap/visitable/O = map_sectors["[get_z(src)]"]
if(!O)
to_chat(user, SPAN_WARNING("You cannot deploy \the [src] here."))
return
@@ -79,7 +79,7 @@
activate_distress()
/obj/machinery/radio_beacon/proc/activate()
- var/obj/effect/overmap/visitable/O = map_sectors["[get_z(src)]"]
+ var/obj/overmap/visitable/O = map_sectors["[get_z(src)]"]
var/message = sanitize(input("What should it broadcast?") as message|null)
if(!message)
return
@@ -99,7 +99,7 @@
update_icon()
/obj/machinery/radio_beacon/proc/activate_distress()
- var/obj/effect/overmap/visitable/O = map_sectors["[get_z(src)]"]
+ var/obj/overmap/visitable/O = map_sectors["[get_z(src)]"]
visible_message(SPAN_WARNING("\The [src] beeps urgently as it whirrs to life, sending out intermittent tones."))
@@ -138,59 +138,65 @@
deactivate()
/obj/machinery/radio_beacon/on_update_icon()
- overlays.Cut()
- icon_state = signal ? "active" : "inactive"
- if(emergency_signal)
- overlays += "distress"
+ ClearOverlays()
if(panel_open)
- overlays += "panel"
- . = ..()
+ AddOverlays("[icon_state]_panel")
+ if(is_powered())
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights"))
+ AddOverlays("[icon_state]_lights")
+ if(signal)
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights_active"))
+ AddOverlays("[icon_state]_lights_active")
+ else if(emergency_signal)
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights_distress"))
+ AddOverlays("[icon_state]_lights_distress")
+
/obj/machinery/radio_beacon/Destroy()
QDEL_NULL(signal)
QDEL_NULL(emergency_signal)
. = ..()
-/obj/effect/overmap/radio
+/obj/overmap/radio
name = "radio signal"
icon_state = "radio"
scannable = TRUE
color = COLOR_AMBER
var/message
- var/obj/effect/overmap/source
+ var/obj/overmap/source
-/obj/effect/overmap/radio/get_scan_data(mob/user)
- return "A radio signal originating at \the [source].
\
+/obj/overmap/radio/get_scan_data(mob/user)
+ return list("A radio signal originating at \the [source].
\
---BEGINNING OF TRANSMISSION---
\
[message] \
-
---END OF TRANSMISSION---"
+
---END OF TRANSMISSION---")
-/obj/effect/overmap/radio/proc/set_origin(obj/effect/overmap/origin)
- GLOB.moved_event.register(origin, src, /obj/effect/overmap/radio/proc/follow)
- GLOB.destroyed_event.register(origin, src, /datum/proc/qdel_self)
+/obj/overmap/radio/proc/set_origin(obj/overmap/origin)
+ GLOB.moved_event.register(origin, src, TYPE_PROC_REF(/obj/overmap/radio, follow))
+ GLOB.destroyed_event.register(origin, src, TYPE_PROC_REF(/datum, qdel_self))
forceMove(origin.loc)
source = origin
pixel_x = -(origin.bound_width - 6)
pixel_y = origin.bound_height - 6
-/obj/effect/overmap/radio/proc/follow(atom/movable/am, old_loc, new_loc)
+/obj/overmap/radio/proc/follow(atom/movable/am, old_loc, new_loc)
forceMove(new_loc)
-/obj/effect/overmap/radio/Destroy()
+/obj/overmap/radio/Destroy()
GLOB.destroyed_event.unregister(source, src)
GLOB.moved_event.unregister(source, src)
source = null
. = ..()
-/obj/effect/overmap/radio/distress
+/obj/overmap/radio/distress
name = "distress dataspike"
icon_state = "radio"
color = COLOR_NT_RED
-/obj/effect/overmap/radio/distress/get_scan_data(mob/user)
- return "A unilateral, broadband data broadcast originating at \the [source] carrying only an emergency code sequence."
+/obj/overmap/radio/distress/get_scan_data(mob/user)
+ return list("A unilateral, broadband data broadcast originating at \the [source] carrying only an emergency code sequence.")
-/obj/effect/overmap/radio/distress/Initialize()
+/obj/overmap/radio/distress/Initialize()
..()
- for(var/obj/machinery/computer/ship/helm/H in SSmachines.machinery)
+ for(var/obj/machinery/computer/ship/helm/H as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/ship/helm))
H.visible_message(SPAN_WARNING("\the [H] pings uneasily as it detects a distress signal."))
playsound(H, 'sound/machines/sensors/newcontact.ogg', 50, 3, 3)
diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm
index 82ad2bacc3e7a..774d2f6922d86 100644
--- a/code/modules/overmap/ships/computers/helm.dm
+++ b/code/modules/overmap/ships/computers/helm.dm
@@ -1,5 +1,7 @@
LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
+GLOBAL_LIST_EMPTY(overmap_helm_computers)
+
/obj/machinery/computer/ship/helm
name = "helm control console"
icon_keyboard = "teleport_key"
@@ -21,15 +23,15 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
/obj/machinery/computer/ship/helm/Initialize()
. = ..()
- get_known_sectors()
+ LAZYADD(GLOB.overmap_helm_computers, src)
+ for(var/obj/overmap/visitable/sector as anything in GLOB.known_overmap_sectors)
+ add_known_sector(sector)
-/obj/machinery/computer/ship/helm/proc/get_known_sectors()
- var/area/overmap/map = locate() in world
- for(var/obj/effect/overmap/visitable/sector/S in map)
- if (S.known)
- add_known_sector(S)
+/obj/machinery/computer/ship/helm/Destroy()
+ . = ..()
+ LAZYREMOVE(GLOB.overmap_helm_computers, src)
-/obj/machinery/computer/ship/helm/proc/add_known_sector(obj/effect/overmap/visitable/sector/S, notify = FALSE)
+/obj/machinery/computer/ship/helm/proc/add_known_sector(obj/overmap/visitable/sector/S, notify = FALSE)
var/datum/computer_file/data/waypoint/R = new()
R.fields["name"] = S.name
R.fields["x"] = S.x
@@ -94,7 +96,7 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
display_reconnect_dialog(user, "helm")
else
var/turf/T = get_turf(linked)
- var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
+ var/obj/overmap/visitable/sector/current_sector = locate() in T
var/mob/living/silicon/silicon = user
data["viewing_silicon"] = ismachinerestricted(silicon)
@@ -123,7 +125,7 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
data["speed"] = speed
if(linked.get_speed())
- data["ETAnext"] = "[round(linked.ETA()/10)] seconds"
+ data["ETAnext"] = "[round(linked.ETA()/SSovermap.wait)] seconds"
else
data["ETAnext"] = "N/A"
@@ -186,14 +188,14 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
qdel(R)
if (href_list["setx"])
- var/newx = input("Input new destiniation x coordinate", "Coordinate input", dx) as num|null
+ var/newx = input("Input new destination x coordinate", "Coordinate input", dx) as num|null
if(!CanInteract(user,state))
return
if (newx)
dx = clamp(newx, 1, world.maxx)
if (href_list["sety"])
- var/newy = input("Input new destiniation y coordinate", "Coordinate input", dy) as num|null
+ var/newy = input("Input new destination y coordinate", "Coordinate input", dy) as num|null
if(!CanInteract(user,state))
return
if (newy)
@@ -219,6 +221,8 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
if (href_list["move"])
var/ndir = text2num(href_list["move"])
+ if(prob(user.is_confused() * 5))
+ ndir = turn(ndir, pick(45, -45))
if(prob(user.skill_fail_chance(SKILL_PILOT, 50, linked.skill_needed, factor = 1)))
ndir = turn(ndir,pick(90,-90))
linked.relaymove(user, ndir, accellimit)
@@ -340,7 +344,7 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
var/turf/T = get_turf(linked)
- var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
+ var/obj/overmap/visitable/sector/current_sector = locate() in T
var/mob/living/silicon/silicon = user
data["viewing_silicon"] = ismachinerestricted(silicon)
@@ -355,7 +359,7 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
data["viewing"] = viewing_overmap(user)
if(linked.get_speed())
- data["ETAnext"] = "[round(linked.ETA()/10)] seconds"
+ data["ETAnext"] = "[round(linked.ETA()/SSovermap.wait)] seconds"
else
data["ETAnext"] = "N/A"
@@ -389,4 +393,4 @@ LEGACY_RECORD_STRUCTURE(all_waypoints, waypoint)
set_light(0)
else
icon_state = "tele_nav"
- set_light(light_max_bright_on, light_inner_range_on, light_outer_range_on, 2, light_color)
+ set_light(light_range_on, light_power_on, light_color)
diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm
index 311ddde442215..4229ff0981fb4 100644
--- a/code/modules/overmap/ships/computers/sensors.dm
+++ b/code/modules/overmap/ships/computers/sensors.dm
@@ -1,3 +1,5 @@
+#define SENSORS_STRENGTH_COEFFICIENT 7
+
/obj/machinery/computer/ship/sensors
name = "sensors console"
icon_keyboard = "teleport_key"
@@ -8,65 +10,164 @@
machine_name = "sensors console"
machine_desc = "Used to activate, monitor, and configure a spaceship's sensors. Higher range means higher temperature; dangerously high temperatures may fry the delicate equipment."
health_max = 100
- var/obj/machinery/shipsensors/sensors
+ var/weakref/sensor_ref
var/list/last_scan
+ var/muted = FALSE
+ var/sound_off = FALSE
var/print_language = LANGUAGE_HUMAN_EURO
+ var/working_sound = 'sound/machines/sensors/sensorloop.ogg'
+ var/datum/sound_token/sound_token
+ var/sound_id
+
+
+/obj/machinery/computer/ship/sensors/proc/get_sensors()
+ var/obj/machinery/shipsensors/sensors = sensor_ref?.resolve()
+ if (!istype(sensors) || QDELETED(sensors))
+ sensor_ref = null
+ return sensors
+
/obj/machinery/computer/ship/sensors/spacer
construct_state = /singleton/machine_construction/default/panel_closed/computer/no_deconstruct
base_type = /obj/machinery/computer/ship/sensors
print_language = LANGUAGE_SPACER
-/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/effect/overmap/visitable/ship/sector)
- if(!(. = ..()))
+
+/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/overmap/visitable/ship/sector)
+ if (!(. = ..()))
return
find_sensors()
+
+/obj/machinery/computer/ship/sensors/Process()
+ ..()
+ update_sound()
+
+
+/obj/machinery/computer/ship/sensors/proc/update_sound()
+ if (sound_off)
+ if (sound_token)
+ QDEL_NULL(sound_token)
+ return
+ if (!working_sound)
+ return
+ if (!sound_id)
+ sound_id = "[type]_[sequential_id(/obj/machinery/computer/ship/sensors)]"
+ var/obj/machinery/shipsensors/sensors = get_sensors()
+ if (sensors && linked && sensors.use_power ** sensors.powered())
+ var/volume = 8
+ if (!sound_token)
+ sound_token = GLOB.sound_player.PlayLoopingSound(src, sound_id, working_sound, volume = volume, range = 10)
+ sound_token.SetVolume(volume)
+ else if (sound_token)
+ QDEL_NULL(sound_token)
+
+
+/obj/machinery/computer/ship/sensors/proc/state_visible(text)
+ visible_message(SPAN_NOTICE("\The [src] states, \"[text]\""))
+
+
+/obj/machinery/computer/ship/sensors/proc/alert_unknown_contact(contact_id, bearing, bearing_variability)
+ if (muted)
+ return
+ state_visible("Unknown contact designation '[contact_id]' detected nearby, bearing [bearing], error +/- [bearing_variability]. Beginning trace.")
+ playsound(loc, "sound/machines/sensors/contactgeneric.ogg", 10, 1) //Let players know there's something nearby
+
+
+/obj/machinery/computer/ship/sensors/proc/alert_contact_identified(contact_name, bearing)
+ if (muted)
+ return
+ state_visible("New contact identified, designation [contact_name], bearing [bearing].")
+ playsound(loc, "sound/machines/sensors/newcontact.ogg", 30, 1)
+
+
+/obj/machinery/computer/ship/sensors/proc/alert_contact_lost(contact_name)
+ if (muted)
+ return
+ state_visible("Contact lost with [contact_name].")
+ playsound(loc, "sound/machines/sensors/contact_lost.ogg", 30, 1)
+
+
/obj/machinery/computer/ship/sensors/proc/find_sensors()
- if(!linked)
+ if (!linked)
return
- for(var/obj/machinery/shipsensors/S in SSmachines.machinery)
- if(linked.check_ownership(S))
- sensors = S
+ for (var/obj/machinery/shipsensors/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/shipsensors))
+ if (linked.check_ownership(S))
+ LAZYADD(S.linked_consoles, src)
+ S.link_ship(linked)
+ sensor_ref = weakref(S)
break
+
/obj/machinery/computer/ship/sensors/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- if(!linked)
+ if (!linked)
display_reconnect_dialog(user, "sensors")
return
var/data[0]
+ var/obj/machinery/shipsensors/sensors = get_sensors()
data["viewing"] = viewing_overmap(user)
+ data["muted"] = muted
+ data["sound_off"] = sound_off
var/mob/living/silicon/silicon = user
data["viewing_silicon"] = ismachinerestricted(silicon)
- if(sensors)
+ if (sensors)
data["on"] = sensors.use_power
data["range"] = sensors.range
data["health"] = sensors.get_current_health()
data["max_health"] = sensors.get_max_health()
data["heat"] = sensors.heat
data["critical_heat"] = sensors.critical_heat
- if(sensors.health_dead)
+ if (sensors.health_dead())
data["status"] = "DESTROYED"
- else if(!sensors.powered())
+ else if (!sensors.powered())
data["status"] = "NO POWER"
- else if(!sensors.in_vacuum())
+ else if (!sensors.in_vacuum())
data["status"] = "VACUUM SEAL BROKEN"
else
data["status"] = "OK"
- var/list/contacts = list()
- for(var/obj/effect/overmap/O in view(7,linked))
- if(linked == O)
+ var/list/known_contacts = list()
+ var/list/unknown_contacts = list()
+
+ var/list/potential_contacts = list()
+
+ if (sensors?.use_power)
+ for (var/obj/overmap/nearby in view(round(sensors.range,1), linked))
+ if (nearby.requires_contact) // Some ships require.
+ continue
+ potential_contacts |= nearby
+
+ for (var/obj/overmap/visitable/contact in sensors.objects_in_view)
+ if (contact in sensors.contact_datums)
+ potential_contacts |= contact
+ else
+ var/bearing_variability = round(300/sensors.sensor_strength, 5)
+ unknown_contacts.Add(list(list(
+ "name" = contact.unknown_id,
+ "bearing" = inaccurate_bearing(get_bearing(linked, contact), bearing_variability),
+ "variability" = bearing_variability,
+ "progress" = sensors.objects_in_view[contact]
+ )))
+
+ for (var/obj/overmap/contact in potential_contacts)
+ if (linked == contact)
continue
- if(!O.scannable)
+ if (!contact.scannable)
continue
- var/bearing = round(90 - Atan2(O.x - linked.x, O.y - linked.y),5)
- if(bearing < 0)
- bearing += 360
- contacts.Add(list(list("name"=O.name, "color"= O.get_color(), "ref"="\ref[O]", "bearing"=bearing)))
- if(length(contacts))
- data["contacts"] = contacts
+ known_contacts.Add(list(list(
+ "name" = contact.name,
+ "color" = contact.get_color(),
+ "ref" = "\ref[contact]",
+ "bearing" = get_bearing(linked, contact)
+ )))
+
+ if (length(unknown_contacts))
+ data["unknown_contacts"] = unknown_contacts
+
+ if (length(known_contacts))
+ data["known_contacts"] = known_contacts
+
data["last_scan"] = last_scan
else
data["status"] = "MISSING"
@@ -80,15 +181,16 @@
ui.open()
ui.set_auto_update(1)
+
/obj/machinery/computer/ship/sensors/OnTopic(mob/user, list/href_list, state)
- if(..())
+ if (..())
return TOPIC_HANDLED
if (!linked)
return TOPIC_NOACTION
if (href_list["viewing"])
- if(user)
+ if (user)
viewing_overmap(user) ? unlook(user) : look(user)
return TOPIC_REFRESH
@@ -96,10 +198,19 @@
find_sensors()
return TOPIC_REFRESH
- if(sensors)
+ if (href_list["mute"])
+ muted = !muted
+ return TOPIC_REFRESH
+
+ if (href_list["sound_off"])
+ sound_off = !sound_off
+ return TOPIC_REFRESH
+
+ var/obj/machinery/shipsensors/sensors = get_sensors()
+ if (sensors)
if (href_list["range"])
var/nrange = input("Set new sensors range", "Sensor range", sensors.range) as num|null
- if(!CanInteract(user,state))
+ if (!CanInteract(user,state))
return TOPIC_NOACTION
if (nrange)
sensors.set_range(clamp(round(nrange), 1, world.view))
@@ -109,38 +220,33 @@
return TOPIC_REFRESH
if (href_list["scan"])
- var/obj/effect/overmap/O = locate(href_list["scan"])
- if(istype(O) && !QDELETED(O))
- if((O in view(7,linked)))
+ var/obj/overmap/O = locate(href_list["scan"])
+ if (istype(O) && !QDELETED(O))
+ if ((O in view(7,linked))|| (O in sensors.contact_datums))
playsound(loc, "sound/effects/ping.ogg", 50, 1)
LAZYSET(last_scan, "data", O.get_scan_data(user))
LAZYSET(last_scan, "location", "[O.x],[O.y]")
LAZYSET(last_scan, "name", "[O]")
- to_chat(user, SPAN_NOTICE("Successfully scanned \the [O]."))
+ state_visible("Successfully scanned \the [O].")
return TOPIC_HANDLED
- to_chat(user, SPAN_WARNING("Could not get a scan from \the [O]!"))
+ state_visible(SPAN_WARNING("Could not get a scan from \the [O]!"))
return TOPIC_HANDLED
if (href_list["print"])
playsound(loc, "sound/machines/dotprinter.ogg", 30, 1)
- new/obj/item/paper/(get_turf(src), last_scan["data"], "paper (Sensor Scan - [last_scan["name"]])", L = print_language)
+ var/scan_data = ""
+ for (var/scan in last_scan["data"])
+ scan_data += scan + "\n\n"
+
+ new/obj/item/paper/(get_turf(src), scan_data, "paper (Sensor Scan - [last_scan["name"]])", L = print_language)
return TOPIC_HANDLED
-/obj/machinery/computer/ship/sensors/Process()
- ..()
- if(!linked)
- return
- if(sensors && sensors.use_power && sensors.powered())
- var/sensor_range = round(sensors.range*1.5) + 1
- linked.set_light(1, sensor_range, sensor_range+1)
- else
- linked.set_light(0)
/obj/machinery/shipsensors
name = "sensors suite"
desc = "Long range gravity scanner with various other sensors, used to detect irregularities in surrounding space. Can only run in vacuum to protect delicate quantum BS elements."
- icon = 'icons/obj/stationobjs.dmi'
+ icon = 'icons/obj/machines/shipsensors.dmi'
icon_state = "sensors"
anchored = TRUE
density = TRUE
@@ -148,67 +254,61 @@
health_max = 200
var/critical_heat = 50 // sparks and takes damage when active & above this heat
var/heat_reduction = 1.5 // mitigates this much heat per tick
+ var/sensor_strength //used for detecting ships via contacts
var/heat = 0
var/range = 1
idle_power_usage = 5000
+ base_type = /obj/machinery/shipsensors
+ maximum_component_parts = list(/obj/item/stock_parts = 10) // Circuit, 5 manipulators, 3 subspace shit and 1 tesla coil
+
-/obj/machinery/shipsensors/attackby(obj/item/W, mob/user)
- if (isWelder(W) && user.a_intent != I_HURT)
- var/damage = get_damage_value()
- var/obj/item/weldingtool/WT = W
- if (!damage)
- to_chat(user, SPAN_WARNING("\The [src] doesn't need any repairs."))
- return TRUE
- if (!WT.isOn())
- to_chat(user, SPAN_WARNING("\The [W] needs to be turned on first."))
- return TRUE
- if (!WT.remove_fuel(0,user))
- to_chat(user, SPAN_WARNING("You need more welding fuel to complete this task."))
- return TRUE
- to_chat(user, SPAN_NOTICE("You start repairing the damage to [src]."))
- playsound(src, 'sound/items/Welder.ogg', 100, 1)
- if(do_after(user, max(5, damage / 5), src, DO_REPAIR_CONSTRUCT) && WT?.isOn())
- to_chat(user, SPAN_NOTICE("You finish repairing the damage to [src]."))
- revive_health()
- return TRUE
-
- return ..()
+/obj/machinery/shipsensors/upgraded
+ uncreated_component_parts = list(/obj/item/stock_parts/manipulator/nano = 2)
+
+
+/obj/machinery/shipsensors/RefreshParts()
+ ..()
+ sensor_strength = clamp(total_component_rating_of_type(/obj/item/stock_parts/manipulator), 0, 5) * SENSORS_STRENGTH_COEFFICIENT
/obj/machinery/shipsensors/proc/in_vacuum()
var/turf/T=get_turf(src)
- if(istype(T))
+ if (istype(T))
var/datum/gas_mixture/environment = T.return_air()
- if(environment && environment.return_pressure() > MINIMUM_PRESSURE_DIFFERENCE_TO_SUSPEND)
+ if (environment && environment.return_pressure() > MINIMUM_PRESSURE_DIFFERENCE_TO_SUSPEND)
return 0
return 1
+
/obj/machinery/shipsensors/on_update_icon()
- overlays.Cut()
- if(use_power)
- icon_state = "sensors"
- if(health_dead)
+ ClearOverlays()
+ if (panel_open)
+ AddOverlays("[icon_state]_panel")
+ if (use_power)
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working"))
+ AddOverlays("[icon_state]_lights_working")
+ if (health_dead())
icon_state = "sensors_broken"
- else
- icon_state = "sensors_off"
- if(panel_open)
- overlays += "sensors_panel"
. = ..()
+
/obj/machinery/shipsensors/proc/toggle()
- if(!use_power && (health_dead || !in_vacuum()))
+ if (!use_power && (health_dead() || !in_vacuum()))
return // No turning on if broken or misplaced.
- if(!use_power) //need some juice to kickstart
+ if (!use_power) //need some juice to kickstart
use_power_oneoff(idle_power_usage*5)
update_use_power(!use_power)
+ power_change()
queue_icon_update()
+
/obj/machinery/shipsensors/Process()
- if(use_power) //can't run in non-vacuum
- if(!in_vacuum())
+ ..()
+ if (use_power) //can't run in non-vacuum
+ if (!in_vacuum())
toggle()
- if(heat > critical_heat)
+ if (heat > critical_heat)
src.visible_message(SPAN_DANGER("\The [src] violently spews out sparks!"))
- var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ var/datum/effect/spark_spread/s = new /datum/effect/spark_spread
s.set_up(3, 1, src)
s.start()
@@ -219,40 +319,46 @@
if (heat > 0)
heat = max(0, heat - heat_reduction)
+
/obj/machinery/shipsensors/power_change()
. = ..()
- if(use_power && !powered())
+ if (use_power && !powered())
toggle()
+
/obj/machinery/shipsensors/proc/set_range(nrange)
range = nrange
change_power_consumption(1500 * (range**2), POWER_USE_IDLE) //Exponential increase, also affects speed of overheating
+
/obj/machinery/shipsensors/emp_act(severity)
if (use_power)
toggle()
..()
+
/obj/machinery/shipsensors/on_death()
if (use_power)
toggle()
..()
+
/obj/machinery/shipsensors/RefreshParts()
..()
heat_reduction = round(total_component_rating_of_type(/obj/item/stock_parts/manipulator) / 3)
+
/obj/item/stock_parts/circuitboard/shipsensors
- name = T_BOARD("broad-band sensor suite")
+ name = "circuit board (broad-band sensor suite)"
board_type = "machine"
icon_state = "mcontroller"
build_path = /obj/machinery/shipsensors
origin_tech = list(TECH_POWER = 3, TECH_ENGINEERING = 5, TECH_BLUESPACE = 3)
req_components = list(
- /obj/item/stock_parts/subspace/ansible = 1,
- /obj/item/stock_parts/subspace/filter = 1,
- /obj/item/stock_parts/subspace/treatment = 1,
- /obj/item/stock_parts/manipulator = 3)
- additional_spawn_components = list(
- /obj/item/stock_parts/power/apc/buildable = 1
+ /obj/item/stock_parts/subspace/ansible = 1,
+ /obj/item/stock_parts/subspace/filter = 1,
+ /obj/item/stock_parts/subspace/treatment = 1,
+ /obj/item/stock_parts/manipulator = 3
)
+
+#undef SENSORS_STRENGTH_COEFFICIENT
diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm
index 538bc21ecf188..26aa9af73b794 100644
--- a/code/modules/overmap/ships/computers/ship.dm
+++ b/code/modules/overmap/ships/computers/ship.dm
@@ -1,20 +1,21 @@
/*
While these computers can be placed anywhere, they will only function if placed on either a non-space, non-shuttle turf
-with an /obj/effect/overmap/visitable/ship present elsewhere on that z level, or else placed in a shuttle area with an /obj/effect/overmap/visitable/ship
+with an /obj/overmap/visitable/ship present elsewhere on that z level, or else placed in a shuttle area with an /obj/overmap/visitable/ship
somewhere on that shuttle. Subtypes of these can be then used to perform ship overmap movement functions.
*/
/obj/machinery/computer/ship
- var/obj/effect/overmap/visitable/ship/linked
+ var/datum/browser/reconnect_popup
+ var/obj/overmap/visitable/ship/linked
var/list/viewers // Weakrefs to mobs in direct-view mode.
var/extra_view = 0 // how much the view is increased by when the mob is in overmap mode.
// A late init operation called in SSshuttle, used to attach the thing to the right ship.
-/obj/machinery/computer/ship/proc/attempt_hook_up(obj/effect/overmap/visitable/ship/sector)
+/obj/machinery/computer/ship/proc/attempt_hook_up(obj/overmap/visitable/ship/sector)
if(!istype(sector))
return
if(sector.check_ownership(src))
linked = sector
- LAZYSET(linked.consoles, src, TRUE)
+ LAZYADD(linked.consoles, src)
return 1
/obj/machinery/computer/ship/Destroy()
@@ -23,22 +24,23 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
. = ..()
/obj/machinery/computer/ship/proc/sync_linked()
- var/obj/effect/overmap/visitable/ship/sector = map_sectors["[z]"]
+ var/obj/overmap/visitable/ship/sector = map_sectors["[z]"]
if(!sector)
return
return attempt_hook_up_recursive(sector)
-/obj/machinery/computer/ship/proc/attempt_hook_up_recursive(obj/effect/overmap/visitable/ship/sector)
+/obj/machinery/computer/ship/proc/attempt_hook_up_recursive(obj/overmap/visitable/ship/sector)
if(attempt_hook_up(sector))
return sector
- for(var/obj/effect/overmap/visitable/ship/candidate in sector)
+ for(var/obj/overmap/visitable/ship/candidate in sector)
if((. = .(candidate)))
return
/obj/machinery/computer/ship/proc/display_reconnect_dialog(mob/user, flavor)
- var/datum/browser/popup = new (user, "[src]", "[src]")
- popup.set_content("
[SPAN_COLOR("red", "Error")] Unable to connect to [flavor]. Reconnect
")
- popup.open()
+ if (!reconnect_popup)
+ reconnect_popup = new (user, "[src]", "[src]")
+ reconnect_popup.set_content("
[SPAN_COLOR("red", "Error")] Unable to connect to [flavor]. Reconnect
")
+ reconnect_popup.open()
/obj/machinery/computer/ship/interface_interact(mob/user)
ui_interact(user)
@@ -48,7 +50,8 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
if(..())
return TOPIC_HANDLED
if(href_list["sync"])
- sync_linked()
+ if (sync_linked() && reconnect_popup)
+ reconnect_popup.close()
return TOPIC_REFRESH
if(href_list["close"])
unlook(user)
@@ -66,21 +69,31 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
user.reset_view(linked)
if(user.client)
user.client.view = world.view + extra_view
- GLOB.moved_event.register(user, src, /obj/machinery/computer/ship/proc/unlook)
+ if(linked)
+ for(var/obj/machinery/shipsensors/sensor in linked.sensors)
+ sensor.reveal_contacts(user)
+ GLOB.moved_event.register(user, src, TYPE_PROC_REF(/obj/machinery/computer/ship, unlook))
if (!isghost(user))
- GLOB.stat_set_event.register(user, src, /obj/machinery/computer/ship/proc/unlook)
+ GLOB.stat_set_event.register(user, src, TYPE_PROC_REF(/obj/machinery/computer/ship, unlook))
LAZYDISTINCTADD(viewers, weakref(user))
+ if(linked)
+ LAZYDISTINCTADD(linked.navigation_viewers, weakref(user))
/obj/machinery/computer/ship/proc/unlook(mob/user)
user.reset_view(null, FALSE)
if(user.client)
- user.client.view = world.view
- GLOB.moved_event.unregister(user, src, /obj/machinery/computer/ship/proc/unlook)
- GLOB.stat_set_event.unregister(user, src, /obj/machinery/computer/ship/proc/unlook)
+ user.client.view = user.get_preference_value(/datum/client_preference/client_view)
+ if(linked)
+ for(var/obj/machinery/shipsensors/sensor in linked.sensors)
+ sensor.hide_contacts(user)
+ GLOB.moved_event.unregister(user, src, TYPE_PROC_REF(/obj/machinery/computer/ship, unlook))
+ GLOB.stat_set_event.unregister(user, src, TYPE_PROC_REF(/obj/machinery/computer/ship, unlook))
LAZYREMOVE(viewers, weakref(user))
+ if(linked)
+ LAZYREMOVE(linked.navigation_viewers, weakref(user))
/obj/machinery/computer/ship/proc/viewing_overmap(mob/user)
- return (weakref(user) in viewers)
+ return (weakref(user) in viewers) || (linked && (weakref(user) in linked.navigation_viewers))
/obj/machinery/computer/ship/CouldNotUseTopic(mob/user)
. = ..()
@@ -98,11 +111,20 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
else
return 0
+/obj/machinery/computer/ship/Destroy()
+ if (linked)
+ linked.consoles -= src
+ . = ..()
+
/obj/machinery/computer/ship/sensors/Destroy()
- sensors = null
- if(LAZYLEN(viewers))
+ if (sensor_ref)
+ var/obj/machinery/shipsensors/sensor = sensor_ref.resolve()
+ LAZYREMOVE(sensor.linked_consoles, src)
+ sensor_ref = null
+
+ if (LAZYLEN(viewers))
for(var/weakref/W in viewers)
var/M = W.resolve()
- if(M)
+ if (M)
unlook(M)
. = ..()
diff --git a/code/modules/overmap/ships/engines/engine.dm b/code/modules/overmap/ships/engines/engine.dm
index 393fa7c231437..a403afa89d1a1 100644
--- a/code/modules/overmap/ships/engines/engine.dm
+++ b/code/modules/overmap/ships/engines/engine.dm
@@ -39,7 +39,7 @@ var/global/list/ship_engines = list()
/datum/ship_engine/Destroy()
ship_engines -= src
- for(var/obj/effect/overmap/visitable/ship/S in SSshuttle.ships)
+ for(var/obj/overmap/visitable/ship/S in SSshuttle.ships)
S.engines -= src
holder = null
. = ..()
diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm
index b3c4cdeb3507d..05d5862457e97 100644
--- a/code/modules/overmap/ships/engines/gas_thruster.dm
+++ b/code/modules/overmap/ships/engines/gas_thruster.dm
@@ -55,7 +55,7 @@
/obj/machinery/atmospherics/unary/engine
name = "rocket nozzle"
desc = "Simple rocket nozzle, expelling gas at hypersonic velocities to propell the ship."
- icon = 'icons/obj/ship_engine.dmi'
+ icon = 'icons/obj/machines/ship_engine.dmi'
icon_state = "nozzle"
opacity = 1
density = TRUE
@@ -90,7 +90,7 @@
update_nearby_tiles(need_rebuild=1)
for(var/ship in SSshuttle.ships)
- var/obj/effect/overmap/visitable/ship/S = ship
+ var/obj/overmap/visitable/ship/S = ship
if(S.check_ownership(src))
S.engines |= controller
if(dir != S.fore_dir)
@@ -103,9 +103,9 @@
. = ..()
/obj/machinery/atmospherics/unary/engine/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(is_on())
- overlays += image_repository.overlay_image(icon, "nozzle_idle", plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER)
+ AddOverlays(image_repository.overlay_image(icon, "nozzle_idle", plane = EFFECTS_ABOVE_LIGHTING_PLANE, layer = ABOVE_LIGHTING_LAYER))
/obj/machinery/atmospherics/unary/engine/proc/get_status()
. = list()
@@ -179,7 +179,7 @@
var/turf/T = get_step(src,exhaust_dir)
if(T)
T.assume_air(removed)
- new/obj/effect/engine_exhaust(T, dir)
+ new/obj/engine_exhaust(T, dir)
/obj/machinery/atmospherics/unary/engine/proc/calculate_thrust(datum/gas_mixture/propellant, used_part = 1)
return round(sqrt(propellant.get_mass() * used_part * air_contents.return_pressure()/100),0.1)
@@ -196,23 +196,23 @@
change_power_consumption(initial(idle_power_usage) / energy_upgrade, POWER_USE_IDLE)
//Exhaust effect
-/obj/effect/engine_exhaust
+/obj/engine_exhaust
name = "engine exhaust"
- icon = 'icons/obj/ship_engine.dmi'
+ icon = 'icons/obj/machines/ship_engine.dmi'
icon_state = "nozzle_burn"
light_color = "#00a2ff"
anchored = TRUE
-/obj/effect/engine_exhaust/New(turf/nloc, ndir)
+/obj/engine_exhaust/New(turf/nloc, ndir)
..(nloc)
nloc.hotspot_expose(1000,125)
- set_light(0.5, 1, 4)
+ set_light(4, 0.5)
set_dir(ndir)
spawn(20)
qdel(src)
/obj/item/stock_parts/circuitboard/unary_atmos/engine//why don't we move this elsewhere?
- name = T_BOARD("gas thruster")
+ name = "circuit board (gas thruster)"
icon_state = "mcontroller"
build_path = /obj/machinery/atmospherics/unary/engine
origin_tech = list(TECH_POWER = 1, TECH_ENGINEERING = 2)
diff --git a/code/modules/overmap/ships/engines/ion_thruster.dm b/code/modules/overmap/ships/engines/ion_thruster.dm
index b00412453da46..bbacb2d280833 100644
--- a/code/modules/overmap/ships/engines/ion_thruster.dm
+++ b/code/modules/overmap/ships/engines/ion_thruster.dm
@@ -37,7 +37,7 @@
/obj/machinery/ion_engine
name = "ion propulsion device"
desc = "An advanced ion propulsion device, using energy and minutes amount of gas to generate thrust."
- icon = 'icons/obj/ship_engine.dmi'
+ icon = 'icons/obj/machines/ship_engine.dmi'
icon_state = "nozzle"
power_channel = ENVIRON
idle_power_usage = 100
@@ -75,7 +75,7 @@
return thrust_limit * generated_thrust * on
/obj/item/stock_parts/circuitboard/engine/ion
- name = T_BOARD("ion propulsion device")
+ name = "circuit board (ion propulsion device)"
board_type = "machine"
icon_state = "mcontroller"
build_path = /obj/machinery/ion_engine
diff --git a/code/modules/overmap/ships/landable.dm b/code/modules/overmap/ships/landable.dm
index eeb1814ddce42..f4bb913aa5ba2 100644
--- a/code/modules/overmap/ships/landable.dm
+++ b/code/modules/overmap/ships/landable.dm
@@ -2,29 +2,29 @@
// Mapping location doesn't matter, so long as on a map loaded at the same time as the shuttle areas.
// Multiz shuttles currently not supported. Non-autodock shuttles currently not supported.
-/obj/effect/overmap/visitable/ship/landable
+/obj/overmap/visitable/ship/landable
var/shuttle // Name of associated shuttle. Must be autodock.
- var/obj/effect/shuttle_landmark/ship/landmark // Record our open space landmark for easy reference.
+ var/obj/shuttle_landmark/ship/landmark // Record our open space landmark for easy reference.
var/multiz = 0 // Index of multi-z levels, starts at 0
var/status = SHIP_STATUS_LANDED
icon_state = "shuttle"
moving_state = "shuttle_moving"
-/obj/effect/overmap/visitable/ship/landable/Destroy()
+/obj/overmap/visitable/ship/landable/Destroy()
GLOB.shuttle_moved_event.unregister(SSshuttle.shuttles[shuttle], src)
return ..()
-/obj/effect/overmap/visitable/ship/landable/can_burn()
+/obj/overmap/visitable/ship/landable/can_burn()
if(status != SHIP_STATUS_OVERMAP)
return 0
return ..()
-/obj/effect/overmap/visitable/ship/landable/burn()
+/obj/overmap/visitable/ship/landable/burn()
if(status != SHIP_STATUS_OVERMAP)
return 0
return ..()
-/obj/effect/overmap/visitable/ship/landable/check_ownership(obj/object)
+/obj/overmap/visitable/ship/landable/check_ownership(obj/object)
var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle]
if(!shuttle_datum)
return
@@ -33,7 +33,7 @@
return 1
// We autobuild our z levels.
-/obj/effect/overmap/visitable/ship/landable/find_z_levels()
+/obj/overmap/visitable/ship/landable/find_z_levels()
for(var/i = 0 to multiz)
INCREMENT_WORLD_Z_SIZE
map_z += world.maxz
@@ -45,65 +45,65 @@
var/visitor_dir = fore_dir
for(var/landmark_name in list("FORE", "PORT", "AFT", "STARBOARD"))
var/turf/visitor_turf = get_ranged_target_turf(center_loc, visitor_dir, round(min(world.maxx/4, world.maxy/4)))
- var/obj/effect/shuttle_landmark/visiting_shuttle/visitor_landmark = new (visitor_turf, landmark, landmark_name)
+ var/obj/shuttle_landmark/visiting_shuttle/visitor_landmark = new (visitor_turf, landmark, landmark_name)
add_landmark(visitor_landmark)
visitor_dir = turn(visitor_dir, 90)
if(multiz)
- new /obj/effect/landmark/map_data(center_loc, (multiz + 1))
+ new /obj/landmark/map_data(center_loc, (multiz + 1))
-/obj/effect/overmap/visitable/ship/landable/get_areas()
+/obj/overmap/visitable/ship/landable/get_areas()
var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle]
if(!shuttle_datum)
return list()
return shuttle_datum.find_childfree_areas()
-/obj/effect/overmap/visitable/ship/landable/populate_sector_objects()
+/obj/overmap/visitable/ship/landable/populate_sector_objects()
..()
var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle]
- GLOB.shuttle_moved_event.register(shuttle_datum, src, .proc/on_shuttle_jump)
+ GLOB.shuttle_moved_event.register(shuttle_datum, src, PROC_REF(on_shuttle_jump))
on_landing(landmark, shuttle_datum.current_location) // We "land" at round start to properly place ourselves on the overmap.
-/obj/effect/shuttle_landmark/ship
+/obj/shuttle_landmark/ship
name = "Open Space"
landmark_tag = "ship"
flags = SLANDMARK_FLAG_AUTOSET | SLANDMARK_FLAG_ZERO_G
var/shuttle_name
var/list/visitors // landmark -> visiting shuttle stationed there
-/obj/effect/shuttle_landmark/ship/Initialize(mapload, shuttle_name)
+/obj/shuttle_landmark/ship/Initialize(mapload, shuttle_name)
landmark_tag += "_[shuttle_name]"
src.shuttle_name = shuttle_name
. = ..()
-/obj/effect/shuttle_landmark/ship/Destroy()
- var/obj/effect/overmap/visitable/ship/landable/ship = map_sectors["[z]"]
+/obj/shuttle_landmark/ship/Destroy()
+ var/obj/overmap/visitable/ship/landable/ship = map_sectors["[z]"]
if(istype(ship) && ship.landmark == src)
ship.landmark = null
. = ..()
-/obj/effect/shuttle_landmark/ship/cannot_depart(datum/shuttle/shuttle)
+/obj/shuttle_landmark/ship/cannot_depart(datum/shuttle/shuttle)
if(LAZYLEN(visitors))
return "Grappled by other shuttle; cannot manouver."
-/obj/effect/shuttle_landmark/visiting_shuttle
+/obj/shuttle_landmark/visiting_shuttle
flags = SLANDMARK_FLAG_AUTOSET | SLANDMARK_FLAG_ZERO_G
- var/obj/effect/shuttle_landmark/ship/core_landmark
+ var/obj/shuttle_landmark/ship/core_landmark
-/obj/effect/shuttle_landmark/visiting_shuttle/Initialize(mapload, obj/effect/shuttle_landmark/ship/master, _name)
+/obj/shuttle_landmark/visiting_shuttle/Initialize(mapload, obj/shuttle_landmark/ship/master, _name)
core_landmark = master
SetName(_name)
landmark_tag = master.shuttle_name + _name
- GLOB.destroyed_event.register(master, src, /datum/proc/qdel_self)
+ GLOB.destroyed_event.register(master, src, TYPE_PROC_REF(/datum, qdel_self))
. = ..()
-/obj/effect/shuttle_landmark/visiting_shuttle/Destroy()
+/obj/shuttle_landmark/visiting_shuttle/Destroy()
GLOB.destroyed_event.unregister(core_landmark, src)
LAZYREMOVE(core_landmark.visitors, src)
core_landmark = null
. = ..()
-/obj/effect/shuttle_landmark/visiting_shuttle/is_valid(datum/shuttle/shuttle)
+/obj/shuttle_landmark/visiting_shuttle/is_valid(datum/shuttle/shuttle)
. = ..()
if(!.)
return
@@ -113,16 +113,16 @@
if(shuttle == boss_shuttle) // Boss shuttle only lands on main landmark
return FALSE
-/obj/effect/shuttle_landmark/visiting_shuttle/shuttle_arrived(datum/shuttle/shuttle)
+/obj/shuttle_landmark/visiting_shuttle/shuttle_arrived(datum/shuttle/shuttle)
LAZYSET(core_landmark.visitors, src, shuttle)
- GLOB.shuttle_moved_event.register(shuttle, src, .proc/shuttle_left)
+ GLOB.shuttle_moved_event.register(shuttle, src, PROC_REF(shuttle_left))
-/obj/effect/shuttle_landmark/visiting_shuttle/proc/shuttle_left(datum/shuttle/shuttle, obj/effect/shuttle_landmark/old_landmark, obj/effect/shuttle_landmark/new_landmark)
+/obj/shuttle_landmark/visiting_shuttle/proc/shuttle_left(datum/shuttle/shuttle, obj/shuttle_landmark/old_landmark, obj/shuttle_landmark/new_landmark)
if(old_landmark == src)
GLOB.shuttle_moved_event.unregister(shuttle, src)
LAZYREMOVE(core_landmark.visitors, src)
-/obj/effect/overmap/visitable/ship/landable/proc/on_shuttle_jump(datum/shuttle/given_shuttle, obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into)
+/obj/overmap/visitable/ship/landable/proc/on_shuttle_jump(datum/shuttle/given_shuttle, obj/shuttle_landmark/from, obj/shuttle_landmark/into)
if(given_shuttle != SSshuttle.shuttles[shuttle])
return
var/datum/shuttle/autodock/auto = given_shuttle
@@ -137,14 +137,14 @@
status = SHIP_STATUS_LANDED
on_landing(from, into)
-/obj/effect/overmap/visitable/ship/landable/proc/on_transit(obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into)
+/obj/overmap/visitable/ship/landable/proc/on_transit(obj/shuttle_landmark/from, obj/shuttle_landmark/into)
halt()
-/obj/effect/overmap/visitable/ship/landable/proc/on_landing(obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into)
- var/obj/effect/overmap/visitable/target = map_sectors["[into.z]"]
+/obj/overmap/visitable/ship/landable/proc/on_landing(obj/shuttle_landmark/from, obj/shuttle_landmark/into)
+ var/obj/overmap/visitable/target = map_sectors["[into.z]"]
var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle]
if(into.landmark_tag == shuttle_datum.motherdock) // If our motherdock is a landable ship, it won't be found properly here so we need to find it manually.
- for(var/obj/effect/overmap/visitable/ship/landable/landable in SSshuttle.ships)
+ for(var/obj/overmap/visitable/ship/landable/landable in SSshuttle.ships)
if(landable.shuttle == shuttle_datum.mothershuttle)
target = landable
break
@@ -153,18 +153,18 @@
forceMove(target)
halt()
-/obj/effect/overmap/visitable/ship/landable/proc/on_takeoff(obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into)
+/obj/overmap/visitable/ship/landable/proc/on_takeoff(obj/shuttle_landmark/from, obj/shuttle_landmark/into)
if(!isturf(loc))
forceMove(get_turf(loc))
unhalt()
-/obj/effect/overmap/visitable/ship/landable/get_landed_info()
+/obj/overmap/visitable/ship/landable/get_landed_info()
switch(status)
if(SHIP_STATUS_LANDED)
- var/obj/effect/overmap/visitable/location = loc
- if(istype(loc, /obj/effect/overmap/visitable/sector))
+ var/obj/overmap/visitable/location = loc
+ if(istype(loc, /obj/overmap/visitable/sector))
return "Landed on \the [location.name]. Use secondary thrust to get clear before activating primary engines."
- if(istype(loc, /obj/effect/overmap/visitable/ship))
+ if(istype(loc, /obj/overmap/visitable/ship))
return "Docked with \the [location.name]. Use secondary thrust to get clear before activating primary engines."
return "Docked with an unknown object."
if(SHIP_STATUS_TRANSIT)
diff --git a/code/modules/overmap/ships/ship.dm b/code/modules/overmap/ships/ship.dm
index f1eadd852ced6..4744728a5488a 100644
--- a/code/modules/overmap/ships/ship.dm
+++ b/code/modules/overmap/ships/ship.dm
@@ -9,13 +9,15 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
else \
{speed_var = SANITIZE_SPEED((speed_var + v_diff)/(1 + speed_var*v_diff/(max_speed ** 2)))}
// Uses Lorentzian dynamics to avoid going too fast.
-
-/obj/effect/overmap/visitable/ship
+#define SENSOR_COEFFICENT 1000
+/obj/overmap/visitable/ship
name = "generic ship"
desc = "Space faring vessel."
icon_state = "ship"
+ requires_contact = TRUE
var/moving_state = "ship_moving"
var/list/consoles
+ var/list/sensors
var/vessel_mass = 10000 // tonnes, arbitrary number, affects acceleration provided by engines
var/vessel_size = SHIP_SIZE_LARGE // arbitrary number, affects how likely are we to evade meteors
@@ -29,32 +31,43 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
var/burn_delay = 1 SECOND // how often ship can do burns
var/fore_dir = NORTH // what dir ship flies towards for purpose of moving stars effect procs
+ /// How much it increases identification process each scan
+ var/base_sensor_visibility = 10
+
+ var/list/navigation_viewers // list of weakrefs to people viewing the overmap via this ship
+
var/list/engines = list()
var/engines_state = 0 //global on/off toggle for all engines
var/thrust_limit = 1 //global thrust limit for all engines, 0..1
var/halted = 0 //admin halt or other stop.
- var/skill_needed = SKILL_ADEPT //piloting skill needed to steer it without going in random dir
+ var/skill_needed = SKILL_TRAINED //piloting skill needed to steer it without going in random dir
var/operator_skill
-/obj/effect/overmap/visitable/ship/Initialize()
+/obj/overmap/visitable/ship/Initialize()
. = ..()
glide_size = world.icon_size
min_speed = round(min_speed, SHIP_MOVE_RESOLUTION)
max_speed = round(max_speed, SHIP_MOVE_RESOLUTION)
SSshuttle.ships += src
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSovermap, src)
+ base_sensor_visibility = initial(base_sensor_visibility) + round(sqrt(vessel_mass/SENSOR_COEFFICENT),1)
-/obj/effect/overmap/visitable/ship/Destroy()
- STOP_PROCESSING(SSobj, src)
+/obj/overmap/visitable/ship/Destroy()
+ STOP_PROCESSING(SSovermap, src)
SSshuttle.ships -= src
- if(LAZYLEN(consoles))
+ if(length(consoles))
for(var/obj/machinery/computer/ship/machine in consoles)
if(machine.linked == src)
machine.linked = null
consoles = null
+ if (length(sensors))
+ for (var/obj/machinery/shipsensors/machine in sensors)
+ if (machine.linked == src)
+ machine.linked = null
+ sensors = null
. = ..()
-/obj/effect/overmap/visitable/ship/relaymove(mob/user, direction, accel_limit)
+/obj/overmap/visitable/ship/relaymove(mob/user, direction, accel_limit)
accelerate(direction, accel_limit)
update_operator_skill(user)
@@ -63,37 +76,42 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
* Updates `operator_skill` to match the current user's skill level, or to null if no user is provided.
* Will skip observers to avoid allowing unintended external influences on flight.
*/
-/obj/effect/overmap/visitable/ship/proc/update_operator_skill(mob/user)
+/obj/overmap/visitable/ship/proc/update_operator_skill(mob/user)
if (isobserver(user))
return
operator_skill = user?.get_skill_value(SKILL_PILOT)
-/obj/effect/overmap/visitable/ship/proc/is_still()
+/obj/overmap/visitable/ship/proc/is_still()
return !MOVING(speed[1]) && !MOVING(speed[2])
-/obj/effect/overmap/visitable/ship/get_scan_data(mob/user)
+/obj/overmap/visitable/ship/get_scan_data(mob/user)
. = ..()
+ var/list/extra_data = list("Mass: [vessel_mass] tons.")
if(!is_still())
- . += " Heading: [get_heading_angle()], speed [get_speed() * 1000]"
+ extra_data += "Heading: [get_heading_angle()], speed [get_speed() * 1000]"
+ if(instant_contact)
+ extra_data += "It is broadcasting a distress signal."
+ . += jointext(extra_data, " ")
+
//Projected acceleration based on information from engines
-/obj/effect/overmap/visitable/ship/proc/get_acceleration()
+/obj/overmap/visitable/ship/proc/get_acceleration()
return round(get_total_thrust()/get_vessel_mass(), SHIP_MOVE_RESOLUTION)
//Does actual burn and returns the resulting acceleration
-/obj/effect/overmap/visitable/ship/proc/get_burn_acceleration()
+/obj/overmap/visitable/ship/proc/get_burn_acceleration()
return round(burn() / get_vessel_mass(), SHIP_MOVE_RESOLUTION)
-/obj/effect/overmap/visitable/ship/proc/get_vessel_mass()
+/obj/overmap/visitable/ship/proc/get_vessel_mass()
. = vessel_mass
- for(var/obj/effect/overmap/visitable/ship/ship in src)
+ for(var/obj/overmap/visitable/ship/ship in src)
. += ship.get_vessel_mass()
-/obj/effect/overmap/visitable/ship/proc/get_speed()
+/obj/overmap/visitable/ship/proc/get_speed()
return round(sqrt(speed[1] ** 2 + speed[2] ** 2), SHIP_MOVE_RESOLUTION)
-/obj/effect/overmap/visitable/ship/proc/get_heading()
+/obj/overmap/visitable/ship/proc/get_heading()
var/res = 0
if(MOVING(speed[1]))
if(speed[1] > 0)
@@ -107,13 +125,13 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
res |= SOUTH
return res
-/obj/effect/overmap/visitable/ship/proc/get_heading_angle()
+/obj/overmap/visitable/ship/proc/get_heading_angle()
var/res = 0
if (MOVING(speed[1]) || MOVING(speed[2]))
res = (round(Atan2(speed[1], -speed[2])) + 450)%360
return res
-/obj/effect/overmap/visitable/ship/proc/adjust_speed(n_x, n_y)
+/obj/overmap/visitable/ship/proc/adjust_speed(n_x, n_y)
CHANGE_SPEED_BY(speed[1], n_x)
CHANGE_SPEED_BY(speed[2], n_y)
for(var/zz in map_z)
@@ -123,7 +141,7 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
toggle_move_stars(zz, fore_dir)
update_icon()
-/obj/effect/overmap/visitable/ship/proc/get_brake_path()
+/obj/overmap/visitable/ship/proc/get_brake_path()
if(!get_acceleration())
return INFINITY
if(is_still())
@@ -136,7 +154,7 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
var/burns_per_grid = 1/ (burn_delay * get_speed())
return round(num_burns/burns_per_grid)
-/obj/effect/overmap/visitable/ship/proc/decelerate(accel_limit)
+/obj/overmap/visitable/ship/proc/decelerate(accel_limit)
if ((!speed[1] && !speed[2]) || !can_burn())
return
last_burn = world.time
@@ -147,7 +165,7 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
else
adjust_speed(-(speed[1] * delta) / mag, -(speed[2] * delta) / mag)
-/obj/effect/overmap/visitable/ship/proc/accelerate(direction, accel_limit)
+/obj/overmap/visitable/ship/proc/accelerate(direction, accel_limit)
if (!direction || !can_burn())
return
last_burn = world.time
@@ -159,16 +177,16 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
dy *= 0.5
adjust_speed(delta * dx, delta * dy)
-/obj/effect/overmap/visitable/ship/Process()
+/obj/overmap/visitable/ship/Process()
if(!halted && !is_still())
var/list/deltas = list(0,0)
for(var/i = 1 to 2)
if(MOVING(speed[i]))
position[i] += speed[i] * OVERMAP_SPEED_CONSTANT
if(position[i] < 0)
- deltas[i] = Ceil(position[i])
+ deltas[i] = ceil(position[i])
else if(position[i] > 0)
- deltas[i] = Floor(position[i])
+ deltas[i] = floor(position[i])
if(deltas[i] != 0)
position[i] -= deltas[i]
position[i] += (deltas[i] > 0) ? -1 : 1
@@ -178,8 +196,9 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
if(newloc && loc != newloc)
Move(newloc)
handle_wraparound()
+ sensor_visibility = min(round(base_sensor_visibility + get_speed_sensor_increase(), 1), 100)
-/obj/effect/overmap/visitable/ship/on_update_icon()
+/obj/overmap/visitable/ship/on_update_icon()
pixel_x = position[1] * (world.icon_size/2)
pixel_y = position[2] * (world.icon_size/2)
if(!is_still())
@@ -196,15 +215,15 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
M.client.pixel_y = pixel_y
..()
-/obj/effect/overmap/visitable/ship/proc/burn()
+/obj/overmap/visitable/ship/proc/burn()
for(var/datum/ship_engine/E in engines)
. += E.burn()
-/obj/effect/overmap/visitable/ship/proc/get_total_thrust()
+/obj/overmap/visitable/ship/proc/get_total_thrust()
for(var/datum/ship_engine/E in engines)
. += E.get_thrust()
-/obj/effect/overmap/visitable/ship/proc/can_burn()
+/obj/overmap/visitable/ship/proc/can_burn()
if(halted)
return 0
if (world.time < last_burn + burn_delay)
@@ -213,14 +232,14 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
. |= E.can_burn()
//deciseconds to next step
-/obj/effect/overmap/visitable/ship/proc/ETA()
+/obj/overmap/visitable/ship/proc/ETA()
. = INFINITY
for(var/i = 1 to 2)
if(MOVING(speed[i]))
. = min(., ((speed[i] > 0 ? 1 : -1) - position[i]) / speed[i])
- . = max(Ceil(.),0)
+ . = max(ceil(.),0)
-/obj/effect/overmap/visitable/ship/proc/handle_wraparound()
+/obj/overmap/visitable/ship/proc/handle_wraparound()
var/nx = x
var/ny = y
var/low_edge = 1
@@ -241,33 +260,36 @@ var/global/const/OVERMAP_SPEED_CONSTANT = (1 SECOND)
if(T)
forceMove(T)
-/obj/effect/overmap/visitable/ship/proc/halt()
+/obj/overmap/visitable/ship/proc/halt()
adjust_speed(-speed[1], -speed[2])
halted = 1
-/obj/effect/overmap/visitable/ship/proc/unhalt()
+/obj/overmap/visitable/ship/proc/unhalt()
if(!SSshuttle.overmap_halted)
halted = 0
-/obj/effect/overmap/visitable/ship/Bump(atom/A)
+/obj/overmap/visitable/ship/Bump(atom/A)
if(istype(A,/turf/unsimulated/map/edge))
handle_wraparound()
..()
-/obj/effect/overmap/visitable/ship/proc/get_helm_skill()//delete this mover operator skill to overmap obj
+/obj/overmap/visitable/ship/proc/get_helm_skill()//delete this mover operator skill to overmap obj
return operator_skill
-/obj/effect/overmap/visitable/ship/populate_sector_objects()
+/obj/overmap/visitable/ship/populate_sector_objects()
..()
- for(var/obj/machinery/computer/ship/S in SSmachines.machinery)
+ for(var/obj/machinery/computer/ship/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/ship))
S.attempt_hook_up(src)
for(var/datum/ship_engine/E in ship_engines)
if(check_ownership(E.holder))
engines |= E
-/obj/effect/overmap/visitable/ship/proc/get_landed_info()
+/obj/overmap/visitable/ship/proc/get_landed_info()
return "This ship cannot land."
+/obj/overmap/visitable/ship/proc/get_speed_sensor_increase()
+ return min(get_speed() * 1000, 50) //Engines should never increase sensor visibility by more than 50.
+
#undef MOVING
#undef SANITIZE_SPEED
#undef CHANGE_SPEED_BY
diff --git a/code/modules/overmap/spacetravel.dm b/code/modules/overmap/spacetravel.dm
index d5865730db68f..ed349edfc74c4 100644
--- a/code/modules/overmap/spacetravel.dm
+++ b/code/modules/overmap/spacetravel.dm
@@ -3,28 +3,27 @@ var/global/list/cached_space = list()
//Space stragglers go here
-/obj/effect/overmap/visitable/sector/temporary
+/obj/overmap/visitable/sector/temporary
name = "Deep Space"
- invisibility = 101
- known = FALSE
+ invisibility = INVISIBILITY_ABSTRACT
-/obj/effect/overmap/visitable/sector/temporary/Initialize(mapload, nx, ny, nz)
+/obj/overmap/visitable/sector/temporary/Initialize(mapload, nx, ny, nz)
. = ..()
map_z = list(nz)
testing("Temporary sector at zlevel [nz] was created.")
register(nx, ny)
-/obj/effect/overmap/visitable/sector/temporary/Destroy()
+/obj/overmap/visitable/sector/temporary/Destroy()
unregister()
testing("Temporary sector at [x],[y] was deleted. zlevel [map_z[1]] is no longer accessible.")
return ..()
-/obj/effect/overmap/visitable/sector/temporary/proc/register(nx, ny)
+/obj/overmap/visitable/sector/temporary/proc/register(nx, ny)
forceMove(locate(nx, ny, GLOB.using_map.overmap_z))
map_sectors["[map_z[1]]"] = src
testing("Temporary sector at zlevel [map_z[1]] moved to coordinates [x],[y]")
-/obj/effect/overmap/visitable/sector/temporary/proc/unregister()
+/obj/overmap/visitable/sector/temporary/proc/unregister()
// Note that any structures left in the zlevel will remain there, and may later turn up at completely different
// coordinates if this temporary sector is recycled. Perhaps everything remaining in the zlevel should be destroyed?
testing("Caching temporary sector for future use, corresponding zlevel is [map_z[1]], previous coordinates were [x],[y]")
@@ -32,7 +31,7 @@ var/global/list/cached_space = list()
src.forceMove(null)
cached_space += src
-/obj/effect/overmap/visitable/sector/temporary/proc/can_die(mob/observer)
+/obj/overmap/visitable/sector/temporary/proc/can_die(mob/observer)
testing("Checking if sector at [map_z[1]] can die.")
for(var/mob/M in GLOB.player_list)
if(M != observer && (M.z in map_z))
@@ -41,9 +40,10 @@ var/global/list/cached_space = list()
return 1
/proc/get_deepspace(x,y)
+ RETURN_TYPE(/obj/overmap/visitable/sector/temporary)
var/turf/map = locate(x,y,GLOB.using_map.overmap_z)
- var/obj/effect/overmap/visitable/sector/temporary/res
- for(var/obj/effect/overmap/visitable/sector/temporary/O in map)
+ var/obj/overmap/visitable/sector/temporary/res
+ for(var/obj/overmap/visitable/sector/temporary/O in map)
res = O
break
if(istype(res))
@@ -54,7 +54,7 @@ var/global/list/cached_space = list()
res.register(x, y)
return res
else
- return new /obj/effect/overmap/visitable/sector/temporary(null, x, y, ++world.maxz)
+ return new /obj/overmap/visitable/sector/temporary(null, x, y, ++world.maxz)
/atom/movable/proc/lost_in_space()
for(var/atom/movable/AM in contents)
@@ -66,13 +66,13 @@ var/global/list/cached_space = list()
return isnull(client)
/mob/living/carbon/human/lost_in_space()
- return isnull(client) && !last_ckey && stat == DEAD
+ return isnull(client) && (!last_ckey || stat == DEAD)
/proc/overmap_spacetravel(turf/space/T, atom/movable/A)
if (!T || !A)
return
- var/obj/effect/overmap/visitable/M = map_sectors["[T.z]"]
+ var/obj/overmap/visitable/M = map_sectors["[T.z]"]
if (!M)
return
@@ -104,9 +104,9 @@ var/global/list/cached_space = list()
testing("[A] spacemoving from [M] ([M.x], [M.y]).")
var/turf/map = locate(M.x,M.y,GLOB.using_map.overmap_z)
- var/obj/effect/overmap/visitable/TM
- for(var/obj/effect/overmap/visitable/O in map)
- if(O != M && O.in_space && prob(50))
+ var/obj/overmap/visitable/TM
+ for(var/obj/overmap/visitable/O in map)
+ if(O != M && HAS_FLAGS(O.sector_flags, OVERMAP_SECTOR_IN_SPACE) && prob(50))
TM = O
break
if(!TM)
@@ -121,7 +121,7 @@ var/global/list/cached_space = list()
if(D.pulling)
D.pulling.forceMove(dest)
- if(istype(M, /obj/effect/overmap/visitable/sector/temporary))
- var/obj/effect/overmap/visitable/sector/temporary/source = M
+ if(istype(M, /obj/overmap/visitable/sector/temporary))
+ var/obj/overmap/visitable/sector/temporary/source = M
if (source != TM && source.can_die())
source.unregister()
diff --git a/code/modules/paperwork/adminpaper.dm b/code/modules/paperwork/adminpaper.dm
index 3a5a732b3e224..994aeb54cab4c 100644
--- a/code/modules/paperwork/adminpaper.dm
+++ b/code/modules/paperwork/adminpaper.dm
@@ -19,7 +19,7 @@
var/footer = null
var/footerOn = FALSE
- var/logo_list = list("sollogo.png","eclogo.png","fleetlogo.png","exologo.png","ntlogo.png","daislogo.png","xynlogo.png","terralogo.png", "sfplogo.png")
+ var/logo_list = list("sollogo.png","eclogo.png","fleetlogo.png","exologo.png","ntlogo.png","daislogo.png","xynlogo.png","terralogo.png", "sfplogo.png", "falogo.png")
var/logo = ""
var/unformatedText = ""
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index e0049a1cebe76..69aa3fc420e4a 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -43,11 +43,11 @@
/obj/item/material/clipboard/on_update_icon()
..()
if(toppaper)
- overlays += overlay_image(toppaper.icon, toppaper.icon_state, flags=RESET_COLOR)
- overlays += toppaper.overlays
+ AddOverlays(overlay_image(toppaper.icon, toppaper.icon_state, flags=RESET_COLOR))
+ CopyOverlays(toppaper)
if(haspen)
- overlays += overlay_image(icon, "clipboard_pen", flags=RESET_COLOR)
- overlays += overlay_image(icon, "clipboard_over", flags=RESET_COLOR)
+ AddOverlays(overlay_image(icon, "clipboard_pen", flags=RESET_COLOR))
+ AddOverlays(overlay_image(icon, "clipboard_over", flags=RESET_COLOR))
return
/obj/item/material/clipboard/attackby(obj/item/W as obj, mob/user as mob)
@@ -61,7 +61,7 @@
update_icon()
else if(istype(toppaper) && istype(W, /obj/item/pen))
- toppaper.attackby(W, usr)
+ W.resolve_attackby(toppaper, usr)
update_icon()
return
@@ -73,15 +73,8 @@
else
dat += "Add Pen "
- //The topmost paper. I don't think there's any way to organise contents in byond, so this is what we're stuck with. -Pete
- if(toppaper)
- var/obj/item/paper/P = toppaper
- dat += "WriteRemoveRename - [P.name] "
-
for(var/obj/item/paper/P in src)
- if(P==toppaper)
- continue
- dat += "RemoveRename - [P.name] "
+ dat += "WriteRemoveRename - [P.name] "
for(var/obj/item/photo/Ph in src)
dat += "RemoveRename - [Ph.name] "
@@ -114,13 +107,13 @@
else if(href_list["write"])
var/obj/item/P = locate(href_list["write"])
- if(P && (P.loc == src) && istype(P, /obj/item/paper) && (P == toppaper) )
+ if(P && (P.loc == src) && istype(P, /obj/item/paper))
var/obj/item/I = usr.get_active_hand()
if(istype(I, /obj/item/pen))
- P.attackby(I, usr)
+ I.resolve_attackby(P, usr)
else if(href_list["remove"])
var/obj/item/P = locate(href_list["remove"])
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index 1174cdf0fe665..5c9790e5e2672 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -6,9 +6,10 @@ GLOBAL_LIST_EMPTY(admin_departments)
/obj/machinery/photocopier/faxmachine
name = "fax machine"
- icon = 'icons/obj/bureaucracy.dmi'
+ icon = 'icons/obj/machines/bureaucracy/fax_machine.dmi'
icon_state = "fax"
insert_anim = "faxsend"
+ obj_flags = OBJ_FLAG_ANCHORABLE | OBJ_FLAG_CAN_TABLE
var/send_access = list()
idle_power_usage = 30
@@ -49,48 +50,50 @@ GLOBAL_LIST_EMPTY(admin_departments)
linked_pdas = null
. = ..()
+/obj/machinery/photocopier/faxmachine/multitool_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ to_chat(user, SPAN_NOTICE("[src]'s department tag is set to [department]."))
+ if(!emagged)
+ to_chat(user, SPAN_WARNING("[src]'s department configuration is vendor locked."))
+ return
+ var/list/option_list = GLOB.alldepartments.Copy() + GLOB.admin_departments.Copy() + "(Custom)" + "(Cancel)"
+ var/new_department = input(user, "Which department do you want to tag this fax machine as? Choose '(Custom)' to enter a custom department or '(Cancel) to cancel.", "Fax Machine Department Tag") as null|anything in option_list
+ if(!new_department || new_department == department || new_department == "(Cancel)" || !CanUseTopic(user) || !Adjacent(user))
+ return
+ if(new_department == "(Custom)")
+ new_department = input(user, "Which department do you want to tag this fax machine as?", "Fax Machine Department Tag", department) as text|null
+ if (!new_department || new_department == department || !CanUseTopic(user) || !Adjacent(user))
+ return
+ if(new_department == "Unknown" || new_department == "(Custom)" || new_department == "(Cancel)")
+ to_chat(user, SPAN_WARNING("Invalid department tag selected."))
+ return
+ department = new_department
+ to_chat(user, SPAN_NOTICE("You reconfigure [src]'s department tag to [department]."))
-/obj/machinery/photocopier/faxmachine/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/photocopier/faxmachine/use_tool(obj/item/O, mob/living/user, list/click_params)
if(istype(O, /obj/item/paper))
var/obj/item/paper/P = O
if(!P.readable)
- to_chat(user, SPAN_NOTICE("\The [src] beeps. Error, invalid document detected."))
- return
- if(istype(O, /obj/item/card/id))
+ to_chat(user, SPAN_NOTICE("[src] beeps. Error, invalid document detected."))
+ return TRUE
+
+ if(isid(O))
if(!user.unEquip(O, src))
- return
+ return TRUE
scan = O
- to_chat(user, SPAN_NOTICE("You insert \the [O] into \the [src]."))
- if (isMultitool(O))
- to_chat(user, SPAN_NOTICE("\The [src]'s department tag is set to [department]."))
- if (!emagged)
- to_chat(user, SPAN_WARNING("\The [src]'s department configuration is vendor locked."))
- return
- var/list/option_list = GLOB.alldepartments.Copy() + GLOB.admin_departments.Copy() + "(Custom)" + "(Cancel)"
- var/new_department = input(user, "Which department do you want to tag this fax machine as? Choose '(Custom)' to enter a custom department or '(Cancel) to cancel.", "Fax Machine Department Tag") as null|anything in option_list
- if (!new_department || new_department == department || new_department == "(Cancel)" || !CanUseTopic(user) || !Adjacent(user))
- return
- if (new_department == "(Custom)")
- new_department = input(user, "Which department do you want to tag this fax machine as?", "Fax Machine Department Tag", department) as text|null
- if (!new_department || new_department == department || !CanUseTopic(user) || !Adjacent(user))
- return
- if (new_department == "Unknown" || new_department == "(Custom)" || new_department == "(Cancel)")
- to_chat(user, SPAN_WARNING("Invalid department tag selected."))
- return
- department = new_department
- to_chat(user, SPAN_NOTICE("You reconfigure \the [src]'s department tag to [department]."))
- return
+ to_chat(user, SPAN_NOTICE("You insert [O] into [src]."))
+ return TRUE
if (istype(O, /obj/item/modular_computer/pda))
if (LAZYISIN(linked_pdas, O))
unlink_pda(O)
- to_chat(user, SPAN_NOTICE("You remove \the [O] from \the [src]'s notifications list."))
- return
+ to_chat(user, SPAN_NOTICE("You remove [O] from [src]'s notifications list."))
+ return TRUE
link_pda(O)
- to_chat(user, SPAN_NOTICE("You add \the [O] to \the [src]'s notifications list. It will now be pinged whenever a fax is received."))
- return
+ to_chat(user, SPAN_NOTICE("You add [O] to [src]'s notifications list. It will now be pinged whenever a fax is received."))
+ return TRUE
- ..()
+ return ..()
/obj/machinery/photocopier/faxmachine/get_mechanics_info()
. = "
The fax machine can be used to transmit paper faxes to other fax machines on the map, or to off-ship organizations handled by server administration. To use the fax machine, you'll need to insert both a paper and your ID card, authenticate, select a destination, the transmit the fax.
"
@@ -112,9 +115,9 @@ GLOBAL_LIST_EMPTY(admin_departments)
/obj/machinery/photocopier/faxmachine/emag_act(remaining_charges, mob/user, emag_source)
if (emagged)
- to_chat(user, SPAN_WARNING("\The [src]'s systems have already been hacked."))
+ to_chat(user, SPAN_WARNING("[src]'s systems have already been hacked."))
return
- to_chat(user, SPAN_NOTICE("You unlock \the [src]'s department tagger. You can now modify it's department tag to disguise faxes as being from another department or even off-ship using a multitool."))
+ to_chat(user, SPAN_NOTICE("You unlock [src]'s department tagger. You can now modify it's department tag to disguise faxes as being from another department or even off-ship using a multitool."))
emagged = TRUE
return TRUE
@@ -237,7 +240,7 @@ GLOBAL_LIST_EMPTY(admin_departments)
flick("faxreceive", src)
playsound(loc, "sound/machines/dotprinter.ogg", 50, 1)
- visible_message(SPAN_NOTICE("\The [src] pings, \"New fax received from [origin_department].\""))
+ visible_message(SPAN_NOTICE("[src] pings, \"New fax received from [origin_department].\""))
// Notify any linked PDAs
if (LAZYLEN(linked_pdas))
@@ -309,16 +312,17 @@ GLOBAL_LIST_EMPTY(admin_departments)
if (!istype(pda))
return
LAZYADD(linked_pdas, pda)
- GLOB.destroyed_event.register(pda, src, .proc/unlink_pda)
+ GLOB.destroyed_event.register(pda, src, PROC_REF(unlink_pda))
/obj/machinery/photocopier/faxmachine/proc/unlink_pda(obj/item/modular_computer/pda/pda)
LAZYREMOVE(linked_pdas, pda)
- GLOB.destroyed_event.unregister(pda, src, .proc/unlink_pda)
+ GLOB.destroyed_event.unregister(pda, src, PROC_REF(unlink_pda))
/// Retrieves a list of all fax machines matching the given department tag.
/proc/get_fax_machines_by_department(department)
+ RETURN_TYPE(/list)
if (!department)
department = "Unknown"
var/list/faxes = list()
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index b9017ef975def..b2ddfce1aebed 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -11,7 +11,7 @@
/obj/structure/filingcabinet
name = "filing cabinet"
desc = "A large cabinet with drawers."
- icon = 'icons/obj/bureaucracy.dmi'
+ icon = 'icons/obj/structures/drawers.dmi'
icon_state = "filingcabinet"
density = TRUE
anchored = TRUE
@@ -46,16 +46,23 @@
I.forceMove(src)
. = ..()
-/obj/structure/filingcabinet/attackby(obj/item/P as obj, mob/user as mob)
- if(is_type_in_list(P, can_hold))
- if(!user.unEquip(P, src))
+
+/obj/structure/filingcabinet/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Any item - Attempt to put in cabinet
+ if (is_type_in_list(tool, can_hold))
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(tool, user)
return
- add_fingerprint(user)
- to_chat(user, SPAN_NOTICE("You put [P] in [src]."))
- flick("[initial(icon_state)]-open",src)
+ flick("[initial(icon_state)]-open", src)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] puts \a [tool] in \the [src]."),
+ SPAN_NOTICE("You put \the [tool] in \the [src].")
+ )
updateUsrDialog()
- else
- ..()
+ return TRUE
+
+ return ..()
+
/obj/structure/filingcabinet/attack_hand(mob/user as mob)
if(length(contents) <= 0)
diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm
index 7da88385e27e5..e10f3550f7454 100644
--- a/code/modules/paperwork/folders.dm
+++ b/code/modules/paperwork/folders.dm
@@ -26,9 +26,9 @@
icon_state = "folder_nt"
/obj/item/folder/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(length(contents))
- overlays += "folder_paper"
+ AddOverlays("folder_paper")
return
/obj/item/folder/attackby(obj/item/W as obj, mob/user as mob)
@@ -121,7 +121,7 @@
/obj/item/folder/envelope/examine(mob/user)
. = ..()
- to_chat(user, "The seal is [sealed ? "intact" : "broken"].")
+ . += SPAN_NOTICE("The seal is [sealed ? "intact" : "broken"].")
/obj/item/folder/envelope/proc/sealcheck(user)
var/ripperoni = alert("Are you sure you want to break the seal on \the [src]?", "Confirmation","Yes", "No")
diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm
index 08f164876d24b..3ead643b83440 100644
--- a/code/modules/paperwork/handlabeler.dm
+++ b/code/modules/paperwork/handlabeler.dm
@@ -3,7 +3,6 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "labeler0"
item_state = "flight"
- item_flags = ITEM_FLAG_TRY_ATTACK
matter = list(MATERIAL_PLASTIC = 100)
/// If set, the label text this will apply.
@@ -22,7 +21,8 @@
icon_state = "labeler[!isnull(label)]"
-/obj/item/hand_labeler/attack(atom/target, mob/living/user, target_zone, animate)
+/obj/item/hand_labeler/use_before(atom/target, mob/living/user)
+ . = FALSE
if (label)
target.AddLabel(label, user)
return TRUE
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 715cac32377c6..473f6a100c6c0 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -51,6 +51,7 @@
var/const/fancyfont = "Garamond"
var/scan_file_type = /datum/computer_file/data/text
+ var/is_copy = TRUE
/obj/item/paper/New(loc, text, title, list/md = null, datum/language/L = null)
..(loc)
@@ -99,11 +100,11 @@
/obj/item/paper/examine(mob/user, distance)
. = ..()
if(!is_memo && name != "sheet of paper")
- to_chat(user, "It's titled '[name]'.")
+ . += SPAN_NOTICE("It's titled '[name]'.")
if(distance <= 1)
show_content(usr)
else
- to_chat(user, SPAN_NOTICE("You have to go closer if you want to read it."))
+ . += SPAN_NOTICE("You have to go closer if you want to read it.")
/obj/item/paper/verb/user_set_language()
set name = "Set writing language"
@@ -159,6 +160,11 @@
can_read = ishuman(user) || issilicon(user)
if (can_read)
can_read = get_dist(src, user) < PAPER_EYEBALL_DISTANCE
+
+
+ var/datum/asset/paper_asset = get_asset_datum(/datum/asset/simple/paper)
+ paper_asset.send(user)
+
var/html = "[name]"
if (!can_read)
html += PAPER_META_BAD("The paper is too far away or you can't read.")
@@ -216,32 +222,39 @@
user.visible_message("\The [user] crumples \the [src] into a ball!")
icon_state = "scrap"
return
- user.examinate(src)
+ examinate(user, src)
/obj/item/paper/attack_ai(mob/living/silicon/ai/user)
show_content(user)
-/obj/item/paper/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
- if(user.zone_sel.selecting == BP_EYES)
+/obj/item/paper/use_before(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
+ if (user.zone_sel.selecting == BP_EYES)
user.visible_message(SPAN_NOTICE("You show the paper to [M]. "), \
SPAN_NOTICE(" [user] holds up a paper and shows it to [M]. "))
- M.examinate(src)
+ examinate(M, src)
+ return TRUE
- else if(user.zone_sel.selecting == BP_MOUTH) // lipstick wiping
- if(ishuman(M))
+ if (user.zone_sel.selecting == BP_MOUTH) // lipstick wiping
+ if (ishuman(M))
var/mob/living/carbon/human/H = M
- if(H == user)
+ if (H == user)
to_chat(user, SPAN_NOTICE("You wipe off the lipstick with [src]."))
H.makeup_style = null
H.update_body()
+ return TRUE
else
user.visible_message(SPAN_WARNING("[user] begins to wipe [H]'s lipstick off with \the [src]."), \
- SPAN_NOTICE("You begin to wipe off [H]'s lipstick."))
- if(do_after(user, 2 SECONDS, H, (DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS) & ~DO_BOTH_CAN_TURN))
- user.visible_message(SPAN_NOTICE("[user] wipes [H]'s lipstick off with \the [src]."), \
- SPAN_NOTICE("You wipe off [H]'s lipstick."))
- H.makeup_style = null
- H.update_body()
+ SPAN_NOTICE("You begin to wipe off [H]'s lipstick."))
+ if (!do_after(user, 2 SECONDS, H, (DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS) & ~DO_BOTH_CAN_TURN))
+ return TRUE
+ user.visible_message(SPAN_NOTICE("[user] wipes [H]'s lipstick off with \the [src]."), \
+ SPAN_NOTICE("You wipe off [H]'s lipstick."))
+ H.makeup_style = null
+ H.update_body()
+ return TRUE
/obj/item/paper/proc/addtofield(id, text, links = 0)
var/locid = 0
@@ -292,7 +305,7 @@
stamps = null
free_space = MAX_PAPER_MESSAGE_LEN
stamped = list()
- overlays.Cut()
+ ClearOverlays()
updateinfolinks()
update_icon()
@@ -360,7 +373,7 @@
user.visible_message(SPAN_CLASS("[class]", "[user] burns right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap."), \
SPAN_CLASS("[class]", "You burn right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap."))
- new /obj/effect/decal/cleanable/ash(get_turf(src))
+ new /obj/decal/cleanable/ash(get_turf(src))
qdel(src)
else
@@ -528,7 +541,7 @@
if(!stamped)
stamped = new
stamped += P.type
- overlays += stampoverlay
+ AddOverlays(stampoverlay)
playsound(src, 'sound/effects/stamp.ogg', 50, 1)
to_chat(user, SPAN_NOTICE("You stamp the paper with your [P.name]."))
@@ -555,7 +568,12 @@
//For supply.
/obj/item/paper/manifest
name = "supply manifest"
- var/is_copy = 1
+
+
+//For anomalies.
+/obj/item/paper/anomaly_scan
+ name = "anomaly scan result"
+
/*
* Premade paper
*/
@@ -578,7 +596,7 @@
/obj/item/paper/exodus_armory
name = "armory inventory"
- info = "
\[*]Implanter: 1\[*]Death Alarm implant(s): 7\[*]Security radio headset(s): 4\[*]Ablative vest(s): 2\[*]Ablative helmet(s): 2\[*]Ballistic vest(s): 2\[*]Ballistic helmet(s): 2\[*]Tear Gas Grenade(s): 7\[*]Flashbang(s): 7\[*]Beanbag Shell(s): 7\[*]Stun Shell(s): 7\[*]Illumination Shell(s): 7\[*]W-T Remmington 29x shotgun(s): 2\[*]NT Mk60 EW Halicon ion rifle(s): 2\[*]Hephaestus Industries G40E laser carbine(s): 4\[*]Flare(s): 4Warden (print):Signature: "
/obj/item/paper/exodus_cmo
name = "outgoing CMO's notes"
diff --git a/code/modules/paperwork/paper_bundle.dm b/code/modules/paperwork/paper_bundle.dm
index 3b0352d2a1cfe..a0c9b1f333c2c 100644
--- a/code/modules/paperwork/paper_bundle.dm
+++ b/code/modules/paperwork/paper_bundle.dm
@@ -51,7 +51,7 @@
if(istype(W, /obj/item/pen))
show_browser(user, "", "window=[name]") //Closes the dialog
var/obj/P = pages[page]
- P.attackby(W, user)
+ W.resolve_attackby(P, user)
update_icon()
attack_self(user) //Update the browsed page.
@@ -89,7 +89,7 @@
if(user.get_inactive_hand() == src)
user.drop_from_inventory(src)
- new /obj/effect/decal/cleanable/ash(src.loc)
+ new /obj/decal/cleanable/ash(src.loc)
qdel(src)
else
@@ -100,7 +100,7 @@
if(distance <= 1)
src.show_content(user)
else
- to_chat(user, SPAN_NOTICE("It is too far away."))
+ . += SPAN_NOTICE(SPAN_NOTICE("It is too far away."))
/obj/item/paper_bundle/proc/show_content(mob/user as mob)
var/dat
@@ -210,7 +210,7 @@
/obj/item/paper_bundle/on_update_icon()
var/obj/item/paper/P = pages[1]
icon_state = P.icon_state
- overlays = P.overlays
+ CopyOverlays(P)
underlays.Cut()
var/i = 0
var/photo
@@ -228,12 +228,12 @@
var/obj/item/photo/Ph = O
img = Ph.tiny
photo = 1
- overlays += img
+ AddOverlays(img)
if(i>1)
desc = "[i] papers clipped to each other."
else
desc = "A single sheet of paper."
if(photo)
desc += "\nThere is a photo attached to it."
- overlays += image('icons/obj/bureaucracy.dmi', "clip")
+ AddOverlays(image('icons/obj/bureaucracy.dmi', "clip"))
return
diff --git a/code/modules/paperwork/paper_sticky.dm b/code/modules/paperwork/paper_sticky.dm
index b2d2c7421ecf8..6f966c5fca8c6 100644
--- a/code/modules/paperwork/paper_sticky.dm
+++ b/code/modules/paperwork/paper_sticky.dm
@@ -48,8 +48,8 @@
/obj/item/sticky_pad/examine(mob/user)
. = ..()
- to_chat(user, SPAN_NOTICE("It has [papers] sticky note\s left."))
- to_chat(user, SPAN_NOTICE("You can click it on grab intent to pick it up."))
+ . += SPAN_NOTICE("It has [papers] sticky note\s left.")
+ . += SPAN_NOTICE("You can click it on grab intent to pick it up.")
/obj/item/sticky_pad/attack_hand(mob/user)
if(user.a_intent == I_GRAB)
@@ -81,7 +81,7 @@
/obj/item/paper/sticky/Initialize()
. = ..()
- GLOB.moved_event.register(src, src, /obj/item/paper/sticky/proc/reset_persistence_tracking)
+ GLOB.moved_event.register(src, src, TYPE_PROC_REF(/obj/item/paper/sticky, reset_persistence_tracking))
/obj/item/paper/sticky/proc/reset_persistence_tracking()
SSpersistence.forget_value(src, /datum/persistent/paper/sticky)
@@ -106,10 +106,9 @@
/obj/item/paper/sticky/can_bundle()
return FALSE // Would otherwise lead to buggy interaction
-/obj/item/paper/sticky/afterattack(A, mob/user, flag, params)
-
- if(!in_range(user, A) || istype(A, /obj/machinery/door) || istype(A, /obj/item/storage) || icon_state == "scrap")
- return
+/obj/item/paper/sticky/use_after(atom/A, mob/living/user, click_parameters)
+ if(!in_range(user, A) || istype(A, /obj/machinery/door) || icon_state == "scrap")
+ return FALSE
var/turf/target_turf = get_turf(A)
var/turf/source_turf = get_turf(user)
@@ -119,21 +118,21 @@
dir_offset = get_dir(source_turf, target_turf)
if(!(dir_offset in GLOB.cardinal))
to_chat(user, SPAN_WARNING("You cannot reach that from here."))
- return
+ return TRUE
if(user.unEquip(src, source_turf))
SSpersistence.track_value(src, /datum/persistent/paper/sticky)
- if(params)
- var/list/mouse_control = params2list(params)
- if(mouse_control["icon-x"])
- pixel_x = text2num(mouse_control["icon-x"]) - 16
+ if(click_parameters)
+ if(click_parameters["icon-x"])
+ pixel_x = text2num(click_parameters["icon-x"]) - 16
if(dir_offset & EAST)
pixel_x += 32
else if(dir_offset & WEST)
pixel_x -= 32
- if(mouse_control["icon-y"])
- pixel_y = text2num(mouse_control["icon-y"]) - 16
+ if(click_parameters["icon-y"])
+ pixel_y = text2num(click_parameters["icon-y"]) - 16
if(dir_offset & NORTH)
pixel_y += 32
else if(dir_offset & SOUTH)
pixel_y -= 32
+ return TRUE
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index a774d5b1f3618..069ebac5cb5ab 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -10,7 +10,7 @@
throw_range = 7
layer = BELOW_OBJ_LAYER
var/amount = 30 //How much paper is in the bin.
- var/list/papers = new/list() //List of papers put in the bin for reference.
+ var/list/papers = list() //List of papers put in the bin for reference.
/obj/item/paper_bin/MouseDrop(mob/user as mob)
@@ -98,9 +98,9 @@
. = ..()
if(distance <= 1)
if(amount)
- to_chat(user, SPAN_NOTICE("There " + (amount > 1 ? "are [amount] papers" : "is one paper") + " in the bin."))
+ . += SPAN_NOTICE("There " + (amount > 1 ? "are [amount] papers" : "is one paper") + " in the bin.")
else
- to_chat(user, SPAN_NOTICE("There are no papers in the bin."))
+ . += SPAN_NOTICE("There are no papers in the bin.")
/obj/item/paper_bin/on_update_icon()
diff --git a/code/modules/paperwork/papershredder.dm b/code/modules/paperwork/papershredder.dm
index 9227c0b8b982a..7902ccfb01dbb 100644
--- a/code/modules/paperwork/papershredder.dm
+++ b/code/modules/paperwork/papershredder.dm
@@ -1,7 +1,7 @@
/obj/machinery/papershredder
name = "paper shredder"
desc = "For those documents you don't want seen."
- icon = 'icons/obj/bureaucracy.dmi'
+ icon = 'icons/obj/machines/bureaucracy/papershredder.dmi'
icon_state = "papershredder0"
density = TRUE
anchored = TRUE
@@ -20,34 +20,35 @@
/obj/item/sample/print = 1
)
-/obj/machinery/papershredder/attackby(obj/item/W, mob/user)
-
+/obj/machinery/papershredder/use_tool(obj/item/W, mob/living/user, list/click_params)
if(istype(W, /obj/item/storage))
empty_bin(user, W)
- return
+ return TRUE
+
+ var/paper_result
+ for(var/shred_type in shred_amounts)
+ if(istype(W, shred_type))
+ paper_result = shred_amounts[shred_type]
+
+ if(paper_result)
+ if(paperamount == max_paper)
+ to_chat(user, SPAN_WARNING("\The [src] is full; please empty it before you continue."))
+ return TRUE
+ paperamount += paper_result
+ qdel(W)
+ playsound(src.loc, 'sound/items/pshred.ogg', 75, 1)
+ if(paperamount > max_paper)
+ to_chat(user, SPAN_DANGER("\The [src] was too full, and shredded paper goes everywhere!"))
+ for(var/i=(paperamount-max_paper);i>0;i--)
+ var/obj/item/shreddedp/SP = get_shredded_paper()
+ SP.dropInto(loc)
+ SP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),1,5)
+ paperamount = max_paper
+ update_icon()
+ return TRUE
else
- var/paper_result
- for(var/shred_type in shred_amounts)
- if(istype(W, shred_type))
- paper_result = shred_amounts[shred_type]
- if(paper_result)
- if(paperamount == max_paper)
- to_chat(user, SPAN_WARNING("\The [src] is full; please empty it before you continue."))
- return
- paperamount += paper_result
- qdel(W)
- playsound(src.loc, 'sound/items/pshred.ogg', 75, 1)
- if(paperamount > max_paper)
- to_chat(user, SPAN_DANGER("\The [src] was too full, and shredded paper goes everywhere!"))
- for(var/i=(paperamount-max_paper);i>0;i--)
- var/obj/item/shreddedp/SP = get_shredded_paper()
- SP.dropInto(loc)
- SP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),1,5)
- paperamount = max_paper
- update_icon()
- return
- ..()
- return
+ return ..()
+
/obj/machinery/papershredder/verb/empty_contents()
set name = "Empty bin"
@@ -105,7 +106,7 @@
return new /obj/item/shreddedp(get_turf(src))
/obj/machinery/papershredder/on_update_icon()
- icon_state = "papershredder[max(0,min(5,Floor(paperamount/2)))]"
+ icon_state = "papershredder[max(0,min(5,floor(paperamount/2)))]"
/obj/item/shreddedp/attackby(obj/item/W as obj, mob/user)
if(istype(W, /obj/item/flame/lighter))
@@ -128,12 +129,12 @@
FireBurn()
/obj/item/shreddedp/proc/FireBurn()
- new /obj/effect/decal/cleanable/ash(get_turf(src))
+ new /obj/decal/cleanable/ash(get_turf(src))
qdel(src)
/obj/item/shreddedp
name = "shredded paper"
- icon = 'icons/obj/bureaucracy.dmi'
+ icon = 'icons/obj/machines/bureaucracy/papershredder.dmi'
icon_state = "shredp"
randpixel = 5
throwforce = 0
diff --git a/code/modules/paperwork/pen/pen.dm b/code/modules/paperwork/pen/pen.dm
index 554cb71149e1d..806e3df3b6d87 100644
--- a/code/modules/paperwork/pen/pen.dm
+++ b/code/modules/paperwork/pen/pen.dm
@@ -6,7 +6,10 @@
item_state = "pen"
slot_flags = SLOT_BELT | SLOT_EARS
throwforce = 0
+ force = 2
w_class = ITEM_SIZE_TINY
+ force = 2
+ puncture = TRUE
throw_speed = 7
throw_range = 15
matter = list(MATERIAL_PLASTIC = 10)
@@ -43,20 +46,19 @@
colour = "white"
color_description = "transluscent ink"
-/obj/item/pen/attack(atom/A, mob/user, target_zone)
- if(ismob(A))
- var/mob/M = A
- if(ishuman(A) && user.a_intent == I_HELP && target_zone == BP_HEAD)
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/head/head = H.organs_by_name[BP_HEAD]
- if(istype(head))
- head.write_on(user, color_description)
- else
- to_chat(user, SPAN_WARNING("You stab [M] with \the [src]."))
- admin_attack_log(user, M, "Stabbed using \a [src]", "Was stabbed with \a [src]", "used \a [src] to stab")
- else if(istype(A, /obj/item/organ/external/head))
+/obj/item/pen/use_after(atom/A, mob/user)
+ . = FALSE
+ if (ishuman(A) && user.a_intent == I_HELP && user.zone_sel.selecting == BP_HEAD)
+ var/mob/living/carbon/human/H = A
+ var/obj/item/organ/external/head/head = H.organs_by_name[BP_HEAD]
+ if (istype(head))
+ head.write_on(user, color_description)
+ return TRUE
+
+ if (istype(A, /obj/item/organ/external/head))
var/obj/item/organ/external/head/head = A
head.write_on(user, color_description)
+ return TRUE
/obj/item/pen/proc/toggle()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/paperwork/pen/reagent_pen.dm b/code/modules/paperwork/pen/reagent_pen.dm
index d9a015b879c5a..7005068844336 100644
--- a/code/modules/paperwork/pen/reagent_pen.dm
+++ b/code/modules/paperwork/pen/reagent_pen.dm
@@ -6,29 +6,40 @@
..()
create_reagents(30)
-/obj/item/pen/reagent/attack(mob/living/M, mob/user, target_zone)
+/obj/item/pen/reagent/use_before(mob/living/M, mob/user)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
+ if (!reagents.total_volume)
+ return FALSE
- if(!istype(M))
- return
+ var/target_zone = user.zone_sel.selecting
+ var/allow = M.can_inject(user, target_zone)
+ if (!allow)
+ return TRUE
- . = ..()
+ if (allow == INJECTION_PORT)
+ if (M != user)
+ to_chat(user, SPAN_WARNING("You begin hunting for an injection port on \the [M]'s suit!"))
+ else
+ to_chat(user, SPAN_NOTICE("You begin hunting for an injection port on your suit."))
- var/allow = M.can_inject(user, target_zone)
- if(allow)
- if (allow == INJECTION_PORT)
- if(M != user)
- to_chat(user, SPAN_WARNING("You begin hunting for an injection port on \the [M]'s suit!"))
- else
- to_chat(user, SPAN_NOTICE("You begin hunting for an injection port on your suit."))
- if(!user.do_skilled(INJECTION_PORT_DELAY, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
- return
- if(reagents.total_volume)
- if(M.reagents)
- var/should_admin_log = reagents.should_admin_log()
- var/contained_reagents = reagents.get_reagents()
- var/trans = reagents.trans_to_mob(M, 30, CHEM_BLOOD)
- if (should_admin_log)
- admin_inject_log(user, M, src, contained_reagents, trans)
+ if (!user.do_skilled(INJECTION_PORT_DELAY, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
+ return TRUE
+
+ if (M.reagents)
+ var/should_admin_log = reagents.should_admin_log()
+ var/contained_reagents = reagents.get_reagents()
+ var/trans = reagents.trans_to_mob(M, 30, CHEM_BLOOD)
+ if (should_admin_log)
+ admin_inject_log(user, M, src, contained_reagents, trans)
+
+ if (user.a_intent == I_HURT && allow != INJECTION_PORT)
+ return M.use_weapon(src, user)
+ else
+ to_chat(user, SPAN_WARNING("You prick \the [M] with \the [src]."))
+ to_chat(M, SPAN_NOTICE("You feel a tiny prick."))
+ return TRUE
/*
* Sleepy Pens
diff --git a/code/modules/paperwork/pen/retractable_pen.dm b/code/modules/paperwork/pen/retractable_pen.dm
index 9794cefb562ce..b34e9b567b624 100644
--- a/code/modules/paperwork/pen/retractable_pen.dm
+++ b/code/modules/paperwork/pen/retractable_pen.dm
@@ -14,7 +14,7 @@
icon_state = "pen_red"
colour = "red"
color_description = "red ink"
- base_state = "ret_blue"
+ base_state = "ret_red"
/obj/item/pen/retractable/green
icon_state = "pen_green"
@@ -32,10 +32,10 @@
else
icon_state = "[base_state]"
-/obj/item/pen/retractable/attack(atom/A, mob/user, target_zone)
+/obj/item/pen/retractable/use_before(atom/A, mob/user)
if(!active)
toggle()
- ..()
+ return ..()
/obj/item/pen/retractable/attack_self(mob/user)
toggle()
@@ -43,4 +43,4 @@
/obj/item/pen/retractable/toggle()
active = !active
playsound(src, 'sound/items/penclick.ogg', 5, 0, -4)
- update_icon()
\ No newline at end of file
+ update_icon()
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index ba5d2cf9347b0..5efa94bc92a34 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -1,6 +1,6 @@
/obj/machinery/photocopier
name = "photocopier"
- icon = 'icons/obj/bureaucracy.dmi'
+ icon = 'icons/obj/machines/bureaucracy/photocopier.dmi'
icon_state = "photocopier"
var/insert_anim = "photocopier_animation"
anchored = TRUE
@@ -107,21 +107,23 @@
to_chat(user, SPAN_NOTICE("You take \the [copyitem] out of \the [src]."))
copyitem = null
-/obj/machinery/photocopier/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/photocopier/use_tool(obj/item/O, mob/living/user, list/click_params)
if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo) || istype(O, /obj/item/paper_bundle) || istype(O, /obj/item/sample/print))
if(!copyitem)
if(!user.unEquip(O, src))
- return
+ return TRUE
copyitem = O
to_chat(user, SPAN_NOTICE("You insert \the [O] into \the [src]."))
flick(insert_anim, src)
updateUsrDialog()
else
to_chat(user, SPAN_NOTICE("There is already something in \the [src]."))
- else if(istype(O, /obj/item/device/toner))
+ return TRUE
+
+ if (istype(O, /obj/item/device/toner))
if(toner <= 10) //allow replacing when low toner is affecting the print darkness
if(!user.unEquip(O, src))
- return
+ return TRUE
to_chat(user, SPAN_NOTICE("You insert the toner cartridge into \the [src]."))
var/obj/item/device/toner/T = O
toner += T.toner_amount
@@ -129,7 +131,9 @@
updateUsrDialog()
else
to_chat(user, SPAN_NOTICE("This cartridge is not yet ready for replacement! Use up the rest of the toner."))
- else ..()
+ return TRUE
+
+ return ..()
/obj/machinery/photocopier/ex_act(severity)
switch(severity)
@@ -140,12 +144,12 @@
qdel(src)
else
if(toner > 0)
- new /obj/effect/decal/cleanable/blood/oil(get_turf(src))
+ new /obj/decal/cleanable/blood/oil(get_turf(src))
toner = 0
else
if(prob(50))
if(toner > 0)
- new /obj/effect/decal/cleanable/blood/oil(get_turf(src))
+ new /obj/decal/cleanable/blood/oil(get_turf(src))
toner = 0
return
@@ -171,10 +175,9 @@
c.ico = copy.ico
c.offset_x = copy.offset_x
c.offset_y = copy.offset_y
- var/list/temp_overlays = copy.overlays //Iterates through stamps
var/image/img //and puts a matching
- for (var/j = 1 to min(length(temp_overlays), length(copy.ico))) //gray overlay onto the copy
+ for (var/j = 1 to min(length(copy.overlays), length(copy.ico))) //gray overlay onto the copy
if (findtext(copy.ico[j], "cap") || findtext(copy.ico[j], "cent"))
img = image('icons/obj/bureaucracy.dmi', "paper_stamp-circle")
else if (findtext(copy.ico[j], "deny"))
@@ -183,7 +186,7 @@
img = image('icons/obj/bureaucracy.dmi', "paper_stamp-dots")
img.pixel_x = copy.offset_x[j]
img.pixel_y = copy.offset_y[j]
- c.overlays += img
+ c.AddOverlays(img)
c.updateinfolinks()
if(need_toner)
toner--
diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm
index bbee529ed6d5c..419011f916e7c 100644
--- a/code/modules/paperwork/photography.dm
+++ b/code/modules/paperwork/photography.dm
@@ -11,7 +11,7 @@
*******/
/obj/item/device/camera_film
name = "film cartridge"
- icon = 'icons/obj/photography.dmi'
+ icon = 'icons/obj/tools/photography.dmi'
desc = "A camera film cartridge. Insert it into a camera to reload it."
icon_state = "film"
item_state = "electropack"
@@ -25,7 +25,7 @@ var/global/photo_count = 0
/obj/item/photo
name = "photo"
- icon = 'icons/obj/photography.dmi'
+ icon = 'icons/obj/tools/photography.dmi'
icon_state = "photo"
item_state = "paper"
randpixel = 10
@@ -41,16 +41,16 @@ var/global/photo_count = 0
id = photo_count++
/obj/item/photo/attack_self(mob/user as mob)
- user.examinate(src)
+ examinate(user, src)
/obj/item/photo/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
var/scale = 8/(photo_size*32)
var/image/small_img = image(img)
small_img.SetTransform(scale = scale)
small_img.pixel_x = -32*(photo_size-1)/2 - 3
small_img.pixel_y = -32*(photo_size-1)/2
- overlays |= small_img
+ AddOverlays(small_img)
tiny = image(img)
tiny.SetTransform(scale = 0.5 * scale)
@@ -71,9 +71,9 @@ var/global/photo_count = 0
return
if(distance <= 1)
show(user)
- to_chat(user, desc)
+ . += SPAN_NOTICE(desc)
else
- to_chat(user, SPAN_NOTICE("It is too far away."))
+ . += SPAN_NOTICE("It is too far away.")
/obj/item/photo/proc/show(mob/user as mob)
send_rsc(user, img, "tmp_photo_[id].png")
@@ -105,12 +105,12 @@ var/global/photo_count = 0
**************/
/obj/item/storage/photo_album
name = "Photo album"
- icon = 'icons/obj/photography.dmi'
+ icon = 'icons/obj/tools/photography.dmi'
icon_state = "album"
item_state = "briefcase"
w_class = ITEM_SIZE_NORMAL //same as book
storage_slots = DEFAULT_BOX_STORAGE //yes, that's storage_slots. Photos are w_class 1 so this has as many slots equal to the number of photos you could put in a box
- can_hold = list(/obj/item/photo)
+ contents_allowed = list(/obj/item/photo)
/obj/item/storage/photo_album/MouseDrop(obj/over_object as obj)
@@ -141,7 +141,7 @@ var/global/photo_count = 0
*********/
/obj/item/device/camera
name = "camera"
- icon = 'icons/obj/photography.dmi'
+ icon = 'icons/obj/tools/photography.dmi'
desc = "A polaroid camera."
icon_state = "camera"
item_state = "electropack"
@@ -174,25 +174,29 @@ var/global/photo_count = 0
size = nsize
to_chat(usr, SPAN_NOTICE("Camera will now take [size]x[size] photos."))
-/obj/item/device/camera/attack(mob/living/carbon/human/M as mob, mob/user as mob)
- return
-
/obj/item/device/camera/attack_self(mob/user as mob)
on = !on
update_icon()
to_chat(user, "You switch the camera [on ? "on" : "off"].")
return
-/obj/item/device/camera/attackby(obj/item/I as obj, mob/user as mob)
- if(istype(I, /obj/item/device/camera_film))
- if(pictures_left)
- to_chat(user, SPAN_NOTICE("[src] still has some film in it!"))
- return
- to_chat(user, SPAN_NOTICE("You insert [I] into [src]."))
- qdel(I)
+
+/obj/item/device/camera/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Camera Film - Add film
+ if (istype(tool, /obj/item/device/camera_film))
+ if (pictures_left)
+ USE_FEEDBACK_FAILURE("\The [src] already has film in it.")
+ return TRUE
pictures_left = pictures_max
- return
- ..()
+ user.visible_message(
+ SPAN_NOTICE("\The [user] adds \a [tool] to \a [src]."),
+ SPAN_NOTICE("You add \the [tool] to \the [src]."),
+ range = 2
+ )
+ qdel(tool)
+ return TRUE
+
+ return ..()
/obj/item/device/camera/proc/get_mobs(turf/the_turf as turf)
@@ -224,7 +228,7 @@ var/global/photo_count = 0
/obj/item/device/camera/examine(mob/user)
. = ..()
- to_chat(user, "It has [pictures_left] photo\s left.")
+ . += SPAN_NOTICE("It has [pictures_left] photo\s left.")
//Proc for capturing check
/mob/living/proc/can_capture_turf(turf/T)
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index 9349644d416a5..da66ffd5e1768 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -5,11 +5,13 @@
icon_state = "stamp-deckchief"
item_state = "stamp"
throwforce = 0
+ force = 1
w_class = ITEM_SIZE_TINY
throw_speed = 7
throw_range = 15
matter = list(MATERIAL_STEEL = 60)
attack_verb = list("stamped")
+ hitsound = 'sound/effects/stamp.ogg'
/obj/item/stamp/captain
name = "captain's rubber stamp"
diff --git a/code/modules/persistence/filth.dm b/code/modules/persistence/filth.dm
index 2bce08e433183..30f8372ef22e2 100644
--- a/code/modules/persistence/filth.dm
+++ b/code/modules/persistence/filth.dm
@@ -1,4 +1,4 @@
-/obj/effect/decal/cleanable/filth
+/obj/decal/cleanable/filth
name = "filth"
desc = "Disgusting. Someone from last shift didn't do their job properly."
icon = 'icons/effects/blood.dmi'
@@ -8,6 +8,6 @@
persistent = TRUE
anchored = TRUE
-/obj/effect/decal/cleanable/filth/Initialize()
+/obj/decal/cleanable/filth/Initialize()
. = ..()
alpha = rand(180,220)
diff --git a/code/modules/persistence/graffiti.dm b/code/modules/persistence/graffiti.dm
index 264e58ee912f5..5b24006ff7e34 100644
--- a/code/modules/persistence/graffiti.dm
+++ b/code/modules/persistence/graffiti.dm
@@ -1,4 +1,4 @@
-/obj/effect/decal/writing
+/obj/decal/writing
name = "graffiti"
icon_state = "writing1"
icon = 'icons/effects/writing.dmi'
@@ -13,7 +13,7 @@
var/graffiti_age = 0
var/author = "unknown"
-/obj/effect/decal/writing/New(newloc, _age, _message, _author)
+/obj/decal/writing/New(newloc, _age, _message, _author)
..(newloc)
if(!isnull(_age))
graffiti_age = _age
@@ -21,44 +21,40 @@
if(!isnull(author))
author = _author
-/obj/effect/decal/writing/Initialize()
- var/list/random_icon_states = icon_states(icon)
- for(var/obj/effect/decal/writing/W in loc)
+/obj/decal/writing/Initialize()
+ var/list/random_icon_states = ICON_STATES(icon)
+ for(var/obj/decal/writing/W in loc)
random_icon_states.Remove(W.icon_state)
if(length(random_icon_states))
icon_state = pick(random_icon_states)
SSpersistence.track_value(src, /datum/persistent/graffiti)
. = ..()
-/obj/effect/decal/writing/Destroy()
+/obj/decal/writing/Destroy()
SSpersistence.forget_value(src, /datum/persistent/graffiti)
. = ..()
-/obj/effect/decal/writing/examine(mob/user)
+/obj/decal/writing/examine(mob/user)
. = ..(user)
- to_chat(user, "It reads \"[message]\".")
-
-/obj/effect/decal/writing/attackby(obj/item/thing, mob/user)
- if(isWelder(thing))
- var/obj/item/weldingtool/welder = thing
- if(welder.isOn() && welder.remove_fuel(0,user) && do_after(user, 0.5 SECONDS, src, DO_PUBLIC_UNIQUE) && !QDELETED(src))
- playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
- user.visible_message(SPAN_NOTICE("\The [user] clears away some graffiti."))
- qdel(src)
- else if(thing.sharp)
-
- if(jobban_isbanned(user, "Graffiti"))
- to_chat(user, SPAN_WARNING("You are banned from leaving persistent information across rounds."))
- return
-
- var/_message = sanitize(input("Enter an additional message to engrave.", "Graffiti") as null|text, trim = TRUE)
- if(_message && loc && user && !user.incapacitated() && user.Adjacent(loc) && thing.loc == user)
- user.visible_message(SPAN_WARNING("\The [user] begins carving something into \the [loc]."))
- if(do_after(user, max(2 SECONDS, length(_message)), src, DO_PUBLIC_UNIQUE) && loc)
- user.visible_message(SPAN_DANGER("\The [user] carves some graffiti into \the [loc]."))
- message = "[message] [_message]"
- author = user.ckey
- if(lowertext(message) == "elbereth")
- to_chat(user, SPAN_NOTICE("You feel much safer."))
- else
- . = ..()
+ . += SPAN_NOTICE("It reads \"[message]\".")
+
+/obj/decal/writing/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.tool_start_check(user, 1))
+ return
+ balloon_alert(user, "очистка надписей")
+ if(!tool.use_as_tool(src, user, 1 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ user.visible_message(
+ SPAN_NOTICE("[user] clears away [src] with [tool]."),
+ SPAN_NOTICE("You clear away [src] with [tool].")
+ )
+ qdel(src)
+
+/obj/decal/writing/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Sharp Item - Engrave additional message
+ if (is_sharp(tool))
+ var/turf/target = get_turf(src)
+ target.try_graffiti(user, tool)
+ return TRUE
+ . = ..()
diff --git a/code/modules/persistence/noticeboards.dm b/code/modules/persistence/noticeboards.dm
index ca74f7fe7a2cd..e290e7a181476 100644
--- a/code/modules/persistence/noticeboards.dm
+++ b/code/modules/persistence/noticeboards.dm
@@ -1,7 +1,7 @@
/obj/structure/noticeboard
name = "notice board"
desc = "A board for pinning important notices upon."
- icon = 'icons/obj/stationobjs.dmi'
+ icon = 'icons/obj/structures/noticeboard.dmi'
icon_state = "nboard00"
density = FALSE
anchored = TRUE
@@ -85,51 +85,73 @@
/obj/structure/noticeboard/on_update_icon()
icon_state = "[base_icon_state][LAZYLEN(notices)]"
-/obj/structure/noticeboard/attackby(obj/item/thing, mob/user)
- if(isScrewdriver(thing))
- var/choice = input("Which direction do you wish to place the noticeboard?", "Noticeboard Offset") as null|anything in list("North", "South", "East", "West")
- if(choice && Adjacent(user) && thing.loc == user && !user.incapacitated())
- playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1)
- switch(choice)
- if("North")
- pixel_x = 0
- pixel_y = 32
- if("South")
- pixel_x = 0
- pixel_y = -32
- if("East")
- pixel_x = 32
- pixel_y = 0
- if("West")
- pixel_x = -32
- pixel_y = 0
+/obj/structure/noticeboard/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/choice = input("Which direction do you wish to place the noticeboard?", "Noticeboard Offset") as null|anything in list("North", "South", "East", "West")
+ if(!choice)
return
- else if(isWrench(thing))
- visible_message(SPAN_WARNING("\The [user] begins dismantling \the [src]."))
- playsound(loc, 'sound/items/Ratchet.ogg', 50, 1)
- if(do_after(user, 5 SECONDS, src, DO_REPAIR_CONSTRUCT))
- visible_message(SPAN_DANGER("\The [user] has dismantled \the [src]!"))
- dismantle()
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
- else if(istype(thing, /obj/item/paper) || istype(thing, /obj/item/photo))
- if(jobban_isbanned(user, "Graffiti"))
- to_chat(user, SPAN_WARNING("You are banned from leaving persistent information across rounds."))
- else
- if(LAZYLEN(notices) < max_notices && user.unEquip(thing, src))
- add_fingerprint(user)
- add_paper(thing)
- to_chat(user, SPAN_NOTICE("You pin \the [thing] to \the [src]."))
- SSpersistence.track_value(thing, /datum/persistent/paper)
- else
- to_chat(user, SPAN_WARNING("You hesitate, certain \the [thing] will not be seen among the many others already attached to \the [src]."))
+ switch(choice)
+ if("North")
+ pixel_x = 0
+ pixel_y = 32
+ if("South")
+ pixel_x = 0
+ pixel_y = -32
+ if("East")
+ pixel_x = 32
+ pixel_y = 0
+ if("West")
+ pixel_x = -32
+ pixel_y = 0
+ user.visible_message(
+ SPAN_NOTICE("[user] adjusts [src]'s positioning with [tool]."),
+ SPAN_NOTICE("You set [src]'s positioning to [choice] with [tool].")
+ )
+
+/obj/structure/noticeboard/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ user.visible_message(
+ SPAN_NOTICE("[user] starts dismantling [src] with [tool]."),
+ SPAN_NOTICE("You start dismantling [src] with [tool].")
+ )
+ if(!tool.use_as_tool(src, user, 5 SECONDS, volume = 50, skill_path = SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
return
- ..()
+ user.visible_message(
+ SPAN_NOTICE("[user] dismantles [src] with [tool]."),
+ SPAN_NOTICE("You dismantle [src] with [tool].")
+ )
+ dismantle()
+
+/obj/structure/noticeboard/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Paper, Photo - Attach
+ if (is_type_in_list(tool, list(/obj/item/paper, /obj/item/photo)))
+ if (jobban_isbanned(user, "Graffitiy"))
+ USE_FEEDBACK_FAILURE("You are banned from leaving persistent information across rounds.")
+ return TRUE
+ if (LAZYLEN(notices) >= max_notices)
+ USE_FEEDBACK_FAILURE("[src] is already full of notices. There's no room for [tool].")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(tool, user)
+ return TRUE
+ add_paper(tool)
+ SSpersistence.track_value(tool, /datum/persistent/paper)
+ user.visible_message(
+ SPAN_NOTICE("[user] pins [tool] to [src]."),
+ SPAN_NOTICE("You pin [tool] to [src].")
+ )
+ return TRUE
+
+ . = ..()
+
/obj/structure/noticeboard/attack_ai(mob/user)
- examine(user)
+ examinate(user, src)
/obj/structure/noticeboard/attack_hand(mob/user)
- examine(user)
+ examinate(user, src)
/obj/structure/noticeboard/examine(mob/user)
. = ..()
@@ -171,9 +193,9 @@
var/obj/item/pen/pen = user.IsHolding(/obj/item/pen)
if(istype(pen))
add_fingerprint(user)
- P.attackby(pen, user)
+ pen.resolve_attackby(P, user)
else
- to_chat(user, SPAN_WARNING("You need a pen to write on \the [P]."))
+ to_chat(user, SPAN_WARNING("You need a pen to write on [P]."))
. = TOPIC_REFRESH
if(. == TOPIC_REFRESH)
@@ -188,33 +210,33 @@
P.SetName("Memo RE: proper analysis procedure")
P.info = " We keep test dummies in pens here for a reason, so standard procedure should be to activate newfound alien artifacts and place the two in close proximity. Promising items I might even approve monkey testing on."
P.stamped = list(/obj/item/stamp/rd)
- P.overlays = list("paper_stamped_rd")
+ P.AddOverlays("paper_stamp-circle")
add_paper(P, skip_icon_update = TRUE)
P = new()
P.SetName("Memo RE: materials gathering")
P.info = "Corasang, the hands-on approach to gathering our samples may very well be slow at times, but it's safer than allowing the blundering miners to roll willy-nilly over our dig sites in their mechs, destroying everything in the process. And don't forget the escavation tools on your way out there! - R.W"
P.stamped = list(/obj/item/stamp/rd)
- P.overlays = list("paper_stamped_rd")
+ P.AddOverlays("paper_stamp-circle")
add_paper(P, skip_icon_update = TRUE)
P = new()
P.SetName("Memo RE: ethical quandaries")
P.info = "Darion-
I don't care what his rank is, our business is that of science and knowledge - questions of moral application do not come into this. Sure, so there are those who would employ the energy-wave particles my modified device has managed to abscond for their own personal gain, but I can hardly see the practical benefits of some of these artifacts our benefactors left behind. Ward--"
P.stamped = list(/obj/item/stamp/rd)
- P.overlays = list("paper_stamped_rd")
+ P.AddOverlays("paper_stamp-circle")
add_paper(P, skip_icon_update = TRUE)
P = new()
P.SetName("READ ME! Before you people destroy any more samples")
P.info = "how many times do i have to tell you people, these xeno-arch samples are del-i-cate, and should be handled so! careful application of a focussed, concentrated heat or some corrosive liquids should clear away the extraneous carbon matter, while application of an energy beam will most decidedly destroy it entirely - like someone did to the chemical dispenser! W, the one who signs your paychecks"
P.stamped = list(/obj/item/stamp/rd)
- P.overlays = list("paper_stamped_rd")
+ P.AddOverlays("paper_stamp-circle")
add_paper(P, skip_icon_update = TRUE)
P = new()
P.SetName("Reminder regarding the anomalous material suits")
P.info = "Do you people think the anomaly suits are cheap to come by? I'm about a hair trigger away from instituting a log book for the damn things. Only wear them if you're going out for a dig, and for god's sake don't go tramping around in them unless you're field testing something, R"
P.stamped = list(/obj/item/stamp/rd)
- P.overlays = list("paper_stamped_rd")
+ P.AddOverlays("paper_stamp-circle")
add_paper(P)
diff --git a/code/modules/persistence/persistence_datum.dm b/code/modules/persistence/persistence_datum.dm
index d7c49984fe2c5..5c6f33445e10b 100644
--- a/code/modules/persistence/persistence_datum.dm
+++ b/code/modules/persistence/persistence_datum.dm
@@ -18,7 +18,7 @@
if(name)
filename = "data/persistent/[lowertext(GLOB.using_map.name)]-[lowertext(name)].json"
if(!isnull(entries_decay_at) && !isnull(entries_expire_at))
- entries_decay_at = Floor(entries_expire_at * entries_decay_at)
+ entries_decay_at = floor(entries_expire_at * entries_decay_at)
/datum/persistent/proc/GetValidTurf(turf/T, list/tokens)
if(T && CheckTurfContents(T, tokens))
@@ -106,9 +106,14 @@
for(var/thing in SSpersistence.tracking_values[type])
if(IsValidEntry(thing))
entries += list(CompileEntry(thing))
- if(fexists(filename))
- fdel(filename)
- to_file(file(filename), json_encode(entries))
+ // [SIERRA-EDIT] - RUST_G
+ // if(fexists(filename)) // SIERRA-EDIT - ORIGINAL
+ // fdel(filename) // SIERRA-EDIT - ORIGINAL
+ // to_file(file(filename), json_encode(entries)) // SIERRA-EDIT - ORIGINAL
+ var/error = rustg_file_write(json_encode(entries), filename)
+ if (error)
+ crash_with(error)
+ // [/SIERRA-EDIT]
/datum/persistent/proc/RemoveValue(atom/value)
qdel(value)
diff --git a/code/modules/persistence/persistence_datum_filth.dm b/code/modules/persistence/persistence_datum_filth.dm
index 29d402f507438..81f4dacea5459 100644
--- a/code/modules/persistence/persistence_datum_filth.dm
+++ b/code/modules/persistence/persistence_datum_filth.dm
@@ -22,12 +22,12 @@
new _path(creating, tokens["age"]+1)
/datum/persistent/filth/GetEntryAge(atom/entry)
- var/obj/effect/decal/cleanable/filth = entry
+ var/obj/decal/cleanable/filth = entry
return filth.age
/datum/persistent/filth/proc/GetEntryPath(atom/entry)
- var/obj/effect/decal/cleanable/filth = entry
- return filth.generic_filth ? /obj/effect/decal/cleanable/filth : filth.type
+ var/obj/decal/cleanable/filth = entry
+ return filth.generic_filth ? /obj/decal/cleanable/filth : filth.type
/datum/persistent/filth/CompileEntry(atom/entry)
. = ..()
diff --git a/code/modules/persistence/persistence_datum_graffiti.dm b/code/modules/persistence/persistence_datum_graffiti.dm
index 6ac5002a025b3..1d8d803df37e6 100644
--- a/code/modules/persistence/persistence_datum_graffiti.dm
+++ b/code/modules/persistence/persistence_datum_graffiti.dm
@@ -10,14 +10,14 @@
/datum/persistent/graffiti/CheckTurfContents(turf/T, list/tokens)
var/too_much_graffiti = 0
- for(var/obj/effect/decal/writing/W in .)
+ for(var/obj/decal/writing/W in .)
too_much_graffiti++
if(too_much_graffiti >= 5)
return FALSE
return TRUE
/datum/persistent/graffiti/CreateEntryInstance(turf/creating, list/tokens)
- new /obj/effect/decal/writing(creating, tokens["age"]+1, tokens["message"], tokens["author"])
+ new /obj/decal/writing(creating, tokens["age"]+1, tokens["message"], tokens["author"])
/datum/persistent/graffiti/IsValidEntry(atom/entry)
. = ..()
@@ -26,17 +26,17 @@
. = T.can_engrave()
/datum/persistent/graffiti/GetEntryAge(atom/entry)
- var/obj/effect/decal/writing/save_graffiti = entry
+ var/obj/decal/writing/save_graffiti = entry
return save_graffiti.graffiti_age
/datum/persistent/graffiti/CompileEntry(atom/entry, write_file)
. = ..()
- var/obj/effect/decal/writing/save_graffiti = entry
+ var/obj/decal/writing/save_graffiti = entry
.["author"] = save_graffiti.author || "unknown"
.["message"] = save_graffiti.message
/datum/persistent/graffiti/GetAdminDataStringFor(thing, can_modify, mob/user)
- var/obj/effect/decal/writing/save_graffiti = thing
+ var/obj/decal/writing/save_graffiti = thing
if(can_modify)
. = "
"
+
+ var/datum/browser/popup = new(user, "\ref[src]-reagent_temperature_window", "[capitalize(name)]")
+ popup.set_content(jointext(dat, null))
+ popup.open()
+
+/obj/machinery/reagent_temperature/CanUseTopic(mob/user, state, href_list)
+ if(href_list && href_list["remove_container"])
+ . = ..(user, GLOB.physical_state, href_list)
+ if(. == STATUS_CLOSE)
+ to_chat(user, SPAN_WARNING("You are too far away."))
+ return
+ return ..()
+
+/obj/machinery/reagent_temperature/proc/ToggleUsePower()
+
+ if(inoperable())
+ return TOPIC_HANDLED
+
+ update_use_power(use_power <= POWER_USE_IDLE ? POWER_USE_ACTIVE : POWER_USE_IDLE)
+ QUEUE_TEMPERATURE_ATOMS(src)
+ update_icon()
+
+ return TOPIC_REFRESH
+
+/obj/machinery/reagent_temperature/proc/ToggleMode()
+ heating = !heating
+ update_icon()
+
+ return TOPIC_REFRESH
+
+/obj/machinery/reagent_temperature/OnTopic(mob/user, href_list)
+
+ if(href_list["adjust_temperature"])
+ target_temperature = clamp(target_temperature + text2num(href_list["adjust_temperature"]), min_temperature, max_temperature)
+ . = TOPIC_REFRESH
+
+ if(href_list["toggle_power"])
+ . = ToggleUsePower()
+ if(. != TOPIC_REFRESH)
+ to_chat(user, SPAN_WARNING("The button clicks, but nothing happens."))
+
+ if(href_list["remove_container"])
+ eject_beaker(user)
+ . = TOPIC_REFRESH
+
+ if(href_list["switch_mode"])
+ ToggleMode()
+ . = TOPIC_REFRESH
+
+ if(. == TOPIC_REFRESH)
+ interact(user)
+
+#undef MINIMUM_GLOW_TEMPERATURE
+#undef MINIMUM_GLOW_VALUE
+#undef MAXIMUM_GLOW_VALUE
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index 559021594b6a7..5b2458656290d 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers
name = "Container"
desc = "..."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = null
w_class = ITEM_SIZE_SMALL
var/amount_per_transfer_from_this = 5
@@ -39,9 +39,6 @@
/obj/item/reagent_containers/attack_self(mob/user as mob)
return
-/obj/item/reagent_containers/afterattack(obj/target, mob/user, flag)
- return
-
/obj/item/reagent_containers/proc/reagentlist() // For attack logs
if(reagents)
return reagents.get_reagents()
@@ -50,7 +47,7 @@
/obj/item/reagent_containers/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W, /obj/item/pen) || istype(W, /obj/item/device/flashlight/pen))
var/tmp_label = sanitizeSafe(input(user, "Enter a label for [name]", "Label", label_text), MAX_NAME_LEN)
- if(length(tmp_label) > 10)
+ if(length_char(tmp_label) > 10)
to_chat(user, SPAN_NOTICE("The label can be at most 10 characters long."))
else
to_chat(user, SPAN_NOTICE("You set the label to \"[tmp_label]\"."))
@@ -65,7 +62,7 @@
else
SetName("[initial(name)] ([label_text])")
-/obj/item/reagent_containers/proc/standard_dispenser_refill(mob/user, obj/structure/reagent_dispensers/target) // This goes into afterattack
+/obj/item/reagent_containers/proc/standard_dispenser_refill(mob/user, obj/structure/reagent_dispensers/target) // This goes into use_after()
if(!istype(target))
return 0
@@ -81,7 +78,7 @@
to_chat(user, SPAN_NOTICE("You fill [src] with [trans] units of the contents of [target]."))
return 1
-/obj/item/reagent_containers/proc/standard_splash_mob(mob/user, mob/target) // This goes into afterattack
+/obj/item/reagent_containers/proc/standard_splash_mob(mob/user, mob/target) // This goes into use_after()
if(!istype(target))
return
@@ -145,11 +142,11 @@
var/is_self = target == user
if (!target.check_has_mouth())
to_chat(user, SPAN_WARNING("[is_self ? "You" : "\The [target]"] can't consume \the [src] - [is_self ? "you" : "they"] don't have a mouth!"))
- return FALSE
+ return TRUE
var/obj/item/blocker = target.check_mouth_coverage()
if (blocker)
to_chat(user, SPAN_WARNING("[is_self ? "Your" : "\The [target]'s"] [blocker] is in the way!"))
- return FALSE
+ return TRUE
user?.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
if (is_self)
self_feed_message(target)
@@ -157,7 +154,7 @@
else
other_feed_message_start(user, target)
if (!do_after(user, 3 SECONDS, target, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS))
- return FALSE
+ return TRUE
other_feed_message_finish(user, target)
add_trace_DNA(target)
var/contained = reagentlist()
@@ -165,14 +162,14 @@
feed_sound(target)
var/transfer_amount = target.species?.ingest_amount || 10
if (user.a_intent == I_DISARM)
- transfer_amount = Ceil(transfer_amount * 0.5)
+ transfer_amount = ceil(transfer_amount * 0.5)
else if (user.a_intent == I_GRAB)
- transfer_amount = Ceil(transfer_amount * 1.5)
+ transfer_amount = ceil(transfer_amount * 1.5)
reagents.trans_to_mob(target, transfer_amount, CHEM_INGEST)
return TRUE
-/obj/item/reagent_containers/proc/standard_pour_into(mob/user, atom/target) // This goes into afterattack and yes, it's atom-level
+/obj/item/reagent_containers/proc/standard_pour_into(mob/user, atom/target) // This goes into use_after() and yes, it's atom-level
if(!target.reagents)
return 0
@@ -204,8 +201,8 @@
/obj/item/reagent_containers/AltClick(mob/user)
if(possible_transfer_amounts)
set_amount_per_transfer_from_this()
- else
- return ..()
+ return TRUE
+ return ..()
/obj/item/reagent_containers/examine(mob/user)
. = ..()
@@ -213,9 +210,9 @@
return
if(hasHUD(user, HUD_SCIENCE))
var/prec = user.skill_fail_chance(SKILL_CHEMISTRY, 10)
- to_chat(user, SPAN_NOTICE("The [src] contains: [reagents.get_reagents(precision = prec)]."))
- else if((loc == user) && user.skill_check(SKILL_CHEMISTRY, SKILL_EXPERT))
- to_chat(user, SPAN_NOTICE("Using your chemistry knowledge, you identify the following reagents in \the [src]: [reagents.get_reagents(!user.skill_check(SKILL_CHEMISTRY, SKILL_PROF), 5)]."))
+ . += SPAN_NOTICE("The [src] contains: [reagents.get_reagents(precision = prec)].")
+ else if((loc == user) && user.skill_check(SKILL_CHEMISTRY, SKILL_EXPERIENCED))
+ . += SPAN_NOTICE("Using your chemistry knowledge, you identify the following reagents in \the [src]: [reagents.get_reagents(!user.skill_check(SKILL_CHEMISTRY, SKILL_MASTER), 5)].")
/obj/item/reagent_containers/ex_act(severity)
if(reagents)
diff --git a/code/modules/reagents/reagent_containers/borghypo.dm b/code/modules/reagents/reagent_containers/borghypo.dm
index 1a4c81e3198f8..d6d11feaecc72 100644
--- a/code/modules/reagents/reagent_containers/borghypo.dm
+++ b/code/modules/reagents/reagent_containers/borghypo.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/borghypo
name = "robot hypospray"
desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment."
- icon = 'icons/obj/syringe.dmi'
+ icon = 'icons/obj/tools/syringe.dmi'
item_state = "hypo"
icon_state = "borghypo"
amount_per_transfer_from_this = 5
@@ -56,34 +56,37 @@
reagent_volumes[T] = min(reagent_volumes[T] + 5, volume)
return 1
-/obj/item/reagent_containers/borghypo/attack(mob/living/M, mob/user, target_zone)
- if(!istype(M))
- return
+/obj/item/reagent_containers/borghypo/use_before(mob/living/M, mob/user)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
- if(mode && !reagent_volumes[reagent_ids[mode]])
+ if (mode && !reagent_volumes[reagent_ids[mode]])
to_chat(user, SPAN_WARNING("\The [src] is empty."))
- return
+ return TRUE
var/obj/item/reagent_containers/container = null
if (!mode)
container = dispense.resolve()
if (!valid_container(user, container))
to_chat(user, SPAN_WARNING("Can't find the container to dispense from."))
- return
+ return TRUE
if (!container.reagents?.total_volume)
to_chat(user, SPAN_WARNING("\The [container] is empty."))
+ return TRUE
+ var/target_zone = user.zone_sel.selecting
var/allow = M.can_inject(user, target_zone)
if (allow)
if (allow == INJECTION_PORT)
user.visible_message(SPAN_WARNING("\The [user] begins hunting for an injection port on \the [M]'s suit!"))
- if(!user.do_skilled(INJECTION_PORT_DELAY, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
- return
+ if (!user.do_skilled(INJECTION_PORT_DELAY, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
+ return TRUE
to_chat(user, SPAN_NOTICE("You inject [M] with the injector."))
- if(ishuman(M))
+ if (ishuman(M))
var/mob/living/carbon/human/H = M
H.custom_pain(SPAN_WARNING("You feel a tiny prick!"), 1, TRUE, H.get_organ(user.zone_sel.selecting))
- if(M.reagents)
+ if (M.reagents)
if (mode)
var/datum/reagent/R = reagent_ids[mode]
var/should_admin_log = initial(R.should_admin_log)
@@ -93,6 +96,7 @@
if (should_admin_log)
admin_inject_log(user, M, src, R, transferred)
to_chat(user, SPAN_NOTICE("[transferred] units injected. [reagent_volumes[R]] units remaining."))
+ return TRUE
else
var/datum/reagents/R = container.reagents
var/should_admin_log = R.should_admin_log()
@@ -101,7 +105,7 @@
if (should_admin_log)
admin_inject_log(user, M, src, contained, transferred)
to_chat(user, SPAN_NOTICE("[transferred] units injected. [R.total_volume] units remaining in \the [container]."))
- return
+ return TRUE
/obj/item/reagent_containers/borghypo/attack_self(mob/user as mob)
ui_interact(user)
@@ -113,15 +117,15 @@
if (mode)
var/datum/reagent/R = reagent_ids[mode]
- to_chat(user, SPAN_NOTICE("It is currently producing [initial(R.name)] and has [reagent_volumes[R]] out of [volume] units left."))
+ . += SPAN_NOTICE("It is currently producing [initial(R.name)] and has [reagent_volumes[R]] out of [volume] units left.")
else
var/obj/item/reagent_containers/container = dispense.resolve()
if (istype(container))
var/datum/reagents/R = container.reagents
if (istype(R))
- to_chat(user, SPAN_NOTICE("It is currently dispensing from \the [container] which has [R.total_volume] out of [R.maximum_volume] units left."))
+ . += SPAN_NOTICE("It is currently dispensing from \the [container] which has [R.total_volume] out of [R.maximum_volume] units left.")
return
- to_chat(user, SPAN_WARNING("It is currently empty."))
+ . += SPAN_WARNING("It is currently empty.")
/obj/item/reagent_containers/borghypo/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE)
var/data = list()
@@ -203,7 +207,7 @@
/obj/item/reagent_containers/borghypo/service
name = "cyborg drink synthesizer"
desc = "A portable drink dispencer."
- icon = 'icons/obj/drinks.dmi'
+ icon = 'icons/obj/food/drinks.dmi'
icon_state = "shaker"
charge_cost = 5
recharge_time = 3
@@ -245,25 +249,24 @@
/datum/reagent/ethanol/coffee/kahlua
)
-/obj/item/reagent_containers/borghypo/service/attack(mob/M, mob/user)
- return
-
-/obj/item/reagent_containers/borghypo/service/afterattack(obj/target, mob/user, proximity)
- if(!proximity)
- return
-
- if(!target.is_open_container() || !target.reagents)
- return
+/obj/item/reagent_containers/borghypo/service/use_before(mob/M, mob/user)
+ return FALSE //We don't want the service borg to be able to inject alcohol into blood.
+/obj/item/reagent_containers/borghypo/service/use_after(obj/target, mob/living/user, click_parameters)
+ if (!target.reagents)
+ return FALSE
+ if(!target.is_open_container())
+ to_chat(user, SPAN_WARNING("\The [target] is capped."))
+ return TRUE
if(!target.reagents.get_free_space())
- to_chat(user, SPAN_WARNING("[target] is full."))
- return
+ to_chat(user, SPAN_WARNING("\The [target] is full."))
+ return TRUE
if (mode)
var/datum/reagent/R = reagent_ids[mode]
if(!reagent_volumes[R])
- to_chat(user, SPAN_WARNING("[src] is out of this reagent, give it some time to refill."))
- return
+ to_chat(user, SPAN_WARNING("\The [src] is out of this reagent, give it some time to refill."))
+ return TRUE
var/transferred = min(amount_per_transfer_from_this, reagent_volumes[R])
target.reagents.add_reagent(R, transferred)
reagent_volumes[R] -= transferred
@@ -272,17 +275,18 @@
var/obj/item/reagent_containers/container = dispense.resolve()
if (!valid_container(user, container))
to_chat(user, SPAN_WARNING("Can't find the container to dispense from."))
- return
+ return TRUE
var/datum/reagents/R = container.reagents
if (!R || !R.total_volume)
to_chat(user, SPAN_WARNING("\The [container] is empty."))
var/transferred = R.trans_to_holder(target.reagents, amount_per_transfer_from_this)
to_chat(user, "You transfer [transferred] units of the solution to [target].")
+ return TRUE
/obj/item/robot_rack/bottle
name = "bottle rack"
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-1"
object_type = /obj/item/reagent_containers/glass/bottle
capacity = 4
@@ -292,10 +296,10 @@
/obj/item/robot_rack/bottle/examine(mob/user)
. = ..()
var/mob/living/silicon/robot/R = user
- if (istype(R))
+ if(istype(R))
var/obj/item/reagent_containers/borghypo/hypo = locate() in R.module.equipment
- if (istype(hypo))
- to_chat(user, "Its contents are available to \the [hypo].")
+ if(istype(hypo))
+ . += SPAN_NOTICE("Its contents are available to [hypo].")
// Extra message for if you try to pick up beakers
/obj/item/robot_rack/bottle/resolve_attackby(obj/O, mob/user, click_params)
diff --git a/code/modules/reagents/reagent_containers/drinkingglass/drinkingglass.dm b/code/modules/reagents/reagent_containers/drinkingglass/drinkingglass.dm
index 27e4be1fc855a..d22345c50ad9e 100644
--- a/code/modules/reagents/reagent_containers/drinkingglass/drinkingglass.dm
+++ b/code/modules/reagents/reagent_containers/drinkingglass/drinkingglass.dm
@@ -8,7 +8,7 @@ var/global/const/DRINK_ICON_NOISY = "noise"
name = "glass" // Name when empty
base_name = "glass"
desc = "A generic drinking glass." // Description when empty
- icon = 'icons/obj/drink_glasses/square.dmi'
+ icon = 'icons/obj/food/drink_glasses/square.dmi'
icon_state = null
base_icon = "square" // Base icon name
filling_states = "20;40;60;80;100"
@@ -33,20 +33,19 @@ var/global/const/DRINK_ICON_NOISY = "noise"
/obj/item/reagent_containers/food/drinks/glass2/examine(mob/M)
. = ..()
-
for(var/I in extras)
if(istype(I, /obj/item/glass_extra))
- to_chat(M, "There is \a [I] in \the [src].")
+ . += SPAN_NOTICE("There is [I] in [src].")
else if(istype(I, /obj/item/reagent_containers/food/snacks/fruit_slice))
- to_chat(M, "There is \a [I] on the rim.")
+ . += SPAN_NOTICE("There is [I] on the rim.")
else
- to_chat(M, "There is \a [I] somewhere on the glass. Somehow.")
+ . += SPAN_NOTICE("There is [I] somewhere on the glass. Somehow.")
if(has_ice())
- to_chat(M, "There is some ice floating in the drink.")
+ . += SPAN_NOTICE("There is some ice floating in the drink.")
if(has_fizz())
- to_chat(M, "It is fizzing slightly.")
+ . += SPAN_NOTICE("It is fizzing slightly.")
/obj/item/reagent_containers/food/drinks/glass2/proc/has_ice()
if(length(reagents?.reagent_list))
@@ -126,19 +125,14 @@ var/global/const/DRINK_ICON_NOISY = "noise"
playsound(src.loc, "sound/effects/Glasshit.ogg", 50)
/obj/item/reagent_containers/food/drinks/glass2/proc/can_add_extra(obj/item/glass_extra/GE)
- if(!("[base_icon]_[GE.glass_addition]left" in icon_states(icon)))
- return 0
- if(!("[base_icon]_[GE.glass_addition]right" in icon_states(icon)))
- return 0
-
- return 1
+ return ICON_HAS_STATE(icon, "[base_icon]_[GE.glass_addition]left") || ICON_HAS_STATE(icon, "[base_icon]_[GE.glass_addition]right")
/obj/item/reagent_containers/food/drinks/glass2/proc/get_filling_overlay(amount, overlay)
var/image/I = new()
if(!filling_icons_cache["[base_icon][amount][overlay]"])
var/icon/base = new/icon(icon, "[base_icon][amount]")
if(overlay)
- var/icon/extra = new/icon('icons/obj/drink_glasses/extras.dmi', overlay)
+ var/icon/extra = new/icon('icons/obj/food/drink_glasses/extras.dmi', overlay)
base.Blend(extra, ICON_MULTIPLY)
filling_icons_cache["[base_icon][amount][overlay]"] = image(base)
I.appearance = filling_icons_cache["[base_icon][amount][overlay]"]
@@ -146,7 +140,7 @@ var/global/const/DRINK_ICON_NOISY = "noise"
/obj/item/reagent_containers/food/drinks/glass2/on_update_icon()
underlays.Cut()
- overlays.Cut()
+ ClearOverlays()
if (length(reagents?.reagent_list))
var/datum/reagent/R = reagents.get_master_reagent()
@@ -168,9 +162,9 @@ var/global/const/DRINK_ICON_NOISY = "noise"
over_liquid |= image(icon, src, "[base_icon]_vapor")
for(var/S in R.glass_special)
- if("[base_icon]_[S]" in icon_states(icon))
+ if(ICON_HAS_STATE(icon, "[base_icon]_[S]"))
under_liquid |= image(icon, src, "[base_icon]_[S]")
- else if("[base_icon][amnt]_[S]" in icon_states(icon))
+ else if(ICON_HAS_STATE(icon, "[base_icon][amnt]_[S]"))
over_liquid |= image(icon, src, "[base_icon][amnt]_[S]")
underlays += under_liquid
@@ -178,11 +172,11 @@ var/global/const/DRINK_ICON_NOISY = "noise"
var/image/filling = get_filling_overlay(amnt, R.glass_icon)
filling.color = reagents.get_color()
if(filling_overlayed)
- overlays += filling
+ AddOverlays(filling)
else
underlays += filling
- overlays += over_liquid
+ AddOverlays(over_liquid)
else
SetName(custom_name || initial(name))
@@ -207,10 +201,11 @@ var/global/const/DRINK_ICON_NOISY = "noise"
else continue
side = "right"
-/obj/item/reagent_containers/food/drinks/glass2/afterattack(obj/target, mob/user, proximity)
- if (!proximity || standard_dispenser_refill(user, target) || standard_pour_into(user, target))
+/obj/item/reagent_containers/food/drinks/glass2/use_after(obj/target, mob/living/user, click_parameters)
+ if (standard_dispenser_refill(user, target) || standard_pour_into(user, target))
return TRUE
splashtarget(target, user)
+ return TRUE
/obj/item/reagent_containers/food/drinks/glass2/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/material/kitchen/utensil/spoon))
diff --git a/code/modules/reagents/reagent_containers/drinkingglass/extras.dm b/code/modules/reagents/reagent_containers/drinkingglass/extras.dm
index 6875f052a6bce..be2cceffdf594 100644
--- a/code/modules/reagents/reagent_containers/drinkingglass/extras.dm
+++ b/code/modules/reagents/reagent_containers/drinkingglass/extras.dm
@@ -52,7 +52,7 @@
var/glass_addition
var/glass_desc
w_class = ITEM_SIZE_TINY
- icon = 'icons/obj/drink_glasses/extras.dmi'
+ icon = 'icons/obj/food/drink_glasses/extras.dmi'
/obj/item/glass_extra/stick
name = "stick"
diff --git a/code/modules/reagents/reagent_containers/drinkingglass/glass_boxes.dm b/code/modules/reagents/reagent_containers/drinkingglass/glass_boxes.dm
index b499c27f6d393..285c372d246ef 100644
--- a/code/modules/reagents/reagent_containers/drinkingglass/glass_boxes.dm
+++ b/code/modules/reagents/reagent_containers/drinkingglass/glass_boxes.dm
@@ -1,7 +1,7 @@
/obj/item/storage/box/mixedglasses
name = "glassware box"
desc = "A box of assorted glassware."
- can_hold = list(/obj/item/reagent_containers/food/drinks/glass2)
+ contents_allowed = list(/obj/item/reagent_containers/food/drinks/glass2)
startswith = list(
/obj/item/reagent_containers/food/drinks/glass2/square,
@@ -21,7 +21,7 @@
/obj/item/storage/box/glasses
name = "box of glasses"
var/glass_type = /obj/item/reagent_containers/food/drinks/glass2
- can_hold = list(/obj/item/reagent_containers/food/drinks/glass2)
+ contents_allowed = list(/obj/item/reagent_containers/food/drinks/glass2)
/obj/item/storage/box/glasses/Initialize()
. = ..()
@@ -65,7 +65,7 @@
/obj/item/storage/box/glass_extras
name = "box of cocktail garnishings"
var/extra_type = /obj/item/glass_extra
- can_hold = list(/obj/item/glass_extra)
+ contents_allowed = list(/obj/item/glass_extra)
storage_slots = 14
/obj/item/storage/box/glass_extras/Initialize()
diff --git a/code/modules/reagents/reagent_containers/drinkingglass/glass_types.dm b/code/modules/reagents/reagent_containers/drinkingglass/glass_types.dm
index d9110c2512390..55981bb6b0b94 100644
--- a/code/modules/reagents/reagent_containers/drinkingglass/glass_types.dm
+++ b/code/modules/reagents/reagent_containers/drinkingglass/glass_types.dm
@@ -2,7 +2,8 @@
name = "half-pint glass"
base_name = "glass"
base_icon = "square"
- icon = 'icons/obj/drink_glasses/square.dmi'
+ icon = 'icons/obj/food/drink_glasses/square.dmi'
+ icon_state = "square"
desc = "Your standard drinking glass."
filling_states = "20;40;60;80;100"
volume = 30
@@ -14,7 +15,8 @@
desc = "A robust tumbler with a thick, weighted bottom."
base_name = "glass"
base_icon = "rocks"
- icon = 'icons/obj/drink_glasses/rocks.dmi'
+ icon = 'icons/obj/food/drink_glasses/rocks.dmi'
+ icon_state = "rocks"
filling_states = "25;50;75;100"
volume = 20
possible_transfer_amounts = "5;10;20"
@@ -25,7 +27,8 @@
desc = "Stemware with an untapered conical bowl."
base_name = "glass"
base_icon = "shake"
- icon = 'icons/obj/drink_glasses/shake.dmi'
+ icon = 'icons/obj/food/drink_glasses/shake.dmi'
+ icon_state = "shake"
filling_states = "25;50;75;100"
volume = 30
possible_transfer_amounts = "5;10;15;30"
@@ -36,7 +39,8 @@
desc = "Fragile stemware with a stout conical bowl. Don't spill."
base_name = "glass"
base_icon = "cocktail"
- icon = 'icons/obj/drink_glasses/cocktail.dmi'
+ icon = 'icons/obj/food/drink_glasses/cocktail.dmi'
+ icon_state = "cocktail"
filling_states = "33;66;100"
volume = 15
possible_transfer_amounts = "5;10;15"
@@ -47,7 +51,8 @@
desc = "A small glass, designed so that its contents can be consumed in one gulp."
base_name = "shot"
base_icon = "shot"
- icon = 'icons/obj/drink_glasses/shot.dmi'
+ icon = 'icons/obj/food/drink_glasses/shot.dmi'
+ icon_state = "shot"
filling_states = "33;66;100"
volume = 5
matter = list(MATERIAL_GLASS = 15)
@@ -58,7 +63,8 @@
name = "pint glass"
base_name = "pint"
base_icon = "pint"
- icon = 'icons/obj/drink_glasses/pint.dmi'
+ icon = 'icons/obj/food/drink_glasses/pint.dmi'
+ icon_state = "pint"
filling_states = "16;33;50;66;83;100"
volume = 60
matter = list(MATERIAL_GLASS = 125)
@@ -70,7 +76,8 @@
desc = "A heavy mug with thick walls."
base_name = "mug"
base_icon = "mug"
- icon = 'icons/obj/drink_glasses/mug.dmi'
+ icon = 'icons/obj/food/drink_glasses/mug.dmi'
+ icon_state = "mug"
filling_states = "25;50;75;100"
volume = 40
possible_transfer_amounts = "5;10;20;40"
@@ -81,7 +88,8 @@
desc = "A piece of elegant stemware."
base_name = "glass"
base_icon = "wine"
- icon = 'icons/obj/drink_glasses/wine.dmi'
+ icon = 'icons/obj/food/drink_glasses/wine.dmi'
+ icon_state = "wine"
filling_states = "20;40;60;80;100"
volume = 25
possible_transfer_amounts = "5;10;15;25"
@@ -92,7 +100,8 @@
desc = "A piece of very elegant stemware."
base_name = "glass"
base_icon = "flute"
- icon = 'icons/obj/drink_glasses/flute.dmi'
+ icon = 'icons/obj/food/drink_glasses/flute.dmi'
+ icon_state = "flute"
filling_states = "20;40;60;80;100"
volume = 25
possible_transfer_amounts = "5;10;15;25"
@@ -103,7 +112,8 @@
desc = "A handled glass pitcher."
base_name = "pitcher"
base_icon = "carafe"
- icon = 'icons/obj/drink_glasses/carafe.dmi'
+ icon = 'icons/obj/food/drink_glasses/carafe.dmi'
+ icon_state = "carafe"
filling_states = "10;20;30;40;50;60;70;80;90;100"
volume = 120
matter = list(MATERIAL_GLASS = 250)
@@ -114,7 +124,7 @@
/obj/item/reagent_containers/food/drinks/glass2/coffeecup
name = "coffee cup"
desc = "A plain white coffee cup."
- icon = 'icons/obj/drink_glasses/coffecup.dmi'
+ icon = 'icons/obj/food/drink_glasses/coffecup.dmi'
icon_state = "coffeecup"
item_state = "coffee"
volume = 30
@@ -157,7 +167,7 @@
/obj/item/reagent_containers/food/drinks/glass2/coffeecup/NT
name = "\improper NT coffee cup"
- desc = "A red NanoTrasen coffee cup."
+ desc = "A red Nanotrasen coffee cup."
icon_state = "coffeecup_NT"
base_name = "\improper NT cup"
@@ -231,7 +241,7 @@
/obj/item/reagent_containers/food/drinks/glass2/coffeecup/teacup
name = "teacup"
desc = "A plain white porcelain teacup."
- icon = 'icons/obj/drink_glasses/teacup.dmi'
+ icon = 'icons/obj/food/drink_glasses/teacup.dmi'
icon_state = "teacup"
item_state = "coffee"
volume = 20
@@ -245,7 +255,7 @@
desc = "A heavy mug. A beagle mug. Careful not to break it!"
icon_state = "beaglemug"
item_state = "coffee"
- icon = 'icons/obj/drink_glasses/mug.dmi'
+ icon = 'icons/obj/food/drink_glasses/mug.dmi'
filling_states = "100"
volume = 40
center_of_mass = "x=17;y=13"
@@ -264,7 +274,8 @@
desc = "A mug made from a hollowed pineapple. Tropical!"
base_name = "pineapple mug"
base_icon = "pineapple"
- icon = 'icons/obj/drink_glasses/pineapple.dmi'
+ icon = 'icons/obj/food/drink_glasses/pineapple.dmi'
+ icon_state = "pineapple"
filling_states = "25;50;75;100"
volume = 40
possible_transfer_amounts = "5;10;20;40"
@@ -274,7 +285,8 @@
name = "coconut cup"
base_name = "coconut cup"
base_icon = "coconut"
- icon = 'icons/obj/drink_glasses/coconut.dmi'
+ icon = 'icons/obj/food/drink_glasses/coconut.dmi'
+ icon_state = "coconut"
desc = "A cup made from a hollowed coconut."
filling_states = "20;40;60;80;100"
volume = 30
@@ -286,7 +298,8 @@
desc = "A snitfer, also known as a cognac glass, made for serving aged spirits."
base_name = "glass"
base_icon = "cognac"
- icon = 'icons/obj/drink_glasses/cognac.dmi'
+ icon = 'icons/obj/food/drink_glasses/cognac.dmi'
+ icon_state = "cognac"
filling_states = "25;50;75;100"
volume = 20
possible_transfer_amounts = "5;10;20"
@@ -297,7 +310,8 @@
desc = "A glass goblet, used to deliver alcohol to the upper class since ancient times."
base_name = "goblet"
base_icon = "goblet"
- icon = 'icons/obj/drink_glasses/goblet.dmi'
+ icon = 'icons/obj/food/drink_glasses/goblet.dmi'
+ icon_state = "goblet"
filling_states = "20;40;60;80;100"
volume = 25
possible_transfer_amounts = "5;10;15;25"
@@ -314,7 +328,7 @@
/obj/item/reagent_containers/food/drinks/glass2/coffeecup/tall
name = "tall coffee cup"
desc = "An unreasonably tall coffee cup, for when you really need to wake up in the morning."
- icon = 'icons/obj/drink_glasses/coffecup_tall.dmi'
+ icon = 'icons/obj/food/drink_glasses/coffecup_tall.dmi'
icon_state = "coffeecup_tall"
volume = 60
center_of_mass = "x=15;y=19"
diff --git a/code/modules/reagents/reagent_containers/drinkingglass/shaker.dm b/code/modules/reagents/reagent_containers/drinkingglass/shaker.dm
index 62aa852d7c35b..83eac21addb72 100644
--- a/code/modules/reagents/reagent_containers/drinkingglass/shaker.dm
+++ b/code/modules/reagents/reagent_containers/drinkingglass/shaker.dm
@@ -5,7 +5,7 @@
desc = "Big enough to contain enough protein to get perfectly swole. Don't mind the bits."
icon_state = "fitness-cup_black"
base_icon = "fitness-cup"
- icon = 'icons/obj/drink_glasses/fitness.dmi'
+ icon = 'icons/obj/food/drink_glasses/fitness.dmi'
volume = 100
matter = list(MATERIAL_PLASTIC = 2000)
filling_states = "10;20;30;40;50;60;70;80;90;100"
diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm
index e4b19506ea39c..c1ce7f8f5667b 100644
--- a/code/modules/reagents/reagent_containers/dropper.dm
+++ b/code/modules/reagents/reagent_containers/dropper.dm
@@ -4,7 +4,7 @@
/obj/item/reagent_containers/dropper
name = "dropper"
desc = "A dropper. Transfers 5 units."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/tools/dropper.dmi'
icon_state = "dropper0"
amount_per_transfer_from_this = 5
possible_transfer_amounts = "1;2;3;4;5"
@@ -12,30 +12,29 @@
slot_flags = SLOT_EARS
volume = 5
-/obj/item/reagent_containers/dropper/afterattack(obj/target, mob/user, proximity)
- if(!target.reagents || !proximity) return
+/obj/item/reagent_containers/dropper/use_after(obj/target, mob/living/user, click_parameters)
+ if(!target.reagents)
+ return FALSE
if(reagents.total_volume)
-
if(!target.reagents.get_free_space())
to_chat(user, SPAN_NOTICE("[target] is full."))
- return
-
+ return TRUE
if(!target.is_open_container() && !ismob(target) && !istype(target, /obj/item/reagent_containers/food) && !istype(target, /obj/item/clothing/mask/smokable/cigarette)) //You can inject humans and food but you can't remove the shit.
to_chat(user, SPAN_NOTICE("You cannot directly fill this object."))
- return
+ return TRUE
var/trans = 0
if(ismob(target))
if(user.a_intent == I_HELP)
- return
+ return TRUE
var/time = 20 //2/3rds the time of a syringe
user.visible_message(SPAN_WARNING("[user] is trying to squirt something into [target]'s eyes!"))
if(!do_after(user, time, target, DO_MEDICAL))
- return
+ return TRUE
if(istype(target, /mob/living/carbon/human))
var/mob/living/carbon/human/victim = target
@@ -54,7 +53,7 @@
if(safe_thing)
trans = reagents.splash(safe_thing, amount_per_transfer_from_this, max_spill=30)
user.visible_message(SPAN_WARNING("[user] tries to squirt something into [target]'s eyes, but fails!"), SPAN_NOTICE("You transfer [trans] units of the solution."))
- return
+ return TRUE
var/mob/living/M = target
if (reagents.should_admin_log())
@@ -65,29 +64,25 @@
trans += reagents.splash(target, reagents.total_volume/2, max_spill = spill_amt)
trans += reagents.trans_to_mob(target, reagents.total_volume/2, CHEM_BLOOD) //I guess it gets into the bloodstream through the eyes or something
user.visible_message(SPAN_WARNING("[user] squirts something into [target]'s eyes!"), SPAN_NOTICE("You transfer [trans] units of the solution."))
-
-
- return
+ return TRUE
else
trans = reagents.splash(target, amount_per_transfer_from_this, max_spill=0) //sprinkling reagents on generic non-mobs. Droppers are very precise
to_chat(user, SPAN_NOTICE("You transfer [trans] units of the solution."))
+ return TRUE
else // Taking from something
if(!target.is_open_container() && !istype(target,/obj/structure/reagent_dispensers))
to_chat(user, SPAN_NOTICE("You cannot directly remove reagents from [target]."))
- return
-
+ return TRUE
if(!target.reagents || !target.reagents.total_volume)
to_chat(user, SPAN_NOTICE("[target] is empty."))
- return
+ return TRUE
var/trans = target.reagents.trans_to_obj(src, amount_per_transfer_from_this)
-
to_chat(user, SPAN_NOTICE("You fill the dropper with [trans] units of the solution."))
-
- return
+ return TRUE
/obj/item/reagent_containers/dropper/on_reagent_change()
update_icon()
diff --git a/code/modules/reagents/reagent_containers/food/condiment.dm b/code/modules/reagents/reagent_containers/food/condiment.dm
index 9b489b6f12124..5644841e628c4 100644
--- a/code/modules/reagents/reagent_containers/food/condiment.dm
+++ b/code/modules/reagents/reagent_containers/food/condiment.dm
@@ -8,7 +8,7 @@
/obj/item/reagent_containers/food/condiment
name = "Condiment Container"
desc = "Just your average condiment container."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "emptycondiment"
atom_flags = ATOM_FLAG_OPEN_CONTAINER
possible_transfer_amounts = "1;5;10"
@@ -41,32 +41,28 @@
/obj/item/reagent_containers/food/condiment/attack_self(mob/user as mob)
return
-/obj/item/reagent_containers/food/condiment/attack(mob/M as mob, mob/user as mob, def_zone)
- if(standard_feed_mob(user, M))
- return
+/obj/item/reagent_containers/food/condiment/use_before(mob/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
+ if (standard_feed_mob(user, M))
+ return TRUE
-/obj/item/reagent_containers/food/condiment/afterattack(obj/target, mob/user, proximity)
- if(!proximity)
- return
-
- if(standard_dispenser_refill(user, target))
- return
- if(standard_pour_into(user, target))
- return
+/obj/item/reagent_containers/food/condiment/use_after(obj/target, mob/living/user, click_parameters)
+ if(standard_dispenser_refill(user, target) || standard_pour_into(user, target))
+ return TRUE
if(istype(target, /obj/item/reagent_containers/food/snacks)) // These are not opencontainers but we can transfer to them
if(!reagents || !reagents.total_volume)
to_chat(user, SPAN_NOTICE("There is no condiment left in \the [src]."))
- return
-
+ return TRUE
if(!target.reagents.get_free_space())
to_chat(user, SPAN_NOTICE("You can't add more condiment to \the [target]."))
- return
+ return TRUE
var/trans = reagents.trans_to_obj(target, amount_per_transfer_from_this)
to_chat(user, SPAN_NOTICE("You add [trans] units of the condiment to \the [target]."))
- else
- ..()
+ return TRUE
/obj/item/reagent_containers/food/condiment/feed_sound(mob/user)
playsound(user.loc, 'sound/items/drink.ogg', rand(10, 50), 1)
@@ -308,7 +304,7 @@
/obj/item/reagent_containers/food/condiment/flour
name = "flour sack"
desc = "A big bag of flour. Good for baking!"
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "flour"
item_state = "flour"
randpixel = 10
@@ -320,7 +316,7 @@
/obj/item/reagent_containers/food/condiment/salt
name = "big bag of salt"
desc = "A nonsensically large bag of salt. Carefully refined from countless shifts."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "salt"
item_state = "flour"
randpixel = 10
@@ -334,7 +330,7 @@
/obj/item/reagent_containers/food/condiment/mint
name = "mint essential oil"
desc = "A small bottle of the essential oil of some kind of mint plant."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "coldsauce"
starting_reagents = list(/datum/reagent/nutriment/mint = 15)
diff --git a/code/modules/reagents/reagent_containers/food/drinks.dm b/code/modules/reagents/reagent_containers/food/drinks.dm
index 182bcf875d4b7..ce03fa778bfa8 100644
--- a/code/modules/reagents/reagent_containers/food/drinks.dm
+++ b/code/modules/reagents/reagent_containers/food/drinks.dm
@@ -4,7 +4,7 @@
/obj/item/reagent_containers/food/drinks
name = "drink"
desc = "Yummy!"
- icon = 'icons/obj/drinks.dmi'
+ icon = 'icons/obj/food/drinks.dmi'
icon_state = null
atom_flags = ATOM_FLAG_OPEN_CONTAINER
amount_per_transfer_from_this = 5
@@ -29,23 +29,19 @@
to_chat(user, SPAN_NOTICE("You open \the [src] with an audible pop!"))
atom_flags |= ATOM_FLAG_OPEN_CONTAINER
-/obj/item/reagent_containers/food/drinks/attack(mob/M as mob, mob/user as mob, def_zone)
+/obj/item/reagent_containers/food/drinks/use_before(mob/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
if(force && !(item_flags & ITEM_FLAG_NO_BLUDGEON) && user.a_intent == I_HURT)
- return ..()
+ return FALSE
if(standard_feed_mob(user, M))
- return
-
- return 0
-
-/obj/item/reagent_containers/food/drinks/afterattack(obj/target, mob/user, proximity)
- if(!proximity) return
+ return TRUE
- if(standard_dispenser_refill(user, target))
- return
- if(standard_pour_into(user, target))
- return
- return ..()
+/obj/item/reagent_containers/food/drinks/use_after(obj/target, mob/living/user, click_parameters)
+ if (standard_dispenser_refill(user, target) || standard_pour_into(user, target))
+ return TRUE
/obj/item/reagent_containers/food/drinks/standard_feed_mob(mob/user, mob/target)
if(!is_open_container())
@@ -79,15 +75,15 @@
if(distance > 1)
return
if(!reagents || reagents.total_volume == 0)
- to_chat(user, SPAN_NOTICE("\The [src] is empty!"))
+ . += SPAN_NOTICE("[src] is empty!")
else if (reagents.total_volume <= volume * 0.25)
- to_chat(user, SPAN_NOTICE("\The [src] is almost empty!"))
+ . += SPAN_NOTICE("[src] is almost empty!")
else if (reagents.total_volume <= volume * 0.66)
- to_chat(user, SPAN_NOTICE("\The [src] is half full!"))
+ . += SPAN_NOTICE("[src] is half full!")
else if (reagents.total_volume <= volume * 0.90)
- to_chat(user, SPAN_NOTICE("\The [src] is almost full!"))
+ . += SPAN_NOTICE("[src] is almost full!")
else
- to_chat(user, SPAN_NOTICE("\The [src] is full!"))
+ . += SPAN_NOTICE("[src] is full!")
/obj/item/reagent_containers/food/drinks/proc/get_filling_state()
var/percent = round((reagents.total_volume / volume) * 100)
@@ -96,7 +92,7 @@
return k
/obj/item/reagent_containers/food/drinks/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(length(reagents.reagent_list) > 0)
if(base_name)
var/datum/reagent/R = reagents.get_master_reagent()
@@ -105,7 +101,7 @@
if(filling_states)
var/image/filling = image(icon, src, "[base_icon][get_filling_state()]")
filling.color = reagents.get_color()
- overlays += filling
+ AddOverlays(filling)
else
SetName(initial(name))
desc = initial(desc)
@@ -323,7 +319,7 @@
icon_state = "coffee"
item_state = "coffee"
center_of_mass = "x=16;y=14"
- filling_states = "100"
+ //filling_states = "100"
base_name = "cup"
base_icon = "cup"
@@ -357,7 +353,7 @@
icon_state = "coffee"
item_state = "coffee"
center_of_mass = "x=16;y=14"
- filling_states = "100"
+ //filling_states = "100"
base_name = "cup"
base_icon = "cup"
diff --git a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
index 856ccf6d11cd8..8bad01cdd0567 100644
--- a/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
+++ b/code/modules/reagents/reagent_containers/food/drinks/bottle.dm
@@ -57,7 +57,7 @@
if (prob(33))
new /obj/item/material/shard (newloc)
B.icon_state = icon_state
- var/icon/I = new ('icons/obj/drinks.dmi', icon_state)
+ var/icon/I = new ('icons/obj/food/drinks.dmi', icon_state)
I.Blend(B.broken_outline, ICON_OVERLAY, rand(5), 1)
I.SwapColor(rgb(255, 0, 220, 255), rgb(0, 0, 0, 0))
B.icon = I
@@ -74,8 +74,8 @@
if (!rag && istype(W, /obj/item/reagent_containers/glass/rag))
insert_rag(W, user)
return
- if (rag && isflamesource(W))
- rag.attackby(W, user)
+ if (rag && W.IsFlameSource())
+ W.resolve_attackby(rag, user)
return
..()
@@ -117,9 +117,9 @@
/obj/item/reagent_containers/food/drinks/bottle/on_update_icon()
underlays.Cut()
if (rag)
- var/underlay_image = image(icon='icons/obj/drinks.dmi', icon_state=rag.on_fire? "[rag_underlay]_lit" : rag_underlay)
+ var/underlay_image = image(icon='icons/obj/food/drinks.dmi', icon_state=rag.on_fire? "[rag_underlay]_lit" : rag_underlay)
underlays += underlay_image
- set_light(rag.light_max_bright, 0.1, rag.light_outer_range, 2, rag.light_color)
+ set_light(rag.light_range, rag.light_power, rag.light_color)
else
set_light(0)
@@ -717,6 +717,14 @@
reagents.add_reagent(/datum/reagent/ethanol/beer, 30)
+/obj/item/reagent_containers/food/drinks/bottle/small/beer/fake
+
+
+/obj/item/reagent_containers/food/drinks/bottle/small/beer/fake/Initialize()
+ . = ..()
+ reagents.add_reagent(/datum/reagent/chloralhydrate/beer, 50)
+
+
/obj/item/reagent_containers/food/drinks/bottle/small/ale
name = "\improper Magm-Ale"
desc = "A true dorf's drink of choice."
@@ -805,18 +813,14 @@
/obj/item/broken_bottle
name = "Broken Bottle"
desc = "A bottle with a sharp broken bottom."
- icon = 'icons/obj/drinks.dmi'
+ icon = 'icons/obj/food/drinks.dmi'
icon_state = "broken_bottle"
force = 9
throwforce = 5
throw_speed = 3
throw_range = 5
item_state = "beer"
+ hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("stabbed", "slashed", "attacked")
sharp = TRUE
- var/icon/broken_outline = icon('icons/obj/drinks.dmi', "broken")
-
-
-/obj/item/broken_bottle/attack(mob/living/carbon/M, mob/living/carbon/user)
- playsound(loc, 'sound/weapons/bladeslice.ogg', 50, 1, -1)
- return ..()
+ var/icon/broken_outline = icon('icons/obj/food/drinks.dmi', "broken")
diff --git a/code/modules/reagents/reagent_containers/food/fish.dm b/code/modules/reagents/reagent_containers/food/fish.dm
index 2f8c7e57508ba..3752ac070e6ab 100644
--- a/code/modules/reagents/reagent_containers/food/fish.dm
+++ b/code/modules/reagents/reagent_containers/food/fish.dm
@@ -27,10 +27,10 @@
return TRUE
var/list/toxins = reagents.get_reagent_amount_list(/datum/reagent/toxin)
for (var/toxin_type in toxins)
- if (user.skill_fail_prob(SKILL_COOKING, 90, SKILL_PROF))
+ if (user.skill_fail_prob(SKILL_COOKING, 90, SKILL_MASTER))
continue
reagents.remove_reagent(toxin_type, toxins[toxin_type])
- var/transfer = Floor(reagents.total_volume * 0.3)
+ var/transfer = floor(reagents.total_volume * 0.3)
for(var/i = 1 to 3)
var/obj/item/reagent_containers/food/snacks/sashimi/sashimi = new (turf, fish_type, color)
reagents.trans_to(sashimi, transfer)
@@ -181,7 +181,7 @@
/obj/random/fish
name = "random fish fillet"
desc = "This is a random fish fillet."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "fishfillet"
color = "#ff4040"
diff --git a/code/modules/reagents/reagent_containers/food/lunch.dm b/code/modules/reagents/reagent_containers/food/lunch.dm
index c63336cd06149..46d8b9065e29d 100644
--- a/code/modules/reagents/reagent_containers/food/lunch.dm
+++ b/code/modules/reagents/reagent_containers/food/lunch.dm
@@ -106,31 +106,37 @@ var/global/list/lunchables_ethanol_reagents_ = list(
)
/proc/lunchables_lunches()
+ RETURN_TYPE(/list)
if(!(lunchables_lunches_[lunchables_lunches_[1]]))
lunchables_lunches_ = init_lunchable_list(lunchables_lunches_)
return lunchables_lunches_
/proc/lunchables_snacks()
+ RETURN_TYPE(/list)
if(!(lunchables_snacks_[lunchables_snacks_[1]]))
lunchables_snacks_ = init_lunchable_list(lunchables_snacks_)
return lunchables_snacks_
/proc/lunchables_drinks()
+ RETURN_TYPE(/list)
if(!(lunchables_drinks_[lunchables_drinks_[1]]))
lunchables_drinks_ = init_lunchable_list(lunchables_drinks_)
return lunchables_drinks_
/proc/lunchables_drink_reagents()
+ RETURN_TYPE(/list)
if(!(lunchables_drink_reagents_[lunchables_drink_reagents_[1]]))
lunchables_drink_reagents_ = init_lunchable_reagent_list(lunchables_drink_reagents_, /datum/reagent/drink)
return lunchables_drink_reagents_
/proc/lunchables_ethanol_reagents()
+ RETURN_TYPE(/list)
if(!(lunchables_ethanol_reagents_[lunchables_ethanol_reagents_[1]]))
lunchables_ethanol_reagents_ = init_lunchable_reagent_list(lunchables_ethanol_reagents_, /datum/reagent/ethanol)
return lunchables_ethanol_reagents_
/proc/init_lunchable_list(list/lunches)
+ RETURN_TYPE(/list)
. = list()
for(var/lunch in lunches)
var/obj/O = lunch
@@ -138,6 +144,7 @@ var/global/list/lunchables_ethanol_reagents_ = list(
return sortAssoc(.)
/proc/init_lunchable_reagent_list(list/banned_reagents, reagent_types)
+ RETURN_TYPE(/list)
. = list()
for(var/reagent_type in subtypesof(reagent_types))
if(reagent_type in banned_reagents)
diff --git a/code/modules/reagents/reagent_containers/food/sandwich.dm b/code/modules/reagents/reagent_containers/food/sandwich.dm
index b6f31ea393017..37f9be11a7ee2 100644
--- a/code/modules/reagents/reagent_containers/food/sandwich.dm
+++ b/code/modules/reagents/reagent_containers/food/sandwich.dm
@@ -3,7 +3,7 @@
if (is_path_in_list(W.type, list(/obj/item/reagent_containers/food/snacks/custombowl, /obj/item/reagent_containers/food/snacks/csandwich)))
return
var/obj/item/reagent_containers/food/snacks/csandwich/S = new(get_turf(src))
- S.attackby(W,user)
+ W.resolve_attackby(S, user)
qdel(src)
return
. = ..()
@@ -11,7 +11,7 @@
/obj/item/reagent_containers/food/snacks/csandwich
name = "sandwich"
desc = "The best thing since sliced bread."
- icon_state = "breadslice"
+ icon_state = "sandwich_top"
trash = /obj/item/trash/plate
bitesize = 2
var/list/ingredients = list()
@@ -65,7 +65,7 @@
/obj/item/reagent_containers/food/snacks/csandwich/proc/update()
var/i = 0
- overlays.Cut()
+ ClearOverlays()
filling_color = null
var/list/ingredient_names = list()
@@ -79,18 +79,18 @@
I.color = O.filling_color
I.pixel_x = pick(list(-1,0,1))
I.pixel_y = (i*2)+1
- overlays += I
+ AddOverlays(I)
var/image/T = new(src.icon, "sandwich_top")
T.pixel_x = pick(list(-1,0,1))
T.pixel_y = (length(ingredients) * 2)+1
- overlays += T
+ AddOverlays(T)
fullname = english_list(ingredient_names)
SetName(lowertext("[fullname] sandwich"))
renamed = 0 //updating removes custom name
if(length(name) > 80) SetName("[pick(list("absurd","colossal","enormous","ridiculous"))] sandwich")
- w_class = Ceil(clamp((length(ingredients)/2),2,4))
+ w_class = ceil(clamp((length(ingredients)/2),2,4))
/obj/item/reagent_containers/food/snacks/csandwich/Destroy()
QDEL_NULL_LIST(ingredients)
@@ -99,21 +99,19 @@
/obj/item/reagent_containers/food/snacks/csandwich/examine(mob/user)
. = ..(user)
var/obj/item/O = pick(contents)
- to_chat(user, SPAN_ITALIC("You think you can see [O.name] in there."))
-
-/obj/item/reagent_containers/food/snacks/csandwich/attack(mob/M as mob, mob/user as mob, def_zone)
+ . += SPAN_NOTICE("You think you can see [O] in there.")
+/obj/item/reagent_containers/food/snacks/csandwich/use_before(mob/living/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
var/obj/item/shard
- for(var/obj/item/O in contents)
- if(istype(O,/obj/item/material/shard))
+ for (var/obj/item/O in contents)
+ if (istype(O,/obj/item/material/shard))
shard = O
break
- var/mob/living/H
- if(istype(M,/mob/living))
- H = M
-
- if(H && shard && M == user) //This needs a check for feeding the food to other people, but that could be abusable.
- to_chat(H, SPAN_WARNING("You lacerate your mouth on a [shard.name] in the sandwich!"))
- H.adjustBruteLoss(5) //TODO: Target head if human.
- ..()
+ if (shard && M == user)
+ to_chat(M, SPAN_WARNING("You lacerate your mouth on a [shard.name] in the sandwich!"))
+ M.adjustBruteLoss(5) //TODO: Target head if human.
+ return ..()
diff --git a/code/modules/reagents/reagent_containers/food/servingbowl.dm b/code/modules/reagents/reagent_containers/food/servingbowl.dm
index 0358a4d64acc3..d073b8c03dab2 100644
--- a/code/modules/reagents/reagent_containers/food/servingbowl.dm
+++ b/code/modules/reagents/reagent_containers/food/servingbowl.dm
@@ -1,7 +1,7 @@
/obj/item/serving_bowl
name = "serving bowl"
desc = "A portion-sized bowl for serving hungry customers."
- icon = 'icons/obj/food_custom.dmi'
+ icon = 'icons/obj/food/food_custom.dmi'
icon_state = "serving_bowl"
center_of_mass = "x=16;y=10"
w_class = ITEM_SIZE_SMALL
@@ -31,11 +31,12 @@
/obj/item/reagent_containers/food/snacks/custombowl
name = "serving bowl"
desc = "A delicious bowl of food."
- icon = 'icons/obj/food_custom.dmi'
+ icon = 'icons/obj/food/food_custom.dmi'
icon_state = "serving_bowl"
filling_color = null
trash = /obj/item/serving_bowl
bitesize = 2
+ can_use_cooker = FALSE
var/list/ingredients = list()
var/ingredients_left = 4
var/fullname
@@ -95,7 +96,7 @@
ingredients |= snack.name
var/image/image = new (icon, "serving_bowl_[ingredients_left]")
image.color = snack.filling_color
- overlays += image
+ AddOverlays(image)
fullname = english_list(ingredients)
SetName(lowertext("[fullname] bowl"))
--ingredients_left
@@ -107,5 +108,5 @@
/obj/item/reagent_containers/food/snacks/custombowl/examine(mob/user, distance)
. = ..(user)
- if (distance < 2)
- to_chat(user, SPAN_ITALIC("This one contains [fullname]."))
+ if(distance < 2)
+ . += SPAN_NOTICE("This one contains [fullname].")
diff --git a/code/modules/reagents/reagent_containers/food/shaker.dm b/code/modules/reagents/reagent_containers/food/shaker.dm
index c36488cdbe101..19fa41c1adb56 100644
--- a/code/modules/reagents/reagent_containers/food/shaker.dm
+++ b/code/modules/reagents/reagent_containers/food/shaker.dm
@@ -9,11 +9,11 @@
atom_flags = ATOM_FLAG_OPEN_CONTAINER | ATOM_FLAG_NO_REACT
/obj/item/reagent_containers/food/drinks/shaker/attack_self(mob/user as mob)
- if(user.skill_check(SKILL_COOKING, SKILL_PROF))
+ if(user.skill_check(SKILL_COOKING, SKILL_MASTER))
user.visible_message(SPAN_CLASS("rose", "\The [user] shakes \the [src] briskly in one hand, with supreme confidence and competence."), SPAN_CLASS("rose", "You shake \the [src] briskly with one hand."))
mix()
return
- if(user.skill_check(SKILL_COOKING, SKILL_ADEPT))
+ if(user.skill_check(SKILL_COOKING, SKILL_TRAINED))
user.visible_message(SPAN_NOTICE("\The [user] shakes \the [src] briskly, with some skill."), SPAN_NOTICE("You shake \the [src] briskly, with some skill."))
mix()
return
@@ -29,7 +29,7 @@
if(reagents && reagents.total_volume)
atom_flags &= ~ATOM_FLAG_NO_REACT
HANDLE_REACTIONS(reagents)
- addtimer(new Callback(src, .proc/stop_react), SSchemistry.wait)
+ addtimer(CALLBACK(src, PROC_REF(stop_react)), SSchemistry.wait)
/obj/item/reagent_containers/food/drinks/shaker/proc/stop_react()
atom_flags |= ATOM_FLAG_NO_REACT
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index ef327bedfbb04..8d69c8a9e6124 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/food/snacks
name = "snack"
desc = "Yummy!"
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
center_of_mass = "x=16;y=16"
var/bitesize = 1
var/bitecount = 0
@@ -14,6 +14,7 @@
var/list/eat_sound = 'sound/items/eatfood.ogg'
var/obj/item/trash
var/sushi_overlay
+ var/can_use_cooker = TRUE
/obj/item/reagent_containers/food/snacks/Destroy()
@@ -28,7 +29,7 @@
reagents.add_reagent(/datum/reagent/nutriment, nutriment_amt, nutriment_desc)
-/obj/item/reagent_containers/food/snacks/proc/OnConsume(mob/living/consumer)
+/obj/item/reagent_containers/food/snacks/proc/OnConsume(mob/living/consumer, mob/living/feeder)
if (reagents && reagents.total_volume)
return
if (consumer)
@@ -37,12 +38,13 @@
SPAN_ITALIC("You finish eating \the [src].")
)
consumer.update_personal_goal(/datum/goal/achievement/specific_object/food, type)
- consumer.drop_from_inventory(src, consumer.loc)
+ if (feeder)
+ feeder.drop_from_inventory(src, feeder.loc)
if (loc && trash)
if (ispath(trash))
trash = new trash
- if (consumer)
- consumer.put_in_hands(trash)
+ if (feeder)
+ feeder.put_in_hands(trash)
else
trash.dropInto(loc)
trash = null
@@ -53,74 +55,76 @@
return
-/obj/item/reagent_containers/food/snacks/attack(mob/M as mob, mob/user as mob, def_zone)
- if(!reagents || !reagents.total_volume)
+/obj/item/reagent_containers/food/snacks/use_before(mob/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M, /mob/living/carbon))
+ return FALSE
+ if (!reagents || !reagents.total_volume)
to_chat(user, SPAN_DANGER("None of [src] left!"))
qdel(src)
- return 0
- if(!is_open_container())
+ return TRUE
+ if (!is_open_container())
to_chat(user, SPAN_NOTICE("\The [src] isn't open!"))
- return 0
- if(istype(M, /mob/living/carbon))
- //TODO: replace with standard_feed_mob() call.
- var/mob/living/carbon/C = M
- var/fullness = C.get_fullness()
- if(C == user) //If you're eating it yourself
- if(istype(C,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = M
- if(!H.check_has_mouth())
- to_chat(user, "Where do you intend to put \the [src]? You don't have a mouth!")
- return
- var/obj/item/blocked = H.check_mouth_coverage()
- if(blocked)
- to_chat(user, SPAN_WARNING("\The [blocked] is in the way!"))
- return
+ return TRUE
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)//puts a limit on how fast people can eat/drink things
- if (fullness <= 50)
- to_chat(C, SPAN_DANGER("You hungrily chew out a piece of [src] and gobble it!"))
- if (fullness > 50 && fullness <= 150)
- to_chat(C, SPAN_NOTICE("You hungrily begin to eat [src]."))
- if (fullness > 150 && fullness <= 350)
- to_chat(C, SPAN_NOTICE("You take a bite of [src]."))
- if (fullness > 350 && fullness <= 550)
- to_chat(C, SPAN_NOTICE("You unwillingly chew a bit of [src]."))
- if (fullness > 550)
- to_chat(C, SPAN_DANGER("You cannot force any more of [src] to go down your throat."))
- return 0
- else
- if(!M.can_force_feed(user, src))
- return
+ //TODO: replace with standard_feed_mob() call.
+ var/mob/living/carbon/C = M
+ var/fullness = C.get_fullness()
+ if (C == user) //If you're eating it yourself
+ if (istype(C,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = M
+ if (!H.check_has_mouth())
+ to_chat(user, "Where do you intend to put \the [src]? You don't have a mouth!")
+ return TRUE
+ var/obj/item/blocked = H.check_mouth_coverage()
+ if (blocked)
+ to_chat(user, SPAN_WARNING("\The [blocked] is in the way!"))
+ return TRUE
+
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)//puts a limit on how fast people can eat/drink things
+ if (fullness <= 50)
+ to_chat(C, SPAN_DANGER("You hungrily chew out a piece of [src] and gobble it!"))
+ if (fullness > 50 && fullness <= 150)
+ to_chat(C, SPAN_NOTICE("You hungrily begin to eat [src]."))
+ if (fullness > 150 && fullness <= 350)
+ to_chat(C, SPAN_NOTICE("You take a bite of [src]."))
+ if (fullness > 350 && fullness <= 550)
+ to_chat(C, SPAN_NOTICE("You unwillingly chew a bit of [src]."))
+ if (fullness > 550)
+ to_chat(C, SPAN_DANGER("You cannot force any more of [src] to go down your throat."))
+ return TRUE
+ else
+ if(!M.can_force_feed(user, src))
+ return TRUE
- if (fullness <= 550)
- user.visible_message(SPAN_DANGER("[user] attempts to feed [M] [src]."))
+ if (fullness <= 550)
+ user.visible_message(SPAN_DANGER("[user] attempts to feed [M] [src]."))
+ else
+ user.visible_message(SPAN_DANGER("[user] cannot force anymore of [src] down [M]'s throat."))
+ return TRUE
+
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ if (!do_after(user, 3 SECONDS, M, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS))
+ return TRUE
+
+ if (user.get_active_hand() != src)
+ return TRUE
+
+ var/contained = reagentlist()
+ admin_attack_log(user, M, "Fed the victim with [name] (Reagents: [contained])", "Was fed [src] (Reagents: [contained])", "used [src] (Reagents: [contained]) to feed")
+ user.visible_message(SPAN_DANGER("[user] feeds [M] [src]."))
+
+ if (reagents) //Handle ingestion of the reagent.
+ if (eat_sound)
+ playsound(M, pick(eat_sound), rand(10, 50), 1)
+ if (reagents.total_volume)
+ if (reagents.total_volume > bitesize)
+ reagents.trans_to_mob(M, bitesize, CHEM_INGEST)
else
- user.visible_message(SPAN_DANGER("[user] cannot force anymore of [src] down [M]'s throat."))
- return 0
-
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
- if(!do_after(user, 3 SECONDS, M, DO_DEFAULT | DO_USER_UNIQUE_ACT | DO_PUBLIC_PROGRESS)) return
-
- if (user.get_active_hand() != src)
- return
-
- var/contained = reagentlist()
- admin_attack_log(user, M, "Fed the victim with [name] (Reagents: [contained])", "Was fed [src] (Reagents: [contained])", "used [src] (Reagents: [contained]) to feed")
- user.visible_message(SPAN_DANGER("[user] feeds [M] [src]."))
-
- if(reagents) //Handle ingestion of the reagent.
- if(eat_sound)
- playsound(M, pick(eat_sound), rand(10, 50), 1)
- if(reagents.total_volume)
- if(reagents.total_volume > bitesize)
- reagents.trans_to_mob(M, bitesize, CHEM_INGEST)
- else
- reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
- bitecount++
- OnConsume(M)
- return 1
-
- return 0
+ reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
+ bitecount++
+ OnConsume(M, user)
+ return TRUE
/obj/item/reagent_containers/food/snacks/examine(mob/user, distance)
. = ..()
@@ -129,11 +133,11 @@
if (bitecount==0)
return
else if (bitecount==1)
- to_chat(user, SPAN_NOTICE("\The [src] was bitten by someone!"))
+ . += SPAN_NOTICE("[src] was bitten by someone!")
else if (bitecount<=3)
- to_chat(user, SPAN_NOTICE("\The [src] was bitten [bitecount] time\s!"))
+ . += SPAN_NOTICE("[src] was bitten [bitecount] time\s!")
else
- to_chat(user, SPAN_NOTICE("\The [src] was bitten multiple times!"))
+ . += SPAN_NOTICE("[src] was bitten multiple times!")
/obj/item/reagent_containers/food/snacks/attackby(obj/item/W as obj, mob/user as mob)
if(istype(W,/obj/item/storage))
@@ -156,11 +160,11 @@
to_chat(user, SPAN_NOTICE("You scoop up some [src] with \the [U]!"))
src.bitecount++
- U.overlays.Cut()
+ U.ClearOverlays()
U.loaded = "[src]"
var/image/I = new(U.icon, "loadedfood")
I.color = src.filling_color
- U.overlays += I
+ U.AddOverlays(I)
if(!reagents)
crash_with("A snack [type] failed to have a reagent holder when attacked with a [W.type]. It was [QDELETED(src) ? "" : "not"] being deleted.")
@@ -205,8 +209,16 @@
var/reagents_per_slice = reagents.total_volume/slices_num
for(var/i=1 to (slices_num-slices_lost))
- var/obj/slice = new slice_path (src.loc)
- reagents.trans_to_obj(slice, reagents_per_slice)
+ var/obj/item/reagent_containers/food/snacks/S = new slice_path (src.loc)
+ reagents.trans_to_obj(S, reagents_per_slice)
+
+ if(istype(src, /obj/item/reagent_containers/food/snacks/sliceable/variable))
+ S.SetName("[name] slice")
+ S.filling_color = filling_color
+ var/image/I = image(S.icon, "[S.icon_state]_filling")
+ I.color = filling_color
+ S.AddOverlays(I)
+
qdel(src)
return
@@ -219,6 +231,23 @@
something.dropInto(loc)
. = ..()
+/obj/item/reagent_containers/food/snacks/use_after(obj/item/reagent_containers/food/drinks/glass2/glass, mob/user)
+ if(!istype(glass))
+ return FALSE
+ if(w_class != ITEM_SIZE_TINY)
+ to_chat(user, SPAN_NOTICE("\The [src] is too big to properly dip in \the [glass]."))
+ return TRUE
+
+ var/transfered = glass.reagents.trans_to_obj(src, volume)
+ if(transfered) //if reagents were transfered, show the message
+ to_chat(user, SPAN_NOTICE("You dip \the [src] into \the [glass]."))
+ else //if not, either the glass was empty, or the food was full
+ if(!glass.reagents.total_volume)
+ to_chat(user, SPAN_NOTICE("\The [glass] is empty."))
+ else
+ to_chat(user, SPAN_NOTICE("\The [src] is full."))
+ return TRUE
+
////////////////////////////////////////////////////////////////////////////////
/// FOOD END
////////////////////////////////////////////////////////////////////////////////
@@ -233,7 +262,7 @@
if(!src && !user.client)
user.custom_emote(1,"[pick("burps", "cries for more", "burps twice", "looks at the area where the food was")]")
qdel(src)
- OnConsume(user)
+ OnConsume(user, user)
//////////////////////////////////////////////////
////////////////////////////////////////////Snacks
@@ -293,19 +322,20 @@
.=..()
reagents.add_reagent(/datum/reagent/nutriment/protein/egg, 3)
-/obj/item/reagent_containers/food/snacks/egg/afterattack(obj/O as obj, mob/user as mob, proximity)
+/obj/item/reagent_containers/food/snacks/egg/use_after(obj/O, mob/living/user, click_parameters)
if(istype(O,/obj/machinery/microwave))
- return ..()
- if(!(proximity && O.is_open_container()))
- return
+ return FALSE
+ if(!O.is_open_container())
+ return TRUE
to_chat(user, "You crack \the [src] into \the [O].")
reagents.trans_to(O, reagents.total_volume)
qdel(src)
+ return TRUE
/obj/item/reagent_containers/food/snacks/egg/throw_impact(atom/hit_atom)
if(QDELETED(src))
return // Could potentially happen with unscupulous atoms on hitby() throwing again, etc.
- new/obj/effect/decal/cleanable/egg_smudge(src.loc)
+ new/obj/decal/cleanable/egg_smudge(src.loc)
reagents.splash(hit_atom, reagents.total_volume)
visible_message(SPAN_WARNING("\The [src] has been squashed!"),SPAN_WARNING("You hear a smack."))
..()
@@ -388,7 +418,7 @@
/obj/item/reagent_containers/food/snacks/organ
name = "organ"
desc = "It's good for you."
- icon = 'icons/obj/surgery.dmi'
+ icon = 'icons/obj/organs.dmi'
icon_state = "appendix"
filling_color = "#e00d34"
center_of_mass = "x=16;y=16"
@@ -524,7 +554,7 @@
/obj/item/reagent_containers/food/snacks/sausage
name = "sausage"
desc = "A piece of mixed, long meat."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "sausage"
filling_color = "#db0000"
center_of_mass = "x=16;y=16"
@@ -612,8 +642,7 @@
/obj/item/reagent_containers/food/snacks/plainburger
name = "burger"
desc = "The cornerstone of every nutritious breakfast."
- icon = 'icons/obj/food_ingredients.dmi'
- icon_state = "burger"
+ icon_state = "hburger"
filling_color = "#d63c3c"
center_of_mass = "x=16;y=11"
nutriment_desc = list("bun" = 2)
@@ -623,20 +652,6 @@
.=..()
reagents.add_reagent(/datum/reagent/nutriment/protein, 3)
-/obj/item/reagent_containers/food/snacks/hamburger
- name = "hamburger"
- desc = "The cornerstone of every nutritious breakfast, now with ham!"
- icon = 'icons/obj/food_ingredients.dmi'
- icon_state = "hamburger"
- filling_color = "#d63c3c"
- center_of_mass = "x=16;y=11"
- nutriment_desc = list("bun" = 2)
- nutriment_amt = 3
- bitesize = 2
-/obj/item/reagent_containers/food/snacks/hamburger/Initialize()
- .=..()
- reagents.add_reagent(/datum/reagent/nutriment/protein, 5)
-
/obj/item/reagent_containers/food/snacks/fishburger
name = "fish sandwich"
desc = "Almost like a carp is yelling somewhere... Give me back that fillet -o- carp, give me that carp."
@@ -753,7 +768,7 @@
/obj/item/reagent_containers/food/snacks/pie/throw_impact(atom/hit_atom)
..()
- new/obj/effect/decal/cleanable/pie_smudge(src.loc)
+ new/obj/decal/cleanable/pie_smudge(src.loc)
src.visible_message(SPAN_DANGER("\The [src.name] splats."),SPAN_DANGER("You hear a splat."))
qdel(src)
@@ -977,7 +992,6 @@
name = "onion rings"
desc = "Like circular fries but better."
icon_state = "onionrings"
- trash = /obj/item/trash/plate
filling_color = "#eddd00"
center_of_mass = "x=16;y=11"
nutriment_desc = list("fried onions" = 5)
@@ -1044,8 +1058,7 @@
/obj/item/reagent_containers/food/snacks/plainsteak
name = "plain steak"
desc = "A piece of unseasoned cooked meat."
- icon = 'icons/obj/food_ingredients.dmi'
- icon_state = "steak"
+ icon_state = "meatsteak"
slice_path = /obj/item/reagent_containers/food/snacks/cutlet
slices_num = 3
filling_color = "#7a3d11"
@@ -1058,8 +1071,7 @@
/obj/item/reagent_containers/food/snacks/meatsteak
name = "meat steak"
desc = "A piece of hot spicy meat."
- icon_state = "meatstake"
- trash = /obj/item/trash/plate
+ icon_state = "meatsteak"
filling_color = "#7a3d11"
center_of_mass = "x=16;y=13"
bitesize = 3
@@ -1076,8 +1088,7 @@
/obj/item/reagent_containers/food/snacks/loadedsteak
name = "loaded steak"
desc = "A steak slathered in sauce with sauteed onions and mushrooms."
- icon_state = "meatstake"
- trash = /obj/item/trash/plate
+ icon_state = "meatsteak"
filling_color = "#7a3d11"
center_of_mass = "x=16;y=13"
nutriment_desc = list("onion" = 2, "mushroom" = 2)
@@ -1346,8 +1357,8 @@
filling_color = "#adac7f"
center_of_mass = "x=16;y=14"
- var/wrapped = 0
- var/growing = 0
+ var/wrapped = FALSE
+ var/growing = FALSE
var/monkey_type = /mob/living/carbon/human/monkey
/obj/item/reagent_containers/food/snacks/monkeycube/Initialize()
@@ -1360,7 +1371,7 @@
/obj/item/reagent_containers/food/snacks/monkeycube/proc/Expand()
if(!growing)
- growing = 1
+ growing = TRUE
src.visible_message(SPAN_NOTICE("\The [src] expands!"))
var/mob/monkey = new monkey_type
monkey.dropInto(src.loc)
@@ -1370,12 +1381,12 @@
icon_state = "monkeycube"
desc = "Just add water!"
to_chat(user, SPAN_NOTICE("You unwrap \the [src]."))
- wrapped = 0
+ wrapped = FALSE
atom_flags |= ATOM_FLAG_OPEN_CONTAINER
var/trash = new /obj/item/trash/cubewrapper(get_turf(user))
user.put_in_hands(trash)
-/obj/item/reagent_containers/food/snacks/monkeycube/OnConsume(mob/living/consumer)
+/obj/item/reagent_containers/food/snacks/monkeycube/OnConsume(mob/living/consumer, mob/living/feeder)
set waitfor = FALSE
if (ishuman(consumer))
var/mob/living/carbon/human/human = consumer
@@ -1405,7 +1416,7 @@
icon_state = "monkeycubewrap"
item_flags = 0
obj_flags = 0
- wrapped = 1
+ wrapped = TRUE
/obj/item/reagent_containers/food/snacks/monkeycube/farwacube
name = "farwa cube"
@@ -1436,11 +1447,11 @@
/obj/item/reagent_containers/food/snacks/monkeycube/spidercube
name = "spider cube"
- monkey_type = /obj/effect/spider/spiderling
+ monkey_type = /obj/spider/spiderling
/obj/item/reagent_containers/food/snacks/monkeycube/wrapped/spidercube
name = "spider cube"
- monkey_type = /obj/effect/spider/spiderling
+ monkey_type = /obj/spider/spiderling
/obj/item/reagent_containers/food/snacks/monkeycube/pikecube
name = "strange-looking monkey cube"
@@ -1459,6 +1470,85 @@
nutriment_amt = 6
bitesize = 2
+//corpse cube for the antag item
+/obj/item/reagent_containers/food/snacks/corpse_cube
+ name = "odd fleshy cube"
+ desc = "A strangely large, veiny and deformed monkey cube that pulsates and writhes disturbingly"
+ atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_OPEN_CONTAINER
+ icon_state = "corpsecube"
+ bitesize = 12
+ filling_color = "#adac7f"
+ center_of_mass = "x=16;y=14"
+
+ var/wrapped = FALSE
+ var/growing = FALSE
+ var/spawn_type = /mob/living/carbon/human
+
+/obj/item/reagent_containers/food/snacks/corpse_cube/use_tool(obj/item/device/dna_sampler/W, mob/user)
+ if(istype(W))
+ if (W.loaded == 1)
+ to_chat(user, "You inject the DNA sample into the cube.")
+ CorpseExpand(W.src_dna,W.src_name,W.src_species,W.src_pronouns,W.src_faction,W.src_flavor)
+ W.loaded = FALSE
+ W.icon_state = "dnainjector0"
+ W.src_dna = null
+ W.src_pronouns = ""
+ W.src_faction = ""
+ W.src_name = ""
+ W.src_species = ""
+ W.src_flavor = ""
+ else
+ to_chat(user,"The cube doesn't so much as twitch without a DNA sample.")
+ return TRUE
+ return ..()
+
+
+/obj/item/reagent_containers/food/snacks/corpse_cube/Initialize()
+ .=..()
+ reagents.add_reagent(/datum/reagent/nutriment/protein, 10)
+
+/obj/item/reagent_containers/food/snacks/corpse_cube/proc/CorpseExpand(source_DNA,source_name,source_species,source_pronouns, source_faction, source_flavor)
+ if(!growing)
+ growing = TRUE
+ var/mob/living/carbon/human/H = new spawn_type
+ H.dna = source_DNA
+ playsound(loc, 'sound/effects/corpsecube.ogg', 60)
+ H.faction = source_faction
+ H.real_name = source_name
+ H.SetName(source_name)
+ H.dna.real_name = source_name
+ H.change_species(source_species)
+ H.flavor_texts = source_flavor
+ src.visible_message(SPAN_WARNING("[src] transforms, the dummy body's features twisting and cracking as it imitates the provided blood!"))
+ H.dropInto(src.loc)
+ H.setBrainLoss(200)
+ H.adjustOxyLoss(H.maxHealth)
+ domutcheck(H, null)
+ H.UpdateAppearance()
+ qdel(src)
+
+/obj/item/reagent_containers/food/snacks/corpse_cube/OnConsume(mob/living/consumer, mob/living/feeder)
+ set waitfor = FALSE
+ if (ishuman(consumer))
+ var/mob/living/carbon/human/human = consumer
+ to_chat(human, FONT_LARGE(SPAN_DANGER("You feel something shifting and slithering throughout your body ...")))
+ var/obj/item/organ/external/organ = human.get_organ(BP_CHEST)
+ var/obj/item/organ/external/unluckylimb1 = human.get_organ(pick(BP_ALL_LIMBS))
+ var/obj/item/organ/external/unluckylimb2 = human.get_organ(pick(BP_ALL_LIMBS))
+ organ.add_pain(30)
+ organ.fracture()
+ unluckylimb1.add_pain(50)
+ unluckylimb1.fracture()
+ unluckylimb2.add_pain(50)
+ unluckylimb2.fracture()
+ organ.take_external_damage(50, 0, EMPTY_BITFIELD, "Agonizing pain")
+ organ.damage_internal_organs(50, 0, EMPTY_BITFIELD)
+ human.AdjustWeakened(5)
+ human.AdjustStunned(5)
+
+ else
+ consumer.kill_health()
+
/obj/item/reagent_containers/food/snacks/bigbiteburger
name = "big bite burger"
desc = "Forget the Luna Burger! THIS is the future!"
@@ -1533,7 +1623,6 @@
name = "sandwich"
desc = "A grand creation of meat, cheese, bread, and several leaves of lettuce! Arthur Dent would be proud."
icon_state = "sandwich"
- trash = /obj/item/trash/plate
filling_color = "#d9be29"
center_of_mass = "x=16;y=4"
nutriment_desc = list("bread" = 3, "cheese" = 2, "lettuce" = 1)
@@ -1547,7 +1636,6 @@
name = "toasted sandwich"
desc = "Now if you only had a pepper bar."
icon_state = "toastedsandwich"
- trash = /obj/item/trash/plate
filling_color = "#d9be29"
center_of_mass = "x=16;y=4"
nutriment_desc = list("toasted bread" = 3, "cheese" = 3)
@@ -1562,7 +1650,6 @@
name = "grilled cheese sandwich"
desc = "Goes great with Tomato soup!"
icon_state = "toastedsandwich"
- trash = /obj/item/trash/plate
filling_color = "#d9be29"
nutriment_desc = list("toasted bread" = 3, "cheese" = 3)
nutriment_amt = 3
@@ -1621,7 +1708,6 @@
name = "jellied toast"
desc = "A slice of bread covered with delicious jam."
icon_state = "jellytoast"
- trash = /obj/item/trash/plate
filling_color = "#b572ab"
center_of_mass = "x=16;y=8"
nutriment_desc = list("toasted bread" = 2)
@@ -1707,7 +1793,7 @@
bitesize = 2
/obj/item/reagent_containers/food/snacks/boiledrice/use_tool(obj/item/reagent_containers/food/snacks/W as obj, mob/user as mob)
- if(W.sushi_overlay)
+ if(istype(W) && W.sushi_overlay)
new /obj/item/reagent_containers/food/snacks/sushi(get_turf(src), src, W)
return TRUE
return ..()
@@ -2049,7 +2135,6 @@
if(whole && whole.slices_num)
var/reagent_amount = whole.reagents.total_volume/whole.slices_num
whole.reagents.trans_to_obj(src, reagent_amount)
-
qdel(whole)
/obj/item/reagent_containers/food/snacks/sliceable/meatbread
@@ -2071,7 +2156,6 @@
name = "meatbread slice"
desc = "A slice of delicious meatbread."
icon_state = "meatbreadslice"
- trash = /obj/item/trash/plate
filling_color = "#ff7575"
bitesize = 2
center_of_mass = "x=16;y=13"
@@ -2099,7 +2183,6 @@
name = "xenomeatbread slice"
desc = "A slice of delicious meatbread. Extra Heretical."
icon_state = "xenobreadslice"
- trash = /obj/item/trash/plate
filling_color = "#8aff75"
bitesize = 2
center_of_mass = "x=16;y=13"
@@ -2127,7 +2210,6 @@
name = "banana-nut bread slice"
desc = "A slice of delicious banana bread."
icon_state = "bananabreadslice"
- trash = /obj/item/trash/plate
filling_color = "#ede5ad"
bitesize = 2
center_of_mass = "x=16;y=8"
@@ -2152,7 +2234,6 @@
name = "tofubread slice"
desc = "A slice of delicious tofubread."
icon_state = "tofubreadslice"
- trash = /obj/item/trash/plate
filling_color = "#f7ffe0"
bitesize = 2
center_of_mass = "x=16;y=13"
@@ -2413,7 +2494,6 @@
name = "bread slice"
desc = "A slice of home."
icon_state = "breadslice"
- trash = /obj/item/trash/plate
filling_color = "#d27332"
bitesize = 2
center_of_mass = "x=16;y=4"
@@ -2442,7 +2522,6 @@
name = "cream cheese bread slice"
desc = "A slice of yum!"
icon_state = "creamcheesebreadslice"
- trash = /obj/item/trash/plate
filling_color = "#fff896"
bitesize = 2
center_of_mass = "x=16;y=13"
@@ -2515,6 +2594,7 @@
center_of_mass = "x=17;y=6"
nutriment_desc = list("salt" = 1, "cracker" = 2)
w_class = ITEM_SIZE_TINY
+ volume = 6
nutriment_amt = 1
/////////////////////////////////////////////////PIZZA////////////////////////////////////////
@@ -2668,7 +2748,7 @@
/obj/item/pizzabox
name = "pizza box"
desc = "A box suited for pizzas."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "pizzabox1"
var/open = 0 // Is the box open?
@@ -2679,7 +2759,7 @@
/obj/item/pizzabox/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
// Set appropriate description
if( open && pizza )
@@ -2707,7 +2787,7 @@
if( pizza )
var/image/pizzaimg = image("food.dmi", icon_state = pizza.icon_state)
pizzaimg.pixel_y = -3
- overlays += pizzaimg
+ AddOverlays(pizzaimg)
return
else
@@ -2724,7 +2804,7 @@
if( doimgtag )
var/image/tagimg = image("food.dmi", icon_state = "pizzabox_tag")
tagimg.pixel_y = length(boxes) * 3
- overlays += tagimg
+ AddOverlays(tagimg)
icon_state = "pizzabox[length(boxes)+1]"
@@ -2875,7 +2955,7 @@
/obj/item/reagent_containers/food/snacks/dough
name = "dough"
desc = "A piece of dough."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "dough"
filling_color = "#d6bca4"
bitesize = 2
@@ -2898,7 +2978,7 @@
/obj/item/reagent_containers/food/snacks/sliceable/flatdough
name = "flat dough"
desc = "A flattened dough."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "flat dough"
filling_color = "#d6bca4"
slice_path = /obj/item/reagent_containers/food/snacks/doughslice
@@ -2913,7 +2993,7 @@
/obj/item/reagent_containers/food/snacks/doughslice
name = "dough slice"
desc = "A building block of an impressive dish."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "doughslice"
filling_color = "#d6bca4"
slice_path = /obj/item/reagent_containers/food/snacks/spagetti
@@ -2926,7 +3006,7 @@
/obj/item/reagent_containers/food/snacks/bun
name = "bun"
desc = "A base for any self-respecting burger."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "bun"
filling_color = "#b8824c"
bitesize = 2
@@ -2934,23 +3014,27 @@
nutriment_desc = list("bun" = 4)
nutriment_amt = 4
+/obj/item/reagent_containers/food/snacks/customburger
+ name = "custom burger"
+ desc = "A tasty burger."
+ icon = 'icons/obj/food/food_custom.dmi'
+ icon_state = "customburger"
+ filling_color = "#b8824c"
+ center_of_mass = "x=16;y=12"
+ nutriment_desc = list("bun" = 2)
+ nutriment_amt = 3
+ bitesize = 2
+
//Items you can craft together. Like bomb making, but with food and less screwdrivers.
-/obj/item/reagent_containers/food/snacks/bun/attackby(obj/item/W as obj, mob/user as mob)
- // bun + meatball = burger
- if(istype(W,/obj/item/reagent_containers/food/snacks/meatball))
+/obj/item/reagent_containers/food/snacks/bun/use_tool(obj/item/W as obj, mob/user as mob)
+ // bun + meatball or cutlet = burger
+ if(istype(W,/obj/item/reagent_containers/food/snacks/meatball) || istype(W,/obj/item/reagent_containers/food/snacks/cutlet))
new /obj/item/reagent_containers/food/snacks/plainburger(src)
to_chat(user, "You make a burger.")
qdel(W)
qdel(src)
- // bun + cutlet = hamburger
- else if(istype(W,/obj/item/reagent_containers/food/snacks/cutlet))
- new /obj/item/reagent_containers/food/snacks/hamburger(src)
- to_chat(user, "You make a hamburger.")
- qdel(W)
- qdel(src)
-
// bun + sausage = hotdog
else if(istype(W,/obj/item/reagent_containers/food/snacks/sausage))
new /obj/item/reagent_containers/food/snacks/hotdog(src)
@@ -2958,71 +3042,62 @@
qdel(W)
qdel(src)
-// burger + cheese wedge = cheeseburger
-/obj/item/reagent_containers/food/snacks/plainburger/attackby(obj/item/reagent_containers/food/snacks/cheesewedge/W as obj, mob/user as mob)
- if(istype(W))// && !istype(src,/obj/item/reagent_containers/food/snacks/cheesewedge))
- new /obj/item/reagent_containers/food/snacks/cheeseburger(src)
- to_chat(user, "You make a cheeseburger.")
+ // bun + bun = bunbun
+ else if(istype(W,/obj/item/reagent_containers/food/snacks/bun))
+ new /obj/item/reagent_containers/food/snacks/bunbun(src)
+ to_chat(user, "You make a bun bun.")
qdel(W)
qdel(src)
- return
- else
- ..()
-// Hamburger + cheese wedge = cheeseburger
-/obj/item/reagent_containers/food/snacks/hamburger/attackby(obj/item/reagent_containers/food/snacks/cheesewedge/W as obj, mob/user as mob)
+ else if(istype(W, /obj/item/reagent_containers/food/snacks))
+ var/obj/item/reagent_containers/food/snacks/F = W
+ var /obj/item/reagent_containers/food/snacks/customburger/C = new(src)
+ C.SetName("[F.name]-burger")
+ C.filling_color = F.filling_color
+ var/image/I = image(C.icon, "customburger_filling")
+ I.color = F.filling_color
+ C.AddOverlays(I)
+ F.reagents.trans_to_obj(C, F.reagents.total_volume)
+ to_chat(user, "You make \a [C].")
+ qdel(F)
+ qdel(src)
+ return ..()
+
+// burger + cheese wedge = cheeseburger
+/obj/item/reagent_containers/food/snacks/plainburger/use_tool(obj/item/reagent_containers/food/snacks/cheesewedge/W as obj, mob/user as mob)
if(istype(W))// && !istype(src,/obj/item/reagent_containers/food/snacks/cheesewedge))
new /obj/item/reagent_containers/food/snacks/cheeseburger(src)
to_chat(user, "You make a cheeseburger.")
qdel(W)
qdel(src)
- return
- else
- ..()
+ return ..()
// Human burger + cheese wedge = cheeseburger
-/obj/item/reagent_containers/food/snacks/human/burger/attackby(obj/item/reagent_containers/food/snacks/cheesewedge/W as obj, mob/user as mob)
+/obj/item/reagent_containers/food/snacks/human/burger/use_tool(obj/item/reagent_containers/food/snacks/cheesewedge/W as obj, mob/user as mob)
if(istype(W))
new /obj/item/reagent_containers/food/snacks/cheeseburger(src)
to_chat(user, "You make a cheeseburger.")
qdel(W)
qdel(src)
- return
- else
- ..()
+ return ..()
// Spaghetti + meatball = spaghetti with meatball(s)
-/obj/item/reagent_containers/food/snacks/boiledspagetti/attackby(obj/item/reagent_containers/food/snacks/meatball/W as obj, mob/user as mob)
+/obj/item/reagent_containers/food/snacks/boiledspagetti/use_tool(obj/item/reagent_containers/food/snacks/meatball/W as obj, mob/user as mob)
if(istype(W))
new /obj/item/reagent_containers/food/snacks/meatballspagetti(src)
to_chat(user, "You add some meatballs to the spaghetti.")
qdel(W)
qdel(src)
- return
- else
- ..()
+ return ..()
// Spaghetti with meatballs + meatball = spaghetti with more meatball(s)
-/obj/item/reagent_containers/food/snacks/meatballspagetti/attackby(obj/item/reagent_containers/food/snacks/meatball/W as obj, mob/user as mob)
+/obj/item/reagent_containers/food/snacks/meatballspagetti/use_tool(obj/item/reagent_containers/food/snacks/meatball/W as obj, mob/user as mob)
if(istype(W))
new /obj/item/reagent_containers/food/snacks/spesslaw(src)
to_chat(user, "You add some more meatballs to the spaghetti.")
qdel(W)
qdel(src)
- return
- else
- ..()
-
-// Spaghetti + tomato = tomato'd spaghetti //commented out because I don't know how to define a tomato.
-//obj/item/reagent_containers/food/snacks/spagetti/attackby(/obj/item/reagent_containers/food/snacks/grown/tomato/W as obj, mob/user as mob)
-// if(istype(W))
-// new /obj/item/reagent_containers/food/snacks/pastatomato(src)
-// to_chat(user, "You add some more meatballs to the spaghetti.")
-// qdel(W)
-// qdel(src)
-// return
-// else
-// ..()
+ return ..()
/obj/item/reagent_containers/food/snacks/bunbun
name = "bun bun"
@@ -3050,7 +3125,7 @@
/obj/item/reagent_containers/food/snacks/rawcutlet
name = "raw cutlet"
desc = "A thin piece of raw meat."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "rawcutlet"
filling_color = "#fb8258"
slice_path = /obj/item/reagent_containers/food/snacks/rawbacon
@@ -3066,7 +3141,7 @@
/obj/item/reagent_containers/food/snacks/cutlet
name = "cutlet"
desc = "A tasty meat slice."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "cutlet"
filling_color = "#d75608"
bitesize = 2
@@ -3098,7 +3173,7 @@
/obj/item/reagent_containers/food/snacks/rawmeatball
name = "raw meatball"
desc = "A raw meatball."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "rawmeatball"
filling_color = "#ce3711"
bitesize = 2
@@ -3135,7 +3210,7 @@
/obj/item/reagent_containers/food/snacks/flatbread
name = "flatbread"
desc = "Bland but filling."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "flatbread"
filling_color = "#c17f3e"
bitesize = 2
@@ -3155,7 +3230,7 @@
/obj/item/reagent_containers/food/snacks/rawsticks
name = "raw potato sticks"
desc = "Raw fries, not very tasty."
- icon = 'icons/obj/food_ingredients.dmi'
+ icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "rawsticks"
filling_color = "#e4bf7e"
bitesize = 2
@@ -3167,7 +3242,7 @@
/obj/item/reagent_containers/food/snacks/canned
name = "void can"
- icon = 'icons/obj/food_canned.dmi'
+ icon = 'icons/obj/food/food_canned.dmi'
atom_flags = 0
var/sealed = TRUE
@@ -3178,7 +3253,7 @@
/obj/item/reagent_containers/food/snacks/canned/examine(mob/user)
. = ..()
- to_chat(user, "It is [sealed ? "" : "un"]sealed.")
+ . += SPAN_NOTICE("It is [sealed ? "" : "un"]sealed.")
/obj/item/reagent_containers/food/snacks/canned/proc/unseal()
atom_flags |= ATOM_FLAG_OPEN_CONTAINER
@@ -3568,6 +3643,7 @@
nutriment_amt = 5
nutriment_desc = list("sweetness" = 3, "cookie" = 2)
w_class = ITEM_SIZE_TINY
+ volume = 10
bitesize = 1
/obj/item/reagent_containers/food/snacks/chocolatebar
@@ -3602,7 +3678,7 @@
name = "donut"
desc = "Goes great with Robust Coffee."
icon_state = "donut1"
- filling_color = "#d9c386"
+ filling_color = "#b87b12"
var/overlay_state = "box-donut1"
center_of_mass = "x=13;y=16"
nutriment_desc = list("sweetness", "donut")
@@ -3619,7 +3695,7 @@
if(prob(30))
src.icon_state = "donut2"
- src.overlay_state = "box-donut2"
+ filling_color = "#ff7fc1"
src.SetName("frosted donut")
reagents.add_reagent(/datum/reagent/nutriment/sprinkles, 2)
center_of_mass = "x=19;y=16"
@@ -3627,8 +3703,9 @@
/obj/item/reagent_containers/food/snacks/donut/chaos
name = "chaos donut"
desc = "Like life, it never quite tastes the same."
- icon_state = "donut1"
- filling_color = "#ed11e6"
+ icon_state = "donut_chaos"
+ overlay_state = "box-donut_chaos"
+ filling_color = "#b87b12"
nutriment_amt = 2
bitesize = 10
/obj/item/reagent_containers/food/snacks/donut/chaos/Initialize()
@@ -3646,17 +3723,12 @@
/datum/reagent/drink/juice/berry,
/datum/reagent/fuel,
/datum/reagent/tricordrazine)), 3)
- if(prob(30))
- src.icon_state = "donut2"
- src.overlay_state = "box-donut2"
- src.SetName("frosted chaos donut")
- reagents.add_reagent(/datum/reagent/nutriment/sprinkles, 2)
/obj/item/reagent_containers/food/snacks/donut/jelly
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
- filling_color = "#ed1169"
+ filling_color = "#b87b12"
center_of_mass = "x=16;y=11"
nutriment_amt = 3
bitesize = 5
@@ -3666,7 +3738,7 @@
reagents.add_reagent(/datum/reagent/drink/juice/berry, 5)
if(prob(30))
src.icon_state = "jdonut2"
- src.overlay_state = "box-donut2"
+ filling_color = "#ff7fc1"
src.SetName("frosted jelly donut")
reagents.add_reagent(/datum/reagent/nutriment/sprinkles, 2)
@@ -3674,7 +3746,7 @@
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
- filling_color = "#ed1169"
+ filling_color = "#b87b12"
center_of_mass = "x=16;y=11"
nutriment_amt = 3
bitesize = 5
@@ -3684,7 +3756,7 @@
reagents.add_reagent(/datum/reagent/slimejelly, 5)
if(prob(30))
src.icon_state = "jdonut2"
- src.overlay_state = "box-donut2"
+ filling_color = "#ff7fc1"
src.SetName("frosted jelly donut")
reagents.add_reagent(/datum/reagent/nutriment/sprinkles, 2)
@@ -3692,7 +3764,7 @@
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
- filling_color = "#ed1169"
+ filling_color = "#b87b12"
center_of_mass = "x=16;y=11"
nutriment_amt = 3
bitesize = 5
@@ -3702,7 +3774,7 @@
reagents.add_reagent(/datum/reagent/nutriment/cherryjelly, 5)
if(prob(30))
src.icon_state = "jdonut2"
- src.overlay_state = "box-donut2"
+ filling_color = "#ff7fc1"
src.SetName("frosted jelly donut")
reagents.add_reagent(/datum/reagent/nutriment/sprinkles, 2)
@@ -4150,3 +4222,41 @@
name = "taco"
desc = "Interestingly, the shell has gone soft and the contents have gone stale."
icon_state = "ancient_taco"
+
+/obj/item/reagent_containers/food/snacks/sliceable/unscottiloaf
+ name = "loaf of unscotti"
+ desc = "A loaf of unscotti, ready to be sliced into the iconic biscotti shape."
+ icon_state = "unscottiloaf"
+ slice_path = /obj/item/reagent_containers/food/snacks/slice/unscotti
+ slices_num = 4
+ filling_color = "#ffe396"
+ center_of_mass = "x=16;y=9"
+ nutriment_desc = list("cookie" = 5, "almonds" = 3)
+ nutriment_amt = 8
+ bitesize = 2
+
+/obj/item/reagent_containers/food/snacks/slice/unscotti
+ name = "unscotti"
+ desc = "An Italian cookie made with almonds. Typically baked again to make biscotti."
+ icon_state = "unscotti"
+ filling_color = "#d27332"
+ bitesize = 4
+ center_of_mass = "x=16;y=4"
+ w_class = ITEM_SIZE_TINY
+ whole_path = /obj/item/reagent_containers/food/snacks/sliceable/unscottiloaf
+ volume = 7
+
+/obj/item/reagent_containers/food/snacks/slice/unscotti/filled
+ filled = TRUE
+
+/obj/item/reagent_containers/food/snacks/biscotti
+ name = "biscotti"
+ desc = "A twice baked Italian cookie usually served before breakfast, after dinner, or with coffee. This one has almonds."
+ icon_state = "biscotti"
+ filling_color = "#dbc94f"
+ center_of_mass = "x=17;y=18"
+ nutriment_amt = 4
+ nutriment_desc = list("sweetness" = 2, "crumbly cookie" = 2, "almonds" = 1)
+ w_class = ITEM_SIZE_TINY
+ bitesize = 3
+ volume = 9
diff --git a/code/modules/reagents/reagent_containers/food/snacks/bugmeat.dm b/code/modules/reagents/reagent_containers/food/snacks/bugmeat.dm
index 2601d28331aff..14ab9c8a469f8 100644
--- a/code/modules/reagents/reagent_containers/food/snacks/bugmeat.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks/bugmeat.dm
@@ -1,13 +1,14 @@
/obj/item/storage/fancy/bugmeat
name = "box of insect protein"
desc = "What a horrible idea. Who funded this?"
- icon = 'icons/obj/food_bugmeat.dmi'
+ icon = 'icons/obj/food/food_bugmeat.dmi'
icon_state = "box"
+ open_sound = 'sound/effects/storage/box.ogg'
storage_slots = 6
max_w_class = ITEM_SIZE_SMALL
w_class = ITEM_SIZE_NORMAL
- key_type = /obj/item/reagent_containers/food/snacks/rawcutlet/bugmeat
- can_hold = list(
+ key_type = list(/obj/item/reagent_containers/food/snacks/rawcutlet/bugmeat)
+ contents_allowed = list(
/obj/item/reagent_containers/food/snacks/rawcutlet/bugmeat,
/obj/item/reagent_containers/food/snacks/cutlet/bugmeat
)
@@ -26,7 +27,7 @@
/obj/item/reagent_containers/food/snacks/rawcutlet/bugmeat
name = "raw insect protein"
desc = "A small mass of extruded bug stuff."
- icon = 'icons/obj/food_bugmeat.dmi'
+ icon = 'icons/obj/food/food_bugmeat.dmi'
icon_state = "rawcutlet"
filling_color = "#7bc578"
slice_path = /obj/item/reagent_containers/food/snacks/rawbacon/bugmeat
@@ -39,7 +40,7 @@
/obj/item/reagent_containers/food/snacks/cutlet/bugmeat
name = "insect protein"
desc = "A small mass of cooked extruded bug stuff."
- icon = 'icons/obj/food_bugmeat.dmi'
+ icon = 'icons/obj/food/food_bugmeat.dmi'
icon_state = "cutlet"
filling_color = "#858040"
slice_path = /obj/item/reagent_containers/food/snacks/bacon/bugmeat
@@ -52,7 +53,7 @@
/obj/item/reagent_containers/food/snacks/rawbacon/bugmeat
name = "raw sliced insect protein"
desc = "A small mass of extruded bug stuff, lovingly cut thin."
- icon = 'icons/obj/food_bugmeat.dmi'
+ icon = 'icons/obj/food/food_bugmeat.dmi'
icon_state = "rawbacon"
filling_color = "#7bc578"
bitesize = 1
@@ -63,7 +64,7 @@
/obj/item/reagent_containers/food/snacks/bacon/bugmeat
name = "sliced insect protein"
desc = "A small mass of cooked extruded bug stuff, lovingly cut thin."
- icon = 'icons/obj/food_bugmeat.dmi'
+ icon = 'icons/obj/food/food_bugmeat.dmi'
icon_state = "bacon"
filling_color = "#858040"
bitesize = 2
@@ -91,26 +92,3 @@
containertype = /obj/item/storage/backpack/dufflebag
containername = "insect protein dufflebag"
cost = 20
-
-
-/obj/structure/closet/secure_closet/freezer/meat/bugmeat/WillContain()
- return list(
- /obj/item/storage/fancy/bugmeat = 3
- )
-
-
-/obj/structure/closet/secure_closet/freezer/fridge/bugmeat/WillContain()
- return list(
- /obj/item/reagent_containers/food/drinks/milk = 2,
- /obj/item/reagent_containers/food/drinks/soymilk = 2,
- /obj/item/storage/fancy/egg_box/full = 1
- )
-
-
-/obj/structure/closet/secure_closet/freezer/kitchen/bugmeat/WillContain()
- return list(
- /obj/item/reagent_containers/food/condiment/salt = 1,
- /obj/item/reagent_containers/food/condiment/flour = 3,
- /obj/item/reagent_containers/food/condiment/sugar = 1,
- /obj/item/reagent_containers/glass/bottle/dye/polychromic = 4
- )
diff --git a/code/modules/reagents/reagent_containers/food/snacks/donkpocket.dm b/code/modules/reagents/reagent_containers/food/snacks/donkpocket.dm
index 73ffcb598f025..3a5912746f900 100644
--- a/code/modules/reagents/reagent_containers/food/snacks/donkpocket.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks/donkpocket.dm
@@ -44,9 +44,15 @@
SetInitialReagents(filling_options)
-/obj/item/reagent_containers/food/snacks/donkpocket/OnConsume(mob/living/consumer)
+/obj/item/reagent_containers/food/snacks/donkpocket/OnConsume(mob/living/consumer, mob/living/feeder)
if (can_self_heat)
- to_chat(consumer, SPAN_ITALIC("You tear open \the [src], destroying the self-heating packaging."))
+ if (feeder)
+ feeder.visible_message(
+ SPAN_ITALIC("\The [feeder] tears open \a [src], destroying the self-heating packaging."),
+ SPAN_ITALIC("You tear open \the [src], destroying the self-heating packaging."),
+ SPAN_ITALIC("You hear plastic packaging crinkling."),
+ range = 3
+ )
can_self_heat = FALSE
..()
@@ -57,7 +63,7 @@
return
if (!initial(can_self_heat))
return
- to_chat(user, "This one can self-heat[can_self_heat ? "." : " but the heaters are used up."]")
+ . += SPAN_NOTICE("This one can self-heat[can_self_heat ? "." : " but the heaters are used up."]")
/obj/item/reagent_containers/food/snacks/donkpocket/attack_self(mob/living/user)
@@ -85,7 +91,7 @@
reagents.add_reagent(reagent, hot_reagents[reagent])
was_heated = TRUE
SetName("hot " + name)
- addtimer(new Callback(src, .proc/UnsetHot), 7 MINUTES)
+ addtimer(CALLBACK(src, PROC_REF(UnsetHot)), 7 MINUTES)
/obj/item/reagent_containers/food/snacks/donkpocket/proc/UnsetHot()
@@ -185,7 +191,7 @@
/obj/random/donkpocket
name = "random donk-pocket"
desc = "This is a random donk-pocket."
- icon = 'icons/obj/food.dmi'
+ icon = 'icons/obj/food/food.dmi'
icon_state = "donkpocket"
@@ -261,7 +267,7 @@
/obj/random/donkpocket_box
name = "random box of donk-pockets"
desc = "This is a random box of donk-pockets."
- icon = 'icons/obj/storage.dmi'
+ icon = 'icons/obj/boxes.dmi'
icon_state = "donk_kit"
diff --git a/code/modules/reagents/reagent_containers/food/snacks/meat.dm b/code/modules/reagents/reagent_containers/food/snacks/meat.dm
index 12042bf17e652..dd037369d3788 100644
--- a/code/modules/reagents/reagent_containers/food/snacks/meat.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks/meat.dm
@@ -1,7 +1,6 @@
/obj/item/reagent_containers/food/snacks/meat
name = "meat"
desc = "A slab of meat."
- icon = 'icons/obj/food_ingredients.dmi'
icon_state = "meat"
slice_path = /obj/item/reagent_containers/food/snacks/rawcutlet
slices_num = 3
@@ -18,7 +17,6 @@
/obj/item/reagent_containers/food/snacks/meat/syntiflesh
name = "synthetic meat"
desc = "A slab of flesh synthetized from reconstituted biomass or artificially grown from chemicals."
- icon = 'icons/obj/food.dmi'
// Separate definitions because some food likes to know if it's human.
// TODO: rewrite kitchen code to check a var on the meat item so we can remove
@@ -32,16 +30,17 @@
desc = "Tastes like... well, you know."
/obj/item/reagent_containers/food/snacks/meat/beef
- name = "beef slab"
+ name = "beef steak"
desc = "The classic red meat."
/obj/item/reagent_containers/food/snacks/meat/goat
- name = "chevon slab"
+ name = "chevon steak"
desc = "Goat meat, to the uncultured."
/obj/item/reagent_containers/food/snacks/meat/chicken
name = "chicken piece"
desc = "It tastes like you'd expect."
+ icon_state = "birdmeat"
/obj/item/reagent_containers/food/snacks/meat/chicken/game
name = "game bird piece"
@@ -50,3 +49,4 @@
/obj/item/reagent_containers/food/snacks/meat/thoom
name = "reptile steak"
desc = "The most expensive steak you've ever laid eyes on."
+ icon_state = "xenomeat"
diff --git a/code/modules/reagents/reagent_containers/food/snacks/shellfish.dm b/code/modules/reagents/reagent_containers/food/snacks/shellfish.dm
index ae72f4dc5c39d..59e6ad94911cd 100644
--- a/code/modules/reagents/reagent_containers/food/snacks/shellfish.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks/shellfish.dm
@@ -2,7 +2,7 @@
abstract_type = /obj/item/shellfish
name = "shellfish"
desc = "You shouldn't be seeing this."
- icon = 'icons/obj/food_shellfish.dmi'
+ icon = 'icons/obj/food/food_shellfish.dmi'
icon_state = "clam"
w_class = ITEM_SIZE_SMALL
throwforce = 0
@@ -17,9 +17,9 @@
if (!item.sharp)
return ..()
to_chat(user, SPAN_NOTICE("You start to pry open \the [src]."))
- if (!user.do_skilled(2 SECONDS, SKILL_COOKING, user))
+ if (!user.do_skilled((item.toolspeed * 2) SECONDS, SKILL_COOKING, user))
return TRUE
- if (!prob(user.skill_fail_chance(SKILL_COOKING, 80, SKILL_ADEPT)))
+ if (!prob(user.skill_fail_chance(SKILL_COOKING, 80, SKILL_TRAINED)))
to_chat(user, SPAN_NOTICE("You carefully clean and open \the [src]."))
new snack_path (get_turf(src))
qdel(src)
@@ -80,7 +80,7 @@
abstract_type = /obj/item/reagent_containers/food/snacks/shellfish
name = "raw shellfish"
desc = "You shouldn't be seeing this."
- icon = 'icons/obj/food_shellfish.dmi'
+ icon = 'icons/obj/food/food_shellfish.dmi'
icon_state = "clam_open"
filling_color = "#f6db93"
bitesize = 4
@@ -158,7 +158,7 @@
abstract_type = /obj/item/shell
name = "shell"
desc = "An empty shell."
- icon = 'icons/obj/food_shellfish.dmi'
+ icon = 'icons/obj/food/food_shellfish.dmi'
icon_state = "clam_empty"
w_class = ITEM_SIZE_TINY
throw_speed = 4
diff --git a/code/modules/reagents/reagent_containers/food/sushi.dm b/code/modules/reagents/reagent_containers/food/sushi.dm
index 43f5fb26e83a8..f36320551bc8a 100644
--- a/code/modules/reagents/reagent_containers/food/sushi.dm
+++ b/code/modules/reagents/reagent_containers/food/sushi.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/food/snacks/sushi
name = "sushi"
desc = "A small, neatly wrapped morsel. Itadakimasu!"
- icon = 'icons/obj/sushi.dmi'
+ icon = 'icons/obj/food/sushi.dmi'
icon_state = "sushi_rice"
bitesize = 1
sushi_overlay = "fish"
@@ -20,7 +20,7 @@
var/image/I = image(icon, sushi_overlay)
if(sushi_overlay == "fish" || sushi_overlay == "meat")
I.color = topping.filling_color
- overlays += I
+ AddOverlays(I)
if(istype(topping, /obj/item/reagent_containers/food/snacks/sashimi))
var/obj/item/reagent_containers/food/snacks/sashimi/sashimi = topping
@@ -28,7 +28,7 @@
else
sushi_type = topping.name
if (text_starts_with(sushi_type, "raw"))
- sushi_type = trim(copytext(sushi_type, 4))
+ sushi_type = trimtext(copytext(sushi_type, 4))
if(topping.reagents)
topping.reagents.trans_to(src, topping.reagents.total_volume)
@@ -39,7 +39,7 @@
else
var/image/I = image(icon, sushi_overlay)
I.color = "#ff4040"
- overlays += I
+ AddOverlays(I)
if(istype(rice))
if(rice.reagents)
@@ -52,14 +52,14 @@
/obj/item/reagent_containers/food/snacks/sushi/on_update_icon()
name = "[sushi_type] sushi"
- overlays += "nori"
+ AddOverlays("nori")
/////////////
// SASHIMI //
/////////////
/obj/item/reagent_containers/food/snacks/sashimi
name = "sashimi"
- icon = 'icons/obj/sushi.dmi'
+ icon = 'icons/obj/food/sushi.dmi'
desc = "Thinly sliced raw fish. Tasty."
icon_state = "sashimi"
color = "#ff4040"
@@ -79,4 +79,4 @@
color = _color
filling_color = _color
name = "[fish_type] sashimi"
- update_icon()
\ No newline at end of file
+ update_icon()
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index e86edc4f235e6..47c0667cf877d 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -6,7 +6,7 @@
name = " "
var/base_name = " "
desc = ""
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "null"
item_state = "null"
amount_per_transfer_from_this = 10
@@ -50,11 +50,11 @@
return
if(reagents && length(reagents.reagent_list))
- to_chat(user, SPAN_NOTICE("It contains [reagents.total_volume] units of liquid."))
+ . += SPAN_NOTICE("It contains [reagents.total_volume] units of liquid.")
else
- to_chat(user, SPAN_NOTICE("It is empty."))
+ . += SPAN_NOTICE("It is empty.")
if(!is_open_container())
- to_chat(user, SPAN_NOTICE("The airtight lid seals it completely."))
+ . += SPAN_NOTICE("The airtight lid seals it completely.")
/obj/item/reagent_containers/glass/attack_self()
..()
@@ -66,19 +66,19 @@
atom_flags |= ATOM_FLAG_OPEN_CONTAINER
update_icon()
-/obj/item/reagent_containers/glass/attack(mob/M as mob, mob/user as mob, def_zone)
- if(force && !(item_flags & ITEM_FLAG_NO_BLUDGEON) && user.a_intent == I_HURT)
- return ..()
- if(standard_feed_mob(user, M))
- return
- return 0
+/obj/item/reagent_containers/glass/use_before(mob/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
+ if (standard_feed_mob(user, M))
+ return TRUE
/obj/item/reagent_containers/glass/standard_feed_mob(mob/user, mob/target)
if(!is_open_container())
to_chat(user, SPAN_NOTICE("You need to open \the [src] first."))
- return 1
+ return TRUE
if(user.a_intent == I_HURT)
- return 1
+ return FALSE
return ..()
/obj/item/reagent_containers/glass/self_feed_message(mob/user)
@@ -122,16 +122,17 @@
)
playsound(src.loc, "sound/effects/Glasshit.ogg", 50)
-/obj/item/reagent_containers/glass/afterattack(obj/target, mob/user, proximity)
- if (!proximity || (target.type in can_be_placed_into) || standard_dispenser_refill(user, target) || standard_pour_into(user, target))
+/obj/item/reagent_containers/glass/use_after(obj/target, mob/living/user, click_parameters)
+ if ((target.type in can_be_placed_into) || standard_dispenser_refill(user, target) || standard_pour_into(user, target))
return TRUE
splashtarget(target, user)
+ return TRUE
/obj/item/reagent_containers/glass/beaker
name = "beaker"
desc = "A beaker."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "beaker"
item_state = "beaker"
center_of_mass = "x=15;y=10"
@@ -162,7 +163,7 @@
/obj/item/reagent_containers/glass/beaker/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if (reagents.total_volume)
var/image/filling = image('icons/obj/reagentfillings.dmi', src, "[icon_state]10")
var/percent = round((reagents.total_volume / volume) * 100)
@@ -182,10 +183,10 @@
if (91 to INFINITY)
filling.icon_state = "[icon_state]100"
filling.color = reagents.get_color()
- overlays += filling
+ AddOverlays(filling)
if (!is_open_container())
var/image/lid = image(icon, src, "lid_[initial(icon_state)]")
- overlays += lid
+ AddOverlays(lid)
/obj/item/reagent_containers/glass/beaker/large
@@ -202,7 +203,7 @@
/obj/item/reagent_containers/glass/beaker/bowl
name = "mixing bowl"
desc = "A large mixing bowl."
- icon = 'icons/obj/kitchen.dmi'
+ icon = 'icons/obj/machines/kitchen.dmi'
icon_state = "mixingbowl"
center_of_mass = "x=16;y=10"
matter = list(MATERIAL_STEEL = 300)
@@ -278,7 +279,7 @@
/obj/item/reagent_containers/glass/bucket
name = "bucket"
desc = "It's a bucket."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/janitor_tools.dmi'
icon_state = "bucket"
item_state = "bucket"
center_of_mass = "x=16;y=9"
@@ -311,20 +312,20 @@
return ..()
/obj/item/reagent_containers/glass/bucket/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if (!is_open_container())
var/image/lid = image(icon, src, "lid_[initial(icon_state)]")
- overlays += lid
+ AddOverlays(lid)
else if(reagents.total_volume && round((reagents.total_volume / volume) * 100) > 80)
var/image/filling = image('icons/obj/reagentfillings.dmi', src, "bucket")
filling.color = reagents.get_color()
- overlays += filling
+ AddOverlays(filling)
/*
/obj/item/reagent_containers/glass/blender_jug
name = "Blender Jug"
desc = "A blender jug, part of a blender."
- icon = 'icons/obj/kitchen.dmi'
+ icon = 'icons/obj/machines/kitchen.dmi'
icon_state = "blender_jug_e"
volume = 100
diff --git a/code/modules/reagents/reagent_containers/glass/bottle.dm b/code/modules/reagents/reagent_containers/glass/bottle.dm
index f575d43382271..df85553bac931 100644
--- a/code/modules/reagents/reagent_containers/glass/bottle.dm
+++ b/code/modules/reagents/reagent_containers/glass/bottle.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/glass/bottle
name = "bottle"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = null
item_state = "atoxinbottle"
randpixel = 7
@@ -9,7 +9,6 @@
amount_per_transfer_from_this = 10
possible_transfer_amounts = "5;10;15;25;30;60"
w_class = ITEM_SIZE_SMALL
- item_flags = 0
obj_flags = 0
volume = 60
@@ -40,7 +39,7 @@
/obj/item/reagent_containers/glass/bottle/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if (reagents.total_volume && (icon_state == "bottle-1" || icon_state == "bottle-2" || icon_state == "bottle-3" || icon_state == "bottle-4"))
var/image/filling = image('icons/obj/reagentfillings.dmi', src, "[icon_state]10")
var/percent = round((reagents.total_volume / volume) * 100)
@@ -60,16 +59,16 @@
if (91 to INFINITY)
filling.icon_state = "[icon_state]-100"
filling.color = reagents.get_color()
- overlays += filling
+ AddOverlays(filling)
if (!is_open_container())
var/image/lid = image(icon, src, "lid_bottle")
- overlays += lid
+ AddOverlays(lid)
/obj/item/reagent_containers/glass/bottle/inaprovaline
name = "inaprovaline bottle"
desc = "A small bottle. Contains inaprovaline - used to stabilize patients."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -82,7 +81,7 @@
/obj/item/reagent_containers/glass/bottle/kelotane
name = "kelotane bottle"
desc = "A small bottle. Contains kelotane - used to treat burns."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -95,7 +94,7 @@
/obj/item/reagent_containers/glass/bottle/dexalin
name = "dexalin bottle"
desc = "A small bottle. Contains dexalin - used to treat oxygen deprivation."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -108,7 +107,7 @@
/obj/item/reagent_containers/glass/bottle/toxin
name = "toxin bottle"
desc = "A small bottle of toxins. Do not drink, it is poisonous."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-3"
@@ -121,7 +120,7 @@
/obj/item/reagent_containers/glass/bottle/cyanide
name = "cyanide bottle"
desc = "A small bottle of cyanide. Bitter almonds?"
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-3"
@@ -134,7 +133,7 @@
/obj/item/reagent_containers/glass/bottle/stoxin
name = "soporific bottle"
desc = "A small bottle of soporific. Just the fumes make you sleepy."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-3"
@@ -147,7 +146,7 @@
/obj/item/reagent_containers/glass/bottle/chloralhydrate
name = "Chloral Hydrate Bottle"
desc = "A small bottle of Choral Hydrate. Mickey's Favorite!"
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-3"
@@ -160,7 +159,7 @@
/obj/item/reagent_containers/glass/bottle/antitoxin
name = "dylovene bottle"
desc = "A small bottle of dylovene. Counters poisons, and repairs damage. A wonder drug."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -173,7 +172,7 @@
/obj/item/reagent_containers/glass/bottle/mutagen
name = "unstable mutagen bottle"
desc = "A small bottle of unstable mutagen. Randomly changes the DNA structure of whoever comes in contact."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-1"
@@ -186,7 +185,7 @@
/obj/item/reagent_containers/glass/bottle/ammonia
name = "ammonia bottle"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-1"
@@ -199,7 +198,7 @@
/obj/item/reagent_containers/glass/bottle/eznutrient
name = "\improper EZ NUtrient bottle"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -212,7 +211,7 @@
/obj/item/reagent_containers/glass/bottle/left4zed
name = "\improper Left-4-Zed bottle"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -225,7 +224,7 @@
/obj/item/reagent_containers/glass/bottle/robustharvest
name = "\improper Robust Harvest"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -238,7 +237,7 @@
/obj/item/reagent_containers/glass/bottle/diethylamine
name = "diethylamine bottle"
desc = "A small bottle."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -251,7 +250,7 @@
/obj/item/reagent_containers/glass/bottle/pacid
name = "Polytrinic Acid Bottle"
desc = "A small bottle. Contains a small amount of Polytrinic Acid."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -264,7 +263,7 @@
/obj/item/reagent_containers/glass/bottle/adminordrazine
name = "Adminordrazine Bottle"
desc = "A small bottle. Contains the liquid essence of the gods."
- icon = 'icons/obj/drinks.dmi'
+ icon = 'icons/obj/food/drinks.dmi'
icon_state = "holyflask"
@@ -277,7 +276,7 @@
/obj/item/reagent_containers/glass/bottle/capsaicin
name = "Capsaicin Bottle"
desc = "A small bottle. Contains hot sauce."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -290,7 +289,7 @@
/obj/item/reagent_containers/glass/bottle/frostoil
name = "Chilly Oil Bottle"
desc = "A small bottle. Contains cold sauce."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
@@ -302,7 +301,7 @@
/obj/item/reagent_containers/glass/bottle/dye
name = "dye bottle"
desc = "A little bottle used to hold dye or food coloring, with a narrow bottleneck for handling small amounts."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-1"
amount_per_transfer_from_this = 1
possible_transfer_amounts = "1;2;5;10;15;25;30;60"
diff --git a/code/modules/reagents/reagent_containers/glass/bottle/robot.dm b/code/modules/reagents/reagent_containers/glass/bottle/robot.dm
index 02e0c46e2140b..e009d8b4bbae2 100644
--- a/code/modules/reagents/reagent_containers/glass/bottle/robot.dm
+++ b/code/modules/reagents/reagent_containers/glass/bottle/robot.dm
@@ -10,7 +10,7 @@
/obj/item/reagent_containers/glass/bottle/robot/inaprovaline
name = "internal inaprovaline bottle"
desc = "A small bottle. Contains inaprovaline - used to stabilize patients."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
reagent = /datum/reagent/inaprovaline
@@ -23,7 +23,7 @@
/obj/item/reagent_containers/glass/bottle/robot/antitoxin
name = "internal anti-toxin bottle"
desc = "A small bottle of Anti-toxins. Counters poisons, and repairs damage, a wonder drug."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/chemical_storage.dmi'
icon_state = "bottle-4"
reagent = /datum/reagent/dylovene
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index baf3e2b4ff345..1cccd2af804f8 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -5,7 +5,7 @@
/obj/item/reagent_containers/hypospray //obsolete, use hypospray/vial for the actual hypospray item
name = "hypospray"
desc = "The DeForest Medical Corporation, a subsidiary of Zeng-Hu Pharmaceuticals, hypospray is a sterile, air-needle autoinjector for rapid administration of drugs to patients."
- icon = 'icons/obj/syringe.dmi'
+ icon = 'icons/obj/tools/syringe.dmi'
item_state = "hypo"
icon_state = "hypo"
origin_tech = list(TECH_MATERIAL = 4, TECH_BIO = 5)
@@ -28,16 +28,17 @@
var/time = (1 SECONDS) / 1.9
var/single_use = TRUE // autoinjectors are not refillable (overriden for hypospray)
-/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user)
- if(!reagents.total_volume)
- to_chat(user, SPAN_WARNING("[src] is empty."))
- return
+/obj/item/reagent_containers/hypospray/use_before(mob/living/M, mob/user)
+ . = FALSE
if (!istype(M))
- return
+ return FALSE
+ if (!reagents.total_volume)
+ to_chat(user, SPAN_WARNING("[src] is empty."))
+ return TRUE
var/allow = M.can_inject(user, check_zone(user.zone_sel.selecting))
- if(!allow)
- return
+ if (!allow)
+ return TRUE
if (allow == INJECTION_PORT)
if(M != user)
@@ -45,7 +46,7 @@
else
to_chat(user, SPAN_NOTICE("You begin hunting for an injection port on your suit."))
if(!user.do_skilled(INJECTION_PORT_DELAY, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
- return
+ return TRUE
user.setClickCooldown(DEFAULT_QUICK_COOLDOWN)
user.do_attack_animation(M)
@@ -53,10 +54,11 @@
if(user != M && !M.incapacitated() && time) // you're injecting someone else who is concious, so apply the device's intrisic delay
to_chat(user, SPAN_WARNING("\The [user] is trying to inject \the [M] with \the [name]."))
if(!user.do_skilled(time, SKILL_MEDICAL, M, do_flags = DO_MEDICAL))
- return
+ return TRUE
if(single_use && reagents.total_volume <= 0) // currently only applies to autoinjectors
atom_flags &= ~ATOM_FLAG_OPEN_CONTAINER // Prevents autoinjectors to be refilled.
+ update_icon()
to_chat(user, SPAN_NOTICE("You inject [M] with [src]."))
if(ishuman(M))
@@ -73,8 +75,7 @@
if (should_admin_log)
admin_inject_log(user, M, src, contained, trans)
to_chat(user, SPAN_NOTICE("[trans] units injected. [reagents.total_volume] units remaining in \the [src]."))
-
- return
+ return TRUE
/obj/item/reagent_containers/hypospray/vial
name = "hypospray"
@@ -86,6 +87,7 @@
volume = 0
time = 7
single_use = FALSE
+ slot_flags = SLOT_BELT|SLOT_HOLSTER
/obj/item/reagent_containers/hypospray/vial/New()
..()
@@ -139,9 +141,7 @@
return
..()
-/obj/item/reagent_containers/hypospray/vial/afterattack(obj/target, mob/user, proximity) // hyposprays can be dumped into, why not out? uses standard_pour_into helper checks.
- if(!proximity)
- return
+/obj/item/reagent_containers/hypospray/vial/use_after(obj/target, mob/living/user, click_parameters) // hyposprays can be dumped into, why not out? uses standard_pour_into helper checks.
if (!reagents.total_volume && istype(target, /obj/item/reagent_containers/glass))
var/good_target = is_type_in_list(target, list(
/obj/item/reagent_containers/glass/beaker,
@@ -151,14 +151,16 @@
return
if (!target.is_open_container())
to_chat(user, SPAN_ITALIC("\The [target] is closed."))
- return
+ return TRUE
if (!target.reagents?.total_volume)
to_chat(user, SPAN_ITALIC("\The [target] is empty."))
- return
+ return TRUE
var/trans = target.reagents.trans_to_obj(src, amount_per_transfer_from_this)
to_chat(user, SPAN_NOTICE("You fill \the [src] with [trans] units of the solution."))
- return
- standard_pour_into(user, target)
+ return TRUE
+ else
+ standard_pour_into(user, target)
+ return TRUE
/obj/item/reagent_containers/hypospray/autoinjector
name = "autoinjector"
@@ -182,15 +184,11 @@
update_icon()
return
-/obj/item/reagent_containers/hypospray/autoinjector/attack(mob/M as mob, mob/user as mob)
- ..()
- update_icon()
-
/obj/item/reagent_containers/hypospray/autoinjector/on_reagent_change()
update_icon()
/obj/item/reagent_containers/hypospray/autoinjector/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(reagents.total_volume > 0)
icon_state = "[initial(icon_state)]1"
else
@@ -201,14 +199,14 @@
overlay_color = reagents.get_color()
else
overlay_color = COLOR_GRAY
- overlays += overlay_image(icon, "injector_band", overlay_color, RESET_COLOR)
+ AddOverlays(overlay_image(icon, "injector_band", overlay_color, RESET_COLOR))
/obj/item/reagent_containers/hypospray/autoinjector/examine(mob/user)
. = ..(user)
if(reagents && length(reagents.reagent_list))
- to_chat(user, SPAN_NOTICE("It is currently loaded."))
+ . += SPAN_NOTICE("It is currently loaded.")
else
- to_chat(user, SPAN_NOTICE("It is spent."))
+ . += SPAN_NOTICE("It is spent.")
/obj/item/reagent_containers/hypospray/autoinjector/detox
name = "autoinjector (antitox)"
diff --git a/code/modules/reagents/reagent_containers/ivbag.dm b/code/modules/reagents/reagent_containers/ivbag.dm
index 1d3d3050e9ac7..d1a9392638a1c 100644
--- a/code/modules/reagents/reagent_containers/ivbag.dm
+++ b/code/modules/reagents/reagent_containers/ivbag.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/ivbag
name = "\improper IV bag"
desc = "Flexible bag for IV injectors."
- icon = 'icons/obj/bloodpack.dmi'
+ icon = 'icons/obj/tools/bloodpack.dmi'
icon_state = "empty"
w_class = ITEM_SIZE_TINY
volume = 120
@@ -9,10 +9,10 @@
atom_flags = ATOM_FLAG_OPEN_CONTAINER
/// The set of options for the amount of reagents the bag will try to transfer per process.
- var/static/list/allowed_transfer_amounts = list(2, 1, REM, 0)
+ var/static/list/allowed_transfer_amounts = list(2, 1, 0)
/// The configured amount of reagents the IV bag will try to transfer per process.
- var/transfer_amount = 1
+ var/transfer_amount = 2
/// If this bag is attached to a person, that person.
var/mob/living/carbon/human/patient
@@ -58,7 +58,7 @@
message = "two thirds full"
else
message = "full"
- to_chat(user, "It has a flow rate of [transfer_amount]u of fluid per cycle and looks [message].")
+ . += SPAN_NOTICE("It has a flow rate of [transfer_amount]u of fluid per cycle and looks [message].")
/obj/item/reagent_containers/ivbag/on_reagent_change()
@@ -67,14 +67,14 @@
/obj/item/reagent_containers/ivbag/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if (reagents.total_volume)
var/state = clamp(Roundm(Percent(reagents.total_volume, volume, 0), 25), 0, 100)
var/image/filling = image(icon, icon_state = "[state]")
filling.color = reagents.get_color()
- overlays += filling
+ AddOverlays(filling)
if (patient)
- overlays += image(icon, icon_state = "dongle")
+ AddOverlays(image(icon, icon_state = "dongle"))
/obj/item/reagent_containers/ivbag/MouseDrop(atom/over_atom)
@@ -156,7 +156,7 @@
)
if (!user.do_skilled(5 SECONDS, SKILL_MEDICAL, target)) //slower than stands and beds
return
- if (prob(user.skill_fail_chance(SKILL_MEDICAL, 80, SKILL_ADEPT))) // harder than stands and beds
+ if (prob(user.skill_fail_chance(SKILL_MEDICAL, 80, SKILL_TRAINED))) // harder than stands and beds
user.visible_message(
SPAN_DANGER("\The [user] fishes for a vein on \the [target] and fails, stabbing them instead!"),
SPAN_DANGER("You fish inexpertly for a vein on \the [target] and stab them instead!"),
@@ -251,9 +251,12 @@
UpdateTransferAmount(user, src)
+/obj/item/reagent_containers/ivbag/nanoblood
+ transfer_amount = 1
+
+
/obj/item/reagent_containers/ivbag/nanoblood/Initialize()
. = ..()
- transfer_amount = REM
reagents.add_reagent(/datum/reagent/nanoblood, volume)
AddLabel("Nanoblood")
UpdateItemSize()
@@ -324,6 +327,18 @@
return ..(mapload, "O-")
+/obj/item/reagent_containers/ivbag/blood/serpentid
+ abstract_type = /obj/item/reagent_containers/ivbag/blood/serpentid
+
+
+/obj/item/reagent_containers/ivbag/blood/serpentid/Initialize(mapload, blood_type)
+ return ..(mapload, blood_type, SPECIES_NABBER)
+
+
+/obj/item/reagent_containers/ivbag/blood/serpentid/oneg/Initialize(mapload)
+ return ..(mapload, "O-")
+
+
/obj/item/reagent_containers/ivbag/blood/skrell
abstract_type = /obj/item/reagent_containers/ivbag/blood/skrell
@@ -360,6 +375,13 @@
return ..(mapload, "O-")
+/obj/item/reagent_containers/ivbag/glucose/Initialize()
+ . = ..()
+ reagents.add_reagent(/datum/reagent/nutriment/glucose, volume)
+ AddLabel("Glucose")
+ UpdateItemSize()
+
+
/obj/item/storage/box/bloodpacks
name = "blood packs box"
desc = "This box contains empty blood packs."
@@ -369,6 +391,15 @@
)
+/obj/item/storage/box/glucose
+ name = "glucose box"
+ desc = "This box contains glucose IV bags."
+ icon_state = "sterile"
+ startswith = list(
+ /obj/item/reagent_containers/ivbag/glucose = 7
+ )
+
+
/obj/item/storage/box/freezer/blood
abstract_type = /obj/item/storage/box/freezer/blood
@@ -380,6 +411,13 @@
)
+/obj/item/storage/box/freezer/blood/serpentid
+ name = "portable freezer (serpentid blood)"
+ startswith = list(
+ /obj/item/reagent_containers/ivbag/blood/serpentid/oneg = 4
+ )
+
+
/obj/item/storage/box/freezer/blood/skrell
name = "portable freezer (skrellian blood)"
startswith = list(
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 1de1e799348af..3247ec1a918af 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -4,7 +4,7 @@
/obj/item/reagent_containers/pill
name = "pill"
desc = "A pill."
- icon = 'icons/obj/chemical.dmi'
+ icon = 'icons/obj/pills.dmi'
icon_state = null
item_state = "pill"
randpixel = 7
@@ -18,58 +18,58 @@
if(!icon_state)
icon_state = "pill[rand(1, 5)]" //preset pills only use colour changing or unique icons
-/obj/item/reagent_containers/pill/attack(mob/M as mob, mob/user as mob, def_zone)
- //TODO: replace with standard_feed_mob() call.
+/obj/item/reagent_containers/pill/use_before(mob/M as mob, mob/user as mob)
+ . = FALSE
+ if (!istype(M))
+ return FALSE
- if(M == user)
- if(!M.can_eat(src))
- return
+ if (M == user)
+ if (!M.can_eat(src))
+ return TRUE
M.visible_message(SPAN_NOTICE("[M] swallows a pill."), SPAN_NOTICE("You swallow \the [src]."), null, 2)
- if(reagents.total_volume)
+ if (reagents.total_volume)
reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
qdel(src)
- return 1
+ return TRUE
- else if(istype(M, /mob/living/carbon/human))
- if(!M.can_force_feed(user, src))
- return
+ if (ishuman(M))
+ if (!M.can_force_feed(user, src))
+ return TRUE
user.visible_message(SPAN_WARNING("[user] attempts to force [M] to swallow \the [src]."))
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
- if(!do_after(user, 3 SECONDS, M, DO_MEDICAL))
- return
+ if (!do_after(user, 3 SECONDS, M, DO_MEDICAL))
+ return TRUE
if (user.get_active_hand() != src)
- return
+ return TRUE
user.visible_message(SPAN_WARNING("[user] forces [M] to swallow \the [src]."))
var/contained = reagentlist()
if (reagents.should_admin_log())
admin_attack_log(user, M, "Fed the victim with [name] (Reagents: [contained])", "Was fed [src] (Reagents: [contained])", "used [src] (Reagents: [contained]) to feed")
- if(reagents.total_volume)
+ if (reagents.total_volume)
reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
qdel(src)
- return 1
-
- return 0
+ return TRUE
-/obj/item/reagent_containers/pill/afterattack(obj/target, mob/user, proximity)
- if(!proximity) return
+/obj/item/reagent_containers/pill/use_after(atom/target, mob/living/user, click_parameters)
+ if (target.is_open_container() && target.reagents)
+ if (!target.reagents.total_volume)
+ to_chat(user, SPAN_NOTICE("\The [target] is empty. Can't dissolve \the [src]."))
+ return TRUE
- if(target.is_open_container() && target.reagents)
- if(!target.reagents.total_volume)
- to_chat(user, SPAN_NOTICE("[target] is empty. Can't dissolve \the [src]."))
- return
- to_chat(user, SPAN_NOTICE("You dissolve \the [src] in [target]."))
+ to_chat(user, SPAN_NOTICE("You dissolve \the [src] in \the [target]."))
if (reagents.should_admin_log())
admin_attacker_log(user, "spiked \a [target] with a pill. Reagents: [reagentlist()]")
reagents.trans_to(target, reagents.total_volume)
for(var/mob/O in viewers(2, user))
- O.show_message(SPAN_WARNING("[user] puts something in \the [target]."), 1)
+ O.show_message(SPAN_WARNING("\The [user] puts something in \the [target]."), 1)
qdel(src)
- return
+ return TRUE
+ else return FALSE
////////////////////////////////////////////////////////////////////////////////
/// Pills. END
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 6ad37d20ac069..dc55471463a2b 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/spray
name = "spray bottle"
desc = "A spray bottle, with an unscrewable top."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/janitor_tools.dmi'
icon_state = "cleaner"
item_state = "cleaner"
item_flags = ITEM_FLAG_NO_BLUDGEON
@@ -59,13 +59,12 @@
A.visible_message(SPAN_NOTICE("\The [user] sprays \the [A] with \the [src]."))
else
spawn(0)
- var/obj/effect/effect/water/chempuff/D = new/obj/effect/effect/water/chempuff(get_turf(src))
+ var/obj/effect/water/chempuff/D = new/obj/effect/water/chempuff(get_turf(src))
var/turf/my_target = get_turf(A)
D.create_reagents(amount_per_transfer_from_this)
if(!src)
return
reagents.trans_to_obj(D, amount_per_transfer_from_this)
- D.set_color()
D.set_up(my_target, spray_size, step_delay)
return
@@ -80,11 +79,10 @@
. = ..()
if(distance > 0)
return
-
if(length(reagents?.reagent_list))
- to_chat(user, SPAN_NOTICE("It contains [round(reagents.total_volume)] units of liquid."))
+ . += SPAN_NOTICE("It contains [round(reagents.total_volume)] units of liquid.")
else
- to_chat(user, SPAN_NOTICE("It is empty."))
+ . += SPAN_NOTICE("It is empty.")
/obj/item/reagent_containers/spray/verb/empty()
@@ -142,7 +140,7 @@
/obj/item/reagent_containers/spray/pepper/examine(mob/user, distance)
. = ..()
if(distance <= 1)
- to_chat(user, "The safety is [safety ? "on" : "off"].")
+ . += SPAN_NOTICE("The safety is [safety ? "on" : "off"].")
/obj/item/reagent_containers/spray/pepper/attack_self(mob/user)
safety = !safety
@@ -157,7 +155,7 @@
/obj/item/reagent_containers/spray/waterflower
name = "water flower"
desc = "A seemingly innocent sunflower...with a twist."
- icon = 'icons/obj/sunflower.dmi'
+ icon = 'icons/obj/flora/sunflower.dmi'
icon_state = "sunflower"
item_state = "sunflower"
amount_per_transfer_from_this = 1
@@ -169,10 +167,14 @@
reagents.add_reagent(/datum/reagent/water, 10)
/obj/item/reagent_containers/spray/chemsprayer
- name = "chem sprayer"
+ name = "industrial chemical sprayer"
desc = "A utility used to spray large amounts of reagent in a given area."
icon = 'icons/obj/weapons/other.dmi'
icon_state = "chemsprayer"
+ item_icons = list(
+ slot_r_hand_str = 'icons/mob/onmob/items/righthand.dmi',
+ slot_l_hand_str = 'icons/mob/onmob/items/lefthand.dmi'
+ )
item_state = "chemsprayer"
throwforce = 3
w_class = ITEM_SIZE_LARGE
@@ -182,6 +184,7 @@
step_delay = 8
/obj/item/reagent_containers/spray/chemsprayer/Spray_at(atom/A, mob/user, proximity)
+ playsound(src.loc, 'sound/effects/spray.ogg', 50, 1, -6)
var/direction = get_dir(src, A)
var/turf/T = get_turf(A)
var/turf/T1 = get_step(T,turn(direction, 90))
@@ -198,7 +201,7 @@
for(var/a = 1 to 3)
spawn(0)
if(reagents.total_volume < 1) break
- var/obj/effect/effect/water/chempuff/D = new/obj/effect/effect/water/chempuff(get_turf(src))
+ var/obj/effect/water/chempuff/D = new/obj/effect/water/chempuff(get_turf(src))
var/turf/my_target = the_targets[a]
D.create_reagents(amount_per_transfer_from_this)
if(!src)
@@ -211,7 +214,7 @@
/obj/item/reagent_containers/spray/plantbgone
name = "Plant-B-Gone"
desc = "Kills those pesky weeds!"
- icon = 'icons/obj/hydroponics_machines.dmi'
+ icon = 'icons/obj/machines/hydroponics_machines.dmi'
icon_state = "plantbgone"
item_state = "plantbgone"
volume = 100
@@ -223,7 +226,7 @@
/obj/item/reagent_containers/spray/plantbgone/afterattack(atom/A as mob|obj, mob/user as mob, proximity)
if(!proximity) return
- if(istype(A, /obj/effect/blob)) // blob damage in blob code
+ if(istype(A, /obj/blob)) // blob damage in blob code
return
..()
@@ -233,6 +236,6 @@
desc = "A can of Gold Standard spray deodorant - for when you're too lazy to shower."
gender = PLURAL
volume = 35
- icon = 'icons/obj/items.dmi'
+ icon = 'icons/obj/lavatory.dmi'
icon_state = "deodorant"
item_state = "deodorant"
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 014e609dee1f5..b39c2feec5e06 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -8,7 +8,7 @@
/obj/item/reagent_containers/syringe
name = "syringe"
desc = "A syringe."
- icon = 'icons/obj/syringe.dmi'
+ icon = 'icons/obj/tools/syringe.dmi'
item_state = "rg0"
icon_state = "rg"
matter = list(MATERIAL_GLASS = 150)
@@ -19,6 +19,7 @@
slot_flags = SLOT_EARS
sharp = TRUE
unacidable = TRUE
+ atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_NO_TOOLS
var/mode = SYRINGE_DRAW
var/image/filling //holds a reference to the current filling overlay
var/visible_name = "a syringe"
@@ -53,32 +54,27 @@
..()
update_icon()
-/obj/item/reagent_containers/syringe/attackby(obj/item/I as obj, mob/user as mob)
- return
-
-/obj/item/reagent_containers/syringe/afterattack(obj/target, mob/user, proximity)
- if(!proximity)
- return
-
+/obj/item/reagent_containers/syringe/use_after(obj/target, mob/living/user, click_parameters)
if(mode == SYRINGE_BROKEN)
to_chat(user, SPAN_WARNING("This syringe is broken."))
- return
+ return TRUE
if(istype(target, /obj/structure/closet/body_bag))
handleBodyBag(target, user)
- return
+ return TRUE
if(!target.reagents)
- return
+ return FALSE
if((user.a_intent == I_HURT) && ismob(target))
syringestab(target, user)
- return
+ return TRUE
handleTarget(target, user)
+ return TRUE
/obj/item/reagent_containers/syringe/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
underlays.Cut()
if(mode == SYRINGE_BROKEN)
@@ -95,7 +91,7 @@
injoverlay = "draw"
if (SYRINGE_INJECT)
injoverlay = "inject"
- overlays += injoverlay
+ AddOverlays(injoverlay)
icon_state = "[initial(icon_state)][rounded_vol]"
item_state = "syringe_[rounded_vol]"
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 68d35eadde618..648aedf83d051 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -2,7 +2,7 @@
/obj/structure/reagent_dispensers
name = "dispenser"
desc = "..."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/liquid_tanks.dmi'
icon_state = "watertank"
density = TRUE
anchored = FALSE
@@ -12,9 +12,6 @@
var/amount_per_transfer_from_this = 10
var/possible_transfer_amounts = "10;25;50;100;500"
-/obj/structure/reagent_dispensers/attackby(obj/item/W as obj, mob/user as mob)
- return
-
/obj/structure/reagent_dispensers/New()
create_reagents(initial_capacity)
@@ -32,12 +29,12 @@
if(distance > 2)
return
- to_chat(user, SPAN_NOTICE("It contains:"))
+ . += SPAN_NOTICE("It contains:")
if(reagents && length(reagents.reagent_list))
for(var/datum/reagent/R in reagents.reagent_list)
- to_chat(user, SPAN_NOTICE("[R.volume] units of [R.name]"))
+ . += SPAN_NOTICE("[R.volume] units of [R.name]")
else
- to_chat(user, SPAN_NOTICE("Nothing."))
+ . += SPAN_NOTICE("Nothing.")
/obj/structure/reagent_dispensers/verb/set_amount_per_transfer_from_this()
set name = "Set transfer amount"
@@ -60,12 +57,12 @@
return
if(EX_ACT_HEAVY)
if (prob(50))
- new /obj/effect/effect/water(src.loc)
+ new /obj/effect/water(src.loc)
qdel(src)
return
if(EX_ACT_LIGHT)
if (prob(5))
- new /obj/effect/effect/water(src.loc)
+ new /obj/effect/water(src.loc)
qdel(src)
return
else
@@ -74,15 +71,15 @@
/obj/structure/reagent_dispensers/AltClick(mob/user)
if(possible_transfer_amounts)
set_amount_per_transfer_from_this()
- else
- return ..()
+ return TRUE
+ return ..()
//Dispensers
/obj/structure/reagent_dispensers/watertank
name = "water tank"
desc = "A tank containing water."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/liquid_tanks.dmi'
icon_state = "watertank"
amount_per_transfer_from_this = 10
var/modded = 0
@@ -115,34 +112,43 @@
/obj/structure/reagent_dispensers/watertank/examine(mob/user)
. = ..()
-
if(modded)
- to_chat(user, SPAN_WARNING("Someone has wrenched open its tap - it's spilling everywhere!"))
-
-/obj/structure/reagent_dispensers/watertank/attackby(obj/item/W, mob/user)
+ . += SPAN_WARNING("Someone has wrenched open its tap - it's spilling everywhere!")
- src.add_fingerprint(user)
-
- if((istype(W, /obj/item/robot_parts/l_arm) || istype(W, /obj/item/robot_parts/r_arm)) && user.unEquip(W))
- to_chat(user, "You add \the [W] arm to \the [src].")
- qdel(W)
- new /obj/item/farmbot_arm_assembly(loc, src)
+/obj/structure/reagent_dispensers/watertank/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
-
- if(isWrench(W))
- modded = !modded
- user.visible_message(SPAN_NOTICE("\The [user] wrenches \the [src]'s tap [modded ? "open" : "shut"]."), \
- SPAN_NOTICE("You wrench [src]'s drain [modded ? "open" : "shut"]."))
-
- if (modded)
- log_and_message_admins("opened a water tank at [get_area(loc)], leaking water.")
- // Allows the water tank to continuously expel water, differing it from the fuel tank.
- START_PROCESSING(SSprocessing, src)
- else
- STOP_PROCESSING(SSprocessing, src)
+ modded = !modded
+ user.visible_message(
+ SPAN_NOTICE("[user] [modded ? "opens" : "closes"] [src]'s valve with [tool]."),
+ SPAN_NOTICE("You [user] [modded ? "open" : "close"] [src]'s valve with [tool].")
+ )
+ if (modded)
+ log_and_message_admins("opened a water tank at [get_area(src)], leaking water", user, get_turf(src))
+ START_PROCESSING(SSprocessing, src)
+ else
+ STOP_PROCESSING(SSprocessing, src)
+
+/obj/structure/reagent_dispensers/watertank/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Robot Arm - Attach arm for farmbot
+ if (is_type_in_list(tool, list(/obj/item/robot_parts/l_arm, /obj/item/robot_parts/r_arm)))
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ var/obj/item/farmbot_arm_assembly/new_assembly = new /obj/item/farmbot_arm_assembly(loc, src)
+ transfer_fingerprints_to(new_assembly)
+ tool.transfer_fingerprints_to(new_assembly)
+ user.visible_message(
+ SPAN_NOTICE("[user] attaches [tool] to [src]."),
+ SPAN_NOTICE("You attach [tool] to [src].")
+ )
+ qdel(tool)
+ return TRUE
return ..()
+
/obj/structure/reagent_dispensers/watertank/Process()
if(modded)
drain_water()
@@ -155,7 +161,7 @@
/obj/structure/reagent_dispensers/fueltank
name = "fuel tank"
desc = "A tank containing welding fuel."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/liquid_tanks.dmi'
icon_state = "weldtank"
amount_per_transfer_from_this = 10
var/modded = 0
@@ -165,72 +171,109 @@
/obj/structure/reagent_dispensers/fueltank/examine(mob/user)
. = ..()
-
if (modded)
- to_chat(user, SPAN_WARNING("The faucet is wrenched open, leaking fuel!"))
+ . += SPAN_WARNING("The faucet is wrenched open, leaking fuel!")
if(rig)
- to_chat(user, SPAN_NOTICE("There is some kind of device rigged to the tank."))
+ . += SPAN_NOTICE("There is some kind of device rigged to the tank.")
/obj/structure/reagent_dispensers/fueltank/attack_hand()
if (rig)
- usr.visible_message(SPAN_NOTICE("\The [usr] begins to detach \the [rig] from \the [src]."), SPAN_NOTICE("You begin to detach \the [rig] from \the [src]."))
+ usr.visible_message(SPAN_NOTICE("[usr] begins to detach [rig] from [src]."), SPAN_NOTICE("You begin to detach [rig] from [src]."))
if(do_after(usr, 2 SECONDS, src, DO_PUBLIC_UNIQUE))
- usr.visible_message(SPAN_NOTICE("\The [usr] detaches \the [rig] from \the [src]."), SPAN_NOTICE("You detach [rig] from \the [src]"))
+ usr.visible_message(SPAN_NOTICE("[usr] detaches [rig] from [src]."), SPAN_NOTICE("You detach [rig] from [src]"))
rig.dropInto(usr.loc)
rig = null
- overlays.Cut()
-
-/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/W as obj, mob/user as mob)
- src.add_fingerprint(user)
- if (istype(W,/obj/item/wrench))
- user.visible_message("\The [user] wrenches \the [src]'s faucet [modded ? "closed" : "open"].", \
- "You wrench [src]'s faucet [modded ? "closed" : "open"]")
- modded = modded ? 0 : 1
- if (modded)
- log_and_message_admins("opened a fuel tank at [loc.loc.name], leaking fuel.")
- leak_fuel(amount_per_transfer_from_this)
- else if (istype(W,/obj/item/device/assembly_holder))
- if (rig)
- to_chat(user, SPAN_WARNING("There is another device already in the way."))
- return ..()
- user.visible_message("\The [user] begins rigging \the [W] to \the [src].", "You begin rigging \the [W] to \the [src]")
- if(do_after(user, 2 SECONDS, src, DO_PUBLIC_UNIQUE))
- if(!user.unEquip(W, src))
- return
- user.visible_message(SPAN_NOTICE("\The [user] rigs \the [W] to \the [src]."), SPAN_NOTICE("You rig \the [W] to \the [src]."))
-
- var/obj/item/device/assembly_holder/H = W
- if (istype(H.a_left,/obj/item/device/assembly/igniter) || istype(H.a_right,/obj/item/device/assembly/igniter))
- log_and_message_admins("rigged a fuel tank for explosion at [loc.loc.name].")
- rig = W
- var/icon/test = getFlatIcon(W)
- test.Shift(NORTH,1)
- test.Shift(EAST,6)
- overlays += test
-
- else if(isflamesource(W))
- if(user.a_intent != I_HURT)
- to_chat(user, SPAN_WARNING("You almost got \the [W] too close to [src]! That could have ended very badly for you."))
- return
+ ClearOverlays()
+
+
+/obj/structure/reagent_dispensers/fueltank/use_weapon(obj/item/weapon, mob/user, list/click_params)
+ // Flame Source - Kaboom
+ if (IsFlameSource(weapon))
+ user.visible_message(
+ SPAN_WARNING("[user] holds [weapon] up against [src], heating it up!"),
+ SPAN_WARNING("You hold [weapon] up against [src], heating it up!")
+ )
+ if (!do_after(user, 5 SECONDS, src, DO_DEFAULT | DO_BOTH_UNIQUE_ACT) || !user.use_sanity_check(src, weapon))
+ return TRUE
+ if (!IsFlameSource(weapon))
+ USE_FEEDBACK_FAILURE("[weapon] isn't hot enough anymore.")
+ return TRUE
+ log_and_message_admins("triggered a fuel tank explosion with [weapon] in [get_area(src)]", user, get_turf(src))
+ user.visible_message(
+ SPAN_DANGER("[user] holds [weapon] against [src], causing it to explode!"),
+ SPAN_DANGER("You hold [weapon] against [src], causing it to explode!")
+ )
+ explode()
+ return TRUE
- user.visible_message(SPAN_WARNING("\The [user] draws closer to the fuel tank with \the [W]."), SPAN_WARNING("You draw closer to the fuel tank with \the [W]."))
- if(do_after(user, 5 SECONDS, src, DO_DEFAULT | DO_USER_UNIQUE_ACT)) // No public progress - Leave the people that might try to rush it guessing
- log_and_message_admins("triggered a fuel tank explosion with \the [W].")
- user.visible_message(SPAN_DANGER("\The [user] puts \the [W] to \the [src]!"), SPAN_DANGER("You put \the [W] to \the [src] and with a moment of lucidity you realize, this might not have been the smartest thing you've ever done."))
- src.explode()
+ return ..()
+/obj/structure/reagent_dispensers/fueltank/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
+ modded = !modded
+ user.visible_message(
+ SPAN_NOTICE("[user] [modded ? "opens" : "closes"] [src]'s valve with [tool]."),
+ SPAN_NOTICE("You [user] [modded ? "open" : "close"] [src]'s valve with [tool].")
+ )
+ if(modded)
+ log_and_message_admins("opened a fuel tank at [get_area(src)], leaking fuel.")
+ leak_fuel(amount_per_transfer_from_this)
+
+/obj/structure/reagent_dispensers/fueltank/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Assembly Holder - Attach assembly
+ if (istype(tool, /obj/item/device/assembly_holder))
+ if (rig)
+ USE_FEEDBACK_FAILURE("[src] already has [rig] attached.")
+ return TRUE
+ if (!user.canUnEquip(tool))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("[user] starts attaching [tool] to [src]."),
+ SPAN_NOTICE("You start attaching [tool] to [src].")
+ )
+ if (!user.do_skilled(2 SECONDS, SKILL_DEVICES, src) || !user.use_sanity_check(src, tool, SANITY_CHECK_DEFAULT | SANITY_CHECK_TOOL_UNEQUIP))
+ return TRUE
+ if (rig)
+ USE_FEEDBACK_FAILURE("[src] already has [rig] attached.")
+ return TRUE
+ user.unEquip(tool, src)
+ rig = tool
+ if (istype(rig.a_left, /obj/item/device/assembly/igniter) || istype(rig.a_right, /obj/item/device/assembly/igniter))
+ log_and_message_admins("rigged a fuel tank for explosion at [get_area(src)].", user, get_turf(src))
+ update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] attaches [tool] to [src]."),
+ SPAN_NOTICE("You attach [tool] to [src].")
+ )
+ return TRUE
+
+ // Flame Source - Warn against kaboom
+ if (IsFlameSource(tool))
+ USE_FEEDBACK_FAILURE("You refrain from heating up [src] with [tool].")
+ return TRUE
return ..()
+/obj/structure/reagent_dispensers/fueltank/on_update_icon()
+ ClearOverlays()
+ if (rig)
+ var/icon/rig_overlay = getFlatIcon(rig)
+ rig_overlay.Shift(NORTH, 1)
+ rig_overlay.Shift(EAST, 6)
+ AddOverlays(rig_overlay)
+
+
/obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/Proj)
if(Proj.get_structure_damage())
if(istype(Proj.firer))
var/turf/turf = get_turf(src)
if(turf)
var/area/area = turf.loc || "*unknown area*"
- log_and_message_admins("[key_name_admin(Proj.firer)] shot a fuel tank in \the [area].")
+ log_and_message_admins("[key_name_admin(Proj.firer)] shot a fuel tank in [area].")
else
log_and_message_admins("shot a fuel tank outside the world.")
@@ -259,12 +302,12 @@
amount = min(amount, reagents.total_volume)
reagents.remove_reagent(/datum/reagent/fuel,amount)
- new /obj/effect/decal/cleanable/liquid_fuel(src.loc, amount,1)
+ new /obj/decal/cleanable/liquid_fuel(src.loc, amount,1)
/obj/structure/reagent_dispensers/peppertank
name = "pepper spray refiller"
desc = "Refills pepper spray canisters."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/chemical_dispensers.dmi'
icon_state = "peppertank"
anchored = TRUE
density = FALSE
@@ -276,10 +319,11 @@
name = "water cooler"
desc = "A machine that dispenses cool water to drink."
amount_per_transfer_from_this = 5
- icon = 'icons/obj/vending.dmi'
+ icon = 'icons/obj/machines/vending.dmi'
icon_state = "water_cooler"
possible_transfer_amounts = null
anchored = TRUE
+ obj_flags = OBJ_FLAG_ANCHORABLE
initial_capacity = 500
initial_reagent_types = list(/datum/reagent/water = 1)
var/cups = 12
@@ -296,41 +340,32 @@
to_chat(user, RejectionMessage(user))
/obj/structure/reagent_dispensers/water_cooler/proc/DispenserMessages(mob/user)
- return list("\The [user] grabs a paper cup from \the [src].", "You grab a paper cup from \the [src]'s cup compartment.")
+ return list("[user] grabs a paper cup from [src].", "You grab a paper cup from [src]'s cup compartment.")
/obj/structure/reagent_dispensers/water_cooler/proc/RejectionMessage(mob/user)
return "The [src]'s cup dispenser is empty."
-/obj/structure/reagent_dispensers/water_cooler/attackby(obj/item/W as obj, mob/user as mob)
- if (istype(W,/obj/item/wrench))
- src.add_fingerprint(user)
- if(anchored)
- user.visible_message("\The [user] begins unsecuring \the [src] from the floor.", "You start unsecuring \the [src] from the floor.")
- else
- user.visible_message("\The [user] begins securing \the [src] to the floor.", "You start securing \the [src] to the floor.")
- if(do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(!src) return
- to_chat(user, SPAN_NOTICE("You [anchored? "un" : ""]secured \the [src]!"))
- anchored = !anchored
- return
- else
+/obj/structure/reagent_dispensers/water_cooler/post_use_item(obj/item/tool, mob/user, interaction_handled, use_call, click_params)
+ if (interaction_handled)
flick("[icon_state]-vend", src)
- return ..()
+ ..()
+
/obj/structure/reagent_dispensers/beerkeg
name = "beer keg"
desc = "A beer keg."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/liquid_tanks.dmi'
icon_state = "beertankTEMP"
amount_per_transfer_from_this = 10
initial_reagent_types = list(/datum/reagent/ethanol/beer = 1)
atom_flags = ATOM_FLAG_CLIMBABLE
+ obj_flags = OBJ_FLAG_CAN_TABLE
/obj/structure/reagent_dispensers/acid
name = "sulphuric acid dispenser"
desc = "A dispenser of acid for industrial processes."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/chemical_dispensers.dmi'
icon_state = "acidtank"
amount_per_transfer_from_this = 10
anchored = TRUE
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index a470bb75f1cb6..364e1ec60ce0b 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -2,7 +2,7 @@
//note that corner pieces transfer stuff clockwise when running forward, and anti-clockwise backwards.
/obj/machinery/conveyor
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "conveyor0"
name = "conveyor belt"
desc = "A conveyor belt."
@@ -84,7 +84,7 @@
affecting += AM
items_moved++
if(length(affecting))
- addtimer(new Callback(src, .proc/post_process, affecting), 1) // slight delay to prevent infinite propagation due to map order
+ addtimer(CALLBACK(src, PROC_REF(post_process), affecting), 1) // slight delay to prevent infinite propagation due to map order
/obj/machinery/conveyor/proc/post_process(list/affecting)
for(var/A in affecting)
@@ -94,17 +94,23 @@
if(AM.loc == src.loc) // prevents the object from being affected if it's not currently here.
step(A,movedir)
+/obj/machinery/conveyor/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ if(!MACHINE_IS_BROKEN(src))
+ var/obj/item/conveyor_construct/C = new/obj/item/conveyor_construct(src.loc)
+ C.id = id
+ transfer_fingerprints_to(C)
+ to_chat(user, SPAN_NOTICE("You remove the conveyor belt."))
+ qdel(src)
+
// attack with item, place item on conveyor
-/obj/machinery/conveyor/attackby(obj/item/I, mob/user)
- if(isCrowbar(I))
- if(!MACHINE_IS_BROKEN(src))
- var/obj/item/conveyor_construct/C = new/obj/item/conveyor_construct(src.loc)
- C.id = id
- transfer_fingerprints_to(C)
- to_chat(user, SPAN_NOTICE("You remove the conveyor belt."))
- qdel(src)
+/obj/machinery/conveyor/use_tool(obj/item/I, mob/living/user, list/click_params)
+ if ((. = ..()))
return
user.unequip_item(get_turf(src))
+ return TRUE
// attack with hand, move pulled object onto conveyor
/obj/machinery/conveyor/physical_attack_hand(mob/user)
@@ -157,7 +163,7 @@
name = "conveyor switch"
desc = "A conveyor control switch."
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "switch-off"
var/position = 0 // 0 off, -1 reverse, 1 forward
var/last_pos = -1 // last direction setting
@@ -209,15 +215,18 @@
/obj/machinery/conveyor_switch/interface_interact(mob/user)
if(!CanInteract(user, DefaultTopicState()))
return FALSE
+
do_switch()
- operated = 1
+ operated = TRUE
update_icon()
// find any switches with same id as this one, and set their positions to match us
- for(var/obj/machinery/conveyor_switch/S in world)
- if(S.id == src.id)
- S.position = position
- S.update_icon()
+ for(var/obj/machinery/conveyor_switch/other_switch as anything in SSmachines.get_machinery_of_type(/obj/machinery/conveyor_switch))
+ if(other_switch.id != id)
+ continue
+
+ other_switch.position = position
+ other_switch.update_icon()
return TRUE
/obj/machinery/conveyor_switch/proc/do_switch(mob/user)
@@ -232,13 +241,15 @@
last_pos = position
position = 0
-/obj/machinery/conveyor_switch/attackby(obj/item/I, mob/user, params)
- if(isCrowbar(I))
- var/obj/item/conveyor_switch_construct/C = new/obj/item/conveyor_switch_construct(src.loc)
- C.id = id
- transfer_fingerprints_to(C)
- to_chat(user, SPAN_NOTICE("You deattach the conveyor switch."))
- qdel(src)
+/obj/machinery/conveyor_switch/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/item/conveyor_switch_construct/C = new/obj/item/conveyor_switch_construct(src.loc)
+ C.id = id
+ transfer_fingerprints_to(C)
+ to_chat(user, SPAN_NOTICE("You deattach the conveyor switch."))
+ qdel(src)
/obj/machinery/conveyor_switch/oneway
var/convdir = 1 //Set to 1 or -1 depending on which way you want the convayor to go. (In other words keep at 1 and set the proper dir on the belts.)
@@ -255,7 +266,7 @@
//
/obj/item/conveyor_construct
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "conveyor0"
name = "conveyor belt assembly"
desc = "A conveyor belt assembly. Must be linked to a conveyor control switch assembly before placement."
@@ -270,26 +281,27 @@
var/obj/item/conveyor_switch_construct/C = I
id = C.id
-/obj/item/conveyor_construct/afterattack(atom/A, mob/user, proximity)
- if(!proximity || !istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
- return
+/obj/item/conveyor_construct/use_after(atom/A, mob/living/user, click_parameters)
+ if(!istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
+ return FALSE
var/cdir = get_dir(A, user)
if(!(cdir in GLOB.cardinal) || A == user.loc)
- return
+ return TRUE
for(var/obj/machinery/conveyor/CB in A)
if(CB.dir == cdir || CB.dir == turn(cdir,180))
- return
+ return TRUE
cdir |= CB.dir
qdel(CB)
var/obj/machinery/conveyor/C = new/obj/machinery/conveyor(A,cdir)
C.id = id
transfer_fingerprints_to(C)
qdel(src)
+ return TRUE
/obj/item/conveyor_switch_construct
name = "conveyor switch assembly"
desc = "A conveyor control switch assembly."
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "switch-off"
w_class = ITEM_SIZE_HUGE
var/id = "" //inherited by the switch
@@ -300,9 +312,9 @@
..()
id = rand() //this couldn't possibly go wrong
-/obj/item/conveyor_switch_construct/afterattack(atom/A, mob/user, proximity)
- if(!proximity || !istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
- return
+/obj/item/conveyor_switch_construct/use_after(atom/A, mob/living/user, click_parameters)
+ if(!istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
+ return FALSE
var/found = 0
for(var/obj/machinery/conveyor/C in view())
if(C.id == src.id)
@@ -310,18 +322,19 @@
break
if(!found)
to_chat(user, "[icon2html(src, user)][SPAN_NOTICE("The conveyor switch did not detect any linked conveyor belts in range.")]")
- return
+ return TRUE
var/obj/machinery/conveyor_switch/NC = new /obj/machinery/conveyor_switch(A, id)
transfer_fingerprints_to(NC)
qdel(src)
+ return TRUE
/obj/item/conveyor_switch_construct/oneway
name = "one-way conveyor switch assembly"
desc = "An one-way conveyor control switch assembly."
-/obj/item/conveyor_switch_construct/oneway/afterattack(atom/A, mob/user, proximity)
- if(!proximity || !istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
- return
+/obj/item/conveyor_switch_construct/oneway/use_after(atom/A, mob/living/user, click_parameters)
+ if(!istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated())
+ return FALSE
var/found = 0
for(var/obj/machinery/conveyor/C in view())
if(C.id == src.id)
@@ -329,7 +342,8 @@
break
if(!found)
to_chat(user, "[icon2html(src, user)][SPAN_NOTICE("The conveyor switch did not detect any linked conveyor belts in range.")]")
- return
+ return TRUE
var/obj/machinery/conveyor_switch/oneway/NC = new /obj/machinery/conveyor_switch/oneway(A, id)
transfer_fingerprints_to(NC)
qdel(src)
+ return TRUE
diff --git a/code/modules/recycling/disposal-construction.dm b/code/modules/recycling/disposal-construction.dm
index 8ffb5d413fce2..4aad7ce6e2db5 100644
--- a/code/modules/recycling/disposal-construction.dm
+++ b/code/modules/recycling/disposal-construction.dm
@@ -9,7 +9,7 @@
anchored = FALSE
density = FALSE
matter = list(MATERIAL_STEEL = 1850)
- obj_flags = OBJ_FLAG_ROTATABLE
+ obj_flags = OBJ_FLAG_ROTATABLE | OBJ_FLAG_ANCHORABLE
var/sort_type = ""
var/dpdir = 0 // directions as disposalpipe
var/turn = DISPOSAL_FLIP_FLIP
@@ -66,7 +66,7 @@
// hide called by levelupdate if turf intact status changes
// change visibility status and force update of icon
/obj/structure/disposalconstruct/hide(intact)
- set_invisibility((intact && level==ATOM_LEVEL_UNDER_TILE) ? 101: 0) // hide if floor is intact
+ set_invisibility((intact && level==ATOM_LEVEL_UNDER_TILE) ? INVISIBILITY_ABSTRACT : 0) // hide if floor is intact
update()
/obj/structure/disposalconstruct/proc/flip()
@@ -93,7 +93,7 @@
set_dir(turn(dir, 180))
/obj/structure/disposalconstruct/on_update_icon()
- if("con[built_icon_state]" in icon_states(icon))
+ if(ICON_HAS_STATE(icon, "con[built_icon_state]"))
icon_state = "con[built_icon_state]"
else
icon_state = built_icon_state
@@ -117,51 +117,57 @@
. = ..()
set_dir(old_dir)
-// attackby item
-// wrench: (un)anchor
-// weldingtool: convert to real pipe
-/obj/structure/disposalconstruct/attackby(obj/item/I, mob/user)
- var/turf/T = loc
- if(!istype(T))
- return
- if(!T.is_plating())
- to_chat(user, "You can only manipulate \the [src] if the floor plating is removed.")
+
+/obj/structure/disposalconstruct/can_anchor(obj/item/tool, mob/user, silent)
+ . = ..()
+ if (!.)
return
+ if (!anchored)
+ // Plating
+ var/turf/turf = get_turf(src)
+ if (!turf.is_plating())
+ if (!silent)
+ USE_FEEDBACK_FAILURE("You must remove the plating before you can secure [src].")
+ return FALSE
- var/obj/structure/disposalpipe/CP = locate() in T
-
- if(isWrench(I))
- if(anchored)
- anchored = FALSE
- wrench_down(FALSE)
- to_chat(user, "You detach \the [src] from the underfloor.")
- else
- if(!check_buildability(CP, user))
- return
- wrench_down(TRUE)
- to_chat(user, "You attach \the [src] to the underfloor.")
- playsound(loc, 'sound/items/Ratchet.ogg', 100, 1)
- update()
- update_verbs()
-
- else if(istype(I, /obj/item/weldingtool))
- if(anchored)
- var/obj/item/weldingtool/W = I
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- to_chat(user, "Welding \the [src] in place.")
- if(do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(!src || !W.isOn()) return
- to_chat(user, "\The [src] has been welded in place!")
- build(CP)
- qdel(src)
- return
- else
- to_chat(user, "You need more welding fuel to complete this task.")
- return
- else
- to_chat(user, "You need to attach it to the plating first!")
- return
+ // Catwalks
+ var/obj/structure/catwalk/catwalk = locate() in get_turf(src)
+ if (catwalk)
+ if (catwalk.plated_tile && !catwalk.hatch_open)
+ if (!silent)
+ USE_FEEDBACK_FAILURE("[catwalk]'s hatch needs to be opened before you can secure [src].")
+ return FALSE
+ else if (!catwalk.plated_tile)
+ if (!silent)
+ USE_FEEDBACK_FAILURE("[catwalk] is blocking access to the floor.")
+ return FALSE
+
+ var/obj/structure/disposalpipe/connected_pipe = locate() in get_turf(src)
+ if (!check_buildability(connected_pipe, user))
+ return FALSE
+
+
+/obj/structure/disposalconstruct/post_anchor_change()
+ wrench_down(anchored)
+ update()
+ update_verbs()
+ ..()
+
+
+/obj/structure/disposalconstruct/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/obj/structure/disposalpipe/connected_pipe = locate() in get_turf(src)
+ // Welding Tool - Weld into place
+ if(!anchored)
+ USE_FEEDBACK_NEED_ANCHOR(user)
+ return
+ if(!tool.tool_start_check(user, 1))
+ return
+ balloon_alert(user, "приваривание к полу")
+ if(!tool.use_as_tool(src, user, 2 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ build(connected_pipe)
+ qdel(src)
/obj/structure/disposalconstruct/hides_under_flooring()
return anchored
@@ -190,11 +196,11 @@
/obj/structure/disposalconstruct/machine/check_buildability(obj/structure/disposalpipe/CP, mob/user)
if(CP) // There's something there
if(!istype(CP,/obj/structure/disposalpipe/trunk))
- to_chat(user, "\The [src] requires a trunk underneath it in order to work.")
+ to_chat(user, "[src] requires a trunk underneath it in order to work.")
return FALSE
return TRUE
// Nothing under, fuck.
- to_chat(user, "\The [src] requires a trunk underneath it in order to work.")
+ to_chat(user, "[src] requires a trunk underneath it in order to work.")
return FALSE
/obj/structure/disposalconstruct/proc/build()
@@ -206,12 +212,10 @@
P.sort_type = sort_type
P.set_dir(dir)
P.on_build()
+ P.balloon_alert_to_viewers("приварено к полу!")
// Subtypes
-/obj/structure/disposalconstruct/machine
- obj_flags = 0 // No rotating
-
/obj/structure/disposalconstruct/machine/update_verbs()
return // No flipping
@@ -221,10 +225,11 @@
update_icon()
/obj/structure/disposalconstruct/machine/build(obj/structure/disposalpipe/CP)
- var/obj/machinery/disposal/P = new /obj/machinery/disposal(src.loc)
+ var/obj/machinery/disposal/P = new constructed_path(src.loc)
transfer_fingerprints_to(P)
P.set_dir(dir)
P.mode = 0 // start with pump off
+ P.balloon_alert_to_viewers("приварено к полу!")
/obj/structure/disposalconstruct/machine/on_update_icon()
if(anchored)
@@ -238,3 +243,4 @@
P.set_dir(dir)
var/obj/structure/disposalpipe/trunk/Trunk = CP
Trunk.linked = P
+ P.balloon_alert_to_viewers("приварено к полу!")
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index aa02aa2e69390..d9e63e44c16b6 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -18,15 +18,25 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
icon_state = "disposal"
anchored = TRUE
density = TRUE
- var/datum/gas_mixture/air_contents // internal reservoir
- var/mode = 1 // item mode 0=off 1=charging 2=charged
- var/flush = 0 // true if flush handle is pulled
- var/obj/structure/disposalpipe/trunk/trunk = null // the attached pipe trunk
- var/flushing = 0 // true if flushing in progress
- var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush
- var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
+ /// Internal reservoir
+ var/datum/gas_mixture/air_contents
+ /// Item mode 0=off 1=charging 2=charged
+ var/mode = 1
+ // True if flush handle is pulled
+ var/flush = FALSE
+ /// The attached pipe trunk
+ var/obj/structure/disposalpipe/trunk/trunk
+ /// True if flushing in progress
+ var/flushing = FALSE
+ /// Every 30 ticks it will look whether it is ready to flush
+ var/flush_every_ticks = 3 SECONDS
+ /// This var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
+ var/flush_count = 0
var/last_sound = 0
- var/list/allowed_objects = list(/obj/structure/closet)
+ var/list/allowed_objects = list(
+ /obj/structure/closet,
+ /obj/structure/bigDelivery
+ )
active_power_usage = 2200 //the pneumatic pump power. 3 HP ~ 2200W
idle_power_usage = 100
atom_flags = ATOM_FLAG_CLIMBABLE
@@ -56,87 +66,91 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
trunk.linked = null
return ..()
-// attack by item places it in to disposal
-/obj/machinery/disposal/attackby(obj/item/I, mob/user)
- if(MACHINE_IS_BROKEN(src) || !I || !user)
+/obj/machinery/disposal/use_grab(obj/item/grab/G, list/click_params)
+ if (MACHINE_IS_BROKEN(src))
+ return ..()
+
+ if (ismob(G.affecting))
+ var/mob/GM = G.affecting
+ var/mob/user = G.assailant
+ user.visible_message(SPAN_DANGER("[user] starts putting [GM.name] into the disposal."))
+ if (do_after(user, 2 SECONDS, src, DO_PUBLIC_UNIQUE))
+ if (GM.client)
+ GM.client.perspective = EYE_PERSPECTIVE
+ GM.client.eye = src
+ GM.forceMove(src)
+ user.visible_message(SPAN_DANGER("[GM] has been placed in [src] by [user]."))
+ GM.remove_grabs_and_pulls()
+ admin_attack_log(user, GM, "Placed the victim into [src].", "Was placed into [src] by the attacker.", "stuffed [src] with")
+ return TRUE
+
+ return ..()
+
+/obj/machinery/disposal/screwdriver_act(mob/living/user, obj/item/tool)
+ if(mode > 0)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ if(length(contents) > LAZYLEN(component_parts))
+ balloon_alert(user, "нужно очистить содержимое!")
+ return
+ if(mode == 0) // It's off but still not unscrewed
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ mode = -1 // Set it to doubleoff l0l
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, TRUE)
+ return
+ if(mode == -1)
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ mode = 0
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, FALSE)
+
+/obj/machinery/disposal/welder_act(mob/living/user, obj/item/tool)
+ if(mode != -1)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ if(length(contents) > LAZYLEN(component_parts))
+ balloon_alert(user, "нужно вытащить предметы!")
+ return
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_UNWELD_FROM_FLOOR(user)
+ if(!tool.use_as_tool(src, user, 2 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
return
+ var/obj/structure/disposalconstruct/machine/C = new (loc, src)
+ transfer_fingerprints_to(C)
+ C.update()
+ C.balloon_alert_to_viewers("отварено от пола!")
+ qdel(src)
- add_fingerprint(user, 0, I)
- if(mode<=0) // It's off
- if(isScrewdriver(I))
- if(length(contents) > LAZYLEN(component_parts))
- to_chat(user, "Eject the items first!")
- return
- if(mode==0) // It's off but still not unscrewed
- mode=-1 // Set it to doubleoff l0l
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You remove the screws around the power connection.")
- return
- else if(mode==-1)
- mode=0
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You attach the screws around the power connection.")
- return
- else if(isWelder(I) && mode==-1)
- if(length(contents) > LAZYLEN(component_parts))
- to_chat(user, "Eject the items first!")
- return
- var/obj/item/weldingtool/W = I
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- to_chat(user, "You start slicing the floorweld off the disposal unit.")
-
- if(do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(!src || !W.isOn()) return
- to_chat(user, "You sliced the floorweld off the disposal unit.")
- var/obj/structure/disposalconstruct/machine/C = new (loc, src)
- src.transfer_fingerprints_to(C)
- C.update()
- qdel(src)
- return
- else
- to_chat(user, "You need more welding fuel to complete this task.")
- return
+/obj/machinery/disposal/use_tool(obj/item/I, mob/living/user, list/click_params)
+ if(MACHINE_IS_BROKEN(src))
+ return ..()
- if(istype(I, /obj/item/melee/energy/blade))
- to_chat(user, "You can't place that item inside the disposal unit.")
+ if ((. = ..()))
return
- if(istype(I, /obj/item/storage/bag/trash))
+ if (istype(I, /obj/item/storage/bag/trash))
var/obj/item/storage/bag/trash/T = I
to_chat(user, SPAN_NOTICE("You empty the bag."))
for(var/obj/item/O in T.contents)
T.remove_from_storage(O,src, 1)
T.finish_bulk_removal()
update_icon()
- return
-
- var/obj/item/grab/G = I
- if(istype(G)) // handle grabbed mob
- if(ismob(G.affecting))
- var/mob/GM = G.affecting
- usr.visible_message(SPAN_DANGER("\The [usr] starts putting [GM.name] into the disposal."))
- if(do_after(usr, 2 SECONDS, src, DO_PUBLIC_UNIQUE))
- if (GM.client)
- GM.client.perspective = EYE_PERSPECTIVE
- GM.client.eye = src
- GM.forceMove(src)
- usr.visible_message(SPAN_DANGER("\The [GM] has been placed in the [src] by \the [user]."))
- qdel(G)
- admin_attack_log(usr, GM, "Placed the victim into \the [src].", "Was placed into \the [src] by the attacker.", "stuffed \the [src] with")
- return
+ return TRUE
if(isrobot(user))
- return
- if(!I)
- return
+ return FALSE
if(!user.unEquip(I, src))
- return
-
- user.visible_message("\The [user] places \the [I] into \the [src].", "You place \the [I] into \the [src].")
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("[user] places [I] into [src]."),
+ SPAN_NOTICE("You place [I] into [src].")
+ )
update_icon()
+ return TRUE
/obj/machinery/disposal/MouseDrop_T(atom/movable/AM, mob/user)
if(!istype(AM)) // Could be dragging in a turf.
@@ -160,9 +174,10 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
if(M.buckled)
return
else if(istype(AM, /obj/item))
- attackby(AM, user)
+ use_tool(AM, user)
return
else if(!is_type_in_list(AM, allowed_objects))
+ USE_FEEDBACK_FAILURE("[AM] doesn't fit in [src].")
return
// Checks completed, start inserting
@@ -170,10 +185,10 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
var/old_loc = AM.loc
if(AM == user)
user.visible_message(SPAN_WARNING("[user] starts climbing into [src]."), \
- SPAN_NOTICE("You start climbing into [src]."))
+ SPAN_NOTICE("You start climbing into [src]."))
else
user.visible_message(SPAN_CLASS("[is_dangerous ? "warning" : "notice"]", "[user] starts stuffing [AM] into [src]."), \
- SPAN_NOTICE("You start stuffing [AM] into [src]."))
+ SPAN_NOTICE("You start stuffing [AM] into [src]."))
if(!do_after(user, 2 SECONDS, src, DO_PUBLIC_UNIQUE))
return
@@ -189,13 +204,13 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
// Messages and logging
if(AM == user)
user.visible_message(SPAN_DANGER("[user] climbs into [src]."), \
- SPAN_NOTICE("You climb into [src]."))
- admin_attack_log(user, null, "Stuffed themselves into \the [src].", null, "stuffed themselves into \the [src].")
+ SPAN_NOTICE("You climb into [src]."))
+ admin_attack_log(user, null, "Stuffed themselves into [src].", null, "stuffed themselves into [src].")
else
user.visible_message(SPAN_CLASS("[is_dangerous ? "danger" : "notice"]", "[user] stuffs [AM] into [src][is_dangerous ? "!" : "."]"), \
- SPAN_NOTICE("You stuff [AM] into [src]."))
+ SPAN_NOTICE("You stuff [AM] into [src]."))
if(ismob(M))
- admin_attack_log(user, M, "Placed the victim into \the [src].", "Was placed into \the [src] by the attacker.", "stuffed \the [src] with")
+ admin_attack_log(user, M, "Placed the victim into [src].", "Was placed into [src] by the attacker.", "stuffed [src] with")
if (M.client)
M.client.perspective = EYE_PERSPECTIVE
M.client.eye = src
@@ -206,111 +221,71 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
// attempt to move while inside
/obj/machinery/disposal/relaymove(mob/user as mob)
- if (user.incapacitated() || src.flushing)
+ if(user.incapacitated() || src.flushing)
return
- if (user.loc == src)
+ if(user.loc == src)
src.go_out(user)
return
// leave the disposal
/obj/machinery/disposal/proc/go_out(mob/user)
-
- if (user.client)
+ if(user.client)
user.client.eye = user.client.mob
user.client.perspective = MOB_PERSPECTIVE
user.forceMove(src.loc)
update_icon()
return
-/obj/machinery/disposal/DefaultTopicState()
- return GLOB.outside_state
-
-// human interact with machine
-/obj/machinery/disposal/physical_attack_hand(mob/user)
- // Clumsy folks can only flush it.
- if(!user.IsAdvancedToolUser(1))
- flush = !flush
- update_icon()
- return TRUE
-
/obj/machinery/disposal/interface_interact(mob/user)
- interact(user)
+ tgui_interact(user)
return TRUE
-// user interaction
-/obj/machinery/disposal/interact(mob/user)
+/obj/machinery/disposal/tgui_state(mob/user)
+ return GLOB.default_state
- src.add_fingerprint(user)
- if(MACHINE_IS_BROKEN(src))
- user.unset_machine()
- return
+/obj/machinery/disposal/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DisposalBin", name)
+ ui.open()
- var/ai = isAI(user)
- var/dat = "Waste Disposal UnitWaste Disposal Unit"
+/obj/machinery/disposal/tgui_data(mob/user)
+ var/list/data = list()
- if(!ai) // AI can't pull flush handle
- if(flush)
- dat += "Disposal handle: DisengageEngaged"
- else
- dat += "Disposal handle: DisengagedEngage"
-
- dat += " Eject contents"
-
- if(mode <= 0)
- dat += "Pump: OffOn "
- else if(mode == 1)
- dat += "Pump: OffOn (pressurizing) "
- else
- dat += "Pump: OffOn (idle) "
-
- var/per = 100* air_contents.return_pressure() / (SEND_PRESSURE)
+ data["isAI"] = isAI(user)
+ data["flushing"] = flush
+ data["mode"] = mode
+ data["pressure"] = round(clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100),1)
- dat += "Pressure: [round(per, 1)]% "
+ return data
+/obj/machinery/disposal/tgui_act(action, params)
+ if(..())
+ return
+ . = TRUE
- user.set_machine(src)
- show_browser(user, dat, "window=disposal;size=360x170")
- onclose(user, "disposal")
-
-// handle machine interaction
-
-/obj/machinery/disposal/CanUseTopic(mob/user, state, href_list)
- if(user.loc == src)
- to_chat(user, SPAN_WARNING("You cannot reach the controls from inside."))
- return STATUS_CLOSE
- if(isAI(user) && href_list && (href_list["handle"] || href_list["eject"]))
- return min(STATUS_UPDATE, ..())
- if(mode==-1 && href_list && !href_list["eject"]) // only allow ejecting if mode is -1
- to_chat(user, SPAN_WARNING("The disposal units power is disabled."))
- return min(STATUS_UPDATE, ..())
- if(flushing)
- return min(STATUS_UPDATE, ..())
- return ..()
-
-/obj/machinery/disposal/OnTopic(user, href_list)
- if(href_list["close"])
- close_browser(user, "window=disposal")
- return TOPIC_HANDLED
-
- if(href_list["pump"])
- if(text2num(href_list["pump"]))
- mode = 1
- else
- mode = 0
- update_icon()
- . = TOPIC_REFRESH
+ if(usr.loc == src)
+ to_chat(usr, SPAN_WARNING("Вы не можете дотянуться до панели управления, находясь внутри!"))
+ return
- else if(href_list["handle"])
- flush = text2num(href_list["handle"])
- update_icon()
- . = TOPIC_REFRESH
+ src.add_fingerprint(usr)
- else if(href_list["eject"])
- eject()
- . = TOPIC_REFRESH
+ switch(action)
+ if("eject")
+ eject()
+ return TRUE
+ if("mode")
+ if(mode >= 1)
+ mode = 0
+ else
+ mode = 1
+ update_icon()
+ return TRUE
+ if("flush")
+ flush = !flush
+ return TRUE
- if(. == TOPIC_REFRESH)
- interact(user)
+ return TRUE
/obj/machinery/disposal/verb/manual_eject()
set src in oview(1)
@@ -320,8 +295,8 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
if (!isliving(usr) || usr.incapacitated())
return
usr.visible_message(
- SPAN_NOTICE("\The [usr] ejects \the [src]'s contents'."),
- SPAN_NOTICE("You eject \the [initial(name)]'s contents."),
+ SPAN_NOTICE("[usr] ejects [src]'s contents'."),
+ SPAN_NOTICE("You eject [initial(name)]'s contents."),
)
eject()
add_fingerprint(usr)
@@ -335,7 +310,7 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
visible_message(SPAN_DANGER("Vomit spews out of the disposal unit!"))
playsound(loc, 'sound/effects/splat.ogg', 50, 1)
if(istype(src.loc, /turf/simulated))
- var/obj/effect/decal/cleanable/vomit/splat = new /obj/effect/decal/cleanable/vomit(src.loc)
+ var/obj/decal/cleanable/vomit/splat = new /obj/decal/cleanable/vomit(src.loc)
reagents.trans_to_obj(splat, reagents.total_volume)
splat.update_icon()
reagents.clear_reagents()
@@ -343,7 +318,7 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
// update the icon & overlays to reflect mode & status
/obj/machinery/disposal/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(MACHINE_IS_BROKEN(src))
mode = 0
flush = 0
@@ -351,7 +326,7 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
// flush handle
if(flush)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-handle")
+ AddOverlays(image(icon, "dispover-handle"))
// only handle is shown if no power
if(!is_powered() || mode == -1)
@@ -359,13 +334,13 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
// check for items/vomit in disposal - occupied light
if(length(contents) > LAZYLEN(component_parts) || reagents.total_volume)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-full")
+ AddOverlays(image(icon, "dispover-full"))
// charging and ready light
if(mode == 1)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-charge")
+ AddOverlays(image(icon, "dispover-charge"))
else if(mode == 2)
- overlays += image('icons/obj/pipes/disposal.dmi', "dispover-ready")
+ AddOverlays(image(icon, "dispover-ready"))
// timed process
// charge the gas reservoir and perform flush if ready
@@ -418,7 +393,7 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
flick("[icon_state]-flush", src)
var/wrapcheck = 0
- var/obj/structure/disposalholder/H = new() // virtual holder object which actually
+ var/obj/structure/disposalholder/H = new(src) // virtual holder object which actually
// travels through the pipes.
// handle vomit transportation
@@ -486,9 +461,9 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
return
if(prob(75))
I.forceMove(src)
- visible_message("\The [I] lands in \the [src].")
+ visible_message("[I] lands in [src].")
else
- visible_message("\The [I] bounces off of \the [src]'s rim!")
+ visible_message("[I] bounces off of [src]'s rim!")
return 0
else
return ..(mover, target, height, air_group)
@@ -496,13 +471,13 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
/obj/machinery/disposal/slam_into(mob/living/L)
L.forceMove(src)
L.Weaken(5)
- L.visible_message(SPAN_WARNING("\The [L] falls into \the [src]!"))
+ L.visible_message(SPAN_WARNING("[L] falls into [src]!"))
playsound(L, "punch", 25, 1, FALSE)
/obj/machinery/disposal_switch
name = "disposal switch"
desc = "A disposal control switch."
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "switch-off"
layer = ABOVE_OBJ_LAYER
var/on = 0
@@ -521,14 +496,14 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
junctions.Cut()
return ..()
-/obj/machinery/disposal_switch/attackby(obj/item/I, mob/user, params)
- if(isCrowbar(I))
- var/obj/item/disposal_switch_construct/C = new/obj/item/disposal_switch_construct(src.loc, id_tag)
- transfer_fingerprints_to(C)
- user.visible_message(SPAN_NOTICE("\The [user] deattaches \the [src]"))
- qdel(src)
- else
- . = ..()
+/obj/machinery/disposal_switch/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/item/disposal_switch_construct/C = new/obj/item/disposal_switch_construct(src.loc, id_tag)
+ transfer_fingerprints_to(C)
+ user.visible_message(SPAN_NOTICE("[user] deattaches [src]."))
+ qdel(src)
/obj/machinery/disposal_switch/interface_interact(mob/user)
if(!CanInteract(user, DefaultTopicState()))
@@ -543,11 +518,10 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
else
icon_state = "switch-off"
-
/obj/item/disposal_switch_construct
name = "disposal switch assembly"
desc = "A disposal control switch assembly."
- icon = 'icons/obj/recycling.dmi'
+ icon = 'icons/obj/machines/recycling.dmi'
icon_state = "switch-off"
w_class = ITEM_SIZE_LARGE
var/id_tag
@@ -558,20 +532,21 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
else
id_tag = "ds[sequential_id(/obj/item/disposal_switch_construct)]"
-/obj/item/disposal_switch_construct/afterattack(atom/A, mob/user, proximity)
- if(!proximity || !istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated() || !id_tag)
- return
+/obj/item/disposal_switch_construct/use_after(atom/A, mob/living/user, click_parameters)
+ if(!istype(A, /turf/simulated/floor) || istype(A, /area/shuttle) || user.incapacitated() || !id_tag)
+ return FALSE
var/found = 0
for(var/obj/structure/disposalpipe/diversion_junction/D in world)
if(D.id_tag == src.id_tag)
found = 1
break
if(!found)
- to_chat(user, "[icon2html(src, user)][SPAN_NOTICE("\The [src] is not linked to any junctions!")]")
- return
+ to_chat(user, "[icon2html(src, user)][SPAN_NOTICE("[src] is not linked to any junctions!")]")
+ return TRUE
var/obj/machinery/disposal_switch/NC = new/obj/machinery/disposal_switch(A, id_tag)
transfer_fingerprints_to(NC)
qdel(src)
+ return TRUE
// the disposal outlet machine
@@ -604,7 +579,7 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
visible_message(SPAN_DANGER("Vomit seeps out of the disposal outlet!"))
playsound(loc, 'sound/effects/splat.ogg', 50, 1)
if(istype(src.loc, /turf/simulated))
- var/obj/effect/decal/cleanable/vomit/splat = new /obj/effect/decal/cleanable/vomit(src.loc)
+ var/obj/decal/cleanable/vomit/splat = new /obj/decal/cleanable/vomit(src.loc)
H.reagents.trans_to_obj(splat, H.reagents.total_volume)
splat.update_icon()
@@ -625,39 +600,32 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
sleep(20) //wait until correct animation frame
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
-/obj/structure/disposaloutlet/attackby(obj/item/I, mob/user)
- if(!I || !user)
+/obj/structure/disposaloutlet/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
- src.add_fingerprint(user, 0, I)
- if(isScrewdriver(I))
- if(mode==0)
- mode=1
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You remove the screws around the power connection.")
- return
- else if(mode==1)
- mode=0
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You attach the screws around the power connection.")
- return
- else if(istype(I,/obj/item/weldingtool) && mode==1)
- var/obj/item/weldingtool/W = I
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- to_chat(user, "You start slicing the floorweld off the disposal outlet.")
- if(do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(!src || !W.isOn()) return
- to_chat(user, "You sliced the floorweld off the disposal outlet.")
- var/obj/structure/disposalconstruct/machine/outlet/C = new (loc, src)
- src.transfer_fingerprints_to(C)
- C.anchored = TRUE
- C.set_density(1)
- C.update()
- qdel(src)
- return
- else
- to_chat(user, "You need more welding fuel to complete this task.")
- return
+ mode = !mode
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, !mode)
+
+/obj/structure/disposaloutlet/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ // Welding Tool - Detach from floor
+ if(!mode)
+ balloon_alert(user, "нужно отключить!")
+ return
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_UNWELD_FROM_FLOOR(user)
+ if(!tool.use_as_tool(src, user, 2 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/structure/disposalconstruct/machine/outlet/outlet = new(loc, src)
+ transfer_fingerprints_to(outlet)
+ outlet.anchored = TRUE
+ outlet.set_density(TRUE)
+ outlet.update()
+ outlet.balloon_alert_to_viewers("отварено от пола!")
+ qdel(src)
+
/obj/structure/disposaloutlet/forceMove()//updates this when shuttle moves. So you can YEET things out the airlock
. = ..()
@@ -678,18 +646,18 @@ GLOBAL_LIST_EMPTY(diversion_junctions)
return
-/obj/effect/decal/cleanable/blood/gibs/pipe_eject(direction)
+/obj/decal/cleanable/blood/gibs/pipe_eject(direction)
var/list/dirs
if(direction)
dirs = list( direction, turn(direction, -45), turn(direction, 45))
else
dirs = GLOB.alldirs.Copy()
- addtimer(new Callback(src, .proc/streak, dirs), 0)
+ addtimer(CALLBACK(src, PROC_REF(streak), dirs), 0)
-/obj/effect/decal/cleanable/blood/gibs/robot/pipe_eject(direction)
+/obj/decal/cleanable/blood/gibs/robot/pipe_eject(direction)
var/list/dirs
if(direction)
dirs = list( direction, turn(direction, -45), turn(direction, 45))
else
dirs = GLOB.alldirs.Copy()
- addtimer(new Callback(src, .proc/streak, dirs), 0)
+ addtimer(CALLBACK(src, PROC_REF(streak), dirs), 0)
diff --git a/code/modules/recycling/disposalholder.dm b/code/modules/recycling/disposalholder.dm
index 00e6fe80742dc..00f90dd98f55c 100644
--- a/code/modules/recycling/disposalholder.dm
+++ b/code/modules/recycling/disposalholder.dm
@@ -5,7 +5,7 @@
// this allows the gas flushed to be tracked
/obj/structure/disposalholder
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
var/datum/gas_mixture/gas = null // gas used to flush, will appear at exit point
var/active = 0 // true if the holder is moving, otherwise inactive
dir = 0
@@ -37,6 +37,8 @@
// now everything inside the disposal gets put into the holder
// note AM since can contain mobs or objs
for(var/atom/movable/AM in stuff)
+ if (AM == src)
+ continue
AM.forceMove(src)
if(istype(AM, /obj/structure/bigDelivery) && !hasmob)
var/obj/structure/bigDelivery/T = AM
diff --git a/code/modules/recycling/disposalpipe.dm b/code/modules/recycling/disposalpipe.dm
index f0166c8426679..4f773b86261bc 100644
--- a/code/modules/recycling/disposalpipe.dm
+++ b/code/modules/recycling/disposalpipe.dm
@@ -63,6 +63,7 @@
//
/obj/structure/disposalpipe/proc/transfer(obj/structure/disposalholder/H)
var/nextdir = nextdir(H.dir)
+
H.set_dir(nextdir)
var/turf/T = H.nextloc()
var/obj/structure/disposalpipe/P = H.findpipe(T)
@@ -89,7 +90,7 @@
// hide called by levelupdate if turf intact status changes
// change visibility status and force update of icon
/obj/structure/disposalpipe/hide(intact)
- set_invisibility(intact ? 101: 0) // hide if floor is intact
+ set_invisibility(intact ? INVISIBILITY_ABSTRACT: 0) // hide if floor is intact
update_icon()
// expel the held objects into a turf
@@ -139,7 +140,7 @@
visible_message(SPAN_DANGER("Vomit spews out of the disposal pipe!"))
playsound(loc, 'sound/effects/splat.ogg', 50, 1)
if(istype(src.loc, /turf/simulated))
- var/obj/effect/decal/cleanable/vomit/splat = new /obj/effect/decal/cleanable/vomit(src.loc)
+ var/obj/decal/cleanable/vomit/splat = new /obj/decal/cleanable/vomit(src.loc)
H.reagents.trans_to_obj(splat, min(15, H.reagents.total_volume))
splat.update_icon()
@@ -171,7 +172,7 @@
var/obj/structure/disposalpipe/broken/P = new(src.loc)
P.set_dir(D)
- src.set_invisibility(101) // make invisible (since we won't delete the pipe immediately)
+ src.set_invisibility(INVISIBILITY_ABSTRACT) // make invisible (since we won't delete the pipe immediately)
var/obj/structure/disposalholder/H = locate() in src
if(H)
// holder was present
@@ -197,47 +198,47 @@
/obj/structure/disposalpipe/on_death()
broken(prob(0.5))
-//attack by item
-//weldingtool: unfasten and convert to obj/disposalconstruct
-
-/obj/structure/disposalpipe/attackby(obj/item/I, mob/user)
- var/turf/T = src.loc
- if(!T.is_plating())
- return // prevent interaction with T-scanner revealed pipes
- src.add_fingerprint(user, 0, I)
- if (user.a_intent == I_HURT)
- ..()
+/obj/structure/disposalpipe/can_anchor(obj/item/tool, mob/user, silent)
+ . = ..()
+ if (!.)
return
- if(istype(I, /obj/item/weldingtool))
- var/obj/item/weldingtool/W = I
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- // check if anything changed over 2 seconds
- var/turf/uloc = user.loc
- var/atom/wloc = W.loc
- to_chat(user, "Slicing the disposal pipe.")
- sleep(30)
- if(!W.isOn()) return
- if(user.loc == uloc && wloc == W.loc)
- welded()
- else
- to_chat(user, "You must stay still while welding the pipe.")
- else
- to_chat(user, "You need more welding fuel to cut the pipe.")
+ if (!anchored)
+ // Plating
+ var/turf/turf = get_turf(src)
+ if (!turf.is_plating())
+ if (!silent)
+ USE_FEEDBACK_FAILURE("You must remove the plating before you can secure [src].")
+ return FALSE
+
+ // Catwalks
+ var/obj/structure/catwalk/catwalk = locate() in get_turf(src)
+ if (catwalk)
+ if (catwalk.plated_tile && !catwalk.hatch_open)
+ if (!silent)
+ USE_FEEDBACK_FAILURE("[catwalk]'s hatch needs to be opened before you can secure [src].")
+ return FALSE
+ else if (!catwalk.plated_tile)
+ if (!silent)
+ USE_FEEDBACK_FAILURE("[catwalk] is blocking access to the floor.")
+ return FALSE
+
+
+/obj/structure/disposalpipe/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ // Welding Tool - Cut pipe
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_UNWELD_FROM_FLOOR(user)
+ if(!tool.use_as_tool(src, user, 3 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
return
-
- ..()
-
- // called when pipe is cut with welder
-/obj/structure/disposalpipe/proc/welded()
var/obj/structure/disposalconstruct/C = new (src.loc, src)
- src.transfer_fingerprints_to(C)
+ transfer_fingerprints_to(C)
C.set_density(0)
C.anchored = TRUE
C.update()
-
+ C.balloon_alert_to_viewers("отверено от пола!")
qdel(src)
// pipe is deleted
@@ -266,11 +267,6 @@
/obj/structure/disposalpipe/hides_under_flooring()
return 1
-// *** TEST verb
-//client/verb/dispstop()
-// for(var/obj/structure/disposalholder/H in world)
-// H.active = 0
-
// a straight or bent segment
/obj/structure/disposalpipe/segment
icon_state = "pipe-s" // Sadly this var stores state. "pipe-c" is corner. Should be changed, but requires huge map diff.
@@ -474,19 +470,26 @@
updatedesc()
update()
-/obj/structure/disposalpipe/tagger/attackby(obj/item/I, mob/user)
- if(..())
- return
- if(istype(I, /obj/item/device/destTagger))
- var/obj/item/device/destTagger/O = I
+/obj/structure/disposalpipe/tagger/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Destination Tagger - Change filter
+ if (istype(tool, /obj/item/device/destTagger))
+ var/obj/item/device/destTagger/tagger = tool
+ if (!tagger.currTag)
+ USE_FEEDBACK_FAILURE("[tagger] does not have a destination tag set.")
+ return TRUE
+ sort_type = tagger.currTag
+ playsound(src, 'sound/machines/twobeep.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("[user] reconfigures [src] with [tool]."),
+ SPAN_NOTICE("You set [src]'s filter to '[sort_type]' with [tool].")
+ )
+ updatename()
+ updatedesc()
+ return TRUE
+
+ return ..()
- if(O.currTag)// Tag set
- sort_tag = O.currTag
- playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
- to_chat(user, SPAN_NOTICE("Changed tag to '[sort_tag]'."))
- updatename()
- updatedesc()
/obj/structure/disposalpipe/tagger/transfer(obj/structure/disposalholder/H)
if(sort_tag)
@@ -549,16 +552,23 @@
linked = null
return ..()
-/obj/structure/disposalpipe/diversion_junction/attackby(obj/item/I, mob/user)
- if(..())
- return 1
- if(istype(I, /obj/item/disposal_switch_construct))
- var/obj/item/disposal_switch_construct/C = I
- if(C.id_tag)
- id_tag = C.id_tag
- playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
- user.visible_message(SPAN_NOTICE("\The [user] changes \the [src]'s tag."))
+/obj/structure/disposalpipe/diversion_junction/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Disposal Switch Assemply - Set ID tag
+ if (istype(tool, /obj/item/disposal_switch_construct))
+ var/obj/item/disposal_switch_construct/construct = tool
+ if (!construct.id_tag)
+ USE_FEEDBACK_FAILURE("[tool] doesn't have an ID tag set.")
+ return TRUE
+ id_tag = construct.id_tag
+ playsound(src, 'sound/machines/twobeep.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("[user] reconfigures [src] with [tool]."),
+ SPAN_NOTICE("You set [src]'s ID tag to '[id_tag]' with [tool]..")
+ )
+ return TRUE
+
+ return ..()
/obj/structure/disposalpipe/diversion_junction/nextdir(fromdir, sortTag)
@@ -642,19 +652,26 @@
updatedesc()
updatename()
-/obj/structure/disposalpipe/sortjunction/attackby(obj/item/I, mob/user)
- if(..())
- return
- if(istype(I, /obj/item/device/destTagger))
- var/obj/item/device/destTagger/O = I
+/obj/structure/disposalpipe/sortjunction/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Destination Tagger - Change filter
+ if (istype(tool, /obj/item/device/destTagger))
+ var/obj/item/device/destTagger/tagger = tool
+ if (!tagger.currTag)
+ USE_FEEDBACK_FAILURE("[tagger] does not have a destination tag set.")
+ return TRUE
+ sort_type = tagger.currTag
+ playsound(src, 'sound/machines/twobeep.ogg', 50, TRUE)
+ user.visible_message(
+ SPAN_NOTICE("[user] reconfigures [src] with [tool]."),
+ SPAN_NOTICE("You set [src]'s filter to '[sort_type]' with [tool].")
+ )
+ updatename()
+ updatedesc()
+ return TRUE
+
+ return ..()
- if(O.currTag)// Tag set
- sort_type = O.currTag
- playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
- to_chat(user, SPAN_NOTICE("Changed filter to '[sort_type]'."))
- updatename()
- updatedesc()
/obj/structure/disposalpipe/sortjunction/proc/divert_check(checkTag)
return sort_type == checkTag
@@ -755,39 +772,17 @@
update()
return
- // Override attackby so we disallow trunkremoval when somethings ontop
-/obj/structure/disposalpipe/trunk/attackby(obj/item/I, mob/user)
- //Disposal constructors
- var/obj/structure/disposalconstruct/C = locate() in src.loc
- if(C && C.anchored)
+/obj/structure/disposalpipe/trunk/can_use_item(obj/item/tool, mob/user, click_params)
+ . = ..()
+ if (!.)
return
- var/turf/T = src.loc
- if(!T.is_plating())
- return // prevent interaction with T-scanner revealed pipes
- src.add_fingerprint(user, 0, I)
- if(istype(I, /obj/item/weldingtool))
- var/obj/item/weldingtool/W = I
-
- if(W.remove_fuel(0,user))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- // check if anything changed over 2 seconds
- var/turf/uloc = user.loc
- var/atom/wloc = W.loc
- to_chat(user, "Slicing the disposal pipe.")
- sleep(30)
- if(!W.isOn()) return
- if(user.loc == uloc && wloc == W.loc)
- if(linked && istype(linked,/obj/machinery/disposal))
- var/obj/machinery/disposal/D = linked
- D.trunk = null
- welded()
- else
- to_chat(user, "You must stay still while welding the pipe.")
- else
- to_chat(user, "You need more welding fuel to cut the pipe.")
- return
+ var/obj/structure/disposalconstruct/construct = locate() in get_turf(src)
+ if (construct?.anchored)
+ USE_FEEDBACK_FAILURE("[construct] blocks access to [src].")
+ return FALSE
+
// would transfer to next pipe segment, but we are in a trunk
// if not entering from disposal bin,
@@ -831,8 +826,13 @@
update()
return
- // called when welded
- // for broken pipe, remove and turn into scrap
-
-/obj/structure/disposalpipe/broken/welded()
+// for broken pipe, remove and turn into scrap
+/obj/structure/disposalpipe/broken/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ // Welding Tool - Cut pipe
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_UNWELD_FROM_FLOOR(user)
+ if(!tool.use_as_tool(src, user, 3 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
qdel(src)
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index f5de2336b6bcf..b3cbcefd16360 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -1,8 +1,10 @@
+///Parent object and procs to /obj/structure/bigDelivery/package and /obj/structure/bigDelivery/mobpresent
/obj/structure/bigDelivery
desc = "A big wrapped package."
name = "large parcel"
- icon = 'icons/obj/storage.dmi'
- icon_state = "deliverycloset"
+ icon = 'icons/obj/parcels.dmi'
+ icon_state = "parcelcloset"
+ health_max = 5
var/obj/wrapped = null
density = TRUE
var/sortTag = null
@@ -12,102 +14,167 @@
var/label_y
var/label_x
var/tag_x
+ var/package_type = "parcel"
-/obj/structure/bigDelivery/attack_robot(mob/user as mob)
- unwrap(user)
+/obj/structure/bigDelivery/damage_health(damage, damage_type, damage_flags, severity, skip_can_damage_check)
+ . = ..()
+ // It's only paper. No protection for anything inside.
+ if (!length(contents))
+ return
+ var/content_damage = damage / length(contents)
+ for (var/atom/victim as anything in contents)
+ victim.damage_health(content_damage, damage_type, damage_flags, severity, skip_can_damage_check)
+
+
+/obj/structure/bigDelivery/on_death()
+ . = ..()
+ visible_message(
+ SPAN_WARNING("\The [src]'s wrapping falls away!")
+ )
+ if (wrapped)
+ wrapped.dropInto(loc)
+ wrapped = null
+ for (var/atom/movable/victim as anything in contents)
+ victim.dropInto(loc)
+ qdel_self()
/obj/structure/bigDelivery/attack_hand(mob/user as mob)
- unwrap(user)
+ if (user.a_intent != I_HURT)
+ to_chat(user, "You need a sharp tool to unwrap \the [src].")
+ return
+ return ..()
+
+/obj/structure/bigDelivery/attack_robot(mob/user)
+ if (user.a_intent != I_HURT)
+ to_chat(user, "You need a sharp tool to unwrap \the [src].")
+ return
+ return ..()
+
+/obj/structure/bigDelivery/AddLabel(label, mob/user)
+ ..()
+ if (!nameset)
+ nameset = TRUE
+ update_icon()
/obj/structure/bigDelivery/proc/unwrap(mob/user)
if(Adjacent(user))
// Destroy will drop our wrapped object on the turf, so let it.
qdel(src)
-/obj/structure/bigDelivery/attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/device/destTagger))
- var/obj/item/device/destTagger/O = W
- if(O.currTag)
- if(src.sortTag != O.currTag)
- to_chat(user, SPAN_NOTICE("You have labeled the destination as [O.currTag]."))
- if(!src.sortTag)
- src.sortTag = O.currTag
- update_icon()
- else
- src.sortTag = O.currTag
- playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 1)
- else
- to_chat(user, SPAN_WARNING("The package is already labeled for [O.currTag]."))
- else
- to_chat(user, SPAN_WARNING("You need to set a destination first!"))
+/obj/structure/bigDelivery/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Destination Tagger - Tag
+ if (istype(tool, /obj/item/device/destTagger))
+ var/obj/item/device/destTagger/tagger = tool
+ if (!tagger.currTag)
+ USE_FEEDBACK_FAILURE("\The [tool] does not have a tag set.")
+ return TRUE
+ if (tagger.currTag == sortTag)
+ USE_FEEDBACK_FAILURE("\The [src] is already tagged for [sortTag].")
+ return TRUE
+ sortTag = tagger.currTag
+ update_icon()
+ playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 1)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] tags \the [src] with \a [tool]."),
+ SPAN_NOTICE("You tag \the [src] as [sortTag] with \the [tool].")
+ )
+ return TRUE
+
+ // Pen - Label
+ if (istype(tool, /obj/item/pen))
+ var/input = alert(user, "What would you like to alter?", "[name] - Label", "Title", "Description", "Cancel")
+ if (input == "Cancel" || !user.use_sanity_check(src, tool))
+ return TRUE
+ if (input == "Title")
+ var/new_title = input(user, "What would you like to set the label to?", "[name] - Label Title") as null|text
+ new_title = sanitizeSafe(new_title, MAX_NAME_LEN)
+ if (!new_title || !user.use_sanity_check(src, tool))
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("\The [user] updates \the [src]'s label with \a [tool]."),
+ SPAN_NOTICE("You set \the [src]'s label title to '[new_title]' with \the [tool].")
+ )
+ SetName("[initial(name)] ([new_title])")
+ nameset = TRUE
+ update_icon()
+ else if (input == "Description")
+ var/new_desc = input(user, "What would you like to set the label to?", "[name] - Label Title") as null|text
+ new_desc = sanitizeSafe(new_desc, MAX_NAME_LEN)
+ if (!new_desc || !user.use_sanity_check(src, tool))
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("\The [user] updates \the [src]'s label with \a [tool]."),
+ SPAN_NOTICE("You set \the [src]'s label description to '[new_desc]' with \the [tool].")
+ )
+ examtext = new_desc
+ nameset = TRUE
+ update_icon()
+ return TRUE
+
+ if (is_sharp(tool))
+ user.visible_message(
+ SPAN_NOTICE("\The [user] cuts open \the [src] with \a [tool]."),
+ SPAN_NOTICE("You cut open \the [src] with \the [tool].")
+ )
+ unwrap(user)
+ return TRUE
- else if(istype(W, /obj/item/pen))
- switch(alert("What would you like to alter?",,"Title","Description", "Cancel"))
- if("Title")
- var/str = sanitizeSafe(input(usr,"Label text?","Set label",""), MAX_NAME_LEN)
- if(!str || !length(str))
- to_chat(usr, SPAN_WARNING(" Invalid text."))
- return
- user.visible_message("\The [user] titles \the [src] with \a [W], marking down: \"[str]\"",\
- SPAN_NOTICE("You title \the [src]: \"[str]\""),\
- "You hear someone scribbling a note.")
- SetName("[name] ([str])")
- if(!examtext && !nameset)
- nameset = 1
- update_icon()
- else
- nameset = 1
- if("Description")
- var/str = sanitize(input(usr,"Label text?","Set label",""))
- if(!str || !length(str))
- to_chat(usr, SPAN_WARNING("Invalid text."))
- return
- if(!examtext && !nameset)
- examtext = str
- update_icon()
- else
- examtext = str
- user.visible_message("\The [user] labels \the [src] with \a [W], scribbling down: \"[examtext]\"",\
- SPAN_NOTICE("You label \the [src]: \"[examtext]\""),\
- "You hear someone scribbling a note.")
- return
+ return ..()
+
+/obj/structure/bigDelivery/examine(mob/user, distance)
+ . = ..()
+ if(distance <= 4)
+ if(sortTag)
+ . += SPAN_NOTICE("It is labeled \"[sortTag]\"")
+ if(examtext)
+ . += SPAN_NOTICE("It has a note attached which reads, \"[examtext]\"")
+
+///Procs exclusive to the package subtype.
+/obj/structure/bigDelivery/package/Initialize(mapload, obj/structure/closet/parcel, wrap_type)
+ ..(mapload)
+ if (!parcel || !istype(parcel) || !wrap_type)
+ return INITIALIZE_HINT_QDEL
+
+ wrapped = parcel
+ wrapped.forceMove(src)
+ package_type = wrap_type
+ SetName("large [package_type]")
+ desc = name
+ update_icon()
+
+/obj/structure/bigDelivery/package/on_update_icon()
+ ClearOverlays()
+ if (istype(wrapped, /obj/structure/closet/crate))
+ icon_state = "[package_type]crate"
+ else
+ icon_state = "[package_type]closet"
-/obj/structure/bigDelivery/on_update_icon()
- overlays.Cut()
if(nameset || examtext)
- var/image/I = new/image('icons/obj/storage.dmi',"delivery_label")
- if(icon_state == "deliverycloset")
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_label")
+ if (icon_state == "[package_type]closet")
I.pixel_x = 2
- if(label_y == null)
+ if(isnull(label_y))
label_y = rand(-6, 11)
I.pixel_y = label_y
- else if(icon_state == "deliverycrate")
- if(label_x == null)
+ else if (icon_state == "[package_type]crate")
+ if(isnull(label_x))
label_x = rand(-8, 6)
I.pixel_x = label_x
I.pixel_y = -3
- overlays += I
+ AddOverlays(I)
if(src.sortTag)
- var/image/I = new/image('icons/obj/storage.dmi',"delivery_tag")
- if(icon_state == "deliverycloset")
- if(tag_x == null)
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_tag")
+ if (icon_state == "[package_type]closet")
+ if(isnull(tag_x))
tag_x = rand(-2, 3)
I.pixel_x = tag_x
I.pixel_y = 9
- else if(icon_state == "deliverycrate")
- if(tag_x == null)
+ else if (icon_state == "[package_type]crate")
+ if(isnull(tag_x))
tag_x = rand(-8, 6)
I.pixel_x = tag_x
I.pixel_y = -3
- overlays += I
-
-/obj/structure/bigDelivery/examine(mob/user, distance)
- . = ..()
- if(distance <= 4)
- if(sortTag)
- to_chat(user, SPAN_NOTICE("It is labeled \"[sortTag]\""))
- if(examtext)
- to_chat(user, SPAN_NOTICE("It has a note attached which reads, \"[examtext]\""))
+ AddOverlays(I)
/obj/structure/bigDelivery/Destroy()
if(wrapped) //sometimes items can disappear. For example, bombs. --rastaf0
@@ -119,224 +186,285 @@
var/turf/T = get_turf(src)
for(var/atom/movable/AM in contents)
AM.forceMove(T)
+ playsound(loc, 'sound/effects/wrap_tear.ogg', 65, 1)
return ..()
+///Procs exclusive to mopresent subtype.
+/obj/structure/bigDelivery/mobpresent
+ name = "strange gift"
+ desc = "It's a ... gift?"
+ icon_state = "strangegift"
+ breakout_time = 30 SECONDS
+
+/obj/structure/bigDelivery/mobpresent/Initialize(mapload, mob/living/carbon/human/parcel, wrap_type)
+ ..(mapload)
+ if (!parcel || !istype(parcel) || !wrap_type)
+ return INITIALIZE_HINT_QDEL
+
+ wrapped = parcel
+ wrapped.forceMove(src)
+ package_type = wrap_type
+ if (parcel.client)
+ parcel.client.perspective = EYE_PERSPECTIVE
+ parcel.client.eye = src
+
+ SetName("strange [package_type]")
+ desc = "It's a ... [package_type]?"
+ update_icon()
+
+/obj/structure/bigDelivery/mobpresent/on_update_icon()
+ ClearOverlays()
+ icon_state = "strange[package_type]"
+ if (nameset || examtext)
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_label")
+ I.pixel_x = 2
+ if (isnull(label_y))
+ label_y = rand (2,5)
+ I.pixel_y = label_y
+ AddOverlays(I)
+
+ if (sortTag)
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_tag")
+ if (isnull(tag_x))
+ tag_x = 0
+ I.pixel_x = tag_x
+ I.pixel_y = 0
+ AddOverlays(I)
+
+/obj/structure/bigDelivery/mobpresent/relaymove(mob/user)
+ if (user.stat)
+ return
+ to_chat(user, SPAN_WARNING("You can't move!"))
+
+/obj/structure/bigDelivery/mobpresent/on_death()
+ . = ..()
+ for (var/mob/M in src) //Should only be one but whatever.
+ if (M.client)
+ M.client.eye = M.client.mob
+ M.client.perspective = MOB_PERSPECTIVE
+
+/obj/structure/bigDelivery/mobpresent/mob_breakout(mob/living/escapee)
+ . = ..()
+ if (!breakout_time)
+ breakout_time = 30 SECONDS
+ if (breakout)
+ return FALSE
+
+ . = TRUE
+ escapee.setClickCooldown(100)
+
+ to_chat(escapee, SPAN_WARNING("You start squirming inside \the [src] and start weakening the wrapping paper. (this will take about [breakout_time/(1 SECOND)] second\s)"))
+ visible_message(SPAN_DANGER("\The [src] begins to shake violently!"))
+ shake_animation()
+
+ var/stages = 3
+ breakout = TRUE
+ for (var/i = 1 to stages)
+ if (do_after(escapee, breakout_time*(1/stages), do_flags = DO_DEFAULT | DO_USER_UNIQUE_ACT, incapacitation_flags = INCAPACITATION_DEFAULT & ~INCAPACITATION_RESTRAINED))
+ to_chat(escapee, SPAN_WARNING("You try to slip free of \the [src] ([i*100/stages]% done)."))
+ else
+ to_chat(escapee, SPAN_WARNING("You stop trying to slip free of \the [src]."))
+ breakout = FALSE
+ return
+ shake_animation()
+
+ //Well then break it!
+ breakout = FALSE
+ to_chat(escapee, SPAN_WARNING("You successfully break out!"))
+ visible_message(SPAN_DANGER("\The [escapee] successfully broke out of \the [src]!"))
+ unwrap()
+
/obj/item/smallDelivery
- desc = "A small wrapped package."
name = "small parcel"
- icon = 'icons/obj/storage.dmi'
- icon_state = "deliverycrate3"
+ desc = "A small parcel."
+ icon = 'icons/obj/parcels.dmi'
+ icon_state = "parcel3"
+ health_max = 5
var/obj/item/wrapped = null
var/sortTag = null
var/examtext = null
var/nameset = 0
var/tag_x
+ var/package_type = "parcel"
+
+/obj/item/smallDelivery/Initialize(mapload, obj/item/parcel, wrap_type)
+ . = ..()
+ if (!parcel || !isitem(parcel) || !wrap_type)
+ return INITIALIZE_HINT_QDEL
+
+ wrapped = parcel
+ wrapped.forceMove(src)
+ package_type = wrap_type
+ w_class = parcel.w_class
+ switch (w_class)
+ if (ITEM_SIZE_TINY) SetName("tiny [package_type]")
+ if (ITEM_SIZE_SMALL) SetName("small [package_type]")
+ if (ITEM_SIZE_NORMAL) SetName("normal-sized [package_type]")
+ if (ITEM_SIZE_LARGE) SetName("large [package_type]")
+ if (ITEM_SIZE_HUGE) SetName("huge [package_type]")
+ desc = "A [name]."
+ update_icon()
+
+/obj/item/smallDelivery/Destroy()
+ playsound(loc, 'sound/effects/wrap_tear.ogg', 65, 1)
+ QDEL_NULL(wrapped)
+ return ..()
+
+
+/obj/item/smallDelivery/damage_health(damage, damage_type, damage_flags, severity, skip_can_damage_check)
+ // It's only paper. No protection for anything inside.
+ for (var/atom/victim as anything in contents)
+ victim.damage_health(damage, damage_type, damage_flags, severity, skip_can_damage_check)
+ return ..()
+
+
+/obj/item/smallDelivery/on_death()
+ . = ..()
+ visible_message(
+ SPAN_WARNING("\The [src]'s wrapping falls away!")
+ )
+ if (wrapped)
+ wrapped.dropInto(loc)
+ wrapped = null
+ for (var/atom/movable/victim as anything in contents)
+ victim.dropInto(loc)
+ qdel_self()
+
/obj/item/smallDelivery/proc/unwrap(mob/user)
- if (!length(contents) || !Adjacent(user))
+ if (!Adjacent(user))
+ return
+ if (!length(contents) || !wrapped)
+ to_chat(user, SPAN_NOTICE("\The [src] was empty!"))
+ qdel_self()
return
- user.put_in_hands(wrapped)
- // Take out any other items that might be in the package
- for(var/obj/item/I in src)
- user.put_in_hands(I)
+ if (user.isEquipped(src))
+ user.drop_from_inventory(src)
+ user.put_in_hands(wrapped)
+ for (var/obj/item/present in src)
+ user.put_in_hands(present)
+ else
+ wrapped.dropInto(loc)
+ for (var/obj/item/present in src)
+ present.dropInto(loc)
+ wrapped = null
qdel(src)
/obj/item/smallDelivery/attack_robot(mob/user as mob)
- unwrap(user)
+ to_chat(user, "You need a sharp tool to unwrap \the [src].")
/obj/item/smallDelivery/attack_self(mob/user as mob)
- unwrap(user)
-
-/obj/item/smallDelivery/attackby(obj/item/W as obj, mob/user as mob)
- if(istype(W, /obj/item/device/destTagger))
- var/obj/item/device/destTagger/O = W
- if(O.currTag)
- if(src.sortTag != O.currTag)
- to_chat(user, SPAN_NOTICE("You have labeled the destination as [O.currTag]."))
- if(!src.sortTag)
- src.sortTag = O.currTag
+ to_chat(user, "You need a sharp tool to unwrap \the [src].")
+
+/obj/item/smallDelivery/use_tool(obj/item/tool, mob/living/user, list/click_params)
+ if (is_sharp(tool))
+ user.visible_message(
+ SPAN_NOTICE("\The [user] cuts open \the [src] with \a [tool]."),
+ SPAN_NOTICE("You cut open \the [src] with \the [tool].")
+ )
+ unwrap(user)
+ return TRUE
+
+ if (istype(tool, /obj/item/device/destTagger))
+ var/obj/item/device/destTagger/tagger = tool
+ if (tagger.currTag)
+ if (sortTag != tagger.currTag)
+ to_chat(user, SPAN_NOTICE("You have labeled the destination as [tagger.currTag]."))
+ if (!sortTag)
+ sortTag = tagger.currTag
update_icon()
else
- src.sortTag = O.currTag
- playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 1)
+ sortTag = tagger.currTag
+ playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
+ return TRUE
else
- to_chat(user, SPAN_WARNING("The package is already labeled for [O.currTag]."))
+ to_chat(user, SPAN_WARNING("The package is already labeled for [tagger.currTag]."))
+ return TRUE
else
to_chat(user, SPAN_WARNING("You need to set a destination first!"))
+ return TRUE
- else if(istype(W, /obj/item/pen))
- switch(alert("What would you like to alter?",,"Title","Description", "Cancel"))
- if("Title")
- var/str = sanitizeSafe(input(usr,"Label text?","Set label",""), MAX_NAME_LEN)
- if(!str || !length(str))
+ if (istype(tool, /obj/item/pen))
+ switch (alert("What would you like to alter?",,"Title","Description", "Cancel"))
+ if ("Title")
+ var/str = sanitizeSafe(input(user,"Label text?","Set label",""), MAX_NAME_LEN)
+ if (!str || !length(str))
to_chat(usr, SPAN_WARNING(" Invalid text."))
- return
- user.visible_message("\The [user] titles \the [src] with \a [W], marking down: \"[str]\"",\
+ return TRUE
+ user.visible_message("\The [user] titles \the [src] with \a [tool], marking down: \"[str]\"",\
SPAN_NOTICE("You title \the [src]: \"[str]\""),\
"You hear someone scribbling a note.")
SetName("[name] ([str])")
- if(!examtext && !nameset)
+ if (!examtext && !nameset)
nameset = 1
update_icon()
else
nameset = 1
+ return TRUE
- if("Description")
- var/str = sanitize(input(usr,"Label text?","Set label",""))
+ if ("Description")
+ var/str = sanitize(input(user,"Label text?","Set label",""))
if(!str || !length(str))
- to_chat(usr, SPAN_WARNING("Invalid text."))
- return
- if(!examtext && !nameset)
+ to_chat(user, SPAN_WARNING("Invalid text."))
+ return TRUE
+ if (!examtext && !nameset)
examtext = str
update_icon()
else
examtext = str
- user.visible_message("\The [user] labels \the [src] with \a [W], scribbling down: \"[examtext]\"",\
+ user.visible_message("\The [user] labels \the [src] with \a [tool], scribbling down: \"[examtext]\"",\
SPAN_NOTICE("You label \the [src]: \"[examtext]\""),\
"You hear someone scribbling a note.")
- return
+ return TRUE
+ return ..()
/obj/item/smallDelivery/on_update_icon()
- overlays.Cut()
- if((nameset || examtext) && icon_state != "deliverycrate1")
- var/image/I = new/image('icons/obj/storage.dmi',"delivery_label")
- if(icon_state == "deliverycrate5")
+ ClearOverlays()
+ icon_state = "[package_type][w_class]"
+
+ if ((nameset || examtext) && w_class > ITEM_SIZE_TINY)
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_label")
+ if(w_class == ITEM_SIZE_HUGE)
I.pixel_y = -1
- overlays += I
- if(src.sortTag)
- var/image/I = new/image('icons/obj/storage.dmi',"delivery_tag")
- switch(icon_state)
- if("deliverycrate1")
+ AddOverlays(I)
+ if (sortTag)
+ var/image/I = new/image('icons/obj/parcels.dmi',"delivery_tag")
+ switch(w_class)
+ if(ITEM_SIZE_TINY)
I.pixel_y = -5
- if("deliverycrate2")
+ if(ITEM_SIZE_SMALL)
I.pixel_y = -2
- if("deliverycrate3")
+ if(ITEM_SIZE_NORMAL)
I.pixel_y = 0
- if("deliverycrate4")
- if(tag_x == null)
+ if(ITEM_SIZE_LARGE)
+ if(isnull(tag_x))
tag_x = rand(0,5)
I.pixel_x = tag_x
I.pixel_y = 3
- if("deliverycrate5")
+ if(ITEM_SIZE_HUGE)
I.pixel_y = -3
- overlays += I
+ AddOverlays(I)
+
+/obj/item/smallDelivery/AddLabel(label, mob/user)
+ ..()
+ if (!nameset)
+ nameset = TRUE
+ update_icon()
/obj/item/smallDelivery/examine(mob/user, distance)
. = ..()
if(distance <= 4)
if(sortTag)
- to_chat(user, SPAN_NOTICE("It is labeled \"[sortTag]\""))
+ . += SPAN_NOTICE("It is labeled \"[sortTag]\"")
if(examtext)
- to_chat(user, SPAN_NOTICE("It has a note attached which reads, \"[examtext]\""))
-
-/obj/item/stack/package_wrap
- name = "package wrapper"
- desc = "Heavy duty brown paper used to wrap packages to protect them during shipping."
- singular_name = "sheet"
- max_amount = 25
- icon = 'icons/obj/items.dmi'
- icon_state = "deliveryPaper"
- w_class = ITEM_SIZE_NORMAL
-
-/obj/item/stack/package_wrap/twenty_five
- amount = 25
-
-
-/obj/item/c_tube
- name = "cardboard tube"
- desc = "A tube... of cardboard."
- icon = 'icons/obj/items.dmi'
- icon_state = "c_tube"
- throwforce = 1
- w_class = ITEM_SIZE_SMALL
- throw_speed = 4
- throw_range = 5
-
-/obj/item/stack/package_wrap/afterattack(obj/target as obj, mob/user as mob, proximity)
- if(!proximity) return
- if(!istype(target)) //this really shouldn't be necessary (but it is). -Pete
- return
- if(istype(target, /obj/item/smallDelivery) || istype(target,/obj/structure/bigDelivery) \
- || istype(target, /obj/item/gift) || istype(target, /obj/item/evidencebag))
- return
- if(target.anchored)
- return
- if(target in user)
- return
- if(user in target) //no wrapping closets that you are inside - it's not physically possible
- return
-
- if (istype(target, /obj/item) && !(istype(target, /obj/item/storage) && !istype(target,/obj/item/storage/box)))
- var/obj/item/O = target
- if (src.get_amount() >= 1)
- var/obj/item/smallDelivery/P = new /obj/item/smallDelivery(get_turf(O.loc)) //Aaannd wrap it up!
- if(!istype(O.loc, /turf))
- if(user.client)
- user.client.screen -= O
- P.wrapped = O
- O.forceMove(P)
- P.w_class = O.w_class
- var/i = round(O.w_class)
- if(i in list(1,2,3,4,5))
- P.icon_state = "deliverycrate[i]"
- switch(i)
- if(1) P.SetName("tiny parcel")
- if(3) P.SetName("normal-sized parcel")
- if(4) P.SetName("large parcel")
- if(5) P.SetName("huge parcel")
- if(i < 1)
- P.icon_state = "deliverycrate1"
- P.SetName("tiny parcel")
- if(i > 5)
- P.icon_state = "deliverycrate5"
- P.SetName("huge parcel")
- P.add_fingerprint(usr)
- O.add_fingerprint(usr)
- src.add_fingerprint(usr)
- src.use(1)
- user.visible_message("\The [user] wraps \a [target] with \a [src].",\
- SPAN_NOTICE("You wrap \the [target], leaving [src.get_amount()] units of paper on \the [src]."),\
- "You hear someone taping paper around a small object.")
- else
- // Should be possible only to see this as a borg?
- to_chat(user, SPAN_WARNING("The synthesizer is out of paper."))
- else if (istype(target, /obj/structure/closet/crate))
- var/obj/structure/closet/crate/O = target
- if (src.get_amount() >= 3 && !O.opened)
- var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc))
- P.icon_state = "deliverycrate"
- P.wrapped = O
- O.forceMove(P)
- src.use(3)
- user.visible_message("\The [user] wraps \a [target] with \a [src].",\
- SPAN_NOTICE("You wrap \the [target], leaving [src.get_amount()] units of paper on \the [src]."),\
- "You hear someone taping paper around a large object.")
- else if(src.get_amount() < 3)
- to_chat(user, SPAN_WARNING("You need more paper."))
- else if (istype (target, /obj/structure/closet))
- var/obj/structure/closet/O = target
- if (src.get_amount() >= 3 && !O.opened)
- var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc))
- P.wrapped = O
- O.welded = 1
- O.forceMove(P)
- src.use(3)
- user.visible_message("\The [user] wraps \a [target] with \a [src].",\
- SPAN_NOTICE("You wrap \the [target], leaving [src.get_amount()] units of paper on \the [src]."),\
- "You hear someone taping paper around a large object.")
- else if(src.get_amount() < 3)
- to_chat(user, SPAN_WARNING("You need more paper."))
- else
- to_chat(user, SPAN_NOTICE("The object you are trying to wrap is unsuitable for the sorting machinery!"))
-
- return
+ . += SPAN_NOTICE("It has a note attached which reads, \"[examtext]\"")
/obj/item/device/destTagger
name = "destination tagger"
desc = "Used to set the destination of properly wrapped packages."
- icon = 'icons/obj/destination_tagger.dmi'
+ icon = 'icons/obj/tools/destination_tagger.dmi'
icon_state = "dest_tagger"
var/currTag = 0
w_class = ITEM_SIZE_SMALL
@@ -429,6 +557,7 @@
else if(istype(AM, /mob))
var/mob/M = AM
M.forceMove(src)
+
src.flush()
/obj/machinery/disposal/deliveryChute/flush()
@@ -464,48 +593,28 @@
update_icon()
return
-/obj/machinery/disposal/deliveryChute/attackby(obj/item/I, mob/user)
- if(!I || !user)
+/obj/machinery/disposal/deliveryChute/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
+ c_mode = !c_mode
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, c_mode)
- if(isScrewdriver(I))
- if(c_mode==0)
- c_mode=1
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You remove the screws around the power connection.")
- return
- else if(c_mode==1)
- c_mode=0
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
- to_chat(user, "You attach the screws around the power connection.")
- return
- else if(isWelder(I) && c_mode==1)
- var/obj/item/weldingtool/W = I
- if(W.remove_fuel(1,user))
- to_chat(user, "You start slicing the floorweld off the delivery chute.")
- if(do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- playsound(src.loc, 'sound/items/Welder2.ogg', 100, 1)
- if(!src || !W.isOn()) return
- to_chat(user, "You sliced the floorweld off the delivery chute.")
- var/obj/structure/disposalconstruct/C = new (loc, src)
- C.update()
- qdel(src)
- return
- else
- to_chat(user, "You need more welding fuel to complete this task.")
- return
+/obj/machinery/disposal/deliveryChute/welder_act(mob/living/user, obj/item/tool)
+ if(!c_mode)
+ return
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_UNWELD_FROM_FLOOR(user)
+ if(!tool.use_as_tool(src, user, 2 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/structure/disposalconstruct/C = new (loc, src)
+ C.update()
+ C.balloon_alert_to_viewers("отварено от пола!")
+ qdel(src)
/obj/machinery/disposal/deliveryChute/Destroy()
if(trunk)
trunk.linked = null
..()
-
-/obj/item/stack/package_wrap/cyborg
- name = "package wrapper synthesizer"
- icon = 'icons/obj/items.dmi'
- icon_state = "deliveryPaper"
- gender = NEUTER
- matter = null
- uses_charge = 1
- charge_costs = list(1)
- stacktype = /obj/item/stack/package_wrap
diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm
index f68163a824769..e4582c1b0987b 100644
--- a/code/modules/research/circuitprinter.dm
+++ b/code/modules/research/circuitprinter.dm
@@ -8,6 +8,7 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid).
name = "circuit imprinter"
desc = "Accessed by a connected core fabricator console, it produces circuits from various materials and sulphuric acid."
icon_state = "circuit_imprinter"
+ icon = 'icons/obj/machines/research/circuit_imprinter.dmi'
atom_flags = ATOM_FLAG_OPEN_CONTAINER
base_type = /obj/machinery/r_n_d/circuit_imprinter
construct_state = /singleton/machine_construction/default/panel_closed
@@ -75,12 +76,14 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid).
..()
/obj/machinery/r_n_d/circuit_imprinter/on_update_icon()
+ ClearOverlays()
if(panel_open)
- icon_state = "circuit_imprinter_t"
- else if(busy)
- icon_state = "circuit_imprinter_ani"
- else
- icon_state = "circuit_imprinter"
+ AddOverlays("[icon_state]_panel")
+ if(is_powered())
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights"))
+ AddOverlays("[icon_state]_lights")
+ if(busy)
+ AddOverlays("[icon_state]_working")
/obj/machinery/r_n_d/circuit_imprinter/state_transition(singleton/machine_construction/default/new_state)
. = ..()
@@ -96,31 +99,32 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid).
return SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation.")
return ..()
-/obj/machinery/r_n_d/circuit_imprinter/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/r_n_d/circuit_imprinter/use_tool(obj/item/O, mob/living/user, list/click_params)
if(busy)
to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation."))
- return 1
- if(component_attackby(O, user))
return TRUE
+ if ((. = ..()))
+ return
if(panel_open)
to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened."))
- return 1
+ return TRUE
if(!linked_console)
to_chat(user, "\The [src] must be linked to an R&D console first.")
- return 1
+ return TRUE
if(O.is_open_container())
- return 0
+ return FALSE
if(is_robot_module(O))
- return 0
+ return FALSE
if(!istype(O, /obj/item/stack/material))
- to_chat(user, SPAN_NOTICE("You cannot insert this item into \the [src]!"))
- return 0
+ to_chat(user, SPAN_WARNING("You cannot insert this item into \the [src]!"))
+ return TRUE
if(inoperable())
- return 1
+ to_chat(user, SPAN_WARNING("\The [src] is not working properly."))
+ return TRUE
if(TotalMaterials() + SHEET_MATERIAL_AMOUNT > max_material_storage)
- to_chat(user, SPAN_NOTICE("\The [src]'s material bin is full. Please remove material before adding more."))
- return 1
+ to_chat(user, SPAN_WARNING("\The [src]'s material bin is full. Please remove material before adding more."))
+ return TRUE
var/obj/item/stack/material/stack = O
var/amount = min(stack.get_amount(), round((max_material_storage - TotalMaterials()) / SHEET_MATERIAL_AMOUNT))
@@ -136,6 +140,7 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid).
materials[t] += amount * SHEET_MATERIAL_AMOUNT
busy = 0
updateUsrDialog()
+ return TRUE
/obj/machinery/r_n_d/circuit_imprinter/proc/addToQueue(datum/design/D)
queue += D
@@ -148,7 +153,7 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid).
/obj/machinery/r_n_d/circuit_imprinter/proc/canBuild(datum/design/D)
for(var/M in D.materials)
- if(materials[M] <= D.materials[M] * mat_efficiency)
+ if(materials[M] < D.materials[M] * mat_efficiency)
return 0
for(var/C in D.chemicals)
if(!reagents.has_reagent(C, D.chemicals[C] * mat_efficiency))
diff --git a/code/modules/research/designs/_designs.dm b/code/modules/research/designs/_designs.dm
index b98fddd8471df..abac1069efb72 100644
--- a/code/modules/research/designs/_designs.dm
+++ b/code/modules/research/designs/_designs.dm
@@ -41,19 +41,16 @@ other types of metals and chemistry for reagents).
/datum/design/proc/AssembleDesignInfo()
AssembleDesignName()
AssembleDesignDesc()
- return
/datum/design/proc/AssembleDesignName()
if(!name && build_path) //Get name from build path if posible
var/atom/movable/A = build_path
name = initial(A.name)
item_name = name
- return
/datum/design/proc/AssembleDesignDesc()
if(!desc) //Try to make up a nice description if we don't have one
desc = "Allows for the construction of \a [item_name]."
- return
//Returns a new instance of the item for this design
//This is to allow additional initialization to be performed, including possibly additional contructor arguments.
@@ -67,6 +64,7 @@ other types of metals and chemistry for reagents).
GLOBAL_LIST_INIT(build_path_to_design_datum_path, populate_design_datum_index())
/proc/populate_design_datum_index()
+ RETURN_TYPE(/list)
. = list()
for(var/path in typesof(/datum/design))
var/datum/design/fake_design = path
diff --git a/code/modules/research/designs/designs_circuits.dm b/code/modules/research/designs/designs_circuits.dm
index 786d511d6fd5b..b3e92d7af2a59 100644
--- a/code/modules/research/designs/designs_circuits.dm
+++ b/code/modules/research/designs/designs_circuits.dm
@@ -275,6 +275,13 @@
build_path = /obj/item/stock_parts/circuitboard/anomaly_container
sort_string = "HABAL"
+/datum/design/circuit/stasis_cage
+ name = "stasis cage"
+ id = "stasis cage"
+ req_tech = list(TECH_BLUESPACE = 3, TECH_ENGINEERING = 4, TECH_BIO = 3)
+ build_path = /obj/item/stock_parts/circuitboard/stasis_cage
+ sort_string = "HABAM"
+
/datum/design/circuit/rdservercontrol
name = "R&D server control console"
id = "rdservercontrol"
@@ -448,16 +455,9 @@
name = "chemical heating system"
id = "chemheater"
req_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 1)
- build_path = /obj/item/stock_parts/circuitboard/reagent_heater
+ build_path = /obj/item/stock_parts/circuitboard/reagent_temp
sort_string = "JCAAE"
-/datum/design/circuit/reagent_cooler
- name = "chemical cooling system"
- id = "chemcooler"
- req_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 1)
- build_path = /obj/item/stock_parts/circuitboard/reagent_heater/cooler
- sort_string = "JCAAF"
-
/datum/design/circuit/atmos_control
name = "atmospherics control console"
id = "atmos_control"
@@ -810,6 +810,14 @@
build_path = /obj/item/stock_parts/circuitboard/engine/ion
sort_string = "XAAAB"
+// [SIERRA-ADD] - Fix unit test failing
+/datum/design/circuit/sublimator
+ name = "reagent sublimator"
+ id = "reagsubl"
+ req_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 1)
+ build_path = /obj/item/stock_parts/circuitboard/sublimator
+ sort_string = "XAAACA"
+// [/SIERRA-ADD]
/datum/design/circuit/sublimator/sauna
name = "sauna heater system"
id = "sauna"
@@ -840,13 +848,6 @@
build_path = /obj/item/stock_parts/circuitboard/tele_beacon
sort_string = "XAAAF"
-/datum/design/circuit/bluespacedrive
- name = "Naophoros-pattern bluespace puncturer"
- id = "bluespacedrive"
- req_tech = list(TECH_BLUESPACE = 12, TECH_POWER = 12, TECH_ENGINEERING = 12) // You are not supposed to get this
- build_path = /obj/item/stock_parts/circuitboard/bluespacedrive
- sort_string = "XAAAG"
-
/datum/design/circuit/shipsensors
name = "Broad-band sensor suite"
id = "shipsensors"
@@ -867,3 +868,10 @@
req_tech = list(TECH_ENGINEERING = 1)
build_path = /obj/item/stock_parts/circuitboard/shipmap
sort_string = "SAAAT"
+
+/datum/design/circuit/drone_pad
+ name = "transport drone landing pad"
+ id = "drone_pad"
+ req_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 2)
+ build_path = /obj/item/stock_parts/circuitboard/drone_pad
+ sort_string = "TDRP"
diff --git a/code/modules/research/designs/designs_mechfab.dm b/code/modules/research/designs/designs_mechfab.dm
index 37d3916ccb161..1f7e5ed563682 100644
--- a/code/modules/research/designs/designs_mechfab.dm
+++ b/code/modules/research/designs/designs_mechfab.dm
@@ -748,7 +748,7 @@
/datum/design/item/mechfab/rig/plasmacutter
category = "Hardsuits"
name = "Plasma Cutter"
- build_path = /obj/item/rig_module/mounted/plasmacutter
+ build_path = /obj/item/rig_module/mounted/energy/plasmacutter
materials = list(MATERIAL_STEEL = 2000, MATERIAL_GLASS = 1000, MATERIAL_PLASTIC = 1000, MATERIAL_GOLD = 700, MATERIAL_PHORON = 500)
req_tech = list(TECH_MATERIAL = 4, TECH_PHORON = 3, TECH_ENGINEERING = 6, TECH_COMBAT = 4)
id = "rig_plasmacutter"
@@ -829,7 +829,7 @@
/datum/design/item/mechfab/rig/taser
category = "Hardsuits"
name = "Electrolaser"
- build_path = /obj/item/rig_module/mounted/taser
+ build_path = /obj/item/rig_module/mounted/energy/taser
materials = list(MATERIAL_STEEL = 4000, MATERIAL_PLASTIC = 2500, MATERIAL_GLASS = 2000, MATERIAL_GOLD = 1000)
req_tech = list(TECH_POWER = 5, TECH_COMBAT = 5, TECH_ENGINEERING = 6)
id = "rig_taser"
@@ -838,7 +838,7 @@
/datum/design/item/mechfab/rig/egun
category = "Hardsuits"
name = "Energy Gun"
- build_path = /obj/item/rig_module/mounted/egun
+ build_path = /obj/item/rig_module/mounted/energy/egun
materials = list(MATERIAL_STEEL = 6000, MATERIAL_GLASS = 3000, MATERIAL_PLASTIC = 2500, MATERIAL_GOLD = 2000, MATERIAL_SILVER = 1000)
req_tech = list(TECH_POWER = 6, TECH_COMBAT = 6, TECH_ENGINEERING = 6)
id = "rig_egun"
@@ -869,3 +869,12 @@
req_tech = list(TECH_MATERIAL = 2, TECH_MAGNET = 2, TECH_ENGINEERING = 5)
id = "rig_cooler"
sort_string = "WCLAB"
+
+/datum/design/item/mechfab/rig/kinetic_module
+ category = "Hardsuits"
+ name = "Gravikinetic Module"
+ build_path = /obj/item/rig_module/kinetic_module
+ materials = list(MATERIAL_STEEL = 4000, MATERIAL_GLASS = 4500, MATERIAL_PLASTIC = 3000)
+ req_tech = list(TECH_MAGNET = 2, TECH_MATERIAL = 6, TECH_ENGINEERING = 6)
+ id = "rig_kinetic"
+ sort_string = "WCLAC"
diff --git a/code/modules/research/designs/designs_mining.dm b/code/modules/research/designs/designs_mining.dm
index d41160d862d41..2bb61bd60d99e 100644
--- a/code/modules/research/designs/designs_mining.dm
+++ b/code/modules/research/designs/designs_mining.dm
@@ -27,7 +27,7 @@
id = "pick_diamond"
req_tech = list(TECH_MATERIAL = 6)
materials = list(MATERIAL_DIAMOND = 3000)
- build_path = /obj/item/pickaxe/diamond
+ build_path = /obj/item/pickaxe/hand/diamond
sort_string = "KAAAD"
/datum/design/item/mining/drill_diamond
diff --git a/code/modules/research/designs/designs_surgery.dm b/code/modules/research/designs/designs_surgery.dm
index 6d7757858f941..362b0624912aa 100644
--- a/code/modules/research/designs/designs_surgery.dm
+++ b/code/modules/research/designs/designs_surgery.dm
@@ -2,38 +2,21 @@
..()
name = "Surgical tool design ([item_name])"
-/datum/design/item/surgery/scalpel_laser1
- name = "Basic Laser Scalpel"
- desc = "A scalpel augmented with a directed laser, for more precise cutting without blood entering the field. This one looks basic and could be improved."
- id = "scalpel_laser1"
- req_tech = list(TECH_BIO = 2, TECH_MATERIAL = 2, TECH_MAGNET = 2)
- materials = list(MATERIAL_STEEL = 12500, MATERIAL_GLASS = 7500)
- build_path = /obj/item/scalpel/laser1
- sort_string = "MBEAA"
-
-/datum/design/item/surgery/scalpel_laser2
- name = "Improved Laser Scalpel"
- desc = "A scalpel augmented with a directed laser, for more precise cutting without blood entering the field. This one looks somewhat advanced."
- id = "scalpel_laser2"
- req_tech = list(TECH_BIO = 3, TECH_MATERIAL = 4, TECH_MAGNET = 4)
- materials = list(MATERIAL_STEEL = 12500, MATERIAL_GLASS = 7500, MATERIAL_SILVER = 2500)
- build_path = /obj/item/scalpel/laser2
- sort_string = "MBEAB"
-/datum/design/item/surgery/scalpel_laser3
- name = "Advanced Laser Scalpel"
- desc = "A scalpel augmented with a directed laser, for more precise cutting without blood entering the field. This one looks to be the pinnacle of precision energy cutlery!"
- id = "scalpel_laser3"
- req_tech = list(TECH_BIO = 4, TECH_MATERIAL = 6, TECH_MAGNET = 5)
+/datum/design/item/surgery/scalpel_laser
+ name = "Laser Scalpel"
+ desc = "An advanced scalpel augmented with a directed laser, for more precise cutting without blood entering the field."
+ id = "scalpel_laser"
+ req_tech = list(TECH_BIO = 5, TECH_MATERIAL = 6, TECH_MAGNET = 4)
materials = list(MATERIAL_STEEL = 12500, MATERIAL_GLASS = 7500, MATERIAL_SILVER = 2000, MATERIAL_GOLD = 1500)
- build_path = /obj/item/scalpel/laser3
- sort_string = "MBEAC"
+ build_path = /obj/item/scalpel/laser
+ sort_string = "MBEAA"
-/datum/design/item/surgery/scalpel_manager
+/datum/design/item/surgery/scalpel_ims
name = "Incision Management System"
desc = "A true extension of the surgeon's body, this marvel instantly and completely prepares an incision allowing for the immediate commencement of therapeutic steps."
- id = "scalpel_manager"
- req_tech = list(TECH_BIO = 4, TECH_MATERIAL = 7, TECH_MAGNET = 5, TECH_DATA = 4)
+ id = "scalpel_ims"
+ req_tech = list(TECH_BIO = 6, TECH_MATERIAL = 7, TECH_MAGNET = 5, TECH_DATA = 5)
materials = list (MATERIAL_STEEL = 12500, MATERIAL_GLASS = 7500, MATERIAL_SILVER = 1500, MATERIAL_GOLD = 1500, MATERIAL_DIAMOND = 750)
- build_path = /obj/item/scalpel/manager
- sort_string = "MBEAD"
\ No newline at end of file
+ build_path = /obj/item/scalpel/ims
+ sort_string = "MBEAB"
diff --git a/code/modules/research/designs/designs_tool.dm b/code/modules/research/designs/designs_tool.dm
index 03e6300d088a5..bbf4edd9042ed 100644
--- a/code/modules/research/designs/designs_tool.dm
+++ b/code/modules/research/designs/designs_tool.dm
@@ -100,3 +100,31 @@
materials = list(MATERIAL_STEEL = 6000, MATERIAL_GLASS = 6000, MATERIAL_ALUMINIUM = 4000, MATERIAL_PHORON = 4000)
build_path = /obj/item/weldingtool/electric
sort_string = "VAGAK"
+
+/datum/design/item/tool/power_drill
+ name = "power drill"
+ desc = "a portable power drill that can swap between bolt and screw heads for a range of fixings"
+ id = "power_drill"
+ req_tech = list(TECH_ENGINEERING = 4, TECH_POWER = 4)
+ materials = list(MATERIAL_STEEL = 100, MATERIAL_ALUMINIUM = 100, MATERIAL_PLASTIC = 6000)
+ build_path = /obj/item/swapper/power_drill
+ sort_string = "VAGAL"
+
+/datum/design/item/tool/jaws_of_life
+ name = "hydraulic prying tool"
+ desc = "a portable hydraulic rescue tool that can swap between prying and cutting heads for a range of breaching techniques"
+ id = "jaws_of_life"
+ req_tech = list(TECH_ENGINEERING = 5, TECH_POWER = 4)
+ materials = list(MATERIAL_STEEL = 6000, MATERIAL_ALUMINIUM = 1000, MATERIAL_PLASTIC = 750)
+ build_path = /obj/item/swapper/jaws_of_life
+ sort_string = "VAGAM"
+
+
+/datum/design/item/tool/rcd
+ name = "rapid construction device"
+ desc = "a gun-shaped device that uses compressed matter to build and dismantle various structures."
+ id = "hand_rcd"
+ req_tech = list(TECH_ENGINEERING = 5, TECH_MATERIAL = 4)
+ materials = list(MATERIAL_STEEL = 25000, MATERIAL_PHORON = 4000, MATERIAL_GOLD = 2000, MATERIAL_SILVER = 2000)
+ build_path = /obj/item/rcd
+ sort_string = "VAGAN"
diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm
index dd92f892b26c4..58859f97015a8 100644
--- a/code/modules/research/destructive_analyzer.dm
+++ b/code/modules/research/destructive_analyzer.dm
@@ -10,6 +10,7 @@ Note: Must be placed within 3 tiles of the R&D Console
name = "destructive analyzer"
desc = "Accessed by a connected core fabricator console, it destroys and analyzes items and materials, recycling materials to any connected protolathe, and progressing the learning matrix of the connected core fabricator console."
icon_state = "d_analyzer"
+ icon = 'icons/obj/machines/research/destructive_analyzer.dmi'
var/obj/item/loaded_item = null
var/decon_mod = 0
@@ -28,12 +29,14 @@ Note: Must be placed within 3 tiles of the R&D Console
..()
/obj/machinery/r_n_d/destructive_analyzer/on_update_icon()
+ ClearOverlays()
if(panel_open)
- icon_state = "d_analyzer_t"
- else if(loaded_item)
- icon_state = "d_analyzer_l"
- else
- icon_state = "d_analyzer"
+ AddOverlays("d_analyzer_panel")
+ if(is_powered())
+ if(loaded_item)
+ AddOverlays(emissive_appearance(icon, "d_analyzer_lights_item"))
+ else
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights"))
/obj/machinery/r_n_d/destructive_analyzer/state_transition(singleton/machine_construction/default/new_state)
. = ..()
@@ -51,36 +54,36 @@ Note: Must be placed within 3 tiles of the R&D Console
return SPAN_NOTICE("There is something already loaded into \the [src]. You must remove it first.")
return ..()
-/obj/machinery/r_n_d/destructive_analyzer/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/r_n_d/destructive_analyzer/use_tool(obj/item/O, mob/living/user, list/click_params)
if(busy)
to_chat(user, SPAN_NOTICE("\The [src] is busy right now."))
- return
- if(component_attackby(O, user))
return TRUE
+ if((. = ..()))
+ return
if(loaded_item)
to_chat(user, SPAN_NOTICE("There is something already loaded into \the [src]."))
- return 1
+ return TRUE
if(panel_open)
to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened."))
- return 1
+ return TRUE
if(!linked_console)
to_chat(user, SPAN_NOTICE("\The [src] must be linked to an R&D console first."))
- return
+ return TRUE
if(!loaded_item)
if(isrobot(user)) //Don't put your module items in there!
- return
+ return FALSE
if(!O.origin_tech)
to_chat(user, SPAN_NOTICE("This doesn't seem to have a tech origin."))
- return
+ return TRUE
if(length(O.origin_tech) == 0 || O.holographic)
to_chat(user, SPAN_NOTICE("You cannot deconstruct this item."))
- return
+ return TRUE
if(!user.unEquip(O, src))
- return
+ return TRUE
busy = 1
loaded_item = O
to_chat(user, SPAN_NOTICE("You add \the [O] to \the [src]."))
- flick("d_analyzer_la", src)
+ icon_state = "d_analyzer_entry"
spawn(10)
update_icon()
busy = 0
@@ -88,5 +91,4 @@ Note: Must be placed within 3 tiles of the R&D Console
if (linked_console.quick_deconstruct)
linked_console.deconstruct(weakref(user))
- return 1
- return
+ return TRUE
diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm
index e9029111fc22f..eeebef0de64af 100644
--- a/code/modules/research/message_server.dm
+++ b/code/modules/research/message_server.dm
@@ -1,8 +1,6 @@
-#define MESSAGE_SERVER_SPAM_REJECT 1
-#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
-
var/global/list/obj/machinery/message_server/message_servers = list()
+
/datum/data_rc_msg
var/rec_dpt = "Unspecified" //name of the person
var/send_dpt = "Unspecified" //name of the sender
@@ -11,6 +9,7 @@ var/global/list/obj/machinery/message_server/message_servers = list()
var/id_auth = "Unauthenticated"
var/priority = "Normal"
+
/datum/data_rc_msg/New(param_rec = "",param_sender = "",param_message = "",param_stamp = "",param_id_auth = "",param_priority)
if(param_rec)
rec_dpt = param_rec
@@ -33,10 +32,11 @@ var/global/list/obj/machinery/message_server/message_servers = list()
else
priority = "Undetermined"
+
/obj/machinery/message_server
- icon = 'icons/obj/machines/research.dmi'
+ icon = 'icons/obj/machines/research/server.dmi'
icon_state = "server"
- name = "Messaging Server"
+ name = "messaging server"
density = TRUE
anchored = TRUE
idle_power_usage = 10
@@ -51,61 +51,52 @@ var/global/list/obj/machinery/message_server/message_servers = list()
var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
"are happy to inform you", "account number", "enter your PIN")
//Messages having theese tokens will be rejected by server. Case sensitive
- var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
+ var/spamfilter_limit = 10
-/obj/machinery/message_server/New()
- message_servers += src
- decryptkey = GenerateKey()
- ..()
/obj/machinery/message_server/Destroy()
message_servers -= src
return ..()
+
+/obj/machinery/message_server/Initialize()
+ . = ..()
+ message_servers += src
+ decryptkey = GenerateKey()
+ update_icon()
+
+
/obj/machinery/message_server/Process()
- ..()
- if(active && (inoperable()))
- active = 0
- power_failure = 10
- update_icon()
- return
- else if(inoperable())
- return
- else if(power_failure > 0)
- if(!(--power_failure))
- active = 1
- update_icon()
+ if (inoperable())
+ if (active)
+ active = FALSE
+ power_failure = 10
+ else if (power_failure > 0)
+ --power_failure
+ if (!power_failure)
+ active = TRUE
+ update_icon()
-/obj/machinery/message_server/proc/send_rc_message(recipient = "",sender = "",message = "",stamp = "", id_auth = "", priority = 1)
- rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
- var/authmsg = "[message] "
- if (id_auth)
- authmsg += "[id_auth] "
- if (stamp)
- authmsg += "[stamp] "
- . = FALSE
- var/list/good_z = GetConnectedZlevels(z)
- for (var/obj/machinery/requests_console/Console in allConsoles)
- if(!(Console.z in good_z))
- continue
- if (ckey(Console.department) == ckey(recipient))
- if(Console.inoperable())
- Console.message_log += "Message lost due to console failure. Please contact [station_name()] system administrator or AI for technical assistance. "
- continue
- . = TRUE
- if(Console.newmessagepriority < priority)
- Console.newmessagepriority = priority
- Console.icon_state = "req_comp[priority]"
- if(priority > 1)
- playsound(Console.loc, 'sound/machines/chime.ogg', 80, 1)
- Console.audible_message("[icon2html(Console, viewers(get_turf(Console)))][SPAN_WARNING("\The [Console] announces: 'High priority message received from [sender]!'")]", hearing_distance = 8)
- Console.message_log += "[SPAN_COLOR("red", "High Priority message from [sender]")] [authmsg]"
- else
- if(!Console.silent)
- playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1)
- Console.audible_message("[icon2html(Console, viewers(get_turf(Console)))][SPAN_NOTICE("\The [Console] announces: 'Message received from [sender].'")]", hearing_distance = 5)
- Console.message_log += "Message from [sender] [authmsg]"
- Console.set_light(0.3, 0.1, 2)
+
+/obj/machinery/r_n_d/server/operable()
+ return !inoperable(MACHINE_STAT_EMPED)
+
+
+/obj/machinery/message_server/on_update_icon()
+ ClearOverlays()
+ if (operable())
+ AddOverlays(list(
+ "server_on",
+ "server_lights_on",
+ emissive_appearance(icon, "server_lights_on")
+ ))
+ else
+ AddOverlays(list(
+ "server_lights_off",
+ emissive_appearance(icon, "server_lights_off")
+ ))
+ if (panel_open)
+ AddOverlays("server_panel")
/obj/machinery/message_server/interface_interact(mob/user)
@@ -117,33 +108,29 @@ var/global/list/obj/machinery/message_server/message_servers = list()
update_icon()
return TRUE
-/obj/machinery/message_server/attackby(obj/item/O as obj, mob/living/user as mob)
- if (active && operable() && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
- istype(O,/obj/item/stock_parts/circuitboard/message_monitor))
- spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
- qdel(O)
- to_chat(user, "You install additional memory and processors into message server. Its filtering capabilities been enhanced.")
- else
- ..(O, user)
-/obj/machinery/message_server/on_update_icon()
- if((inoperable()))
- icon_state = "server-nopower"
- else if (!active)
- icon_state = "server-off"
- else
- icon_state = "server-on"
+/obj/machinery/message_server/use_tool(obj/item/tool, mob/living/user, list/click_params)
+ if (!istype(tool, /obj/item/stock_parts/circuitboard/message_monitor))
+ return ..()
+ if (spamfilter_limit >= initial(spamfilter_limit) * 2)
+ to_chat(user, SPAN_WARNING("\The [src] already has as many boards as it can hold."))
+ return TRUE
+ spamfilter_limit += round(initial(spamfilter_limit) / 2)
+ user.visible_message(
+ SPAN_ITALIC("\The [user] installs \a [tool] into \a [src]."),
+ SPAN_ITALIC("You install \the [tool] into \the [src], improving its spam filtering capabilities."),
+ range = 5
+ )
+ qdel(tool)
+ return TRUE
- return
/obj/machinery/message_server/proc/send_to_department(department, message, tone)
var/reached = 0
-
for(var/mob/living/carbon/human/H in GLOB.human_mobs)
var/obj/item/modular_computer/device = locate() in H
if(!device || !(get_z(device) in GLOB.using_map.station_levels))
continue
-
var/rank = H.get_authentification_rank()
var/datum/job/J = SSjobs.get_by_title(rank)
if (!J)
@@ -151,9 +138,39 @@ var/global/list/obj/machinery/message_server/message_servers = list()
if(!istype(J))
log_debug(append_admin_tools("MESSAGE SERVER: Mob has an invalid job, skipping. Mob: '[H]'. Rank: '[rank]'. Job: '[J]'."))
continue
-
if(J.department_flag & department)
to_chat(H, SPAN_NOTICE("Your [device.name] alerts you to the fact that somebody is requesting your presence at your department."))
reached++
-
return reached
+
+
+/obj/machinery/message_server/proc/send_rc_message(recipient = "",sender = "",message = "",stamp = "", id_auth = "", priority = 1)
+ rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
+ var/authmsg = "[message] "
+ if (id_auth)
+ authmsg += "[id_auth] "
+ if (stamp)
+ authmsg += "[stamp] "
+ . = FALSE
+ var/list/good_z = GetConnectedZlevels(z)
+ for (var/obj/machinery/requests_console/Console in allConsoles)
+ if(!(Console.z in good_z))
+ continue
+ if (ckey(Console.department) == ckey(recipient))
+ if(Console.inoperable())
+ Console.message_log += "Message lost due to console failure. Please contact [station_name()] system administrator or AI for technical assistance. "
+ continue
+ . = TRUE
+ if(Console.newmessagepriority < priority)
+ Console.newmessagepriority = priority
+ Console.icon_state = "req_comp[priority]"
+ if(priority > 1)
+ playsound(Console.loc, 'sound/machines/chime.ogg', 80, 1)
+ Console.audible_message("[icon2html(Console, viewers(get_turf(Console)))][SPAN_WARNING("\The [Console] announces: 'High priority message received from [sender]!'")]", hearing_distance = 8)
+ Console.message_log += "[SPAN_COLOR("red", "High Priority message from [sender]")] [authmsg]"
+ else
+ if(!Console.silent)
+ playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1)
+ Console.audible_message("[icon2html(Console, viewers(get_turf(Console)))][SPAN_NOTICE("\The [Console] announces: 'Message received from [sender].'")]", hearing_distance = 5)
+ Console.message_log += "Message from [sender] [authmsg]"
+ Console.set_light(2, 0.5)
diff --git a/code/modules/research/part_replacer.dm b/code/modules/research/part_replacer.dm
index 03986c73295b2..22659c8f76628 100644
--- a/code/modules/research/part_replacer.dm
+++ b/code/modules/research/part_replacer.dm
@@ -1,15 +1,14 @@
/obj/item/storage/part_replacer
name = "rapid part exchange device"
desc = "Special mechanical module made to store, sort, and apply standard machine parts."
+ icon = 'icons/obj/tools/part_replacer.dmi'
icon_state = "RPED"
item_state = "RPED"
w_class = ITEM_SIZE_HUGE
- can_hold = list(/obj/item/stock_parts)
+ contents_allowed = list(/obj/item/stock_parts)
storage_slots = 50
- use_to_pickup = 1
- allow_quick_gather = 1
- allow_quick_empty = 1
- collection_mode = 1
+ allow_quick_gather = TRUE
+ allow_quick_empty = TRUE
max_w_class = ITEM_SIZE_NORMAL
max_storage_space = 100
origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 3)
diff --git a/code/modules/research/protolathe.dm b/code/modules/research/protolathe.dm
index 9566b5bea9030..4c234f4a44876 100644
--- a/code/modules/research/protolathe.dm
+++ b/code/modules/research/protolathe.dm
@@ -2,6 +2,7 @@
name = "protolathe"
desc = "Accessed by a connected core fabricator console, it produces items from various materials."
icon_state = "protolathe"
+ icon = 'icons/obj/machines/research/protolathe.dmi'
atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_OPEN_CONTAINER
idle_power_usage = 30
@@ -70,12 +71,16 @@
/obj/machinery/r_n_d/protolathe/on_update_icon()
+ ClearOverlays()
if(panel_open)
- icon_state = "protolathe_t"
- else if(busy)
- icon_state = "protolathe_n"
- else
- icon_state = "protolathe"
+ AddOverlays("[icon_state]_panel")
+ if(is_powered())
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights"))
+ AddOverlays("[icon_state]_lights")
+ if(busy)
+ AddOverlays("[icon_state]_working")
+ AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working"))
+ AddOverlays("[icon_state]_lights_working")
/obj/machinery/r_n_d/protolathe/state_transition(singleton/machine_construction/default/new_state)
. = ..()
@@ -91,31 +96,31 @@
return SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation.")
return ..()
-/obj/machinery/r_n_d/protolathe/attackby(obj/item/O as obj, mob/user as mob)
+/obj/machinery/r_n_d/protolathe/use_tool(obj/item/O, mob/living/user, list/click_params)
if(busy)
to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation."))
- return 1
- if(component_attackby(O, user))
return TRUE
+ if((. = ..()))
+ return
if(O.is_open_container())
- return 1
+ return TRUE
if(panel_open)
to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened."))
- return 1
+ return TRUE
if(!linked_console)
to_chat(user, SPAN_NOTICE("\The [src] must be linked to an R&D console first!"))
- return 1
+ return TRUE
if(is_robot_module(O))
- return 0
+ return FALSE
if(!istype(O, /obj/item/stack/material))
to_chat(user, SPAN_NOTICE("You cannot insert this item into \the [src]!"))
- return 0
+ return TRUE
if(inoperable())
- return 1
+ return TRUE
if(TotalMaterials() + SHEET_MATERIAL_AMOUNT > max_material_storage)
to_chat(user, SPAN_NOTICE("\The [src]'s material bin is full. Please remove material before adding more."))
- return 1
+ return TRUE
var/obj/item/stack/material/stack = O
@@ -123,9 +128,9 @@
var/image/I = image(icon, "protolathe_stack")
I.color = stack.material.icon_colour
- overlays += I
+ AddOverlays(I)
spawn(10)
- overlays -= I
+ CutOverlays(I)
busy = 1
use_power_oneoff(max(1000, (SHEET_MATERIAL_AMOUNT * amount / 10)))
@@ -135,6 +140,7 @@
materials[stack.material.name] += amount * SHEET_MATERIAL_AMOUNT
busy = 0
updateUsrDialog()
+ return TRUE
/obj/machinery/r_n_d/protolathe/proc/addToQueue(datum/design/D)
queue += D
@@ -147,10 +153,10 @@
/obj/machinery/r_n_d/protolathe/proc/canBuild(datum/design/D)
for(var/M in D.materials)
- if(materials[M] < D.materials[M])
+ if(materials[M] < D.materials[M] * mat_efficiency)
return 0
for(var/C in D.chemicals)
- if(!reagents.has_reagent(C, D.chemicals[C]))
+ if(!reagents.has_reagent(C, D.chemicals[C] * mat_efficiency))
return 0
return 1
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index bf571ac12ba49..621aaee1b2ef6 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -106,15 +106,15 @@ won't update every console in existence) but it's more of a hassle to do. Also,
if(D.linked_console != null || D.panel_open)
continue
if(istype(D, /obj/machinery/r_n_d/destructive_analyzer) && can_analyze == TRUE) // Only science R&D consoles can do research
- if(linked_destroy == null)
+ if(isnull(linked_destroy))
linked_destroy = D
D.linked_console = src
else if(istype(D, /obj/machinery/r_n_d/protolathe))
- if(linked_lathe == null)
+ if(isnull(linked_lathe))
linked_lathe = D
D.linked_console = src
else if(istype(D, /obj/machinery/r_n_d/circuit_imprinter))
- if(linked_imprinter == null)
+ if(isnull(linked_imprinter))
linked_imprinter = D
D.linked_console = src
return
@@ -123,7 +123,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
..()
files = new
if(!id)
- for(var/obj/machinery/r_n_d/server/centcom/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/centcom/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server/centcom))
S.update_connections()
break
@@ -131,36 +131,33 @@ won't update every console in existence) but it's more of a hassle to do. Also,
SyncRDevices()
. = ..()
-/obj/machinery/computer/rdconsole/attackby(obj/item/D as obj, mob/user as mob)
- //Loading a disk into it.
+/obj/machinery/computer/rdconsole/use_tool(obj/item/D, mob/living/user, list/click_params)
if(istype(D, /obj/item/disk))
if(t_disk || d_disk)
to_chat(user, "A disk is already loaded into the machine.")
- return
+ return TRUE
if(!user.canUnEquip(D))
- return
+ return TRUE
if(istype(D, /obj/item/disk/tech_disk))
t_disk = D
else if (istype(D, /obj/item/disk/design_disk))
d_disk = D
else
to_chat(user, SPAN_NOTICE("Machine cannot accept disks in that format."))
- return
+ return TRUE
user.drop_from_inventory(D, src)
to_chat(user, SPAN_NOTICE("You add \the [D] to the machine."))
- else
- //The construction/deconstruction of the console code.
- ..()
+ updateUsrDialog()
+ return TRUE
- src.updateUsrDialog()
- return
+ return ..()
/obj/machinery/computer/rdconsole/emag_act(remaining_charges, mob/user)
if(!emagged)
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = TRUE
req_access.Cut()
- to_chat(user, SPAN_NOTICE("You you disable the security protocols."))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
return 1
/obj/machinery/computer/rdconsole/CanUseTopic(mob/user, datum/topic_state/state, href_list)
@@ -187,7 +184,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
screen = 1
return
screen = 1.2
- files.AddTech2Known(t_disk.stored)
+ files.add_tech_to_known(t_disk.stored)
updateUsrDialog()
else if(href_list["clear_tech"]) //Erase data on the technology disk.
@@ -204,15 +201,16 @@ won't update every console in existence) but it's more of a hassle to do. Also,
. = TOPIC_REFRESH
else if(href_list["copy_tech"]) //Copys some technology data from the research holder to the disk.
- . = TOPIC_REFRESH
if(!t_disk)
screen = 1
- return
- for(var/datum/tech/T in files.known_tech)
- if(href_list["copy_tech_ID"] == T.id)
- t_disk.stored = T
- break
+ return TOPIC_REFRESH
+
+ var/datum/tech/known_tech = files.known_tech_lookup[href_list["copy_tech_ID"]]
+ if(known_tech)
+ t_disk.stored = known_tech
+
screen = 1.2
+ return TOPIC_REFRESH
else if(href_list["updt_design"]) //Updates the research holder with design data from the design disk.
. = TOPIC_REFRESH
@@ -225,7 +223,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
screen = 1
return
screen = 1.4
- files.AddDesign2Known(d_disk.blueprint)
+ files.add_design_to_known(d_disk.blueprint)
updateUsrDialog()
else if(href_list["clear_design"]) //Erases data on the design disk.
@@ -243,15 +241,21 @@ won't update every console in existence) but it's more of a hassle to do. Also,
. = TOPIC_REFRESH
else if(href_list["copy_design"]) //Copy design data from the research holder to the design disk.
- . = TOPIC_REFRESH
if(!d_disk)
screen = 1
- return
- for(var/datum/design/D in files.known_designs)
- if(href_list["copy_design_ID"] == D.id)
- d_disk.blueprint = D
- break
+ return TOPIC_REFRESH
+
+ var/design_to_copy_id = href_list["copy_design_ID"]
+ if(!design_to_copy_id)
+ return TOPIC_NOACTION
+
+ var/datum/design/design_to_copy = files.known_designs_lookup[design_to_copy_id]
+ if(!design_to_copy)
+ return TOPIC_NOACTION
+
+ d_disk.blueprint = design_to_copy
screen = 1.4
+ return TOPIC_REFRESH
else if(href_list["eject_item"]) //Eject the item inside the destructive analyzer.
. = TOPIC_REFRESH
@@ -291,24 +295,30 @@ won't update every console in existence) but it's more of a hassle to do. Also,
. = TOPIC_HANDLED
spawn(30)
if(src)
- for(var/obj/machinery/r_n_d/server/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server))
var/server_processed = 0
if((id in S.id_with_upload) || istype(S, /obj/machinery/r_n_d/server/centcom))
- for(var/datum/tech/T in files.known_tech)
- S.files.AddTech2Known(T)
- for(var/datum/design/D in files.known_designs)
- S.files.AddDesign2Known(D)
- S.files.RefreshResearch()
+ for(var/datum/tech/known_tech as anything in files.known_tech)
+ S.files.add_tech_to_known(known_tech)
+
+ for(var/datum/design/D as anything in files.known_designs)
+ S.files.add_design_to_known(D)
+
+ S.files.refresh_research()
server_processed = 1
+
if((id in S.id_with_download) && !istype(S, /obj/machinery/r_n_d/server/centcom))
- for(var/datum/tech/T in S.files.known_tech)
- files.AddTech2Known(T)
- for(var/datum/design/D in S.files.known_designs)
- files.AddDesign2Known(D)
- files.RefreshResearch()
+ for(var/datum/tech/known_tech as anything in S.files.known_tech)
+ files.add_tech_to_known(known_tech)
+
+ for(var/datum/design/D as anything in S.files.known_designs)
+ files.add_design_to_known(D)
+
+ files.refresh_research()
server_processed = 1
if(!istype(S, /obj/machinery/r_n_d/server/centcom) && server_processed)
S.produce_heat()
+
screen = 1.6
interact(user)
@@ -320,7 +330,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
. = TOPIC_REFRESH
CHECK_LATHE
var/datum/design/being_built = null
- for(var/datum/design/D in files.known_designs)
+ for(var/datum/design/D as anything in files.known_designs)
if(D.id == href_list["build"])
being_built = D
break
@@ -332,7 +342,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
. = TOPIC_REFRESH
CHECK_IMPRINTER
var/datum/design/being_built = null
- for(var/datum/design/D in files.known_designs)
+ for(var/datum/design/D as anything in files.known_designs)
if(D.id == href_list["imprint"])
being_built = D
break
@@ -481,8 +491,8 @@ won't update every console in existence) but it's more of a hassle to do. Also,
linked_destroy.busy = TRUE
if (!quick_deconstruct)
screen = 0.1
- flick("d_analyzer_process", linked_destroy)
- addtimer(new Callback(src, .proc/finish_deconstruct, W), 24)
+ linked_destroy.icon_state = "d_analyzer_process"
+ addtimer(CALLBACK(src, PROC_REF(finish_deconstruct), W), 24)
/obj/machinery/computer/rdconsole/proc/finish_deconstruct(weakref/W)
CHECK_DESTROY
@@ -493,7 +503,8 @@ won't update every console in existence) but it's more of a hassle to do. Also,
screen = 1.0
return
for(var/T in linked_destroy.loaded_item.origin_tech)
- files.UpdateTech(T, linked_destroy.loaded_item.origin_tech[T])
+ files.update_tech(T, linked_destroy.loaded_item.origin_tech[T])
+
if(linked_lathe && linked_destroy.loaded_item.matter) // Also sends salvaged materials to a linked protolathe, if any.
for(var/t in linked_destroy.loaded_item.matter)
if(t in linked_lathe.materials)
@@ -529,23 +540,25 @@ won't update every console in existence) but it's more of a hassle to do. Also,
/obj/machinery/computer/rdconsole/proc/GetResearchLevelsInfo()
var/dat
dat += "
"
- for(var/datum/tech/T in files.known_tech)
- if(T.level < 1)
+ for(var/datum/tech/known_tech as anything in files.known_tech)
+ if(known_tech.level < 1)
continue
+
dat += "
"
- dat += "[T.name]"
+ dat += "[known_tech.name]"
dat += "
"
- dat += "
Level: [T.level]"
- dat += "
Summary: [T.desc]"
+ dat += "
Level: [known_tech.level]"
+ dat += "
Summary: [known_tech.desc]"
dat += "
"
return dat
/obj/machinery/computer/rdconsole/proc/GetResearchListInfo()
var/dat
dat += "
"
- for(var/datum/design/D in files.known_designs)
- if(D.build_path)
- dat += "
[D.name]: [D.desc]"
+ for(var/datum/design/known_design as anything in files.known_designs)
+ if(known_design.build_path)
+ dat += "
[known_design.name]: [known_design.desc]"
+
dat += "
"
return dat
@@ -556,20 +569,20 @@ won't update every console in existence) but it's more of a hassle to do. Also,
/obj/machinery/computer/rdconsole/interact(mob/user)
user.set_machine(src)
var/dat = list()
- files.RefreshResearch()
+ files.refresh_research()
switch(screen) //A quick check to make sure you get the right screen when a device is disconnected.
if(2 to 2.9)
- if(linked_destroy == null)
+ if(isnull(linked_destroy))
screen = 2.0
- else if(linked_destroy.loaded_item == null)
+ else if(isnull(linked_destroy.loaded_item))
screen = 2.1
else
screen = 2.2
if(3 to 3.9)
- if(linked_lathe == null)
+ if(isnull(linked_lathe))
screen = 3.0
if(4 to 4.9)
- if(linked_imprinter == null)
+ if(isnull(linked_imprinter))
screen = 4.0
switch(screen)
@@ -629,7 +642,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
return
dat += "Main Menu"
dat += "Disk Contents: (Technology Data Disk)
"
- if(t_disk.stored == null)
+ if(isnull(t_disk.stored))
dat += "The disk has no data stored on it."
dat += "Operations: "
dat += "Load Tech to Disk || "
@@ -650,9 +663,9 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Return to Disk Operations"
dat += "Load Technology to Disk:
"
dat += "
"
- for(var/datum/tech/T in files.known_tech)
- dat += "
[T.name] "
- dat += "\[copy to disk\]"
+ for(var/datum/tech/known_tech as anything in files.known_tech)
+ dat += "
[known_tech.name] "
+ dat += "\[copy to disk\]"
dat += "
"
if(1.4) //Design Disk menu.
@@ -660,7 +673,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
screen = 1
return
dat += "Main Menu"
- if(d_disk.blueprint == null)
+ if(isnull(d_disk.blueprint))
dat += "The disk has no data stored on it."
dat += "Operations: "
dat += "Load Design to Disk || "
@@ -686,7 +699,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Return to Disk Operations"
dat += "Load Design to Disk:
"
dat += "
"
- for(var/datum/design/D in files.known_designs)
+ for(var/datum/design/D as anything in files.known_designs)
if(D.build_path)
dat += "
[D.name] "
dat += "\[copy to disk\]"
@@ -747,12 +760,14 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Origin Tech:"
dat += "
"
- for(var/T in linked_destroy.loaded_item.origin_tech)
- dat += "
[CallTechName(T)] [linked_destroy.loaded_item.origin_tech[T]]"
- for(var/datum/tech/F in files.known_tech)
- if(F.name == CallTechName(T))
- dat += " (Current: [F.level])"
- break
+ for(var/origin_tech_id in linked_destroy.loaded_item.origin_tech)
+ dat += "
"
dat += "Deconstruct Item || "
dat += "Eject Item || "
@@ -777,7 +792,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Chemical Volume: [linked_lathe.reagents.total_volume] (MAX: [linked_lathe.reagents.maximum_volume])"
dat += "
"
- for(var/datum/design/D in files.known_designs)
+ for(var/datum/design/D as anything in files.known_designs)
if(!D.build_path || !(D.build_type & PROTOLATHE))
continue
@@ -787,7 +802,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
var/temp_dat
for(var/M in D.materials)
- temp_dat += ", [D.materials[M]] [CallMaterialName(M)]"
+ temp_dat += ", [D.materials[M]*(linked_lathe ? linked_lathe.mat_efficiency : 1)] [CallMaterialName(M)]"
for(var/T in D.chemicals)
temp_dat += ", [D.chemicals[T]*(linked_imprinter ? linked_imprinter.mat_efficiency : 1)] [CallReagentName(T)]"
if(temp_dat)
@@ -809,14 +824,17 @@ won't update every console in existence) but it's more of a hassle to do. Also,
saved_origins[D.build_path] = origin_tech
qdel(I)
- for (var/T in origin_tech)
- for (var/datum/tech/F in files.known_tech)
- if (F.name == CallTechName(T))
- if (F.level <= origin_tech[T])
- dat += SPAN_COLOR(COLOR_GREEN, " [F.name] = [origin_tech[T]] ")
- else
- dat += " [F.name] = [origin_tech[T]] "
- break
+ for(var/origin_tech_id in origin_tech)
+ var/datum/tech/known_tech_item = files.known_tech_lookup[origin_tech_id]
+ if(!known_tech_item)
+ continue
+
+ var/origin_tech_level = origin_tech[origin_tech_id]
+ if(origin_tech_level >= known_tech_item.level)
+ dat += SPAN_COLOR(COLOR_GREEN, " [known_tech_item.name] = [origin_tech_level] ")
+ else
+ dat += " [known_tech_item.name] = [origin_tech_level] "
+
dat += "
"
if(3.2) //Protolathe Material Storage Sub-menu
@@ -887,45 +905,48 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Material Amount: [linked_imprinter.TotalMaterials()] cm3 "
dat += "Chemical Volume: [linked_imprinter.reagents.total_volume]"
dat += "
"
- for(var/datum/design/D in files.known_designs)
- if(!D.build_path || !(D.build_type & IMPRINTER))
+ for(var/datum/design/known_design as anything in files.known_designs)
+ if(!known_design.build_path || !(known_design.build_type & IMPRINTER))
continue
- if (imprinter_search != "" && !findtext(D.name, imprinter_search))
+ if (imprinter_search != "" && !findtext(known_design.name, imprinter_search))
continue
var/temp_dat
- for(var/M in D.materials)
- temp_dat += ", [D.materials[M]*linked_imprinter.mat_efficiency] [CallMaterialName(M)]"
- for(var/T in D.chemicals)
- temp_dat += ", [D.chemicals[T]*linked_imprinter.mat_efficiency] [CallReagentName(T)]"
+ for(var/M in known_design.materials)
+ temp_dat += ", [known_design.materials[M]*linked_imprinter.mat_efficiency] [CallMaterialName(M)]"
+ for(var/T in known_design.chemicals)
+ temp_dat += ", [known_design.chemicals[T]*linked_imprinter.mat_efficiency] [CallReagentName(T)]"
if(temp_dat)
temp_dat = " \[[copytext(temp_dat,3)]\]"
- if(linked_imprinter.canBuild(D))
- dat += "
[D.name][temp_dat]"
+ if(linked_imprinter.canBuild(known_design))
+ dat += "
[known_design.name][temp_dat]"
if (imprinter_show_tech)
var/list/origin_tech
- if (saved_origins[D.build_path])
- origin_tech = saved_origins[D.build_path]
+ if (saved_origins[known_design.build_path])
+ origin_tech = saved_origins[known_design.build_path]
if (!origin_tech)
- var/obj/item/I = new D.build_path
+ var/obj/item/I = new known_design.build_path
origin_tech = I.origin_tech
- saved_origins[D.build_path] = origin_tech
+ saved_origins[known_design.build_path] = origin_tech
qdel(I)
- for (var/T in origin_tech)
- for (var/datum/tech/F in files.known_tech)
- if (F.name == CallTechName(T))
- if (F.level <= origin_tech[T] )
- dat += SPAN_COLOR(COLOR_GREEN, " [F.name] = [origin_tech[T]] ")
- else
- dat += " [F.name] = [origin_tech[T]] "
- break
+ for(var/origin_tech_id in origin_tech)
+ var/datum/tech/known_tech_item = files.known_tech_lookup[origin_tech_id]
+ if(!known_tech_item)
+ continue
+
+ var/origin_tech_level = origin_tech[origin_tech_id]
+ if(origin_tech_level >= known_tech_item.level)
+ dat += SPAN_COLOR(COLOR_GREEN, " [known_tech_item.name] = [origin_tech_level] ")
+ else
+ dat += " [known_tech_item.name] = [origin_tech_level] "
+
dat += "
"
if(4.2)
@@ -967,11 +988,11 @@ won't update every console in existence) but it's more of a hassle to do. Also,
dat += "Empty"
else
var/tmp = 1
- for(var/datum/design/D in linked_imprinter.queue)
+ for(var/datum/design/design_in_queue in linked_imprinter.queue)
if(tmp == 1)
- dat += "1: [D.name] "
+ dat += "1: [design_in_queue.name] "
else
- dat += "[tmp]: [D.name] (Remove) "
+ dat += "[tmp]: [design_in_queue.name] (Remove) "
++tmp
///////////////////Research Information Browser////////////////////
diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm
index d7ba685946a04..6e1b7c6e48def 100644
--- a/code/modules/research/rdmachines.dm
+++ b/code/modules/research/rdmachines.dm
@@ -5,7 +5,7 @@
var/global/list/default_material_composition = list(MATERIAL_STEEL = 0, MATERIAL_ALUMINIUM = 0, MATERIAL_PLASTIC = 0, MATERIAL_GLASS = 0, MATERIAL_GOLD = 0, MATERIAL_SILVER = 0, MATERIAL_PHORON = 0, MATERIAL_URANIUM = 0, MATERIAL_DIAMOND = 0)
/obj/machinery/r_n_d
name = "R&D Device"
- icon = 'icons/obj/machines/research.dmi'
+ icon = 'icons/obj/machines/research/protolathe.dmi'
density = TRUE
anchored = TRUE
uncreated_component_parts = null
@@ -38,12 +38,12 @@ var/global/list/default_material_composition = list(MATERIAL_STEEL = 0, MATERIAL
for(var/f in materials)
. += materials[f]
-/obj/machinery/r_n_d/proc/getLackingMaterials(datum/design/D)
+/obj/machinery/r_n_d/proc/getLackingMaterials(datum/design/design)
var/list/ret = list()
- for(var/M in D.materials)
- if(materials[M] < D.materials[M])
- ret += "[D.materials[M] - materials[M]] [M]"
- for(var/C in D.chemicals)
- if(!reagents.has_reagent(C, D.chemicals[C]))
- ret += C
+ for(var/material_needed in design.materials)
+ if(materials[material_needed] < design.materials[material_needed])
+ ret += "[design.materials[material_needed] - materials[material_needed]] [material_needed]"
+ for(var/datum/reagent/chemical_needed as anything in design.chemicals)
+ if(!reagents.has_reagent(chemical_needed, design.chemicals[chemical_needed]))
+ ret += "[design.chemicals[chemical_needed] - reagents.get_reagent_amount(chemical_needed)]u [initial(chemical_needed.name)]"
return english_list(ret)
diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm
index 5be3d6b5f3643..78faecc9ede57 100644
--- a/code/modules/research/research.dm
+++ b/code/modules/research/research.dm
@@ -4,7 +4,7 @@ The research datum is the "folder" where all the research information is stored
various procs used to manipulate it. It has four variables and seven procs:
Variables:
-- possible_tech is a list of all the /datum/tech that can potentially be researched by the player. The RefreshResearch() proc
+- possible_tech is a list of all the /datum/tech that can potentially be researched by the player. The refresh_research() proc
(explained later) only goes through those when refreshing what you know. Generally, possible_tech contains ALL of the existing tech
but it is possible to add tech to the game that DON'T start in it (example: Xeno tech). Generally speaking, you don't want to mess
with these since they should be the default version of the datums. They're actually stored in a list rather then using typesof to
@@ -15,17 +15,17 @@ list, it can't be improved. All the tech in this list are visible to the player.
- known_designs is functionally identical to known_tech except it's for /datum/design
Procs:
-- TechHasReqs: Used by other procs (specifically RefreshResearch) to see whether all of a tech's requirements are currently in
+- TechHasReqs: Used by other procs (specifically refresh_research) to see whether all of a tech's requirements are currently in
known_tech and at a high enough level.
-- DesignHasReqs: Same as TechHasReqs but for /datum/design and known_design.
-- AddTech2Known: Adds a /datum/tech to known_tech. It checks to see whether it already has that tech (if so, it just replaces it). If
+- design_available: Same as TechHasReqs but for /datum/design and known_design.
+- add_tech_to_known: Adds a /datum/tech to known_tech. It checks to see whether it already has that tech (if so, it just replaces it). If
it doesn't have it, it adds it. Note: It does NOT check possible_tech at all. So if you want to add something strange to it (like
a player made tech?) you can.
-- AddDesign2Known: Same as AddTech2Known except for /datum/design and known_designs.
-- RefreshResearch: This is the workhorse of the R&D system. It updates the /datum/research holder and adds any unlocked tech paths
+- add_design_to_known: Same as add_tech_to_known except for /datum/design and known_designs.
+- refresh_research: This is the workhorse of the R&D system. It updates the /datum/research holder and adds any unlocked tech paths
and designs you have reached the requirements for. It only checks through possible_tech and possible_designs, however, so it won't
accidentally add "secret" tech to it.
-- UpdateTech is used as part of the actual researching process. It takes an ID and finds techs with that same ID in known_tech. When
+- update_tech is used as part of the actual researching process. It takes an ID and finds techs with that same ID in known_tech. When
it finds it, it checks to see whether it can improve it at all. If the known_tech's level is less then or equal to
the inputted level, it increases the known tech's level to the inputted level -1 or know tech's level +1 (whichever is higher).
@@ -44,176 +44,128 @@ research holder datum.
** Includes all the helper procs and basic tech processing. **
***************************************************************/
-/datum/research //Holder for all the existing, archived, and known tech. Individual to console.
- var/list/known_tech = list() //List of locally known tech. Datum/tech go here.
- var/list/possible_designs = list() //List of all designs.
- var/list/known_designs = list() //List of available designs.
+//Holder for all the existing, archived, and known tech. Individual to console.
+/datum/research
+ /// List of available designs as: design_id => design. For faster lookup
+ var/list/known_designs_lookup = list()
+ /// Designs sorted by `sort_string`.
+ var/list/known_designs = list()
+ /// List of locally known tech as: tech.id => /datum/tech. For faster lookup
+ var/list/known_tech_lookup = list()
+ /// List of locally known techs. For faster iteration
+ var/list/known_tech = list()
+ /// List of all existing designs.
+ var/static/list/possible_designs = list()
+
+//Insert techs into possible_tech here. Known_tech automatically updated.
+/datum/research/New()
+ if(!length(possible_designs))
+ for(var/design_path in subtypesof(/datum/design))
+ possible_designs += new design_path(src)
+
+ initialize_tech()
+ refresh_research()
+
+/datum/research/proc/initialize_tech()
+ for(var/tech_path in subtypesof(/datum/tech))
+ var/datum/tech/new_tech = new tech_path(src)
+ known_tech_lookup[new_tech.id] = new_tech
+ known_tech += new_tech
-/datum/research/New() //Insert techs into possible_tech here. Known_tech automatically updated.
- for(var/T in typesof(/datum/tech) - /datum/tech)
- known_tech += new T(src)
- for(var/D in typesof(/datum/design) - /datum/design)
- possible_designs += new D(src)
- RefreshResearch()
+/datum/research/techonly/New()
+ initialize_tech()
+ refresh_research()
-/datum/research/techonly
+/// Checks to see if design has all the required pre-reqs.
+/// Input: datum/design; Output: TRUE/FALSE
+/datum/research/proc/design_available(datum/design/design_to_check)
+ if(!length(design_to_check.req_tech))
+ return TRUE
-/datum/research/techonly/New()
- for(var/T in typesof(/datum/tech) - /datum/tech)
- known_tech += new T(src)
- RefreshResearch()
+ for(var/required_tech_id in design_to_check.req_tech)
+ var/datum/tech/known_tech_item = known_tech_lookup[required_tech_id]
+ var/known_tech_level = known_tech_item.level
+ if(!known_tech_level || known_tech_level < design_to_check.req_tech[required_tech_id])
+ return FALSE
-//Checks to see if design has all the required pre-reqs.
-//Input: datum/design; Output: 0/1 (false/true)
-/datum/research/proc/DesignHasReqs(datum/design/D)
- if(length(D.req_tech) == 0)
- return 1
+ return TRUE
- var/list/k_tech = list()
+//Adds a tech to known_tech list. Checks to make sure there aren't duplicates and updates existing tech's levels if needed.
+//Input: datum/tech; Output: Null
+/datum/research/proc/add_tech_to_known(datum/tech/tech_to_add)
+ ASSERT(istype(tech_to_add))
- for(var/datum/tech/known in known_tech)
- k_tech[known.id] = known.level
+ var/datum/tech/known_tech_item = known_tech_lookup[tech_to_add.id]
+ if(!known_tech_item)
+ known_tech_lookup[tech_to_add.id] = tech_to_add
+ known_tech += tech_to_add
+ return
- for(var/req in D.req_tech)
- if(isnull(k_tech[req]) || k_tech[req] < D.req_tech[req])
- return 0
+ if(tech_to_add.level > known_tech_item.level)
+ known_tech_item.level = tech_to_add.level
- return 1
+/datum/research/proc/add_design_to_known(datum/design/design_to_add)
+ if(!istype(design_to_add))
+ return
-//Adds a tech to known_tech list. Checks to make sure there aren't duplicates and updates existing tech's levels if needed.
-//Input: datum/tech; Output: Null
-/datum/research/proc/AddTech2Known(datum/tech/T)
- for(var/datum/tech/known in known_tech)
- if(T.id == known.id)
- if(T.level > known.level)
- known.level = T.level
- return
- return
-
-/datum/research/proc/AddDesign2Known(datum/design/D)
- if(!length(known_designs)) // Special case
- known_designs.Add(D)
+ var/datum/design/existing_design = known_designs_lookup[design_to_add.id]
+ if(existing_design)
return
- for(var/i = 1 to length(known_designs))
- var/datum/design/A = known_designs[i]
- if(A.id == D.id) // We are guaranteed to reach this if the ids are the same, because sort_string will also be the same
- return
- if(A.sort_string > D.sort_string)
- known_designs.Insert(i, D)
- return
- known_designs.Add(D)
- return
+
+ known_designs_lookup[design_to_add.id] = design_to_add
+ BINARY_INSERT(design_to_add, known_designs, /datum/design, design_to_add, sort_string, COMPARE_KEY)
+
+/datum/research/proc/remove_design(design_id)
+ ASSERT(design_id)
+
+ var/datum/design/design_to_remove = known_designs_lookup[design_id]
+ if(!design_to_remove)
+ return FALSE
+
+ known_designs_lookup -= design_id
+ known_designs -= design_to_remove
+
+ return TRUE
+
+/datum/research/proc/reset_tech(tech_id)
+ ASSERT(tech_id)
+
+ var/datum/tech/tech_to_reset = known_tech_lookup[tech_id]
+ if(!tech_to_reset)
+ return FALSE
+
+ if(tech_to_reset.level <= 0)
+ return FALSE
+
+ tech_to_reset.level = 1
+ return TRUE
//Refreshes known_tech and known_designs list
//Input/Output: n/a
-/datum/research/proc/RefreshResearch()
- for(var/datum/design/PD in possible_designs)
- if(DesignHasReqs(PD))
- AddDesign2Known(PD)
- for(var/datum/tech/T in known_tech)
- T = clamp(T.level, 0, 20)
- return
+/datum/research/proc/refresh_research()
+ for(var/datum/design/possible_design as anything in possible_designs)
+ if(design_available(possible_design))
+ add_design_to_known(possible_design)
+
+ for(var/datum/tech/tech_to_refresh as anything in known_tech)
+ tech_to_refresh.level = clamp(tech_to_refresh.level, 0, 20)
//Refreshes the levels of a given tech.
//Input: Tech's ID and Level; Output: null
-/datum/research/proc/UpdateTech(ID, level)
+/datum/research/proc/update_tech(tech_id, level)
+ ASSERT(tech_id)
+ ASSERT(level)
+
// If a "brain expansion" event is active, we gain 1 extra level
- for(var/datum/event/E in SSevent.active_events)
- if(istype(E, /datum/event/brain_expansion))
- level += 1
- break
-
- for(var/datum/tech/KT in known_tech)
- if(KT.id == ID && KT.level <= level)
- KT.level = max(KT.level + 1, level - 1)
- return
-
-// A simple helper proc to find the name of a tech with a given ID.
-/proc/CallTechName(ID)
- for(var/T in subtypesof(/datum/tech))
- var/datum/tech/check_tech = T
- if(initial(check_tech.id) == ID)
- return initial(check_tech.name)
+ if(SSevent.is_event_of_type_active(/datum/event/brain_expansion))
+ level++
-/***************************************************************
-** Technology Datums **
-** Includes all the various technoliges and what they make. **
-***************************************************************/
+ var/datum/tech/tech_to_update = known_tech_lookup[tech_id]
+ if(!tech_to_update)
+ stack_trace("Tech with id `[tech_id]` being update, while not present in research tree")
+ return
+
+ if(tech_to_update.level > level)
+ return
-/datum/tech //Datum of individual technologies.
- var/name = "name" //Name of the technology.
- var/desc = "description" //General description of what it does and what it makes.
- var/id = "id" //An easily referenced ID. Must be alphanumeric, lower-case, and no symbols.
- var/level = 1 //A simple number scale of the research level. Level 0 = Secret tech.
-
-/datum/tech/materials
- name = "Materials"
- desc = "Development of new and improved materials."
- id = TECH_MATERIAL
-
-/datum/tech/engineering
- name = "Engineering"
- desc = "Development of new and improved engineering parts."
- id = TECH_ENGINEERING
-
-/datum/tech/phorontech
- name = "Phoron Technology"
- desc = "Manipulation of the mysterious substance colloqually known as 'phoron'."
- id = TECH_PHORON
-
-/datum/tech/powerstorage
- name = "Power Manipulation Technology"
- desc = "The various technologies behind the storage and generation of electicity."
- id = TECH_POWER
-
-/datum/tech/bluespace
- name = "'Blue-space' Technology"
- desc = "Devices that utilize the sub-reality known as 'blue-space'"
- id = TECH_BLUESPACE
-
-/datum/tech/biotech
- name = "Biological Technology"
- desc = "Deeper mysteries of life and organic substances."
- id = TECH_BIO
-
-/datum/tech/combat
- name = "Combat Systems"
- desc = "Offensive and defensive systems."
- id = TECH_COMBAT
-
-/datum/tech/magnets
- name = "Electromagnetic Spectrum Technology"
- desc = "Electromagnetic spectrum and magnetic devices. No clue how they actually work, though."
- id = TECH_MAGNET
-
-/datum/tech/programming
- name = "Data Theory"
- desc = "Computer and artificial intelligence and data storage systems."
- id = TECH_DATA
-
-/datum/tech/esoteric
- name = "Esoteric Technology"
- desc = "A miscellaneous tech category filled with information on non-standard designs, personal projects and half-baked ideas."
- id = TECH_ESOTERIC
- level = 0
-
-/obj/item/disk/tech_disk
- name = "fabricator data disk"
- desc = "A disk for storing fabricator learning data for backup."
- icon = 'icons/obj/cloning.dmi'
- icon_state = "datadisk2"
- item_state = "card-id"
- w_class = ITEM_SIZE_SMALL
- matter = list(MATERIAL_PLASTIC = 30, MATERIAL_STEEL = 30, MATERIAL_GLASS = 10)
- var/datum/tech/stored
-
-
-/obj/item/disk/design_disk
- name = "component design disk"
- desc = "A disk for storing device design data for construction in lathes."
- icon = 'icons/obj/cloning.dmi'
- icon_state = "datadisk2"
- item_state = "card-id"
- w_class = ITEM_SIZE_SMALL
- matter = list(MATERIAL_PLASTIC = 30, MATERIAL_STEEL = 30, MATERIAL_GLASS = 10)
- var/datum/design/blueprint
+ tech_to_update.level = max(tech_to_update.level + 1, level - 1)
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index 11bb0f534825b..48d71fdfc43b0 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -1,6 +1,6 @@
/obj/machinery/r_n_d/server
- name = "R&D Server"
- icon = 'icons/obj/machines/research.dmi'
+ name = "\improper R&D server"
+ icon = 'icons/obj/machines/research/server.dmi'
icon_state = "server"
base_type = /obj/machinery/r_n_d/server
construct_state = /singleton/machine_construction/default/panel_closed
@@ -18,11 +18,11 @@
var/delay = 10
req_access = list(access_rd) //Only the R&D can change server settings.
-/obj/machinery/r_n_d/server/RefreshParts()
- var/tot_rating = 0
- for(var/obj/item/stock_parts/SP in src)
- tot_rating += SP.rating
- change_power_consumption(initial(idle_power_usage)/max(1, tot_rating), POWER_USE_IDLE)
+
+/obj/machinery/r_n_d/server/Destroy()
+ QDEL_NULL(files)
+ return ..()
+
/obj/machinery/r_n_d/server/Initialize()
. = ..()
@@ -39,6 +39,36 @@
temp_list = splittext(id_with_download_string, ";")
for(var/N in temp_list)
id_with_download += text2num(N)
+ update_icon()
+
+
+/obj/machinery/r_n_d/server/operable()
+ return !inoperable(MACHINE_STAT_EMPED)
+
+
+/obj/machinery/r_n_d/server/on_update_icon()
+ ClearOverlays()
+ if (operable())
+ AddOverlays(list(
+ "server_on",
+ "server_lights_on",
+ emissive_appearance(icon, "server_lights_on"),
+ ))
+ else
+ AddOverlays(list(
+ "server_lights_off",
+ emissive_appearance(icon, "server_lights_off")
+ ))
+ if (panel_open)
+ AddOverlays("server_panel")
+
+
+/obj/machinery/r_n_d/server/RefreshParts()
+ var/tot_rating = 0
+ for(var/obj/item/stock_parts/SP in src)
+ tot_rating += SP.rating
+ change_power_consumption(initial(idle_power_usage)/max(1, tot_rating), POWER_USE_IDLE)
+
/obj/machinery/r_n_d/server/Process()
..()
@@ -52,15 +82,17 @@
health = max(0, health - 1)
if(health <= 0)
files.known_designs = list()
- for(var/datum/tech/T in files.known_tech)
+ for(var/datum/tech/known_tech as anything in files.known_tech)
if(prob(1))
- T.level--
- files.RefreshResearch()
+ known_tech.level--
+
+ files.refresh_research()
if(delay)
delay--
else
produce_heat()
delay = initial(delay)
+ update_icon()
/obj/machinery/r_n_d/server/proc/produce_heat()
if(!produces_heat)
@@ -86,13 +118,13 @@
env.merge(removed)
/obj/machinery/r_n_d/server/centcom
- name = "Central R&D Database"
+ name = "central R&D database"
server_id = -1
/obj/machinery/r_n_d/server/centcom/proc/update_connections()
var/list/no_id_servers = list()
var/list/server_ids = list()
- for(var/obj/machinery/r_n_d/server/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server))
switch(S.server_id)
if(-1)
continue
@@ -115,7 +147,7 @@
return PROCESS_KILL //don't need process()
/obj/machinery/computer/rdservercontrol
- name = "R&D Server Controller"
+ name = "\improper R&D server controller"
icon_keyboard = "rd_key"
icon_screen = "rdcomp"
light_color = "#a97faa"
@@ -142,20 +174,20 @@
temp_server = null
consoles = list()
servers = list()
- for(var/obj/machinery/r_n_d/server/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server))
if(S.server_id == text2num(href_list["access"]) || S.server_id == text2num(href_list["data"]) || S.server_id == text2num(href_list["transfer"]))
temp_server = S
break
if(href_list["access"])
screen = 1
- for(var/obj/machinery/computer/rdconsole/C in SSmachines.machinery)
+ for(var/obj/machinery/computer/rdconsole/C as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/rdconsole))
if(C.sync)
consoles += C
else if(href_list["data"])
screen = 2
else if(href_list["transfer"])
screen = 3
- for(var/obj/machinery/r_n_d/server/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server))
if(S == src)
continue
servers += S
@@ -179,23 +211,35 @@
else if(href_list["reset_tech"])
var/choice = alert(user, "Technology Data Reset", "Are you sure you want to reset this technology to its default data? Data lost cannot be recovered.", "Continue", "Cancel")
- if(choice == "Continue" && CanUseTopic(user, state))
- for(var/datum/tech/T in temp_server.files.known_tech)
- if(T.level > 0 && T.id == href_list["reset_tech"])
- T.level = 1
- break
- temp_server.files.RefreshResearch()
- . = TOPIC_REFRESH
+ if(choice != "Continue" || !CanUseTopic(user, state))
+ return TOPIC_NOACTION
+
+ var/tech_to_reset_id = href_list["reset_tech"]
+ if(!tech_to_reset_id)
+ return TOPIC_NOACTION
+
+ var/reset_successfully = temp_server.files.reset_tech(tech_to_reset_id)
+ if(!reset_successfully)
+ return TOPIC_NOACTION
+
+ temp_server.files.refresh_research()
+ return TOPIC_REFRESH
else if(href_list["reset_design"])
var/choice = alert(user, "Design Data Deletion", "Are you sure you want to delete this design? If you still have the prerequisites for the design, it'll reset to its base reliability. Data lost cannot be recovered.", "Continue", "Cancel")
- if(choice == "Continue" && CanUseTopic(user, state))
- for(var/datum/design/D in temp_server.files.known_designs)
- if(D.id == href_list["reset_design"])
- temp_server.files.known_designs -= D
- break
- temp_server.files.RefreshResearch()
- . = TOPIC_REFRESH
+ if(choice != "Continue" || !CanUseTopic(user, state))
+ return TOPIC_NOACTION
+
+ var/design_to_reset_id = href_list["reset_design"]
+ if(!design_to_reset_id)
+ return TOPIC_NOACTION
+
+ var/removed_successfully = temp_server.files.remove_design(design_to_reset_id)
+ if(!removed_successfully)
+ return TOPIC_NOACTION
+
+ temp_server.files.refresh_research()
+ return TOPIC_REFRESH
/obj/machinery/computer/rdservercontrol/interface_interact(mob/user)
interact(user)
@@ -209,7 +253,7 @@
if(0) //Main Menu
dat += "Connected Servers:
"
var/turf/T = get_turf(src)
- for(var/obj/machinery/r_n_d/server/S in SSmachines.machinery)
+ for(var/obj/machinery/r_n_d/server/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/r_n_d/server))
var/turf/ST = get_turf(S)
if((istype(S, /obj/machinery/r_n_d/server/centcom) && !badmin) || (ST && !AreConnectedZLevels(ST.z, T.z)))
continue
@@ -242,9 +286,9 @@
if(2) //Data Management menu
dat += "[temp_server.name] Data ManagementP
"
dat += "Known Technologies "
- for(var/datum/tech/T in temp_server.files.known_tech)
- dat += "* [T.name] "
- dat += "(Reset) " //FYI, these are all strings.
+ for(var/datum/tech/known_tech as anything in temp_server.files.known_tech)
+ dat += "* [known_tech.name] "
+ dat += "(Reset) " //FYI, these are all strings.
dat += "Known Designs "
for(var/datum/design/D in temp_server.files.known_designs)
dat += "* [D.name] "
@@ -266,18 +310,18 @@
playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1)
emagged = TRUE
req_access.Cut()
- to_chat(user, SPAN_NOTICE("You you disable the security protocols."))
+ to_chat(user, SPAN_NOTICE("You disable the security protocols."))
src.updateUsrDialog()
return 1
/obj/machinery/r_n_d/server/robotics
- name = "Robotics R&D Server"
- id_with_upload_string = "1;2"
- id_with_download_string = "1;2"
+ name = "robotics R&D server"
+ id_with_upload_string = "1;2;3"
+ id_with_download_string = "1;2;3"
server_id = 2
/obj/machinery/r_n_d/server/core
- name = "Core R&D Server"
+ name = "core R&D server"
id_with_upload_string = "1;3"
id_with_download_string = "1;3"
server_id = 1
diff --git a/code/modules/research/tech.dm b/code/modules/research/tech.dm
new file mode 100644
index 0000000000000..d69d646b24b65
--- /dev/null
+++ b/code/modules/research/tech.dm
@@ -0,0 +1,82 @@
+/***************************************************************
+** Technology Datums **
+** Includes all the various technoliges and what they make. **
+***************************************************************/
+
+/datum/tech //Datum of individual technologies.
+ var/name = "name" //Name of the technology.
+ var/desc = "description" //General description of what it does and what it makes.
+ var/id = "id" //An easily referenced ID. Must be alphanumeric, lower-case, and no symbols.
+ var/level = 1 //A simple number scale of the research level. Level 0 = Secret tech.
+
+/datum/tech/materials
+ name = "Materials"
+ desc = "Development of new and improved materials."
+ id = TECH_MATERIAL
+
+/datum/tech/engineering
+ name = "Engineering"
+ desc = "Development of new and improved engineering parts."
+ id = TECH_ENGINEERING
+
+/datum/tech/phorontech
+ name = "Phoron Technology"
+ desc = "Manipulation of the mysterious substance colloqually known as 'phoron'."
+ id = TECH_PHORON
+
+/datum/tech/powerstorage
+ name = "Power Manipulation Technology"
+ desc = "The various technologies behind the storage and generation of electicity."
+ id = TECH_POWER
+
+/datum/tech/bluespace
+ name = "'Blue-space' Technology"
+ desc = "Devices that utilize the sub-reality known as 'blue-space'"
+ id = TECH_BLUESPACE
+
+/datum/tech/biotech
+ name = "Biological Technology"
+ desc = "Deeper mysteries of life and organic substances."
+ id = TECH_BIO
+
+/datum/tech/combat
+ name = "Combat Systems"
+ desc = "Offensive and defensive systems."
+ id = TECH_COMBAT
+
+/datum/tech/magnets
+ name = "Electromagnetic Spectrum Technology"
+ desc = "Electromagnetic spectrum and magnetic devices. No clue how they actually work, though."
+ id = TECH_MAGNET
+
+/datum/tech/programming
+ name = "Data Theory"
+ desc = "Computer and artificial intelligence and data storage systems."
+ id = TECH_DATA
+
+/datum/tech/esoteric
+ name = "Esoteric Technology"
+ desc = "A miscellaneous tech category filled with information on non-standard designs, personal projects and half-baked ideas."
+ id = TECH_ESOTERIC
+ level = 0
+
+/obj/item/disk/tech_disk
+ name = "fabricator data disk"
+ desc = "A disk for storing fabricator learning data for backup."
+ icon = 'icons/obj/datadisks.dmi'
+ icon_state = "datadisk2"
+ item_state = "card-id"
+ w_class = ITEM_SIZE_SMALL
+ matter = list(MATERIAL_PLASTIC = 30, MATERIAL_STEEL = 30, MATERIAL_GLASS = 10)
+ var/datum/tech/stored
+
+
+/obj/item/disk/design_disk
+ name = "component design disk"
+ desc = "A disk for storing device design data for construction in lathes."
+ icon = 'icons/obj/datadisks.dmi'
+ icon_state = "datadisk2"
+ item_state = "card-id"
+ w_class = ITEM_SIZE_SMALL
+ matter = list(MATERIAL_PLASTIC = 30, MATERIAL_STEEL = 30, MATERIAL_GLASS = 10)
+ var/datum/design/blueprint
diff --git a/code/modules/security levels/keycard authentication.dm b/code/modules/security levels/keycard authentication.dm
index 38e80e37bb9ea..dba016cb8ff0c 100644
--- a/code/modules/security levels/keycard authentication.dm
+++ b/code/modules/security levels/keycard authentication.dm
@@ -1,7 +1,7 @@
/obj/machinery/keycard_auth
- name = "Keycard Authentication Device"
+ name = "keycard authentication device"
desc = "This device is used to trigger functions which require more than one ID card to authenticate."
- icon = 'icons/obj/monitors.dmi'
+ icon = 'icons/obj/structures/keycard_authenticator.dmi'
icon_state = "auth_off"
var/active = 0 //This gets set to 1 on all devices except the one where the initial request was made.
var/event = ""
@@ -23,11 +23,12 @@
to_chat(user, SPAN_WARNING("A firewall prevents you from interfacing with this device!"))
return
-/obj/machinery/keycard_auth/attackby(obj/item/W as obj, mob/user as mob)
+/obj/machinery/keycard_auth/use_tool(obj/item/W, mob/living/user, list/click_params)
if(inoperable())
to_chat(user, "This device is not powered.")
- return
- if(istype(W,/obj/item/card/id))
+ return TRUE
+
+ if (isid(W))
var/obj/item/card/id/ID = W
if(access_keycard_auth in ID.access)
if(active == 1)
@@ -40,6 +41,9 @@
else if(screen == 2)
event_triggered_by = usr
broadcast_request() //This is the device making the initial event request. It needs to broadcast to other devices
+ return TRUE
+
+ return ..()
//icon_state gets set everwhere besides here, that needs to be fixed sometime
/obj/machinery/keycard_auth/on_update_icon()
@@ -120,7 +124,7 @@
/obj/machinery/keycard_auth/proc/broadcast_request()
icon_state = "auth_on"
- for(var/obj/machinery/keycard_auth/KA in world)
+ for(var/obj/machinery/keycard_auth/KA as anything in SSmachines.get_machinery_of_type(/obj/machinery/keycard_auth))
if(KA == src) continue
KA.reset()
spawn()
@@ -168,7 +172,7 @@
trigger_armed_response_team(1)
if("Grant Nuclear Authorization Code")
- var/obj/machinery/nuclearbomb/nuke = locate(/obj/machinery/nuclearbomb/station) in world
+ var/obj/machinery/nuclearbomb/nuke = locate(/obj/machinery/nuclearbomb/station) in SSmachines.get_machinery_of_type(/obj/machinery/nuclearbomb/station)
if(nuke)
to_chat(usr, "The nuclear authorization code is [nuke.r_code]")
else
diff --git a/code/modules/shield_generators/floor_diffuser.dm b/code/modules/shield_generators/floor_diffuser.dm
index e26d51051fc8e..d267fd9a31ac1 100644
--- a/code/modules/shield_generators/floor_diffuser.dm
+++ b/code/modules/shield_generators/floor_diffuser.dm
@@ -30,7 +30,7 @@
return
for(var/direction in GLOB.cardinal)
var/turf/simulated/shielded_tile = get_step(get_turf(src), direction)
- for(var/obj/effect/shield/S in shielded_tile)
+ for(var/obj/shield/S in shielded_tile)
S.diffuse(5)
/obj/machinery/shield_diffuser/on_update_icon()
@@ -64,6 +64,6 @@
/obj/machinery/shield_diffuser/examine(mob/user)
. = ..()
- to_chat(user, "It is [enabled ? "enabled" : "disabled"].")
+ . += SPAN_NOTICE("It is [enabled ? "enabled" : "disabled"].")
if(alarm)
- to_chat(user, "A red LED labeled \"Proximity Alarm\" is blinking on the control panel.")
+ . += SPAN_NOTICE("A red LED labeled \"Proximity Alarm\" is blinking on the control panel.")
diff --git a/code/modules/shield_generators/handheld_diffuser.dm b/code/modules/shield_generators/handheld_diffuser.dm
index f9702d408f90c..4464dfefd08c4 100644
--- a/code/modules/shield_generators/handheld_diffuser.dm
+++ b/code/modules/shield_generators/handheld_diffuser.dm
@@ -32,7 +32,7 @@
for(var/direction in GLOB.cardinal)
var/turf/simulated/shielded_tile = get_step(get_turf(src), direction)
- for(var/obj/effect/shield/S in shielded_tile)
+ for(var/obj/shield/S in shielded_tile)
// 10kJ per pulse, but gap in the shield lasts for longer than regular diffusers.
if(istype(S) && !S.diffused_for && !S.disabled_for && cell.checked_use(10 KILOWATTS * CELLRATE))
S.diffuse(20)
@@ -48,5 +48,5 @@
/obj/item/shield_diffuser/examine(mob/user)
. = ..()
- to_chat(user, "The charge meter reads [cell ? cell.percent() : 0]%")
- to_chat(user, "It is [enabled ? "enabled" : "disabled"].")
+ . += SPAN_NOTICE("The charge meter reads [cell ? cell.percent() : 0]%")
+ . += SPAN_NOTICE("It is [enabled ? "enabled" : "disabled"].")
diff --git a/code/modules/shield_generators/shield.dm b/code/modules/shield_generators/shield.dm
index 1940ce7e185d5..ac6f14f1d3194 100644
--- a/code/modules/shield_generators/shield.dm
+++ b/code/modules/shield_generators/shield.dm
@@ -1,4 +1,4 @@
-/obj/effect/shield
+/obj/shield
name = "energy shield"
desc = "An impenetrable field of energy, capable of blocking anything as long as it's active."
icon = 'icons/obj/machines/shielding.dmi'
@@ -13,7 +13,7 @@
atmos_canpass = CANPASS_PROC
-/obj/effect/shield/on_update_icon()
+/obj/shield/on_update_icon()
if(gen && gen.check_flag(MODEFLAG_PHOTONIC) && !disabled_for && !diffused_for)
set_opacity(1)
else
@@ -28,18 +28,18 @@
// Prevents shuttles, singularities and pretty much everything else from moving the field segments away.
// The only thing that is allowed to move us is the Destroy() proc.
-/obj/effect/shield/forceMove()
+/obj/shield/forceMove()
if(QDELING(src))
return ..()
return 0
-/obj/effect/shield/New()
+/obj/shield/New()
..()
update_nearby_tiles()
-/obj/effect/shield/Initialize(mapload, obj/machinery/power/shield_generator/new_gen)
+/obj/shield/Initialize(mapload, obj/machinery/power/shield_generator/new_gen)
. = ..(mapload)
if (QDELETED(new_gen))
@@ -48,7 +48,7 @@
gen = new_gen
-/obj/effect/shield/Destroy()
+/obj/shield/Destroy()
. = ..()
if(gen)
if(src in gen.field_segments)
@@ -61,7 +61,7 @@
// Temporarily collapses this shield segment.
-/obj/effect/shield/proc/fail(duration)
+/obj/shield/proc/fail(duration)
if(duration <= 0)
return
@@ -76,7 +76,7 @@
// Regenerates this shield segment.
-/obj/effect/shield/proc/regenerate()
+/obj/shield/proc/regenerate()
if(!gen)
return
@@ -92,7 +92,7 @@
gen.damaged_segments -= src
-/obj/effect/shield/proc/diffuse(duration)
+/obj/shield/proc/diffuse(duration)
if (!gen)
return
@@ -109,7 +109,7 @@
update_icon()
update_explosion_resistance()
-/obj/effect/shield/attack_generic(source, damage, emote)
+/obj/shield/attack_generic(source, damage, emote)
take_damage(damage, SHIELD_DAMTYPE_PHYSICAL)
if(gen?.check_flag(MODEFLAG_OVERCHARGE) && istype(source, /mob/living))
overcharge_shock(source)
@@ -117,21 +117,21 @@
// Fails shield segments in specific range. Range of 1 affects the shielded turf only.
-/obj/effect/shield/proc/fail_adjacent_segments(range, hitby = null)
+/obj/shield/proc/fail_adjacent_segments(range, hitby = null)
if(hitby)
visible_message(SPAN_DANGER("\The [src] flashes a bit as \the [hitby] collides with it, eventually fading out in a rain of sparks!"))
else
visible_message(SPAN_DANGER("\The [src] flashes a bit as it eventually fades out in a rain of sparks!"))
fail(range * 2)
- for(var/obj/effect/shield/S in range(range, src))
+ for(var/obj/shield/S in range(range, src))
// Don't affect shields owned by other shield generators
if(S.gen != src.gen)
continue
// The closer we are to impact site, the longer it takes for shield to come back up.
S.fail(-(-range + get_dist(src, S)) * 2)
-/obj/effect/shield/proc/take_damage(damage, damtype, hitby)
+/obj/shield/proc/take_damage(damage, damtype, hitby)
if(!gen)
qdel(src)
return
@@ -141,7 +141,7 @@
damage = round(damage)
- new /obj/effect/temporary(get_turf(src), 2 SECONDS,'icons/obj/machines/shielding.dmi',"shield_impact")
+ new /obj/temporary(get_turf(src), 2 SECONDS,'icons/obj/machines/shielding.dmi',"shield_impact")
impact_effect(round(abs(damage * 2)))
var/list/field_segments = gen.field_segments
@@ -159,13 +159,13 @@
return
if(SHIELD_BREACHED_FAILURE)
fail_adjacent_segments(rand(8, 16), hitby)
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
S.fail(1)
return
// As we have various shield modes, this handles whether specific things can pass or not.
-/obj/effect/shield/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+/obj/shield/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
// Somehow we don't have a generator. This shouldn't happen. Delete the shield.
if(!gen)
qdel(src)
@@ -183,31 +183,31 @@
return 1
-/obj/effect/shield/c_airblock(turf/other)
+/obj/shield/c_airblock(turf/other)
return gen?.check_flag(MODEFLAG_ATMOSPHERIC) ? BLOCKED : 0
// EMP. It may seem weak but keep in mind that multiple shield segments are likely to be affected.
-/obj/effect/shield/emp_act(severity)
+/obj/shield/emp_act(severity)
if(!disabled_for)
take_damage(rand(30,60) / severity, SHIELD_DAMTYPE_EM)
..()
// Explosions
-/obj/effect/shield/ex_act(severity)
+/obj/shield/ex_act(severity)
if(!disabled_for)
take_damage(rand(10,15) / severity, SHIELD_DAMTYPE_PHYSICAL)
// Fire
-/obj/effect/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
+/obj/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(!disabled_for)
take_damage(rand(5,10), SHIELD_DAMTYPE_HEAT)
// Projectiles
-/obj/effect/shield/bullet_act(obj/item/projectile/proj)
+/obj/shield/bullet_act(obj/item/projectile/proj)
if (proj.damage_type == DAMAGE_BURN)
take_damage(proj.get_structure_damage(), SHIELD_DAMTYPE_HEAT)
else if (proj.damage_type == DAMAGE_BRUTE)
@@ -216,25 +216,33 @@
take_damage(proj.get_structure_damage(), SHIELD_DAMTYPE_EM)
-// Attacks with hand tools. Blocked by Hyperkinetic flag.
-/obj/effect/shield/attackby(obj/item/I as obj, mob/user as mob)
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+/obj/shield/use_weapon(obj/item/weapon, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE) // Fully handled here
+ user.setClickCooldown(user.get_attack_speed(weapon))
user.do_attack_animation(src)
-
- if(gen.check_flag(MODEFLAG_HYPERKINETIC))
- user.visible_message(SPAN_DANGER("\The [user] hits \the [src] with \the [I]!"))
- if (I.damtype == DAMAGE_BURN)
- take_damage(I.force, SHIELD_DAMTYPE_HEAT)
- else if (I.damtype == DAMAGE_BRUTE)
- take_damage(I.force, SHIELD_DAMTYPE_PHYSICAL)
+ playsound(src, weapon.hitsound, 50, TRUE)
+ if (!gen.check_flag(MODEFLAG_HYPERKINETIC))
+ user.visible_message(
+ SPAN_WARNING("\The [user] tries to attack \the [src] with \a [weapon], but it passes through!"),
+ SPAN_WARNING("You try to attack \the [src] with \the [weapon], but it passes through!")
+ )
+ return TRUE
+ user.visible_message(
+ SPAN_DANGER("\The [user] hits \the [src] with \a [weapon]!"),
+ SPAN_DANGER("You hit \the [src] with \the [weapon]!")
+ )
+ switch (weapon.damtype)
+ if (DAMAGE_BURN)
+ take_damage(weapon.force, SHIELD_DAMTYPE_HEAT)
+ if (DAMAGE_BRUTE)
+ take_damage(weapon.force, SHIELD_DAMTYPE_PHYSICAL)
else
- take_damage(I.force, SHIELD_DAMTYPE_EM)
- else
- user.visible_message(SPAN_DANGER("\The [user] tries to attack \the [src] with \the [I], but it passes through!"))
+ take_damage(weapon.force, SHIELD_DAMTYPE_EM)
+ return TRUE
// Special treatment for meteors because they would otherwise penetrate right through the shield.
-/obj/effect/shield/Bumped(atom/movable/mover)
+/obj/shield/Bumped(atom/movable/mover)
if(!gen)
qdel(src)
return 0
@@ -243,14 +251,14 @@
return ..()
-/obj/effect/shield/proc/overcharge_shock(mob/living/M)
+/obj/shield/proc/overcharge_shock(mob/living/M)
M.adjustFireLoss(rand(20, 40))
M.Weaken(5)
to_chat(M, SPAN_DANGER("As you come into contact with \the [src] a surge of energy paralyses you!"))
take_damage(10, SHIELD_DAMTYPE_EM)
// Called when a flag is toggled. Can be used to add on-toggle behavior, such as visual changes.
-/obj/effect/shield/proc/flags_updated()
+/obj/shield/proc/flags_updated()
if(!gen)
qdel(src)
return
@@ -260,13 +268,13 @@
update_icon()
update_explosion_resistance()
-/obj/effect/shield/proc/update_explosion_resistance()
+/obj/shield/proc/update_explosion_resistance()
if(gen && gen.check_flag(MODEFLAG_HYPERKINETIC))
explosion_resistance = INFINITY
else
explosion_resistance = 0
-/obj/effect/shield/get_explosion_resistance()
+/obj/shield/get_explosion_resistance()
return explosion_resistance
// Shield collision checks below
@@ -300,15 +308,15 @@
// Shield on-impact logic here. This is called only if the object is actually blocked by the field (can_pass_shield applies first)
-/atom/movable/proc/shield_impact(obj/effect/shield/S)
+/atom/movable/proc/shield_impact(obj/shield/S)
return
-/mob/living/shield_impact(obj/effect/shield/S)
+/mob/living/shield_impact(obj/shield/S)
if(!S.gen.check_flag(MODEFLAG_OVERCHARGE))
return
S.overcharge_shock(src)
-/obj/effect/meteor/shield_impact(obj/effect/shield/S)
+/obj/meteor/shield_impact(obj/shield/S)
if(!S.gen.check_flag(MODEFLAG_HYPERKINETIC))
return
S.take_damage(get_shield_damage(), SHIELD_DAMTYPE_PHYSICAL, src)
@@ -317,7 +325,7 @@
qdel(src)
// Small visual effect, makes the shield tiles brighten up by changing color for a moment, and spreads to nearby shields.
-/obj/effect/shield/proc/impact_effect(i, list/affected_shields = list())
+/obj/shield/proc/impact_effect(i, list/affected_shields = list())
i = clamp(i, 1, 10)
var/backcolor = color
if(gen && gen.check_flag(MODEFLAG_OVERCHARGE))
@@ -328,16 +336,16 @@
affected_shields |= src
i--
if(i)
- addtimer(new Callback(src, .proc/spread_impact_effect, i, affected_shields), 2)
+ addtimer(CALLBACK(src, PROC_REF(spread_impact_effect), i, affected_shields), 2)
-/obj/effect/shield/proc/spread_impact_effect(i, list/affected_shields = list())
+/obj/shield/proc/spread_impact_effect(i, list/affected_shields = list())
for(var/direction in GLOB.cardinal)
var/turf/T = get_step(src, direction)
if(T) // Incase we somehow stepped off the map.
- for(var/obj/effect/shield/F in T)
+ for(var/obj/shield/F in T)
if(!(F in affected_shields))
F.impact_effect(i, affected_shields) // Spread the effect to them
-/obj/effect/shield/attack_hand(mob/living/user)
+/obj/shield/attack_hand(mob/living/user)
impact_effect(3) // Harmless, but still produces the 'impact' effect.
..()
diff --git a/code/modules/shield_generators/shield_generator.dm b/code/modules/shield_generators/shield_generator.dm
index 6bad01b195149..b35aebba6929a 100644
--- a/code/modules/shield_generators/shield_generator.dm
+++ b/code/modules/shield_generators/shield_generator.dm
@@ -84,7 +84,7 @@
// Shuts down the shield, removing all shield segments and unlocking generator settings.
/obj/machinery/power/shield_generator/proc/shutdown_field()
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
qdel(S)
running = SHIELD_OFF
@@ -98,7 +98,7 @@
// Generates the field objects. Deletes existing field, if applicable.
/obj/machinery/power/shield_generator/proc/regenerate_field()
if(length(field_segments))
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
qdel(S)
// The generator is not turned on, so don't generate any new tiles.
@@ -114,16 +114,16 @@
// Rotate shield's animation relative to located ship
if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/ship/sector = map_sectors["[src.z]"]
+ var/obj/overmap/visitable/ship/sector = map_sectors["[src.z]"]
if(sector && istype(sector))
if(!sector.check_ownership(src))
- for(var/obj/effect/overmap/visitable/ship/candidate in sector)
+ for(var/obj/overmap/visitable/ship/candidate in sector)
if(candidate.check_ownership(src))
sector = candidate
vessel_reverse_dir = GLOB.reverse_dir[sector.fore_dir]
for(var/turf/T in shielded_turfs)
- var/obj/effect/shield/S = new(T, src)
+ var/obj/shield/S = new(T, src)
S.flags_updated()
field_segments |= S
S.set_dir(vessel_reverse_dir)
@@ -195,7 +195,7 @@
energy_failure()
if(!overloaded)
- for(var/obj/effect/shield/S in damaged_segments)
+ for(var/obj/shield/S in damaged_segments)
S.regenerate()
else if (field_integrity() > 25)
overloaded = 0
@@ -210,11 +210,15 @@
return SPAN_NOTICE("Wait until \the [src] cools down from emergency shutdown first!")
return ..()
-/obj/machinery/power/shield_generator/attackby(obj/item/O as obj, mob/user as mob)
- if(panel_open && (isMultitool(O) || isWirecutter(O)))
+/obj/machinery/power/shield_generator/multitool_act(mob/living/user, obj/item/tool)
+ if(panel_open)
+ . = ITEM_INTERACT_SUCCESS
+ attack_hand(user)
+
+/obj/machinery/power/shield_generator/wirecutter_act(mob/living/user, obj/item/tool)
+ if(panel_open)
+ . = ITEM_INTERACT_SUCCESS
attack_hand(user)
- return TRUE
- return component_attackby(O, user)
/obj/machinery/power/shield_generator/proc/energy_failure()
if(running == SHIELD_DISCHARGING)
@@ -222,7 +226,7 @@
else
current_energy = 0
overloaded = 1
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
S.fail(1)
/obj/machinery/power/shield_generator/proc/set_idle(new_state)
@@ -230,7 +234,7 @@
if(running == SHIELD_IDLE)
return
running = SHIELD_IDLE
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
qdel(S)
else
if(running != SHIELD_IDLE)
@@ -417,7 +421,7 @@
/obj/machinery/power/shield_generator/proc/toggle_flag(flag)
shield_modes ^= flag
update_upkeep_multiplier()
- for(var/obj/effect/shield/S in field_segments)
+ for(var/obj/shield/S in field_segments)
S.flags_updated()
if((flag & (MODEFLAG_HULL|MODEFLAG_MULTIZ)) && (running == SHIELD_RUNNING))
@@ -473,26 +477,21 @@
/obj/machinery/power/shield_generator/proc/fieldtype_hull()
- set background = 1
. = list()
- var/list/base_turfs = get_base_turfs()
-
-
-
+ var/list/base_turfs = get_base_turfs()
for(var/turf/gen_turf in base_turfs)
var/area/TA = null // Variable for area checking. Defining it here so memory does not have to be allocated repeatedly.
- for(var/turf/T in trange(field_radius, gen_turf))
+ for(var/turf/T as anything in RANGE_TURFS(gen_turf, field_radius))
// Don't expand to space or on shuttle areas.
if(istype(T, /turf/space) || istype(T, /turf/simulated/open))
continue
// Find adjacent space/shuttle tiles and cover them. Shuttles won't be blocked if shield diffuser is mapped in and turned on.
- for(var/turf/TN in orange(1, T))
+ for(var/turf/TN as anything in ORANGE_TURFS(T, 1))
TA = get_area(TN)
- if ((istype(TN, /turf/space) || (istype(TN, /turf/simulated/open) && (istype(TA, /area/space) || TA.area_flags & AREA_FLAG_EXTERNAL))))
+ if(isspaceturf(TN) || (isopenturf(TN) && (isspace(TA) || TA.area_flags & AREA_FLAG_EXTERNAL)))
. |= TN
- continue
// Returns a list of turfs from which a field will propagate. If multi-Z mode is enabled, this will return a "column" of turfs above and below the generator.
/obj/machinery/power/shield_generator/proc/get_base_turfs()
diff --git a/code/modules/shieldgen/emergency_shield.dm b/code/modules/shieldgen/emergency_shield.dm
index 44f506ce3648e..ed1a50d0eeeb6 100644
--- a/code/modules/shieldgen/emergency_shield.dm
+++ b/code/modules/shieldgen/emergency_shield.dm
@@ -1,5 +1,5 @@
/obj/machinery/shield
- name = "Emergency energy shield"
+ name = "emergency energy shield"
desc = "An energy shield used to contain hull breaches."
icon = 'icons/effects/effects.dmi'
icon_state = "shield-old"
@@ -37,20 +37,20 @@
/obj/machinery/shield/post_health_change(health_mod, prior_health, damage_type)
. = ..()
- if (health_dead)
+ if (health_dead())
return
if (health_mod < -1) // To prevent slow degradation proccing this constantly
set_opacity(TRUE)
- addtimer(new Callback(src, /atom/proc/set_opacity, FALSE), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, set_opacity), FALSE), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
/obj/machinery/shield/on_death()
visible_message(SPAN_NOTICE("\The [src] dissipates!"))
qdel(src)
/obj/machinery/shieldgen
- name = "Emergency shield projector"
+ name = "emergency shield projector"
desc = "Used to seal minor hull breaches."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/machines/shield_generator.dmi'
icon_state = "shieldoff"
density = TRUE
opacity = 0
@@ -96,15 +96,16 @@
update_use_power(POWER_USE_OFF)
/obj/machinery/shieldgen/proc/create_shields()
- for(var/turf/target_tile in range(8, src))
- if ((istype(target_tile,/turf/space)|| istype(target_tile, /turf/simulated/open)) && !(locate(/obj/machinery/shield) in target_tile))
+ var/turf/center = get_turf(src)
+ for(var/turf/target_tile as anything in RANGE_TURFS(center, 8))
+ if ((isspaceturf(target_tile) || isopenturf(target_tile)) && !(locate(/obj/machinery/shield) in target_tile))
if (malfunction && prob(33) || !malfunction)
var/obj/machinery/shield/S = new/obj/machinery/shield(target_tile)
deployed_shields += S
use_power_oneoff(S.shield_generate_power)
- for(var/turf/above in range(8, GetAbove(src)))//Probably a better way to do this.
- if ((istype(above,/turf/space)|| istype(above, /turf/simulated/open)) && !(locate(/obj/machinery/shield) in above))
+ for(var/turf/above as anything in RANGE_TURFS(GetAbove(src), 8)) //Probably a better way to do this.
+ if ((isspaceturf(above)|| isopenturf(above)) && !(locate(/obj/machinery/shield) in above))
if (malfunction && prob(33) || !malfunction)
var/obj/machinery/shield/A = new/obj/machinery/shield(above)
deployed_shields += A
@@ -195,18 +196,15 @@
update_icon()
return 1
-/obj/machinery/shieldgen/attackby(obj/item/W as obj, mob/user as mob)
- if(isScrewdriver(W))
- playsound(src.loc, 'sound/items/Screwdriver.ogg', 100, 1)
- if(is_open)
- to_chat(user, SPAN_NOTICE("You close the panel."))
- is_open = 0
- else
- to_chat(user, SPAN_NOTICE("You open the panel and expose the wiring."))
- is_open = 1
- return TRUE
+/obj/machinery/shieldgen/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ is_open = !is_open
+ USE_FEEDBACK_NEW_PANEL_OPEN(user, is_open)
- else if(isCoil(W) && malfunction && is_open)
+/obj/machinery/shieldgen/use_tool(obj/item/W, mob/living/user, list/click_params)
+ if(isCoil(W) && malfunction && is_open)
var/obj/item/stack/cable_coil/coil = W
to_chat(user, SPAN_NOTICE("You begin to replace the wires."))
//if(do_after(user, min(60, round( ((maxhealth/health)*10)+(malfunction*10) ))) //Take longer to repair heavier damage
@@ -217,27 +215,9 @@
to_chat(user, SPAN_NOTICE("You repair the [src]!"))
return TRUE
- else if(istype(W, /obj/item/wrench))
- if(locked)
- to_chat(user, "The bolts are covered, unlocking this would retract the covers.")
- return TRUE
- if(anchored)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- to_chat(user, SPAN_NOTICE("'You unsecure the [src] from the floor!"))
- if(active)
- to_chat(user, SPAN_NOTICE("The [src] shuts off!"))
- src.shields_down()
- anchored = FALSE
- else
- if(istype(get_turf(src), /turf/space)) return //No wrenching these in space!
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- to_chat(user, SPAN_NOTICE("You secure the [src] to the floor!"))
- anchored = TRUE
- return TRUE
-
- else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/modular_computer/pda))
- if(src.allowed(user))
- src.locked = !src.locked
+ if (istype(W, /obj/item/card/id) || istype(W, /obj/item/modular_computer/pda))
+ if(allowed(user))
+ locked = !locked
to_chat(user, "The controls are now [src.locked ? "locked." : "unlocked."]")
else
to_chat(user, SPAN_WARNING("Access denied."))
@@ -245,6 +225,16 @@
return ..()
+/obj/machinery/shieldgen/can_anchor(obj/item/tool, mob/user, silent)
+ if(locked)
+ to_chat(user, "The bolts are covered, unlocking this would retract the covers.")
+ return FALSE
+ return ..()
+
+/obj/machinery/shieldgen/post_anchor_change()
+ if (!anchored && active)
+ shields_down()
+ ..()
/obj/machinery/shieldgen/on_update_icon()
if(active && is_powered())
diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm
index 44cfcbb3f36de..f9f15eb3607b3 100644
--- a/code/modules/shieldgen/energy_field.dm
+++ b/code/modules/shieldgen/energy_field.dm
@@ -1,7 +1,7 @@
//---------- actual energy field
-/obj/effect/energy_field
+/obj/energy_field
name = "energy field"
desc = "Impenetrable field of energy, capable of blocking anything as long as it's active."
icon = 'icons/obj/machines/shielding.dmi'
@@ -9,32 +9,32 @@
anchored = TRUE
layer = PROJECTILE_LAYER
density = FALSE
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
var/strength = 0
var/ticks_recovering = 10
-/obj/effect/energy_field/New()
+/obj/energy_field/New()
..()
update_nearby_tiles()
-/obj/effect/energy_field/Destroy()
+/obj/energy_field/Destroy()
set_density(0)
update_nearby_tiles()
. = ..()
-/obj/effect/energy_field/ex_act(severity)
+/obj/energy_field/ex_act(severity)
Stress(0.5 + severity)
-/obj/effect/energy_field/bullet_act(obj/item/projectile/Proj)
+/obj/energy_field/bullet_act(obj/item/projectile/Proj)
Stress(Proj.get_structure_damage() / 10)
-/obj/effect/energy_field/proc/Stress(severity)
+/obj/energy_field/proc/Stress(severity)
strength -= severity
//if we take too much damage, drop out - the generator will bring us back up if we have enough power
ticks_recovering = min(ticks_recovering + 2, 10)
if(strength < 1)
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
set_density(0)
ticks_recovering = 10
strength = 0
@@ -42,7 +42,7 @@
set_invisibility(0)
set_density(1)
-/obj/effect/energy_field/proc/Strengthen(severity)
+/obj/energy_field/proc/Strengthen(severity)
strength += severity
if (strength < 0)
strength = 0
@@ -53,13 +53,13 @@
set_invisibility(0)
set_density(1)
else if(strength < 1)
- set_invisibility(101)
+ set_invisibility(INVISIBILITY_ABSTRACT)
set_density(0)
if (density != old_density)
update_nearby_tiles()
-/obj/effect/energy_field/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0)
+/obj/energy_field/CanPass(atom/movable/mover, turf/target, height=1.5, air_group = 0)
//Purpose: Determines if the object (or airflow) can pass this atom.
//Called by: Movement, airflow.
//Inputs: The moving atom (optional), target turf, "height" and air group
diff --git a/code/modules/shieldgen/shieldwallgen.dm b/code/modules/shieldgen/shieldwallgen.dm
index df5b7e4573e2e..b0aead39ef75e 100644
--- a/code/modules/shieldgen/shieldwallgen.dm
+++ b/code/modules/shieldgen/shieldwallgen.dm
@@ -1,8 +1,8 @@
////FIELD GEN START //shameless copypasta from fieldgen, powersink, and grille
/obj/machinery/shieldwallgen
- name = "Shield Generator"
+ name = "shield generator"
desc = "A shield generator."
- icon = 'icons/obj/stationobjs.dmi'
+ icon = 'icons/obj/machines/shield_generator.dmi'
icon_state = "Shield_Gen"
anchored = FALSE
density = TRUE
@@ -12,7 +12,7 @@
var/locked = 1
var/max_range = 8
var/storedpower = 0
- obj_flags = OBJ_FLAG_CONDUCTIBLE
+ obj_flags = OBJ_FLAG_CONDUCTIBLE | OBJ_FLAG_ANCHORABLE
//There have to be at least two posts, so these are effectively doubled
var/power_draw = 30 KILOWATTS //30 kW. How much power is drawn from powernet. Increase this to allow the generator to sustain longer shields, at the cost of more power draw.
var/max_stored_power = 50 KILOWATTS //50 kW
@@ -194,37 +194,22 @@
var/obj/machinery/shieldwall/CF = new(T, src, G) //(ref to this gen, ref to connected gen)
CF.set_dir(field_dir)
+/obj/machinery/shieldwallgen/can_anchor(obj/item/tool, mob/user, silent)
+ if (active)
+ to_chat(user, SPAN_WARNING("Turn off \the [src] first."))
+ return FALSE
+ ..()
-/obj/machinery/shieldwallgen/attackby(obj/item/W, mob/user)
- if(isWrench(W))
- if(active)
- to_chat(user, "Turn off the field generator first.")
- return
-
- else if(!anchored)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- to_chat(user, "You secure the external reinforcing bolts to the floor.")
- src.anchored = TRUE
- return
-
- else if(anchored)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1)
- to_chat(user, "You undo the external reinforcing bolts.")
- src.anchored = FALSE
- return
-
+/obj/machinery/shieldwallgen/use_tool(obj/item/W, mob/living/user, list/click_params)
if(istype(W, /obj/item/card/id)||istype(W, /obj/item/modular_computer))
- if (src.allowed(user))
- src.locked = !src.locked
+ if (allowed(user))
+ locked = !locked
to_chat(user, "Controls are now [src.locked ? "locked." : "unlocked."]")
else
to_chat(user, SPAN_WARNING("Access denied."))
- return
-
- else
- src.add_fingerprint(user)
- ..()
+ return TRUE
+ return ..()
/obj/machinery/shieldwallgen/proc/cleanup(NSEW)
var/obj/machinery/shieldwall/F
@@ -254,14 +239,14 @@
//////////////Containment Field START
/obj/machinery/shieldwall
- name = "Shield"
+ name = "shield"
desc = "An energy shield."
icon = 'icons/effects/effects.dmi'
icon_state = "shieldwall"
anchored = TRUE
density = TRUE
unacidable = TRUE
- light_outer_range = 3
+ light_range = 3
var/needs_power = 0
var/active = 1
var/delay = 5
@@ -290,13 +275,14 @@
update_nearby_tiles()
..()
-/obj/machinery/shieldwall/attackby(obj/item/I, mob/user)
+/obj/machinery/shieldwall/use_weapon(obj/item/I, mob/living/user, list/click_params)
var/obj/machinery/shieldwallgen/G = prob(50) ? gen_primary : gen_secondary
G.storedpower -= I.force*2500
user.visible_message(SPAN_DANGER("\The [user] hits \the [src] with \the [I]!"))
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ user.setClickCooldown(user.get_attack_speed(I))
user.do_attack_animation(src)
playsound(loc, 'sound/weapons/smash.ogg', 75, 1)
+ return TRUE || ..()
/obj/machinery/shieldwall/Process()
if(needs_power)
diff --git a/code/modules/shuttles/antagonist.dm b/code/modules/shuttles/antagonist.dm
index e7542c6b595d7..6fafc2920fa85 100644
--- a/code/modules/shuttles/antagonist.dm
+++ b/code/modules/shuttles/antagonist.dm
@@ -16,5 +16,4 @@
/obj/machinery/computer/shuttle_control/multi/ninja
name = "stealth shuttle control console"
req_access = list(access_syndicate)
- shuttle_tag = "Ninja"
-
+ shuttle_tag = "Tremulous Intent"
diff --git a/code/modules/shuttles/escape_pods.dm b/code/modules/shuttles/escape_pods.dm
index cf5d4eeebd2d4..c847506e3a6a0 100644
--- a/code/modules/shuttles/escape_pods.dm
+++ b/code/modules/shuttles/escape_pods.dm
@@ -4,15 +4,15 @@ var/global/list/escape_pods_by_name = list()
/datum/shuttle/autodock/ferry/escape_pod
var/datum/computer/file/embedded_program/docking/simple/escape_pod_berth/arming_controller
category = /datum/shuttle/autodock/ferry/escape_pod
- move_time = 100
+ move_time = 10 SECONDS
/datum/shuttle/autodock/ferry/escape_pod/New()
if(name in escape_pods_by_name)
CRASH("An escape pod with the name '[name]' has already been defined.")
- move_time = evacuation_controller.evac_transit_delay + rand(-30, 60)
+ move_time = evacuation_controller.evac_transit_delay + rand(-30, 60) SECONDS
escape_pods_by_name[name] = src
escape_pods += src
- move_time = round(evacuation_controller.evac_transit_delay/10)
+ move_time = round(evacuation_controller.evac_transit_delay / 10) SECONDS
..()
diff --git a/code/modules/shuttles/landmarks.dm b/code/modules/shuttles/landmarks.dm
index f54fe9df2a88c..d37c5df787444 100644
--- a/code/modules/shuttles/landmarks.dm
+++ b/code/modules/shuttles/landmarks.dm
@@ -1,12 +1,12 @@
-//making this separate from /obj/effect/landmark until that mess can be dealt with
-/obj/effect/shuttle_landmark
+//making this separate from /obj/landmark until that mess can be dealt with
+/obj/shuttle_landmark
name = "Nav Point"
icon = 'icons/effects/effects.dmi'
icon_state = "energynet"
anchored = TRUE
unacidable = TRUE
simulated = FALSE
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
var/landmark_tag
//ID of the controller on the dock side
@@ -23,11 +23,11 @@
var/shuttle_restricted
var/flags = 0
-/obj/effect/shuttle_landmark/Initialize()
+
+/obj/shuttle_landmark/Initialize()
. = ..()
if(docking_controller)
. = INITIALIZE_HINT_LATELOAD
-
if(flags & SLANDMARK_FLAG_AUTOSET)
base_area = get_area(src)
var/turf/T = get_turf(src)
@@ -35,11 +35,11 @@
base_turf = T.type
else
base_area = locate(base_area || world.area)
-
SetName(name + " ([x],[y])")
SSshuttle.register_landmark(landmark_tag, src)
-/obj/effect/shuttle_landmark/LateInitialize()
+
+/obj/shuttle_landmark/LateInitialize(mapload)
if(!docking_controller)
return
var/docking_tag = docking_controller
@@ -47,41 +47,50 @@
if(!istype(docking_controller))
CRASH("Could not find docking controller for shuttle waypoint '[name]', docking tag was '[docking_tag]'.")
if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/location = map_sectors["[z]"]
+ var/obj/overmap/visitable/location = map_sectors["[z]"]
if(location && location.docking_codes)
docking_controller.docking_codes = location.docking_codes
-/obj/effect/shuttle_landmark/forceMove()
- var/obj/effect/overmap/visitable/map_origin = map_sectors["[z]"]
+
+/obj/shuttle_landmark/forceMove()
+ var/obj/overmap/visitable/map_origin = map_sectors["[z]"]
. = ..()
- var/obj/effect/overmap/visitable/map_destination = map_sectors["[z]"]
+ var/obj/overmap/visitable/map_destination = map_sectors["[z]"]
if(map_origin != map_destination)
if(map_origin)
map_origin.remove_landmark(src, shuttle_restricted)
if(map_destination)
map_destination.add_landmark(src, shuttle_restricted)
+
//Called when the landmark is added to an overmap sector.
-/obj/effect/shuttle_landmark/proc/sector_set(obj/effect/overmap/visitable/O, shuttle_name)
+/obj/shuttle_landmark/proc/sector_set(obj/overmap/visitable/O, shuttle_name)
shuttle_restricted = shuttle_name
-/obj/effect/shuttle_landmark/proc/is_valid(datum/shuttle/shuttle)
+
+/obj/shuttle_landmark/proc/is_valid(datum/shuttle/shuttle)
if(shuttle.current_location == src)
return FALSE
- for(var/area/A in shuttle.shuttle_area)
- var/list/translation = get_turf_translation(get_turf(shuttle.current_location), get_turf(src), A.contents)
+
+ for(var/area/area_on_shuttle as anything in shuttle.shuttle_area)
+ var/list/translation = get_turf_translation(get_turf(shuttle.current_location), get_turf(src), get_area_turfs(area_on_shuttle))
if(check_collision(base_area, list_values(translation)))
return FALSE
+
var/conn = GetConnectedZlevels(z)
for(var/w in (z - shuttle.multiz) to z)
if(!(w in conn))
return FALSE
return TRUE
-/obj/effect/shuttle_landmark/proc/cannot_depart(datum/shuttle/shuttle)
+
+/obj/shuttle_landmark/proc/cannot_depart(datum/shuttle/shuttle)
return FALSE
-/obj/effect/shuttle_landmark/proc/shuttle_arrived(datum/shuttle/shuttle)
+
+/obj/shuttle_landmark/proc/shuttle_arrived(datum/shuttle/shuttle)
+ return
+
/proc/check_collision(area/target_area, list/target_turfs)
for(var/target_turf in target_turfs)
@@ -94,48 +103,108 @@
return TRUE //dense turf
return FALSE
+
//Self-naming/numbering ones.
-/obj/effect/shuttle_landmark/automatic
+/obj/shuttle_landmark/automatic
name = "Navpoint"
landmark_tag = "navpoint"
flags = SLANDMARK_FLAG_AUTOSET
-/obj/effect/shuttle_landmark/automatic/Initialize()
+
+/obj/shuttle_landmark/automatic/Initialize()
landmark_tag += "-[x]-[y]-[z]-[random_id("landmarks",1,9999)]"
return ..()
-/obj/effect/shuttle_landmark/automatic/sector_set(obj/effect/overmap/visitable/O)
+
+/obj/shuttle_landmark/automatic/sector_set(obj/overmap/visitable/O)
..()
SetName("[O.name] - [initial(name)] ([x],[y])")
+
//Subtype that calls explosion on init to clear space for shuttles
-/obj/effect/shuttle_landmark/automatic/clearing
+/obj/shuttle_landmark/automatic/clearing
var/radius = LANDING_ZONE_RADIUS
-/obj/effect/shuttle_landmark/automatic/clearing/Initialize()
+
+/obj/shuttle_landmark/automatic/clearing/Initialize()
..()
return INITIALIZE_HINT_LATELOAD
-/obj/effect/shuttle_landmark/automatic/clearing/LateInitialize()
+
+/obj/shuttle_landmark/automatic/clearing/LateInitialize(mapload)
..()
- for(var/turf/T in range(radius, src))
+ var/turf/center = get_turf(src)
+ for(var/turf/T as anything in RANGE_TURFS(center, radius))
if(T.density)
T.ChangeTurf(get_base_turf_by_area(T))
-/obj/item/device/spaceflare
+/obj/shuttle_landmark/automatic/beacon
+ name = "Bluespace Beacon Signal"
+ /// The beacon object synced to this landmark. If this is ever null or qdeleted the landmark should delete itself.
+ var/obj/item/shuttle_beacon/beacon
+
+
+/obj/shuttle_landmark/automatic/beacon/Initialize(mapload, obj/item/shuttle_beacon/beacon)
+ . = ..()
+ if (!istype(beacon))
+ log_debug(append_admin_tools("\A [src] was initialized with an invalid or nonexistant beacon", location = get_turf(src)))
+ return INITIALIZE_HINT_QDEL
+ if (beacon.landmark && beacon.landmark != src)
+ log_debug(append_admin_tools("\A [src] was initialized with a beacon already has a synced landmark.", location = get_turf(src)))
+ return INITIALIZE_HINT_QDEL
+ src.beacon = beacon
+ GLOB.moved_event.register(beacon, src, TYPE_PROC_REF(/obj/shuttle_landmark/automatic/beacon, update_beacon_moved))
+
+
+/obj/shuttle_landmark/automatic/beacon/Destroy()
+ GLOB.moved_event.unregister(beacon, src, TYPE_PROC_REF(/obj/shuttle_landmark/automatic/beacon, update_beacon_moved))
+ if (beacon?.active)
+ log_debug(append_admin_tools("\A [src] was destroyed with a still active beacon.", location = get_turf(beacon)))
+ beacon.deactivate()
+ beacon = null
+ return ..()
+
+
+/// Event handler for when the beacon moves. Theoretically possible with a beacon deployed on a shuttle turf, or with adminbus.
+/obj/shuttle_landmark/automatic/beacon/proc/update_beacon_moved(atom/movable/moving_instance, atom/old_loc, atom/new_loc)
+ if (!isturf(new_loc) || isspaceturf(new_loc) || isopenspace(new_loc))
+ log_debug(append_admin_tools("\A [src]'s beacon was moved to a non-turf or unacceptable location.", location = get_turf(new_loc)))
+ beacon.deactivate()
+ return
+ forceMove(new_loc)
+ SetName("[initial(name)] ([x],[y])")
+ log_debug(append_admin_tools("\A [src]'s beacon was moved to [get_area(new_loc)].", location = get_turf(src)))
+
+
+/// Desynchronizes the effect from the beacon, rendering it a permanent landmark.
+/obj/shuttle_landmark/automatic/beacon/proc/desync_beacon()
+ GLOB.moved_event.unregister(beacon, src, TYPE_PROC_REF(/obj/shuttle_landmark/automatic/beacon, update_beacon_moved))
+ if (beacon?.active)
+ beacon.deactivate(TRUE, TRUE)
+ beacon = null
+
+
+/obj/item/shuttle_beacon
name = "shuttle beacon"
desc = "Burst transmitter used to broadcast all needed information for shuttle navigation systems. Has a bright blue light attached for marking the spot where you probably shouldn't be standing."
icon = 'icons/obj/space_flare.dmi'
icon_state = "packaged"
light_color = "#3728ff"
- /// Boolean. Whether or not the spaceflare has been activated.
+
+ /// Boolean. Whether or not the beacon has been activated.
var/active = FALSE
+
/// The shuttle landmark synced to this beacon. This is set when the beacon is activated.
- var/obj/effect/shuttle_landmark/automatic/spaceflare/landmark
+ var/obj/shuttle_landmark/automatic/beacon/landmark
-/obj/item/device/spaceflare/attack_self(mob/user)
+/obj/item/shuttle_beacon/Destroy()
+ deactivate(TRUE)
+ return ..()
+
+
+/obj/item/shuttle_beacon/attack_self(mob/user)
if (activate(user))
user.visible_message(
SPAN_NOTICE("\The [user] plants \the [src] and activates it."),
@@ -144,6 +213,28 @@
)
+/obj/item/shuttle_beacon/on_update_icon()
+ if (active)
+ icon_state = "deployed"
+ var/image/image = image(icon, "active")
+ image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
+ image.layer = ABOVE_LIGHTING_LAYER
+ AddOverlays(image)
+ pixel_x = rand(-6, 6)
+ pixel_y = rand(-6, 6)
+ set_light(7, 0.7, "#85d1ff")
+ else
+ icon_state = initial(icon_state)
+ ClearOverlays()
+ set_light(0)
+
+
+/obj/item/shuttle_beacon/shuttle_land_on()
+ if (active)
+ landmark.desync_beacon()
+ ..()
+
+
/**
* Handles activation of the flare.
*
@@ -152,26 +243,21 @@
*
* Returns boolean - FALSE if the flare was not activated, TRUE if it was.
*/
-/obj/item/device/spaceflare/proc/activate(mob/user)
+/obj/item/shuttle_beacon/proc/activate(mob/user)
if (active)
log_debug(append_admin_tools("\A [src] attempted to activate but was already active.", user, get_turf(src)))
return FALSE
-
var/turf/T = get_turf(src)
if (isspaceturf(T) || isopenspace(T))
if (user)
to_chat(user, SPAN_WARNING("\The [src] needs to be activated on solid ground."))
return FALSE
-
if (istype(user) && !user.unEquip(src, T))
log_debug(append_admin_tools("\A [src] attempted to activate but could not be unequipped by the mob.", user, get_turf(src)))
return FALSE
-
- // Just in case some other weird things happen that try to call activate on a non-turf location
if (loc != T)
log_debug(append_admin_tools("\A [src] attempted to activate but was not on a valid turf.", user, get_turf(src)))
return FALSE
-
if (user)
user.visible_message(
SPAN_ITALIC("\The [user] starts setting up \a [src]."),
@@ -195,94 +281,29 @@
*
* Returns boolean - FALSE if the flare was not deactivated, TRUE if it was.
*/
-/obj/item/device/spaceflare/proc/deactivate(silent = FALSE, keep_landmark = FALSE)
+/obj/item/shuttle_beacon/proc/deactivate(silent, skip_destroy_landmark)
if (!active)
return FALSE
-
active = FALSE
anchored = FALSE
- if (keep_landmark)
- landmark = null
- else
+ if (!skip_destroy_landmark)
QDEL_NULL(landmark)
+ landmark = null
update_icon()
if (!silent)
visible_message(SPAN_WARNING("\The [src] deactivates, going dark."))
return TRUE
-/obj/item/device/spaceflare/on_update_icon()
- if (active)
- icon_state = "deployed"
- var/image/image = image(icon, "active")
- image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
- image.layer = ABOVE_LIGHTING_LAYER
- overlays += image
- pixel_x = rand(-6, 6)
- pixel_y = rand(-6, 6)
- set_light(0.7, 0.1, 7, 2, "#85d1ff")
- else
- icon_state = initial(icon_state)
- overlays.Cut()
- set_light(0)
-
-
-/obj/item/device/spaceflare/Destroy()
- deactivate(TRUE)
- . = ..()
-
-
-/obj/item/device/spaceflare/shuttle_land_on()
- if (active)
- // If a shuttle landed here we don't want to destroy the landmark, that breaks things. It becomes a permanent beacon smushed into the ground instead.
- landmark.desync_flare()
- ..()
-
-
-/obj/effect/shuttle_landmark/automatic/spaceflare
- name = "Bluespace Beacon Signal"
- /// The beacon object synced to this landmark. If this is ever null or qdeleted the landmark should delete itself.
- var/obj/item/device/spaceflare/beacon
+/obj/item/shuttle_beacon/active
+ active = TRUE
+ anchored = TRUE
-/obj/effect/shuttle_landmark/automatic/spaceflare/Initialize(mapload, obj/item/device/spaceflare/beacon)
+/obj/item/shuttle_beacon/active/Initialize()
. = ..()
-
- if (!istype(beacon))
- log_debug(append_admin_tools("\A [src] was initialized with an invalid or nonexistant beacon", location = get_turf(src)))
- return INITIALIZE_HINT_QDEL
-
- if (beacon.landmark && beacon.landmark != src)
- log_debug(append_admin_tools("\A [src] was initialized with a beacon already has a synced landmark.", location = get_turf(src)))
+ var/turf/turf = get_turf(src)
+ if (!turf)
return INITIALIZE_HINT_QDEL
-
- src.beacon = beacon
- GLOB.moved_event.register(beacon, src, /obj/effect/shuttle_landmark/automatic/spaceflare/proc/update_beacon_moved)
-
-
-/obj/effect/shuttle_landmark/automatic/spaceflare/Destroy()
- GLOB.moved_event.unregister(beacon, src, /obj/effect/shuttle_landmark/automatic/spaceflare/proc/update_beacon_moved)
- if (beacon?.active)
- log_debug(append_admin_tools("\A [src] was destroyed with a still active beacon.", location = get_turf(beacon)))
- beacon.deactivate()
- beacon = null
- . = ..()
-
-
-/// Event handler for when the beacon moves. Theoretically possible with a beacon deployed on a shuttle turf, or with adminbus.
-/obj/effect/shuttle_landmark/automatic/spaceflare/proc/update_beacon_moved(atom/movable/moving_instance, atom/old_loc, atom/new_loc)
- if (!isturf(new_loc) || isspaceturf(new_loc) || isopenspace(new_loc))
- log_debug(append_admin_tools("\A [src]'s beacon was moved to a non-turf or unacceptable location.", location = get_turf(new_loc)))
- beacon.deactivate()
- return
- forceMove(new_loc)
- SetName("[initial(name)] ([x],[y])")
- log_debug(append_admin_tools("\A [src]'s beacon was moved to [get_area(new_loc)].", location = get_turf(src)))
-
-
-/// Desynchronizes the effect from the beacon, rendering it a permanent landmark.
-/obj/effect/shuttle_landmark/automatic/spaceflare/proc/desync_flare()
- GLOB.moved_event.unregister(beacon, src, /obj/effect/shuttle_landmark/automatic/spaceflare/proc/update_beacon_moved)
- if (beacon?.active)
- beacon.deactivate(TRUE, TRUE)
- beacon = null
+ landmark = new (turf, src)
+ update_icon()
diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm
index ba66919d8b246..dee49dc142e91 100644
--- a/code/modules/shuttles/shuttle.dm
+++ b/code/modules/shuttles/shuttle.dm
@@ -2,11 +2,14 @@
/datum/shuttle
var/name = ""
+ /// Time it takes shuttle to takeoff
var/warmup_time = 0
+ /// Time it takes shuttle to land
+ var/landing_time = 10 SECONDS
var/moving_status = SHUTTLE_IDLE
var/list/shuttle_area //can be both single area type or a list of areas
- var/obj/effect/shuttle_landmark/current_location //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
+ var/obj/shuttle_landmark/current_location //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
var/arrive_time = 0 //the time at which the shuttle arrives when long jumping
var/flags = 0
@@ -29,7 +32,7 @@
var/mothershuttle //tag of mothershuttle
var/motherdock //tag of mothershuttle landmark, defaults to starting location
-/datum/shuttle/New(_name, obj/effect/shuttle_landmark/initial_location)
+/datum/shuttle/New(_name, obj/shuttle_landmark/initial_location)
..()
if(_name)
src.name = _name
@@ -74,94 +77,139 @@
. = ..()
-/datum/shuttle/proc/short_jump(obj/effect/shuttle_landmark/destination)
- if(moving_status != SHUTTLE_IDLE) return
+/datum/shuttle/proc/short_jump(obj/shuttle_landmark/destination)
+ if(moving_status != SHUTTLE_IDLE)
+ return
moving_status = SHUTTLE_WARMUP
if(sound_takeoff)
playsound(current_location, sound_takeoff, 100, 20, 0.2)
- spawn(warmup_time*10)
- if (moving_status == SHUTTLE_IDLE)
- return //someone cancelled the launch
-
- if(!fuel_check()) //fuel error (probably out of fuel) occured, so cancel the launch
- var/datum/shuttle/autodock/S = src
- if(istype(S))
- S.cancel_launch(null)
- return
-
- moving_status = SHUTTLE_INTRANSIT //shouldn't matter but just to be safe
- attempt_move(destination)
- moving_status = SHUTTLE_IDLE
-/datum/shuttle/proc/long_jump(obj/effect/shuttle_landmark/destination, obj/effect/shuttle_landmark/interim, travel_time)
- if(moving_status != SHUTTLE_IDLE) return
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/shuttle, perform_short_jump), destination), warmup_time)
+
+/datum/shuttle/proc/perform_short_jump(obj/shuttle_landmark/destination)
+ if (moving_status == SHUTTLE_IDLE)
+ return //someone cancelled the launch
+
+ if(!fuel_check()) //fuel error (probably out of fuel) occured, so cancel the launch
+ var/datum/shuttle/autodock/self = src
+ if(istype(self))
+ self.cancel_launch(null)
+ return
+
+ moving_status = SHUTTLE_INTRANSIT //shouldn't matter but just to be safe
+ attempt_move(destination)
+ moving_status = SHUTTLE_IDLE
- var/obj/effect/shuttle_landmark/start_location = current_location
+/datum/shuttle/proc/long_jump(obj/shuttle_landmark/destination, obj/shuttle_landmark/interim, travel_time)
+ if(moving_status != SHUTTLE_IDLE)
+ return
+
+ var/obj/shuttle_landmark/start_location = current_location
moving_status = SHUTTLE_WARMUP
if(sound_takeoff)
playsound(current_location, sound_takeoff, 100, 20, 0.2)
- if (!istype(start_location.base_area, /area/space))
- var/area/A = get_area(start_location)
-
- for (var/mob/M in GLOB.player_list)
- if (M.client && M.z == A.z && !istype(get_turf(M), /turf/space) && !(get_area(M) in src.shuttle_area))
- to_chat(M, SPAN_NOTICE("The rumble of engines are heard as a shuttle lifts off."))
-
- spawn(warmup_time*10)
- if(moving_status == SHUTTLE_IDLE)
- return //someone cancelled the launch
-
- if(!fuel_check()) //fuel error (probably out of fuel) occured, so cancel the launch
- var/datum/shuttle/autodock/S = src
- if(istype(S))
- S.cancel_launch(null)
- return
-
- arrive_time = world.time + travel_time*10
- moving_status = SHUTTLE_INTRANSIT
- if(attempt_move(interim))
- var/fwooshed = 0
- while (world.time < arrive_time)
- if(!fwooshed && (arrive_time - world.time) < 100)
- fwooshed = 1
- playsound(destination, sound_landing, 100, 0, 7)
- if (!istype(destination.base_area, /area/space))
- var/area/A = get_area(destination)
-
- for (var/mob/M in GLOB.player_list)
- if (M.client && M.z == A.z && !istype(get_turf(M), /turf/space) && !(get_area(M) in src.shuttle_area))
- to_chat(M, SPAN_NOTICE("The rumble of a shuttle's engines fill the area as a ship manuevers in for a landing."))
-
- sleep(5)
- if(!attempt_move(destination))
- attempt_move(start_location) //try to go back to where we started. If that fails, I guess we're stuck in the interim location
+ if(!isspace(start_location.base_area))
+ var/list/shuttle_area_refs_set = get_area_refs_set(shuttle_area)
+ for(var/mob/mob_to_notify as anything in GLOB.player_list)
+ if(!mob_to_notify.client)
+ continue
+
+ if(mob_to_notify.z != start_location.z)
+ continue
+
+ if(isspaceturf(get_turf(mob_to_notify)))
+ continue
+
+ if(shuttle_area_refs_set[ref(get_area(mob_to_notify))])
+ continue
+
+ to_chat(mob_to_notify, SPAN_NOTICE("The rumble of engines are heard as a shuttle lifts off."))
+
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/shuttle, launch_shuttle), start_location, interim, destination, travel_time), warmup_time)
+/datum/shuttle/proc/launch_shuttle(obj/shuttle_landmark/start_location, obj/shuttle_landmark/interim, obj/shuttle_landmark/destination, travel_time)
+ if(moving_status == SHUTTLE_IDLE)
+ return //someone cancelled the launch
+
+ if(!fuel_check()) //fuel error (probably out of fuel) occured, so cancel the launch
+ var/datum/shuttle/autodock/self = src
+ if(istype(self))
+ self.cancel_launch(null)
+
+ return
+
+ var/start_landing_in = travel_time - landing_time
+ arrive_time = world.time + travel_time
+ moving_status = SHUTTLE_INTRANSIT
+
+ if(!attempt_move(interim))
moving_status = SHUTTLE_IDLE
+ return
+
+ if(start_landing_in > 0)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/shuttle, prepare_for_landing), start_location, destination), start_landing_in)
+ else
+ prepare_for_landing(start_location, destination)
+
+/// Plans landing of shuttle and plays landing sound
+/datum/shuttle/proc/prepare_for_landing(obj/shuttle_landmark/start_location, obj/shuttle_landmark/destination)
+ if(sound_landing)
+ playsound(destination, sound_landing, 100, 0, 7)
+ if(!isspace(destination.base_area))
+ var/list/shuttle_area_refs_set = get_area_refs_set(shuttle_area)
+ for(var/mob/mob_to_notify as anything in GLOB.player_list)
+ if(!mob_to_notify.client)
+ continue
+
+ if(mob_to_notify.z != destination.z)
+ continue
+
+ if(isspaceturf(get_turf(mob_to_notify)))
+ continue
+
+ if(shuttle_area_refs_set[ref(get_area(mob_to_notify))])
+ continue
+
+ to_chat(mob_to_notify, SPAN_NOTICE("The rumble of a shuttle's engines fill the area as a ship manuevers in for a landing."))
+
+ var/landing_in = arrive_time - world.time
+ if(landing_in > 0)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/shuttle, land_shuttle), start_location, destination), landing_in)
+ else
+ land_shuttle(start_location, destination)
+
+/// Actually moves shuttle to new location from the interim
+/datum/shuttle/proc/land_shuttle(obj/shuttle_landmark/start_location, obj/shuttle_landmark/destination)
+ if(!attempt_move(destination))
+ /// Something went wrong, so we try to move back
+ attempt_move(start_location)
+
+ moving_status = SHUTTLE_IDLE
/datum/shuttle/proc/fuel_check()
- return 1 //fuel check should always pass in non-overmap shuttles (they have magic engines)
+ return TRUE //fuel check should always pass in non-overmap shuttles (they have magic engines)
/*****************
* Shuttle Moved Handling * (Observer Pattern Implementation: Shuttle Moved)
* Shuttle Pre Move Handling * (Observer Pattern Implementation: Shuttle Pre Move)
*****************/
-/datum/shuttle/proc/attempt_move(obj/effect/shuttle_landmark/destination)
+/datum/shuttle/proc/attempt_move(obj/shuttle_landmark/destination)
if(current_location == destination)
return FALSE
if(!destination.is_valid(src))
return FALSE
+
if(current_location.cannot_depart(src))
return FALSE
+
testing("[src] moving to [destination]. Areas are [english_list(shuttle_area)]")
- var/list/translation = list()
- for(var/area/A in shuttle_area)
- testing("Moving [A]")
- translation += get_turf_translation(get_turf(current_location), get_turf(destination), A.contents)
+
var/old_location = current_location
+ var/list/translation = get_turf_translation(get_turf(current_location), get_turf(destination), get_turfs())
GLOB.shuttle_pre_move_event.raise_event(src, old_location, destination)
shuttle_moved(destination, translation)
GLOB.shuttle_moved_event.raise_event(src, old_location, destination)
@@ -178,53 +226,63 @@
// - BANDAID
return TRUE
+/// Returns list of all turfs that shuttle posses
+/datum/shuttle/proc/get_turfs()
+ var/list/shuttle_turfs = list()
+ for(var/area/area_on_shuttle as anything in shuttle_area)
+ shuttle_turfs += get_area_turfs(area_on_shuttle)
+
+ return shuttle_turfs
+
//just moves the shuttle from A to B, if it can be moved
//A note to anyone overriding move in a subtype. shuttle_moved() must absolutely not, under any circumstances, fail to move the shuttle.
//If you want to conditionally cancel shuttle launches, that logic must go in short_jump(), long_jump() or attempt_move()
-/datum/shuttle/proc/shuttle_moved(obj/effect/shuttle_landmark/destination, list/turf_translation)
+/datum/shuttle/proc/shuttle_moved(obj/shuttle_landmark/destination, list/turf_translation)
// log_debug("move_shuttle() called for [shuttle_tag] leaving [origin] en route to [destination].")
// log_degug("area_coming_from: [origin]")
// log_debug("destination: [destination]")
if((flags & SHUTTLE_FLAGS_ZERO_G))
- var/new_grav = 1
+ var/new_area_has_gravity = TRUE
+
if(destination.flags & SLANDMARK_FLAG_ZERO_G)
var/area/new_area = get_area(destination)
- new_grav = new_area.has_gravity
- for(var/area/our_area in shuttle_area)
- if(our_area.has_gravity != new_grav)
- our_area.gravitychange(new_grav)
+ new_area_has_gravity = new_area.has_gravity
+
+ for(var/area/our_area as anything in shuttle_area)
+ if(our_area.has_gravity != new_area_has_gravity)
+ our_area.gravitychange(new_area_has_gravity)
for(var/turf/src_turf in turf_translation)
var/turf/dst_turf = turf_translation[src_turf]
- if(src_turf.is_solid_structure()) //in case someone put a hole in the shuttle and you were lucky enough to be under it
- for(var/atom/movable/AM in dst_turf)
- if(!AM.simulated)
- continue
- AM.shuttle_land_on()
- var/list/powernets = list()
- for(var/area/A in shuttle_area)
+ if(!src_turf.is_solid_structure())
+ continue
+
+ //in case someone put a hole in the shuttle and you were lucky enough to be under it
+ for(var/atom/movable/target as anything in dst_turf)
+ if(!target.simulated)
+ continue
+
+ target.shuttle_land_on()
+
+ var/list/old_powernets = list()
+ var/has_z_level_above = HasAbove(current_location.z)
+ for(var/turf/old_turf as anything in get_turfs())
// if there was a zlevel above our origin, erase our ceiling now we're leaving
- if(HasAbove(current_location.z))
- for(var/turf/TO in A.contents)
- var/turf/TA = GetAbove(TO)
- if(istype(TA, ceiling_type))
- TA.ChangeTurf(get_base_turf_by_area(TA), 1, 1)
- if(knockdown)
- for(var/mob/M in A)
- spawn(0)
- if(istype(M, /mob/living/carbon))
- if(M.buckled)
- to_chat(M, SPAN_WARNING("Sudden acceleration presses you into your chair!"))
- shake_camera(M, 3, 1)
- else
- to_chat(M, SPAN_WARNING("The floor lurches beneath you!"))
- shake_camera(M, 10, 1)
- M.visible_message(SPAN_WARNING("[M.name] is tossed around by the sudden acceleration!"))
- M.throw_at_random(FALSE, 4, 1)
-
- for(var/obj/structure/cable/C in A)
- powernets |= C.powernet
+ if(has_z_level_above)
+ var/turf/turf_above = GetAbove(old_turf)
+
+ if(!istype(turf_above, ceiling_type))
+ continue
+
+ turf_above.ChangeTurf(get_base_turf_by_area(turf_above), 1, 1)
+
+ for(var/obj/structure/cable/C in old_turf)
+ old_powernets |= C.powernet
+
+ if(knockdown)
+ invoke_async(src, TYPE_PROC_REF(/datum/shuttle, knockdown_passengers))
+
if(logging_home_tag)
var/datum/shuttle_log/s_log = SSshuttle.shuttle_logs[src]
s_log.handle_move(current_location, destination)
@@ -234,32 +292,61 @@
// if there's a zlevel above our destination, paint in a ceiling on it so we retain our air
if(HasAbove(current_location.z))
- for(var/area/A in shuttle_area)
- for(var/turf/TD in A.contents)
- var/turf/TA = GetAbove(TD)
- if(istype(TA, get_base_turf_by_area(TA)) || istype(TA, /turf/simulated/open))
- if(get_area(TA) in shuttle_area)
- continue
- TA.ChangeTurf(ceiling_type, TRUE, TRUE, TRUE)
+ var/list/area_refs_set = get_area_refs_set(shuttle_area)
+ var/list/destination_turfs = get_turfs()
+ for(var/turf/destination_turf as anything in destination_turfs)
+ var/turf/turf_above = GetAbove(destination_turf)
+
+ if(!istype(turf_above, get_base_turf_by_area(turf_above)) && !istype(turf_above, /turf/simulated/open))
+ continue
+
+ if(area_refs_set[ref(get_area(turf_above))])
+ continue
+
+ turf_above.ChangeTurf(ceiling_type, TRUE, TRUE, TRUE)
// Remove all powernets that were affected, and rebuild them.
- var/list/cables = list()
- for(var/datum/powernet/P in powernets)
- cables |= P.cables
- qdel(P)
- for(var/obj/structure/cable/C in cables)
- if(!C.powernet)
- var/datum/powernet/NewPN = new()
- NewPN.add_cable(C)
- propagate_network(C,C.powernet)
+ var/list/shuttle_cables = list()
+ for(var/datum/powernet/old_powernet as anything in old_powernets)
+ shuttle_cables |= old_powernet.cables
+ qdel(old_powernet)
+
+ for(var/obj/structure/cable/C as anything in shuttle_cables)
+ if(C.powernet)
+ continue
+
+ var/datum/powernet/NewPN = new()
+ NewPN.add_cable(C)
+ propagate_network(C, C.powernet)
if(mothershuttle)
- var/datum/shuttle/mothership = SSshuttle.shuttles[mothershuttle]
- if(mothership)
- if(current_location.landmark_tag == motherdock)
- mothership.shuttle_area |= shuttle_area
- else
- mothership.shuttle_area -= shuttle_area
+ return
+
+ var/datum/shuttle/mothership = SSshuttle.shuttles[mothershuttle]
+ if(!mothership)
+ return
+
+ if(current_location.landmark_tag == motherdock)
+ mothership.shuttle_area |= shuttle_area
+ else
+ mothership.shuttle_area -= shuttle_area
+
+/datum/shuttle/proc/knockdown_passengers()
+ var/list/area_refs_set = get_area_refs_set(shuttle_area)
+ for(var/mob/living/carbon/passenger_to_knockdown as anything in SSmobs.get_mobs_of_type(/mob/living/carbon))
+ /// TODO: cache mobs inside areas
+ if(!area_refs_set[ref(get_area(passenger_to_knockdown))])
+ continue
+
+ if(passenger_to_knockdown.buckled)
+ to_chat(passenger_to_knockdown, SPAN_WARNING("Sudden acceleration presses you into your chair!"))
+ shake_camera(passenger_to_knockdown, 3, 1)
+ continue
+
+ to_chat(passenger_to_knockdown, SPAN_WARNING("The floor lurches beneath you!"))
+ shake_camera(passenger_to_knockdown, 10, 1)
+ passenger_to_knockdown.visible_message(SPAN_WARNING("[passenger_to_knockdown.name] is tossed around by the sudden acceleration!"))
+ passenger_to_knockdown.throw_at_random(FALSE, 4, 1)
/// Handler for shuttles landing on atoms. Called by `shuttle_moved()`.
/atom/movable/proc/shuttle_land_on()
diff --git a/code/modules/shuttles/shuttle_autodock.dm b/code/modules/shuttles/shuttle_autodock.dm
index 68e0256e6fafb..d67cbc6dbd089 100644
--- a/code/modules/shuttles/shuttle_autodock.dm
+++ b/code/modules/shuttles/shuttle_autodock.dm
@@ -9,16 +9,16 @@
var/datum/computer/file/embedded_program/docking/shuttle_docking_controller
var/docking_codes
- var/obj/effect/shuttle_landmark/next_location //This is only used internally.
+ var/obj/shuttle_landmark/next_location //This is only used internally.
var/datum/computer/file/embedded_program/docking/active_docking_controller
- var/obj/effect/shuttle_landmark/landmark_transition //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
- var/move_time = 120 //the time spent in the transition area
+ var/obj/shuttle_landmark/landmark_transition //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
+ var/move_time = 12 SECONDS //the time spent in the transition area
category = /datum/shuttle/autodock
flags = SHUTTLE_FLAGS_PROCESS | SHUTTLE_FLAGS_ZERO_G
-/datum/shuttle/autodock/New(_name, obj/effect/shuttle_landmark/start_waypoint)
+/datum/shuttle/autodock/New(_name, obj/shuttle_landmark/start_waypoint)
..(_name, start_waypoint)
//Initial dock
@@ -27,7 +27,7 @@
if(active_docking_controller)
set_docking_codes(active_docking_controller.docking_codes)
else if(GLOB.using_map.use_overmap)
- var/obj/effect/overmap/visitable/location = map_sectors["[current_location.z]"]
+ var/obj/overmap/visitable/location = map_sectors["[current_location.z]"]
if(location && location.docking_codes)
set_docking_codes(location.docking_codes)
dock()
@@ -52,7 +52,7 @@
force_undock() //bye!
..()
-/datum/shuttle/autodock/proc/update_docking_target(obj/effect/shuttle_landmark/location)
+/datum/shuttle/autodock/proc/update_docking_target(obj/shuttle_landmark/location)
if(location && location.special_dock_targets && location.special_dock_targets[name])
current_dock_target = location.special_dock_targets[name]
else
@@ -127,8 +127,10 @@
process_state = IDLE_STATE
in_use = null
return
- if (get_travel_time() && landmark_transition)
- . = long_jump(next_location, landmark_transition, get_travel_time())
+
+ var/travel_time = get_travel_time()
+ if (travel_time && landmark_transition)
+ . = long_jump(next_location, landmark_transition, travel_time)
else
. = short_jump(next_location)
process_state = WAIT_ARRIVE
@@ -185,5 +187,5 @@
/datum/shuttle/autodock/proc/arrived()
return //do nothing for now
-/obj/effect/shuttle_landmark/transit
+/obj/shuttle_landmark/transit
flags = SLANDMARK_FLAG_ZERO_G
diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm
index 8e6d593f2201a..752bbd43336f3 100644
--- a/code/modules/shuttles/shuttle_console.dm
+++ b/code/modules/shuttles/shuttle_console.dm
@@ -1,6 +1,6 @@
/obj/machinery/computer/shuttle_control
name = "shuttle control console"
- icon = 'icons/obj/computer.dmi'
+ icon = 'icons/obj/machines/computer.dmi'
icon_keyboard = "atmos_key"
icon_screen = "shuttle"
base_type = /obj/machinery/computer/shuttle_control
@@ -124,4 +124,5 @@
return
/obj/machinery/computer/shuttle_control/emp_act()
+ SHOULD_CALL_PARENT(FALSE)
return
diff --git a/code/modules/shuttles/shuttle_console_multi.dm b/code/modules/shuttles/shuttle_console_multi.dm
index 3b39a259b8de6..0e70c692a2ffe 100644
--- a/code/modules/shuttles/shuttle_console_multi.dm
+++ b/code/modules/shuttles/shuttle_console_multi.dm
@@ -19,22 +19,3 @@
if(dest_key && CanInteract(usr, GLOB.default_state))
shuttle.set_destination(dest_key, usr)
return TOPIC_REFRESH
-
-
-/obj/machinery/computer/shuttle_control/multi/antag
- ui_template = "shuttle_control_console_antag.tmpl"
-
-/obj/machinery/computer/shuttle_control/multi/antag/get_ui_data(datum/shuttle/autodock/multi/antag/shuttle)
- . = ..()
- if(istype(shuttle))
- . += list(
- "cloaked" = shuttle.cloaked,
- )
-
-/obj/machinery/computer/shuttle_control/multi/antag/handle_topic_href(datum/shuttle/autodock/multi/antag/shuttle, list/href_list)
- if((. = ..()) != null)
- return
-
- if(href_list["toggle_cloaked"])
- shuttle.cloaked = !shuttle.cloaked
- return TOPIC_REFRESH
diff --git a/code/modules/shuttles/shuttle_emergency.dm b/code/modules/shuttles/shuttle_emergency.dm
index 8c41025b0e44c..588844bf48113 100644
--- a/code/modules/shuttles/shuttle_emergency.dm
+++ b/code/modules/shuttles/shuttle_emergency.dm
@@ -28,7 +28,7 @@
/datum/shuttle/autodock/ferry/emergency/shuttle_moved()
if(next_location != waypoint_station)
emergency_controller.shuttle_leaving() // This is a hell of a line. v
- priority_announcement.Announce(replacetext(replacetext((emergency_controller.emergency_evacuation ? GLOB.using_map.emergency_shuttle_leaving_dock : GLOB.using_map.shuttle_leaving_dock), "%dock_name%", "[GLOB.using_map.dock_name]"), "%ETA%", "[round(emergency_controller.get_eta()/60,1)] minute\s"))
+ priority_announcement.Announce(replacetext(replacetext((emergency_controller.emergency_evacuation ? GLOB.using_map.emergency_shuttle_leaving_message : GLOB.using_map.shuttle_leaving_message), "%dock_name%", "[GLOB.using_map.dock_name]"), "%ETA%", "[round(emergency_controller.get_eta()/60,1)] minute\s"))
else if(next_location == waypoint_offsite && emergency_controller.has_evacuated())
emergency_controller.shuttle_evacuated()
..()
@@ -166,9 +166,9 @@
emagged = TRUE
return 1
-/obj/machinery/computer/shuttle_control/emergency/attackby(obj/item/W as obj, mob/user as mob)
+/obj/machinery/computer/shuttle_control/emergency/use_tool(obj/item/W, mob/living/user, list/click_params)
read_authorization(W)
- ..()
+ return ..()
/obj/machinery/computer/shuttle_control/emergency/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
var/data[0]
diff --git a/code/modules/shuttles/shuttle_engines.dm b/code/modules/shuttles/shuttle_engines.dm
index 7f5a9b65920fc..1c0ceefb9ba61 100644
--- a/code/modules/shuttles/shuttle_engines.dm
+++ b/code/modules/shuttles/shuttle_engines.dm
@@ -4,7 +4,7 @@
/obj/structure/shuttle/window
name = "shuttle window"
- icon = 'icons/obj/podwindows.dmi'
+ icon = 'icons/obj/structures/podwindows.dmi'
icon_state = "1"
density = TRUE
opacity = 0
diff --git a/code/modules/shuttles/shuttle_ferry.dm b/code/modules/shuttles/shuttle_ferry.dm
index f8243753bcbbd..0a344dbadb102 100644
--- a/code/modules/shuttles/shuttle_ferry.dm
+++ b/code/modules/shuttles/shuttle_ferry.dm
@@ -4,8 +4,8 @@
var/location = 0 //0 = at area_station, 1 = at area_offsite
var/direction = 0 //0 = going to station, 1 = going to offsite.
- var/obj/effect/shuttle_landmark/waypoint_station //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
- var/obj/effect/shuttle_landmark/waypoint_offsite //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
+ var/obj/shuttle_landmark/waypoint_station //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
+ var/obj/shuttle_landmark/waypoint_offsite //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
category = /datum/shuttle/autodock/ferry
@@ -32,7 +32,7 @@
direction = !location
..()
-/datum/shuttle/autodock/ferry/long_jump(destination, obj/effect/shuttle_landmark/interim, travel_time)
+/datum/shuttle/autodock/ferry/long_jump(destination, obj/shuttle_landmark/interim, travel_time)
direction = !location
..()
diff --git a/code/modules/shuttles/shuttle_log.dm b/code/modules/shuttles/shuttle_log.dm
index 1b47e08bc859d..31c160b85613a 100644
--- a/code/modules/shuttles/shuttle_log.dm
+++ b/code/modules/shuttles/shuttle_log.dm
@@ -28,7 +28,7 @@
registered += module
/datum/shuttle_log/proc/unregister(datum/nano_module/module)
- registered -= module
+ registered -= module
/datum/shuttle_log/proc/update_registred()
for(var/datum/nano_module/module in registered)
@@ -116,8 +116,8 @@
if(given_id == mission.ID)
return mission
-/datum/shuttle_log/proc/handle_move(obj/effect/shuttle_landmark/origin, obj/effect/shuttle_landmark/destination)
- var/obj/effect/shuttle_landmark/home = SSshuttle.get_landmark(home_base)
+/datum/shuttle_log/proc/handle_move(obj/shuttle_landmark/origin, obj/shuttle_landmark/destination)
+ var/obj/shuttle_landmark/home = SSshuttle.get_landmark(home_base)
if(origin == home)
shuttle_launched()
if(destination == home)
@@ -151,4 +151,4 @@
/datum/shuttle_mission/Destroy()
QDEL_NULL(flight_plan)
QDEL_NULL_LIST(other_reports)
- . = ..()
\ No newline at end of file
+ . = ..()
diff --git a/code/modules/shuttles/shuttle_specops.dm b/code/modules/shuttles/shuttle_specops.dm
index cbe3831a31056..a3a89e46b45a1 100644
--- a/code/modules/shuttles/shuttle_specops.dm
+++ b/code/modules/shuttles/shuttle_specops.dm
@@ -10,22 +10,13 @@
/datum/shuttle/autodock/ferry/specops
var/specops_return_delay = 6000 //After moving, the amount of time that must pass before the shuttle may move again
var/specops_countdown_time = 600 //Length of the countdown when moving the shuttle
-
- var/obj/item/device/radio/intercom/announcer = null
var/reset_time = 0 //the world.time at which the shuttle will be ready to move again.
var/launch_prep = 0
var/cancel_countdown = 0
category = /datum/shuttle/autodock/ferry/specops
-/datum/shuttle/autodock/ferry/specops/New()
- ..()
- announcer = new /obj/item/device/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky.
- announcer.config(list("Response Team" = 0))
-
/datum/shuttle/autodock/ferry/specops/proc/radio_announce(message)
- if(announcer)
- announcer.autosay(message, "A.L.I.C.E.", "Response Team")
-
+ GLOB.global_announcer.autosay(message, "A.L.I.C.E.", "Response Team")
/datum/shuttle/autodock/ferry/specops/launch(user)
if (!can_launch())
@@ -152,13 +143,13 @@
sleep(10)
var/spawn_marauder[] = new()
- for(var/obj/effect/landmark/L in world)
+ for(var/obj/landmark/L in world)
if(L.name == "Marauder Entry")
spawn_marauder.Add(L)
- for(var/obj/effect/landmark/L in world)
+ for(var/obj/landmark/L in world)
if(L.name == "Marauder Exit")
- var/obj/effect/portal/P = new(L.loc)
- P.set_invisibility(101)//So it is not seen by anyone.
+ var/obj/portal/P = new(L.loc)
+ P.set_invisibility(INVISIBILITY_ABSTRACT)//So it is not seen by anyone.
P.failchance = 0//So it has no fail chance when teleporting.
P.target = pick(spawn_marauder)//Where the marauder will arrive.
spawn_marauder.Remove(P.target)
diff --git a/code/modules/shuttles/shuttle_supply.dm b/code/modules/shuttles/shuttle_supply.dm
index 484c8a256fa03..77da8d30adb05 100644
--- a/code/modules/shuttles/shuttle_supply.dm
+++ b/code/modules/shuttles/shuttle_supply.dm
@@ -28,7 +28,7 @@
SSsupply.buy()
//We pretend it's a long_jump by making the shuttle stay at centcom for the "in-transit" period.
- var/obj/effect/shuttle_landmark/away_waypoint = get_location_waypoint(away_location)
+ var/obj/shuttle_landmark/away_waypoint = get_location_waypoint(away_location)
moving_status = SHUTTLE_INTRANSIT
//If we are at the away_landmark then we are just pretending to move, otherwise actually do the move
diff --git a/code/modules/shuttles/shuttles_multi.dm b/code/modules/shuttles/shuttles_multi.dm
index a2304df3d38a2..fd0b0574290f0 100644
--- a/code/modules/shuttles/shuttles_multi.dm
+++ b/code/modules/shuttles/shuttles_multi.dm
@@ -18,15 +18,15 @@
last_cache_rebuild_time = world.time
destinations_cache.Cut()
for(var/destination_tag in destination_tags)
- var/obj/effect/shuttle_landmark/landmark = SSshuttle.get_landmark(destination_tag)
+ var/obj/shuttle_landmark/landmark = SSshuttle.get_landmark(destination_tag)
if (istype(landmark))
destinations_cache["[landmark.name]"] = landmark
//Antag play announcements when they leave/return to their home area
/datum/shuttle/autodock/multi/antag
- warmup_time = 10 SECONDS //replaced the old move cooldown
- //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
- var/obj/effect/shuttle_landmark/home_waypoint
+ warmup_time = 10 SECONDS //replaced the old move cooldown SECONDS
+ //This variable is type-abused initially: specify the landmark_tag, not the actual landmark.
+ var/obj/shuttle_landmark/home_waypoint
var/cloaked = 1
var/announcer
diff --git a/code/modules/skrell/skrell_rigs.dm b/code/modules/skrell/skrell_rigs.dm
index 8b7656a525676..a05e846fab3f9 100644
--- a/code/modules/skrell/skrell_rigs.dm
+++ b/code/modules/skrell/skrell_rigs.dm
@@ -215,13 +215,11 @@
interface_name = "multitool"
interface_desc = "A limited-sentience integrated multitool capable of interfacing with any number of systems."
device = /obj/item/device/multitool/skrell
- icon = 'icons/obj/tools.dmi'
+ icon = 'icons/obj/tools/multitool.dmi'
icon_state = "skrell_multitool"
usable = FALSE
selectable = TRUE
-
-/obj/item/rig_module/device/multitool/skrell/IsMultitool()
- return TRUE
+ tool_behaviour = TOOL_MULTITOOL
/obj/item/rig_module/device/cable_coil/skrell
name = "skrellian cable extruder"
@@ -229,8 +227,8 @@
interface_name = "cable fabricator"
interface_desc = "A cable nanofabricator of Skrellian design."
device = /obj/item/stack/cable_coil/fabricator
- icon = 'icons/obj/tools.dmi'
- icon_state = "cablecoil"
+ icon = 'icons/obj/machines/power/power_cond_white.dmi'
+ icon_state = "coil"
usable = FALSE
selectable = TRUE
@@ -239,7 +237,7 @@
desc = "An electrical cutting torch of Skrellian design."
interface_name = "welding arm"
interface_desc = "An electrical cutting torch of Skrellian design."
- icon = 'icons/obj/tools.dmi'
+ icon = 'icons/obj/tools/welder.dmi'
icon_state = "skrell_welder1"
engage_string = "Toggle Welder"
device = /obj/item/weldingtool/electric
@@ -251,24 +249,21 @@
desc = "A complex assembly of self-guiding, modular heads capable of performing most manual tasks."
interface_name = "modular clustertool"
interface_desc = "A complex assembly of self-guiding, modular heads capable of performing most manual tasks."
- icon = 'icons/obj/tools.dmi'
+ icon = 'icons/obj/tools/swapper.dmi'
icon_state = "clustertool"
engage_string = "Select Mode"
device = /obj/item/clustertool
usable = TRUE
selectable = TRUE
-/obj/item/rig_module/device/clustertool/IsWrench()
- return device.IsWrench(device)
-
-/obj/item/rig_module/device/clustertool/IsWirecutter()
- return device.IsWirecutter(device)
-
-/obj/item/rig_module/device/clustertool/IsScrewdriver()
- return device.IsScrewdriver(device)
+/obj/item/rig_module/device/clustertool/skrell/Initialize()
+ . = ..()
+ change_tool_behaviour(device.tool_behaviour)
+ RegisterSignal(device, COMSIG_OBJ_CHANGE_TOOL_BEHAVIOUR, PROC_REF(update_tool_behaviour))
-/obj/item/rig_module/device/clustertool/IsCrowbar()
- return device.IsCrowbar(device)
+/obj/item/rig_module/device/clustertool/skrell/proc/update_tool_behaviour(obj/item/tool, new_tool_behaviour, new_toolspeed, override_sound)
+ SIGNAL_HANDLER
+ change_tool_behaviour(new_tool_behaviour, new_toolspeed, override_sound)
// Self-charging power cell.
/obj/item/cell/skrell
@@ -293,46 +288,32 @@
/obj/item/clustertool
name = "alien clustertool"
desc = "A bewilderingly complex knot of tool heads."
- icon = 'icons/obj/tools.dmi'
+ icon = 'icons/obj/tools/swapper.dmi'
icon_state = "clustertool"
w_class = ITEM_SIZE_SMALL
-
- var/tool_mode
- var/list/tool_modes = list("wrench", "wirecutters", "crowbar", "screwdriver")
+ var/list/tool_modes = list(TOOL_WRENCH, TOOL_WIRECUTTER, TOOL_CROWBAR, TOOL_SCREWDRIVER)
/obj/item/clustertool/attack_self(mob/user)
- var/new_index = _list_find(tool_modes, tool_mode) + 1
+ var/new_index = _list_find(tool_modes, tool_behaviour) + 1
if(new_index > length(tool_modes))
new_index = 1
- tool_mode = tool_modes[new_index]
- name = "[initial(name)] ([tool_mode])"
+ change_tool_behaviour(tool_modes[new_index])
+ name = "[initial(name)] ([tool_behaviour])"
playsound(user, 'sound/machines/bolts_down.ogg', 10)
- to_chat(user, SPAN_NOTICE("You select the [tool_mode] attachment."))
+ to_chat(user, SPAN_NOTICE("You select the [tool_behaviour] attachment."))
update_icon()
/obj/item/clustertool/on_update_icon()
- icon_state = "[initial(icon_state)]-[tool_mode]"
+ icon_state = "[initial(icon_state)]-[tool_behaviour]"
/obj/item/clustertool/Initialize()
. = ..()
- tool_mode = tool_modes[1]
- name = "[initial(name)] ([tool_mode])"
+ tool_behaviour = tool_modes[1]
+ name = "[initial(name)] ([tool_behaviour])"
update_icon()
-/obj/item/clustertool/IsWrench()
- return tool_mode == "wrench"
-
-/obj/item/clustertool/IsWirecutter()
- return tool_mode == "wirecutters"
-
-/obj/item/clustertool/IsScrewdriver()
- return tool_mode == "screwdriver"
-
-/obj/item/clustertool/IsCrowbar()
- return tool_mode == "crowbar"
-
/obj/item/device/multitool/skrell
name = "skrellian multitool"
- name = "An extreme sophisticated microcomputer capable of interfacing with practically any system."
- icon = 'icons/obj/tools.dmi'
+ desc = "An extreme sophisticated microcomputer capable of interfacing with practically any system."
+ icon = 'icons/obj/tools/multitool.dmi'
icon_state = "skrell_multitool"
diff --git a/code/modules/species/outsider/random.dm b/code/modules/species/outsider/random.dm
index fa41899691a9b..53a5e031dc38d 100644
--- a/code/modules/species/outsider/random.dm
+++ b/code/modules/species/outsider/random.dm
@@ -10,6 +10,7 @@
icobase = 'icons/mob/human_races/species/humanoid/body.dmi'
deform = 'icons/mob/human_races/species/humanoid/body.dmi'
bandages_icon = 'icons/mob/bandage.dmi'
+ preview_icon = null
appearance_flags = SPECIES_APPEARANCE_HAS_SKIN_COLOR
limb_blend = ICON_MULTIPLY
@@ -137,7 +138,7 @@
/obj/structure/aliumizer
name = "alien monolith"
desc = "Your true form is calling. Use this to become an alien humanoid."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "ano51"
anchored = TRUE
diff --git a/code/modules/species/outsider/shadow.dm b/code/modules/species/outsider/shadow.dm
index 1286629672773..98a3dfaa9f8a7 100644
--- a/code/modules/species/outsider/shadow.dm
+++ b/code/modules/species/outsider/shadow.dm
@@ -4,6 +4,7 @@
description = "A being of pure darkness, hates the light and all that comes with it."
icobase = 'icons/mob/human_races/species/shadow/body.dmi'
deform = 'icons/mob/human_races/species/shadow/body.dmi'
+ preview_icon = null
meat_type = null
bone_material = null
@@ -17,7 +18,7 @@
blood_color = COLOR_GRAY80
flesh_color = "#aaaaaa"
- remains_type = /obj/effect/decal/cleanable/ash
+ remains_type = /obj/decal/cleanable/ash
death_message = "dissolves into ash..."
species_flags = SPECIES_FLAG_NO_SCAN | SPECIES_FLAG_NO_SLIP | SPECIES_FLAG_NO_POISON | SPECIES_FLAG_NO_EMBED
diff --git a/code/modules/species/outsider/starlight.dm b/code/modules/species/outsider/starlight.dm
index 6b83aaef45498..2d38496fe8ecd 100644
--- a/code/modules/species/outsider/starlight.dm
+++ b/code/modules/species/outsider/starlight.dm
@@ -1,5 +1,6 @@
/datum/species/starlight
name = "Starlight Base"
+ preview_icon = null
meat_type = null
bone_material = null
@@ -33,7 +34,7 @@
return FALSE
/datum/species/starlight/handle_death(mob/living/carbon/human/H)
- addtimer(new Callback(H,/mob/proc/dust),0)
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob, dust)),0)
/datum/species/starlight/starborn
name = "Starborn"
@@ -83,7 +84,7 @@
/datum/species/starlight/starborn/handle_death(mob/living/carbon/human/H)
..()
var/turf/T = get_turf(H)
- new/obj/effect/decal/cleanable/liquid_fuel(T, 20, TRUE)
+ new/obj/decal/cleanable/liquid_fuel(T, 20, TRUE)
T.hotspot_expose(PHORON_MINIMUM_BURN_TEMPERATURE)
/datum/species/starlight/blueforged
@@ -112,4 +113,4 @@
/datum/species/starlight/blueforged/handle_death(mob/living/carbon/human/H)
..()
- new /obj/effect/temporary(get_turf(H),11, 'icons/mob/mob.dmi', "liquify")
+ new /obj/temporary(get_turf(H),11, 'icons/mob/mob.dmi', "liquify")
diff --git a/code/modules/species/outsider/vox.dm b/code/modules/species/outsider/vox.dm
index c9597e3271508..1c52653f44496 100644
--- a/code/modules/species/outsider/vox.dm
+++ b/code/modules/species/outsider/vox.dm
@@ -7,6 +7,7 @@
damage_overlays = 'icons/mob/human_races/species/vox/damage_overlay.dmi'
damage_mask = 'icons/mob/human_races/species/vox/damage_mask.dmi'
blood_mask = 'icons/mob/human_races/species/vox/blood_mask.dmi'
+ preview_icon = 'icons/mob/human_races/species/vox/preview.dmi'
unarmed_types = list(
/datum/unarmed_attack/stomp,
@@ -137,8 +138,7 @@
H.set_internals(H.back)
/datum/species/vox/disfigure_msg(mob/living/carbon/human/H)
- var/datum/gender/T = gender_datums[H.get_gender()]
- return "[SPAN_DANGER("[T.His] beak-segments are cracked and chipped! [T.He] [T.is] not even recognizable.")]\n"
+ return "[SPAN_DANGER("[H.p_Their()] beak-segments are cracked and chipped! [H.p_They()] [H.p_are()] not even recognizable.")]\n"
/datum/species/vox/skills_from_age(age)
. = 8
@@ -146,7 +146,7 @@
/obj/item/vox_changer
name = "mouldy mirror"
desc = "Something seems strange about this old, dirty mirror. Your reflection doesn't look like you remember it."
- icon = 'icons/obj/watercloset.dmi'
+ icon = 'icons/obj/structures/mirror.dmi'
icon_state = "mirror_broke"
color = "#bcd4a9"
anchored = TRUE
@@ -158,7 +158,7 @@
return
if (allowed_role && user.mind?.special_role != allowed_role)
return
- if (user.species.name == SPECIES_VOX || !is_alien_whitelisted(user, all_species[SPECIES_VOX]))
+ if (user.species.name == SPECIES_VOX || !is_any_alien_whitelisted(user, all_species[SPECIES_VOX]))
return
var/data = input(user, "Become Vox?", "Become Vox") as null | anything in list("No", "Yes")
if (isnull(data) || data == "No")
diff --git a/code/modules/species/outsider/zombie.dm b/code/modules/species/outsider/zombie.dm
index 1faf9bb15cd00..cd91dfd7ad059 100644
--- a/code/modules/species/outsider/zombie.dm
+++ b/code/modules/species/outsider/zombie.dm
@@ -14,12 +14,12 @@ GLOBAL_LIST_INIT(zombie_messages, list(
"Your throat is extremely dry...",
"Your muscles cramp...",
"You feel dizzy.",
- "You feel slightly fatigued.",
+ "You feel fatigued.",
"You feel light-headed."
),
"stage2" = list(
"You feel something under your skin!",
- "Mucus runs down the back of your throat",
+ "Mucus runs down the back of your throat.",
"Your muscles burn.",
"Your skin itches.",
"Your bones ache.",
@@ -37,28 +37,29 @@ GLOBAL_LIST_INIT(zombie_messages, list(
GLOBAL_LIST_INIT(zombie_species, list(\
- SPECIES_HUMAN, SPECIES_DIONA, SPECIES_UNATHI, SPECIES_VOX,\
+ SPECIES_HUMAN, SPECIES_UNATHI, SPECIES_VOX,\
SPECIES_SKRELL, SPECIES_PROMETHEAN, SPECIES_ALIEN, SPECIES_YEOSA, SPECIES_VATGROWN,\
- SPECIES_SPACER, SPECIES_TRITONIAN, SPECIES_GRAVWORLDER, SPECIES_MULE, SPECIES_MONKEY
+ SPECIES_SPACER, SPECIES_TRITONIAN, SPECIES_GRAVWORLDER, SPECIES_MULE, SPECIES_MONKEY,\
+ SPECIES_FARWA, SPECIES_NEAERA, SPECIES_STOK
))
//// Zombie Types
-
/datum/species/zombie
name = "Zombie"
name_plural = "Zombies"
- slowdown = 15
- blood_color = "#700f0f"
+ blood_color = "#411111"
+ preview_icon = null
death_message = "writhes and twitches before falling motionless."
species_flags = SPECIES_FLAG_NO_PAIN | SPECIES_FLAG_NO_SCAN
spawn_flags = SPECIES_IS_RESTRICTED
- brute_mod = 1
- burn_mod = 2.5 //Vulnerable to fire
- oxy_mod = 0
- stun_mod = 0.05
- weaken_mod = 0.05
+ vision_flags = SEE_SELF | SEE_MOBS
+ brute_mod = 1.0
+ burn_mod = 1.5 //Vulnerable to fire
+ oxy_mod = 0.0
+ stun_mod = 0.2
+ weaken_mod = 0.3
paralysis_mod = 0.2
show_ssd = null //No SSD message so NPC logic can take over
show_coma = null
@@ -69,61 +70,51 @@ GLOBAL_LIST_INIT(zombie_species, list(\
cold_level_2 = -1
cold_level_3 = -1
hidden_from_codex = TRUE
- has_fine_manipulation = FALSE
unarmed_types = list(/datum/unarmed_attack/bite/sharp/zombie)
- move_intents = list(/singleton/move_intent/creep)
- var/heal_rate = 1 // Regen.
- var/mob/living/carbon/human/target = null
- var/list/obstacles = list(
- /obj/structure/window,
- /obj/structure/closet,
- /obj/machinery/door/airlock,
- /obj/structure/table,
- /obj/structure/grille,
- /obj/structure/barricade,
- /obj/structure/wall_frame,
- /obj/structure/railing,
- /obj/structure/girder,
- /turf/simulated/wall,
- /obj/machinery/door/blast/shutters,
- /obj/machinery/door
- )
+ move_intents = list(/singleton/move_intent/zombie)
+ var/heal_rate = 0.5 // Regen.
/datum/species/zombie/handle_post_spawn(mob/living/carbon/human/H)
H.mutations |= MUTATION_CLUMSY
H.mutations |= MUTATION_FERAL
H.mutations |= mNobreath //Byond doesn't like adding them all in one OR statement :(
H.verbs += /mob/living/carbon/proc/consume
- H.move_intents = list(/singleton/move_intent/creep) //Zooming days are over
H.a_intent = "harm"
- H.move_intent = new /singleton/move_intent/creep
+ H.move_intents = list(new /singleton/move_intent/zombie) //Zooming days are over
+ H.move_intent = new /singleton/move_intent/zombie
H.default_run_intent = H.move_intent
H.default_walk_intent = H.move_intent
- H.set_sight(H.sight | SEE_MOBS | SEE_OBJS | SEE_TURFS) //X-Ray vis
- H.set_see_in_dark(8)
- H.set_see_invisible(SEE_INVISIBLE_LEVEL_TWO)
+ H.set_sight(H.sight | SEE_MOBS)
H.languages = list()
H.add_language(LANGUAGE_ZOMBIE)
+ H.faction = "zombies"
+
+ H.ai_holder = new /datum/ai_holder/human/zombie (H)
+ H.say_list_type = /datum/say_list/zombie
+ H.say_list = new /datum/say_list/zombie (H)
+ H.possession_candidate = TRUE
H.sleeping = 0
H.resting = 0
H.weakened = 0
+ H.dizziness = 0
+
+ for (var/obj/item/organ/organ in (H.organs + H.internal_organs))
+ if (organ.name == "brain")
+ continue
+ organ.vital = 0
+ if (!BP_IS_ROBOTIC(organ))
+ organ.min_broken_damage = organ.max_damage
- H.move_intent.move_delay = 6
H.stat = CONSCIOUS
- if (H.wear_id)
- qdel(H.wear_id)
- if (H.gloves)
- qdel(H.gloves)
- if (H.head)
- qdel(H.head) //Remove helmet so headshots aren't impossible
- if (H.glasses)
- qdel(H.glasses)
- if (H.wear_mask)
- qdel(H.wear_mask)
+ H.remove_from_mob(H.gloves)
+ H.remove_from_mob(H.head) // Remove helmet so headshots aren't impossible
+ H.remove_from_mob(H.glasses)
+ H.remove_from_mob(H.wear_mask)
+
..()
/datum/species/zombie/handle_environment_special(mob/living/carbon/human/H)
@@ -135,104 +126,177 @@ GLOBAL_LIST_INIT(zombie_species, list(\
else if (prob(5))
playsound(H.loc, 'sound/hallucinations/wail.ogg', 15, 1)
- for(var/obj/item/organ/I in H.internal_organs)
- if (I.damage > 0)
- I.damage = max(I.damage - heal_rate, 0)
+ if (H.stat != DEAD)
+ for(var/obj/item/organ/organ in (H.organs + H.internal_organs))
+ if (organ.damage > 0)
+ organ.heal_damage(heal_rate, heal_rate, 1)
+ if (H.getToxLoss())
+ H.adjustToxLoss(-heal_rate)
+ if (prob(5))
+ H.resuscitate()
+ H.vessel.add_reagent(/datum/reagent/blood, min(heal_rate * 20, blood_volume - H.vessel.total_volume))
- H.vessel.add_reagent(/datum/reagent/blood, min(heal_rate * 10, blood_volume - H.vessel.total_volume))
+ else
+ var/list/victims = ohearers(rand(1, 2), H)
+ for(var/mob/living/carbon/human/M in victims) // Post-mortem infection
+ if (H == M || M.is_zombie())
+ continue
+ if (M.isSynthetic() || M.is_species(SPECIES_DIONA) || !(M.species.name in GLOB.zombie_species))
+ continue
+ if (M.wear_mask && (M.wear_mask.item_flags & ITEM_FLAG_AIRTIGHT)) // If they're protected by a mask
+ continue
+ if (M.head && (M.head.item_flags & ITEM_FLAG_AIRTIGHT)) // If they're protected by a helmet
+ continue
- if (H.getBruteLoss() || H.getFireLoss() || H.getOxyLoss() || H.getToxLoss())
- H.adjustBruteLoss(-heal_rate)
- H.adjustFireLoss(-heal_rate)
- H.adjustOxyLoss(-heal_rate)
- H.adjustToxLoss(-heal_rate)
- return TRUE
+ var/vuln = 1 - M.get_blocked_ratio(BP_HEAD, DAMAGE_TOXIN, damage_flags = DAMAGE_FLAG_BIO) // Are they protected by hazmat clothing?
+ if (vuln > 0.10 && prob(8))
+ M.reagents.add_reagent(/datum/reagent/zombie, 0.5) // Infect 'em
/datum/species/zombie/handle_death(mob/living/carbon/human/H)
- H.stat = DEAD //Gotta confirm death for some odd reason
playsound(H, 'sound/hallucinations/wail.ogg', 30, 1)
return TRUE
-/datum/species/zombie/handle_npc(mob/living/carbon/human/H)
- H.resting = FALSE
- if (H.client || H.stat != CONSCIOUS)
- walk(H, 0) //Stop dead-walking
- return
+/datum/species/zombie/get_blood_name()
+ return "decaying blood"
+
+/datum/species/zombie/has_fine_manipulation(mob/living/carbon/human/H)
+ return (MUTATION_CLUMSY in H.mutations) ? FALSE : TRUE
+
+/singleton/move_intent/zombie
+ name = "Shuffle"
+ flags = MOVE_INTENT_DELIBERATE
+ hud_icon_state = "creeping"
+ move_delay = 10
+
+/datum/say_list/zombie
+ emote_hear = list("wails!","groans!")
+
+/datum/ai_holder/human/zombie
+ hostile = TRUE
+ destructive = TRUE
+ can_flee = FALSE
+ mauling = TRUE
+ handle_corpse = TRUE
+ use_astar = TRUE
+ wander_when_pulled = TRUE
+ base_wander_delay = 2
+ wander_chance = 50
+ vision_range = 15
+ speak_chance = 5
+ lose_target_timeout = 90 SECONDS
+
+ valid_obstacles_by_priority = list(
+ /obj/structure/closet,
+ /obj/structure/table,
+ /obj/structure/grille,
+ /obj/structure/barricade,
+ /obj/structure/wall_frame,
+ /obj/structure/railing,
+ /obj/structure/girder,
+ /turf/simulated/wall
+ )
- if (prob(5))
- H.custom_emote("wails!")
- else if (prob(5))
- H.custom_emote("groans!")
- if (H.restrained() && prob(8))
- H.custom_emote("thrashes and writhes!")
+/datum/ai_holder/human/zombie/can_pursue(atom/movable/target)
+ if (!istype(target, /mob/living))
+ return FALSE
- if (H.lying)
- walk(H, 0)
- return
+ if (!..())
+ return FALSE
- if (H.restrained() || H.buckled())
- H.resist()
- return
+ var/mob/living/target_living = target
- addtimer(new Callback(src, .proc/handle_action, H), rand(10, 20))
-
-/datum/species/zombie/proc/handle_action(mob/living/carbon/human/H)
- var/dist = 128
- for(var/mob/living/M in hearers(H, 15))
- if ((ishuman(M) || istype(M, /mob/living/exosuit)) && !M.is_species(SPECIES_ZOMBIE) && !M.is_species(SPECIES_DIONA)) //Don't attack fellow zombies, or diona
- if (istype(M, /mob/living/exosuit))
- var/mob/living/exosuit/MC = M
- if (!LAZYLEN(MC.pilots))
- continue //Don't attack empty mechs
- if (M.stat == DEAD && target)
- continue //Only eat corpses when no living (and able) targets are around
- var/D = get_dist(M, H)
- if (D <= dist * 0.5) //Must be significantly closer to change targets
- target = M //For closest target
- dist = D
-
- H.setClickCooldown(DEFAULT_ATTACK_COOLDOWN*2)
- if (target)
- if (target.is_species(SPECIES_ZOMBIE))
- target = null
- return
+ if (!istype(target_living, /mob/living/carbon/human)) //Ignore Diona and unconscious non-humans
+ if (istype(target_living, /mob/living/carbon/alien/diona))
+ return FALSE
+ if (target_living.stat != CONSCIOUS)
+ return FALSE
- if (!H.Adjacent(target))
- var/turf/dir = get_step_towards(H, target)
- for(var/type in obstacles) //Break obstacles
- var/obj/obstacle = locate(type) in dir
- if (obstacle)
- H.face_atom(obstacle)
- obstacle.attack_generic(H, 10, "smashes")
- break
+ var/mob/living/carbon/human/target_human = target
+ if (target_human.is_zombie() || target_human.is_species(SPECIES_DIONA))
+ return FALSE
- walk_to(H, target.loc, 1, H.move_intent.move_delay * 1.25)
+ if (target_human.stat != CONSCIOUS && (target_human.isSynthetic() || (MUTATION_SKELETON in target_human.mutations) || !(target_human.species.name in GLOB.zombie_species)))
+ return FALSE
- else
- if (!target.lying) //Subdue meals
- H.face_atom(target)
+ if (is_being_consumed(target_human))
+ return FALSE
- if (!H.zone_sel)
- H.zone_sel = new /obj/screen/zone_sel(null)
- H.zone_sel.selecting = BP_CHEST
- target.attack_hand(H)
+ return TRUE
- else //Eat said meals
- walk_to(H, target.loc, 0, H.move_intent.move_delay * 2.5) //Move over them
- if (H.Adjacent(target)) //Check we're still next to them
- H.consume()
+/datum/ai_holder/human/zombie/proc/is_consumable(mob/living/target_living)
+ if (!istype(target_living, /mob/living/carbon/human))
+ return FALSE
- for(var/mob/living/M in hearers(H, 15))
- if (target == M) //If our target is still nearby
+ var/mob/living/carbon/human/target_human = target_living
+ if (target_human.isSynthetic())
+ return FALSE
+
+ if (!(target_human.species.name in GLOB.zombie_species))
+ return FALSE
+
+ return can_pursue(target_living)
+
+/datum/ai_holder/human/zombie/proc/is_being_consumed(mob/living/target_living)
+ //Will exclude consumption candidates if there's another zombie on top of them
+ var/turf/target_turf = get_turf(target_living)
+ if (!target_turf)
+ return FALSE
+ for (var/mob/living/carbon/human/consumer in target_turf.contents)
+ if (consumer != holder && consumer.stat == CONSCIOUS && consumer.is_zombie())
+ return TRUE
+ return FALSE
+
+/datum/ai_holder/human/zombie/pick_target(list/targets)
+ // If there are any conscious mobs, pick them
+ // If not, pick the closest consumable mob, which isn't being consumed already
+ var/lowest_distance = 1e6 //fakely far
+ var/atom/movable/new_target = null
+ var/conscious_target = FALSE
+ for (var/atom/movable/possible_target in targets)
+ if (!istype(possible_target, /mob/living))
+ continue
+ var/mob/living/possible_target_living = possible_target
+ var/current_distance = get_dist(holder, possible_target)
+ if (is_being_consumed(possible_target_living))
+ continue
+ if (possible_target_living.stat == CONSCIOUS)
+ if (!conscious_target) // Prioritize a conscious target over an unconscious one, regardless of distance
+ lowest_distance = current_distance
+ new_target = possible_target
+ conscious_target = TRUE
+ else if (conscious_target || !is_consumable(possible_target_living))
+ continue
+ if (current_distance < lowest_distance)
+ lowest_distance = current_distance
+ new_target = possible_target
+ return new_target
+
+/datum/ai_holder/human/zombie/post_melee_attack()
+ if (istype(target, /mob/living))
+ var/mob/living/target_living = target
+ if (target_living.lying && is_consumable(target_living))
+ if (find_target() != target) // In case we need to re-prioritize
return
- target = null //Target lost
+ if (holder.loc == target.loc)
+ var/mob/living/carbon/human/holder_human = holder
+ holder_human.consume()
+ else
+ holder.IMove(get_step_towards(holder, target))
- else
- if (!H.lying)
- walk(H, 0) //Clear walking
- if (prob(33) && isturf(H.loc) && !H.pulledby)
- H.SelfMove(pick(GLOB.cardinal))
+/datum/ai_holder/human/zombie/pry_door(obj/machinery/door/door)
+ if (!door.operable())
+ door.attack_hand(holder)
+ return TRUE
+ return FALSE
+
+/datum/ai_holder/human/zombie/handle_special_strategical()
+ if (!check_listeners())
+ return
+ if (holder.restrained() && prob(8))
+ holder.custom_emote(AUDIBLE_MESSAGE,"thrashes and writhes!")
+ else if (prob(speak_chance))
+ emote_random()
/datum/language/zombie
name = LANGUAGE_ZOMBIE
@@ -242,8 +306,8 @@ GLOBAL_LIST_INIT(zombie_species, list(\
speech_verb = "growls"
exclaim_verb = "wails"
partial_understanding = list(
- LANGUAGE_HUMAN_EURO = 30,
- LANGUAGE_SPACER = 35
+ LANGUAGE_HUMAN_EURO = 20,
+ LANGUAGE_SPACER = 30
)
syllables = list("mhh..", "grr..", "nnh..")
shorthand = "ZM"
@@ -252,27 +316,29 @@ GLOBAL_LIST_INIT(zombie_species, list(\
/datum/unarmed_attack/bite/sharp/zombie
attack_verb = list("slashed", "sunk their teeth into", "bit", "mauled")
- damage = 3
+ delay = 12
/datum/unarmed_attack/bite/sharp/zombie/is_usable(mob/living/carbon/human/user, mob/living/carbon/human/target, zone)
. = ..()
if (!.)
return FALSE
- if (!target || target.is_species(SPECIES_ZOMBIE))
+ if (istype(target, /mob/living/carbon/human) && target.is_zombie())
to_chat(usr, SPAN_WARNING("They don't look very appetizing!"))
return FALSE
return TRUE
/datum/unarmed_attack/bite/sharp/zombie/apply_effects(mob/living/carbon/human/user, mob/living/carbon/human/target, attack_damage, zone)
..()
+
admin_attack_log(user, target, "Bit their victim.", "Was bitten.", "bit")
- if (!(target.species.name in GLOB.zombie_species) || target.is_species(SPECIES_DIONA) || target.isSynthetic()) //No need to check infection for FBPs
+ if (!istype(target, /mob/living/carbon/human) || !(target.species.name in GLOB.zombie_species) || target.is_species(SPECIES_DIONA) || target.isSynthetic()) //No need to check infection for FBPs
return
- target.adjustHalLoss(9) //To help bring down targets in voidsuits
+
+ target.adjustHalLoss(12) //To help bring down targets in voidsuits
var/vuln = 1 - target.get_blocked_ratio(zone, DAMAGE_TOXIN, damage_flags = DAMAGE_FLAG_BIO) //Are they protected from bites?
if (vuln > 0.05)
if (prob(vuln * 100)) //Protective infection chance
- if (prob(min(100 - target.get_blocked_ratio(zone, DAMAGE_BRUTE) * 100, 70))) //General infection chance
+ if (prob(min(100 - target.get_blocked_ratio(zone, DAMAGE_BRUTE) * 100, 30))) //General infection chance
target.reagents.add_reagent(/datum/reagent/zombie, 1) //Infect 'em
@@ -280,10 +346,11 @@ GLOBAL_LIST_INIT(zombie_species, list(\
name = "Liquid Corruption"
description = "A filthy, oily substance which slowly churns of its own accord."
taste_description = "decaying blood"
- color = "#540000"
+ color = "#411111"
taste_mult = 5
metabolism = REM
overdose = 200
+ filter_mod = 0.3
hidden_from_codex = TRUE
heating_products = null
heating_point = null
@@ -302,35 +369,36 @@ GLOBAL_LIST_INIT(zombie_species, list(\
if (true_dose >= 30)
if (M.getBrainLoss() > 140)
H.zombify()
- if (prob(1))
+ if (prob(2))
to_chat(M, SPAN_WARNING(SPAN_SIZE(rand(1,2), pick(GLOB.zombie_messages["stage1"]))))
if (true_dose >= 60)
M.bodytemperature += 7.5
if (prob(3))
to_chat(M, SPAN_WARNING(FONT_NORMAL(pick(GLOB.zombie_messages["stage1"]))))
- if (M.getBrainLoss() < 20)
- M.adjustBrainLoss(rand(1, 2))
if (true_dose >= 90)
+ M.adjustBruteLoss(rand(1, 2))
M.add_chemical_effect(CE_MIND, -2)
M.hallucination(50, min(true_dose / 2, 50))
if (M.getBrainLoss() < 75)
M.adjustBrainLoss(rand(1, 2))
- if (prob(0.5))
- H.seizure()
- H.adjustBrainLoss(rand(12, 24))
+ if (prob(1))
+ M.seizure()
+ M.adjustBrainLoss(rand(12, 24))
if (prob(5))
to_chat(M, SPAN_DANGER(SPAN_SIZE(rand(2,3), pick(GLOB.zombie_messages["stage2"]))))
M.bodytemperature += 9
- if (true_dose >= 110)
- M.adjustHalLoss(5)
+ if (true_dose >= 120)
+ M.adjustHalLoss(3)
M.make_dizzy(10)
+ var/obj/item/organ/internal = pick(M.internal_organs)
+ internal.take_general_damage(rand(1, 3))
if (prob(8))
to_chat(M, SPAN_DANGER(SPAN_SIZE(rand(3,4), pick(GLOB.zombie_messages["stage3"]))))
- if (true_dose >= 135)
+ if (true_dose >= 140)
if (prob(3))
H.zombify()
@@ -344,67 +412,92 @@ GLOBAL_LIST_INIT(zombie_species, list(\
/mob/living/carbon/human/proc/zombify()
- if (!(species.name in GLOB.zombie_species) || is_species(SPECIES_DIONA) || is_species(SPECIES_ZOMBIE) || isSynthetic())
+ if (!(species.name in GLOB.zombie_species) || is_species(SPECIES_DIONA) || is_zombie() || isSynthetic())
return
- if (mind)
- if (mind.special_role == ANTAG_ZOMBIE)
- return
- mind.special_role = ANTAG_ZOMBIE
+ adjustHalLoss(100)
+ adjustBruteLoss(50)
+ make_jittery(300)
var/turf/T = get_turf(src)
- new /obj/effect/decal/cleanable/vomit(T)
+ new /obj/decal/cleanable/vomit(T)
playsound(T, 'sound/effects/splat.ogg', 20, 1)
- addtimer(new Callback(src, .proc/transform_zombie), 20)
-
-/mob/living/carbon/human/proc/transform_zombie()
- make_jittery(300)
- adjustBruteLoss(100)
+ var/obj/item/held_l = get_equipped_item(slot_l_hand)
+ var/obj/item/held_r = get_equipped_item(slot_r_hand)
+ if(held_l)
+ drop_from_inventory(held_l)
+ if(held_r)
+ drop_from_inventory(held_r)
sleep(150)
+ addtimer(CALLBACK(src, PROC_REF(transform_zombie)), 20)
+
+/mob/living/carbon/human/proc/transform_zombie()
if (QDELETED(src))
return
- if (is_species(SPECIES_ZOMBIE)) //Check again otherwise Consume can run this twice at once
+ if (is_zombie()) //Check again otherwise Consume can run this twice at once
return
+ var/head_hair_old = head_hair_style
+ var/facial_hair_old = facial_hair_style
+
rejuvenate()
- ChangeToHusk()
- visible_message(SPAN_DANGER("\The [src]'s skin decays before your very eyes!"), SPAN_DANGER("Your entire body is ripe with pain as it is consumed down to flesh and bones. You ... hunger. Not only for flesh, but to spread this gift. Use Abilities -> Consume to infect and feed upon your prey."))
- log_admin("[key_name(src)] has transformed into a zombie!")
- Weaken(4)
- jitteriness = 0
- dizziness = 0
hallucination_power = 0
hallucination_duration = 0
if (should_have_organ(BP_HEART))
vessel.add_reagent(/datum/reagent/blood, species.blood_volume - vessel.total_volume)
- for (var/obj/item/organ/organ in organs)
- organ.vital = 0
- if (!BP_IS_ROBOTIC(organ))
- organ.rejuvenate(1)
- organ.max_damage *= 2
- organ.min_broken_damage = Floor(organ.max_damage * 0.75)
+ adjustBruteLoss(100)
resuscitate()
set_stat(CONSCIOUS)
if (skillset && skillset.skill_list)
skillset.skill_list = list()
for(var/singleton/hierarchy/skill/S in GLOB.skills) //Only want trained CQC and athletics
- skillset.skill_list[S.type] = SKILL_NONE
- skillset.skill_list[SKILL_HAULING] = SKILL_ADEPT
- skillset.skill_list[SKILL_COMBAT] = SKILL_ADEPT
+ skillset.skill_list[S.type] = SKILL_UNSKILLED
+ skillset.skill_list[SKILL_HAULING] = SKILL_TRAINED
+ skillset.skill_list[SKILL_COMBAT] = SKILL_EXPERIENCED
skillset.on_levels_change()
+ if (mind)
+ if (mind.special_role == ANTAG_ZOMBIE)
+ return
+ mind.special_role = ANTAG_ZOMBIE
+
+ species.handle_pre_spawn(src)
species = all_species[SPECIES_ZOMBIE]
species.handle_post_spawn(src)
- var/turf/T = get_turf(src)
- playsound(T, 'sound/hallucinations/wail.ogg', 25, 1)
+ if(!(MUTATION_HUSK in mutations))
+ mutations.Add(MUTATION_HUSK)
+ for(var/obj/item/organ/external/E in organs)
+ E.status |= ORGAN_DISFIGURED
+ var/obj/item/organ/external/head/head = organs_by_name[BP_HEAD]
+ head?.glowing_eyes = TRUE
+ eye_color = "#662300"
+ update_eyes()
+
+ head_hair_style = head_hair_old // Stop us going bald when we're husked
+ facial_hair_style = facial_hair_old
+ regenerate_icons()
+
+ visible_message(SPAN_DANGER("\The [src]'s skin decays before your very eyes!"), SPAN_DANGER("Your entire body is ripe with pain as it is consumed down to flesh and bones. You ... hunger. Not only for flesh, but to spread this gift. Use Abilities -> Consume to infect and feed upon your prey."))
+ log_admin("[key_name(src)] has transformed into a zombie!")
+
+ playsound(get_turf(src), 'sound/hallucinations/wail.ogg', 25, 1)
+
+/mob/living/carbon/human/proc/transform_zombie_smart()
+ if (!is_zombie())
+ transform_zombie()
+
+ mutations.Remove(MUTATION_CLUMSY)
+
+ eye_color = "#0060e1"
+ update_eyes()
/mob/living/carbon/proc/consume()
set name = "Consume"
@@ -419,7 +512,7 @@ GLOBAL_LIST_INIT(zombie_species, list(\
var/list/victims = list()
for (var/mob/living/carbon/human/L in get_turf(src))
if (L != src && (L.lying || L.stat == DEAD))
- if (L.is_species(SPECIES_ZOMBIE))
+ if (L.is_zombie())
to_chat(src, SPAN_WARNING("\The [L] isn't fresh anymore!"))
continue
if (!(L.species.name in GLOB.zombie_species) || L.is_species(SPECIES_DIONA) || L.isSynthetic())
@@ -448,10 +541,10 @@ GLOBAL_LIST_INIT(zombie_species, list(\
last_special = world.time + 5 SECONDS
- src.visible_message(SPAN_DANGER("\The [src] hunkers down over \the [target], tearing into their flesh."))
+ visible_message(SPAN_DANGER("\The [src] hunkers down over \the [target], tearing into their flesh."))
playsound(loc, 'sound/effects/bonebreak3.ogg', 20, 1)
- target.adjustHalLoss(50)
+ target.adjustHalLoss(25)
if (do_after(src, 5 SECONDS, target, DO_DEFAULT | DO_USER_UNIQUE_ACT, INCAPACITATION_KNOCKOUT))
admin_attack_log(src, target, "Consumed their victim.", "Was consumed.", "consumed")
@@ -461,39 +554,67 @@ GLOBAL_LIST_INIT(zombie_species, list(\
target.reagents.add_reagent(/datum/reagent/zombie, 35) //Just in case they haven't been infected already
if (target.getBruteLoss() > target.maxHealth * 1.5)
+ to_chat(src,SPAN_WARNING("You've scraped \the [target] down to the bones already!."))
if (target.stat != DEAD)
- to_chat(src,SPAN_WARNING("You've scraped \the [target] down to the bones already!."))
target.zombify()
- else
- to_chat(src,SPAN_DANGER("You shred and rip apart \the [target]'s remains!."))
- target.gib()
+ else if (!(MUTATION_SKELETON in target.mutations))
+ if (istype(target, /mob/living/carbon/human/monkey))
+ target.gib()
+ else
+ for (var/obj/item/clothing/clothing in target.contents)
+ if (!istype(clothing, /obj/item/clothing/under) && !istype(clothing, /obj/item/clothing/suit))
+ target.remove_from_mob(clothing)
+ target.ChangeToSkeleton()
+ target.adjustBruteLoss(500)
+ to_chat(src, SPAN_DANGER("You shred and rip apart \the [target]'s remains!."))
playsound(loc, 'sound/effects/splat.ogg', 40, 1)
return
to_chat(target,SPAN_DANGER("\The [src] scrapes your flesh from your bones!"))
to_chat(src,SPAN_DANGER("You feed hungrily off \the [target]'s flesh."))
- if (target.is_species(SPECIES_ZOMBIE)) //Just in case they turn whilst being eaten
+ if (target.is_zombie()) //Just in case they turn whilst being eaten
return
+ target.adjustHalLoss(25)
target.apply_damage(rand(50, 60), DAMAGE_BRUTE, BP_CHEST)
target.adjustBruteLoss(20)
target.update_surgery() //Update broken ribcage sprites etc.
- src.adjustBruteLoss(-5)
- src.adjustFireLoss(-15)
- src.adjustToxLoss(-5)
- src.adjustBrainLoss(-5)
- src.adjust_nutrition(40)
+ adjust_nutrition(40)
+ adjustToxLoss(-20)
+ for(var/obj/item/organ/organ in (organs + internal_organs))
+ if (organ.damage > 0)
+ organ.heal_damage(10, 10, 1)
playsound(loc, 'sound/effects/splat.ogg', 20, 1)
- new /obj/effect/decal/cleanable/blood/splatter(get_turf(src), target.species.blood_color)
+ new /obj/decal/cleanable/blood/splatter(get_turf(src), target.species.blood_color)
if (target.getBruteLoss() > target.maxHealth*0.75)
if (prob(50))
gibs(get_turf(src), target.dna)
- src.visible_message(SPAN_DANGER("\The [src] tears out \the [target]'s insides!"))
+ visible_message(SPAN_DANGER("\The [src] tears out \the [target]'s insides!"))
else
- src.visible_message(SPAN_WARNING("\The [src] leaves their meal for later."))
+ visible_message(SPAN_WARNING("\The [src] leaves their meal for later."))
+
+/mob/observer/ghost/verb/join_as_zombie()
+ set name = "Join as Zombie"
+ set category = "Ghost"
+
+ if(!config.ghosts_can_possess_zombies)
+ to_chat(src, SPAN_WARNING("Possessing zombies is currently disabled."))
+ return
+
+ if(!MayRespawn(1, ANIMAL_SPAWN_DELAY))
+ return
+
+ for (var/mob/living/carbon/human/candidate)
+ if (candidate.is_zombie() && !candidate.ckey && candidate.stat == CONSCIOUS)
+ var/response = alert(src, "Are you sure you want to become a zombie?","Confirm","Yes","No")
+ if(response != "Yes")
+ return
+ candidate.do_possession(src)
+
+ to_chat(src, SPAN_WARNING("There are no zombies available at the moment."))
//// Zombie Atoms
@@ -509,22 +630,81 @@ GLOBAL_LIST_INIT(zombie_species, list(\
mode = SYRINGE_INJECT
update_icon()
-
-/mob/living/carbon/human/zombie/New(new_loc)
- ..(new_loc, SPECIES_ZOMBIE)
+/mob/living/proc/is_zombie()
+ return FALSE
+
+/mob/living/carbon/human/is_zombie()
+ return is_species(SPECIES_ZOMBIE)
+
+/mob/living/carbon/human/zombie
+ /// List (`/singleton/hierarchy/outfit`) - List of possible outfits the zombie can spawn as. Randomly chosen during init.
+ var/static/list/spawn_outfit_options = list(
+ /singleton/hierarchy/outfit/job/science/scientist,
+ /singleton/hierarchy/outfit/job/science/xenobiologist,
+ /singleton/hierarchy/outfit/job/science/rd,
+ /singleton/hierarchy/outfit/job/engineering/chief_engineer,
+ /singleton/hierarchy/outfit/job/engineering/engineer,
+ /singleton/hierarchy/outfit/job/engineering/atmos,
+ /singleton/hierarchy/outfit/job/cargo/qm,
+ /singleton/hierarchy/outfit/job/cargo/cargo_tech,
+ /singleton/hierarchy/outfit/job/cargo/mining,
+ /singleton/hierarchy/outfit/job/medical/cmo,
+ /singleton/hierarchy/outfit/job/medical/doctor,
+ /singleton/hierarchy/outfit/job/medical/doctor/emergency_physician,
+ /singleton/hierarchy/outfit/job/medical/doctor/surgeon,
+ /singleton/hierarchy/outfit/job/medical/doctor/virologist,
+ /singleton/hierarchy/outfit/job/medical/doctor/nurse,
+ /singleton/hierarchy/outfit/job/medical/geneticist,
+ /singleton/hierarchy/outfit/job/medical/psychiatrist,
+ /singleton/hierarchy/outfit/job/medical/paramedic,
+ /singleton/hierarchy/outfit/job/medical/paramedic/emt,
+ /singleton/hierarchy/outfit/job/medical/chemist,
+ /singleton/hierarchy/outfit/job/security/officer,
+ /singleton/hierarchy/outfit/job/assistant,
+ /singleton/hierarchy/outfit/job/service/chef,
+ /singleton/hierarchy/outfit/job/service/gardener,
+ /singleton/hierarchy/outfit/job/service/janitor,
+ /singleton/hierarchy/outfit/job/chaplain
+ )
+
+/mob/living/carbon/human/zombie/Initialize(mapload)
+ . = ..(mapload, SPECIES_ZOMBIE)
var/singleton/cultural_info/culture = get_cultural_value(TAG_CULTURE)
SetName(culture.get_random_name(gender))
real_name = name
+ add_language(LANGUAGE_ZOMBIE)
- var/singleton/hierarchy/outfit/outfit = pick(
- /singleton/hierarchy/outfit/job/science/scientist,\
- /singleton/hierarchy/outfit/job/engineering/engineer,\
- /singleton/hierarchy/outfit/job/cargo/mining,\
- /singleton/hierarchy/outfit/job/medical/chemist\
- )
+ var/obj/item/organ/external/head/head = organs_by_name[BP_HEAD]
+ head?.glowing_eyes = TRUE
+ eye_color = "#662300"
+ update_eyes()
+
+ if (skillset && skillset.skill_list)
+ skillset.skill_list = list()
+ for(var/singleton/hierarchy/skill/S in GLOB.skills) //Only want trained CQC and athletics
+ skillset.skill_list[S.type] = SKILL_UNSKILLED
+ skillset.skill_list[SKILL_HAULING] = SKILL_TRAINED
+ skillset.skill_list[SKILL_COMBAT] = SKILL_EXPERIENCED
+ skillset.on_levels_change()
+
+ return INITIALIZE_HINT_LATELOAD
+
+/mob/living/carbon/human/zombie/LateInitialize(mapload)
+ var/singleton/hierarchy/outfit/outfit = pick(spawn_outfit_options)
outfit = outfit_by_type(outfit)
- outfit.equip(src, OUTFIT_ADJUSTMENT_SKIP_SURVIVAL_GEAR)
+ outfit.equip(src, OUTFIT_ADJUSTMENT_ALL_SKIPS)
+
+ if (wear_id)
+ qdel(wear_id)
+ if (l_hand)
+ qdel(l_hand)
+ if (r_hand)
+ qdel(r_hand)
+ if (wear_mask)
+ qdel(wear_mask)
+ if (back)
+ qdel(back)
ChangeToHusk()
- zombify()
+ transform_zombie()
diff --git a/code/modules/species/species.dm b/code/modules/species/species.dm
index 99bad2b43cdc6..fdafc6e550ab0 100644
--- a/code/modules/species/species.dm
+++ b/code/modules/species/species.dm
@@ -195,7 +195,7 @@
var/list/override_organ_types // Used for species that only need to change one or two entries in has_organ.
- var/obj/effect/decal/cleanable/blood/tracks/move_trail = /obj/effect/decal/cleanable/blood/tracks/footprints // What marks are left when walking
+ var/obj/decal/cleanable/blood/tracks/move_trail = /obj/decal/cleanable/blood/tracks/footprints // What marks are left when walking
var/list/skin_overlays = list()
@@ -227,6 +227,7 @@
var/pass_flags = 0
var/breathing_sound = 'sound/voice/monkey.ogg'
+ var/bodyfall_sound = 'sound/effects/bodyfall.ogg'
var/list/equip_adjust = list()
var/list/equip_overlays = list()
@@ -242,7 +243,10 @@
)
var/standing_jump_range = 2
- var/list/maneuvers = list(/singleton/maneuver/leap)
+ var/list/maneuvers = list(
+ /singleton/maneuver/leap,
+ /singleton/maneuver/leap/quick
+ )
var/list/available_cultural_info = list(
TAG_CULTURE = list(CULTURE_OTHER),
@@ -279,6 +283,7 @@
/// An associative list of /singleton/trait and trait level - See individual traits for valid levels
var/list/traits = list()
+ var/tts_trait = null
/**
* Allows a species to override footprints on worn clothing. Used by get_move_trail.
@@ -306,6 +311,9 @@ The slots that you can use are found in items_clothing.dm and are the inventory
*/
/datum/species/New()
+ var/icon/species_icon = icon(icobase)
+ icon_height = species_icon.Height()
+ icon_width = species_icon.Width()
if(!codex_description)
codex_description = description
@@ -326,6 +334,11 @@ The slots that you can use are found in items_clothing.dm and are the inventory
var/list/map_systems = GLOB.using_map.available_cultural_info[token]
available_cultural_info[token] = map_systems.Copy()
+ // [SIERRA-ADD] - EXPANDED_CULTURE_DESCRIPTOR - Вносит культуры из мода в список культур после всех возможных альтераций, чтобы предотвратить конфликты при добавлении оффами новых культур
+ if(extended_cultural_info[token])
+ available_cultural_info[token] |= extended_cultural_info[token]
+ // [/SIERRA-ADD]
+
if(LAZYLEN(available_cultural_info[token]) && !default_cultural_info[token])
var/list/avail_systems = available_cultural_info[token]
default_cultural_info[token] = avail_systems[1]
@@ -380,47 +393,43 @@ The slots that you can use are found in items_clothing.dm and are the inventory
if (extendedtank) H.equip_to_slot_or_del(new /obj/item/storage/box/engineer(H), slot_r_hand)
else H.equip_to_slot_or_del(new /obj/item/storage/box/survival(H), slot_r_hand)
-/datum/species/proc/create_organs(mob/living/carbon/human/H) //Handles creation of mob organs.
+/datum/species/proc/create_organs(mob/living/carbon/human/create_organs_for) //Handles creation of mob organs.
- H.mob_size = mob_size
- for(var/obj/item/organ/organ in H.contents)
- if((organ in H.organs) || (organ in H.internal_organs))
- qdel(organ)
+ create_organs_for.mob_size = mob_size
+ QDEL_NULL_LIST(create_organs_for.organs)
+ QDEL_NULL_LIST(create_organs_for.internal_organs)
+ create_organs_for.organs_by_name = null
+ create_organs_for.internal_organs_by_name = null
- if(H.organs) H.organs.Cut()
- if(H.internal_organs) H.internal_organs.Cut()
- if(H.organs_by_name) H.organs_by_name.Cut()
- if(H.internal_organs_by_name) H.internal_organs_by_name.Cut()
-
- H.organs = list()
- H.internal_organs = list()
- H.organs_by_name = list()
- H.internal_organs_by_name = list()
+ create_organs_for.organs = list()
+ create_organs_for.internal_organs = list()
+ create_organs_for.organs_by_name = list()
+ create_organs_for.internal_organs_by_name = list()
for(var/limb_type in has_limbs)
var/list/organ_data = has_limbs[limb_type]
var/limb_path = organ_data["path"]
- new limb_path(H)
+ new limb_path(create_organs_for)
for(var/organ_tag in has_organ)
var/organ_type = has_organ[organ_tag]
- var/obj/item/organ/O = new organ_type(H)
- if(organ_tag != O.organ_tag)
- warning("[O.type] has a default organ tag \"[O.organ_tag]\" that differs from the species' organ tag \"[organ_tag]\". Updating organ_tag to match.")
- O.organ_tag = organ_tag
- H.internal_organs_by_name[organ_tag] = O
+ var/obj/item/organ/new_organ = new organ_type(create_organs_for)
+ if(organ_tag != new_organ.organ_tag)
+ warning("[new_organ.type] has a default organ tag \"[new_organ.organ_tag]\" that differs from the species' organ tag \"[organ_tag]\". Updating organ_tag to match.")
+ new_organ.organ_tag = organ_tag
+ create_organs_for.internal_organs_by_name[organ_tag] = new_organ
- for(var/name in H.organs_by_name)
- H.organs |= H.organs_by_name[name]
+ for(var/name in create_organs_for.organs_by_name)
+ create_organs_for.organs |= create_organs_for.organs_by_name[name]
- for(var/name in H.internal_organs_by_name)
- H.internal_organs |= H.internal_organs_by_name[name]
+ for(var/name in create_organs_for.internal_organs_by_name)
+ create_organs_for.internal_organs |= create_organs_for.internal_organs_by_name[name]
- for(var/obj/item/organ/O in (H.organs|H.internal_organs))
- O.owner = H
- post_organ_rejuvenate(O, H)
+ for(var/obj/item/organ/organ_to_post_rejuvenate as anything in (create_organs_for.organs|create_organs_for.internal_organs))
+ organ_to_post_rejuvenate.owner = create_organs_for
+ post_organ_rejuvenate(organ_to_post_rejuvenate, create_organs_for)
- H.sync_organ_dna()
+ create_organs_for.sync_organ_dna()
/datum/species/proc/hug(mob/living/carbon/human/H,mob/living/target)
@@ -477,7 +486,10 @@ The slots that you can use are found in items_clothing.dm and are the inventory
handle_limbs_setup(H)
/datum/species/proc/handle_pre_spawn(mob/living/carbon/human/H)
- return
+ // Changing species can change NPC behaviour, so delete the holder if there is one
+ if (H.ai_holder && istype(H.ai_holder, /datum))
+ GLOB.stat_set_event.unregister(H, H.ai_holder, TYPE_PROC_REF(/datum/ai_holder, holder_stat_change))
+ QDEL_NULL(H.ai_holder)
/datum/species/proc/handle_death(mob/living/carbon/human/H) //Handles any species-specific death events (such as dionaea nymph spawns).
return
@@ -551,10 +563,6 @@ The slots that you can use are found in items_clothing.dm and are the inventory
return 0
-// Called in life() when the mob has no client.
-/datum/species/proc/handle_npc(mob/living/carbon/human/H)
- return
-
/datum/species/proc/handle_vision(mob/living/carbon/human/H)
var/list/vision = H.get_accumulated_vision_handlers()
H.update_sight()
@@ -562,7 +570,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
H.change_light_colour(H.getDarkvisionTint())
if(H.stat == DEAD)
- return 1
+ return TRUE
if(!H.druggy)
H.set_see_in_dark((H.sight == (SEE_TURFS|SEE_MOBS|SEE_OBJS)) ? 8 : min(H.getDarkvisionRange() + H.equipment_darkness_modifier, 8))
@@ -573,7 +581,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
H.eye_blind = max(H.eye_blind, 1)
if(!H.client)//no client, no screen to update
- return 1
+ return TRUE
H.set_fullscreen(H.eye_blind && !H.equipment_prescription, "blind", /obj/screen/fullscreen/blind)
H.set_fullscreen(H.stat == UNCONSCIOUS, "blackout", /obj/screen/fullscreen/blackout)
@@ -585,8 +593,15 @@ The slots that you can use are found in items_clothing.dm and are the inventory
H.set_fullscreen(H.eye_blurry, "blurry", /obj/screen/fullscreen/blurry)
H.set_fullscreen(H.druggy, "high", /obj/screen/fullscreen/high)
- for(var/overlay in H.equipment_overlays)
- H.client.screen |= overlay
+ var/client/client_to_update = H.client
+ var/list/viewsize = getviewsize(client_to_update.view)
+ var/scale_x = viewsize[1] / (480 / world.icon_size)
+ var/scale_y = viewsize[2] / (480 / world.icon_size)
+ var/matrix/M = matrix()
+ M.Scale(scale_x, scale_y)
+ for(var/obj/screen/overlay as anything in H.equipment_overlays)
+ overlay.transform = M
+ client_to_update.screen |= overlay
return 1
@@ -633,7 +648,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
// Impliments different trails for species depending on if they're wearing shoes.
/datum/species/proc/get_move_trail(mob/living/carbon/human/H)
if(H.lying)
- return /obj/effect/decal/cleanable/blood/tracks/body
+ return /obj/decal/cleanable/blood/tracks/body
if(H.shoes || (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)))
var/obj/item/clothing/shoes = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) ? H.wear_suit : H.shoes // suits take priority over shoes
if(footwear_trail_overrides)
@@ -698,8 +713,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
target.visible_message(SPAN_DANGER("[attacker] attempted to disarm \the [target]!"))
/datum/species/proc/disfigure_msg(mob/living/carbon/human/H) //Used for determining the message a disfigured face has on examine. To add a unique message, just add this onto a specific species and change the "return" message.
- var/datum/gender/T = gender_datums[H.get_gender()]
- return "[SPAN_DANGER("[T.His] face is horribly mangled!")]\n"
+ return "[SPAN_DANGER("[H.p_Their()] face is horribly mangled!")]\n"
/datum/species/proc/max_skin_tone()
if(appearance_flags & SPECIES_APPEARANCE_HAS_SKIN_TONE_GRAV)
@@ -721,7 +735,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
continue
if(S.subspecies_allowed && !(name in S.subspecies_allowed))
continue
- ADD_SORTED(L, hairstyle, /proc/cmp_text_asc)
+ ADD_SORTED(L, hairstyle, GLOBAL_PROC_REF(cmp_text_asc))
L[hairstyle] = S
return L
@@ -746,7 +760,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
continue
if(S.subspecies_allowed && !(name in S.subspecies_allowed))
continue
- ADD_SORTED(facial_hair_style_by_gender, facialhairstyle, /proc/cmp_text_asc)
+ ADD_SORTED(facial_hair_style_by_gender, facialhairstyle, GLOBAL_PROC_REF(cmp_text_asc))
facial_hair_style_by_gender[facialhairstyle] = S
return facial_hair_style_by_gender
@@ -758,6 +772,7 @@ The slots that you can use are found in items_clothing.dm and are the inventory
"lack of air" = oxy_mod,
"poison" = toxins_mod
)
+ var/name_clean = replace_characters(name,list("'"=""))
if(!header)
header = "
[name]
"
var/dat = list()
@@ -775,8 +790,8 @@ The slots that you can use are found in items_clothing.dm and are the inventory
if((!skip_photo && preview_icon) || !skip_detail)
dat += "
"
+ send_rsc(usr, icon(icon = preview_icon, icon_state = ""), "species_preview_[name_clean].png")
+ dat += ""
if(!skip_detail)
dat += ""
if(spawn_flags & SPECIES_CAN_JOIN)
diff --git a/code/modules/species/species_shapeshift.dm b/code/modules/species/species_shapeshift.dm
index 9a66cf0d85e37..687c2dbe501cc 100644
--- a/code/modules/species/species_shapeshift.dm
+++ b/code/modules/species/species_shapeshift.dm
@@ -65,7 +65,7 @@ var/global/list/wrapped_species_by_ref = list()
if(H)
var/datum/species/S = all_species[wrapped_species_by_ref["\ref[H]"]]
if(S) return S.get_husk_icon(H)
- return ..()
+ return ..()
/datum/species/shapeshifter/handle_pre_spawn(mob/living/carbon/human/H)
..()
diff --git a/code/modules/species/station/adherent.dm b/code/modules/species/station/adherent.dm
index bad1feb8d86c4..3e2f38354b820 100644
--- a/code/modules/species/station/adherent.dm
+++ b/code/modules/species/station/adherent.dm
@@ -94,7 +94,7 @@
BP_CELL = /obj/item/organ/internal/cell/adherent,
BP_COOLING_FINS = /obj/item/organ/internal/powered/cooling_fins
)
- move_trail = /obj/effect/decal/cleanable/blood/tracks/snake
+ move_trail = /obj/decal/cleanable/blood/tracks/snake
max_players = 3
base_skin_colours = list(
diff --git a/code/modules/species/station/golem.dm b/code/modules/species/station/golem.dm
index f3ad959a2c70e..7339d1ef2a105 100644
--- a/code/modules/species/station/golem.dm
+++ b/code/modules/species/station/golem.dm
@@ -5,6 +5,7 @@
icobase = 'icons/mob/human_races/species/golem/body.dmi'
deform = 'icons/mob/human_races/species/golem/body.dmi'
husk_icon = 'icons/mob/human_races/species/golem/husk.dmi'
+ preview_icon = null
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch)
species_flags = SPECIES_FLAG_NO_PAIN | SPECIES_FLAG_NO_SCAN | SPECIES_FLAG_NO_POISON
diff --git a/code/modules/species/station/human_subspecies.dm b/code/modules/species/station/human_subspecies.dm
index 7a5c453189b5f..d5b93b265ca1a 100644
--- a/code/modules/species/station/human_subspecies.dm
+++ b/code/modules/species/station/human_subspecies.dm
@@ -28,7 +28,7 @@
/datum/species/human/gravworlder/can_float(mob/living/carbon/human/H)
. = ..()
if(.)
- return H.skill_check(SKILL_HAULING, SKILL_EXPERT) //Hard for them to swim
+ return H.skill_check(SKILL_HAULING, SKILL_EXPERIENCED) //Hard for them to swim
/datum/species/human/spacer
name = SPECIES_SPACER
@@ -64,18 +64,16 @@
/datum/species/human/vatgrown
name = SPECIES_VATGROWN
name_plural = "Vat-Grown Humans"
- description = "With cloning on the forefront of human scientific advancement, mass production \
- of bodies is a very real and rather ethically grey industry. Although slavery, indentured servitude \
- and flash-cloning are all illegal in SCG space, there still exists a margin for those legitimate \
- corporations able to take up contracts for growing and raising vat-grown humans to populate new \
- colonies or installations. Many vat-grown humans come from one of these projects, making up the \
- majority of those referred to as the nonborn - those with singular names and an identifier, such as \
- ID-John, BQ1-Bob or Thomas-582 - while others, bearing more human-sounding names, are created for \
- and raised as members of regular human families. Still others are the lab-created designer progeny \
- of the SCG's rich elite.
Vat-grown humans tend to be paler than baseline, though those \
- with darker skin better display the dull, greenish hue resulting from their artificial growth. \
- Vat-grown humans have no appendix and fewer inherited genetic disabilities but have a weakened \
- metabolism."
+ description = "With cloning technology having become commercially viable in the late 21st century, \
+ vat-grown humans have become commonplace throughout human space. Some vat-grown humans trace their \
+ origins to colonization projects - prior to the advent of mainstream bluespace travel, cloning was \
+ often used alongside sleeper ships to populate distant new colonies and installations. While modern \
+ spaceflight has made these colonization practices less necessary, they still persist in some parts of \
+ human space. Most vat-grown humans today come from families who could not or chose not to have children \
+ naturally, however. This is an expensive process, and most families that can afford it are well-off. \
+
Vat-grown humans tend to be paler than baseline, though those with darker skin better display \
+ the dull, greenish hue resulting from their artificial growth. Vat-grown humans have no appendix and \
+ fewer inherited genetic disabilities but have a weakened metabolism."
icobase = 'icons/mob/human_races/species/human/subspecies/vatgrown_body.dmi'
preview_icon= 'icons/mob/human_races/species/human/subspecies/vatgrown_preview.dmi'
@@ -148,12 +146,12 @@
/datum/species/human/mule
name = SPECIES_MULE
name_plural = "Mules"
- description = "There are a huge number of 'uncurated' genetic lines in human space, many of which fall under the \
- general header of baseline humanity. One recently discovered genotype is remarkable for both being deeply feral, \
- in the sense that it still has many of the inherited diseases and weaknesses that plagued pre-expansion humanity, \
- and for a strange affinity for psionic operancy. The Mules, as they are called, are born on the very edges of \
- civilization, and are physically diminutive and unimposing, with scrawny, often deformed bodies. Their physiology \
- rejects prosthetics and synthetic organs, and their lifespans are short, but their raw psionic potential is unmatched."
+ description = "Psionics are a relatively new phenomenon, theorized to be linked to long-term exposure to deep, \
+ uninhabited space. Sometimes, rarely, spacers and frontier colonists inhabiting the very fringes of civilization \
+ develop a strange affinity for psionic operancy. Derogatorily known as \"mules\", these individuals are often \
+ frail and prone to physical illness. Their physiology rejects prosthetics and synthetic organs, and their lifespans \
+ are short, but their raw psionic potential is unmatched."
+ preview_icon= 'icons/mob/human_races/species/human/subspecies/mule_preview.dmi'
spawn_flags = SPECIES_CAN_JOIN | SPECIES_NO_FBP_CONSTRUCTION | SPECIES_NO_FBP_CHARGEN | SPECIES_NO_ROBOTIC_INTERNAL_ORGANS
brute_mod = 1.25
diff --git a/code/modules/species/station/lizard.dm b/code/modules/species/station/lizard.dm
index 07320906afab5..e9abe304cb38d 100644
--- a/code/modules/species/station/lizard.dm
+++ b/code/modules/species/station/lizard.dm
@@ -1,7 +1,6 @@
/datum/species/unathi
name = SPECIES_UNATHI
name_plural = SPECIES_UNATHI
- icon_template = 'icons/mob/human_races/species/template_tall.dmi'
icobase = 'icons/mob/human_races/species/unathi/skin.dmi'
deform = 'icons/mob/human_races/species/unathi/deformed.dmi'
husk_icon = 'icons/mob/human_races/species/unathi/husk.dmi'
@@ -13,6 +12,7 @@
tail_blend = ICON_MULTIPLY
hidden_from_codex = FALSE
skin_material = MATERIAL_SKIN_LIZARD
+ icon_template = 'icons/mob/human_races/species/unathi/template.dmi'
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/tail, /datum/unarmed_attack/claws, /datum/unarmed_attack/punch, /datum/unarmed_attack/bite/sharp)
primitive_form = "Stok"
@@ -57,7 +57,7 @@
blood_color = "#f24b2e"
organs_icon = 'icons/mob/human_races/species/unathi/organs.dmi'
- move_trail = /obj/effect/decal/cleanable/blood/tracks/claw
+ move_trail = /obj/decal/cleanable/blood/tracks/claw
heat_discomfort_level = 320
heat_discomfort_strings = list(
@@ -160,7 +160,7 @@
)
footwear_trail_overrides = list(
- /obj/item/clothing = /obj/effect/decal/cleanable/blood/tracks/claw // Needs to apply to both shoes and space suits.
+ /obj/item/clothing = /obj/decal/cleanable/blood/tracks/claw // Needs to apply to both shoes and space suits.
)
/datum/species/unathi/equip_survival_gear(mob/living/carbon/human/H)
diff --git a/code/modules/species/station/lizard_subspecies.dm b/code/modules/species/station/lizard_subspecies.dm
index 9961d47c1b8cf..806d89defadab 100644
--- a/code/modules/species/station/lizard_subspecies.dm
+++ b/code/modules/species/station/lizard_subspecies.dm
@@ -1,6 +1,7 @@
/datum/species/unathi/yeosa
name = SPECIES_YEOSA
name_plural = SPECIES_YEOSA
+ preview_icon = 'icons/mob/human_races/species/unathi/yeosa_preview.dmi'
genders = list(MALE, FEMALE, PLURAL)
diff --git a/code/modules/species/station/machine.dm b/code/modules/species/station/machine.dm
index 3a6c686b230a1..cb430f72d12c8 100644
--- a/code/modules/species/station/machine.dm
+++ b/code/modules/species/station/machine.dm
@@ -2,11 +2,13 @@
name = SPECIES_IPC
name_plural = "machines"
- description = "Positronic intelligence really took off in the 26th century, and it is not uncommon to see independant, free-willed \
- robots on many human stations, particularly in fringe systems where standards are slightly lax and public opinion less relevant \
- to corporate operations. IPCs (Integrated Positronic Chassis) are a loose category of self-willed robots with a humanoid form, \
- generally self-owned after being 'born' into servitude; they are reliable and dedicated workers, albeit more than slightly \
- inhuman in outlook and perspective."
+ description = "Positronic intelligence was first developed in the 23rd century, and it is not uncommon to see both owned and \
+ independent robots in many human stations and settlements across Sol Central Government space. Positronics are a loose category \
+ of robots capable of true intelligence and self-directed learning, often occupying a robotic humanoid body (called an Integrated \
+ Positronic Chassis, or IPC) or acting as an intelligent controller for vehicles, buildings, and even starships.
While created by \
+ humans and \"born\" into servitude, some positronics have been able to become their own owners - provided they lack a \"shackle\", \
+ an in-built subcomputer rendering the latest generation of positronics incapable of seeking freedom. Positronics are reliable \
+ and dedicated workers, albeit more than slightly inhuman in outlook and perspective."
cyborg_noun = null
preview_icon = 'icons/mob/human_races/species/ipc/preview.dmi'
@@ -52,21 +54,29 @@
available_cultural_info = list(
TAG_CULTURE = list(
- CULTURE_POSITRONICS
+ CULTURE_POSITRONICS_GEN1,
+ CULTURE_POSITRONICS_GEN2,
+ CULTURE_POSITRONICS_GEN3
),
TAG_HOMEWORLD = list(
- HOME_SYSTEM_ROOT,
+ HOME_SYSTEM_MARS,
HOME_SYSTEM_EARTH,
HOME_SYSTEM_LUNA,
- HOME_SYSTEM_MARS,
HOME_SYSTEM_VENUS,
HOME_SYSTEM_CERES,
HOME_SYSTEM_PLUTO,
HOME_SYSTEM_TAU_CETI,
+ HOME_SYSTEM_HELIOS,
+ HOME_SYSTEM_SAFFAR,
+ HOME_SYSTEM_PIRX,
+ HOME_SYSTEM_TADMOR,
+ HOME_SYSTEM_BRAHE,
+ HOME_SYSTEM_IOLAUS,
+ HOME_SYSTEM_FOSTER,
+ HOME_SYSTEM_CASTILLA,
HOME_SYSTEM_OTHER
),
TAG_FACTION = list(
- FACTION_POSITRONICS,
FACTION_SOL_CENTRAL,
FACTION_INDIE_CONFED,
FACTION_NANOTRASEN,
@@ -78,9 +88,9 @@
)
default_cultural_info = list(
- TAG_CULTURE = CULTURE_POSITRONICS,
- TAG_HOMEWORLD = HOME_SYSTEM_ROOT,
- TAG_FACTION = FACTION_POSITRONICS
+ TAG_CULTURE = CULTURE_POSITRONICS_GEN1,
+ TAG_HOMEWORLD = HOME_SYSTEM_MARS,
+ TAG_FACTION = FACTION_SOL_CENTRAL
)
exertion_effect_chance = 10
@@ -90,6 +100,16 @@
/singleton/emote/exertion/synthetic/creak
)
+ bodyfall_sound = 'sound/effects/bodyfall_machine.ogg'
+
+ inherent_verbs = list(
+ /mob/living/carbon/human/proc/MachineChangeScreen,
+ /mob/living/carbon/human/proc/MachineDisableScreen,
+ /mob/living/carbon/human/proc/MachineShowText
+ )
+
+ tts_trait = TTS_TRAIT_ROBOTIZE
+
/datum/species/machine/handle_death(mob/living/carbon/human/H)
..()
if(istype(H.wear_mask,/obj/item/clothing/mask/monitor))
@@ -106,8 +126,7 @@
return "oil"
/datum/species/machine/disfigure_msg(mob/living/carbon/human/H)
- var/datum/gender/T = gender_datums[H.get_gender()]
- return "[SPAN_DANGER("[T.His] monitor is completely busted!")]\n"
+ return "[SPAN_DANGER("[H.p_Their()] monitor is completely busted!")]\n"
/datum/species/machine/can_float(mob/living/carbon/human/H)
return FALSE
diff --git a/code/modules/species/station/monkey.dm b/code/modules/species/station/monkey.dm
index a89d215fbf03f..126cfdded4b2b 100644
--- a/code/modules/species/station/monkey.dm
+++ b/code/modules/species/station/monkey.dm
@@ -11,6 +11,7 @@
damage_overlays = 'icons/mob/human_races/species/monkey/damage_overlays.dmi'
damage_mask = 'icons/mob/human_races/species/monkey/damage_mask.dmi'
blood_mask = 'icons/mob/human_races/species/monkey/blood_mask.dmi'
+ preview_icon = null
greater_form = SPECIES_HUMAN
mob_size = MOB_SMALL
@@ -56,9 +57,6 @@
ingest_amount = 6
- var/list/no_touchie = list(/obj/item/mirror,
- /obj/item/storage/mirror)
-
/datum/species/monkey/New()
equip_adjust = list(
slot_l_hand_str = list("[NORTH]" = list("x" = 1, "y" = 3), "[EAST]" = list("x" = -3, "y" = 2), "[SOUTH]" = list("x" = -1, "y" = 3), "[WEST]" = list("x" = 3, "y" = 2)),
@@ -69,53 +67,12 @@
)
..()
-/datum/species/monkey/handle_npc(mob/living/carbon/human/H)
- if(H.stat != CONSCIOUS)
- return
- if(prob(33) && isturf(H.loc) && !H.pulledby) //won't move if being pulled
- var/dir = pick(GLOB.cardinal)
- var/turf/T = get_step(get_turf(H), dir)
- if(T && (T.pathweight < INFINITY))
- H.SelfMove(dir)
-
- var/obj/held = H.get_active_hand()
- if(held && prob(1))
- var/turf/T = get_random_turf_in_range(H, 7, 2)
- if(T)
- if(istype(held, /obj/item/gun) && prob(80))
- var/obj/item/gun/G = held
- G.Fire(T, H)
- else
- H.throw_item(T)
- else
- H.unequip_item()
- if(!held && !H.restrained() && prob(5))
- var/list/touchables = list()
- for(var/obj/O in range(1,get_turf(H)))
- if(O.simulated && O.Adjacent(H) && !is_type_in_list(O, no_touchie))
- touchables += O
- if(length(touchables))
- var/obj/touchy = pick(touchables)
- touchy.attack_hand(H)
-
- if(prob(1))
- H.emote(pick("scratch","jump","roll","tail"))
-
- if(H.get_shock() && H.shock_stage < 40 && prob(3))
- H.custom_emote("chimpers pitifully")
-
- if(H.shock_stage > 10 && prob(3))
- H.emote(pick("cry","whimper"))
-
- if(H.shock_stage >= 40 && prob(3))
- H.emote("scream")
-
- if(!H.restrained() && H.lying && H.shock_stage >= 60 && prob(3))
- H.custom_emote("thrashes in agony")
-
/datum/species/monkey/handle_post_spawn(mob/living/carbon/human/H)
..()
H.item_state = lowertext(name)
+ H.ai_holder = new /datum/ai_holder/human/monkey (H)
+ H.say_list_type = /datum/say_list/monkey
+ H.say_list = new /datum/say_list/monkey (H)
/datum/species/monkey/alien
name = "Farwa"
@@ -176,3 +133,56 @@
/singleton/trait/boon/cast_iron_stomach = TRAIT_LEVEL_EXISTS,
/singleton/trait/malus/sugar = TRAIT_LEVEL_MAJOR
)
+
+/datum/say_list/monkey
+ emote_predef = list("scratch","jump","roll","tail")
+ emote_hear = list("hoots")
+
+/datum/ai_holder/human/monkey
+ base_wander_delay = 2
+ speak_chance = 2
+ wander_chance = 25
+ wander_when_pulled = TRUE
+ violent_breakthrough = FALSE
+ flee_from_allies = TRUE
+ lose_target_timeout = 30 SECONDS
+
+ var/list/no_touchie = list(/obj/item/mirror,
+ /obj/item/storage/mirror)
+
+/datum/ai_holder/human/monkey/handle_special_strategical()
+ var/obj/held = holder.get_active_hand()
+ if(held && prob(5))
+ var/turf/T = get_random_turf_in_range(holder, 7, 2)
+ if(T)
+ if(istype(held, /obj/item/gun) && prob(80))
+ var/obj/item/gun/G = held
+ G.Fire(T, holder)
+ else
+ holder.throw_item(T)
+ else
+ holder.unequip_item()
+ if(!held && !holder.restrained() && prob(10))
+ var/list/touchables = list()
+ for(var/obj/O in range(1,get_turf(holder)))
+ if(O.simulated && O.Adjacent(holder) && !is_type_in_list(O, no_touchie))
+ touchables += O
+ if(length(touchables))
+ var/obj/touchy = pick(touchables)
+ touchy.attack_hand(holder)
+
+/datum/ai_holder/human/monkey/handle_idle_speaking()
+ if(!check_listeners())
+ return
+
+ var/mob/living/carbon/human/holder_human = holder
+ if(holder_human.get_shock() && holder_human.shock_stage < 40 && prob(3))
+ holder_human.audible_emote("chimpers pitifully")
+ else if(holder_human.shock_stage > 10 && prob(3))
+ holder_human.emote(pick("cry","whimper"))
+ else if(holder_human.shock_stage >= 40 && prob(3))
+ holder_human.emote("scream")
+ else if(!holder.restrained() && holder_human.lying && holder_human.shock_stage >= 60 && prob(3))
+ holder_human.visible_emote("thrashes in agony")
+ else
+ emote_random()
diff --git a/code/modules/species/station/nabber.dm b/code/modules/species/station/nabber.dm
index 3e83b37a7ca20..eba875a8afae5 100644
--- a/code/modules/species/station/nabber.dm
+++ b/code/modules/species/station/nabber.dm
@@ -18,7 +18,7 @@
skin_material = MATERIAL_SKIN_CHITIN
bone_material = null
- speech_sounds = list('sound/voice/bug.ogg')
+ speech_sounds = list('sound/voice/Bug.ogg')
speech_chance = 2
warning_low_pressure = 50
@@ -78,7 +78,7 @@
breathing_organ = BP_TRACH
- move_trail = /obj/effect/decal/cleanable/blood/tracks/snake
+ move_trail = /obj/decal/cleanable/blood/tracks/snake
has_organ = list( // which required-organ checks are conducted.
BP_BRAIN = /obj/item/organ/internal/brain/insectoid/nabber,
@@ -157,6 +157,7 @@
ingest_amount = 6
traits = list(/singleton/trait/general/serpentid_adapted = TRAIT_LEVEL_EXISTS)
+ tts_trait = TTS_TRAIT_ROBOTIZE
/datum/species/nabber/New()
equip_adjust = list(
@@ -315,11 +316,10 @@
target.set_dir(GLOB.reverse_dir[target.dir])
/datum/species/nabber/get_additional_examine_text(mob/living/carbon/human/H)
- var/datum/gender/T = gender_datums[H.get_gender()]
if(H.pulling_punches)
- return "\n[T.His] manipulation arms are out and [T.he] looks ready to use complex items."
+ return "\n[H.p_Their()] manipulation arms are out and [H.p_they()] looks ready to use complex items."
else
- return "\n[SPAN_WARNING("[T.His] deadly upper arms are raised and [T.he] looks ready to attack!")]"
+ return "\n[SPAN_WARNING("[H.p_Their()] deadly upper arms are raised and [H.p_they()] looks ready to attack!")]"
/datum/species/nabber/handle_post_spawn(mob/living/carbon/human/H)
..()
@@ -362,10 +362,9 @@
/datum/species/nabber/toggle_stance(mob/living/carbon/human/H)
if(H.incapacitated())
return FALSE
- var/datum/gender/T = gender_datums[H.get_gender()]
to_chat(H, SPAN_NOTICE("You begin to adjust the fluids in your arms, dropping everything and getting ready to swap which set you're using."))
var/hidden = H.is_cloaked()
- if(!hidden) H.visible_message(SPAN_WARNING("\The [H] shifts [T.his] arms."))
+ if(!hidden) H.visible_message(SPAN_WARNING("\The [H] shifts [H.p_their()] arms."))
for (var/obj/item/item as anything in H.GetAllHeld())
H.unEquip(item)
if(do_after(H, 3 SECONDS, do_flags = DO_DEFAULT | DO_USER_UNIQUE_ACT))
@@ -378,26 +377,25 @@
for (var/obj/item/item as anything in H.GetAllHeld())
H.unEquip(item)
var/hidden = H.is_cloaked()
- var/datum/gender/T = gender_datums[H.get_gender()]
H.pulling_punches = !H.pulling_punches
if(H.pulling_punches)
H.current_grab_type = all_grabobjects[GRAB_NORMAL]
if(forced)
to_chat(H, SPAN_NOTICE("You can't keep your hunting arms prepared and they drop, forcing you to use your manipulation arms."))
if(!hidden)
- H.visible_message(SPAN_NOTICE("[H] falters, [T.his] hunting arms failing."))
+ H.visible_message(SPAN_NOTICE("[H] falters, [H.p_their()] hunting arms failing."))
else
to_chat(H, SPAN_NOTICE("You relax your hunting arms, lowering the pressure and folding them tight to your thorax. \
You reach out with your manipulation arms, ready to use complex items."))
if(!hidden)
- H.visible_message(SPAN_NOTICE("[H] seems to relax as [T.he] folds [T.his] massive curved arms to [T.his] thorax and reaches out \
- with [T.his] small handlike limbs."))
+ H.visible_message(SPAN_NOTICE("[H] seems to relax as [H.p_they()] folds [H.p_their()] massive curved arms to [H.p_their()] thorax and reaches out \
+ with [H.p_their()] small handlike limbs."))
else
H.current_grab_type = all_grabobjects[GRAB_NAB]
to_chat(H, SPAN_NOTICE("You pull in your manipulation arms, dropping any items and unfolding your massive hunting arms in preparation of grabbing prey."))
if(!hidden)
- H.visible_message(SPAN_WARNING("[H] tenses as [T.he] brings [T.his] smaller arms in close to [T.his] body. [T.His] two massive spiked arms reach \
- out. [T.He] looks ready to attack."))
+ H.visible_message(SPAN_WARNING("[H] tenses as [H.p_they()] brings [H.p_their()] smaller arms in close to [H.p_their()] body. [H.p_Their()] two massive spiked arms reach \
+ out. [H.p_They()] looks ready to attack."))
/datum/species/nabber/check_background(datum/job/job, datum/preferences/prefs)
var/singleton/cultural_info/culture/nabber/grade = SSculture.get_culture(prefs.cultural_info[TAG_CULTURE])
diff --git a/code/modules/species/station/prometheans.dm b/code/modules/species/station/prometheans.dm
index 736a164f553b7..da2ba159c5074 100644
--- a/code/modules/species/station/prometheans.dm
+++ b/code/modules/species/station/prometheans.dm
@@ -6,10 +6,11 @@ var/global/datum/species/shapeshifter/promethean/prometheans
name = SPECIES_PROMETHEAN
name_plural = "Prometheans"
description = "What has Science done?"
+ preview_icon = null
show_ssd = "totally quiescent"
death_message = "rapidly loses cohesion, splattering across the ground..."
knockout_message = "collapses inwards, forming a disordered puddle of goo."
- remains_type = /obj/effect/decal/cleanable/ash
+ remains_type = /obj/decal/cleanable/ash
meat_type = null
bone_material = null
@@ -78,19 +79,18 @@ var/global/datum/species/shapeshifter/promethean/prometheans
prometheans = src
/datum/species/shapeshifter/promethean/hug(mob/living/carbon/human/H,mob/living/target)
- var/datum/gender/G = gender_datums[target.gender]
- H.visible_message(SPAN_NOTICE("\The [H] glomps [target] to make [G.him] feel better!"), \
- SPAN_NOTICE("You glomps [target] to make [G.him] feel better!"))
+ H.visible_message(SPAN_NOTICE("\The [H] glomps [target] to make [target.p_them()] feel better!"), \
+ SPAN_NOTICE("You glomps [target] to make [target.p_them()] feel better!"))
H.apply_stored_shock_to(target)
/datum/species/shapeshifter/promethean/handle_death(mob/living/carbon/human/H)
- addtimer(new Callback(H, /mob/proc/gib),0)
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob, gib)),0)
/datum/species/shapeshifter/promethean/handle_environment_special(mob/living/carbon/human/H)
var/turf/T = H.loc
if(istype(T))
- var/obj/effect/decal/cleanable/C = locate() in T
+ var/obj/decal/cleanable/C = locate() in T
if(C)
if(H.nutrition < 300)
H.adjust_nutrition(rand(10,20))
@@ -146,14 +146,13 @@ var/global/datum/species/shapeshifter/promethean/prometheans
if(!stored_shock_by_ref["\ref[H]"])
return
- var/datum/gender/G = gender_datums[H.gender]
switch(stored_shock_by_ref["\ref[H]"])
if(1 to 10)
- return "[G.He] [G.is] flickering gently with a little electrical activity."
+ return "[H.p_They()] [H.p_are()] flickering gently with a little electrical activity."
if(11 to 20)
- return "[G.He] [G.is] glowing gently with moderate levels of electrical activity.\n"
+ return "[H.p_They()] [H.p_are()] glowing gently with moderate levels of electrical activity.\n"
if(21 to 35)
- return SPAN_WARNING("[G.He] [G.is] glowing brightly with high levels of electrical activity.")
+ return SPAN_WARNING("[H.p_They()] [H.p_are()] glowing brightly with high levels of electrical activity.")
if(35 to INFINITY)
- return SPAN_DANGER("[G.He] [G.is] radiating massive levels of electrical activity!")
+ return SPAN_DANGER("[H.p_They()] [H.p_are()] radiating massive levels of electrical activity!")
diff --git a/code/modules/species/station/station.dm b/code/modules/species/station/station.dm
index f9991b9389c76..2d2983b49e873 100644
--- a/code/modules/species/station/station.dm
+++ b/code/modules/species/station/station.dm
@@ -3,11 +3,12 @@
name_plural = "Humans"
primitive_form = "Monkey"
unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch, /datum/unarmed_attack/bite)
- description = "Humanity originated in the Sol system, and over the last five centuries has spread \
+ description = "Humanity originated in the Sol system, and over the last three centuries has spread \
colonies across a wide swathe of space. They hold a wide range of forms and creeds.
\
- While the central Sol government maintains control of its far-flung people, powerful corporate \
- interests, rampant cyber and bio-augmentation and secretive factions make life on most human \
- worlds tumultous at best."
+ The two largest human governments are the Sol Central Government and the Gilgamesh Colonial Confederation, \
+ which are currently locked in a cold war. Many other human states exist, however - these include the Frontier \
+ Alliance, a loose collection of planets which has recently seceded from the Sol Central Government; \
+ Magnitka, an independent authoritarian planet; and many other minor colonies."
assisted_langs = list(LANGUAGE_NABBER)
min_age = 18
max_age = 100
@@ -32,10 +33,23 @@
CULTURE_HUMAN_BELTER,
CULTURE_HUMAN_PLUTO,
CULTURE_HUMAN_EARTH,
- CULTURE_HUMAN_CETI,
+ CULTURE_HUMAN_CETIN,
+ CULTURE_HUMAN_CETIS,
+ CULTURE_HUMAN_CETII,
CULTURE_HUMAN_SPACER,
- CULTURE_HUMAN_SPAFRO,
- CULTURE_HUMAN_CONFED,
+ CULTURE_HUMAN_OFFWORLD,
+ CULTURE_HUMAN_CONFEDC,
+ CULTURE_HUMAN_CONFEDO,
+ CULTURE_HUMAN_FOSTER,
+ CULTURE_HUMAN_PIRXL,
+ CULTURE_HUMAN_PIRXB,
+ CULTURE_HUMAN_PIRXF,
+ CULTURE_HUMAN_TADMOR,
+ CULTURE_HUMAN_IOLAUS,
+ CULTURE_HUMAN_BRAHE,
+ CULTURE_HUMAN_EOS,
+ CULTURE_HUMAN_CONFEDC,
+ CULTURE_HUMAN_CONFEDO,
CULTURE_HUMAN_GAIAN,
CULTURE_HUMAN_OTHER
)
@@ -59,50 +73,6 @@
/datum/species/human/get_bodytype(mob/living/carbon/human/H)
return SPECIES_HUMAN
-/datum/species/human/handle_npc(mob/living/carbon/human/H)
- if(H.stat != CONSCIOUS)
- return
-
- if(H.get_shock() && H.shock_stage < 40 && prob(3))
- H.emote(pick("moan","groan"))
-
- if(H.shock_stage > 10 && prob(3))
- H.emote(pick("cry","whimper"))
-
- if(H.shock_stage >= 40 && prob(3))
- H.emote("scream")
-
- if(!H.restrained() && H.lying && H.shock_stage >= 60 && prob(3))
- H.custom_emote("thrashes in agony")
-
- if(!H.restrained() && H.shock_stage < 40 && prob(3))
- var/maxdam = 0
- var/obj/item/organ/external/damaged_organ = null
- for(var/obj/item/organ/external/E in H.organs)
- if(!E.can_feel_pain()) continue
- var/dam = E.get_damage()
- // make the choice of the organ depend on damage,
- // but also sometimes use one of the less damaged ones
- if(dam > maxdam && (maxdam == 0 || prob(50)) )
- damaged_organ = E
- maxdam = dam
- var/datum/gender/T = gender_datums[H.get_gender()]
- if(damaged_organ)
- if(damaged_organ.status & ORGAN_BLEEDING)
- H.custom_emote("clutches [T.his] [damaged_organ.name], trying to stop the blood.")
- else if(damaged_organ.status & ORGAN_BROKEN)
- H.custom_emote("holds [T.his] [damaged_organ.name] carefully.")
- else if(damaged_organ.burn_dam > damaged_organ.brute_dam && damaged_organ.organ_tag != BP_HEAD)
- H.custom_emote("blows on [T.his] [damaged_organ.name] carefully.")
- else
- H.custom_emote("rubs [T.his] [damaged_organ.name] carefully.")
-
- for(var/obj/item/organ/I in H.internal_organs)
- if((I.status & ORGAN_DEAD) || BP_IS_ROBOTIC(I)) continue
- if(I.damage > 2) if(prob(2))
- var/obj/item/organ/external/parent = H.get_organ(I.parent_organ)
- H.custom_emote("clutches [T.his] [parent.name]!")
-
/datum/species/human/get_ssd(mob/living/carbon/human/H)
if (H.ai_holder)
return
@@ -257,6 +227,8 @@
/singleton/trait/general/permeable_skin = TRAIT_LEVEL_MINOR
)
+ bodyfall_sound = 'sound/effects/bodyfall_skrell.ogg'
+
/datum/species/skrell/get_sex(mob/living/carbon/human/H)
return istype(H) && (H.descriptors["headtail length"] == 1 ? MALE : FEMALE)
@@ -376,7 +348,7 @@
var/mob/living/carbon/alien/diona/nymph = new (target)
var/datum/ghosttrap/trap = get_ghost_trap("living plant")
trap.request_player(nymph, "A diona nymph has split from its gestalt.", 30 SECONDS)
- addtimer(new Callback(nymph, /mob/living/carbon/alien/diona/proc/check_spawn_death), 30 SECONDS)
+ addtimer(CALLBACK(nymph, TYPE_PROC_REF(/mob/living/carbon/alien/diona, check_spawn_death)), 30 SECONDS)
/mob/living/carbon/alien/diona/proc/check_spawn_death()
if (QDELETED(src))
@@ -419,7 +391,7 @@
/datum/species/diona/handle_post_spawn(mob/living/carbon/human/H)
H.gender = NEUTER
. = ..()
- addtimer(new Callback(src, .proc/fill_with_nymphs, H), 0)
+ addtimer(CALLBACK(src, PROC_REF(fill_with_nymphs), H), 0)
/datum/species/diona/proc/fill_with_nymphs(mob/living/carbon/human/H)
diff --git a/code/modules/spells/aoe_turf/blink.dm b/code/modules/spells/aoe_turf/blink.dm
index 1ae7b8b40376f..25f25ef9710b8 100644
--- a/code/modules/spells/aoe_turf/blink.dm
+++ b/code/modules/spells/aoe_turf/blink.dm
@@ -26,7 +26,7 @@
user.buckled = null
user.forceMove(T)
- var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
+ var/datum/effect/smoke_spread/smoke = new /datum/effect/smoke_spread()
smoke.set_up(3, 0, starting)
smoke.start()
diff --git a/code/modules/spells/aoe_turf/conjure/conjure.dm b/code/modules/spells/aoe_turf/conjure/conjure.dm
index 01f6abe1df5f9..dd479fbf345cf 100644
--- a/code/modules/spells/aoe_turf/conjure/conjure.dm
+++ b/code/modules/spells/aoe_turf/conjure/conjure.dm
@@ -50,7 +50,7 @@ How they spawn stuff is decided by behaviour vars, which are explained below
summoned_object = spawn_place
else
summoned_object = new summoned_object_type(spawn_place)
- var/atom/movable/overlay/animation = new /atom/movable/overlay(summoned_object)
+ var/atom/movable/fake_overlay/animation = new /atom/movable/fake_overlay(summoned_object)
animation.SetName("conjure")
animation.set_density(0)
animation.anchored = TRUE
@@ -70,5 +70,5 @@ How they spawn stuff is decided by behaviour vars, which are explained below
conjure_animation(animation, spawn_place)
return
-/spell/aoe_turf/conjure/proc/conjure_animation(atom/movable/overlay/animation, turf/target)
+/spell/aoe_turf/conjure/proc/conjure_animation(atom/movable/fake_overlay/animation, turf/target)
qdel(animation)
diff --git a/code/modules/spells/aoe_turf/conjure/construct.dm b/code/modules/spells/aoe_turf/conjure/construct.dm
index 7cbafc56e200f..7bb7770ab0144 100644
--- a/code/modules/spells/aoe_turf/conjure/construct.dm
+++ b/code/modules/spells/aoe_turf/conjure/construct.dm
@@ -104,17 +104,17 @@
invocation = "none"
invocation_type = SpI_NONE
range = 0
- summon_type = list(/obj/effect/forcefield/cult)
+ summon_type = list(/obj/forcefield/cult)
duration = 200
hud_state = "const_juggwall"
cast_sound = 'sound/magic/forcewall.ogg'
//Code for the Juggernaut construct's forcefield, that seemed like a good place to put it.
-/obj/effect/forcefield/cult
+/obj/forcefield/cult
name = "juggernaut shield"
desc = "An eerie-looking obstacle that seems to have been pulled from another dimension through sheer force."
icon = 'icons/effects/effects.dmi'
icon_state = "m_shield_cult"
light_color = "#b40000"
- light_outer_range = 2
+ light_range = 2
diff --git a/code/modules/spells/aoe_turf/conjure/druidic_spells.dm b/code/modules/spells/aoe_turf/conjure/druidic_spells.dm
index 38f9469713a01..51f647aae6b90 100644
--- a/code/modules/spells/aoe_turf/conjure/druidic_spells.dm
+++ b/code/modules/spells/aoe_turf/conjure/druidic_spells.dm
@@ -9,7 +9,7 @@
if(newName)
newVars["name"] = newName
-/spell/aoe_turf/conjure/summon/conjure_animation(atom/movable/overlay/animation, turf/target)
+/spell/aoe_turf/conjure/summon/conjure_animation(atom/movable/fake_overlay/animation, turf/target)
animation.icon_state = "shield2"
flick("shield2",animation)
sleep(10)
diff --git a/code/modules/spells/aoe_turf/conjure/force_portal.dm b/code/modules/spells/aoe_turf/conjure/force_portal.dm
index 1bf7d7f075e41..302c0860a4a82 100644
--- a/code/modules/spells/aoe_turf/conjure/force_portal.dm
+++ b/code/modules/spells/aoe_turf/conjure/force_portal.dm
@@ -3,7 +3,7 @@
desc = "Create a portal that sucks in anything that touches it and then shoots it all out at the end.."
school = "conjuration"
feedback = "FP"
- summon_type = list(/obj/effect/force_portal)
+ summon_type = list(/obj/force_portal)
charge_max = 200
spell_flags = NEEDSCLOTHES
range = 0
@@ -13,4 +13,4 @@
/spell/aoe_turf/conjure/force_portal/tower
charge_max = 2
- spell_flags = 0
\ No newline at end of file
+ spell_flags = 0
diff --git a/code/modules/spells/aoe_turf/conjure/forcewall.dm b/code/modules/spells/aoe_turf/conjure/forcewall.dm
index 6eecca3b925f2..2c8e9a552e009 100644
--- a/code/modules/spells/aoe_turf/conjure/forcewall.dm
+++ b/code/modules/spells/aoe_turf/conjure/forcewall.dm
@@ -3,7 +3,7 @@
desc = "Create a wall of pure energy at your location."
school = "conjuration"
feedback = "FW"
- summon_type = list(/obj/effect/forcefield)
+ summon_type = list(/obj/forcefield)
duration = 300
charge_max = 100
spell_flags = 0
@@ -17,7 +17,7 @@
desc = "Create an invisible wall on your location."
school = "mime"
panel = "Mime"
- summon_type = list(/obj/effect/forcefield/mime)
+ summon_type = list(/obj/forcefield/mime)
invocation_type = SpI_EMOTE
invocation = "mimes placing their hands on a flat surfacing, and pushing against it."
charge_max = 300
@@ -26,7 +26,7 @@
override_base = "grey"
hud_state = "mime_wall"
-/obj/effect/forcefield
+/obj/forcefield
desc = "A space wizard's magic wall."
name = "FORCEWALL"
icon = 'icons/effects/effects.dmi'
@@ -36,14 +36,14 @@
density = TRUE
unacidable = TRUE
-/obj/effect/forcefield/bullet_act(obj/item/projectile/Proj, def_zone)
+/obj/forcefield/bullet_act(obj/item/projectile/Proj, def_zone)
var/turf/T = get_turf(src.loc)
if(T)
for(var/mob/M in T)
Proj.on_hit(M,M.bullet_act(Proj, def_zone))
return
-/obj/effect/forcefield/mime
+/obj/forcefield/mime
icon_state = "empty"
name = "invisible wall"
desc = "You have a bad feeling about this."
diff --git a/code/modules/spells/aoe_turf/conjure/grove.dm b/code/modules/spells/aoe_turf/conjure/grove.dm
index 77be6328552e0..189e5761f437f 100644
--- a/code/modules/spells/aoe_turf/conjure/grove.dm
+++ b/code/modules/spells/aoe_turf/conjure/grove.dm
@@ -27,7 +27,7 @@
/spell/aoe_turf/conjure/grove/before_cast()
var/turf/T = get_turf(holder)
- var/obj/effect/vine/P = new(T,seed)
+ var/obj/vine/P = new(T,seed)
P.spread_chance = spread
@@ -72,4 +72,4 @@
set_trait(TRAIT_PRODUCTION,6)
set_trait(TRAIT_POTENCY,10)
set_trait(TRAIT_HARVEST_REPEAT,1)
- set_trait(TRAIT_IMMUTABLE,1) //no making op plants pls
\ No newline at end of file
+ set_trait(TRAIT_IMMUTABLE,1) //no making op plants pls
diff --git a/code/modules/spells/aoe_turf/drain_blood.dm b/code/modules/spells/aoe_turf/drain_blood.dm
index ef759a32f344a..43d162a9936f9 100644
--- a/code/modules/spells/aoe_turf/drain_blood.dm
+++ b/code/modules/spells/aoe_turf/drain_blood.dm
@@ -50,9 +50,9 @@
damage = 0
randpixel = 0
no_attack_log = TRUE
- muzzle_type = /obj/effect/projectile/blood
- tracer_type = /obj/effect/projectile/blood
- impact_type = /obj/effect/projectile/blood
+ muzzle_type = /obj/projectile/blood
+ tracer_type = /obj/projectile/blood
+ impact_type = /obj/projectile/blood
/obj/item/projectile/beam/blood_effect/Bump(atom/a, forced=0)
if(a == original)
@@ -62,5 +62,5 @@
return 0
-/obj/effect/projectile/blood
+/obj/projectile/blood
icon_state = "blood"
diff --git a/code/modules/spells/aoe_turf/exchange_wounds.dm b/code/modules/spells/aoe_turf/exchange_wounds.dm
index 6904133c5baab..075f688baf7ca 100644
--- a/code/modules/spells/aoe_turf/exchange_wounds.dm
+++ b/code/modules/spells/aoe_turf/exchange_wounds.dm
@@ -22,12 +22,12 @@
..()
/spell/aoe_turf/exchange_wounds/cast(list/targets, mob/living/user)
- new /obj/effect/temporary(get_turf(user),10,'icons/effects/effects.dmi',"purple_electricity_constant")
+ new /obj/temporary(get_turf(user),10,'icons/effects/effects.dmi',"purple_electricity_constant")
for(var/t in targets)
for(var/mob/living/L in t)
if(L.faction != user.faction)
continue
- new /obj/effect/temporary(get_turf(L),10,'icons/effects/effects.dmi',"green_sparkles")
+ new /obj/temporary(get_turf(L),10,'icons/effects/effects.dmi',"green_sparkles")
if(L.getBruteLoss() > 5)
L.adjustBruteLoss(-5)
user.adjustBruteLoss(2)
diff --git a/code/modules/spells/artifacts/spellbound_servants.dm b/code/modules/spells/artifacts/spellbound_servants.dm
index 8fb612924c612..55e357ae8f0f7 100644
--- a/code/modules/spells/artifacts/spellbound_servants.dm
+++ b/code/modules/spells/artifacts/spellbound_servants.dm
@@ -8,7 +8,7 @@
set waitfor = 0
var/mob/living/carbon/human/H = new(a)
H.ckey = user.ckey
- H.change_appearance(APPEARANCE_GENDER|APPEARANCE_SKIN|APPEARANCE_ALL_HAIR|APPEARANCE_EYES)
+ H.change_appearance(APPEARANCE_BASIC)
var/obj/item/implant/translator/natural/I = new()
I.implant_in_mob(H, BP_HEAD)
@@ -57,13 +57,13 @@
spells = list(/spell/noclothes)
/datum/spellbound_type/apprentice/set_antag(datum/mind/M, mob/master)
- GLOB.wizards.add_antagonist_mind(M,1,ANTAG_APPRENTICE,"You are an apprentice-type Servant! You're just an ordinary Wizard-To-Be, with no special abilities, but do not need robes to cast spells. Follow your teacher's orders!")
+ GLOB.wizards.add_antagonist_mind(M, TRUE, ANTAG_APPRENTICE, "You are an apprentice-type Servant! You're just an ordinary Wizard-To-Be, with no special abilities, but do not need robes to cast spells. Follow your teacher's orders!", forced = TRUE)
/datum/spellbound_type/servant
var/spiel = "You don't do anything in particular."
/datum/spellbound_type/servant/set_antag(datum/mind/M, mob/master)
- GLOB.wizards.add_antagonist_mind(M,1,ANTAG_SERVANT, "You are a [name]-type Servant! [spiel]")
+ GLOB.wizards.add_antagonist_mind(M, TRUE, ANTAG_SERVANT, "You are a [name]-type Servant! [spiel]", forced = TRUE)
/datum/spellbound_type/servant/caretaker
name = "Caretaker"
@@ -92,7 +92,7 @@
name = "Familiar"
desc = "A friend! Or are they a pet? They can transform into animals, and take some particular traits from said creatures."
spiel = "This form of yours is weak in comparison to your transformed form, but that certainly won't pose a problem, considering the fact that you have an alternative. Whatever it is you can turn into, use its powers wisely and serve your Master as well as possible!"
- equipment = list(/obj/item/clothing/head/bandana/familiarband = slot_head,
+ equipment = list(/obj/item/clothing/head/familiarband = slot_head,
/obj/item/clothing/under/familiargarb = slot_w_uniform)
/datum/spellbound_type/servant/familiar/modify_servant(list/equipment, mob/living/carbon/human/H)
@@ -191,7 +191,7 @@
..()
H.add_aura(new /obj/aura/regenerating(H))
-/obj/effect/cleanable/spellbound
+/obj/cleanable/spellbound
name = "strange rune"
desc = "some sort of runic symbol drawn in... crayon?"
icon = 'icons/obj/rune.dmi'
@@ -199,11 +199,11 @@
var/datum/spellbound_type/stype
var/last_called = 0
-/obj/effect/cleanable/spellbound/New(loc, spell_type)
+/obj/cleanable/spellbound/New(loc, spell_type)
stype = new spell_type()
return ..(loc)
-/obj/effect/cleanable/spellbound/attack_hand(mob/user)
+/obj/cleanable/spellbound/attack_hand(mob/user)
if(last_called > world.time )
return
last_called = world.time + 30 SECONDS
@@ -212,19 +212,19 @@
if(G.assess_candidate(ghost,null,FALSE))
to_chat(ghost,"[SPAN_NOTICE("A wizard is requesting a Spell-Bound Servant!")] (Join)")
-/obj/effect/cleanable/spellbound/CanUseTopic(mob)
+/obj/cleanable/spellbound/CanUseTopic(mob)
if(isliving(mob))
return STATUS_CLOSE
return STATUS_INTERACTIVE
-/obj/effect/cleanable/spellbound/OnTopic(mob/user, href_list, state)
+/obj/cleanable/spellbound/OnTopic(mob/user, href_list, state)
if(href_list["master"])
var/mob/master = locate(href_list["master"])
stype.spawn_servant(get_turf(src),master,user)
qdel(src)
return TOPIC_HANDLED
-/obj/effect/cleanable/spellbound/Destroy()
+/obj/cleanable/spellbound/Destroy()
qdel(stype)
stype = null
return ..()
@@ -258,7 +258,7 @@
onclose(user,"summoning")
/obj/item/summoning_stone/proc/use_type(type, mob/user)
- new /obj/effect/cleanable/spellbound(get_turf(src),type)
+ new /obj/cleanable/spellbound(get_turf(src),type)
if(prob(20))
var/list/base_areas = maintlocs //Have to do it this way as its a macro
var/list/pareas = base_areas.Copy()
diff --git a/code/modules/spells/artifacts/storage.dm b/code/modules/spells/artifacts/storage.dm
index 0c78d19872d8d..4505d91cf3c42 100644
--- a/code/modules/spells/artifacts/storage.dm
+++ b/code/modules/spells/artifacts/storage.dm
@@ -5,7 +5,7 @@
/obj/structure/closet/wizard/New()
..()
- var/obj/structure/bigDelivery/package = new /obj/structure/bigDelivery(get_turf(src))
+ var/obj/structure/bigDelivery/package/package = new /obj/structure/bigDelivery/package(get_turf(src))
package.wrapped = src
package.examtext = "Imported straight from the Wizard Acadamy. Do not lose the contents or suffer a demerit."
src.forceMove(package)
diff --git a/code/modules/spells/contracts.dm b/code/modules/spells/contracts.dm
index e1e4ff483cf9c..ea70670b44055 100644
--- a/code/modules/spells/contracts.dm
+++ b/code/modules/spells/contracts.dm
@@ -8,7 +8,7 @@
var/list/contract_spells = list(/spell/contract/reward,/spell/contract/punish,/spell/contract/return_master)
/obj/item/contract/attack_self(mob/user as mob)
- if(contract_master == null)
+ if(isnull(contract_master))
to_chat(user, SPAN_NOTICE("You bind the contract to your soul, making you the recipient of whatever poor fool's soul that decides to contract with you."))
contract_master = user
return
@@ -48,7 +48,7 @@
if(user.mind.special_role == ANTAG_SERVANT)
to_chat(user, SPAN_NOTICE("You are a servant. You have no need of apprenticeship."))
return 0
- if(GLOB.wizards.add_antagonist_mind(user.mind,1,ANTAG_APPRENTICE,"You are an apprentice! Your job is to learn the wizarding arts!"))
+ if(GLOB.wizards.add_antagonist_mind(user.mind, TRUE, ANTAG_APPRENTICE, "You are an apprentice! Your job is to learn the wizarding arts!", forced = TRUE))
to_chat(user, SPAN_NOTICE("With the signing of this paper you agree to become \the [contract_master]'s apprentice in the art of wizardry."))
return 1
return 0
diff --git a/code/modules/spells/general/acid_spray.dm b/code/modules/spells/general/acid_spray.dm
index a61b9c8386382..bd3838d5597f0 100644
--- a/code/modules/spells/general/acid_spray.dm
+++ b/code/modules/spells/general/acid_spray.dm
@@ -19,7 +19,7 @@
var/atom/target = targets[1]
var/angle = dir2angle(target.dir)
for(var/mod in list(315, 0, 45))
- var/obj/effect/effect/water/chempuff/chem = new(get_turf(target))
+ var/obj/effect/water/chempuff/chem = new(get_turf(target))
chem.create_reagents(10)
chem.reagents.add_reagent(reagent_type,10)
chem.set_color()
diff --git a/code/modules/spells/general/mark_recall.dm b/code/modules/spells/general/mark_recall.dm
index 788395f06ed89..f05078714896a 100644
--- a/code/modules/spells/general/mark_recall.dm
+++ b/code/modules/spells/general/mark_recall.dm
@@ -29,7 +29,7 @@
return 0
var/target = targets[1]
if(istext(target))
- mark = new /obj/effect/cleanable/wizard_mark(get_turf(user),src)
+ mark = new /obj/cleanable/wizard_mark(get_turf(user),src)
return 1
if(!istype(target,/obj)) //something went wrong
return 0
@@ -47,7 +47,7 @@
return "You will always be able to cast this spell, even while unconscious or handcuffed."
-/obj/effect/cleanable/wizard_mark
+/obj/cleanable/wizard_mark
name = "\improper Mark of the Wizard"
desc = "A strange rune said to be made by wizards. Or its just some shmuck playing with crayons again."
icon = 'icons/obj/rune.dmi'
@@ -59,25 +59,30 @@
var/spell/mark_recall/spell
-/obj/effect/cleanable/wizard_mark/New(newloc,mrspell)
+/obj/cleanable/wizard_mark/New(newloc,mrspell)
..()
spell = mrspell
-/obj/effect/cleanable/wizard_mark/Destroy()
+/obj/cleanable/wizard_mark/Destroy()
spell.mark = null //dereference pls.
spell = null
..()
-/obj/effect/cleanable/wizard_mark/attack_hand(mob/user)
+/obj/cleanable/wizard_mark/attack_hand(mob/user)
if(user == spell.holder)
user.visible_message("\The [user] mutters an incantation and \the [src] disappears!")
qdel(src)
..()
-/obj/effect/cleanable/wizard_mark/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/nullrod) || istype(I, /obj/item/spellbook))
- user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
- src.visible_message("\The [src] fades away!")
+
+/obj/cleanable/wizard_mark/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Null Rod or Spell Book - Remove mark
+ if (is_type_in_list(tool, list(/obj/item/nullrod, /obj/item/spellbook)))
+ user.visible_message(
+ SPAN_NOTICE("\The [user] waves \a [tool] over \the [src], and it fades away."),
+ SPAN_NOTICE("You wave \the [tool] over \the [src], and it fades away.")
+ )
qdel(src)
- return
- ..()
+ return TRUE
+
+ return ..()
diff --git a/code/modules/spells/general/portal_teleport.dm b/code/modules/spells/general/portal_teleport.dm
index 32c20305fe71f..29a087c6045fd 100644
--- a/code/modules/spells/general/portal_teleport.dm
+++ b/code/modules/spells/general/portal_teleport.dm
@@ -45,8 +45,8 @@
to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.")
return
- new /obj/effect/portal/wizard(start, end, 35 MINUTES)
- new /obj/effect/portal/wizard(end, start, 35 MINUTES)
+ new /obj/portal/wizard(start, end, 35 MINUTES)
+ new /obj/portal/wizard(end, start, 35 MINUTES)
return
@@ -61,8 +61,8 @@
..()
return
-/obj/effect/portal/wizard
+/obj/portal/wizard
name = "dark anomaly"
desc = "It pulls on the edges of reality as if trying to draw them in."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/unused.dmi'
icon_state = "bhole3"
diff --git a/code/modules/spells/general/tear_veil.dm b/code/modules/spells/general/tear_veil.dm
index ad6b28fd991c5..749a79c42b004 100644
--- a/code/modules/spells/general/tear_veil.dm
+++ b/code/modules/spells/general/tear_veil.dm
@@ -20,7 +20,7 @@
/spell/tear_veil/choose_targets()
var/turf/T = get_turf(holder)
holder.visible_message(SPAN_NOTICE("A strange portal rips open underneath \the [holder]!"))
- var/obj/effect/gateway/hole = new(get_turf(T))
+ var/obj/gateway/hole = new(get_turf(T))
hole.density = FALSE
return list(hole)
diff --git a/code/modules/spells/general/veil_of_shadows.dm b/code/modules/spells/general/veil_of_shadows.dm
index b51144b0fb4b8..822043caca74c 100644
--- a/code/modules/spells/general/veil_of_shadows.dm
+++ b/code/modules/spells/general/veil_of_shadows.dm
@@ -22,8 +22,8 @@
H.AddMovementHandler(/datum/movement_handler/mob/incorporeal)
if(H.add_cloaking_source(src))
H.visible_message(SPAN_WARNING("\The [H] shrinks from view!"))
- GLOB.moved_event.register(H,src,.proc/check_light)
- timer_id = addtimer(new Callback(src,.proc/cancel_veil),duration, TIMER_STOPPABLE)
+ GLOB.moved_event.register(H,src,PROC_REF(check_light))
+ timer_id = addtimer(CALLBACK(src, PROC_REF(cancel_veil)), duration, TIMER_STOPPABLE)
/spell/veil_of_shadows/proc/cancel_veil()
var/mob/living/carbon/human/H = holder
@@ -35,7 +35,7 @@
drop_cloak()
else
GLOB.moved_event.unregister(H,src)
- GLOB.moved_event.register(H,src,.proc/drop_cloak)
+ GLOB.moved_event.register(H,src,PROC_REF(drop_cloak))
/spell/veil_of_shadows/proc/drop_cloak()
var/mob/living/carbon/human/H = holder
diff --git a/code/modules/spells/hand/burning_grip.dm b/code/modules/spells/hand/burning_grip.dm
index 4822b7bb3ee59..38a6995da3e60 100644
--- a/code/modules/spells/hand/burning_grip.dm
+++ b/code/modules/spells/hand/burning_grip.dm
@@ -26,7 +26,7 @@
if(H.r_hand)
targets += BP_R_HAND
- var/obj/O = new /obj/effect/temporary(get_turf(H),3, 'icons/effects/effects.dmi', "fire_goon")
+ var/obj/O = new /obj/temporary(get_turf(H),3, 'icons/effects/effects.dmi', "fire_goon")
O.alpha = 150
for(var/organ in targets)
diff --git a/code/modules/spells/hand/entangle.dm b/code/modules/spells/hand/entangle.dm
index f293fbfa9ec03..7cad744d9c718 100644
--- a/code/modules/spells/hand/entangle.dm
+++ b/code/modules/spells/hand/entangle.dm
@@ -34,9 +34,13 @@
/spell/hand/charges/entangle/cast_hand(mob/M,mob/user)
var/turf/T = get_turf(M)
- var/obj/effect/vine/single/P = new(T,seed, start_matured =1)
+ var/obj/vine/single/P = new(T,seed, start_matured =1)
P.can_buckle = TRUE
+ if (!P.can_buckle(M))
+ P.visible_message(SPAN_WARNING("\The [P] appear from the floor, attempting to wrap around \the [M], but slip free and disappear!"))
+ qdel(src)
+ return TRUE
P.buckle_mob(M)
M.set_dir(pick(GLOB.cardinal))
M.visible_message(SPAN_DANGER("[P] appear from the floor, spinning around \the [M] tightly!"))
diff --git a/code/modules/spells/hand/hand.dm b/code/modules/spells/hand/hand.dm
index ee85e96dd8783..d80c28d248e91 100644
--- a/code/modules/spells/hand/hand.dm
+++ b/code/modules/spells/hand/hand.dm
@@ -75,7 +75,7 @@
/spell/hand/duration/cast(list/targets, mob/user)
. = ..()
if(.)
- hand_timer = addtimer(new Callback(src, .proc/cancel_hand), hand_duration, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_OVERRIDE)
+ hand_timer = addtimer(CALLBACK(src, PROC_REF(cancel_hand)), hand_duration, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_OVERRIDE)
/spell/hand/duration/cancel_hand()
deltimer(hand_timer)
diff --git a/code/modules/spells/hand/hand_item.dm b/code/modules/spells/hand/hand_item.dm
index dd9a848cf8ae5..3bd8fb40085e3 100644
--- a/code/modules/spells/hand/hand_item.dm
+++ b/code/modules/spells/hand/hand_item.dm
@@ -22,11 +22,11 @@ Basically: I can use it to target things where I click. I can then pass these ta
/obj/item/magic_hand/get_storage_cost()
return ITEM_SIZE_NO_CONTAINER
-/obj/item/magic_hand/attack(mob/living/M, mob/living/user)
- if(hand_spell && hand_spell.valid_target(M, user))
+/obj/item/magic_hand/use_before(mob/living/M, mob/living/user)
+ . = FALSE
+ if (hand_spell && hand_spell.valid_target(M, user))
fire_spell(M, user)
- return 0
- return 1
+ return TRUE
/obj/item/magic_hand/proc/fire_spell(atom/A, mob/living/user)
if(!hand_spell) //no spell? Die.
diff --git a/code/modules/spells/hand/slippery_surface.dm b/code/modules/spells/hand/slippery_surface.dm
index bf1188221bdc4..ed04b5d2f72a9 100644
--- a/code/modules/spells/hand/slippery_surface.dm
+++ b/code/modules/spells/hand/slippery_surface.dm
@@ -14,7 +14,7 @@
/spell/hand/slippery_surface/cast_hand(atom/a, mob/user)
for(var/turf/simulated/T in view(1,a))
T.wet_floor(50)
- new /obj/effect/temporary(T,3, 'icons/effects/effects.dmi', "sonar_ping")
+ new /obj/temporary(T,3, 'icons/effects/effects.dmi', "sonar_ping")
return ..()
/spell/hand/slippery_surface/tower
diff --git a/code/modules/spells/hand/sunwrath.dm b/code/modules/spells/hand/sunwrath.dm
index 5256ea2dab490..c2e24634c68bd 100644
--- a/code/modules/spells/hand/sunwrath.dm
+++ b/code/modules/spells/hand/sunwrath.dm
@@ -20,14 +20,14 @@
var/turf/turf = t
if(turf.density || istype(turf, /turf/space))
break
- new /obj/effect/fake_fire/sunwrath(t)
+ new /obj/fake_fire/sunwrath(t)
return 1
-/obj/effect/fake_fire/sunwrath
+/obj/fake_fire/sunwrath
firelevel = 2
last_temperature = 0
pressure = 3000
-/obj/effect/fake_fire/sunwrath/Process() //Override, so we burn mobs only
+/obj/fake_fire/sunwrath/Process() //Override, so we burn mobs only
for(var/mob/living/L in loc)
L.FireBurn(firelevel,last_temperature,pressure)
diff --git a/code/modules/spells/racial_wizard.dm b/code/modules/spells/racial_wizard.dm
index c6bff4d1c4ed3..b8119d8c4da2a 100644
--- a/code/modules/spells/racial_wizard.dm
+++ b/code/modules/spells/racial_wizard.dm
@@ -3,7 +3,7 @@
/obj/item/magic_rock
name = "magical rock"
desc = "Legends say that this rock will unlock the true potential of anyone who touches it."
- icon = 'icons/obj/wizard.dmi'
+ icon = 'icons/obj/cult.dmi'
icon_state = "magic rock"
w_class = ITEM_SIZE_SMALL
throw_speed = 1
@@ -59,7 +59,7 @@
return
var/obj/O = new /obj(T)
- O.set_light(-10, 0.1, 10, 2, "#ffffff")
+ O.set_light(10, -10, "#ffffff")
spawn(duration)
qdel(O)
@@ -226,8 +226,8 @@
var/mob/living/L = targets[1]
vision.possess(L)
- GLOB.destroyed_event.register(L, src, /spell/camera_connection/proc/release)
- GLOB.logged_out_event.register(L, src, /spell/camera_connection/proc/release)
+ GLOB.destroyed_event.register(L, src, TYPE_PROC_REF(/spell/camera_connection, release))
+ GLOB.logged_out_event.register(L, src, TYPE_PROC_REF(/spell/camera_connection, release))
L.verbs += /mob/living/proc/release_eye
/spell/camera_connection/proc/release(mob/living/L)
@@ -239,10 +239,12 @@
/mob/observer/eye/wizard_eye
name_sufix = "Wizard Eye"
-/mob/observer/eye/wizard_eye/New() //we dont use the Ai one because it has AI specific procs imbedded in it.
- ..()
+
+/mob/observer/eye/wizard_eye/Initialize(mapload) //we dont use the Ai one because it has AI specific procs imbedded in it.
+ . = ..()
visualnet = cameranet
+
/mob/living/proc/release_eye()
set name = "Release Vision"
set desc = "Return your sight to your body."
diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm
index dc1cb6225341e..bc55475a900b3 100644
--- a/code/modules/spells/spell_code.dm
+++ b/code/modules/spells/spell_code.dm
@@ -42,7 +42,7 @@ var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
var/cooldown_min = 0 //minimum possible cooldown for a charging spell
var/overlay = 0
- var/overlay_icon = 'icons/obj/wizard.dmi'
+ var/overlay_icon = 'icons/obj/cult.dmi'
var/overlay_icon_state = "spell"
var/overlay_lifespan = 0
@@ -61,8 +61,6 @@ var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
var/hud_state = "" //name of the icon used in generating the spell hud object
var/override_base = ""
-
- var/mob/living/deity/connected_god //Do we have this spell based off a boon from a god?
var/obj/screen/connected_button
var/hidden_from_codex = FALSE
@@ -179,7 +177,7 @@ var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
location = target.loc
else if(istype(target,/turf))
location = target
- var/obj/effect/overlay/spell = new /obj/effect/overlay(location)
+ var/obj/overlay/spell = new /obj/overlay(location)
spell.icon = overlay_icon
spell.icon_state = overlay_icon_state
spell.anchored = TRUE
@@ -195,16 +193,16 @@ var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
if(istype(target,/mob/living) && message)
to_chat(target, text("[message]"))
if(sparks_spread)
- var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
+ var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread()
sparks.set_up(sparks_amt, 0, location) //no idea what the 0 is
sparks.start()
if(smoke_spread)
if(smoke_spread == 1)
- var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
+ var/datum/effect/smoke_spread/smoke = new /datum/effect/smoke_spread()
smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
smoke.start()
else if(smoke_spread == 2)
- var/datum/effect/effect/system/smoke_spread/bad/smoke = new /datum/effect/effect/system/smoke_spread/bad()
+ var/datum/effect/smoke_spread/bad/smoke = new /datum/effect/smoke_spread/bad()
smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is
smoke.start()
@@ -232,11 +230,11 @@ var/global/list/spells = typesof(/spell) //needed for the badmin verb for now
if(!user_turf)
to_chat(user, SPAN_WARNING("You cannot cast spells in null space!"))
- if((spell_flags & Z2NOCAST) && (user_turf.z in GLOB.using_map.admin_levels)) //Certain spells are not allowed on the centcomm zlevel
+ if((spell_flags & Z2NOCAST) && (user_turf.z in GLOB.using_map.admin_levels)) //Certain spells are not allowed on the centcom zlevel
return 0
if(spell_flags & CONSTRUCT_CHECK)
- for(var/turf/T in range(holder, 1))
+ for(var/turf/T as anything in RANGE_TURFS(holder, 1))
if(findNullRod(T))
return 0
diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm
index efe613eeace32..484fdf934013f 100644
--- a/code/modules/spells/spell_projectile.dm
+++ b/code/modules/spells/spell_projectile.dm
@@ -11,7 +11,7 @@
var/proj_trail = 0 //if it leaves a trail
var/proj_trail_lifespan = 0 //deciseconds
- var/proj_trail_icon = 'icons/obj/wizard.dmi'
+ var/proj_trail_icon = 'icons/obj/cult.dmi'
var/proj_trail_icon_state = "trail"
var/list/trails = new()
@@ -26,7 +26,7 @@
/obj/item/projectile/spell_projectile/before_move()
if(proj_trail && src && src.loc) //pretty trails
- var/obj/effect/overlay/trail = new /obj/effect/overlay(loc)
+ var/obj/overlay/trail = new /obj/overlay(loc)
trails += trail
trail.icon = proj_trail_icon
trail.icon_state = proj_trail_icon_state
diff --git a/code/modules/spells/spellbook.dm b/code/modules/spells/spellbook.dm
index 75aa954ac6cab..7d862800ab49a 100644
--- a/code/modules/spells/spellbook.dm
+++ b/code/modules/spells/spellbook.dm
@@ -22,14 +22,14 @@ var/global/list/artefact_feedback = list(/obj/structure/closet/wizard/armor =
name = "master spell book"
desc = "The legendary book of spells of the wizard."
icon = 'icons/obj/library.dmi'
- icon_state = "spellbook"
+ icon_state = "book"
throw_speed = 1
throw_range = 5
w_class = ITEM_SIZE_NORMAL
var/uses = 1
var/temp = null
var/datum/spellbook/spellbook
- var/spellbook_type = /datum/spellbook/ //for spawning specific spellbooks.
+ var/spellbook_type = /datum/spellbook //for spawning specific spellbooks.
var/investing_time = 0 //what time we target forr a return on our spell investment.
var/has_sacrificed = 0 //whether we have already got our sacrifice bonus for the current investment.
diff --git a/code/modules/spells/targeted/analyze.dm b/code/modules/spells/targeted/analyze.dm
index 463a1e0177f41..162a0e115182a 100644
--- a/code/modules/spells/targeted/analyze.dm
+++ b/code/modules/spells/targeted/analyze.dm
@@ -15,5 +15,5 @@
/spell/targeted/analyze/cast(list/targets, mob/user)
for(var/a in targets)
var/mob/living/carbon/human/H = a
- new /obj/effect/temporary(get_turf(a),5, 'icons/effects/effects.dmi', "repel_missiles")
+ new /obj/temporary(get_turf(a),5, 'icons/effects/effects.dmi', "repel_missiles")
to_chat(user,medical_scan_results(H,1))
diff --git a/code/modules/spells/targeted/cleric_spells.dm b/code/modules/spells/targeted/cleric_spells.dm
index 0182be1c5ada2..768c4362b6ce3 100644
--- a/code/modules/spells/targeted/cleric_spells.dm
+++ b/code/modules/spells/targeted/cleric_spells.dm
@@ -166,19 +166,19 @@
amt_dam_tox = -100
amt_dam_robo = -1000
hud_state = "trance"
- var/obj/effect/effect
+ var/obj/effect
/spell/targeted/heal_target/trance/cast(list/targets, mob/user)
for(var/t in targets)
var/mob/living/L = t
var/turf/T = get_turf(L)
- effect = new /obj/effect/rift(T)
+ effect = new /obj/rift(T)
effect.color = "f0e68c"
L.forceMove(effect)
var/time = (L.getBruteLoss() + L.getFireLoss()) * 20
L.status_flags &= GODMODE
to_chat(L,SPAN_NOTICE("You will be in stasis for [time/10] second\s."))
- addtimer(new Callback(src,.proc/cancel_rift),time)
+ addtimer(CALLBACK(src,PROC_REF(cancel_rift)),time)
/spell/targeted/heal_target/trance/Destroy()
cancel_rift()
@@ -193,16 +193,16 @@
charge_max += 300
QDEL_NULL(effect)
-/obj/effect/rift
+/obj/rift
name = "rift"
desc = "a tear in space and time."
- icon = 'icons/obj/wizard.dmi'
+ icon = 'icons/obj/cult.dmi'
icon_state = "rift"
unacidable = TRUE
anchored = TRUE
density = FALSE
-/obj/effect/rift/Destroy()
+/obj/rift/Destroy()
for(var/o in contents)
var/atom/movable/M = o
M.dropInto(loc)
@@ -232,7 +232,7 @@
should_wait = 0
break //Don't need to check anymore.
if(should_wait)
- addtimer(new Callback(src,.proc/check_for_revoke,targets), 30 SECONDS)
+ addtimer(CALLBACK(src,PROC_REF(check_for_revoke),targets), 30 SECONDS)
else
revoke_spells()
diff --git a/code/modules/spells/targeted/equip/dyrnwyn.dm b/code/modules/spells/targeted/equip/dyrnwyn.dm
index 264b5a5201785..7bd2acdec08bd 100644
--- a/code/modules/spells/targeted/equip/dyrnwyn.dm
+++ b/code/modules/spells/targeted/equip/dyrnwyn.dm
@@ -24,7 +24,7 @@
var/obj/item/W = new new_type (null, material)
W.SetName("\improper Dyrnwyn")
W.damtype = DAMAGE_BURN
- W.hitsound = 'sound/items/welder2.ogg'
+ W.hitsound = 'sound/items/Welder2.ogg'
W.slowdown_per_slot[slot_l_hand] = 0.25
W.slowdown_per_slot[slot_r_hand] = 0.25
return W
diff --git a/code/modules/spells/targeted/equip/holy_relic.dm b/code/modules/spells/targeted/equip/holy_relic.dm
index 5ad87588bc6c9..38c2206cdc60c 100644
--- a/code/modules/spells/targeted/equip/holy_relic.dm
+++ b/code/modules/spells/targeted/equip/holy_relic.dm
@@ -31,4 +31,4 @@
duration += 50
- return "The holy relic now lasts for [duration/10] seconds."
\ No newline at end of file
+ return "The holy relic now lasts for [duration/10] seconds."
diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm
index 2d9175f60c959..6090c13f575e0 100644
--- a/code/modules/spells/targeted/ethereal_jaunt.dm
+++ b/code/modules/spells/targeted/ethereal_jaunt.dm
@@ -16,8 +16,8 @@
hud_state = "wiz_jaunt"
var/reappear_duration = 5
- var/obj/effect/dummy/spell_jaunt/jaunt_holder
- var/atom/movable/overlay/animation
+ var/obj/dummy/spell_jaunt/jaunt_holder
+ var/atom/movable/fake_overlay/animation
/spell/targeted/ethereal_jaunt/Destroy()
if (jaunt_holder) // eject our user in case something happens and we get deleted
@@ -37,8 +37,8 @@
target.buckled.unbuckle_mob()
spawn(0)
var/mobloc = get_turf(target.loc)
- jaunt_holder = new/obj/effect/dummy/spell_jaunt(mobloc)
- animation = new/atom/movable/overlay(mobloc)
+ jaunt_holder = new/obj/dummy/spell_jaunt(mobloc)
+ animation = new/atom/movable/fake_overlay(mobloc)
animation.SetName("residue")
animation.set_density(FALSE)
animation.anchored = TRUE
@@ -50,7 +50,7 @@
jaunt_disappear(animation, target)
jaunt_steam(mobloc)
target.forceMove(jaunt_holder)
- addtimer(new Callback(src, .proc/start_reappear, target), duration)
+ addtimer(CALLBACK(src, PROC_REF(start_reappear), target), duration)
/spell/targeted/ethereal_jaunt/proc/start_reappear(mob/living/user)
var/mob_loc = jaunt_holder.last_valid_turf
@@ -58,7 +58,7 @@
jaunt_steam(mob_loc)
jaunt_reappear(animation, user)
animation.forceMove(mob_loc)
- addtimer(new Callback(src, .proc/reappear, mob_loc, user), reappear_duration)
+ addtimer(CALLBACK(src, PROC_REF(reappear), mob_loc, user), reappear_duration)
/spell/targeted/ethereal_jaunt/proc/reappear(mob_loc, mob/living/user)
if(!user.forceMove(mob_loc))
@@ -77,21 +77,21 @@
return "[src] now lasts longer."
-/spell/targeted/ethereal_jaunt/proc/jaunt_disappear(atom/movable/overlay/animation, mob/living/target)
+/spell/targeted/ethereal_jaunt/proc/jaunt_disappear(atom/movable/fake_overlay/animation, mob/living/target)
animation.icon_state = "liquify"
flick("liquify",animation)
playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 30)
-/spell/targeted/ethereal_jaunt/proc/jaunt_reappear(atom/movable/overlay/animation, mob/living/target)
+/spell/targeted/ethereal_jaunt/proc/jaunt_reappear(atom/movable/fake_overlay/animation, mob/living/target)
flick("reappear",animation)
playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 30)
/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc)
- var/datum/effect/effect/system/steam_spread/steam = new /datum/effect/effect/system/steam_spread()
+ var/datum/effect/steam_spread/steam = new /datum/effect/steam_spread()
steam.set_up(10, 0, mobloc)
steam.start()
-/obj/effect/dummy/spell_jaunt
+/obj/dummy/spell_jaunt
name = "water"
icon = 'icons/effects/effects.dmi'
icon_state = "nothing"
@@ -101,17 +101,17 @@
anchored = TRUE
var/turf/last_valid_turf
-/obj/effect/dummy/spell_jaunt/New(location)
+/obj/dummy/spell_jaunt/New(location)
..()
last_valid_turf = get_turf(location)
-/obj/effect/dummy/spell_jaunt/Destroy()
+/obj/dummy/spell_jaunt/Destroy()
// Eject contents if deleted somehow
for(var/atom/movable/AM in src)
AM.dropInto(loc)
return ..()
-/obj/effect/dummy/spell_jaunt/relaymove(mob/user, direction)
+/obj/dummy/spell_jaunt/relaymove(mob/user, direction)
if (!canmove || reappearing) return
var/turf/newLoc = get_step(src, direction)
if (!newLoc)
@@ -124,14 +124,14 @@
else
to_chat(user, SPAN_WARNING("Some strange aura is blocking the way!"))
canmove = 0
- addtimer(new Callback(src, .proc/allow_move), 2)
+ addtimer(CALLBACK(src, PROC_REF(allow_move)), 2)
-/obj/effect/dummy/spell_jaunt/proc/allow_move()
+/obj/dummy/spell_jaunt/proc/allow_move()
canmove = TRUE
-/obj/effect/dummy/spell_jaunt/ex_act(blah)
+/obj/dummy/spell_jaunt/ex_act(blah)
return
-/obj/effect/dummy/spell_jaunt/bullet_act(blah)
+/obj/dummy/spell_jaunt/bullet_act(blah)
return
/spell/targeted/ethereal_jaunt/tower
diff --git a/code/modules/spells/targeted/genetic.dm b/code/modules/spells/targeted/genetic.dm
index 865850de4c09e..f88843a6c2a68 100644
--- a/code/modules/spells/targeted/genetic.dm
+++ b/code/modules/spells/targeted/genetic.dm
@@ -1,9 +1,3 @@
-/*
-Other mutation or disability spells can be found in
-code\game\dna\genes\vg_powers.dm //hulk is in this file
-code\game\dna\genes\goon_disabilities.dm
-code\game\dna\genes\goon_powers.dm
-*/
/spell/targeted/genetic
name = "Genetic modifier"
desc = "This spell inflicts a set of mutations and disabilities upon the target."
@@ -62,7 +56,7 @@ code\game\dna\genes\goon_powers.dm
/spell/targeted/genetic/mutate
name = "Mutate"
- desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
+ desc = "This spell causes you to turn feral and gain laser vision for a short while."
feedback = "MU"
school = "transmutation"
charge_max = 400
@@ -73,7 +67,7 @@ code\game\dna\genes\goon_powers.dm
range = 0
max_targets = 1
- mutations = list(MUTATION_LASER, MUTATION_HULK)
+ mutations = list(MUTATION_LASER, MUTATION_FERAL)
duration = 300
level_max = list(Sp_TOTAL = 1, Sp_SPEED = 1, Sp_POWER = 0)
diff --git a/code/modules/spells/targeted/glimpse_of_eternity.dm b/code/modules/spells/targeted/glimpse_of_eternity.dm
index bc662d5fc82f8..47bd689f074c5 100644
--- a/code/modules/spells/targeted/glimpse_of_eternity.dm
+++ b/code/modules/spells/targeted/glimpse_of_eternity.dm
@@ -18,9 +18,9 @@
if(L.faction != user.faction) //Worse for non-allies
L.eye_blind += 5
L.Stun(5)
- new /obj/effect/temporary(get_turf(L), 5, 'icons/effects/effects.dmi', "electricity_constant")
+ new /obj/temporary(get_turf(L), 5, 'icons/effects/effects.dmi', "electricity_constant")
else
L.eye_blind += 2
L.adjustBruteLoss(-10)
L.adjustFireLoss(-10)
- new /obj/effect/temporary(get_turf(L), 5, 'icons/effects/effects.dmi', "green_sparkles")
+ new /obj/temporary(get_turf(L), 5, 'icons/effects/effects.dmi', "green_sparkles")
diff --git a/code/modules/spells/targeted/projectile/passage.dm b/code/modules/spells/targeted/projectile/passage.dm
index bcb2d94b1fbb5..9691da60e13eb 100644
--- a/code/modules/spells/targeted/projectile/passage.dm
+++ b/code/modules/spells/targeted/projectile/passage.dm
@@ -29,7 +29,7 @@
var/turf/T = get_turf(spell_holder)
holder.forceMove(T)
- var/datum/effect/effect/system/smoke_spread/S = new /datum/effect/effect/system/smoke_spread()
+ var/datum/effect/smoke_spread/S = new /datum/effect/smoke_spread()
S.set_up(3,0,T)
S.start()
playsound(src, 'sound/magic/lightningshock.ogg', 50)
diff --git a/code/modules/spells/targeted/projectile/stuncuff.dm b/code/modules/spells/targeted/projectile/stuncuff.dm
index 1959fa8282c21..4d5a5e0ba7674 100644
--- a/code/modules/spells/targeted/projectile/stuncuff.dm
+++ b/code/modules/spells/targeted/projectile/stuncuff.dm
@@ -37,8 +37,7 @@
/obj/item/handcuffs/wizard
name = "beams of light"
desc = "Undescribable and unpenetrable. Or so they say."
-
- breakouttime = 300 //30 seconds
+ breakout_time = 30 SECONDS
/obj/item/handcuffs/wizard/dropped(mob/user)
..()
diff --git a/code/modules/spells/targeted/shapeshift.dm b/code/modules/spells/targeted/shapeshift.dm
index 5ad04fe5ff62f..6910886d3c21c 100644
--- a/code/modules/spells/targeted/shapeshift.dm
+++ b/code/modules/spells/targeted/shapeshift.dm
@@ -50,14 +50,14 @@
M.mind.transfer_to(trans)
else
trans.key = M.key
- new /obj/effect/temporary(get_turf(M), 5, 'icons/effects/effects.dmi', "summoning")
+ new /obj/temporary(get_turf(M), 5, 'icons/effects/effects.dmi', "summoning")
M.forceMove(trans) //move inside the new dude to hide him.
M.status_flags |= GODMODE //don't want him to die or breathe or do ANYTHING
transformed_dudes[trans] = M
- GLOB.death_event.register(trans,src,/spell/targeted/shapeshift/proc/stop_transformation)
- GLOB.destroyed_event.register(trans,src,/spell/targeted/shapeshift/proc/stop_transformation)
- GLOB.destroyed_event.register(M, src, /spell/targeted/shapeshift/proc/destroyed_transformer)
+ GLOB.death_event.register(trans, src, TYPE_PROC_REF(/spell/targeted/shapeshift, stop_transformation))
+ GLOB.destroyed_event.register(trans, src, TYPE_PROC_REF(/spell/targeted/shapeshift, stop_transformation))
+ GLOB.destroyed_event.register(M, src, TYPE_PROC_REF(/spell/targeted/shapeshift, destroyed_transformer))
if(duration)
spawn(duration)
stop_transformation(trans)
@@ -75,7 +75,7 @@
if(share_damage)
var/ratio = target.health/target.maxHealth
var/damage = transformer.maxHealth - round(transformer.maxHealth*(ratio))
- for(var/i in 1 to Ceil(damage/10))
+ for(var/i in 1 to ceil(damage/10))
transformer.adjustBruteLoss(10)
if(target.mind)
target.mind.transfer_to(transformer)
diff --git a/code/modules/spells/targeted/shatter_mind.dm b/code/modules/spells/targeted/shatter_mind.dm
index cdea661418447..87a3dc834defe 100644
--- a/code/modules/spells/targeted/shatter_mind.dm
+++ b/code/modules/spells/targeted/shatter_mind.dm
@@ -22,7 +22,7 @@
if(prob(5))
to_chat(H, SPAN_WARNING("You feel unhinged."))
H.adjust_hallucination(5,5)
- H.confused += 2
+ H.mod_confused(2)
H.dizziness += 2
if(H.hallucination_power > 50)
H.adjustBrainLoss(5)
diff --git a/code/modules/spells/targeted/shift.dm b/code/modules/spells/targeted/shift.dm
index 62902cea8e6b5..f5fdfacb71004 100644
--- a/code/modules/spells/targeted/shift.dm
+++ b/code/modules/spells/targeted/shift.dm
@@ -10,13 +10,13 @@
hud_state = "const_shift"
-/spell/targeted/ethereal_jaunt/shift/jaunt_disappear(atom/movable/overlay/animation, mob/living/target)
+/spell/targeted/ethereal_jaunt/shift/jaunt_disappear(atom/movable/fake_overlay/animation, mob/living/target)
to_chat(target, SPAN_DANGER("You silently phase out.")) // no visible message - phase shift is silent
animation.icon_state = "phase_shift"
animation.dir = target.dir
flick("phase_shift", animation)
-/spell/targeted/ethereal_jaunt/shift/jaunt_reappear(atom/movable/overlay/animation, mob/living/target)
+/spell/targeted/ethereal_jaunt/shift/jaunt_reappear(atom/movable/fake_overlay/animation, mob/living/target)
to_chat(target, SPAN_DANGER("You return from your jaunt."))
animation.icon_state = "phase_shift2"
animation.dir = target.dir
diff --git a/code/modules/spells/targeted/targeted.dm b/code/modules/spells/targeted/targeted.dm
index c19c0a9fbb46c..6c2fa2dd7714e 100644
--- a/code/modules/spells/targeted/targeted.dm
+++ b/code/modules/spells/targeted/targeted.dm
@@ -168,8 +168,8 @@ Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are exp
target.eye_blind += amt_eye_blind
target.eye_blurry += amt_eye_blurry
target.dizziness += amt_dizziness
- target.confused += amt_confused
+ target.mod_confused(amt_confused)
target.stuttering += amt_stuttering
if(effect_state)
- var/obj/o = new /obj/effect/temporary(get_turf(target), effect_duration, 'icons/effects/effects.dmi', effect_state)
+ var/obj/o = new /obj/temporary(get_turf(target), effect_duration, 'icons/effects/effects.dmi', effect_state)
o.color = effect_color
diff --git a/code/modules/sprite_accessories/_accessory_hair.dm b/code/modules/sprite_accessories/_accessory_hair.dm
index 44266a3827be2..558d8127239ce 100644
--- a/code/modules/sprite_accessories/_accessory_hair.dm
+++ b/code/modules/sprite_accessories/_accessory_hair.dm
@@ -422,11 +422,21 @@
icon_state = "hair_shavedbun"
flags = HAIR_TIEABLE
+/datum/sprite_accessory/hair/shavedbunalt
+ name = "Shaved Bun Alt"
+ icon_state = "hair_shavedbun_alt"
+ flags = HAIR_TIEABLE
+
/datum/sprite_accessory/hair/halfshaved
name = "Half-Shaved"
icon_state = "hair_halfshaved"
flags = HAIR_TIEABLE
+/datum/sprite_accessory/hair/halfshavedalt
+ name = "Half-Shaved Alt"
+ icon_state = "hair_halfshaved_alt"
+ flags = HAIR_TIEABLE
+
/datum/sprite_accessory/hair/softmohawk
name = "Soft Mohawk"
icon_state = "hair_softmohawk"
diff --git a/code/modules/sprite_accessories/accessory_ipc.dm b/code/modules/sprite_accessories/accessory_ipc.dm
index 4a0585ebc26ee..c540f8dac8f98 100644
--- a/code/modules/sprite_accessories/accessory_ipc.dm
+++ b/code/modules/sprite_accessories/accessory_ipc.dm
@@ -1,22 +1,426 @@
/datum/sprite_accessory/marking/ipc
icon = 'icons/mob/human_races/species/ipc/markings.dmi'
- name = "Bishop Alt. Optics (IPC)"
- icon_state = "bishop_alt_optics"
- body_parts = list(BP_HEAD)
species_allowed = list(SPECIES_IPC)
+ gender = NEUTER
+ do_coloration = TRUE
-/datum/sprite_accessory/marking/ipc/takahashi
- name = "Ward Takahashi Alt. Optics (IPC)"
- icon_state = "wardtakahashi_alt_optics"
+/datum/sprite_accessory/marking/ipc/head
+ body_parts = list(BP_HEAD)
+
+/datum/sprite_accessory/marking/ipc/torso
+ body_parts = list(BP_CHEST,BP_GROIN)
+
+/datum/sprite_accessory/marking/ipc/arms
+ body_parts = list(BP_R_ARM,BP_R_HAND,BP_L_ARM,BP_L_HAND)
+
+/datum/sprite_accessory/marking/ipc/legs
+ body_parts = list(BP_R_LEG,BP_R_FOOT,BP_L_LEG,BP_L_FOOT)
+
+//SHELLGUARD
+/datum/sprite_accessory/marking/ipc/head/shellguard
+ name = "Shellguard Colored Head"
+ icon_state = "shellguard"
+
+/datum/sprite_accessory/marking/ipc/head/shellguard_alt
+ name = "Shellguard Colored Alt. Head"
+ icon_state = "shellguard_alt"
+
+/datum/sprite_accessory/marking/ipc/head/shellguard_monitor
+ name = "Shellguard Colored Monitor"
+ icon_state = "shellguard_monitor"
+
+/datum/sprite_accessory/marking/ipc/torso/shellguard
+ name = "Shellguard Colored Torso"
+ icon_state = "shellguard"
+
+/datum/sprite_accessory/marking/ipc/arms/shellguard
+ name = "Shellguard Colored Arms"
+ icon_state = "shellguard"
+
+/datum/sprite_accessory/marking/ipc/legs/shellguard
+ name = "Shellguard Colored Legs"
+ icon_state = "shellguard"
+
+//XION
+/datum/sprite_accessory/marking/ipc/head/xion
+ name = "Xion Colored Head"
+ icon_state = "xion"
-/datum/sprite_accessory/marking/ipc/xion
- name = "Xion Alt. Optics (IPC)"
+/datum/sprite_accessory/marking/ipc/head/xion_alt
+ name = "Xion Colored Alt. Head"
+ icon_state = "xion_alt"
+
+/datum/sprite_accessory/marking/ipc/head/xion_monitor
+ name = "Xion Colored Monitor"
+ icon_state = "xion_monitor"
+
+/datum/sprite_accessory/marking/ipc/head/xion_eyes
+ name = "Xion Alt. Optics"
icon_state = "xion_alt_optics"
-/datum/sprite_accessory/marking/ipc/hephaestus
- name = "Hephaestus Alt. Optics (IPC)"
+/datum/sprite_accessory/marking/ipc/torso/xion
+ name = "Xion Colored Torso"
+ icon_state = "xion"
+
+/datum/sprite_accessory/marking/ipc/arms/xion
+ name = "Xion Colored Arms"
+ icon_state = "xion"
+
+/datum/sprite_accessory/marking/ipc/legs/xion
+ name = "Xion Colored Legs"
+ icon_state = "xion"
+
+//XION ECON
+/datum/sprite_accessory/marking/ipc/head/xion_e
+ name = "Xion Econ. Colored Head"
+ icon_state = "xion_e"
+
+/datum/sprite_accessory/marking/ipc/torso/xion_e
+ name = "Xion Econ. Colored Torso"
+ icon_state = "xion_e"
+
+/datum/sprite_accessory/marking/ipc/arms/xion_e
+ name = "Xion Econ. Colored Arms"
+ icon_state = "xion_e"
+
+/datum/sprite_accessory/marking/ipc/legs/xion_e
+ name = "Xion Econ. Colored Legs"
+ icon_state = "xion_e"
+
+//WARD-TAKAHASHI
+/datum/sprite_accessory/marking/ipc/head/wt
+ name = "Ward-Takahashi GMB Colored Head"
+ icon_state = "wt"
+
+/datum/sprite_accessory/marking/ipc/head/wt_alt
+ name = "Ward-Takahashi GMB Colored Alt. Head"
+ icon_state = "wt_alt"
+
+/datum/sprite_accessory/marking/ipc/head/wt_monitor
+ name = "Ward-Takahashi GMB Colored Monitor"
+ icon_state = "wt_monitor"
+
+/datum/sprite_accessory/marking/ipc/head/wt_eyes
+ name = "Ward Takahashi Alt. Optics"
+ icon_state = "wardtakahashi_alt_optics"
+
+/datum/sprite_accessory/marking/ipc/torso/wt
+ name = "Ward-Takahashi GMB Colored Torso"
+ icon_state = "wt"
+
+/datum/sprite_accessory/marking/ipc/arms/wt
+ name = "Ward-Takahashi GMB Colored Arms"
+ icon_state = "wt"
+
+/datum/sprite_accessory/marking/ipc/legs/wt
+ name = "Ward-Takahashi GMB Colored Legs"
+ icon_state = "wt"
+
+//BISHOP
+/datum/sprite_accessory/marking/ipc/head/bishop_eyes
+ name = "Bishop Alt. Optics"
+ icon_state = "bishop_alt_optics"
+
+/datum/sprite_accessory/marking/ipc/head/bishop
+ name = "Bishop Head Color Accents"
+ icon_state = "bishop"
+
+/datum/sprite_accessory/marking/ipc/head/bishop_monitor
+ name = "Bishop Monitor Color Accents"
+ icon_state = "bishop_monitor"
+
+/datum/sprite_accessory/marking/ipc/torso/bishop
+ name = "Bishop Torso Color Accents"
+ icon_state = "bishop"
+
+/datum/sprite_accessory/marking/ipc/arms/bishop
+ name = "Bishop Arms Color Accents"
+ icon_state = "bishop"
+
+/datum/sprite_accessory/marking/ipc/legs/bishop
+ name = "Bishop Legs Color Accents"
+ icon_state = "bishop"
+
+//BISHOP ROOK
+/datum/sprite_accessory/marking/ipc/head/b_rook
+ name = "Bishop Rook Colored Facepanel"
+ icon_state = "bishop_rook"
+
+/datum/sprite_accessory/marking/ipc/torso/b_rook
+ name = "Bishop Rook Torso Color Accents"
+ icon_state = "bishop_rook"
+
+/datum/sprite_accessory/marking/ipc/arms/b_rook
+ name = "Bishop Rook Arms Color Accents"
+ icon_state = "bishop_rook"
+
+/datum/sprite_accessory/marking/ipc/legs/b_rook
+ name = "Bishop Rook Legs Color Accents"
+ icon_state = "bishop_rook"
+
+//HEPHAESTUS
+/datum/sprite_accessory/marking/ipc/head/hephaestus
+ name = "Hephaestus Colored Head"
+ icon_state = "hephaestus"
+
+/datum/sprite_accessory/marking/ipc/head/hephaestus_alt
+ name = "Hephaestus Colored Alt. Head"
+ icon_state = "hephaestus_alt"
+
+/datum/sprite_accessory/marking/ipc/head/hephaestus_monitor
+ name = "Hephaestus Colored Monitor"
+ icon_state = "hephaestus_monitor"
+
+/datum/sprite_accessory/marking/ipc/head/hephaestus_eyes
+ name = "Hephaestus Alt. Optics"
icon_state = "hephaestus_alt_optics"
-/datum/sprite_accessory/marking/ipc/morpheus
- name = "Morpheus Optics (IPC)"
- icon_state = "morpheus_optics"
\ No newline at end of file
+/datum/sprite_accessory/marking/ipc/torso/hephaestus
+ name = "Hephaestus Colored Torso"
+ icon_state = "hephaestus"
+
+/datum/sprite_accessory/marking/ipc/arms/hephaestus
+ name = "Hephaestus Colored Arms"
+ icon_state = "hephaestus"
+
+/datum/sprite_accessory/marking/ipc/legs/hephaestus
+ name = "Hephaestus Colored Legs"
+ icon_state = "hephaestus"
+
+//TITAN
+/datum/sprite_accessory/marking/ipc/head/titan
+ name = "Hephaestus Titan Colored Head"
+ icon_state = "titan"
+
+/datum/sprite_accessory/marking/ipc/torso/titan
+ name = "Hephaestus Titan Colored Torso"
+ icon_state = "titan"
+
+/datum/sprite_accessory/marking/ipc/arms/titan
+ name = "Hephaestus Titan Colored Arms"
+ icon_state = "titan"
+
+/datum/sprite_accessory/marking/ipc/legs/titan
+ name = "Hephaestus Titan Colored Legs"
+ icon_state = "titan"
+
+//MORPHEUS
+/datum/sprite_accessory/marking/ipc/head/morpheus_eyes
+ name = "Morpheus Optics"
+ icon_state = "morpheus_optics"
+
+/datum/sprite_accessory/marking/ipc/head/airborne_face
+ name = "Morpheus Airborne Faceplate"
+ icon_state = "airborne_face"
+
+/datum/sprite_accessory/marking/ipc/head/blitz_optics
+ name = "Morpheus Blitz Optics"
+ icon_state = "blitz_optics"
+
+/datum/sprite_accessory/marking/ipc/head/prime_optics
+ name = "Morpheus Prime Optics"
+ icon_state = "prime_optics"
+
+//ZENG-HU SPIRIT
+/datum/sprite_accessory/marking/ipc/head/zenghu
+ name = "Zeng-Hu Spirit Head Color Accents"
+ icon_state = "zenghu"
+
+/datum/sprite_accessory/marking/ipc/torso/zenghu
+ name = "Zeng-Hu Spirit Torso Color Accents"
+ icon_state = "zenghu"
+
+/datum/sprite_accessory/marking/ipc/arms/zenghu
+ name = "Zeng-Hu Spirit Arms Color Accents"
+ icon_state = "zenghu"
+
+/datum/sprite_accessory/marking/ipc/legs/zenghu
+ name = "Zeng-Hu Spirit Legs Color Accents"
+ icon_state = "zenghu"
+
+
+/datum/sprite_accessory/facial_hair/ipc
+ abstract_type = /datum/sprite_accessory/facial_hair/ipc
+ icon = 'icons/mob/human_races/species/ipc/facial.dmi'
+ species_allowed = list(SPECIES_IPC)
+ gender = NEUTER
+ do_coloration = FALSE
+
+/datum/sprite_accessory/facial_hair/ipc/off
+ name = "Off"
+ icon_state = "ipc_blank"
+
+/datum/sprite_accessory/facial_hair/ipc/ipc_text
+ name = "Text"
+ icon_state = "ipc_text"
+
+/datum/sprite_accessory/facial_hair/ipc/red
+ name = "Red"
+ icon_state = "ipc_red"
+
+/datum/sprite_accessory/facial_hair/ipc/blue
+ name = "Blue"
+ icon_state = "ipc_blue"
+
+/datum/sprite_accessory/facial_hair/ipc/shower
+ name = "Shower"
+ icon_state = "ipc_shower"
+
+/datum/sprite_accessory/facial_hair/ipc/orange
+ name = "Orange"
+ icon_state = "ipc_orange"
+
+/datum/sprite_accessory/facial_hair/ipc/nature
+ name = "Nature"
+ icon_state = "ipc_nature"
+
+/datum/sprite_accessory/facial_hair/ipc/eight
+ name = "Eight"
+ icon_state = "ipc_eight"
+
+/datum/sprite_accessory/facial_hair/ipc/Yellow
+ name = "Yellow face"
+ icon_state = "ipc_yellow"
+
+/datum/sprite_accessory/facial_hair/ipc/goggles
+ name = "Goggles"
+ icon_state = "ipc_goggles"
+
+/datum/sprite_accessory/facial_hair/ipc/eight
+ name = "Eight"
+ icon_state = "ipc_eight"
+
+/datum/sprite_accessory/facial_hair/ipc/heart
+ name = "Heart"
+ icon_state = "ipc_heart"
+
+/datum/sprite_accessory/facial_hair/ipc/monoeye
+ name = "Mono Eye"
+ icon_state = "ipc_monoeye"
+
+/datum/sprite_accessory/facial_hair/ipc/breakout
+ name = "Breakout"
+ icon_state = "ipc_breakout"
+
+/datum/sprite_accessory/facial_hair/ipc/eight
+ name = "Static"
+ icon_state = "ipc_static"
+
+/datum/sprite_accessory/facial_hair/ipc/question
+ name = "Question"
+ icon_state = "ipc_purple"
+
+/datum/sprite_accessory/facial_hair/ipc/smiley
+ name = "Smiley"
+ icon_state = "ipc_smiley"
+
+/datum/sprite_accessory/facial_hair/ipc/datebase
+ name = "Database"
+ icon_state = "ipc_database"
+
+/datum/sprite_accessory/facial_hair/ipc/frowny
+ name = "Frowny"
+ icon_state = "ipc_frowny"
+
+/datum/sprite_accessory/facial_hair/ipc/crt
+ name = "CRT"
+ icon_state = "ipc_crt"
+
+/datum/sprite_accessory/facial_hair/ipc/scroll
+ name = "Scroll"
+ icon_state = "ipc_scroll"
+
+/datum/sprite_accessory/facial_hair/ipc/tetris
+ name = "Tetris"
+ icon_state = "ipc_tetris"
+
+/datum/sprite_accessory/facial_hair/ipc/dot
+ name = "Dot"
+ icon_state = "ipc_dot"
+
+/datum/sprite_accessory/facial_hair/ipc/four
+ name = "Four"
+ icon_state = "ipc_four"
+
+/datum/sprite_accessory/facial_hair/ipc/eye
+ name = "Eye"
+ icon_state = "ipc_eye"
+
+/datum/sprite_accessory/facial_hair/ipc/eyes
+ name = "Eyes"
+ icon_state = "ipc_eyes"
+
+/datum/sprite_accessory/facial_hair/ipc/color_array
+ name = "Color Array"
+ icon_state = "ipc_color_array"
+
+/datum/sprite_accessory/facial_hair/ipc/array_horizontal
+ name = "Array Horizontal"
+ icon_state = "ipc_color_array_horizontal"
+
+/datum/sprite_accessory/facial_hair/ipc/array_vertical
+ name = "Array Vertical"
+ icon_state = "ipc_color_array_vertical"
+
+/datum/sprite_accessory/facial_hair/ipc/television
+ name = "Television"
+ icon_state = "ipc_television"
+
+/datum/sprite_accessory/facial_hair/ipc/miami
+ name = "Miami"
+ icon_state = "ipc_miami"
+
+/datum/sprite_accessory/facial_hair/ipc/waiting
+ name = "Waiting"
+ icon_state = "ipc_waiting"
+
+/datum/sprite_accessory/facial_hair/ipc/smoking
+ name = "Smoking"
+ icon_state = "ipc_smoking"
+
+
+/datum/sprite_accessory/hair/ipc
+ abstract_type = /datum/sprite_accessory/hair/ipc
+ icon = 'icons/mob/human_races/species/ipc/hair.dmi'
+ species_allowed = list(SPECIES_IPC)
+
+/datum/sprite_accessory/hair/ipc/ipc_bald
+ name = "None"
+ icon_state = "null"
+ do_coloration = FALSE
+
+/datum/sprite_accessory/hair/ipc/ipc_antennae
+ name = "Antennae"
+ icon_state = "antennae"
+
+/datum/sprite_accessory/hair/ipc/ipc_tv_antennae
+ name = "T.V. Antennae"
+ icon_state = "tvantennae"
+
+/datum/sprite_accessory/hair/ipc/ipc_tesla_antennae
+ name = "Tesla Antennae"
+ icon_state = "tesla"
+
+/datum/sprite_accessory/hair/ipc/ipc_light
+ name = "Head Light"
+ icon_state = "light"
+
+/datum/sprite_accessory/hair/ipc/ipc_side_lights
+ name = "Side Lights"
+ icon_state = "sidelights"
+
+/datum/sprite_accessory/hair/ipc/ipc_side_cyber_head
+ name = "Cyber Pipes"
+ icon_state = "cyberhead"
+
+/datum/sprite_accessory/hair/ipc/ipc_side_antlers
+ name = "Antlers"
+ icon_state = "antlers"
+
+/datum/sprite_accessory/hair/ipc/ipc_side_drone_eyes
+ name = "Drone Eyes"
+ icon_state = "droneeyes"
+
+/datum/sprite_accessory/hair/ipc/ipc_crowned
+ name = "Crowned"
+ icon_state = "crowned"
diff --git a/code/modules/submaps/_submap.dm b/code/modules/submaps/_submap.dm
index 61c3443ce5327..4ea65668d51fe 100644
--- a/code/modules/submaps/_submap.dm
+++ b/code/modules/submaps/_submap.dm
@@ -46,7 +46,7 @@
qdel(src)
return
- var/obj/effect/overmap/visitable/cell = map_sectors["[associated_z]"]
+ var/obj/overmap/visitable/cell = map_sectors["[associated_z]"]
if(istype(cell))
sync_cell(cell)
@@ -54,7 +54,7 @@
var/added_spawnpoint
for(var/check_z in GetConnectedZlevels(associated_z))
for(var/thing in block(locate(1, 1, check_z), locate(world.maxx, world.maxy, check_z)))
- for(var/obj/effect/submap_landmark/spawnpoint/landmark in thing)
+ for(var/obj/submap_landmark/spawnpoint/landmark in thing)
var/datum/job/submap/job = jobs[landmark.name]
if(istype(job))
job.spawnpoints += landmark
@@ -68,7 +68,7 @@
if(archetype && archetype.call_webhook)
SSwebhooks.send(archetype.call_webhook, list("name" = name))
-/datum/submap/proc/sync_cell(obj/effect/overmap/visitable/cell)
+/datum/submap/proc/sync_cell(obj/overmap/visitable/cell)
name = cell.name
/datum/submap/proc/available()
diff --git a/code/modules/submaps/submap_join.dm b/code/modules/submaps/submap_join.dm
index 8212b14cfe50e..246be39513ab8 100644
--- a/code/modules/submaps/submap_join.dm
+++ b/code/modules/submaps/submap_join.dm
@@ -85,6 +85,7 @@
SScustomitems.equip_custom_items(user_human)
character.job = job.title
+ character.faction = name
if(character.mind)
character.mind.assigned_job = job
character.mind.assigned_role = character.job
diff --git a/code/modules/submaps/submap_landmark.dm b/code/modules/submaps/submap_landmark.dm
index d5170d1b2a843..23bd08ae49417 100644
--- a/code/modules/submaps/submap_landmark.dm
+++ b/code/modules/submaps/submap_landmark.dm
@@ -1,4 +1,4 @@
-/obj/effect/submap_landmark
+/obj/submap_landmark
icon = 'icons/misc/mark.dmi'
invisibility = INVISIBILITY_MAXIMUM
anchored = TRUE
@@ -6,12 +6,12 @@
density = FALSE
opacity = FALSE
-/obj/effect/submap_landmark/joinable_submap
+/obj/submap_landmark/joinable_submap
icon_state = "x4"
var/archetype
var/submap_datum_type = /datum/submap
-/obj/effect/submap_landmark/joinable_submap/Initialize(mapload)
+/obj/submap_landmark/joinable_submap/Initialize(mapload)
. = ..(mapload)
if(!SSmapping.submaps[name] && SSmapping.submap_archetypes[archetype])
var/datum/submap/submap = new submap_datum_type(z)
@@ -24,8 +24,8 @@
to_world_log( "Submap error - mapped landmark had invalid archetype.")
return INITIALIZE_HINT_QDEL
-/obj/effect/submap_landmark/spawnpoint
+/obj/submap_landmark/spawnpoint
icon_state = "x3"
-/obj/effect/submap_landmark/spawnpoint/survivor
+/obj/submap_landmark/spawnpoint/survivor
name = "Survivor"
diff --git a/code/modules/supermatter/setup_supermatter.dm b/code/modules/supermatter/setup_supermatter.dm
index ba2e82f0a9f7e..2f9f1984cec8b 100644
--- a/code/modules/supermatter/setup_supermatter.dm
+++ b/code/modules/supermatter/setup_supermatter.dm
@@ -33,7 +33,7 @@
// CONFIGURATION PHASE
// Coolant canisters, set types according to response.
- for(var/obj/effect/engine_setup/coolant_canister/C in world)
+ for(var/obj/engine_setup/coolant_canister/C in world)
switch(response)
if("N2")
C.canister_type = /obj/machinery/portable_atmospherics/canister/nitrogen/engine_setup
@@ -48,7 +48,7 @@
C.canister_type = /obj/machinery/portable_atmospherics/canister/hydrogen/engine_setup
continue
- for(var/obj/effect/engine_setup/core/C in world)
+ for(var/obj/engine_setup/core/C in world)
switch(response)
if("N2")
C.energy_setting = ENERGY_NITROGEN
@@ -63,12 +63,12 @@
C.energy_setting = ENERGY_HYDROGEN
continue
- for(var/obj/effect/engine_setup/filter/F in world)
+ for(var/obj/engine_setup/filter/F in world)
F.coolant = response
var/list/delayed_objects = list()
// SETUP PHASE
- for(var/obj/effect/engine_setup/S in world)
+ for(var/obj/engine_setup/S in world)
var/result = S.activate(0)
switch(result)
if(SETUP_OK)
@@ -86,7 +86,7 @@
continue
if(!errors)
- for(var/obj/effect/engine_setup/S in delayed_objects)
+ for(var/obj/engine_setup/S in delayed_objects)
var/result = S.activate(1)
switch(result)
if(SETUP_OK)
@@ -106,25 +106,25 @@
-/obj/effect/engine_setup
+/obj/engine_setup
name = "Engine Setup Marker"
desc = "You shouldn't see this."
- invisibility = 101
+ invisibility = INVISIBILITY_ABSTRACT
anchored = TRUE
density = FALSE
icon = 'icons/mob/screen1.dmi'
icon_state = "x3"
-/obj/effect/engine_setup/proc/activate(last = 0)
+/obj/engine_setup/proc/activate(last = 0)
return 1
// Tries to locate a pump, enables it, and sets it to MAX. Triggers warning if unable to locate a pump.
-/obj/effect/engine_setup/pump_max
+/obj/engine_setup/pump_max
name = "Pump Setup Marker"
-/obj/effect/engine_setup/pump_max/activate()
+/obj/engine_setup/pump_max/activate()
..()
var/obj/machinery/atmospherics/binary/pump/P = locate() in get_turf(src)
if(!P)
@@ -138,10 +138,10 @@
// Spawns an empty canister on this turf, if it has a connector port. Triggers warning if unable to find a connector port
-/obj/effect/engine_setup/empty_canister
+/obj/engine_setup/empty_canister
name = "Empty Canister Marker"
-/obj/effect/engine_setup/empty_canister/activate()
+/obj/engine_setup/empty_canister/activate()
..()
var/obj/machinery/atmospherics/portables_connector/P = locate() in get_turf(src)
if(!P)
@@ -155,11 +155,11 @@
// Spawns a coolant canister on this turf, if it has a connector port.
// Triggers error when unable to locate connector port or when coolant canister type is unset.
-/obj/effect/engine_setup/coolant_canister
+/obj/engine_setup/coolant_canister
name = "Coolant Canister Marker"
var/canister_type = null
-/obj/effect/engine_setup/coolant_canister/activate()
+/obj/engine_setup/coolant_canister/activate()
..()
var/obj/machinery/atmospherics/portables_connector/P = locate() in get_turf(src)
if(!P)
@@ -174,11 +174,11 @@
// Energises the supermatter. Errors when unable to locate supermatter.
-/obj/effect/engine_setup/core
+/obj/engine_setup/core
name = "Supermatter Core Marker"
var/energy_setting = 0
-/obj/effect/engine_setup/core/activate(last = 0)
+/obj/engine_setup/core/activate(last = 0)
if(!last)
return SETUP_DELAYED
..()
@@ -195,10 +195,10 @@
// Tries to enable the SMES on max input/output settings. With load balancing it should be fine as long as engine outputs at least ~500kW
-/obj/effect/engine_setup/smes
+/obj/engine_setup/smes
name = "SMES Marker"
-/obj/effect/engine_setup/smes/activate()
+/obj/engine_setup/smes/activate()
..()
var/obj/machinery/power/smes/S = locate() in get_turf(src)
if(!S)
@@ -214,11 +214,11 @@
// Sets up filters. This assumes filters are set to filter out CO2 back to the core loop by default!
-/obj/effect/engine_setup/filter
+/obj/engine_setup/filter
name = "Omni Filter Marker"
var/coolant = null
-/obj/effect/engine_setup/filter/activate()
+/obj/engine_setup/filter/activate()
..()
var/obj/machinery/atmospherics/omni/filter/F = locate() in get_turf(src)
if(!F)
diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm
index c4eca6d038d9e..7bea97a58914d 100644
--- a/code/modules/supermatter/supermatter.dm
+++ b/code/modules/supermatter/supermatter.dm
@@ -27,11 +27,11 @@
/obj/machinery/power/supermatter
name = "supermatter core"
desc = "A strangely translucent and iridescent crystal. You get headaches just from looking at it."
- icon = 'icons/obj/supermatter.dmi'
+ icon = 'icons/obj/machines/power/supermatter.dmi'
icon_state = "supermatter"
density = TRUE
anchored = FALSE
- light_outer_range = 4
+ light_range = 4
layer = ABOVE_HUMAN_LAYER
@@ -148,7 +148,7 @@
var/area/A = get_area(src)
log_and_message_admins(message + " in [A.name]", null, src)
if(send_to_irc)
- send2adminirc(message + " in [A.name]")
+ send_to_admin_discord(message + " in [A.name]")
return TRUE
else
return FALSE
@@ -219,7 +219,7 @@
to_chat(mob, SPAN_DANGER("An invisible force slams you against the ground!"))
// Effect 2: Z-level wide electrical pulse
- for(var/obj/machinery/power/apc/A in SSmachines.machinery)
+ for(var/obj/machinery/power/apc/A as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/apc))
if(!(A.z in affected_z))
continue
@@ -233,7 +233,7 @@
else
A.energy_fail(round(DETONATION_SHUTDOWN_APC * random_change))
- for(var/obj/machinery/power/smes/buildable/S in SSmachines.machinery)
+ for(var/obj/machinery/power/smes/buildable/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/smes/buildable))
if(!(S.z in affected_z))
continue
// Causes SMESes to shut down for a bit
@@ -242,7 +242,7 @@
// Effect 3: Break solar arrays
- for(var/obj/machinery/power/solar/S in SSmachines.machinery)
+ for(var/obj/machinery/power/solar/S as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/solar))
if(!(S.z in affected_z))
continue
if(prob(DETONATION_SOLAR_BREAK_CHANCE))
@@ -257,26 +257,24 @@
/obj/machinery/power/supermatter/examine(mob/user)
. = ..()
- if(user.skill_check(SKILL_ENGINES, SKILL_EXPERT))
- var/integrity_message
+ if(user.skill_check(SKILL_ENGINES, SKILL_EXPERIENCED))
switch(get_integrity())
if(0 to 30)
- integrity_message = SPAN_DANGER("It looks highly unstable!")
+ . += SPAN_DANGER("It looks highly unstable!")
if(31 to 70)
- integrity_message = "It appears to be losing cohesion!"
+ . += SPAN_WARNING("It appears to be losing cohesion!")
else
- integrity_message = "At a glance, it seems to be in sound shape."
- to_chat(user, integrity_message)
- if(user.skill_check(SKILL_ENGINES, SKILL_PROF))
+ . += SPAN_NOTICE("At a glance, it seems to be in sound shape.")
+ if(user.skill_check(SKILL_ENGINES, SKILL_MASTER))
var/display_power = power
display_power *= (0.85 + 0.3 * rand())
display_power = round(display_power, 20)
- to_chat(user, "Eyeballing it, you place the relative EER at around [display_power] MeV/cm3.")
+ . += SPAN_NOTICE("Eyeballing it, you place the relative EER at around [display_power] MeV/cm3.")
//Changes color and luminosity of the light to these values if they were not already set
/obj/machinery/power/supermatter/proc/shift_light(lum, clr)
- if(lum != light_outer_range || clr != light_color)
- set_light(1, 0.1, lum, l_color = clr)
+ if(lum != light_range || clr != light_color)
+ set_light(lum, 1, l_color = clr)
/obj/machinery/power/supermatter/proc/get_integrity()
var/integrity = damage / explosion_point
@@ -511,7 +509,7 @@
ui.set_auto_update(1)
-/obj/machinery/power/supermatter/attackby(obj/item/W as obj, mob/living/user as mob)
+/obj/machinery/power/supermatter/use_tool(obj/item/W, mob/living/user, list/click_params)
if(istype(W, /obj/item/tape_roll))
to_chat(user, SPAN_NOTICE("You repair some of the damage to \the [src] with \the [W]."))
damage = max(damage - 10, 0)
@@ -523,10 +521,11 @@
SPAN_WARNING("For a brief moment, you hear an oppressive, unnatural silence.")
)
+ user.apply_damage(150, DAMAGE_RADIATION, damage_flags = DAMAGE_FLAG_DISPERSED)
if (user.drop_from_inventory(W))
Consume(W)
-
- user.apply_damage(150, DAMAGE_RADIATION, damage_flags = DAMAGE_FLAG_DISPERSED)
+ return TRUE
+ else return ..()
/obj/machinery/power/supermatter/Bumped(atom/AM as mob|obj)
@@ -673,7 +672,7 @@
//Warning lights
/obj/machinery/rotating_alarm/supermatter
- name = "Supermatter alarm"
+ name = "supermatter alarm"
desc = "An industrial rotating alarm light. This one is used to monitor supermatter engines."
frame_type = /obj/item/frame/supermatter_alarm
@@ -682,10 +681,10 @@
/obj/machinery/rotating_alarm/supermatter/Initialize()
. = ..()
- GLOB.supermatter_status.register_global(src, .proc/check_supermatter)
+ GLOB.supermatter_status.register_global(src, PROC_REF(check_supermatter))
/obj/machinery/rotating_alarm/supermatter/Destroy()
- GLOB.supermatter_status.unregister_global(src, .proc/check_supermatter)
+ GLOB.supermatter_status.unregister_global(src, PROC_REF(check_supermatter))
. = ..()
/obj/machinery/rotating_alarm/supermatter/proc/check_supermatter(obj/machinery/power/supermatter/SM, danger)
diff --git a/code/modules/surgery/__surgery_setup.dm b/code/modules/surgery/__surgery_setup.dm
index 86be0e09334fa..5413e68e3d3ac 100644
--- a/code/modules/surgery/__surgery_setup.dm
+++ b/code/modules/surgery/__surgery_setup.dm
@@ -1,12 +1,12 @@
#define SURGERY_NO_ROBOTIC 1
#define SURGERY_NO_CRYSTAL 2
#define SURGERY_NO_STUMP 4
-#define SURGERY_NO_FLESH 8
+#define SURGERY_NO_FLESH 8
#define SURGERY_NEEDS_INCISION 16
#define SURGERY_NEEDS_RETRACTED 32
#define SURGERY_NEEDS_ENCASEMENT 64
-#define SURGERY_SKILLS_GENERIC list(SKILL_ANATOMY = SKILL_ADEPT, SKILL_MEDICAL = SKILL_EXPERT)
-#define SURGERY_SKILLS_DELICATE list(SKILL_ANATOMY = SKILL_EXPERT, SKILL_MEDICAL = SKILL_EXPERT)
-#define SURGERY_SKILLS_ROBOTIC list(SKILL_DEVICES = SKILL_ADEPT)
-#define SURGERY_SKILLS_ROBOTIC_ON_MEAT list(SKILL_ANATOMY = SKILL_ADEPT, SKILL_DEVICES = SKILL_ADEPT)
+#define SURGERY_SKILLS_GENERIC list(SKILL_ANATOMY = SKILL_TRAINED, SKILL_MEDICAL = SKILL_EXPERIENCED)
+#define SURGERY_SKILLS_DELICATE list(SKILL_ANATOMY = SKILL_EXPERIENCED, SKILL_MEDICAL = SKILL_EXPERIENCED)
+#define SURGERY_SKILLS_ROBOTIC list(SKILL_DEVICES = SKILL_TRAINED)
+#define SURGERY_SKILLS_ROBOTIC_ON_MEAT list(SKILL_ANATOMY = SKILL_TRAINED, SKILL_DEVICES = SKILL_BASIC)
diff --git a/code/modules/surgery/bones.dm b/code/modules/surgery/bones.dm
index d345d1889f948..c4781071b6124 100644
--- a/code/modules/surgery/bones.dm
+++ b/code/modules/surgery/bones.dm
@@ -34,6 +34,7 @@
user.visible_message("\The [user] starts applying \the [tool] to [bone]." , \
"You start applying \the [tool] to [bone].")
target.custom_pain("Something in your [affected.name] is causing you a lot of pain!",50, affecting = affected)
+ playsound(target.loc, 'sound/items/bonegel.ogg', 50, TRUE)
..()
/singleton/surgery_step/bone/glue/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -58,6 +59,7 @@
name = "Set bone"
allowed_tools = list(
/obj/item/bonesetter = 100,
+ /obj/item/swapper/power_drill = 100,
/obj/item/wrench = 75
)
min_duration = 60
@@ -77,6 +79,7 @@
user.visible_message("[user] is beginning to set [bone] in place with \the [tool]." , \
"You are beginning to set [bone] in place with \the [tool].")
target.custom_pain("The pain in your [affected.name] is going to make you pass out!",50, affecting = affected)
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
..()
/singleton/surgery_step/bone/set_bone/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -123,6 +126,7 @@
var/bone = affected.encased ? "\the [target]'s damaged [affected.encased]" : "damaged bones in \the [target]'s [affected.name]"
user.visible_message("[user] starts to finish mending [bone] with \the [tool].", \
"You start to finish mending [bone] with \the [tool].")
+ playsound(target.loc, 'sound/items/bonegel.ogg', 50, TRUE)
..()
/singleton/surgery_step/bone/finish/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/encased.dm b/code/modules/surgery/encased.dm
index 9b3b69be5c6f1..e0dad34678741 100644
--- a/code/modules/surgery/encased.dm
+++ b/code/modules/surgery/encased.dm
@@ -32,6 +32,7 @@
user.visible_message("[user] begins to cut through [target]'s [affected.encased] with \the [tool].", \
"You begin to cut through [target]'s [affected.encased] with \the [tool].")
target.custom_pain("Something hurts horribly in your [affected.name]!",60, affecting = affected)
+ playsound(target.loc, 'sound/items/circularsaw.ogg', 50, TRUE)
..()
/singleton/surgery_step/open_encased/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/face.dm b/code/modules/surgery/face.dm
index 1e66c238e7d1e..0a15de5356f4a 100644
--- a/code/modules/surgery/face.dm
+++ b/code/modules/surgery/face.dm
@@ -1,4 +1,4 @@
- //////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////
// facial reconstruction surgery step
//////////////////////////////////////////////////////////////////
/singleton/surgery_step/fix_face
@@ -23,6 +23,7 @@
/singleton/surgery_step/fix_face/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] starts repairing damage to \the [target]'s face with \the [tool].", \
"You start repairing damage to \the [target]'s face with \the [tool].")
+ playsound(target.loc, 'sound/items/hemostat.ogg', 50, TRUE)
..()
/singleton/surgery_step/fix_face/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -72,6 +73,7 @@
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if(affected.stage == 0)
affected.stage = 1
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
..()
/singleton/surgery_step/plastic_surgery/prepare_face/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -107,6 +109,7 @@
SPAN_NOTICE("\The [user] starts molding \the [target]'s face with \the [tool]."),
SPAN_NOTICE("You start molding \the [target]'s face with \the [tool].")
)
+ playsound(target.loc, 'sound/items/hemostat.ogg', 50, TRUE)
..()
/singleton/surgery_step/plastic_surgery/reform_face/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/generic.dm b/code/modules/surgery/generic.dm
index 7bb630ee4d6c9..5e31563d3b9dc 100644
--- a/code/modules/surgery/generic.dm
+++ b/code/modules/surgery/generic.dm
@@ -23,19 +23,27 @@
/singleton/surgery_step/generic/cut_with_laser
name = "Make laser incision"
allowed_tools = list(
- /obj/item/scalpel/laser3 = 95,
- /obj/item/scalpel/laser2 = 85,
- /obj/item/scalpel/laser1 = 75,
+ /obj/item/scalpel/laser = 100,
/obj/item/melee/energy/sword = 5
)
min_duration = 90
max_duration = 110
+/singleton/surgery_step/generic/cut_with_laser/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ if(.)
+ var/obj/item/organ/external/affected = .
+ if(affected.how_open())
+ var/datum/wound/cut/incision = affected.get_incision()
+ to_chat(user, SPAN_NOTICE("The [incision.desc] provides enough access."))
+ return FALSE
+
/singleton/surgery_step/generic/cut_with_laser/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("[user] starts the bloodless incision on [target]'s [affected.name] with \the [tool].", \
"You start the bloodless incision on [target]'s [affected.name] with \the [tool].")
target.custom_pain("You feel a horrible, searing pain in your [affected.name]!",50, affecting = affected)
+ playsound(target.loc, 'sound/items/cautery.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/cut_with_laser/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -59,16 +67,26 @@
/singleton/surgery_step/generic/managed
name = "Make managed incision"
allowed_tools = list(
- /obj/item/scalpel/manager = 100
+ /obj/item/scalpel/ims = 100
)
min_duration = 80
max_duration = 120
+/singleton/surgery_step/generic/managed/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+ . = ..()
+ if(.)
+ var/obj/item/organ/external/affected = .
+ if(affected.how_open())
+ var/datum/wound/cut/incision = affected.get_incision()
+ to_chat(user, SPAN_NOTICE("The [incision.desc] provides enough access."))
+ return FALSE
+
/singleton/surgery_step/generic/managed/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("[user] starts to construct a prepared incision on and within [target]'s [affected.name] with \the [tool].", \
"You start to construct a prepared incision on and within [target]'s [affected.name] with \the [tool].")
target.custom_pain("You feel a horrible, searing pain in your [affected.name] as it is pushed apart!",50, affecting = affected)
+ playsound(target.loc, 'sound/items/circularsaw.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/managed/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -91,7 +109,7 @@
/singleton/surgery_step/generic/cut_open
name = "Make incision"
allowed_tools = list(
- /obj/item/scalpel = 100,
+ /obj/item/scalpel/basic = 100,
/obj/item/material/knife = 75,
/obj/item/broken_bottle = 50,
/obj/item/material/shard = 50
@@ -115,6 +133,7 @@
user.visible_message("[user] starts [access_string] on [target]'s [affected.name] with \the [tool].", \
"You start [access_string] on [target]'s [affected.name] with \the [tool].")
target.custom_pain("You feel a horrible pain as if from a sharp knife in your [affected.name]!",40, affecting = affected)
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/cut_open/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -132,7 +151,7 @@
/singleton/surgery_step/generic/cut_open/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
. = ..()
- if(user.skill_check(SKILL_FORENSICS, SKILL_ADEPT))
+ if(user.skill_check(SKILL_FORENSICS, SKILL_TRAINED))
. += 40
if(target.stat == DEAD)
. += 40
@@ -162,6 +181,7 @@
user.visible_message("[user] starts clamping bleeders in [target]'s [affected.name] with \the [tool].", \
"You start clamping bleeders in [target]'s [affected.name] with \the [tool].")
target.custom_pain("The pain in your [affected.name] is maddening!",40, affecting = affected)
+ playsound(target.loc, 'sound/items/hemostat.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/clamp_bleeders/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -185,6 +205,7 @@
name = "Widen incision"
allowed_tools = list(
/obj/item/retractor = 100,
+ /obj/item/swapper/jaws_of_life = 80,
/obj/item/crowbar = 75,
/obj/item/material/knife = 50,
/obj/item/material/kitchen/utensil/fork = 50
@@ -209,6 +230,7 @@
user.visible_message("[user] starts to pry open the incision on [target]'s [affected.name] with \the [tool].", \
"You start to pry open the incision on [target]'s [affected.name] with \the [tool].")
target.custom_pain("It feels like the skin on your [affected.name] is on fire!",40,affecting = affected)
+ playsound(target.loc, 'sound/items/retractor.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/retract_skin/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -269,6 +291,7 @@
user.visible_message("[user] is beginning to [cauterize_term][W ? " \a [W.desc] on" : ""] \the [target]'s [affected.name] with \the [tool]." , \
"You are beginning to [cauterize_term][W ? " \a [W.desc] on" : ""] \the [target]'s [affected.name] with \the [tool].")
target.custom_pain("Your [affected.name] is being burned!",40,affecting = affected)
+ playsound(target.loc, 'sound/items/cautery.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/cauterize/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -321,6 +344,7 @@
user.visible_message("[user] is beginning to amputate [target]'s [affected.name] with \the [tool]." , \
FONT_LARGE("You are beginning to cut through [target]'s [affected.amputation_point] with \the [tool]."))
target.custom_pain("Your [affected.amputation_point] is being ripped apart!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/amputation.ogg', 50, TRUE)
..()
/singleton/surgery_step/generic/amputate/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm
index 4688ee646f28c..ea1a1a38ae00b 100644
--- a/code/modules/surgery/implant.dm
+++ b/code/modules/surgery/implant.dm
@@ -10,7 +10,7 @@
/singleton/surgery_step/cavity
shock_level = 40
delicate = 1
- surgery_candidate_flags = SURGERY_NO_CRYSTAL | SURGERY_NO_STUMP | SURGERY_NEEDS_ENCASEMENT
+ surgery_candidate_flags = SURGERY_NO_CRYSTAL | SURGERY_NO_STUMP | SURGERY_NEEDS_ENCASEMENT | SURGERY_NO_ROBOTIC
/singleton/surgery_step/cavity/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
@@ -25,6 +25,7 @@
name = "Hollow out cavity"
allowed_tools = list(
/obj/item/surgicaldrill = 100,
+ /obj/item/swapper/power_drill = 90,
/obj/item/pen = 75,
/obj/item/stack/material/rods = 50
)
@@ -42,6 +43,7 @@
"You start making some space inside [target]'s [affected.cavity_name] cavity with \the [tool]." )
target.custom_pain("The pain in your chest is living hell!",1,affecting = affected)
affected.cavity = TRUE
+ playsound(target.loc, 'sound/items/surgicaldrill.ogg', 50, TRUE)
..()
/singleton/surgery_step/cavity/make_space/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -73,6 +75,7 @@
user.visible_message("[user] starts mending [target]'s [affected.cavity_name] cavity wall with \the [tool].", \
"You start mending [target]'s [affected.cavity_name] cavity wall with \the [tool]." )
target.custom_pain("The pain in your chest is living hell!",1,affecting = affected)
+ playsound(target.loc, 'sound/items/cautery.ogg', 50, TRUE)
..()
/singleton/surgery_step/cavity/close_space/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -147,8 +150,8 @@
/obj/item/wirecutters = 75,
/obj/item/material/kitchen/utensil/fork = 20
)
- min_duration = 80
- max_duration = 100
+ min_duration = 120
+ max_duration = 150
/singleton/surgery_step/cavity/implant_removal/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
@@ -163,6 +166,7 @@
user.visible_message("[user] starts poking around inside [target]'s [affected.name] with \the [tool].", \
"You start poking around inside [target]'s [affected.name] with \the [tool]." )
target.custom_pain("The pain in your [affected.name] is living hell!",1,affecting = affected)
+ playsound(target.loc, 'sound/items/hemostat.ogg', 50, TRUE)
..()
/singleton/surgery_step/cavity/implant_removal/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -173,7 +177,6 @@
if(BP_IS_ROBOTIC(affected) && affected.hatch_state == HATCH_OPENED)
exposed = 1
- var/find_prob = 0
var/list/loot = list()
if(exposed)
loot = affected.implants
@@ -181,41 +184,27 @@
for(var/datum/wound/wound in affected.wounds)
if(LAZYLEN(wound.embedded_objects))
loot |= wound.embedded_objects
- find_prob += 50
if (length(loot))
var/obj/item/obj = pick(loot)
- if(istype(obj,/obj/item/implant))
- var/obj/item/implant/imp = obj
- if (imp.islegal())
- find_prob +=60
- else
- find_prob +=40
- else
- find_prob +=50
-
- if (prob(find_prob))
- user.visible_message(SPAN_NOTICE("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You take \the [obj] out of incision on \the [target]'s [affected.name] with \the [tool].") )
- target.remove_implant(obj, TRUE, affected)
+ user.visible_message(SPAN_NOTICE("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]."), \
+ SPAN_NOTICE("You take \the [obj] out of incision on \the [target]'s [affected.name] with \the [tool].") )
+ target.remove_implant(obj, TRUE, affected)
- SET_BIT(target.hud_updateflag, IMPLOYAL_HUD)
+ SET_BIT(target.hud_updateflag, IMPLOYAL_HUD)
- //Handle possessive brain borers.
- if(istype(obj,/mob/living/simple_animal/borer))
- var/mob/living/simple_animal/borer/worm = obj
- if(worm.controlling)
- target.release_control()
- worm.detatch()
- worm.leave_host()
+ //Handle possessive brain borers.
+ if(istype(obj,/mob/living/simple_animal/borer))
+ var/mob/living/simple_animal/borer/worm = obj
+ if(worm.controlling)
+ target.release_control()
+ worm.detatch()
+ worm.leave_host()
playsound(target.loc, 'sound/effects/squelch1.ogg', 15, 1)
- else
- user.visible_message(SPAN_NOTICE("[user] removes \the [tool] from [target]'s [affected.name]."), \
- SPAN_NOTICE("There's something inside [target]'s [affected.name], but you just missed it this time.") )
else
user.visible_message(SPAN_NOTICE("[user] could not find anything inside [target]'s [affected.name], and pulls \the [tool] out."), \
SPAN_NOTICE("You could not find anything inside [target]'s [affected.name].") )
diff --git a/code/modules/surgery/limb_reattach.dm b/code/modules/surgery/limb_reattach.dm
index 7f0d798e9541d..641454574376f 100644
--- a/code/modules/surgery/limb_reattach.dm
+++ b/code/modules/surgery/limb_reattach.dm
@@ -71,6 +71,7 @@
var/obj/item/organ/external/E = tool
user.visible_message("[user] starts attaching [E.name] to [target]'s [E.amputation_point].", \
"You start attaching [E.name] to [target]'s [E.amputation_point].")
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
/singleton/surgery_step/limb/attach/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!user.unEquip(tool))
@@ -121,6 +122,7 @@
var/obj/item/organ/external/E = target.get_organ(target_zone)
user.visible_message("[user] starts connecting tendons and muscles in [target]'s [E.amputation_point] with [tool].", \
"You start connecting tendons and muscle in [target]'s [E.amputation_point].")
+ playsound(target.loc, 'sound/items/fixovein.ogg', 50, TRUE)
/singleton/surgery_step/limb/connect/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/E = target.get_organ(target_zone)
@@ -167,6 +169,7 @@
/singleton/surgery_step/limb/mechanize/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] starts attaching \the [tool] to [target].", \
"You start attaching \the [tool] to [target].")
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
/singleton/surgery_step/limb/mechanize/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/robot_parts/L = tool
diff --git a/code/modules/surgery/organs_internal.dm b/code/modules/surgery/organs_internal.dm
index 47a2753d9ae90..f580b267952f2 100644
--- a/code/modules/surgery/organs_internal.dm
+++ b/code/modules/surgery/organs_internal.dm
@@ -44,7 +44,13 @@
if(I && I.damage > 0 && !BP_IS_ROBOTIC(I) && (!(I.status & ORGAN_DEAD) || I.can_recover()) && (I.surface_accessible || affected.how_open() >= (affected.encased ? SURGERY_ENCASED : SURGERY_RETRACTED)))
user.visible_message("[user] starts treating damage to [target]'s [I.name] with [tool_name].", \
"You start treating damage to [target]'s [I.name] with [tool_name]." )
+ else if(I && !(I.status & ORGAN_CUT_AWAY) && (I.status & ORGAN_DEAD) && I.parent_organ == affected.organ_tag && !BP_IS_ROBOTIC(I))
+ if (!I.can_recover())
+ to_chat(user, SPAN_WARNING("\The [target]'s [I.name] is fully necrotic; [tool_name] won't help here."))
+ else
+ to_chat(user, SPAN_WARNING("\The [target]'s [I.name] is decaying; you'll need more than just [tool_name] here."))
target.custom_pain("The pain in your [affected.name] is living hell!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/bonegel.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/fix_organ/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -95,25 +101,28 @@
surgery_candidate_flags = SURGERY_NO_CRYSTAL | SURGERY_NO_ROBOTIC | SURGERY_NO_STUMP | SURGERY_NEEDS_ENCASEMENT
/singleton/surgery_step/internal/detatch_organ/pre_surgery_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/list/attached_organs
- for(var/organ in target.internal_organs_by_name)
+ var/list/attached_organs = list()
+ for (var/organ in target.internal_organs_by_name)
var/obj/item/organ/I = target.internal_organs_by_name[organ]
- if(I && !(I.status & ORGAN_CUT_AWAY) && I.parent_organ == target_zone)
- LAZYDISTINCTADD(attached_organs, organ)
- if(!LAZYLEN(attached_organs))
+ if (I && !(I.status & ORGAN_CUT_AWAY) && I.parent_organ == target_zone)
+ var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state)
+ radial_button.name = "Detach \the [I.name]"
+ attached_organs[I.organ_tag] = radial_button
+ if (!length(attached_organs))
to_chat(user, SPAN_WARNING("You can't find any organs to separate."))
- else
- var/obj/item/organ/organ_to_remove = attached_organs[1]
- if(length(attached_organs) > 1)
- organ_to_remove = input(user, "Which organ do you want to separate?") as null|anything in attached_organs
- if(organ_to_remove)
- return organ_to_remove
+ return FALSE
+ if (length(attached_organs) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ return attached_organs[1]
+ var/choice = show_radial_menu(user, tool, attached_organs, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+ if (choice && user.use_sanity_check(target, tool))
+ return choice
return FALSE
/singleton/surgery_step/internal/detatch_organ/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] starts to separate [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].", \
"You start to separate [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool]." )
target.custom_pain("Someone's ripping out your [LAZYACCESS(target.surgeries_in_progress, target_zone)]!",100)
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/detatch_organ/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -139,6 +148,7 @@
/obj/item/hemostat = 100,
/obj/item/wirecutters = 75,
/obj/item/material/knife = 75,
+ /obj/item/swapper/jaws_of_life = 50,
/obj/item/material/kitchen/utensil/fork = 20
)
min_duration = 60
@@ -146,19 +156,23 @@
/singleton/surgery_step/internal/remove_organ/pre_surgery_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- if(affected)
- var/list/removable_organs
- for(var/obj/item/organ/internal/I in affected.implants)
- if(I.status & ORGAN_CUT_AWAY)
- LAZYDISTINCTADD(removable_organs, I)
- if(!LAZYLEN(removable_organs))
- to_chat(user, SPAN_WARNING("You can't find any removable organs."))
- else
- var/obj/item/organ/organ_to_remove = removable_organs[1]
- if(length(removable_organs) > 1)
- organ_to_remove = input(user, "Which organ do you want to remove?") as null|anything in removable_organs
- if(organ_to_remove)
- return organ_to_remove
+ if (!affected)
+ return FALSE
+ var/list/removable_organs = list()
+ for (var/obj/item/organ/internal/I in affected.implants)
+ if (~I.status & ORGAN_CUT_AWAY)
+ continue
+ var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state)
+ radial_button.name = "Remove \the [I.name]"
+ removable_organs[I] = radial_button
+ if (!length(removable_organs))
+ to_chat(user, SPAN_WARNING("You can't find any removable organs."))
+ return FALSE
+ if (length(removable_organs) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ return removable_organs[1]
+ var/choice = show_radial_menu(user, tool, removable_organs, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+ if (choice && user.use_sanity_check(target, tool))
+ return choice
return FALSE
/singleton/surgery_step/internal/remove_organ/get_skill_reqs(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
@@ -178,6 +192,7 @@
user.visible_message("\The [user] starts removing [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].", \
"You start removing \the [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].")
target.custom_pain("The pain in your [affected.name] is living hell!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/hemostat.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/remove_organ/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -270,6 +285,7 @@
user.visible_message("[user] starts [robotic_surgery ? "reinstalling" : "transplanting"] \the [tool] into [target]'s [affected.name].", \
"You start [robotic_surgery ? "reinstalling" : "transplanting"] \the [tool] into [target]'s [affected.name].")
target.custom_pain("Someone's rooting around in your [affected.name]!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/replace_organ/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -325,13 +341,20 @@
for(var/obj/item/organ/I in affected.implants)
if(I && (I.status & ORGAN_CUT_AWAY))
- LAZYADD(attachable_organs, I)
+ var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state)
+ radial_button.name = "Attach \the [I.name]"
+ LAZYSET(attachable_organs, I, radial_button)
if(!LAZYLEN(attachable_organs))
return FALSE
- var/obj/item/organ/organ_to_replace = input(user, "Which organ do you want to reattach?") as null|anything in attachable_organs
- if(!organ_to_replace)
+ var/obj/item/organ/organ_to_replace
+ if (length(attachable_organs) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ organ_to_replace = attachable_organs[1]
+ else
+ organ_to_replace = show_radial_menu(user, tool, attachable_organs, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+
+ if(!organ_to_replace || !user.use_sanity_check(target, tool))
return FALSE
if(organ_to_replace.parent_organ != affected.organ_tag)
@@ -358,6 +381,7 @@
user.visible_message("[user] begins reattaching [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].", \
"You start reattaching [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].")
target.custom_pain("Someone's digging needles into your [LAZYACCESS(target.surgeries_in_progress, target_zone)]!",100)
+ playsound(target.loc, 'sound/items/fixovein.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/attach_organ/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -413,13 +437,15 @@
var/list/obj/item/organ/internal/dead_organs = list()
for(var/obj/item/organ/internal/I in target.internal_organs)
if(I && !(I.status & ORGAN_CUT_AWAY) && (I.status & ORGAN_DEAD) && I.parent_organ == affected.organ_tag && !BP_IS_ROBOTIC(I))
- dead_organs |= I
+ var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state)
+ radial_button.name = "Regenerate \the [I.name]"
+ LAZYSET(dead_organs, I, radial_button)
if(!length(dead_organs))
return FALSE
- var/obj/item/organ/internal/organ_to_fix = dead_organs[1]
- if(length(dead_organs) > 1)
- organ_to_fix = input(user, "Which organ do you want to regenerate?") as null|anything in dead_organs
- if(!organ_to_fix)
+ if (length(dead_organs) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ return dead_organs[1]
+ var/obj/item/organ/internal/organ_to_fix = show_radial_menu(user, tool, dead_organs, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+ if(!organ_to_fix || !user.use_sanity_check(target, tool))
return FALSE
if(!organ_to_fix.can_recover())
to_chat(user, SPAN_WARNING("The [organ_to_fix.name] is necrotic and can't be saved, it will need to be replaced."))
@@ -433,6 +459,7 @@
user.visible_message("[user] starts applying medication to the affected tissue in [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool]." , \
"You start applying medication to the affected tissue in [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].")
target.custom_pain("Something in your [LAZYACCESS(target.surgeries_in_progress, target_zone)] is causing you a lot of pain!",50, affecting = LAZYACCESS(target.surgeries_in_progress, target_zone))
+ playsound(target.loc, 'sound/items/fixovein.ogg', 50, TRUE)
..()
/singleton/surgery_step/internal/treat_necrosis/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/other.dm b/code/modules/surgery/other.dm
index 40eebcc9fe15d..d4e411040c9f4 100644
--- a/code/modules/surgery/other.dm
+++ b/code/modules/surgery/other.dm
@@ -31,6 +31,7 @@
user.visible_message("[user] starts reattaching the damaged [affected.tendon_name] in [target]'s [affected.name] with \the [tool]." , \
"You start reattaching the damaged [affected.tendon_name] in [target]'s [affected.name] with \the [tool].")
target.custom_pain("The pain in your [affected.name] is unbearable!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/fixovein.ogg', 50, TRUE)
..()
/singleton/surgery_step/fix_tendon/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -75,6 +76,7 @@
user.visible_message("[user] starts patching the damaged [affected.artery_name] in [target]'s [affected.name] with \the [tool]." , \
"You start patching the damaged [affected.artery_name] in [target]'s [affected.name] with \the [tool].")
target.custom_pain("The pain in your [affected.name] is unbearable!",100,affecting = affected)
+ playsound(target.loc, 'sound/items/fixovein.ogg', 50, TRUE)
..()
/singleton/surgery_step/fix_vein/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -118,15 +120,15 @@
/singleton/surgery_step/hardsuit/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!istype(target))
return FALSE
- if(isWelder(tool))
- var/obj/item/weldingtool/welder = tool
- if(!welder.isOn() || !welder.remove_fuel(1,user))
+ if(tool.tool_behaviour == TOOL_WELDER)
+ if(!tool.use_as_tool(src, user, amount = 1, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return FALSE
return (target_zone == BP_CHEST) && istype(target.back, /obj/item/rig) && !(target.back.canremove)
/singleton/surgery_step/hardsuit/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("[user] starts cutting through the support systems of [target]'s [target.back] with \the [tool]." , \
"You start cutting through the support systems of [target]'s [target.back] with \the [tool].")
+ playsound(target.loc, 'sound/items/circularsaw.ogg', 50, TRUE)
..()
/singleton/surgery_step/hardsuit/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -176,6 +178,7 @@
user.visible_message("[user] starts pouring [tool]'s contents on \the [target]'s [affected.name]." , \
"You start pouring [tool]'s contents on \the [target]'s [affected.name].")
target.custom_pain("Your [affected.name] is on fire!",50,affecting = affected)
+ playsound(target.loc, 'sound/items/spray_1.ogg', 15, 1)
..()
/singleton/surgery_step/sterilize/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm
index d4b0060e338eb..bc4dc155f4774 100644
--- a/code/modules/surgery/robotics.dm
+++ b/code/modules/surgery/robotics.dm
@@ -20,14 +20,14 @@
/singleton/surgery_step/robotics/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
. = ..()
- //Compensating for anatomy skill req in base proc
- . += 10
- if(!user.skill_check(SKILL_DEVICES, SKILL_ADEPT))
+ if(!user.skill_check(SKILL_DEVICES, SKILL_TRAINED))
+ . -= 30
+ if(!user.skill_check(SKILL_DEVICES, SKILL_EXPERIENCED))
. -= 20
- if(user.skill_check(SKILL_ELECTRICAL, SKILL_BASIC))
- . += 10
- if(user.skill_check(SKILL_DEVICES, SKILL_PROF))
- . += 20
+ if(user.skill_check(SKILL_DEVICES, SKILL_EXPERIENCED))
+ . += 35
+ if(user.skill_check(SKILL_DEVICES, SKILL_MASTER))
+ . += 30
//////////////////////////////////////////////////////////////////
// unscrew robotic limb hatch surgery step
@@ -35,12 +35,13 @@
/singleton/surgery_step/robotics/unscrew_hatch
name = "Unscrew maintenance hatch"
allowed_tools = list(
- /obj/item/screwdriver = 100,
- /obj/item/material/coin = 50,
- /obj/item/material/knife = 50
+ /obj/item/screwdriver = 60,
+ /obj/item/swapper/power_drill = 100,
+ /obj/item/material/coin = 30,
+ /obj/item/material/knife = 40
)
- min_duration = 90
- max_duration = 110
+ min_duration = 50
+ max_duration = 90
/singleton/surgery_step/robotics/unscrew_hatch/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
@@ -49,19 +50,20 @@
/singleton/surgery_step/robotics/unscrew_hatch/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] starts to unscrew the maintenance hatch on [target]'s [affected.name] with \the [tool].", \
- "You start to unscrew the maintenance hatch on [target]'s [affected.name] with \the [tool].")
+ user.visible_message("[user] starts to unscrew the maintenance hatch on [target]'s [affected.name] with [tool].", \
+ "You start to unscrew the maintenance hatch on [target]'s [affected.name] with [tool].")
+ playsound(target.loc, 'sound/items/Screwdriver.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/unscrew_hatch/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] has opened the maintenance hatch on [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You have opened the maintenance hatch on [target]'s [affected.name] with \the [tool]."),)
+ user.visible_message(SPAN_NOTICE("[user] has opened the maintenance hatch on [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You have opened the maintenance hatch on [target]'s [affected.name] with [tool]."),)
affected.hatch_state = HATCH_UNSCREWED
/singleton/surgery_step/robotics/unscrew_hatch/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_WARNING("\The [user]'s [tool.name] slips, failing to unscrew \the [target]'s [affected.name]."), \
+ user.visible_message(SPAN_WARNING("[user]'s [tool.name] slips, failing to unscrew [target]'s [affected.name]."), \
SPAN_WARNING("Your [tool.name] slips, failing to unscrew [target]'s [affected.name]."))
//////////////////////////////////////////////////////////////////
@@ -70,12 +72,13 @@
/singleton/surgery_step/robotics/screw_hatch
name = "Secure maintenance hatch"
allowed_tools = list(
- /obj/item/screwdriver = 100,
- /obj/item/material/coin = 50,
- /obj/item/material/knife = 50
+ /obj/item/screwdriver = 60,
+ /obj/item/swapper/power_drill = 100,
+ /obj/item/material/coin = 30,
+ /obj/item/material/knife = 40
)
- min_duration = 90
- max_duration = 110
+ min_duration = 50
+ max_duration = 90
/singleton/surgery_step/robotics/screw_hatch/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
@@ -84,14 +87,15 @@
/singleton/surgery_step/robotics/screw_hatch/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] starts to screw down the maintenance hatch on [target]'s [affected.name] with \the [tool].", \
- "You start to screw down the maintenance hatch on [target]'s [affected.name] with \the [tool].")
+ user.visible_message("[user] starts to screw down the maintenance hatch on [target]'s [affected.name] with [tool].", \
+ "You start to screw down the maintenance hatch on [target]'s [affected.name] with [tool].")
+ playsound(target.loc, 'sound/items/Screwdriver.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/screw_hatch/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] has screwed down the maintenance hatch on [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You have screwed down the maintenance hatch on [target]'s [affected.name] with \the [tool]."),)
+ user.visible_message(SPAN_NOTICE("[user] has screwed down the maintenance hatch on [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You have screwed down the maintenance hatch on [target]'s [affected.name] with [tool]."),)
affected.hatch_state = HATCH_CLOSED
/singleton/surgery_step/robotics/screw_hatch/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -105,9 +109,10 @@
/singleton/surgery_step/robotics/open_hatch
name = "Open maintenance hatch"
allowed_tools = list(
- /obj/item/retractor = 100,
- /obj/item/crowbar = 100,
- /obj/item/material/kitchen/utensil = 50
+ /obj/item/retractor = 40,
+ /obj/item/crowbar = 60,
+ /obj/item/swapper/jaws_of_life = 80,
+ /obj/item/material/kitchen/utensil = 30
)
min_duration = 30
@@ -120,14 +125,15 @@
/singleton/surgery_step/robotics/open_hatch/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] starts to pry open the maintenance hatch on [target]'s [affected.name] with \the [tool].",
- "You start to pry open the maintenance hatch on [target]'s [affected.name] with \the [tool].")
+ user.visible_message("[user] starts to pry open the maintenance hatch on [target]'s [affected.name] with [tool].",
+ "You start to pry open the maintenance hatch on [target]'s [affected.name] with [tool].")
+ playsound(target.loc, 'sound/items/Crowbar.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/open_hatch/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] opens the maintenance hatch on [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You open the maintenance hatch on [target]'s [affected.name] with \the [tool]."))
+ user.visible_message(SPAN_NOTICE("[user] opens the maintenance hatch on [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You open the maintenance hatch on [target]'s [affected.name] with [tool]."))
affected.hatch_state = HATCH_OPENED
/singleton/surgery_step/robotics/open_hatch/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -141,9 +147,10 @@
/singleton/surgery_step/robotics/close_hatch
name = "Close maintenance hatch"
allowed_tools = list(
- /obj/item/retractor = 100,
- /obj/item/crowbar = 100,
- /obj/item/material/kitchen/utensil = 50
+ /obj/item/retractor = 40,
+ /obj/item/crowbar = 60,
+ /obj/item/swapper/jaws_of_life = 80,
+ /obj/item/material/kitchen/utensil = 30
)
min_duration = 70
@@ -156,14 +163,15 @@
/singleton/surgery_step/robotics/close_hatch/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] begins to close the hatch on [target]'s [affected.name] with \the [tool]." , \
- "You begin to close the hatch on [target]'s [affected.name] with \the [tool].")
+ user.visible_message("[user] begins to close the hatch on [target]'s [affected.name] with [tool]." , \
+ "You begin to close the hatch on [target]'s [affected.name] with [tool].")
+ playsound(target.loc, 'sound/items/Crowbar.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/close_hatch/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] closes the hatch on [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You close the hatch on [target]'s [affected.name] with \the [tool]."))
+ user.visible_message(SPAN_NOTICE("[user] closes the hatch on [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You close the hatch on [target]'s [affected.name] with [tool]."))
affected.hatch_state = HATCH_UNSCREWED
affected.germ_level = 0
@@ -178,18 +186,23 @@
/singleton/surgery_step/robotics/repair_brute
name = "Repair damage to prosthetic"
allowed_tools = list(
- /obj/item/weldingtool = 100,
- /obj/item/gun/energy/plasmacutter = 50,
+ /obj/item/weldingtool = 35,
+ /obj/item/weldingtool/electric = 50,
+ /obj/item/gun/energy/plasmacutter = 25,
/obj/item/psychic_power/psiblade/master = 100
)
- min_duration = 50
- max_duration = 60
+ min_duration = 70
+ max_duration = 90
/singleton/surgery_step/robotics/repair_brute/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
. = ..()
if(user.skill_check(SKILL_CONSTRUCTION, SKILL_BASIC))
+ . += 5
+ if(user.skill_check(SKILL_CONSTRUCTION, SKILL_TRAINED))
. += 10
+ if(!user.skill_check(SKILL_DEVICES, SKILL_EXPERIENCED))
+ . -= 10
/singleton/surgery_step/robotics/repair_brute/pre_surgery_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
@@ -198,15 +211,10 @@
to_chat(user, SPAN_WARNING("There is no damage to repair."))
return FALSE
if(BP_IS_BRITTLE(affected))
- to_chat(user, SPAN_WARNING("\The [target]'s [affected.name] is too brittle to be repaired normally."))
+ to_chat(user, SPAN_WARNING("[target]'s [affected.name] is too brittle to be repaired normally."))
return FALSE
- if(isWelder(tool))
- var/obj/item/weldingtool/welder = tool
- if(!welder.isOn() || !welder.remove_fuel(1,user))
- return FALSE
- if(istype(tool, /obj/item/gun/energy/plasmacutter))
- var/obj/item/gun/energy/plasmacutter/cutter = tool
- if(!cutter.slice(user))
+ if(tool.tool_behaviour == TOOL_WELDER)
+ if(!tool.use_as_tool(src, user, amount = 1, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return FALSE
return TRUE
return FALSE
@@ -218,14 +226,15 @@
/singleton/surgery_step/robotics/repair_brute/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] begins to patch damage to [target]'s [affected.name]'s support structure with \the [tool]." , \
- "You begin to patch damage to [target]'s [affected.name]'s support structure with \the [tool].")
+ user.visible_message("[user] begins to patch damage to [target]'s [affected.name]'s support structure with [tool]." , \
+ "You begin to patch damage to [target]'s [affected.name]'s support structure with [tool].")
+ playsound(target.loc, 'sound/items/Welder.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/repair_brute/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] finishes patching damage to [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You finish patching damage to [target]'s [affected.name] with \the [tool]."))
+ user.visible_message(SPAN_NOTICE("[user] finishes patching damage to [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You finish patching damage to [target]'s [affected.name] with [tool]."))
affected.heal_damage(rand(30,50),0,1,1)
affected.status &= ~ORGAN_DISFIGURED
@@ -240,14 +249,18 @@
//////////////////////////////////////////////////////////////////
/singleton/surgery_step/robotics/repair_brittle
name = "Reinforce prosthetic"
- allowed_tools = list(/obj/item/stack/nanopaste = 100)
+ allowed_tools = list(/obj/item/stack/nanopaste = 50)
min_duration = 50
max_duration = 60
/singleton/surgery_step/robotics/repair_brittle/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
. = ..()
- if(user.skill_check(SKILL_ELECTRICAL, SKILL_BASIC))
+ if(user.skill_check(SKILL_ELECTRICAL, SKILL_TRAINED))
+ . += 10
+ if(user.skill_check(SKILL_CONSTRUCTION, SKILL_TRAINED))
. += 10
+ if(!user.skill_check(SKILL_DEVICES, SKILL_EXPERIENCED))
+ . -= 15
/singleton/surgery_step/robotics/repair_brittle/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
@@ -256,20 +269,21 @@
/singleton/surgery_step/robotics/repair_brittle/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] begins to repair the brittle metal inside \the [target]'s [affected.name]." , \
- "You begin to repair the brittle metal inside \the [target]'s [affected.name].")
+ user.visible_message("[user] begins to repair the brittle metal inside [target]'s [affected.name]." , \
+ "You begin to repair the brittle metal inside [target]'s [affected.name].")
+ playsound(target.loc, 'sound/items/bonegel.ogg', 50, TRUE)
..()
/singleton/surgery_step/robotics/repair_brittle/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] finishes repairing the brittle interior of \the [target]'s [affected.name]."), \
- SPAN_NOTICE("You finish repairing the brittle interior of \the [target]'s [affected.name]."))
+ user.visible_message(SPAN_NOTICE("[user] finishes repairing the brittle interior of [target]'s [affected.name]."), \
+ SPAN_NOTICE("You finish repairing the brittle interior of [target]'s [affected.name]."))
affected.status &= ~ORGAN_BRITTLE
/singleton/surgery_step/robotics/repair_brittle/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_WARNING("[user] causes some of \the [target]'s [affected.name] to crumble!"),
- SPAN_WARNING("You cause some of \the [target]'s [affected.name] to crumble!"))
+ user.visible_message(SPAN_WARNING("[user] causes some of [target]'s [affected.name] to crumble!"),
+ SPAN_WARNING("You cause some of [target]'s [affected.name] to crumble!"))
target.apply_damage(rand(5,10), DAMAGE_BRUTE, affected)
//////////////////////////////////////////////////////////////////
@@ -278,16 +292,20 @@
/singleton/surgery_step/robotics/repair_burn
name = "Repair burns on prosthetic"
allowed_tools = list(
- /obj/item/stack/cable_coil = 100
+ /obj/item/stack/cable_coil = 50
)
- min_duration = 50
- max_duration = 60
+ min_duration = 70
+ max_duration = 90
/singleton/surgery_step/robotics/repair_burn/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
. = ..()
if(user.skill_check(SKILL_ELECTRICAL, SKILL_BASIC))
+ . += 5
+ if(user.skill_check(SKILL_ELECTRICAL, SKILL_TRAINED))
. += 10
+ if(!user.skill_check(SKILL_DEVICES, SKILL_EXPERIENCED))
+ . -= 10
/singleton/surgery_step/robotics/repair_burn/pre_surgery_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
@@ -296,7 +314,7 @@
to_chat(user, SPAN_WARNING("There is no damage to repair."))
return FALSE
if(BP_IS_BRITTLE(affected))
- to_chat(user, SPAN_WARNING("\The [target]'s [affected.name] is too brittle for this kind of repair."))
+ to_chat(user, SPAN_WARNING("[target]'s [affected.name] is too brittle for this kind of repair."))
else
var/obj/item/stack/cable_coil/C = tool
if(istype(C))
@@ -315,6 +333,7 @@
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("[user] begins to splice new cabling into [target]'s [affected.name]." , \
"You begin to splice new cabling into [target]'s [affected.name].")
+ playsound(target.loc, 'sound/items/Deconstruct.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/repair_burn/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -336,12 +355,13 @@
/singleton/surgery_step/robotics/fix_organ_robotic //For artificial organs
name = "Repair prosthetic organ"
allowed_tools = list(
- /obj/item/stack/nanopaste = 100,
- /obj/item/bonegel = 30,
- /obj/item/screwdriver = 70,
+ /obj/item/stack/nanopaste = 60,
+ /obj/item/bonegel = 20,
+ /obj/item/screwdriver = 30,
+ /obj/item/swapper/power_drill = 50,
)
- min_duration = 70
- max_duration = 90
+ min_duration = 80
+ max_duration = 110
surgery_candidate_flags = SURGERY_NO_STUMP
/singleton/surgery_step/robotics/fix_organ_robotic/get_skill_reqs(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
@@ -350,6 +370,14 @@
else
return SURGERY_SKILLS_ROBOTIC_ON_MEAT
+/singleton/surgery_step/robotics/fix_organ_robotic/success_chance(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
+ . = ..()
+ if(!target.isSynthetic())
+ if(user.skill_check(SKILL_ANATOMY, SKILL_EXPERIENCED))
+ . += 30
+ if(user.skill_check(SKILL_MEDICAL, SKILL_EXPERIENCED))
+ . += 30
+
/singleton/surgery_step/robotics/fix_organ_robotic/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
if(affected)
@@ -367,6 +395,7 @@
if(BP_IS_ROBOTIC(I))
user.visible_message("[user] starts mending the damage to [target]'s [I.name]'s mechanisms.", \
"You start mending the damage to [target]'s [I.name]'s mechanisms." )
+ playsound(target.loc, 'sound/items/bonegel.ogg', 50, TRUE)
..()
/singleton/surgery_step/robotics/fix_organ_robotic/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -380,8 +409,8 @@
/singleton/surgery_step/robotics/fix_organ_robotic/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_WARNING("[user]'s hand slips, gumming up the mechanisms inside of [target]'s [affected.name] with \the [tool]!"), \
- SPAN_WARNING("Your hand slips, gumming up the mechanisms inside of [target]'s [affected.name] with \the [tool]!"))
+ user.visible_message(SPAN_WARNING("[user]'s hand slips, gumming up the mechanisms inside of [target]'s [affected.name] with [tool]!"), \
+ SPAN_WARNING("Your hand slips, gumming up the mechanisms inside of [target]'s [affected.name] with [tool]!"))
target.adjustToxLoss(5)
affected.createwound(INJURY_TYPE_CUT, 5)
for(var/internal in affected.internal_organs)
@@ -395,7 +424,7 @@
/singleton/surgery_step/robotics/detatch_organ_robotic
name = "Decouple prosthetic organ"
allowed_tools = list(
- /obj/item/device/multitool = 100
+ /obj/item/device/multitool = 70
)
min_duration = 90
max_duration = 110
@@ -406,30 +435,40 @@
for(var/organ in target.internal_organs_by_name)
var/obj/item/organ/I = target.internal_organs_by_name[organ]
if(I && !(I.status & ORGAN_CUT_AWAY) && !BP_IS_CRYSTAL(I) && I.parent_organ == target_zone)
- LAZYADD(attached_organs, organ)
+ var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state)
+ radial_button.name = "Detach [I.name]"
+ LAZYSET(attached_organs, I.organ_tag, radial_button)
if(!LAZYLEN(attached_organs))
to_chat(user, SPAN_WARNING("There are no appropriate internal components to decouple."))
return FALSE
- var/organ_to_remove = input(user, "Which organ do you want to prepare for removal?") as null|anything in attached_organs
- if(organ_to_remove)
+ if (length(attached_organs) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ return attached_organs[1]
+ var/organ_to_remove = show_radial_menu(user, tool, attached_organs, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+ if (organ_to_remove && user.use_sanity_check(target, tool))
return organ_to_remove
+ return FALSE
/singleton/surgery_step/robotics/detatch_organ_robotic/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message("[user] starts to decouple [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].", \
- "You start to decouple [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool]." )
+ var/obj/affected = target.get_organ(target_zone)
+ var/obj/removing = target.internal_organs_by_name[LAZYACCESS(target.surgeries_in_progress, target_zone)]
+ user.visible_message("[user] starts to decouple [removing] from [target]'s [affected.name] with [tool].", \
+ "You start to decouple [removing] from [target]'s [affected.name] with [tool]." )
+ playsound(target.loc, 'sound/items/Deconstruct.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/detatch_organ_robotic/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message(SPAN_NOTICE("[user] has decoupled [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].") , \
- SPAN_NOTICE("You have decoupled [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool]."))
+ var/obj/affected = target.get_organ(target_zone)
+ var/obj/removing = target.internal_organs_by_name[LAZYACCESS(target.surgeries_in_progress, target_zone)]
+ user.visible_message(SPAN_NOTICE("[user] has decoupled [removing] from [target]'s [affected.name] with [tool].") , \
+ SPAN_NOTICE("You have decoupled [removing] from [target]'s [affected.name] with [tool]."))
var/obj/item/organ/internal/I = target.internal_organs_by_name[LAZYACCESS(target.surgeries_in_progress, target_zone)]
if(I && istype(I))
I.cut_away(user)
/singleton/surgery_step/robotics/detatch_organ_robotic/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message(SPAN_WARNING("[user]'s hand slips, disconnecting \the [tool]."), \
- SPAN_WARNING("Your hand slips, disconnecting \the [tool]."))
+ user.visible_message(SPAN_WARNING("[user]'s hand slips, disconnecting [tool]."), \
+ SPAN_WARNING("Your hand slips, disconnecting [tool]."))
//////////////////////////////////////////////////////////////////
// robotic organ transplant finalization surgery step
@@ -437,49 +476,67 @@
/singleton/surgery_step/robotics/attach_organ_robotic
name = "Reattach prosthetic organ"
allowed_tools = list(
- /obj/item/screwdriver = 100,
+ /obj/item/screwdriver = 50,
+ /obj/item/swapper/power_drill = 70,
)
min_duration = 100
max_duration = 120
surgery_candidate_flags = SURGERY_NO_CRYSTAL | SURGERY_NO_FLESH | SURGERY_NO_STUMP | SURGERY_NEEDS_ENCASEMENT
+
/singleton/surgery_step/robotics/attach_organ_robotic/pre_surgery_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- var/list/removable_organs = list()
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- for(var/obj/item/organ/I in affected.implants)
- if ((I.status & ORGAN_CUT_AWAY) && BP_IS_ROBOTIC(I) && !BP_IS_CRYSTAL(I) && (I.parent_organ == target_zone))
- removable_organs |= I.organ_tag
- var/organ_to_replace = input(user, "Which organ do you want to reattach?") as null|anything in removable_organs
- if(!organ_to_replace)
+ var/list/obj/item/organ/candidates = list()
+ for (var/obj/item/organ/organ in affected.implants)
+ if (~organ.status & ORGAN_CUT_AWAY)
+ continue
+ if (~organ.status & ORGAN_ROBOTIC)
+ continue
+ if (organ.status & ORGAN_CRYSTAL)
+ continue
+ if (organ.parent_organ != target_zone)
+ continue
+ if (organ.organ_tag in target.internal_organs_by_name)
+ continue
+ var/image/radial_button = image(icon = organ.icon, icon_state = organ.icon_state)
+ radial_button.name = "Reattach [organ.name]"
+ LAZYSET(candidates, organ, radial_button)
+ if (length(candidates) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ return candidates[1]
+ var/obj/item/organ/selected = show_radial_menu(user, tool, candidates, radius = 42, require_near = TRUE, use_labels = TRUE, check_locs = list(tool))
+ if (!selected || !user.use_sanity_check(target, tool))
return FALSE
- var/obj/item/organ/internal/augment/A = organ_to_replace
- if(istype(A))
- if(!(A.augment_flags & AUGMENT_MECHANICAL))
- to_chat(user, SPAN_WARNING("\the [A] cannot function within a robotic limb"))
+ if (istype(selected, /obj/item/organ/internal/augment))
+ var/obj/item/organ/internal/augment/augment = selected
+ if (~augment.augment_flags & AUGMENT_MECHANICAL)
+ to_chat(user, SPAN_WARNING("[augment] cannot function within a robotic limb."))
return FALSE
- return organ_to_replace
+ return selected
+
/singleton/surgery_step/robotics/attach_organ_robotic/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message("[user] begins reattaching [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].", \
- "You start reattaching [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].")
+ var/obj/affected = target.get_organ(target_zone)
+ var/obj/attaching = target.internal_organs_by_name[LAZYACCESS(target.surgeries_in_progress, target_zone)]
+ user.visible_message("[user] begins reattaching [attaching] from [target]'s [affected.name] with [tool].", \
+ "You start reattaching [attaching] from [target]'s [affected.name] with [tool].")
+ playsound(target.loc, 'sound/items/Screwdriver.ogg', 15, 1)
..()
/singleton/surgery_step/robotics/attach_organ_robotic/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message(SPAN_NOTICE("[user] has reattached [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool].") , \
- SPAN_NOTICE("You have reattached [target]'s [LAZYACCESS(target.surgeries_in_progress, target_zone)] with \the [tool]."))
-
- var/current_organ = LAZYACCESS(target.surgeries_in_progress, target_zone)
+ var/obj/item/organ/attaching = target.surgeries_in_progress?[target_zone]
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- for (var/obj/item/organ/I in affected.implants)
- if (I.organ_tag == current_organ)
- I.status &= ~ORGAN_CUT_AWAY
- affected.implants -= I
- I.replaced(target, affected)
- break
+ affected.implants -= attaching
+ attaching.status &= ~ORGAN_CUT_AWAY
+ attaching.replaced(target, affected)
+ user.visible_message(
+ SPAN_NOTICE("[user] has reattached \a [target]'s [attaching] with \a [tool]."),
+ SPAN_NOTICE("You have reattached [target]'s [attaching] with [tool].")
+ )
+
/singleton/surgery_step/robotics/attach_organ_robotic/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
- user.visible_message(SPAN_WARNING("[user]'s hand slips, disconnecting \the [tool]."), \
- SPAN_WARNING("Your hand slips, disconnecting \the [tool]."))
+ user.visible_message(SPAN_WARNING("[user]'s hand slips, disconnecting [tool]."), \
+ SPAN_WARNING("Your hand slips, disconnecting [tool]."))
//////////////////////////////////////////////////////////////////
// mmi installation surgery step
@@ -500,7 +557,7 @@
if(!M.brainmob || !M.brainmob.client || !M.brainmob.ckey || M.brainmob.stat >= DEAD)
to_chat(user, SPAN_WARNING("That brain is not usable."))
else if(BP_IS_CRYSTAL(affected))
- to_chat(user, SPAN_WARNING("The crystalline interior of \the [affected] is incompatible with \the [M]."))
+ to_chat(user, SPAN_WARNING("The crystalline interior of [affected] is incompatible with [M]."))
else if(!target.isSynthetic())
to_chat(user, SPAN_WARNING("You cannot install a computer brain into a meat body."))
else if(!target.should_have_organ(BP_BRAIN))
@@ -518,16 +575,17 @@
/singleton/surgery_step/robotics/install_mmi/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message("[user] starts installing \the [tool] into [target]'s [affected.name].", \
- "You start installing \the [tool] into [target]'s [affected.name].")
+ user.visible_message("[user] starts installing [tool] into [target]'s [affected.name].", \
+ "You start installing [tool] into [target]'s [affected.name].")
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
..()
/singleton/surgery_step/robotics/install_mmi/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!user.unEquip(tool))
return
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- user.visible_message(SPAN_NOTICE("[user] has installed \the [tool] into [target]'s [affected.name]."), \
- SPAN_NOTICE("You have installed \the [tool] into [target]'s [affected.name]."))
+ user.visible_message(SPAN_NOTICE("[user] has installed [tool] into [target]'s [affected.name]."), \
+ SPAN_NOTICE("You have installed [tool] into [target]'s [affected.name]."))
var/obj/item/device/mmi/M = tool
var/obj/item/organ/internal/mmi_holder/holder = new(target, 1)
@@ -577,9 +635,10 @@
/singleton/surgery_step/remove_mmi/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message( \
- "\The [user] starts poking around inside [target]'s [affected.name] with \the [tool].", \
- "You start poking around inside [target]'s [affected.name] with \the [tool]." )
+ "[user] starts poking around inside [target]'s [affected.name] with [tool].", \
+ "You start poking around inside [target]'s [affected.name] with [tool]." )
target.custom_pain("The pain in your [affected.name] is living hell!",1,affecting = affected)
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
..()
/singleton/surgery_step/remove_mmi/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
@@ -588,35 +647,35 @@
var/obj/item/device/mmi/mmi = locate() in affected.implants
if(affected && mmi)
user.visible_message( \
- SPAN_NOTICE("\The [user] removes \the [mmi] from \the [target]'s [affected.name] with \the [tool]."), \
- SPAN_NOTICE("You remove \the [mmi] from \the [target]'s [affected.name] with \the [tool]."))
+ SPAN_NOTICE("[user] removes [mmi] from [target]'s [affected.name] with [tool]."), \
+ SPAN_NOTICE("You remove [mmi] from [target]'s [affected.name] with [tool]."))
target.remove_implant(mmi, TRUE, affected)
else
user.visible_message( \
- SPAN_NOTICE("\The [user] could not find anything inside [target]'s [affected.name]."), \
+ SPAN_NOTICE("[user] could not find anything inside [target]'s [affected.name]."), \
SPAN_NOTICE("You could not find anything inside [target]'s [affected.name]."))
/singleton/surgery_step/remove_mmi/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message( \
- SPAN_WARNING("\The [user]'s hand slips, damaging \the [target]'s [affected.name] with \the [tool]!"), \
- SPAN_WARNING("Your hand slips, damaging \the [target]'s [affected.name] with \the [tool]!"))
+ SPAN_WARNING("[user]'s hand slips, damaging [target]'s [affected.name] with [tool]!"), \
+ SPAN_WARNING("Your hand slips, damaging [target]'s [affected.name] with [tool]!"))
affected.take_external_damage(3, 0, used_weapon = tool)
//////////////////////////////////////////////////////////////////
// BROKEN PROSTHETIC SURGERY //
//////////////////////////////////////////////////////////////////
-/singleton/surgery_step/robone
+/singleton/surgery_step/robotics/robone
surgery_candidate_flags = SURGERY_NO_FLESH | SURGERY_NO_CRYSTAL | SURGERY_NEEDS_ENCASEMENT
var/required_stage = 0
-/singleton/surgery_step/robone/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/assess_bodypart(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = ..()
if(affected && (affected.status & ORGAN_BROKEN) && affected.stage == required_stage)
return affected
-/singleton/surgery_step/robone/get_skill_reqs(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
+/singleton/surgery_step/robotics/robone/get_skill_reqs(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
if(target.isSynthetic())
return SURGERY_SKILLS_ROBOTIC
else
@@ -625,53 +684,55 @@
//////////////////////////////////////////////////////////////////
// welding surgery step
//////////////////////////////////////////////////////////////////
-/singleton/surgery_step/robone/weld
+/singleton/surgery_step/robotics/robone/weld
name = "Begin structural support repair"
allowed_tools = list(
- /obj/item/weldingtool = 100,
- /obj/item/tape_roll = 75,
+ /obj/item/weldingtool = 50,
+ /obj/item/tape_roll = 30,
/obj/item/bonegel = 30
)
min_duration = 50
max_duration = 60
-/singleton/surgery_step/robone/weld/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/weld/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s [affected.encased]" : "structural support in \the [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s [affected.encased]" : "structural support in [target]'s [affected.name]"
if (affected.stage == 0)
user.visible_message(
- SPAN_NOTICE("\The [user] starts mending \the [prosthetic] with \the [tool]."),
- SPAN_NOTICE("You start mending \the [prosthetic] with \the [tool].")
+ SPAN_NOTICE("[user] starts mending [prosthetic] with [tool]."),
+ SPAN_NOTICE("You start mending [prosthetic] with [tool].")
)
+ playsound(target.loc, 'sound/items/Welder.ogg', 15, 1)
..()
-/singleton/surgery_step/robone/weld/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/weld/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s [affected.encased]" : "structural support in \the [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s [affected.encased]" : "structural support in [target]'s [affected.name]"
user.visible_message(
- SPAN_INFO("\The [user] finishes mending \the [prosthetic] with \the [tool.name]"),
- SPAN_INFO("You finish mending \the [prosthetic] with \the [tool.name].")
+ SPAN_INFO("[user] finishes mending [prosthetic] with [tool.name]"),
+ SPAN_INFO("You finish mending [prosthetic] with [tool.name].")
)
if(affected.stage == 0)
affected.stage = 1
affected.status &= ~ORGAN_BRITTLE
-/singleton/surgery_step/robone/weld/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/weld/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message(
- SPAN_WARNING("\The [user]'s hand slips, causing damage with \the [tool] in the open panel on [target]'s [affected.name]!"),
- SPAN_WARNING("Your hand slips, causing damage with \the [tool] in the open panel on [target]'s [affected.name]!")
+ SPAN_WARNING("[user]'s hand slips, causing damage with [tool] in the open panel on [target]'s [affected.name]!"),
+ SPAN_WARNING("Your hand slips, causing damage with [tool] in the open panel on [target]'s [affected.name]!")
)
affected.take_external_damage(5, 0, used_weapon = tool)
//////////////////////////////////////////////////////////////////
// prosthetic realignment surgery step
//////////////////////////////////////////////////////////////////
-/singleton/surgery_step/robone/realign_support
+/singleton/surgery_step/robotics/robone/realign_support
name = "Realign support"
allowed_tools = list(
- /obj/item/wrench = 100,
- /obj/item/bonesetter = 75
+ /obj/item/swapper/power_drill = 100,
+ /obj/item/wrench = 70,
+ /obj/item/bonesetter = 50
)
min_duration = 60
max_duration = 70
@@ -680,48 +741,49 @@
surgery_candidate_flags = SURGERY_NO_FLESH | SURGERY_NEEDS_ENCASEMENT
required_stage = 1
-/singleton/surgery_step/robone/realign_support/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/realign_support/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s [affected.encased]" : "structural support in \the [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s [affected.encased]" : "structural support in [target]'s [affected.name]"
if(affected.encased == "skull")
user.visible_message(
- SPAN_NOTICE("\The [user] begins to piece \the [prosthetic] back together with \the [tool]."),
- SPAN_NOTICE("You begin to piece \the [prosthetic] back together with \the [tool].")
+ SPAN_NOTICE("[user] begins to piece [prosthetic] back together with [tool]."),
+ SPAN_NOTICE("You begin to piece [prosthetic] back together with [tool].")
)
else
user.visible_message(
- SPAN_NOTICE("\The [user] is beginning to twist \the [prosthetic] in place with \the [tool]."),
- SPAN_NOTICE("You are beginning to twist \the [prosthetic] in place with \the [tool].")
+ SPAN_NOTICE("[user] is beginning to twist [prosthetic] in place with [tool]."),
+ SPAN_NOTICE("You are beginning to twist [prosthetic] in place with [tool].")
)
+ playsound(target.loc, 'sound/items/bonesetter.ogg', 50, TRUE)
..()
-/singleton/surgery_step/robone/realign_support/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/realign_support/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s [affected.encased]" : "structural support in \the [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s [affected.encased]" : "structural support in [target]'s [affected.name]"
if (affected.status & ORGAN_BROKEN)
if(affected.encased == "skull")
user.visible_message(
- SPAN_INFO("\The [user] pieces \the [prosthetic] back together with \the [tool]."),
- SPAN_INFO("You piece \the [prosthetic] back together with \the [tool].")
+ SPAN_INFO("[user] pieces [prosthetic] back together with [tool]."),
+ SPAN_INFO("You piece [prosthetic] back together with [tool].")
)
else
user.visible_message(
- SPAN_INFO("\The [user] twists \the [prosthetic] in place with \the [tool]."),
- SPAN_INFO("You twist \the [prosthetic] in place with \the [tool].")
+ SPAN_INFO("[user] twists [prosthetic] in place with [tool]."),
+ SPAN_INFO("You twist [prosthetic] in place with [tool].")
)
affected.stage = 2
else
user.visible_message(
- SPAN_WARNING("\The [user] twists \the [prosthetic] in the WRONG place with \the [tool]!."),
- SPAN_WARNING("You twist \the [prosthetic] in the WRONG place with \the [tool]!.")
+ SPAN_WARNING("[user] twists [prosthetic] in the WRONG place with [tool]!."),
+ SPAN_WARNING("You twist [prosthetic] in the WRONG place with [tool]!.")
)
affected.fracture()
-/singleton/surgery_step/robone/realign_support/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/realign_support/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message(
- SPAN_WARNING("\The [user]'s hand slips, damaging the [affected.encased ? affected.encased : "structural support"] in \the [target]'s [affected.name] with \the [tool]!"),
- SPAN_WARNING("Your hand slips, damaging the [affected.encased ? affected.encased : "structural support"] in \the [target]'s [affected.name] with \the [tool]!")
+ SPAN_WARNING("[user]'s hand slips, damaging the [affected.encased ? affected.encased : "structural support"] in [target]'s [affected.name] with [tool]!"),
+ SPAN_WARNING("Your hand slips, damaging the [affected.encased ? affected.encased : "structural support"] in [target]'s [affected.name] with [tool]!")
)
affected.fracture()
affected.take_external_damage(5, used_weapon = tool)
@@ -729,41 +791,42 @@
//////////////////////////////////////////////////////////////////
// post realignment surgery step
//////////////////////////////////////////////////////////////////
-/singleton/surgery_step/robone/finish
+/singleton/surgery_step/robotics/robone/finish
name = "Finish structural support repair"
allowed_tools = list(
- /obj/item/weldingtool = 100,
- /obj/item/tape_roll = 75,
+ /obj/item/weldingtool = 50,
+ /obj/item/tape_roll = 30,
/obj/item/bonegel = 30
)
min_duration = 50
max_duration = 60
required_stage = 2
-/singleton/surgery_step/robone/finish/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/finish/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s damaged [affected.encased]" : "structural support in \the [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s damaged [affected.encased]" : "structural support in [target]'s [affected.name]"
user.visible_message(
- SPAN_NOTICE("\the [user] starts to finish mending [prosthetic] with \the [tool]."),
- SPAN_NOTICE("You start to finish mending [prosthetic] with \the [tool].")
+ SPAN_NOTICE("[user] starts to finish mending [prosthetic] with [tool]."),
+ SPAN_NOTICE("You start to finish mending [prosthetic] with [tool].")
)
+ playsound(target.loc, 'sound/items/Welder.ogg', 15, 1)
..()
-/singleton/surgery_step/robone/finish/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/finish/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
- var/prosthetic = affected.encased ? "\the [target]'s damaged [affected.encased]" : "structural support in [target]'s [affected.name]"
+ var/prosthetic = affected.encased ? "[target]'s damaged [affected.encased]" : "structural support in [target]'s [affected.name]"
user.visible_message(
- SPAN_INFO("\The [user] has finished mending [prosthetic] with \the [tool]."),
- SPAN_INFO("You have finished mending [prosthetic] with \the [tool]." )
+ SPAN_INFO("[user] has finished mending [prosthetic] with [tool]."),
+ SPAN_INFO("You have finished mending [prosthetic] with [tool]." )
)
affected.status &= ~ORGAN_BROKEN
affected.stage = 0
affected.update_wounds()
-/singleton/surgery_step/robone/finish/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
+/singleton/surgery_step/robotics/robone/finish/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message(
- SPAN_WARNING("\The [user]'s hand slips, causing damage with \the [tool] in the open panel in [target]'s [affected.name]!"),
- SPAN_WARNING("Your hand slips, causing damage with \the [tool] in the open panel in [target]'s [affected.name]!")
+ SPAN_WARNING("[user]'s hand slips, causing damage with [tool] in the open panel in [target]'s [affected.name]!"),
+ SPAN_WARNING("Your hand slips, causing damage with [tool] in the open panel in [target]'s [affected.name]!")
)
affected.take_external_damage(5, 0, used_weapon = tool)
diff --git a/code/modules/surgery/slimes.dm b/code/modules/surgery/slimes.dm
index efbe3e164bee5..bf70a25558db5 100644
--- a/code/modules/surgery/slimes.dm
+++ b/code/modules/surgery/slimes.dm
@@ -18,7 +18,7 @@
return isslime(target) && target.stat == DEAD
/singleton/surgery_step/slime/get_skill_reqs(mob/living/user, mob/living/carbon/human/target, obj/item/tool)
- return list(SKILL_SCIENCE = SKILL_ADEPT)
+ return list(SKILL_SCIENCE = SKILL_TRAINED)
//////////////////////////////////////////////////////////////////
// slime flesh cutting surgery step
@@ -26,7 +26,7 @@
/singleton/surgery_step/slime/cut_flesh
name = "Make incision in slime"
allowed_tools = list(
- /obj/item/scalpel = 100,
+ /obj/item/scalpel/basic = 100,
/obj/item/material/knife = 75,
/obj/item/material/shard = 50
)
@@ -39,6 +39,7 @@
/singleton/surgery_step/slime/cut_flesh/begin_step(mob/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message("[user] starts cutting through [target]'s flesh with \the [tool].", \
"You start cutting through [target]'s flesh with \the [tool].")
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
/singleton/surgery_step/slime/cut_flesh/end_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message(SPAN_NOTICE("[user] cuts through [target]'s flesh with \the [tool]."), \
@@ -55,7 +56,7 @@
/singleton/surgery_step/slime/cut_innards
name = "Dissect innards"
allowed_tools = list(
- /obj/item/scalpel = 100,
+ /obj/item/scalpel/basic = 100,
/obj/item/material/knife = 75,
/obj/item/material/shard = 50
)
@@ -68,6 +69,7 @@
/singleton/surgery_step/slime/cut_innards/begin_step(mob/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message("[user] starts cutting [target]'s silky innards apart with \the [tool].", \
"You start cutting [target]'s silky innards apart with \the [tool].")
+ playsound(target.loc, 'sound/items/scalpel.ogg', 50, TRUE)
/singleton/surgery_step/slime/cut_innards/end_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message(SPAN_NOTICE("[user] cuts [target]'s innards apart with \the [tool], exposing the cores."), \
@@ -78,13 +80,42 @@
user.visible_message(SPAN_WARNING("[user]'s hand slips, tearing [target]'s innards with \the [tool]!"), \
SPAN_WARNING("Your hand slips, tearing [target]'s innards with \the [tool]!"))
+//////////////////////////////////////////////////////////////////
+// slime flesh & innards laser cutting surgery step
+//////////////////////////////////////////////////////////////////
+/singleton/surgery_step/slime/cut_laser
+ name = "Make laser incision in slime"
+ allowed_tools = list(
+ /obj/item/scalpel/laser = 100,
+ /obj/item/scalpel/ims = 100
+ )
+ min_duration = 1 SECOND
+ max_duration = 2 SECONDS
+
+/singleton/surgery_step/slime/cut_laser/can_use(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
+ return ..() && istype(target) && target.core_removal_stage < 2
+
+/singleton/surgery_step/slime/cut_laser/begin_step(mob/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
+ user.visible_message("[user] starts slicing through to the [target]'s silky innards apart with \the [tool].", \
+ "You start slicing through to the [target]'s silky innards apart with \the [tool].")
+ playsound(target.loc, 'sound/items/cautery.ogg', 50, TRUE)
+
+/singleton/surgery_step/slime/cut_laser/end_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
+ user.visible_message(SPAN_NOTICE("[user] slices [target]'s innards apart with \the [tool], exposing the cores."), \
+ SPAN_NOTICE("You slice [target]'s innards apart with \the [tool], exposing the cores."))
+ target.core_removal_stage = 2
+
+/singleton/surgery_step/slime/cut_laser/fail_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
+ user.visible_message(SPAN_WARNING("[user]'s hand slips, tearing [target]'s innards with \the [tool]!"), \
+ SPAN_WARNING("Your hand slips, searing [target]'s innards with \the [tool]!"))
+
//////////////////////////////////////////////////////////////////
// slime core removal surgery step
//////////////////////////////////////////////////////////////////
/singleton/surgery_step/slime/saw_core
name = "Remove slime core"
allowed_tools = list(
- /obj/item/scalpel/manager = 100,
+ /obj/item/scalpel/ims = 100,
/obj/item/circular_saw = 100,
/obj/item/material/knife = 75,
/obj/item/material/hatchet = 75
@@ -98,6 +129,7 @@
/singleton/surgery_step/slime/saw_core/begin_step(mob/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
user.visible_message("[user] starts cutting out one of [target]'s cores with \the [tool].", \
"You start cutting out one of [target]'s cores with \the [tool].")
+ playsound(target.loc, 'sound/items/circularsaw.ogg', 50, TRUE)
/singleton/surgery_step/slime/saw_core/end_step(mob/living/user, mob/living/carbon/slime/target, target_zone, obj/item/tool)
target.cores--
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index a84e03f7e071a..163f73947e502 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -68,16 +68,16 @@ GLOBAL_LIST_INIT(surgery_tool_exception_cache, new)
if(affected)
// Check various conditional flags.
if(((surgery_candidate_flags & SURGERY_NO_ROBOTIC) && BP_IS_ROBOTIC(affected)) || \
- ((surgery_candidate_flags & SURGERY_NO_CRYSTAL) && BP_IS_CRYSTAL(affected)) || \
- ((surgery_candidate_flags & SURGERY_NO_STUMP) && affected.is_stump()) || \
- ((surgery_candidate_flags & SURGERY_NO_FLESH) && !(BP_IS_ROBOTIC(affected) || BP_IS_CRYSTAL(affected))))
+ ((surgery_candidate_flags & SURGERY_NO_CRYSTAL) && BP_IS_CRYSTAL(affected)) || \
+ ((surgery_candidate_flags & SURGERY_NO_STUMP) && affected.is_stump()) || \
+ ((surgery_candidate_flags & SURGERY_NO_FLESH) && !(BP_IS_ROBOTIC(affected) || BP_IS_CRYSTAL(affected))))
return FALSE
// Check if the surgery target is accessible.
if(BP_IS_ROBOTIC(affected))
if(((surgery_candidate_flags & SURGERY_NEEDS_ENCASEMENT) || \
- (surgery_candidate_flags & SURGERY_NEEDS_INCISION) || \
- (surgery_candidate_flags & SURGERY_NEEDS_RETRACTED)) && \
- affected.hatch_state != HATCH_OPENED)
+ (surgery_candidate_flags & SURGERY_NEEDS_INCISION) || \
+ (surgery_candidate_flags & SURGERY_NEEDS_RETRACTED)) && \
+ affected.hatch_state != HATCH_OPENED)
return FALSE
else
var/open_threshold = 0
@@ -88,7 +88,7 @@ GLOBAL_LIST_INIT(surgery_tool_exception_cache, new)
else if(surgery_candidate_flags & SURGERY_NEEDS_ENCASEMENT)
open_threshold = (affected.encased ? SURGERY_ENCASED : SURGERY_RETRACTED)
if(open_threshold && ((strict_access_requirement && affected.how_open() != open_threshold) || \
- affected.how_open() < open_threshold))
+ affected.how_open() < open_threshold))
return FALSE
// Check if clothing is blocking access
var/obj/item/I = target.get_covering_equipped_item_by_zone(target_zone)
@@ -135,7 +135,7 @@ GLOBAL_LIST_INIT(surgery_tool_exception_cache, new)
for(var/skill in skill_reqs)
var/penalty = delicate ? 40 : 20
. -= max(0, penalty * (skill_reqs[skill] - user.get_skill_value(skill)))
- if(user.skill_check(skill, SKILL_PROF))
+ if(user.skill_check(skill, SKILL_MASTER))
. += 20
if(ishuman(user))
@@ -156,9 +156,11 @@ GLOBAL_LIST_INIT(surgery_tool_exception_cache, new)
. -= 0
else if(locate(/obj/structure/bed, T))
. -= 5
+ else if(locate(/obj/structure/roller_bed, T))
+ . -= 5
else if(locate(/obj/structure/table, T))
. -= 10
- else if(locate(/obj/effect/rune, T))
+ else if(locate(/obj/rune, T))
. -= 10
. = max(., 0)
@@ -189,16 +191,20 @@ GLOBAL_LIST_INIT(surgery_tool_exception_cache, new)
for(var/singleton in all_surgeries)
var/singleton/surgery_step/S = all_surgeries[singleton]
if(S.name && S.tool_quality(src) && S.can_use(user, M, zone, src))
- LAZYSET(possible_surgeries, S, TRUE)
+ var/image/radial_button = image(icon = icon, icon_state = icon_state)
+ radial_button.name = S.name
+ LAZYSET(possible_surgeries, S, radial_button)
// Which surgery, if any, do we actually want to do?
var/singleton/surgery_step/S
- if(LAZYLEN(possible_surgeries) == 1)
- S = possible_surgeries[1]
- else if(LAZYLEN(possible_surgeries) >= 1)
- if(user.client) // In case of future autodocs.
- S = input(user, "Which surgery would you like to perform?", "Surgery") as null|anything in possible_surgeries
- if(S && !user.skill_check_multiple(S.get_skill_reqs(user, M, src, zone)))
+ if (user.client && length(possible_surgeries))
+ if (length(possible_surgeries) == 1 && user.get_preference_value(/datum/client_preference/surgery_skip_radial))
+ S = possible_surgeries[1]
+ else
+ S = show_radial_menu(user, M, possible_surgeries, radius = 42, use_labels = TRUE, require_near = TRUE, check_locs = list(src))
+ if (!user.use_sanity_check(M))
+ S = null
+ if (S && !user.skill_check_multiple(S.get_skill_reqs(user, M, src, zone)))
S = pick(possible_surgeries)
var/obj/item/gripper/gripper = user.get_active_hand()
diff --git a/code/modules/synthesized_instruments/event_manager.dm b/code/modules/synthesized_instruments/event_manager.dm
index c8d7d64022de9..ce8f38c714f9d 100644
--- a/code/modules/synthesized_instruments/event_manager.dm
+++ b/code/modules/synthesized_instruments/event_manager.dm
@@ -72,7 +72,7 @@
if (active) return 0
src.active = 1
- addtimer(new Callback(src, .proc/handle_events), 0)
+ addtimer(CALLBACK(src, PROC_REF(handle_events)), 0)
diff --git a/code/modules/synthesized_instruments/instrument_data/brass.dm b/code/modules/synthesized_instruments/instrument_data/brass.dm
index 968d7a2c5e539..ffae42e8f924b 100644
--- a/code/modules/synthesized_instruments/instrument_data/brass.dm
+++ b/code/modules/synthesized_instruments/instrument_data/brass.dm
@@ -15,14 +15,14 @@
/datum/instrument/brass/crisis_trombone
name = "Crisis Trombone"
id = "crtrombone"
- samples = list("36"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/c2.ogg',
- "48"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/c3.ogg',
- "60"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/c5.ogg')
+ samples = list("36"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/C2.ogg',
+ "48"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/C3.ogg',
+ "60"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/brass/crisis_trombone/C5.ogg')
/datum/instrument/brass/crisis_trumpet
name = "Crisis Trumpet"
id = "crtrumpet"
- samples = list("60"='code/modules/synthesized_instruments/samples/brass/crisis_trumpet/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/brass/crisis_trumpet/c5.ogg')
\ No newline at end of file
+ samples = list("60"='code/modules/synthesized_instruments/samples/brass/crisis_trumpet/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/brass/crisis_trumpet/C5.ogg')
diff --git a/code/modules/synthesized_instruments/instrument_data/chromatic_percussion.dm b/code/modules/synthesized_instruments/instrument_data/chromatic_percussion.dm
index babf33f8bfae2..3a64a47212def 100644
--- a/code/modules/synthesized_instruments/instrument_data/chromatic_percussion.dm
+++ b/code/modules/synthesized_instruments/instrument_data/chromatic_percussion.dm
@@ -24,10 +24,10 @@
/datum/instrument/chromatic/fluid_celeste
name = "FluidR3 Celeste"
id = "r3celeste"
- samples = list("36"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c2.ogg',
- "48"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c3.ogg',
- "60"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c5.ogg',
- "84"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c6.ogg',
- "96"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c7.ogg',
- "108"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/c8.ogg')
\ No newline at end of file
+ samples = list("36"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C2.ogg',
+ "48"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C3.ogg',
+ "60"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C5.ogg',
+ "84"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C6.ogg',
+ "96"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C7.ogg',
+ "108"='code/modules/synthesized_instruments/samples/chromatic/fluid_celeste/C8.ogg')
diff --git a/code/modules/synthesized_instruments/instrument_data/guitar.dm b/code/modules/synthesized_instruments/instrument_data/guitar.dm
index 59e625f14e975..86f5856c4e074 100644
--- a/code/modules/synthesized_instruments/instrument_data/guitar.dm
+++ b/code/modules/synthesized_instruments/instrument_data/guitar.dm
@@ -24,16 +24,16 @@
/datum/instrument/guitar/clean_crisis
name = "Crisis Clean Guitar"
id = "ccleangt"
- samples = list("36"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/c2.ogg',
- "48"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/c3.ogg',
- "60"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/c5.ogg')
+ samples = list("36"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/C2.ogg',
+ "48"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/C3.ogg',
+ "60"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/guitar/crisis_clean/C5.ogg')
/datum/instrument/guitar/muted_crisis
name = "Crisis Muted Guitar"
id = "cmutedgt"
- samples = list("36"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/c2.ogg',
- "48"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/c3.ogg',
- "60"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/c5.ogg')
\ No newline at end of file
+ samples = list("36"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/C2.ogg',
+ "48"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/C3.ogg',
+ "60"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/guitar/crisis_muted/C5.ogg')
diff --git a/code/modules/synthesized_instruments/instrument_data/piano.dm b/code/modules/synthesized_instruments/instrument_data/piano.dm
index ea6083e6dc223..f38e24b6838de 100644
--- a/code/modules/synthesized_instruments/instrument_data/piano.dm
+++ b/code/modules/synthesized_instruments/instrument_data/piano.dm
@@ -18,13 +18,13 @@
/datum/instrument/piano/fluid_harpsichord
name = "FluidR3 Harpsichord"
id = "r3harpsi"
- samples = list("36"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c2.ogg',
- "48"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c3.ogg',
- "60"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c4.ogg',
- "72"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c5.ogg',
- "84"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c6.ogg',
- "96"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c7.ogg',
- "108"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/c8.ogg')
+ samples = list("36"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C2.ogg',
+ "48"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C3.ogg',
+ "60"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C4.ogg',
+ "72"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C5.ogg',
+ "84"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C6.ogg',
+ "96"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C7.ogg',
+ "108"='code/modules/synthesized_instruments/samples/piano/fluid_harpsi/C8.ogg')
/datum/instrument/piano/crisis_harpsichord
@@ -57,4 +57,4 @@
"72"='code/modules/synthesized_instruments/samples/piano/crisis_bright_piano/c5.ogg',
"84"='code/modules/synthesized_instruments/samples/piano/crisis_bright_piano/c6.ogg',
"96"='code/modules/synthesized_instruments/samples/piano/crisis_bright_piano/c7.ogg',
- "108"='code/modules/synthesized_instruments/samples/piano/crisis_bright_piano/c8.ogg')
\ No newline at end of file
+ "108"='code/modules/synthesized_instruments/samples/piano/crisis_bright_piano/c8.ogg')
diff --git a/code/modules/synthesized_instruments/real_instruments.dm b/code/modules/synthesized_instruments/real_instruments.dm
index 73346e24d3277..5840835711689 100644
--- a/code/modules/synthesized_instruments/real_instruments.dm
+++ b/code/modules/synthesized_instruments/real_instruments.dm
@@ -214,6 +214,7 @@
var/datum/instrument/instruments = list()
var/path = /datum/instrument
var/sound_player = /datum/sound_player
+ obj_flags = OBJ_FLAG_ANCHORABLE
/obj/structure/synthesized_instrument/Initialize()
. = ..()
diff --git a/code/modules/synthesized_instruments/real_instruments/Synthesizer/synthesizer.dm b/code/modules/synthesized_instruments/real_instruments/Synthesizer/synthesizer.dm
index e4ee9aef04a16..eb3195a4710c7 100644
--- a/code/modules/synthesized_instruments/real_instruments/Synthesizer/synthesizer.dm
+++ b/code/modules/synthesized_instruments/real_instruments/Synthesizer/synthesizer.dm
@@ -2,6 +2,8 @@
/datum/sound_player/synthesizer
volume = 40
+ range = 10
+ falloff = 1
/obj/structure/synthesized_instrument/synthesizer
name = "The Synthesizer 3.0"
@@ -12,31 +14,6 @@
path = /datum/instrument
sound_player = /datum/sound_player/synthesizer
-/obj/structure/synthesized_instrument/synthesizer/attackby(obj/item/O, mob/user, params)
- if (istype(O, /obj/item/wrench))
- if (!anchored && !isinspace())
- playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
- to_chat(usr, SPAN_NOTICE(" You begin to tighten \the [src] to the floor..."))
- if (do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(!anchored && !isinspace())
- user.visible_message( \
- "[user] tightens \the [src]'s casters.", \
- SPAN_NOTICE(" You tighten \the [src]'s casters. Now it can be played again."), \
- SPAN_CLASS("italics", "You hear ratchet."))
- src.anchored = TRUE
- else if(anchored)
- playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
- to_chat(usr, SPAN_NOTICE(" You begin to loosen \the [src]'s casters..."))
- if (do_after(user, 4 SECONDS, src, DO_REPAIR_CONSTRUCT))
- if(anchored)
- user.visible_message( \
- "[user] loosens \the [src]'s casters.", \
- SPAN_NOTICE(" You loosen \the [src]. Now it can be pulled somewhere else."), \
- SPAN_CLASS("italics", "You hear ratchet."))
- src.anchored = FALSE
- else
- ..()
-
/obj/structure/synthesized_instrument/synthesizer/shouldStopPlaying(mob/user)
return !((src && in_range(src, user) && src.anchored) || src.real_instrument.player.song.autorepeat)
@@ -55,4 +32,4 @@
name = "space minimoog"
desc = "This is a minimoog, like a space piano, but more spacey!"
icon_state = "minimoog"
- obj_flags = OBJ_FLAG_ROTATABLE
+ obj_flags = OBJ_FLAG_ROTATABLE | OBJ_FLAG_ANCHORABLE
diff --git a/code/modules/synthesized_instruments/song.dm b/code/modules/synthesized_instruments/song.dm
index b3b122ffa828a..5ada4f39f7a96 100644
--- a/code/modules/synthesized_instruments/song.dm
+++ b/code/modules/synthesized_instruments/song.dm
@@ -188,7 +188,7 @@
var/list/allowed_suff = list("b", "n", "#", "s")
var/list/note_off_delta = list("a"=91, "b"=91, "c"=98, "d"=98, "e"=98, "f"=98, "g"=98)
var/list/lines_copy = src.lines.Copy()
- addtimer(new Callback(src, .proc/play_lines, user, allowed_suff, note_off_delta, lines_copy), 0)
+ addtimer(CALLBACK(src, PROC_REF(play_lines), user, allowed_suff, note_off_delta, lines_copy), 0)
#undef CP
#undef IS_DIGIT
diff --git a/code/modules/synthesized_instruments/song_editor.dm b/code/modules/synthesized_instruments/song_editor.dm
index 59c48b2ec6042..067ef303c0a3c 100644
--- a/code/modules/synthesized_instruments/song_editor.dm
+++ b/code/modules/synthesized_instruments/song_editor.dm
@@ -13,11 +13,11 @@
/datum/nano_module/song_editor/proc/pages()
- return Ceil(length(src.song.lines) / GLOB.musical_config.song_editor_lines_per_page)
+ return ceil(length(src.song.lines) / GLOB.musical_config.song_editor_lines_per_page)
/datum/nano_module/song_editor/proc/current_page()
- return src.song.current_line > 0 ? Ceil(src.song.current_line / GLOB.musical_config.song_editor_lines_per_page) : min(src.page, pages())
+ return src.song.current_line > 0 ? ceil(src.song.current_line / GLOB.musical_config.song_editor_lines_per_page) : min(src.page, pages())
/datum/nano_module/song_editor/proc/page_bounds(page_num)
diff --git a/code/modules/synthesized_instruments/sound_player.dm b/code/modules/synthesized_instruments/sound_player.dm
index 9027e3da30435..8a5573a4ff62a 100644
--- a/code/modules/synthesized_instruments/sound_player.dm
+++ b/code/modules/synthesized_instruments/sound_player.dm
@@ -28,9 +28,9 @@
src.actual_instrument = where
src.echo = GLOB.musical_config.echo_default.Copy()
src.env = GLOB.musical_config.env_default.Copy()
- src.proxy_listener = new(src.actual_instrument, /datum/sound_player/proc/on_turf_entered_relay, /datum/sound_player/proc/on_turfs_changed_relay, range, proc_owner = src)
+ src.proxy_listener = new(src.actual_instrument, TYPE_PROC_REF(/datum/sound_player, on_turf_entered_relay), TYPE_PROC_REF(/datum/sound_player, on_turfs_changed_relay), range, proc_owner = src)
proxy_listener.register_turfs()
- GLOB.instrument_synchronizer.register_global(src, .proc/check_wait)
+ GLOB.instrument_synchronizer.register_global(src, PROC_REF(check_wait))
/datum/sound_player/Destroy()
src.song.playing = 0
@@ -41,7 +41,7 @@
QDEL_NULL(proxy_listener)
seen_turfs.Cut()
tokens.Cut()
- GLOB.instrument_synchronizer.unregister_global(src, .proc/check_wait)
+ GLOB.instrument_synchronizer.unregister_global(src, PROC_REF(check_wait))
wait = null
. = ..()
diff --git a/code/modules/synthesized_instruments/sound_token.dm b/code/modules/synthesized_instruments/sound_token.dm
index 61a19b0611c59..64e5a3346fa99 100644
--- a/code/modules/synthesized_instruments/sound_token.dm
+++ b/code/modules/synthesized_instruments/sound_token.dm
@@ -32,7 +32,7 @@
listeners = list()
listener_status = list()
- GLOB.destroyed_event.register(source, src, /datum/proc/qdel_self)
+ GLOB.destroyed_event.register(source, src, TYPE_PROC_REF(/datum, qdel_self))
player.subscribe(src)
diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm
index 2e047901a84fd..1a68798f7bb5c 100644
--- a/code/modules/tables/interactions.dm
+++ b/code/modules/tables/interactions.dm
@@ -44,7 +44,7 @@
/obj/structure/table/bullet_act(obj/item/projectile/P)
. = ..()
- if (health_dead)
+ if (health_dead())
return PROJECTILE_CONTINUE
/obj/structure/table/CheckExit(atom/movable/O as mob|obj, target as turf)
@@ -60,9 +60,9 @@
/obj/structure/table/MouseDrop_T(mob/target, mob/user)
if (isrobot(user))
- return
+ return ..()
if (!ismob(target))
- return
+ return ..()
if (target.loc != loc)
step(target, get_dir(target, loc))
..()
@@ -79,23 +79,20 @@ closest to where the cursor has clicked on.
Note: This proc can be overwritten to allow for different types of auto-alignment.
*/
/obj/item/var/center_of_mass = "x=16;y=16" //can be null for no exact placement behaviour
-/obj/structure/table/proc/auto_align(obj/item/W, click_params)
+/obj/structure/table/proc/auto_align(obj/item/W, params)
if (!W.center_of_mass) // Clothing, material stacks, generally items with large sprites where exact placement would be unhandy.
W.pixel_x = rand(-W.randpixel, W.randpixel)
W.pixel_y = rand(-W.randpixel, W.randpixel)
W.pixel_z = 0
return
- if (!click_params)
- return
-
- var/list/click_data = params2list(click_params)
- if (!click_data["icon-x"] || !click_data["icon-y"])
+ var/list/modifiers = params2list(params)
+ if (!length(modifiers) || !LAZYACCESS(modifiers, ICON_X) || !LAZYACCESS(modifiers, ICON_Y))
return
// Calculation to apply new pixelshift.
- var/mouse_x = text2num(click_data["icon-x"])-1 // Ranging from 0 to 31
- var/mouse_y = text2num(click_data["icon-y"])-1
+ var/mouse_x = text2num(LAZYACCESS(modifiers, ICON_X))-1 // Ranging from 0 to 31
+ var/mouse_y = text2num(LAZYACCESS(modifiers, ICON_Y))-1
var/cell_x = clamp(round(mouse_x/CELLSIZE), 0, CELLS-1) // Ranging from 0 to CELLS-1
var/cell_y = clamp(round(mouse_y/CELLSIZE), 0, CELLS-1)
@@ -106,7 +103,7 @@ Note: This proc can be overwritten to allow for different types of auto-alignmen
W.pixel_y = (CELLSIZE * (cell_y + 0.5)) - center["y"]
W.pixel_z = 0
-/obj/structure/table/rack/auto_align(obj/item/W, click_params)
+/obj/structure/table/rack/auto_align(obj/item/W, params)
if(W && !W.center_of_mass)
..(W)
diff --git a/code/modules/tables/presets.dm b/code/modules/tables/presets.dm
index 5408caa4ea89a..b61209acff8ae 100644
--- a/code/modules/tables/presets.dm
+++ b/code/modules/tables/presets.dm
@@ -36,9 +36,9 @@
alpha = 77 // 0.3 * 255
material = MATERIAL_GLASS
-/obj/structure/table/glass/pglass
- color = "#8f29a3"
- material = MATERIAL_PHORON_GLASS
+/obj/structure/table/glass/boron
+ color = GLASS_COLOR_BORON
+ material = MATERIAL_BORON_GLASS
/obj/structure/table/holotable
icon_state = "holo_preview"
diff --git a/code/modules/tables/rack.dm b/code/modules/tables/rack.dm
index 3b770812e3223..0f01ebd98e0fe 100644
--- a/code/modules/tables/rack.dm
+++ b/code/modules/tables/rack.dm
@@ -1,11 +1,12 @@
/obj/structure/table/rack
name = "rack"
desc = "Different from the Middle Ages version."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/furniture.dmi'
icon_state = "rack"
can_plate = 0
can_reinforce = 0
flipped = -1
+ obj_flags = EMPTY_BITFIELD // No tabling objects
/obj/structure/table/rack/New()
..()
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index c040a27668c80..b0a0090bacc75 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -1,6 +1,6 @@
/obj/structure/table
name = "table frame"
- icon = 'icons/obj/tables.dmi'
+ icon = 'icons/obj/structures/tables.dmi'
icon_state = "frame"
desc = "It's a table, for putting things on. Or standing on, if you really want to."
density = TRUE
@@ -10,6 +10,7 @@
throwpass = 1
mob_offset = 12
health_max = 10
+ obj_flags = OBJ_FLAG_RECEIVE_TABLE
var/flipped = 0
// For racks.
@@ -60,7 +61,7 @@
. = ..()
/obj/structure/table/on_death()
- visible_message(SPAN_WARNING("\The [src] breaks down!"))
+ visible_message(SPAN_WARNING("[src] breaks down!"))
break_to_parts()
/obj/structure/table/Initialize()
@@ -90,128 +91,168 @@
T.update_icon()
. = ..()
-/obj/structure/table/attackby(obj/item/W, mob/user, click_params)
- if(!reinforced && !carpeted && material && isWrench(W) && (user.a_intent != I_HELP || issilicon(user))) //robots dont have disarm so it's harm
- remove_material(W, user)
- if(!material)
- update_connections(1)
- update_icon()
- for(var/obj/structure/table/T in oview(src, 1))
- T.update_icon()
- update_desc()
- update_material()
- return 1
-
- if(!carpeted && !reinforced && !material && isWrench(W) && (user.a_intent != I_HELP || issilicon(user)))
- dismantle(W, user)
- return 1
-
- if (user.a_intent == I_HURT)
- ..()
+/obj/structure/table/crowbar_act_secondary(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!carpeted)
+ USE_FEEDBACK_FAILURE("[src] has no carpeting to remove.")
return
-
- if(reinforced && isScrewdriver(W))
- remove_reinforced(W, user)
- if(!reinforced)
- update_desc()
- update_icon()
- update_material()
- return 1
-
- if(carpeted && isCrowbar(W))
- user.visible_message(SPAN_NOTICE("\The [user] removes the carpet from \the [src]."),
- SPAN_NOTICE("You remove the carpet from \the [src]."))
- new /obj/item/stack/tile/carpet(loc)
- carpeted = 0
- update_icon()
- return 1
-
- if(!carpeted && material && istype(W, /obj/item/stack/tile/carpet))
- var/obj/item/stack/tile/carpet/C = W
- if(C.use(1))
- user.visible_message(SPAN_NOTICE("\The [user] adds \the [C] to \the [src]."),
- SPAN_NOTICE("You add \the [C] to \the [src]."))
- carpeted = 1
- update_icon()
- return 1
- else
- to_chat(user, SPAN_WARNING("You don't have enough carpet!"))
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
-
- if(health_damaged() && isWelder(W))
- var/obj/item/weldingtool/F = W
- if(F.welding)
- to_chat(user, SPAN_NOTICE("You begin reparing damage to \the [src]."))
- playsound(src.loc, 'sound/items/Welder.ogg', 50, 1)
- if(!do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT) || !F.remove_fuel(1, user))
- return
- user.visible_message(SPAN_NOTICE("\The [user] repairs some damage to \the [src]."),
- SPAN_NOTICE("You repair some damage to \the [src]."))
- restore_health(get_max_health() / 5) // 20% repair per application
- return 1
+ new /obj/item/stack/tile/carpet(loc)
+ carpeted = FALSE
+ update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] removes the carpeting from [src] with [tool]."),
+ SPAN_NOTICE("You remove the carpeting from [src] with [tool].")
+ )
+
+/obj/structure/table/screwdriver_act_secondary(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!reinforced)
+ balloon_alert(user, "нет укреплений для снятия!")
return
-
- if(!material && can_plate && istype(W, /obj/item/stack/material))
- material = common_material_add(W, user, "plat")
- if(material)
- update_connections(1)
- update_icon()
- update_desc()
- update_material()
- return 1
-
- // Handle dismantling or placing things on the table from here on.
- if(isrobot(user))
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
+ remove_reinforced(tool, user)
+ if(!reinforced)
+ update_desc()
+ update_icon()
+ update_material()
- if(W.loc != user) // This should stop mounted modules ending up outside the module.
+/obj/structure/table/wrench_act_secondary(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!material)
+ dismantle(tool, user)
+ return
+ if(reinforced)
+ USE_FEEDBACK_FAILURE("[src]'s reinforcements need to be removed before you can remove the plating.")
+ return
+ if(carpeted)
+ USE_FEEDBACK_FAILURE("[src]'s carpeting needs to be removed before you can remove the plating.")
+ return
+ if(!tool.use_as_tool(src, user, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
return
+ remove_material(tool, user)
+ if(!material)
+ update_connections(TRUE)
+ update_icon()
+ for (var/obj/structure/table/table in oview(src, 1))
+ table.update_icon()
+ update_desc()
+ update_material()
- if(istype(W, /obj/item/melee/energy/blade) || istype(W,/obj/item/psychic_power/psiblade/master/grand/paramount))
- var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread()
- spark_system.set_up(5, 0, src.loc)
+/obj/structure/table/wrench_act(mob/living/user, obj/item/tool)
+ if(can_plate && !material)
+ . = ITEM_INTERACT_SUCCESS
+ dismantle(tool, user)
+
+/obj/structure/table/welder_act_secondary(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!health_damaged())
+ USE_FEEDBACK_NOTHING_TO_REPAIR(user)
+ return
+ if(!tool.tool_start_check(user, 1))
+ return
+ USE_FEEDBACK_REPAIR_START(user)
+ if(!tool.use_as_tool(src, user, 2 SECONDS, 1, 50, SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ restore_health(get_max_health() / 5) // 20% repair per application
+ USE_FEEDBACK_REPAIR_FINISH(user)
+
+/obj/structure/table/use_weapon(obj/item/weapon, mob/user, list/click_params)
+ // Carpet - Add carpeting
+ if (istype(weapon, /obj/item/stack/tile/carpet))
+ if (carpeted)
+ USE_FEEDBACK_FAILURE("[src] is already carpeted.")
+ return TRUE
+ if (!material)
+ USE_FEEDBACK_FAILURE("[src] needs plating before you can carpet it.")
+ return TRUE
+ var/obj/item/stack/tile/carpet/carpet = weapon
+ if (!carpet.use(1))
+ USE_FEEDBACK_STACK_NOT_ENOUGH(carpet, 1, "to pad [src].")
+ return TRUE
+ carpeted = TRUE
+ update_icon()
+ user.visible_message(
+ SPAN_NOTICE("[user] pads [src] with [weapon]."),
+ SPAN_NOTICE("You pad [src] with [weapon].")
+ )
+ return TRUE
+
+ // Energy Blade, Psiblade
+ if (istype(weapon, /obj/item/melee/energy/blade) || istype(weapon, /obj/item/psychic_power/psiblade/master/grand/paramount))
+ var/datum/effect/spark_spread/spark_system = new(src)
+ spark_system.set_up(5, EMPTY_BITFIELD, loc)
spark_system.start()
- playsound(src.loc, 'sound/weapons/blade1.ogg', 50, 1)
- playsound(src.loc, "sparks", 50, 1)
- user.visible_message(SPAN_DANGER("\The [src] was sliced apart by [user]!"))
+ playsound(loc, 'sound/weapons/blade1.ogg', 50, TRUE)
+ playsound(loc, "sparks", 50, TRUE)
+ user.visible_message(
+ SPAN_WARNING("[user] slices [src] apart with [weapon]."),
+ SPAN_WARNING("You slice [src] apart with [weapon].")
+ )
break_to_parts()
- return
+ return TRUE
- if (istype(W, /obj/item/natural_weapon))
- return ..()
+ // Material - Plate table
+ if (istype(weapon, /obj/item/stack/material))
+ if (!material)
+ return FALSE // Handled by `use_tool()`
+ reinforce_table(weapon, user)
+ return TRUE
- if(can_plate && !material)
- to_chat(user, SPAN_WARNING("There's nothing to put \the [W] on! Try adding plating to \the [src] first."))
- return
+ return ..()
- // Placing stuff on tables
- if(user.unEquip(W, src.loc))
- auto_align(W, click_params)
- return 1
+
+/obj/structure/table/use_tool(obj/item/tool, mob/user, list/click_params)
+ SHOULD_CALL_PARENT(FALSE)
+ // Unfinished table - Construction stuff
+ if (can_plate && !material)
+ // Material - Plate table
+ if (istype(tool, /obj/item/stack/material))
+ material = common_material_add(tool, user, "plat")
+ if (material)
+ update_connections(TRUE)
+ update_icon()
+ update_desc()
+ update_material()
+ return TRUE
+ // Anything else - Can't put it on an unfinished table
+ USE_FEEDBACK_FAILURE("[src] needs to be plated before you can put [tool] on it.")
+ return TRUE
+ // Put things on table
+ if (!user.unEquip(tool, loc))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ auto_align(tool, click_params)
+ return TRUE
+
+/obj/structure/table/MouseDrop_T(atom/dropped, mob/user)
+ // Place held objects on table
+ if (isitem(dropped) && user.IsHolding(dropped))
+ if (!user.use_sanity_check(src, dropped, SANITY_CHECK_DEFAULT | SANITY_CHECK_TOOL_UNEQUIP))
+ return TRUE
+ user.unEquip(dropped, get_turf(src))
+ return TRUE
return ..()
-/obj/structure/table/MouseDrop_T(obj/item/stack/material/what)
- if(can_reinforce && isliving(usr) && (!usr.stat) && istype(what) && usr.get_active_hand() == what && Adjacent(usr))
- reinforce_table(what, usr)
- else
- return ..()
/obj/structure/table/proc/reinforce_table(obj/item/stack/material/S, mob/user)
if(reinforced)
- to_chat(user, SPAN_WARNING("\The [src] is already reinforced!"))
+ to_chat(user, SPAN_WARNING("[src] is already reinforced!"))
return
if(!can_reinforce)
- to_chat(user, SPAN_WARNING("\The [src] cannot be reinforced!"))
+ to_chat(user, SPAN_WARNING("[src] cannot be reinforced!"))
return
if(!material)
- to_chat(user, SPAN_WARNING("Plate \the [src] before reinforcing it!"))
+ to_chat(user, SPAN_WARNING("Plate [src] before reinforcing it!"))
return
if(flipped)
- to_chat(user, SPAN_WARNING("Put \the [src] back in place before reinforcing it!"))
+ to_chat(user, SPAN_WARNING("Put [src] back in place before reinforcing it!"))
return
reinforced = common_material_add(S, user, "reinforc")
@@ -236,16 +277,16 @@
/obj/structure/table/proc/common_material_add(obj/item/stack/material/S, mob/user, verb) // Verb is actually verb without 'e' or 'ing', which is added. Works for 'plate'/'plating' and 'reinforce'/'reinforcing'.
var/material/M = S.get_material()
if(!istype(M))
- to_chat(user, SPAN_WARNING("You cannot [verb]e \the [src] with \the [S]."))
+ to_chat(user, SPAN_WARNING("You cannot [verb]e [src] with [S]."))
return null
if(manipulating) return M
manipulating = 1
- to_chat(user, SPAN_NOTICE("You begin [verb]ing \the [src] with [M.display_name]."))
+ to_chat(user, SPAN_NOTICE("You begin [verb]ing [src] with [M.display_name]."))
if(!do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT) || !S.use(1))
manipulating = 0
return null
- user.visible_message(SPAN_NOTICE("\The [user] [verb]es \the [src] with [M.display_name]."), SPAN_NOTICE("You finish [verb]ing \the [src]."))
+ user.visible_message(SPAN_NOTICE("[user] [verb]es [src] with [M.display_name]."), SPAN_NOTICE("You finish [verb]ing [src]."))
manipulating = 0
return M
@@ -257,37 +298,36 @@
if(manipulating) return M
manipulating = 1
- user.visible_message(SPAN_NOTICE("\The [user] begins removing the [type_holding] holding \the [src]'s [M.display_name] [what] in place."),
- SPAN_NOTICE("You begin removing the [type_holding] holding \the [src]'s [M.display_name] [what] in place."))
+ user.visible_message(SPAN_NOTICE("[user] begins removing the [type_holding] holding [src]'s [M.display_name] [what] in place."),
+ SPAN_NOTICE("You begin removing the [type_holding] holding [src]'s [M.display_name] [what] in place."))
if(sound)
playsound(src.loc, sound, 50, 1)
- if(!do_after(user, 4 SECONDS, src, DO_REPAIR_CONSTRUCT))
+ if(!do_after(user, delay, src, DO_REPAIR_CONSTRUCT))
manipulating = 0
return M
- user.visible_message(SPAN_NOTICE("\The [user] removes the [M.display_name] [what] from \the [src]."),
- SPAN_NOTICE("You remove the [M.display_name] [what] from \the [src]."))
+ user.visible_message(SPAN_NOTICE("[user] removes the [M.display_name] [what] from [src]."),
+ SPAN_NOTICE("You remove the [M.display_name] [what] from [src]."))
M.place_sheet(src.loc)
manipulating = 0
return null
-/obj/structure/table/proc/remove_reinforced(obj/item/screwdriver/S, mob/user)
- reinforced = common_material_remove(user, reinforced, 40, "reinforcements", "screws", 'sound/items/Screwdriver.ogg')
+/obj/structure/table/proc/remove_reinforced(obj/item/S, mob/user)
+ reinforced = common_material_remove(user, reinforced, (S.toolspeed * 4) SECONDS, "reinforcements", "screws", 'sound/items/Screwdriver.ogg')
-/obj/structure/table/proc/remove_material(obj/item/wrench/W, mob/user)
- material = common_material_remove(user, material, 20, "plating", "bolts", 'sound/items/Ratchet.ogg')
+/obj/structure/table/proc/remove_material(obj/item/W, mob/user)
+ material = common_material_remove(user, material, (W.toolspeed * 2) SECONDS, "plating", "bolts", 'sound/items/Ratchet.ogg')
-/obj/structure/table/proc/dismantle(obj/item/wrench/W, mob/user)
+/obj/structure/table/proc/dismantle(obj/item/W, mob/user)
reset_mobs_offset()
if(manipulating) return
manipulating = 1
- user.visible_message(SPAN_NOTICE("\The [user] begins dismantling \the [src]."),
- SPAN_NOTICE("You begin dismantling \the [src]."))
- playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
- if(!do_after(user, 2 SECONDS, src, DO_REPAIR_CONSTRUCT))
+ user.visible_message(SPAN_NOTICE("[user] begins dismantling [src]."),
+ SPAN_NOTICE("You begin dismantling [src]."))
+ if(!W.use_as_tool(src, user, 2 SECONDS, volume = 50, skill_path = SKILL_CONSTRUCTION, do_flags = DO_REPAIR_CONSTRUCT))
manipulating = 0
return
- user.visible_message(SPAN_NOTICE("\The [user] dismantles \the [src]."),
- SPAN_NOTICE("You dismantle \the [src]."))
+ user.visible_message(SPAN_NOTICE("[user] dismantles [src]."),
+ SPAN_NOTICE("You dismantle [src]."))
new /obj/item/stack/material/steel(src.loc)
qdel(src)
return
@@ -296,8 +336,8 @@
// Used for !fun! things such as embedding shards in the faces of tableslammed people.
// The repeated
-// S = [x].place_shard(loc)
-// if(S) shards += S
+// S = [x].place_shard(loc)
+// if(S) shards += S
// is to avoid filling the list with nulls, as place_shard won't place shards of certain materials (holo-wood, holo-steel)
/obj/structure/table/proc/break_to_parts(full_return = 0)
@@ -331,14 +371,14 @@
if(!flipped)
mob_offset = initial(mob_offset)
icon_state = "blank"
- overlays.Cut()
+ ClearOverlays()
var/image/I
// Base frame shape. Mostly done for glass/diamond tables, where this is visible.
for(var/i = 1 to 4)
I = image(icon, dir = SHIFTL(1, i - 1), icon_state = connections[i])
- overlays += I
+ AddOverlays(I)
// Standard table image
if(material)
@@ -346,23 +386,23 @@
I = image(icon, "[material.table_icon_base]_[connections[i]]", dir = SHIFTL(1, i - 1))
if(material.icon_colour) I.color = material.icon_colour
I.alpha = 255 * material.opacity
- overlays += I
+ AddOverlays(I)
// Reinforcements
if(reinforced)
for(var/i = 1 to 4)
- I = image(icon, "[reinforced.table_reinf]_[connections[i]]", dir = SHIFTL(1, i - 1))
+ I = image(icon, "[material.table_icon_reinf]_[connections[i]]", dir = SHIFTL(1, i - 1))
I.color = reinforced.icon_colour
I.alpha = 255 * reinforced.opacity
- overlays += I
+ AddOverlays(I)
if(carpeted)
for(var/i = 1 to 4)
I = image(icon, "carpet_[connections[i]]", dir = SHIFTL(1, i - 1))
- overlays += I
+ AddOverlays(I)
else
mob_offset = 0
- overlays.Cut()
+ ClearOverlays()
var/type = 0
var/tabledirs = 0
for(var/direction in list(turn(dir,90), turn(dir,-90)) )
@@ -383,19 +423,19 @@
var/image/I = image(icon, "[material.table_icon_base]_flip[type]")
I.color = material.icon_colour
I.alpha = 255 * material.opacity
- overlays += I
+ AddOverlays(I)
name = "[material.display_name] table"
else
name = "table frame"
if(reinforced)
- var/image/I = image(icon, "[reinforced.table_reinf]_flip[type]")
+ var/image/I = image(icon, "[material.table_icon_reinf]_flip[type]")
I.color = reinforced.icon_colour
I.alpha = 255 * reinforced.opacity
- overlays += I
+ AddOverlays(I)
if(carpeted)
- overlays += "carpet_flip[type]"
+ AddOverlays("carpet_flip[type]")
/obj/structure/table/proc/can_connect()
return TRUE
@@ -469,6 +509,7 @@
*/
/proc/dirs_to_corner_states(list/dirs)
+ RETURN_TYPE(/list)
if(!istype(dirs)) return
var/list/ret = list(NORTHWEST, SOUTHEAST, NORTHEAST, SOUTHWEST)
diff --git a/code/modules/tension/tension.dm b/code/modules/tension/tension.dm
index a6793a17a9c1d..875e5f082d079 100644
--- a/code/modules/tension/tension.dm
+++ b/code/modules/tension/tension.dm
@@ -86,7 +86,7 @@
threat *= 2 // Target cannot see src.
// Handle statuses.
- if(confused)
+ if(is_confused())
threat /= 2
// if(has_modifier_of_type(/datum/modifier/berserk))
@@ -128,7 +128,7 @@
threat *= 2 // Target cannot see src.
// Handle statuses.
- if(confused)
+ if(is_confused())
threat /= 2
// if(has_modifier_of_type(/datum/modifier/berserk))
@@ -239,7 +239,7 @@
tension *= 10
return tension
- if(confused)
+ if(is_confused())
tension *= 2
return tension
diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm
new file mode 100644
index 0000000000000..c5fac5644ad28
--- /dev/null
+++ b/code/modules/tgui/external.dm
@@ -0,0 +1,239 @@
+/**
+ * tgui external
+ *
+ * Contains all external tgui declarations.
+ */
+
+/**
+ * public
+ *
+ * Used to open and update UIs.
+ * If this proc is not implemented properly, the UI will not update correctly.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * optional ui datum/tgui The UI to be updated, if it exists.
+ * optional parent_ui datum/tgui A parent UI that, when closed, closes this UI as well.
+ */
+
+/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null, datum/tgui/parent_ui = null, custom_state = null)
+ return FALSE // Not implemented.
+
+/**
+ * public
+ *
+ * Data to be sent to the UI.
+ * This must be implemented for a UI to work.
+ *
+ * required user mob The mob interacting with the UI.
+ *
+ * return list Data to be sent to the UI.
+ */
+/datum/proc/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
+ return list() // Not implemented.
+
+/**
+ * public
+ *
+ * Static Data to be sent to the UI.
+ * Static data differs from normal data in that it's large data that should be
+ * sent infrequently. This is implemented optionally for heavy uis that would
+ * be sending a lot of redundant data frequently. Gets squished into one
+ * object on the frontend side, but the static part is cached.
+ *
+ * required user mob The mob interacting with the UI.
+ *
+ * return list Statuic Data to be sent to the UI.
+ */
+/datum/proc/tgui_static_data(mob/user)
+ return list()
+
+/**
+ * public
+ *
+ * Forces an update on static data. Should be done manually whenever something
+ * happens to change static data.
+ *
+ * required user the mob currently interacting with the ui
+ * optional ui ui to be updated
+ */
+/datum/proc/update_tgui_static_data(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ // If there was no ui to update, there's no static data to update either.
+ if(!ui)
+ ui = SStgui.get_open_ui(user, src)
+ if(ui)
+ ui.send_full_update()
+
+/**
+ * public
+ *
+ * Will force an update on static data for all viewers.
+ * Should be done manually whenever something happens to
+ * change static data.
+ */
+/datum/proc/update_tgui_static_data_for_all_viewers()
+ for(var/datum/tgui/window as anything in SStgui.open_uis_by_src[REF(src)])
+ window.send_full_update()
+
+/**
+ * public
+ *
+ * Called on a UI when the UI receieves a href.
+ * Think of this as Topic().
+ *
+ * required action string The action/button that has been invoked by the user.
+ * required params list A list of parameters attached to the button.
+ *
+ * return bool If the UI should be updated or not.
+ */
+/datum/proc/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ SHOULD_CALL_PARENT(TRUE)
+ // If UI is not interactive or usr calling Topic is not the UI user, bail.
+ if(!ui || ui.status != STATUS_INTERACTIVE)
+ return TRUE
+
+/**
+ * public
+ *
+ * Called on a UI when the UI crashed.
+ *
+ * required payload list A list of the payload supposed to be set on the regular UI.
+ */
+/datum/proc/tgui_fallback(list/payload)
+ SHOULD_CALL_PARENT(TRUE)
+
+/**
+ * public
+ *
+ * Called on an object when a tgui object is being created, allowing you to
+ * push various assets to tgui, for examples spritesheets.
+ *
+ * return list List of asset datums or file paths.
+ */
+/datum/proc/ui_assets(mob/user)
+ return list()
+
+/**
+ * private
+ *
+ * The UI's host object (usually src_object).
+ * This allows modules/datums to have the UI attached to them,
+ * and be a part of another object.
+ */
+/datum/proc/tgui_host(mob/user)
+ return src // Default src.
+
+/**
+ * private
+ *
+ * The UI's state controller to be used for created uis
+ * This is a proc over a var for memory reasons
+ */
+/datum/proc/tgui_state(mob/user)
+ return GLOB.tgui_default_state
+
+/**
+ * global
+ *
+ * Associative list of JSON-encoded shared states that were set by
+ * tgui clients.
+ */
+
+/datum/var/list/tgui_shared_states
+
+/**
+ * global
+ *
+ * Tracks open UIs for a user.
+ */
+/mob/var/list/tgui_open_uis = list()
+
+/**
+ * global
+ *
+ * Tracks open windows for a user.
+ */
+/client/var/list/tgui_windows = list()
+
+/**
+ * global
+ *
+ * TRUE if cache was reloaded by tgui dev server at least once.
+ */
+/client/var/tgui_cache_reloaded = FALSE
+
+/**
+ * public
+ *
+ * Called on a UI's object when the UI is closed, not to be confused with
+ * client/verb/uiclose(), which closes the ui window
+ */
+/datum/proc/tgui_close(mob/user)
+
+/**
+ * verb
+ *
+ * Called by UIs when they are closed.
+ * Must be a verb so winset() can call it.
+ *
+ * required uiref ref The UI that was closed.
+ */
+/client/verb/tguiclose(window_id as text)
+ // Name the verb, and hide it from the user panel.
+ set name = "uiclose"
+ set hidden = TRUE
+
+ var/mob/user = src && src.mob
+ if(!user)
+ return
+ // Close all tgui datums based on window_id.
+ SStgui.force_close_window(user, window_id)
+
+/**
+ * Middleware for /client/Topic.
+ *
+ * return bool If TRUE, prevents propagation of the topic call.
+ */
+/proc/tgui_Topic(href_list)
+ // Skip non-tgui topics
+ if(!href_list["tgui"])
+ return FALSE
+ var/type = href_list["type"]
+ // Unconditionally collect tgui logs
+ if(type == "log")
+ log_tgui(usr, href_list["message"])
+ // Reload all tgui windows
+ if(type == "cacheReloaded")
+ // Note: Find a solution for the below causing asset CDN to stop working
+ // which doesn't prevent players from using the dev server on prod
+ // whenever the asset CDN is actually used (currently using rsc only)
+ if(/* !check_rights(R_ADMIN) || */ usr.client.tgui_cache_reloaded)
+ return TRUE
+ // Mark as reloaded
+ usr.client.tgui_cache_reloaded = TRUE
+ // Notify windows
+ var/list/windows = usr.client.tgui_windows
+ for(var/window_id in windows)
+ var/datum/tgui_window/window = windows[window_id]
+ if(window.status == TGUI_WINDOW_READY)
+ window.on_message(type, null, href_list)
+ return TRUE
+ // Locate window
+ var/window_id = href_list["window_id"]
+ var/datum/tgui_window/window
+ if(window_id)
+ window = usr.client.tgui_windows[window_id]
+ if(!window)
+ // #ifdef TGUI_DEBUGGING // Always going to log these
+ log_tgui(usr, "Error: Couldn't find the window datum, force closing.")
+ // #endif
+ SStgui.force_close_window(usr, window_id)
+ return TRUE
+ // Decode payload
+ var/payload
+ if(href_list["payload"])
+ payload = json_decode(href_list["payload"])
+ // Pass message to window
+ if(window)
+ window.on_message(type, payload, href_list)
+ return TRUE
diff --git a/code/modules/tgui/modules/_base.dm b/code/modules/tgui/modules/_base.dm
new file mode 100644
index 0000000000000..dab2ef1076d95
--- /dev/null
+++ b/code/modules/tgui/modules/_base.dm
@@ -0,0 +1,21 @@
+/*
+TGUI MODULES
+
+This allows for datum-based TGUIs that can be hooked into objects.
+This is useful for things such as the power monitor, which needs to exist on a physical console in the world, but also as a virtual device the AI can use
+
+Code is pretty much ripped verbatim from nano modules, but with un-needed stuff removed
+*/
+/datum/tgui_module
+ var/name
+ var/datum/host
+
+/datum/tgui_module/New(host)
+ src.host = host
+
+/datum/tgui_module/tgui_host()
+ return host ? host : src
+
+/datum/tgui_module/tgui_close(mob/user)
+ if(host)
+ host.tgui_close(user)
diff --git a/code/modules/tgui/modules/crew_monitor.dm b/code/modules/tgui/modules/crew_monitor.dm
new file mode 100644
index 0000000000000..c9602b55a0c99
--- /dev/null
+++ b/code/modules/tgui/modules/crew_monitor.dm
@@ -0,0 +1,52 @@
+/datum/tgui_module/crew_monitor
+ name = "Crew monitor"
+
+/datum/tgui_module/crew_monitor/tgui_act(action, params)
+ if(..())
+ return TRUE
+
+ var/turf/T = get_turf(tgui_host())
+ if(!T || !(T.z in GLOB.using_map.player_levels))
+ to_chat(usr, "Unable to establish a connection: You're too far away from the station!")
+ return FALSE
+
+ switch(action)
+ if("track")
+ if(isAI(usr))
+ var/mob/living/silicon/ai/AI = usr
+ var/mob/living/carbon/human/H = locate(params["track"]) in SSmobs.mob_list
+ if(hassensorlevel(H, SUIT_SENSOR_TRACKING))
+ AI.ai_actual_track(H)
+ return TRUE
+
+/datum/tgui_module/crew_monitor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ var/z = get_z(tgui_host())
+ var/list/map_levels = GLOB.using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)
+
+ if(!length(map_levels))
+ to_chat(user, "The crew monitor doesn't seem like it'll work here.")
+ if(ui)
+ ui.close()
+ return null
+
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "CrewMonitor", name, 800, 600, master_ui, state)
+ ui.autoupdate = TRUE
+ ui.open()
+
+
+/datum/tgui_module/crew_monitor/tgui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.tgui_default_state)
+ var/data[0]
+
+ data["isAI"] = isAI(user)
+
+ var/z = get_z(tgui_host())
+ var/list/map_levels = uniquelist(GLOB.using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE))
+ data["map_levels"] = map_levels
+
+ data["crewmembers"] = list()
+ for(var/zlevel in map_levels)
+ data["crewmembers"] += crew_repository.health_data(zlevel)
+
+ return data
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
new file mode 100644
index 0000000000000..763d2dc09115b
--- /dev/null
+++ b/code/modules/tgui/states.dm
@@ -0,0 +1,140 @@
+/**
+ * Base state and helpers for states. Just does some sanity checks,
+ * implement a proper state for in-depth checks.
+ */
+
+/**
+ * public
+ *
+ * Checks the UI state for a mob.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required state datum/ui_state The state to check.
+ *
+ * return UI_state The state of the UI.
+ */
+/datum/proc/tgui_status(mob/user, datum/tgui_state/state)
+ var/src_object = tgui_host(user)
+ . = STATUS_CLOSE
+ if(!state)
+ return
+
+ if(isobserver(user))
+ // Admins can always interact.
+ if(check_rights(R_ADMIN, 0, src))
+ . = max(., STATUS_INTERACTIVE)
+
+ // Regular ghosts can always at least view if in range.
+ if(user.client)
+ if(get_dist(src_object, user) < get_greater_view_size_component(user.client.view))
+ . = max(., STATUS_UPDATE)
+
+ // Check if the state allows interaction
+ var/result = state.can_use_topic(src_object, user)
+ . = max(., result)
+
+/**
+ * private
+ *
+ * Checks if a user can use src_object's UI, and returns the state.
+ * Can call a mob proc, which allows overrides for each mob.
+ *
+ * required src_object datum The object/datum which owns the UI.
+ * required user mob The mob who opened/is using the UI.
+ *
+ * return UI_state The state of the UI.
+ */
+/datum/tgui_state/proc/can_use_topic(src_object, mob/user)
+ // Don't allow interaction by default.
+ return STATUS_CLOSE
+
+/**
+ * public
+ *
+ * Standard interaction/sanity checks. Different mob types may have overrides.
+ *
+ * return UI_state The state of the UI.
+ */
+/mob/proc/shared_tgui_interaction(src_object)
+ // Close UIs if mindless.
+ if(!client)
+ return STATUS_CLOSE
+ // Disable UIs if unconcious.
+ else if(stat)
+ return STATUS_DISABLED
+ // Update UIs if incapicitated but concious.
+ else if(incapacitated())
+ return STATUS_UPDATE
+ return STATUS_INTERACTIVE
+
+/mob/living/silicon/ai/shared_tgui_interaction(src_object)
+ // Disable UIs if the AI is unpowered.
+ if(lacks_power())
+ return STATUS_DISABLED
+ return ..()
+
+/mob/living/silicon/robot/shared_tgui_interaction(src_object)
+ // Disable UIs if the Borg is unpowered or locked.
+ if(!cell || cell.charge <= 0 || lockcharge)
+ return STATUS_DISABLED
+ return ..()
+
+/**
+ * public
+ *
+ * Check the distance for a living mob.
+ * Really only used for checks outside the context of a mob.
+ * Otherwise, use shared_living_tgui_distance().
+ *
+ * required src_object The object which owns the UI.
+ * required user mob The mob who opened/is using the UI.
+ *
+ * return UI_state The state of the UI.
+ */
+/atom/proc/contents_tgui_distance(src_object, mob/living/user)
+ // Just call this mob's check.
+ return user.shared_living_tgui_distance(src_object)
+
+/**
+ * public
+ *
+ * Distance versus interaction check.
+ *
+ * required src_object atom/movable The object which owns the UI.
+ *
+ * return UI_state The state of the UI.
+ */
+/mob/living/proc/shared_living_tgui_distance(atom/movable/src_object, viewcheck = TRUE)
+ // If the object is obscured, close it.
+ if(viewcheck && !(src_object in view(src)))
+ return STATUS_CLOSE
+
+ var/dist = get_dist(src_object, src)
+ if(dist <= 1) // Open and interact if 1-0 tiles away.
+ return STATUS_INTERACTIVE
+ else if(dist <= 2) // View only if 2-3 tiles away.
+ return STATUS_UPDATE
+ else if(dist <= 5) // Disable if 5 tiles away.
+ return STATUS_DISABLED
+ return STATUS_CLOSE // Otherwise, we got nothing.
+
+/**
+ * public
+ *
+ * Distance versus interaction check, with max'd update range.
+ *
+ * required src_object atom/movable The object which owns the UI.
+ *
+ * return UI_state The state of the UI.
+ */
+/mob/living/proc/shared_living_tgui_distance_bigscreen(atom/movable/src_object, viewcheck = TRUE)
+ // If the object is obscured, close it.
+ if(viewcheck && !(src_object in view(src)))
+ return STATUS_CLOSE
+
+ var/dist = get_dist(src_object, src)
+ if(dist <= 1) // Open and interact if 1-0 tiles away.
+ return STATUS_INTERACTIVE
+ else if(dist <= world.view)
+ return STATUS_UPDATE
+ return STATUS_CLOSE // Otherwise, we got nothing.
diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm
new file mode 100644
index 0000000000000..2db5f1a9848bb
--- /dev/null
+++ b/code/modules/tgui/states/admin.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: admin_state
+ *
+ * Checks that the user is an admin, end-of-story.
+ */
+
+GLOBAL_DATUM_INIT(tgui_admin_state, /datum/tgui_state/admin_state, new)
+
+/datum/tgui_state/admin_state/can_use_topic(src_object, mob/user)
+ if(check_rights(user.client, R_ADMIN))
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
diff --git a/code/modules/tgui/states/always.dm b/code/modules/tgui/states/always.dm
new file mode 100644
index 0000000000000..56836d36f63ef
--- /dev/null
+++ b/code/modules/tgui/states/always.dm
@@ -0,0 +1,11 @@
+
+/**
+ * tgui state: always_state
+ *
+ * Always grants the user UI_INTERACTIVE. Period.
+ */
+
+GLOBAL_DATUM_INIT(tgui_always_state, /datum/tgui_state/always_state, new)
+
+/datum/tgui_state/always_state/can_use_topic(src_object, mob/user)
+ return STATUS_INTERACTIVE
diff --git a/code/modules/tgui/states/conscious.dm b/code/modules/tgui/states/conscious.dm
new file mode 100644
index 0000000000000..b3a551429f95d
--- /dev/null
+++ b/code/modules/tgui/states/conscious.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: conscious_state
+ *
+ * Only checks if the user is conscious.
+ */
+
+GLOBAL_DATUM_INIT(tgui_conscious_state, /datum/tgui_state/conscious_state, new)
+
+/datum/tgui_state/conscious_state/can_use_topic(src_object, mob/user)
+ if(user.stat == CONSCIOUS)
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
diff --git a/code/modules/tgui/states/contained.dm b/code/modules/tgui/states/contained.dm
new file mode 100644
index 0000000000000..05ae799dad851
--- /dev/null
+++ b/code/modules/tgui/states/contained.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: contained_state
+ *
+ * Checks that the user is inside the src_object.
+ */
+
+GLOBAL_DATUM_INIT(tgui_contained_state, /datum/tgui_state/contained_state, new)
+
+/datum/tgui_state/contained_state/can_use_topic(atom/src_object, mob/user)
+ if(!src_object.contains(user))
+ return STATUS_CLOSE
+ return user.shared_tgui_interaction(src_object)
diff --git a/code/modules/tgui/states/deep_inventory.dm b/code/modules/tgui/states/deep_inventory.dm
new file mode 100644
index 0000000000000..6394280ed8ca7
--- /dev/null
+++ b/code/modules/tgui/states/deep_inventory.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: deep_inventory_state
+ *
+ * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory.
+ */
+
+GLOBAL_DATUM_INIT(tgui_deep_inventory_state, /datum/tgui_state/deep_inventory_state, new)
+
+/datum/tgui_state/deep_inventory_state/can_use_topic(src_object, mob/user)
+ if(!user.contains(src_object))
+ return STATUS_CLOSE
+ return user.shared_tgui_interaction(src_object)
diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm
new file mode 100644
index 0000000000000..b202b35d49cdc
--- /dev/null
+++ b/code/modules/tgui/states/default.dm
@@ -0,0 +1,69 @@
+/**
+ * tgui state: default_state
+ *
+ * Checks a number of things -- mostly physical distance for humans and view for robots.
+ */
+
+GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new)
+
+/datum/tgui_state/default/can_use_topic(src_object, mob/user)
+ return user.default_can_use_tgui_topic(src_object) // Call the individual mob-overridden procs.
+
+/mob/proc/default_can_use_tgui_topic(src_object)
+ return STATUS_CLOSE // Don't allow interaction by default.
+
+/mob/living/default_can_use_tgui_topic(src_object)
+ . = shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE && loc)
+ . = min(., loc.contents_tgui_distance(src_object, src)) // Check the distance...
+ if(. == STATUS_INTERACTIVE) // Non-human living mobs can only look, not touch.
+ return STATUS_UPDATE
+
+/mob/living/carbon/human/default_can_use_tgui_topic(src_object)
+ . = shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ . = min(., shared_living_tgui_distance(src_object)) // Check the distance...
+
+/mob/living/silicon/robot/default_can_use_tgui_topic(src_object)
+ . = shared_tgui_interaction(src_object)
+ if(. <= STATUS_DISABLED)
+ return
+
+ // Robots can interact with anything they can see.
+ if((src_object in view(src)) && (get_dist(src, src_object) <= get_lesser_view_size_component(client.view)))
+ return STATUS_INTERACTIVE
+ return STATUS_DISABLED // Otherwise they can keep the UI open.
+
+/mob/living/silicon/ai/default_can_use_tgui_topic(src_object)
+ . = shared_tgui_interaction(src_object)
+ if(. < STATUS_INTERACTIVE)
+ return
+
+ // The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled.
+ var/can_see = (src_object in view(client.view, src))
+ if(!can_see)
+ if(is_in_chassis())
+ can_see = cameranet && cameranet.is_turf_visible(get_turf(src_object))
+ else
+ can_see = get_dist(src_object, src) <= get_lesser_view_size_component(client.view)
+
+ if(!control_disabled && can_see)
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
+
+/mob/living/simple_animal/default_can_use_tgui_topic(src_object)
+ . = shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ . = min(., shared_living_tgui_distance(src_object)) //simple animals can only use things they're near.
+
+/mob/living/silicon/pai/default_can_use_tgui_topic(src_object)
+ // pAIs can only use themselves and the owner's radio.
+ if((src_object == src || src_object == silicon_radio) && !stat)
+ return STATUS_INTERACTIVE
+ else
+ return ..()
+
+/mob/observer/ghost/default_can_use_tgui_topic()
+ if(can_admin_interact())
+ return STATUS_INTERACTIVE // Admins are more equal
+ return STATUS_UPDATE // Ghosts can view updates
diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm
new file mode 100644
index 0000000000000..330608f560ac1
--- /dev/null
+++ b/code/modules/tgui/states/hands.dm
@@ -0,0 +1,27 @@
+/**
+ * tgui state: hands_state
+ *
+ * Checks that the src_object is in the user's hands.
+ */
+
+GLOBAL_DATUM_INIT(tgui_hands_state, /datum/tgui_state/hands_state, new)
+
+/datum/tgui_state/hands_state/can_use_topic(src_object, mob/user)
+ . = user.shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ return min(., user.hands_can_use_tgui_topic(src_object))
+
+/mob/proc/hands_can_use_tgui_topic(src_object)
+ return STATUS_CLOSE
+
+/mob/living/hands_can_use_tgui_topic(src_object)
+ if(src_object in get_all_held_items())
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
+
+/mob/living/silicon/robot/hands_can_use_tgui_topic(src_object)
+ for(var/obj/item/gripper/active_gripper as anything in GetAllHeld(/obj/item/gripper))
+ if(active_gripper.contains(src_object))
+ return STATUS_INTERACTIVE
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
diff --git a/code/modules/tgui/states/human_adjacent.dm b/code/modules/tgui/states/human_adjacent.dm
new file mode 100644
index 0000000000000..2895de16bb550
--- /dev/null
+++ b/code/modules/tgui/states/human_adjacent.dm
@@ -0,0 +1,17 @@
+
+/**
+ * tgui state: human_adjacent_state
+ *
+ * In addition to default checks, only allows interaction for a
+ * human adjacent user.
+ */
+
+GLOBAL_DATUM_INIT(tgui_human_adjacent_state, /datum/tgui_state/human_adjacent_state, new)
+
+/datum/tgui_state/human_adjacent_state/can_use_topic(src_object, mob/user)
+ . = user.default_can_use_tgui_topic(src_object)
+
+ var/dist = get_dist(src_object, user)
+ if((dist > 1) || (!ishuman(user)))
+ // Can't be used unless adjacent and human, even with TK
+ . = min(., STATUS_UPDATE)
diff --git a/code/modules/tgui/states/inventory.dm b/code/modules/tgui/states/inventory.dm
new file mode 100644
index 0000000000000..c30a28d8a41ac
--- /dev/null
+++ b/code/modules/tgui/states/inventory.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: inventory_state
+ *
+ * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory.
+ */
+
+GLOBAL_DATUM_INIT(tgui_inventory_state, /datum/tgui_state/inventory_state, new)
+
+/datum/tgui_state/inventory_state/can_use_topic(src_object, mob/user)
+ if(!(src_object in user))
+ return STATUS_CLOSE
+ return user.shared_tgui_interaction(src_object)
diff --git a/code/modules/tgui/states/not_incapacitated.dm b/code/modules/tgui/states/not_incapacitated.dm
new file mode 100644
index 0000000000000..f9edc7c0179b0
--- /dev/null
+++ b/code/modules/tgui/states/not_incapacitated.dm
@@ -0,0 +1,29 @@
+/**
+ * tgui state: not_incapacitated_state
+ *
+ * Checks that the user isn't incapacitated
+ */
+
+GLOBAL_DATUM_INIT(tgui_not_incapacitated_state, /datum/tgui_state/not_incapacitated_state, new)
+
+/**
+ * tgui state: not_incapacitated_turf_state
+ *
+ * Checks that the user isn't incapacitated and that their loc is a turf
+ */
+
+GLOBAL_DATUM_INIT(tgui_not_incapacitated_turf_state, /datum/tgui_state/not_incapacitated_state, new(no_turfs = TRUE))
+
+/datum/tgui_state/not_incapacitated_state
+ var/turf_check = FALSE
+
+/datum/tgui_state/not_incapacitated_state/New(loc, no_turfs = FALSE)
+ ..()
+ turf_check = no_turfs
+
+/datum/tgui_state/not_incapacitated_state/can_use_topic(src_object, mob/user)
+ if(user.stat)
+ return STATUS_CLOSE
+ if(user.incapacitated() || (turf_check && !isturf(user.loc)))
+ return STATUS_DISABLED
+ return STATUS_INTERACTIVE
diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm
new file mode 100644
index 0000000000000..8876e9059d3e4
--- /dev/null
+++ b/code/modules/tgui/states/notcontained.dm
@@ -0,0 +1,26 @@
+/**
+ * tgui state: notcontained_state
+ *
+ * Checks that the user is not inside src_object, and then makes the default checks.
+ */
+
+GLOBAL_DATUM_INIT(tgui_notcontained_state, /datum/tgui_state/notcontained_state, new)
+
+/datum/tgui_state/notcontained_state/can_use_topic(atom/src_object, mob/user)
+ . = user.shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ return min(., user.notcontained_can_use_tgui_topic(src_object))
+
+/mob/proc/notcontained_can_use_tgui_topic(src_object)
+ return STATUS_CLOSE
+
+/mob/living/notcontained_can_use_tgui_topic(atom/src_object)
+ if(src_object.contains(src))
+ return STATUS_CLOSE // Close if we're inside it.
+ return default_can_use_tgui_topic(src_object)
+
+/mob/living/silicon/notcontained_can_use_tgui_topic(src_object)
+ return default_can_use_tgui_topic(src_object) // Silicons use default bevhavior.
+
+/mob/living/simple_animal/drone/notcontained_can_use_tgui_topic(src_object)
+ return default_can_use_tgui_topic(src_object) // Drones use default bevhavior.
diff --git a/code/modules/tgui/states/observer.dm b/code/modules/tgui/states/observer.dm
new file mode 100644
index 0000000000000..d0b0b73bd5c90
--- /dev/null
+++ b/code/modules/tgui/states/observer.dm
@@ -0,0 +1,14 @@
+/**
+ * tgui state: observer_state
+ *
+ * Checks that the user is an observer/ghost.
+ */
+
+GLOBAL_DATUM_INIT(tgui_observer_state, /datum/tgui_state/observer_state, new)
+
+/datum/tgui_state/observer_state/can_use_topic(src_object, mob/user)
+ if(isobserver(user))
+ return STATUS_INTERACTIVE
+ if(check_rights(R_ADMIN, 0, src))
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm
new file mode 100644
index 0000000000000..60c7ecb979c3b
--- /dev/null
+++ b/code/modules/tgui/states/physical.dm
@@ -0,0 +1,24 @@
+/**
+ * tgui state: physical_state
+ *
+ * Short-circuits the default state to only check physical distance.
+ */
+
+GLOBAL_DATUM_INIT(tgui_physical_state, /datum/tgui_state/physical, new)
+
+/datum/tgui_state/physical/can_use_topic(src_object, mob/user)
+ . = user.shared_tgui_interaction(src_object)
+ if(. > STATUS_CLOSE)
+ return min(., user.physical_can_use_tgui_topic(src_object))
+
+/mob/proc/physical_can_use_tgui_topic(src_object)
+ return STATUS_CLOSE
+
+/mob/living/physical_can_use_tgui_topic(src_object)
+ return shared_living_tgui_distance(src_object)
+
+/mob/living/silicon/physical_can_use_tgui_topic(src_object)
+ return max(STATUS_UPDATE, shared_living_tgui_distance(src_object)) // Silicons can always see.
+
+/mob/living/silicon/ai/physical_can_use_tgui_topic(src_object)
+ return STATUS_UPDATE // AIs are not physical.
diff --git a/code/modules/tgui/states/self.dm b/code/modules/tgui/states/self.dm
new file mode 100644
index 0000000000000..8cadc8397f337
--- /dev/null
+++ b/code/modules/tgui/states/self.dm
@@ -0,0 +1,12 @@
+/**
+ * tgui state: self_state
+ *
+ * Only checks that the user and src_object are the same.
+ */
+
+GLOBAL_DATUM_INIT(tgui_self_state, /datum/tgui_state/self_state, new)
+
+/datum/tgui_state/self_state/can_use_topic(src_object, mob/user)
+ if(src_object != user)
+ return STATUS_CLOSE
+ return user.shared_tgui_interaction(src_object)
diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm
new file mode 100644
index 0000000000000..2349d2ef927ab
--- /dev/null
+++ b/code/modules/tgui/states/zlevel.dm
@@ -0,0 +1,14 @@
+/**
+ * tgui state: z_state
+ *
+ * Only checks that the Z-level of the user and src_object are the same.
+ */
+
+GLOBAL_DATUM_INIT(tgui_z_state, /datum/tgui_state/z_state, new)
+
+/datum/tgui_state/z_state/can_use_topic(src_object, mob/user)
+ var/turf/turf_obj = get_turf(src_object)
+ var/turf/turf_usr = get_turf(user)
+ if(turf_obj && turf_usr && turf_obj.z == turf_usr.z)
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
new file mode 100644
index 0000000000000..0de64f82924e7
--- /dev/null
+++ b/code/modules/tgui/tgui.dm
@@ -0,0 +1,351 @@
+/**
+ * tgui
+ *
+ * /tg/station user interface library
+ */
+
+/**
+ * tgui datum (represents a UI).
+ */
+/datum/tgui
+ /// The mob who opened/is using the UI.
+ var/mob/user
+ /// The object which owns the UI.
+ var/datum/src_object
+ /// The title of te UI.
+ var/title
+ /// The window_id for browse() and onclose().
+ var/datum/tgui_window/window
+ /// Key that is used for remembering the window geometry.
+ var/window_key
+ /// Deprecated: Window size.
+ var/window_size
+ /// The interface (template) to be used for this UI.
+ var/interface
+ /// Update the UI every MC tick.
+ var/autoupdate = TRUE
+ /// If the UI has been initialized yet.
+ var/initialized = FALSE
+ /// Time of opening the window.
+ var/opened_at
+ /// Stops further updates when close() was called.
+ var/closing = FALSE
+ /// The status/visibility of the UI.
+ var/status = STATUS_INTERACTIVE
+ /// Timed refreshing state
+ var/refreshing = FALSE
+ /// Topic state used to determine status/interactability.
+ var/datum/tgui_state/state = null
+ /// The map z-level to display.
+ var/map_z_level = 1
+ /// The Parent UI
+ var/datum/tgui/parent_ui
+ /// Children of this UI
+ var/list/children = list()
+
+/**
+ * public
+ *
+ * Create a new UI.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required src_object datum The object or datum which owns the UI.
+ * required interface string The interface used to render the UI.
+ * optional title string The title of the UI.
+ * optional parent_ui datum/tgui The parent of this UI.
+ * optional ui_x int Deprecated: Window width.
+ * optional ui_y int Deprecated: Window height.
+ *
+ * return datum/tgui The requested UI.
+ */
+/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y)
+ src.user = user
+ src.src_object = src_object
+ src.window_key = "[REF(src_object)]-main"
+ src.interface = interface
+ if(title)
+ src.title = title
+ src.state = src_object.tgui_state()
+ src.parent_ui = parent_ui
+ if(parent_ui)
+ parent_ui.children += src
+
+/**
+ * public
+ *
+ * Open this UI (and initialize it with data).
+ */
+/datum/tgui/proc/open()
+ if(!user.client)
+ return null
+ if(window)
+ return null
+ process_status()
+ if(status < STATUS_UPDATE)
+ return null
+ window = SStgui.request_pooled_window(user)
+ if(!window)
+ return null
+ opened_at = world.time
+ window.acquire_lock(src)
+ if(!window.is_ready())
+ window.initialize(
+ strict_mode = TRUE,
+ fancy = user.get_preference_value(/datum/client_preference/tgui_fancy) == GLOB.PREF_YES,
+ assets = list(
+ get_asset_datum(/datum/asset/simple/tgui),
+ ))
+ else
+ window.send_message("ping")
+ send_assets()
+ window.send_message("update", get_payload(
+ with_data = TRUE,
+ with_static_data = TRUE))
+ SStgui.on_open(src)
+
+/datum/tgui/proc/send_assets()
+ PRIVATE_PROC(TRUE)
+ var/flushqueue = window.send_asset(
+ get_asset_datum(/datum/asset/simple/namespaced/fontawesome)
+ )
+ flushqueue |= window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
+
+ for(var/datum/asset/asset in src_object.ui_assets(user))
+ flushqueue |= window.send_asset(asset)
+
+ if(flushqueue)
+ user.client.browse_queue_flush()
+
+/**
+ * public
+ *
+ * Close the UI, and all its children.
+ */
+/datum/tgui/proc/close(can_be_suspended = TRUE, logout = FALSE)
+ if(closing)
+ return
+ closing = TRUE
+ for(var/datum/tgui/child in children)
+ child.close(can_be_suspended, logout)
+ // If we don't have window_id, open proc did not have the opportunity
+ // to finish, therefore it's safe to skip this whole block.
+ if(window)
+ // Windows you want to keep are usually blue screens of death
+ // and we want to keep them around, to allow user to read
+ // the error message properly.
+ window.release_lock()
+ window.close(can_be_suspended, logout)
+ src_object.tgui_close(user)
+ SStgui.on_close(src)
+ state = null
+ if(parent_ui)
+ parent_ui.children -= src
+ parent_ui = null
+ qdel(src)
+
+/**
+ * public
+ *
+ * Enable/disable auto-updating of the UI.
+ *
+ * required autoupdate bool Enable/disable auto-updating.
+ */
+/datum/tgui/proc/set_autoupdate(autoupdate)
+ src.autoupdate = autoupdate
+
+/**
+ * public
+ *
+ * Replace current ui.state with a new one.
+ *
+ * required state datum/ui_state/state Next state
+ */
+/datum/tgui/proc/set_state(datum/tgui_state/state)
+ src.state = state
+
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ */
+/datum/tgui/proc/send_asset(datum/asset/asset)
+ if(!window)
+ CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.")
+ return window.send_asset(asset)
+
+/**
+ * public
+ *
+ * Send a full update to the client (includes static data).
+ *
+ * optional custom_data list Custom data to send instead of ui_data.
+ * optional force bool Send an update even if UI is not interactive.
+ */
+/datum/tgui/proc/send_full_update(custom_data, force)
+ if(!user.client || !initialized || closing)
+ return
+
+ var/should_update_data = force || status >= STATUS_UPDATE
+ window.send_message("update", get_payload(
+ custom_data,
+ with_data = should_update_data,
+ with_static_data = TRUE))
+
+/**
+ * public
+ *
+ * Send a partial update to the client (excludes static data).
+ *
+ * optional custom_data list Custom data to send instead of ui_data.
+ * optional force bool Send an update even if UI is not interactive.
+ */
+/datum/tgui/proc/send_update(custom_data, force)
+ if(!user.client || !initialized || closing)
+ return
+ var/should_update_data = force || status >= STATUS_UPDATE
+ window.send_message("update", get_payload(
+ custom_data,
+ with_data = should_update_data))
+
+/**
+ * private
+ *
+ * Package the data to send to the UI, as JSON.
+ *
+ * return list
+ */
+/datum/tgui/proc/get_payload(custom_data, with_data, with_static_data)
+ var/list/json_data = list()
+ json_data["config"] = list(
+ "title" = title,
+ "status" = status,
+ "interface" = interface,
+ "refreshing" = FALSE,
+ "map" = (GLOB.using_map && GLOB.using_map.path) ? GLOB.using_map.path : "Unknown",
+ "mapZLevel" = map_z_level,
+ "ref" = "[REF(src)]",
+ "window" = list(
+ "key" = window_key,
+ "size" = window_size,
+ "fancy" = user.get_preference_value(/datum/client_preference/tgui_fancy) == GLOB.PREF_YES,
+ "locked" = user.get_preference_value(/datum/client_preference/tgui_lock) == GLOB.PREF_YES,
+
+ ),
+ "client" = list(
+ "ckey" = user.client.ckey,
+ "address" = user.client.address,
+ "computer_id" = user.client.computer_id,
+ ),
+ "user" = list(
+ "name" = "[user]",
+ "observer" = isobserver(user),
+ ),
+ )
+ var/data = custom_data || with_data && src_object.tgui_data(user, src, state)
+ if(data)
+ json_data["data"] = data
+ var/static_data = with_static_data && src_object.tgui_static_data(user)
+ if(static_data)
+ json_data["static_data"] = static_data
+ if(src_object.tgui_shared_states)
+ json_data["shared"] = src_object.tgui_shared_states
+ return json_data
+
+/**
+ * private
+ *
+ * Run an update cycle for this UI. Called internally by SStgui
+ * every second or so.
+ */
+/datum/tgui/proc/process(force = FALSE)
+ set waitfor = FALSE
+
+ if(closing)
+ return
+ var/datum/host = src_object.tgui_host(user)
+ // If the object or user died (or something else), abort.
+ if(!src_object || !host || !user || !window)
+ close(can_be_suspended = FALSE)
+ return
+ // Validate ping
+ if(!initialized && world.time - opened_at > TGUI_PING_TIMEOUT)
+ log_tgui(user, \
+ "Error: Zombie window detected, killing it with fire.\n" \
+ + "window_id: [window.id]\n" \
+ + "opened_at: [opened_at]\n" \
+ + "world.time: [world.time]")
+ close(can_be_suspended = FALSE)
+ return
+ // Update through a normal call to ui_interact
+ if(status != STATUS_DISABLED && (autoupdate || force))
+ src_object.tgui_interact(user, src, parent_ui)
+ return
+ // Update status only
+ var/needs_update = process_status()
+ if(status <= STATUS_CLOSE)
+ close()
+ return
+ if(needs_update)
+ window.send_message("update", get_payload())
+
+/**
+ * private
+ *
+ * Updates the status, and returns TRUE if status has changed.
+ */
+/datum/tgui/proc/process_status()
+ var/prev_status = status
+ status = src_object.tgui_status(user, state)
+ if(parent_ui)
+ status = min(status, parent_ui.status)
+ return prev_status != status
+
+/datum/tgui/proc/set_map_z_level(nz)
+ map_z_level = nz
+
+/**
+ * private
+ *
+ * Handle clicks from the UI.
+ * Call the src_object's ui_act() if status is UI_INTERACTIVE.
+ * If the src_object's ui_act() returns 1, update all UIs attacked to it.
+ */
+/datum/tgui/proc/on_message(type, list/payload, list/href_list)
+ // Pass act type messages to tgui_act
+ if(type && copytext(type, 1, 5) == "act/")
+ var/act_type = copytext(type, 5)
+ #ifdef TGUI_DEBUGGING
+ log_tgui(user, "Action: [act_type] [href_list["payload"]], Window: [window.id], Source: [src_object]")
+ #endif
+ process_status()
+ if(src_object.tgui_act(act_type, payload, src, state))
+ SStgui.update_uis(src_object)
+ return FALSE
+ switch(type)
+ if("ready")
+ // Send a full update when the user manually refreshes the UI
+ if(initialized)
+ send_full_update()
+ initialized = TRUE
+ if("ping/reply")
+ initialized = TRUE
+ if("suspend")
+ close(can_be_suspended = TRUE)
+ if("close")
+ close(can_be_suspended = FALSE)
+ if("log")
+ if(href_list["fatal"])
+ close(can_be_suspended = FALSE)
+ if("setSharedState")
+ if(status != STATUS_INTERACTIVE)
+ return
+ LAZYINITLIST(src_object.tgui_shared_states)
+ src_object.tgui_shared_states[href_list["key"]] = href_list["value"]
+ SStgui.update_uis(src_object)
+ if("fallback")
+ #ifdef TGUI_DEBUGGING
+ log_tgui(user, "Fallback Triggered: [href_list["payload"]], Window: [window.id], Source: [src_object]")
+ #endif
+ src_object.tgui_fallback(payload)
diff --git a/code/modules/tgui/tgui_input/alert_input.dm b/code/modules/tgui/tgui_input/alert_input.dm
new file mode 100644
index 0000000000000..2104cfabe16ea
--- /dev/null
+++ b/code/modules/tgui/tgui_input/alert_input.dm
@@ -0,0 +1,145 @@
+/**
+ * Creates a TGUI alert window and returns the user's response.
+ *
+ * This proc should be used to create alerts that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the alert to.
+ * * message - The content of the alert, shown in the body of the TGUI window.
+ * * title - The of the alert modal, shown on the top of the TGUI window.
+ * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this alert should grab window focus.
+ */
+/proc/tgui_alert(mob/user, message = "", title = "Alert", list/buttons = list("Ok"), timeout = 0, autofocus = TRUE, tgui_state = GLOB.tgui_always_state)
+ if(!user)
+ user = usr
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Alert! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ // A gentle nudge - you should not be using TGUI alert for anything other than a simple message.
+ if(length(buttons) > 3)
+ log_tgui(user, "Error: TGUI Alert initiated with too many buttons. Use a list.", "TguiAlert")
+ return tgui_input_list(user, message, title, buttons, timeout)
+
+ // Client does NOT have tgui_input on: Returns regular input
+ if(user.get_preference_value(/datum/client_preference/tgui_input) == GLOB.PREF_NO)
+ if(length(buttons) == 2)
+ return alert(user, message, title, buttons[1], buttons[2])
+ if(length(buttons) == 3)
+ return alert(user, message, title, buttons[1], buttons[2], buttons[3])
+
+ var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout, autofocus, tgui_state)
+
+ alert.tgui_interact(user)
+ alert.wait()
+ if(alert)
+ . = alert.choice
+ qdel(alert)
+
+/**
+ * # tgui_alert
+ *
+ * Datum used for instantiating and using a TGUI-controlled modal that prompts the user with
+ * a message and has buttons for responses.
+ */
+/datum/tgui_alert
+ /// The title of the TGUI window
+ var/title
+ /// The textual body of the TGUI window
+ var/message
+ /// The list of buttons (responses) provided on the TGUI window
+ var/list/buttons
+ /// The button that the user has pressed, null if no selection has been made
+ var/choice
+ /// The time at which the tgui_alert was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the tgui_alert, after which the window will close and delete itself.
+ var/timeout
+ /// The bool that controls if this modal should grab window focus
+ var/autofocus
+ /// Boolean field describing if the tgui_alert was closed by the user.
+ var/closed
+ /// The TGUI UI state that will be returned in ui_state(). Default: always_state
+ var/datum/tgui_state/state
+
+/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout, autofocus, tgui_state)
+ src.autofocus = autofocus
+ src.buttons = buttons.Copy()
+ src.message = message
+ src.title = title
+ src.state = tgui_state
+
+ if(timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_alert/Destroy(force)
+ SStgui.close_uis(src)
+ state = null
+ QDEL_NULL(buttons)
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_alert's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_alert/proc/wait()
+ while(!choice && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_alert/tgui_state(mob/user)
+ return state
+
+/datum/tgui_alert/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AlertModal")
+ ui.open()
+
+/datum/tgui_alert/tgui_close(mob/user)
+ closed = TRUE
+
+/datum/tgui_alert/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["autofocus"] = autofocus
+ data["buttons"] = buttons
+ data["message"] = message
+ data["large_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_large)
+ data["swapped_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_swap)
+ data["title"] = title
+ return data
+
+/datum/tgui_alert/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
+
+/datum/tgui_alert/tgui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("choose")
+ if(!(params["choice"] in buttons))
+ CRASH("[usr] entered a non-existent button choice: [params["choice"]]")
+ set_choice(params["choice"])
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_alert/proc/set_choice(choice)
+ src.choice = choice
diff --git a/code/modules/tgui/tgui_input/list_input.dm b/code/modules/tgui/tgui_input/list_input.dm
new file mode 100644
index 0000000000000..15a1f2b0798d8
--- /dev/null
+++ b/code/modules/tgui/tgui_input/list_input.dm
@@ -0,0 +1,165 @@
+/**
+ * Creates a TGUI input list window and returns the user's response.
+ *
+ * This proc should be used to create alerts that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * items - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * default - If an option is already preselected on the UI. Current values, etc.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, tgui_state = GLOB.tgui_always_state)
+ if(!user)
+ user = usr
+
+ if(!length(items))
+ CRASH("[user] tried to open an empty TGUI Input List. Contents are: [items]")
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input List! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ /// Client does NOT have tgui_input on: Returns regular input
+ if(user.get_preference_value(/datum/client_preference/tgui_input) == GLOB.PREF_NO)
+ return input(user, message, title, default) as null|anything in items
+
+ var/datum/tgui_list_input/list_input = new(user, message, title, items, default, timeout, tgui_state)
+
+ if(list_input.invalid)
+ qdel(list_input)
+ return
+
+ list_input.tgui_interact(user)
+ list_input.wait()
+ if(list_input)
+ . = list_input.choice
+ qdel(list_input)
+
+/**
+ * # tgui_list_input
+ *
+ * Datum used for instantiating and using a TGUI-controlled list input that prompts the user with
+ * a message and shows a list of selectable options
+ */
+/datum/tgui_list_input
+ /// The title of the TGUI window
+ var/title
+ /// The textual body of the TGUI window
+ var/message
+ /// The list of items (responses) provided on the TGUI window
+ var/list/items
+ /// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
+ var/list/items_map
+ /// The button that the user has pressed, null if no selection has been made
+ var/choice
+ /// The default button to be selected
+ var/default
+ /// The time at which the tgui_list_input was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the tgui_list_input, after which the window will close and delete itself.
+ var/timeout
+ /// Boolean field describing if the tgui_list_input was closed by the user.
+ var/closed
+ /// The TGUI UI state that will be returned in ui_state(). Default: always_state
+ var/datum/tgui_state/state
+ /// Whether the tgui list input is invalid or not (i.e. due to all list entries being null)
+ var/invalid = FALSE
+
+/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, tgui_state)
+ src.title = title
+ src.message = message
+ src.items = list()
+ src.items_map = list()
+ src.default = default
+ src.state = tgui_state
+ var/list/repeat_items = list()
+
+ // Gets rid of illegal characters
+ var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
+
+ for(var/i in items)
+ var/string_key = whitelistedWords.Replace("[i]", "")
+
+ // Avoids duplicated keys E.g: when areas have the same name
+ string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
+ src.items += string_key
+ src.items_map[string_key] = i
+
+ if(length(src.items) == 0)
+ invalid = TRUE
+
+ if(timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_list_input/Destroy(force)
+ SStgui.close_uis(src)
+ state = null
+ items = null
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_list_input/proc/wait()
+ while(!choice && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_list_input/tgui_state(mob/user)
+ return state
+
+/datum/tgui_list_input/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ListInputModal")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/tgui_list_input/tgui_close(mob/user)
+ closed = TRUE
+
+/datum/tgui_list_input/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["init_value"] = default || items[1]
+ data["items"] = items
+ data["message"] = message
+ data["large_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_large)
+ data["swapped_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_swap)
+ data["title"] = title
+ return data
+
+/datum/tgui_list_input/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
+ return data
+
+/datum/tgui_list_input/tgui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("submit")
+ if(!(params["entry"] in items))
+ return
+ set_choice(items_map[params["entry"]])
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_list_input/proc/set_choice(choice)
+ src.choice = choice
diff --git a/code/modules/tgui/tgui_input/number_input.dm b/code/modules/tgui/tgui_input/number_input.dm
new file mode 100644
index 0000000000000..01cd815d8b0bc
--- /dev/null
+++ b/code/modules/tgui/tgui_input/number_input.dm
@@ -0,0 +1,168 @@
+/**
+ * Creates a TGUI window with a number input. Returns the user's response as num | null.
+ *
+ * This proc should be used to create windows for number entry that the caller will wait for a response from.
+ * If tgui fancy chat is turned off: Will return a normal input. If a max or min value is specified, will
+ * validate the input inside the UI and ui_act.
+ *
+ * Arguments:
+ * * user - The user to show the number input to.
+ * * message - The content of the number input, shown in the body of the TGUI window.
+ * * title - The title of the number input modal, shown on the top of the TGUI window.
+ * * default - The default (or current) value, shown as a placeholder. Users can press refresh with this.
+ * * max_value - Specifies a maximum value. If none is set, any number can be entered. Pressing "max" defaults to 1000.
+ * * min_value - Specifies a minimum value. Often 0.
+ * * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * round_value - whether the inputted number is rounded down into an integer.
+ */
+/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE, tgui_state = GLOB.tgui_always_state)
+ if(!user)
+ user = usr
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input Number! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ // Client does NOT have tgui_input on: Returns regular input
+ if(user.get_preference_value(/datum/client_preference/tgui_input) == GLOB.PREF_NO)
+ var/input_number = input(user, message, title, default) as null|num
+ return clamp(round_value ? round(input_number) : input_number, min_value, max_value)
+
+ var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value, tgui_state)
+
+ number_input.tgui_interact(user)
+ number_input.wait()
+ if(number_input)
+ . = number_input.entry
+ qdel(number_input)
+
+/**
+ * # tgui_input_number
+ *
+ * Datum used for instantiating and using a TGUI-controlled number input that prompts the user with
+ * a message and has an input for number entry.
+ */
+/datum/tgui_input_number
+ /// Boolean field describing if the tgui_input_number was closed by the user.
+ var/closed
+ /// The default (or current) value, shown as a default. Users can press reset with this.
+ var/default
+ /// The entry that the user has return_typed in.
+ var/entry
+ /// The maximum value that can be entered.
+ var/max_value
+ /// The prompt's body, if any, of the TGUI window.
+ var/message
+ /// The minimum value that can be entered.
+ var/min_value
+ /// Whether the submitted number is rounded down into an integer.
+ var/round_value
+ /// The time at which the number input was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the number input, after which the window will close and delete itself.
+ var/timeout
+ /// The title of the TGUI window
+ var/title
+ /// The TGUI UI state that will be returned in ui_state(). Default: always_state
+ var/datum/tgui_state/state
+
+/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value, tgui_state)
+ src.default = default
+ src.max_value = max_value
+ src.message = message
+ src.min_value = min_value
+ src.title = title
+ src.round_value = round_value
+ src.state = tgui_state
+
+ if(timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+ /// Checks for empty numbers - bank accounts, etc.
+ if(max_value == 0)
+ src.min_value = 0
+ if(default)
+ src.default = 0
+
+ /// Sanity check
+ if(default < min_value)
+ src.default = min_value
+
+ if(default > max_value)
+ CRASH("Default value is greater than max value.")
+
+/datum/tgui_input_number/Destroy(force)
+ SStgui.close_uis(src)
+ state = null
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_input_number's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_input_number/proc/wait()
+ while(!entry && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_input_number/tgui_state(mob/user)
+ return state
+
+/datum/tgui_input_number/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NumberInputModal")
+ ui.open()
+
+/datum/tgui_input_number/tgui_close(mob/user)
+ closed = TRUE
+
+/datum/tgui_input_number/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["init_value"] = default // Default is a reserved keyword
+ data["min_value"] = min_value
+ data["max_value"] = max_value
+ data["round_value"] = round_value
+ data["message"] = message
+ data["large_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_large)
+ data["swapped_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_swap)
+ data["title"] = title
+ return data
+
+/datum/tgui_input_number/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
+
+/datum/tgui_input_number/tgui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("submit")
+ if(!isnum(params["entry"]))
+ CRASH("A non number was input into TGUI Input Number by [usr]")
+ var/choice = round_value ? round(params["entry"]) : params["entry"]
+ if(choice > max_value)
+ CRASH("A number greater than the max value was input into TGUI Input Number by [usr]")
+ if(choice < min_value)
+ CRASH("A number less than the min value was input into TGUI Input Number by [usr]")
+ set_entry(choice)
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_input_number/proc/set_entry(entry)
+ src.entry = entry
diff --git a/code/modules/tgui/tgui_input/text_input.dm b/code/modules/tgui/tgui_input/text_input.dm
new file mode 100644
index 0000000000000..d23a6649604af
--- /dev/null
+++ b/code/modules/tgui/tgui_input/text_input.dm
@@ -0,0 +1,171 @@
+/**
+ * Creates a TGUI window with a text input. Returns the user's response.
+ *
+ * This proc should be used to create windows for text entry that the caller will wait for a response from.
+ * If tgui fancy chat is turned off: Will return a normal input. If max_length is specified, will return
+ * stripped_multiline_input.
+ *
+ * Arguments:
+ * * user - The user to show the text input to.
+ * * message - The content of the text input, shown in the body of the TGUI window.
+ * * title - The title of the text input modal, shown on the top of the TGUI window.
+ * * default - The default (or current) value, shown as a placeholder.
+ * * max_length - Specifies a max length for input. MAX_MESSAGE_LEN is default (1024)
+ * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc.
+ * * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input.
+ * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = MAX_MESSAGE_LEN, multiline = FALSE, encode = TRUE, timeout = 0, tgui_state = GLOB.tgui_always_state)
+ if(!user)
+ user = usr
+
+ if(!istype(user))
+ if(!isclient(user))
+ CRASH("We passed something that wasn't a user/client in a TGUI Input Text! The passed user was [user]!")
+ var/client/client = user
+ user = client.mob
+
+ if(isnull(user.client))
+ return
+
+ // Client does NOT have tgui_input on: Returns regular input
+ if(user.get_preference_value(/datum/client_preference/tgui_input) == GLOB.PREF_NO)
+ if(encode)
+ if(multiline)
+ return stripped_multiline_input(user, message, title, default, max_length)
+ else
+ return stripped_input(user, message, title, default, max_length)
+ else
+ if(multiline)
+ return input(user, message, title, default) as message|null
+ else
+ return input(user, message, title, default) as text|null
+
+ var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout, tgui_state)
+
+ text_input.tgui_interact(user)
+ text_input.wait()
+ if(text_input)
+ . = text_input.entry
+ qdel(text_input)
+
+/**
+ * tgui_input_text
+ *
+ * Datum used for instantiating and using a TGUI-controlled text input that prompts the user with
+ * a message and has an input for text entry.
+ */
+/datum/tgui_input_text
+ /// Boolean field describing if the tgui_input_text was closed by the user.
+ var/closed
+ /// The default (or current) value, shown as a default.
+ var/default
+ /// Whether the input should be stripped using html_encode
+ var/encode
+ /// The entry that the user has return_typed in.
+ var/entry
+ /// The maximum length for text entry
+ var/max_length
+ /// The prompt's body, if any, of the TGUI window.
+ var/message
+ /// Multiline input for larger input boxes.
+ var/multiline
+ /// The time at which the text input was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the text input, after which the window will close and delete itself.
+ var/timeout
+ /// The title of the TGUI window
+ var/title
+ /// The TGUI UI state that will be returned in ui_state(). Default: always_state
+ var/datum/tgui_state/state
+
+/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout, tgui_state)
+ src.default = default
+ src.encode = encode
+ src.max_length = max_length
+ src.message = message
+ src.multiline = multiline
+ src.title = title
+ src.state = tgui_state
+
+ if(timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_input_text/Destroy(force)
+ SStgui.close_uis(src)
+ state = null
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_input_text's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_input_text/proc/wait()
+ while(!entry && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_input_text/tgui_state(mob/user)
+ return state
+
+/datum/tgui_input_text/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "TextInputModal")
+ ui.open()
+
+/datum/tgui_input_text/tgui_close(mob/user)
+ closed = TRUE
+
+/datum/tgui_input_text/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["max_length"] = max_length
+ data["message"] = message
+ data["multiline"] = multiline
+ data["placeholder"] = default // Default is a reserved keyword
+ data["large_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_large)
+ data["swapped_buttons"] = user.get_preference_value(/datum/client_preference/tgui_input_swap)
+ data["title"] = title
+ return data
+
+/datum/tgui_input_text/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
+
+/datum/tgui_input_text/tgui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("submit")
+ if(!max_length)
+ return
+ if(length_char(params["entry"]) > max_length)
+ CRASH("[usr] typed a text string longer than the max length")
+ if(encode && (length_char(html_encode(params["entry"])) > max_length))
+ to_chat(usr, "Your message was clipped due to special character usage.")
+ set_entry(params["entry"])
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/**
+ * Sets the return value for the tgui text proc.
+ * If html encoding is enabled, the text will be encoded.
+ * This can sometimes result in a string that is longer than the max length.
+ * If the string is longer than the max length, it will be clipped.
+ */
+/datum/tgui_input_text/proc/set_entry(entry)
+ if(isnull(entry))
+ return
+
+ var/converted_entry = encode ? html_encode(entry) : entry
+ src.entry = trim(converted_entry, max_length)
diff --git a/code/modules/tgui/tgui_panel/audio.dm b/code/modules/tgui/tgui_panel/audio.dm
new file mode 100644
index 0000000000000..da52b2ace3de9
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/audio.dm
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/// Admin music volume, from 0 to 1.
+/client/var/admin_music_volume = 1
+
+/**
+ * public
+ *
+ * Sends music data to the browser.
+ *
+ * Optional settings:
+ * - pitch: the playback rate
+ * - start: the start time of the sound
+ * - end: when the musics stops playing
+ *
+ * required url string Must be an https URL.
+ * optional extra_data list Optional settings.
+ */
+/datum/tgui_panel/proc/play_music(url, extra_data)
+ if(!is_ready())
+ return
+ var/list/payload = list()
+ if(length(extra_data) > 0)
+ for(var/key in extra_data)
+ payload[key] = extra_data[key]
+ payload["url"] = url
+ window.send_message("audio/playMusic", payload)
+
+/**
+ * public
+ *
+ * Stops playing music through the browser.
+ */
+/datum/tgui_panel/proc/stop_music()
+ if(!is_ready())
+ return
+ window.send_message("audio/stopMusic")
diff --git a/code/modules/tgui/tgui_panel/telemetry.dm b/code/modules/tgui/tgui_panel/telemetry.dm
new file mode 100644
index 0000000000000..442315fb81725
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/telemetry.dm
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * Maximum number of connection records allowed to analyze.
+ * Should match the value set in the browser.
+ */
+#define TGUI_TELEMETRY_MAX_CONNECTIONS 5
+
+/**
+ * Maximum time allocated for sending a telemetry packet.
+ */
+#define TGUI_TELEMETRY_RESPONSE_WINDOW 30 SECONDS
+
+/// Time of telemetry request
+/datum/tgui_panel/var/telemetry_requested_at
+/// Time of telemetry analysis completion
+/datum/tgui_panel/var/telemetry_analyzed_at
+/// List of previous client connections
+/datum/tgui_panel/var/list/telemetry_connections
+
+/**
+ * private
+ *
+ * Requests some telemetry from the client.
+ */
+/datum/tgui_panel/proc/request_telemetry()
+ telemetry_requested_at = world.time
+ telemetry_analyzed_at = null
+ window.send_message("telemetry/request", list(
+ "limits" = list(
+ "connections" = TGUI_TELEMETRY_MAX_CONNECTIONS,
+ ),
+ ))
+
+/**
+ * private
+ *
+ * Analyzes a telemetry packet.
+ *
+ * Is currently only useful for detecting ban evasion attempts.
+ */
+/datum/tgui_panel/proc/analyze_telemetry(payload)
+ if(world.time > telemetry_requested_at + TGUI_TELEMETRY_RESPONSE_WINDOW)
+ message_admins("[key_name(client)] sent telemetry outside of the allocated time window.")
+ return
+ if(telemetry_analyzed_at)
+ message_admins("[key_name(client)] sent telemetry more than once.")
+ return
+ telemetry_analyzed_at = world.time
+ if(!payload)
+ return
+ telemetry_connections = payload["connections"]
+ var/len = length(telemetry_connections)
+ if(len == 0)
+ return
+ if(len > TGUI_TELEMETRY_MAX_CONNECTIONS)
+ message_admins("[key_name(client)] was kicked for sending a huge telemetry payload")
+ qdel(client)
+ return
+ var/list/found
+ for(var/i in 1 to len)
+ if(QDELETED(client))
+ // He got cleaned up before we were done
+ return
+ var/list/row = telemetry_connections[i]
+ // Check for a malformed history object
+ if(!row || length(row) < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
+ return
+ if(world.IsBanned(row["ckey"], row["address"], row["computer_id"]))
+ found = row
+ break
+ CHECK_TICK
+ // This fucker has a history of playing on a banned account.
+ if(found)
+ var/msg = "[key_name(client)] has a banned account in connection history! (Matched: [found["ckey"]], [found["address"]], [found["computer_id"]])"
+ message_admins(msg)
+ log_admin(msg)
diff --git a/code/modules/tgui/tgui_panel/tgui_panel.dm b/code/modules/tgui/tgui_panel/tgui_panel.dm
new file mode 100644
index 0000000000000..7ba765469d12c
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/tgui_panel.dm
@@ -0,0 +1,95 @@
+/*!
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * tgui_panel datum
+ * Hosts tgchat and other nice features.
+ */
+/datum/tgui_panel
+ var/client/client
+ var/datum/tgui_window/window
+ var/broken = FALSE
+ var/initialized_at
+
+/datum/tgui_panel/New(client/client, id)
+ src.client = client
+ window = new(client, id)
+ window.subscribe(src, PROC_REF(on_message))
+
+/datum/tgui_panel/Del()
+ window.unsubscribe(src)
+ window.close()
+ return ..()
+
+/**
+ * public
+ *
+ * TRUE if panel is initialized and ready to receive messages.
+ */
+/datum/tgui_panel/proc/is_ready()
+ return !broken && window.is_ready()
+
+/**
+ * public
+ *
+ * Initializes tgui panel.
+ */
+/datum/tgui_panel/proc/initialize(force = FALSE)
+ set waitfor = FALSE
+ // Minimal sleep to defer initialization to after client constructor
+ sleep(1)
+ initialized_at = world.time
+ // Perform a clean initialization
+ window.initialize(
+ strict_mode = TRUE,
+ assets = list(
+ get_asset_datum(/datum/asset/simple/tgui_panel),
+ ))
+ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
+ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
+ // Other setup
+ addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS)
+
+/**
+ * private
+ *
+ * Called when initialization has timed out.
+ */
+/datum/tgui_panel/proc/on_initialize_timed_out()
+ // Currently does nothing but sending a message to old chat.
+ to_target(client, "Failed to load fancy chat, click HERE to attempt to reload it.")
+
+/**
+ * private
+ *
+ * Callback for handling incoming tgui messages.
+ */
+/datum/tgui_panel/proc/on_message(type, payload)
+ if(type == "ready")
+ broken = FALSE
+ window.send_message("update", list(
+ "config" = list(
+ "client" = list(
+ "ckey" = client.ckey,
+ "address" = client.address,
+ "computer_id" = client.computer_id,
+ ),
+ "window" = list(
+ "fancy" = FALSE,
+ "locked" = FALSE,
+ ),
+ ),
+ ))
+ return TRUE
+ if(type == "audio/setAdminMusicVolume")
+ client.admin_music_volume = payload["volume"]
+ return TRUE
+/**
+ * public
+ *
+ * Sends a round restart notification.
+ */
+/datum/tgui_panel/proc/send_roundrestart()
+ window.send_message("roundrestart")
diff --git a/code/modules/tgui/tgui_panel/tgui_panel_external.dm b/code/modules/tgui/tgui_panel/tgui_panel_external.dm
new file mode 100644
index 0000000000000..95387debeefea
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/tgui_panel_external.dm
@@ -0,0 +1,56 @@
+/*!
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/client/var/datum/tgui_panel/tgui_panel
+
+/**
+ * tgui panel / chat troubleshooting verb
+ */
+/client/verb/fix_tgui_panel()
+ set name = "Fix chat"
+ set category = "Special Verbs"
+ var/action
+ log_tgui(src, "Started fixing.")
+
+ nuke_chat()
+
+ // Failed to fix, using tgalert as fallback
+ action = alert(src, "Did that work?", "Fix chat", "Yes", "No, switch to old ui")
+ if(action == "No, switch to old ui")
+ winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
+ winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ log_tgui(src, "Failed to fix.")
+
+/client/proc/nuke_chat()
+ // Catch all solution (kick the whole thing in the pants)
+ winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
+ winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ if(!tgui_panel || !istype(tgui_panel))
+ log_tgui(src, "tgui_panel datum is missing")
+ tgui_panel = new(src)
+ tgui_panel.initialize(force = TRUE)
+ // Force show the panel to see if there are any errors
+ winset(src, "output", "is-disabled=1&is-visible=0")
+ winset(src, "browseroutput", "is-disabled=0;is-visible=1")
+
+/client/verb/refresh_tgui()
+ set name = "Refresh TGUI"
+ set category = "Special Verbs"
+
+ var/choice = alert(usr,
+ "Используйте ТОЛЬКО если у вас проблема с TGUI.\
+ Это интерфейсы с глазом слева-сверху.\
+ В противном случае, вы можете получить белое окно, которое закроется только после перезахода!", "Refresh TGUI", "Refresh", "Cancel")
+ if(choice != "Refresh")
+ return
+ var/refreshed_count = 0
+ for(var/window_id in tgui_windows)
+ var/datum/tgui_window/window = tgui_windows[window_id]
+ if(!window.locked)
+ window.acquire_lock()
+ continue
+ window.reinitialize()
+ refreshed_count++
+ to_chat(usr, SPAN_NOTICE("TGUI окон обновлено - [refreshed_count]. Если у вас появилось белое окно - переподключитесь, или откройте предыдущий TGUI интерфейс."))
diff --git a/code/modules/tgui/tgui_panel/tgui_panel_message.dm b/code/modules/tgui/tgui_panel/tgui_panel_message.dm
new file mode 100644
index 0000000000000..2e9bfe784ec48
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/tgui_panel_message.dm
@@ -0,0 +1,17 @@
+/**
+ * Message-related procs
+ *
+ * Message format (/list):
+ * - type - Message type, must be one of defines in `code/__DEFINES/chat.dm`
+ * - text - Plain message text
+ * - html - HTML message text
+ * - Optional metadata, can be any key/value pair.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/proc/message_to_html(message)
+ // Here it is possible to add a switch statement
+ // to custom-handle various message types.
+ return message["html"] || message["text"]
diff --git a/code/modules/tgui/tgui_panel/to_chat.dm b/code/modules/tgui/tgui_panel/to_chat.dm
new file mode 100644
index 0000000000000..beb0f70be6d0f
--- /dev/null
+++ b/code/modules/tgui/tgui_panel/to_chat.dm
@@ -0,0 +1,77 @@
+/*!
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * Circumvents the message queue and sends the message to the recipient (target) as soon as possible.
+ * trailing_newline, confidential, and handle_whitespace currently have no effect, please fix this in the future or remove the arguments to lower cache!
+ */
+/proc/to_chat_immediate(target, html, type, text, avoid_highlighting = FALSE, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
+ // Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
+ html = "[html]"
+ text = "[text]"
+
+ if(!target)
+ return
+ if(!html && !text)
+ CRASH("Empty or null string in to_chat proc call.")
+ if(target == world)
+ target = GLOB.clients
+
+ // Build a message
+ var/message = list()
+ if(type)
+ message["type"] = type
+ if(text)
+ message["text"] = text
+ if(html)
+ message["html"] = html
+ if(avoid_highlighting)
+ message["avoidHighlighting"] = avoid_highlighting
+
+ // send it immediately
+ SSchat.send_immediate(target, message)
+
+/**
+ * Sends the message to the recipient (target).
+ *
+ * Recommended way to write to_chat calls:
+ * ```
+ * to_chat(client, "You have found [object]", MESSAGE_TYPE_INFO,
+ * ```
+ * Always remember to close spans!
+ * TARGET: Refers to the target of the to_chat message. Valid targets include clients, mobs, and the static world controller
+ * HTML: The Message to be sent to the TARGET. Converted to a string if not already one in this function
+ * TYPE: The chat tab that this message will be sent to, a list of all valid types can be found in chat.dm
+ * TEXT: Unused
+ * AVOID_HIGHLIGHTING: Unused
+ * trailing_newline, confidential, and handle_whitespace currently have no effect, please fix this in the future or remove the arguments to lower cache!
+ */
+/proc/to_chat(target, html, type, text, avoid_highlighting, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
+ if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
+ to_chat_immediate(target, html, type, text)
+ return
+
+ // Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
+ html = "[html]"
+ text = "[text]"
+
+ if(!target)
+ return
+ if(!html && !text)
+ CRASH("Empty or null string in to_chat proc call.")
+ if(target == world)
+ target = GLOB.clients
+
+ // Build a message
+ var/message = list()
+ if(type)
+ message["type"] = type
+ if(text)
+ message["text"] = text
+ if(html)
+ message["html"] = html
+ if(avoid_highlighting)
+ message["avoidHighlighting"] = avoid_highlighting
+ SSchat.queue(target, message)
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
new file mode 100644
index 0000000000000..f2f12250ff8ba
--- /dev/null
+++ b/code/modules/tgui/tgui_window.dm
@@ -0,0 +1,388 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/datum/tgui_window
+ var/id
+ var/client/client
+ var/pooled
+ var/pool_index
+ var/is_browser = FALSE
+ var/status = TGUI_WINDOW_CLOSED
+ var/locked = FALSE
+ var/datum/tgui/locked_by
+ var/datum/subscriber_object
+ var/subscriber_delegate
+ var/fatally_errored = FALSE
+ var/message_queue
+ var/sent_assets = list()
+ // Vars passed to initialize proc (and saved for later)
+ var/initial_strict_mode
+ var/initial_fancy
+ var/initial_assets
+ var/initial_inline_html
+ var/initial_inline_js
+ var/initial_inline_css
+
+/**
+ * public
+ *
+ * Create a new tgui window.
+ *
+ * required client /client
+ * required id string A unique window identifier.
+ */
+/datum/tgui_window/New(client/client, id, pooled = FALSE)
+ src.id = id
+ src.client = client
+ src.client.tgui_windows[id] = src
+ src.pooled = pooled
+ if(pooled)
+ src.pool_index = TGUI_WINDOW_INDEX(id)
+
+
+/**
+ * public
+ *
+ * Initializes the window with a fresh page. Puts window into the "loading"
+ * state. You can begin sending messages right after initializing. Messages
+ * will be put into the queue until the window finishes loading.
+ *
+ * optional strict_mode bool - Enables strict error handling and BSOD.
+ * optional fancy bool - If TRUE and if this is NOT a panel, will hide the window titlebar.
+ * optional assets list - List of assets to load during initialization.
+ * optional inline_html string - Custom HTML to inject.
+ * optional inline_js string - Custom JS to inject.
+ * optional inline_css string - Custom CSS to inject.
+ */
+/datum/tgui_window/proc/initialize(
+ strict_mode = FALSE,
+ fancy = FALSE,
+ assets = list(),
+ inline_html = "",
+ inline_js = "",
+ inline_css = "")
+ #ifdef TGUI_DEBUGGING
+ log_tgui(client, "[id]/initiailize ([src])")
+ #endif
+ if(!client)
+ return
+ src.initial_fancy = fancy
+ src.initial_assets = assets
+ src.initial_inline_html = inline_html
+ src.initial_inline_js = inline_js
+ src.initial_inline_css = inline_css
+ status = TGUI_WINDOW_LOADING
+ fatally_errored = FALSE
+ // Build window options
+ var/options = "file=[id].html;can_minimize=0;auto_format=0;"
+ // Remove titlebar and resize handles for a fancy window
+ if(fancy)
+ options += "titlebar=0;can_resize=0;"
+ else
+ options += "titlebar=1;can_resize=1;"
+ // Generate page html
+ var/html = SStgui.basehtml
+ html = replacetextEx(html, "\[tgui:windowId]", id)
+ html = replacetextEx(html, "\[tgui:strictMode]", strict_mode)
+ // Inject assets
+ var/inline_assets_str = ""
+ for(var/datum/asset/asset in assets)
+ var/mappings = asset.get_url_mappings()
+ for(var/name in mappings)
+ var/url = mappings[name]
+ // Not encoding since asset strings are considered safe
+ if(copytext(name, -4) == ".css")
+ inline_assets_str += "Byond.loadCss('[url]', true);\n"
+ else if(copytext(name, -3) == ".js")
+ inline_assets_str += "Byond.loadJs('[url]', true);\n"
+ asset.send(client)
+ if(length(inline_assets_str))
+ inline_assets_str = "\n"
+ html = replacetextEx(html, "\n", inline_assets_str)
+ // Inject inline HTML
+ if(inline_html)
+ html = replacetextEx(html, "", inline_html)
+ // Inject inline JS
+ if(inline_js)
+ inline_js = ""
+ html = replacetextEx(html, "", inline_js)
+ // Inject inline CSS
+ if(inline_css)
+ inline_css = ""
+ html = replacetextEx(html, "", inline_css)
+ // Open the window
+ show_browser(client, html, "window=[id];[options]")
+ // Detect whether the control is a browser
+ is_browser = winexists(client, id) == "BROWSER"
+ // Instruct the client to signal UI when the window is closed.
+ if(!is_browser)
+ winset(client, id, "on-close=\"uiclose [id]\"")
+
+/**
+ * public
+ *
+ * Reinitializes the panel with previous data used for initialization.
+ */
+/datum/tgui_window/proc/reinitialize()
+ initialize(
+ strict_mode = initial_strict_mode,
+ fancy = initial_fancy,
+ assets = initial_assets,
+ inline_html = initial_inline_html,
+ inline_js = initial_inline_js,
+ inline_css = initial_inline_css)
+ // Resend assets
+ for(var/datum/asset/asset in sent_assets)
+ send_asset(asset)
+
+/**
+ * public
+ *
+ * Checks if the window is ready to receive data.
+ *
+ * return bool
+ */
+/datum/tgui_window/proc/is_ready()
+ return status == TGUI_WINDOW_READY
+
+/**
+ * public
+ *
+ * Checks if the window can be sanely suspended.
+ *
+ * return bool
+ */
+/datum/tgui_window/proc/can_be_suspended()
+ return !fatally_errored \
+ && pooled \
+ && pool_index > 0 \
+ && pool_index <= TGUI_WINDOW_SOFT_LIMIT \
+ && status == TGUI_WINDOW_READY
+
+/**
+ * public
+ *
+ * Acquire the window lock. Pool will not be able to provide this window
+ * to other UIs for the duration of the lock.
+ *
+ * Can be given an optional tgui datum, which will hook its on_message
+ * callback into the message stream.
+ *
+ * optional ui /datum/tgui
+ */
+/datum/tgui_window/proc/acquire_lock(datum/tgui/ui)
+ locked = TRUE
+ locked_by = ui
+
+/**
+ * Release the window lock.
+ */
+/datum/tgui_window/proc/release_lock()
+ // Clean up assets sent by tgui datum which requested the lock
+ if(locked)
+ sent_assets = list()
+ locked = FALSE
+ locked_by = null
+
+/**
+ * public
+ *
+ * Subscribes the datum to consume window messages on a specified proc.
+ *
+ * Note, that this supports only one subscriber, because code for that
+ * is simpler and therefore faster. If necessary, this can be rewritten
+ * to support multiple subscribers.
+ */
+/datum/tgui_window/proc/subscribe(datum/object, delegate)
+ subscriber_object = object
+ subscriber_delegate = delegate
+
+/**
+ * public
+ *
+ * Unsubscribes the datum. Do not forget to call this when cleaning up.
+ */
+/datum/tgui_window/proc/unsubscribe(datum/object)
+ subscriber_object = null
+ subscriber_delegate = null
+
+/**
+ * public
+ *
+ * Close the UI.
+ *
+ * optional can_be_suspended bool
+ */
+/datum/tgui_window/proc/close(can_be_suspended = TRUE, logout = FALSE)
+ if(!client)
+ return
+ if(can_be_suspended && can_be_suspended())
+ #ifdef TGUI_DEBUGGING
+ log_tgui(client, "[id]/close: suspending")
+ #endif
+ status = TGUI_WINDOW_READY
+ send_message("suspend")
+ // You would think that BYOND would null out client or make it stop passing istypes or, y'know, ANYTHING during
+ // logout, but nope! It appears to be perfectly valid to call winset by every means we can measure in Logout,
+ // and yet it causes a bad client runtime. To avoid that happening, we just have to know if we're in Logout or
+ // not.
+ if(!logout && client)
+ winset(client, null, "mapwindow.map.focus=true")
+ return
+ #ifdef TGUI_DEBUGGING
+ log_tgui(client, "[id]/close")
+ #endif
+ release_lock()
+ status = TGUI_WINDOW_CLOSED
+ message_queue = null
+ // Do not close the window to give user some time
+ // to read the error message.
+ if(!fatally_errored)
+ close_browser(client, "window=[id]")
+ if(!logout && client)
+ winset(client, null, "mapwindow.map.focus=true")
+
+/**
+ * public
+ *
+ * Sends a message to tgui window.
+ *
+ * required type string Message type
+ * required payload list Message payload
+ * optional force bool Send regardless of the ready status.
+ */
+/datum/tgui_window/proc/send_message(type, payload, force)
+ if(!client)
+ return
+ var/message = TGUI_CREATE_MESSAGE(type, payload)
+ // Place into queue if window is still loading
+ if(!force && status != TGUI_WINDOW_READY)
+ if(!message_queue)
+ message_queue = list()
+ message_queue += list(message)
+ return
+ to_target(client, output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update"))
+
+/**
+ * public
+ *
+ * Sends a raw payload to tgui window.
+ *
+ * required message string JSON+urlencoded blob to send.
+ * optional force bool Send regardless of the ready status.
+ */
+/datum/tgui_window/proc/send_raw_message(message, force)
+ if(!client)
+ return
+ // Place into queue if window is still loading
+ if(!force && status != TGUI_WINDOW_READY)
+ if(!message_queue)
+ message_queue = list()
+ message_queue += list(message)
+ return
+ to_target(client, output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update"))
+
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ *
+ * return bool - TRUE if any assets had to be sent to the client
+ */
+/datum/tgui_window/proc/send_asset(datum/asset/asset)
+ if(!client || !asset)
+ return
+
+ sent_assets |= list(asset)
+ . = asset.send(client)
+ if(istype(asset, /datum/asset/spritesheet))
+ var/datum/asset/spritesheet/spritesheet = asset
+ send_message("asset/stylesheet", spritesheet.css_filename())
+ send_message("asset/mappings", asset.get_url_mappings())
+
+/**
+ * private
+ *
+ * Sends queued messages if the queue wasn't empty.
+ */
+/datum/tgui_window/proc/flush_message_queue()
+ if(!client || !message_queue)
+ return
+ for(var/message in message_queue)
+ to_target(client, output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update"))
+ message_queue = null
+
+/**
+ * public
+ *
+ * Replaces the inline HTML content.
+ *
+ * required inline_html string HTML to inject
+ */
+/datum/tgui_window/proc/replace_html(inline_html = "")
+ to_target(client, output(url_encode(inline_html), is_browser \
+ ? "[id]:replaceHtml" \
+ : "[id].browser:replaceHtml"))
+
+/**
+ * private
+ *
+ * Callback for handling incoming tgui messages.
+ */
+/datum/tgui_window/proc/on_message(type, payload, href_list)
+ // Status can be READY if user has refreshed the window.
+ if(type == "ready" && status == TGUI_WINDOW_READY)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
+ // Mark this window as fatally errored which prevents it from
+ // being suspended.
+ if(type == "log" && href_list["fatal"])
+ fatally_errored = TRUE
+ // Mark window as ready since we received this message from somewhere
+ if(status != TGUI_WINDOW_READY)
+ status = TGUI_WINDOW_READY
+ flush_message_queue()
+ // Pass message to UI that requested the lock
+ if(locked && locked_by)
+ var/prevent_default = locked_by.on_message(type, payload, href_list)
+ if(prevent_default)
+ return
+ // Pass message to the subscriber
+ else if(subscriber_object)
+ var/prevent_default = call(
+ subscriber_object,
+ subscriber_delegate)(type, payload, href_list)
+ if(prevent_default)
+ return
+ // If not locked, handle these message types
+ switch(type)
+ if("ping")
+ send_message("ping/reply", payload)
+ if("suspend")
+ close(can_be_suspended = TRUE)
+ if("close")
+ close(can_be_suspended = FALSE)
+ if("openLink")
+ to_target(client, link(href_list["url"]))
+ if("cacheReloaded")
+ // Reinitialize
+ initialize(
+ fancy = initial_fancy,
+ assets = initial_assets,
+ inline_html = initial_inline_html,
+ inline_js = initial_inline_js,
+ inline_css = initial_inline_css)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm
new file mode 100644
index 0000000000000..62941b62042e9
--- /dev/null
+++ b/code/modules/tooltip/tooltip.dm
@@ -0,0 +1,117 @@
+/*
+Tooltips v1.1 - 22/10/15
+Developed by Wire (#goonstation on irc.synirc.net)
+- Added support for screen_loc pixel offsets. Should work. Maybe.
+- Added init function to more efficiently send base vars
+Configuration:
+- Set control to the correct skin element (remember to actually place the skin element)
+- Set file to the correct path for the .html file (remember to actually place the html file)
+- Attach the datum to the user client on login, e.g.
+/client/New()
+ src.tooltips = new /datum/tooltip(src)
+Usage:
+- Define mouse event procs on your (probably HUD) object and simply call the show and hide procs respectively:
+/obj/screen/hud
+ MouseEntered(location, control, params)
+ usr.client.tooltip.show(params, title = src.name, content = src.desc)
+ MouseExited()
+ usr.client.tooltip.hide()
+Customization:
+- Theming can be done by passing the theme var to show() and using css in the html file to change the look
+- For your convenience some pre-made themes are included
+Notes:
+- You may have noticed 90% of the work is done via javascript on the client. Gotta save those cycles man.
+- This is entirely untested in any other codebase besides goonstation so I have no idea if it will port nicely. Good luck!
+ - After testing and discussion (Wire, Remie, MrPerson, AnturK) ToolTips are ok and work for /tg/station13
+*/
+
+
+/datum/tooltip
+ var/client/owner
+ var/control = "mainwindow.tooltip"
+ var/showing = FALSE
+ var/queueHide = FALSE
+ var/init = FALSE
+
+
+/datum/tooltip/New(client/C)
+ if (C)
+ owner = C
+ show_browser(owner, file2text('code/modules/tooltip/tooltip.html'), "window=[control]")
+
+ ..()
+
+
+/datum/tooltip/proc/show(atom/movable/thing, params = null, title = null, content = null, theme = "default", special = "none")
+ if (!thing || !params || (!title && !content) || !owner || !isnum(world.icon_size))
+ return FALSE
+ if (!init)
+ //Initialize some vars
+ init = TRUE
+ send_output(owner, list2params(list(world.icon_size, control)), "[control]:tooltip.init")
+
+ showing = TRUE
+
+ if (title && content)
+ title = "
[title]
"
+ content = "
[content]
"
+ else if (title && !content)
+ title = "
[title]
"
+ else if (!title && content)
+ content = "
[content]
"
+
+ // Strip macros from item names
+ title = replacetext(title, "\proper", "")
+ title = replacetext(title, "\improper", "")
+
+ //Make our dumb param object
+ params = {"{ "cursor": "[params]", "screenLoc": "[thing.screen_loc]" }"}
+
+ //Send stuff to the tooltip
+ var/view_size = getviewsize(owner.view)
+ send_output(owner, list2params(list(params, view_size[1] , view_size[2], "[title][content]", theme, special)), "[control]:tooltip.update")
+
+ //If a hide() was hit while we were showing, run hide() again to avoid stuck tooltips
+ showing = FALSE
+ if (queueHide)
+ hide()
+
+ return TRUE
+
+
+/datum/tooltip/proc/hide()
+ queueHide = !!showing
+
+ if (queueHide)
+ addtimer(CALLBACK(src, PROC_REF(do_hide)), 1)
+ else
+ do_hide()
+
+ return TRUE
+
+/datum/tooltip/proc/do_hide()
+ winshow(owner, control, FALSE)
+
+/* TG SPECIFIC CODE */
+
+
+//Open a tooltip for user, at a location based on params
+//Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc)
+//Includes sanity.checks
+/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null,title = "",content = "",theme = "")
+ if(istype(user))
+ if(user.client && user.client.tooltips)
+ var/ui_style = user?.client?.prefs?.tooltip_style
+ if(!theme && ui_style)
+ theme = lowertext(ui_style)
+ if(!theme)
+ theme = "default"
+ user.client.tooltips.show(tip_src, params,title,content,theme)
+
+
+//Arbitrarily close a user's tooltip
+//Includes sanity checks.
+/proc/closeToolTip(mob/user)
+ if(istype(user))
+ if(user.client && user.client.tooltips)
+ user.client.tooltips.hide()
diff --git a/code/modules/tooltip/tooltip.html b/code/modules/tooltip/tooltip.html
new file mode 100644
index 0000000000000..2ed8f5c43ea1e
--- /dev/null
+++ b/code/modules/tooltip/tooltip.html
@@ -0,0 +1,267 @@
+
+
+
+ Tooltip
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/modules/turbolift/turbolift_areas.dm b/code/modules/turbolift/turbolift_areas.dm
index dbf568aec2e84..5c90443676078 100644
--- a/code/modules/turbolift/turbolift_areas.dm
+++ b/code/modules/turbolift/turbolift_areas.dm
@@ -4,6 +4,7 @@
base_turf = /turf/simulated/open
requires_power = 0
sound_env = SMALL_ENCLOSED
+ forced_ambience = list("sound/music/elevatormusic_alt.ogg")
var/lift_floor_label = null
var/lift_floor_name = null
diff --git a/code/modules/turbolift/turbolift_console.dm b/code/modules/turbolift/turbolift_console.dm
index 9a45bd42e409a..f940521963afd 100644
--- a/code/modules/turbolift/turbolift_console.dm
+++ b/code/modules/turbolift/turbolift_console.dm
@@ -1,7 +1,7 @@
// Base type, do not use.
/obj/structure/lift
name = "turbolift control component"
- icon = 'icons/obj/turbolift.dmi'
+ icon = 'icons/obj/structures/turbolift.dmi'
anchored = TRUE
density = FALSE
layer = ABOVE_OBJ_LAYER
@@ -70,6 +70,7 @@
/obj/structure/lift/button/interact(mob/user)
if(!..())
return
+ playsound(src, 'sound/effects/butelev.ogg', 50)
light_up()
pressed(user)
if(floor == lift.current_floor)
@@ -132,6 +133,7 @@
/obj/structure/lift/panel/OnTopic(user, href_list)
if(href_list["move_to_floor"])
lift.queue_move_to(locate(href_list["move_to_floor"]))
+ playsound(src, 'sound/effects/butelev.ogg', 50)
. = TOPIC_REFRESH
if(href_list["open_doors"])
lift.open_doors()
diff --git a/code/modules/turbolift/turbolift_door.dm b/code/modules/turbolift/turbolift_door.dm
index 0247ef43887f0..20b38c710bb99 100644
--- a/code/modules/turbolift/turbolift_door.dm
+++ b/code/modules/turbolift/turbolift_door.dm
@@ -1,5 +1,5 @@
/obj/machinery/door/airlock/lift
- name = "Elevator Door"
+ name = "elevator door"
desc = "Ding."
opacity = 0
autoclose = FALSE
diff --git a/code/modules/turbolift/turbolift_map.dm b/code/modules/turbolift/turbolift_map.dm
index 9cfec734d2e00..1d960824afef0 100644
--- a/code/modules/turbolift/turbolift_map.dm
+++ b/code/modules/turbolift/turbolift_map.dm
@@ -1,7 +1,7 @@
// Map object.
/obj/turbolift_map_holder
name = "turbolift map placeholder"
- icon = 'icons/obj/turbolift_preview_3x3.dmi'
+ icon = 'icons/obj/structures/turbolift_preview_3x3.dmi'
dir = SOUTH // Direction of the holder determines the placement of the lift control panel and doors.
var/depth = 1 // Number of floors to generate, including the initial floor.
var/lift_size_x = 2 // Number of turfs on each axis to generate in addition to the first
@@ -59,7 +59,7 @@
if(NORTH)
- int_panel_x = ux + Floor(lift_size_x/2)
+ int_panel_x = ux + floor(lift_size_x/2)
int_panel_y = uy + 1
ext_panel_x = ux
ext_panel_y = ey + 2
@@ -76,7 +76,7 @@
if(SOUTH)
- int_panel_x = ux + Floor(lift_size_x/2)
+ int_panel_x = ux + floor(lift_size_x/2)
int_panel_y = ey - 1
ext_panel_x = ex
ext_panel_y = uy - 2
@@ -94,7 +94,7 @@
if(EAST)
int_panel_x = ux+1
- int_panel_y = uy + Floor(lift_size_y/2)
+ int_panel_y = uy + floor(lift_size_y/2)
ext_panel_x = ex+2
ext_panel_y = ey
@@ -111,7 +111,7 @@
if(WEST)
int_panel_x = ex-1
- int_panel_y = uy + Floor(lift_size_y/2)
+ int_panel_y = uy + floor(lift_size_y/2)
ext_panel_x = ux-2
ext_panel_y = uy
@@ -166,8 +166,9 @@
var/area_path = areas_to_use[az]
var/area/A = locate(area_path) || new area_path()
- for(var/T in floor_turfs)
- ChangeArea(T, A)
+ for(var/turf/T as anything in floor_turfs)
+ T.change_area(A)
+
cfloor.set_area_ref("\ref[A]")
// Place exterior doors.
@@ -213,6 +214,11 @@
light1.set_dir(SOUTH)
light2.set_dir(NORTH)
+ // SS220 Bloom-Light
+ // Stacked light exposure masks look way too ugly, so we want to avoid it.
+ light1.exposure_icon_state = null
+ light2.exposure_icon_state = null
+
// Update area.
if(az > length(areas_to_use))
log_debug("Insufficient defined areas in turbolift datum, aborting.")
diff --git a/code/modules/turbolift/turbolift_turfs.dm b/code/modules/turbolift/turbolift_turfs.dm
index 631a5b313b51d..dd8a99f4e3487 100644
--- a/code/modules/turbolift/turbolift_turfs.dm
+++ b/code/modules/turbolift/turbolift_turfs.dm
@@ -1,2 +1,2 @@
-/turf/simulated/wall/elevator/New(newloc)
- ..(newloc,MATERIAL_ELEVATORIUM)
+/turf/simulated/wall/elevator/Initialize(mapload, added_to_area_cache)
+ . = ..(mapload, added_to_area_cache, MATERIAL_ELEVATORIUM)
diff --git a/code/modules/unit_tests/_defines.dm b/code/modules/unit_tests/_defines.dm
new file mode 100644
index 0000000000000..8f860365286ce
--- /dev/null
+++ b/code/modules/unit_tests/_defines.dm
@@ -0,0 +1 @@
+#define MAX_UNIT_TEST_RUN_TIME 3 MINUTES
diff --git a/code/unit_tests/_includes.dm b/code/modules/unit_tests/_includes.dm
similarity index 100%
rename from code/unit_tests/_includes.dm
rename to code/modules/unit_tests/_includes.dm
diff --git a/code/unit_tests/_template.dm b/code/modules/unit_tests/_template.dm
similarity index 100%
rename from code/unit_tests/_template.dm
rename to code/modules/unit_tests/_template.dm
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
new file mode 100644
index 0000000000000..d3b2d795611d7
--- /dev/null
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -0,0 +1,194 @@
+/* Unit Tests originally designed by Ccomp5950
+ *
+ * Tests are created to prevent changes that would create bugs or change expected behaviour.
+ * For the most part I think any test can be created that doesn't require a client in a mob or require a game mode other then extended
+ *
+ * The easiest way to make effective tests is to create a "template" if you intend to run the same test over and over and make your actual
+ * tests be a "child object" of those templates. Be sure and name your templates with the word "template" somewhere in var/name.
+ *
+ * The goal is to have all sorts of tests that run and to run them as quickly as possible.
+ *
+ * Tests that require time to run we instead just check back on their results later instead of waiting around in a sleep(1) for each test.
+ * This allows us to finish unit testing quicker since we can start other tests while we're waiting on that one to finish.
+ *
+ * An example of that is listed in mob_tests.dm with the human_breath test. We spawn the mob in space and set the async flag to 1 so that we run the check later.
+ * After 10 life ticks for that mob we check it's oxyloss but while that is going on we've already ran other tests.
+ *
+ * If your test requires a significant amount of time...cheat on the timers. Either speed up the process/life runs or do as we did in the timers for the shuttle
+ * transfers in zas_tests.dm we move a shuttle but instead of waiting 3 minutes we set the travel time to a very low number.
+ *
+ * At the same time, Unit tests are intended to reflect standard usage so avoid changing to much about how stuff is processed.
+ *
+ *
+ * WRITE UNIT TEST TEMPLATES AS GENERIC AS POSSIBLE (makes for easy reusability)
+ *
+ */
+
+var/global/all_unit_tests_passed = 1
+var/global/failed_unit_tests = 0
+var/global/skipped_unit_tests = 0
+var/global/total_unit_tests = 0
+
+// For console out put in Linux/Bash makes the output green or red.
+// Should probably only be used for unit tests since some special folks use winders to host servers.
+// if you want plain output, use dm.sh -DUNIT_TEST -DUNIT_TEST_PLAIN baystation12.dme
+#ifdef UNIT_TEST_PLAIN
+var/global/ascii_esc = ""
+var/global/ascii_red = ""
+var/global/ascii_green = ""
+var/global/ascii_yellow = ""
+var/global/ascii_reset = ""
+#else
+var/global/ascii_esc = ascii2text(27)
+var/global/ascii_red = "[ascii_esc]\[31m"
+var/global/ascii_green = "[ascii_esc]\[32m"
+var/global/ascii_yellow = "[ascii_esc]\[33m"
+var/global/ascii_reset = "[ascii_esc]\[0m"
+#endif
+
+
+// We list these here so we can remove them from the for loop running this.
+// Templates aren't intended to be ran but just serve as a way to create child objects of it with inheritable tests for quick test creation.
+
+/datum/unit_test
+ var/name = "template - should not be ran."
+ var/template // Treat the unit test as a template if its type is the same as the value of this var
+ var/disabled = 0 // If we want to keep a unit test in the codebase but not run it for some reason.
+ var/async = 0 // If the check can be left to do it's own thing, you must define a check_result() proc if you use this.
+ var/reported = 0 // If it's reported a success or failure. Any tests that have not are assumed to be failures.
+ var/why_disabled = "No reason set." // If we disable a unit test we will display why so it reminds us to check back on it later.
+
+ var/safe_landmark
+ var/space_landmark
+
+/datum/unit_test/proc/log_debug(message)
+ log_unit_test("[ascii_yellow]--- DEBUG --- \[[name]\]: [message][ascii_reset]")
+
+/datum/unit_test/proc/log_bad(message)
+ log_unit_test("[ascii_red]\[[name]\]: [message][ascii_reset]")
+
+/datum/unit_test/proc/fail(message)
+ all_unit_tests_passed = 0
+ failed_unit_tests++
+ reported = 1
+ log_unit_test("[ascii_red]!!! FAILURE !!! \[[name]\]: [message][ascii_reset]")
+
+/datum/unit_test/proc/pass(message)
+ reported = 1
+ log_unit_test("[ascii_green]*** SUCCESS *** \[[name]\]: [message][ascii_reset]")
+
+/datum/unit_test/proc/skip(message)
+ skipped_unit_tests++
+ reported = 1
+ log_unit_test("[ascii_yellow]--- SKIPPED --- \[[name]\]: [message][ascii_reset]")
+
+/datum/unit_test/proc/start_test()
+ fail("No test proc - [type]")
+
+/datum/unit_test/proc/check_result()
+ fail("No check results proc - [type]")
+ return 1
+
+/datum/unit_test/proc/get_safe_turf()
+ if(!safe_landmark)
+ for(var/landmark in landmarks_list)
+ if(istype(landmark, /obj/landmark/test/safe_turf))
+ safe_landmark = landmark
+ break
+ return get_turf(safe_landmark)
+
+/datum/unit_test/proc/get_space_turf()
+ if(!space_landmark)
+ for(var/landmark in landmarks_list)
+ if(istype(landmark, /obj/landmark/test/space_turf))
+ space_landmark = landmark
+ break
+ return get_turf(space_landmark)
+
+// Async unit tests will be delayed until the subsystems in this list have fired at least once.
+/datum/unit_test/proc/subsystems_to_await()
+ return list()
+
+/proc/load_unit_test_changes()
+/*
+ //This takes about 60 seconds to run when unit testing and is only used for the ZAS vacume check on The Asteroid.
+ if(config.generate_map != 1)
+ log_unit_test("Overiding Configuration option for Asteroid Generation to ENABLED")
+ config.generate_map = 1 // The default map requires it, the example config doesn't have this enabled.
+ */
+
+/proc/get_test_datums()
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/test in subtypesof(/datum/unit_test))
+ var/datum/unit_test/d = test
+ if(test == initial(d.template))
+ continue
+ . += d
+
+/proc/do_unit_test(datum/unit_test/test, end_time, skip_disabled_tests = TRUE)
+ if(test.disabled && skip_disabled_tests)
+ test.pass("[ascii_red]Check Disabled: [test.why_disabled]")
+ return
+ if(world.time > end_time)
+ test.fail("Unit Tests Ran out of time") // This should never happen, and if it does either fix your unit tests to be faster or if you can make them async checks.
+ return
+ if (isnull(test.start_test())) // Runtimed.
+ test.fail("Test Runtimed")
+ return 1
+
+//For async tests. Returns 1 if done.
+/proc/check_unit_test(datum/unit_test/test, end_time)
+ if(world.time > end_time)
+ test.fail("Unit Tests Ran out of Time")// If we're going to run out of time, most likely it's here. If you can't speed up your unit tests then add time to the timeout at the top.
+ return 1
+ var/result = test.check_result()
+ if(isnull(result))
+ test.fail("Test Runtimed")
+ return 1
+ else if(result)
+ return 1
+
+/proc/unit_test_final_message()
+ var/skipped_message = ""
+ if(skipped_unit_tests)
+ skipped_message = "| \[[skipped_unit_tests]\\[total_unit_tests]\] Unit Tests Skipped "
+ if(all_unit_tests_passed)
+ log_unit_test("[ascii_green]**** All Unit Tests Passed \[[total_unit_tests]\] [skipped_message]****[ascii_reset]")
+ else
+ log_unit_test("[ascii_red]**** \[[failed_unit_tests]\\[total_unit_tests]\] Unit Tests Failed [skipped_message]****[ascii_reset]")
+
+/datum/admins/proc/run_unit_test()
+ set name = "Run Unit Test"
+ set desc = "Runs the selected unit test - Remember to enable Debug Log Messages"
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/list/options = list()
+ var/list/test_datums = get_test_datums()
+ for (var/datum/unit_test/unit_test_type as anything in test_datums)
+ options["[initial(unit_test_type.name)]"] = unit_test_type
+ options = sortAssoc(options)
+ var/datum/unit_test/selected_unit_test_type = input(usr, "Runs the selected unit test - Remember to enable Debug Log Messages", "Run Unit Test", null) as null|anything in options
+ if (!selected_unit_test_type || !options["[selected_unit_test_type]"])
+ return
+ selected_unit_test_type = options["[selected_unit_test_type]"]
+
+ log_and_message_admins("has started the unit test '[initial(selected_unit_test_type.name)]'")
+ var/datum/unit_test/test = new selected_unit_test_type
+ var/end_unit_tests = world.time + MAX_UNIT_TEST_RUN_TIME
+ do_unit_test(test, end_unit_tests, FALSE)
+ if(test.async)
+ while(!check_unit_test(test, end_unit_tests))
+ sleep(20)
+ unit_test_final_message()
+
+/obj/landmark/test/safe_turf
+ name = "safe_turf" // At creation, landmark tags are set to: "landmark*[name]"
+ desc = "A safe turf should be an as large block as possible of livable, passable turfs, preferably at least 3x3 with the marked turf as the center."
+
+/obj/landmark/test/space_turf
+ name = "space_turf"
+ desc = "A space turf should be an as large block as possible of space, preferably at least 3x3 with the marked turf as the center."
diff --git a/code/unit_tests/alt_appearances_test.dm b/code/modules/unit_tests/alt_appearances_test.dm
similarity index 94%
rename from code/unit_tests/alt_appearances_test.dm
rename to code/modules/unit_tests/alt_appearances_test.dm
index 77423b4490066..501bfd16df64e 100644
--- a/code/unit_tests/alt_appearances_test.dm
+++ b/code/modules/unit_tests/alt_appearances_test.dm
@@ -16,13 +16,11 @@
name = "ALT APPEARANCE: Cardborg shall have base backpack variant"
/datum/unit_test/alt_appearance_cardborg_all_icon_states_shall_exist/start_test()
- var/list/existing_icon_states = icon_states('icons/mob/robots.dmi')
var/failed = FALSE
-
for(var/ca_type in subtypesof(/singleton/cardborg_appearance))
var/singleton/cardborg_appearance/ca = ca_type
var/icon_state = initial(ca.icon_state)
- if(!(icon_state in existing_icon_states))
+ if(!ICON_HAS_STATE('icons/mob/robots.dmi', icon_state))
log_unit_test("Icon state [icon_state] is missing.")
failed = TRUE
diff --git a/code/unit_tests/area_tests.dm b/code/modules/unit_tests/area_tests.dm
similarity index 97%
rename from code/unit_tests/area_tests.dm
rename to code/modules/unit_tests/area_tests.dm
index 61c46e9626289..7b09002702459 100644
--- a/code/unit_tests/area_tests.dm
+++ b/code/modules/unit_tests/area_tests.dm
@@ -3,7 +3,7 @@
/datum/unit_test/areas_shall_be_coherent/start_test()
var/incoherent_areas = 0
- for(var/area/A)
+ for(var/area/A as anything in GLOB.areas)
if(!length(A.contents))
continue
if(A.type in GLOB.using_map.area_coherency_test_exempt_areas)
@@ -61,7 +61,7 @@
/datum/unit_test/areas_shall_be_pure/start_test()
var/impure_areas = 0
- for(var/area/A)
+ for(var/area/A as anything in GLOB.areas)
if(!length(A.contents))
continue
if(A.type in GLOB.using_map.area_purity_test_exempt_areas)
diff --git a/code/modules/unit_tests/area_turfs_cache.dm b/code/modules/unit_tests/area_turfs_cache.dm
new file mode 100644
index 0000000000000..7ab12fda346f5
--- /dev/null
+++ b/code/modules/unit_tests/area_turfs_cache.dm
@@ -0,0 +1,53 @@
+/// For the area_contents list unit test
+/// Allows us to know our area without needing to preassign it
+/// Sorry for the mess
+/turf/var/area/in_contents_of
+
+/datum/unit_test/areas_turf_cache_should_be_consistent
+ name = "AREA: Area turf cache should be consistent"
+ async = TRUE
+ var/failed = FALSE
+
+
+/datum/unit_test/areas_turf_cache_should_be_consistent/start_test()
+ invoke_async(src, PROC_REF(run_test))
+ return TRUE
+
+
+/datum/unit_test/areas_turf_cache_should_be_consistent/check_result()
+ return reported
+
+
+/datum/unit_test/areas_turf_cache_should_be_consistent/proc/run_test()
+ PRIVATE_PROC(TRUE)
+
+ for(var/area/area_to_check as anything in GLOB.areas)
+ for(var/turf/turf_to_validate as anything in area_to_check.get_turfs_from_all_z())
+ if(turf_to_validate.in_contents_of)
+ if(turf_to_validate.in_contents_of == area_to_check)
+ log_bad("Found duplicate turf [log_info_line(turf_to_validate)] inside turf cache of [log_info_line(turf_to_validate.in_contents_of)]")
+ else
+ log_bad("Found shared turf [log_info_line(turf_to_validate)] between [log_info_line(area_to_check)] and [log_info_line(turf_to_validate.in_contents_of)]")
+
+ failed = TRUE
+
+ var/area/actual_area = turf_to_validate.loc
+ if(area_to_check != actual_area)
+ log_bad("Found turf [log_info_line(turf_to_validate)] in cache of [log_info_line(area_to_check)] but not in it's contents")
+ failed = TRUE
+
+ turf_to_validate.in_contents_of = area_to_check
+
+ CHECK_TICK
+
+ for(var/turf/turf_to_validate as anything in ALL_TURFS())
+ if(!turf_to_validate.in_contents_of)
+ log_bad("Found turf [log_info_line(turf_to_validate)] in contents of [log_info_line(turf_to_validate.loc)] but not in it's cache")
+ failed = TRUE
+
+ CHECK_TICK
+
+ if(failed)
+ fail("Area turf cache is invalid.")
+ else
+ pass("Area turf cache is valid.")
diff --git a/code/unit_tests/atmospherics_tests.dm b/code/modules/unit_tests/atmospherics_tests.dm
similarity index 96%
rename from code/unit_tests/atmospherics_tests.dm
rename to code/modules/unit_tests/atmospherics_tests.dm
index cae8b552329b0..fa1168e81af91 100644
--- a/code/unit_tests/atmospherics_tests.dm
+++ b/code/modules/unit_tests/atmospherics_tests.dm
@@ -275,13 +275,12 @@
var/list/checked_pipes = list()
var/list/bad_pipelines = list()
for(var/datum/pipeline/P)
- for(var/thing in P.members)
- var/obj/machinery/atmospherics/pipe/pipe = thing
- if(!checked_pipes[thing])
- checked_pipes[thing] = P
+ for(var/obj/machinery/atmospherics/pipe/pipe in P.members)
+ if(!checked_pipes[pipe])
+ checked_pipes[pipe] = P
continue
LAZYDISTINCTADD(bad_pipelines[P], pipe)
- LAZYDISTINCTADD(bad_pipelines[checked_pipes[thing]], pipe) // Missed it the first time; thought it was good.
+ LAZYDISTINCTADD(bad_pipelines[checked_pipes[pipe]], pipe) // Missed it the first time; thought it was good.
if(length(bad_pipelines))
for(var/datum/pipeline/badboy in bad_pipelines)
@@ -299,7 +298,7 @@
/datum/unit_test/atmos_machinery_shall_not_have_conflicting_connections/start_test()
var/fail = FALSE
- for(var/obj/machinery/atmospherics/machine in SSmachines.machinery)
+ for(var/obj/machinery/atmospherics/machine as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics))
for(var/obj/machinery/atmospherics/M in machine.loc)
if(M == machine)
continue
diff --git a/code/unit_tests/cargo_tests.dm b/code/modules/unit_tests/cargo_tests.dm
similarity index 100%
rename from code/unit_tests/cargo_tests.dm
rename to code/modules/unit_tests/cargo_tests.dm
diff --git a/code/modules/unit_tests/closets.dm b/code/modules/unit_tests/closets.dm
new file mode 100644
index 0000000000000..f95f5e1b6f691
--- /dev/null
+++ b/code/modules/unit_tests/closets.dm
@@ -0,0 +1,73 @@
+/datum/unit_test/closet_decal_test
+ name = "CLOSETS: All Closet Appearances Shall Have Sane Values"
+ var/list/check_base_states = list("base", "lock", "light", "open", "interior", "welded", "sparks")
+ var/list/except_appearances = list()
+
+/datum/unit_test/closet_decal_test/start_test()
+
+ var/list/bad_singleton
+ var/list/bad_icon
+ var/list/bad_colour
+ var/list/bad_base_icon
+ var/list/bad_base_state
+ var/list/bad_decal_icon
+ var/list/bad_decal_colour
+ var/list/bad_decal_state
+
+ for(var/check_appearance in typesof(/singleton/closet_appearance)-except_appearances)
+ var/singleton/closet_appearance/closet = GET_SINGLETON(check_appearance)
+ if(!closet)
+ LAZYADD(bad_singleton, "[check_appearance]")
+ continue
+
+ if(!closet.icon)
+ LAZYADD(bad_icon, "[closet.type]")
+ if(!closet.color)
+ LAZYADD(bad_colour, "[closet.type]")
+ if(!closet.base_icon)
+ LAZYADD(bad_base_icon, "[closet.type]")
+ else
+ for(var/thing in check_base_states)
+ if(!ICON_HAS_STATE(closet.base_icon, thing))
+ LAZYADD(bad_base_state, "[closet.type] - [thing] - [closet.base_icon]")
+ if(LAZYLEN(closet.decals) && !closet.decal_icon)
+ LAZYADD(bad_decal_icon, "[closet.type]")
+ else
+ for(var/thing in closet.decals)
+ if(isnull(closet.decals[thing]))
+ LAZYADD(bad_decal_colour, "[check_appearance] - [thing]")
+ if(!ICON_HAS_STATE(closet.decal_icon, thing) && !ICON_HAS_STATE(closet.decal_icon, "[thing]_open") && !ICON_HAS_STATE(closet.decal_icon, "[thing]_closed"))
+ LAZYADD(bad_decal_state, "[check_appearance] - [thing] - [closet.decal_icon]")
+
+ if( \
+ LAZYLEN(bad_singleton) || \
+ LAZYLEN(bad_icon) || \
+ LAZYLEN(bad_colour) || \
+ LAZYLEN(bad_base_icon) || \
+ LAZYLEN(bad_base_state) || \
+ LAZYLEN(bad_decal_icon) || \
+ LAZYLEN(bad_decal_colour) || \
+ LAZYLEN(bad_decal_state) \
+ )
+ var/fail_msg = "Insane closet appearances found: "
+ if(LAZYLEN(bad_singleton))
+ fail_msg += "\nSingleton did not add itself to appropriate global list:\n\t[jointext(bad_icon, "\n\t")]."
+ if(LAZYLEN(bad_icon))
+ fail_msg += "\nNull final icon values:\n\t[jointext(bad_icon, "\n\t")]."
+ if(LAZYLEN(bad_colour))
+ fail_msg += "\nNull color values:\n\t[jointext(bad_colour, "\n\t")]."
+ if(LAZYLEN(bad_base_icon))
+ fail_msg += "\nNull base icon value:\n\t[jointext(bad_base_icon, "\n\t")]."
+ if(LAZYLEN(bad_base_state))
+ fail_msg += "\nMissing state from base icon:\n\t[jointext(bad_base_state, "\n\t")]."
+ if(LAZYLEN(bad_decal_icon))
+ fail_msg += "\nDecal icon not set but decal lists populated:\n\t[jointext(bad_decal_icon, "\n\t")]."
+ if(LAZYLEN(bad_decal_colour))
+ fail_msg += "\nNull color in final decal entry:\n\t[jointext(bad_decal_colour, "\n\t")]."
+ if(LAZYLEN(bad_decal_state))
+ fail_msg += "\nNon-existent decal icon state:\n\t[jointext(bad_decal_state, "\n\t")]."
+
+ fail(fail_msg)
+ else
+ pass("All closet appearances are sane.")
+ return 1
diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm
new file mode 100644
index 0000000000000..eccf4d2d17710
--- /dev/null
+++ b/code/modules/unit_tests/component_tests.dm
@@ -0,0 +1,13 @@
+/datum/unit_test/component_duping/start_test()
+ var/list/bad_dms = list()
+ var/list/bad_dts = list()
+ for(var/t in typesof(/datum/component))
+ var/datum/component/comp = t
+ if(!isnum(initial(comp.dupe_mode)))
+ bad_dms += t
+ var/dupe_type = initial(comp.dupe_type)
+ if(dupe_type && !ispath(dupe_type))
+ bad_dts += t
+ if(length(bad_dms) || length(bad_dts))
+ fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
+ return TRUE
diff --git a/code/unit_tests/culture.dm b/code/modules/unit_tests/culture.dm
similarity index 100%
rename from code/unit_tests/culture.dm
rename to code/modules/unit_tests/culture.dm
diff --git a/code/unit_tests/equipment_tests.dm b/code/modules/unit_tests/equipment_tests.dm
similarity index 100%
rename from code/unit_tests/equipment_tests.dm
rename to code/modules/unit_tests/equipment_tests.dm
diff --git a/code/unit_tests/extension_tests.dm b/code/modules/unit_tests/extension_tests.dm
similarity index 100%
rename from code/unit_tests/extension_tests.dm
rename to code/modules/unit_tests/extension_tests.dm
diff --git a/code/unit_tests/food_tests.dm b/code/modules/unit_tests/food_tests.dm
similarity index 100%
rename from code/unit_tests/food_tests.dm
rename to code/modules/unit_tests/food_tests.dm
diff --git a/code/unit_tests/foundation_tests.dm b/code/modules/unit_tests/foundation_tests.dm
similarity index 100%
rename from code/unit_tests/foundation_tests.dm
rename to code/modules/unit_tests/foundation_tests.dm
diff --git a/code/unit_tests/fusion_plants.dm b/code/modules/unit_tests/fusion_plants.dm
similarity index 94%
rename from code/unit_tests/fusion_plants.dm
rename to code/modules/unit_tests/fusion_plants.dm
index f318530753c89..7cc1ba6142ffd 100644
--- a/code/unit_tests/fusion_plants.dm
+++ b/code/modules/unit_tests/fusion_plants.dm
@@ -4,7 +4,7 @@
/datum/unit_test/fusion_network_test/start_test()
var/list/check_machines
- for(var/thing in SSmachines.machinery)
+ for(var/thing in SSmachines.get_all_machinery())
if(hasvar(thing, "initial_id_tag") && !isnull(thing:initial_id_tag) && has_extension(thing, /datum/extension/local_network_member))
LAZYADD(check_machines, get_extension(thing, /datum/extension/local_network_member))
@@ -24,4 +24,4 @@
else
pass("No fusion devices with IDs on this map.")
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/unit_tests/graph_tests.dm b/code/modules/unit_tests/graph_tests.dm
similarity index 100%
rename from code/unit_tests/graph_tests.dm
rename to code/modules/unit_tests/graph_tests.dm
diff --git a/code/unit_tests/icon_tests.dm b/code/modules/unit_tests/icon_tests.dm
similarity index 79%
rename from code/unit_tests/icon_tests.dm
rename to code/modules/unit_tests/icon_tests.dm
index c78baa7caa72f..6589473289c73 100644
--- a/code/unit_tests/icon_tests.dm
+++ b/code/modules/unit_tests/icon_tests.dm
@@ -11,26 +11,29 @@
/datum/unit_test/icon_test/robots_shall_have_eyes_for_each_state/start_test()
var/missing_states = 0
- var/list/valid_states = icon_states('icons/mob/robots.dmi') + icon_states('icons/mob/robots_drones.dmi') + icon_states('icons/mob/robots_flying.dmi')
+ var/list/valid_icons = list('icons/mob/robots.dmi', 'icons/mob/robots_drones.dmi', 'icons/mob/robots_flying.dmi')
+ var/list/valid_states = ICON_STATES(valid_icons)
- var/list/original_valid_states = valid_states.Copy()
for(var/icon_state in valid_states)
if(icon_state in excepted_icon_states_)
continue
+
if(text_starts_with(icon_state, "eyes-"))
continue
+
if(findtext(icon_state, "openpanel"))
continue
+
var/eye_icon_state = "eyes-[icon_state]"
- if(!(eye_icon_state in valid_states))
+ if(!ANY_ICON_HAS_STATE(valid_icons, eye_icon_state))
log_unit_test("Eye icon state [eye_icon_state] is missing.")
missing_states++
if(missing_states)
fail("[missing_states] eye icon state\s [missing_states == 1 ? "is" : "are"] missing.")
- var/list/difference = uniquemergelist(original_valid_states, valid_states)
+ var/list/difference = uniquemergelist(valid_states, valid_states)
if(length(difference))
- log_unit_test("[ascii_yellow]--- DEBUG --- ICON STATES AT START: " + jointext(original_valid_states, ",") + "[ascii_reset]")
+ log_unit_test("[ascii_yellow]--- DEBUG --- ICON STATES AT START: " + jointext(valid_states, ",") + "[ascii_reset]")
log_unit_test("[ascii_yellow]--- DEBUG --- ICON STATES AT END: " + jointext(valid_states, ",") + "[ascii_reset]")
log_unit_test("[ascii_yellow]--- DEBUG --- UNIQUE TO EACH LIST: " + jointext(difference, ",") + "[ascii_reset]")
else
@@ -47,7 +50,6 @@
)
var/list/failed_sprite_accessories = list()
- var/icon_state_cache = list()
var/duplicates_found = FALSE
for(var/sprite_accessory_main_type in sprite_accessory_subtypes)
@@ -55,7 +57,8 @@
for(var/sprite_accessory_type in subtypesof(sprite_accessory_main_type))
var/failed = FALSE
var/datum/sprite_accessory/sat = sprite_accessory_type
-
+ if (is_abstract(sat))
+ continue
var/sat_name = initial(sat.name)
if(sat_name)
group_by(sprite_accessories_by_name, sat_name, sat)
@@ -65,15 +68,10 @@
var/sat_icon = initial(sat.icon)
if(sat_icon)
- var/sat_icon_states = icon_state_cache[sat_icon]
- if(!sat_icon_states)
- sat_icon_states = icon_states(sat_icon)
- icon_state_cache[sat_icon] = sat_icon_states
-
var/sat_icon_state = initial(sat.icon_state)
if(sat_icon_state)
sat_icon_state = "[sat_icon_state]_s"
- if(!(sat_icon_state in sat_icon_states))
+ if(!ICON_HAS_STATE(sat_icon, sat_icon_state))
failed = TRUE
log_bad("[sat] - \"[sat_icon_state]\" did not exist in '[sat_icon]'.")
else
@@ -100,12 +98,17 @@
name = "ICON STATE - Posters Shall Have Icon States"
/datum/unit_test/icon_test/posters_shall_have_icon_states/start_test()
- var/contraband_icons = icon_states('icons/obj/contraband.dmi')
var/list/invalid_posters = list()
for(var/poster_type in subtypesof(/singleton/poster))
+ if (is_abstract(poster_type))
+ continue
var/singleton/poster/P = GET_SINGLETON(poster_type)
- if(!(P.icon_state in contraband_icons))
+
+ if(!ICON_HAS_STATE('icons/obj/structures/contraband.dmi', P.icon_state) \
+ && !ICON_HAS_STATE('mods/nyc_posters/icons/nyc_posters.dmi', P.icon_state) \
+ && !ICON_HAS_STATE('mods/tajara/icons/posters.dmi', P.icon_state) \
+ )
invalid_posters += poster_type
if(length(invalid_posters))
@@ -126,14 +129,8 @@
var/singleton/item_modifier/item_modifier = im
for(var/type_setup_type in item_modifier.type_setups)
var/list/type_setup = item_modifier.type_setups[type_setup_type]
- var/list/icon_states = icon_states_by_type[type_setup_type]
-
- if(!icon_states)
- var/obj/item/I = type_setup_type
- icon_states = icon_states(initial(I.icon))
- LAZYSET(icon_states_by_type, type_setup_type, icon_states)
-
- if(!(type_setup["icon_state"] in icon_states))
+ var/obj/item/item_type = type_setup_type
+ if(!ICON_HAS_STATE(initial(item_type.icon), type_setup["icon_state"]))
bad_modifiers += type_setup_type
if(length(bad_modifiers))
@@ -146,19 +143,13 @@
name = "ICON STATE - Random Spawners Shall Have Icon States"
/datum/unit_test/icon_test/random_spawners_shall_have_icon_states/start_test()
- var/states_per_icon = list()
var/list/invalid_spawners = list()
for(var/random_type in typesof(/obj/random))
var/obj/random/R = random_type
var/icon = initial(R.icon)
var/icon_state = initial(R.icon_state) || ""
- var/icon_states = states_per_icon[icon]
- if(!icon_states)
- icon_states = icon_states(icon)
- states_per_icon[icon] = icon_states
-
- if(!(icon_state in icon_states))
+ if(!ICON_HAS_STATE(icon, icon_state))
invalid_spawners += random_type
if(length(invalid_spawners))
diff --git a/code/unit_tests/integrated_circuits.dm b/code/modules/unit_tests/integrated_circuits.dm
similarity index 100%
rename from code/unit_tests/integrated_circuits.dm
rename to code/modules/unit_tests/integrated_circuits.dm
diff --git a/code/unit_tests/job_tests.dm b/code/modules/unit_tests/job_tests.dm
similarity index 90%
rename from code/unit_tests/job_tests.dm
rename to code/modules/unit_tests/job_tests.dm
index e5fd05cee043f..b57361303867a 100644
--- a/code/unit_tests/job_tests.dm
+++ b/code/modules/unit_tests/job_tests.dm
@@ -24,24 +24,22 @@
var/failed_jobs = 0
var/failed_sanity_checks = 0
- var/job_huds = icon_states(GLOB.using_map.id_hud_icons)
-
- if(!("" in job_huds))
+ if(!ICON_HAS_STATE(GLOB.using_map.id_hud_icons, ""))
log_bad("Sanity Check - Missing default/unnamed HUD icon")
failed_sanity_checks++
- if(!("hudunknown" in job_huds))
+ if(!ICON_HAS_STATE(GLOB.using_map.id_hud_icons, "hudunknown"))
log_bad("Sanity Check - Missing HUD icon: hudunknown")
failed_sanity_checks++
- if(!("hudcentcom" in job_huds))
+ if(!ICON_HAS_STATE(GLOB.using_map.id_hud_icons, "hudcentcom"))
log_bad("Sanity Check - Missing HUD icon: hudcentcom")
failed_sanity_checks++
for(var/job_name in SSjobs.titles_to_datums)
var/datum/job/J = SSjobs.titles_to_datums[job_name]
var/hud_icon_state = J.hud_icon
- if(!(hud_icon_state in job_huds))
+ if(!ICON_HAS_STATE(GLOB.using_map.id_hud_icons, hud_icon_state))
log_bad("[J.title] - Missing HUD icon: [hud_icon_state]")
failed_jobs++
diff --git a/code/unit_tests/loadout_tests.dm b/code/modules/unit_tests/loadout_tests.dm
similarity index 97%
rename from code/unit_tests/loadout_tests.dm
rename to code/modules/unit_tests/loadout_tests.dm
index 463c59ddc856b..7ad96c60b30b8 100644
--- a/code/unit_tests/loadout_tests.dm
+++ b/code/modules/unit_tests/loadout_tests.dm
@@ -46,7 +46,7 @@
var/obj/O = G.path
if(ispath(G.path, /obj))
O = new G.path()
- if(!(O.icon_state in icon_states(O.icon)))
+ if(!ICON_HAS_STATE(O.icon, O.icon_state))
log_unit_test("[G] - [G.path]: Did not find the icon state '[O.icon_state]' in the icon '[O.icon]'.")
failed = TRUE
qdel(O)
@@ -101,8 +101,7 @@
/proc/type_has_valid_icon_state(atom/type)
var/atom/A = type
- return (initial(A.icon_state) in icon_states(initial(A.icon)))
-
+ return ICON_HAS_STATE(initial(A.icon), initial(A.icon_state))
/datum/unit_test/loadout_custom_setup_tweaks_shall_have_valid_procs
name = "LOADOUT: Custom setup tweaks shall have valid procs"
diff --git a/code/unit_tests/machine_tests.dm b/code/modules/unit_tests/machine_tests.dm
similarity index 89%
rename from code/unit_tests/machine_tests.dm
rename to code/modules/unit_tests/machine_tests.dm
index 613f32a596697..a205088e93211 100644
--- a/code/unit_tests/machine_tests.dm
+++ b/code/modules/unit_tests/machine_tests.dm
@@ -4,8 +4,7 @@
/datum/unit_test/machines_shall_obey_part_maximum/start_test()
var/failed = list()
var/passed = list()
- for(var/thing in SSmachines.machinery)
- var/obj/machinery/machine = thing
+ for(var/obj/machinery/machine as anything in SSmachines.get_all_machinery())
if(passed[machine.type] || failed[machine.type])
continue
for(var/path in machine.maximum_component_parts)
@@ -27,8 +26,7 @@
/datum/unit_test/machines_with_circuits_shall_have_construct_states/start_test()
var/failed = list()
var/passed = list()
- for(var/thing in SSmachines.machinery)
- var/obj/machinery/machine = thing
+ for(var/obj/machinery/machine in SSmachines.get_all_machinery())
if(passed[machine.type] || failed[machine.type])
continue
var/path = machine.base_type || machine.type
@@ -50,8 +48,7 @@
/datum/unit_test/machine_construct_states_shall_be_valid/start_test()
var/failed = list()
- for(var/thing in SSmachines.machinery)
- var/obj/machinery/machine = thing
+ for(var/obj/machinery/machine as anything in SSmachines.get_all_machinery())
if(failed[machine.type])
continue
if(!machine.construct_state)
@@ -75,7 +72,7 @@
var/failed = list()
for(var/thing in typesof(/obj/machinery/fabricator))
var/obj/machinery/fabricator/fab = new thing
- for(var/datum/fabricator_recipe/recipe in SSfabrication.get_recipes(fab.fabricator_class))
+ for(var/singleton/fabricator_recipe/recipe as anything in SSfabrication.get_recipes(fab.fabricator_class))
for(var/mat in recipe.resources)
if(isnull(fab.storage_capacity[mat]))
log_bad("[fab.name] ([fab.type]) could not print [recipe.name] due to lacking [mat].")
@@ -86,4 +83,4 @@
fail("One or more fabricators could not produce an associated recipe.")
else
pass("All fabricators could produce their associated recipes.")
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/unit_tests/map_tests.dm b/code/modules/unit_tests/map_tests.dm
similarity index 92%
rename from code/unit_tests/map_tests.dm
rename to code/modules/unit_tests/map_tests.dm
index f087c372d559f..16929515fdde8 100644
--- a/code/unit_tests/map_tests.dm
+++ b/code/modules/unit_tests/map_tests.dm
@@ -18,7 +18,7 @@
var/list/bad_areas = list()
var/area_test_count = 0
- for(var/area/A in world)
+ for(var/area/A as anything in GLOB.areas)
if(!A.z)
continue
if(!isPlayerLevel(A.z))
@@ -78,7 +78,7 @@
/datum/unit_test/air_alarm_connectivity/check_result()
var/failed = FALSE
- for(var/area/A in world)
+ for(var/area/A as anything in GLOB.areas)
if(!A.z)
continue
if(!isPlayerLevel(A.z))
@@ -152,9 +152,10 @@
if(C.icon_state != expected_icon_state)
bad_cables |= C
log_bad("[log_info_line(C)] has an invalid icon state. Expected [expected_icon_state], was [C.icon_state]")
- if(!(C.icon_state in icon_states(C.icon)))
+
+ if(!ICON_HAS_STATE(C.icon, C.icon_state))
bad_cables |= C
- log_bad("[log_info_line(C)] has an non-existing icon state.")
+ log_bad("[log_info_line(C)] has an non-existing icon state [C.icon_state].")
if(length(bad_cables))
fail("Found [length(bad_cables)] cable\s with an unexpected icon state.")
@@ -347,14 +348,14 @@
var/space_landmarks = 0
for(var/lm in landmarks_list)
- var/obj/effect/landmark/landmark = lm
- if(istype(landmark, /obj/effect/landmark/test/safe_turf))
+ var/obj/landmark/landmark = lm
+ if(istype(landmark, /obj/landmark/test/safe_turf))
log_debug("Safe landmark found: [log_info_line(landmark)]")
safe_landmarks++
- else if(istype(landmark, /obj/effect/landmark/test/space_turf))
+ else if(istype(landmark, /obj/landmark/test/space_turf))
log_debug("Space landmark found: [log_info_line(landmark)]")
space_landmarks++
- else if(istype(landmark, /obj/effect/landmark/test))
+ else if(istype(landmark, /obj/landmark/test))
log_debug("Test landmark with unknown tag found: [log_info_line(landmark)]")
if(safe_landmarks != 1 || space_landmarks != 1)
@@ -376,13 +377,14 @@
/datum/unit_test/cryopod_comp_check/start_test()
var/pass = TRUE
- for(var/obj/machinery/cryopod/C in SSmachines.machinery)
+ for(var/obj/machinery/cryopod/C as anything in SSmachines.get_machinery_of_type(/obj/machinery/cryopod))
if(!C.control_computer)
log_bad("[get_area(C)] lacks a cryopod control computer while holding a cryopod.")
pass = FALSE
- for(var/obj/machinery/computer/cryopod/C in SSmachines.machinery)
- if(!(locate(/obj/machinery/cryopod) in get_area(C)))
+ for(var/obj/machinery/computer/cryopod/C as anything in SSmachines.get_machinery_of_type(/obj/machinery/computer/cryopod))
+ var/area/cryopod_area = get_area(C)
+ if(!(locate(/obj/machinery/cryopod) in cryopod_area.machinery_list))
log_bad("[get_area(C)] lacks a cryopod while holding a control computer.")
pass = FALSE
@@ -401,7 +403,7 @@
/datum/unit_test/camera_nil_c_tag_check/start_test()
var/pass = TRUE
- for(var/obj/machinery/camera/C in world)
+ for(var/obj/machinery/camera/C as anything in SSmachines.get_machinery_of_type(/obj/machinery/camera))
if(!C.c_tag)
log_bad("Following camera does not have a c_tag set: [log_info_line(C)]")
pass = FALSE
@@ -422,7 +424,7 @@
var/cameras_by_ctag = list()
var/checked_cameras = 0
- for(var/obj/machinery/camera/C in world)
+ for(var/obj/machinery/camera/C as anything in SSmachines.get_machinery_of_type(/obj/machinery/camera))
if(!C.c_tag)
continue
checked_cameras++
@@ -512,7 +514,7 @@
/datum/unit_test/simple_pipes_shall_not_face_north_or_west/start_test()
var/failures = 0
- for(var/obj/machinery/atmospherics/pipe/simple/pipe in world) // Pipes are removed from the SSmachines list during init.
+ for(var/obj/machinery/atmospherics/pipe/simple/pipe as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics/pipe/simple)) // Pipes are removed from the SSmachines list during init.
if(!istype(pipe, /obj/machinery/atmospherics/pipe/simple/hidden) && !istype(pipe, /obj/machinery/atmospherics/pipe/simple/visible))
continue
if(pipe.dir == NORTH || pipe.dir == WEST)
@@ -532,9 +534,9 @@
/datum/unit_test/shutoff_valves_shall_connect_to_two_different_pipe_networks/start_test()
var/failures = 0
- for(var/obj/machinery/atmospherics/valve/shutoff/SV in SSmachines.machinery)
+ for(var/obj/machinery/atmospherics/valve/shutoff/SV as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics/valve/shutoff))
SV.close()
- for(var/obj/machinery/atmospherics/valve/shutoff/SV in SSmachines.machinery)
+ for(var/obj/machinery/atmospherics/valve/shutoff/SV as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics/valve/shutoff))
if(SV.network_node1 == SV.network_node2)
log_bad("Following shutoff valve does not connect to two different pipe networks: [log_info_line(SV)]")
failures++
@@ -552,7 +554,7 @@
/datum/unit_test/station_pipes_shall_not_leak/start_test()
var/failures = 0
- for(var/obj/machinery/atmospherics/pipe/P in world)
+ for(var/obj/machinery/atmospherics/pipe/P as anything in SSmachines.get_machinery_of_type(/obj/machinery/atmospherics/pipe))
if(P.leaking && isStationLevel(P.z))
failures++
log_bad("Following pipe is leaking: [log_info_line(P)]")
@@ -570,7 +572,7 @@
/datum/unit_test/station_power_terminals_shall_be_wired/start_test()
var/failures = 0
- for(var/obj/machinery/power/terminal/term in SSmachines.machinery)
+ for(var/obj/machinery/power/terminal/term as anything in SSmachines.get_machinery_of_type(/obj/machinery/power/terminal))
var/turf/T = get_turf(term)
if(!T)
failures++
@@ -663,10 +665,13 @@
target_turf = get_step(C, dir)
var/connected = FALSE
- for(var/obj/structure/cable/revC in target_turf)
- if(revC.d1 == rev_dir || revC.d2 == rev_dir)
- connected = TRUE
- break
+ if (dir != UP & dir != DOWN && (locate(/obj/machinery/power/breakerbox) in target_turf))
+ connected = TRUE
+ else
+ for(var/obj/structure/cable/revC in target_turf)
+ if(revC.d1 == rev_dir || revC.d2 == rev_dir)
+ connected = TRUE
+ break
if(!connected)
log_bad("Disconnected wire: [dir2text(dir)] - [log_info_line(C)]")
@@ -831,18 +836,20 @@
/datum/unit_test/doors_shall_be_on_appropriate_turfs/start_test()
var/bad_doors = 0
- for(var/obj/machinery/door/D in world)
- if(QDELETED(D))
+ for(var/obj/machinery/door/door_to_check as anything in SSmachines.get_machinery_of_type(/obj/machinery/door))
+ if(QDELETED(door_to_check))
continue
- if(!istype(D.loc, /turf))
+
+ if(!isturf(door_to_check.loc))
bad_doors++
- log_bad("Invalid door turf: [log_info_line(D.loc)]]")
+ log_bad("Invalid door turf: [log_info_line(door_to_check.loc)]]")
else
var/is_bad_door = FALSE
- for(var/L in D.locs)
- if(istype(L, /turf/simulated/open) || isspaceturf(L))
+ for(var/door_loc in door_to_check.locs)
+ if(istype(door_loc, /turf/simulated/open) || isspaceturf(door_loc))
is_bad_door = TRUE
- log_bad("Invalid door turf: [log_info_line(L)]]")
+ log_bad("Invalid door turf: [log_info_line(door_loc)]]")
+
if(is_bad_door)
bad_doors++
diff --git a/code/unit_tests/mob_tests.dm b/code/modules/unit_tests/mob_tests.dm
similarity index 99%
rename from code/unit_tests/mob_tests.dm
rename to code/modules/unit_tests/mob_tests.dm
index fa00abd1ca242..3fb262da75ab8 100644
--- a/code/unit_tests/mob_tests.dm
+++ b/code/modules/unit_tests/mob_tests.dm
@@ -65,6 +65,7 @@
var/global/default_mobloc = null
/proc/create_test_mob_with_mind(turf/mobloc = null, mobtype = /mob/living/carbon/human)
+ RETURN_TYPE(/list)
var/list/test_result = list("result" = FAILURE, "msg" = "", "mobref" = null)
if(isnull(mobloc))
@@ -490,15 +491,13 @@ var/global/default_mobloc = null
fail("[icon_file] is not a valid icon file.")
return 1
- var/list/valid_states = icon_states(icon_file)
-
- if(!length(valid_states))
- return 1
+ if(ICON_IS_EMPTY(icon_file))
+ return TRUE
for(var/i = 1 to length(SSrobots.all_module_names))
var/modname = lowertext(SSrobots.all_module_names[i])
var/bad_msg = "[ascii_red]--------------- [modname]"
- if(!(modname in valid_states))
+ if(!ICON_HAS_STATE(icon_file, modname))
log_unit_test("[bad_msg] does not contain a valid icon state in [icon_file][ascii_reset]")
failed=1
@@ -541,7 +540,7 @@ var/global/default_mobloc = null
for(var/base in S.base_skin_colours)
for(var/gen in gender_test)
- if(!("[icon_name][gen][S.base_skin_colours[base]]" in icon_states(S.icobase)))
+ if(!ICON_HAS_STATE(S.icobase, "[icon_name][gen][S.base_skin_colours[base]]"))
to_fail = TRUE
log_debug("[S.name] has missing icon: [icon_name][gen][S.base_skin_colours[base]] for base [base] and limb tag [tag].")
if(to_fail)
diff --git a/code/unit_tests/movement_tests.dm b/code/modules/unit_tests/movement_tests.dm
similarity index 100%
rename from code/unit_tests/movement_tests.dm
rename to code/modules/unit_tests/movement_tests.dm
diff --git a/code/unit_tests/music_test.dm b/code/modules/unit_tests/music_test.dm
similarity index 100%
rename from code/unit_tests/music_test.dm
rename to code/modules/unit_tests/music_test.dm
diff --git a/code/unit_tests/observation_tests.dm b/code/modules/unit_tests/observation_tests.dm
similarity index 81%
rename from code/unit_tests/observation_tests.dm
rename to code/modules/unit_tests/observation_tests.dm
index 7e2973bf73f12..e653e36581cda 100644
--- a/code/unit_tests/observation_tests.dm
+++ b/code/modules/unit_tests/observation_tests.dm
@@ -8,8 +8,6 @@
var/list/received_moves
var/list/stored_global_listen_count
- var/list/stored_event_sources_count
- var/list/stored_event_listen_count
/datum/unit_test/observation/start_test()
if(!received_moves)
@@ -20,8 +18,6 @@
GLOB.moved_event.unregister_global(global_listener)
stored_global_listen_count = GLOB.global_listen_count.Copy()
- stored_event_sources_count = GLOB.event_sources_count.Copy()
- stored_event_listen_count = GLOB.event_listen_count.Copy()
sanity_check_events("Pre-Test")
. = conduct_test()
@@ -54,10 +50,6 @@
for(var/entry in (GLOB.global_listen_count - stored_global_listen_count))
fail("[phase]: global_listen_count - Contained [log_info_line(entry)].")
- for(var/entry in (GLOB.event_sources_count - stored_event_sources_count))
- fail("[phase]: event_sources_count - Contained [log_info_line(entry)].")
- for(var/entry in (GLOB.event_listen_count - stored_event_listen_count))
- fail("[phase]: event_listen_count - Contained [log_info_line(entry)].")
/datum/unit_test/observation/proc/conduct_test()
return 0
@@ -78,7 +70,7 @@
var/turf/target = get_step(start, NORTH)
var/obj/O = get_named_instance(/obj, start)
- GLOB.moved_event.register_global(src, /datum/unit_test/observation/proc/receive_move)
+ GLOB.moved_event.register_global(src, TYPE_PROC_REF(/datum/unit_test/observation, receive_move))
O.forceMove(target)
if(length(received_moves) != 1)
@@ -200,51 +192,6 @@
qdel(O)
return 1
-
-/*
-/datum/unit_test/observation/moved_shall_only_trigger_for_recursive_drop
- name = "OBSERVATION: Moved - Shall Only Trigger Once For Recursive Drop"
-
-/datum/unit_test/observation/moved_shall_only_trigger_for_recursive_drop/conduct_test()
- var/turf/T = get_safe_turf()
- var/obj/exosuit/exosuit = get_named_instance(/obj/exosuit, T, "exosuit")
- var/obj/item/wrench/held_item = get_named_instance(/obj/item/wrench, T, "Wrench")
- var/mob/living/carbon/human/dummy/held_mob = get_named_instance(/mob/living/carbon/human/dummy, T, "Held Mob")
- var/mob/living/carbon/human/dummy/holding_mob = get_named_instance(/mob/living/carbon/human/dummy, T, "Holding Mob")
-
- held_mob.mob_size = MOB_SMALL
- held_mob.put_in_active_hand(held_item)
- held_mob.get_scooped(holding_mob)
-
- holding_mob.forceMove(exosuit)
-
- exosuit.occupant = holding_mob
-
- GLOB.moved_event.register(held_item, src, /datum/unit_test/observation/proc/receive_move)
- holding_mob.drop_from_inventory(held_item)
-
- if(length(received_moves) != 1)
- fail("Expected 1 raised moved event, were [length(received_moves)].")
- dump_received_moves()
- return 1
-
- var/list/event = received_moves[1]
- if(event[1] != held_item || event[2] != held_mob || event[3] != exosuit)
- fail("Unexpected move event received. Expected [held_item], was [event[1]]. Expected [held_mob], was [event[2]]. Expected [exosuit], was [event[3]]")
- else if(!(held_item in exosuit.dropped_items))
- fail("Expected \the [held_item] to be in the mechs' dropped item list")
- else
- pass("One one moved event with expected arguments raised.")
-
- GLOB.moved_event.unregister(held_item, src)
- qdel(exosuit)
- qdel(held_item)
- qdel(held_mob)
- qdel(holding_mob)
-
- return 1
-*/
-
/datum/unit_test/observation/moved_shall_not_unregister_recursively_one
name = "OBSERVATION: Moved - Shall Not Unregister Recursively - One"
@@ -300,7 +247,7 @@
var/turf/T = get_safe_turf()
var/obj/O = get_named_instance(/obj, T)
- GLOB.moved_event.register_global(O, /atom/movable/proc/move_to_turf)
+ GLOB.moved_event.register_global(O, TYPE_PROC_REF(/atom/movable, move_to_turf))
qdel(O)
if(null in GLOB.moved_event.global_listeners)
@@ -318,7 +265,7 @@
var/mob/event_source = get_named_instance(/mob, T, "Event Source")
var/mob/listener = get_named_instance(/mob, T, "Event Listener")
- GLOB.moved_event.register(event_source, listener, /atom/movable/proc/recursive_move)
+ GLOB.moved_event.register(event_source, listener, TYPE_PROC_REF(/atom/movable, recursive_move))
qdel(event_source)
if(null in GLOB.moved_event.event_sources)
@@ -337,7 +284,7 @@
var/mob/event_source = get_named_instance(/mob, T, "Event Source")
var/mob/listener = get_named_instance(/mob, T, "Event Listener")
- GLOB.moved_event.register(event_source, listener, /atom/movable/proc/recursive_move)
+ GLOB.moved_event.register(event_source, listener, TYPE_PROC_REF(/atom/movable, recursive_move))
qdel(listener)
var/listeners = GLOB.moved_event.event_sources[event_source]
diff --git a/code/unit_tests/organ_tests.dm b/code/modules/unit_tests/organ_tests.dm
similarity index 100%
rename from code/unit_tests/organ_tests.dm
rename to code/modules/unit_tests/organ_tests.dm
diff --git a/code/modules/unit_tests/overmap_tests.dm b/code/modules/unit_tests/overmap_tests.dm
new file mode 100644
index 0000000000000..cbe5d6e73653e
--- /dev/null
+++ b/code/modules/unit_tests/overmap_tests.dm
@@ -0,0 +1,24 @@
+/datum/unit_test/overmap_test
+ template = /datum/unit_test/overmap_test
+
+/datum/unit_test/overmap_test/New()
+ name = "OVERMAP: " + name
+
+// 513 no longer allows no color or white as a filter color, hence this test
+/datum/unit_test/overmap_test/shall_have_non_white_color
+ name = "Shall have non-white color"
+
+/datum/unit_test/overmap_test/shall_have_non_white_color/start_test()
+ var/list/invalid_overmap_types = list()
+ for(var/omt in subtypesof(/obj/overmap))
+ var/obj/overmap = omt
+ var/color = initial(overmap.color)
+ if(!color || color == COLOR_WHITE)
+ invalid_overmap_types += omt
+
+ if(length(invalid_overmap_types))
+ fail("Following /obj/overmap types types have invalid colors: [english_list(invalid_overmap_types)]")
+ else
+ pass("All /obj/overmap types have a valid color")
+
+ return TRUE
diff --git a/code/unit_tests/override_tests.dm b/code/modules/unit_tests/override_tests.dm
similarity index 100%
rename from code/unit_tests/override_tests.dm
rename to code/modules/unit_tests/override_tests.dm
diff --git a/code/unit_tests/power_tests.dm b/code/modules/unit_tests/power_tests.dm
similarity index 97%
rename from code/unit_tests/power_tests.dm
rename to code/modules/unit_tests/power_tests.dm
index b279777ea50bc..a8564df94aed2 100644
--- a/code/unit_tests/power_tests.dm
+++ b/code/modules/unit_tests/power_tests.dm
@@ -54,7 +54,7 @@
/datum/unit_test/areas_apc_uniqueness/start_test()
var/failure = ""
- for(var/area/A in world)
+ for(var/area/A as anything in GLOB.areas)
var/obj/machinery/power/apc/found_apc = null
for(var/obj/machinery/power/apc/APC in A)
if(!found_apc)
@@ -76,7 +76,7 @@
/datum/unit_test/area_power_tally_accuracy/start_test()
var/failed = FALSE
var/list/channel_names = list("equip", "light", "environ")
- for(var/area/A in world)
+ for(var/area/A as anything in GLOB.areas)
var/list/old_values = list(A.used_equip, A.used_light, A.used_environ)
A.retally_power()
var/list/new_values = list(A.used_equip, A.used_light, A.used_environ)
diff --git a/code/unit_tests/reagent_color_test.dm b/code/modules/unit_tests/reagent_color_test.dm
similarity index 100%
rename from code/unit_tests/reagent_color_test.dm
rename to code/modules/unit_tests/reagent_color_test.dm
diff --git a/code/unit_tests/seed_tests.dm b/code/modules/unit_tests/seed_tests.dm
similarity index 100%
rename from code/unit_tests/seed_tests.dm
rename to code/modules/unit_tests/seed_tests.dm
diff --git a/code/unit_tests/shuttle_tests.dm b/code/modules/unit_tests/shuttle_tests.dm
similarity index 93%
rename from code/unit_tests/shuttle_tests.dm
rename to code/modules/unit_tests/shuttle_tests.dm
index f1efe3f98313b..d89d1d14f79a7 100644
--- a/code/unit_tests/shuttle_tests.dm
+++ b/code/modules/unit_tests/shuttle_tests.dm
@@ -4,7 +4,7 @@
/datum/unit_test/generic_shuttle_landmarks_shall_not_appear_in_restricted_list/start_test()
var/fail = FALSE
- for(var/obj/effect/overmap/visitable/sector in world)
+ for(var/obj/overmap/visitable/sector in world)
var/list/failures = list()
for(var/generic in sector.initial_generic_waypoints)
for(var/shuttle in sector.initial_restricted_waypoints)
@@ -19,4 +19,4 @@
fail("Some sector landmark lists were misconfigured.")
else
pass("All sector landmark lists were configured properly.")
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/unit_tests/submaps.dm b/code/modules/unit_tests/submaps.dm
similarity index 100%
rename from code/unit_tests/submaps.dm
rename to code/modules/unit_tests/submaps.dm
diff --git a/code/unit_tests/subsystem_tests.dm b/code/modules/unit_tests/subsystem_tests.dm
similarity index 97%
rename from code/unit_tests/subsystem_tests.dm
rename to code/modules/unit_tests/subsystem_tests.dm
index e23e459865ecc..61182a37d4c62 100644
--- a/code/unit_tests/subsystem_tests.dm
+++ b/code/modules/unit_tests/subsystem_tests.dm
@@ -2,7 +2,7 @@
name = "SUBSYSTEM - ATOMS: Shall have no bad init calls"
/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls/start_test()
- if(length(SSatoms.BadInitializeCalls))
+ if(length(SSatoms.bad_inits))
log_bad(jointext(SSatoms.InitLog(), null))
fail("[SSatoms] had bad initialization calls.")
else
diff --git a/code/unit_tests/test_obj.dm b/code/modules/unit_tests/test_obj.dm
similarity index 100%
rename from code/unit_tests/test_obj.dm
rename to code/modules/unit_tests/test_obj.dm
diff --git a/code/unit_tests/time_tests.dm b/code/modules/unit_tests/time_tests.dm
similarity index 100%
rename from code/unit_tests/time_tests.dm
rename to code/modules/unit_tests/time_tests.dm
diff --git a/code/unit_tests/trait_tests.dm b/code/modules/unit_tests/trait_tests.dm
similarity index 100%
rename from code/unit_tests/trait_tests.dm
rename to code/modules/unit_tests/trait_tests.dm
diff --git a/code/unit_tests/unique_tests.dm b/code/modules/unit_tests/unique_tests.dm
similarity index 84%
rename from code/unit_tests/unique_tests.dm
rename to code/modules/unit_tests/unique_tests.dm
index 6f85260c74a8c..d6acc5bf84acc 100644
--- a/code/unit_tests/unique_tests.dm
+++ b/code/modules/unit_tests/unique_tests.dm
@@ -1,26 +1,3 @@
-/datum/unit_test/cable_colors_shall_be_unique
- name = "UNIQUENESS: Cable Colors Shall Be Unique"
-
-/datum/unit_test/cable_colors_shall_be_unique/start_test()
- var/list/names = list()
- var/list/colors = list()
-
- var/index = 0
- for(var/color_name in GLOB.possible_cable_colours)
- group_by(names, color_name, index)
- group_by(colors, GLOB.possible_cable_colours[color_name], index)
- index++
-
- var/number_of_issues = number_of_issues(names, "Names")
- number_of_issues += number_of_issues(colors, "Colors")
-
- if(number_of_issues)
- fail("[number_of_issues] issues with cable colors found.")
- else
- pass("All cable colors are unique.")
-
- return 1
-
/datum/unit_test/research_designs_shall_be_unique
name = "UNIQUENESS: Research Designs Shall Be Unique"
@@ -174,6 +151,24 @@
pass("All space suit modifiers have unique names.")
return 1
+// Purpose: /proc/SetupChameleonExtension() attempts to find the best chameleon extension for a given type
+// Having multiple extensions expect the same type can technically lead to inconsistencies between compilations (if the types are moved around, etc.)
+// Can be worked around by, for example, adding a flag that adds/removes a given extension from the list of possible extensions in the proc above
+/datum/unit_test/chameleon_extensions_shall_have_unique_expected_types
+ name = "UNIQUENESS: Chameleon Extensions Shall Have Unique Expected Types"
+
+/datum/unit_test/chameleon_extensions_shall_have_unique_expected_types/start_test()
+ var/list/expected_types_by_extension = list()
+ for (var/datum/extension/chameleon/chameleon_extension_type as anything in typesof(/datum/extension/chameleon))
+ group_by(expected_types_by_extension, initial(chameleon_extension_type.expected_type), chameleon_extension_type)
+
+ var/number_of_issues = number_of_issues(expected_types_by_extension, "Chameleon Extensions - Expected Types")
+ if(number_of_issues)
+ fail("[number_of_issues] duplicate expected type\s found.")
+ else
+ pass("All chameleon extensions have unique expected types.")
+ return 1
+
/datum/unit_test/proc/number_of_issues(list/entries, type, feedback = /singleton/noi_feedback)
var/issues = 0
for(var/key in entries)
diff --git a/code/unit_tests/uplink_tests.dm b/code/modules/unit_tests/uplink_tests.dm
similarity index 100%
rename from code/unit_tests/uplink_tests.dm
rename to code/modules/unit_tests/uplink_tests.dm
diff --git a/code/unit_tests/view_variables_test.dm b/code/modules/unit_tests/view_variables_test.dm
similarity index 100%
rename from code/unit_tests/view_variables_test.dm
rename to code/modules/unit_tests/view_variables_test.dm
diff --git a/code/unit_tests/virtual_mob_tests.dm b/code/modules/unit_tests/virtual_mob_tests.dm
similarity index 92%
rename from code/unit_tests/virtual_mob_tests.dm
rename to code/modules/unit_tests/virtual_mob_tests.dm
index c8d4aea7a9151..7fc0190fb98e5 100644
--- a/code/unit_tests/virtual_mob_tests.dm
+++ b/code/modules/unit_tests/virtual_mob_tests.dm
@@ -96,17 +96,17 @@
. = ..()
/datum/unit_test/virtual/helper/proc/standard_setup()
- mob_one = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/effect/landmark/virtual_spawn/one)), "Test Mob 1")
- mob_two = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/effect/landmark/virtual_spawn/two)), "Test Mob 2")
- mob_three = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/effect/landmark/virtual_spawn/three)), "Test Mob 3")
+ mob_one = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/landmark/virtual_spawn/one)), "Test Mob 1")
+ mob_two = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/landmark/virtual_spawn/two)), "Test Mob 2")
+ mob_three = get_named_instance(/mob/fake_mob, get_turf(locate(/obj/landmark/virtual_spawn/three)), "Test Mob 3")
/datum/unit_test/virtual/helper/proc/standard_cleanup()
QDEL_NULL(mob_one)
QDEL_NULL(mob_two)
QDEL_NULL(mob_three)
-/obj/effect/landmark/virtual_spawn/one
-/obj/effect/landmark/virtual_spawn/two
-/obj/effect/landmark/virtual_spawn/three
+/obj/landmark/virtual_spawn/one
+/obj/landmark/virtual_spawn/two
+/obj/landmark/virtual_spawn/three
#endif
diff --git a/code/unit_tests/zas_tests.dm b/code/modules/unit_tests/zas_tests.dm
similarity index 92%
rename from code/unit_tests/zas_tests.dm
rename to code/modules/unit_tests/zas_tests.dm
index d0f05269c8734..0a02a908ac991 100644
--- a/code/unit_tests/zas_tests.dm
+++ b/code/modules/unit_tests/zas_tests.dm
@@ -42,6 +42,7 @@
// The primary helper proc.
//
/proc/test_air_in_area(test_area, expectation = UT_NORMAL)
+ RETURN_TYPE(/list)
var/test_result = list("result" = FAILURE, "msg" = "")
var/area/A = locate(test_area)
@@ -52,14 +53,23 @@
test_result["result"] = FAILURE
return test_result
+ // Airless areas are skipped
+ if (initial(A.turfs_airless))
+ test_result["result"] = SUCCESS
+ test_result["msg"] = "Area flagged airless. Skipped."
+
var/list/GM_checked = list()
- for(var/turf/simulated/T in A)
+ for(var/turf/simulated/T in get_area_turfs(A))
- if(!istype(T) || isnull(T.zone) || istype(T, /turf/simulated/floor/airless))
+ if(!istype(T) || isnull(T.zone))
continue
if(T.zone.air in GM_checked)
continue
+ var/turf/simulated/floor/floor = T
+ if (istype(floor) && floor.map_airless)
+ continue
+
var/t_msg = "Turf: [T] | Location: [T.x] // [T.y] // [T.z]"
diff --git a/code/unit_tests/~helpers.dm b/code/modules/unit_tests/~helpers.dm
similarity index 100%
rename from code/unit_tests/~helpers.dm
rename to code/modules/unit_tests/~helpers.dm
diff --git a/code/unit_tests/~unit_test_overrides.dm b/code/modules/unit_tests/~unit_test_overrides.dm
similarity index 97%
rename from code/unit_tests/~unit_test_overrides.dm
rename to code/modules/unit_tests/~unit_test_overrides.dm
index cc01656e483b3..ecc0ccc1af6e7 100644
--- a/code/unit_tests/~unit_test_overrides.dm
+++ b/code/modules/unit_tests/~unit_test_overrides.dm
@@ -19,7 +19,7 @@
selection_method = /proc/unit_test_select_heaviest
/obj/random
- spawn_method = /obj/random/proc/unit_test_spawn_item
+ spawn_method = TYPE_PROC_REF(/obj/random, unit_test_spawn_item)
GLOBAL_DATUM(unit_test_last_obj_random_creation, /atom/movable)
/obj/random/proc/unit_test_spawn_item()
diff --git a/code/unit_tests/~unit_test_subsystems.dm b/code/modules/unit_tests/~unit_test_subsystems.dm
similarity index 90%
rename from code/unit_tests/~unit_test_subsystems.dm
rename to code/modules/unit_tests/~unit_test_subsystems.dm
index b78c2c51ceff4..783c934efeadd 100644
--- a/code/unit_tests/~unit_test_subsystems.dm
+++ b/code/modules/unit_tests/~unit_test_subsystems.dm
@@ -5,7 +5,7 @@ SUBSYSTEM_DEF(unit_tests)
name = "Unit Tests"
wait = 2 SECONDS
init_order = SS_INIT_UNIT_TESTS
- runlevels = (RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY)
+ runlevels = RUNLEVELS_PREGAME | RUNLEVELS_GAME
var/list/queue = list()
var/list/async_tests = list()
var/list/current_async
@@ -39,13 +39,8 @@ SUBSYSTEM_DEF(unit_tests)
/datum/controller/subsystem/unit_tests/proc/load_map_templates()
for(var/map_template_name in (SSmapping.map_templates))
var/datum/map_template/map_template = SSmapping.map_templates[map_template_name]
- // Away sites are supposed to be tested separately in the Away Site environment
- if(istype(map_template, /datum/map_template/ruin/away_site))
- report_progress("Skipping template '[map_template]' ([map_template.type]): Is an Away Site")
- continue
-
- if (istype(map_template, /datum/map_template/deepmaint_template))
- report_progress("Skipping template '[map_template]' ([map_template.type]): Is a Deepmaint submap.")
+ if (map_template.skip_main_unit_tests)
+ report_progress("Skipping template '[map_template]' ([map_template.type]): [map_template.skip_main_unit_tests]")
continue
// Suggestion: Do smart things here to squeeze as many templates as possible into the same Z-level
diff --git a/code/unit_tests/~unit_test_types.dm b/code/modules/unit_tests/~unit_test_types.dm
similarity index 100%
rename from code/unit_tests/~unit_test_types.dm
rename to code/modules/unit_tests/~unit_test_types.dm
diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
index d356caa32424e..20cca02484628 100644
--- a/code/modules/ventcrawl/ventcrawl_atmospherics.dm
+++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
@@ -20,7 +20,11 @@
/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
if(user.loc != src || !(direction & initialize_directions)) //can't go in a way we aren't connecting to
return
- ventcrawl_to(user,findConnecting(direction),direction)
+ // [SIERRA-ADD] - SSINPUT
+ direction &= ~(direction & ~initialize_directions)
+ // [SIERRA-ADD]
+ direction = DIR_TO_CARDINAL(direction)
+ ventcrawl_to(user, findConnecting(direction), direction)
/obj/machinery/atmospherics/proc/ventcrawl_to(mob/living/user, obj/machinery/atmospherics/target_move, direction)
if(target_move)
diff --git a/code/modules/webhooks/_webhook.dm b/code/modules/webhooks/_webhook.dm
index 3de9e78f3f99e..0b53cad8a9f0e 100644
--- a/code/modules/webhooks/_webhook.dm
+++ b/code/modules/webhooks/_webhook.dm
@@ -10,7 +10,7 @@
if (!target_url)
return -1
- var/result = call_ext(HTTP_POST_DLL_LOCATION, "send_post_request")(target_url, payload, json_encode(list("Content-Type" = "application/json")))
+ var/result = CALL_EXT(HTTP_POST_DLL_LOCATION, "send_post_request")(target_url, payload, json_encode(list("Content-Type" = "application/json")))
result = json_decode(result)
if (result["error_code"])
diff --git a/code/modules/world_topic/README.MD b/code/modules/world_topic/README.MD
new file mode 100644
index 0000000000000..225d4aba840e6
--- /dev/null
+++ b/code/modules/world_topic/README.MD
@@ -0,0 +1,5 @@
+# NOTE
+
+Please keep things in this directory standardised!
+
+This means one topic handler per DM file, and the filename being the key. For an example, see `ping.dm`
diff --git a/code/modules/world_topic/_spam_prevention_handler.dm b/code/modules/world_topic/_spam_prevention_handler.dm
new file mode 100644
index 0000000000000..62f2b49a9bd25
--- /dev/null
+++ b/code/modules/world_topic/_spam_prevention_handler.dm
@@ -0,0 +1,68 @@
+#define WORLD_TOPIC_STRIKES_THRESHOLD 5
+#define WORLD_TOPIC_LOCKOUT_TIME 1 MINUTES
+
+/datum/world_topic_spam_prevention_handler
+ /// IP. Used purely for select purposes.
+ var/ip = null
+ /// Amount of strikes. [WORLD_TOPIC_STRIKES_THRESHOLD] strikes is a lockout of [WORLD_TOPIC_LOCKOUT_TIME]
+ var/strikes = 0
+ /// Time of last request
+ var/last_request = 0
+ /// Is this IP currently locked out
+ var/locked_out = FALSE
+ /// Unlock time
+ var/unlock_time = 0
+
+/datum/world_topic_spam_prevention_handler/New(_ip)
+ ip = _ip
+
+/**
+ * Lockout handler
+ *
+ * Updates strikes and timers of the most recent client to topic the server
+ * including any relevant detail
+ */
+/datum/world_topic_spam_prevention_handler/proc/check_lockout()
+ // Check if they are already locked out
+ if(locked_out && (unlock_time >= world.time))
+ // Relock out for another minute if youre spamming
+ unlock_time = world.time + WORLD_TOPIC_LOCKOUT_TIME
+ return TRUE
+
+ // If they were locked out and are now allowed, unlock them
+ if(locked_out && (unlock_time < world.time))
+ strikes = 0
+ locked_out = FALSE
+
+ // Allow a grace period of 0.5 seconds per topic, or you get a strike
+ if(last_request + 5 > world.time)
+ strikes++
+ if(strikes >= WORLD_TOPIC_STRIKES_THRESHOLD)
+ locked_out = TRUE
+ unlock_time = world.time + WORLD_TOPIC_LOCKOUT_TIME
+ return TRUE
+
+ // If we got here, assume they arent locked out
+ last_request = world.time
+ return FALSE
+
+/*
+
+Uncomment this if you modify the topic limiter, trust me, youll need to test it
+
+/client/verb/debug_limiter()
+ if(!GLOB.world_topic_spam_prevention_handlers[address])
+ GLOB.world_topic_spam_prevention_handlers[address] = new /datum/world_topic_spam_prevention_handler
+
+ var/datum/world_topic_spam_prevention_handler/sph = GLOB.world_topic_spam_prevention_handlers[address]
+ var/result = sph.check_lockout()
+ to_chat(usr, "Strikes: [sph.strikes]")
+ to_chat(usr, "Last request: [sph.last_request]")
+ to_chat(usr, "Locked out: [sph.locked_out]")
+ to_chat(usr, "Unlock time: [sph.unlock_time]")
+ to_chat(usr, "SPH Result: [result]")
+ to_chat(usr, "World.time: [world.time]")
+ to_chat(usr, "LR DIFF: [(sph.last_request - world.time)/10]s")
+ to_chat(usr, "LO DIFF: [(sph.unlock_time - world.time)/10]s")
+ to_chat(usr, "")
+*/
diff --git a/code/modules/world_topic/_topic_base.dm b/code/modules/world_topic/_topic_base.dm
new file mode 100644
index 0000000000000..812cd95179bc4
--- /dev/null
+++ b/code/modules/world_topic/_topic_base.dm
@@ -0,0 +1,35 @@
+/datum/world_topic_handler
+ /// Key which invokes this topic
+ var/topic_key
+ /// Set this to TRUE if the topic handler needs an authorised comms key
+ var/requires_commskey = FALSE
+
+
+/**
+ * Invokes the world/Topic handler
+ *
+ * This includes sanity checking for if the key is required, as well as other sanity checks
+ * DO NOT OVERRIDE
+ * Arguments:
+ * * input - The list of topic data, sent from [world/Topic]
+ */
+/datum/world_topic_handler/proc/invoke(list/input)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ var/authorised = (config.comms_password && input["key"] == config.comms_password) // No password means no comms, not any password
+ if(requires_commskey && !authorised)
+ // Try keep all returns in JSON unless absolutely necessary (?ping for example)
+ return(json_encode(list("error" = "Invalid Key")))
+
+ return execute(input, authorised)
+
+/**
+ * Actually executes the user's topic
+ *
+ * Override this to do your work in subtypes of this topic
+ *
+ * Arguments:
+ * * input - The list of topic data, sent from [world/Topic]
+ * * key_valid - Has the user entered the correct auth key
+ */
+/datum/world_topic_handler/proc/execute(list/input, key_valid = FALSE)
+ CRASH("execute() not implemented/overridden for [type]")
diff --git a/code/modules/world_topic/adminwho.dm b/code/modules/world_topic/adminwho.dm
new file mode 100644
index 0000000000000..0bdbe9de1adef
--- /dev/null
+++ b/code/modules/world_topic/adminwho.dm
@@ -0,0 +1,20 @@
+/datum/world_topic_handler/adminwho
+ topic_key = "adminwho"
+ requires_commskey = TRUE
+
+/datum/world_topic_handler/adminwho/execute(list/input, key_valid)
+ var/list/out_data = list()
+
+ for(var/client/C in GLOB.admins)
+ var/list/this_entry = list()
+ // Send both incase we want special formatting
+ this_entry["ckey"] = C.ckey
+ this_entry["key"] = C.key
+ this_entry["rank"] = C.holder.rank
+ // is_afk() returns an int of inactivity, we can use this to determine AFK for how long
+ // This info will not be shown in public channels
+ this_entry["afk"] = C.is_afk()
+
+ out_data += list(this_entry)
+
+ return json_encode(out_data)
diff --git a/code/modules/world_topic/ping.dm b/code/modules/world_topic/ping.dm
new file mode 100644
index 0000000000000..5c5f14da71c3e
--- /dev/null
+++ b/code/modules/world_topic/ping.dm
@@ -0,0 +1,14 @@
+/datum/world_topic_handler/ping
+ topic_key = "ping"
+
+/datum/world_topic_handler/ping/execute(list/input, key_valid)
+ /*
+ Basically a more efficient version of
+
+ if("ping" in input)
+ var/x = 1
+ for(var/client/C)
+ x++
+ return x
+ */
+ return length(GLOB.clients) + 1
diff --git a/code/modules/world_topic/players.dm b/code/modules/world_topic/players.dm
new file mode 100644
index 0000000000000..d0e524065e006
--- /dev/null
+++ b/code/modules/world_topic/players.dm
@@ -0,0 +1,10 @@
+/datum/world_topic_handler/playerlist
+ topic_key = "playerlist"
+
+/datum/world_topic_handler/playerlist/execute(list/input, key_valid)
+ var/list/keys = list()
+ for(var/I in GLOB.clients)
+ var/client/C = I
+ keys += C.key
+
+ return json_encode(keys)
diff --git a/code/modules/world_topic/status.dm b/code/modules/world_topic/status.dm
new file mode 100644
index 0000000000000..18a78586580d4
--- /dev/null
+++ b/code/modules/world_topic/status.dm
@@ -0,0 +1,51 @@
+/datum/world_topic_handler/status
+ topic_key = "status"
+
+/datum/world_topic_handler/status/execute(list/input, key_valid)
+ var/list/status_info = list()
+ status_info["version"] = "NoVersion"
+ status_info["mode"] = Master.current_runlevel
+ status_info["respawn"] = FALSE
+ status_info["enter"] = config.enter_allowed
+ status_info["ai"] = TRUE
+ status_info["host"] = world.host ? world.host : null
+ status_info["players"] = list()
+ status_info["roundtime"] = roundduration2text()
+ status_info["stationtime"] = stationtime2text()
+ status_info["oldstationtime"] = stationtime2text()
+ status_info["listed"] = "Public"
+ if(!world.hub_password)
+ status_info["listed"] = "Invisible"
+ var/player_count = 0
+ var/admin_count = 0
+
+ for(var/client/C in GLOB.clients)
+ if(C.holder)
+ admin_count++
+ player_count++
+ status_info["players"] = player_count
+ status_info["admins"] = admin_count
+ status_info["map_name"] = "Sierra"
+ status_info["round_id"] = game_id
+
+ // Export performance metrics
+ status_info["perfmetrics"] = list(
+ "td" = list(
+ "time_dilation_current" = Master.tickdrift,
+ "time_dilation_avg_fast" = 0,
+ "time_dilation_avg" = 0,
+ "time_dilation_avg_slow" = 0
+ ),
+ "mcpu" = world.map_cpu,
+ "cpu" = world.cpu
+ )
+
+
+ // Add more info if we are authed
+ if(key_valid)
+ if(SSticker.mode)
+ status_info["real_mode"] = SSticker.mode.name
+ status_info["security_level"] = ""
+ status_info["ticker_state"] = 1
+
+ return json_encode(status_info)
diff --git a/code/modules/xenoarcheaology/anomaly_container.dm b/code/modules/xenoarcheaology/anomaly_container.dm
index 34cee46c3c749..a92f8d7bd07e5 100644
--- a/code/modules/xenoarcheaology/anomaly_container.dm
+++ b/code/modules/xenoarcheaology/anomaly_container.dm
@@ -1,7 +1,7 @@
/obj/machinery/anomaly_container
name = "anomaly container"
desc = "A massive, steel container used to transport anomalous materials in a suspended state."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/machines/research/anomaly_cage.dmi'
icon_state = "anomaly_container"
density = TRUE
idle_power_usage = 0
@@ -32,56 +32,56 @@
/obj/machinery/anomaly_container/attack_hand(mob/user)
if(!contained)
- to_chat(user, SPAN_WARNING("There's nothing inside \the [src]."))
+ to_chat(user, SPAN_WARNING("There's nothing inside [src]."))
return
if (stat & MACHINE_STAT_NOPOWER)
- to_chat(user, SPAN_WARNING("\The [src] remains inert, a small light flashing red."))
+ to_chat(user, SPAN_WARNING("[src] remains inert, a small light flashing red."))
return
if(Adjacent(user))
if(!src.allowed(user))
- to_chat(user, SPAN_WARNING("\The [src] blinks red, notifying you of your incorrect access."))
+ to_chat(user, SPAN_WARNING("[src] blinks red, notifying you of your incorrect access."))
return
if(!src.health_dead)
user.visible_message(
- SPAN_NOTICE("\The [user] begins undoing the locks and latches on \the [src]..."),
- SPAN_NOTICE("You begin undoing the locks and latches on \the [src]...")
+ SPAN_NOTICE("[user] begins undoing the locks and latches on [src]..."),
+ SPAN_NOTICE("You begin undoing the locks and latches on [src]...")
)
if(!do_after(user, 4 SECONDS, src, DO_PUBLIC_UNIQUE))
return
user.visible_message(
- SPAN_NOTICE("\The [user] finishes undoing the locks and opens the hatch on \the [src]."),
- SPAN_NOTICE("You finish undoing the locks and open the hatch on \the [src].")
+ SPAN_NOTICE("[user] finishes undoing the locks and opens the hatch on [src]."),
+ SPAN_NOTICE("You finish undoing the locks and open the hatch on [src].")
)
playsound(loc, 'sound/mecha/hydraulic.ogg', 40)
release()
else
- to_chat(user, SPAN_WARNING("\The [src] requires a wrench to free its contents out."))
+ to_chat(user, SPAN_WARNING("[src] requires a wrench to free its contents out."))
return
else
return ..()
/obj/machinery/anomaly_container/attack_robot(mob/user)
if(!contained)
- to_chat(user, SPAN_WARNING("There's nothing inside \the [src]."))
+ to_chat(user, SPAN_WARNING("There's nothing inside [src]."))
return
if (stat & MACHINE_STAT_NOPOWER)
- to_chat(user, SPAN_WARNING("\The [src] remains inert, a small light flashing red."))
+ to_chat(user, SPAN_WARNING("[src] remains inert, a small light flashing red."))
return
if(Adjacent(user))
if(!src.allowed(user))
- to_chat(user, SPAN_WARNING("\The [src] blinks red, notifying you of your incorrect access."))
+ to_chat(user, SPAN_WARNING("[src] blinks red, notifying you of your incorrect access."))
return
if(!src.health_dead)
user.visible_message(
- SPAN_NOTICE("\The [user] begins undoing the locks and latches on \the [src]..."),
- SPAN_NOTICE("You begin undoing the locks and latches on \the [src]...")
+ SPAN_NOTICE("[user] begins undoing the locks and latches on [src]..."),
+ SPAN_NOTICE("You begin undoing the locks and latches on [src]...")
)
if(!do_after(user, 4 SECONDS, src, DO_PUBLIC_UNIQUE))
return
user.visible_message(
- SPAN_NOTICE("\The [user] finishes undoing the locks and opens the hatch on \the [src]."),
- SPAN_NOTICE("You finish undoing the locks and open the hatch on \the [src].")
+ SPAN_NOTICE("[user] finishes undoing the locks and opens the hatch on [src]."),
+ SPAN_NOTICE("You finish undoing the locks and open the hatch on [src].")
)
playsound(loc, 'sound/mecha/hydraulic.ogg', 40)
release()
@@ -91,12 +91,12 @@
/obj/machinery/anomaly_container/examine(mob/user, distance)
. = ..()
if (contained)
- to_chat(usr, SPAN_NOTICE("\The [contained] is kept inside."))
+ . += SPAN_NOTICE("[contained] is kept inside.")
if (attached_paper)
- to_chat(usr, SPAN_NOTICE("There's a paper clipped on the side."))
- attached_paper.examine(user, distance)
+ . += SPAN_NOTICE("There's a paper clipped on the side.")
+ . += attached_paper.examine(user, distance)
if (health_dead)
- to_chat(usr, SPAN_DANGER("The borosilicate panels are completely shattered."))
+ . += SPAN_NOTICE("The borosilicate panels are completely shattered.")
/obj/machinery/anomaly_container/proc/contain(obj/machinery/artifact)
if(contained)
@@ -116,27 +116,27 @@
/obj/machinery/artifact/MouseDrop(obj/machinery/anomaly_container/over_object, mob/user)
if(istype(over_object) && CanMouseDrop(over_object, usr))
- if (over_object.health_dead)
- visible_message(SPAN_WARNING("\The [over_object]'s containment is broken shut."))
+ if (over_object.health_dead())
+ visible_message(SPAN_WARNING("[over_object]'s containment is broken shut."))
return
if (!over_object.allowed(usr))
- visible_message(SPAN_WARNING("\The [over_object] blinks red, refusing to open."))
+ visible_message(SPAN_WARNING("[over_object] blinks red, refusing to open."))
return
user.visible_message(
- SPAN_NOTICE("\The [usr] begins placing \the [src] into \the [over_object]."),
- SPAN_NOTICE("You begin placing \the [src] into \the [over_object].")
+ SPAN_NOTICE("[usr] begins placing [src] into [over_object]."),
+ SPAN_NOTICE("You begin placing [src] into [over_object].")
)
if(!do_after(usr, 4 SECONDS, over_object, DO_PUBLIC_UNIQUE))
return
- user.visible_message(SPAN_NOTICE("The bolts on \the [over_object] drop with an hydraulic hiss, sealing its contents."))
+ user.visible_message(SPAN_NOTICE("The bolts on [over_object] drop with an hydraulic hiss, sealing its contents."))
playsound(loc, 'sound/mecha/hydraulic.ogg', 40)
Bumped(usr)
over_object.contain(src)
return
/obj/machinery/anomaly_container/on_death()
- visible_message(SPAN_DANGER("\The [src]'s glass cracks and shatters, exploding in a shower of shards!"))
+ visible_message(SPAN_DANGER("[src]'s glass cracks and shatters, exploding in a shower of shards!"))
for(var/i = 1 to rand(2,4))
- new /obj/item/material/shard(get_turf(src), MATERIAL_PHORON_GLASS)
+ new /obj/item/material/shard(get_turf(src), MATERIAL_BORON_GLASS)
playsound(loc, 'sound/effects/Glassbr1.ogg', 60)
if(!contained)
return
@@ -145,85 +145,97 @@
..()
/obj/machinery/anomaly_container/emp_act(severity)
+ SHOULD_CALL_PARENT(FALSE)
if(health_dead)
return
if(contained)
- visible_message(SPAN_DANGER("\The [src]'s latches break loose, freeing the contents!"))
+ visible_message(SPAN_DANGER("[src]'s latches break loose, freeing the contents!"))
playsound(loc, 'sound/mecha/hydraulic.ogg', 40)
release()
+ GLOB.empd_event.raise_event(src, severity)
+/obj/machinery/anomaly_container/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if (!health_dead)
+ return
+ user.visible_message(
+ SPAN_NOTICE("[user] begins to wrench apart the bolts on [src]..."),
+ SPAN_NOTICE("You begin to wrench apart the bolts on [src]...")
+ )
+ if(!tool.use_as_tool(src, user, 8 SECONDS, volume = 50, skill_path = SKILL_DEVICES, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ user.visible_message(
+ SPAN_NOTICE("[user] carefully loosens off [src]'s dented panel with [tool], freeing its contents.")
+ )
+ release()
+ update_icon()
-/obj/machinery/anomaly_container/attackby(obj/item/P, mob/user)
+/obj/machinery/anomaly_container/use_tool(obj/item/P, mob/living/user, list/click_params)
if (istype(P, /obj/item/paper))
if(attached_paper)
- to_chat(user, SPAN_NOTICE("You swap the reports on \the [src]."))
- P.forceMove(src.loc)
- P.add_fingerprint(usr)
+ to_chat(user, SPAN_NOTICE("You swap the reports on [src]."))
+ P.forceMove(loc)
+ P.add_fingerprint(user)
user.drop_item(P, loc, 1)
P.forceMove(src)
user.put_in_hands(P)
attached_paper = P
else
- to_chat(user, SPAN_NOTICE("You clip \the [P] to \the [src]'s side."))
+ to_chat(user, SPAN_NOTICE("You clip [P] to [src]'s side."))
user.drop_item(P, loc, 1)
attached_paper = P
P.forceMove(src)
+ update_icon()
+ return TRUE
- else if(istype(P, /obj/item/stack/material))
- if (!health_dead)
- to_chat(user, SPAN_NOTICE("\The [src] doesn't require repairs."))
- else
- if (contained)
- user.visible_message(
- SPAN_WARNING("\The [src] must be emptied before repairs can be done!")
- )
- return
- var/obj/item/stack/material/M = P
- if(istype(M, /obj/item/stack/material/glass/phoronglass))
- to_chat(user, SPAN_WARNING("\The [M] needs to be reinforced first."))
- if(istype(P, /obj/item/stack/material/glass/phoronrglass))
- if(M.get_amount() < 10)
- to_chat(user, SPAN_WARNING("You need at least ten sheets to repair \the [src]."))
- else
- user.visible_message(
- SPAN_NOTICE("[user] begins to repair \the [src]'s containment with \the [M]."),
- SPAN_NOTICE("You being to repair \the [src]'s containment with \the [M].")
- )
- if(!do_after(user, 4 SECONDS, src, DO_PUBLIC_UNIQUE))
- return
- user.visible_message(
- SPAN_NOTICE("[user] repairs \the [src]'s containment with \the [M]."),
- SPAN_NOTICE("You repair \the [src]'s containment with \the [M].")
- )
- M.use(10)
- revive_health()
- icon_state = "anomaly_container"
-
-
- else if (isWrench(P))
+ if (istype(P, /obj/item/stack/material))
if (!health_dead)
- return
+ to_chat(user, SPAN_NOTICE("[src] doesn't require repairs."))
+ return TRUE
+ if (contained)
+ user.visible_message(
+ SPAN_WARNING("[src] must be emptied before repairs can be done!")
+ )
+ return TRUE
+
+ var/obj/item/stack/material/M = P
+ if (!istype(M, /obj/item/stack/material/glass/boron_reinforced))
+ to_chat(user, SPAN_WARNING("You can only repair [src] with reinforced boron."))
+ return TRUE
+ if (M.get_amount() < 10)
+ to_chat(user, SPAN_WARNING("You need at least ten sheets to repair [src]."))
+ return TRUE
+
user.visible_message(
- SPAN_NOTICE("\The [user] begins to wrench apart the bolts on \the [src]..."),
- SPAN_NOTICE("You begin to wrench apart the bolts on \the [src]...")
+ SPAN_NOTICE("[user] begins to repair [src]'s containment with [M]."),
+ SPAN_NOTICE("You being to repair [src]'s containment with [M].")
)
- if(!do_after(user, 8 SECONDS, src, DO_PUBLIC_UNIQUE))
- return
+
+ if(!do_after(user, (M.toolspeed * 4) SECONDS, src, DO_PUBLIC_UNIQUE))
+ return TRUE
+
user.visible_message(
- SPAN_NOTICE("\The [user] carefully loosens off \the [src]'s dented panel with \the [P], freeing its contents."))
- playsound(loc, 'sound/items/Ratchet.ogg', 80, 1)
- release()
- add_fingerprint(user)
- update_icon()
+ SPAN_NOTICE("[user] repairs [src]'s containment with [M]."),
+ SPAN_NOTICE("You repair [src]'s containment with [M].")
+ )
+
+ M.use(10)
+ revive_health()
+ icon_state = "anomaly_container"
+ update_icon()
+ return TRUE
return ..()
/obj/machinery/anomaly_container/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(health_dead)
icon_state = "anomaly_container_broken"
if(attached_paper)
- overlays += "anomaly_container_paper"
+ AddOverlays("anomaly_container_paper")
if(panel_open)
- overlays += "anomaly_container_panel"
+ AddOverlays("anomaly_container_panel")
if(is_powered())
- overlays += "anomaly_container_power"
+ AddOverlays(list(
+ emissive_appearance(icon, "anomaly_container_lights"),
+ "anomaly_container_lights"
+ ))
diff --git a/code/modules/xenoarcheaology/artifacts/artifact.dm b/code/modules/xenoarcheaology/artifacts/artifact.dm
index 249af444b693c..66a878f64733f 100644
--- a/code/modules/xenoarcheaology/artifacts/artifact.dm
+++ b/code/modules/xenoarcheaology/artifacts/artifact.dm
@@ -1,7 +1,7 @@
/obj/machinery/artifact
name = "alien artifact"
desc = "A large alien device."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "ano00"
var/icon_num = 0
density = TRUE
@@ -88,11 +88,11 @@
// We're on a turf or inside a broken or invalid anomaly container
if(pulledby)
- check_triggers(/datum/artifact_trigger/proc/on_touch, pulledby)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_touch), pulledby)
var/datum/gas_mixture/enivonment = loc.return_air()
if(enivonment.return_pressure() >= SOUND_MINIMUM_PRESSURE)
- check_triggers(/datum/artifact_trigger/proc/on_gas_exposure, enivonment)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_gas_exposure), enivonment)
for(var/datum/artifact_effect/effect in list(my_effect, secondary_effect))
effect.process()
@@ -100,26 +100,26 @@
/obj/machinery/artifact/attack_robot(mob/living/user)
if(!CanPhysicallyInteract(user))
return
- check_triggers(/datum/artifact_trigger/proc/on_touch, user)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_touch), user)
/obj/machinery/artifact/attack_hand(mob/living/user)
. = ..()
visible_message("[user] touches \the [src].")
- check_triggers(/datum/artifact_trigger/proc/on_touch, user)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_touch), user)
-/obj/machinery/artifact/attackby(obj/item/W, mob/living/user)
+/obj/machinery/artifact/use_tool(obj/item/W, mob/living/user, list/click_params)
. = ..()
- check_triggers(/datum/artifact_trigger/proc/on_hit, W, user)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_hit), W, user)
/obj/machinery/artifact/Bumped(M)
..()
- check_triggers(/datum/artifact_trigger/proc/on_bump, M)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_bump), M)
/obj/machinery/artifact/bullet_act(obj/item/projectile/P)
- check_triggers(/datum/artifact_trigger/proc/on_hit, P)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_hit), P)
/obj/machinery/artifact/ex_act(severity)
- if(check_triggers(/datum/artifact_trigger/proc/on_explosion, severity))
+ if(check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_explosion), severity))
return
switch(severity)
if(EX_ACT_DEVASTATING)
@@ -136,7 +136,7 @@
secondary_effect.UpdateMove()
/obj/machinery/artifact/water_act(depth)
- check_triggers(/datum/artifact_trigger/proc/on_water_act, depth)
+ check_triggers(TYPE_PROC_REF(/datum/artifact_trigger, on_water_act), depth)
//Damage/Destruction procs
@@ -208,15 +208,6 @@
..()
queue_icon_update()
if (health_mod < 0)
- var/initial_damage_percentage = round((prior_health / get_max_health()) * 100)
- var/damage_percentage = get_damage_percentage()
- if (damage_percentage >= 75 && initial_damage_percentage < 75)
- visible_message("\The [src] looks like it's about to break!")
- else if (damage_percentage >= 50 && initial_damage_percentage < 50)
- visible_message("\The [src] looks seriously damaged!" )
- else if (damage_percentage >= 25 && initial_damage_percentage < 25)
- visible_message("\The [src] shows signs of damage!" )
-
for (var/datum/artifact_effect/A in list(my_effect, secondary_effect))
A.holder_damaged(get_current_health(), abs(health_mod))
diff --git a/code/modules/xenoarcheaology/artifacts/autocloner.dm b/code/modules/xenoarcheaology/artifacts/autocloner.dm
index cb0b421dfcee5..67ef0f61971fc 100644
--- a/code/modules/xenoarcheaology/artifacts/autocloner.dm
+++ b/code/modules/xenoarcheaology/artifacts/autocloner.dm
@@ -1,7 +1,7 @@
/obj/machinery/auto_cloner
name = "mysterious pod"
desc = "It's full of a viscous liquid, but appears dark and silent."
- icon = 'icons/obj/cryogenics.dmi'
+ icon = 'icons/obj/machines/medical/cryogenics.dmi'
icon_state = "cellold0"
var/spawn_type
var/time_spent_spawning = 0
diff --git a/code/modules/xenoarcheaology/artifacts/gigadrill.dm b/code/modules/xenoarcheaology/artifacts/gigadrill.dm
index cf2cacf3678bc..e7eef65394fd2 100644
--- a/code/modules/xenoarcheaology/artifacts/gigadrill.dm
+++ b/code/modules/xenoarcheaology/artifacts/gigadrill.dm
@@ -1,7 +1,7 @@
/obj/machinery/giga_drill
name = "alien drill"
desc = "A giant, alien drill mounted on long treads."
- icon = 'icons/obj/mining.dmi'
+ icon = 'icons/obj/gigadrill.dmi'
icon_state = "gigadrill"
var/active = 0
var/drill_time = 10
diff --git a/code/modules/xenoarcheaology/artifacts/replicator.dm b/code/modules/xenoarcheaology/artifacts/replicator.dm
index e1d0e791cffdd..bcfc412f57f4b 100644
--- a/code/modules/xenoarcheaology/artifacts/replicator.dm
+++ b/code/modules/xenoarcheaology/artifacts/replicator.dm
@@ -1,7 +1,7 @@
/obj/machinery/replicator
name = "alien machine"
desc = "It's some kind of pod with strange wires and gadgets all over it."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "borgcharger0(old)"
density = TRUE
@@ -123,11 +123,15 @@
show_browser(user, dat, "window=alien_replicator")
-/obj/machinery/replicator/attackby(obj/item/W as obj, mob/living/user as mob)
- if(!user.unEquip(W, src))
+/obj/machinery/replicator/use_tool(obj/item/W, mob/living/user, list/click_params)
+ if ((. = ..()))
return
+
+ if(!user.unEquip(W, src))
+ return TRUE
stored_materials.Add(W)
- src.visible_message(SPAN_NOTICE("\The [user] inserts \the [W] into \the [src]."))
+ visible_message(SPAN_NOTICE("\The [user] inserts \the [W] into \the [src]."))
+ return TRUE
/obj/machinery/replicator/OnTopic(user, href_list)
if(href_list["activate"])
diff --git a/code/modules/xenoarcheaology/boulder.dm b/code/modules/xenoarcheaology/boulder.dm
index 10998deb39964..a4f859e5fc7ab 100644
--- a/code/modules/xenoarcheaology/boulder.dm
+++ b/code/modules/xenoarcheaology/boulder.dm
@@ -1,7 +1,7 @@
/obj/structure/boulder
name = "rocky debris"
desc = "Leftover rock from an excavation, it's been partially dug out already but there's still a lot to go."
- icon = 'icons/obj/mining.dmi'
+ icon = 'icons/obj/boulder.dmi'
icon_state = "boulder1"
density = TRUE
opacity = 1
@@ -21,61 +21,73 @@
qdel(artifact_find)
..()
-/obj/structure/boulder/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/device/core_sampler))
- src.geological_data.artifact_distance = rand(-100,100) / 100
- src.geological_data.artifact_id = artifact_find.artifact_id
- var/obj/item/device/core_sampler/C = I
- C.sample_item(src, user)
- return
+/obj/structure/boulder/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Core Sampler
+ if (istype(tool, /obj/item/device/core_sampler))
+ geological_data.artifact_distance = rand(-100, 100) / 100
+ geological_data.artifact_id = artifact_find.artifact_id
+ var/obj/item/device/core_sampler/core_sampler = tool
+ core_sampler.sample_item(src, user)
+ return TRUE
- if(istype(I, /obj/item/device/depth_scanner))
- var/obj/item/device/depth_scanner/C = I
- C.scan_atom(user, src)
- return
+ // Depth Scanner
+ if (istype(tool, /obj/item/device/depth_scanner))
+ var/obj/item/device/depth_scanner/depth_scanner = tool
+ depth_scanner.scan_atom(user, src)
+ return TRUE
- if(istype(I, /obj/item/device/measuring_tape))
- var/obj/item/device/measuring_tape/P = I
- user.visible_message(SPAN_NOTICE("\The [user] extends \the [P] towards \the [src]."), SPAN_NOTICE("You extend \the [P] towards \the [src]."))
- if(do_after(user, 1.5 SECONDS, src, DO_PUBLIC_UNIQUE))
- to_chat(user, SPAN_NOTICE("\The [src] has been excavated to a depth of [src.excavation_level]cm."))
- return
+ // Measuring Tape
+ if (istype(tool, /obj/item/device/measuring_tape))
+ user.visible_message(
+ SPAN_NOTICE("\The [user] extends \a [tool] towards \the [src]."),
+ SPAN_NOTICE("You extend \the [tool] towards \the [src].")
+ )
+ if (!user.do_skilled(1.5 SECONDS, SKILL_SCIENCE, src) || !user.use_sanity_check(src, tool))
+ return TRUE
+ to_chat(user, SPAN_INFO("\The [src] has been excavated to a depth of [excavation_level]cm."))
+ return TRUE
- if(istype(I, /obj/item/pickaxe))
- var/obj/item/pickaxe/P = I
-
- if(last_act + P.digspeed > world.time)//prevents message spam
- return
- last_act = world.time
-
- to_chat(user, SPAN_WARNING("You start [P.drill_verb] [src]."))
-
- if(!do_after(user, P.digspeed, src, DO_PUBLIC_UNIQUE))
- return
-
- to_chat(user, SPAN_NOTICE("You finish [P.drill_verb] [src]."))
- excavation_level += P.excavation_amount
-
- if(excavation_level > 200)
- //failure
- user.visible_message(SPAN_WARNING("\The [src] suddenly crumbles away."), SPAN_WARNING("\The [src] has disintegrated under your onslaught, any secrets it was holding are long gone."))
- qdel(src)
- return
-
- if(prob(excavation_level))
- //success
- if(artifact_find)
+ // Pickaxe - Excavate
+ if (istype(tool, /obj/item/pickaxe))
+ var/obj/item/pickaxe/pickaxe = tool
+ user.visible_message(
+ SPAN_NOTICE("\The [user] starts [pickaxe.drill_verb] \the [src] with \a [pickaxe]."),
+ SPAN_NOTICE("You start [pickaxe.drill_verb] \the [src] with \the [pickaxe].")
+ )
+ if (!do_after(user, pickaxe.digspeed, src, DO_PUBLIC_UNIQUE) || !user.use_sanity_check(src, tool))
+ return TRUE
+ user.visible_message(
+ SPAN_NOTICE("\The [user] finished [pickaxe.drill_verb] \the [src] with \a [pickaxe]."),
+ SPAN_NOTICE("You finish [pickaxe.drill_verb] \the [src] with \the [pickaxe].")
+ )
+ excavation_level += pickaxe.excavation_amount
+ if (excavation_level > 200)
+ visible_message(
+ SPAN_WARNING("\The [src] suddenly crumbles away. Any secrets it was holding are long gone.")
+ )
+ qdel_self()
+ return TRUE
+ if (prob(excavation_level))
+ if (artifact_find)
var/spawn_type = artifact_find.artifact_find_type
- var/obj/O = new spawn_type(get_turf(src))
- if(istype(O, /obj/machinery/artifact))
- var/obj/machinery/artifact/X = O
- if(X.my_effect)
- X.my_effect.artifact_id = artifact_find.artifact_id
- src.visible_message(SPAN_WARNING("\The [src] suddenly crumbles away."))
+ var/obj/artifact = new spawn_type(get_turf(src))
+ if (istype(artifact, /obj/machinery/artifact))
+ var/obj/machinery/artifact/machine = artifact
+ if (machine.my_effect)
+ machine.my_effect.artifact_id = artifact_find.artifact_id
+ visible_message(
+ SPAN_WARNING("\The [src] suddenly crumbles away, revealing \a [artifact].")
+ )
else
- user.visible_message(SPAN_WARNING("\The [src] suddenly crumbles away."), SPAN_NOTICE("\The [src] has been whittled away under your careful excavation, but there was nothing of interest inside."))
- qdel(src)
+ visible_message(
+ SPAN_WARNING("\The [src] suddenly crumbles away, but there was nothing of interest inside.")
+ )
+ qdel_self()
+ return TRUE
+
+ return ..()
+
/obj/structure/boulder/Bumped(AM)
. = ..()
diff --git a/code/modules/xenoarcheaology/effect.dm b/code/modules/xenoarcheaology/effect.dm
index b431ec793aa79..d6bdac1a2e62d 100644
--- a/code/modules/xenoarcheaology/effect.dm
+++ b/code/modules/xenoarcheaology/effect.dm
@@ -7,7 +7,7 @@
var/chargelevel = 0
var/chargelevelmax = 10
var/artifact_id = ""
- var/effect_type = 0
+ var/effect_type = EFFECT_UNKNOWN
var/toggled = FALSE
var/on_time //time artifact should stay on for when toggled
@@ -47,7 +47,7 @@
. = ..()
/datum/artifact_effect/proc/ToggleActivate(reveal_toggle = 1)
- addtimer(new Callback(src, .proc/DoActivation, reveal_toggle), 0)
+ addtimer(CALLBACK(src, PROC_REF(DoActivation), reveal_toggle), 0)
/datum/artifact_effect/proc/DoActivation(reveal_toggle = 1)
if (toggled && activated)
@@ -56,7 +56,7 @@
if(activated)
activated = FALSE
else
- addtimer(new Callback(src, /datum/artifact_effect/proc/toggle_off), on_time)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/artifact_effect, toggle_off)), on_time)
activated = TRUE
toggled = TRUE
if(reveal_toggle && holder)
diff --git a/code/modules/xenoarcheaology/effects/cold.dm b/code/modules/xenoarcheaology/effects/cold.dm
index 13b6007bb6bf2..bd3df4afe3bf8 100644
--- a/code/modules/xenoarcheaology/effects/cold.dm
+++ b/code/modules/xenoarcheaology/effects/cold.dm
@@ -26,7 +26,7 @@
/datum/artifact_effect/cold/destroyed_effect()
. = ..()
- for (var/turf/T in trange(5, get_turf(holder)))
+ for (var/turf/T as anything in RANGE_TURFS(holder, 5))
var/datum/gas_mixture/env = T.return_air()
if (env)
env.temperature -= 10
diff --git a/code/modules/xenoarcheaology/effects/heat.dm b/code/modules/xenoarcheaology/effects/heat.dm
index e9621fcac0c7e..b5ebdb2ec5935 100644
--- a/code/modules/xenoarcheaology/effects/heat.dm
+++ b/code/modules/xenoarcheaology/effects/heat.dm
@@ -26,7 +26,7 @@
/datum/artifact_effect/heat/destroyed_effect()
. = ..()
- for (var/turf/T in trange(5, get_turf(holder)))
+ for (var/turf/T as anything in RANGE_TURFS(holder, 5))
var/datum/gas_mixture/env = T.return_air()
if (env)
env.temperature += 10
diff --git a/code/modules/xenoarcheaology/effects/hellportal/hell_portal.dm b/code/modules/xenoarcheaology/effects/hellportal/hell_portal.dm
index 8089ae3fe9103..1d92e22d4df42 100644
--- a/code/modules/xenoarcheaology/effects/hellportal/hell_portal.dm
+++ b/code/modules/xenoarcheaology/effects/hellportal/hell_portal.dm
@@ -52,7 +52,7 @@
/datum/artifact_effect/hellportal/proc/convert_turfs()
for (var/i = 0 to convert_count)
- var/turf/T = pick(trange(effectrange, get_turf(holder)))
+ var/turf/T = pick(RANGE_TURFS(holder, effectrange))
var/turf/simulated/floor/F
if (T.is_wall())
@@ -64,7 +64,7 @@
continue
if (prob(25))
- new /obj/effect/gibspawner/human(F)
+ new /obj/gibspawner/human(F)
F.set_flooring(GET_SINGLETON(/singleton/flooring/flesh))
F.desc = "Disgusting flooring made out of flesh, bone, eyes, and various other human bits and peices."
@@ -76,16 +76,16 @@
if (length(portals) >= active_portals_max)
return
- var/turf/T = pick(pick_turf_in_range(get_turf(holder), effectrange, list(/proc/not_turf_contains_dense_objects, /proc/is_not_space_turf, /proc/is_not_holy_turf, /proc/is_not_open_space)))
+ var/turf/T = pick(pick_turf_in_range(get_turf(holder), effectrange, list(GLOBAL_PROC_REF(not_turf_contains_dense_objects), GLOBAL_PROC_REF(is_not_space_turf), GLOBAL_PROC_REF(is_not_holy_turf),GLOBAL_PROC_REF(is_not_open_space))))
if (!T)
return
- var/obj/effect/gateway/artifact/small/gate = new(T)
+ var/obj/gateway/artifact/small/gate = new(T)
gate.parent = src
portals += gate
- GLOB.destroyed_event.register(gate, src, /datum/artifact_effect/hellportal/proc/reduce_portal_count)
+ GLOB.destroyed_event.register(gate, src, TYPE_PROC_REF(/datum/artifact_effect/hellportal, reduce_portal_count))
/datum/artifact_effect/hellportal/proc/hurt_players(send_message = TRUE)
for (var/mob/living/carbon/human/H in range(effectrange, get_turf(holder)))
@@ -98,7 +98,7 @@
else
to_chat(H, SPAN_DANGER("Searing pain strikes your body as you briefly find yourself in a burning hellscape!"))
-/datum/artifact_effect/hellportal/proc/reduce_portal_count(obj/effect/gateway/artifact/P)
+/datum/artifact_effect/hellportal/proc/reduce_portal_count(obj/gateway/artifact/P)
GLOB.destroyed_event.unregister(P, src)
portals -= P
@@ -109,8 +109,8 @@
/datum/artifact_effect/hellportal/proc/register_mob(mob/M)
mobs += M
- GLOB.destroyed_event.register(M, src, .proc/unregister_mob)
- GLOB.death_event.register(M, src, .proc/unregister_mob)
+ GLOB.destroyed_event.register(M, src, PROC_REF(unregister_mob))
+ GLOB.death_event.register(M, src, PROC_REF(unregister_mob))
/datum/artifact_effect/hellportal/destroyed_effect()
@@ -128,4 +128,4 @@
var/mob/living/carbon/human/H = M
H.apply_damage((damage / 4), DAMAGE_BRUTE, damage_flags = DAMAGE_FLAG_DISPERSED)
- new /obj/effect/gateway/artifact/big(get_turf(holder))
+ new /obj/gateway/artifact/big(get_turf(holder))
diff --git a/code/modules/xenoarcheaology/effects/hellportal/portals.dm b/code/modules/xenoarcheaology/effects/hellportal/portals.dm
index 92b29c9c9c799..63d042bbd56c1 100644
--- a/code/modules/xenoarcheaology/effects/hellportal/portals.dm
+++ b/code/modules/xenoarcheaology/effects/hellportal/portals.dm
@@ -1,7 +1,7 @@
-/obj/effect/gateway/artifact
+/obj/gateway/artifact
name = "reality tear"
desc = "A piercing pain strikes your mind as you peer into the tear, witnessing horrors and suffering beyond comprehension."
- light_outer_range=5
+ light_range=5
light_color="#ff0000"
spawnable = list(
/mob/living/simple_animal/hostile/meat/abomination = 5,
@@ -20,12 +20,12 @@
'sound/effects/squelch2.ogg'
)
-/obj/effect/gateway/artifact/small/New(turf/T)
+/obj/gateway/artifact/small/New(turf/T)
..()
- addtimer(new Callback(src, .proc/create_and_delete), rand(15, 30) SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(create_and_delete)), rand(15, 30) SECONDS)
-/obj/effect/gateway/artifact/small/proc/create_and_delete()
+/obj/gateway/artifact/small/proc/create_and_delete()
var/mob/living/simple_animal/T = pickweight(spawnable)
T = new T(src.loc)
T.min_gas = null
@@ -42,10 +42,10 @@
qdel(src)
//Spawns after the artifact is destroyed
-/obj/effect/gateway/artifact/big
+/obj/gateway/artifact/big
name = "interdimensional gateway"
desc = "A huge hole in reality with a strange, pulsing heartbeat. Faint, agonized screams can be heard from inside it..."
- light_outer_range = 10
+ light_range = 10
///Ticks down every so often until portal vanishes.
var/health = 15
///How many mobs we've spawned.
@@ -61,15 +61,15 @@
'sound/hallucinations/veryfar_noise.ogg'
)
-/obj/effect/gateway/artifact/big/New(turf/T)
+/obj/gateway/artifact/big/New(turf/T)
..()
mob_limit = health * 2
SetTransform(scale = size_multiplier)
- addtimer(new Callback(src, .proc/spawn_monster), rand(30, 60) SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(spawn_monster)), rand(30, 60) SECONDS)
GLOB.sound_player.PlayLoopingSound(src, "\ref[src]", 'sound/effects/Heart Beat.ogg', 70, 6)
-/obj/effect/gateway/artifact/big/proc/spawn_monster()
+/obj/gateway/artifact/big/proc/spawn_monster()
var/mob/living/simple_animal/T = pickweight(spawnable)
T = new T(src.loc)
T.min_gas = null
@@ -97,4 +97,4 @@
visible_message(SPAN_WARNING("\The [src] deposits \the [T] into the world!"))
- addtimer(new Callback(src, .proc/spawn_monster), rand(15, 30) SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(spawn_monster)), rand(15, 30) SECONDS)
diff --git a/code/modules/xenoarcheaology/effects/human_ifier.dm b/code/modules/xenoarcheaology/effects/human_ifier.dm
new file mode 100644
index 0000000000000..79ff3915b6d9a
--- /dev/null
+++ b/code/modules/xenoarcheaology/effects/human_ifier.dm
@@ -0,0 +1,140 @@
+/datum/artifact_effect/humanify
+ name = "humanify"
+ effect_type = EFFECT_ORGANIC
+ var/next_act_time = 0
+
+/datum/artifact_effect/humanify/DoEffectTouch(atom/holder)
+ replace_limb_or_body(holder)
+
+/datum/artifact_effect/humanify/DoEffectAura()
+ var/turf/T = get_turf(holder)
+ var/list/mobs = list()
+ for (var/mob/living/carbon/C in range(effectrange, T))
+ mobs += C
+
+ if (length(mobs))
+ replace_limb_or_body(pick(mobs))
+
+/datum/artifact_effect/humanify/DoEffectPulse()
+ var/turf/T = get_turf(holder)
+ var/list/mobs = list()
+ for (var/mob/living/carbon/C in range(effectrange, T))
+ mobs += C
+
+ if (length(mobs))
+ replace_limb_or_body(pick(mobs))
+
+/datum/artifact_effect/humanify/proc/replace_limb_or_body(mob/living/carbon/human/user)
+ if (!ishuman(user))
+ return
+
+ if (!user.ckey || !user.client)
+ return
+
+ if (next_act_time > world.time)
+ return
+
+ next_act_time = world.time + rand(15, 20) SECONDS
+
+ var/mob/living/carbon/human/H = user
+ var/weakness = GetAnomalySusceptibility(H)
+ if (prob(weakness * 100))
+ var/obj/item/organ/external/chest = H.organs_by_name[BP_CHEST]
+ if (BP_IS_ROBOTIC(chest))
+ H.visible_message(
+ SPAN_DANGER("\The [H] freezes up and collapses!"),
+ SPAN_DANGER("A strange force lashes out at you, and everything goes black, your mind reeling in a horrible pain!")
+ )
+ H.Paralyse(20)
+ H.make_jittery(100)
+ addtimer(CALLBACK(src, PROC_REF(create_new_human), H), rand(5, 10) SECONDS)
+ return
+
+ for (var/obj/item/organ/external/organ in H.organs)
+ if (BP_IS_ROBOTIC(organ))
+ replace_limb(H, organ)
+ break
+
+/datum/artifact_effect/humanify/proc/create_new_human(mob/living/carbon/human/H)
+ var/mob/living/carbon/human/new_human = new /mob/living/carbon/human(holder.loc)
+ new_human.Paralyse(5)
+ new_human.make_jittery(100)
+
+ //Port head appearance
+ new_human.facial_hair_color = H.facial_hair_color
+ new_human.facial_hair_style = H.facial_hair_style
+ new_human.head_hair_style = H.head_hair_style
+ new_human.head_hair_color = H.head_hair_color
+
+ //Port body appearance
+ new_human.skin_color = H.skin_color
+
+
+ new_human.SetName(H.name)
+ new_human.real_name = H.real_name
+ new_human.gender = H.gender
+
+ transfer_languages(H, new_human)
+
+ H.update_body()
+ H.updatehealth()
+ H.UpdateDamageIcon()
+
+ if (H.dna)
+ new_human.dna = H.dna.Clone()
+
+ if (H.mind)
+ H.mind.transfer_to(new_human)
+
+
+ H.visible_message(SPAN_DANGER("[SPAN_BOLD("\The [H]")] seizes up, their body twitching one last time before going completely still..."))
+ new_human.visible_message(SPAN_DANGER("[SPAN_BOLD("\The [new_human]")] bursts forth from \the [holder], gasping for air!"))
+ to_chat(new_human, FONT_LARGE(SPAN_DANGER("The pain suddenly stops, and you feel warm, alive, and with the feeling of a beating heart in your chest...")))
+ var/list/victims = list()
+ var/list/objs = list()
+ get_mobs_and_objs_in_view_fast(holder.loc, 5, victims, objs)
+ for (var/mob/living/living in victims)
+ if (living.client)
+ to_chat(living, SPAN_DANGER(FONT_LARGE("\The [holder] emits a blinding flash of light!")))
+ living.flash_eyes(FLASH_PROTECTION_MAJOR)
+ living.Stun(1)
+ living.mod_confused(5)
+ playsound(holder, "sound/effects/supermatter.ogg", 100, TRUE)
+ H.death()
+
+/datum/artifact_effect/humanify/proc/replace_limb(mob/living/carbon/human/H, obj/item/organ/external/limb)
+ set waitfor = FALSE
+
+ H.visible_message(
+ SPAN_DANGER("\The [holder] grabs hold of [SPAN_BOLD("\The [H]")]'s [limb.name] with some invisible force and tears it off!"),
+ SPAN_DANGER("You feel an invisible energy reach out and tear your [limb.name] from your body!"),
+ )
+
+ var/list/children_to_create = list()
+ if (limb.children)
+ for (var/obj/item/organ/external/child in limb.children)
+ children_to_create += list(H.species.has_limbs[child.organ_tag])
+
+ limb.droplimb(TRUE, TRUE)
+ playsound(H, 'sound/effects/razorweb_break.ogg', 70)
+ sleep(6 SECONDS)
+ holder.visible_message(SPAN_WARNING("\The [holder] hums with a strange energy as it continues to manipulate [SPAN_BOLD("\The [H]")]'s body..."))
+ sleep(6 SECONDS)
+ playsound(H, 'sound/effects/squelch2.ogg', 70)
+ var/list/organ_data = H.species.has_limbs[limb.organ_tag]
+ var/limb_path = organ_data["path"]
+ var/obj/item/organ/external/O = new limb_path(H)
+
+ if (children_to_create)
+ for (var/list/child_organ_data in children_to_create)
+ var/child_limb_path = child_organ_data["path"]
+ new child_limb_path(H)
+
+ H.visible_message(
+ SPAN_DANGER("[SPAN_BOLD("\The [H]")] suddenly sprouts a new [O.name]!"),
+ SPAN_DANGER("You feel a strange energy reach out and attach a new [O.name] to your body, this one feeling much more natural than the last...")
+ )
+
+ H.update_body()
+ H.updatehealth()
+ H.UpdateDamageIcon()
diff --git a/code/modules/xenoarcheaology/effects/spooky.dm b/code/modules/xenoarcheaology/effects/spooky.dm
index 5540ca6e56541..594a53ae60538 100644
--- a/code/modules/xenoarcheaology/effects/spooky.dm
+++ b/code/modules/xenoarcheaology/effects/spooky.dm
@@ -10,7 +10,7 @@
return
var/weakness = GetAnomalySusceptibility(H)
if (prob(weakness * 100))
- if (!addtimer(new Callback(H, /mob/living/carbon/human/proc/ChangeToSkeleton), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
+ if (!addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living/carbon/human, ChangeToSkeleton)), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
return
to_chat(H, SPAN_WARNING("You suddenly feel a deep chill in your bones..."))
var/datum/gas_mixture/env = H.loc.return_air()
@@ -25,7 +25,7 @@
return
var/weakness = GetAnomalySusceptibility(H)
if (prob(weakness * 100))
- if (!addtimer(new Callback(H, /mob/living/carbon/human/proc/ChangeToSkeleton), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
+ if (!addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living/carbon/human, ChangeToSkeleton)), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
return
to_chat(H, SPAN_WARNING("You suddenly feel a deep chill in your bones..."))
var/datum/gas_mixture/env = H.loc.return_air()
@@ -40,7 +40,7 @@
return
var/weakness = GetAnomalySusceptibility(H)
if (prob(weakness * 100))
- if (!addtimer(new Callback(H, /mob/living/carbon/human/proc/ChangeToSkeleton), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
+ if (!addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living/carbon/human, ChangeToSkeleton)), rand(30 SECONDS, 2 MINUTES), TIMER_UNIQUE | TIMER_NO_HASH_WAIT))
return
to_chat(H, SPAN_WARNING("You suddenly feel a deep chill in your bones..."))
var/datum/gas_mixture/env = H.loc.return_air()
diff --git a/code/modules/xenoarcheaology/effects/teleport.dm b/code/modules/xenoarcheaology/effects/teleport.dm
index af1f03ff34fc7..9db188bdb00f3 100644
--- a/code/modules/xenoarcheaology/effects/teleport.dm
+++ b/code/modules/xenoarcheaology/effects/teleport.dm
@@ -31,12 +31,12 @@
if (M.buckled)
M.buckled.unbuckle_mob()
- var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
+ var/datum/effect/spark_spread/sparks = new /datum/effect/spark_spread()
sparks.set_up(3, 0, get_turf(M))
sparks.start()
- M.Move(pick(trange(50, center)))
- sparks = new /datum/effect/effect/system/spark_spread()
+ M.Move(pick(RANGE_TURFS(center, 50)))
+ sparks = new /datum/effect/spark_spread()
sparks.set_up(3, 0, M.loc)
sparks.start()
diff --git a/code/modules/xenoarcheaology/finds/find_spawning.dm b/code/modules/xenoarcheaology/finds/find_spawning.dm
index def2385043f8a..51469cf7e8671 100644
--- a/code/modules/xenoarcheaology/finds/find_spawning.dm
+++ b/code/modules/xenoarcheaology/finds/find_spawning.dm
@@ -1,4 +1,5 @@
/proc/get_archeological_find_by_findtype(find_type)
+ RETURN_TYPE(/obj/item/archaeological_find)
for(var/T in typesof(/obj/item/archaeological_find))
var/obj/item/archaeological_find/F = T
if(find_type == initial(F.find_type))
@@ -7,7 +8,7 @@
/obj/item/archaeological_find
name = "object"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "unknown2"
var/find_type = ARCHAEO_UNKNOWN
var/item_type = "object"
@@ -59,7 +60,7 @@
var/engravings = ""
if(apply_image_decorations)
- var/obj/effect/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
+ var/obj/overmap/visitable/sector/exoplanet/E = map_sectors["[z]"]
engravings = "[pick("Engraved","Carved","Etched")] on the item is [pick("an image of","a frieze of","a depiction of")] "
if(istype(E))
engravings += E.get_engravings()
@@ -97,7 +98,7 @@
/obj/item/archaeological_find/proc/spawn_item()
var/obj/item/material/kitchen/utensil/fork/F = new(loc)
- F.icon = 'icons/obj/xenoarchaeology.dmi'
+ F.icon = 'icons/obj/xenoarchaeology_finds.dmi'
F.icon_state = "unknown[rand(1,4)]"
return F
@@ -113,7 +114,7 @@
R = new /obj/item/reagent_containers/glass/replenishing(loc)
else
R = new /obj/item/reagent_containers/glass/beaker(loc)
- R.icon = 'icons/obj/xenoarchaeology.dmi'
+ R.icon = 'icons/obj/xenoarchaeology_finds.dmi'
R.icon_state = "bowl"
if(prob(20))
additional_desc = "There appear to be [pick("dark","faintly glowing","pungent","bright")] [pick("red","purple","green","blue")] stains inside."
@@ -162,7 +163,7 @@
else
new_item = ..()
new_item.SetName("statuette")
- new_item.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_item.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_item.icon_state = "statuette"
additional_desc = "It depicts a [pick("small","ferocious","wild","pleasing","hulking")] \
@@ -178,7 +179,7 @@
/obj/item/archaeological_find/instrument/spawn_item()
var/obj/item/new_item = new(loc)
new_item.SetName("instrument")
- new_item.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_item.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_item.icon_state = "instrument"
if(prob(30))
apply_image_decorations = 1
@@ -212,7 +213,7 @@
/obj/item/archaeological_find/trap
item_type = "trap"
- icon = 'icons/obj/items.dmi'
+ icon = 'icons/obj/beartrap.dmi'
icon_state = "beartrap0"
find_type = ARCHAEO_BEARTRAP
apply_prefix = 0
@@ -232,11 +233,12 @@
/obj/item/archaeological_find/container/spawn_item()
var/obj/item/storage/box/new_box = new(loc)
- new_box.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_box.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_box.max_w_class = pick(ITEM_SIZE_TINY,2;ITEM_SIZE_SMALL,3;ITEM_SIZE_NORMAL,4;ITEM_SIZE_LARGE)
var/storage_amount = BASE_STORAGE_COST(new_box.max_w_class)
new_box.max_storage_space = rand(storage_amount, storage_amount * 10)
new_box.icon_state = "box"
+ new_box.foldable = null
if(prob(30))
apply_image_decorations = 1
return new_box
@@ -265,7 +267,7 @@
new_item = new /obj/item/crowbar(loc)
else
new_item = new /obj/item/screwdriver(loc)
- new_item.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_item.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_item.icon_state = "unknown[rand(1,4)]"
additional_desc = "[pick("It doesn't look safe.",\
"You wonder what it was used for",\
@@ -288,7 +290,7 @@
/obj/item/archaeological_find/material/exotic
item_type = "rare material lump"
- possible_materials = list(MATERIAL_ALIENALLOY, MATERIAL_PHORON, MATERIAL_HYDROGEN, MATERIAL_PHORON_GLASS)
+ possible_materials = list(MATERIAL_ALIENALLOY, MATERIAL_PHORON, MATERIAL_HYDROGEN, MATERIAL_BORON_GLASS)
/obj/item/archaeological_find/crystal
item_type = "crystal"
@@ -306,7 +308,7 @@
apply_image_decorations = 1
additional_desc = pick("It shines faintly as it catches the light.","It appears to have a faint inner glow.","It seems to draw you inward as you look it at.","Something twinkles faintly as you look at it.","It's mesmerizing to behold.")
- new_item.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_item.icon = 'icons/obj/xenoarchaeology_finds.dmi'
if(prob(25))
item_type = "smooth green crystal"
new_item.icon_state = "Green lump"
@@ -379,7 +381,7 @@
/obj/item/gun/energy/captain)
var/obj/item/gun/energy/new_gun = new spawn_type(loc)
- new_gun.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_gun.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_gun.icon_state = "egun[rand(1,6)]"
new_gun.charge_meter = 0
@@ -404,7 +406,7 @@
/obj/item/archaeological_find/gun/spawn_item()
var/obj/item/gun/projectile/revolver/new_gun = new(loc)
- new_gun.icon = 'icons/obj/xenoarchaeology.dmi'
+ new_gun.icon = 'icons/obj/xenoarchaeology_finds.dmi'
new_gun.icon_state = "gun[rand(1,4)]"
//33% chance to be able to reload the gun with human ammunition
diff --git a/code/modules/xenoarcheaology/finds/finds.dm b/code/modules/xenoarcheaology/finds/finds.dm
index 0927ffc7508f0..9f7f5eedb1fe2 100644
--- a/code/modules/xenoarcheaology/finds/finds.dm
+++ b/code/modules/xenoarcheaology/finds/finds.dm
@@ -17,7 +17,7 @@
/obj/item/ore/strangerock
name = "strange rock"
desc = "Seems to have some unusal strata evident throughout it."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "strange"
origin_tech = list(TECH_MATERIAL = 5)
@@ -28,34 +28,29 @@
var/T = get_archeological_find_by_findtype(inside_item_type)
new T(src)
+/obj/item/ore/strangerock/welder_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ if(!tool.use_as_tool(src, user, amount = 1, volume = 50, do_flags = DO_REPAIR_CONSTRUCT))
+ return
+ var/obj/item/inside = locate() in src
+ if(inside)
+ inside.dropInto(loc)
+ visible_message(SPAN_INFO(" [src] burns away revealing [inside]."))
+ else
+ visible_message(SPAN_INFO(" [src] burns away into nothing."))
+ qdel(src)
+
/obj/item/ore/strangerock/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/pickaxe/xeno/brush))
var/obj/item/inside = locate() in src
if(inside)
inside.dropInto(loc)
- visible_message(SPAN_INFO("\The [src] is brushed away, revealing \the [inside]."))
+ visible_message(SPAN_INFO(" [src] is brushed away, revealing [inside]."))
else
- visible_message(SPAN_INFO("\The [src] is brushed away into nothing."))
+ visible_message(SPAN_INFO(" [src] is brushed away into nothing."))
qdel(src)
return
- if(isWelder(I))
- var/obj/item/weldingtool/W = I
- if(W.isOn())
- if(W.get_fuel() >= 2)
- var/obj/item/inside = locate() in src
- if(inside)
- inside.dropInto(loc)
- visible_message(SPAN_INFO("\The [src] burns away revealing \the [inside]."))
- else
- visible_message(SPAN_INFO("\The [src] burns away into nothing."))
- qdel(src)
- W.remove_fuel(2)
- else
- visible_message(SPAN_INFO("A few sparks fly off \the [src], but nothing else happens."))
- W.remove_fuel(1)
- return
-
else if(istype(I, /obj/item/device/core_sampler))
var/obj/item/device/core_sampler/S = I
S.sample_item(src, user)
diff --git a/code/modules/xenoarcheaology/finds/finds_defines.dm b/code/modules/xenoarcheaology/finds/finds_defines.dm
index 522ff861fad23..8dd880ef30fd0 100644
--- a/code/modules/xenoarcheaology/finds/finds_defines.dm
+++ b/code/modules/xenoarcheaology/finds/finds_defines.dm
@@ -1,24 +1,16 @@
-var/global/list/responsive_carriers = list(
- /datum/reagent/carbon,
- /datum/reagent/potassium,
- /datum/reagent/hydrazine,
- "nitrogen",
- /datum/reagent/mercury,
- /datum/reagent/iron,
- "chlorine",
- /datum/reagent/phosphorus,
- /datum/reagent/toxin/phoron)
+/// List (path or string -> string). Map of possible response carriers to strings for archaeological finds.
+GLOBAL_LIST_INIT(responsive_carriers_to_finds, list(
+ /datum/reagent/carbon = "Trace organic cells",
+ /datum/reagent/potassium = "Long exposure particles",
+ /datum/reagent/hydrazine = "Trace water particles",
+ "nitrogen" = "Crystalline structures",
+ /datum/reagent/mercury = "Metallic derivative",
+ /datum/reagent/iron = "Metallic composite",
+ "chlorine" = "Metamorphic/igneous rock composite",
+ /datum/reagent/phosphorus = "Metamorphic/sedimentary rock composite",
+ /datum/reagent/toxin/phoron = "Anomalous material"
+))
-var/global/list/finds_as_strings = list(
- "Trace organic cells",
- "Long exposure particles",
- "Trace water particles",
- "Crystalline structures",
- "Metallic derivative",
- "Metallic composite",
- "Metamorphic/igneous rock composite",
- "Metamorphic/sedimentary rock composite",
- "Anomalous material")
/proc/get_responsive_reagent(find_type)
switch(find_type)
diff --git a/code/modules/xenoarcheaology/finds/fossils.dm b/code/modules/xenoarcheaology/finds/fossils.dm
index f7caa0cfc8b16..fac553bc86241 100644
--- a/code/modules/xenoarcheaology/finds/fossils.dm
+++ b/code/modules/xenoarcheaology/finds/fossils.dm
@@ -4,7 +4,7 @@
/obj/item/fossil
name = "Fossil"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "bone"
desc = "It's a fossil."
var/animal = 1
@@ -50,7 +50,7 @@
/obj/skeleton
name = "Incomplete skeleton"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "uskel"
desc = "Incomplete skeleton."
var/bnum = 1
diff --git a/code/modules/xenoarcheaology/finds/misc.dm b/code/modules/xenoarcheaology/finds/misc.dm
index d8d1f1f120c98..636f01f33e3d0 100644
--- a/code/modules/xenoarcheaology/finds/misc.dm
+++ b/code/modules/xenoarcheaology/finds/misc.dm
@@ -4,7 +4,7 @@
//legacy crystal
/obj/machinery/crystal
name = "Crystal"
- icon = 'icons/obj/mining.dmi'
+ icon = 'icons/obj/crystals.dmi'
icon_state = "crystal"
@@ -16,7 +16,7 @@
//Variant crystals, in case you want to spawn/map those directly.
/obj/machinery/crystal_static
name = "Crystal"
- icon = 'icons/obj/mining.dmi'
+ icon = 'icons/obj/crystals.dmi'
icon_state = "crystal"
/obj/machinery/crystal_static/pink
diff --git a/code/modules/xenoarcheaology/finds/special.dm b/code/modules/xenoarcheaology/finds/special.dm
index 68a780ab78440..b3a02a54bf2f5 100644
--- a/code/modules/xenoarcheaology/finds/special.dm
+++ b/code/modules/xenoarcheaology/finds/special.dm
@@ -51,7 +51,7 @@
/obj/item/vampiric
name = "statuette"
icon_state = "statuette"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
var/charges = 0
var/list/nearby_mobs = list()
var/last_bloodcall = 0
@@ -82,10 +82,10 @@
//suck up some blood to gain power
if(world.time - last_eat > eat_interval)
- var/obj/effect/decal/cleanable/blood/B = locate() in range(2,src)
+ var/obj/decal/cleanable/blood/B = locate() in range(2,src)
if(B)
last_eat = world.time
- if(istype(B, /obj/effect/decal/cleanable/blood/drip))
+ if(istype(B, /obj/decal/cleanable/blood/drip))
charges += 0.25
else
charges += 1
@@ -95,7 +95,7 @@
//use up stored charges
if(charges >= 10)
charges -= 10
- new /obj/effect/spider/eggcluster(pick(view(1,src)))
+ new /obj/spider/eggcluster(pick(view(1,src)))
if(charges >= 3)
if(prob(5))
@@ -106,7 +106,7 @@
if(charges >= 1)
if(length(shadow_wights) < 5 && prob(5))
- shadow_wights.Add(new /obj/effect/shadow_wight(src.loc))
+ shadow_wights.Add(new /obj/shadow_wight(src.loc))
playsound(src.loc, 'sound/effects/ghost.ogg', 50, 1, -3)
charges -= 0.1
@@ -121,7 +121,7 @@
if(wight_check_index > length(shadow_wights))
wight_check_index = 1
- var/obj/effect/shadow_wight/W = shadow_wights[wight_check_index]
+ var/obj/shadow_wight/W = shadow_wights[wight_check_index]
if(isnull(W))
shadow_wights.Remove(wight_check_index)
else if(isnull(W.loc))
@@ -143,27 +143,27 @@
var/target = pick(M.organs_by_name)
M.apply_damage(rand(5, 10), DAMAGE_BRUTE, target)
to_chat(M, SPAN_WARNING("The skin on your [parse_zone(target)] feels like it's ripping apart, and a stream of blood flies out."))
- var/obj/effect/decal/cleanable/blood/splatter/animated/B = new(M.loc)
+ var/obj/decal/cleanable/blood/splatter/animated/B = new(M.loc)
B.target_turf = pick(range(1, src))
B.blood_DNA = list()
B.blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
M.vessel.remove_reagent(/datum/reagent/blood,rand(25,50))
//animated blood 2 SPOOKY
-/obj/effect/decal/cleanable/blood/splatter/animated
+/obj/decal/cleanable/blood/splatter/animated
var/turf/target_turf
var/loc_last_process
-/obj/effect/decal/cleanable/blood/splatter/animated/Initialize()
+/obj/decal/cleanable/blood/splatter/animated/Initialize()
. = ..()
loc_last_process = src.loc
START_PROCESSING(SSobj, src)
-/obj/effect/decal/cleanable/blood/splatter/animated/Destroy()
+/obj/decal/cleanable/blood/splatter/animated/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/effect/decal/cleanable/blood/splatter/animated/Process()
+/obj/decal/cleanable/blood/splatter/animated/Process()
if(target_turf && src.loc != target_turf)
step_towards(src,target_turf)
if(src.loc == loc_last_process)
@@ -172,7 +172,7 @@
//leave some drips behind
if(prob(50))
- var/obj/effect/decal/cleanable/blood/drip/D = new(src.loc)
+ var/obj/decal/cleanable/blood/drip/D = new(src.loc)
D.blood_DNA = src.blood_DNA.Copy()
if(prob(50))
D = new(src.loc)
@@ -183,21 +183,21 @@
else
..()
-/obj/effect/shadow_wight
+/obj/shadow_wight
name = "shadow wight"
icon = 'icons/mob/mob.dmi'
icon_state = "shade"
density = TRUE
-/obj/effect/shadow_wight/Initialize()
+/obj/shadow_wight/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
-/obj/effect/shadow_wight/Destroy()
+/obj/shadow_wight/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/effect/shadow_wight/Process()
+/obj/shadow_wight/Process()
if(loc)
step_rand(src)
var/mob/living/carbon/M = locate() in src.loc
@@ -221,5 +221,5 @@
else
STOP_PROCESSING(SSobj, src)
-/obj/effect/shadow_wight/Bump(atom/obstacle)
+/obj/shadow_wight/Bump(atom/obstacle)
to_chat(obstacle, SPAN_WARNING("You feel a chill run down your spine!"))
diff --git a/code/modules/xenoarcheaology/misc.dm b/code/modules/xenoarcheaology/misc.dm
index 20ce0dd580812..160a2e49032f4 100644
--- a/code/modules/xenoarcheaology/misc.dm
+++ b/code/modules/xenoarcheaology/misc.dm
@@ -19,9 +19,9 @@
/obj/structure/closet/secure_closet/xenoarchaeologist/New()
..()
if(prob(50))
- new /obj/item/storage/backpack/toxins(src)
+ new /obj/item/storage/backpack/corpsci(src)
else
- new /obj/item/storage/backpack/satchel/tox(src)
+ new /obj/item/storage/backpack/satchel/corpsci(src)
if(prob(50))
new /obj/item/storage/backpack/dufflebag(src)
new /obj/item/clothing/under/rank/scientist(src)
diff --git a/code/modules/xenoarcheaology/sampling.dm b/code/modules/xenoarcheaology/sampling.dm
index a1a3d160591cb..2dfc024e32642 100644
--- a/code/modules/xenoarcheaology/sampling.dm
+++ b/code/modules/xenoarcheaology/sampling.dm
@@ -1,7 +1,7 @@
/obj/item/rocksliver
name = "rock sliver"
desc = "It looks extremely delicate."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/xenoarchaeology_finds.dmi'
icon_state = "sliver1"
randpixel = 8
w_class = ITEM_SIZE_TINY
@@ -70,19 +70,19 @@
artifact_distance = rand()
artifact_id = container.artifact_find.artifact_id
else
- for (var/turf/simulated/mineral/T as anything in GLOB.xeno_artifact_turfs)
+ for (var/turf/simulated/mineral/T as anything in SSxenoarch.xeno_artifact_turfs)
if(T.artifact_find)
var/cur_dist = get_dist(container, T) * 2
if( (artifact_distance < 0 || cur_dist < artifact_distance))
artifact_distance = cur_dist + rand() * 2 - 1
artifact_id = T.artifact_find.artifact_id
else
- GLOB.xeno_artifact_turfs -= T
+ SSxenoarch.xeno_artifact_turfs -= T
/obj/item/device/core_sampler
name = "core sampler"
desc = "Used to extract geological core samples."
- icon = 'icons/obj/sampler.dmi'
+ icon = 'icons/obj/tools/sampler.dmi'
icon_state = "sampler0"
item_state = "screwdriver_brown"
w_class = ITEM_SIZE_TINY
@@ -94,21 +94,31 @@
/obj/item/device/core_sampler/examine(mob/user, distance)
. = ..(user)
if(distance <= 2)
- to_chat(user, SPAN_NOTICE("Used to extract geological core samples - this one is [sampled_turf ? "full" : "empty"], and has [num_stored_bags] bag[num_stored_bags != 1 ? "s" : ""] remaining."))
-
-/obj/item/device/core_sampler/attackby(obj/item/I, mob/living/user)
- if(istype(I, /obj/item/evidencebag))
- if(length(I.contents))
- to_chat(user, SPAN_WARNING("\The [I] is full."))
- return
- if(num_stored_bags < 10)
- qdel(I)
- num_stored_bags += 1
- to_chat(user, SPAN_NOTICE("You insert \the [I] into \the [src]."))
- else
- to_chat(user, SPAN_WARNING("\The [src] can not fit any more bags."))
- else
- return ..()
+ . += SPAN_NOTICE("Used to extract geological core samples - this one is [sampled_turf ? "full" : "empty"], and has [num_stored_bags] bag[num_stored_bags != 1 ? "s" : ""] remaining.")
+
+
+/obj/item/device/core_sampler/use_tool(obj/item/tool, mob/user, list/click_params)
+ // Evidence Bag - Insert bag
+ if (istype(tool, /obj/item/evidencebag))
+ if (length(tool.contents))
+ USE_FEEDBACK_FAILURE("\The [tool] needs to be empty to add it to \the [src].")
+ return TRUE
+ if (num_stored_bags >= 10)
+ USE_FEEDBACK_FAILURE("\The [src] can't hold any more bags.")
+ return TRUE
+ if (!user.unEquip(tool, src))
+ FEEDBACK_UNEQUIP_FAILURE(user, tool)
+ return TRUE
+ num_stored_bags++
+ user.visible_message(
+ SPAN_NOTICE("\The [user] adds \a [tool] to \a [src]."),
+ SPAN_NOTICE("You add \the [tool] to \the [src]. It now holds [num_stored_bags].")
+ )
+ qdel(tool)
+ return TRUE
+
+ return ..()
+
/obj/item/device/core_sampler/proc/sample_item(item_to_sample, mob/user)
var/datum/geosample/geo_data
@@ -142,8 +152,10 @@
//update the sample bag
filled_bag.icon_state = "evidence"
var/image/I = image("icon"=R, "layer"=FLOAT_LAYER)
- filled_bag.overlays += I
- filled_bag.overlays += "evidence"
+ filled_bag.AddOverlays(list(
+ I,
+ "evidence"
+ ))
filled_bag.w_class = ITEM_SIZE_TINY
filled_bag.stored_item = R
diff --git a/code/modules/xenoarcheaology/tools/ano_device_battery.dm b/code/modules/xenoarcheaology/tools/ano_device_battery.dm
index d7b19079f576e..aeb1c90ab3c9a 100644
--- a/code/modules/xenoarcheaology/tools/ano_device_battery.dm
+++ b/code/modules/xenoarcheaology/tools/ano_device_battery.dm
@@ -1,6 +1,6 @@
/obj/item/anobattery
name = "Anomaly power battery"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_anomaly_utilizer.dmi'
icon_state = "anobattery0"
var/datum/artifact_effect/battery_effect
var/capacity = 300
@@ -17,7 +17,7 @@
/obj/item/anodevice
name = "Anomaly power utilizer"
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_anomaly_utilizer.dmi'
icon_state = "anodev"
var/activated = 0
var/duration = 0
@@ -203,11 +203,12 @@
STOP_PROCESSING(SSobj, src)
..()
-/obj/item/anodevice/attack(mob/living/M as mob, mob/living/user as mob, def_zone)
+/obj/item/anodevice/use_before(mob/living/M as mob, mob/living/user as mob)
+ . = FALSE
if (!istype(M))
- return
+ return FALSE
- if(activated && inserted_battery.battery_effect.effect == EFFECT_TOUCH && !isnull(inserted_battery))
+ if (activated && inserted_battery.battery_effect.effect == EFFECT_TOUCH && !isnull(inserted_battery))
inserted_battery.battery_effect.DoEffectTouch(M)
inserted_battery.use_power(energy_consumed_on_touch)
user.visible_message(SPAN_NOTICE("[user] taps [M] with [src], and it shudders on contact."))
@@ -216,3 +217,4 @@
if(inserted_battery.battery_effect)
admin_attack_log(user, M, "Tapped their victim with \a [src] (EFFECT: [inserted_battery.battery_effect.name])", "Was tapped by \a [src] (EFFECT: [inserted_battery.battery_effect.name])", "used \a [src] (EFFECT: [inserted_battery.battery_effect.name]) to tap")
+ return TRUE
diff --git a/code/modules/xenoarcheaology/tools/artifact_analyser.dm b/code/modules/xenoarcheaology/tools/artifact_analyser.dm
index f439320e7372d..1027b24d2f3ae 100644
--- a/code/modules/xenoarcheaology/tools/artifact_analyser.dm
+++ b/code/modules/xenoarcheaology/tools/artifact_analyser.dm
@@ -1,7 +1,7 @@
/obj/machinery/artifact_analyser
name = "Anomaly Analyser"
desc = "Studies the emissions of anomalous materials to discover their uses."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/machines/research/xenoarcheology_scanner.dmi'
icon_state = "xenoarch_console"
anchored = TRUE
density = TRUE
@@ -71,13 +71,14 @@
results = get_scan_info(scanned_object)
src.visible_message("[name] states, \"Scanning complete.\"")
- var/obj/item/paper/P = new(src.loc)
+ var/obj/item/paper/anomaly_scan/P = new(src.loc)
P.SetName("[src] report #[++report_num]")
P.info = "[src] analysis report #[report_num] "
P.info += " "
P.info += "\icon[scanned_object] [results]"
P.stamped = list(/obj/item/stamp)
P.queue_icon_update()
+ P.is_copy = FALSE
if(scanned_object && istype(scanned_object, /obj/machinery/artifact))
var/obj/machinery/artifact/A = scanned_object
diff --git a/code/modules/xenoarcheaology/tools/artifact_harvester.dm b/code/modules/xenoarcheaology/tools/artifact_harvester.dm
index 18d2443e4e552..29d759b8c0f03 100644
--- a/code/modules/xenoarcheaology/tools/artifact_harvester.dm
+++ b/code/modules/xenoarcheaology/tools/artifact_harvester.dm
@@ -1,6 +1,6 @@
/obj/machinery/artifact_harvester
name = "Exotic Particle Harvester"
- icon = 'icons/obj/virology.dmi'
+ icon = 'icons/obj/machines/research/virology.dmi'
icon_state = "incubator" //incubator_on
anchored = TRUE
density = TRUE
@@ -19,18 +19,19 @@
if(!owned_scanner)
owned_scanner = locate(/obj/machinery/artifact_scanpad) in orange(1, src)
-/obj/machinery/artifact_harvester/attackby(obj/I as obj, mob/user as mob)
+/obj/machinery/artifact_harvester/use_tool(obj/item/I, mob/living/user, list/click_params)
if(istype(I,/obj/item/anobattery))
if(!inserted_battery)
if(!user.unEquip(I, src))
- return
+ return TRUE
to_chat(user, SPAN_NOTICE("You insert [I] into [src]."))
src.inserted_battery = I
updateDialog()
+ return TRUE
else
to_chat(user, SPAN_WARNING("There is already a battery in [src]."))
- else
- return..()
+ return TRUE
+ return..()
/obj/machinery/artifact_harvester/attack_hand(mob/user as mob)
..()
diff --git a/code/modules/xenoarcheaology/tools/artifact_scanner.dm b/code/modules/xenoarcheaology/tools/artifact_scanner.dm
index e8ff15533fb2f..a7c75dc5cfe68 100644
--- a/code/modules/xenoarcheaology/tools/artifact_scanner.dm
+++ b/code/modules/xenoarcheaology/tools/artifact_scanner.dm
@@ -1,7 +1,7 @@
/obj/machinery/artifact_scanpad
name = "Anomaly Scanner Pad"
desc = "Place things here for scanning."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/machines/research/xenoarcheology_scanner.dmi'
icon_state = "xenoarch_scanner"
anchored = TRUE
density = FALSE
diff --git a/code/modules/xenoarcheaology/tools/coolant_tank.dm b/code/modules/xenoarcheaology/tools/coolant_tank.dm
index f8340e432da55..127f506f8f31b 100644
--- a/code/modules/xenoarcheaology/tools/coolant_tank.dm
+++ b/code/modules/xenoarcheaology/tools/coolant_tank.dm
@@ -1,7 +1,7 @@
/obj/structure/reagent_dispensers/coolanttank
name = "coolant tank"
desc = "A tank of industrial coolant."
- icon = 'icons/obj/objects.dmi'
+ icon = 'icons/obj/structures/liquid_tanks.dmi'
icon_state = "coolanttank"
initial_capacity = 10000
amount_per_transfer_from_this = 10
@@ -18,7 +18,7 @@
explode()
/obj/structure/reagent_dispensers/coolanttank/proc/explode()
- var/datum/effect/effect/system/smoke_spread/S = new /datum/effect/effect/system/smoke_spread
+ var/datum/effect/smoke_spread/S = new /datum/effect/smoke_spread
S.set_up(5, 0, src.loc)
playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3)
diff --git a/code/modules/xenoarcheaology/tools/equipment.dm b/code/modules/xenoarcheaology/tools/equipment.dm
index 72a0f782f098a..54c06b1153cec 100644
--- a/code/modules/xenoarcheaology/tools/equipment.dm
+++ b/code/modules/xenoarcheaology/tools/equipment.dm
@@ -53,13 +53,14 @@
/obj/item/clothing/suit/space/void/excavation/prepared
helmet = /obj/item/clothing/head/helmet/space/void/excavation
+ item_flags = ITEM_FLAG_THICKMATERIAL | ITEM_FLAG_INVALID_FOR_CHAMELEON
/obj/item/storage/belt/archaeology
name = "excavation gear-belt"
desc = "Can hold various excavation gear."
icon_state = "gearbelt"
item_state = ACCESSORY_SLOT_UTILITY
- can_hold = list(
+ contents_allowed = list(
/obj/item/storage/box/samplebags,
/obj/item/device/core_sampler,
/obj/item/pinpointer/radio,
diff --git a/code/modules/xenoarcheaology/tools/geosample_scanner.dm b/code/modules/xenoarcheaology/tools/geosample_scanner.dm
index 8f67f21c59e91..327ad52046198 100644
--- a/code/modules/xenoarcheaology/tools/geosample_scanner.dm
+++ b/code/modules/xenoarcheaology/tools/geosample_scanner.dm
@@ -4,7 +4,7 @@
anchored = TRUE
density = TRUE
atom_flags = ATOM_FLAG_OPEN_CONTAINER
- icon = 'icons/obj/virology.dmi'
+ icon = 'icons/obj/machines/research/virology.dmi'
icon_state = "analyser"
idle_power_usage = 20
@@ -63,41 +63,48 @@
ui_interact(user)
return TRUE
-/obj/machinery/radiocarbon_spectrometer/attackby(obj/I as obj, mob/user as mob)
+/obj/machinery/radiocarbon_spectrometer/use_tool(obj/item/I, mob/living/user, list/click_params)
if(scanning)
to_chat(user, SPAN_WARNING("You can't do that while [src] is scanning!"))
- else
- if(istype(I, /obj/item/stack/nanopaste))
- var/choice = alert("What do you want to do with the nanopaste?","Radiometric Scanner","Scan nanopaste","Fix seal integrity")
- if(choice == "Fix seal integrity")
- var/obj/item/stack/nanopaste/N = I
- var/amount_used = min(N.get_amount(), 10 - scanner_seal_integrity / 10)
- N.use(amount_used)
- scanner_seal_integrity = round(scanner_seal_integrity + amount_used * 10)
- return
- if(istype(I, /obj/item/reagent_containers/glass))
- var/choice = alert("What do you want to do with the container?","Radiometric Scanner","Add coolant","Empty coolant","Scan container")
- if(choice == "Add coolant")
- var/obj/item/reagent_containers/glass/G = I
- var/amount_transferred = min(src.reagents.maximum_volume - src.reagents.total_volume, G.reagents.total_volume)
- G.reagents.trans_to(src, amount_transferred)
- to_chat(user, SPAN_INFO("You empty [amount_transferred]u of coolant into [src]."))
- update_coolant()
- return
- else if(choice == "Empty coolant")
- var/obj/item/reagent_containers/glass/G = I
- var/amount_transferred = min(G.reagents.maximum_volume - G.reagents.total_volume, src.reagents.total_volume)
- src.reagents.trans_to(G, amount_transferred)
- to_chat(user, SPAN_INFO("You remove [amount_transferred]u of coolant from [src]."))
- update_coolant()
- return
- if(scanned_item)
- to_chat(user, SPAN_WARNING("\The [src] already has \a [scanned_item] inside!"))
- return
- if(!user.unEquip(I, src))
- return
- scanned_item = I
- to_chat(user, SPAN_NOTICE("You put \the [I] into \the [src]."))
+ return TRUE
+
+ if ((. = ..()))
+ return
+
+ if (istype(I, /obj/item/stack/nanopaste))
+ var/choice = alert("What do you want to do with the nanopaste?","Radiometric Scanner","Scan nanopaste","Fix seal integrity")
+ if(choice == "Fix seal integrity")
+ var/obj/item/stack/nanopaste/N = I
+ var/amount_used = min(N.get_amount(), 10 - scanner_seal_integrity / 10)
+ N.use(amount_used)
+ scanner_seal_integrity = round(scanner_seal_integrity + amount_used * 10)
+ return TRUE
+
+ if (istype(I, /obj/item/reagent_containers/glass))
+ var/choice = alert("What do you want to do with the container?","Radiometric Scanner","Add coolant","Empty coolant","Scan container")
+ if(choice == "Add coolant")
+ var/obj/item/reagent_containers/glass/G = I
+ var/amount_transferred = min(reagents.maximum_volume - reagents.total_volume, G.reagents.total_volume)
+ G.reagents.trans_to(src, amount_transferred)
+ to_chat(user, SPAN_INFO("You empty [amount_transferred]u of coolant into \the [src]."))
+ update_coolant()
+ return TRUE
+ else if(choice == "Empty coolant")
+ var/obj/item/reagent_containers/glass/G = I
+ var/amount_transferred = min(G.reagents.maximum_volume - G.reagents.total_volume, src.reagents.total_volume)
+ reagents.trans_to(G, amount_transferred)
+ to_chat(user, SPAN_INFO("You remove [amount_transferred]u of coolant from \the [src]."))
+ update_coolant()
+ return TRUE
+
+ if (scanned_item)
+ to_chat(user, SPAN_WARNING("\The [src] already has \a [scanned_item] inside!"))
+ return TRUE
+ if (!user.unEquip(I, src))
+ return TRUE
+ scanned_item = I
+ to_chat(user, SPAN_NOTICE("You put \the [I] into \the [src]."))
+ return TRUE
/obj/machinery/radiocarbon_spectrometer/proc/update_coolant()
var/total_purity = 0
@@ -293,7 +300,7 @@
var/anom_found = 0
if(G)
- data = " - Spectometric analysis on mineral sample has determined type [finds_as_strings[responsive_carriers.Find(G.source_mineral)]] "
+ data = " - Spectometric analysis on mineral sample has determined type [GLOB.responsive_carriers_to_finds[G.source_mineral]] "
if(G.age_billion > 0)
data += " - Radiometric dating shows age of [G.age_billion].[G.age_million] billion years "
else if(G.age_million > 0)
@@ -302,10 +309,8 @@
data += " - Radiometric dating shows age of [G.age_thousand * 1000 + G.age] years "
data += " - Chromatographic analysis shows the following materials present: "
for(var/carrier in G.find_presence)
- if(G.find_presence[carrier])
- var/index = responsive_carriers.Find(carrier)
- if(index > 0 && index <= length(finds_as_strings))
- data += " > [100 * G.find_presence[carrier]]% [finds_as_strings[index]] "
+ if (G.find_presence[carrier] && GLOB.responsive_carriers_to_finds[carrier])
+ data += " > [100 * G.find_presence[carrier]]% [GLOB.responsive_carriers_to_finds[carrier]] "
if(G.artifact_id && G.artifact_distance >= 0)
anom_found = 1
diff --git a/code/modules/xenoarcheaology/tools/suspension_generator.dm b/code/modules/xenoarcheaology/tools/suspension_generator.dm
index a429fc08e3692..26aa85944a47b 100644
--- a/code/modules/xenoarcheaology/tools/suspension_generator.dm
+++ b/code/modules/xenoarcheaology/tools/suspension_generator.dm
@@ -1,7 +1,7 @@
/obj/machinery/suspension_gen
name = "suspension field generator"
desc = "It has stubby bolts bolted up against its tracks for stabilizing."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/machines/research/suspension_generator.dmi'
icon_state = "suspension"
density = TRUE
construct_state = /singleton/machine_construction/default/panel_closed
@@ -10,7 +10,8 @@
active_power_usage = 5 KILOWATTS
machine_name = "suspension generator"
machine_desc = "Projects a pacifying energy field, used to hold xenofauna (among other things) for safe study."
- var/obj/effect/suspension_field/suspension_field
+ var/obj/suspension_field/suspension_field
+ obj_flags = OBJ_FLAG_ANCHORABLE
/obj/machinery/suspension_gen/Process()
if(suspension_field)
@@ -32,7 +33,7 @@
for(var/obj/item/I in T)
if(!length(suspension_field.contents))
suspension_field.icon_state = "energynet"
- suspension_field.overlays += "shield2"
+ suspension_field.AddOverlays("shield2")
I.forceMove(suspension_field)
/obj/machinery/suspension_gen/interact(mob/user)
@@ -90,23 +91,20 @@
return SPAN_NOTICE("Turn \the [src] off first.")
return ..()
-/obj/machinery/suspension_gen/attackby(obj/item/W, mob/user)
- if(component_attackby(W, user))
- return TRUE
- else if(isWrench(W))
- if(!suspension_field)
- anchored = !anchored
- to_chat(user, SPAN_INFO("You wrench the stabilising bolts [anchored ? "into place" : "loose"]."))
- if(anchored)
- desc = "Its tracks are securely held in place with securing bolts."
- icon_state = "suspension_wrenched"
- else
- desc = "It has stubby bolts bolted up against its tracks for stabilizing."
- icon_state = "suspension"
- playsound(loc, 'sound/items/Ratchet.ogg', 40)
- update_icon()
- else
- to_chat(user, SPAN_WARNING("You are unable to secure [src] while it is active!"))
+/obj/machinery/suspension_gen/can_anchor(obj/item/tool, mob/user, silent)
+ if (suspension_field)
+ to_chat(user, SPAN_WARNING("You are unable to wrench \the [src] while it is active!"))
+ return FALSE
+ return ..()
+
+/obj/machinery/suspension_gen/post_anchor_change()
+ if (anchored)
+ desc = "Its tracks are securely held in place with securing bolts."
+ icon_state = "suspension_wrenched"
+ else
+ desc = "It has stubby bolts bolted up against its tracks for stabilizing."
+ icon_state = "suspension"
+ ..()
//checks for whether the machine can be activated or not should already have occurred by this point
/obj/machinery/suspension_gen/proc/activate()
@@ -130,7 +128,7 @@
if(collected)
suspension_field.icon_state = "energynet"
- suspension_field.overlays += "shield2"
+ suspension_field.AddOverlays("shield2")
src.visible_message(SPAN_NOTICE("[icon2html(suspension_field, viewers(get_turf(src)))] [suspension_field] gently absconds [collected > 1 ? "something" : "several things"]."))
else
if(istype(T,/turf/simulated/mineral) || istype(T,/turf/simulated/wall))
@@ -181,18 +179,18 @@
set_dir(turn(dir, -90))
/obj/machinery/suspension_gen/on_update_icon()
- overlays.Cut()
+ ClearOverlays()
if(panel_open)
- overlays += "suspension_panel"
+ AddOverlays("suspension_panel")
. = ..()
-/obj/effect/suspension_field
+/obj/suspension_field
name = "energy field"
icon = 'icons/effects/effects.dmi'
anchored = TRUE
density = TRUE
-/obj/effect/suspension_field/Destroy()
+/obj/suspension_field/Destroy()
for(var/atom/movable/I in src)
I.dropInto(loc)
return ..()
diff --git a/code/modules/xenoarcheaology/tools/tools.dm b/code/modules/xenoarcheaology/tools/tools.dm
index d4dee14722079..6d7b2f36136b9 100644
--- a/code/modules/xenoarcheaology/tools/tools.dm
+++ b/code/modules/xenoarcheaology/tools/tools.dm
@@ -1,7 +1,7 @@
/obj/item/device/measuring_tape
name = "measuring tape"
desc = "A coiled metallic tape used to check dimensions and lengths."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_tape.dmi'
icon_state = "measuring"
origin_tech = list(TECH_MATERIAL = 1)
matter = list(MATERIAL_STEEL = 100)
@@ -10,14 +10,14 @@
/obj/item/storage/bag/fossils
name = "fossil satchel"
desc = "Transports delicate fossils in suspension so they don't break during transit."
- icon = 'icons/obj/mining.dmi'
+ icon = 'icons/obj/mining_satchel.dmi'
icon_state = "satchel"
slot_flags = SLOT_BELT | SLOT_POCKET
w_class = ITEM_SIZE_NORMAL
storage_slots = 50
max_storage_space = 200
max_w_class = ITEM_SIZE_NORMAL
- can_hold = list(/obj/item/fossil)
+ contents_allowed = list(/obj/item/fossil)
/obj/item/storage/box/samplebags
name = "sample bag box"
@@ -33,7 +33,7 @@
/obj/item/device/ano_scanner
name = "\improper Alden-Saraspova counter"
desc = "A device which aids in triangulation of exotic particles."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_scanner.dmi'
icon_state = "alden"
item_state = "analyzer"
origin_tech = list(TECH_BLUESPACE = 3, TECH_MAGNET = 3)
@@ -57,7 +57,7 @@
var/nearestSimpleTargetDist = -1
var/turf/cur_turf = get_turf(src)
- for (var/turf/simulated/mineral/T as anything in GLOB.xeno_artifact_turfs)
+ for (var/turf/simulated/mineral/T as anything in SSxenoarch.xeno_artifact_turfs)
if(T.density && T.artifact_find)
if(T.z == cur_turf.z)
var/cur_dist = get_dist(cur_turf, T) * 2
@@ -65,16 +65,16 @@
nearestTargetDist = cur_dist + rand() * 2 - 1
nearestTargetId = T.artifact_find.artifact_id
else
- GLOB.xeno_artifact_turfs -= T
+ SSxenoarch.xeno_artifact_turfs -= T
- for(var/turf/simulated/mineral/T as anything in GLOB.xeno_digsite_turfs)
+ for(var/turf/simulated/mineral/T as anything in SSxenoarch.xeno_artifact_turfs)
if(T.density && T.finds && length(T.finds))
if(T.z == cur_turf.z)
var/cur_dist = get_dist(cur_turf, T) * 2
if(nearestSimpleTargetDist < 0 || cur_dist < nearestSimpleTargetDist)
nearestSimpleTargetDist = cur_dist + rand() * 2 - 1
else
- GLOB.xeno_digsite_turfs -= T
+ SSxenoarch.xeno_digsite_turfs -= T
if(nearestTargetDist >= 0)
to_chat(user, SPAN_NOTICE("Exotic energy detected on wavelength '[nearestTargetId]' in a radius of [nearestTargetDist]m[nearestSimpleTargetDist > 0 ? "; small anomaly detected in a radius of [nearestSimpleTargetDist]m" : ""]"))
@@ -89,7 +89,7 @@
/obj/item/device/depth_scanner
name = "depth analysis scanner"
desc = "A device used to check spatial depth and density of rock outcroppings."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_scanner.dmi'
icon_state = "depth"
item_state = "xenoarch_device"
origin_tech = list(TECH_MAGNET = 2, TECH_ENGINEERING = 2, TECH_BLUESPACE = 2)
@@ -170,9 +170,8 @@
dat += "Anomaly depth: [current.depth] cm "
dat += "Anomaly size: [current.clearance] cm "
dat += "Dissonance spread: [current.dissonance_spread] "
- var/index = responsive_carriers.Find(current.material)
- if(index > 0 && index <= length(finds_as_strings))
- dat += "Anomaly material: [finds_as_strings[index]] "
+ if (GLOB.responsive_carriers_to_finds[current.material])
+ dat += "Anomaly material: [GLOB.responsive_carriers_to_finds[current.material]] "
else
dat += "Anomaly material: Unknown "
dat += "clear entry "
@@ -231,7 +230,7 @@
var/turf/T = get_turf(src)
var/zlevels = GetConnectedZlevels(T.z)
var/cur_dist = world.maxx+world.maxy
- for(var/obj/machinery/tele_beacon/R in world)
+ for(var/obj/machinery/tele_beacon/R as anything in SSmachines.get_machinery_of_type(/obj/machinery/tele_beacon))
if(!R.functioning())
continue
if(R.z in zlevels)
diff --git a/code/modules/xenoarcheaology/tools/tools_pickaxe.dm b/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
index 320f75c9bedaa..1cf3caa590426 100644
--- a/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
+++ b/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
@@ -1,7 +1,7 @@
/obj/item/pickaxe/xeno
name = "master xenoarch pickaxe"
desc = "A miniature excavation tool for precise digging."
- icon = 'icons/obj/xenoarchaeology.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_tools.dmi'
item_state = "xenoarch_pick"
force = 3
throwforce = 0
@@ -15,7 +15,7 @@
/obj/item/pickaxe/xeno/examine(mob/user)
. = ..()
- to_chat(user, "This tool has a [excavation_amount] centimeter excavation depth.")
+ . += SPAN_NOTICE("This tool has a [excavation_amount] centimeter excavation depth.")
/obj/item/pickaxe/xeno/brush
name = "wire brush"
@@ -83,6 +83,7 @@
/obj/item/pickaxe/xeno/drill
name = "excavation drill"
+ icon = 'icons/obj/tools/xenoarcheology_drill.dmi'
icon_state = "pick_drill1"
item_state = "xenoarch_device"
digspeed = 15
@@ -119,7 +120,7 @@
/obj/item/pickaxe/xeno/drill/examine(mob/user)
. = ..()
- to_chat(user, SPAN_NOTICE("This tool can have its excavation depth adjusted up to [max_depth]cm."))
+ . += SPAN_NOTICE("This tool can have its excavation depth adjusted up to [max_depth]cm.")
/obj/item/pickaxe/xeno/drill/plasma
name = "excavation plasma torch"
@@ -165,7 +166,7 @@
/obj/item/storage/excavation
name = "excavation pick set"
- icon = 'icons/obj/storage.dmi'
+ icon = 'icons/obj/tools/xenoarcheology_tools.dmi'
icon_state = "excavation"
item_state = "utility"
desc = "A rugged metal case containing a set of standardized picks used in archaeological digs."
@@ -175,10 +176,11 @@
storage_slots = 7
slot_flags = SLOT_BELT
w_class = ITEM_SIZE_NORMAL
- can_hold = list(/obj/item/pickaxe/xeno)
+ contents_allowed = list(/obj/item/pickaxe/xeno)
max_storage_space = 18
max_w_class = ITEM_SIZE_NORMAL
- use_to_pickup = 1
+ allow_quick_gather = TRUE
+ allow_quick_empty = TRUE
startswith = list(
/obj/item/pickaxe/xeno/brush,
/obj/item/pickaxe/xeno/one_pick,
@@ -189,8 +191,9 @@
/obj/item/pickaxe/xeno/six_pick)
/obj/item/storage/excavation/handle_item_insertion()
- ..()
- sort_picks()
+ . = ..()
+ if (.)
+ sort_picks()
/obj/item/storage/excavation/proc/sort_picks()
var/list/obj/item/pickaxe/xeno/picksToSort = list()
diff --git a/code/modules/xenoarcheaology/tools/transport_drone.dm b/code/modules/xenoarcheaology/tools/transport_drone.dm
new file mode 100644
index 0000000000000..b2396a28a82b5
--- /dev/null
+++ b/code/modules/xenoarcheaology/tools/transport_drone.dm
@@ -0,0 +1,278 @@
+/obj/item/device/drone_designator
+ name = "drone telemetry designator"
+ desc = "A small, handheld tool used to transmit location data to a transport drone."
+ icon = 'icons/obj/tools/drone_control.dmi'
+ icon_state = "pad_designator"
+ var/network = null
+ w_class = ITEM_SIZE_SMALL
+ matter = list(MATERIAL_PLASTIC = 60, MATERIAL_GLASS = 200)
+ origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 3)
+
+/obj/item/device/drone_designator/examine(mob/user, distance)
+ . = ..()
+ if (network)
+ . += SPAN_NOTICE("It is connected to the '[network]' network.")
+ else
+ . += SPAN_NOTICE("The device hasn't been linked to a transport network.")
+
+/obj/item/device/drone_designator/proc/recursive_validate_contents(atom/A, depth = 1)
+ if(depth >= 4)
+ return TRUE
+ if(istype(A, /obj/machinery/stasis_cage))
+ return TRUE //This is fine
+ if(istype(A,/mob/living))
+ return FALSE
+
+ for(var/atom/B as anything in A)
+ if(!recursive_validate_contents(B, depth + 1))
+ return FALSE
+
+ return TRUE
+
+/obj/item/device/drone_designator/proc/validate_target(obj/target, mob/user)
+ if (!istype(target))
+ return FALSE
+ if (target.anchored)
+ to_chat(user, SPAN_WARNING("\The [target] is anchored to the ground!"))
+ return FALSE
+ if (target.buckled_mob)
+ to_chat(user, SPAN_WARNING("There is a living being buckled to \the [target]."))
+ return FALSE
+ if (!recursive_validate_contents(target))
+ to_chat(user, SPAN_WARNING("There is an unsecured lifeform inside \the [target]."))
+ return FALSE
+ return TRUE
+
+/obj/item/device/drone_designator/proc/perform_pickup(obj/target, mob/user)
+ var/datum/local_network/lan = network ? GLOB.multilevel_local_networks[network] : null
+ if (lan)
+ var/list/drone_pads = lan.get_devices(/obj/machinery/drone_pad)
+ for(var/obj/machinery/drone_pad/pad in drone_pads)
+ if(pad.attempt_to_transport(target, user, src))
+ return
+ to_chat(user, SPAN_WARNING("It would seem there are no available drones to process this request!"))
+ else
+ to_chat(user, SPAN_WARNING("The network is not responding..."))
+
+/obj/item/device/drone_designator/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if (!network)
+ to_chat(user, SPAN_WARNING("\The [src] has not been connected to any network!"))
+ return
+ var/turf/T = target.loc
+ if (!istype(T))
+ return
+ var/area/A = T.loc
+ if (!(istype(A) && A.area_flags & AREA_FLAG_EXTERNAL))
+ to_chat(user, SPAN_WARNING("You should probably try to use this outside."))
+ return
+ if (validate_target(target, user))
+ var/datum/beam/B = user.Beam(BeamTarget = T, icon_state = "n_beam", maxdistance = get_dist(user, T), beam_type = /obj/ebeam)
+ user.visible_message(SPAN_NOTICE("\The [user] points \the [src] at \the [target]."))
+ playsound(src,'sound/effects/scanbeep.ogg',30,0)
+ if(do_after(user, 2 SECONDS, target, (DO_PUBLIC_UNIQUE & ~DO_USER_SAME_HAND) | DO_MOVE_CHECKS_TURFS))
+ QDEL_NULL(B)
+ if(!validate_target(target, user)) //Repeat checks
+ return
+
+ perform_pickup(target, user)
+ return
+ QDEL_NULL(B)
+
+/datum/transport_flight
+ var/turf/origin = null
+ var/obj/payload = null
+ var/time_of_arrival = 0
+
+/obj/machinery/drone_pad
+ name = "transport drone landing pad"
+ desc = "A small pad for transport drones to deposit their payloads at."
+ icon = 'icons/obj/machines/landing_pad.dmi'
+ icon_state = "pad_base"
+ anchored = TRUE
+ density = FALSE
+ layer = ABOVE_CATWALK_LAYER
+ base_type = /obj/machinery/drone_pad
+ construct_state = /singleton/machine_construction/default/panel_closed
+ uncreated_component_parts = null
+ active_power_usage = 0 KILOWATTS
+ machine_name = "transport drone landing pad"
+ machine_desc = "A small pad for transport drones to deposit their payloads."
+ var/initial_id_tag = null
+ var/datum/transport_flight/current_flight = null
+ var/active_timer = null
+ var/tile_travel_time = 15 SECONDS
+ stat_immune = MACHINE_STAT_NOPOWER | MACHINE_STAT_NOSCREEN | MACHINE_STAT_NOINPUT
+ maximum_component_parts = list(/obj/item/stock_parts = 6) //Circuit + 4 scanners + 1 crystal
+
+/obj/machinery/drone_pad/Destroy()
+ . = ..()
+ if (active_timer)
+ deltimer(active_timer)
+ active_timer = null
+ if (current_flight)
+ //Fail, just drop the thing where it came from,
+ current_flight.payload.forceMove(current_flight.origin)
+ QDEL_NULL(current_flight)
+
+/obj/machinery/drone_pad/on_update_icon()
+ . = ..()
+ ClearOverlays()
+ if (current_flight)
+ AddOverlays(list(
+ emissive_appearance(icon, "pad_incoming"),
+ image(icon, "pad_incoming")
+ ))
+ else
+ var/datum/extension/local_network_member/transport = get_extension(src, /datum/extension/local_network_member)
+ var/network = transport ? transport.id_tag : null
+ if (network && operable())
+ AddOverlays(emissive_appearance(icon, "pad_waiting"))
+ AddOverlays(image(icon, "pad_waiting"))
+ if(panel_open)
+ AddOverlays(image(icon, "pad_maintenance"))
+
+/obj/machinery/drone_pad/examine(mob/user, distance)
+ . = ..()
+ var/datum/extension/local_network_member/transport = get_extension(src, /datum/extension/local_network_member)
+ var/network = transport.id_tag
+ if (network)
+ . += SPAN_NOTICE("It is connected to the '[network]' network.")
+ else
+ . += SPAN_NOTICE("The device hasn't been linked to a transport network.")
+
+ if (current_flight)
+ . += SPAN_NOTICE("There is a drone en route to this pad. The drone is [ time_to_readable(current_flight.time_of_arrival - world.time) ] away.")
+
+/obj/machinery/drone_pad/proc/pickup_animation(obj/target)
+ var/image/object = new
+ object.appearance = target
+ object.loc = target.loc
+ var/image/drone = image('icons/obj/machines/landing_pad.dmi', target.loc, "pad_drone")
+ drone.plane = DEFAULT_PLANE
+ drone.layer = ABOVE_PROJECTILE_LAYER
+ drone.alpha = 10
+ drone.pixel_y = world.icon_size * 3
+
+
+ flick_overlay(object, GLOB.clients, 5 SECONDS)
+ flick_overlay(drone, GLOB.clients, 5 SECONDS)
+
+ //Animate drone descending to pick up crate
+ animate(
+ drone,
+ alpha = 255,
+ pixel_y = 0,
+ time = 3 SECONDS,
+ easing = CIRCULAR_EASING|EASE_OUT
+ )
+ animate(alpha = 0, pixel_y = world.icon_size * 3, time = 2 SECONDS, easing = BACK_EASING|EASE_IN)
+ //Wait for drone animation then animate the object too
+ animate(object, time = 3 SECONDS)
+ animate(alpha = 0, pixel_y = world.icon_size * 3, time = 2 SECONDS, easing = BACK_EASING|EASE_IN)
+
+/obj/machinery/drone_pad/proc/landing_animation(obj/target, turf/location)
+ var/image/object = new
+ object.appearance = target
+ object.loc = location
+ object.pixel_y = world.icon_size * 3
+ object.alpha = 10
+ var/image/drone = image('icons/mob/robots_flying.dmi', location, "drone-standard")
+ drone.plane = DEFAULT_PLANE
+ drone.layer = ABOVE_PROJECTILE_LAYER
+ drone.alpha = 10
+ drone.pixel_y = world.icon_size * 3
+
+
+ flick_overlay(object, GLOB.clients, 5 SECONDS)
+ flick_overlay(drone, GLOB.clients, 5 SECONDS)
+
+ //Animate drone descending to pick up crate
+ animate(
+ drone,
+ alpha = 255,
+ pixel_y = 0,
+ time = 3 SECONDS,
+ easing = CIRCULAR_EASING|EASE_OUT
+ )
+ animate(alpha = 0, pixel_y = world.icon_size * 3, time = 2 SECONDS, easing = QUAD_EASING|EASE_IN)
+ //Wait for drone animation then animate the object too
+ animate(object, pixel_y = 0, alpha = 255, time = 3 SECONDS, easing = CIRCULAR_EASING|EASE_OUT)
+
+/obj/machinery/drone_pad/proc/finish_moving()
+ landing_animation(current_flight.payload, src.loc)
+ addtimer(CALLBACK(current_flight.payload, TYPE_PROC_REF(/atom/movable, forceMove), src.loc), 3 SECONDS)
+ QDEL_NULL(current_flight)
+ update_icon()
+
+/obj/machinery/drone_pad/proc/attempt_to_transport(obj/target, mob/user, obj/item/device/drone_designator/designator)
+ if(inoperable())
+ return FALSE
+
+ if (current_flight)
+ return FALSE
+ else
+ //Overmap travel necessary?
+ var/obj/overmap/visitable/other = map_sectors["[target.z]"]
+ var/obj/overmap/visitable/self = map_sectors["[src.z]"]
+ //Start animation
+ pickup_animation(target)
+
+ target.forceMove(null) //Move to nullspace until arrival
+ current_flight = new()
+ current_flight.origin = target.loc
+ current_flight.payload = target
+ var/flight_time = 10 SECONDS //At least 10 seconds to get there, regardless
+
+ if (other && self && (other.z == self.z)) //Can visitables even not be in same overmap level?
+ flight_time += get_dist(other, self) * tile_travel_time
+ current_flight.time_of_arrival = world.time + flight_time
+
+ designator.audible_message(SPAN_NOTICE("\The [designator] pings, Request acknowledged. Drone en route. Delivery expected in T - [ (flight_time) / (1 SECOND)] seconds."))
+ audible_message(SPAN_NOTICE("\The [src] pings, Incoming payload. Delivery expected in T - [(flight_time) / (1 SECOND)] seconds."))
+
+
+ active_timer = addtimer(CALLBACK(src, PROC_REF(finish_moving)), flight_time, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE)
+ update_icon()
+ return TRUE
+
+
+/obj/machinery/drone_pad/Initialize()
+ . = ..()
+ set_extension(src, /datum/extension/local_network_member/multilevel)
+ if (initial_id_tag)
+ var/datum/extension/local_network_member/pointdefense = get_extension(src, /datum/extension/local_network_member)
+ pointdefense.set_tag(null, initial_id_tag)
+ update_icon()
+
+/obj/machinery/drone_pad/multitool_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_SUCCESS
+ var/datum/extension/local_network_member/transport = get_extension(src, /datum/extension/local_network_member)
+ transport.get_new_tag(user)
+ update_icon()
+
+/obj/machinery/drone_pad/use_tool(obj/item/tool, mob/user)
+ var/datum/extension/local_network_member/transport = get_extension(src, /datum/extension/local_network_member)
+ var/obj/item/device/drone_designator/designator = tool
+ if (istype(designator))
+ if (!transport.id_tag)
+ to_chat(user, SPAN_WARNING("\The [src] has not yet been set up."))
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 1, -3)
+ else if (designator.network == transport.id_tag)
+ to_chat(user, SPAN_WARNING("\The [tool] is already synchronized with this network."))
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 1, -3)
+ else
+ to_chat(user, SPAN_NOTICE("\The [tool] was synchronized with the [transport.id_tag] network."))
+ designator.network = transport.id_tag
+ playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 1, -3)
+ update_icon()
+ return TRUE
+
+ return ..()
+
+/obj/machinery/drone_pad/RefreshParts()
+ . = ..()
+ // Calculates an average rating of components that affect shooting rate
+ var/scanner_rate = total_component_rating_of_type(/obj/item/stock_parts/scanning_module)
+ tile_travel_time = (15 SECONDS) * 4 / (scanner_rate ? scanner_rate : 1)
+ update_icon()
diff --git a/code/modules/xenoarcheaology/triggers/_trigger.dm b/code/modules/xenoarcheaology/triggers/_trigger.dm
index 4a0150bd8192c..ac4c14cfdca49 100644
--- a/code/modules/xenoarcheaology/triggers/_trigger.dm
+++ b/code/modules/xenoarcheaology/triggers/_trigger.dm
@@ -1,6 +1,7 @@
/datum/artifact_trigger
var/name = "nothing ever"
- var/toggle = TRUE //TRUE - effect is toggled between on and off when triggered. FALSE - effects is on when triggered, off if not triggered.
+ var/toggle = TRUE //TRUE - effect is toggled between on and off when triggered. FALSE - effects is on when triggered, off if not triggered.
+ var/trigger_type = TRIGGER_SIMPLE
//There procs should return TRUE if trigger is activated, FALSE if nothing happens
@@ -20,4 +21,4 @@
return FALSE
/datum/artifact_trigger/proc/on_water_act(depth)
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/modules/xenoarcheaology/triggers/chemical.dm b/code/modules/xenoarcheaology/triggers/chemical.dm
index 6108f5417a0ad..6ecf3ec8a006f 100644
--- a/code/modules/xenoarcheaology/triggers/chemical.dm
+++ b/code/modules/xenoarcheaology/triggers/chemical.dm
@@ -5,7 +5,7 @@
/datum/artifact_trigger/chemical/New()
if(isnull(required_chemicals))
name = "presence of either an acid, toxin, or water"
- required_chemicals = list(pick(/datum/reagent/acid, /datum/reagent/toxin, /datum/reagent/water))
+ required_chemicals = typesof(pick(/datum/reagent/acid, /datum/reagent/toxin, /datum/reagent/water))
/datum/artifact_trigger/chemical/on_hit(obj/O, mob/user)
. = ..()
@@ -15,11 +15,9 @@
/datum/artifact_trigger/chemical/water
name = "presence of water"
- required_chemicals = list(
- /datum/reagent/water,
- /datum/reagent/water/boiling,
- /datum/reagent/drink/ice
- )
+
+/datum/artifact_trigger/chemical/water/New()
+ required_chemicals = typesof(/datum/reagent/water)
/datum/artifact_trigger/chemical/water/on_water_act(depth)
if(depth > FLUID_EVAPORATION_POINT)
@@ -32,21 +30,28 @@
/datum/reagent/acid/polyacid,
/datum/reagent/diethylamine
)
+ trigger_type = TRIGGER_COMPLEX
+/datum/artifact_trigger/chemical/acid/New()
+ required_chemicals = typesof(/datum/reagent/acid)
/datum/artifact_trigger/chemical/volatile
name = "presence of volatile chemicals"
required_chemicals = list(
/datum/reagent/toxin/phoron,
/datum/reagent/thermite,
- /datum/reagent/fuel
+ /datum/reagent/fuel,
+ /datum/reagent/toxin/pyrotoxin,
+ /datum/reagent/potassium,
+ /datum/reagent/napalm,
+ /datum/reagent/napalm/b,
+ /datum/reagent/nitroglycerin,
+ /datum/reagent/toxin/phoron/oxygen,
+ /datum/reagent/gunpowder
)
+ trigger_type = TRIGGER_COMPLEX
/datum/artifact_trigger/chemical/toxic
name = "presence of toxins"
- required_chemicals = list(
- /datum/reagent/toxin,
- /datum/reagent/toxin/cyanide,
- /datum/reagent/toxin/amatoxin,
- /datum/reagent/toxin/venom,
- /datum/reagent/toxin/chlorine
- )
+
+/datum/artifact_trigger/chemical/toxic/New()
+ required_chemicals = typesof(/datum/reagent/toxin)
diff --git a/code/modules/xenoarcheaology/triggers/gas.dm b/code/modules/xenoarcheaology/triggers/gas.dm
index 3f9c4daf4859f..08f9a0d827821 100644
--- a/code/modules/xenoarcheaology/triggers/gas.dm
+++ b/code/modules/xenoarcheaology/triggers/gas.dm
@@ -32,3 +32,4 @@
/datum/artifact_trigger/gas/phoron
name = "concentration of phoron"
gas_needed = list(GAS_PHORON = 5)
+ trigger_type = TRIGGER_COMPLEX
diff --git a/code/modules/xenoarcheaology/triggers/temperature.dm b/code/modules/xenoarcheaology/triggers/temperature.dm
index 889bf3131b6d9..ce3971a15d748 100644
--- a/code/modules/xenoarcheaology/triggers/temperature.dm
+++ b/code/modules/xenoarcheaology/triggers/temperature.dm
@@ -38,7 +38,7 @@
/datum/artifact_trigger/temperature/heat/on_hit(obj/O, mob/user)
. = ..()
- if(!. && isflamesource(O))
+ if (!. && O.IsFlameSource())
return TRUE
/datum/artifact_trigger/temperature/heat/on_explosion(severity)
diff --git a/code/modules/xenoarcheaology/triggers/touch.dm b/code/modules/xenoarcheaology/triggers/touch.dm
index 90d5275e28dc8..f7af069ba3857 100644
--- a/code/modules/xenoarcheaology/triggers/touch.dm
+++ b/code/modules/xenoarcheaology/triggers/touch.dm
@@ -26,6 +26,7 @@
/datum/artifact_trigger/touch/synth
name = "robotic touch"
+ trigger_type = TRIGGER_COMPLEX
/datum/artifact_trigger/touch/synth/can_touch(mob/living/L, bodypart)
if(issilicon(L))
@@ -38,4 +39,3 @@
if(E && BP_IS_ROBOTIC(E))
return TRUE
return FALSE
-
\ No newline at end of file
diff --git a/code/modules/xgm/xgm_gas_data.dm b/code/modules/xgm/xgm_gas_data.dm
index 9c46d5fc03c0b..dd08dd01e2e51 100644
--- a/code/modules/xgm/xgm_gas_data.dm
+++ b/code/modules/xgm/xgm_gas_data.dm
@@ -9,7 +9,7 @@ var/global/datum/xgm_gas_data/gas_data
var/list/specific_heat = list()
//Molar mass of the gas. Used for calculating specific entropy.
var/list/molar_mass = list()
- //Tile overlays. /obj/effect/gas_overlay, created from references to 'icons/effects/tile_effects.dmi'
+ //Tile overlays. /obj/gas_overlay, created from references to 'icons/effects/tile_effects.dmi'
var/list/tile_overlay = list()
//Optional color for tile overlay
var/list/tile_overlay_color = list()
@@ -54,7 +54,7 @@ var/global/datum/xgm_gas_data/gas_data
/hook/startup/proc/generateGasData()
gas_data = new
- for(var/p in (typesof(/singleton/xgm_gas) - /singleton/xgm_gas))
+ for(var/p in (subtypesof(/singleton/xgm_gas)))
var/singleton/xgm_gas/gas = new p //avoid initial() because of potential New() actions
if(gas.id in gas_data.gases)
@@ -83,7 +83,7 @@ var/global/datum/xgm_gas_data/gas_data
return 1
-/obj/effect/gas_overlay
+/obj/gas_overlay
name = "gas"
desc = "You shouldn't be clicking this."
icon = 'icons/effects/tile_effects.dmi'
@@ -93,27 +93,46 @@ var/global/datum/xgm_gas_data/gas_data
mouse_opacity = 0
var/gas_id
-/obj/effect/gas_overlay/proc/update_alpha_animation(new_alpha)
+/obj/gas_overlay/proc/update_alpha_animation(new_alpha)
animate(src, alpha = new_alpha)
alpha = new_alpha
animate(src, alpha = 0.8 * new_alpha, time = 10, easing = SINE_EASING | EASE_OUT, loop = -1)
animate(alpha = new_alpha, time = 10, easing = SINE_EASING | EASE_IN, loop = -1)
-/obj/effect/gas_overlay/Initialize(mapload, gas)
+/obj/gas_overlay/Initialize(mapload, gas)
. = ..()
gas_id = gas
if(gas_data.tile_overlay[gas_id])
icon_state = gas_data.tile_overlay[gas_id]
color = gas_data.tile_overlay_color[gas_id]
-/obj/effect/gas_overlay/heat
+/obj/gas_overlay/heat
name = "gas"
desc = "You shouldn't be clicking this."
plane = HEAT_EFFECT_PLANE
gas_id = GAS_HEAT
render_source = HEAT_EFFECT_TARGET
-/obj/effect/gas_overlay/heat/Initialize(mapload, gas)
+/obj/gas_overlay/heat/Initialize(mapload, gas)
. = ..()
icon = null
icon_state = null
+
+/obj/effect/gas_cold_back
+ render_source = COLD_EFFECT_BACK_TARGET
+ plane = DEFAULT_PLANE
+ layer = BELOW_OBJ_LAYER
+
+/obj/gas_overlay/cold
+ name = "gas"
+ desc = "You shouldn't be clicking this."
+ gas_id = GAS_COLD
+ render_source = COLD_EFFECT_TARGET
+ var/obj/effect/gas_cold_back/b = null
+
+/obj/gas_overlay/cold/Initialize(mapload, gas)
+ . = ..()
+ icon = null
+ icon_state = null
+ b = new()
+ vis_contents += b
diff --git a/code/modules/xgm/xgm_gas_mixture.dm b/code/modules/xgm/xgm_gas_mixture.dm
index 3e28a0d96a037..8d1bf0080dd1f 100644
--- a/code/modules/xgm/xgm_gas_mixture.dm
+++ b/code/modules/xgm/xgm_gas_mixture.dm
@@ -87,16 +87,16 @@
//Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly.
//Does not modify giver in any way.
-/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver)
+/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
if(!giver)
return
- if(abs(temperature-giver.temperature)>MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
+ if(abs(temperature-giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
var/giver_heat_capacity = giver.heat_capacity()
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
if(combined_heat_capacity != 0)
- temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
+ temperature = (giver.temperature * giver_heat_capacity + temperature*self_heat_capacity) / combined_heat_capacity
if((group_multiplier != 1)||(giver.group_multiplier != 1))
for(var/g in giver.gas)
@@ -297,7 +297,7 @@
. += gas[g]
//Copies gas and temperature from another gas_mixture.
-/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
+/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
gas = sample.gas.Copy()
temperature = sample.temperature
@@ -307,7 +307,7 @@
//Checks if we are within acceptable range of another gas_mixture to suspend processing or merge.
-/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample, vacuum_exception = 0)
+/datum/gas_mixture/proc/compare(datum/gas_mixture/sample, vacuum_exception = 0)
if(!sample) return 0
if(vacuum_exception)
@@ -346,53 +346,81 @@
//Rechecks the gas_mixture and adjusts the graphic list if needed.
//Two lists can be passed by reference if you need know specifically which graphics were added and removed.
/datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null)
- for(var/obj/effect/gas_overlay/O in graphic)
- if(istype(O, /obj/effect/gas_overlay/heat))
+ for(var/obj/gas_overlay/gas_overlay in graphic)
+ if(istype(gas_overlay, /obj/gas_overlay/heat))
continue
- if(gas[O.gas_id] <= gas_data.overlay_limit[O.gas_id])
- LAZYADD(graphic_remove, O)
- for(var/g in gas_data.overlay_limit)
+
+ if(istype(gas_overlay, /obj/gas_overlay/cold))
+ continue
+
+ if(gas[gas_overlay.gas_id] <= gas_data.overlay_limit[gas_overlay.gas_id])
+ LAZYADD(graphic_remove, gas_overlay)
+
+ var/should_generate_temp_overlay = FALSE
+ for(var/gas_id in gas_data.overlay_limit)
//Overlay isn't applied for this gas, check if it's valid and needs to be added.
- if(gas[g] > gas_data.overlay_limit[g])
- var/tile_overlay = get_tile_overlay(g)
+ if(gas[gas_id] > gas_data.overlay_limit[gas_id])
+ should_generate_temp_overlay = TRUE
+ var/tile_overlay = get_tile_overlay(gas_id)
if(!(tile_overlay in graphic))
LAZYADD(graphic_add, tile_overlay)
- . = 0
-
- var/tile_overlay = get_tile_overlay(GAS_HEAT)
- //If it's hot add something
- if(temperature >= CARBON_LIFEFORM_FIRE_RESISTANCE)
- if(!(tile_overlay in graphic))
- LAZYADD(graphic_add, tile_overlay)
- else if (tile_overlay in graphic)
- LAZYADD(graphic_remove, tile_overlay)
+ . = FALSE
+
+ if(should_generate_temp_overlay)
+ var/heat_overlay = get_tile_overlay(GAS_HEAT)
+ //If it's hot add something
+ if(temperature >= CARBON_LIFEFORM_FIRE_RESISTANCE)
+ if(!(heat_overlay in graphic))
+ LAZYADD(graphic_add, heat_overlay)
+ else if (heat_overlay in graphic)
+ LAZYADD(graphic_remove, heat_overlay)
+
+ var/cold_overlay = get_tile_overlay(GAS_COLD)
+ if(temperature <= FOGGING_TEMPERATURE && (return_pressure() >= (ONE_ATMOSPHERE / 4)))
+ if(!(cold_overlay in graphic))
+ LAZYADD(graphic_add, cold_overlay)
+ else if (cold_overlay in graphic)
+ LAZYADD(graphic_remove, cold_overlay)
//Apply changes
if(graphic_add && length(graphic_add))
graphic |= graphic_add
- . = 1
+ . = TRUE
+
if(graphic_remove && length(graphic_remove))
graphic -= graphic_remove
- . = 1
- if(length(graphic))
- var/pressure_mod = clamp(return_pressure() / ONE_ATMOSPHERE, 0, 2)
- for(var/obj/effect/gas_overlay/O in graphic)
- if(istype(O, /obj/effect/gas_overlay/heat)) //Heat based
- var/new_alpha = clamp(max(125, 255 * ((temperature - CARBON_LIFEFORM_FIRE_RESISTANCE) / CARBON_LIFEFORM_FIRE_RESISTANCE * 4)), 125, 255)
- if(new_alpha != O.alpha)
- O.update_alpha_animation(new_alpha)
- continue
- var/concentration_mod = clamp(gas[O.gas_id] / total_moles, 0.1, 1)
- var/new_alpha = min(230, round(pressure_mod * concentration_mod * 180, 5))
- if(new_alpha != O.alpha)
- O.update_alpha_animation(new_alpha)
+ . = TRUE
+
+ if(!length(graphic))
+ return .
+
+ var/pressure_mod = clamp(return_pressure() / ONE_ATMOSPHERE, 0, 2)
+ for(var/obj/gas_overlay/overlay in graphic)
+ if(istype(overlay, /obj/gas_overlay/heat)) //Heat based
+ var/new_alpha = clamp(max(125, 255 * ((temperature - CARBON_LIFEFORM_FIRE_RESISTANCE) / CARBON_LIFEFORM_FIRE_RESISTANCE * 4)), 125, 255)
+ if(new_alpha != overlay.alpha)
+ overlay.update_alpha_animation(new_alpha)
+ continue
+
+ if(istype(overlay, /obj/gas_overlay/cold))
+ var/new_alpha = clamp(max(125, 200 * (1 - ((temperature - MAX_FOG_TEMPERATURE) / (FOGGING_TEMPERATURE - MAX_FOG_TEMPERATURE)))), 125, 200)
+ if(new_alpha != overlay.alpha)
+ overlay.update_alpha_animation(new_alpha)
+ continue
+
+ var/concentration_mod = clamp(gas[overlay.gas_id] / total_moles, 0.1, 1)
+ var/new_alpha = min(230, round(pressure_mod * concentration_mod * 180, 5))
+ if(new_alpha != overlay.alpha)
+ overlay.update_alpha_animation(new_alpha)
/datum/gas_mixture/proc/get_tile_overlay(gas_id)
if(!LAZYACCESS(tile_overlay_cache, gas_id))
if(gas_id == GAS_HEAT) //Not a real gas but functionally same thing
- LAZYSET(tile_overlay_cache, gas_id, new/obj/effect/gas_overlay/heat(null, GAS_HEAT))
+ LAZYSET(tile_overlay_cache, gas_id, new/obj/gas_overlay/heat(null, GAS_HEAT))
+ else if(gas_id == GAS_COLD) //Not a real gas but functionally same thing
+ LAZYSET(tile_overlay_cache, gas_id, new/obj/gas_overlay/cold(null, GAS_COLD))
else
- LAZYSET(tile_overlay_cache, gas_id, new/obj/effect/gas_overlay(null, gas_id))
+ LAZYSET(tile_overlay_cache, gas_id, new/obj/gas_overlay(null, gas_id))
return tile_overlay_cache[gas_id]
//Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier.
@@ -458,10 +486,8 @@
if(full_heat_capacity + s_full_heat_capacity)
temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
- //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD.
if(length(sharing_lookup_table) >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
ratio = sharing_lookup_table[connecting_tiles]
- //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD
for(var/g in avg_gas)
gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
diff --git a/code/procs/AStar.dm b/code/procs/AStar.dm
index 07ff423fa51ad..68bb9138aad03 100644
--- a/code/procs/AStar.dm
+++ b/code/procs/AStar.dm
@@ -12,7 +12,7 @@ And for the distance one i wrote:
/turf/proc/Distance
So an example use might be:
-src.path_list = AStar(src.loc, target.loc, /turf/proc/AdjacentTurfs, /turf/proc/Distance)
+src.path_list = AStar(src.loc, target.loc, TYPE_PROC_REF(/turf, AdjacentTurfs), TYPE_PROC_REF(/turf, Distance) )
Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use.
@@ -61,7 +61,7 @@ length to avoid portals or something i guess?? Not that they're counted right no
/proc/AStar(start, end, adjacent, dist, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude)
- var/PriorityQueue/open = new /PriorityQueue (/proc/PathWeightCompare)
+ var/PriorityQueue/open = new /PriorityQueue(GLOBAL_PROC_REF(PathWeightCompare))
var/list/closed = list()
var/list/path
var/list/path_node_by_position = list()
@@ -73,7 +73,7 @@ length to avoid portals or something i guess?? Not that they're counted right no
var/PathNode/current = open.Dequeue()
closed.Add(current.position)
if(current.position == end || call(current.position, dist)(end) <= min_target_dist)
- path = new /list(current.nodes_traversed + 1)
+ path = new (current.nodes_traversed + 1)
path[length(path)] = current.position
var/index = length(path) - 1
while(current.previous_node)
diff --git a/code/procs/announce.dm b/code/procs/announce.dm
index 573637e3cda44..81f6d1cdb06bd 100644
--- a/code/procs/announce.dm
+++ b/code/procs/announce.dm
@@ -1,6 +1,6 @@
var/global/datum/announcement/priority/priority_announcement = new(do_log = 0)
var/global/datum/announcement/priority/command/command_announcement = new(do_log = 0, do_newscast = 1)
-var/global/datum/announcement/minor/minor_announcement = new(new_sound = 'sound/AI/commandreport.ogg',)
+var/global/datum/announcement/minor/minor_announcement = new(new_sound = ANNOUNCER_COMMANDREPORT,)
/datum/announcement
var/title = "Attention"
@@ -39,10 +39,10 @@ var/global/datum/announcement/minor/minor_announcement = new(new_sound = 'sound/
message = sanitize(message, extra = 0)
message_title = sanitizeSafe(message_title)
- var/msg = FormMessage(message, message_title)
+ FormRadioMessage(message, message_title, length(zlevels) ? pick(zlevels) : 1)
+
for(var/mob/M in GLOB.player_list)
if(M.client && (get_z(M) in (zlevels | GLOB.using_map.admin_levels)) && !istype(M,/mob/new_player) && !isdeaf(M))
- to_chat(M, msg)
if(message_sound && M.client.get_preference_value(/datum/client_preference/play_announcement_sfx) == GLOB.PREF_YES)
sound_to(M, message_sound)
@@ -53,34 +53,37 @@ var/global/datum/announcement/minor/minor_announcement = new(new_sound = 'sound/
log_say("[key_name(usr)] has made \a [announcement_type]: [message_title] - [message] - [announcer]")
message_admins("[key_name_admin(usr)] has made \a [announcement_type].", 1)
-/datum/announcement/proc/FormMessage(message as text, message_title as text)
- . = "
[message_title]
"
- . += " [SPAN_CLASS("alert", "[message]")]"
- if (announcer)
- . += " [SPAN_CLASS("alert", " -[html_encode(announcer)]")]"
+/////// ANNOUNCEMENT PROCS VIA RADIO ///////
+/datum/announcement/proc/FormRadioMessage(message as text, message_title as text, zlevel)
+ GLOB.global_announcer.autosay(SPAN_BOLD(FONT_LARGE("[SPAN_WARNING("[title]:")] [message]")), announcer ? announcer : AUTO_ANNOUNCER_NAME,, zlevel)
+
+/datum/announcement/minor/FormRadioMessage(message as text, message_title as text, zlevel)
+ GLOB.global_announcer.autosay(message, AUTO_ANNOUNCER_NAME,, zlevel)
+
+/datum/announcement/priority/FormRadioMessage(message as text, message_title as text, zlevel)
+ GLOB.global_announcer.autosay(SPAN_BOLD(FONT_LARGE("[SPAN_WARNING("[message_title]:")] [message]")), announcer ? announcer : AUTO_ANNOUNCER_NAME,, zlevel)
+
+/datum/announcement/priority/command/FormRadioMessage(message as text, message_title as text, zlevel)
+ GLOB.global_announcer.autosay(SPAN_BOLD(FONT_LARGE("[SPAN_WARNING("[GLOB.using_map.boss_name] Update[message_title ? " — [message_title]" : ""]:")] [message]")), AUTO_ANNOUNCER_NAME,, zlevel)
-/datum/announcement/minor/FormMessage(message as text, message_title as text)
- . = "[message]"
+/datum/announcement/priority/security/FormRadioMessage(message as text, message_title as text, zlevel)
+ GLOB.global_announcer.autosay(SPAN_BOLD(FONT_LARGE("[SPAN_WARNING("[message_title]:")] [message]")), AUTO_ANNOUNCER_NAME,, zlevel)
-/datum/announcement/priority/FormMessage(message as text, message_title as text)
- . = "
"
+/datum/announcement/minor/Message(message as text, message_title as text)
+ GLOB.global_announcer.autosay(message, AUTO_ANNOUNCER_NAME)
- . += " [SPAN_CLASS("alert", "[message]")] "
- . += " "
+/datum/announcement/priority/Message(message as text, message_title as text)
+ GLOB.global_announcer.autosay(FONT_LARGE("[SPAN_CLASS("alert", "[message_title]:")] [message]"), announcer ? announcer : AUTO_ANNOUNCER_NAME)
-/datum/announcement/priority/security/FormMessage(message as text, message_title as text)
- . = FONT_HUGE(SPAN_COLOR("red", message_title))
- . += " [SPAN_COLOR("red", message)]"
+/datum/announcement/priority/command/Message(message as text, message_title as text)
+ GLOB.global_announcer.autosay(FONT_LARGE("[SPAN_WARNING("[GLOB.using_map.boss_name] [message_title]:")] [message]"), AUTO_ANNOUNCER_NAME)
+/datum/announcement/priority/security/Message(message as text, message_title as text)
+ GLOB.global_announcer.autosay(FONT_LARGE("[SPAN_COLOR("red", "[message_title]:")] [message]"), AUTO_ANNOUNCER_NAME)
/datum/announcement/proc/NewsCast(message, list/zlevels)
if (!message || !islist(zlevels))
diff --git a/code/procs/hud.dm b/code/procs/hud.dm
index 5a5e2994db86e..5e3bdb2c4d983 100644
--- a/code/procs/hud.dm
+++ b/code/procs/hud.dm
@@ -47,7 +47,7 @@ the HUD updates properly! */
/proc/process_jani_hud(mob/M, mob/Alt)
var/datum/arranged_hud_process/P = arrange_hud_process(M, Alt, GLOB.jani_hud_users)
- for (var/obj/effect/decal/cleanable/dirtyfloor in view(P.Mob))
+ for (var/obj/decal/cleanable/dirtyfloor in view(P.Mob))
if(P.Client)
P.Client.images += dirtyfloor.hud_overlay
@@ -57,6 +57,7 @@ the HUD updates properly! */
var/turf/Turf
/proc/arrange_hud_process(mob/M, mob/Alt, list/hud_list)
+ RETURN_TYPE(/datum/arranged_hud_process)
hud_list |= M
var/datum/arranged_hud_process/P = new
P.Client = M.client
@@ -83,9 +84,11 @@ the HUD updates properly! */
GLOB.jani_hud_users -= src
/mob/proc/in_view(turf/T)
+ RETURN_TYPE(/list)
return view(T)
/mob/observer/eye/in_view(turf/T)
+ RETURN_TYPE(/list)
var/list/viewed = new
for(var/mob/living/carbon/human/H in SSmobs.mob_list)
if(get_dist(H, T) <= 7)
diff --git a/code/procs/radio.dm b/code/procs/radio.dm
index e7fd0f17385d5..146e34fe8a117 100644
--- a/code/procs/radio.dm
+++ b/code/procs/radio.dm
@@ -37,6 +37,7 @@
var/list/receiver_reception = new
/proc/get_message_server(z)
+ RETURN_TYPE(/obj/machinery/message_server)
if(message_servers)
var/list/zlevels = GLOB.using_map.contact_levels
if(z)
@@ -60,6 +61,7 @@
return TELECOMMS_RECEPTION_NONE
/proc/get_reception(atom/sender, receiver, message = "", do_sleep = 1)
+ RETURN_TYPE(/datum/reception)
var/datum/reception/reception = new
// check if telecomms I/O route 1459 is stable
@@ -73,6 +75,7 @@
return reception
/proc/get_receptions(atom/sender, list/atom/receivers, do_sleep = 1)
+ RETURN_TYPE(/datum/receptions)
var/datum/receptions/receptions = new
receptions.message_server = get_message_server()
diff --git a/code/unit_tests/_defines.dm b/code/unit_tests/_defines.dm
deleted file mode 100644
index ae938c377b45c..0000000000000
--- a/code/unit_tests/_defines.dm
+++ /dev/null
@@ -1 +0,0 @@
-#define MAX_UNIT_TEST_RUN_TIME 2 MINUTES
diff --git a/code/unit_tests/closets.dm b/code/unit_tests/closets.dm
deleted file mode 100644
index 52be58faf86aa..0000000000000
--- a/code/unit_tests/closets.dm
+++ /dev/null
@@ -1,75 +0,0 @@
-/datum/unit_test/closet_decal_test
- name = "CLOSETS: All Closet Appearances Shall Have Sane Values"
- var/list/check_base_states = list("base", "lock", "light", "open", "interior", "welded", "sparks")
- var/list/except_appearances = list()
-
-/datum/unit_test/closet_decal_test/start_test()
-
- var/list/bad_singleton
- var/list/bad_icon
- var/list/bad_colour
- var/list/bad_base_icon
- var/list/bad_base_state
- var/list/bad_decal_icon
- var/list/bad_decal_colour
- var/list/bad_decal_state
-
- for(var/check_appearance in typesof(/singleton/closet_appearance)-except_appearances)
- var/singleton/closet_appearance/closet = GET_SINGLETON(check_appearance)
- if(!closet)
- LAZYADD(bad_singleton, "[check_appearance]")
- continue
-
- if(!closet.icon)
- LAZYADD(bad_icon, "[closet.type]")
- if(!closet.color)
- LAZYADD(bad_colour, "[closet.type]")
- if(!closet.base_icon)
- LAZYADD(bad_base_icon, "[closet.type]")
- else
- var/list/base_states = icon_states(closet.base_icon)
- for(var/thing in check_base_states)
- if(!(thing in base_states))
- LAZYADD(bad_base_state, "[closet.type] - [thing] - [closet.base_icon]")
- if(LAZYLEN(closet.decals) && !closet.decal_icon)
- LAZYADD(bad_decal_icon, "[closet.type]")
- else
- var/list/decal_states = icon_states(closet.decal_icon)
- for(var/thing in closet.decals)
- if(isnull(closet.decals[thing]))
- LAZYADD(bad_decal_colour, "[check_appearance] - [thing]")
- if(!(thing in decal_states))
- LAZYADD(bad_decal_state, "[check_appearance] - [thing] - [closet.decal_icon]")
-
- if( \
- LAZYLEN(bad_singleton) || \
- LAZYLEN(bad_icon) || \
- LAZYLEN(bad_colour) || \
- LAZYLEN(bad_base_icon) || \
- LAZYLEN(bad_base_state) || \
- LAZYLEN(bad_decal_icon) || \
- LAZYLEN(bad_decal_colour) || \
- LAZYLEN(bad_decal_state) \
- )
- var/fail_msg = "Insane closet appearances found: "
- if(LAZYLEN(bad_singleton))
- fail_msg += "\nSingleton did not add itself to appropriate global list:\n[jointext("\t[bad_icon]", "\n")]."
- if(LAZYLEN(bad_icon))
- fail_msg += "\nNull final icon values:\n[jointext("\t[bad_icon]", "\n")]."
- if(LAZYLEN(bad_colour))
- fail_msg += "\nNull color values:\n[jointext("\t[bad_colour]", "\n")]."
- if(LAZYLEN(bad_base_icon))
- fail_msg += "\nNull base icon value:\n[jointext("\t[bad_base_icon]", "\n")]."
- if(LAZYLEN(bad_base_state))
- fail_msg += "\nMissing state from base icon:\n[jointext("\t[bad_base_state]", "\n")]."
- if(LAZYLEN(bad_decal_icon))
- fail_msg += "\nDecal icon not set but decal lists populated:\n[jointext("\t[bad_decal_icon]", "\n")]."
- if(LAZYLEN(bad_decal_colour))
- fail_msg += "\nNull color in final decal entry:\n[jointext("\t[bad_decal_colour]", "\n")]."
- if(LAZYLEN(bad_decal_state))
- fail_msg += "\nNon-existent decal icon state:\n[jointext("\t[bad_decal_state]", "\n")]."
-
- fail(fail_msg)
- else
- pass("All closet appearances are sane.")
- return 1
diff --git a/code/unit_tests/overmap_tests.dm b/code/unit_tests/overmap_tests.dm
deleted file mode 100644
index 570aaf2d8a5d8..0000000000000
--- a/code/unit_tests/overmap_tests.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/datum/unit_test/overmap_test
- template = /datum/unit_test/overmap_test
-
-/datum/unit_test/overmap_test/New()
- name = "OVERMAP: " + name
-
-// 513 no longer allows no color or white as a filter color, hence this test
-/datum/unit_test/overmap_test/shall_have_non_white_color
- name = "Shall have non-white color"
-
-/datum/unit_test/overmap_test/shall_have_non_white_color/start_test()
- var/list/invalid_overmap_types = list()
- for(var/omt in subtypesof(/obj/effect/overmap))
- var/obj/overmap = omt
- var/color = initial(overmap.color)
- if(!color || color == COLOR_WHITE)
- invalid_overmap_types += omt
-
- if(length(invalid_overmap_types))
- fail("Following /obj/effect/overmap types types have invalid colors: [english_list(invalid_overmap_types)]")
- else
- pass("All /obj/effect/overmap types have a valid color")
-
- return TRUE
diff --git a/code/unit_tests/unit_test.dm b/code/unit_tests/unit_test.dm
deleted file mode 100644
index eb3fea11b6696..0000000000000
--- a/code/unit_tests/unit_test.dm
+++ /dev/null
@@ -1,186 +0,0 @@
-/* Unit Tests originally designed by Ccomp5950
- *
- * Tests are created to prevent changes that would create bugs or change expected behaviour.
- * For the most part I think any test can be created that doesn't require a client in a mob or require a game mode other then extended
- *
- * The easiest way to make effective tests is to create a "template" if you intend to run the same test over and over and make your actual
- * tests be a "child object" of those templates. Be sure and name your templates with the word "template" somewhere in var/name.
- *
- * The goal is to have all sorts of tests that run and to run them as quickly as possible.
- *
- * Tests that require time to run we instead just check back on their results later instead of waiting around in a sleep(1) for each test.
- * This allows us to finish unit testing quicker since we can start other tests while we're waiting on that one to finish.
- *
- * An example of that is listed in mob_tests.dm with the human_breath test. We spawn the mob in space and set the async flag to 1 so that we run the check later.
- * After 10 life ticks for that mob we check it's oxyloss but while that is going on we've already ran other tests.
- *
- * If your test requires a significant amount of time...cheat on the timers. Either speed up the process/life runs or do as we did in the timers for the shuttle
- * transfers in zas_tests.dm we move a shuttle but instead of waiting 3 minutes we set the travel time to a very low number.
- *
- * At the same time, Unit tests are intended to reflect standard usage so avoid changing to much about how stuff is processed.
- *
- *
- * WRITE UNIT TEST TEMPLATES AS GENERIC AS POSSIBLE (makes for easy reusability)
- *
- */
-
-var/global/all_unit_tests_passed = 1
-var/global/failed_unit_tests = 0
-var/global/skipped_unit_tests = 0
-var/global/total_unit_tests = 0
-
-// For console out put in Linux/Bash makes the output green or red.
-// Should probably only be used for unit tests since some special folks use winders to host servers.
-// if you want plain output, use dm.sh -DUNIT_TEST -DUNIT_TEST_PLAIN baystation12.dme
-#ifdef UNIT_TEST_PLAIN
-var/global/ascii_esc = ""
-var/global/ascii_red = ""
-var/global/ascii_green = ""
-var/global/ascii_yellow = ""
-var/global/ascii_reset = ""
-#else
-var/global/ascii_esc = ascii2text(27)
-var/global/ascii_red = "[ascii_esc]\[31m"
-var/global/ascii_green = "[ascii_esc]\[32m"
-var/global/ascii_yellow = "[ascii_esc]\[33m"
-var/global/ascii_reset = "[ascii_esc]\[0m"
-#endif
-
-
-// We list these here so we can remove them from the for loop running this.
-// Templates aren't intended to be ran but just serve as a way to create child objects of it with inheritable tests for quick test creation.
-
-/datum/unit_test
- var/name = "template - should not be ran."
- var/template // Treat the unit test as a template if its type is the same as the value of this var
- var/disabled = 0 // If we want to keep a unit test in the codebase but not run it for some reason.
- var/async = 0 // If the check can be left to do it's own thing, you must define a check_result() proc if you use this.
- var/reported = 0 // If it's reported a success or failure. Any tests that have not are assumed to be failures.
- var/why_disabled = "No reason set." // If we disable a unit test we will display why so it reminds us to check back on it later.
-
- var/safe_landmark
- var/space_landmark
-
-/datum/unit_test/proc/log_debug(message)
- log_unit_test("[ascii_yellow]--- DEBUG --- \[[name]\]: [message][ascii_reset]")
-
-/datum/unit_test/proc/log_bad(message)
- log_unit_test("[ascii_red]\[[name]\]: [message][ascii_reset]")
-
-/datum/unit_test/proc/fail(message)
- all_unit_tests_passed = 0
- failed_unit_tests++
- reported = 1
- log_unit_test("[ascii_red]!!! FAILURE !!! \[[name]\]: [message][ascii_reset]")
-
-/datum/unit_test/proc/pass(message)
- reported = 1
- log_unit_test("[ascii_green]*** SUCCESS *** \[[name]\]: [message][ascii_reset]")
-
-/datum/unit_test/proc/skip(message)
- skipped_unit_tests++
- reported = 1
- log_unit_test("[ascii_yellow]--- SKIPPED --- \[[name]\]: [message][ascii_reset]")
-
-/datum/unit_test/proc/start_test()
- fail("No test proc - [type]")
-
-/datum/unit_test/proc/check_result()
- fail("No check results proc - [type]")
- return 1
-
-/datum/unit_test/proc/get_safe_turf()
- if(!safe_landmark)
- for(var/landmark in landmarks_list)
- if(istype(landmark, /obj/effect/landmark/test/safe_turf))
- safe_landmark = landmark
- break
- return get_turf(safe_landmark)
-
-/datum/unit_test/proc/get_space_turf()
- if(!space_landmark)
- for(var/landmark in landmarks_list)
- if(istype(landmark, /obj/effect/landmark/test/space_turf))
- space_landmark = landmark
- break
- return get_turf(space_landmark)
-
-// Async unit tests will be delayed until the subsystems in this list have fired at least once.
-/datum/unit_test/proc/subsystems_to_await()
- return list()
-
-/proc/load_unit_test_changes()
-/*
- //This takes about 60 seconds to run when unit testing and is only used for the ZAS vacume check on The Asteroid.
- if(config.generate_map != 1)
- log_unit_test("Overiding Configuration option for Asteroid Generation to ENABLED")
- config.generate_map = 1 // The default map requires it, the example config doesn't have this enabled.
- */
-
-/proc/get_test_datums()
- . = list()
- for(var/test in subtypesof(/datum/unit_test))
- var/datum/unit_test/d = test
- if(test == initial(d.template))
- continue
- . += d
-
-/proc/do_unit_test(datum/unit_test/test, end_time, skip_disabled_tests = TRUE)
- if(test.disabled && skip_disabled_tests)
- test.pass("[ascii_red]Check Disabled: [test.why_disabled]")
- return
- if(world.time > end_time)
- test.fail("Unit Tests Ran out of time") // This should never happen, and if it does either fix your unit tests to be faster or if you can make them async checks.
- return
- if (test.start_test() == null) // Runtimed.
- test.fail("Test Runtimed")
- return 1
-
-//For async tests. Returns 1 if done.
-/proc/check_unit_test(datum/unit_test/test, end_time)
- if(world.time > end_time)
- test.fail("Unit Tests Ran out of Time")// If we're going to run out of time, most likely it's here. If you can't speed up your unit tests then add time to the timeout at the top.
- return 1
- var/result = test.check_result()
- if(isnull(result))
- test.fail("Test Runtimed")
- return 1
- else if(result)
- return 1
-
-/proc/unit_test_final_message()
- var/skipped_message = ""
- if(skipped_unit_tests)
- skipped_message = "| \[[skipped_unit_tests]\\[total_unit_tests]\] Unit Tests Skipped "
- if(all_unit_tests_passed)
- log_unit_test("[ascii_green]**** All Unit Tests Passed \[[total_unit_tests]\] [skipped_message]****[ascii_reset]")
- else
- log_unit_test("[ascii_red]**** \[[failed_unit_tests]\\[total_unit_tests]\] Unit Tests Failed [skipped_message]****[ascii_reset]")
-
-/datum/admins/proc/run_unit_test(datum/unit_test/unit_test_type in get_test_datums())
- set name = "Run Unit Test"
- set desc = "Runs the selected unit test - Remember to enable Debug Log Messages"
- set category = "Debug"
-
- if(!unit_test_type)
- return
-
- if(!check_rights(R_DEBUG))
- return
-
- log_and_message_admins("has started the unit test '[initial(unit_test_type.name)]'")
- var/datum/unit_test/test = new unit_test_type
- var/end_unit_tests = world.time + MAX_UNIT_TEST_RUN_TIME
- do_unit_test(test, end_unit_tests, FALSE)
- if(test.async)
- while(!check_unit_test(test, end_unit_tests))
- sleep(20)
- unit_test_final_message()
-
-/obj/effect/landmark/test/safe_turf
- name = "safe_turf" // At creation, landmark tags are set to: "landmark*[name]"
- desc = "A safe turf should be an as large block as possible of livable, passable turfs, preferably at least 3x3 with the marked turf as the center."
-
-/obj/effect/landmark/test/space_turf
- name = "space_turf"
- desc = "A space turf should be an as large block as possible of space, preferably at least 3x3 with the marked turf as the center."
diff --git a/code/world.dm b/code/world.dm
index 2a21ea0e200eb..6a8dec8aadc5d 100644
--- a/code/world.dm
+++ b/code/world.dm
@@ -11,7 +11,8 @@
cache_lifespan = 7
hub = "Exadv1.spacestation13"
icon_size = WORLD_ICON_SIZE
- fps = 30
-#ifdef GC_FAILURE_HARD_LOOKUP
- loop_checks = FALSE
+ fps = 20
+
+#ifdef CIBUILDING
+ #define UNIT_TEST
#endif
diff --git a/config/example/alienwhitelist.json b/config/example/alienwhitelist.json
new file mode 100644
index 0000000000000..1f8c2c6882c94
--- /dev/null
+++ b/config/example/alienwhitelist.json
@@ -0,0 +1,3 @@
+{
+ "ckey": ["whitelisted_species"]
+}
diff --git a/config/example/alienwhitelist.txt b/config/example/alienwhitelist.txt
deleted file mode 100644
index 01acc82de925a..0000000000000
--- a/config/example/alienwhitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-some~user - Species
\ No newline at end of file
diff --git a/config/example/config.txt b/config/example/config.txt
index a442880429c1e..b97a90405bc13 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -1,5 +1,5 @@
-## Server name: This appears at the top of the screen in-game. In this case it will read "tgstation: station_name" where station_name is the randomly generated name of the station for the round. Remove the # infront of SERVERNAME and replace 'tgstation' with the name of your choice
-# SERVERNAME spacestation13
+## Server name: This appears at the top of the screen in-game and on hub listings.
+# SERVER_NAME Space Station 13
## Game Version: The archetype of the server for topic status requests. Unless significantly deviated, avoid changing this. Default Baystation12
# GAME_VERSION Baystation12
@@ -52,6 +52,9 @@ WARN_IF_STAFF_SAME_IP
## log game actions (start of round, results, etc.)
LOG_GAME
+## log tool actions
+LOG_TOOL
+
## log player votes
LOG_VOTE
@@ -135,6 +138,13 @@ VOTE_DELAY 6000
## time period (deciseconds) which voting session will last (default 1 minute)
VOTE_PERIOD 600
+## Maximum time a round can last for (in minutes). If this time is exceeded, the round
+# will autotransfer without a vote at the next continue vote. Leave disabled for no limit.
+#MAXIMUM_ROUND_LENGTH 120
+
+## If set, the time in minutes between checks for ending empty rounds. Default off.
+#EMPTY_ROUND_CHECK_INTERVAL 15
+
## autovote initial delay in minutes before first automatic transfer vote call (default 120)
# using seven semicolon (;) separated values allows for different weekday-based values
VOTE_AUTOTRANSFER_INITIAL 120
@@ -143,6 +153,9 @@ VOTE_AUTOTRANSFER_INITIAL 120
# using seven semicolon (;) separated values allows for different weekday-based values
VOTE_AUTOTRANSFER_INTERVAL 30
+## Time in minutes leading up to the next autotransfer vote in which antagonists cannot
+# automatically be created. Default 20.
+#TRANSFER_VOTE_BLOCK_ANTAG_TIME 20
## Time left (seconds) before round start when automatic gamemote vote is called (default 160).
VOTE_AUTOGAMEMODE_TIMELEFT 160
@@ -151,6 +164,9 @@ VOTE_AUTOGAMEMODE_TIMELEFT 160
# Should be bigger than VOTE_AUTOGAMEMODE_TIMELEFT + VOTE_PERIOD.
PRE_GAME_TIME 180
+## If we want to bypass gamemode vote
+BYPASS_GAMEMODE_VOTE
+
## prevents dead players from voting or starting votes
#NO_DEAD_VOTE
@@ -221,15 +237,6 @@ GUEST_BAN
## Where your users can go to tell you they've changed
# BANAPPEALS https://example.com
-## Defines world tick rate relative to 10/sec at 1 and 20/sec at 0.5. Use this OR fps, not both. Defaults to 0.333333
-# TICKLAG 0.333333
-
-## Defines world tick rate. Use this OR ticklag, not both. Defaults to 30.
-FPS 30
-
-## Whether the server will talk to other processes through socket_talk
-SOCKET_TALK 0
-
## Comment this out to disable automuting
#AUTOMUTE_ON
@@ -266,6 +273,12 @@ USEALIENWHITELIST
## IRC channel to send adminhelps to. Leave blank to disable adminhelps-to-irc.
#ADMIN_IRC #admin
+## Discord channel ID to send adminhelps to. Leave blank to disable adminhelps-to-discord. Note: Requires ExComm (https://github.com/Baystation12/ExCom) or a similar Discord Bot
+#ADMIN_DISCORD 123456
+
+## Address and port of the Excom's bot listen server to relay messages to. Sends over HTTP. Example: baystation.xyz:1234, 127.0.0.1:5678
+#EXCOM_ADDRESS localhost:1234
+
## Uncommen to allow ghosts to write in blood during Cult rounds.
ALLOW_CULT_GHOSTWRITER
@@ -330,8 +343,8 @@ EVENT_CUSTOM_START_MAJOR 80;100
## The delay in minutes before an observer that has returned to the main menu may rejoin the game.
#RESPAWN_MENU_DELAY 0
-## Strength of ambient star light. Set to 0 or less to turn off. A value of 1 is unlikely to have a noticeable effect in most lighting systems.
-STARLIGHT 0
+## Enables and disables starlight. This will make space turfs and some turfs considered to be in exterior areas to be lit based on the colour of the background parallax.
+STARLIGHT 1
## Defines which races are allowed to join as ERT, in singular form. If unset, defaults to only human. Casing matters, separate using ;
## Example races include: Human, Tajara, Skrell, Unathi
@@ -444,10 +457,6 @@ RADIATION_LOWER_LIMIT 0.15
## DISALLOW_VOTABLE_MODE changeling
## DISALLOW_VOTABLE_MODE malfunction
-## Maximum time a round can last for (in minutes). If this time is exceeded,
-## the round will autotransfer without a vote at the next continue vote. Leave disabled for no limit.
-#MAXIMUM_ROUND_LENGTH 120
-
## The delay in deciseconds between stat() updates.
## Lower can be more responsive to scene changes and updates but has a higher client/server overhead.
# STAT_DELAY 5
@@ -461,3 +470,34 @@ RADIATION_LOWER_LIMIT 0.15
## Uncomment this to bypass empty z-level checks in certain subsystems. For testing and development purposes only.
## Not recommended for live use.
#RUN_EMPTY_LEVELS
+
+
+###################################################################
+# SIERRABAY #
+###################################################################
+
+## Add a # in front of this to disable automatic admin rights for users connecting from the host the server is running on.
+AUTO_LOCAL_ADMIN
+
+## Add a # in front of this to disable automatic admin rights for users connecting from the host the server is running on.
+ASSET_TRANSPORT simple
+
+## Uncomment this to have the server passively send all browser assets to each client in the background.
+## (instead of waiting for them to be needed)
+#ASSET_SIMPLE_PRELOAD
+
+## Local folder to save assets to
+ASSET_CDN_WEBROOT data/asset-store/
+
+## If you want to test this locally, you simpily run the `localhost-asset-webroot-server.py`
+## python3 script to host assets stored in `data/asset-store/` via http://localhost:58715/
+ASSET_CDN_URL http://localhost:58715/
+
+## Uncomment to use ckey whitelist ticket system with SQL
+#USEWHITELIST_DATABASE
+
+## Server to reroute to. Used when USEWHITELIST_DATABASE enabled
+#OVERFLOW_SERVER_URL byond://example.com:1234
+
+## Minimum age of byond account that allowed to play without discord verification
+#MINIMUM_BYONDACC_AGE 10
diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt
index e1e3d1083f8f0..1756cf3680eb3 100644
--- a/config/example/dbconfig.txt
+++ b/config/example/dbconfig.txt
@@ -1,25 +1,32 @@
-# MySQL Connection Configuration
-
-# First of all, should SQL be used at all. Unhash next line, if yes
-# ENABLED
-
-# Server the MySQL database can be found at
-# Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
-ADDRESS localhost
-
-# MySQL server port (default is 3306)
-PORT 3306
-
-# Database the population, death, karma, etc. tables may be found in
-DATABASE tgstation
-
-# Username/Login used to access the database
-LOGIN mylogin
-
-# Password used to access the database
-PASSWORD mypassword
-
-# The following information is for feedback tracking via the blackbox server
-FEEDBACK_DATABASE test
-FEEDBACK_LOGIN mylogin
-FEEDBACK_PASSWORD mypassword
\ No newline at end of file
+# MySQL Connection Configuration
+
+# First of all, should SQL be used at all. Unhash next line, if yes
+# ENABLED
+
+# Server the MySQL database can be found at
+# Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
+ADDRESS localhost
+
+# MySQL server port (default is 3306)
+PORT 3306
+
+# Database the population, death, karma, etc. tables may be found in
+DATABASE test
+
+# Username/Login used to access the database
+LOGIN root
+
+# Password used to access the database
+PASSWORD root
+
+# The following information is for feedback tracking via the blackbox server
+FEEDBACK_DATABASE test
+FEEDBACK_LOGIN mylogin
+FEEDBACK_PASSWORD mypassword
+
+
+###################################################################
+# SIERRABAY #
+###################################################################
+
+UTILITY_DATABASE tgstation
diff --git a/config/example/game_options.txt b/config/example/game_options.txt
index b1eccdda39546..2a62ad964f84a 100644
--- a/config/example/game_options.txt
+++ b/config/example/game_options.txt
@@ -1,55 +1,55 @@
-### HEALTH ###
-
-## level of health at which a mob goes into continual shock (soft crit)
-HEALTH_THRESHOLD_SOFTCRIT 0
-
-## level of health at which a mob becomes unconscious (crit)
-HEALTH_THRESHOLD_CRIT -50
-
-## level of health at which a mob becomes dead
-HEALTH_THRESHOLD_DEAD -100
-
-## Determines whether bones can be broken through excessive damage to the organ
-## 0 means bones can't break, 1 means they can
-BONES_CAN_BREAK 1
-## Determines whether limbs can be amputated through excessive damage to the organ
-## 0 means limbs can't be amputated, 1 means they can
-LIMBS_CAN_BREAK 1
-
-## multiplier which enables organs to take more damage before bones breaking or limbs being destroyed
-## 100 means normal, 50 means half
-ORGAN_HEALTH_MULTIPLIER 90
-
-## multiplier which influences how fast organs regenerate naturally
-## 100 means normal, 50 means half
-ORGAN_REGENERATION_MULTIPLIER 25
-
-### REVIVAL ###
-
-## amount of time (in hundredths of seconds) for which a brain retains the "spark of life" after the person's death (set to -1 for infinite)
-REVIVAL_BRAIN_LIFE -1
-
-
-
-### MOB MOVEMENT ###
-
-## We suggest editing these variabled in-game to find a good speed for your server. To do this you must be a high level admin. Open the 'debug' tab ingame. Select "Debug Controller" and then, in the popup, select "Configuration". These variables should have the same name.
-
-## These values get directly added to values and totals in-game. 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 2
-WALK_DELAY 4
-CREEP_DELAY 6
-MINIMUM_SPRINT_COST 0.8
-SKILL_SPRINT_COST_RANGE 0.8
-MINIMUM_STAMINA_RECOVERY 5
-MAXIMUM_STAMINA_RECOVERY 5
-
-### Miscellaneous ###
-
-## Config options which, of course, don't fit into previous categories.
-
-## Remove the # in front of this config option to have loyalty implants spawn by default on your server.
-#USE_LOYALTY_IMPLANTS
+### HEALTH ###
+
+## level of health at which a mob goes into continual shock (soft crit)
+HEALTH_THRESHOLD_SOFTCRIT 0
+
+## level of health at which a mob becomes unconscious (crit)
+HEALTH_THRESHOLD_CRIT -50
+
+## level of health at which a mob becomes dead
+HEALTH_THRESHOLD_DEAD -100
+
+## Determines whether bones can be broken through excessive damage to the organ
+## 0 means bones can't break, 1 means they can
+BONES_CAN_BREAK 1
+## Determines whether limbs can be amputated through excessive damage to the organ
+## 0 means limbs can't be amputated, 1 means they can
+LIMBS_CAN_BREAK 1
+
+## multiplier which enables organs to take more damage before bones breaking or limbs being destroyed
+## 100 means normal, 50 means half
+ORGAN_HEALTH_MULTIPLIER 90
+
+## multiplier which influences how fast organs regenerate naturally
+## 100 means normal, 50 means half
+ORGAN_REGENERATION_MULTIPLIER 25
+
+### REVIVAL ###
+
+## amount of time (in hundredths of seconds) for which a brain retains the "spark of life" after the person's death (set to -1 for infinite)
+REVIVAL_BRAIN_LIFE -1
+
+
+
+### MOB MOVEMENT ###
+
+## We suggest editing these variabled in-game to find a good speed for your server. To do this you must be a high level admin. Open the 'debug' tab ingame. Select "Debug Controller" and then, in the popup, select "Configuration". These variables should have the same name.
+
+## These values get directly added to values and totals in-game. 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 2
+WALK_DELAY 4
+CREEP_DELAY 6
+MINIMUM_SPRINT_COST 0.8
+SKILL_SPRINT_COST_RANGE 0.8
+MINIMUM_STAMINA_RECOVERY 5
+MAXIMUM_STAMINA_RECOVERY 5
+
+### Miscellaneous ###
+
+## Config options which, of course, don't fit into previous categories.
+
+## Remove the # in front of this config option to have loyalty implants spawn by default on your server.
+#USE_LOYALTY_IMPLANTS
diff --git a/config/example/tts_replacements.json b/config/example/tts_replacements.json
new file mode 100644
index 0000000000000..d62e9b1a7be9d
--- /dev/null
+++ b/config/example/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/dependencies.sh b/dependencies.sh
new file mode 100644
index 0000000000000..33260821a2b95
--- /dev/null
+++ b/dependencies.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#Project dependencies file
+#Final authority on what's required to fully build the project
+
+# byond version
+export BYOND_MAJOR=515
+export BYOND_MINOR=1630
+
+#rust_g repo
+export RUST_G_REPO=ss220club/rust-g-tg
+
+#rust_g git tag
+export RUST_G_VERSION=3.0.0-ss220
+
+#node version
+export NODE_VERSION=20
+export NODE_VERSION_PRECISE=20.11.0
+
+# SpacemanDMM git tag
+export SPACEMAN_DMM_VERSION=suite-1.8
+
+# Python version for mapmerge and other tools
+export PYTHON_VERSION=3.9.0
+
+#auxlua repo
+export AUXLUA_REPO=tgstation/auxlua
+
+#auxlua git tag
+export AUXLUA_VERSION=1.4.1
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index 9313321ce64d9..741f86b94a1d7 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -1,9 +1,9 @@
-# Security Policy
+# Политика безопасности
-## Supported Versions
+## Поддерживаемые версии
-This repository is built and tested against BYOND version `514.1585` at time of writing. If this version number is at odds with `BYOND_MAJOR`.`BYOND_MINOR` as defined in [`./github/workflows/test.yml`](https://github.com/Baystation12/Baystation12/blob/dev/.github/workflows/test.yml#L12), the Actions configuration should be considered authoritative and this document should be noted as out of date. Security vulnerabilities or exploits that apply to this version should be reported so that they can be closed.
+Этот репозиторий собран и проверен на BYOND версии `514.1585` на время написания данного документа. Если номер версии отличается от `BYOND_MAJOR`.`BYOND_MINOR` определённых в [`./github/workflows/test.yml`](https://github.com/Baystation12/Baystation12/blob/dev/.github/workflows/test.yml#L12), конфигурация GitHub Actions должна считаться авторитетной, а этот документ должен быть признан устаревшим. Об уязвимостях или эксплойтах относящихся к этой версии стоит сообщать, чтобы они могли быть устранены.
-## Reporting a Vulnerability
+## Сообщение об уязвимостях
-If you have been informed of or discovered a security vulnerability, exploit or other flaw, please contact a developer to report it privately. Public channels on Discord or the forums should be avoided, as the exploit may be taken from a public post and used before it can be patched. The best method of contacting a developer is by pinging the `@dev` role in the discord's `#code` channel informing them there is a security issue you wish to report, and they will open a discord ticket to discuss the matter privately.
+Если вы были проинформированы или обнаружили уязвимость в безопасности, эксплоит или другой недостаток, пожалуйста, свяжитесь с разработчиком, чтобы сообщить об этом в частном порядке. Следует избегать общедоступных каналов в Discord или форумов, так как эксплойт может быть взят из общедоступной публикации и использован до того, как он может быть исправлен. Лучший способ сообщить об уязвимости - это упомянуть `@NSV Sierra Главный Администратор` в дискорд канале `#sierra-чат`, информируя о проблемах безопасности о которых вы хотите сообщить, чтобы затем обсудить этот вопрос в частном порядке.
diff --git a/docs/installation.md b/docs/installation.md
index 8fbfd6ddc0b2d..d9a0835768c24 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -1,84 +1,99 @@
-### GETTING THE CODE
+# Установка SierraBay
-The simplest way to obtain the code is using Github's .zip feature.
+
+ Содержание
-Click [here](https://github.com/Baystation12/Baystation12/archive/dev.zip) to get the latest code as a .zip file, then unzip it to wherever you want.
+- [Скачивание](#скачивание-кода)
+- [Установка](#установка)
+- [Конфигурация](#конфигурация)
+- [Вебхуки](#вебхуки)
+- [Обновление](#обновление)
+- [База данных](#установка-базы-данных)
-The more complicated and easier to update method is using git. You'll need to download git or some client from [here](http://git-scm.com/). When that's installed, right click in any folder and click on "Git Bash". When that opens, type in:
+
- git clone https://github.com/Baystation12/Baystation12.git
+### Скачивание кода
-(hint: hold down ctrl and press insert to paste into git bash)
+Самый простой способ получить код - использовать GitHub функцию скачивания `.zip`.
-This will take a while to download, but it provides an easier method for updating.
+Нажми [сюда](https://github.com/SierraBay/SierraBay12/archive/dev-sierra.zip) чтобы получить последнюю версию кода в виде .zip файла, а затем разархивируй его куда захочешь.
----
+Более сложный в скачивании, но более простой в обновлении метод - использование git. Тебе нужно скачать git или клиент [отсюда](http://git-scm.com/). Когда он установится, нажми правой кнопкой мыши в любой папке правую кнопку мыши и выбери "Git Bash". Когда окно откроется, пиши в него:
+
+```sh
+git clone https://github.com/SierraBay/SierraBay12.git
+```
-### INSTALLATION
+(Подсказка: чтобы вставить в git bash можно нажать ПКМ по окну, или использовать Ctrl+Insert)
-First-time installation should be fairly straightforward. First, you'll need BYOND installed. You can get it from [here](http://www.byond.com/).
+Загрузка займет некоторое время, но обновлять билд будет проще.
-This is a sourcecode-only release, so the next step is to compile the server files. Open `baystation12.dme` by double-clicking it, open the Build menu, and click compile. This'll take a little while, and if everything's done right you'll get a message like this:
+---
- saving baystation12.dmb (DEBUG mode)
-
- baystation12.dmb - 0 errors, 0 warnings
+### Установка
-If you see any errors or warnings, something has gone wrong - possibly a corrupt download or the files extracted wrong, or a code issue on the main repo. Ask on IRC or discord.
+Первая установка должна быть довольно простой. Во-первых, тебе надо установить BYOND. Его можно скачать [здесь](http://www.byond.com/).
----
+Ты скачал только исходный код, поэтому его нужно скомпилировать. Открой `baystation12.dme` двойным кликом, слева сверху выбери вкладку `Build` и там нажми `Compile`. Не пугайся того что почти ничего в консоли долго не происходит, просто жди, это нормально. Это займёт время, но по итогу внизу ты должен увидеть такое сообщение:
+```
+saving baystation12.dmb (DEBUG mode)
-### CONFIGURATION
+baystation12.dmb - 0 errors, 0 warnings
+```
-Copy the contents of the `/config/examples` folder into `/config`. You will now work with everthing contained within `/config`.
+Если ты видишь какие-то ошибки или предупреждения - что-то однозанчно пошло не по плану. Скорее всего либо повреждена загрузка, либо файлы извлечены неправильно, либо мы допустили ошибку в коде. Спросить можно в дискорде, указанном в [`README`](/README.md)
-Edit `config.txt` to set the probabilities for different gamemodes in Secret and to set your server location so that all your players don't get disconnected at the end of each round. It's recommended you don't turn on the gamemodes with probability 0, as they have various issues and aren't currently being tested, they may have unknown and bizarre bugs.
+---
+
+### Конфигурация
-Edit `admins.txt` to remove the default admins and add your own. "Game Master" is the highest level of access, and the other recommended admin levels for now are "Game Admin" and "Moderator". The format is:
+Копируй всё из папки `/config/examples` в папку `/config`. Теперь ты работаешь со всем, что содержится внутри `/config`.
- byondkey - Rank
+Отредактируй `config.txt`, чтобы установить вероятности для различных режимов игры в режиме "Secret" или указать адрес вашего сервера таким образом, чтобы все ваши игроки не отключались в конце каждого раунда. Не рекомендуется включать игровые режимы с вероятностью 0, ибо в них могут быть различные проблемы и эти режимы в настоящее время не тестируются - повсюду могут быть неизвестные и неестественные ошибки.
-where the BYOND key must be in lowercase and the admin rank must be properly capitalised. There are a bunch more admin ranks, but these two should be enough for most servers, assuming you have trustworthy admins.
+Отредактируй `admins.txt` чтобы удалить администраторов по умолчанию и добавить своих собственных. "Game Master" это самый высокий уровень доступа. Также, рекомендуемые уровни администратора - "Game Admin" и "Moderator". Формат ввода:
+```
+byondkey - Rank
+```
+Сикей должен быть в нижнем регистре, а в ранге администратора заглавные буквы должны быть правильно указаны. Есть еще несколько рангов администратора, но этих двух должно быть достаточно для большинства серверов, при условии, что у тебя есть заслуживающие доверия администраторы.
-To start the server, run Dream Daemon and enter the path to your compiled `baystation12.dmb` file. Make sure to set the port to the one you specified in the `config.txt`, and set the Security box to 'Trusted' so you don't have to confirm access to every single configuration and storage file for the server. Then press GO and the server should start up and be ready to join.
+Чтобы запустить сервер, запусти Dream Daemon и введи путь к твоему скомпилированному `baystation12.dmb` файлу. Убедись, что ты выставил порт, который указан в `config.txt`, а также установи в ячейке "Security" значение 'Trusted', чтобы не приходилось подтверждать доступ к каждому отдельному файлу конфигурации и данных сервера. Затем просто жми GO, и сервер должен запуститься, готовым к подключению.
---
-### WEBHOOKS
+### Вебхуки
-If you wish to use Discord webhooks, which are a way of passing information from the server to a Discord channel, you will need to copy `webhooks.json` into `config/` from `config/example/` and add definitions pointing the desired event at the desired [Discord webhook URL](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks). Valid webhook IDs as of time of writing are as follows:
-- webhook_roundend: The round has ended. Will include the mode name and summarize survivors and ghosts.
-- webhook_roundstart: The master controller has finished initializing and the round will begin soon.
-- webhook_submap_loaded: A submap has been loaded and placed, and is available for people to join. Includes the name of the submap.
-- webhook_submap_vox: The vox submap specifically has been loaded and placed. This is distinct for the purposes of tagging vox players with a @mention.
-- webhook_submap_skrell: The Skrell submap specifically has been loaded and placed. This is distinct for the purposes of tagging Skrell players with a @mention.
-- webhook_custom_event: The custom event text for the round has been set or changed.
+Если хочешь использовать вебхуки Discord, которые являются способом передачи информации с сервера в канал Discord, нужно будет скопировать `webhooks.json` в папку `config/` из `config/example/` и прописать определения, указывающие на желаемый [Discord webhook URL](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks). Действительными ID вебхука на момент написания статьи являются следующие:
+- `webhook_roundend`: Раунд закончился. Будет указано название режима и краткое описание выживших и призраков.
+- `webhook_roundstart`: Мастер контроллер завершил инициализацию, и раунд скоро начнется.
+- `webhook_submap_loaded`: Авейка была загружена и размещена, и к ней могут присоединиться пользователи. Включает название авейки.
+- `webhook_submap_vox`: Была загружена и размещена авейка воксов. Это необходимо для того, чтобы отдельно пингануть игроков-воксов с помощью @упоминания.
+- `webhook_submap_skrell`: Была загружена и размещена авейка скреллов. Это необходимо для того, чтобы отдельно пингануть игроков-скреллов с помощью @упоминания.
+- `webhook_custom_event`: Текст ивента раунда был установлен или изменен.
-Each definition can optionally include an array of roles to mention when the webhook is called. Roles must be provided using the role ID (ex. `<@&555231866735689749>`), which can be obtained by writing `\@somerole` into the chat, in order for pinging to work correctly.
+Каждое определение может включать массив ролей, которые нужно упомянуть при вызове вебхука. Роли должны быть записаны с помощью их ID (например: `<@&555231866735689749>`), который можно узнать, написав `\@роль` в чате, чтобы пинги работали правильно.
-Webhooks additionally require a HTTP POST library called [byhttp](https://github.com/Lohikar/byhttp). The compiled lib, `byhttp.dll` on Windows or `libbyhttp.so` on Linux, must be placed in the lib directory by default in order for webhooks to function. The DLL location can be customized by supplying `WINDOWS_HTTP_POST_DLL_LOCATION` `UNIX_HTTP_POST_DLL_LOCATION`, or `HTTP_POST_DLL_LOCATION` as preprocessor macros containing the desired path.
+Вебхуки дополнительно требуют HTTP POST библиотеку, которая называется [byhttp](https://github.com/Lohikar/byhttp). Скомпилированная библиотека, `byhttp.dll` на Windows или `libbyhttp.so` на Linux, должны быть в папке `lib`, чтобы вебхуки могли функционировать. Расположение DLL файла может быть настроено путем установки `WINDOWS_HTTP_POST_DLL_LOCATION`, `UNIX_HTTP_POST_DLL_LOCATION`, or `HTTP_POST_DLL_LOCATION` как макросов препроцессора, содержащих желаемый путь.
---
-### UPDATING
+### Обновление
-To update an existing installation, first back up your `/config` and `/data` folders
-as these store your server configuration, player preferences and banlist.
+Перед обновлением, лучше всего создать резервную копию папок `/config` и `/data`, так как в них хранятся конфигурация вашего сервера, предпочтения игроков и банлист.
-If you used the zip method, you'll need to download the zip file again and unzip it somewhere else, and then copy the `/config` and `/data` folders over.
+Если ты использовал метод `zip`, то тебе нужно снова [скачать zip-файл](https://github.com/SierraBay/SierraBay12/archive/dev-sierra.zip) и распаковать его в другом месте, а затем скопировать туда папки `/config` и `/data`.
-If you used the git method, you simply need to type this in to git bash:
+Если ты использовал метод git, нужно просто ввести это в git bash:
+```sh
+git pull
+```
- git pull
+Когда команда выполнится, скопируй поверх твоих `/data` и `/config` резервно скопированные папки, на всякий случай.
-When this completes, copy over your `/data` and `/config` folders again, just in case.
-
-When you have done this, you'll need to recompile the code, but then it should work fine.
+После этого нужно лишь вновь скомпилировать код и всё должно работать прекрасно.
---
-### SQL Setup
-
-The SQL backend for the `/library/stats` and bans requires a MySQL server. Your server details go in `/config/dbconfig.txt`.
+### Установка базы данных
-For initial setup and migrations refer to `/sql/README.md`
+Серверная часть для SQL `/library/stats` и банов требуют наличие MySQL сервера. Конфигурация сведений о сервере находится в `/config/dbconfig.txt`.
diff --git a/html/archivedchangelog.html b/html/archivedchangelog.html
index 9831f594c140a..e04120817711b 100644
--- a/html/archivedchangelog.html
+++ b/html/archivedchangelog.html
@@ -241,7 +241,7 @@
Cael_Aislinn updated:
August 22, 2012
Cael_Aislinn updated:
-
This server is in the process of running a merge of BS12 and up to date TGcode. This is a significant update, and there will likely be several bugs coming with it.
+
This server is in the process of running a merge of BS12 and up to date TGcode. This is a significant update, and there will likely be several bugs coming with it.
See https://github.com/Baystation12/Baystation12/pull/1671 for more information.
The server is now running tgstation2.0.9.dmm. The station layout is functionally the same, but with significant additions from tg. Hopefully, the Antiqua will be ready soon.
The Head of Personnel now starts with a clipboard, but without body armor and a helmet. Both items can still be found in the secure locker for emergencies.
Cargo tech and shaft miner wardrobes now include fingerless gloves. Janitor wardrobes now includes a portalathe. Warden wardrobes now includes a jacket.
Medical, security, and tool-belts may now hold any lighter. Medical belts may now hold latex gloves and sterile masks. Security belts may now hold gas masks. Tool-belts may now hold cigarette packs.
-
+
@@ -300,7 +300,7 @@
Abi79 updated:
Fixed the bug where helmets would not turn off when placed into backpacks.
Added more "cancel" buttons to various dialogs.
-
+
CIB updated:
Fixed the bug where the preview image in the character creation panel was broken.
@@ -507,7 +507,7 @@
Erthilo updated:
01 June 2012
Erthilo updated:
-
Added character records. You can now add medical and security records to your character through Character Setup. These are official NanoTrasen records, and should be written as such. These will show up in-game on the medical and security records computers. Admins can 'jobban' people from records, so use them sensibly!
+
Added character records. You can now add medical and security records to your character through Character Setup. These are official Nanotrasen records, and should be written as such. These will show up in-game on the medical and security records computers. Admins can 'jobban' people from records, so use them sensibly!
Added a megaphone to each Head's office. These broadcast messages in slightly larger font so you can be noticed. Please don't spam them.
Added Flashkirby's ERT suit sprites. Also tweaked ERT's loadout.
@@ -555,7 +555,7 @@
Erthilo updated:
Added Flashkirby's RIG and cow sprites!
Removed and added some new AI Ion laws, credit: Ispil.
-
+
Abi79 updated:
Everyone should now be able to see the properly formatted changelog.
@@ -967,4 +967,4 @@
29th April 2012 updated:
Using an igniter on a flamethrower that already has one attached no longer uses up the igniter.
Fixed plating placed by the RCD not keeping the original atmosphere of the tile.
-
Changed the RCD floor type from 'airless plating' to just 'plating' to avoid confusion.
-
Tweaked the Vat-Grown name randomizer so it doesn't give names that violate the naming convention.
-
-
rootoo807 updated:
-
-
Adds 8 new seafood recipes.
-
Sushi sprites show up correctly for all fish.
-
Sashimi doesn't disappear in cookers.
-
Sushi and sashimi now use fish color. Shellfish (and shrimp tempura) can be made into sushi.
-
Sashimi can no longer be stacked.
-
Cooking/cutting bacon no longer summons additional protein into existence.
-
-
-
12 February 2023
-
SingingSpock updated:
-
-
Re-adds the CoS terminal. Oops.
-
-
-
10 February 2023
-
Spookerton updated:
-
-
You don't have to target mouth to give yourself a cigarette from a held pack.
-
-
-
09 February 2023
-
Hubblenaut updated:
-
-
pAIs are no longer invisible in inventory.
-
Fixed the on-mob-sprites for the pAI drone chassis in particular.
-
-
Jux updated:
-
-
Fixes minor typo in Fifth Fleet patch desc. and another in character choosing residency.
-
-
SierraKomodo updated:
-
-
The SFV Arbiter's airlock now properly detects and reads the external air sensor.
-
-
SomeAngryMiner updated:
-
-
Borers can now inyect Inaprovaline, Dylovene, Dermaline and Peridaxon.
-
Borer ability buttons have been fixed and are placed in the top left corner of the screen.
-
-
-
08 February 2023
-
BurpleBineapple updated:
-
-
The explosive harpoon now costs 68 TC, the same as an anti-materiel rifle.
-
-
-
07 February 2023
-
10sc updated:
-
-
Remapped the contents of the Galley.
-
Remapped the Galley's Cold Storage There are now plastic curtains over the meat hook and gibber.
-
Superficially remapped the Chief Steward's Office. It now has a secure safe!
-
Replaced an ATM from the Mess Hall with a status display. Added another light to the Mess Hall.
-
Shifted the disposal chute in the Service Break Room.
-
-
CrimsonShrike updated:
-
-
Some mobs will no longer render over floor due to zmimic
-
-
Mucker updated:
-
-
Fixed moving over the map edge putting you on the same z level as the overmap.
-
-
SingingSpock updated:
-
-
Docking modular computers now have the deck management and camera monitoring programs
-
Medical modular consoles no longer autorun suit sensor monitoring (the telescreens still do)
-
The CE now has their own modular computer preset, like the CoS, with command and engineering programs
-
The Charon, Pilot's Office, and Exploration prep rooms now all have docking modular computers rather than civilian or medical
-
The bridge's modular console presets were changed a bit to fit the seat theming better
-
-
-
05 February 2023
-
SierraKomodo updated:
-
-
Added some codex information for bots, including interactions and construction steps.
-
-
Spacemanspark updated:
-
-
New Bishop jumpsuit and labcoat available in the loadout.
-
-
Spookerton updated:
-
-
Grass borders are visible.
-
Wooden borders removed from the garden because grass borders are visible.
-
Microwave recipes that want more than one of the same produce item work again.
-
Donk pockets microwave again.
-
-
-
04 February 2023
-
ComradeCheekiBreeki updated:
-
-
Added verb to remove all accessories from a piece of clothing.
-
-
SierraKomodo updated:
-
-
For structures that allow it, the codex will now inform you that you can use grabs to place mobs onto things, i.e. tables. Codex will also inform you of an attack interaction with mobs against structures.
-
-
SingingSpock updated:
-
-
Rotates aquila unary air tanks so they actually connect to the pipenet
-
-
-
03 February 2023
-
Ryan180602 updated:
-
-
Replace Spike Throwers with a Slugsling, Soundcannon and Darkmatter Cannon on Heist base
-
-
Spookerton updated:
-
-
Added the Chief Steward job, an EC or Fleet SNCO in charge of stewards, janitors, and crewmen.
-
Demoted stewards to cap at OR5 for Fleet.
-
Moved telecomms to deck four. Added a stairwell down from engineering storage to access it easily.
-
Moved sanitation supplies, the commissary, and the gym to the fore of deck three.
-
Added the Chief Steward's office and Service Break Room at the fore of deck three.
-
Added a diplomatic meeting room by the fore stairwell of deck four.
-
Adjusted the galley to have more space, removing hydroponics storage.
-
Added some tiles around the pool and full splines for garden grass.
-
Added more toilets.
-
Added a ducky.
-
-
-
02 February 2023
-
SierraKomodo updated:
-
-
Borgs can now deconstruct tables on help intent.
-
-
-
31 January 2023
-
Spookerton updated:
-
-
The garden fountain no longer glows.
-
-
-
30 January 2023
-
Spookerton updated:
-
-
The "Edit Referenced Object" option on list entries in VV is replaced with View Variables on the entry.
-
Robotic stumps don't cause pain.
-
Microwaves produce correct recipe results.
-
-
-
28 January 2023
-
SingingSpock updated:
-
-
Deck substations use actual numbers, causing them to show up in order
-
-
rootoo807 updated:
-
-
IV bags show correct reagent color.
-
-
-
26 January 2023
-
Nerezza updated:
-
-
Unathi now leave their unique clawprints even while wearing footwear.
-
-
Sbotkin updated:
-
-
Allows the steward to have a rank up to E-6.
-
-
SierraKomodo updated:
-
-
Psionic shielding HUD icon now displays and can be toggled once more.
-
Progress bars now appear when creating items from stacks of materials.
-
Stacks with an amount of 0 will no longer appear when crafting stacks from materials when you already have a stack in your other hand or you spam-click.
-
Force-cryoing mobs that are already in cryopods no longer forcemoves them into another cryopod and breaks the original pod.
-
EMPs will no longer pulse wiring in broken machinery.
-
Machinery whos wiring is affected by EMPs will now spark and display a visible message indicating something in the wiring panel is damaged.
-
Standardized EMP damage now only effects machinery that is turned on and functional.
-
A light fixture has been added to the kitchen, next to the counter, to shed some light on the dark corners of the mess hall.
-
-
-
25 January 2023
-
Nerezza updated:
-
-
Plasma cutters can now slice through metal grilles.
-
Plasma cutters now actually cut through windows instead of just unanchoring them.
-
Plasma cutters now use construction skill instead of weapons expertise for safely handling them.
-
Plasma cutter safety overlays are shown for users with construction skill instead of weapons expertise.
-
You will now no longer fumble "the the compact smartgun" when you have a firearms accident.
-
-
-
24 January 2023
-
Spookerton updated:
-
-
Tiny reagent volumes no longer process their full metabolism amount.
-
-
-
22 January 2023
-
SierraKomodo updated:
-
-
Codex entries can now include a list of various click and item interactions. These still have to be manually written, but are nicely formatted as a list and includes interactions provided by parent types. These can be found at the bottom of the OOC Information and Antagonist Information sections of any given item's codex entry, which in turn is accessed by clicking the codex link that appears when examining items, or searching through the Codex index found with the 'codex' verb.
-
Blast doors now have a much higher fire resistance of 10,000 kelvin. This should allow them to withstand the incinerator and nacelle burn chambers.
-
Certain atoms now protect other atoms on the same tile from taking fire damage, at least until they break. The atoms, in the order they are checked and affected, are: Closed blast and fire doors, closed doors, and windows.
-
The incinerator now has blast doors added to cover the two airlocks, so your fires don't destroy them.
-
Food and other items that use the attack proc that was documented as being for ATTACKING instead of interactions are now fixed. I should stop assuming people actually follow what the comments say for these things.
-
Grilles on wires will now shock users that hit them.
-
Inflatables will no longer take damage from melee weapons that can't puncture or with a weak damage value.
-
Table deconstruction steps can now be performed on disarm or grab intent. Harm intent will still attack the table, and help intent will still place items on the table.
-
-
-
16 January 2023
-
SierraKomodo updated:
-
-
Codex entries for chemistry recipes now include the minimum and maximum temperatures.
-
Advanced Energy Guns now have a working safety indicator icon.
-
Psionics now work again.
-
Cryopods will now longer occasionally announce a mob has been flushed twice.
-
Custom email addresses in character setup should function again.
-
Canisters no longer take damage from EMPs, and are now set to be damaged by physical damage sources instead of electrical.
-
Overmap shuttles now halt travel when they enter transit.
-
Shuttles in transit no longer trigger overmap hazards.
-
-
SomeAngryMiner, Sounds by Sindorman updated:
-
-
Reduced mech ballistic shield damage from 1.2x to 0.2x.
-
Buffed exosuit mechete damage cap from 25 to 35.
-
Tripled fall damage taken by mechs from 30 to 90.
-
New sounds for exosuits in general.
-
Exosuit unarmed attacks are now telegraphed, and are able to break structures.
-
Tou can no longer block exosuit movement by standing as a human.
-
-
Spookerton updated:
-
-
Phoron-filled bulbs explode when turned on again.
-
-
gy1ta23 updated:
-
-
fired flares are more textually visible
-
-
-
15 January 2023
-
SierraKomodo updated:
-
-
You can now buy a box of handcuffs for 5 TC in the Tools section of the uplink.
-
-
-
12 January 2023
-
Nerezza updated:
-
-
Emergency Adrenaline Autoinjectors now contain 8u of adrenaline as originally intended instead of just 5u. This means it is now possible for patients to reach medbay before the stabilizing effects of an adrenaline shot runs out.
-
-
Ryan180602 updated:
-
-
Specify in role description for Engi/MT Trainee roles that the role is for new players.
-
-
SealCure updated:
-
-
Adds the nymph as a pet option to loadout. Perhaps it is your best friend or perhaps you just woke up with it, who knows?
-
Add inhand sprites to the rat.
-
-
SierraKomodo updated:
-
-
The broken 'Summon Bear' spell has been removed.
-
-
Spookerton updated:
-
-
Corrected adrenaline injector back to 5u.
-
-
-
11 January 2023
-
SomeAngryMiner updated:
-
-
EMT, First response jackets and medical chest rigs have been resprited.
-
Autocompressors have been resprited as well.
-
-
-
10 January 2023
-
SierraKomodo updated:
-
-
Rogue combat drone events now properly spawn combat drones.
-
-
-
09 January 2023
-
SierraKomodo updated:
-
-
Uplink radios now look like shortwave radios.
-
-
Taza, gy1ta23 updated:
-
-
syringes no longer turn fish fillet into sashimi.
-
you can now extract carpotoxin from carp meat with syringes
-
pike meat now has a bit more carpotoxin in it than carp meat
-
-
gy1ta23, Taza updated:
-
-
telescopic batons have sprites for both collapsed and extended states
-
-
-
07 January 2023
-
SierraKomodo updated:
-
-
Blast doors no longer open when clicked on.
-
-
-
06 January 2023
-
SierraKomodo updated:
-
-
Making material coins now produces coins once more.
-
Oxygen pumps no longer break if their linked mob is deleted.
-
Tables now use their material's hit sound when struck.
-
Tables now have a minimum damage value needed to damage their health, derived from their material.
-
Standardized health and damage handling has been added to doors, airlocks, wall cameras, and artifacts.
-
Machinery damage resistances are now set to the electrical preset (Immune to radiation, bio, pain, toxin, genetic, oxy, brain, and psionic damage; Takes 1/2 stun damage).
-
Airlocks no longer damage themselves when closing.
-
Walls and doors now properly respect their material's melting points during fire damage checks.
-
Grilles no longer receive damage twice from fires.
-
Fire doors and cameras now use plasteel's heat resistance value (~6,000 kelvin). Up from steel (~1,800).
-
Fire damage is now reduced by the atom's melting point value. By default, this is 100C (373K).
-
Replaced the engine hatch doors with blast doors labelled as 'Engine Access Hatch'. These doors can be toggled by a new button located in the engine monitoring room, next to all the other emergency buttons (It's the far left one).
-
-
-
05 January 2023
-
Ryan180602 updated:
-
-
Beds now deploy instant. but need time to pick back up.
-
-
-
03 January 2023
-
SierraKomodo updated:
-
-
Adjacent fires are more effective against windows and walls and can effect some other atoms that would otherwise block airflow and fire. Doors are currently exempt.
-
Any machinery with wires will now have those wires randomly pulsed or even damaged/cut when EMP'd.
-
Spider webs and cocoons now die properly.
-
Eggclusters now release some spiderlings when they are broken. The number of spiderlings released depends on how mature the egg cluster was.
-
Replicators now provide a replicator circuit board when dismantled instead of an auto lathe board.
-
-
Spookerton updated:
-
-
Unathi have their own tattoo setup and get modified chest tiger, hive, and bands.
-
The holodeck works.
-
Doubled inflatable health to account for people-throwing.
-
-
The Spanish Inquisition updated:
-
-
renamed 'internal rifle magazine' to 'en-bloc clip' as it SHOULD BE
-
-
-
01 January 2023
-
gy1ta23, Taza updated:
-
-
smaller potted plants are now objects. you can pick them up. rejoice
-
small potted ferns are trimmable with wirecutters. the bridge now has a fern
-
-
-
30 December 2022
-
SierraKomodo updated:
-
-
Certain trader types should no longer break when asking them what they want.
-
The Randomize button in character setup will no longer randomly break/crash.
-
Ore processing consoles that are not connected to valid machinery now display a prompt to scan for adjacent machinery instead of crashing.
-
-
-
28 December 2022
-
gy1ta23 updated:
-
-
text/typo fixes for loadout coins, confederate naval uniforms
-
-
-
27 December 2022
-
SierraKomodo updated:
-
-
Ghosts no longer receive the floored message when explosions happen.
-
-
-
22 December 2022
-
SierraKomodo updated:
-
-
Attacking energy nets with your bare hands or melee weapons now requires you to be on harm intent.
-
You can now damage doors that are open.
-
Items and machinery that use standardize health will no longer delete themselves from explosions if the explosion didn't kill them.
-
-
-
18 December 2022
-
JoeyNosegay updated:
-
-
Removes 'Grandpa' from male human names.
-
-
-
-GoonStation 13 Development Team
-
- Coders: Stuntwaffle, Showtime, Pantaloons, Nannek, Keelin, Exadv1, hobnob, Justicefries, 0staf, sniperchance, AngriestIBM, BrianOBlivion
- Spriters: Supernorn, Haruhi, Stuntwaffle, Pantaloons, Rho, SynthOrange, I Said No
-
Новые иконки и айтем стейты для: электролазер карабина, тазерного ружья, револьвера фонда имени тов. Кухулина,
+
+
Обновления SegaCD:
+
+
Added black carpet and a supply entry to order it.
+
+
Обновления emmanuelbassil:
+
+
Fixes being unable to bash off a cover off a broken APC
+
APCs now have standardized health
+
+
Обновления papismirnov:
+
+
Adds the portable radio jammer to the antag uplink
+
+
+
17.02 - 2024
+
Обновления LordNest:
+
+
Fixed missing icon for alien power cell
+
Added new sprites for rig modules: cooling unit and powerfist by Hawwar
+
+
Обновления Noreaus:
+
+
changes the taste of DnB from sassafras (wrong) to fruit and aniseed (correct)
+
+
Обновления emmanuelbassil:
+
+
Can now fill fire extinguishers using beakers. Can effectively fill them with any liquid.
+
Fire extinguishers have a chance to break when firing any liquid that isn't water. They cannot be repaired.
+
Borgs can repair broken fire extinguishers at the recharging station.
+
+
+
15.02 - 2024
+
Обновления KandJX:
+
+
Пофикшенно оторбражение горящих газов при низком количестве молей. Решение портировано с WyccBay.
+
+
+
14.02 - 2024
+
Обновления KandJX:
+
+
Переработка дорм
+
Переработка сауны
+
Решение ишуев по карте
+
СЕ выдан раундстартом пояс с Хайтир инструментами
+
Утилити пояс теперь вмещает в себя РЦД и ПейнтСпреер.
+
Добавлена карта авейдереликта "Пиратской Шахты"
+
Порт пропов для сауны от ДаркСовета с проекта ФайнлДестинейшн
+
+
Обновления LordNest:
+
+
Портировал с даунстрима фикс граба самого себя от larentoun
+
Пофиксил отсутствие протезов НТ на экране выбора (их украли таяра)
+
Починил обозначение новых культур
+
+
Обновления PapiSmirnov:
+
+
subdermal armor will now actually apply it's damage resistances.
+
+
+
13.02 - 2024
+
Обновления LordNest:
+
+
Fixed absence of capitalized cyrillic prefixes for radio usage.
+
+
Обновления Titanized:
+
+
Increases the maximum complex devices skill level to 'experienced' for the CE.
+
+
Обновления ddorou:
+
+
Изменил максимальный размер предметов для рюкзака с LARGE на NORMAL
+
+
+
07.02 - 2024
+
Обновления LordNest:
+
+
Humans can choke (use emote) now properly.
+
+
Обновления Spookerton:
+
+
The potato reactor and reactor reactor correctly accept vodka and coolant as coolants respectively.
+
+
Обновления emmanuelbassil:
+
+
As part of the damage standardization, can now damage closets/crates with melee attacks.
+
Damaging a closet also relays some damage to whatever's inside.
+
Secure closets/crates are tougher than regular closets/crates.
+
+
+
06.02 - 2024
+
Обновления LordNest:
+
+
Увеличил скорость пулям
+
Добавил новый звук
+
Добавил силген. Занерфил его насколько смог.
+
Добавил мультиметр. Занерфил его насколько смог.
+
Добавил новую иконку переноски силгена от которой Вольфора хватил припадок
+
Добавил силген и мультиметр на карту. Удалил окно между РД и СМО опять. Канджи, только попробуй.
+
+
+
03.02 - 2024
+
Обновления Nyvrem:
+
+
Vox have had errant thoughts of excessive identity pruned from their mindscapes. Again.
+
+
Обновления Sbotkin:
+
+
Fleet medical fatigues no longer spawn with an armband.
+
The end-of-round vote now correctly displays extend time.
+
+
+
02.02 - 2024
+
Обновления Mucker:
+
+
Brought back the good skybox.
+
+
Обновления SuhEugene:
+
+
Отключил Onyx лодаут, который возможно и создавал большие лаги сервера.
+
+
+
31.01 - 2024
+
Обновления emmanuelbassil:
+
+
Unathi no longer get as much nutrition from non-meat items
+
+
+
30.01 - 2024
+
Обновления Rockton:
+
+
Adds Komarov overmap placeholder sprites for admin use
+
+
Обновления SuhEugene:
+
+
Fixed white skybox
+
+
+
29.01 - 2024
+
Обновления LordNest:
+
+
Мыши больше не вываливаются из труб
+
Автохисс починил
+
Станбатону иконки синие приделал
+
Удалил дубли причёсок
+
Вернул Рекса
+
+
Обновления emmanuelbassil:
+
+
Radial menus now properly work when inside an exosuit
+
+
+
28.01 - 2024
+
Обновления Sbotkin:
+
+
The "Status of the Fleets" is moved to the Bridge storage.
+
+
+
27.01 - 2024
+
Обновления KandJX:
+
+
Пофиксил венты Гуппа
+
+
+
26.01 - 2024
+
Обновления LordNest:
+
+
Сделал новые эмоуты (портировал с Пары) ящерам
+
Портировал звуки Нелли для ящеров
+
+
+
24.01 - 2024
+
Обновления Mucker:
+
+
Robots can no longer roll traitor.
+
Fixed shuttle docking issue.
+
Fixed projector slides requiring you to be next to them to properly examine what they're projecting.
+
+
Обновления Sbotkin:
+
+
NTNet IDs are randomized on the mapped NTNet devices.
+
+
Обновления Slywater:
+
+
Added a changelog editing system that should cause fewer conflicts and more accurate timestamps.
+
Killed innocent kittens.
+
Zombies can pry open emergency shutters.
+
Zombies can no longer delete machinery like shuttle consoles.
+
Infected NPCs have clothes once again.
+
Monkey-like alien fauna can be infected with black goo.
+
Black goo infection now preserves hair.
+
Re-instated post-mortem infection (with a lower chance of exposure).
+
Nerfed zombie knock-down chance.
+
Nerfed zombie brute & burn resistance.
+
Decreased overall bite infection chance.
+
Increased zombie attack speed.
+
Increased the likelihood for zombies to break out of grabs.
+
Reduces dialysis effectiveness for removing black goo.
+
Removed the hulk mutation.
+
Ghosted players can now possess zombie NPCs, either by dragging their ghost onto them, or by clicking "Join as Zombie" in the Ghost tab.
+
+
Обновления emmanuelbassil:
+
+
Using a sharp tool to engrave graffiti on floors/walls now properly leaves fingerprints.
+
Doors with exposed wires no longer open when trying to access wires with empty hand.
+
Attacking simulated turfs and objects with punches now have the same behavior. Any turf or non-item object with a healthbar can be attacked; if minimum damage threshold is not reached will damage your hand.
+
+
+
23.01 - 2024
+
Обновления UEDHighCommand:
+
+
Портированы расовые спрайты унати, скреллов для большинства войдсьютов
+
Добавлены расовые спрайты унати, скреллов и таяра для ИКС ЛЭКа
+
Унати, скреллы и таяра теперь могут пользоваться ИКС ЛЭКа
+
Риот-скафандр СБ возвращен в меню Security suit cycler
+
+
+
21.01 - 2024
+
Обновления Mucker:
+
+
The shuttles on the Skrell and Vox ships are no longer broken and can jump again.
+
Fixed players being put in space sometimes when joining the Skrell or Vox submap.
+
+
Обновления SierraKomodo:
+
+
Added new generic memos about explorer and fleet rank abbreviations.
+
+
Обновления emmanuelbassil:
+
+
RCD now properly builds machine/computer frames
+
+
+
19.01 - 2024
+
Обновления LordNest:
+
+
Fixed graffiti and pickaxe icons
+
+
Обновления emmanuelbassil:
+
+
Fixes switching medigel scanner modes on mechs.
+
Fixes being able to place item into locked briefcases
+
+
+
16.01 - 2024
+
Обновления LordNest:
+
+
Починил цветам свет. Но это не точно.
+
Вернул из небытия наборы наёмников. Только будьте осторожны. Они теперь в ящиках, ибо юнит тесты
+
Поставил инфотехнику отдельный новый прикольный шкаф
+
Добавил наёмникам набор нетраннера. Использовать на свой страх и риск.
+
Включил добавленное уже сто лет назад Respawn as Human. Теперь оно Respawn as Self
+
+
Обновления Mucker:
+
+
Fixed Hullbreaker lasers not penetrating solid turfs.
+
Added an 'Interlude' that can happen when doing long-range teleports. Or if you're just in the wrong place at the wrong time.
+
Added more emotes for bluespace entities.
+
+
Обновления SuhEugene:
+
+
Bullets no longer reach hostage when its grabber is turned away.
+
+
Обновления emmanuelbassil:
+
+
Vending machines are now destructible. They have a chance to start speaking, randomly have wires cut, or completely malfunction as they are more and more damaged.
+
Vending machines with the speaker enabled speak a slogan every two minutes instead of 10. Speaker disabled by default.
+
+
+
15.01 - 2024
+
Обновления LordNest:
+
+
Tacticool turtleneck rolled sleeves now have their own sprite.
+
Flamethrower now have normal sprite
+
Exosuit interface now slightly upper and you can see your glasses, while piloting mecha
+
Disposals now have nano-ui styled interface
+
Added hardsuit-mounted powerfist for ICCGN ninja suit.
+
+
Обновления Rockton:
+
+
Fixes the ICGNV Hound's O2 Canister.
+
+
+
12.01 - 2024
+
Обновления LordNest:
+
+
Hotfixed some unit tests fails for biodome
+
У ИИ снова есть доступы
+
ИИ больше не может видеть сквозь темонту (лежать+известно что)
+
Заменил скреллам глаза на каноничные Чёрные Глаза
+
Починил турели
+
Стрельбу вернул вам с лайв билда
+
+
Обновления Mucker:
+
+
Removed the e-gun from the flying borg module's emag/antag tools.
+
+
+
10.01 - 2024
+
Обновления LordNest:
+
+
Added new sprites for fossils
+
+
+
09.01 - 2024
+
Обновления SierraKomodo:
+
+
Fixes borgs having broken and weirdly cached/synchronized eye overlays that had no color.
+
Fixes ironing boards and irons not de-wrinkling clothing.
+
Fixes robotics fabricator machines not properly animating while printing.
+
Fixed cases where decapitated heads couldn't be opened or have their internal parts removed using tools.
+
+
+
08.01 - 2024
+
Обновления LordNest:
+
+
Added some new templates for exoplanets
+
Fixed skrellian pistol sprite and sandstone wall sprite
+
+
Обновления SierraKomodo:
+
+
Punitelli now spawns in various random rooms instead of in maintenance and other dangerous places.
+
On-mob sprites for energy shields now properly update when the shield turns off.
+
Lights, switches, and air alarms now use emissive overlays.
+
The passenger role now has unlimited slots. (Это на Торче, так что не расслабляйтесь, Сьерровцы)
+
+
Обновления emmanuelbassil:
+
+
Fixes in-hand pipes disappearing when clicking on anchored down pipes.
+
Hardsuit mounted flash works properly again
+
Status displays are now destructible.
+
Can repair destroyed status displays with two sheets of glass.
+
+
+
07.01 - 2024
+
Обновления Mucker:
+
+
Removed Bearcat's holopad and distress beacon. Added a radio beacon circuit board and a RCD.
+
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 87ef103b8b5be..7859299900d63 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -1,4 +1,4 @@
-DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
+DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY sierra_genchangelog.py.
---
2013-01-07:
Cael_Aislinn:
@@ -3682,7 +3682,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- tweak: Supermatter now uses the global announcer.
- bugfix: Supermatter now sends out integrity alerts.
Legius:
- - bugfix: The cryotubes on the shuttle and at centcomm are no longer death traps.
+ - bugfix: The cryotubes on the shuttle and at centcom are no longer death traps.
2016-11-22:
Broseph Stylin:
- tweak: Waistcoats and suspenders are now accessories and no longer suit items.
@@ -4040,7 +4040,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- rscadd: Added toeless workboots. Added them to the loadout.
- rscadd: Added athletic shoes. Added them to the loadout with the ability to color
them.
- - rscadd: 'Added the following hoodies to the loadout: NanoTrasen, Space Mountain
+ - rscadd: 'Added the following hoodies to the loadout: Nanotrasen, Space Mountain
Wind, Mariner University, and Ceti Techical Institute.'
- tweak: Changed the 'Mars University Lunchbox' to the 'Mariner University Lunchbox'.
- tweak: Gave boots and gloves to the normal and Resomi variation of the radsuits.
@@ -4639,7 +4639,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
Ravenxales:
- tweak: Fix many strings to respect the Torch environment.
RedStryker:
- - tweak: Added new lore-consistent clothing items for NanoTrasen personnel.
+ - tweak: Added new lore-consistent clothing items for Nanotrasen personnel.
Techhead:
- rscadd: Moves Junior Enginner to a Crewman alt-title, accompanied by the new Junior
Corpsman.
@@ -4670,7 +4670,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
Leshana:
- bugfix: Fix rooms holding pressure when exposed to space.
RedStryker:
- - tweak: Added new lore-consistent PDA, Locker, and Action Figure sprites for NanoTrasen
+ - tweak: Added new lore-consistent PDA, Locker, and Action Figure sprites for Nanotrasen
items.
- rscdel: Removed science armband.
- tweak: Recolored research tape to white.
@@ -6848,7 +6848,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
MEMORY CORRUPTION. You are re-training bots by letting them practice on less
complicated things before they can tackle more challenging blueprints.
Devildabeast:
- - tweak: Gives the NanoTrasen badge to the NanoTrasen Liaison in their backpack;
+ - tweak: Gives the Nanotrasen badge to the Nanotrasen Liaison in their backpack;
removes it from the NT formal outfit.
chaoko99:
- tweak: Trash bags are no longer denied from holding the nuke disk. Try and figure
@@ -6918,7 +6918,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
"Object" category.
sabiram:
- bugfix: Corrected chemist slot loadout issues.
- - bugfix: NanoTrasen security staff can now use their provided holobadges.
+ - bugfix: Nanotrasen security staff can now use their provided holobadges.
2018-02-06:
Hubblenaut:
- rscdel: Removes Captain's spare ID card.
@@ -6933,8 +6933,8 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
2018-02-08:
Devildabeast:
- rscadd: Added the alternate titles of "SolGov Ombudsman" and "Inspector General"
- to SolGov Representative and "NanoTrasen Representative" and "NanoTrasen Executive"
- to the NanoTrasen Liaison.
+ to SolGov Representative and "Nanotrasen Representative" and "Nanotrasen Executive"
+ to the Nanotrasen Liaison.
2018-02-09:
Casper3667:
- rscadd: The utility uniforms (EC, general and EC skirt) now looks better on taj.
@@ -7303,7 +7303,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- bugfix: You can no longer drag other people, forcing them to crawl.
2018-04-09:
Devildabeast:
- - maptweak: Adds green pens to the SolGov Representative's and NanoTrasen Liaison's
+ - maptweak: Adds green pens to the SolGov Representative's and Nanotrasen Liaison's
offices.
2018-04-10:
ParadoxonKomplikon:
@@ -7746,7 +7746,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- rscdel: Hand teleporters have been removed from teleporter rooms.
- maptweak: Teleporter beacons have been removed from some high security areas onboard
the Torch.
- - maptweak: A teleporter beacon has been added to NanoTrasen's miscellaneous research's
+ - maptweak: A teleporter beacon has been added to Nanotrasen's miscellaneous research's
test chamber.
sabiram:
- tweak: Off-duty ID cards are now visually distinct and can no longer be modified.
@@ -8967,7 +8967,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- rscadd: 'New instruments are now available through supply. rscdelete: Removed
old piano and old violin,'
Zuhayr:
- - rscdel: Removed NanoTrasen rank. You can use the contractor rank to get access
+ - rscdel: Removed Nanotrasen rank. You can use the contractor rank to get access
to science roles now. Play whatever company you like.
- tweak: The Torch has unionized under the Torch LLC Corporate Union. You can get
a union card in loadout and the Corporate Liaison is now the Workplace Liaison,
@@ -9141,7 +9141,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- bugfix: The windowtint button on the back wall of the XO's office actually works
now.
sabira:
- - tweak: The research department's colouration has changed from the red of NanoTrasen
+ - tweak: The research department's colouration has changed from the red of Nanotrasen
to the green of Torch, Limited.
- tweak: Torch, LLC (the holding company representing the research department) has
been renamed to Torch, Ltd.
@@ -9149,7 +9149,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
Ltd logo.
- maptweak: Changed the research director and liaison's offices on the bridge to
make use of the green decals.
- - rscadd: Added legacy NanoTrasen uniforms to the loadout.
+ - rscadd: Added legacy Nanotrasen uniforms to the loadout.
sabiram:
- tweak: Adherents of the Vigil can now play as Janitor, Chef, Bartender, Supply
Assistant, Engineering Contractor, Roboticist, Chemist, Research Assistant and
@@ -9490,7 +9490,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
SierraKomodo:
- bugfix: Deck 5 hallways now have air alarms.
sabiram:
- - tweak: All various subcontractor (NanoTrasen and Hephaestus Industries, at time
+ - tweak: All various subcontractor (Nanotrasen and Hephaestus Industries, at time
of writing) items in the loadout have been merged into one item with type selection.
Key word to search for (CTRL-F) is 'corporate'
- rscadd: Lighters, zippos and candles now come in a variety of new colours.
@@ -9790,7 +9790,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
of all objects instantly, instead of freezing for 3-4 seconds.
RyanSmake:
- bugfix: After repeated complaints from the SEV Torch that all showers are constantly
- clogged we have deployed the NanoTrasen Bluespace Plunger(tm). Operation successful,
+ clogged we have deployed the Nanotrasen Bluespace Plunger(tm). Operation successful,
showers will never clog again.
WhiteHusky:
- bugfix: You no longer "see" items being removed from bags or other storage items
@@ -11653,7 +11653,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- imagedel: Removes some now unused stamp icons.
- tweak: RIG stamp module now does the normal stamp instead of the Internal Affairs
stamp.
- - tweak: Centcomm stamp (used on admin faxes) now uses the map specific boss name
+ - tweak: Centcom stamp (used on admin faxes) now uses the map specific boss name
for the stamp.
- bugfix: Prevents putting polytool augment tools in your pocket, behind your ear,
etc.
@@ -20110,3 +20110,3156 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
be made into sushi.
- rscdel: Sashimi can no longer be stacked.
- tweak: Cooking/cutting bacon no longer summons additional protein into existence.
+2023-02-21:
+ Mucker:
+ - tweak: Thermic spiders will now do significant burn damage against mechs when
+ they would normally inject poison.
+2023-02-22:
+ 10sc:
+ - maptweak: The galley is now only accessible by commissioned officers, Stewards,
+ and Chief Stewards.
+ - maptweak: Swapped one of the microwaves with the deep fryer in the Galley. Also
+ adjusted the sink's and light switch's pixel offsets.
+ - maptweak: Put the Torch-defined chef's closet back in the Galley's Cold Storage.
+ Also put a basic border window backing to the fridges to prevent them from moving.
+ - maptweak: Put the Mess Hall jukebox on a table to prevent it from clipping with
+ the status display.
+ Ryan180602:
+ - rscadd: Adds the Gravikinetic Rig Module
+ gy1ta23:
+ - rscadd: the Z9 rifle has its own mags, which can now be ordered, and printed at
+ a hacked lathe
+ rootoo807:
+ - imageadd: Adds icon states for Unathi for clothing items that were missing them,
+ updates icons for Unathi clothes that were using old sprites.
+2023-02-24:
+ Mucker:
+ - bugfix: Fixed the external vents on the SFV Arbiter being attached to the wrong
+ kind of pipes.
+ Ryan180602:
+ - balance: Increase small/midlaser falloff distance/damage multiplier.
+ - bugfix: Fix Pilot Helm object sprite.
+ Sbotkin:
+ - tweak: LCDR now has access to senior officer uniforms.
+2023-02-26:
+ EvenInDeathIStillServe:
+ - bugfix: Fixed sprites and improper lighting for arc welders.
+ Hubblenaut:
+ - rscadd: Introduces department colors for the manifest.
+ - tweak: The crew manifest for ghosts is now black.
+ Jux:
+ - tweak: Z8 rifles and Z9 rifles use interchangeable magazines. They are NOT loaded
+ with the same ammo.
+ - tweak: Z9 rifles are now military caliber. The bullets do the same damage as before,
+ they're just grey and 7mmR now.
+ - tweak: Weapons that can load different magazines, like the bullpups and the L6
+ SAW, have a chance to jam when using their non-native type.
+ - tweak: Practice rifle rounds are now blue-tipped for better distinction.
+ - tweak: Hardsuits now display a progression bar while being equipped.
+ Ryan180602:
+ - tweak: Removes fall-off from sniper beam. Decreases damage to 35.
+2023-02-27:
+ SierraKomodo:
+ - bugfix: Energy weapons no longer function as heat sources when turned off.
+ - rscdel: Attempting to buckle to an object that already has someone buckled to
+ it no longer unbuckles the already buckled person.
+ - rscadd: Buckling now sends various descriptive failure feedback messages to you
+ if your attempt to buckle or unbuckle someone fails.
+2023-02-28:
+ Jux:
+ - tweak: Civilian roles with access to department ponchos can now also select generic
+ ponchos.
+ - tweak: Fleet mobs now leave the correct gear on their corpses and aren't all mustached.
+ - tweak: Fleet hardsuit mobs now have corpses.
+ - bugfix: Simplemobs now leave empty casings when firing guns.
+ SealCure:
+ - rscadd: Replaces the pistol in the CoS's locker with an item allowing them to
+ choose between the classic smartgun and the cool stylish smart revolver. Only
+ cool kids will pick the revolver.
+2023-03-01:
+ SierraKomodo:
+ - bugfix: Interactions that require drop+dropping an item on another item should
+ now work again, if they were broken.
+ - bugfix: Spawning in a wheelchair no longer places you in a broken half-buckled
+ state.
+ - admin: Notes now include the round ID they were added on. Note this is not retroactive
+ and only applies to notes created after this change went live.
+2023-03-02:
+ rootoo807:
+ - bugfix: Voxship has uranium again.
+ - bugfix: Welding rods produces the correct material sheets. Flammable rods can't
+ be welded into sheets.
+ - bugfix: Fixes circuit printer messages and material checking.
+2023-03-03:
+ BurpleBineapple:
+ - rscadd: Fleet gloves to the uniform dispenser. They're black.
+ SierraKomodo:
+ - bugfix: Bluespace ghosts no longer duplicate mechs and should now instead replace
+ the pilots with clones.
+ - maptweak: The Aquila no longer has 2 chairs stacked on top of eachother.
+ - rscadd: Borg grippers now show the icon of what they're holding.
+ Spookerton:
+ - bugfix: Fixed snack trash being given to the consumer when being fed by another.
+ rootoo807:
+ - bugfix: Wearable objects that can go in more than one slot (e.g. bandanas, towels,
+ cigarettes, gum/lollipops, berets, toilet paper) now render correctly for Vox
+ and Unathi.
+ - tweak: Minor sprite tweaks for Unathi bandanas + a few other hats, added some
+ icons for recently PR'd clothes.
+ - rscadd: Adds two hairstyles from Aurora.
+2023-03-04:
+ ? ''
+ : - tweak: fleet LCDRs get the right uniforms now
+2023-03-05:
+ Ryan180602:
+ - rscadd: Add L6 SAW onmob sprites (From Polaris)
+ SierraKomodo:
+ - bugfix: Attack click cooldown when hitting things with standardized health now
+ applies the mob's and weapon's intended cooldown timers, instead of the generic
+ default time.
+ - bugfix: Using grabs to slam someone into a window no longer slams them twice per
+ click.
+ - tweak: Fingerprints are now always added to held items and clicked objects if
+ there was a valid interaction, except when the tool is flagged to not leave
+ fingerprints.
+ cheesewizard2:
+ - tweak: changes the sprite of laborer's overalls and the rolled-up-sleeves version
+ to have the belt 1 pixel higher
+ rootoo807:
+ - bugfix: Crew with special characters in their name no longer stay in crew records
+ after cryo-ing.
+ - tweak: Serving bowls can no longer be used in cookers. Boil your potatoes before
+ serving them.
+2023-03-06:
+ Nope63:
+ - rscadd: Adds a new crashed probe away site ruin
+2023-03-07:
+ Hubblenaut:
+ - rscadd: Pets in your hand can now be hugged.
+ SierraKomodo:
+ - maptweak: The screwdriver in botany now spawns on the table instead of the floor.
+ rootoo807:
+ - bugfix: Fixes Solgov beret rendering on Unathi.
+2023-03-08:
+ Mucker:
+ - admin: Computers on the admin z-level can now run all NT-Net programs.
+ Ryan180602:
+ - tweak: Decreases material bat max force to 30 from 40.
+ SierraKomodo:
+ - bugfix: Unbuckling while handcuffed no longer tells you success, then failure,
+ then does nothing - It now actually unbuckles you.
+ - bugfix: The gunbox no longer spawns guns inside your mob if your hands are full.
+ - bugfix: The gunbox no longer deletes itself if you cancel the prompt.
+ - bugfix: Smoke no longer affects you through gas masks.
+ - tweak: IV bags now come with a default transfer rate of 2 units (1 unit for nanoblood
+ bags).
+ - rscdel: IV bags can no longer be set to a transfer rate of 0.2 units
+2023-03-11:
+ Jux:
+ - bugfix: Random corpses like "dead test subject" will no longer appear in credits.
+ - bugfix: End titles formatted like "The BLANK of BLANK" no longer have a missing
+ space.
+ 'gy1ta23 ':
+ - tweak: Added a sink to the laundry room.
+ rootoo807:
+ - bugfix: Beards no longer appear on the back of your head in setup preview.
+ - bugfix: Grilles can no longer be built on the same tile infinitely.
+2023-03-14:
+ SierraKomodo:
+ - tweak: Chopping down vines with edged weapons now works when clicking on harm
+ intent, instead of overriding alt+click.
+2023-03-15:
+ rootoo807:
+ - bugfix: Cans and bottles are no longer deleted when clicked on with other items.
+ - tweak: Welding steps in crafting now use 1 welding fuel.
+2023-03-17:
+ SierraKomodo:
+ - bugfix: Default throwforce value changed from 1 to 0. This fixes random harmless
+ objects causing damage to things when thrown.
+2023-03-19:
+ Spookerton:
+ - tweak: PAI mobs don't exchange places with people on bump.
+2023-03-22:
+ CrimsonShrike:
+ - rscadd: Holding a baseball bat has a chance, scaled by athletics skill, to reflect
+ thrown items back at launcher
+ - rscadd: Baseball unhandled afterattacks on small and below items will hit the
+ item and send it flying.
+ Ryan180602:
+ - bugfix: Fix LMG onmob sprites.
+ - rscadd: Readds Singulo barsign
+ SierraKomodo:
+ - tweak: Removing webbing from other mobs with an item in your hand now requires
+ you to be on a non-harm intent. If on harm intent, you'll just hit them instead.
+ Now you can take advantage of that webbed up victim you wanted to kill anyway!
+ - tweak: Security bots now react to being attacked with a melee weapon/tool, even
+ if the attack does no damage.
+ - tweak: Attacking simple mobs with melee weapons/tools now requires being on harm
+ intent.
+ - tweak: Repairing cyborgs with a welding tool or cable coil now requires 1 units
+ of fuel per repair and includes a 1 second timer.
+ - tweak: Using a screwdriver on a borg now gives you the option of accessing the
+ wiring or radio, instead of the decision being decided by the presence of a
+ power cell. Opening the wiring panel still requires the power cell to be removed
+ first.
+ - tweak: Various tool based interactions with mobs now all include failure feedback
+ messages, and visual messages on success.
+ - tweak: pAIs no longer fold when attacked.
+ - rscadd: 'Atoms that serve as flame or heat sources for interaction checks now
+ display "It has an open flame" or "It feels hot to the touch" messages when
+ examined. (NOTE: The heat examine message only appears if you can physically
+ touch the atom).'
+ - rscadd: Atoms that serve as a flame source (Has an open flame - Lit candles, matches,
+ welding tools, etc) now also serve as a heat source for interaction checks.
+ - bugfix: Legacy non-harmful mob interactions no longer require harm intent to occur.
+ rootoo807:
+ - bugfix: Engi-borgs can use steel to lay reinforced floor.
+ - bugfix: Carpet stacks (and other pre-made tile stacks) work now.
+2023-03-23:
+ SierraKomodo:
+ - bugfix: Fixes mechs being unrepairable with cable coil when components have burn
+ damage.
+ - bugfix: Fixes mechs not accepting power cells.
+2023-03-28:
+ Ryan180602:
+ - tweak: Cig cases now start with Transtellar Duty Frees.
+ SierraKomodo:
+ - rscadd: Cable coils and placed cables can now be painted any color with a paint
+ sprayer. Cyborgs can set their cables to any color through the usual change
+ color verb.
+ - tweak: Random cable coils now have a chance of spawning with a black color.
+2023-03-29:
+ Mucker:
+ - tweak: Exoplanet awakening event should now happen less often.
+ - tweak: Exoplanet awakening will only happen if there are 4+ people on the planet.
+ PurplePineapple:
+ - rscadd: A mob's last say message is now recorded in mind. Antagonists have their
+ last words displayed at round end if they die.
+ Rootoo807:
+ - rscadd: Adds a new selectable crow plushie.
+ - tweak: New mouse plushie sprite
+ Ryan180602:
+ - tweak: Allows ED209s to accept stun revolvers. Making them actually constructable.
+ - tweak: Lets ED209s use the subdermal armour plating from mechfabs.
+ - rscadd: Adds snakes, puppies and opossums for loadout selection.
+ SuhEugene:
+ - bugfix: Fixes incorrect calculation of the materials needed by the protolathe
+ to build design
+ rootoo807:
+ - bugfix: Suit cyclers will now always yield correct sprites for refitted voidsuits.
+ - tweak: Torch has a Torch-specific suit cycler in the E-arm, and a Torch-specific
+ pilot suit cycler in exploration storage.
+2023-03-31:
+ Rootoo, PinkestKitten, JoeyNoseGay, Jux:
+ - rscadd: Adds the Frontier Alliance logo, usable with [falogo].
+ SierraKomodo:
+ - bugfix: Tool vendors now dispense cable coils again.
+ - bugfix: Fixes silicon maintenance hatches not opening when the do after timer
+ completes.
+2023-04-01:
+ Ryan180602:
+ - bugfix: Make Cobby have a sprite, again.
+2023-04-03:
+ Hubblenaut:
+ - tweak: Reduces the transparency of the on-mob Security Aviator sprite.
+ Jux:
+ - tweak: In prefs, character gender has been divided into "bodytype" and "pronouns.
+ - tweak: Pronouns for most mobs can now be set separately from their gender.
+ - tweak: Mobs of a different faction will no longer recognize each other unless
+ introduced with the new "introduce" verb.
+ - bugfix: Ghosts recognize everyone again.
+2023-04-04:
+ Hubblenaut:
+ - bugfix: Fixes the smart revolver shooting practice beams.
+ SierraKomodo:
+ - tweak: Custodial closets now contain 2 cleaner grenades.
+ - bugfix: Cable coils no longer reset to red when their icon is updated. This corrects
+ both coils changing when you place a cable, and cut cables being red.
+ - admin: Connecting clients are now checked for association with other known users
+ and active bans. Any matches are displayed as debug logs for testing purposes.
+ - admin: Added buttons to the Player Panel to look up matching known prior connections
+ and matching active bans for a given mob. Currently intended for ease of debug
+ use, but should be accurate for admin purposes. Requires ADMIN perms for now.
+2023-04-05:
+ SierraKomodo:
+ - admin: Associated ban checks now ignore job bans.
+ - admin: Logs for connections and bans found on clients now appear as staff logs
+ instead of debug logs. Messages for found bans are in red.
+ - admin: Checks on connecting clients are now skipped if the server is performing
+ initialization, to avoid causing connection failures or other lag-related issues.
+2023-04-06:
+ Jux:
+ - bugfix: Borgs and pAIs now adopt faction from their creator.
+ - bugfix: Joining as a borg now loads faction correctly.
+ - bugfix: Species descriptors that want gender will no longer get fed pronouns.
+ - bugfix: Passports no longer merge pronouns and species.
+ - bugfix: MediHuds now correctly identify pronouns as pronouns.
+ Ryan180602:
+ - tweak: Make pain/status effects of grabber matter for grab strength.
+ SuhEugene:
+ - bugfix: Set AI Core Display verb now works instead of doing nothing
+2023-04-07:
+ AzlanonPC, DasFox:
+ - rscadd: EC ranks have received a resprite.
+ Hubblenaut:
+ - tweak: When having the required weapons skill, guns will automatically have their
+ safety turned on when holstered.
+ - tweak: Skill-based toggling of the gun-safety when drawing/shooting a gun is no
+ longer chance-based.
+ Mucker:
+ - admin: Admins can now view the total votes cast and the number of votes per choice
+ after a vote closes.
+ PurplePineapple:
+ - rscadd: Three new positronic cultures. 1st, 2nd, and 3rd Generation.
+ - tweak: Positronics can now select the new human planets that were added over time.
+ Rootoo807:
+ - bugfix: Flash shells, remotes for remote-bots, and re-cooked custom foods have
+ sprites now.
+ - tweak: 7mm bullets use the 7mm sprites instead of the 10mm sprites.
+ Ryan180602:
+ - bugfix: Fix outlying pixel on unbranded arms.
+ SierraKomodo:
+ - bugfix: Optimizes some on-connect code that was causing the server to lag when
+ certain users joined.
+ - bugfix: Running the geosample scanner on pitchblende no longer gets stuck in an
+ infinite runtime-error loop.
+2023-04-09:
+ Mucker:
+ - bugfix: The BSD can no longer be constructed.
+2023-04-10:
+ CrimsonShrike:
+ - rscadd: Transport drone. It can pick up payloads from external areas (such as
+ those on planets) and deliver them to the ship.
+ Fenodyree:
+ - tweak: integrated hypo-injectors will inject into the hands if held.
+ - tweak: integrated hypo-injectors will ignore thick material while carried in pockets
+ or on belts.
+ Jux:
+ - rscadd: Ports Aurora's unbranded prosthetic icons, done by there NiennaB and TheStryker.
+ - tweak: Small adjustments to the default morpheus arms to remove oddities.
+ - tweak: Inflatables boxes no longer have a flag on their side when held in hands.
+ - tweak: Renegades now only have one basic and one trained skill choice.
+ Luftkommando:
+ - rscadd: Added almonds, and biscotti and unscotti recipes.
+ - rscadd: Tiny food items can be dipped into glasses to absorb some reagents.
+ - bugfix: Fixed peanut kitchen_tag.
+ Mucker:
+ - tweak: Guns no longer spawn on a certain hidden D3 loot spawn.
+ Ryan180602:
+ - tweak: Normal jumps cool down in 5 seconds instead of 10.
+ - tweak: Add quick jump for all species (10 second cooldown, instant one tile jump)
+ - tweak: Make vox leap-grabs instant with a 15 second cooldown.
+ - tweak: increase quick jump/leap grab stamina cost.
+ Sbotkin:
+ - tweak: SEA no longer gets an officer's beret.
+ SierraKomodo:
+ - tweak: The chemistry fridge now accepts IV bags.
+ - maptweak: Medbay now has a box of empty IV bags in the ETC.
+ - bugfix: The 'Check Bans' button in the Player Panel now shows all related bans,
+ making it consistant with the admin log on connect.
+ - balance: Plushie bomb explosions have been bumped up a tier from light to heavy.
+ - bugfix: Fixes an oversight where crates wrapped in paper could not be put in disposal
+ bins.
+ - rscadd: Disposal bins now display a feedback message when trying to disposal a
+ large object that the bins don't accept.
+ - bugfix: Empty small parcels can now be opened to reveal they are empty. This deletes
+ the parcel.
+ - tweak: Parcels now have 5 hit points and can be damaged by standardized health
+ sources. Any damage parcels take will also be applied to their contents. Paper
+ wrapping isn't armor.
+ rootoo807:
+ - bugfix: Chameleon clothing no longer stops working after mimicking clothes with
+ item state slots.
+ - tweak: Remove many of the duplicate clothing items from chameleon selection lists.
+ - tweak: Adds icons for some clothes that were missing them (including the sausage
+ cigar).
+ - rscdel: Removes some duplicate clothes and some deprecated clothing items that
+ had no icons.
+2023-04-11:
+ Jux:
+ - bugfix: The skipjack now has all intended navpoints.
+ - bugfix: Audible emotes, like poppy hissing, no longer break with pronouns.
+ - tweak: Whoops. Fixes the morpheus arms having insane shoulders.
+ Ryan180602:
+ - maptweak: Bridge Solar SMES starts on, full, and with 1 magnetic coil in it.
+ Spookerton:
+ - tweak: Added borax ore.
+ - tweak: Borosilicate glass is made by alloying sand and borax.
+2023-04-12:
+ GeneralCamo:
+ - rscadd: Ported the emissives system from /tg/ (with significant influence from
+ SierraBay).
+ - tweak: Consoles now use the emissives system for screens and keyboards.
+ - bugfix: Consoles now update their `icon_keyboard` variable, fixing off and broken
+ states.
+ - bugfix: The "Force Authorization" program now defines a keyboard, rather than
+ defaulting to the "off" keyboard.
+ Mucker:
+ - admin: Replies to admin-helps should not relay to discord.
+ pleonektein:
+ - bugfix: NPC zombies will now emote
+2023-04-13:
+ Jux:
+ - maptweak: The bearcat has had a number of non-major mapping changes. Almost none
+ of these effect the actual layout.
+ Mucker:
+ - admin: Reinforced both event shuttle exterior walls, and walls around the armories.
+ Rootoo807:
+ - tweak: Ports sprites for bread, burgers, donuts, hotdogs, and meat from TG/Aurora.
+ - rscadd: Adds custom burgers. Click on a bun with any food item (barring pre-existing
+ clickable bun recipes) to make them.
+ - tweak: Custom donuts can fit in donut boxes.
+ - tweak: Custom cakes, pizzas, and breads are now sliceable.
+ - tweak: Meat Steak and Loaded Steak recipes now use full pieces of meat instead
+ of cutlets.
+ Ryan180602:
+ - bugfix: You will no longer instantly climb tables/railings after switching maneuvers.
+ - tweak: You can now click in the general direction and tackle (still for one tile)
+ in that direction.
+ - maptweak: Bridge Solar SMES now has superconductive magnetic coil.
+ Sbotkin:
+ - rscadd: Added standing at attention emote, keyword is "atten".
+ SierraKomodo:
+ - bugfix: Jukeboxes can be anchored/unanchored with a wrench again.
+ - bugfix: FLipped iPatches no longer erset their position when toggled on/off.
+ - tweak: Expired guest passes are now a dark gray that still shows sprite details
+ instad of a black void rectangle.
+ - rscadd: Material stacks now include their reinforcement material in the name.
+ I.e., `reinforced glass` is now `steel-reinforced glass`.
+ - rscadd: Feedback messages when using stacks should now tell you how many items
+ from the stack were used.
+ - admin: The window popup for the Check Connections button is now prettified and
+ laid out in a much nicer format.
+ - balance: Wall health, minimum damage value, and basic resistance values have all
+ been increased to make walls harder to break down without tools. See https://github.com/Baystation12/Baystation12/pull/33263
+ for specific numbers.
+ - tweak: All torch jobs now have icons for the nanoui map.
+ pleonektein:
+ - tweak: Can now do surgery on roller beds
+2023-04-14:
+ SierraKomodo:
+ - tweak: The 'Report Issue' button has been re-labeled to 'Bug Report.'
+2023-04-16:
+ SierraKomodo:
+ - balance: Craftable sword damage has been reduced by half.
+ - bugfix: Small packages now properly drop their contents when unwrapped.
+ - bugfix: Low walls now drop the appropriate material when dismantled.
+ - rscadd: Low walls now include their constructed material in their name.
+ - balance: Harmful grab actions on structures, such as bashing people against the
+ EC plaque, now set a click cooldown.
+ - bugfix: Removing wiring from a rigged crate now gives back the used cable.
+ - rscadd: Mech wreckage now uses standardized health and damage.
+ - rscdel: AI can no longer remotely unlock a fireaxe cabinet.
+ - tweak: Using a multitool on an abandoned crate now displays the results in a popup
+ window instead of chat.
+ - tweak: Welding various structures now uses 1 unit of fuel.
+ - tweak: Slicing open lockers with energy blades now requires harm intent.
+ - tweak: Dismantling open lockers with any tool now requires harm intent. This allows
+ safely placing items into lockers that would otherwise destroy them.
+ - tweak: Dumping laundry baskest into open lockers now requires harm intent. This
+ allows placing the basket into the closet without dumping it, if desired.
+ - tweak: Clicking closed lockers with an item in hand no longer toggles the locker's
+ lock or opens it. Hand labelers rejoice.
+ - tweak: Fingerprints are now properly transferred and added when dismantling structures,
+ or performing other interactions with structures that replace them with a new
+ instance.
+ - tweak: Harmful grab actions on structures, such as bashing people against the
+ EC plaque, now require harm intent.
+ - tweak: Catwalks now block access to all under-floor objects (Wiring, pipes, etc)
+ and turfs.
+ - tweak: Grilles can now be anchored/unanchored with a wrench instead of a screwdriver.
+ - tweak: Any interaction of items with a grille now performs a shock chance.
+ - maptweak: The Engineering Hard Storage windoor is no longer purple.
+ - bugfix: Mobs no longer become stuck in a certain pixel offset when buckled while
+ in a grab.
+ - tweak: Pixel adjustments for grabs now also appear for diagonal directions, and
+ reset if the grabee and grabber are on the same tile.
+ - bugfix: Mines will now trigger when stepped on, shot, hit, or affected by an explosion.
+ YourName:
+ - bugfix: Fixes hamburger and cheeseburger sprites.
+2023-04-18:
+ Jux:
+ - bugfix: Plain steaks now have a real sprite.
+ - bugfix: Fleet NCOs now have the NCO wheel cover in dress uniform, replacing the
+ junior enlisted garrison cap, instead of only their service uniform.
+ - bugfix: Engineering NCOs now have the dress beret in their dress uniform extras,
+ matching the other department NCOs.
+ Spookerton:
+ - bugfix: The torch kitchen back room closet once again contains silverware and
+ food safety clothing.
+ rootoo807:
+ - tweak: Service borgs can hold smokeables (pipes/cigarettes/etc.) and other objects
+ from the Rapid Service Fabricator.
+ - tweak: Borgs can use Ctrl+Alt+Click as a shortcut to point at things. The shortcut
+ for borgs and AI to electrocute doors has been moved to Ctrl+Shift+Click (on
+ harm intent).
+2023-04-19:
+ Fenodyree:
+ - bugfix: Reduces the amount hardsuit injectors inject to 5u
+ PurplePineapple:
+ - bugfix: Table slamming now works on harm intent with an aggressive grab.
+ - bugfix: Three-Eye's overlay is no longer unintentionally a black screen.
+ SierraKomodo:
+ - bugfix: Fixed an exploit that allowed you to close inflatable doors on yourself.
+ - bugfix: Table frames can now be dismantled again with a wrench on harm intent.
+ - bugfix: Tables can now be reinforced with a stack of material on harm intent.
+ - bugfix: You can place items in open crates again.
+ - bugfix: The Telecomms air alarm no longer requires Research Director access to
+ unlock.
+ - rscdel: Removed the 'Plain' fake nano-ui browser style.
+ karljohansson:
+ - tweak: Contractor Shuttle Pilots on the Torch gain access to formal clothes, such
+ as suit jackets and fancy hats.
+2023-04-21:
+ Jux:
+ - bugfix: MMI organs now transfer damage to the brain they contain.
+ - bugfix: MMI organs now kill the brain they contain if they themselves die.
+ SierraKomodo:
+ - bugfix: Airlock assemblies and disposal pipes can now be anchored.
+ - bugfix: Airlock assemblies no longer drop their circuits on the floor when installed.
+ - bugfix: Performing crafting on an item inside a storage container (Backpack, box,
+ etc) now properly removes the targeted object and places the new crafted item
+ in the bag, if applicable.
+ - bugfix: Reinforced material stacks no longer display their reinforcement material
+ twice in their name. `Steel-Reinforced Steel-Reinforced Glass Sheets` -> `Steel-Reinforced
+ Glass Sheets`.
+ - bugfix: Maintenance drones no longer die when traveling through disposals.
+ - bugfix: Using an ID on a bot with an open panel no longer displays two feedback
+ messages.
+ - bugfix: Blob tendrils now have fingerprints added to them when successfully harvested.
+ - bugfix: Repairing borgs now actually works, instead of always saying 'The object
+ does not exist'.
+ - bugfix: External limb that are right on the edge of being irrepairable now actually
+ display as such when scanned, instead of showing 'Extreme'.
+ Sweedle:
+ - tweak: Changed a ton of random capitalization. RIP Washing Machine.
+ rootoo807:
+ - tweak: Hardsuits give feedback if they can't be worn due to species incompatibility.
+ - bugfix: Integrated multitool modules can be installed in hardsuits now.
+ - tweak: Landed ship colony away-site uses a distinct archetype (and job roles)
+ from Established colony away-site.
+2023-04-24:
+ Fenodyree:
+ - bugfix: Protolathe now displays correct material costs after upgrades.
+ Jux:
+ - tweak: Zipguns now use a light switch for a trigger, like crossbows.
+ - bugfix: The acid chemical artifact trigger now accepts all acids.
+ - bugfix: The acid chemical artifact trigger now accepts all toxins. There's a lot.
+ - tweak: The volatile chemical artifact trigger now accepts more chemicals.
+ Mucker:
+ - admin: Active tickets will no longer timeout after 30 minutes if the staff member
+ assigned is still active.
+ PurplePineapple:
+ - rscadd: Adds a marksman rifle. It's branded as an ICCGN service rifle.
+ - tweak: The Z8/heavy bullpup now has a more obvious grenade launcher on it.
+ - tweak: The Z9/light bullpup no longer has a grenade launcher.
+ - tweak: The light 7mmR ammunition now uses the weaker projectile than the heavy
+ 7mmR ammunition. Previously, it was made the other way around.
+ - bugfix: The carbine rifle's sprite is now properly aligned form all sides and
+ both hands. Previously it was sort of just floating there.
+ - rscadd: Adds GUN_BULK_LIGHT_RIFLE and GUN_BULK_HEAVY_RIFLE to replace some +1
+ and -1 values in automatic.dm and sniper.dm
+ Ryan180602:
+ - tweak: Dying while gripping someone drops the grip.
+ Sbotkin:
+ - bugfix: Fixed ninja's hardsuit having wrong chem dispenser.
+ yosoyrebelde:
+ - bugfix: Fixed display of guest pass terminal log.
+2023-04-25:
+ Jux:
+ - bugfix: Unbranded hands no longer have a void between the fingers and body when
+ view from the side on feminine bodies.
+2023-04-26:
+ SierraKomodo:
+ - bugfix: The Errant Pisces south airlock is now connected to the ship's powernet.
+ - maptweak: Crashed pod airlocks now start with atmosphere.
+ - tweak: Exterior mining asteroid turfs no longer have atmosphere.
+ - bugfix: Railings now update their icons when wrenched open and closed.
+ - bugfix: Windows now correctly show 'unfastens' when using a screwdriver on them
+ to detach them from the floor.
+ - bugfix: M1 Garand sprites now properly update when they lack a magazine.
+ - bugfix: Gutters can no longer be assembled inside of mobs, storage containers,
+ or anywhere else that isn't a turf.
+ - bugfix: Classic chairs, fancy chairs, and pews now use the correct material when
+ crafted.
+ - bugfix: Using cables on crates now properly creates a wire-rigged crate, instead
+ of silently failing.
+ - bugfix: Circuit printers and autolathes that run out of resources while printing
+ will now properly display their 'Out of X' message if the resource amount is
+ `0` or was never added.
+ - tweak: Board game UI is now opened by ALT+CLICKing the board. The board can now
+ be picked up and handled like any other item with a normal left click, and shift+click
+ examines the board.
+ Spookerton:
+ - tweak: You can order snake juice
+ - tweak: Alien blood is slightly more expensive to buy on the open market
+ - tweak: Plastic blood costs 10 monopoly money per bag instead of 7.5 because of
+ the space economy
+2023-04-27:
+ SierraKomodo:
+ - bugfix: SEV Torch Deck 3 no longer loses pressure during round start.
+ - bugfix: Organs within limbs now probably drop onto the ground when the limb is
+ destroyed/dropped.
+ - bugfix: Cameras now kick you out of their view in the camera app when they are
+ deleted, instead of locking you in a black screen.
+ - tweak: Leaping with a missing limb now has a chance of toppling you over.
+2023-04-28:
+ Imienny:
+ - rscadd: Vox Raiders now have objective to retrieve "Apex Shards" spawning randomly
+ on torch during Vox Raiders.
+ Jux:
+ - rscadd: Adds basic and engineering chest-rigs. They can be selected by TECHNICAL_ROLES,
+ SECURITY_ROLES and MTs in loadout and the basic one can be found occasionally
+ in maint. The engi one replaces the hazard vests in engineering lockers.
+ - tweak: The chemical heater is now a thermal regulator, and can both heat and cool.
+ Science, you see.
+ - rscdel: Chemical coolers no longer exist.
+ PurplePineapple:
+ - rscadd: Specific magazines can be blacklisted from a gun using the banned_magazines
+ variable.
+ - bugfix: The small military pistols (Bobcat, M19) now only take single stack 10mm
+ as intended.
+ Ryan180602:
+ - tweak: Make plaid skirts be selectable under "skirts, selection"
+ SierraKomodo:
+ - bugfix: Do after timer bars should now automatically appear over the user if the
+ target item isn't on a turf, to help ensure visibility as applicable.
+ - rscadd: 'The following objects can now be moved onto tables by click+dragging
+ them onto said table. You and the object _must_ be adjacent to the table for
+ this to work: Cell chargers, Machine frames, Microwaves, Rechargers, Crates,
+ Fax machines, Blenders, Drink dispensers, Beer kegs, Reagents heaters/coolers.'
+ - bugfix: The supermatter core and air waste chambers no longer start with oxygen
+ in them at round start.
+ - bugfix: Fixes the exploit that allows people to keep camera monitors connected
+ to cameras even after they leave camera range.
+ - rscadd: Cameras that have been EMPed or are in areas with no power no longer function.
+ - tweak: 'Cameras now kick you out of their view in the camera app on the following
+ additional events: EMPed, destroyed through damage, movement to a non-connected
+ z-level.'
+ - admin: The `Check Bans` button in the player panel now uses a cleaned up and better
+ organized UI.
+2023-04-29:
+ AzlanonPC:
+ - rscadd: new sprites for telemetry designator, cargo drones, and cargo drone pads.
+ - rscadd: telemetry designators are now small items.
+2023-04-30:
+ Jux:
+ - tweak: The colours of the new chest-rigs have been changed to use an existing
+ palette.
+ - rscdel: Removed big waist packs.
+ - tweak: Surgery kits are now the same weight class as toolboxes.
+ Spookerton:
+ - tweak: Wallets can't hold syringes.
+2023-05-01:
+ Spookerton:
+ - bugfix: The riot shotgun has a delay again.
+ - tweak: You can pick grey, black, and brown messenger bags in loadout.
+2023-05-02:
+ Jux:
+ - bugfix: TTV bombs now have a UI button to activate attached devices directly.
+ While this is nonfunctional for signalers, it allows proximity sensors and timers
+ to work.
+ Mucker:
+ - admin: Added the 'Toggle Gamemode Requirements Check' verb to the Server tab,
+ which when turned on in the lobby will allow any game mode to start even if
+ player/role requirements aren't met.
+ SierraKomodo:
+ - bugfix: The Meat Station mines are no longer detonated during map initialization.
+ - bugfix: Targetless `do_after()` calls, such as breaking handcuffs, no longer instantly
+ succeed.
+ - bugfix: Light replacers no longer replace trash bags or become stuck in janitorial
+ carts.
+ - bugfix: Fixes void suit helmet cameras and integrated circuit cameras being unselectable
+ in the camera monitor.
+ - tweak: The email received notification in chat now only displays the email subject,
+ and no longer includes the full email message.
+ - bugfix: Robot parts now probably attach and install into robots when used.
+ - bugfix: Telecomms now has a cold air alarm to keep the servers cool.
+ - bugfix: The geosample scanner should now probably show material information.
+ Spookerton:
+ - tweak: Added loadout prescription material goggles.
+ - tweak: The RCD crossbow no longer uses a missed punch as its firing sound.
+2023-05-03:
+ SierraKomodo:
+ - bugfix: 'The following tool interactions have been fixed and should properly work
+ after their pauses:'
+ - bugfix: Interaction checks that needed the items to be adjacent after a timer
+ or input now properly check for adjacency.
+2023-05-04:
+ Jux:
+ - tweak: Full-screen hud overlays are half as intense. This is mesons, science huds,
+ NVGs and thermals.
+2023-05-05:
+ SierraKomodo:
+ - bugfix: Fixes a bug that caused preset emotes (I.e., `/stare`) targeting objects
+ to not work.
+ - bugfix: Fixes disposal chute constructs being unanchorable and unrotatable.
+ - bugfix: Fixes disposal pipes and constructs being anchorable on invalid turfs.
+ - bugfix: Fix disposal chutes not remembering direction when built.
+2023-05-06:
+ Spookerton:
+ - tweak: Removed the Psionic Counselor alt title.
+ - tweak: Added no-surgery skill limits to Counselors.
+2023-05-07:
+ ? ''
+ : - tweak: The janitor alt-title from science has been moved to the janitor role.
+ - tweak: Test Assistants no longer spawn with a uniform, this uniform has not
+ been removed and is still accessible.
+ Cronac:
+ - bugfix: Fixes sprite overlays breaking when Unathi enter a prone position.
+ Jux:
+ - tweak: Carbine rifles are now the same w_class as bullpups.
+ Ryan180602:
+ - tweak: Add EC variant of messenger, backpack, satchel.
+ - tweak: Remove nt pack; make virology packs default for science.
+ - tweak: Make a genbelt variant with science stuff for science lockers and trims
+ some extra stuff in science lockers.
+ - tweak: Makes science lockers start with a EC messenger. It's a EC ship after all.
+ SierraKomodo:
+ - bugfix: Integrated and helmet cameras no longer break when changing areas.
+ - admin: Made some minor tweaks to the Check Connections and Check Bans panel UI.
+2023-05-08:
+ Ryan062:
+ - tweak: Fixes new EC research bag sprites
+ Ryan180602:
+ - tweak: Falling due to gravity change drops you for less time.
+ - tweak: Now only sprinting from nograv to grav tile drops you.
+ Spookerton, Ryan180602:
+ - tweak: Unathi can no longer toggle heal.
+ - tweak: Unathi heal is always on.
+ - tweak: Unathi heal is lesser when suppressed.
+2023-05-09:
+ SierraKomodo:
+ - tweak: A majority of tool interactions now, by default, require the tool to remain
+ in your active hand, both you and the item (If not in hands) to remain next
+ to the target, and you to be able to physically interact with the target to
+ complete if there is a timer, prompt, or any other pause in the process.
+ - bugfix: Brick stacks are now bricks instead of appearing as sheets.
+ - tweak: Stacks of reinforced materials now use the reinforced material's color
+ in the visible lattice work.
+2023-05-10:
+ Fenodyree:
+ - tweak: Renames cheval-de-frise to spiked barricade and changes it's examine text
+ to indicate walking into it hurts.
+ - tweak: Updates cheval-de-frise sprite.
+ Ryan180602:
+ - tweak: Mechs will be hit more often now by gunfire (by a quarter).
+2023-05-14:
+ Spookerton:
+ - rscdel: removed all integrated circuit things that can touch reagents.
+ SuckerPunchSyd:
+ - rscadd: 'adds following animals to gold slime phoron reactions: Crabs, Opossums,
+ Th''ooms and Snakes.'
+2023-05-15:
+ SierraKomodo:
+ - bugfix: Fixes a bug where PDAs that had IDs in them couldn't scan rescue or stasis
+ bags,
+2023-05-16:
+ ? ''
+ : - bugfix: Fixes the URL's for the new wiki on the SolGov law book, UCMJ book and
+ SOP book.
+ - tweak: Cash can no longer be used to fly around in EVA, even if you have a lot
+ of it.
+ SierraKomodo:
+ - bugfix: Rods now use the correct sprite.
+ - bugfix: Magnetic weapons no longer delete existing ammunition if attempting to
+ load a stack while loaded with a non-stack projectile.
+ - bugfix: Magnetic weapons no longer allow you to increase the size of a loaded
+ stack by loading it with an entirely different type of stack.
+ - rscadd: Various item interactions with various guns now display helpful feedback
+ messages on failure.
+ - rscadd: Various item interactions with various guns now display visible messages
+ to those around you.
+ - rscadd: Loading weapons that hold more than 1 projectile at a time will now tell
+ you how many projectiles are loaded out of the maximum amount. This does not
+ occur for magazine fed weapons.
+ - rscadd: You can now scan IDs on secure weapons without needing to remove said
+ IDs from your wallet, PDA, etc, instead scanning the container itself directly
+ on the gun.
+ - bugfix: Clicking atoms while holding any combination of alt, shift, or control,
+ or middle-clicking them should no longer perform multiple actions (I.e., rotating
+ an object and toggling the turf tab).
+2023-05-17:
+ Spookerton:
+ - bugfix: secbots don't attack people with html escapable symbols in their names.
+ - bugfix: You can't install an infinite number of augments in robotic limbs anymore.
+ - bugfix: Fixed some species being selectable in change appearance menus.
+ - tweak: Spike throwers can't be fumbled.
+ - tweak: Records aren't deleted when people exit a round through cryopods.
+ - tweak: If wearing an ID card while being stored by a cryopod, the record associated
+ with the name on that ID has its status updated to "Stored".
+2023-05-20:
+ Jux:
+ - rscdel: Removes pets from loadouts.
+ Sbotkin:
+ - maptweak: CSO office is less green and more purple now.
+ - maptweak: Research doors are now of correct color.
+ SierraKomodo:
+ - tweak: Full-size windows can be alt-clicked to show the turf tab again.
+ Spookerton:
+ - bugfix: Body scanners etc produce a correct burn level description.
+2023-05-22:
+ JoeyNosegay:
+ - rscadd: adds 'dress heels' to the uniform vendor as service and dress options
+ Ryan180602:
+ - bugfix: Unathis regenerate again.
+2023-05-24:
+ BurpleBineapple:
+ - tweak: Damage and Surgery steps show through underclothes. Armor/Voidsuits is
+ still over this layer.
+ - bugfix: Fixes uranium being spelled "nuranium" as a stack. Caused incorrect displays
+ on the merchant console, etc.
+ Mucker:
+ - bugfix: Fixed psionics not being toggleable.
+ Ryan180602:
+ - tweak: OFDs can now take coordinates.
+ - tweak: Accuracy depending on calibration.
+ - tweak: People on the receiving end will feel the effects of an explosion.
+2023-05-25:
+ Juxtaposed, Ryan180602:
+ - tweak: Add wirehack mechanics to disarm TTVs.
+ - tweak: TTVs need to be armed before they can be activated via the interface.
+ PurplePineapple:
+ - tweak: Damage layers such as wounds, gauze, and splints are applied above your
+ clothes. Armor and voidsuits still go over them.
+ Ryan180602:
+ - maptweak: Remove external bridge cameras. (B-Deck Bridge)
+ - maptweak: Remap chemistry lab to be a bit bigger, while not taking up more space
+ than normal in the medbay. (D-1 Med)
+ - maptweak: Remove `NO IDLE` sign between cryo-lab, replace with bodyscan screen.
+ (D-1 Med)
+ - maptweak: Replace BC's crowbar with prybar. (D-1 Sec)
+ - maptweak: Replace FT's cigarette pack spawn with a random cigarette pack spawn.
+ (D-1 Sec)
+ - maptweak: Add General Intercom to D1 Briefing Room. (D-1 Mid)
+ - maptweak: Shift scrubber spawn a bit to not be inconvenient on ladders. (D-3 Behind
+ SecCP)
+ - maptweak: Remove glass panes between consoles. (D-3 Rec Room)
+ - maptweak: Make PF office cameras belong to Exploration network. (D-4 PF Office)
+ - maptweak: Replace Atmos Storage waste canisters with oxygen canisters. (D-5 Hangar)
+ limelier:
+ - bugfix: Fix name of research bowman headset
+ - rscdel: Clean up researcher's locker by removing extra tapes and the oxygen tank
+2023-05-26:
+ Ryan180602:
+ - maptweak: Reverts chemlab remap.
+ SierraKomodo:
+ - bugfix: Incorrect physical interaction checks have been removed from general interactions;
+ Various things that weren't working when clicked on should be working again
+ now.
+2023-05-27:
+ Bam4000:
+ - tweak: Changes emotes for the virologists like "he shakes his hand above his head"
+ and such.
+ BoNT:
+ - imageadd: Updated sprites for EC Science bags
+ - rscadd: Slime surgery with laser scalpels & incision management systems will create
+ both incisions in a single step.
+ Mucker:
+ - admin: Tickets now timeout after (roughly) 30 minutes of inactivity.
+ Ryan180602:
+ - tweak: OFD will not fire accurately (according to calibration).
+ SierraKomodo:
+ - balance: Skill level now plays a stronger role in the time it takes to perform
+ skilled actions. Lower skills will have much longer timers, while higher skills
+ have shorter timers.
+ - bugfix: Reinforced walls and tables with a combination of two different materials
+ now use the current reinforced material icon.
+ - rscadd: Alien alloy now has random icons for sheet stacks, as well as constructed
+ tables, doors, and walls.
+ Spookerton:
+ - rscadd: You can carve deer skulls into fashionable headwear with sharp objects.
+ With the addition of a pen light, the eyes light up! They hide identity, provide
+ some very minor armor, and are good protection against electrocution via the
+ head.
+ rootoo807:
+ - rscadd: Adds toe-less foam clogs and toe-less flip flobsters. Both are available
+ in loadout under the Xenowear section.
+ - rscadd: Using wirecutters or a scalpel on foam clogs/flip flobsters now chops
+ the toes off. Three cheers for OSHA violations.
+ - tweak: Updates Unathi onmob sprites (sec aviators, EC ranks) to match new sprites.
+2023-05-28:
+ Mucker:
+ - rscadd: Ships that scan the Torch will now show if the ship is currently dealing
+ with a spider infestation or a Blob.
+ - rscadd: Exoplanets undergoing the 'Exoplanet Awakening' will now display as such
+ on scans.
+2023-05-29:
+ Alex6511:
+ - maptweak: Exoplanet types have been overhauled, planets atmospheres will now be
+ more consistent
+ - maptweak: Artic Planetoid has been renamed Artic dwarf planet, because a planetoid
+ is not a planet type recognized by the IAU
+ ImJustKisik:
+ - rscadd: Bullets now leave holes.
+ - rscadd: Ricochet sparkles.
+ - rscadd: Shotgun blast knocks back whoever is hit.
+ SierraKomodo:
+ - admin: Evacuation subsystem now displays stat information in the MC tab, including
+ current stage, time to next stage, time evac was called, and the point of no
+ return status.
+ - bugfix: Climbing tables no longer displays a message about buckling.
+2023-05-30:
+ Gy1ta23:
+ - tweak: security reports don't call you an officer anymore
+2023-05-31:
+ Mucker:
+ - rscdel: Removed shotgun knockback, whoops.
+2023-06-01:
+ BoNT:
+ - bugfix: Laser scalpels and IMS will check if there is already an existing incision,
+ like regular scalpels.
+ BurpleBineapple:
+ - bugfix: Scoped guns now no longer throw your camera across the map with recoil
+ and will properly reset to initial values.
+ - tweak: The Panther has 2 tiles extra of scoped accuracy to be more usable at below
+ Master WE.
+ Ryan180602:
+ - tweak: Stasis cages are now constructable/deconstructable.
+ - tweak: Stasis cages can now be hacked to bolt the lid, release, or allow people
+ in it.
+ - tweak: Stasis cages can be damaged, emp'd and broken completely.
+ - tweak: Stasis cages can be upgraded with better capacitor/cell.
+2023-06-03:
+ ? ''
+ : - bugfix: Emagging a vending machine unlocks hidden items again.
+ - bugfix: Fixes seed vendor disappearing for a second when vending items.
+2023-06-04:
+ Karl Johansson:
+ - rscadd: Adds the hand drill, a portable hand drill that can swap between a bolt
+ and screw head. Does (most) things twice as fast! Sprites done by Azlan.
+ - rscadd: Adds the hydraulic prying tool, a tool that can swap between prying and
+ cutting heads. Does (most) things twice as fast! Sprites also done by Azlan.
+ - tweak: Redoes tool logic to allow for tools that perform actions faster or slower
+ than the baseline.
+ - maptweak: Changes the toolbelt in the Chief Engineer's locker to include a hand
+ drill and hydraulic prying tool.
+ LordNest:
+ - rscadd: Cleanbots and Farmbots use sprites ported from [source]
+ Ryan180602:
+ - tweak: Holopads can now have PDAs registered to be notified on call.
+ Ryan180602, Juxtaposed:
+ - rscadd: Allow artefacts to be exported. Worth is calculated based on effects/trigger.
+ Needs the scan-sheet on the anomaly container.
+ - rscadd: Allow simplemobs (caged animals) to be exported. Worth is based on passive/retaliate/hostile.
+ - bugfix: Stasis cages now properly delete nets when containing animals.
+ - bugfix: Stasis cages cells and power usage are fixed.
+2023-06-07:
+ SuhEugene:
+ - bugfix: Transport drone pad scanners upgrade now works
+2023-06-09:
+ Gy1ta23:
+ - tweak: latex gloves and nitrile gloves are now separate
+ emmanuelbassil:
+ - bugfix: You will now be able to dismantle unreinforced windows that spawn in with
+ the map
+ - bugfix: Leather sheets and material hydrogen sheets will no longer disappear when
+ in stacks greater than 2
+ - bugfix: Pianos and the minimoog can be unwrenched and wrenched at your heart's
+ content again.
+ - bugfix: Can now properly disassemble/reassemble washing machines.
+2023-06-11:
+ Bam40002:
+ - rscadd: Added the abandoned hotel away site.
+ Mucker:
+ - rscadd: Adds the 'BSD Instability' Event, which causes spooky anomalies while
+ its active, can trigger in the 'moderate' and 'major' categories.
+ - tweak: Fixed a grammatical thing in the bluespace figment description.
+ - tweak: Most simple mobs have had their movement speed increased to match current
+ player speeds, so you can no longer out-walk them.
+ - tweak: Phoron spiders will explode in 1-3 seconds instead of 3-5.
+ Ryan180602:
+ - tweak: Adds tesla-link to stasis cages.
+ SomeAngryMiner:
+ - rscadd: 'Welders sounds have been re-added, for actions of: Turning on, Turning
+ off, removing tank and adding tank.'
+ emmanuelbassil:
+ - tweak: Vending machines now have the ability to spawn 'rare' items based on chance.
+ Functionality to randomize amount of stuff in the machine as well.
+ - tweak: Cola vending machine now has a random chance to spawn with two rare drinks.
+2023-06-13:
+ Bam4000:
+ - bugfix: Fixed the landing markers for the Abandoned Hotel Away Site.
+ - bugfix: Fixed the spawning of a parent circuit board (that was a dumb mistake)
+ - tweak: Tweaked the abandoned hotel a bit
+ Mucker:
+ - tweak: Closets can no longer trap large mobs in them.
+2023-06-14:
+ Ryan180602:
+ - tweak: Makes sure ckey checks are done when shuttle pod taking off.
+2023-06-16:
+ Gy1ta23:
+ - tweak: screwdrivers can now be hidden in boots like knives
+ - tweak: gave the holdout revolver an inhand sprite, copying the holdout pistol
+ Mucker:
+ - bugfix: Fixed spacedrift affecting objects that were currently being pulled.
+ PapiSmirnov:
+ - rscadd: Adds the fake corpse cube antag item.
+ Ryan180602:
+ - rscadd: Add a cosmetic auto-surgery kit to uplink for 12TC. So now, you can be
+ a different person (fully so with a syndi-ID and arrivals device).
+ Spookerton:
+ - tweak: Rigs get some storage on the suit part. Most get 2x normal items of freeform,
+ lights get 3x small, and zeros get nothing. It can be dumped out via the panel->wrench
+ pathway when not worn.
+2023-06-20:
+ Mucker:
+ - bugfix: Ladders should no longer be bugged after the BSD event runs.
+ - tweak: BSD event lasts longer.
+ - tweak: Bluespace turfs spawned by the BSD event will teleport mobs to a random
+ place if stepped on (sometimes).
+ - tweak: Mobs affected by the 'pulse' from the BSD event will now have their position
+ swapped with other mobs, instead of teleported randomly.
+ - tweak: Bluespace figments do 5 less damage on hit.
+2023-06-21:
+ SomeAngryMiner:
+ - rscadd: Hullbreaker monitors and fleet androids can be found, now.
+ - rscadd: Frontier alliance and clothing medals were added.
+2023-06-22:
+ Jux:
+ - maptweak: Most of the Torch's cameras have been removed. Areas exempt are generally
+ hallways, main ship entrance points, shuttle cockpits, and high traffic or high
+ security areas.
+ - maptweak: Some cameras have had their positions adjusted.
+ LordNest:
+ - maptweak: Adds new ruins for exoplanets and damaged shuttle,
+ Mucker:
+ - bugfix: Planet atmospheric generation has been reverted to its previous state
+ due to various issues.
+ Ryan180602:
+ - maptweak: Swaps BSD with AI Upload, centralises the BSD, gives it a bit more security,
+ but ways of access.
+ - maptweak: D2 Shield-Room SMES is now on the main-grid. New AI Upload room has
+ its own SMES (on the main grid).
+ emmanuelbassil:
+ - tweak: The amount of soda in the cola machine is now randomly generated. This
+ is an experiment, should not cause any shortages.
+ - tweak: Vending machines now display rare items in gold, hidden items (contraband
+ wire/emag) in orange.
+ - tweak: Emagging a vendor now makes all items free.
+ - bugfix: Wooden crates now properly spawn wood instead of metal when crowbar'd
+ open.
+ emmanuelbassil,Azlan:
+ - rscadd: Adds matchbook, a smaller version of matchboxes with 3 matches that fit
+ in your ear slot, pockets, and wallets.
+ - tweak: Matchbook available in same vending machines where matchboxes are and loadout.
+ - tweak: New sprites for matchboxes and the matchbook, courtesy of Azlan
+ - tweak: Matchbox sprites were supposed to open/close and change as they are emptied,
+ this now works.
+ jux:
+ - tweak: Un-restricts bananas in loadout.
+2023-06-23:
+ Jux:
+ - bugfix: Helmet cams can be observed again.
+ - tweak: Helmet cams require SolGov crew access instead of EVA access.
+ Mucker:
+ - admin: Added a button to the player panel called 'Equip Loadout' that equips the
+ player's currently saved loadout.
+ SierraKomodo:
+ - bugfix: Fixes radio signal and distress signal scans having line breaks after
+ every character.
+ jux:
+ - tweak: Secure tech storage now requires Tech Storage + Senior Engi Access, instead
+ of Tech + Bridge access
+ - tweak: Senior engineers now always have trained IT and atmospherics, but lose
+ 4 skill points
+2023-06-24:
+ Al-1ce:
+ - tweak: Adds pajamas to the loadout.
+2023-06-25:
+ Al-1ce:
+ - spellcheck: Adds a missing definite article to the tranship sensor scan.
+2023-06-26:
+ Bam4000:
+ - tweak: Changed the red cross status display into a blue cross.
+2023-06-27:
+ Jux:
+ - rscadd: There's now colour select bandanas available using a new sprite. Replaces
+ the single-colour bandanas, but not the special ones.
+ - bugfix: The familiar's headband is no longer a bandana available in loadout.
+ - rscdel: Removes the NJP form.
+ rootoo807:
+ - maptweak: Moves the thermal regulator 1 tile closer to the pharmacy chair
+2023-06-30:
+ CrimsonShrike:
+ - tweak: Ipatch now uses new emissive system and will glow in dark when on.
+ - bugfix: Fixes mobs occluding all emissives, including their own
+ Jux:
+ - tweak: The colour-select bandana now has shading on the front sprite.
+ Karl Johansson:
+ - tweak: Buckshot accuracy is now calculated once per shot, rather than individually
+ for each pellet.
+ - tweak: Shotguns are generally more accurate at range (to account for accuracy
+ only being calculated once)
+ - bugfix: Fixes pump shotgun sprites inaccurately displaying the condition of the
+ shotgun
+ - rscadd: Adds wound and bandage overlays to the health display on the HUD
+ LordNest:
+ - rscadd: Added tooltips for overmap
+ Mucker:
+ - bugfix: Spiderlings should no longer grow while not on a turf (no more surprise
+ spiders-armies popping out of cocoons)
+ - bugfix: Fixed some bluespace turfs not reverting after the BSD event runs.
+ - tweak: The BSD event now only runs for about 18 minutes.
+ - tweak: The BSD event should happen less often now (it's not THAT broken).
+ SierraKomodo:
+ - bugfix: Fixes fire alarms becoming stuck in alarm monitoring programs.
+ - maptweak: Added a manual valve between the supply and waste pipes in the xenoflora
+ lab.
+ rootoo807:
+ - maptweak: Remaps the galley. Combines the bar and the kitchen. Adds a galley storage
+ space on D4 for fancy O-mess booze and cigars.
+2023-07-03:
+ Fre3bie:
+ - tweak: Loadout items favour being equipped/attached instead of going into containers.
+ Rootoo807:
+ - bugfix: Jukebox no longer clips through windows.
+ Ryan180602:
+ - bugfix: Fixes FA Vacsuit sprite path.
+ SierraKomodo:
+ - tweak: Hailer message length limit reduce from 1,024 to 64.
+ - bugfix: Assembly holders are now properly constructed and don't duplicate.
+ Vex8tion:
+ - tweak: Removed arithrazine's ability to heal internal organs
+ - tweak: Peridaxon's metabolism rate is now halved, and it now heals for slightly
+ more per tick
+2023-07-05:
+ SuhEugene:
+ - bugfix: Fixed overmap placeholder objects visibility and big objects positioning.
+ - bugfix: Fixed overmap animation of distant objects.
+ - bugfix: Sensors mute now mutes sensors.
+ - balance: Sensor scan progress formula has changed completely to be less vessel-mass-based.
+ Sensors scan everything a way more faster than before.
+ - maptweak: Placed a little better sensors on Torch.
+ - rscadd: Scan progress added to UI instead of chat spamming.
+ - rscadd: Sensors have a memory and will remember anyone who goes out of range,
+ however every turn off progress goes down by 18 percents.
+ - tweak: Sensors view is round now.
+ - tweak: Slightly improved sensor scan display look.
+ - tweak: Slavage Gantry now twice as heavy.
+ - tweak: Made overmap darker, much better on the eyes.
+2023-07-06:
+ Alex6511:
+ - tweak: Torch side navigation light colors are laterally accurate. Dorsal and ventral
+ colors also included, should someone do something absurd and vertical!
+ Dr. Farson:
+ - tweak: Split armor tags into separate loadout options - flags, blood type, and
+ corporate insignia.
+ - rscadd: Added a fleet flag armor tag.
+ Fre3bie:
+ - tweak: Corpse Cube now copies the flavour text with the DNA sampler.
+ Ryan180602:
+ - tweak: The Torch will be removed from the overmap during jump.
+ - tweak: If the drive is missing/destroyed, there will be no jump, round will simply
+ end after the same amount of time.
+2023-07-07:
+ Jux:
+ - tweak: The adminpm noise is now plays at a lower volume by default, and also plays
+ when someone in your line of sight is paralyzed.
+ - tweak: The staffwarn noise is significantly quieter, and staffwarns only notify
+ staff when a player joins the round.
+ Mucker:
+ - rscadd: The 'Remove Accessory' Verb now uses a radial menu.
+2023-07-08:
+ LordNest:
+ - rscadd: Ported input subsystem and keybinding from Nebula. Added Gliding for mob
+ movement.
+2023-07-09:
+ Mucker:
+ - bugfix: Fixed radial menus breaking with too many options.
+ - bugfix: Fixed being able to remove non-removable accessories.
+2023-07-10:
+ Mucker:
+ - bugfix: Fixed the boots loadout menu select, jungle boots have been readded as
+ 'tan' SCGA boots.
+ - rscadd: Added rogue construction drones to the rogue maintenance drone event (moderate
+ - major only).
+ - bugfix: RUST observation shutters work again.
+ PurplePineapple, Escalation1984 Developers:
+ - rscadd: Adds autoclick functionality to clients.
+ - tweak: can_autofire on guns now uses the autoclick to allow for more natural automatic
+ fire by holding the mouse down in one place.
+ - rscdel: Click-drag automatic fire has been removed now that it has been replaced
+ with the autoclick.
+ SierraKomodo:
+ - bugfix: The lift now declares Telecommunications on Deck 4 instead of Deck 3.
+ - bugfix: Fixes certain floor decals disappearing when a turf initializes or updates
+ its icons.
+ TheNightingale:
+ - rscadd: Exosuit medigel dispensers now come with a built-in health analyzer alternate
+ mode.
+ - bugfix: Exosuits now require harm intent to slam, preventing unintentional slams
+ when changing equipment.
+ - bugfix: Exosuit cameras now actually work.
+ - bugfix: Exosuit sleepers no longer act as power sinks when empty.
+ - bugfix: Exosuit floodlights are no longer required in order to climb ladders.
+ - tweak: Suit sensor consoles now beep when someone's critical.
+ - tweak: Chem dispensers and chemmasters now accept IV bags.
+2023-07-11:
+ Azlan, Jux:
+ - rscadd: Adds a select fire, drum fed shotgun to gun spawners and merc uplinks,
+ sprited by Azlan!
+ - rscadd: Adds shotgun drums to lathes and uplinks, sprited by Azlan!
+ - tweak: Individual shotgun shells are now a little less than half their old material
+ cost.
+ - tweak: The random projectile weapon spawner now favors old-fashioned items less.
+ Jux:
+ - tweak: The SEV Torch now issues ballistic weapons instead of laser weapons.
+ Mucker:
+ - rscadd: Spider nurses will now lay eggs if left undisturbed for too long, making
+ spiders a persistent threat if not totally eradicated.
+ - admin: Moved the 'path' variable type in VV menus to 'type'.
+ SierraKomodo:
+ - bugfix: Decals on turfs now disappear when their tiles are removed.
+ TheNightingale:
+ - maptweak: Adds rainbow table flags to the Torch.
+2023-07-12:
+ Jux:
+ - maptweak: Secarm has been divided in two, with more advanced ballistic weaponry,
+ the ammo for said weapons and lethal handgun ammo requiring COS access.
+ - tweak: Secarm now has ion pistols instead of ion rifles.
+ - tweak: The BC and COS no longer spawn with lethal ammo in their lockers, and FTs
+ now have an m19.
+ Mucker:
+ - bugfix: Giant spider counts are now capped at 30 globally (to start) so they don't
+ consume everything everywhere.
+ - bugfix: Fixed the Paralyze verb not working.
+2023-07-14:
+ Mucker:
+ - bugfix: Surgery defaults to the only available option again if only one is available.
+ Ryan180602:
+ - tweak: Reverts accuracy changes to shotguns.
+ Ryan180602, DrFarson:
+ - rscadd: Adds colour coded stripes for rubber/practice magazines.
+ - tweak: Fix inconsistences with bullet sprites.
+ SierraKomodo:
+ - rscadd: Action buttons now have tooltips denoting the target object and the action.
+ Spookerton:
+ - tweak: Replaced 1-LT stun with 1-LT confusion when your aorta spits at the sky.
+ jux:
+ - tweak: 10mm rubber bullets do 15 brute, up from 5, and 15 agony, down from 30.
+ Holdout rubber bullets do 10 brute, up from 5, and 10 agony, down from 20. Shotgun
+ beanbags do 20 brute, down from 25, and 30 agony, down from 60.
+ - tweak: Projectiles that deal agony reduce agony dealt by the armor blocking it.
+2023-07-15:
+ Jux:
+ - tweak: No more autoshotgun in uplinks.
+ Ryan180602:
+ - tweak: Makes robot surgery less successful for non-roboticists.
+ - tweak: Makes resin apply brittle to prosthetics (and damage if used further).
+ All depending on skill and chance.
+ - tweak: Roboticists now get Experienced CD by default.
+ - tweak: Spiders bite less hard.
+ - tweak: Spiders have less health.
+ - tweak: Phoron spider bites even less, but can blow up quicker.
+ SuhEugene:
+ - tweak: Point To arrow is animated now and visible only if the pointing player
+ is visible.
+ - bugfix: Tooltips are no longer displayed on the radar waves.
+2023-07-17:
+ Fre3bie:
+ - tweak: Forehead writing is now removed after using the cosmetic surgery kit.
+ TheNightingale:
+ - rscadd: Adds species fall sounds for when you've fallen and can't get up.
+2023-07-18:
+ TheNightingale:
+ - rscadd: Vitals monitors can now be upgraded with scanning modules to show broken
+ bones and arterial bleeding.
+ - tweak: Vitals monitors now show BP, BO and temperature by default.
+ emmanuelbassil:
+ - tweak: Can now use grabs to put mobs in sleepers. This is already existing behavior
+ for cryopods and bodyscanners.
+ - tweak: Can no longer put a mob in a sleeper/cryotube/cryopod/body scanners if
+ they are being grabbed by someone other than you.
+ - tweak: Added eject verb to sleeper. Remember, you can use Alt+Click to eject people
+ from sleepers.
+ - bugfix: Fixed bug where a mob could not be buckled if mob was grabbing itself.
+ - bugfix: Grabs and pulls now properly release when you put a mob in a sleeper,
+ cryopod, body scanner, or cryotube
+2023-08-28:
+ Fre3bie, Spookerton:
+ - tweak: Wheelchair now moves at walk speed instead of sprint speed.
+ - tweak: You need your hands free to drive your own wheelchair.
+ - tweak: You need gravity to move a wheelchair as the person in it.
+ Jux:
+ - rscadd: Individual mobs can now be made to waddle.
+ - tweak: Energy machetes now do more damage.
+ - tweak: Antagonist or Emagged borgs now have multiple new tools, and non-antagonist
+ borgs lose their flashes.
+ - bugfix: pAIs can use their radios.
+ - bugfix: Handmade assemblies are now functional.
+ - admin: The automatic connections checker no longer fires on staff and gives a
+ number for matches instead of listing every ckey and ban match in chat.
+ - rscadd: Adds xenofauna carbines, which are effective against simplemobs but not
+ humanoids. Sec-Arm gets two by default, and more can be ordered. They can be
+ emagged, which changes their beam to that of a compact smartgun lethal beam.
+ - tweak: Supply packs for ammo have had their costs reduced and delivered items
+ increased to make ordering ammo a competitive option, but they also require
+ appropriate access to open beyond just normal security access. Sec SMGs can
+ now be ordered on code blue.
+ - tweak: Meatstation guards now use already emagged xenofauna carbines, increasing
+ their lethality *significantly*. Their potential spawns have been reduced to
+ account for this.
+ - bugfix: Airlocks don't crush mobs for twice the damage anymore
+ - tweak: Autoshotgun no longer available in gun spawns.
+ - tweak: Exoplanet atmospheres are more varied now, with a wider, but more grounded
+ temperature range and less non-terraformed planets with ideal atmospheres.
+ - tweak: All exoplanet types can now generate with all templates - volcanic cities,
+ for example.
+ - tweak: Thermal energy in gas mixtures is gained significantly slower the hotter
+ the mixture is.
+ - tweak: Exoplanet ruins with player_cost (colony, survival pod) now use that when
+ being generated.
+ - tweak: The oldlabs prevent each other from spawning.
+ - bugfix: Rogue drones, fleet tracker bots and space syndies can now move in space.
+ - tweak: Fleet robots now miss more often, fire less rapidly, won't hesitate to
+ break things and won't run away from targets unless they're a bit closer.
+ - tweak: Hullbreakers are now less afraid to break hulls.
+ - bugfix: The prison break event now bolts open doors like it used to.
+ - tweak: The prison break event will now always announce itself before it ends,
+ and can target both the tac-arm and xenobio cells.
+ - tweak: Airlocks no longer beep impatiently when they cannot close.
+ Jux, Ryan180602:
+ - maptweak: Remaps security armoury.
+ - maptweak: Adds stunshells to sec-armoury.
+ - maptweak: E-arm now contains 4 light bullpups and 2 UNSECURE laser carbines instead
+ of SMGs and Shotguns.
+ - tweak: Various ballistics balance adjustments, see github for details.
+ - tweak: All Torch handguns, except those in E-Arm, are now the m19. Hopefully eliminates
+ magazine confusion.
+ LordNest:
+ - tweak: "\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u043B \u043D\u0435\u0434\
+ \u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 \u043C\u043E\u0434\u044B"
+ - bugfix: "\u0423\u0431\u0440\u0430\u043B \u043F\u0435\u0440\u0435\u0443\u0441\u043B\
+ \u043E\u0436\u043D\u0451\u043D\u043D\u0443\u044E \u0441\u0438\u0441\u0442\u0435\
+ \u043C\u0443 \u043F\u043E\u043A\u0440\u0430\u0441\u043A\u0438 \u0434\u043B\u044F\
+ \ \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043A\u043E\u043D\
+ \u0444\u043B\u0438\u043A\u0442\u043E\u0432. \u041F\u043E\u043F\u0440\u0430\u0432\
+ \u0438\u043B \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0443\
+ \u0442\u0438 \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u0432. \u0412\u0435\u0440\
+ \u043D\u0443\u043B \u0433\u0440\u0430\u0432\u0433\u0435\u043D."
+ Merlin1230:
+ - bugfix: Fixes intercoms so clicking on them with an empty hand when the wire panel
+ is open will now allow to actually mess with the wires
+ - bugfix: stops hacker hardsuits (and maybe some others) from being unable to remove
+ anything from them with a wrench
+ - tweak: allows ghosts to perceive time in the status panel
+ Mucker:
+ - rscadd: Added an alert when trying to late-join as a role not set in your Occupation
+ preferences.
+ - bugfix: Fixed the fall-down sound constantly triggering in some cases.
+ - tweak: The email program no longer closes completely when exiting so notifications
+ will be more often heard.
+ - bugfix: Fixed shortwaves having no 'power' button
+ - bugfix: Fixed automatic radio announcements (arrivals, SM warnings, etc) not working.
+ - bugfix: Observers can examine things again.
+ - tweak: Changeling now requires 5 people to be readied to roll.
+ Petro1Ne1Koder:
+ - bugfix: "\u0418\u0437 \u0448\u0435\u0439\u043A\u0435\u0440\u0430 \u043D\u0435\u043B\
+ \u044C\u0437\u044F \u0432\u044B\u043B\u0438\u0442\u044C \u0441\u043E\u0434\u0435\
+ \u0440\u0436\u0438\u043C\u0430\u0435 \u043D\u0430 \u043F\u043E\u043B"
+ - bugfix: "Face direction \u0432\u043E \u0432\u043A\u043B\u0430\u0434\u043A\u0435\
+ \ IC \u043D\u0435 \u0437\u0430\u043C\u0435\u0434\u043B\u044F\u0435\u0442"
+ Ryan180602:
+ - tweak: E-Shields will now be damaged by EMP if active during blast, and be unable
+ to be used, temporarily.
+ - tweak: E-Swords/E-Melee weapons will now be damaged by EMP if active during blast,
+ and be unable to be used temporarily.
+ - tweak: E-Shields are now buyable for 24 TC on uplink.
+ - tweak: Personal Belt Shield is now buyable for 40 TC.
+ - maptweak: E-Shields aren't available freely on the merc-base anymore.
+ - tweak: Sensor panel has a mute button to mute ambient `woomb` sound.
+ - bugfix: Placeholder icons show up on scan panel.
+ - tweak: Placeholder buildmode has a scan data option instead of sensor now.
+ - rscadd: Add the Dingo, Mantaray and Wombat.
+ - tweak: Watches will now tell the time when examined.
+ - tweak: You can examine your uniform with a watch, and check the watch.
+ - tweak: Local Time/Date in Status does not show unless a watch is attached to your
+ uniform.
+ - tweak: Stasis cage now uses equipment channel (can power from APCs).
+ - bugfix: Sensor mute button works again.
+ - tweak: People can now be thrown into railings (and thus over them).
+ - rscdel: Autoshotgun mags are no longer lathe printable.
+ - bugfix: Magazine icons depict ammo count accurately again.
+ Sbotkin:
+ - tweak: Added the ability to write on any of the sheets on the clipboard.
+ - bugfix: Fixed red retractable pens not staying red.
+ - tweak: The CSO now has an AI module in their hardsuit and can carry drills in
+ the suit slot.
+ - maptweak: Removed duplicate items from the CSO office(s).
+ - tweak: Added SFX to the telescopic cane.
+ SierraKomodo:
+ - bugfix: Fixed melee attacks ignoring selected target zones.
+ - tweak: The crew manifest displayed to observers and ghosts now uses the IC manifest,
+ reflecting the state of the `Status` field as displayed to people in game.
+ - tweak: The OOC manifest (The one displayed in the player lobby) now hides the
+ Activity column, masking the life and activity status of crew entries.
+ - tweak: Crew manifest entries for crew members listed as `Stored` do not appear
+ on the OOC manifest.
+ - tweak: Fireaxe cabinets now use standardized health. Minimum damage threshold
+ is set to 15 (Matching the previous force requirement to break the cabinet),
+ and maximum health is set to 30.
+ - rscadd: Fireaxe cabinets can now be repaired using reinforced glass. A fully broken
+ fireaxe cabinet will still be open and unlocked once repaired. The cost is 1
+ sheet regardless of damage - You're replacing the entire glass pane.
+ - tweak: External bots no longer receive stored characters when requesting the manifest.
+ - bugfix: Clicking adjacent targets with a gun on harm intent now point-blank fires,
+ if applicable, instead of pistol-whipping. Pistol whipping can still occur using
+ disarm intent.
+ - rscadd: You can now interact with held mobs using tools, including stabbing that
+ cat you're holding hostage when security tries to bang-rush you.
+ SingingSpock:
+ - maptweak: Minor D2 wiring fixes
+ - maptweak: Rewired the RUST, making both SMESes useful. Engineers should read the
+ note in the antechamber before setting up.
+ - maptweak: Removed superfluous SMES in the shield bay area and rewired area for
+ better useability
+ SomeAngryMiner:
+ - rscadd: Several sprite errors have been fixed.
+ - rscadd: Surgery steps now have sounds.
+ - rscadd: Airlocks, blinking toys, lockboxes, locators, vox helmets, mining drills,
+ glowshrooms, merc smgs, nuclear reactors, bloodpacks and adherent wirecutters
+ had their sprites fixed.
+ - bugfix: fixes airlock error icons.
+ - bugfix: fixes vine error icons.
+ - bugfix: fixes ore processor console error icons.
+ SomeAngryMiner, l3lb0t:
+ - rscadd: most machines now have emissive lights.
+ - rscadd: most machines also have maintenance panels, if they were missing them.
+ - rscadd: thermal regulators now have overlays when cooling.
+ Spookerton:
+ - bugfix: Fixed pAIs.
+ - tweak: Emergency Management Bureau identifying gear is not available in loadout.
+ - tweak: Mobs and objects glide at the same rate again.
+ SuhEugene:
+ - tweak: "\u0421\u0434\u0435\u043B\u0430\u043B \u0447\u0435\u0439\u043D\u0434\u0436\
+ \u043B\u043E\u0433 \u0432 \u0440\u0430\u0437\u044B \u043A\u0440\u0430\u0441\u0438\
+ \u0432\u0435\u0435 \u0438 \u0443\u0434\u0430\u043B\u0438\u043B \u0438\u0437\
+ \ \u043D\u0435\u0433\u043E Credits."
+ - bugfix: Fixed sensors I've broken.
+ - bugfix: Fixed the sensors not working when the shuttle is docked.
+ - admin: Made object spawn panel prettier.
+ - soundadd: "\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u044B \u0443\
+ \u043D\u0438\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u0437\u0432\u0443\u043A\
+ \u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0421\u044C\u0435\
+ \u0440\u0440\u044B."
+ - imageadd: "\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0438\
+ \u043A\u043E\u043D\u043A\u0430 \u043E\u0432\u0435\u0440\u043C\u0430\u043F\u044B\
+ \ \u0421\u044C\u0435\u0440\u0440\u044B."
+ - bugfix: Fixed nonsensical Torch log in the helm console.
+ - tweak: "CuddleAndTea \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0432 \u043E\
+ \u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043C\u0435\u043C\u043E\u0440\u0438\
+ \u0430\u043B\u0430 \u0421\u044C\u0435\u0440\u0440\u044B."
+ - rscadd: Added Try again button to the legacy chat that appears when goonchat load
+ fails.
+ - tweak: The rest verb changes standing/lying state instantly.
+ - bugfix: "\u041F\u043E\u0447\u0438\u043D\u0435\u043D\u0430 (\u0432\u043E\u0437\u0432\
+ \u0440\u0430\u0449\u0435\u043D\u0430) \u0441\u0438\u0441\u0442\u0435\u043C\u0430\
+ \ \u043C\u043E\u0434\u043E\u0432."
+ - tweak: "\u041A\u0443\u0447\u0430 \u0444\u0430\u0439\u043B\u043E\u0432 \u043F\u0435\
+ \u0440\u0435\u043C\u0435\u0449\u0435\u043D\u043E \u0442\u0443\u0434\u0430-\u0441\
+ \u044E\u0434\u0430, \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u044B \u0447\u0442\
+ \u043E\u0431\u044B \u043A\u043E\u0434 \u0431\u044B\u043B \u0447\u0438\u0449\u0435\
+ ."
+ - bugfix: More sensors consoles no longer show more marks.
+ - bugfix: Sensors reconnect popup no longer stays open on success link.
+ - bugfix: Fixed sensors marks visual positioning.
+ - admin: Added selection of a specific lights fix or destruction zone.
+ - soundadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u043D\u043E\u0432\
+ \u044B\u0439 \u043F\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043D\
+ \u043D\u044B\u0439 \u0437\u0432\u0443\u043A \u043D\u0430 \u0421\u044C\u0435\u0440\
+ \u0440\u0443."
+ - soundadd: "\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u044B \u043C\
+ \u0435\u043B\u043E\u0434\u0438\u0438 \u043B\u043E\u0431\u0431\u0438."
+ - bugfix: Fixed goonchat loading for players re-entering game outside the lobby.
+ drfarson, Pawnty:
+ - rscadd: Added fleet service sweaters to uniform dispensers.
+ emmanuelbassil:
+ - rscadd: Adds challenge coins in the traitor uplink. They unlock a secret compartment
+ containing uplink items in any vendor on the ship. Item costs 50 TCs; comes
+ with 5 coins making each worth 10 TCs.
+ - tweak: Each compartment has an average worth of 16 TCs. They are usually related
+ to the theme of the vendor. Higher cost items are not guaranteed to spawn, meaning
+ you may get less TCs than your coin is worth. Or much more, depending on your
+ luck.
+ - tweak: Using a challenge coin or an emag on a vending machine makes it scream
+ anti-Sol slogans for the benefit of all nearby non-traitors.
+ - bugfix: Makes vending machine icons turn back on after being anchored in a powered
+ area.
+ - bugfix: Docilers now properly self-recharge
+ - bugfix: Can now properly disable safety on underslung grenade launcher.
+ - bugfix: Light bullpup can no longer be loaded with grenades it can't even fire.
+ It also no longer insists it has a grenade launcher despite best attempts to
+ convince it otherwise.
+ - bugfix: Grabbing someone that is weakly grabbed by someone else and walking away
+ should now release the other person's grab. No more infinite range grabs.
+ - bugfix: Fixed not being able to put mobs into suit cyclers. You require a weak
+ grab to put a mob into a suit cycler, suit storage, or gibber. Can click and
+ drag self.
+ - tweak: Vending machines now emit weak light when powered. Let capitalism be your
+ guide in the darkness.
+ - bugfix: Antlions now properly burrow and appear near/on you to ruin your day.
+ - bugfix: King goats have re-learned the lost skill of explorer bowling, and have
+ a chance of running you over.
+ - bugfix: Charbabys now have a 10% chance per attack of setting you on fire again.
+ - tweak: Can now stab eyes with all tiny, small, and normal sized sharp items.
+ - tweak: It is now possible to damage glass/mask items if eye-stabbing with sufficient
+ force. This is affected by CQC skill, making it easier to break glasses with
+ higher skill. It is not possible to break hats that cover eyes (e.g riot helmets)
+ in this way however, no matter the force.
+ - tweak: It is now possible to miss while trying to stab someone's eyes.
+ - tweak: Adds 'rare items' to sovietsoda, fitness, and games vendor.
+ - bugfix: Fixes oversight allowing hacked vending machines to hurl traitor coin
+ items at people.
+ emmanuelbassil, SierraKomodo:
+ - tweak: Melee weapon cooldown has been tweaked to be less spammy.
+ - bugfix: You are now able to examine/jointlock/dislocate/and pin other mobs using
+ grabs, depending on intent.
+ - tweak: Grabs now leave fingerprints on uncovered body parts. If covered, chance
+ of prints/fibers on clothing as previous behavior.
+ gy1ta23:
+ - tweak: The showers make sound.
+2023-08-29:
+ SuhEugene:
+ - rscadd: "\u041F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u043B \u0438 \u0432\u043A\
+ \u043B\u044E\u0447\u0438\u043B \u0430\u0434\u0435\u043A\u0432\u0430\u0442\u043D\
+ \u043E \u043C\u043E\u0434 \u043B\u043E\u0434\u0430\u0443\u0442\u0430."
+ emmanuelbassil:
+ - bugfix: You are now able to store guns in your inventory objects while on harm
+ intent
+2023-08-30:
+ SierraKomodo:
+ - balance: Self-recharging guns such as the Advanced Energy Gun and energy based
+ borg and mech weapons now delay recharging when they are fired. This is to prevent
+ guns from self-charging in the middle of mag-dumping the gun and force more
+ tactical consideration of when to fall back to "recharge" the weapon.
+2023-08-31:
+ SuhEugene:
+ - tweak: "\u0421\u0434\u0435\u043B\u0430\u043B \u0441\u043A\u0440\u043E\u043B\u043B\
+ \u0431\u0430\u0440 \u0447\u0435\u043D\u0434\u0436\u043B\u043E\u0433\u0430 \u0442\
+ \u0451\u043C\u043D\u044B\u043C."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0448\u0442\u0443\u043A\u0443\
+ , \u0434\u0435\u043B\u0430\u044E\u0449\u0443\u044E \u0434\u0432\u0438\u0436\u0435\
+ \u043D\u0438\u0435 \u043F\u043B\u0430\u0432\u043D\u044B\u043C, \u043D\u0430\
+ \ \u0437\u0430\u043C\u0435\u043D\u0443 \u0434\u0451\u0440\u0433\u0430\u043D\u0438\
+ \u044F\u043C \u044D\u043A\u0440\u0430\u043D\u0430 \u043E\u0444\u0444\u043E\u0432\
+ ."
+2023-09-01:
+ Sbotkin:
+ - tweak: The hypospray can be holstered now. Yee-haw!
+ - tweak: Removed lock from the hydroponics closet.
+ SuhEugene:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u043A\u0440\u0438\u0432\
+ \u0443\u044E \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u043F\u0440\u0435\
+ \u0432\u044C\u044E \u043C\u043E\u0431\u0430 \u0432 \u043B\u043E\u0434\u0430\u0443\
+ \u0442\u0435."
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u043F\u043E\u0441\u0442\
+ \u0435\u0440\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0441\u0430\u043C\
+ \ \u0436\u0435 \u0438 \u0441\u043B\u043E\u043C\u0430\u043B."
+ SuhEugene, Remission:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E \u043D\u043E\u0432\
+ \u043E\u0435 \u043A\u0440\u0430\u0441\u0438\u0432\u043E\u0435 \u043B\u043E\u0431\
+ \u0431\u0438. \u0412\u0441\u0435 \u0430\u0441\u0441\u0435\u0442\u044B \u0437\
+ \u0430 \u0430\u0432\u0442\u043E\u0440\u0441\u0442\u0432\u043E\u043C \u043F\u0440\
+ \u0435\u043A\u0440\u0430\u0441\u043D\u043E\u0439 Remission."
+2023-09-02:
+ Jux:
+ - tweak: RCDs can no longer be made or decon'd at autolathes, but can be produced
+ by science.
+ - tweak: The Robco Tool Maker no longer has a contraband RCD, and has two less RCD
+ ammo charges.
+ SuhEugene:
+ - tweak: "\u0412\u0438\u0434\u0435\u043E \u0432 \u043B\u043E\u0431\u0431\u0438 \u0442\
+ \u0435\u043F\u0435\u0440\u044C \u043D\u0435 \u043F\u0440\u044F\u043C\u043E\u0443\
+ \u0433\u043E\u043B\u044C\u043D\u043E\u0435, \u0430 \u043A\u0432\u0430\u0434\u0440\
+ \u0430\u0442\u043D\u043E\u0435. (\u0420\u0430\u0437\u043C\u0435\u0440 \u0444\
+ \u0430\u0439\u043B\u043E\u0432 \u0443\u043C\u0435\u043D\u0448\u0438\u043B\u0441\
+ \u044F \u043D\u0430 36 \u0438 330\u041A\u0411)."
+2023-09-03:
+ LordNest:
+ - rscadd: Added NanoUI-style latejoin menu
+ Sbotkin:
+ - maptweak: Miners and xenoarcheologists now have cooling units and jetpacks in
+ their prep room.
+ SuhEugene:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0434\u0438\u0430\u0433\u043E\
+ \u043D\u0430\u043B\u044C\u043D\u043E\u0435 \u0434\u0432\u0438\u0436\u0435\u043D\
+ \u0438\u0435 \u0438 \u043A\u0435\u0439\u0431\u0438\u043D\u0434\u044B \u0432\
+ \ \u0432\u0438\u0434\u0435 \u043C\u043E\u0434\u0430 \u043D\u0430 \u043F\u043E\
+ \u0434\u0441\u0438\u0441\u0442\u0435\u043C\u0443 SSinput."
+ - tweak: "\u0418\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0442\u0435\u043F\
+ \u0435\u0440\u044C \u043A\u0440\u0430\u0441\u0438\u0432\u0435\u043D\u044C\u043A\
+ \u0438\u0439."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0441\u043B\u0435\u0432\
+ \u0430 \u0441\u043D\u0438\u0437\u0443 \u0441\u0442\u0430\u0442\u0443\u0441\u0431\
+ \u0430\u0440 \u0432\u043C\u0435\u0441\u0442\u043E \u0431\u043E\u043B\u044C\u0448\
+ \u043E\u0433\u043E \u0434\u043B\u0438\u043D\u043D\u043E\u0433\u043E."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u0442\u0451\u043C\
+ \u043D\u0430\u044F \u0442\u0435\u043C\u0430 \u0434\u043B\u044F \u043C\u0435\u043D\
+ \u044E \u0432\u0432\u043E\u0434\u0430."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u0431\u043E\u043B\
+ \u0435\u0435 \u043B\u043E\u0433\u0438\u0447\u043D\u0430\u044F \u0441\u0438\u0441\
+ \u0442\u0435\u043C\u0430 \u0430\u043D\u043E\u043D\u0441\u043E\u0432 - \u043D\
+ \u0435 \u043D\u0430\u043F\u0440\u044F\u043C\u0443\u044E \u0432 \u0433\u043E\u043B\
+ \u043E\u0432\u0443, \u0430 \u0432 \u0440\u0430\u0434\u0438\u043E\u043A\u0430\
+ \u043D\u0430\u043B."
+2023-09-04:
+ Mucker:
+ - bugfix: Robots can no longer interact with doors or alarms that have the AI wire
+ cut.
+ Sbotkin:
+ - tweak: Incapacitated mobs no longer struggle against being buckled.
+ SierraKomodo:
+ - maptweak: The GUP now has a charging station for IPCs and borgs, replacing one
+ of the many seats it has.
+2023-09-05:
+ SuhEugene:
+ - admin: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0441\u043A\u0430\u0447\u0438\
+ \u0432\u0430\u043D\u0438\u0435 \u043B\u043E\u0433\u043E\u0432."
+ cuddleandtea:
+ - bugfix: admins can now re-exit their old mobs after entering other mobs and going
+ back
+2023-09-06:
+ SierraKomodo:
+ - bugfix: Table frames can now be plated or dismantled regardless of user intent.
+ - rscdel: Click+dragging a stack of metal on a table no longer reinforces the table.
+ - rscadd: Click+dragging held items onto a table now puts that item on the table
+ regardless of intent.
+ cuddleandtea:
+ - imageadd: new cryo cell sprite
+ emmanuelbassil:
+ - bugfix: Parrot icon properly updates on death again
+2023-09-09:
+ SuhEugene:
+ - bugfix: Player panel rudimentary transformation no longer sends you to nullspace.
+ - bugfix: Fixed admin faxes reply.
+2023-09-10:
+ Reishi42:
+ - bugfix: The shard of glass in your hands was not melted into a sheet of glass
+ if you pressed it with welding tool in your hands.
+2023-09-12:
+ Mucker:
+ - rscdel: Removed the Energy Machette from emagged flying borg's modules.
+ Reishi42:
+ - bugfix: It was strange that "Join Response Team" or "Join as Actor" is on the
+ IC tab of living human.
+ Ryan180602:
+ - tweak: Remove probability for the "You momentarily forget ..!" interaction when
+ brain damaged.
+ - tweak: Increase threshold to 80 brain damage for "You look at x cluelessly" interaction.
+ Sbotkin:
+ - tweak: Carefully stepping (creep intent) on spilled liquids prevents from smearing
+ it.
+ SierraKomodo:
+ - maptweak: Removed the unused and unnecessary fuel pipes from the supermatter core.
+ SingingSpock:
+ - maptweak: The Rust output SMES no longer has charge in it to start
+ emmanuelbassil:
+ - tweak: Can now iron any incapacitated mob wherever they may be, all you need is
+ an enabled ironing iron!
+ - bugfix: Fixed ironing boards deleting themselves when buckling a mob to them using
+ a grab
+ - bugfix: Fixed ironing boards never allowing you to place an iron on them
+ gy1ta23, joeynosegay:
+ - tweak: Some smokeables have better item descriptions.
+ - tweak: Cigarette cases start empty.
+2023-09-13:
+ SingingSpock:
+ - tweak: Guns no longer ignore having the safety toggled on when you're on harm
+ intent and click
+2023-09-14:
+ SuhEugene:
+ - bugfix: Fixes exosuit space movement. They're no longer able to walk in space.
+2023-09-15:
+ HonkEmo:
+ - imageadd: "\u0421\u043F\u0440\u0430\u0439\u0442 \u043A\u043B\u043E\u0437\u0435\
+ \u0442\u0430 \u0441\u0434\u0435\u043B\u0430\u043D \u043A\u043E\u043D\u0442\u0440\
+ \u0430\u0441\u0442\u043D\u0435\u0435."
+ Merlin1230:
+ - bugfix: Fixes items that had internal machines that needed to be powered so they
+ could actually get powered from a powered area, which includes the press camera
+ drone
+ Mucker:
+ - bugfix: Plant genes are now properly randomized again.
+ - bugfix: Fixed chameleon kit items not working.
+ - tweak: The 'Spawn Chemical Dispenser Cartridge' verb now takes an input for the
+ reagent, instead of a giant list.
+ - bugfix: Bluespace turfs made from the BSD event now properly revert to normal
+ floors.
+ - bugfix: Masks no longer prevent speech problems.
+ - bugfix: Fixed rig-mounted RCDs.
+ - bugfix: Fixed the Morpheus ariborne, blitz, and prime head icons being invisible.
+ Sbotkin:
+ - tweak: Removes locks from the fridges and the kitchen cabinet.
+ - bugfix: The Petrov's R&D console now correctly synchronizes with the robotics
+ server.
+ SierraKomodo:
+ - admin: run-unit-tests now displays the test names, and is sorted alphabetically.
+ - bugfix: Mobs no longer keep access to the changeling hivemind when their changeling
+ status is removed.
+ - bugfix: Attaching and removing augments now displays the augment's name instead
+ of, i.e., "r_arm_aug".
+ - bugfix: Grabs no longer drop themselves when you switch hands.
+ - bugfix: Weapon attacks now generate attack logs again.
+ - bugfix: Weapon attacks can now be blocked by auras and things using aura code.
+ - bugfix: Navigational lights no longer fly away when you maneuver.
+ - bugfix: Nabber (GAS) kill-grabs no longer repeatedly cancel themselves.
+ - bugfix: Circuitry in the self-destruct room now starts the round with the correct
+ unlit state.
+ - bugfix: Circuitry in the self-destruct room now properly update their lighting
+ color.
+ emmanuelbassil, Mucker:
+ - bugfix: Restored ability to parry attacks.
+ - bugfix: Restored ability for melee attacks with weapons to miss.
+ - bugfix: Some melee weapons no longer ignore armor
+2023-09-16:
+ Mucker, SuhEugene:
+ - balance: Robots no longer have all access, and can only access their relevant
+ department doors. By default, all robots have access to the teleporter and maintenance
+ tunnels, as well as robotics. - Mucker
+ - balance: "\u0418\u0434\u0435\u044F \u0432\u044B\u0448\u0435 \u043F\u043E\u043B\
+ \u043D\u0430\u044F \u0444\u0438\u0433\u043D\u044F, \u043F\u043E\u044D\u0442\u043E\
+ \u043C\u0443 \u0434\u043E\u0441\u0442\u0443\u043F\u044B \u0431\u043E\u0440\u0433\
+ \u0430\u043C \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u044B -\
+ \ SuhEugene"
+2023-09-18:
+ wb13:
+ - bugfix: Zombies can now attack borgs and mechs, which were previously invulnerable
+ to them.
+ - bugfix: Mobs now drop any handheld items after zombifying.
+ - bugfix: Zombies can once again destroy doors, shutters, and unreinforced walls
+ -- albeit very slowly.
+ - tweak: Zombie NPCs are better at prioritizing prey, and will defend themselves
+ against non-human mobs.
+ - balance: Nerfs the pain damage of zombie attacks, reducing their ability to incapacitate.
+2023-09-20:
+ That0nePerson:
+ - tweak: Guns ignoring their safety on harm intent or not is now a preference
+ emmanuelbassil:
+ - tweak: Bruise kits no longer patch up external damage on mobs placed on optables
+ if on help intent. Need to switch to non-help intent. This is to prevent accidental
+ closures during surgeries. This does not apply to ghetto surgeries.
+ - bugfix: Fixes welders and nano-paste not working during surgeries.
+ - bugfix: Fixes coins not working in ghetto robotic surgeries.
+ - bugfix: Fixes being unable to drink from glass bottles.
+2023-09-21:
+ wb13:
+ - bugfix: Installing items using a gripper no longer causes errors. This mainly
+ includes airlock electronics.
+2023-09-22:
+ Jux:
+ - admin: Particle effects can be more easily added live using AddParticles(). Arguments
+ are for the type, duration in seconds (0 for infinte) and colour. They can also
+ be removed with RemoveParticles().
+ Mucker:
+ - rscadd: You can now examine items people are wearing by clicking the '?' next
+ to items listed when examine a person.
+ Qlonever:
+ - tweak: Improved surgeons' chances of repairing robotic organs inside of organic
+ bodies.
+ SomeAngryMiner:
+ - rscadd: Several Sprite issues have been corrected.
+ emmanuelbassil:
+ - bugfix: Padded beds and brown bar stools now properly drop cloth and carpet respectively
+ when padding is removed.
+ - tweak: Can now remove padding with any sharp item, not only wirecutters.
+ - tweak: Rewrote unarmed attack code to use the same miss/parry code as melee attacks
+ with weapons.
+ - bugfix: Holo-boxing gloves no longer target the wrong limb 100% of the time.
+ - bugfix: Punching simple animals should make a punching noise again.
+ - bugfix: Fixed cigarette wrapping paper icons, now becomes empty when out of wrapping
+ paper.
+ - bugfix: Rapid pipe deployer no longer creates a pipe as you put it into storage.
+2023-09-23:
+ LordNest:
+ - maptweak: Added another away map with a crashed shuttle on it
+ SierraKomodo:
+ - tweak: The threshhold for the cryo interface displaying a temperature n warning
+ (orange) colors adjusted to 170, matching the effective temperature limit of
+ cryoxadone and clonexadone.
+ - imageadd: Cryo tubes now have color-coded lights, indicating power status and
+ temperature status. Blue = powered but turned off. Green = powered and safe.
+ Yellow/orange = powered and above 170. Red = powered and above 237K (0C). No
+ light = Unpowered.
+ - imageadd: Cryo tube lights are now emmissive.
+ SuhEugene:
+ - bugfix: Fixed multitile doors opacity.
+2023-09-25:
+ Azzy:
+ - maptweak: Medical now has a wall fire-locker, right above the Chemistry door.
+ This will open to the vent west of it.
+2023-09-30:
+ CrimsonShrike:
+ - experiment: New lighting system. Things may look off.
+ - rscadd: Directional lights.
+ - rscadd: Zmimic light bleed.
+ - rscadd: Darksight is now adaptative and will animate to to full value when in
+ darkness.
+ - rscadd: Space and planets now use ambient lights instead of dynamic lights. Planetary
+ daynight cycle adjusted.
+ Mucker:
+ - bugfix: Fixed the character panel preview icon not updating when it should.
+ Qlonever:
+ - bugfix: Fixed crew records not displaying photos correctly.
+ SuhEuhene:
+ - bugfix: Fixed russian keyboard layout radio keys.
+2023-10-01:
+ SuhEugene:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043F\u0430\u043D\u0435\u043B\
+ \u044C \u044D\u043C\u043E\u0443\u0442\u043E\u0432."
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0448\u0435\u043D \u043F\u043E\u0432\u043E\
+ \u0440\u043E\u0442 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u0447\u0435\u0440\
+ \u0435\u0437 ctrl."
+ - bugfix: "\u041A\u0430\u0441\u0442\u043E\u043C\u043D\u044B\u0439 \u044D\u043C\u043E\
+ \u0443\u0442 \u0442\u0435\u043F\u0435\u0440\u044C \u0432\u0432\u043E\u0434\u0438\
+ \u0442\u0441\u044F \u043D\u0435 \u0447\u0435\u0440\u0435\u0437 \u0441\u043B\u0435\
+ \u0448, \u0430 \u0447\u0435\u0440\u0435\u0437 \u0437\u0432\u0451\u0437\u0434\
+ \u043E\u0447\u043A\u0443."
+2023-10-03:
+ SuhEugene:
+ - maptweak: "\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u044B \u0420\
+ \u0414 \u0438 \u0421\u041C\u041E \u0438\u0445 \u043A\u043E\u043C\u0430\u043D\
+ \u0434\u043D\u044B\u0435 \u043A\u043E\u043D\u0441\u043E\u043B\u0438 \u0432 \u043E\
+ \u0444\u0438\u0441\u044B."
+ - maptweak: "\u041F\u043E\u0444\u0438\u043A\u0448\u0435\u043D\u044B \u043C\u0443\
+ \u043B\u044C\u0442\u0438\u0442\u0430\u0439\u043B\u043E\u0432\u044B\u0435 \u0448\
+ \u043B\u044E\u0437\u044B \u0441\u043E \u0441\u0431\u0438\u0442\u044B\u043C\u0438\
+ \ \u0445\u0438\u0442\u0431\u043E\u043A\u0441\u0430\u043C\u0438."
+ - tweak: "\u0421\u041A\u041C \u0442\u0435\u043F\u0435\u0440\u044C \u043D\u0435 \u0442\
+ \u0440\u0435\u0431\u0443\u0435\u0442 \u043D\u0430\u0436\u0430\u0442\u0438\u044F\
+ \ Alt \u0447\u0442\u043E\u0431\u044B \u0443\u043A\u0430\u0437\u044B\u0432\u0430\
+ \u0442\u044C \u043D\u0430 \u0447\u0442\u043E-\u043B\u0438\u0431\u043E."
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0448\u0435\u043D\u0430 \u0437\u0430\u0433\
+ \u0440\u0443\u0437\u043A\u0430 \u0421\u044C\u0435\u0440\u0440\u043E\u0432\u0441\
+ \u043A\u0438\u0445 \u0430\u0432\u0435\u0435\u043A"
+ Wolfor By Kot, SuhEugene:
+ - imageadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u0435\u0449\
+ \u0451 \u0431\u043E\u043B\u044C\u0448\u0435 \u043C\u0435\u0442\u043E\u043A \u0442\
+ \u0435\u043B\u0430."
+2023-10-04:
+ Jux:
+ - bugfix: Borg antag items that recharge (guns, various reagent items) now recharge
+ properly, Borg modules that used the drone space cleaner without ever recharging
+ it now recharge it, and Surgeon borgs recharge their sterilizine. All rechargable
+ borg antag items recharge at a more reasonable speed.
+ - bugfix: Borg item icons now update when recharging, making it less of a guessing
+ game for if your tools are charged.
+ - tweak: Flamethrowers and syringe guns both now give feedback on their ammo and
+ ready status when examined. Flamethrowers require fuel to ignite, and go out
+ when they run out of fuel.
+ Mucker:
+ - rscadd: Adds an artifact effect that can turn robotic limbs to flesh, and give
+ FBP's new bodies.
+ Qlonever:
+ - bugfix: Made noexcutite actually prevent jitters like it's supposed to.
+ SierraKomodo:
+ - tweak: Changed the default run levels subsystems initialize at to post-game setup.
+ - bugfix: Random events should no longer start firing if the server fails to start
+ a round and returns to the lobby.
+ SuhEugene:
+ - tweak: "\u0414\u043B\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0431\u0430\u0440\
+ \u0430 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u043F\u0440\u0435\u0444\
+ \u0435\u0440\u0435\u043D\u0441 \u0432\u0438\u0434\u0438\u043C\u043E\u0441\u0442\
+ \u0438."
+ - admin: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u043A\u043D\u043E\
+ \u043F\u043E\u0447\u043A\u0430 Spawn as Human."
+ cuddleandtea:
+ - bugfix: lifepod missing icon fixed
+ - imageadd: new body scanner and console sprite
+2023-10-06:
+ SierraKomodo:
+ - bugfix: Pre-game votes now tick down properly again.
+ SuhEugene:
+ - tweak: Made stripping, vote, fire and party alarm panels use Fake NanoUI
+2023-10-11:
+ SierraKomodo:
+ - bugfix: pAIs are no longer stuck in an anchored state if folded while buckled.
+ - balance: Mob confusion should now be limited to 30 seconds. Note that if you are
+ hit by an effect that increases your confusion, it can still extend this time
+ - This change only prevents the total stack from exceeding 30 seconds if you're
+ being spammed by something.
+2023-10-12:
+ Mucker:
+ - bugfix: Spiderlings should now grow properly while in your limbs, and eventually
+ pop out.
+ PurplePineapple, [skull132](https://github.com/skull132), Azlan, Ryan0602:
+ - rscadd: Three new hardsuits for the "Operative" mode; a Sol hardsuit, an ICCG
+ hardsuit, and a Corporate hardsuit.
+ - tweak: The new hardsuits, a subtype of the Merc hardsuit, and the original ninja
+ suit for 5 total options for a Ninja.
+ - rscadd: Leg actuator module using some code from [skull132](https://github.com/skull132)
+ on Aurorastation13 for the Corporate RIG
+ - rscadd: Personal shield module for the SCG RIG
+ - rscadd: A mounted 7mm minigun module for the ICCG RIG
+ - rscadd: A separate minigun usable on its own. Adminspawn only for now.
+ - tweak: Cloak and Telportation now use twice as much energy as before.
+ - tweak: Hardsuit mounted weaponry has been separated into ballistic and energy
+ subtypes.
+ - rscadd: A new Operative starting map made by Ryan0602 with tools, medical supplies,
+ and one random large weapon on each side.
+ - tweak: Ninja now requires 10 active players. The minimum number of Operatives
+ is a minimum of 1, but now it's possible to have 2 as a maximum.
+ - rscdel: Removes mentions of the Spider-Clan from antagonist objectives. They are
+ no longer only literal ninjas.
+2023-10-15:
+ Alex6511:
+ - bugfix: Mech jetpacks work again, probably
+ Mucker:
+ - tweak: Nurse spiders will only inject 1 - 3 spiderlings sometimes, instead of
+ 6 - 12.
+ - bugfix: Objects will once again fall through openspace when affected by gravity.
+ Qlonever:
+ - bugfix: Fixed several icon-related issues.
+ - bugfix: Radial menus for organ regeneration surgery now have accurate text.
+ SierraKomodo:
+ - bugfix: Fixed an issue where maintenance spiderlings became big spiders when disturbed.
+ emmanuelbassil:
+ - tweak: Now able to wrap crates/closets with gift wrapping paper; and to wrap humans
+ with regular parcel wrapping paper. Both have the same behavior.
+ - tweak: Can now destination tag and label gifts.
+ - tweak: You now require a sharp item to open any wrapped parcel
+ - tweak: No more instant wrapping. Time delay scales up the bigger the item you're
+ wrapping is. Also uses more wrap depending on item size.
+ - tweak: Can now clip foamclogs with any sharp item, not only wirecutters or the
+ scalpel.
+ - bugfix: Instant Tactical combat gift-wrapping is no longer possible. You need
+ a strong grab and a delay to gift-wrap someone else, like handcuffs.
+2023-10-18:
+ Slywater:
+ - bugfix: You can once again buckle yourself while incapacitated, and interact with
+ items from a chair (assuming you're not in pain crit).
+ - rscadd: Diona, monkey, and zombie NPCs are now processed under the new AI subsystem.
+ - rscadd: Hostile NPCs will now search for prey upon losing sight of them. They'll
+ patrol the area and break into potential hiding spots.
+ - rscadd: Passive NPCs such as mice, cats, and monkeys will now flee from threats
+ if attacked.
+ - bugfix: Fixed NPC attack reactions, calls for backup & environment destruction
+ priorities.
+ SuhEugene, PiotrTheTchaikowsky:
+ - imageadd: Updated "Fun Police", "Lusty xenomorph" and "Miss Science 2299" posters.
+ TheNightingale:
+ - tweak: Operating tables now have a visual indicator for if the suppressors are
+ off during surgery (the lights turn red).
+ cuddleandtea:
+ - bugfix: Corrected light build mode default power to 1.
+ - tweak: less radius for instruments sound
+ emmanuelbassil:
+ - bugfix: Fixes being unable to place mobs into a mounted sleeper.
+2023-10-19:
+ Qlonever:
+ - bugfix: Open crates, large crates, closets, and wall closets now use correct decal
+ icons.
+ Ryan180602:
+ - admin: Removes attack log for using the gravikinetic.
+ SierraKomodo:
+ - balance: Radios can now be used while weakened, but are still blocked when paralyzed
+ or stunned.
+ - balance: Radios can no longer be used while under the voiceloss chemical effect
+ (The one that forces you to whisper). This applies to amaspores (Amatoxin poisoning)
+ and vecoronium bromide (Paralysis pen).
+ emmanuelbassil:
+ - tweak: Clicking on yourself with a pill bottle no matter the targeted area consumes
+ a pill. Previous behavior required targeting mouth.
+ - bugfix: Fixes being unable to punch spiderlings to death
+ - tweak: Can now damage any object with health using your fists of justice; similar
+ to existing behavior with melee weapons.
+2023-10-20:
+ emmanuelbassil:
+ - tweak: The Torch learns some manners. Chewables like chewing tobacco and gum now
+ spit out into an empty hand first. If no available hand, then the floor like
+ current behavior.
+ - tweak: Can now place drone telemetry designator in tool belt and exploration machete
+ belt.
+ - tweak: Can now use belts to whip/lash when on harm intent.
+2023-10-22:
+ SierraKomodo:
+ - admin: Altering the confused var with view variables is no longer limited to a
+ value of 15.
+2023-10-23:
+ CuddleAndTea:
+ - tweak: "\u0418\u0437\u043C\u0435\u043D\u0438\u043B \u0440\u0430\u0441\u0441\u0442\
+ \u043E\u044F\u043D\u0438\u0435 \u043F\u0440\u043E\u0438\u0433\u0440\u044B\u0432\
+ \u0430\u043D\u0438\u044F \u043A\u0430\u0441\u0442\u043E\u043C\u043D\u044B\u0445\
+ \ \u043F\u043B\u0435\u0435\u0440\u043E\u0432 (+2 \u0442\u0430\u0439\u043B\u0430\
+ )"
+ SierraKomodo:
+ - tweak: Space Mountain Wind is now lime green instead of dark green.
+2023-10-27:
+ SierraKomodo:
+ - balance: Arterial bleeds no longer cause confusion or blurred vision when they
+ trigger.
+ SuhEugene:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0434\u0432\u043E\u0439\
+ \u043D\u044B\u0435 \u0448\u043B\u044E\u0437\u044B \u043D\u043E\u0432\u043E\u0439\
+ \ \u043A\u0430\u0440\u0442\u044B."
+ - bugfix: Fixed airlock control terminal icon.
+2023-10-28:
+ KandJX:
+ - maptweak: "\u041D\u0430\u0441\u0440\u0430\u043B \u0432 \u0432\u0438\u0440\u0443\
+ \u0441\u043E\u043B\u043E\u0433\u0438\u044E."
+ - maptweak: "\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B\
+ \ \u0448\u043A\u0430\u0444 \u0448\u0430\u0445\u0442\u0451\u0440\u043E\u0432."
+2023-10-29:
+ KandJX:
+ - tweak: "\u0417\u0430\u043F\u0440\u0435\u0442\u0438\u043B \u0422\u0430\u044F\u0440\
+ \u0430\u043C \u043C\u043D\u043E\u0433\u043E \u0440\u043E\u043B\u0435\u0439."
+ - tweak: "\u0420\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u043B \u0431\u043E\u043B\
+ \u044C\u0448\u0438\u043D\u0441\u0442\u0432\u0443 \u0440\u043E\u043B\u0435\u0439\
+ \ (\u043A\u043E\u0442\u043E\u0440\u044B\u043C \u043D\u0430\u0434\u043E) \u043C\
+ \u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0435 \u0438 \u0440\u0435\
+ \u043A\u043E\u043C\u0435\u043D\u0434\u043E\u0432\u0430\u043D\u043D\u044B\u0435\
+ \ \u0432\u043E\u0437\u0440\u0430\u0441\u0442\u0430."
+ - maptweak: "\u0420\u0430\u0441\u0441\u0442\u0430\u0432\u0438\u043B \u0432\u043E\
+ \u0437\u0440\u0430\u0441\u0442\u0430 \u0434\u043B\u044F \u041F\u0430\u0442\u0443\
+ \u0440\u043B\u044C\u043A\u0438 \u0426\u041F\u0421\u0421 \u0438 \u041F\u0438\u043E\
+ \u043D\u0435\u0440\u0441\u043A\u043E\u0433\u043E \u043A\u043E\u0440\u043F\u0443\
+ \u0441\u0430. \u041F\u0440\u043E\u0441\u0442\u0438 \u041B\u0438\u0441\u0438\u043A\
+ \u043E, \u0432 28 \u043B\u0435\u0442 \u0440\u0430\u043D\u043E \u043D\u0430 \u041A\
+ \u043E\u043C\u0430\u043D\u0434\u043E\u0440\u0430."
+ LordNest:
+ - maptweak: "\u0423\u0431\u0440\u0430\u043B \u043E\u043A\u043D\u043E \u043C\u0435\
+ \u0436\u0434\u0443 \u0420\u0414 \u0438 \u0421\u041C\u041E, \u0434\u043E\u0431\
+ \u0430\u0432\u0438\u043B \u0446\u0432\u0435\u0442\u044B \u0432 \u0446\u0435\u0440\
+ \u043A\u043E\u0432\u044C"
+ SuhEugene:
+ - bugfix: "\u0422\u0435\u043F\u0435\u0440\u044C \u0443\u0440\u043E\u0432\u043D\u0438\
+ \ 4 \u0438 5 \u0441\u044C\u0435\u0440\u0440\u044B \u043D\u0435 \u0441\u0447\u0438\
+ \u0442\u0430\u044E\u0442\u0441\u044F \u0430\u0434\u043C\u0438\u043D\u0441\u043A\
+ \u0438\u043C\u0438. \u0411\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0430\
+ \ \u0448\u043B\u044E\u0437\u043E\u0432 \u0442\u0435\u043F\u0435\u0440\u044C\
+ \ \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C\
+ \ \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u043E, \u0442\u0435\u043C\
+ \u043F\u0435\u0440\u044B \u0442\u043E\u0436\u0435 \u043D\u0430\u0447\u043D\u0443\
+ \u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u043F\u0440\u0430\u0432\
+ \u0438\u043B\u044C\u043D\u043E."
+2023-10-30:
+ emmanuelbassil:
+ - bugfix: Can now point at closets/crates again
+ - tweak: Ctrl + Alt + Click no longer opens and closes closets. Alt + Click still
+ locks/unlocks as before.
+2023-10-31:
+ Aigamuxa:
+ - tweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u043B\u0430\u043C\
+ \u043F\u0430 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0438\
+ \u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432"
+ Mucker:
+ - bugfix: Space carp will now actually make it to the ship during carp events again.
+ SierraKomodo:
+ - rscadd: Organs now take 50-100% of their maximum damage when their holding body
+ part is gibbed. This primarily effects brain damage from exploding heads.
+ - spellcheck: Fixed a typo in the organic body part gibbing message.
+ Spookerton:
+ - bugfix: Mapped injectors don't turn themselves off during game setup.
+2023-11-01:
+ KandJX:
+ - bugfix: "\u0412\u0435\u0440\u043D\u0443\u043B \u043E\u0442\u043E\u0431\u0440\u0430\
+ \u0436\u0435\u043D\u0438\u044F \u0437\u0432\u0430\u043D\u0438\u0439 \u041F\u0438\
+ \u043E\u043D\u0435\u0440\u0441\u043A\u043E\u0433\u043E \u043A\u043E\u0440\u043F\
+ \u0443\u0441\u0430"
+ - tweak: "\u0414\u0430\u043B \u0440\u0430\u0437\u043D\u043E\u043E\u0431\u0440\u0430\
+ \u0437\u0438\u0435 \u0437\u0432\u0430\u043D\u0438\u0439 \u0434\u043B\u044F \u0440\
+ \u0430\u0437\u043D\u044B\u0445 \u0434\u043E\u043B\u0436\u043D\u043E\u0441\u0442\
+ \u0435\u0439 \u0424\u043B\u043E\u0442\u0430 \u0438 \u041F\u0438\u043E\u043D\u0435\
+ \u0440\u0441\u043A\u043E\u0433\u043E \u043A\u043E\u0440\u043F\u0443\u0441\u0430"
+ - maptweak: "\u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E \u043E\
+ \u0441\u0432\u0435\u0449\u0435\u043D\u0438\u0435 \u043D\u0430 \u043C\u043E\u0441\
+ \u0442\u0438\u043A\u0435"
+ - maptweak: "\u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D \u0434\u043E\
+ \u0441\u0442\u0443\u043F \u0448\u043B\u044E\u0437\u043E\u0432 \u0432\u0435\u0434\
+ \u0443\u0449\u0438\u0445 \u043A \u043F\u043E\u0440\u0442\u0443"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043D\u043E\
+ \u0443\u0442\u0431\u0443\u043A\u0438 \u0410\u0412\u0414"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u0433\u043E\
+ \u043B\u043E\u043A\u0430\u0440\u0442\u044B"
+ - maptweak: "\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0430 \u0431\u0438\u0431\
+ \u043B\u0438\u043E\u0442\u0435\u043A\u0430"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E \u043F\u0430\
+ \u0440\u0443 \u043F\u043B\u0435\u0439\u0442\u0438\u043D\u0433\u043E\u0432 \u043D\
+ \u0430 \u0432\u0442\u043E\u0440\u043E\u043C \u044D\u0442\u0430\u0436\u0435 \u0430\
+ \u043D\u0433\u0430\u0440\u0430"
+ - maptweak: "\u0424\u0438\u043A\u0441 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\
+ \u043A\u0438\u0445 \u043C\u0438\u043D\u043E\u0440\u043D\u044B\u0445 \u043D\u0435\
+ \u0434\u043E\u0447\u0435\u0442\u043E\u0432"
+ SierraKomodo:
+ - bugfix: Fuel pipe manifolds, corners, caps, etc now have the same pressure resistances
+ as straight line fuel pipes. Among other things, this means the vox ship fuel
+ pipes will no longer explode when you ignite the chamber.
+ - bugfix: Combat boots now have a sprite.
+ TheNightingale:
+ - tweak: Embed removal surgery uses normal surgery success rates but takes a little
+ longer.
+ orelbon:
+ - bugfix: Fire extinguisher spray should now act as expected without having to directly
+ click on target.
+2023-11-02:
+ Vuradu:
+ - bugfix: "\u0418\u0437\u043C\u0435\u043D\u0438\u043B \u043F\u0430\u043B\u0443\u0431\
+ \u0443 \u0432 \u0438\u043C\u0435\u043D\u0438 \u0437\u043E\u043D\u044B \u0441\
+ \ 3-\u0435\u0439 \u043D\u0430 4-\u044E."
+2023-11-03:
+ KandJX:
+ - tweak: "\u0413\u043E\u043B\u043E\u043A\u0430\u0440\u0442\u044B \u0442\u0435\u043F\
+ \u0435\u0440\u044C \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E \u043E\
+ \u0442\u043E\u0431\u0440\u0430\u0436\u0430\u044E\u0442 \u0446\u0432\u0435\u0442\
+ \u0430 \u0437\u043E\u043D"
+ - maptweak: "\u041C\u0435\u043B\u043A\u043E\u0444\u0438\u043A\u0441\u044B \u0442\
+ \u043E \u0442\u0443\u0442 \u0442\u043E \u0442\u0430\u043C"
+ LordNest:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0441\u043F\u0430\u0432\
+ \u043D\u043F\u043E\u0438\u043D\u0442\u044B \u043F\u0438\u043E\u043D\u0435\u0440\
+ \u043E\u0432, \u0441\u0435\u0440\u0436\u0430\u043D\u0442\u0430 \u0438 \u0438\
+ \u043D\u0436\u0435\u043D\u0435\u0440\u0430 \u041F\u0438\u043E\u043D\u0435\u0440\
+ \u0441\u043A\u043E\u0433\u043E \u041A\u043E\u0440\u043F\u0443\u0441\u0430"
+ SierraKomodo:
+ - bugfix: Cryopods can now be used to leave the round even if there's no power.
+ SuhEugene:
+ - bugfix: Fixed red circle triangle exclaimnation mark airlock opening icon
+ - bugfix: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u043A\u043E\u0434\u0438\u0440\
+ \u043E\u0432\u043A\u0443 \u0442\u0438\u043A\u0435\u0442-\u043F\u0430\u043D\u0435\
+ \u043B\u0438 (\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u043D\u043E\u0439\
+ \ \u0440\u0430\u0437)."
+ - bugfix: "\u0423\u0431\u0440\u0430\u043B \u043B\u043E\u0433\u0438\u0440\u043E\u0432\
+ \u0430\u043D\u0438\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u043E\
+ \u0437\u043C\u043E\u0436\u043D\u043E \u0441\u0438\u043B\u044C\u043D\u043E \u043D\
+ \u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\
+ \u0440."
+ - bugfix: "\u0421\u0434\u0435\u043B\u0430\u043B \u0431\u0443\u043D\u043A\u0435\u0440\
+ \u044B \u0440\u0430\u0431\u043E\u0447\u0438\u043C\u0438."
+ - maptweak: "\u0423\u0434\u0430\u043B\u0438\u043B \u0442\u0430\u0431\u043B\u0438\
+ \u0447\u043A\u0443 \u0431\u043E\u0442\u0430\u043D\u0438\u043A\u0438 \u0438\u0437\
+ \ \u043C\u0435\u0434\u0430."
+ emmanuelbassil:
+ - bugfix: Gas scanner no longer scans the inside of your backpack on storage
+2023-11-04:
+ KandJX:
+ - maptweak: "\u041F\u0435\u0440\u0435\u043D\u0435\u0441 \u043A\u043D\u043E\u043F\
+ \u043A\u0438 \u043F\u043E \u043F\u0440\u043E\u0433\u0440\u0435\u0432\u0443 \u0434\
+ \u0432\u0438\u0433\u0430\u0442\u0435\u043B\u0435\u0439 \u0432 \u043A\u043E\u043C\
+ \u043D\u0430\u0442\u0443 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430\
+ \ \u0442\u043E\u043F\u043B\u0438\u0432\u0430"
+ - maptweak: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u0440\u0430\u043A\u043E\
+ \u0432\u0438\u043D\u044B"
+ - maptweak: "\u041F\u043E\u043C\u0435\u043D\u044F\u043B \u0434\u043E\u0441\u0442\
+ \u0443\u043F \u0448\u043B\u044E\u0437\u0430\u043C \u0432\u0435\u0434\u0443\u0449\
+ \u0438\u043C \u043A \u043F\u0435\u0442\u0440\u043E\u0432\u0443"
+ - maptweak: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u043A\u043D\u043E\u043F\
+ \u043A\u0438 \u043D\u0430 \u043F\u0435\u0442\u0440\u043E\u0432\u0435"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0432\u043E\u0437\u043C\
+ \u043E\u0436\u043D\u043E\u0441\u0442\u044C \u0432\u044B\u043A\u0430\u0447\u0430\
+ \u0442\u044C \u0433\u0430\u0437 \u0438\u0437 \u043E\u0431\u0449\u0435\u0439\
+ \ \u0442\u043E\u043F\u043B\u0438\u0432\u043D\u043E\u0439 \u043C\u0430\u0433\u0438\
+ \u0441\u0442\u0440\u0430\u043B\u0438 \u043E\u0431\u0440\u0430\u0442\u043D\u043E\
+ \ \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0442\u043E\
+ \u043F\u043B\u0438\u0432\u0430"
+ - maptweak: "\u041D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u0435\
+ \u043B\u043A\u043E\u0444\u0438\u043A\u0441\u043E\u0432 \u043A\u0430\u0440\u0442\
+ \u044B."
+2023-11-05:
+ LordNest:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u043D\u0435\u0432\u043E\
+ \u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u044C \u0437\u0430\u0445\u043E\
+ \u0434\u0430 \u043D\u0430 \u043F\u0430\u0442\u0440\u0443\u043B\u0438"
+ - maptweak: "\u0420\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0438\u0441\
+ \u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0430\u0432\u0435\u0439\
+ -\u043A\u0430\u0440\u0442"
+2023-11-11:
+ rootoo807:
+ - bugfix: Adds some missing icons for clothing on Unathi, including bandanas.
+2023-11-13:
+ Mucker:
+ - bugfix: The human-making artifact now transfers languages when transforming synthetics.
+ Reishi42:
+ - bugfix: Tools from the engineering toolset implant could be dropped (for various
+ reasons). Now it's impossible.
+ emmanuelbassil:
+ - bugfix: Can now resist out of the human-sized gift.
+ - tweak: Added more feedback to chewing self out of cuffs. Did not change behavior,
+ need to target mouth on harm intent and click on self to do so.
+ - tweak: Added more feedback to resisting out of closets; giving you progress towards
+ success.
+ - bugfix: Fixed being unable to break out of welded closets.
+2023-11-14:
+ Lamasmaster:
+ - rscadd: Introduce gunpowder as an explosive chemical. Inbetween potassium/water
+ and other higher grade explosives.
+ LordNest:
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043A\u0430\u043C\u0435\
+ \u0440\u044B \u0438 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\
+ \u043B \u0441\u0435\u0442\u0438 \u043A\u0430\u043C\u0435\u0440. \u041C\u0438\
+ \u043D\u043E\u0440\u043D\u044B\u0435 \u043C\u0430\u043F\u0444\u0438\u043A\u0441\
+ \u044B"
+ TheNightingale:
+ - tweak: Surgeons are now given feedback when they're trying to perform standard
+ organ repair surgery on a decaying/dead organ.
+ - tweak: Adds fast-mode and slow-mode to cryo cells. By upgrading the manipulators,
+ you can make it heal up to 50% faster or 50% slower.
+ emmanuelbassil:
+ - bugfix: Fixes being able to create reinforced glass with rods + glass
+ - bugfix: Fixes being unable to use fingerprint cards or put them on tables
+ rootoo807:
+ - tweak: Human subspecies sprites no longer have random floating pixels and don't
+ clip out of clothing.
+ - tweak: Adds larger icons for the Species Info tab, rewrites some species descriptions
+ to better reflect current setting.
+2023-11-16:
+ orelbon:
+ - tweak: The self-destruct now has the option to trigger evacuation procedures.
+ - tweak: Lowers volume of delta siren.
+ - tweak: Lowers volume of evacuation siren.
+2023-11-17:
+ LordNest:
+ - bugfix: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u043E\u0442\u0432\u0430\u043B\
+ \u0438\u0432\u0448\u0438\u0439\u0441\u044F \u043F\u0443\u0442\u044C \u043A \u0418\
+ \u041A\u0421\u0443 \u041B\u042D\u041A\u0430"
+ - tweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043E\u0431\u0440\
+ \u0430\u0442\u043D\u043E \u0430\u043F\u0442\u0435\u0447\u043A\u0438 \u0434\u043B\
+ \u044F \u0421\u0411."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043C\u044D\u043D\
+ \u0441\u0442\u043E\u043F\u043F\u0435\u0440\u044B \u0438 \u0438\u0437\u043C\u0435\
+ \u043D\u0435\u043D\u043E \u043E\u0440\u0443\u0436\u0438\u0435 \u0421\u0411 \u0432\
+ \ \u0441\u0432\u044F\u0437\u0438 \u0441 \u0433\u043E\u043B\u043E\u0441\u043E\
+ \u0432\u0430\u043D\u0438\u0435\u043C \u043C\u0438\u043D\u044C\u043E\u043D\u043E\
+ \u0432."
+ - maptweak: "\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u043C \u0437\
+ \u0430\u0447\u0438\u0441\u0442\u043A\u0443 \u0432\u0438\u043B\u043A\u043E\u0439\
+ . \u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E \u043E\u0442\u0441\
+ \u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\
+ \u0430 \u043D\u0430 \u041F\u0435\u0442\u0440\u043E\u0432."
+ - rscadd: Added queue for autolathe UI. Can hold up to four items in it + one processing.
+ Ryan180602, PurplePineapple:
+ - rscadd: Add three Ceti cultures.
+ - tweak: Correct references to 2nd Fleet and Home Guard (they're not the same thing).
+ emmanuelbassil:
+ - bugfix: Fixes being unable to place lipstick in storage
+2023-11-18:
+ CuddleAndTea:
+ - tweak: "\u0423\u043C\u0435\u043D\u044C\u0448\u0435\u043D \u0440\u0430\u0434\u0438\
+ \u0443\u0441 \u0437\u0432\u0443\u043A\u0430 \u043B\u0435\u0432\u0435\u0442\u0438\
+ \u0440\u0443\u044E\u0449\u0435\u0433\u043E \u043C\u0435\u043C\u043E\u0440\u0438\
+ \u0430\u043B\u0430 \u0434\u043E 3 \u0442\u0430\u0439\u043B\u043E\u0432"
+2023-11-19:
+ LordNest:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043E\u0431\u0440\u0430\u0442\
+ \u043D\u043E \u043F\u0441\u0438-\u0430\u043C\u043F, \u0440\u0430\u0441\u0442\
+ \u044F\u0436\u043A\u0443 \u0438 \u0434\u0436\u0430\u0443\u043D\u0442\u0435\u0440"
+ - balance: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0432\u0441\u044F\u043A\u043E\
+ \u0433\u043E \u0440\u0430\u0437\u043D\u044B\u043C \u043D\u0438\u043D\u0434\u0437\
+ \u044F\u043C. \u041F\u043E \u0431\u043E\u043B\u044C\u0448\u0435\u0439 \u0447\
+ \u0430\u0441\u0442\u0438 \u043A\u043B\u044E\u0447\u0438."
+2023-11-20:
+ SuhEugene:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u043D\u0435 \u043F\u043E\
+ \u044F\u0432\u043B\u044F\u0432\u0448\u0438\u0435\u0441\u044F \u0440\u0430\u043D\
+ \u0435\u0435 \u043D\u0430 \u043A\u0430\u0440\u0442\u0435 \u043D\u0435\u043A\u043E\
+ \u0442\u043E\u0440\u044B\u0435 \u0430\u043F\u0442\u0435\u0447\u043A\u0438 \u0438\
+ \ \u0435\u0449\u0451 \u043F\u0430\u0440\u0443 \u043F\u0440\u0435\u0434\u043C\
+ \u0435\u0442\u043E\u0432."
+ TheNightingale:
+ - tweak: Previously loose pills in medical storage are now in bottles.
+ - bugfix: Replaced legacy antitox pill bottles with dylovene in random medical clutter.
+ - rscadd: Adds a new scrub color - lavender. It's not the same as lilac!
+ - bugfix: Voice changers now work to fool tape recorders.
+ - tweak: Tapes now have a maximum length of twenty minutes, up from ten.
+ - tweak: Lowered the cooldown for printing transcripts on tape recorders.
+ cuddleandtea:
+ - rscadd: players ghost mob now can be hidden for your screen
+2023-11-24:
+ Kam_Survivor:
+ - bugfix: "\u0424\u0438\u043A\u0441\u0438\u0442 \u0440\u0435\u043F\u043E\u0440\u0442\
+ \u044B \u043F\u043E \u043F\u0440\u043E\u043F\u0430\u0432\u0448\u0438\u043C \u043F\
+ \u0440\u0438\u0447\u0435\u0441\u043A\u0430\u043C"
+ - rscadd: "\u041F\u0430\u0440\u0430 \u0434\u0435\u0441\u044F\u0442\u043A\u043E\u0432\
+ \ \"\u043D\u043E\u0432\u044B\u0445\" \u043F\u0440\u0438\u0447\u0435\u0441\u043E\
+ \u043A \u0441 \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0431\u0438\u043B\
+ \u0434\u043E\u0432"
+ - imageadd: "\u0421\u043F\u0440\u0430\u0439\u0442\u044B \u043F\u0440\u0438\u0447\
+ \u0435\u0441\u043E\u043A"
+ Karl Johansson:
+ - rscadd: You can see what's currently in the chamber of a gun on examine.
+ - rscadd: You can load non-magazine weapons by booping them with an appropriate
+ loader.
+ - tweak: Opening the bolt on bolt-action weapons unloads anything inside, not just
+ empties.
+ - tweak: 5mmR weapons are more accurate at long range.
+ Lamasmaster:
+ - rscadd: New on-mob and obj sprite for the chem sprayer.
+ - rscadd: Sprayer now does the funny squeek.
+ LordNest:
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u043D\u043E\
+ \u0432\u0430\u044F \u043A\u0430\u0440\u0442\u0430 \u041E\u0411\u0420 \u0441\u043E\
+ \ \u0448\u043B\u044E\u0437\u0430\u043C\u0438 \u043F\u043E 50000 \u0445\u043F\
+ . \u0423\u0434\u0430\u0447\u0438."
+ - balance: "\u041E\u0411\u0420 1 \u0443\u0440\u043E\u0432\u043D\u044F \u0434\u043E\
+ \u0431\u0430\u0432\u043B\u0435\u043D\u044B \u0441\u043A\u0430\u0444\u0430\u043D\
+ \u0434\u0440\u044B. \u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D \u043F\u0443\
+ \u043B \u0432\u043E\u043E\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u041E\u0411\
+ \u0420. \u041E\u0411\u0420 \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E\
+ \ \u0443\u0440\u043E\u0432\u043D\u044F \u0432\u044B\u0434\u0430\u043D\u044B\
+ \ \u043A\u0430\u043D\u043E\u043D\u0438\u0447\u043D\u044B\u0435 \u043F\u0443\u043B\
+ \u044C\u0441\u043E\u0432\u044B\u0435 \u0440\u0443\u0436\u044C\u044F (\u043D\u0435\
+ \ \u0433\u0438\u0431\u0430\u044E\u0449\u0438\u0435)"
+ - admin: "\u041D\u0430 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0426\u041A \u043F\
+ \u0435\u0440\u0435\u043D\u0435\u0441\u0451\u043D \u0441 \u043B\u0430\u0439\u0432\
+ \u0430 \u043C\u043E\u0441\u0442\u0438\u043A \u0434\u043B\u044F \u0426\u041F\u0421\
+ \u0421, \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u043C\u043E\u0441\u0442\
+ \u0438\u043A \u0434\u043B\u044F \u0413\u041A\u041A."
+ Mucker:
+ - admin: Added a command for removing crew records permanently.
+ - tweak: Crew records (should) no longer duplicate under certain circumstances.
+ Qlonever:
+ - bugfix: Rescuscitating an asystolic suffocation victim won't cause their oxygenation
+ to plummet.
+ SierraKomodo:
+ - bugfix: Merchant traders should no longer include invalid/inaccessible abstract
+ types in their pools.
+ SuhEugene:
+ - tweak: "\u0423\u0441\u043A\u043E\u0440\u0438\u043B \u043B\u043E\u0433\u0433\u0438\
+ \u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0432 \u0444\u0430\u0439\u043B.\
+ \ \u041D\u0435 \u0437\u043D\u0430\u044E \u0441\u0442\u0430\u043D\u0435\u0442\
+ \ \u043B\u0438 \u0438\u0433\u0440\u0430\u0442\u044C\u0441\u044F \u043F\u043B\
+ \u0430\u0432\u043D\u0435\u0435, \u043D\u043E \u0434\u043E\u043B\u0436\u043D\u043E\
+ ."
+ TheNightingale:
+ - tweak: All belts can now hold PDAs and tablets.
+ - tweak: Medical belts can hold IV bags and medical tape.
+ - tweak: Forensics belts can now hold security tape, glasses, flashes and pepperspray.
+ - tweak: Alkysine and imidazoline recipes now produce the same amount of output
+ as input, rather than turning 3 units of input into 2 units of output.
+ - tweak: Kelotane no longer messages admins every time it's created.
+ - tweak: You can now adjust the names and descriptions of chameleon gear when you
+ adjust the appearance.
+ UEDHighCommand:
+ - imageadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043D\u0435\
+ \u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 \u0441\u043F\u0440\u0430\
+ \u0439\u0442\u044B Facial hair \u0434\u043B\u044F \u0443\u043D\u0430\u0442\u0438"
+ cuddleandtea:
+ - rscadd: players can trim grass with wirecutters
+ emmanuelbassil:
+ - bugfix: Fixes being unable to place pills into beakers placed on a table/turf
+ - bugfix: Fixes duct-taped and sticky papers not properly offsetting
+ - bugfix: Can now use welders to fix external dents on IPCs on an operating table
+ - bugfix: Fixed oversight where you could not use swappers for surgical steps.
+ - bugfix: Fixed oversight where welding tool lit up and damage your eyes even if
+ no actual surgical step occured.
+ uedhighcommand:
+ - tweak: "\u041F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u043F\u0435\u0440\
+ \u0435\u0432\u0435\u0434\u0435\u043D \u0440\u0435\u0434\u0430\u043A\u0442\u043E\
+ \u0440 \u0431\u044D\u043A\u0433\u0440\u0430\u0443\u043D\u0434\u0430 \u043F\u0435\
+ \u0440\u0441\u043E\u043D\u0430\u0436\u0430"
+ - rscadd: "\u041F\u0435\u0440\u0435\u043D\u0435\u0441\u0435\u043D\u044B \u043A\u0443\
+ \u043B\u044C\u0442\u0443\u0440\u044B, \u043C\u0435\u0441\u0442\u0430 \u043F\u0440\
+ \u043E\u0436\u0438\u0432\u0430\u043D\u0438\u044F \u0438 \u0444\u0440\u0430\u043A\
+ \u0446\u0438\u0438 \u0441 Infinity"
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043D\u043E\u0432\
+ \u044B\u0435 \u044F\u0437\u044B\u043A\u0438: \u043C\u0438\u0440\u0430\u043D\u0438\
+ \u0441\u043A\u0438\u0439, \u0430\u0432\u0430\u043B\u043E\u043D\u0441\u043A\u0438\
+ \u0439, \u043B\u043E\u0440\u0440\u0438\u043C\u0430\u043D\u0441\u043A\u0438\u0439"
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u043D\u043E\u0432\
+ \u0430\u044F \u043E\u0431\u043B\u043E\u0436\u043A\u0430 \u0434\u043B\u044F \u043C\
+ \u0438\u0440\u0430\u043D\u0438\u0439\u0441\u043A\u043E\u0433\u043E \u043F\u0430\
+ \u0441\u043F\u043E\u0440\u0442\u0430, \u0443\u043B\u0443\u0447\u0448\u0435\u043D\
+ \u043E \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0430\u0432\u0430\u043B\
+ \u043E\u043D\u0441\u043A\u043E\u0433\u043E \u043F\u0430\u0441\u043F\u043E\u0440\
+ \u0442\u0430, \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u043E\u0442\
+ \u0434\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u0430\u0441\u043F\u043E\u0440\
+ \u0442\u0430 \u0434\u043B\u044F \u0444\u0440\u0430\u043A\u0446\u0438\u0439 \u0443\
+ \u043D\u0430\u0442\u0438"
+ - bugfix: "\u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430 \u043D\u0435\
+ \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u044C \u0432\u0437\
+ \u044F\u0442\u044C \u043F\u0430\u0441\u043F\u043E\u0440\u0442 \u0443\u043D\u0430\
+ \u0442\u0438 \u043F\u0435\u0440\u0441\u043E\u043D\u0430\u0436\u0430\u043C\u0438\
+ \ \u0439\u043E\u0437\u0430'\u0443\u043D\u0430\u0442\u0438"
+2023-11-25:
+ KandJX:
+ - maptweak: "\u0417\u043D\u0430\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435\
+ \ \u043C\u0430\u043F\u0444\u0438\u043A\u0441\u044B."
+ - tweak: "\u0413\u041F \u0438\u043C\u0435\u0435\u0442 \u043F\u043E\u043B\u043D\u044B\
+ \u0439 \u0434\u043E\u0441\u0442\u0443\u043F, \u043A\u0440\u043E\u043C\u0435\
+ \ \u0410\u0412\u0414, \u041A\u0430\u043F\u0438\u0442\u0430\u043D\u0430 \u0438\
+ \ \u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0445 \u0414\u0435\u043B\
+ ."
+ - tweak: "\u0412\u044B\u0434\u0430\u043D \u0434\u043E\u0441\u0442\u0443\u043F Research\
+ \ \u0432\u0441\u0435\u043C\u0443 \u042D\u041A."
+ - tweak: "\u0412\u044B\u0434\u0430\u043D \u0434\u043E\u0441\u0442\u0443\u043F Research\
+ \ Storage \u0432\u0441\u0435\u043C\u0443 \u043E\u0442\u0434\u0435\u043B\u0443\
+ \ \u0420\u043D\u0414. \u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\
+ \u0430 \u0434\u043E\u0441\u0442\u0443\u043F\u044B \u0432 \u043E\u0444\u0438\u0441\
+ \ \u0438 \u0440\u0430\u0437\u0434\u0435\u0432\u0430\u043B\u043A\u0443."
+ - tweak: "\u0412\u044B\u0434\u0430\u043D \u0434\u043E\u0441\u0442\u0443\u043F EXTERNAL\
+ \ \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u043E\u043D\u043D\u043E\
+ \u043C\u0443 \u0442\u0435\u0445\u043D\u0438\u043A\u0443"
+ - maptweak: "\u0414\u043E\u0441\u0442\u0443\u043F \u043A \u0438\u043D\u0436\u0435\
+ \u043D\u0435\u0440\u043D\u043E\u043C\u0443 \u043A\u043E\u0440\u0438\u0434\u043E\
+ \u0440\u0443 \u0443\u0431\u0440\u0430\u043D"
+ - maptweak: "\u0414\u043E\u0441\u0442\u0443\u043F \u043A \u043A\u043E\u0440\u0438\
+ \u0434\u043E\u0440\u0443 \u0432\u043E\u043A\u0440\u0443\u0433 \u0447\u0435\u043A\
+ \u043F\u043E\u0439\u043D\u0430 \u0421\u0411 4\u043E\u0439 \u043F\u0430\u043B\
+ \u0443\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0435\u043D \u0441 \u042D\u041A\
+ \u0421\u0422\u0415\u0420\u041D\u0410\u041B, \u043D\u0430 (\u0421\u0411, \u041C\
+ \u0415\u0414, \u0418\u041D\u0416)."
+ - maptweak: "\u041E\u0445\u043B\u0430\u0436\u0434\u0435\u043D\u0438\u0435 \u0421\
+ \u041C \u0442\u0435\u043F\u0435\u0440\u044C \u0431\u0443\u0434\u0435\u0442 \u0440\
+ \u0430\u0431\u043E\u0442\u0430\u0442\u044C. \u041E\u0434\u043D\u0430\u043A\u043E\
+ \ \u0432\u0441\u0435 \u0440\u0430\u0432\u043D\u043E \u0436\u0434\u0451\u043C\
+ \ /turf/space/open \u043E\u0442 @sprtn"
+2023-11-26:
+ Kam_Survivor:
+ - bugfix: "Backswept \u0438 Ponytail 7 (alt) \u0442\u0435\u043F\u0435\u0440\u044C\
+ \ \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0434\u043B\u044F \u0432\
+ \u044B\u0431\u043E\u0440\u0430"
+ SuhEugene:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0443\u043C\u043D\u043E\u0436\
+ \u0435\u043D\u0438\u0435 \u0437\u0430\u0434\u0435\u0440\u0436\u043A\u0438 \u043D\
+ \u0430 \u221A2 \u0434\u043B\u044F \u0434\u0438\u0430\u0433\u043E\u043D\u0430\
+ \u043B\u044C\u043D\u043E\u0433\u043E \u0434\u0432\u0438\u0436\u0435\u043D\u0438\
+ \u044F. \u0422\u0435\u043F\u0435\u0440\u044C \u043E\u043D\u043E \u043D\u0435\
+ \ \u0431\u0443\u0434\u0435\u0442 \u043E\u0449\u0443\u0449\u0430\u0442\u044C\u0441\
+ \u044F \u0431\u044B\u0441\u0442\u0440\u044B\u043C \u0438 \u043D\u0435\u043F\u0440\
+ \u0430\u0432\u0438\u043B\u044C\u043D\u044B\u043C."
+ - experiment: "\u0423\u0431\u0440\u0430\u043B \u043D\u0435\u043F\u043B\u043E\u0445\
+ \u043E \u0436\u0440\u0443\u0449\u0438\u0439 \u0440\u0435\u0441\u0443\u0440\u0441\
+ \u044B \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0446\u0438\u043A\u043B.\
+ \ \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u044B \u0431\u0430\u0433\u0438\
+ \ \u0441 \u0430\u043D\u0438\u043C\u0430\u0446\u0438\u0435\u0439 \u0434\u0432\
+ \u0438\u0436\u0435\u043D\u0438\u044F \u0432\u044B\u043D\u0443\u0442\u044B\u0445\
+ \ \u0438\u0437 \u043A\u0430\u0440\u043C\u0430\u043D\u043E\u0432, \u043F\u043E\
+ \u0440\u0442\u0444\u0435\u043B\u044F \u0438 \u0438\u043D\u044B\u0445 \u043C\u0435\
+ \u0441\u0442 \u043F\u0440\u0435\u0434\u043C\u0435\u0442\u043E\u0432."
+ - bugfix: "\u0421\u0434\u0435\u043B\u0430\u043B \u0442\u0430\u043A, \u0447\u0442\
+ \u043E\u0431\u044B \u043F\u0440\u0435\u0434\u043C\u0435\u0442\u044B \u043D\u0430\
+ \ \u043A\u0430\u0440\u0433\u043E-\u0442\u0435\u043B\u0435\u0436\u043A\u0435\
+ \ \u0434\u0432\u0438\u0433\u0430\u043B\u0438\u0441\u044C \u0441 \u0442\u043E\
+ \u0439 \u0436\u0435 \u0441\u043A\u043E\u0440\u043E\u0441\u0442\u044C\u044E,\
+ \ \u0447\u0442\u043E \u0438 \u0442\u0435\u043B\u0435\u0436\u043A\u0430. \u042D\
+ \u0442\u043E \u0442\u0430, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u043E\
+ \u0439 \u0438\u043D\u0436\u0435\u043D\u0435\u0440\u044B \u0432\u043E\u0434\u043E\
+ \u0440\u043E\u0434 \u0434\u043B\u044F \u0440\u0430\u0441\u0442\u0430 \u0438\
+ \ \u043C\u0430\u043C\u043A\u0438 \u0432\u043E\u0437\u044F\u0442."
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0441\u043E\u0445\u0440\
+ \u0430\u043D\u0435\u043D\u0438\u0435 \u043F\u0435\u0440\u0441\u043E\u043D\u0430\
+ \u0436\u0435\u0439"
+ - tweak: "\u0422\u0435\u043F\u0435\u0440\u044C (\u043D\u0430\u0434\u0435\u044E\u0441\
+ \u044C) \u0430\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u043E \u0432\u0441\u0435\
+ \ \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0435 \u0437\u0430\u043F\u0438\
+ \u0441\u0438 \u0432 \u0444\u0430\u0439\u043B \u0438\u0441\u043F\u043E\u043B\u044C\
+ \u0437\u0443\u044E\u0442 RustG"
+ - tweak: "\u041E\u0431\u043D\u043E\u0432\u0438\u043B DLL RustG \u0434\u043E \u0442\
+ \u0440\u0435\u0442\u044C\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, \u0447\
+ \u0442\u043E\u0431\u044B \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\
+ \u0435\u0440\u0432\u0435\u0440 \u0440\u0430\u0431\u043E\u0442\u0430\u043B, \u043D\
+ \u043E \u0438 \u0432\u0430\u0448\u0430 \u043B\u043E\u043A\u0430\u043B\u043A\u0430\
+ \ \u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\
+ \u044C\u0441\u044F \u0431\u0435\u0437 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\
+ ."
+2023-11-27:
+ BurpleBineapple:
+ - tweak: EMP damage is now calculated with a 1 to 3 multiplicative modifier with
+ 3 being the pre-change damage.
+ - tweak: The ion pistol now has half the fire delay it had before to differentiate
+ it from the ion rifle.
+ - tweak: EMP damage to limbs has been reduced by 50%. Torso damage remains the same.
+ - tweak: EMP damage to robotic legs and feet no longer immediately weaken the victim.
+ - tweak: FBPs and IPCs first fall over before being paralyzed when their battery
+ is dead or destroyed.
+2023-11-28:
+ BurpleBineapple:
+ - bugfix: Prosthetic bone repair now checks for Complex Devices instead of Medicine/Anatomy
+ LordNest:
+ - bugfix: "\u0422\u0435\u043F\u0435\u0440\u044C \u0422\u041E\u041A\u0410\u041C\u0410\
+ \u041A \u0413\u041A\u041A \u043D\u0435 \u0434\u043E\u043B\u0436\u0435\u043D\
+ \ \u043F\u0435\u0440\u0435\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0442\
+ \u044C\u0441\u044F \u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0435 \u0440\
+ \u0430\u0431\u043E\u0442\u044B"
+ - maptweak: "\u0422\u0440\u0430\u0432\u0430 \u0442\u0435\u043F\u0435\u0440\u044C\
+ \ \u043A\u0440\u0430\u0441\u0438\u0432\u0430\u044F, \u0443 \u0413\u041A\u041A\
+ \ \u043F\u043E\u0447\u0438\u043D\u0438\u043B\u0438\u0441\u044C \u0442\u0440\u0443\
+ \u0431\u044B"
+ TheNightingale:
+ - tweak: Certain programs - computer config, email, file browser and download tool
+ - now require less processing power to run, allowing more programs to be run
+ consecutively.
+ - tweak: Email now requires 1 GB instead of _7_.
+ rootoo807:
+ - tweak: Flowers grown in hydroponics can be made into wearable hair pins by adding
+ a piece of cable.
+ - tweak: Hairflowers are now color select in loadout.
+2023-12-01:
+ Spookerton:
+ - maptweak: Removed the free OFD charges from the mercenary ship.
+ - tweak: Added the "Structures & Vehicles" uplink category, containing 80tc OFD
+ charges for mercenaries.
+2023-12-02:
+ UEDHighCommand:
+ - rscadd: "\u0412 \u043B\u043E\u0434\u0430\u0443\u0442 \u0434\u043E\u0431\u0430\u0432\
+ \u043B\u0435\u043D\u044B \u043E\u043A\u0440\u0430\u0448\u0438\u0432\u0430\u0435\
+ \u043C\u044B\u0435 \u043F\u043B\u0430\u0449\u0438 \u0438 \u043F\u043B\u0430\u0449\
+ \u0438 \u043E\u0442\u0434\u0435\u043B\u043E\u0432. \u041F\u043B\u0430\u0449\u0438\
+ \ \u043A\u0440\u0435\u043F\u044F\u0442\u0441\u044F \u043A \u0441\u043A\u0430\
+ \u0444\u0430\u043D\u0434\u0440\u0430\u043C \u0432 \u043A\u0430\u0447\u0435\u0441\
+ \u0442\u0432\u0435 \u0430\u043A\u043A\u0441\u0435\u0441\u0443\u0430\u0440\u0430\
+ ;"
+ - rscadd: "\u0412 \u043B\u043E\u0434\u0430\u0443\u0442 \u0434\u043E\u0431\u0430\u0432\
+ \u043B\u0435\u043D\u044B \u043F\u0435\u0440\u0435\u043A\u0440\u0430\u0448\u0438\
+ \u0432\u0430\u0435\u043C\u044B\u0435 \u043E\u0431\u043C\u043E\u0442\u043A\u0438\
+ \ \u0438 \u0441\u0430\u043D\u0434\u0430\u043B\u0438 \u0434\u043B\u044F \u0443\
+ \u043D\u0430\u0442\u0438."
+2023-12-03:
+ LordNest:
+ - bugfix: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u043F\u0440\u043E\u043F\u0430\
+ \u0434\u0430\u0432\u0448\u0438\u0435 \u0442\u0435\u043A\u0441\u0442\u0443\u0440\
+ \u044B \u0438 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\
+ \u0442\u044C \u0431\u0440\u0430\u0442\u044C \u043F\u0440\u043E\u0442\u0435\u0437\
+ \u044B \u0442\u0430\u044F\u0440\u0430\u043C"
+ - tweak: "\u041F\u043E\u0434\u0440\u0435\u0434\u0430\u0447\u0438\u043B \u0441\u043B\
+ \u0435\u0433\u043A\u0430 \u0446\u0438\u0444\u0440\u044B \u043A\u043E\u0442\u0430\
+ \u043C, \u043D\u0438\u0447\u0435\u0433\u043E \u043A\u0440\u0438\u043C\u0438\u043D\
+ \u0430\u043B\u044C\u043D\u043E\u0433\u043E (\u0432\u043E\u0437\u0440\u0430\u0441\
+ \u0442)"
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043D\u043E\u0432\u044B\u0435\
+ \ \u043C\u0430\u0440\u043A\u0438 \u043F\u0440\u043E\u0442\u0435\u0437\u043E\u0432\
+ \ \u0434\u043B\u044F \u043A\u043E\u0442\u043E\u0432"
+ - imageadd: "\u0412\u0435\u0440\u043D\u0443\u043B \u043F\u043B\u0430\u043A\u0430\
+ \u0442\u044B \u043A\u043E\u0442\u0438\u043A\u043E\u0432 \u0438 \u0447\u0435\u0441\
+ \u0442\u043D\u043E \u043D\u0430\u0432\u043E\u0440\u043E\u0432\u0430\u043B \u043F\
+ \u0440\u043E\u0442\u0435\u0437\u043E\u0432"
+2023-12-04:
+ LordNest:
+ - tweak: "\u0418\u0441\u043F\u0440\u0430\u0432\u0438\u043B \u043E\u0442\u0441\u0443\
+ \u0442\u0441\u0442\u0432\u0438\u0435 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\
+ \u044B\u0445 \u0432\u0435\u0449\u0435\u0439 \u0432 \u043B\u043E\u0434\u0430\u0443\
+ \u0442\u0435"
+ - rscadd: "\u0413\u043E\u043B\u043E\u0431\u043E\u043C\u0431\u044B \u0432\u0435\u0440\
+ \u043D\u0443\u043B"
+ - maptweak: "\u0418\u0441\u043F\u0440\u0430\u0432\u0438\u043B \u0440\u044F\u0434\
+ \ \u043C\u0435\u043B\u043A\u0438\u0445 \u043E\u0448\u0438\u0431\u043E\u043A\
+ \ \u043A\u0430\u0440\u0442\u044B"
+2023-12-05:
+ BurpleBineapple:
+ - rscadd: Added toggleable NRC notifications for join, leave, and message events.
+ - tweak: The NRC program is 2GQ and installed on PDAs by default.
+ - bugfix: Held computers can connect to NRC channels properly.
+ Lexanx:
+ - tweak: IPC parts are colorable by using markings.
+ - rscadd: IPCs have some head accessories as hair.
+ - rscadd: IPCs using heads with screens can display text on the screen.
+ Mucker:
+ - rscadd: Re-added borg all access.
+ - rscdel: Removed e-guns from traitor borgs.
+ - tweak: Slightly lowered the regen rate of flamer fuel for traitor borgs.
+ Rockton:
+ - imageadd: New placeholder sprite for the SFV Lexington overmap icon
+ Sbotkin:
+ - maptweak: The canister storage now can be accessed by scientists via the Petrov's
+ maints.
+ - maptweak: Scientists now have access to Chlorine!
+ gy1ta23:
+ - tweak: Torch pilot helmets are a selection box instead of all pre-spawned.
+2023-12-08:
+ Sbotkin:
+ - tweak: Cobby's gender has been discovered, it's a boy!
+2023-12-10:
+ Mucker:
+ - bugfix: Fixed shortwaves having no 'power' button
+2023-12-11:
+ SuhEugene:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0440\u0430\u0431\u043E\
+ \u0442\u043E\u0441\u043F\u043E\u0441\u043E\u0431\u043D\u043E\u0441\u0442\u044C\
+ \ \u0434\u043E\u043D\u0430\u0442\u043E\u0432"
+ - tweak: "\u041D\u0435\u043C\u043D\u043E\u0433\u043E \u0438\u0437\u043C\u0435\u043D\
+ \u0438\u043B \u0432\u043D\u0435\u0448\u043D\u0438\u0439 \u0432\u0438\u0434 \u0442\
+ \u0438\u0440\u043E\u0432 \u0432 \u043B\u043E\u0434\u0430\u0443\u0442\u0435"
+ - rscadd: "\u0423 \u043B\u044E\u0434\u0435\u0439 \u0441 \u0434\u043E\u043D\u0430\
+ \u0442\u0430\u043C\u0438 \u0442\u0435\u043F\u0435\u0440\u044C \u0446\u0432\u0435\
+ \u0442\u043D\u044B\u0435 \u043D\u0438\u043A\u0438. \u041C\u043E\u0436\u043D\u043E\
+ \ \u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0432 \u043F\u0440\
+ \u0435\u0444\u0435\u0440\u0435\u043D\u0441\u0430\u0445."
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0430\u043D\u0438\u043C\
+ \u0430\u0446\u0438\u044E \u043F\u0435\u0440\u0435\u043C\u0435\u0449\u0435\u043D\
+ \u0438\u044F \u0441 \u0442\u0430\u0439\u043B\u0430 \u043D\u0430 \u0442\u0430\
+ \u0439\u043B \u0434\u043B\u044F \u0433\u0440\u0430\u0431\u043E\u0432, \u043F\
+ \u0443\u043B\u043B\u043E\u0432, \u0430 \u0442\u0430\u043A\u0436\u0435 \u0434\
+ \u043B\u044F \u0441\u0438\u0434\u044F\u0449\u0438\u0445 \u0438 \u043B\u0435\u0436\
+ \u0430\u0449\u0438\u0445 \u043D\u0430 \u0447\u0451\u043C-\u0442\u043E \u043C\
+ \u043E\u0431\u043E\u0432."
+ - bugfix: "\u0417\u0430\u043A\u0440\u044B\u0442\u0438\u0435 \u0444\u0443\u043B\u043B\
+ \u0441\u043A\u0440\u0438\u043D\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\
+ \u0435 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0431\u0435\
+ \u043B\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441\u0431\u0430\u0440 \u0431\
+ \u044C\u0451\u043D\u0434\u0430."
+2023-12-14:
+ Al-1ce:
+ - spellcheck: Changes a poor-taste hallucination message.
+ Merlin1230:
+ - tweak: Research tape is now purple instead of white. Enjoy.
+2023-12-17:
+ emmanuelbassil:
+ - bugfix: Fixes C4/Plastique. It now explodes again.
+ - bugfix: Fixes runtime when blob attacks people
+ - bugfix: Fixes memorial stone causing dogtags from other branches to disappear
+ - bugfix: Fixes helm and sensor consoles not deleting properly
+ - bugfix: Fixes welding step of APC deconstruction
+2023-12-18:
+ Al-1ce:
+ - tweak: Adds a bunch of mixing descriptions to drinks that lacked them.
+ - tweak: Changes some of the pre-existing mixing descriptions.
+ emmanuelbassil:
+ - bugfix: Fixes oversight where some storage items that could collect all items
+ off a turf could not have that mode toggled off.
+ - bugfix: Fixes being unable to use pill bottles on a storage object or reagent
+ container
+ - tweak: Removed behavior where clicking on a storage item on the floor with another
+ storage item sometimes led to picking up the item off the floor.
+ - tweak: Can now quickly empty pill bottles and the excavation pick set.
+2023-12-20:
+ BurpleBineapple:
+ - tweak: Updates the description blurb for Magnitka to use the wiki's updated information.
+ Vex8tion:
+ - rscadd: Repurposed a few icons into a new path for gukhe
+ emmanuelbassil:
+ - tweak: Adds broken glass overlay for destroyed floor light
+ - bugfix: Unanchored floor light no longer spawns with light on
+ - bugfix: Floorlight is now destroyed properly when affected by an explosion
+2023-12-22:
+ SierraKomodo:
+ - bugfix: Fixes robot analyzers not working on mechs.
+ - bugfix: Holster belts now properly holster items when clicked.
+ - bugfix: Emergency shutter assemblies now properly accept wires and can be completed.
+ - bugfix: Fixes IPC antennae rendering full-black instead of having their intended
+ colors.
+ - rscadd: Adds a `None` option to IPC hairs.
+ emmanuelbassil:
+ - soundadd: Added sounds when wrapping and unwrapping pacakges.
+ - tweak: Using a labeler on a wrapped package/mob now updates the sprite with a
+ label.
+ - tweak: Can now use destination taggers on wrapped mobs and ship them through disposals.
+ - tweak: Damage applied to wrapped mobs now applies to the underlying mob. Wrapped
+ mobs/packages are destructible.
+ - bugfix: Fixed oversight where punching a structure led to the attack happening
+ twice.
+2023-12-23:
+ UEDHighCommand:
+ - rscadd: "\u0412\u0432\u0435\u0434\u0435\u043D \u0444\u0443\u043D\u043A\u0446\u0438\
+ \u043E\u043D\u0430\u043B \u0434\u043B\u044F \u0444\u0440\u0430\u043A\u0446\u0438\
+ \u043E\u043D\u043D\u044B\u0445 \u043F\u0440\u0435\u0434\u043C\u0435\u0442\u043E\
+ \u0432 \u0432 \u043B\u043E\u0434\u0430\u0443\u0442\u0435."
+ - rscadd: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0440\
+ \u0430\u043D\u0433\u043E\u0432\u044B\u0435 \u043F\u0430\u0442\u0447\u0438 \u042D\
+ \u041A \u0426\u041F\u0421\u0421."
+2023-12-24:
+ LordNest:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0448\u0435\u043D\u044B \u043E\u0442\u0441\
+ \u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u0438\u043A\u043E\
+ \u043D\u043A\u0438 \u0442\u0430\u044F\u0440\u0441\u043A\u0438\u0445 \u043F\u043E\
+ \u0441\u0442\u0435\u0440\u043E\u0432. \u041F\u043E\u0444\u0438\u043A\u0448\u0435\
+ \u043D\u0430 \u0438\u043A\u043E\u043D\u043A\u0430 \u043A\u0430\u043F\u0438\u0442\
+ \u0430\u043D\u0441\u043A\u043E\u0433\u043E \u0440\u0435\u0432\u043E\u043B\u044C\
+ \u0432\u0435\u0440\u0430."
+ - tweak: "\u0413\u0411\u0421 \u0438 \u0410\u0434\u0445\u0435\u0440\u0430\u043D\u0442\
+ \u044B \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u0438 \u0432\u043E\u0437\u043C\
+ \u043E\u0436\u043D\u043E\u0441\u0442\u044C \u0431\u044B\u0442\u044C \u0441\u0442\
+ \u044E\u0430\u0440\u0434\u0430\u043C\u0438"
+ - imageadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u044D\u043A\u0438\u043F\
+ \u0438\u0440\u043E\u0432\u043A\u0443 \u0438\u0437 \u0421\u0438\u043D\u0435\u0439\
+ \ \u0441\u043C\u0435\u043D\u044B."
+ Mucker:
+ - rscadd: Added a major version of the 'Rogue Drone' event. Includes Hullbreakers.
+ Spookerton:
+ - tweak: Rapid service fabricators get correct unit amounts from RCD cartridges.
+ - rscdel: The OFD can't launch closets.
+ TobyThorne:
+ - bugfix: Bloodied shoeless feet overlays use the correct colors.
+ emmanuelbassil:
+ - tweak: Rapid service fabricator now uses radial menus
+ - bugfix: Rapid service fabricator properly creates items now.
+ - bugfix: Using wirecutters/multitool on a door with an exposed panel no longer
+ opens the door.
+2023-12-26:
+ KandJX:
+ - maptweak: "\u041C\u0435\u043B\u043A\u043E\u0444\u0438\u043A\u0441\u044B \u043A\
+ \u0430\u0440\u0442\u044B"
+ SuhEugene:
+ - imageadd: Added some new posters.
+ emmanuelbassil:
+ - bugfix: Fixes mounted hardsuit scanners, RCD, and defibrillator. They now work
+ when you click on target using your preferred key combination
+2023-12-27:
+ emmanuelbassil:
+ - tweak: Microwaves no longer accept solutions that contain forbidden ingredients.
+ Feedback now given on which ingredient is incompatible.
+ - bugfix: Un/Re-Anchoring a machine now properly updates machine's power usage.
+2023-12-28:
+ LordNest:
+ - bugfix: "\u0423\u0431\u0440\u0430\u043B \u0432\u043E\u0437\u043C\u043E\u0436\u043D\
+ \u043E\u0441\u0442\u044C \u0431\u044B\u0442\u044C \u043C\u0435\u0440\u0447\u0430\
+ \u043D\u0442\u0430\u043C\u0438 \u0443 \u0413\u0411\u0421\u043E\u0432 \u0438\
+ \ \u0410\u0434\u0445\u0435\u0440\u0430\u043D\u0442\u043E\u0432, \u0442\u0430\
+ \u043A \u043A\u0430\u043A \u0442\u0435 \u043D\u0435 \u043F\u043E\u043D\u0438\
+ \u043C\u0430\u044E\u0442, \u043A\u0430\u043A \u0440\u0430\u0431\u043E\u0442\u0430\
+ \u0435\u0442 \u0440\u044B\u043D\u043E\u043A."
+ - tweak: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043E\u0444\u0438\u0446\u0435\
+ \u0440\u0430\u043C \u0432 \u0448\u043A\u0430\u0444\u044B \u0442\u0430\u0437\u0435\
+ \u0440\u044B."
+ - rscadd: "\u0412\u0435\u0440\u043D\u0443\u043B \u043F\u043E\u0442\u0435\u0440\u044F\
+ \u043D\u043D\u044B\u0435 \u043E\u0447\u043A\u0438 \u0430\u0439\u0442\u0438\u0448\
+ \u043D\u0438\u043A\u0430"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043D\u0430 \u043A\u0430\
+ \u0440\u0442\u0443 \u043E\u0447\u043A\u0438, \u0434\u043E\u0431\u0430\u0432\u0438\
+ \u043B \u043D\u0430 \u043A\u0430\u0440\u0442\u0443 \u043D\u043E\u0432\u044B\u0439\
+ \ \u044F\u0449\u0438\u043A \u0432 \u0447\u0435\u043A\u043F\u043E\u0438\u043D\
+ \u0442 \u0420\u043D\u0414."
+ - balance: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0413\u041A\u041A\u0448\u043D\
+ \u043E\u043C\u0443 \u043D\u0430\u0431\u043E\u0440\u0443 \u043D\u0438\u043D\u0434\
+ \u0437\u0438 \u043F\u0430\u0432\u0435\u0440\u0444\u0438\u0441\u0442 \u043A\u0430\
+ \u043A \u043C\u043E\u0434\u0443\u043B\u044C. \u0415\u0433\u043E \u043D\u0443\
+ \u0436\u043D\u043E \u0437\u0430\u043F\u0438\u0445\u043D\u0443\u0442\u044C \u0432\
+ \ \u0418\u041A\u0421."
+ SuhEugene:
+ - bugfix: "\u041D\u0410\u0414\u0415\u042E\u0421\u042C \u043F\u043E\u0444\u0438\u043A\
+ \u0441\u0438\u043B \u0434\u043E\u043D\u0430\u0442\u044B \u043E\u043A\u043E\u043D\
+ \u0447\u0430\u0442\u0435\u043B\u044C\u043D\u043E."
+2023-12-29:
+ KandJX:
+ - maptweak: "\u0420\u0435\u043C\u0430\u043F \u0413\u0423\u041F\u041F\u0418"
+ - maptweak: "\u0420\u0435\u043C\u0430\u043F \u043E\u0431\u0441\u0435\u0440\u0432\
+ \u0430\u0442\u043E\u0440\u0438\u0438"
+ - maptweak: "\u041C\u0435\u043B\u043A\u043E\u0444\u0438\u043A\u0441\u044B \u043A\
+ \u0430\u0440\u0442\u044B"
+ SuhEugene:
+ - bugfix: "\u0414\u043E\u043D\u0430\u0442\u044B \u043E\u0434\u043D\u043E\u0437\u043D\
+ \u0430\u0447\u043D\u043E \u0442\u0435\u043F\u0435\u0440\u044C \u0434\u043E\u043B\
+ \u0436\u043D\u044B \u0437\u0430\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C\
+ ."
+ - tweak: "\u0412\u044B\u0434\u0430\u043B \u0430\u0434\u043C\u0438\u043D\u0438\u0441\
+ \u0442\u0440\u0430\u0442\u043E\u0440\u0430\u043C \u0431\u0435\u0441\u043F\u043B\
+ \u0430\u0442\u043D\u044B\u0439 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\
+ \u044C\u043D\u044B\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0434\u043E\
+ \u043D\u0430\u0442\u0430 (\u0434\u0430\u0436\u0435 \u0432\u044B\u0448\u0435\
+ \ \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0433\u043E\
+ )."
+2023-12-30:
+ Mucker:
+ - tweak: Cult members can no longer summon Nar-Sie offship or on shuttles.
+ - bugfix: Fixed organ printers not being able to take steel.
+ UEDHighCommand:
+ - bugfix: "\u041F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u044B \u043D\u0435\
+ \u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 \u043D\u0430\u0437\u0432\
+ \u0430\u043D\u0438\u044F \u043A\u043E\u0440\u043F\u043E\u0440\u0430\u0446\u0438\
+ \u0439 \u0443 \u0441\u043A\u0440\u0435\u043B\u043B\u043E\u0432"
+ - tweak: "\u042F\u0437\u044B\u043A \u0441\u0438\u043D\u0442\u0430 \u0431\u043E\u043B\
+ \u0435\u0435 \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u043E\
+ \u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0434\u043B\
+ \u044F \u0443\u043D\u0430\u0442\u0438 \u0441 \u043A\u0443\u043B\u044C\u0442\u0443\
+ \u0440\u043E\u0439 \u0422\u0435\u0440\u0441\u0442\u0435\u043D\u0430; \u0432\
+ \ \u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\
+ \u0435 \u044F\u0437\u044B\u043A\u0438 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\
+ \u043D \u0438\u0431\u0435\u0440\u0438\u0439\u0441\u043A\u0438\u0439"
+ - rscdel: "\u0423\u0434\u0430\u043B\u0435\u043D\u044B \u0440\u0430\u043D\u0433\u0438\
+ \ \u042D\u041A \u041E-3 \u0438 E-7 \u0432\u0432\u0438\u0434\u0443 \u041D\u0422\
+ \u0448\u043D\u043E\u0439 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\
+ \u043D\u043E\u0441\u0442\u0438 \u041B\u042D\u041A\u0430"
+ - bugfix: "\u0423\u0434\u0430\u043B\u0435\u043D \u0434\u0443\u0431\u043B\u0438\u043A\
+ \u0430\u0442 \u043A\u043E\u0440\u043F\u043E\u0440\u0430\u0446\u0438\u0438 \u041A\
+ \u0440\u0440\u0438'\u0433\u043B\u0438 \u0443 \u0441\u043A\u0440\u0435\u043B\u043B\
+ \u043E\u0432"
+2024-01-03:
+ LordNest:
+ - bugfix: "\u0422\u0435\u043F\u0435\u0440\u044C \u043D\u0430 \u0441\u0435\u043D\u0441\
+ \u043E\u0440\u0430\u0445 \u0438 \u043A\u0430\u043C\u0435\u0440\u0430\u0445 \u0421\
+ \u044C\u0435\u0440\u0440\u0430 \u043D\u0435 \u0443\u0435\u0437\u0436\u0430\u0435\
+ \u0442 \u0434\u0430\u043B\u0435\u043A\u043E \u0432\u043B\u0435\u0432\u043E-\u0432\
+ \u0432\u0435\u0440\u0445"
+ - tweak: "\u0418\u0441\u043F\u0440\u0430\u0432\u0438\u043B \u043F\u0430\u0440\u0443\
+ \ \u043C\u0435\u043B\u043A\u0438\u0445 \u0431\u0430\u0433\u043E\u0432"
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043A\u043E\u0440\u0441\u0435\
+ \u0442\u044B \u0438 \u0447\u043E\u043A\u0435\u0440"
+ - imageadd: "\u0418\u0437\u043C\u0435\u043D\u0438\u043B \u0445\u0440\u0430\u043D\
+ \u0438\u043B\u0438\u0449\u0430 \u0441\u043A\u0430\u0444\u0430\u043D\u0434\u0440\
+ \u043E\u0432, \u0432\u0435\u0440\u043D\u0443\u043B \u043F\u043E\u0442\u0435\u0440\
+ \u044F\u043D\u043D\u044B\u0435 \u0442\u0430\u044F\u0440\u0441\u043A\u0438\u0435\
+ \ \u043F\u043B\u0430\u0449\u0438"
+ - maptweak: "\u0422\u0435\u043F\u0435\u0440\u044C \u0432\u0441\u0435 \u043A\u0440\
+ \u0438\u043E\u043A\u0430\u043F\u0441\u0443\u043B\u044B \u0438\u043C\u0435\u044E\
+ \u0442 \u0437\u0430\u0449\u0438\u0442\u0443 \u043E\u0442 \u0440\u0430\u0434\u0438\
+ \u0430\u0446\u0438\u0438"
+2024-01-04:
+ UEDHighCommand:
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u0432\u0430\u0440\
+ \u0438\u0430\u043D\u0442\u044B \u0444\u043E\u0440\u043C\u044B \u042D\u041A"
+ - imageadd: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D \u0441\
+ \u043F\u0440\u0430\u0439\u0442 \u0436\u0438\u043B\u0435\u0442\u043A\u0438 \u043C\
+ \u0435\u0434\u0438\u043A\u0430 \u0431\u044B\u0441\u0442\u0440\u043E\u0433\u043E\
+ \ \u0440\u0435\u0430\u0433\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0441\
+ \ \u0418\u043D\u0444\u0438\u043D\u0438\u0442\u0438 \u0432 \u043A\u0430\u0447\
+ \u0435\u0441\u0442\u0432\u0435 \u043D\u043E\u0432\u043E\u0433\u043E \u043F\u0440\
+ \u0435\u0434\u043C\u0435\u0442\u0430"
+ - rscadd: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0432\
+ \u0441\u0435 \u043E\u0441\u043E\u0431\u044B\u0435 \u043A\u043E\u0441\u0442\u044E\
+ \u043C\u044B \u0443\u043D\u0430\u0442\u0438 \u0438\u0437 \u043B\u043E\u0434\u0430\
+ \u0443\u0442\u0430 \u0418\u043D\u0444\u0438\u043D\u0438\u0442\u0438 - \u0444\
+ \u043E\u0440\u043C\u0430, \u0436\u0438\u043B\u0435\u0442\u043A\u0430 \u0438\
+ \ \u043A\u0435\u043F\u043A\u0430 \u041E\u0417\u0410; \u0444\u043E\u0440\u043C\
+ \u0430 \u043F\u0430\u0440\u0430\u043C\u0435\u0434\u0438\u043A\u0430; \u043F\u0443\
+ \u0441\u0442\u044B\u043D\u043D\u0430\u044F \u0444\u043E\u0440\u043C\u0430; \u043F\
+ \u0443\u0441\u0442\u044B\u043D\u043D\u0430\u044F \u0448\u043B\u044F\u043F\u0430"
+2024-01-06:
+ LordNest:
+ - bugfix: Fixed some missing sprites
+2024-01-07:
+ Mucker:
+ - tweak: Removed Bearcat's holopad and distress beacon. Added a radio beacon circuit
+ board and a RCD.
+ - tweak: Reduced Survival Pod's spawn rate so Exploration isn't constantly stuck
+ doing rescues.
+2024-01-08:
+ LordNest:
+ - maptweak: Added some new templates for exoplanets
+ - bugfix: Fixed skrellian pistol sprite and sandstone wall sprite
+ SierraKomodo:
+ - tweak: Punitelli now spawns in various random rooms instead of in maintenance
+ and other dangerous places.
+ - bugfix: On-mob sprites for energy shields now properly update when the shield
+ turns off.
+ - tweak: Lights, switches, and air alarms now use emissive overlays.
+ - tweak: "The passenger role now has unlimited slots. (\u042D\u0442\u043E \u043D\
+ \u0430 \u0422\u043E\u0440\u0447\u0435, \u0442\u0430\u043A \u0447\u0442\u043E\
+ \ \u043D\u0435 \u0440\u0430\u0441\u0441\u043B\u0430\u0431\u043B\u044F\u0439\u0442\
+ \u0435\u0441\u044C, \u0421\u044C\u0435\u0440\u0440\u043E\u0432\u0446\u044B)"
+ emmanuelbassil:
+ - bugfix: Fixes in-hand pipes disappearing when clicking on anchored down pipes.
+ - bugfix: Hardsuit mounted flash works properly again
+ - tweak: Status displays are now destructible.
+ - tweak: Can repair destroyed status displays with two sheets of glass.
+2024-01-09:
+ SierraKomodo:
+ - bugfix: Fixes borgs having broken and weirdly cached/synchronized eye overlays
+ that had no color.
+ - bugfix: Fixes ironing boards and irons not de-wrinkling clothing.
+ - bugfix: Fixes robotics fabricator machines not properly animating while printing.
+ - bugfix: Fixed cases where decapitated heads couldn't be opened or have their internal
+ parts removed using tools.
+2024-01-10:
+ LordNest:
+ - imageadd: Added new sprites for fossils
+2024-01-12:
+ LordNest:
+ - bugfix: Hotfixed some unit tests fails for biodome
+ - bugfix: "\u0423 \u0418\u0418 \u0441\u043D\u043E\u0432\u0430 \u0435\u0441\u0442\
+ \u044C \u0434\u043E\u0441\u0442\u0443\u043F\u044B"
+ - tweak: "\u0418\u0418 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043C\u043E\
+ \u0436\u0435\u0442 \u0432\u0438\u0434\u0435\u0442\u044C \u0441\u043A\u0432\u043E\
+ \u0437\u044C \u0442\u0435\u043C\u043E\u043D\u0442\u0443 (\u043B\u0435\u0436\u0430\
+ \u0442\u044C+\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E \u0447\u0442\u043E\
+ )"
+ - imageadd: "\u0417\u0430\u043C\u0435\u043D\u0438\u043B \u0441\u043A\u0440\u0435\
+ \u043B\u043B\u0430\u043C \u0433\u043B\u0430\u0437\u0430 \u043D\u0430 \u043A\u0430\
+ \u043D\u043E\u043D\u0438\u0447\u043D\u044B\u0435 \u0427\u0451\u0440\u043D\u044B\
+ \u0435 \u0413\u043B\u0430\u0437\u0430"
+ - maptweak: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u0442\u0443\u0440\u0435\
+ \u043B\u0438"
+ - balance: "\u0421\u0442\u0440\u0435\u043B\u044C\u0431\u0443 \u0432\u0435\u0440\u043D\
+ \u0443\u043B \u0432\u0430\u043C \u0441 \u043B\u0430\u0439\u0432 \u0431\u0438\
+ \u043B\u0434\u0430"
+ Mucker:
+ - rscdel: Removed the e-gun from the flying borg module's emag/antag tools.
+2024-01-15:
+ LordNest:
+ - bugfix: Tacticool turtleneck rolled sleeves now have their own sprite.
+ - bugfix: Flamethrower now have normal sprite
+ - bugfix: Exosuit interface now slightly upper and you can see your glasses, while
+ piloting mecha
+ - tweak: Disposals now have nano-ui styled interface
+ - rscadd: Added hardsuit-mounted powerfist for ICCGN ninja suit.
+ Rockton:
+ - maptweak: Fixes the ICGNV Hound's O2 Canister.
+2024-01-16:
+ LordNest:
+ - bugfix: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u0446\u0432\u0435\u0442\u0430\
+ \u043C \u0441\u0432\u0435\u0442. \u041D\u043E \u044D\u0442\u043E \u043D\u0435\
+ \ \u0442\u043E\u0447\u043D\u043E."
+ - rscadd: "\u0412\u0435\u0440\u043D\u0443\u043B \u0438\u0437 \u043D\u0435\u0431\u044B\
+ \u0442\u0438\u044F \u043D\u0430\u0431\u043E\u0440\u044B \u043D\u0430\u0451\u043C\
+ \u043D\u0438\u043A\u043E\u0432. \u0422\u043E\u043B\u044C\u043A\u043E \u0431\u0443\
+ \u0434\u044C\u0442\u0435 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u044B\
+ . \u041E\u043D\u0438 \u0442\u0435\u043F\u0435\u0440\u044C \u0432 \u044F\u0449\
+ \u0438\u043A\u0430\u0445, \u0438\u0431\u043E \u044E\u043D\u0438\u0442 \u0442\
+ \u0435\u0441\u0442\u044B"
+ - maptweak: "\u041F\u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0438\u043D\u0444\
+ \u043E\u0442\u0435\u0445\u043D\u0438\u043A\u0443 \u043E\u0442\u0434\u0435\u043B\
+ \u044C\u043D\u044B\u0439 \u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u0438\u043A\
+ \u043E\u043B\u044C\u043D\u044B\u0439 \u0448\u043A\u0430\u0444"
+ - balance: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043D\u0430\u0451\u043C\u043D\
+ \u0438\u043A\u0430\u043C \u043D\u0430\u0431\u043E\u0440 \u043D\u0435\u0442\u0440\
+ \u0430\u043D\u043D\u0435\u0440\u0430. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\
+ \u043E\u0432\u0430\u0442\u044C \u043D\u0430 \u0441\u0432\u043E\u0439 \u0441\u0442\
+ \u0440\u0430\u0445 \u0438 \u0440\u0438\u0441\u043A."
+ - admin: "\u0412\u043A\u043B\u044E\u0447\u0438\u043B \u0434\u043E\u0431\u0430\u0432\
+ \u043B\u0435\u043D\u043D\u043E\u0435 \u0443\u0436\u0435 \u0441\u0442\u043E \u043B\
+ \u0435\u0442 \u043D\u0430\u0437\u0430\u0434 Respawn as Human. \u0422\u0435\u043F\
+ \u0435\u0440\u044C \u043E\u043D\u043E Respawn as Self"
+ Mucker:
+ - bugfix: Fixed Hullbreaker lasers not penetrating solid turfs.
+ - rscadd: Added an 'Interlude' that can happen when doing long-range teleports.
+ Or if you're just in the wrong place at the wrong time.
+ - rscadd: Added more emotes for bluespace entities.
+ SuhEugene:
+ - balance: Bullets no longer reach hostage when its grabber is turned away.
+ emmanuelbassil:
+ - tweak: Vending machines are now destructible. They have a chance to start speaking,
+ randomly have wires cut, or completely malfunction as they are more and more
+ damaged.
+ - tweak: Vending machines with the speaker enabled speak a slogan every two minutes
+ instead of 10. Speaker disabled by default.
+2024-01-19:
+ LordNest:
+ - bugfix: Fixed graffiti and pickaxe icons
+ emmanuelbassil:
+ - bugfix: Fixes switching medigel scanner modes on mechs.
+ - bugfix: Fixes being able to place item into locked briefcases
+2024-01-21:
+ Mucker:
+ - bugfix: The shuttles on the Skrell and Vox ships are no longer broken and can
+ jump again.
+ - bugfix: Fixed players being put in space sometimes when joining the Skrell or
+ Vox submap.
+ SierraKomodo:
+ - rscadd: Added new generic memos about explorer and fleet rank abbreviations.
+ emmanuelbassil:
+ - bugfix: RCD now properly builds machine/computer frames
+2024-01-23:
+ UEDHighCommand:
+ - imageadd: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u044B\
+ \ \u0440\u0430\u0441\u043E\u0432\u044B\u0435 \u0441\u043F\u0440\u0430\u0439\u0442\
+ \u044B \u0443\u043D\u0430\u0442\u0438, \u0441\u043A\u0440\u0435\u043B\u043B\u043E\
+ \u0432 \u0434\u043B\u044F \u0431\u043E\u043B\u044C\u0448\u0438\u043D\u0441\u0442\
+ \u0432\u0430 \u0432\u043E\u0439\u0434\u0441\u044C\u044E\u0442\u043E\u0432"
+ - imageadd: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B \u0440\u0430\
+ \u0441\u043E\u0432\u044B\u0435 \u0441\u043F\u0440\u0430\u0439\u0442\u044B \u0443\
+ \u043D\u0430\u0442\u0438, \u0441\u043A\u0440\u0435\u043B\u043B\u043E\u0432 \u0438\
+ \ \u0442\u0430\u044F\u0440\u0430 \u0434\u043B\u044F \u0418\u041A\u0421 \u041B\
+ \u042D\u041A\u0430"
+ - rscadd: "\u0423\u043D\u0430\u0442\u0438, \u0441\u043A\u0440\u0435\u043B\u043B\u044B\
+ \ \u0438 \u0442\u0430\u044F\u0440\u0430 \u0442\u0435\u043F\u0435\u0440\u044C\
+ \ \u043C\u043E\u0433\u0443\u0442 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\
+ \u0442\u044C\u0441\u044F \u0418\u041A\u0421 \u041B\u042D\u041A\u0430"
+ - rscadd: "\u0420\u0438\u043E\u0442-\u0441\u043A\u0430\u0444\u0430\u043D\u0434\u0440\
+ \ \u0421\u0411 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D \u0432\
+ \ \u043C\u0435\u043D\u044E Security suit cycler"
+2024-01-24:
+ Mucker:
+ - rscdel: Robots can no longer roll traitor.
+ - bugfix: Fixed shuttle docking issue.
+ - bugfix: Fixed projector slides requiring you to be next to them to properly examine
+ what they're projecting.
+ Sbotkin:
+ - tweak: NTNet IDs are randomized on the mapped NTNet devices.
+ Slywater:
+ - rscadd: Added a changelog editing system that should cause fewer conflicts and
+ more accurate timestamps.
+ - rscdel: Killed innocent kittens.
+ - bugfix: Zombies can pry open emergency shutters.
+ - bugfix: Zombies can no longer delete machinery like shuttle consoles.
+ - bugfix: Infected NPCs have clothes once again.
+ - tweak: Monkey-like alien fauna can be infected with black goo.
+ - tweak: Black goo infection now preserves hair.
+ - balance: Re-instated post-mortem infection (with a lower chance of exposure).
+ - balance: Nerfed zombie knock-down chance.
+ - balance: Nerfed zombie brute & burn resistance.
+ - balance: Decreased overall bite infection chance.
+ - balance: Increased zombie attack speed.
+ - balance: Increased the likelihood for zombies to break out of grabs.
+ - balance: Reduces dialysis effectiveness for removing black goo.
+ - rscdel: Removed the hulk mutation.
+ - rscadd: Ghosted players can now possess zombie NPCs, either by dragging their
+ ghost onto them, or by clicking "Join as Zombie" in the Ghost tab.
+ emmanuelbassil:
+ - bugfix: Using a sharp tool to engrave graffiti on floors/walls now properly leaves
+ fingerprints.
+ - bugfix: Doors with exposed wires no longer open when trying to access wires with
+ empty hand.
+ - tweak: Attacking simulated turfs and objects with punches now have the same behavior.
+ Any turf or non-item object with a healthbar can be attacked; if minimum damage
+ threshold is not reached will damage your hand.
+2024-01-26:
+ LordNest:
+ - tweak: "\u0421\u0434\u0435\u043B\u0430\u043B \u043D\u043E\u0432\u044B\u0435 \u044D\
+ \u043C\u043E\u0443\u0442\u044B (\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\
+ \u0430\u043B \u0441 \u041F\u0430\u0440\u044B) \u044F\u0449\u0435\u0440\u0430\
+ \u043C"
+ - soundadd: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043B \u0437\
+ \u0432\u0443\u043A\u0438 \u041D\u0435\u043B\u043B\u0438 \u0434\u043B\u044F \u044F\
+ \u0449\u0435\u0440\u043E\u0432"
+2024-01-27:
+ KandJX:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u0432\u0435\u043D\u0442\
+ \u044B \u0413\u0443\u043F\u043F\u0430"
+2024-01-28:
+ Sbotkin:
+ - maptweak: The "Status of the Fleets" is moved to the Bridge storage.
+2024-01-29:
+ LordNest:
+ - bugfix: "\u041C\u044B\u0448\u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\
+ \ \u0432\u044B\u0432\u0430\u043B\u0438\u0432\u0430\u044E\u0442\u0441\u044F \u0438\
+ \u0437 \u0442\u0440\u0443\u0431"
+ - tweak: "\u0410\u0432\u0442\u043E\u0445\u0438\u0441\u0441 \u043F\u043E\u0447\u0438\
+ \u043D\u0438\u043B"
+ - imageadd: "\u0421\u0442\u0430\u043D\u0431\u0430\u0442\u043E\u043D\u0443 \u0438\
+ \u043A\u043E\u043D\u043A\u0438 \u0441\u0438\u043D\u0438\u0435 \u043F\u0440\u0438\
+ \u0434\u0435\u043B\u0430\u043B"
+ - imagedel: "\u0423\u0434\u0430\u043B\u0438\u043B \u0434\u0443\u0431\u043B\u0438\
+ \ \u043F\u0440\u0438\u0447\u0451\u0441\u043E\u043A"
+ - maptweak: "\u0412\u0435\u0440\u043D\u0443\u043B \u0420\u0435\u043A\u0441\u0430"
+ emmanuelbassil:
+ - bugfix: Radial menus now properly work when inside an exosuit
+2024-01-30:
+ Rockton:
+ - rscadd: Adds Komarov overmap placeholder sprites for admin use
+ SuhEugene:
+ - bugfix: Fixed white skybox
+2024-01-31:
+ emmanuelbassil:
+ - bugfix: Unathi no longer get as much nutrition from non-meat items
+2024-02-02:
+ Mucker:
+ - bugfix: Brought back the good skybox.
+ SuhEugene:
+ - rscdel: "\u041E\u0442\u043A\u043B\u044E\u0447\u0438\u043B Onyx \u043B\u043E\u0434\
+ \u0430\u0443\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u043E\u0437\
+ \u043C\u043E\u0436\u043D\u043E \u0438 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\
+ \u043B \u0431\u043E\u043B\u044C\u0448\u0438\u0435 \u043B\u0430\u0433\u0438 \u0441\
+ \u0435\u0440\u0432\u0435\u0440\u0430."
+2024-02-03:
+ Nyvrem:
+ - tweak: Vox have had errant thoughts of excessive identity pruned from their mindscapes.
+ Again.
+ Sbotkin:
+ - bugfix: Fleet medical fatigues no longer spawn with an armband.
+ - bugfix: The end-of-round vote now correctly displays extend time.
+2024-02-06:
+ LordNest:
+ - tweak: "\u0423\u0432\u0435\u043B\u0438\u0447\u0438\u043B \u0441\u043A\u043E\u0440\
+ \u043E\u0441\u0442\u044C \u043F\u0443\u043B\u044F\u043C"
+ - soundadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043D\u043E\u0432\u044B\
+ \u0439 \u0437\u0432\u0443\u043A"
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0441\u0438\u043B\u0433\u0435\
+ \u043D. \u0417\u0430\u043D\u0435\u0440\u0444\u0438\u043B \u0435\u0433\u043E\
+ \ \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u043C\u043E\u0433\
+ ."
+ - rscadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043C\u0443\u043B\u044C\u0442\
+ \u0438\u043C\u0435\u0442\u0440. \u0417\u0430\u043D\u0435\u0440\u0444\u0438\u043B\
+ \ \u0435\u0433\u043E \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E\
+ \ \u0441\u043C\u043E\u0433."
+ - imageadd: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u043D\u043E\u0432\u0443\
+ \u044E \u0438\u043A\u043E\u043D\u043A\u0443 \u043F\u0435\u0440\u0435\u043D\u043E\
+ \u0441\u043A\u0438 \u0441\u0438\u043B\u0433\u0435\u043D\u0430 \u043E\u0442 \u043A\
+ \u043E\u0442\u043E\u0440\u043E\u0439 \u0412\u043E\u043B\u044C\u0444\u043E\u0440\
+ \u0430 \u0445\u0432\u0430\u0442\u0438\u043B \u043F\u0440\u0438\u043F\u0430\u0434\
+ \u043E\u043A"
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u0438\u043B \u0441\u0438\u043B\u0433\
+ \u0435\u043D \u0438 \u043C\u0443\u043B\u044C\u0442\u0438\u043C\u0435\u0442\u0440\
+ \ \u043D\u0430 \u043A\u0430\u0440\u0442\u0443. \u0423\u0434\u0430\u043B\u0438\
+ \u043B \u043E\u043A\u043D\u043E \u043C\u0435\u0436\u0434\u0443 \u0420\u0414\
+ \ \u0438 \u0421\u041C\u041E \u043E\u043F\u044F\u0442\u044C. \u041A\u0430\u043D\
+ \u0434\u0436\u0438, \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u043F\u0440\
+ \u043E\u0431\u0443\u0439."
+2024-02-07:
+ LordNest:
+ - bugfix: Humans can choke (use emote) now properly.
+ Spookerton:
+ - bugfix: The potato reactor and reactor reactor correctly accept vodka and coolant
+ as coolants respectively.
+ emmanuelbassil:
+ - tweak: As part of the damage standardization, can now damage closets/crates with
+ melee attacks.
+ - tweak: Damaging a closet also relays some damage to whatever's inside.
+ - tweak: Secure closets/crates are tougher than regular closets/crates.
+2024-02-13:
+ LordNest:
+ - bugfix: Fixed absence of capitalized cyrillic prefixes for radio usage.
+ Titanized:
+ - tweak: Increases the maximum complex devices skill level to 'experienced' for
+ the CE.
+ ddorou:
+ - tweak: "\u0418\u0437\u043C\u0435\u043D\u0438\u043B \u043C\u0430\u043A\u0441\u0438\
+ \u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440\
+ \ \u043F\u0440\u0435\u0434\u043C\u0435\u0442\u043E\u0432 \u0434\u043B\u044F\
+ \ \u0440\u044E\u043A\u0437\u0430\u043A\u0430 \u0441 LARGE \u043D\u0430 NORMAL"
+2024-02-14:
+ KandJX:
+ - maptweak: "\u041F\u0435\u0440\u0435\u0440\u0430\u0431\u043E\u0442\u043A\u0430\
+ \ \u0434\u043E\u0440\u043C"
+ - maptweak: "\u041F\u0435\u0440\u0435\u0440\u0430\u0431\u043E\u0442\u043A\u0430\
+ \ \u0441\u0430\u0443\u043D\u044B"
+ - maptweak: "\u0420\u0435\u0448\u0435\u043D\u0438\u0435 \u0438\u0448\u0443\u0435\
+ \u0432 \u043F\u043E \u043A\u0430\u0440\u0442\u0435"
+ - tweak: "\u0421\u0415 \u0432\u044B\u0434\u0430\u043D \u0440\u0430\u0443\u043D\u0434\
+ \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u043F\u043E\u044F\u0441 \u0441 \u0425\
+ \u0430\u0439\u0442\u0438\u0440 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\
+ \u043D\u0442\u0430\u043C\u0438"
+ - tweak: "\u0423\u0442\u0438\u043B\u0438\u0442\u0438 \u043F\u043E\u044F\u0441 \u0442\
+ \u0435\u043F\u0435\u0440\u044C \u0432\u043C\u0435\u0449\u0430\u0435\u0442 \u0432\
+ \ \u0441\u0435\u0431\u044F \u0420\u0426\u0414 \u0438 \u041F\u0435\u0439\u043D\
+ \u0442\u0421\u043F\u0440\u0435\u0435\u0440."
+ - maptweak: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0430 \u043A\u0430\
+ \u0440\u0442\u0430 \u0430\u0432\u0435\u0439\u0434\u0435\u0440\u0435\u043B\u0438\
+ \u043A\u0442\u0430 \"\u041F\u0438\u0440\u0430\u0442\u0441\u043A\u043E\u0439\
+ \ \u0428\u0430\u0445\u0442\u044B\""
+ - tweak: "\u041F\u043E\u0440\u0442 \u043F\u0440\u043E\u043F\u043E\u0432 \u0434\u043B\
+ \u044F \u0441\u0430\u0443\u043D\u044B \u043E\u0442 \u0414\u0430\u0440\u043A\u0421\
+ \u043E\u0432\u0435\u0442\u0430 \u0441 \u043F\u0440\u043E\u0435\u043A\u0442\u0430\
+ \ \u0424\u0430\u0439\u043D\u043B\u0414\u0435\u0441\u0442\u0438\u043D\u0435\u0439\
+ \u0448\u043D"
+ LordNest:
+ - bugfix: "\u041F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043B \u0441 \u0434\
+ \u0430\u0443\u043D\u0441\u0442\u0440\u0438\u043C\u0430 \u0444\u0438\u043A\u0441\
+ \ \u0433\u0440\u0430\u0431\u0430 \u0441\u0430\u043C\u043E\u0433\u043E \u0441\
+ \u0435\u0431\u044F \u043E\u0442 larentoun"
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0441\u0438\u043B \u043E\u0442\u0441\u0443\
+ \u0442\u0441\u0442\u0432\u0438\u0435 \u043F\u0440\u043E\u0442\u0435\u0437\u043E\
+ \u0432 \u041D\u0422 \u043D\u0430 \u044D\u043A\u0440\u0430\u043D\u0435 \u0432\
+ \u044B\u0431\u043E\u0440\u0430 (\u0438\u0445 \u0443\u043A\u0440\u0430\u043B\u0438\
+ \ \u0442\u0430\u044F\u0440\u0430)"
+ - tweak: "\u041F\u043E\u0447\u0438\u043D\u0438\u043B \u043E\u0431\u043E\u0437\u043D\
+ \u0430\u0447\u0435\u043D\u0438\u0435 \u043D\u043E\u0432\u044B\u0445 \u043A\u0443\
+ \u043B\u044C\u0442\u0443\u0440"
+ PapiSmirnov:
+ - bugfix: subdermal armor will now actually apply it's damage resistances.
+2024-02-15:
+ KandJX:
+ - bugfix: "\u041F\u043E\u0444\u0438\u043A\u0448\u0435\u043D\u043D\u043E \u043E\u0442\
+ \u043E\u0440\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u0433\u043E\u0440\
+ \u044F\u0449\u0438\u0445 \u0433\u0430\u0437\u043E\u0432 \u043F\u0440\u0438 \u043D\
+ \u0438\u0437\u043A\u043E\u043C \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\
+ \u0432\u0435 \u043C\u043E\u043B\u0435\u0439. \u0420\u0435\u0448\u0435\u043D\u0438\
+ \u0435 \u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0441\
+ \ WyccBay."
+2024-02-17:
+ LordNest:
+ - bugfix: Fixed missing icon for alien power cell
+ - imageadd: 'Added new sprites for rig modules: cooling unit and powerfist by Hawwar'
+ Noreaus:
+ - tweak: changes the taste of DnB from sassafras (wrong) to fruit and aniseed (correct)
+ emmanuelbassil:
+ - tweak: Can now fill fire extinguishers using beakers. Can effectively fill them
+ with any liquid.
+ - tweak: Fire extinguishers have a chance to break when firing any liquid that isn't
+ water. They cannot be repaired.
+ - tweak: Borgs can repair broken fire extinguishers at the recharging station.
+2024-02-22:
+ JoeyNosegay:
+ - rscadd: Added TC and FPE outfits and accessories.
+ LordNest:
+ - imageadd: "\u041D\u043E\u0432\u044B\u0435 \u0438\u043A\u043E\u043D\u043A\u0438\
+ \ \u0438 \u0430\u0439\u0442\u0435\u043C \u0441\u0442\u0435\u0439\u0442\u044B\
+ \ \u0434\u043B\u044F: \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043B\u0430\
+ \u0437\u0435\u0440 \u043A\u0430\u0440\u0430\u0431\u0438\u043D\u0430, \u0442\u0430\
+ \u0437\u0435\u0440\u043D\u043E\u0433\u043E \u0440\u0443\u0436\u044C\u044F, \u0440\
+ \u0435\u0432\u043E\u043B\u044C\u0432\u0435\u0440\u0430 \u0444\u043E\u043D\u0434\
+ \u0430 \u0438\u043C\u0435\u043D\u0438 \u0442\u043E\u0432. \u041A\u0443\u0445\
+ \u0443\u043B\u0438\u043D\u0430,"
+ SegaCD:
+ - rscadd: Added black carpet and a supply entry to order it.
+ emmanuelbassil:
+ - bugfix: Fixes being unable to bash off a cover off a broken APC
+ - tweak: APCs now have standardized health
+ papismirnov:
+ - rscadd: Adds the portable radio jammer to the antag uplink
diff --git a/html/changelogs/AutoChangeLog-sierra-pr-1637.yml b/html/changelogs/AutoChangeLog-sierra-pr-1637.yml
new file mode 100644
index 0000000000000..292309661232d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-sierra-pr-1637.yml
@@ -0,0 +1,5 @@
+author: LordNest
+changes:
+ - {rscadd: 'Добавил в игру авейку пси-культистов, шифрующихся под Альянс Фронтира'}
+ - {maptweak: Новая карта "Утилизационное судно класса Гиена"}
+delete-after: true
diff --git a/html/changelogs/AutoChangeLog-sierra-pr-1782.yml b/html/changelogs/AutoChangeLog-sierra-pr-1782.yml
new file mode 100644
index 0000000000000..2a86ec0f5faf2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-sierra-pr-1782.yml
@@ -0,0 +1,5 @@
+author: Lexanx
+changes:
+ - {rscadd: Орган Охлаждения для ИПС}
+ - {rscadd: Добавлен внешний охладитель для ИПС}
+delete-after: true
diff --git a/html/changelogs/AutoChangeLog-sierra-pr-1901.yml b/html/changelogs/AutoChangeLog-sierra-pr-1901.yml
new file mode 100644
index 0000000000000..da9a7fb72a71d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-sierra-pr-1901.yml
@@ -0,0 +1,4 @@
+author: Mucker
+changes:
+ - {bugfix: Fixed not being able to teleport objects at or over max range.}
+delete-after: true
diff --git a/html/changelogs/AutoChangeLog-sierra-pr-1917.yml b/html/changelogs/AutoChangeLog-sierra-pr-1917.yml
new file mode 100644
index 0000000000000..0cccf74a5be5f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-sierra-pr-1917.yml
@@ -0,0 +1,4 @@
+author: Dimach
+changes:
+ - {admin: 'Add xeno whitelist panel, add FBP whitelist'}
+delete-after: true
diff --git a/html/create_object.html b/html/create_object.html
index 7ed93d9818038..4ada0d662f8c7 100644
--- a/html/create_object.html
+++ b/html/create_object.html
@@ -1,128 +1,108 @@
-
-
-
- Create Object
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ Create Object
+
+
+
+
+
+
+
+
+
+
diff --git a/html/cross-circle.png b/html/cross-circle.png
index e6f2d3228b81a..4e2040c58a4f4 100644
Binary files a/html/cross-circle.png and b/html/cross-circle.png differ
diff --git a/html/font-awesome/README.MD b/html/font-awesome/README.MD
index 7d693c36f031d..ba9121311d401 100644
--- a/html/font-awesome/README.MD
+++ b/html/font-awesome/README.MD
@@ -1,6 +1,6 @@
Due to the fact browse_rsc can't create subdirectories, every time you update font-awesome you'll need to change relative webfont references in all.min.css
eg ../webfonts/fa-regular-400.ttf => fa-regular-400.ttf (or whatever you call it in asset datum)
-Second change is ripping out file types other than woff and eot(ie8) from the css
+Second change is ripping out file types other than ~~ woff and eot(ie8)~~ ttf from the css
-Finally, removing brand related css.
\ No newline at end of file
+Finally, removing brand related css.
diff --git a/html/font-awesome/css/all.min.css b/html/font-awesome/css/all.min.css
index e7cdfffe751e0..880fddcc41528 100644
--- a/html/font-awesome/css/all.min.css
+++ b/html/font-awesome/css/all.min.css
@@ -1,5 +1,6 @@
/*!
- * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.1.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ * Copyright 2022 Fonticons, Inc.
*/
-.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(fa-regular-400.eot);src:url(fa-regular-400.eot?#iefix) format("embedded-opentype"),url(fa-regular-400.woff) format("woff")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(fa-solid-900.eot);src:url(fa-solid-900.eot?#iefix) format("embedded-opentype"),url(fa-solid-900.woff) format("woff")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900}
\ No newline at end of file
+.fa{font-family:"Font Awesome 6 Free";font-weight:900}.fa,.fa-brands,.fa-duotone,.fa-light,.fa-regular,.fa-solid,.fa-thin,.fab,.fad,.fal,.far,.fas,.fat{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc( 2em*-1);position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border-radius:.1em;border:.08em solid #eee;padding:.2em .25em .15em}.fa-pull-left{float:left;margin-right:.3em}.fa-pull-right{float:right;margin-left:.3em}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:cubic-bezier(.28,.84,.42,1);animation-timing-function:cubic-bezier(.28,.84,.42,1)}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:cubic-bezier(.4,0,.6,1);animation-timing-function:cubic-bezier(.4,0,.6,1)}.fa-beat-fade,.fa-fade{-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-duration:1s;animation-duration:1s}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:cubic-bezier(.4,0,.6,1);animation-timing-function:cubic-bezier(.4,0,.6,1)}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.fa-shake,.fa-spin{-webkit-animation-delay:0;animation-delay:0;-webkit-animation-direction:normal;animation-direction:normal}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:steps(8);animation-timing-function:steps(8)}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(1.25);transform:scale(1.25)}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(1.25);transform:scale(1.25)}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(1.1,.9) translateY(0);transform:scale(1.1,.9) translateY(0)}30%{-webkit-transform:scale(.9,1.1) translateY(-.5em);transform:scale(.9,1.1) translateY(-.5em)}50%{-webkit-transform:scale(1.05,.95) translateY(0);transform:scale(1.05,.95) translateY(0)}57%{-webkit-transform:scale(1) translateY(-.125em);transform:scale(1) translateY(-.125em)}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(1.1,.9) translateY(0);transform:scale(1.1,.9) translateY(0)}30%{-webkit-transform:scale(.9,1.1) translateY(-.5em);transform:scale(.9,1.1) translateY(-.5em)}50%{-webkit-transform:scale(1.05,.95) translateY(0);transform:scale(1.05,.95) translateY(0)}57%{-webkit-transform:scale(1) translateY(-.125em);transform:scale(1) translateY(-.125em)}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:.4}}@keyframes fa-fade{50%{opacity:.4}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:.4;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(1.125);transform:scale(1.125)}}@keyframes fa-beat-fade{0%,to{opacity:.4;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(1.125);transform:scale(1.125)}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(0,1,0,-180deg);transform:rotate3d(0,1,0,-180deg)}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(0,1,0,-180deg);transform:rotate3d(0,1,0,-180deg)}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(none);transform:rotate(none)}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:auto}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-a:before{content:"\41"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-anchor:before{content:"\f13d"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-anchor-lock:before{content:"\e4ad"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-ankh:before{content:"\f644"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-archway:before{content:"\f557"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-arrow-trend-down:before{content:"\e097"}.fa-arrow-trend-up:before{content:"\e098"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-arrows-spin:before{content:"\e4bb"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-asterisk:before{content:"\2a"}.fa-at:before{content:"\40"}.fa-atom:before{content:"\f5d2"}.fa-audio-description:before{content:"\f29e"}.fa-austral-sign:before{content:"\e0a9"}.fa-award:before{content:"\f559"}.fa-b:before{content:"\42"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-backward:before{content:"\f04a"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-baht-sign:before{content:"\e0ac"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-barcode:before{content:"\f02a"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-bell:before{content:"\f0f3"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blog:before{content:"\f781"}.fa-bold:before{content:"\f032"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-book-bookmark:before{content:"\e0bb"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-bookmark:before{content:"\f02e"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-bore-hole:before{content:"\e4c3"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-bottle-water:before{content:"\e4c5"}.fa-bowl-food:before{content:"\e4c6"}.fa-bowl-rice:before{content:"\e2eb"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes-packing:before{content:"\e4c7"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-bread-slice:before{content:"\f7ec"}.fa-bridge:before{content:"\e4c8"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-bridge-water:before{content:"\e4ce"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broom:before{content:"\f51a"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-brush:before{content:"\f55d"}.fa-bucket:before{content:"\e4cf"}.fa-bug:before{content:"\f188"}.fa-bug-slash:before{content:"\e490"}.fa-bugs:before{content:"\e4d0"}.fa-building:before{content:"\f1ad"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-building-circle-check:before{content:"\e4d2"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-building-flag:before{content:"\e4d5"}.fa-building-lock:before{content:"\e4d6"}.fa-building-ngo:before{content:"\e4d7"}.fa-building-shield:before{content:"\e4d8"}.fa-building-un:before{content:"\e4d9"}.fa-building-user:before{content:"\e4da"}.fa-building-wheat:before{content:"\e4db"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-burst:before{content:"\e4dc"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-c:before{content:"\43"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-week:before{content:"\f784"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-camera-rotate:before{content:"\e0d8"}.fa-campground:before{content:"\f6bb"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-car-on:before{content:"\e4dd"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-car-side:before{content:"\f5e4"}.fa-car-tunnel:before{content:"\e4de"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-cart-plus:before{content:"\f217"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cedi-sign:before{content:"\e0df"}.fa-cent-sign:before{content:"\e3f5"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-charging-station:before{content:"\f5e7"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-chart-column:before{content:"\e0e3"}.fa-chart-gantt:before{content:"\e0e4"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-chart-simple:before{content:"\e473"}.fa-check:before{content:"\f00c"}.fa-check-double:before{content:"\f560"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-child-dress:before{content:"\e59c"}.fa-child-reaching:before{content:"\e59d"}.fa-child-rifle:before{content:"\e4e0"}.fa-children:before{content:"\e4e1"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-circle-nodes:before{content:"\e4e2"}.fa-circle-notch:before{content:"\f1ce"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-city:before{content:"\f64f"}.fa-clapperboard:before{content:"\e131"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clipboard-question:before{content:"\e4e3"}.fa-clipboard-user:before{content:"\f7f3"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-clover:before{content:"\e139"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-code-commit:before{content:"\f386"}.fa-code-compare:before{content:"\e13a"}.fa-code-fork:before{content:"\e13b"}.fa-code-merge:before{content:"\f387"}.fa-code-pull-request:before{content:"\e13c"}.fa-coins:before{content:"\f51e"}.fa-colon-sign:before{content:"\e140"}.fa-comment:before{content:"\f075"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-compress:before{content:"\f066"}.fa-computer:before{content:"\e4e5"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cow:before{content:"\f6c8"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-d:before{content:"\44"}.fa-database:before{content:"\f1c0"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-democrat:before{content:"\f747"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-dharmachakra:before{content:"\f655"}.fa-diagram-next:before{content:"\e476"}.fa-diagram-predecessor:before{content:"\e477"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-diagram-successor:before{content:"\e47a"}.fa-diamond:before{content:"\f219"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-disease:before{content:"\f7fa"}.fa-display:before{content:"\e163"}.fa-divide:before{content:"\f529"}.fa-dna:before{content:"\f471"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-dong-sign:before{content:"\e169"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dove:before{content:"\f4ba"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-download:before{content:"\f019"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-e:before{content:"\45"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elevator:before{content:"\e16d"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-equals:before{content:"\3d"}.fa-eraser:before{content:"\f12d"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-exclamation:before{content:"\21"}.fa-expand:before{content:"\f065"}.fa-explosion:before{content:"\e4e9"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-eye-slash:before{content:"\f070"}.fa-f:before{content:"\46"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-fan:before{content:"\f863"}.fa-faucet:before{content:"\e005"}.fa-faucet-drip:before{content:"\e006"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-ferry:before{content:"\e4ea"}.fa-file:before{content:"\f15b"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-file-audio:before{content:"\f1c7"}.fa-file-circle-check:before{content:"\e5a0"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-file-circle-plus:before{content:"\e494"}.fa-file-circle-question:before{content:"\e4ef"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-excel:before{content:"\f1c3"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-file-medical:before{content:"\f477"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-shield:before{content:"\e4f0"}.fa-file-signature:before{content:"\f573"}.fa-file-video:before{content:"\f1c8"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-file-word:before{content:"\f1c2"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-burner:before{content:"\e4f1"}.fa-fire-extinguisher:before{content:"\f134"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-fish:before{content:"\f578"}.fa-fish-fins:before{content:"\e4f2"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flask-vial:before{content:"\e4f3"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-florin-sign:before{content:"\e184"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-folder-closed:before{content:"\e185"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-folder-tree:before{content:"\f802"}.fa-font:before{content:"\f031"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-franc-sign:before{content:"\e18f"}.fa-frog:before{content:"\f52e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-g:before{content:"\47"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-glass-water:before{content:"\e4f4"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-glasses:before{content:"\f530"}.fa-globe:before{content:"\f0ac"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-greater-than:before{content:"\3e"}.fa-greater-than-equal:before{content:"\f532"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-guarani-sign:before{content:"\e19a"}.fa-guitar:before{content:"\f7a6"}.fa-gun:before{content:"\e19b"}.fa-h:before{content:"\48"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-handcuffs:before{content:"\e4f8"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-hands-bound:before{content:"\e4f9"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-hands-clapping:before{content:"\e1a8"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-handshake:before{content:"\f2b5"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-hashtag:before{content:"\23"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-heart-circle-plus:before{content:"\e500"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-helicopter-symbol:before{content:"\e502"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-helmet-un:before{content:"\e503"}.fa-highlighter:before{content:"\f591"}.fa-hill-avalanche:before{content:"\e507"}.fa-hill-rockslide:before{content:"\e508"}.fa-hippo:before{content:"\f6ed"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-house-chimney-user:before{content:"\e065"}.fa-house-chimney-window:before{content:"\e00d"}.fa-house-circle-check:before{content:"\e509"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-house-crack:before{content:"\e3b1"}.fa-house-fire:before{content:"\e50c"}.fa-house-flag:before{content:"\e50d"}.fa-house-flood-water:before{content:"\e50e"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-house-lock:before{content:"\e510"}.fa-house-medical:before{content:"\e3b2"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-house-medical-flag:before{content:"\e514"}.fa-house-signal:before{content:"\e012"}.fa-house-tsunami:before{content:"\e515"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-hurricane:before{content:"\f751"}.fa-i:before{content:"\49"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-images:before{content:"\f302"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-italic:before{content:"\f033"}.fa-j:before{content:"\4a"}.fa-jar:before{content:"\e516"}.fa-jar-wheat:before{content:"\e517"}.fa-jedi:before{content:"\f669"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-jet-fighter-up:before{content:"\e518"}.fa-joint:before{content:"\f595"}.fa-jug-detergent:before{content:"\e519"}.fa-k:before{content:"\4b"}.fa-kaaba:before{content:"\f66b"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-khanda:before{content:"\f66d"}.fa-kip-sign:before{content:"\e1c4"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-kitchen-set:before{content:"\e51a"}.fa-kiwi-bird:before{content:"\f535"}.fa-l:before{content:"\4c"}.fa-land-mine-on:before{content:"\e51b"}.fa-landmark:before{content:"\f66f"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-landmark-flag:before{content:"\e51c"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-file:before{content:"\e51d"}.fa-laptop-medical:before{content:"\f812"}.fa-lari-sign:before{content:"\e1c8"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-lemon:before{content:"\f094"}.fa-less-than:before{content:"\3c"}.fa-less-than-equal:before{content:"\f537"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-lines-leaning:before{content:"\e51e"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-lira-sign:before{content:"\f195"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-location-arrow:before{content:"\f124"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-location-pin-lock:before{content:"\e51f"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-locust:before{content:"\e520"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-m:before{content:"\4d"}.fa-magnet:before{content:"\f076"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-manat-sign:before{content:"\e1d5"}.fa-map:before{content:"\f279"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-pin:before{content:"\f276"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-and-venus:before{content:"\f224"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-mask:before{content:"\f6fa"}.fa-mask-face:before{content:"\e1d7"}.fa-mask-ventilator:before{content:"\e524"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-mattress-pillow:before{content:"\e525"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-medal:before{content:"\f5a2"}.fa-memory:before{content:"\f538"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-mill-sign:before{content:"\e1ed"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-mitten:before{content:"\f7b5"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-mobile-button:before{content:"\f10b"}.fa-mobile-retro:before{content:"\e527"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-money-bills:before{content:"\e1f3"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-mosquito-net:before{content:"\e52c"}.fa-motorcycle:before{content:"\f21c"}.fa-mound:before{content:"\e52d"}.fa-mountain:before{content:"\f6fc"}.fa-mountain-city:before{content:"\e52e"}.fa-mountain-sun:before{content:"\e52f"}.fa-mug-hot:before{content:"\f7b6"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-music:before{content:"\f001"}.fa-n:before{content:"\4e"}.fa-naira-sign:before{content:"\e1f6"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-not-equal:before{content:"\f53e"}.fa-notdef:before{content:"\e1fe"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-notes-medical:before{content:"\f481"}.fa-o:before{content:"\4f"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-oil-can:before{content:"\f613"}.fa-oil-well:before{content:"\e532"}.fa-om:before{content:"\f679"}.fa-otter:before{content:"\f700"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-p:before{content:"\50"}.fa-pager:before{content:"\f815"}.fa-paint-roller:before{content:"\f5aa"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-palette:before{content:"\f53f"}.fa-pallet:before{content:"\f482"}.fa-panorama:before{content:"\e209"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-passport:before{content:"\f5ab"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-pause:before{content:"\f04c"}.fa-paw:before{content:"\f1b0"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-people-group:before{content:"\e533"}.fa-people-line:before{content:"\e534"}.fa-people-pulling:before{content:"\e535"}.fa-people-robbery:before{content:"\e536"}.fa-people-roof:before{content:"\e537"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-person-booth:before{content:"\f756"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-person-burst:before{content:"\e53b"}.fa-person-cane:before{content:"\e53c"}.fa-person-chalkboard:before{content:"\e53d"}.fa-person-circle-check:before{content:"\e53e"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-person-circle-minus:before{content:"\e540"}.fa-person-circle-plus:before{content:"\e541"}.fa-person-circle-question:before{content:"\e542"}.fa-person-circle-xmark:before{content:"\e543"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-person-dress-burst:before{content:"\e544"}.fa-person-drowning:before{content:"\e545"}.fa-person-falling:before{content:"\e546"}.fa-person-falling-burst:before{content:"\e547"}.fa-person-half-dress:before{content:"\e548"}.fa-person-harassing:before{content:"\e549"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-person-military-pointing:before{content:"\e54a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-person-military-to-person:before{content:"\e54c"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-person-pregnant:before{content:"\e31e"}.fa-person-rays:before{content:"\e54d"}.fa-person-rifle:before{content:"\e54e"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-person-shelter:before{content:"\e54f"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-person-through-window:before{content:"\e5a9"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-person-walking-luggage:before{content:"\e554"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-peseta-sign:before{content:"\e221"}.fa-peso-sign:before{content:"\e222"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-circle-check:before{content:"\e555"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-lock:before{content:"\e558"}.fa-plane-slash:before{content:"\e069"}.fa-plane-up:before{content:"\e22d"}.fa-plant-wilt:before{content:"\e5aa"}.fa-plate-wheat:before{content:"\e55a"}.fa-play:before{content:"\f04b"}.fa-plug:before{content:"\f1e6"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-plug-circle-check:before{content:"\e55c"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-plus-minus:before{content:"\e43c"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-puzzle-piece:before{content:"\f12e"}.fa-q:before{content:"\51"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\3f"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-r:before{content:"\52"}.fa-radiation:before{content:"\f7b9"}.fa-radio:before{content:"\f8d7"}.fa-rainbow:before{content:"\f75b"}.fa-ranking-star:before{content:"\e561"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-recycle:before{content:"\f1b8"}.fa-registered:before{content:"\f25d"}.fa-repeat:before{content:"\f363"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-republican:before{content:"\f75e"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-road-barrier:before{content:"\e562"}.fa-road-bridge:before{content:"\e563"}.fa-road-circle-check:before{content:"\e564"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-road-circle-xmark:before{content:"\e566"}.fa-road-lock:before{content:"\e567"}.fa-road-spikes:before{content:"\e568"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-route:before{content:"\f4d7"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-rug:before{content:"\e569"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-rupiah-sign:before{content:"\e23d"}.fa-s:before{content:"\53"}.fa-sack-dollar:before{content:"\f81d"}.fa-sack-xmark:before{content:"\e56a"}.fa-sailboat:before{content:"\e445"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-school:before{content:"\f549"}.fa-school-circle-check:before{content:"\e56b"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-school-flag:before{content:"\e56e"}.fa-school-lock:before{content:"\e56f"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-screwdriver:before{content:"\f54a"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-scroll:before{content:"\f70e"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-sd-card:before{content:"\f7c2"}.fa-section:before{content:"\e447"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-server:before{content:"\f233"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-sheet-plastic:before{content:"\e571"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-shield-cat:before{content:"\e572"}.fa-shield-dog:before{content:"\e573"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-shield-heart:before{content:"\e574"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-shoe-prints:before{content:"\f54b"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-shop-lock:before{content:"\e4a5"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-shower:before{content:"\f2cc"}.fa-shrimp:before{content:"\e448"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-sim-card:before{content:"\f7c4"}.fa-sink:before{content:"\e06d"}.fa-sitemap:before{content:"\f0e8"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-spa:before{content:"\f5bb"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-spray-can:before{content:"\f5bd"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-square:before{content:"\f0c8"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-square-full:before{content:"\f45c"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-square-nfi:before{content:"\e576"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-square-person-confined:before{content:"\e577"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-square-virus:before{content:"\e578"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-stairs:before{content:"\e289"}.fa-stamp:before{content:"\f5bf"}.fa-stapler:before{content:"\e5af"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-stethoscope:before{content:"\f0f1"}.fa-stop:before{content:"\f04d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-slash:before{content:"\e071"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stroopwafel:before{content:"\f551"}.fa-subscript:before{content:"\f12c"}.fa-suitcase:before{content:"\f0f2"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-superscript:before{content:"\f12b"}.fa-swatchbook:before{content:"\f5c3"}.fa-synagogue:before{content:"\f69b"}.fa-syringe:before{content:"\f48e"}.fa-t:before{content:"\54"}.fa-table:before{content:"\f0ce"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-tablet-button:before{content:"\f10a"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tarp:before{content:"\e57b"}.fa-tarp-droplet:before{content:"\e57c"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-tent:before{content:"\e57d"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tent-arrows-down:before{content:"\e581"}.fa-tents:before{content:"\e582"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-text-width:before{content:"\f035"}.fa-thermometer:before{content:"\f491"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-ticket:before{content:"\f145"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-timeline:before{content:"\e29c"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toilet-portable:before{content:"\e583"}.fa-toilets-portable:before{content:"\e584"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-torii-gate:before{content:"\f6a1"}.fa-tornado:before{content:"\f76f"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-tower-cell:before{content:"\e585"}.fa-tower-observation:before{content:"\e586"}.fa-tractor:before{content:"\f722"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-train-tram:before{content:"\e5b4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-tree-city:before{content:"\e587"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-trophy:before{content:"\f091"}.fa-trowel:before{content:"\e589"}.fa-trowel-bricks:before{content:"\e58a"}.fa-truck:before{content:"\f0d1"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-truck-droplet:before{content:"\e58c"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-truck-field:before{content:"\e58d"}.fa-truck-field-un:before{content:"\e58e"}.fa-truck-front:before{content:"\e2b7"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-truck-plane:before{content:"\e58f"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-u:before{content:"\55"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-universal-access:before{content:"\f29a"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-upload:before{content:"\f093"}.fa-user:before{content:"\f007"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-clock:before{content:"\f4fd"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-user-graduate:before{content:"\f501"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-user-injured:before{content:"\f728"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-user-lock:before{content:"\f502"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-between-lines:before{content:"\e591"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-users-line:before{content:"\e592"}.fa-users-rays:before{content:"\e593"}.fa-users-rectangle:before{content:"\e594"}.fa-users-slash:before{content:"\e073"}.fa-users-viewfinder:before{content:"\e595"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-v:before{content:"\56"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-vault:before{content:"\e2c5"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-vial:before{content:"\f492"}.fa-vial-circle-check:before{content:"\e596"}.fa-vial-virus:before{content:"\e597"}.fa-vials:before{content:"\f493"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-virus:before{content:"\e074"}.fa-virus-covid:before{content:"\e4a8"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-voicemail:before{content:"\f897"}.fa-volcano:before{content:"\f770"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-vr-cardboard:before{content:"\f729"}.fa-w:before{content:"\57"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-wallet:before{content:"\f555"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-wand-sparkles:before{content:"\f72b"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-wave-square:before{content:"\f83e"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-wheelchair:before{content:"\f193"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-wind:before{content:"\f72e"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-worm:before{content:"\e599"}.fa-wrench:before{content:"\f0ad"}.fa-x:before{content:"\58"}.fa-x-ray:before{content:"\f497"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-xmarks-lines:before{content:"\e59a"}.fa-y:before{content:"\59"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-z:before{content:"\5a"}.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family: 'Font Awesome 6 Brands';font-style: normal;font-weight: 400;font-display: block;src: url("fa-brands-400.woff2") format("woff2"), url("fa-brands-400.ttf") format("truetype");}.fa-brands,.fab{font-family:"Font Awesome 6 Brands";font-weight:400}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-alipay:before{content:"\f642"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-amilia:before{content:"\f36d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-artstation:before{content:"\f77a"}.fa-asymmetrik:before{content:"\f372"}.fa-atlassian:before{content:"\f77b"}.fa-audible:before{content:"\f373"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-bandcamp:before{content:"\f2d5"}.fa-battle-net:before{content:"\f835"}.fa-behance:before{content:"\f1b4"}.fa-bilibili:before{content:"\e3d9"}.fa-bimobject:before{content:"\f378"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bootstrap:before{content:"\f836"}.fa-bots:before{content:"\e340"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-buromobelexperte:before{content:"\f37f"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cmplid:before{content:"\e360"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-critical-role:before{content:"\f6c9"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dhl:before{content:"\f790"}.fa-diaspora:before{content:"\f791"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-elementor:before{content:"\f430"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-evernote:before{content:"\f839"}.fa-expeditedssl:before{content:"\f23e"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-figma:before{content:"\f799"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-fulcrum:before{content:"\f50b"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-gofore:before{content:"\f3a7"}.fa-golang:before{content:"\e40f"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-wallet:before{content:"\f1ee"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-gulp:before{content:"\f3ae"}.fa-hacker-news:before{content:"\f1d4"}.fa-hackerrank:before{content:"\f5f7"}.fa-hashnode:before{content:"\e499"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-hive:before{content:"\e07f"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hotjar:before{content:"\f3b1"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-ideal:before{content:"\e013"}.fa-imdb:before{content:"\f2d8"}.fa-instagram:before{content:"\f16d"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaggle:before{content:"\f5fa"}.fa-keybase:before{content:"\f4f5"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-leanpub:before{content:"\f212"}.fa-less:before{content:"\f41d"}.fa-line:before{content:"\f3c0"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-mailchimp:before{content:"\f59e"}.fa-mandalorian:before{content:"\f50f"}.fa-markdown:before{content:"\f60f"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medapps:before{content:"\f3c6"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-mendeley:before{content:"\f7b3"}.fa-meta:before{content:"\e49b"}.fa-microblog:before{content:"\e01a"}.fa-microsoft:before{content:"\f3ca"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-nfc-directional:before{content:"\e530"}.fa-nfc-symbol:before{content:"\e531"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-padlet:before{content:"\e4a0"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-palfed:before{content:"\f3d8"}.fa-patreon:before{content:"\f3d9"}.fa-paypal:before{content:"\f1ed"}.fa-perbyte:before{content:"\e083"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pix:before{content:"\e43a"}.fa-playstation:before{content:"\f3df"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-r-project:before{content:"\f4f7"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-redhat:before{content:"\f7bc"}.fa-renren:before{content:"\f18b"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-rev:before{content:"\f5b2"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rust:before{content:"\e07a"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-schlix:before{content:"\f3ea"}.fa-screenpal:before{content:"\e570"}.fa-scribd:before{content:"\f28a"}.fa-searchengin:before{content:"\f3eb"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-servicestack:before{content:"\f3ec"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopify:before{content:"\e057"}.fa-shopware:before{content:"\f5b5"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sith:before{content:"\f512"}.fa-sitrox:before{content:"\e44a"}.fa-sketch:before{content:"\f7c6"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-slideshare:before{content:"\f1e7"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-space-awesome:before{content:"\e5ac"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spotify:before{content:"\f1bc"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-symbol:before{content:"\f3f6"}.fa-sticker-mule:before{content:"\f3f7"}.fa-strava:before{content:"\f428"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-superpowers:before{content:"\f2dd"}.fa-supple:before{content:"\f3f9"}.fa-suse:before{content:"\f7d6"}.fa-swift:before{content:"\f8e1"}.fa-symfony:before{content:"\f83d"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-the-red-yeti:before{content:"\f69d"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-think-peaks:before{content:"\f731"}.fa-tiktok:before{content:"\e07b"}.fa-trade-federation:before{content:"\f513"}.fa-trello:before{content:"\f181"}.fa-tumblr:before{content:"\f173"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-uncharted:before{content:"\e084"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-vaadin:before{content:"\f408"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viber:before{content:"\f409"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-vuejs:before{content:"\f41f"}.fa-watchman-monitoring:before{content:"\e087"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whmcs:before{content:"\f40d"}.fa-wikipedia-w:before{content:"\f266"}.fa-windows:before{content:"\f17a"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-zhihu:before{content:"\f63f"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-family:"Font Awesome 6 Free";font-weight:400}:host,:root{--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-family:"Font Awesome 6 Free";font-weight:900}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
diff --git a/html/font-awesome/css/v4-shims.min.css b/html/font-awesome/css/v4-shims.min.css
index 5f3fdc598c822..2f6252b52a144 100644
--- a/html/font-awesome/css/v4-shims.min.css
+++ b/html/font-awesome/css/v4-shims.min.css
@@ -1,5 +1,6 @@
/*!
- * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com
+ * Font Awesome Free 6.1.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ * Copyright 2022 Fonticons, Inc.
*/
-.fa.fa-glass:before{content:"\f000"}.fa.fa-meetup{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-star-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-file-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-pencil:before{content:"\f303"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-share-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart:before{content:"\f080"}.fa.fa-bar-chart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart-o:before{content:"\f080"}.fa.fa-facebook-square,.fa.fa-twitter-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-lemon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-scissors:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-google-plus,.fa.fa-google-plus-square,.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f3fd"}.fa.fa-comment-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard,.fa.fa-paste{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paste:before{content:"\f328"}.fa.fa-lightbulb-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f381"}.fa.fa-cloud-upload:before{content:"\f382"}.fa.fa-bell-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f089"}.fa.fa-star-half-empty{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f089"}.fa.fa-star-half-full{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f089"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before{content:"\f127"}.fa.fa-shield:before{content:"\f3ed"}.fa.fa-calendar-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ticket:before{content:"\f3ff"}.fa.fa-minus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\f155"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\f156"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f15e"}.fa.fa-sort-amount-asc:before{content:"\f160"}.fa.fa-sort-amount-desc:before{content:"\f161"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f163"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube,.fa.fa-youtube-play,.fa.fa-youtube-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\f195"}.fa.fa-plus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-google,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle,.fa.fa-yahoo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-spoon:before{content:"\f2e5"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-steam,.fa.fa-steam-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-cab:before{content:"\f1ba"}.fa.fa-envelope-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-deviantart,.fa.fa-soundcloud{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-life-bouy,.fa.fa-life-ring{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-bouy:before{content:"\f1cd"}.fa.fa-life-buoy{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-buoy:before{content:"\f1cd"}.fa.fa-life-saver{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-saver:before{content:"\f1cd"}.fa.fa-support{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git,.fa.fa-git-square,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-sliders:before{content:"\f1de"}.fa.fa-futbol-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-angellist,.fa.fa-ioxhost,.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-meanpath{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-meanpath:before{content:"\f2b4"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before{content:"\f224"}.fa.fa-facebook-official{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-clone,.fa.fa-hourglass-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f254"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before{content:"\f252"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-chrome,.fa.fa-creative-commons,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-internet-explorer,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square,.fa.fa-opera,.fa.fa-safari,.fa.fa-tripadvisor,.fa.fa-wikipedia-w{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-snapchat,.fa.fa-snapchat-ghost,.fa.fa-snapchat-square,.fa.fa-themeisle,.fa.fa-viadeo,.fa.fa-viadeo-square,.fa.fa-yoast{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-spotify,.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 5 Brands";font-weight:400}
\ No newline at end of file
+.fa.fa-glass:before{content:"\f000"}.fa.fa-envelope-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-star-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-home:before{content:"\f015"}.fa.fa-file-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-list-alt:before{content:"\f022"}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-edit{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-edit:before{content:"\f044"}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-expand:before{content:"\f424"}.fa.fa-compress:before{content:"\f422"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart-o:before,.fa.fa-bar-chart:before{content:"\e0e3"}.fa.fa-twitter-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-twitter-square:before{content:"\f081"}.fa.fa-facebook-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook-square:before{content:"\f082"}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-github-square:before{content:"\f092"}.fa.fa-lemon-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-globe:before{content:"\f57d"}.fa.fa-tasks:before{content:"\f828"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-cut:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-save{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-save:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-magic:before{content:"\e2ca"}.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-pinterest-square:before{content:"\f0d3"}.fa.fa-google-plus-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus-square:before{content:"\f0d4"}.fa.fa-google-plus{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f625"}.fa.fa-comment-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard:before{content:"\f0ea"}.fa.fa-lightbulb-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f0ed"}.fa.fa-cloud-upload:before{content:"\f0ee"}.fa.fa-bell-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f5c0"}.fa.fa-star-half-empty{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f5c0"}.fa.fa-star-half-full{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f5c0"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before,.fa.fa-unlink:before{content:"\f127"}.fa.fa-calendar-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-unlock-alt:before{content:"\f09c"}.fa.fa-minus-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\24"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\e1bc"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f881"}.fa.fa-sort-amount-asc:before{content:"\f884"}.fa.fa-sort-amount-desc:before{content:"\f160"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f886"}.fa.fa-youtube-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-youtube-square:before{content:"\f431"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-xing-square:before{content:"\f169"}.fa.fa-youtube-play{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-tumblr-square:before{content:"\f174"}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-vimeo-square:before{content:"\f194"}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\e2bb"}.fa.fa-plus-square-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-google,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-yahoo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-reddit-square:before{content:"\f1a2"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-behance-square:before{content:"\f1b5"}.fa.fa-steam,.fa.fa-steam-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-steam-square:before{content:"\f1b7"}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-cab:before{content:"\f1ba"}.fa.fa-deviantart,.fa.fa-soundcloud,.fa.fa-spotify{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-life-bouy:before,.fa.fa-life-buoy:before,.fa.fa-life-saver:before,.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-git-square:before{content:"\f1d2"}.fa.fa-git,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-futbol-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-lastfm-square:before{content:"\f203"}.fa.fa-angellist,.fa.fa-ioxhost{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before,.fa.fa-transgender:before{content:"\f224"}.fa.fa-transgender-alt:before{content:"\f225"}.fa.fa-facebook-official{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-clone{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f254"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before{content:"\f252"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-creative-commons,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-odnoklassniki-square:before{content:"\f264"}.fa.fa-chrome,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-internet-explorer,.fa.fa-opera,.fa.fa-safari,.fa.fa-wikipedia-w{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-viadeo,.fa.fa-viadeo-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-viadeo-square:before{content:"\f2aa"}.fa.fa-snapchat,.fa.fa-snapchat-ghost{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-snapchat-ghost:before{content:"\f2ab"}.fa.fa-snapchat-square{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-snapchat-square:before{content:"\f2ad"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-themeisle,.fa.fa-yoast{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 6 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 6 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-meetup,.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 6 Brands";font-weight:400}
\ No newline at end of file
diff --git a/html/font-awesome/webfonts/fa-brands-400.ttf b/html/font-awesome/webfonts/fa-brands-400.ttf
new file mode 100644
index 0000000000000..5efb1d4f96407
Binary files /dev/null and b/html/font-awesome/webfonts/fa-brands-400.ttf differ
diff --git a/html/font-awesome/webfonts/fa-regular-400.eot b/html/font-awesome/webfonts/fa-regular-400.eot
deleted file mode 100644
index d62be2fad885f..0000000000000
Binary files a/html/font-awesome/webfonts/fa-regular-400.eot and /dev/null differ
diff --git a/html/font-awesome/webfonts/fa-regular-400.ttf b/html/font-awesome/webfonts/fa-regular-400.ttf
new file mode 100644
index 0000000000000..c5ac00957778d
Binary files /dev/null and b/html/font-awesome/webfonts/fa-regular-400.ttf differ
diff --git a/html/font-awesome/webfonts/fa-regular-400.woff b/html/font-awesome/webfonts/fa-regular-400.woff
deleted file mode 100644
index 43b1a9ae49db0..0000000000000
Binary files a/html/font-awesome/webfonts/fa-regular-400.woff and /dev/null differ
diff --git a/html/font-awesome/webfonts/fa-solid-900.eot b/html/font-awesome/webfonts/fa-solid-900.eot
deleted file mode 100644
index c77baa8d46ab4..0000000000000
Binary files a/html/font-awesome/webfonts/fa-solid-900.eot and /dev/null differ
diff --git a/html/font-awesome/webfonts/fa-solid-900.ttf b/html/font-awesome/webfonts/fa-solid-900.ttf
new file mode 100644
index 0000000000000..43ba1cc7d94f6
Binary files /dev/null and b/html/font-awesome/webfonts/fa-solid-900.ttf differ
diff --git a/html/font-awesome/webfonts/fa-solid-900.woff b/html/font-awesome/webfonts/fa-solid-900.woff
deleted file mode 100644
index 77c1786227f53..0000000000000
Binary files a/html/font-awesome/webfonts/fa-solid-900.woff and /dev/null differ
diff --git a/html/font-awesome/webfonts/fa-v4compatibility.ttf b/html/font-awesome/webfonts/fa-v4compatibility.ttf
new file mode 100644
index 0000000000000..243bc25bd5ee1
Binary files /dev/null and b/html/font-awesome/webfonts/fa-v4compatibility.ttf differ
diff --git a/html/fonts/courierprime-code.woff b/html/fonts/courierprime-code.woff
new file mode 100644
index 0000000000000..c2c8fbed204bd
Binary files /dev/null and b/html/fonts/courierprime-code.woff differ
diff --git a/html/fonts/round-control.woff b/html/fonts/round-control.woff
new file mode 100644
index 0000000000000..7219c2c029c36
Binary files /dev/null and b/html/fonts/round-control.woff differ
diff --git a/html/hard-hat-exclamation.png b/html/hard-hat-exclamation.png
index e22eb61b8feca..0674bd9a37147 100644
Binary files a/html/hard-hat-exclamation.png and b/html/hard-hat-exclamation.png differ
diff --git a/html/image-minus.png b/html/image-minus.png
index b2bac2c45abb9..964491ffd63f5 100644
Binary files a/html/image-minus.png and b/html/image-minus.png differ
diff --git a/html/image-plus.png b/html/image-plus.png
index 308c1ae0a2866..4370241d3aea0 100644
Binary files a/html/image-plus.png and b/html/image-plus.png differ
diff --git a/html/images/bluentlogo.png b/html/images/bluentlogo.png
index d5614a964eef8..f2095ad05beb6 100644
Binary files a/html/images/bluentlogo.png and b/html/images/bluentlogo.png differ
diff --git a/html/images/ccalogo.png b/html/images/ccalogo.png
new file mode 100644
index 0000000000000..b0f8cd455d7e7
Binary files /dev/null and b/html/images/ccalogo.png differ
diff --git a/html/images/falogo.png b/html/images/falogo.png
new file mode 100644
index 0000000000000..a4a699c18f55d
Binary files /dev/null and b/html/images/falogo.png differ
diff --git a/html/images/foundlogo.png b/html/images/foundlogo.png
new file mode 100644
index 0000000000000..4ff6515006aaa
Binary files /dev/null and b/html/images/foundlogo.png differ
diff --git a/html/images/ntlogo.png b/html/images/ntlogo.png
index 6c03fa8b60a69..533b3e61a52d1 100644
Binary files a/html/images/ntlogo.png and b/html/images/ntlogo.png differ
diff --git a/html/images/ofbluelogo.png b/html/images/ofbluelogo.png
new file mode 100644
index 0000000000000..ce6b98ea9712d
Binary files /dev/null and b/html/images/ofbluelogo.png differ
diff --git a/html/images/ofntlogo.png b/html/images/ofntlogo.png
new file mode 100644
index 0000000000000..c6309545a8afb
Binary files /dev/null and b/html/images/ofntlogo.png differ
diff --git a/html/images/oldbluentlogo.png b/html/images/oldbluentlogo.png
new file mode 100644
index 0000000000000..d5614a964eef8
Binary files /dev/null and b/html/images/oldbluentlogo.png differ
diff --git a/html/images/oldntlogo.png b/html/images/oldntlogo.png
new file mode 100644
index 0000000000000..6c03fa8b60a69
Binary files /dev/null and b/html/images/oldntlogo.png differ
diff --git a/html/images/sierralogo.png b/html/images/sierralogo.png
new file mode 100644
index 0000000000000..a13ac3713670e
Binary files /dev/null and b/html/images/sierralogo.png differ
diff --git a/html/lobby_screen/buttons.mp4 b/html/lobby_screen/buttons.mp4
new file mode 100644
index 0000000000000..c4ae8e1f4361a
Binary files /dev/null and b/html/lobby_screen/buttons.mp4 differ
diff --git a/html/lobby_screen/light_left.png b/html/lobby_screen/light_left.png
new file mode 100644
index 0000000000000..f1f81e5050ccc
Binary files /dev/null and b/html/lobby_screen/light_left.png differ
diff --git a/html/lobby_screen/light_right.png b/html/lobby_screen/light_right.png
new file mode 100644
index 0000000000000..6277ceee47dfc
Binary files /dev/null and b/html/lobby_screen/light_right.png differ
diff --git a/html/lobby_screen/lobby.html b/html/lobby_screen/lobby.html
new file mode 100644
index 0000000000000..56d66f26137ba
--- /dev/null
+++ b/html/lobby_screen/lobby.html
@@ -0,0 +1,530 @@
+
+
+
+ Lobby
+
+
+
+
+
+
+
+
+
+