Моддинг Alem
Alem 0.22.01 · руководство для моддеров

Как сделать мод для Alem

Мод в Alem — это папка с текстовыми файлами и картинками. Половину модов можно сделать вообще без программирования: вы правите таблицы, из которых собрана игра, а игра кладёт ваши строки поверх своих. А если таблиц мало — есть скрипты на Lua: «если случилось это — сделай то». Всё, что ниже, можно написать на телефоне, в обычном блокноте.

20 таблиц скрипты Lua свои звери и растения свои постройки свои события RU / EN
Скачать Пять готовых модов Оружие, своя руда, баланс и имена — рабочие примеры, которые можно разобрать и переделать под себя.

01Что такое мод в Alem

Почти во всех играх мод — это кусок программы. В Alem не так, и на это есть причина: игра собирается для Android через IL2CPP, а такая сборка физически не умеет запускать чужой C#. Поэтому моддинг здесь устроен иначе — через данные.

Почти все числа и списки игры вынесены из кода в текстовые таблицы: урон каждого оружия, состав каждого рецепта, броня одежды, цена построек, дерево технологий, черты характера, пулы имён, вес и стоимость каждого ресурса. Эти таблицы лежат внутри игры и читаются при запуске.

Мод — это папка с такими же таблицами. Игра сначала читает свою, потом кладёт сверху вашу, запись к записи. Вы можете:

  • поменять числа у существующих вещей — урон ножа, цену стены, вес стали;
  • добавить новые вещи — оружие, одежду, рецепты, технологии, черты характера, имена;
  • завести свой ресурс, которого в игре не было — свою руду, свой металл;
  • подменить любую картинку своей;
  • перевести всё добавленное на английский.

Всё это — обычные текстовые файлы. Их можно писать в блокноте на телефоне, и никакой Unity для этого не нужен.

02Первый мод за пять минут

Самый быстрый способ понять устройство мода — посмотреть на живой. В игре есть кнопка, которая его создаёт.

  1. Главное меню → МОДЫ.
  2. Внизу справа — СОЗДАТЬ ПРИМЕР. Нажмите. В списке появится «Пример мода».
  3. Нажмите у него ВЫКЛ, чтобы стало ВКЛ.
  4. Начните новую игру и загляните в плавильню — там появился рецепт «Выплавить мифрил». А нож стал слабее.
  5. Теперь откройте папку мода (её путь написан прямо на экране «Моды», голубой строкой) и посмотрите файлы внутри. Это и есть весь мод.

Дальше проще всего работать так: скопировать папку примера, переименовать, поправить mod.json и править файлы под себя.

Зачем это нужно

На телефоне нет ни редактора кода, ни удобного файлового менеджера. Кнопка «Создать пример» — способ получить рабочий мод, не имея компьютера вообще.

03Из чего состоит мод

Мод — это папка. Обязателен в ней ровно один файл: mod.json. Всё остальное добавляется по необходимости — берите только те таблицы, которые собираетесь трогать.

структура папки мода
МойМод/
  mod.json          ← паспорт, обязателен
  weapons.json      ← оружие
  recipes.json      ← рецепты станков
  apparel.json      ← одежда и броня
  resources.json    ← ресурсы (в том числе свои)
  buildings.json    ← цены и прочность построек
  techs.json        ← дерево технологий
  pawns.json        ← черты (с эффектами), биографии, имена
  events.json       ← свои события с выбором
  plants.json       ← дикие растения на карте
  animals.json      ← дикие звери
  livestock.json    ← домашний скот
  furniture.json    ← своя мебель и станки
  factions.json     ← державы на глобусе
  biomes.json       ← настройка биомов
  quests.json       ← заказы держав
  needs.json        ← свои потребности колонистов
  storytellers.json ← свои рассказчики
  challenges.json   ← свои испытания
  info.json         ← описания для карточки «?»
  loc.json          ← английский перевод ваших строк
  scripts/          ← скрипты на Lua (см. разделы 32–35)
    myrules.lua
  textures/         ← картинки, повторяют пути игры
    Guns/knife/knife-right.png
    Items/mithril.png
    char/hat/3_down.png

mod.json — паспорт

Единственный обязательный файл. Без него папка не считается модом и в списке появится с ошибкой «нет mod.json».

mod.json
{
 "id": "steppe_pack",
 "name": "Степной набор",
 "author": "Ермахан",
 "version": "1.0",
 "gameVersion": "0.22",
 "desc": "Степное оружие, доспех и новая руда."
}
ПолеОбязательноЧто это
idдаВнутреннее имя мода. Только латинские буквы, цифры, - и _. По нему игра запоминает, включён ли мод, и по нему же называет папку при установке из архива. Двух модов с одним id быть не должно.
nameнетНазвание для списка. Если не указать — покажется id.
authorнетВаше имя.
versionнетВерсия мода, любая строка. По умолчанию 1.0.
gameVersionнетДля какой версии игры сделан. Пока справочно — игра ничего по нему не запрещает.
descнетОписание в одну-две строки. Показывается в карточке мода.
Про id

Не используйте в id точки, пробелы и слэши. Игра их вырезает, и мод может оказаться в папке с неожиданным именем.

04Куда класть мод и как его включить

Игра ничего не качает из интернета. Мод — это файл, который уже лежит на устройстве. Есть два пути.

Способ 1. Архивом (для игроков)

  1. Соберите папку мода в .zip. Можно паковать и саму папку, и её содержимое — игра разберётся с обоими вариантами.
  2. Скачайте файл на телефон.
  3. Меню → МОДЫ → ВЫБРАТЬ ФАЙЛ. Откроется системное окно, найдите свой .zip и подтвердите.
  4. Мод распакуется и появится в списке. Включите его кнопкой ВКЛ.

Игра также сама показывает архивы, которые нашла в «Загрузках», «Документах» и в своей папке — их можно установить нажатием на имя файла. Если список пуст, это не поломка: начиная с Android 11 система закрывает чужие папки, и надёжен только выбор файла вручную.

Способ 2. Папкой (для авторов)

Распакованную папку мода можно положить прямо в папку модов — архив для этого не нужен. Точный путь написан на экране «Моды» голубой строкой, скопируйте его оттуда. Выглядит он примерно так:

android
/storage/emulated/0/Android/data/<пакет игры>/files/Mods/

Положили папку → вернулись в игру → ОБНОВИТЬ → мод в списке. Это самый удобный режим работы, пока вы мод пишете: поправил файл, нажал «Обновить», проверил.

Порядок модов

У включённых модов есть стрелки ▲▼. Порядок решает споры: если два мода правят одно и то же, побеждает тот, кто ниже в списке. То же правило действует и для картинок.

Важно про партии

Меняйте набор модов до начала партии. Сохранение, сделанное с другими модами, может недосчитаться вещей: если мод выключен, его предметы изымаются из колонии, и игра честно об этом напишет. Перед сменой набора модов игра предложит сделать копию сохранения — соглашайтесь.

05Главное правило: слияние по ключу

Это самое важное, что нужно понять про моды Alem. Файл мода не заменяет файл игры. Он на него накладывается — построчно, по ключу записи.

У каждой таблицы есть ключевое поле (обычно id). Игра берёт вашу запись и ищет свою с таким же ключом:

  • Ключ совпал → игра меняет только те поля, которые вы написали. Всё остальное остаётся её.
  • Ключа нет → ваша запись добавляется как новая.

Поэтому чтобы ослабить нож, не нужно переписывать всю таблицу оружия. Достаточно двух строк:

weapons.json вашего мода
{
 "weapons": [
  { "id": "knife", "dmg": 9 }
 ]
}

Урон стал 9, а скорость, дальность, цена, картинка и всё прочее у ножа остались игровыми — включая те, что появятся в будущих версиях.

