Соблюдение этого Гайда – залог успешного мержа Вашей фичи в репозиторий.
Проект МассМета 🧰 – это, постоянно обновляющаяся, модульная ветка от проекта /TG/station. Тут мы добавляем свои фичи и по возможности откатываем неудачные. Прочитав эту инструкцию – Вы поймете как правильно нужно внедрить к нам Вашу идею, чтоб ее могли увидеть в свет другие игроки нашего сервера.
Несоблюдение данного руководства приведёт к стагнации код-базы проекта, как это было до. Осознав наши прошлые ошибки - было принято решение привести проект к модульности подобно той, как на серверах Skyrat, однако доработанной под наши нужды. Оригинальное руководство (на англ.)
Прежде чем открывать PR на слияние, то было бы неплохо проверить Ваш код на работоспособность. А именно:
- Скомпилируйте билд на своей локальной машине.
- Запустите билд с Вашей Фичей и посмотрите как она себя ведёт, если ли какие-либо аномалии.
- Если Ваша Фича предполагает взаимодействие нескольких игроков, то можете воспользоваться Гостевым аккаунтом. Для этого выйдите из BYOND-хаба и зайдите на локалку как Guest-[много циферок].
Как только Вы считаете, что уже достаточно проверили своё творение, то создавайте PR на наш репозиторий, там Вам уже подскажут как можно доработать тот или иной момент.
Это одна из многих видов Систем Контроля Версий. Проект /TG/station располагается именно на нем. Сам Git одновременно и достаточно проработан в плане алгоритмов, он ими же и ограничен. Они не всегда могут однозначно самостоятельно разрешить определенные изменения в коде, что приводит к конфликтам, которые нужно резолвить вручную.
Подробнее про сам Git и как с ним работать тут.
Начнём сразу с показательного примера.
Предположим, что в какой-то строчке оригинального файла foobar.dm
/TG/station было так:
var/something = 1
Однако, под наши нужны нам потребовалось изменить значение с 1 на 2 под какую-то фичу в проекте,
- var/something = 1
+ var/something = 2 //MASSMETA EDIT
Но неожиданно апстрим /TG/station вносит свои изменения (commit: their-feature) в эту же строку файла, меняя её у себя с 1 на 4,
- var/something = 1
+ var/something = 4
Затем мы решили синхронизировать изменения и видим следующее,
<<<<<< HEAD:foobar.dm
var/something = 2 //MASSMETA EDIT
======
var/something = 4
>>>>>> their-feature:foobar.dm
В данном случае в череду коммитов /TG/station внедряется дополнительный, про который известно только нам самим. ГитХаб, видя подобное несоответствие - даёт нам сделать выбор.
Например, нам нужно оставить только Наше изменение, то просто удаляем все что нам добавил ГитХаб и оставляем только нужное,
var/something = 2 //MASSMETA EDIT
Подобного рода конфликты разрешаются именно ручками, однако есть другие подходы в виде модульного кода, о которых мы расскажем далее в данном руководстве.
Подробнее про Ветвления и Слияния.
У Вашего модуля должно быть короткое и информативное название в документации, например - shuttle_toggle
.
Этим уникальным ID Вы затем назовёте:
-
Свою модульную папку
modular_meta/features/shuttle_toggle/
, в которой вы будете локально работать. -
А также в дальнейшем Вы будете помечать все Немодульные изменения в коде /TG/station.
Теперь подробнее про виды модуляризации.
Время от времени наступает момент, когда редактирование оригинальных файлов /TG/station становится неизбежным.
📌 Пожалуйста, не забудьте записать факт их изменения под пунктом "Original TG Changes" в readme.md
вашего модуля.
В этих случаях мы решили применять следующую стандартизацию:
-
Добавление:
//MASSMETA EDIT ADDITION BEGIN (shuttle_toggle) var/adminEmergencyNoRecall = FALSE var/lastMode = SHUTTLE_IDLE var/lastCallTime = 6000 //MASSMETA EDIT ADDITION END
-
Удаление:
//MASSMETA EDIT REMOVAL BEGIN (shuttle_toggle) /* for(var/obj/docking_port/stationary/S in stationary) if(S.id = id) return S */ //MASSMETA EDIT REMOVAL END
-
Изменения:
//MASSMETA EDIT CHANGE BEGIN (shuttle_toggle) /* ORIGINAL if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) */ if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE, SHUTTLE_DISABLED) //MASSMETA EDIT CHANGE END return 1
💡 Если предполагается "Масштабное" изменений кода, то такое переопределение уже можно переместить в модуль. Однако на месте удаления обязательно допишите: (Moved to: modular_meta/features/shuttle_toggle/randomverbs.dm)
⚠️ Обязательно оставляем все что было до вашего вмешательства под пометкой ORIGINAL!
В нашем проекте присутствует папка modular_meta/
, там будут храниться все наши "Модульные" изменения кода /TG/station.
💡 Она полностью независима и этим мы гарантируем, что кодеры с /TG/station не будут туда вмешиваться.
В этой папке есть ещё несколько подпапок и файлов:
Папка/Файл |
---|
~meta_defines/ 📂 |
features/ 📂 |
reverts/ 📂 |
tools/ 📂 |
modular_meta_defines.dm 📄 |
modular_meta_features.dm 📄 |
modular_meta_reverts.dm 📄 |
modularization_guide.md 📝 |
module_template.md 📝 |
readme.md 📝 |
Теперь подробнее про каждую из Папок:
-
~meta_defines/
📂Здесь лежат все наши модульные "определения" (они же defines).
Вынесена отдельно из папки
features/
вследствии нюансов связанных с самим проектом /TG/station. (т.к. сам byond работает с дефайнами только в директорииcode/__DEFINES/
, туда идёт переопределение наших файлов.)Если у вас есть define, который используется более чем в одном файле вашего кода, то он обязательно должен быть объявлен в файле этой папки.
💡 Если define применяется только в рамках одного файла, то его достаточно объявить вверху и внизу файла.
#define MY_DEFINE //some code with MY_DEFINE here #undef MY_DEFINE
📌 Пожалуйста, не забудьте записать факт их добавления под пунктом "Defines:" в
readme.md
вашего модуля.Все файлы в папке включены в
modular_meta/modular_meta_defines.dm
. -
features/
📂Здесь лежат все модульные файлы "Новых Фич", которых нет в апстриме. Каждой присвоен уникальный module_id.
Подробнее про строение папок отдельных модулей расскажем чуть ниже.
Только
includes.dm
файлы каждого модуля включены вmodular_meta_features.dm
. -
reverts/
📂Папка аналогичная
features/
, но там располагаются недавние откаты плохих и возвраты хороших по нашему мнению фич, введёных апстримом /TG/station.❗ Если фича была уже выпилена давно или же апстрим произвел её полное удаление сразу, то она уже может рассматриваться как самостоятельный модуль. Такое помещаем её уже в папку
features/
!Такой модуль именовать уже обязательно с припиской "revert_", например вот так: revert_module_id.
⚠️ Обязательно укажите в файлеreadme.md
модуля ссылку на пиар, который откатываевается!Только
includes.dm
файлы каждого модуля включены вmodular_meta_reverts.dm
. -
tools/
📂Тут лежат все дополнительные инструменты проверки нашего кода.
Они проверяют только файлы в модульной папке, помогая нам не совершать дополнительных ошибкок.
К ним идет прямое обращение только в файле:
.github/workflows/ci_suite.yml
.
Чтобы сохранить общий стиль и обеспечить удобную навигацию по большинству модулей, а также контролировать количество файлов и папок в репозитории, Вы должны располагать определённые типы файлов по своим папкам.
readme.md
.
Папка/Файл | Содержимое |
---|---|
code/ 📁 | Файлы кода: .dm |
icon/ 📁 | Файлы иконок и картинок: .dmi и .png |
sound/ 📁 | Звуковые файлы: .ogg и .waw |
includes.dm 📄 | Перечисление всех файлов в папке code/ |
readme.md 📝 | Полная документация к модулю, пример |
.txt
и .json
вы помещаете в папку strings/massmeta/
, т.к. код /TG/station не может работать нормально со всеми файлами-строк вне этой папки. Укажите факт из добавления в readme.md
вашего модуля.
⛔ У проектов Skyrat присуствует папка master_files/
, однако у нас в проекте её НЕТ! Все переопредения кода у нас помещается полностью в модуль с особыми пометками! Пояснения будут позже.
❗ Также у проектов Skyrat стандартно все новые файлы сразу включаются в общий файл tgstation.dme
, что я считаю достаточно трудным для дальнейшней поддрежки, да и в целом это не особо согласуется с их модульным принципом. У нашего проекта другой поход в этом моменте, как Вы видите.
Здесь располагается код двух типов:
Просто ложите все свои новые файлы кода в папку code/
своего модуля.
💡 Можете разбивать Ваш под по подпапкам, если в этом есть нужда.
Не забывайте проставлять все ваши пути к иконкам и звуку правильно!
С помощью этих файлов мы косвенно изменяем основной код /TG/station. Это позволяет нам очень изящно внедрять свои коррективы, не вмешиваясь напрямую в основной код. Тем самым не нарушая их череду коммитов и не создавая для нас самих в будущем Конфликтов Слияния.
Однако это является и минусом такого подхода. Гитхаб не сможет нам оперативно подсказать где файл поменялся из-за вмешательства апстрима и где следует учесть измененое или дополнительное переопределение. Иногда прямые изменения кода через //MASSMETA EDIT
предпочтительнее. Старайтесь использовать здравый смысл в этом вопросе.
code/
у /TG/station! Просто ложите вместе со всеми файлами в модуль.
Эти файлы выносите в "отдельную группу" с помощью пометки "master_"
, например: master_tg_filename.dm
.
📌 Пожалуйста, не забудьте записать факт таких переопределений под пунктом "Modular Overrides:" в readme.md
вашего модуля.
-
Пример модульного переопределения объекта 💡
Например, Вы решили модульно переопределить иконку и описание у мольберта (easel).
Оригинальный объект в коде /TG/station по пути
code/modules/art/paintings.dm
:/obj/structure/easel name = "easel" desc = "Only for the finest of art!" icon = 'icons/obj/art/artstuff.dmi' icon_state = "easel" density = TRUE resistance_flags = FLAMMABLE max_integrity = 60 var/obj/item/canvas/painting = null
Для этого создайте новый файл желательно с таким же именем как у оригинала и расположите в папке
code/
вашего модуля:code/master_paintings.dm
.Для выполнения нашей цели, наполнение даного файла будет выглядеть примерно так:
//ORIGINAL: code/modules/art/paintings.dm /obj/structure/easel desc = "Let yourself draw!" icon = 'massmeta/features/art/icons/artstuff.dmi' icon_state = "new_easel"
Теперь при компилировании проекта, данное переопределение подменит эти переменные у оригинального объекта мольберта. Тем самым в готовом проекте у мольберта будет уже Новая иконка и описание. Даже код /TG/station менять не пришлось! 🎉
-
Пример модульного добавления фичи в функцию 💡
Для простоты предположим, что вы хотите заставить оружие искрить при выстреле для имитации дульной вспышки.
Также Вы хотите, чтобы это можно было использовать со всеми видами оружия, использующими эту функцию.
В модульном файле объекта можно начать с добавления новой переменной
var/muzzle_flash
./obj/item/gun var/muzzle_flash = TRUE
Теперь у Вас будет у каждого наследника этого объекта доступна эта переменная. После этого, допустим, вы захотите проверять её и вызвать искры после выстрела.
У этого объекта уже есть процедура, что вызывается при стрельбе:
/obj/item/gun/proc/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
Теперь мы начинаем добавлять код для работы нашей фичи в дочернюю процедуру
/obj/item/gun/shoot_live_shot()
./obj/item/gun/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1) . = ..() if(muzzle_flash) spawn_sparks(src)
Тут мы обязательно вызываем такую конструкцию
. = ..()
, она по сути своей говорит, что мы наследуемся от родительской функции. После данной конструкции добаляем весь наш Новый код.Теперь при компилировании проекта, данные "добавления" допишутся в оригинальный объект и функцию. Этим мы добились того, что оружие при выстреле ещё и искрит, при том не вмешиваясь в функции /TG/station напрямую! 🎉
Как вы уже могли заметить, у языка DM частично заложена парадигма ООП, нас в данном случае интересует процесс наследования объектов и их функций.
💡 Вы также можете нажать F1 в Dream Maker и прочитать подробный мануал на английском.
-
.
– это возвращаемое значение нашей функции по умолчанию. Изначально оно равноnull
. -
..
– это возвращаемое значение родительской функции.Через
..()
Вы обращаетесь к родительской функции, этим вызывая её в нужном месте Вашей функции-наследника.
И теперь, если вы сделаетете подобый манёвр . = ..()
, то Вы вызовите родительскую функцию и присвоите её значение возвращаемому значению нашей функции. После этого можете свободно дополнять свою .
чем хотите.
Так же в случае, если Вы не хотите возвращать родительский вывод, то Вы можете сохранить его в любую переменную или просто использовать ..()
Мы так же можем вообще не вызывать ..()
- тогда функция оверрайдится полностью. Но я бы не рекомендовал делать подобное, ради перемещения ориг. функции в модуль и дальнейшей модификации её уже там, ибо когда /TG/station поменяет её у себя, то пиши пропало.
Также учтите, что вы не сможете при модульном дополнии функции использовать те переменные, которые были объявленны в функции оригинале!
Используются карты:
- 🔴 Оригинальные с офф ТГ (без прямых наших изменений)
- 🟡 Заимствованные с других билдов (некоторые желательно не менять, т.к. мы можем подтягивать обновления в других билдов)
- 🟢 Наши самодельные, они же полностью независимые, например, ProtoBoxStation (меняем и чиним как хотим)
Все наши Новые карты лежат по пути massmeta/_maps/map_files/
(не в модульной папке).
К каждой карте идёт дополнительно .json
файл-конфигурации в massmeta/_maps/
, не забудьте добавить его тоже!
Когда вы добавляете новый элемент на карту, то вы должны сперва определеить масштаб переделок.
-
Если это небольшое изменение на 1 предмет, то используйте простой автоматизатор области.
Автомаппер простых областей использует записи точек отсчета, чтобы поместить один элемент в область карты, которая имеет определенный смысл.
-
Если речь идет об изменении целой комнаты, то используйте автоматизатор шаблонов.
Автомаппер использует готовые шаблоны для переопределения участков карты, используя координаты для определения начального местоположения. Примеры смотрите в файле automapper_config.toml.
TGUI - еще один исключительный случай, поскольку он использует Javascript, который не может быть модульным, нежели же код DM.
ВСЕ файлы TGUI находятся в папке /tgui/packages/tgui/interfaces/
и ее подкаталогах. Нет какой-то конкретной папки для Наших TGUI файлов!
📌 Пожалуйста, не забудьте записать факт их добавления/изменения под пунктом "TGUI Files:" в readme.md
вашего модуля.
При изменении оригинальных файлов TGUI поступаем аналогично, как и при изменении вышележащего кода DM, однако схема написания комментариев тут несколько иная.
Вы можете использовать как // MASSMETA EDIT
, так и /* MASSMETA EDIT */
, хотя в некоторых случаях вам придется использовать одно вместо другого. (в некотрых языках '//' - могут не являться комментированием, учтите это)
В целом, старайтесь, чтобы комментарии к изменениям находились на той же строке, что и само изменение. Предпочтительно внутри JSX-тега. Например:
<Button
onClick={() => act('spin', { high_quality: true })}
icon="rat" // MASSMETA EDIT ADDITION
</Button>
<Button
onClick={() => act('spin', { high_quality: true })}
// MASSMETA EDIT ADDITION START - another example, multiline changes
icon="rat"
tooltip="spin the rat."
// MASSMETA EDIT ADDITION END
</Button>
<SomeThing someProp="whatever" /* it also works in self-closing tags */ />
В крайнем случае Вы можете заключить ваше редактирование в фигурные скобки, например так:
{/* MASSMETA EDIT ADDITION START */}
<SomeThing>
someProp="whatever"
</SomeThing>
{/* MASSMETA EDIT ADDITION END */}
// THIS IS A MASSMETA UI FILE
Таким образом, они легко идентифицируются как Наши модульные файлы TGUI .tsx
и .jsx
.
Собственно ничего больше делать и не нужно, комментарии // MASSMETA EDIT
в таком файле TGUI излишне.
Терпение и труд – ТГ к*дера перетрут. Если мы будем последовательны, то в конечном итоге это избавит НАС от будущих болей в области ГМ, когда Нам (Вам) же придется разрешать конфликты вручную. Благодаря более скрупулезному документированию будет сразу понятно, какие изменения были сделаны, где и с помощью каких функций, и все станет гораздо менее двусмысленным и запутанным.
Желаю удачи в ТГ кодинге. Помните, что сообщество всегда готово помочь Вам, если вдруг понадобится помощь.
Оригинальное руководство: Skyrat/NovaSector. Перевод + дополнения: Artemchik542. Рецензент текста: SmArtKar.