Почему так сделано

Если бы мод подменял таблицу целиком, то мод «нож слабее» обязан был бы тащить с собой всё оружие игры — и при первом же обновлении молча вернул бы старые числа всему остальному. Слияние по ключу избавляет от этого.

Ключи таблиц

ФайлСписок внутриКлюч
weapons.jsonweaponsid
weapons.jsonmaterialskey
recipes.jsonrecipesstation + id
apparel.jsonapparelid
resources.jsonresourcesid
buildings.jsonbuildingskind + variant
techs.jsontechsid
pawns.jsontraitsname
pawns.jsonchildhoods / adulthoodsname
pawns.jsonсписки имёнбез ключа — просто дополняются
events.jsoneventsid
plants.jsonplantsid
animals.jsonanimalsid
livestock.jsonlivestockspecies
furniture.jsonfurnitureid
factions.jsonfactionsname
biomes.jsonbiomesid
quests.jsonquestsid
needs.jsonneedsid
storytellers.jsonstorytellersname
challenges.jsonchallengesid
info.jsonentriesключ записи

06Как писать JSON, чтобы он не сломался

Все таблицы — это JSON. Формат простой, но придирчивый. Пять правил покрывают почти все ошибки новичков:

  1. Кавычки только прямые", а не «ёлочки» и не ’. Телефонная клавиатура любит подставлять красивые — следите.
  2. Запятая между элементами, но не после последнего. { "a": 1, "b": 2 } — верно; { "a": 1, } — ошибка.
  3. Дробные числа через точку: 0.5, не 0,5.
  4. Комментариев в JSON нет. Строчку // так нельзя файл не переживёт.
  5. Файл в кодировке UTF-8. Иначе русские названия превратятся в кракозябры.

Значения перечислений (тип урона, слот одежды, ресурс) пишутся именами в кавычках, а не числами: "Sharp", "Head", "Iron". Числа сюда писать нельзя — игра ругнётся в лог и возьмёт запасное значение.

Проверка на месте

Игра проверяет каждый JSON мода сразу при показе списка модов, а не в момент загрузки мира. Если файл битый, вы увидите в карточке мода красную строку «ОШИБКА: weapons.json: …» с точной причиной. Это ваш главный инструмент отладки.

07weapons.json — оружие

Файл состоит из двух списков: materials (из чего делают клинки) и weapons (само оружие).

пример: новое оружие целиком
{
 "weapons": [
  {
   "id": "kylysh",
   "name": "Кылыш",
   "melee": true,
   "family": true,
   "dmg": 17,
   "interval": 1.7,
   "range": 1.5,
   "dtype": "Sharp",
   "hitLabel": "Удар кылышем",
   "drawScale": 0.8,
   "craftWork": 6
  }
 ]
}
ПолеПо умолч.Что делает
idКлюч записи. Латиницей.
name= idНазвание в игре.
spriteId= idЧья картинка используется. Позволяет сделать оружие без своего рисунка: "spriteId": "sword".
meleefalseБлижний бой.
familyfalseСемейство: игра сама породит варианты по материалам (деревянный, железный, стальной…) и рецепты к ним.
handOnlyfalseНе существует как предмет (гранаты в руках).
dmg1Урон за удар или выстрел.
interval1Секунд между ударами. Больше — медленнее.
range1.4Дальность в клетках.
accMul1Множитель точности.
pen0Пробитие брони (0…1 — доля брони, которую оружие игнорирует).
burst0Выстрелов в очереди.
burstCd0Пауза между выстрелами внутри очереди.
dtypeSharpSharp режущий · Blunt дробящий · Heat ожог.
hitLabelКак удар назовётся в описании раны.
drawScale1Размер картинки в руке, за спиной и на земле. 1 = длиной со ствол. У ножа 0.55, у копья 0.88.
spriteAngle0Ручной поворот картинки. Обычно не нужен: игра сама разворачивает вертикально нарисованный клинок.
cost1Type / cost1Основной материал и сколько его.
cost2Type / cost2Второй материал.
moreЕщё ингредиенты списком: [{ "res": "Rivets", "count": 4 }].
craftWork2Сколько работы на изготовление.

Материалы клинков

Список materials — из чего игра порождает варианты оружия с "family": true. Ключ записи — key.

woodlimegran ironbronzesteel
{
 "materials": [
  { "key": "iron", "dmgMul": 1.25 }
 ]
}

Здесь dmgMul — множитель урона, res — ресурс, amount — сколько его уходит, name — как называется вариант («железный»).

Новое оружие само в игре не появится

Мало добавить запись — оружие нужно ещё открыть. Иначе его не будет ни в одном станке. Как это делается — раздел 12, техи: ключ weapon:kylysh или, для семейства, weaponfam:kylysh@iron.

08recipes.json — рецепты станков

Один список recipes, ключ записи — пара station + id. Один и тот же id у разных станков — законно (простое блюдо есть и на плите, и на костре).

Станки

stationЧто это
campfireКостёр
cookКухонная плита
butcherРазделочный стол
furnaceПлавильная печь
steelСталеплавильная печь
stonecutКамнерезный стол
chemХимический стол
tailorШвейный стол
craftВерстак
пример
{
 "recipes": [
  {
   "station": "furnace",
   "id": "mithril_ingot",
   "name": "Выплавить мифрил",
   "in1Type": "SilverOre", "in1": 10,
   "in2Type": "Coal",     "in2": 2,
   "outType": "Mithril",  "outCount": 1,
   "work": 4
  }
 ]
}
ПолеЧто делает
stationНа каком станке. Из таблицы выше.
idКлюч рецепта. Им же он открывается техом: recipe:mithril_ingot.
nameНазвание заказа.
in1Type / in1Первый ингредиент и его количество.
in2Type / in2Второй ингредиент.
in1Group / in2GroupВместо точного вида — группа: 0 точный тип · 1 любое мясо · 2 любая растительная еда. Так написаны блюда: «мясное» берёт оленину, крольчатину или что нашлось.
outType / outCountЧто получится и сколько.
outType2 / outCount2Побочный продукт (например, уголь).
moreДополнительные ингредиенты списком.
workСколько работы на одну порцию.
weaponIdРецепт делает оружие с этим id.
apparelIdРецепт шьёт одежду с этим id.
colorPickПоказать выбор цвета при пошиве.
medicinePoolРецепт берёт лекарства из общего пула.
Оружие и одежда — рецепты бывают лишними

Для оружия с "family": true и для одежды рецепты создаются сами — по таблицам weapons и apparel. Писать их руками в recipes.json нужно только для особых случаев.

09apparel.json — одежда и броня

Один список apparel, ключ — id. Порядок записей в файле = порядок в окне станка.

пример: свой доспех
{
 "apparel": [
  {
   "id": "steppecoat",
   "name": "Степной кафтан",
   "slot": "ArmorOver",
   "armorSharp": 0.3,
   "armorBlunt": 0.22,
   "insCold": 6,
   "maxHp": 130,
   "look": 3,
   "matType": "Leather",
   "matCost": 28,
   "more": [ { "res": "Rivets", "count": 6 } ],
   "tint": "#6B4B2E"
  }
 ]
}
ПолеПо умолч.Что делает
idКлюч. Им же вещь открывается: apparel:steppecoat.
name= idНазвание.
slotTorsoКуда надевается — см. ниже.
armorSharp0Доля блокируемого режущего урона: 0.3 = 30%.
armorBlunt0То же для дробящего.
armorHeat0То же для ожога.
insCold0Насколько градусов сдвигает вниз порог холода. Это и есть «тепло» в описании.
insHeat0Насколько поднимает порог жары.
maxHp100Прочность вещи.
look0Номер картинки слоя. См. раздел 16.
matTypeClothОсновной материал пошива.
matCost10Сколько его нужно.
moreДополнительные материалы списком.
waterproof0Защита от намокания, 0…1.
tendBonus0Прибавка врачу к качеству лечения (халат врача).
colorfulfalseВещь красится в цвет при пошиве — игрок выбирает цвет.
tintПостоянный цвет вещи, "#7A5A38". Такая вещь не красится никогда и нигде.
coversFacefalseЗакрытый шлем: под ним не видно бороды.

Слоты

slotЧто этоГде шьётся
HeadГолова: шапки, шлемышвейный стол; железные и стальные — верстак
TorsoТело: рубаха, куртка, паркашвейный стол
TorsoOverПоверх тела: сорочкашвейный стол
LegsНоги: штанышвейный стол
ArmorOverДоспех поверх всеговерстак

Правило простое: всё, что считается бронёй, делается на верстаке, остальное — на швейном столе. Бронёй считается вещь в слоте ArmorOver, а также головной убор из железа или стали.

Два условия, чтобы вещь появилась

Новая одежда видна в станке только если выполнены оба условия: правильный станок по слоту и выданный ключ apparel:<id> — через тех в techs.json. Забыли ключ — вещь есть в таблице, но её нигде нельзя сшить.

10resources.json — ресурсы, в том числе свои

Здесь живут вес, цена, сытость и признаки каждого вида. Ключ — id. У этой таблицы две работы.

Работа первая: поправить существующий ресурс

Если id совпал с игровым (список — раздел 17), меняются свойства этого вида:

{
 "resources": [
  { "id": "Steel", "value": 14, "weight": 0.5 }
 ]
}

Работа вторая: завести свой ресурс

Незнакомый id — это новый вид. Он получит собственный номер (модовые номера начинаются с 1000), будет лежать на складах, носиться, гореть, торговаться и съедаться — по тем признакам, которые вы укажете.

{
 "resources": [
  {
   "id": "Mithril",
   "name": "Мифрил",
   "nameEn": "Mithril",
   "weight": 0.3,
   "value": 40,
   "mineral": true,
   "icon": "Items/mithril",
   "shape": "ingot",
   "color": "#8FD8E8"
  }
 ]
}
ПолеПо умолч.Что делает
idКлюч. Должен начинаться с буквы. Имя вида, которым на него ссылаются рецепты.
weight0.2Вес единицы.
value1Цена для торговли и оценки богатства колонии.
nutrition0Сытость одной штуки. Только у еды.
nameНазвание по-русски. Только для своих видов — у встроенных имя живёт в переводе игры.
nameEn= nameНазвание по-английски.
iconПуть к картинке, как в игре: "Items/mithril". Файл кладётся в textures/Items/mithril.png.
shapenuggetФорма заглушки, если картинки нет — см. ниже.
colorЦвет заглушки, "#8FD8E8".

Признаки

Это не ярлыки для красоты — каждый читается конкретной частью игры: кухней, огнём, торговлей, хирургией, скотом.

currencyflammablebloodPack organimplantbionic mealanimalProduceplantFood meatmineralfodder food

Формы заглушек (shape)

ingotbarpowder vialcoinmeat graintubercloth herbgemnugget

Без картинки вещь не станет розовым квадратом: игра нарисует силуэт указанной формы вашим цветом. Мод без художника всё равно получает вещь, которую видно и не спутать с соседней стопкой.

Свой ресурс сам не появится

Его никто не добывает и ни из чего не делает, пока вы не добавите рецепт в recipes.json. Жилы в горе и растения на грядке задаёт генератор мира, а не таблица — их модом пока не расширить.

11buildings.json — цена и прочность построек

Эта таблица не создаёт новые постройки — она перекрывает числа существующих. Ключ — kind плюс необязательный variant.

{
 "buildings": [
  { "kind": "Wall", "costAmount": 4, "hp": 220 },
  { "kind": "Turret", "work": 40,
    "extra": [ { "res": "SteelMechanism", "count": 2 } ] }
 ]
}
ПолеЧто делает
kindВид постройки. Имя должно быть игровым — незнакомое даст ошибку в логе.
variantВариант вида: у осадных орудий и машин энергосети своя цена у каждого. Пусто — запись на весь вид.
costTypeОсновной ресурс.
costAmountСколько его.
workСекунд работы строителя.
hpПрочность.
extraДополнительные компоненты списком.

Названия видов подсмотрите в файле игры buildings.json — он лежит внутри сборки и покрывает все 53 вида. Правьте только те строки, которые нужны: остальные останутся игровыми.

12techs.json — технологии и ключи доступа

Это самая важная таблица для авторов контента. Именно тех выдаёт ключ, по которому вещь становится доступной. Нет ключа — вещь есть в таблицах, но её нигде нельзя сделать.

{
 "techs": [
  {
   "id": "steppe_smithing",
   "name": "Степная ковка",
   "branch": "Ремёсла",
   "era": 2,
   "cost": 600,
   "desc": "Кылыш и степной кафтан.",
   "prereq": [ "craft_smithy" ],
   "unlocks": [ "weaponfam:kylysh@iron", "apparel:steppecoat" ]
  }
 ]
}
ПолеПо умолч.Что делает
idКлюч теха.
nameНазвание в окне «Технологии».
branchВетка. Новое название = новая колонка в окне.
era11 ремёсла · 2 железо · 3 порох · 4 сталь · 5 пар и электричество.
cost100Очков исследования.
descОписание.
needAdvancedfalseТребует построенного продвинутого стола исследований.
prereq[]Список id техов, которые нужно изучить раньше.
unlocks[]Что открывает — ключи из таблицы ниже.

Словарь ключей

КлючОткрывает
building:FurnaceПостройку (имя вида).
recipe:mithril_ingotРецепт станка (его id).
weapon:bowКонкретное оружие.
weaponfam:kylysh@ironСемейство + материал: «железный кылыш».
apparel:steppecoatОдежду или броню.
bed:BedrollВариант кровати.
wallmat:WoodМатериал стен.
power:GeneratorМашину энергосети.
siege:MortarОсадное орудие.
crop:oakКультуру грядки.
mech:huntingМеханику игры (охота, рыбалка, приручение, торговля, первая помощь…).
Проверьте себя

Сделали новую вещь — задайте три вопроса: 1) есть ли запись в таблице, 2) есть ли рецепт или подходящий станок, 3) кто выдаёт ключ. Три «да» — вещь в игре. Любое «нет» — её не будет, и игра об этом не предупредит.

13pawns.json — черты, биографии, имена

Самая безопасная таблица для мода: черты лежат в сохранении именами, поэтому добавлять и убирать их можно свободно.

{
 "traits": [
  { "name": "Степняк",
    "desc": "Вырос в седле: ходит на 10% быстрее." },
  { "name": "Белоручка", "blocks": "Уборка",
    "desc": "За грязную работу не берётся." }
 ],
 "traitPairs": [
  { "a": "Степняк", "b": "Белоручка" }
 ],
 "maleNames": [ "Ермахан", "Актос" ],
 "femaleNames": [ "Айгерим" ],
 "lastNames": [ "Сериков" ],
 "nicknames": [ "Белый" ],
 "chronic": [ "Старая рана" ]
}
СписокКлючЧто это
traitsnameЧерты характера. blocks — работа, за которую человек не берётся. desc — описание в карточке «Персона».
traitPairsa + bВзаимоисключающие черты: обе одному человеку не выпадут.
childhoodsnameДетские биографии: bonus — прибавки к навыкам, blocks — навсегда закрытые работы.
adulthoodsnameВзрослые биографии, те же поля.
maleNames / femaleNamesПулы имён. Дополняются, ваши имена добавляются к игровым.
lastNames / nicknamesФамилии и клички.
rareMale / rareFemaleРедкие имена.
rareFullfirstРедкие пары «имя + фамилия» целиком.
chronicХронические болячки из генерации.

Эффекты черты

Черта мода — не просто ярлык: у неё может быть настоящий эффект. Блок effects задаёт до двадцати параметров. Проценты пишутся долями (0.15 = +15%), не указанное поле ни на что не влияет, эффекты разных черт складываются.

{
 "traits": [
  { "name": "Кочевник",
    "desc": "Вырос в пути: ходит быстрее и легче терпит холод.",
    "effects": { "moveSpeed": 0.15, "cold": 6, "hunger": -0.12, "mood": -2 } }
 ]
}
ПолеЧто меняетПолеЧто меняет
workSpeedскорость работыmoveSpeedскорость ходьбы
hungerкак быстро голодаетdamageTakenвходящий урон
aimточность стрельбыmeleeурон в ближнем бою
qualityкачество изделий (1.2 = как Аккуратист)poisonриск отравиться
painчувствительность к болиimmunityскорость иммунитета
sickChanceшанс заболетьcold / heatтерпит холод / жару, °C
carryгрузоподъёмностьresearchскорость исследований
restкачество снаdarkWorkработа в темноте
socialобщительностьmoodочки настроения (8 = Оптимист)
joyDrainкак быстро скучаетblocksработа, за которую не берётся
Можно править и наши черты

Запись { "name": "Трудяга", "effects": { "carry": 0.1 } } добавит Трудяге +10% к грузу, а его родные +15% к скорости работы останутся. Если описания нет, игра соберёт его сама из эффектов.

14loc.json — английский перевод

В игре две локали. Все ваши названия написаны по-русски — и в английской сборке они останутся русскими, пока вы не дадите перевод. Формат простой: русская строка → английская.

loc.json
{
 "en": {
  "Кылыш": "Kylysh",
  "Степной кафтан": "Steppe coat",
  "Степная ковка": "Steppe smithing",
  "Удар кылышем": "Kylysh strike",
  "Выплавить мифрил": "Smelt mithril"
 }
}

Ключ — ровно та строка, которую вы написали в таблице, символ в символ. Лишний пробел или другая буква «ё» — и перевод не найдётся.

  • Перевод мода не может переписать строки игры — свои названия у неё останутся своими.
  • Выключили мод — его строки уходят из таблицы вместе с ним.
  • У ресурсов есть более короткий путь: поле nameEn прямо в resources.json.

15Папка textures — свои картинки

Правило одно: в моде путь картинки такой же, как у игры, плюс .png. Игра при загрузке спрайта сначала спрашивает моды, потом свои файлы.

Что заменяемПуть в моде
Картинку ножа в рукеtextures/Guns/knife/knife-right.png
Картинку своего оружияtextures/Guns/kylysh/kylysh-right.png
Предмет на землеtextures/Items/mithril.png
Обломок гранитаtextures/granite-chunk.png
Слой облика (шапка №3)textures/char/hat/3_down.png

Как рисовать

  • PNG с прозрачностью, пиксель-арт (игра не сглаживает картинки моды — они останутся чёткими).
  • Оружие рисуется горизонтально, остриём вправо. Если нарисовать вертикально (выше, чем шире в 1.4 раза), игра сама повернёт картинку на −90° — она считает, что клинок нарисован вверх. Это касается только ближнего боя: лук так и остаётся вертикальным.
  • Точка отсчёта у оружия — центр картинки; размер задаётся полем drawScale, а не пикселями. Картинка 400×100 и 800×200 будут в игре одного размера.
  • Обломки камня рисуйте квадратными и без «верха»: игра поворачивает их на 0/90/180/270°, чтобы россыпь не выглядела штампованной. Тень с одной стороны выдаст поворот.
  • Если два включённых мода кладут картинку по одному пути — побеждает тот, кто ниже в списке модов.

16Облик колониста: слои и номера look

Колонист собирается из слоёв-картинок. Каждый слой — папка, внутри пронумерованные варианты, каждый в четырёх видах.

один вариант слоя
char/hat/3_down.png   ← вид спереди (обязателен)
char/hat/3_up.png     ← со спины
char/hat/3_left.png   ← влево
char/hat/3_right.png  ← вправо
char/hat/3_down_w.png ← женский вариант (необязателен)

Поле look в apparel.json — это и есть номер варианта в папке своего слоя. Слоты и папки связаны так:

Слот вещиПапка слояЗанято игрой
Headchar/hat/0, 1, 2
Torso · TorsoOver · ArmorOverchar/top/ 0, 1, 2

Значит, ваша новая шапка — это "look": 3 плюс файл textures/char/hat/3_down.png.

Номера идут подряд

Игра набирает варианты слоя по порядку — 0, 1, 2, 3… — и останавливается на первом отсутствующем. Если положить 5_down.png, пропустив 3 и 4, его не увидит никто. Берите следующий свободный номер.

Правила покраски

  • "colorful": true — вещь красится в выбранный цвет при пошиве. Рисуйте её белой: цвет накладывается кодом.
  • "tint": "#7A5A38" — вещь всегда одного цвета. Тоже рисуется белой.
  • Броня (ArmorOver и железные/стальные шлемы) не красится никогда — её рисуют сразу в нужном цвете.
  • Под любым головным убором волосы скрываются автоматически, а под "coversFace": true — ещё и борода.

17Справочник: все ресурсы игры

Эти имена пишутся в полях in1Type, outType, costType, matType, res. Пишите точно, с заглавной буквы.

Сырьё и камень

WoodStoneLimestoneChunk GraniteChunkLimestoneBlockGraniteBlock PlanksHayGems

Руда и металл

IronOreIronCoal SteelCopperOreCopper BronzeLeadOreLead GoldOreSilverOreSulfur SaltpeterGlass

Детали и механизмы

RivetsIronComponentGears SpringsSteelMechanismPreciseParts

Еда

FoodRaspberryRice PotatoMeatVenison WolfMeatRabbitMeatHorseMeat FishMealSimpleMealFine MealBurntPemmicanMilk EggCheeseButter

Ткани и шкуры

ClothLeatherFur Wool

Медицина

HerbsMedicineAntibiotics BloodIBloodIIBloodIII BloodIVKidneyLung LiverHeartEye ArmLegProsthLegWood ProsthLegSteelBionicLegBionicArm BionicEye

Порох и боеприпасы

GunpowderAmmoMusketBall AmmoSmokelessArrowBolts CannonBallGrapeshotGrenade

Деньги и прочее

ImperialMarkSilverGold Corpse

Всего встроенных видов — 83. Ваши добавляются сверх них.

18Справочник: ключи и значения

ГдеПолеВозможные значения
оружиеdtypeSharp · Blunt · Heat
оружиеmaterials.key wood · lime · gran · iron · bronze · steel
одеждаslot Head · Torso · TorsoOver · Legs · ArmorOver
рецептstation campfire · cook · butcher · furnace · steel · stonecut · chem · tailor · craft
рецептin1Group0 точный · 1 любое мясо · 2 любая растительная еда
ресурсshapeingot · bar · powder · vial · coin · meat · grain · tuber · cloth · herb · gem · nugget
техera1 ремёсла · 2 железо · 3 порох · 4 сталь · 5 пар и электричество
техunlocksbuilding: · recipe: · weapon: · weaponfam: · apparel: · bed: · wallmat: · power: · siege: · crop: · mech:

Что открыто с самого начала, без исследований

Полезно знать, чтобы не выдавать ключ второй раз: костёр, пол, дверь, стена, разделочный стол, деревянные стены, спальник, стол исследований, уголок ремесла, рубаха, шапка, штаны, пелёнка, простая еда на костре, разделка, а также нож, копьё и дубина из дерева, известняка и гранита.

19Сейвы и совместимость

Тут стоит понимать, как игра хранит ваши вещи, — это объясняет все странности при смене набора модов.

Номера ресурсов

В сохранении предмет записан номером: «предмет 5, штук 20». У встроенных видов номера вечные. Ресурсам модов номера выдаются по порядку, начиная с 1000 — а значит, они зависят от того, какие моды включены и в каком порядке.

Чтобы это не ломало партии, каждое сохранение носит с собой свою карту «номер → имя». При загрузке она главнее: ваши ресурсы получат ровно те номера, что были в этой партии. Переставили моды местами — колония не пострадает.

Если мод выключили

Вещи, чей вид больше никто не описывает, изымаются из колонии, а игра пишет об этом письмо. Изъятие затрагивает склады, подсумки, груз караванов и задания — там, где был мифрил, останется пустое место, а не «дерево».

Перед сменой набора модов игра предлагает сделать копию сохранения. Соглашайтесь: это один тап, а откатиться потом уже нельзя.

Что безопасно менять на середине партии

БезопасноОпасно
Числа существующих вещей: урон, цена, вес, работа Убрать ресурс, который уже лежит на складе
Добавить черты, имена, биографииУбрать одежду, которая уже надета
Добавить техи и рецептыПоменять id своей вещи (для игры это «старая исчезла, новая появилась»)
Заменить картинкиВыключить мод посреди партии

20Когда что-то не работает

СимптомЧто проверить
Мода нет в спискеЕсть ли в папке mod.json. Лежит ли папка именно в Mods/. Нажали ли «ОБНОВИТЬ».
В карточке красная строка «ОШИБКА»Там же написан файл и причина — обычно лишняя запятая или неровные кавычки.
Мод включён, но ничего не поменялосьСовпадает ли id с игровым (регистр важен: knife, не Knife). Правильное ли имя списка — "weapons", а не "weapon".
Новой вещи нет ни в одном станкеНет ключа. Нужен тех с unlocks — см. раздел 12.
Новая броня не появилась у швейного столаБроня делается на верстаке, а не у швейного стола.
Вместо предмета непонятный камешекКартинка не найдена — проверьте путь в поле icon и файл в textures/. Заглушка рисуется по shape.
Новая шапка не надеваетсяНомер look идёт не подряд, или нет файла <номер>_down.png.
Оружие держат бокомКартинка нарисована не остриём вправо. Либо перерисуйте, либо задайте spriteAngle.
Русские буквы стали кракозябрамиФайл сохранён не в UTF-8.
В английской сборке русские названияНет loc.json или ключ в нём написан не символ в символ.
Порядок работы, который экономит время

Правьте по одному файлу за раз и после каждой правки жмите «ОБНОВИТЬ» на экране модов. Тогда вы всегда знаете, какая именно строка сломала мод. Когда меняешь пять файлов и потом ищешь ошибку — уходит вечер.

21Чего мод пока не может

Честный список границ — чтобы вы не потратили день на то, чего в игре ещё нет.

  • Своего вида работ в приоритетах. Колонист не возьмёт «рыбалку» сам — его отправляет скрипт (alem.task, раздел 34).
  • Своего поведения у зверя. Зверь мода ведёт себя как олень, кролик или волк — какой основе вы его научите.
  • Постройки со своей работой. Мебель мода берёт поведение нашей постройки (раздел 26): станок, склад, кровать, турель. Совсем новой механики у неё нет.
  • Нового биома на глобусе. Биом — это генерация планеты; модом можно только настроить наши пять (раздел 27).
  • Своей жилы в горе. Руду расставляет генератор мира. Свой ресурс выдаётся рецептом, растением, торговцем или скриптом.
  • Своих окон интерфейса. Есть письма и окно с кнопками (alem.dialog), но не свои экраны.
  • Кода на C#. На Android чужой C# не запускается в принципе — это ограничение сборки. Именно поэтому в игру встроен Lua.

Всё остальное уже есть: двадцать таблиц, свои звери, растения, постройки, державы, события, заказы, потребности, рассказчики и испытания — и скрипты, которые связывают это в свои правила.

22Готовый шаблон и чек-лист

Ниже — маленький, но полный мод: новое оружие, доспех, свой ресурс, рецепт, тех, который всё это открывает, и перевод. Скопируйте, переименуйте, правьте.

mod.json
{
 "id": "steppe_pack",
 "name": "Степной набор",
 "author": "ваше имя",
 "version": "1.0",
 "desc": "Кылыш, степной кафтан и мифрил."
}
weapons.json
{
 "weapons": [
  { "id": "kylysh", "name": "Кылыш", "melee": true, "family": true,
    "dmg": 17, "interval": 1.7, "range": 1.5, "dtype": "Sharp",
    "hitLabel": "Удар кылышем", "drawScale": 0.8, "craftWork": 6 }
 ]
}
apparel.json
{
 "apparel": [
  { "id": "steppecoat", "name": "Степной кафтан", "slot": "ArmorOver",
    "armorSharp": 0.3, "armorBlunt": 0.22, "insCold": 6, "maxHp": 130,
    "look": 3, "matType": "Leather", "matCost": 28,
    "more": [ { "res": "Rivets", "count": 6 } ],
    "tint": "#6B4B2E" }
 ]
}
resources.json
{
 "resources": [
  { "id": "Mithril", "name": "Мифрил", "nameEn": "Mithril",
    "weight": 0.3, "value": 40, "mineral": true,
    "shape": "ingot", "color": "#8FD8E8" }
 ]
}
recipes.json
{
 "recipes": [
  { "station": "furnace", "id": "mithril_ingot", "name": "Выплавить мифрил",
    "in1Type": "SilverOre", "in1": 10, "in2Type": "Coal", "in2": 2,
    "outType": "Mithril", "outCount": 1, "work": 4 }
 ]
}
techs.json
{
 "techs": [
  { "id": "steppe_smithing", "name": "Степная ковка", "branch": "Ремёсла",
    "era": 2, "cost": 600, "desc": "Кылыш, кафтан и выплавка мифрила.",
    "unlocks": [ "weaponfam:kylysh@iron", "apparel:steppecoat",
                  "recipe:mithril_ingot" ] }
 ]
}
loc.json
{
 "en": {
  "Кылыш": "Kylysh",
  "Степной кафтан": "Steppe coat",
  "Степная ковка": "Steppe smithing",
  "Мифрил": "Mithril",
  "Выплавить мифрил": "Smelt mithril",
  "Удар кылышем": "Kylysh strike"
 }
}

Чек-лист перед раздачей мода

  • Мод включается и выключается без ошибок в карточке.
  • Все новые вещи реально появляются в станках — проверено в новой игре.
  • У каждой новой строки есть перевод в loc.json.
  • Картинки на месте: в руке, за спиной, на земле, в списке заказов.
  • id мода уникален и не совпадает с чужими.
  • В архиве лежит папка мода или её содержимое — и внутри есть mod.json.
  • В описании написано, для какой версии игры мод и что он меняет.
  • Если есть скрипты — в логе нет строк [Lua] … с ошибками.
  • Мод проверен на новой партии: звери, растения и державы появляются только при создании карты.
И последнее

Начинайте с малого: один мод — одна идея. Набор из трёх видов оружия, который работает, полезнее большого мода, который не включается.

23events.json — свои события

Событие — это письмо, которое приходит игроку посреди партии: находка, беда, предложение. Мод может добавлять свои, и они встанут в общий поток рассказчика.

{
 "events": [
  {
   "id": "steppe_caravan_gift",
   "category": "good", "weight": 1.2, "cooldownDays": 12, "minDay": 4,
   "title": "Караван из Таавуна",
   "text": "Старший говорит: «{colonist}, степь помнит добро» — и оставляет кумыс.",
   "effects": [
     { "type": "give", "resource": "Kumys", "count": 12 },
     { "type": "mood", "value": 4, "days": 2, "text": "Гостили купцы" }
   ]
  }
 ]
}

Когда событие может выпасть

ПолеЧто задаёт
categorygood · neutral · threat. Категория решает, в какой момент рассказчик может его прислать.
weightКак часто выпадает. Все встроенные события категории весят вместе как 4, так что вес 1 — это примерно каждый пятый раз.
cooldownDaysНе чаще раза в столько дней.
oncetrue — только один раз за игру.
minDayС какого игрового дня.
minColonists / maxColonistsСколько живых людей в колонии.
minWealth / maxWealthБогатство колонии.
seasonspring · summer · autumn · winter.
minTemp / maxTempТемпература на улице.
needResource / needCountСколько такого ресурса должно быть в колонии.

Письмо и выбор

title и text — заголовок и текст; {colonist} в тексте заменится именем случайного колониста. Дальше одно из двух: либо effects — и событие просто случается, либо choices — ровно два варианта, у каждого свои последствия.

   "choices": [
     { "text": "Купить", "effects": [
        { "type": "take", "resource": "ImperialMark", "count": 200 },
        { "type": "animal", "species": "Верблюд", "count": 1 } ] },
     { "text": "Отказать", "effects": [
        { "type": "relation", "value": -3 } ] }
   ]

Последствия (effects)

typeЧто делаетПоля
giveКладёт ресурс у колонииresource, count
takeЗабирает ресурсresource, count
moodВоспоминание всем колонистамvalue, days, text
relationМеняет отношения с державойvalue, faction
raidПриближает налётhours
animalПриводит домашний скотspecies, count
letterЕщё одно письмоtitle, text
eventЗапускает другое событие по idtext = id
Две ловушки

Вариантов должно быть ровно два — событие с тремя игра пропустит и напишет ошибку. И цепочка event не должна замыкаться сама на себя: игра оборвёт её на пятом шаге.

24plants.json — дикие растения

Растение мода ведёт себя как ягодный куст: его помечают на сбор, колонист собирает урожай, растение отрастает. Отличается всё остальное — имя, картинка, что даёт, где растёт.

{
 "plants": [
  { "id": "saxaul", "name": "Саксаул",
    "desc": "Корявый пустынный куст: его ветки горят жарко и долго.",
    "texture": "plants/saxaul_full", "emptyTexture": "plants/saxaul_empty",
    "resource": "SaxaulWood", "yieldMin": 8, "yieldMax": 14,
    "work": 2.0, "regrowDays": 4, "scale": 1.2, "blocks": false,
    "biomes": [ "steppe", "desert" ],
    "terrain": [ "sand", "dirt" ], "chance": 0.004 }
 ]
}
ПолеЧто задаёт
name / descИмя на карте и описание в карточке «?».
texture / emptyTextureКартинка со спелыми плодами и после сбора. Без второй растение просто темнеет.
resource · yieldMin · yieldMaxЧто даёт и сколько за сбор.
workСекунд работы при навыке 0.
regrowDaysЗа сколько дней урожай отрастает снова.
scale · blocksРазмер и проходимость.
biomesforest · temperate · steppe · desert · tundra. Пусто — везде, кроме ледника.
terraingrass · dirt · sand · mud. Пусто — трава и земля.
chanceДоля подходящих клеток: 0.004 — примерно одна из двухсот пятидесяти.
Что важно помнить

Растения расставляются при создании карты: в уже начатой партии они не появятся. Сами по себе они не разрастаются, а мороз останавливает отрастание. Если мод выключить, растения станут малиновыми кустами — еда не пропадёт.

25animals.json и livestock.json — звери и скот

Дикие звери

Своё поведение зверю задать нельзя — он берёт его у одного из наших видов (behavior): deer ходит стадом и отбивается, если ранить; rabbit мелкий и пугливый, попадается в силки; wolf — хищник, охотится на дичь и скот.

{
 "animals": [
  { "id": "wild_camel", "name": "Дикий верблюд", "nameFemale": "Дикая верблюдица",
    "desc": "Двугорбый зверь степей. Лягается, если его ранить.",
    "behavior": "deer", "texture": "animals/camel_side",
    "hp": 160, "bodySize": 1.6, "hpMul": 2.4, "speed": 2.6, "scale": 1.25,
    "damageMin": 6, "damageMax": 12,
    "meat": "Beef", "meatMin": 70, "meatMax": 100, "leather": 35,
    "biomes": [ "steppe", "desert" ],
    "groups": 1, "groupMin": 2, "groupMax": 4, "restock": 0.12,
    "tameTo": "Верблюд" }
 ]
}

groups — сколько групп поставить при создании карты, restock — шанс, что зверей этого вида подселят по ходу игры (так они появляются и в начатой партии). tameTo — вид скота, которым зверь станет после приручения; без него приручить нельзя.

Картинка рисуется сбоку, мордой вправо — при ходьбе влево игра отразит её сама. Отдельных кадров для движения вверх и вниз у зверей мода нет.

Домашний скот

Запись в livestock.json добавляет новый вид скота или правит наш. Пустое поле означает «оставить как есть», поэтому для правки хватит одной строки.

{
 "livestock": [
  { "species": "Верблюд", "ruName": "верблюд",
    "femaleName": "Верблюдица", "maleName": "Верблюд", "babyName": "Верблюжонок",
    "spriteKey": "camel", "scale": 1.25, "bodySize": 1.6, "hpMul": 2.5,
    "adultDays": 200, "appetite": 0.8, "pregnancyHours": 960,
    "product": "milk", "productHours": 14, "productAmount": 6,
    "meatType": "Beef", "meat": 120, "leather": 50,
    "price": 420, "maxHerd": 8, "minTempC": -6, "mount": true },

  { "species": "Курица", "price": 75 }
 ]
}

productmilk, egg, wool или none. mount: true делает вид верховым: на нём ездят и он ускоряет караван, как лошадь. Картинки скота кладутся в textures/Animals/<spriteKey>_down.png и остальные стороны.

Имя вида менять нельзя

species лежит в сохранении строкой. Переименуете — в старых партиях скот исчезнет. Для показа есть отдельные поля ruName, maleName и прочие, их правьте свободно.

26furniture.json — своя мебель и станки

Эта таблица добавляет постройки, в отличие от buildings.json, который только меняет числа наших.

Секрет в поле base: предмет мода берёт поведение нашей постройки, а имя, картинку, цену, прочность и свет задаёт мод. Так получаются свои склады, кровати, станки, турели и очаги — без единой строчки кода.

{
 "furniture": [
  { "id": "steppe_chest", "name": "Сундук кочевника",
    "desc": "Расписной сундук для утвари. Работает как полка-склад.",
    "base": "Shelf", "texture": "furniture/chest",
    "cost": [ { "resource": "Wood", "count": 15 },
              { "resource": "Leather", "count": 5 } ],
    "hp": 130, "research": "building:steppe_chest" },

  { "id": "steppe_hearth", "name": "Очаг юрты", "base": "Brazier",
    "light": 7, "lightColor": "#FF9A50" },

  { "id": "felt_bed", "name": "Войлочная постель",
    "base": "Bed", "variant": "Single" }
 ]
}
ПолеЧто задаёт
baseИмя нашей постройки, чьё поведение берётся. Пусто — простой предмет 1×1 без пользы (декор, светильник, преграда).
variantВариант основы: у кровати Single, Double, Hospital, Cradle…; у машины энергосети Lamp, Heater, Cooler…; у осадного орудия Ballista, Mortar
name · desc · textureИмя, подсказка в меню и картинка.
costСписок материалов. Пусто — как у основы.
work · hpВремя стройки и прочность. 0 — как у основы.
light · lightColorРадиус и цвет света.
blocks · scaleНепроходимость и размер картинки (у предмета без основы).
researchКлюч технологии: пока не изучена, плитка в меню заперта. Пусто — доступна сразу.

Какие основы бывают

ShelfMedicineCabinetTrough BedNightstandTable ChairCampfireBrazier TorchColdBoxChessTable DartsHorseshoesGym ToysSchoolDeskLibrary MedicineTableTailorBenchChemLab CookStoveFurnaceSteelFurnace ButcherTableStoneTableResearchBench AdvancedBenchCraftSpotFermentBarrel TurretBarricadeSpikeTrap WoodTrapSnareSiegeWeapon PowerBuildingGraveMemorial Crematorium

Нельзя брать за основу стены, пол, двери, мост, заборы, провода, телеги и шахтный ствол — они ставятся не как мебель.

Свои рецепты у своего станка

Сделали станок на основе, например, TailorBench — рецепты для него пишутся в recipes.json с обычной станцией tailor. Плитки мебели модов стоят в конце вкладки «Мебель».

27factions.json и biomes.json — державы и биомы

Державы

Запись либо правит нашу державу (если name совпал), либо добавляет новую на глобус — со столицей, городами и территорией.

{
 "factions": [
  { "name": "Орда Каракум", "archetype": "raiders", "color": "#C07A38",
    "alwaysHostile": true, "tech": 1.8, "citiesMin": 2, "citiesMax": 4 },
  { "name": "Таавун", "archetype": "traders", "relationMin": 5, "relationMax": 25 }
 ]
}

archetyperaiders, tribal, traders, smiths, healers, syndicate: от него зависят караваны, товары и скорость развития. outlaw — «вне закона»: мало поселений и техника с самого низа. tech — стартовый уровень 0…5.

Почему это безопасно

Глобус строится заново по зерну планеты при каждой загрузке. Наши державы расставляются как раньше, а державы модов дописываются в конец своим генератором — включение мода не сдвинет столицы в уже идущей партии. Правки наших держав (отношения, архетип, техуровень) применяются только в новой игре: в старой всё это хранит сейв.

Биомы

Новый биом добавить нельзя — это генерация планеты. Зато наши пять настраиваются:

{
 "biomes": [
  { "id": "steppe", "trees": 0.7, "bushes": 1.5, "richSoil": 0.03,
    "wildlife": 1.4, "temp": 2 }
 ]
}
ПолеЧто меняетКогда действует
trees · rocks · bushesгустота деревьев, валунов, ягодных кустовновая карта
richSoilсколько богатой почвы (0.05 — заметно)новая карта
tempприбавка к температуре места высадки, °Cсразу
wildlifeсколько дичи и какой её потолоккарта и сразу

Имена биомов: forest, temperate, steppe, desert, tundra.

28quests.json — свои заказы держав

Шаблон заказа на поставку: кто просит, что, сколько, за какой срок и чем платит. Письмо, окно «Задания», сдача груза гонцу и штраф за просрочку — всё это делает игра.

{
 "quests": [
  { "id": "steppe_felt_order",
    "archetypes": [ "tribal", "traders" ], "minRelation": 5, "minDay": 6, "weight": 1.2,
    "items": [ "FeltCloth", "DriedMeat" ], "amountMin": 15, "amountMax": 35,
    "daysMin": 6, "daysMax": 10, "moneyMin": 300, "moneyMax": 520,
    "rewards": [ { "resource": "Iron", "min": 15, "max": 30 } ],
    "relation": 12, "research": 120,
    "title": "Кочевникам: {0} × {1}",
    "text": "«{giver}» просят {amount} × {item}. Срок — {days} дн." }
 ]
}

В заголовке {0} — количество, {1} — ресурс. В тексте доступны {giver}, {amount}, {item} и {days}. Ниже игра сама допишет срок, награду и предупреждение о провале.

weight работает как у событий: обычный заказ игры весит 3. factions позволяет ограничить заказ конкретными державами по имени.

Только поставка

Заказ «убить зверя» или «привести беженца» пока не сделать. И заказы дают только дружественные державы — не больше двух активных сразу.

29needs.json — свои потребности

Новая шкала у каждого колониста: сама убывает, видна в окне «Нужды» и портит настроение, когда падает. Чем её наполнять — решает мод.

{
 "needs": [
  { "id": "thirst", "name": "Жажда", "color": "#4FA8E8",
    "start": 85, "drainPerHour": 3.2, "lowAt": 25,
    "moods": [
      { "below": 45, "mood": -3, "text": "Хочется пить" },
      { "below": 20, "mood": -9, "text": "Мучает жажда" }
    ] }
 ]
}

Когда шкала падает ниже lowAt, скриптам приходит on_need_low(имя, id) — там и решается, что делать: отправить к воде, дать выпить, прислать письмо. Значения хранятся в сохранении отдельно по каждому колонисту.

30Рассказчики и испытания

storytellers.json

Рассказчик задаёт ритм всей партии: как быстро копится напряжение, какая доля налётов, какие налёты чаще, какая передышка в начале, насколько крупные отряды, с какого дня конница и осады.

{
 "storytellers": [
  { "name": "Буран", "character": "Беспощадный",
    "styleLine": "бьёт волнами и почти не даёт продохнуть",
    "style": "waves", "tensionPerDay": 15, "raidShare": 0.65,
    "sizeMin": 1.2, "sizeMax": 1.7, "graceMin": 4, "cavalryDay": 18 }
 ]
}

stylecalm (по напряжению), waves (кризис, затем передышка), chaos (ровный случайный шанс), relentless. Ноль или пустое поле значит «как у игры», поэтому нашего рассказчика можно поправить одной строкой.

Порядок важен

Наши рассказчики всегда идут первыми, модовые дописываются в конец: номер рассказчика лежит в сейве. Выключили мод — партия возьмёт ближайшего нашего, а не чужого.

challenges.json

Своё испытание: правила старта и цель. Цель бывает четырёх видов — raids (отбить столько налётов), days (прожить столько дней), wealth (накопить богатство) и script — когда счёт ведёт сам мод.

{
 "challenges": [
  { "id": "steppe_herd", "name": "Хозяин табуна",
    "desc": "Соберите табун из восьми верблюдов и удержите его.",
    "rules": [ "Цель считает мод", "Рассказчик «Буран»" ],
    "goal": "script", "goalValue": 8, "goalText": "Собрать табун из 8 верблюдов",
    "colonists": 3, "difficulty": 2, "graceDays": 4, "starDay": 40,
    "planetSeed": 40404,
    "cargo": [ { "resource": "Food", "count": 120 } ],
    "weapons": [ "bow" ], "startTech": [ "steppe_craft" ] }
 ]
}

Для цели script скрипт сам считает прогресс и объявляет исход — см. alem.challenge_progress, alem.win и alem.lose в разделе 34.

31info.json — описания для карточки «?»

У каждой вещи, постройки, растения и зверя в игре есть кнопка «?» — окно с описанием сверху и характеристиками снизу. Характеристики игра считает сама, а описание берёт отсюда.

{
 "entries": {
  "res:Kumys":    { "ru": "Кислый напиток из кобыльего молока.",
                    "en": "A sour drink of mare's milk." },
  "weapon:saber": { "ru": "Изогнутый клинок для конного боя.",
                    "en": "A curved blade for fighting from the saddle." }
 }
}

Ключи: res:<ресурс>, weapon:<id>, apparel:<id>, build:<вид>, plant:<id>, animal:wild:<id>. У растений, зверей и мебели есть путь короче — поле desc прямо в их таблице.

32Скрипты на Lua: первый скрипт

Таблицы описывают вещи. Скрипт описывает правила: «если случилось это — сделай то». Файлы .lua кладутся в папку scripts/ внутри мода и грузятся по алфавиту.

scripts/omens.lua
-- Голодная зима: если зимой мало еды, приходит письмо и портится настроение
function on_day(day)
  if alem.season() == "winter" and alem.resource("Food") < 50 then
    alem.letter("Голодная зима", "Старики говорят: духи уходят из холодных домов.")
    alem.mood_all(-4, 2, "Страх голода")
  end
end

Это весь скрипт. Lua — простой язык: переменные, if, циклы и функции. Всё, что нужно знать для модов, помещается в этот раздел и два следующих.

Что нужно помнить сразу

  • Каждый мод живёт отдельно — переменные одного мода не видны другому.
  • Ошибка не роняет игру. Она попадает в лог строкой [Lua] … с именем мода. После трёх ошибок подряд скрипты этого мода выключаются до перезапуска.
  • Бесконечный цикл не повесит игру — вызов прервётся по лимиту в 200 000 шагов, а в лог попадёт ошибка.
  • Песочница. Скрипту недоступны файлы, интернет и что-либо за пределами таблицы alem. Нет io, require, os.execute.
  • Изменения подхватываются при перезапуске игры или при смене набора модов.
Несколько файлов — один хук

Все скрипты мода живут в общей таблице функций. Если два файла объявят function on_day, второй затрёт первый. Когда файлов несколько, подписывайтесь так:

alem.on("on_day", function(day)
  -- этот обработчик не мешает соседнему файлу
end)

33Хуки: когда игра зовёт скрипт

ХукКогда вызывается
on_hour(hour)Каждый игровой час.
on_day(day)Начало нового дня.
on_event(id)Выпало событие — наше (Blizzard, Windfall…) или мода.
on_raid_start() / on_raid_end()Начало и конец налёта.
on_hit(кого, кто, урон, тип, часть, это_колонист)Каждое попадание, когда записывается рана. Здесь делаются эффекты оружия: яд, поджог, оглушение.
on_kill(имя, вид, причина)Смерть человека; вид — colonist или enemy.
on_colonist_died(имя)Умер колонист.
on_need_low(имя, id)Своя потребность упала ниже порога.
on_building_built(id, x, y)Достроена постройка; id — ваш предмет или имя нашего вида.
on_challenge_start(id)Началось испытание.
Про частые хуки

on_hit случается десятки раз в секунду в бою. Если модов со скриптами нет, он не стоит игре ничего, но внутри своего обработчика не делайте тяжёлой работы: длинные циклы там ни к чему.

34Команды alem.*

Узнать

КомандаЧто возвращает
alem.day() · alem.hour()День и час.
alem.season() · alem.temp()Сезон (spring…) и температуру.
alem.wealth()Богатство колонии.
alem.colonists()Список имён живых колонистов.
alem.colonist_count()Сколько их.
alem.health(имя) · alem.mood(имя)Здоровье и настроение.
alem.has_trait(имя, черта)Есть ли черта.
alem.need(имя, "hunger"|"rest"|"recreation"|своя)Значение шкалы; третьим аргументом — задать.
alem.resource(ресурс)Сколько лежит в колонии.
alem.relation(держава)Отношения, −100…100.
alem.enemies()Список врагов: имя, x, y, здоровье.
alem.count_animals(вид) · alem.count_wild(вид)Сколько скота и дичи.
alem.count_buildings(id)Сколько таких построек.
alem.colony_center() · alem.pos(имя) · alem.map_size()Координаты.
alem.challenge()Что за испытание идёт: id, цель, прогресс.

Сделать

КомандаЧто делает
alem.give(ресурс, n) · alem.take(ресурс, n)Выдать или забрать.
alem.memory(имя, значение, дни, текст)Воспоминание одному колонисту.
alem.mood_all(значение, дни, текст)Всем сразу.
alem.letter(заголовок, текст)Письмо игроку.
alem.dialog(заголовок, текст, {кнопки}, функция)Окно с двумя–пятью кнопками; функция получает номер нажатой.
alem.log(текст)Строка в журнал колонии.
alem.go(имя, x, y) · alem.draft(имя, вкл)Приказ идти; мобилизовать.
alem.task(имя, x, y, сек, функция)Колонист идёт и работает там указанное время, потом вызывается ваша функция. Так делаются свои занятия.
alem.attack_nearest(имя) · alem.hunt_nearest(имя)Атаковать ближайшего врага; охотиться на ближайшего зверя.
alem.hurt(имя, урон, тип, подпись)Ранить.
alem.heal(имя) · alem.tend(имя)Вылечить; перевязать.
alem.disease(имя, название, скорость, иммунитет, смертельна, боль) Своя болезнь.
alem.damage_at(x, y, радиус, урон, тип, подпись)Урон по всем в круге — взрыв, молния, облако яда.
alem.ignite(x, y) · alem.extinguish(x, y)Поджечь и потушить.
alem.spawn_item · alem.spawn_weaponПоложить предмет или оружие.
alem.spawn_wild(вид, x, y) · alem.spawn_livestock(вид, x, y) Дикий зверь; скот.
alem.spawn_enemy(x, y, оружие, навык) · alem.raid(очки)Одиночный враг; настоящий налёт через рассказчика.
alem.spawn_wanderer(x, y)Путник у ворот.
alem.build(вид, x, y, материал, сразу)Постройка: наша или мебель мода; чертежом или готовой.
alem.spawn_plant(что, x, y)tree, bush, rock или id растения мода.
alem.relation_add(держава, n) · alem.raid_in(часы)Отношения; приблизить налёт.
alem.event(id)Запустить событие из events.json.
alem.challenge_progress(n) · alem.win() · alem.lose()Своя цель испытания.

Память мода

Скрипту нужно помнить своё: сколько раз что случилось, на каком шаге сюжет. Для этого есть две команды, и всё, что в них положено, лежит в сохранении.

alem.set("deaths", alem.get("deaths", 0) + 1)
if alem.get("deaths", 0) == 3 then alem.event("cursed_land") end

Хранить можно строки, числа и true/false. Ключи у каждого мода свои — чужие не перепутаются.

35Правила и границы скриптов

Пример: жажда и водопой

Связка «таблица + скрипт» выглядит так. В needs.json заведена шкала «Жажда», а скрипт решает, как её утолять:

function on_need_low(name, need)
  if need ~= "thirst" then return end
  local cx, cy = alem.colony_center()
  alem.task(name, cx, cy, 6, function(who)
    if alem.resource("Kumys") > 0 then
      alem.take("Kumys", 1)
      alem.need(who, "thirst", 100)
      alem.memory(who, 3, 1, "Пил кумыс")
    else
      alem.need(who, "thirst", 65)   -- вода утоляет хуже
    end
  end)
end

Чего скрипт не может

  • Отменить или изменить урон. on_hit приходит уже после расчёта: можно добить, поджечь, отравить или вылечить — но не «не дать попасть».
  • Завести новый вид работ. Колониста на своё занятие отправляет alem.task, а не список приоритетов.
  • Сделать своё окно интерфейса. Есть письма и alem.dialog — до пяти кнопок.
  • Достучаться до внутренностей игры. Наружу выведены только команды из раздела 34; всё остальное закрыто песочницей.
Как отлаживать

print("что-то") из скрипта попадает в лог строкой [Lua:<id мода>], а alem.log() — в журнал колонии, который видно прямо в игре. Этого хватает, чтобы понять, где скрипт остановился.