The Herbalist

The Herbalist

51 ratings
Неполное руководство по моддингу Травницы.
By lolbot_iichan
   
Award
Favorite
Favorited
Unfavorite
I. Введение.
Данная заметка довольно сумбурно рассматривает некоторые сценарии расширения и модификации функциональности игры пользовательскими модификациями. Постепенно сюда будут добавляться уточнения, а также всё новые и новые сценарии. Для полного понимания возможностей игры рекомендуется ознакомиться с её полным исходным кодом =)

Предполагается, что у вас есть минимальный опыт программирования или хотя бы смутное представление о том, что это такое. В качестве текстового редактора рекомендую AkelPad или Notepad++. Травница написана на движке RenPy, так что беглое знакомство с этим движком и/или языком программирования Python будет полезным.

Идеи того, что можно модифицировать в игре описаны от простого к сложному, так что если вы в какой-то точке гайда потерялись и перестали понимать, о чём вообще речь, то можете попробовать пропустить оставшиеся разделы и сразу перейти к последнему содержательному пункту "XX. Проверка мода и его публикация.".
II. Подготовка рабочей директории мода.
1. Создайте в директории "C:\Program Files (x86)\Steam\steamapps\common\Herbalist\mods" новый каталог с названием "123456789".
2. Запустите игру, зайдите в меню "Модификации" и убедитесь, что среди списка модов появился пункт 123456789, пока что красный.


3. Название, описание и некоторые детали мода хранятся в текстовом файле с именем "descript.ion". Создайте текстовый файл с таким именем внутри вашего каталога "123456789". Требуемая кодировка файла - UTF-8 без BOM.
4. Вашей модификации нужно название-идентификатор, обеспечивающее уникальность. Рекомендованный формат идентификатора: "some_latin_words_mod", то есть английские слова, написанные маленькими буквами, соединённые подчёркиваниями, последнее слово - "mod". Первой строкой в файле "descript.ion" напишите "id=<идентификатор-вашего-мода>".
5. Второй строкой укажите версию модификации в формате "version=<версия-вашего-мода>". Формат версии свободный, можете написать там "1.2.0.1224", "2016-11-15", "0.5-alpha" или просто "1".
Пример:
id=pumpkin_mod version=0.2
6. Далее идут локализованные строки для каждого поддерживаемого вами языка:
6.1. Секция начинается с id языка в квадратных скобках. Например, [ru] и [en] для русского и английского. Всегда заполняйте русскую и английскую секцию, даже если не собираетесь поддерживать эти языки.
6.2. title - название мода на данном языке
6.3. (опционально) description - описание мода на данном языке
6.4. (опционально) credits - список авторов мода на данном языке
6.5. (опционально) effects - список областей игры, затронутых модом. В будущем это поле, скорее всего, будет заменено списком тэгов.
Пример:
[en] title=Halloween description=Official Halloween special. credits=Idea, coding, sound and graphics: Moonworks effects=+ New items\n+ New recipies\n+ New sprites
7. Иконка мода
7а. В качестве иконки для отображения внутри игры рекомендованы PNG-изображения размером 620х170. Назовите его "<идентификатор-вашего-мода>.png" и поместите внутрь каталога "123456789".
7б. В качестве иконки для Steam подойдёт любое квадратное PNG-изображение весом менее 1Мб. Назовите его "steam_logo.png" и поместите внутрь каталога "123456789".
8. Запустите игру, зайдите в меню "Модификации" и для каждого поддерживаемого языка убедитесь, что ваш мод больше не красный, а по mouseover'у показывается его описание.


9. Создайте внутри каталога "123456789" подкаталог "<идентификатор-вашего-мода>", это будет основной рабочий каталог вашего мода. За его пределами не должно быть ничего кроме упомянутых выше descript.ion и двух картинок.
10. В рабочем каталоге мода создайте текстовый файл "<идентификатор-вашего-мода>.rpy" в кодировке UTF-8 без BOM. Это и будет основной файл мода, в который вы будете писать код вашего мода.
III. Простейший мод: яблоко в амбаре.
Файл мода может содержать произвольное число секций с кодом на языке Python и описанием евентов на языке RenPy.
Секция, которая вам понадобится в любом случае - это импорт необходимых функций в ваш namespace из глобального namespace.
Это выглядит примерно так:
init -1024 python in <идентификатор-вашего-мода>: from renpy.store import <название-одной-необходимой-функции> from renpy.store import <название-другой-необходимой-функции>
Далее обычно следуют другие init-python-секции с кодом на языке python и label-секции с описанием евентов на языке RenPy.

Каждую ночь в Амбаре выполняется один и тот же код, прячущий различные предметы в открываемых зонах. Предметы берутся из некоего списка, названного "ambar". Предметы в этом списке имеют веса, характеризующие частоту нахождения данного предмета. Если задать очень большой вес, то предмет будет находиться намного чаще.

Следующий код добавляет предмет ITEM("APPLE") в список "ambar" с весом 2.
init -1024 python in <идентификатор-вашего-мода>: from renpy.store import ITEM from renpy.store import add_to_weighted_items init python in <идентификатор-вашего-мода>: add_to_weighted_items(ITEM("APPLE"), 2, "ambar")
В результате среди вещей в амбаре, помимо муки и прочих продуктов, будут периодически находиться яблоки. Несмотря на то, что тут всего 5 строчек, если эти 5 строчек записать в rpy-файлом и снабдить описанием, то это может быть уже полностью самодостаточным модом, хоть и очень простым.



Сейчас в игре есть такие взвешенные списки, в которые можно добавлять предметы:
  • редкие предметы в Лесу - "forest extras" (там по умолчанию только Грибы)
  • редкие предметы в Лавке- "shop other" (там по умолчанию только Соль и Губка)
  • предметы в Амбаре - "ambar" (там по умолчанию только Молоко, Яица, Мука, Сахар и Свечка)
  • предметы, которые можно !копать - "ambar shovel" (там по умолчанию только Янтарь, Глина и Сковородка)

Фрагменты кода из реальных модов, описывающие добавление предметов во взвешенные списки:
init python in pumpkin_mod: add_to_weighted_items(ITEM("PUMPKIN_MOD_PUMPKIN"), 1, "forest extras") add_to_weighted_items(ITEM("PUMPKIN_MOD_KNIFE"), 2, "ambar shovel")
init python in winter_mod: add_to_weighted_items(ITEM("WINTER_MOD_LOG"), 1, "forest extras") add_to_weighted_items(ITEM("WINTER_MOD_ICEBERRY"), 1, "forest extras")
IIII. Объявляем собственные предметы.
Ещё одна полезная функция - функция добавления предмета в игру, add_to_items().
Для объявления нового предмета понадобится написать такой код:
init -1024 python in <идентификатор-вашего-мода>: from renpy.store import add_to_items init python in <идентификатор-вашего-мода>: add_to_items(<идентификатор-предмета>, <категория-предмета>, <прочие параметры>)

Основные параметры - id, группа и картинка

1. Рекомендованный формат идентификатора предмета - "YOUR_MODID_ITEM_NAME", то есть заглавные английские буквы с подчёркиваниями, причём первые слова - такие же как в названии мода.
2. Категория предмета - произвольная текстовая строка, рекомендованный формат - 1-2 слова в lowercase.
Предметы из следующих категорий автоматически отображаются в списке предметов в соответствующих разделах Книги:
  • "cooking"
  • "food fried"
  • "food with_apple"
  • "food with_mushroom"
  • "food with_sguha"
  • "food <любая-другая-подгруппа>"
  • "forest herb"
  • "forest mushroom"
  • "forest other"
  • "forest <любая-другая-подгруппа>"
  • "household container"
  • "household edible"
  • "household instrument"
  • "household other"
  • "household <любая-другая-подгруппа>"
  • "quest doll"
  • "quest grandma"
  • "quest helloween"
  • "quest mushrooms"
  • "quest pots"
  • "quest scroll"
  • "quest <любая-другая-подгруппа>"
.3. Иконка предмета задаётся с помощью параметра "image", значением должен быть СПИСОК png-слоёв. Например,
add_to_items("BAKA_MOD_POT", "other baka", image=["items/p_back.png", "baka_mod/baka.png", "items/p_lid.png", "items/p_top.png"])

Озвучка предметов

4. Чтобы озвучить взятие и перекладывание предмета в инвентаре, можно указать параметры "take" и "put". В качестве значения используется путь до ogg-файла. Например,
add_to_items("PUMPKIN_MOD_PUMPKIN_LIGHT", "quest helloween", take="items/take_object.ogg", put="items/put_apple.ogg")

5. Чтобы озвучить изготовление предмета при смешивании, можно указать СПИСОК звуков "oncraft", из них будет играться случайный. Например,
add_to_items("PUMPKIN_MOD_PUMPKIN_CUT", "quest helloween", oncraft=["pumpkin_mod/halloween_pumpkin.ogg"])

Рецепт крафта

6. Чтобы задать рецепт получения предмета из нескольких других предметов, нужно указать СПИСОК id этих предметов в параметре "recipe". Например,
add_to_items("PUMPKIN_MOD_PUMPKIN_CUT", "quest helloween", recipe=["PUMPKIN_MOD_PUMPKIN", "PUMPKIN_MOD_KNIFE"])

Можно указать больше 2 ингридиентов, например, тесто состоит из муки, молока и яиц. Главное, чтобы существовали промежуточные предметы, сумма ингридиентов которых равна ингридиентам данного предмета. Например, recipe=["PUMPKIN_MOD_PUMPKIN", "PUMPKIN_MOD_KNIFE", "CANDLE"] может быть собран из упомянутого выше "PUMPKIN_MOD_PUMPKIN_CUT" и "CANDLE".

Если у предмета нет частей, то нужно задать список из одного предмета, самого себя. Например,
add_to_items("PUMPKIN_MOD_PUMPKIN", "forest other", recipe=["PUMPKIN_MOD_PUMPKIN"])

Купля-продажа:

7. Чтобы назначить цену покупки или продажи в монетах, можно указать параметры "buy_price" и "sell_price". Например,
add_to_items("PUMPKIN_MOD_PUMPKIN_CUT", "quest helloween", sell_price=8)

Если параметр sell_price не указан, то предмет можно продать за 0 монет.
Чтобы предмет было нельзя продать, нужно указать sell_price=None.

Дополнительные моменты:



8. Чтобы указать событие, случающееся при получении предмета, можно задать параметр "pick" label-именем нужного события. Иначе предмет возьмётся без события. Подробности см. в пункте VI. Получением предмета может быть его крафт или, например, нахождение в амбаре. Если не указать этот параметр, то предмет будет получаться без дополнительного евента.



9. Чтобы указать другое изображение, соответствующее предмету в амбаре или в лесу, можно задать параметр "image_pick". Иначе он будет выглядеть как обычно.



10. Чтобы указать специальную рамочку, мигающую вокруг контура предмета в лесу, можно задать параметр "image_edge". Иначе рамочки не будет.

Итого:

В итоге, получаем что-то такое:
init: image item_edge cpump = "pumpkin_mod/pumpc.png" init python in pumpkin_mod: add_to_items("PUMPKIN_MOD_PUMPKIN", "forest other", recipe=["PUMPKIN_MOD_PUMPKIN"], take="items/take_object.ogg", put="items/put_apple.ogg", image=["pumpkin_mod/pump1.png"], image_pick="pumpkin_mod/pump0.png", image_edge="cpump", sell_price=2)
init python in winter_mod: add_to_items("WINTER_MOD_BLIN_SGUHA", "food with_sguha", recipe=["BLIN","WINTER_MOD_SGUHA"], take="items/take_plate_ceramic.ogg", put="items/put_plate_ceramic.ogg", oncraft=["winter_mod/pour_condensed.ogg"], image=["winter_mod/blin_sguha.png"], sell_price=5)
V. Локализация.
Мы до сих пор не озаботились никаким человекочитаемым текстом, поэтому при получении свежесозданного предмета вы увидите не очень красивую табличку.


Игра ищет строки вида "TEXT_<что-нибудь>" в файлах локализации. Для русского и английского языков это russian.tsv и english.tsv.
Это tab-separated таблица вида "<ключ><TAB><значение>".

Для предмета "<YOUR_MODID_ITEM_NAME>" будут искаться строки "TEXT_ITEM_NAME_<YOUR_MODID_ITEM_NAME>" и "TEXT_ITEM_DESCR_<YOUR_MODID_ITEM_NAME>". Например, в файле "pumpkin_mod/english.tsv" есть такие строки для предмета "PUMPKIN_MOD_KNIFE":
TEXT_ITEM_NAME_PUMPKIN_MOD_KNIFE A knife TEXT_ITEM_DESCR_PUMPKIN_MOD_KNIFE An old knife. The blade is covered with rust, but the cutting edge looks sharp.

В идеале, в ваших rpy-файлах вообще не должно быть локализованных строк, только TEXT_ID, который уже ищется в tsv-файлах.



Надписи на ui-виджетах, screen-объектах и реплики переводятся автоматически. Для перевода строки глубоко внутри python-кода можно использовать функцию translate().

Файлы tsv с локализацией нужно добавлять в основной рабочий каталог вашего мода, например "C:\Program Files (x86)\Steam\steamapps\common\Herbalist\mods\1234567890\<идентификатор-вашего-мода>\english.tsv"
VI. События.
Если вы знакомы с RenPy, то вы знаете, что основное предназначение этого движка - работа со сценариями визуальных новел. Обычный сценарий события выглядит примерно так:
label <метка>[(<параметры>)]: show <эмоция Слави> sl <реплика Слави> show <эмоция Слави> sl <реплика Слави> return

Персонажи

В скриптах Травницы рекомендовано хранить все реплики в tsv-словарях, всюду используя TEXT_ID вместо настоящего текста, поэтому реплики Слави обычно выглядят примерно так:
sl "TEXT_SL_YOUR_MOD_YOUR_TEXT_ID"

Остальные объявленные в игре персонажи, от имени которых можно выводить реплики:
  • narr "TEXT_NARRATOR_YOUR_MOD_YOUR_TEXT_ID" - рассказчик, голос за кадром
  • ba "TEXT_BA_YOUR_MOD_YOUR_TEXT_ID" - Бабушка
  • bn "TEXT_BN_YOUR_MOD_YOUR_TEXT_ID" - Банник
  • neko "TEXT_NEKO_YOUR_MOD_YOUR_TEXT_ID" - Кисонька (Мяу? Мяу!)
  • sj "TEXT_SJ_YOUR_MOD_YOUR_TEXT_ID" - Женя Манж Па Сис Жур, монстр из печки
  • tm "TEXT_TM_YOUR_MOD_YOUR_TEXT_ID" - Тётя Маша, хозяйка лавки

Отображение/смена эмоции персонажа производится такой командой:
show expression <персонаж>_show(<эмоция>) as <персонаж> at <место на экране> with diss

Для Слави, <персонаж>="sl", <место на экране> как правило равно "cleft", а список эмоций Слави: normal, smile, smile2, confused, study, lovely, unhappy, cry, winking, serious, surprised, tired, laughs, dontlike, smile3, smile4, saa, confused2, wot, good, scary, study2, yan.

Для Бабушки (<персонаж>="ba"), Кисоньки (<персонаж>="neko"), Тёти Маши (<персонаж>="tm") типичное положение на экране наоборот, справа, "cright", а список эмоций совсем небольшой: normal, smile, confused.

window

Нам не нравится мигание рамочки во время смены эмоций, поэтому мы используем команды "window show/hide" вокруг КАЖДОЙ смены эмоции. В результате получается, например, такой код:
window show show expression sl_show("confused") as sl at cleft with diss window hide sl "TEXT_SL_EVENT_TUTORIAL_PART4_12" window show show expression tm_show("normal") as tm at cright with diss window hide tm "TEXT_TM_EVENT_TUTORIAL_PART4_13" tm "TEXT_TM_EVENT_TUTORIAL_PART4_14" window show show expression tm_show("confused") as tm at cright with diss window hide tm "TEXT_TM_EVENT_TUTORIAL_PART4_15"



Типовые события

Игра ожидает, что для каждого предмета существует событие "event_on_<lower_case_item_id>". При правом клике на предмет будет вызвано такое событие, и в параметрах (i,j) будут переданы координаты предмета в инвентаре. Это, например, позволит его съесть при использовании.

Для предмета ITEM("CANDLE") всё это вместе выглядит вот как-то так:
label event_on_candle(i,j): window show show expression sl_show("smile") as sl at cleft with diss window hide sl "TEXT_SL_USE_ITEM_CANDLE_0" return
События, связанные с получением предметов (см. пункт IIII.9), параметров i, j не имеют. Например,
label event_pick_appleeye: window show show expression sl_show("scary") as sl at cleft with diss window hide sl "TEXT_SL_PICK_ITEM_APPLEEYE_0" return

Смена фона и отображение "CG"

Если в событии хочется поменять фон, то делается это так:
<команда смены фона> with dissolve
В качестве команды может выступать:
  • $ scene_home_bg() - задник избушки с предметами
  • $ scene_ambar_bg() - задник амбара с предметами
  • scene craft_bg - задник зоны крафта
  • scene shop_bg - задник магазина
  • scene black - чёрный фон



Если поверх фона не выводится спрайтов, то это уже как бы и не фон, а картинка-событие, как их иногда называют некоторые личности - CG. Вот как это сделано в евенте с щупальцами, например:
image tentacles_cg1 = "events/cg07-1-1.png" label event_tentacles: <...> scene tentacles_cg1 with dissolve sl "TEXT_SL_EVENT_TENTACLES_1" sl "TEXT_SL_EVENT_TENTACLES_2" <...>

Событие посложнее

Пример более сложного события, где происходит что-то кроме реплик и спрайтов:
$ remove_item(i,j,pot="NONE") # Славя съедает ягодку. $ random_sound_play(slavya_chew_sounds) show expression sl_show("normal") as sl at cleft with diss pause paus window show show expression sl_show("dontlike") as sl at cleft with diss window hide sl "TEXT_SL_USE_ITEM_CBLUE_0" # *Славя булькает* play sound "events/slavya_stomach_1.ogg" show expression sl_show("confused") as sl at cleft with diss pause paus window show show expression sl_show("tired") as sl at cleft with diss window hide sl "TEXT_SL_USE_ITEM_CBLUE_1" #Славя весь оставшийся день проводит дома. $ hp_decrease(ouch_cblue_hp_price,"ouch_for_"+ITEM("CBLUE"))

В этом примере кода встречается ещё ряд полезных строчек-команд:
  • #<произвольный текст> - комментарии, человекочитаемый текст, который разработчики пишут для себя, чтобы при будущем рефакторинге и отладке понимать, что тут вообще происходит
  • pause paus - пауза на полсекунды, нужна для последовательной смены нескольких эмоций, в данном случае normal переходит в dontlike, а confused в tired. Вместо paus=0.5 можно указать свой временной интервал, например, одну секунду: pause 1.0
  • play sound "<имя файла>.ogg" - воспроизведение указанного звукового файла, в вашем случае относительный путь должен рассчитываться относительно рабочей директории мода, например, play sound play sound "pumpkin_mod/halloween_pumpkin.ogg"
Подробнее о командах внутри событий читайте в документации RenPy: https://www.renpy.org/doc/html/quickstart.html

Python-вставки

Ряд команд является вызовом функция языка Python и специфичен для данной игры. Такие строки всегда начинаются с символа доллара ("$").
Из представленных выше к ним относятся:
  • $ random_sound_play(<список>) - издать случайный звук из python-списка
  • $ hp_decrease(<число энергии>,<причина>) - игрок теряет указанное число энергии, об этом делается соответствующая пометка, благодаря чему потом можно посчитать статистику потраченной по этой причине энергии. Энергия не может опуститься ниже нуля.
  • $ remove_item(i,j,pot=<предмет или "NONE">) - (используется в событиях на использование предмета) меняет предмет в инвентаре на другой, соответствует поеданию, использованию, преобразованию, растрачиванию. Третий параметр - либо строка "NONE", либо ITEM ID предмета, которой должен оказаться на месте использованного. Обычно это "DIRTY_POT" (если применялось зелье) или "UNCLEAN_POT" (если применялась еда).


Ещё некоторые полезные функции:
  • $ hp_increase(<число энергии>,<причина>) - игрок получает указанное число энергии, об этом делается соответствующая пометка, благодаря чему потом можно посчитать статистику потраченной по этой причине энергии. Энергия не может подняться выше absolutely_max_hp=666.
  • Сочетанием $hp_increase и $remove_item является функция $ eat_item(i,j,<число энергии>,pot=<предмет или "NONE">) - игрок получает указанное число энергии, а предмет съедается.
  • $ set_flag(<идентификатор-флага>) / $ unset_flag(<идентификатор-флага>) - управление флагами, см. раздел VII.
  • $ unlock_book_memory(<идентификатор-воспоминания>) - разблокировать воспоминание в книге, см. раздел VIII.
  • $ recieve_item_in_event(ITEM(<идентификатор предмета>)) - получить предмет в инвентарь. Если в инвентаре нет места, то предмет заменит какой-то существующий![/list]
VII. Ветвления, условия, флаги, счётчики.
Иногда хочется добиться того, чтобы на разных запусках события отличались, например, чтобы повторное использование предмета было другим. RenPy позволяет использовать if-else блоки внутри label для создания подобной логики:
label <метка>: <действия в любом случае> if <условие>: <действия, если True> else: <действия, если False> <действия в любом случае>

Несколько полезных проверок:
  • if hp_is_enough(X): - проверка, что у Слави не менее Х энергии
  • if money_value_get() < X: - проверка, что у Слави менее X денег
  • if first_of_item(None): - проверка, что в инвентаре есть пустые ячейки
  • if not first_of_item(None): - проверка, что в инвентаре НЕТ пустых ячеек
  • if first_of_item(ITEM("APPLE")): - проверка, что в инвентаре есть предмет Яблоко
  • if not first_of_item(ITEM("APPLE")): - проверка, что в инвентаре НЕТ предмета Яблоко

Флаги

Вместо создания собственных логических переменных, предлагается воспользоваться системой флагов Травница:
  • set_flag(<идентификатор_флага>) - выставить флаг в True
  • unset_flag(<идентификатор_флага>) - опустить флаг в False
  • check_flag(<идентификатор_флага>) - вернуть True, если флаг был выставлен
  • check_and_set_flag(<идентификатор_флага>) - вернуть True, если флаг был выставлен, одновременно выставив его в True для СЛЕДУЮЩИХ проверок

ВНИМАНИЕ: если идентификатор флага заканчивается на "_today", то ночью он сбрасывается в False автоматически. Остальные флаги всю игру пребывают в том положении, в которое их выставили.

Последний вариант удобно применять в таком коде, когда что-то доступно раз в день или раз за всю игру:
label event_on_jam(i,j): if not check_and_set_flag("event_jam_today"): # Славя съедает $ random_sound_play(slavya_chew_sounds) $ eat_item(i,j,eat_jam_hp_price,pot="UNCLEAN_POT") ... else: # Славя не съедает window show show expression sl_show("smile2") as sl at cleft with diss window hide sl "TEXT_SL_USE_ITEM_JAM_2" return



Счётчики

Ещё один полезный функционал, помимо флагов, это счётчики. Почти все действия в игре автоматически увеличивают тот или иной счётчик. Например:
  • при покупке яблока счётчик "get_shop_APPLE" увеличивается на 1, а "spent_shop_for_APPLE" увеличивается на стоимость яблока
  • при срывании травки ITEM("HERB1") в лесу счётчик "get_forest_HERB1" увеличивается на 1, а "tired_forest_pick" увеличивается на энергозатраты
  • при крафте зелья из банки ITEM("POT") и травки ITEM("HERB1") счётчик "get_craft_HERB1&POT" увеличивается на 1, а "tired_craft_for_HERB1&POT" увеличивается на энергозатраты
И так далее.

Существует большой набор функция для выборки информации из счётчиков. Вот некоторые из них:
  • if get_counter("get_forest_") >= X: - проверка, что за все игровые дни все начинающиеся на "get_forest_" счётчики суммарно равны не менее Х (то есть за всё время в лесу найдено не менее X любых предметов)
  • if get_counter_today("get_forest_") >= X: - проверка, что за текущий игровой день все начинающиеся на "get_forest_" счётчики суммарно равны не менее Х (то есть за сегодня в лесу найдено не менее X любых предметов)
  • if get_exact_counter("get_forest_HERB1") >= X: - проверка, что за все игровые дни значения счётчика "get_forest_HERB1" суммарно равны не менее Х (то есть за всё время в лесу найдено не менее X предметов ITEM("HERB1"))
  • if get_exact_counter_today("get_forest_HERB1") >= X: - проверка, что за текущий игровой день значения счётчика "get_forest_HERB1" суммарно равны не менее Х (то есть за сегодня в лесу найдено не менее X предметов ITEM("HERB1"))
  • if all([get_exact_counter("useitem_"+i)>=1 for i in all_in_group("forest herb")]): - проверка, что каждый предмет из группы "forest herb" за игровые дни использовался хоть один раз

Примеры

На счётчиках и флагах построена проверка замысловатых условий в ачивках и некоторых сценках. Например, вот так организован евент с верёвками в амбаре:
def allambar_complete_hook(**kwargs): all_ambar_items = get_all_weighted_items("ambar") + get_all_weighted_items("ambar shovel") if all([get_counter("get_ambar_"+w) for w in all_ambar_items]): set_flag("ambar_unlocked") def event_shibari_hook(**kwargs): if not check_and_set_flag("ambar_today") and check_flag("ambar_unlocked") and not check_flag("ambar_seen"): call_without_ui("event_shibari") set_flag("ambar_seen") # do not pick anything after this return False add_to_hooks("allambar_complete", "after_ambar_pick", allambar_complete_hook) add_to_hooks("event_shibari", "before_ambar_reveal", event_shibari_hook)

Про функцию add_to_hooks() можно прочитать в разделе [X].
VIII. Новые персонажи.
Зимний мод добавляет в игру нового говорящего и новый спрайт.
Рассмотрим эти модификации по отдельности.

Добавление говорящих персонажей

Нам понадобится только функция-конструктор персонажа, в неё мы передадим его имя и цвет имени:
init -1024 python in winter_mod: from renpy.store import Character init python in winter_mod: cir = Character("TEXT_IFACE_WINTER_MOD_CHARACTER_CIR", who_color="4883B6")

Неймспейс персонажа становится частью имени:
narr "TEXT_NARRATOR_EVENT_WINTER_MOD_WINTER_EVENT_1" winter_mod.cir "TEXT_CIR_EVENT_WINTER_MOD_WINTER_EVENT_2" sl "TEXT_SL_EVENT_WINTER_MOD_WINTER_EVENT_3" winter_mod.cir "TEXT_CIR_EVENT_WINTER_MOD_WINTER_EVENT_4"

Добавление отображаемых персонажей

Это делается в три этапа.
1. Сначала отдельные слои регистрируются при помощи функции add_to_sprite_layers().
2. Затем бутерброды регистрируются при помощи функции add_to_sprite_composites().
3. Регистрируется небольшая функция отображения эмоции на полное имя спрайта.

init -1024 python in winter_mod: from renpy.store import add_to_sprite_layers from renpy.store import add_to_sprite_composites from renpy.store import ImageReference init python in winter_mod: add_to_sprite_layers("winter_mod.cir", "body", "winter_mod/cir/body.png") add_to_sprite_layers("winter_mod.cir", "glass", "winter_mod/cir/glass.png") add_to_sprite_layers("winter_mod.cir", "angry", "winter_mod/cir/angry.png") ... add_to_sprite_composites("winter_mod.cir", "angry", (726,1080), ["body","angry", "glass"]) add_to_sprite_composites("winter_mod.cir", "confused",(726,1080), ["body","confused","glass"]) add_to_sprite_composites("winter_mod.cir", "doubt", (726,1080), ["body","doubt", "glass"]) ... def cir_show(emo): return ImageReference(("winter_mod.cir",emo))

В сценарии show-функция используется с указанием неймспейса:
winter_mod.cir "TEXT_CIR_PICK_ITEM_WINTER_MOD_DOLL4_2" window show show expression sl_show("unhappy") as sl at cleft with diss window hide sl "TEXT_SL_PICK_ITEM_WINTER_MOD_DOLL4_3" window show show expression winter_mod.cir_show("normal") as cir at cright with diss window hide winter_mod.cir "TEXT_CIR_PICK_ITEM_WINTER_MOD_DOLL4_4"

Хинт
Очевидно, функции add_to_sprite_composites() и add_to_sprite_layers() можно использовать и для расширения существующих персонажей, например Слави.

Замечание
Описанный в данном разделе метод хорошо подходит для многослойных спрайтов с возможностями расширения реакцией на флаги в show-функции. Если у вас однослойные картинки и вариант определения спрайтов через image кажется вам удобнее - делайте как вам удобнее.
IX. Более сложные рецепты.
Иногда с рецептами не всё так просто, например, вы хотите, чтобы нож не расходывался при нарезке тыквы, грязная банка давалась вместе с результатом выливания зелья на предмет, или рецепт вёл себя иначе при некоторых условиях.

Для этого можно зарегистрировать python-функцию, которая принимает на вход 3 ITEM'а (первый предмет, второй предмет, вероятный результат), и возвращает либо None, либо пару ITEM'ов. Делается это так:
init -1024 python in pumpkin_mod: from renpy.store import add_to_extra_recipes init 1 python in pumpkin_mod: def knife_extra_recipes(a,b,x): if x == ITEM("PUMPKIN_MOD_KNIFE", "PUMPKIN_MOD_PUMPKIN"): return ITEM("PUMPKIN_MOD_KNIFE"), x add_to_extra_recipes("knife", knife_extra_recipes)

Ещё одна модификация рецепта - смена его цены. Чтобы сменить цену одного, некоторых или даже всех рецептов, нужно зарегистрировать python-функцию, которая принимает на вход 2 ITEM'a (what1, what2) и возвращает None или цену крафта в очках энергии. Например, вот так модифицируется цена мытья посуды:
def sponge_recipes_costs(what1,what2): if ITEM("SPONGE") in (what1,what2): return craft_wash_hp_price add_to_hooks("wash", "craft_hp_price", sponge_recipes_costs)

Здесь используется add_to_hooks(<идентификатор-хука>, <на-что-навешивается-хук>, <python-функция>) - одна из самых полезных функций для модификаций поведения, которой будет посвящён отдельный раздел.
[X]. Навешивание ху*ов на действия.
Геймплей Травницы очень прямолинейный: предметы смешиваются, локации сменяют друг друга, энергозатраты предсказуемы и так далее.

Однако почти любое действие игрока в игре можно заблокировать, изменить или сопроводить сценкой-событием. Для этого и нужны хуки. С помощью них можно сделать события, происходящие разово по наступлению очередного утра, события вместо похода дальше в лес, события при срывании 1000го растения, кастомные цены предметов и энергозатраты действий и так далее. В игре более 60 точек, на которые можно навесить свои обработчики.

В прошлом разделе вы видели, что навешивая хук на "craft_hp_price" мы можем менять трудозатраты энергии на крафт по своему усмотрению. Аналогично можно поменять затраты энергии и денег и в других местах:
  • add_to_hooks(<id>,"ambar_dig_hp_price", ...) - менять затраты энергии на копание
  • add_to_hooks(<id>,"ambar_reveal_hp_price", ...) - менять затраты энергии на раскрытие вещей в амбаре
  • add_to_hooks(<id>,"craft_hp_price", ...) - менять затраты энергии на крафт (параметры - what1, what2, **kwargs)
  • add_to_hooks(<id>,"forest_more_hp_price", ...) - менять затраты энергии на хождение дальше в лес
  • add_to_hooks(<id>,"forest_pick_hp_price", ...) - менять затраты энергии сбор предмета в лесу (параметры - what, **kwargs)
  • add_to_hooks(<id>,"shop_sell_price", ...) - менять стоимость предмета в магазине при продаже (параметры - what, **kwargs)
  • add_to_hooks(<id>,"shop_buy_price", ...) - менять стоимость предмета в магазине при покупке (параметры - what, **kwargs)

Например, вот так меняется стоимость хождения дальше в лес, если сегодня скушано соответствующее зелье:
forest_more_small_hp_price = 5 #CONST HP_PRICE def forest_more_extra_stamina_hook(**kwargs): if check_flag("extra_stamina_today"): return forest_more_small_hp_price add_to_hooks("extra_stamina", "forest_more_hp_price", forest_more_extra_stamina_hook)

Хуки могут не только влиять на стоимость действия, они могут отменять действия, не давать им совершаться. Для этих целей есть семейство целей со словом "before" в названии. Здесь действие не произойдёт, если один из хуков вернёт False:
  • add_to_hooks(<id>,"before_ambar_dig", ...) - можно предотвратить копание
  • add_to_hooks(<id>,"before_ambar_reveal", ...) - можно предотвратить раскрытие вещей в амбаре (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_ambar_pick", ...) - можно предотвратить взятие вещей в амбаре (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_cat", ...) - можно предотвратить копание
  • add_to_hooks(<id>,"before_craft", ...) - можно предотвратить крафт предмета (параметры - what1, what2, **kwargs)
  • add_to_hooks(<id>,"before_use", ...) - можно предотвратить использование предмета (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_forest_more", ...) - можно предотвратить хождение дальше в лес
  • add_to_hooks(<id>,"before_forest_pick", ...) - можно предотвратить сбор предмета в лесу (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_buy", ...) - можно предотвратить покупку предмета (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_sell", ...) - можно предотвратить продажу предмета (параметры - what, **kwargs)
  • add_to_hooks(<id>,"before_location_change", ...) - можно предотвратить переход в другую локацию (параметры - id_from, id_to, options_from, options_to, **kwargs)

Например, вот так работает переполнение инвентаря и усталость в лесу:
def event_forest_pick_tired_hook(what, **kwargs): if not hp_is_enough(forest_pick_hp_price(what)): call_without_ui("event_forest_pick_tired") return False def event_forest_pick_overflow_hook(**kwargs): if not first_of_item(None): call_without_ui("event_forest_pick_overflow") return False add_to_hooks("event_forest_pick_tired", "before_forest_pick", event_forest_pick_tired_hook, ["what"]) add_to_hooks("event_forest_pick_overflow", "before_forest_pick", event_forest_pick_overflow_hook)
call_without_ui - функция запуска label-события. Как правило, вы хотите, чтобы Славя или другие персонажи как-то комментировали отказ от действия.



Помимо before-хуков, есть ещё after-хуки (в них не предусмотрен return, зато можно, например, сделать call_without_ui после успешно совершённого действия) и cancel-хуки (аналогично, дёргаются в случае заблокированного действия). Например, вот так реализован евент, когда Славя подрывается на 20ой неудаче:
def event_mass_fail_hook(what, **kwargs): if what == ITEM("DIRTY_POT") and get_counter("get_craft_"+ITEM("DIRTY_POT")) == 20: call_without_ui("event_mass_fail") add_to_hooks("event_mass_fail", "after_craft", event_mass_fail_hook, ["what"])

Ещё одна полезная категория хуков - это действия, производимые в начале игры и во время сна. Например, функция регенерации леса привязана следующим образом:
add_to_hooks("forest", "on_game_start", generate_forest) add_to_hooks("forest", "during_sleep_night", generate_forest)

А утренние события случаются так:
def event_mushrooooms_hook(**kwargs): if check_flag("pink_powder_event_unlocked"): if not check_flag("pink_powder_event_seen"): call_without_ui("event_mushrooooms") set_flag("pink_powder_event_seen") unlock_achievement("mushrooms") return False add_to_hooks("event_mushrooooms", "during_sleep_morning", event_mushrooooms_hook)
В данном случае return False означает, что это последнее утреннее событие, дальнейшая проверка хуков не производится. Это нужно чтобы избежать ситуаций, когда несколько раз показывается, как Славя просыпается.

Туториал также построен на хуках:
  • сначала выполняются хуки "before_tutorial"
  • затем выполняются хуки "during_tutorial", нажатие "Пропустить" прерывает их выполнение
  • если "Пропустить" не было нажато, выполняются хуки "after_tutorial"
  • если "Пропустить" было нажато, выполняются хуки "on_skip_tutorial"
Если вы добавляете новую локацию или механику, можете добавить свой хук с событием на "during_tutorial", и это событие будет показано среди действий, которые Славя делает в туториале перед тем как лечь спать.

XI. Воспоминания в книге.
Для регистрации воспоминания в книге, нужно импортировать и использовать функцию add_to_book_memories(<идентификатор-воспоминания>, <png-иконка>, <label-события>).

Например,
init -1024 python in pumpkin_mod: from renpy.store import add_to_book_memories init 1000 python in pumpkin_mod: add_to_book_memories("pumpkin_cg", "pumpkin_mod/cg_pump.png", "event_pumpkin_mod_helloween")

Обратите внимание на 1000 в "init 1000 python in pumpkin_mod". Меняя это число вы меняете положение вашей иконке относительно других воспоминаний. Чем число больше, тем иконка будет позже. Это правило касается и многих других функций add_to_<что-нибудь>.



С началом новой игры воспоминание заблокировано. Чтобы разблокировать событие в воспоминаниях, нужно где-то в процессе игры вызвать функцию $unlock_book_memory(<идентификатор-воспоминания>).

Пример

Например,
label event_pumpkin_mod_helloween: $ unlock_book_memory("pumpkin_cg") play music "events/how_did_this_happen.ogg" fadein 0.2 fadeout 0.2 scene helloween_bg with dissolve window show show expression sl_show("hslavya") as sl at cleft with diss window hide sl "TEXT_SL_EVENT_PUMPKIN_MOD_HELLOWEEN_0" sl "TEXT_SL_EVENT_PUMPKIN_MOD_HELLOWEEN_1" call back_to_game return

Строчка play music "events/how_did_this_happen.ogg" fadein 0.2 fadeout 0.2 мягко включает музыку "how_did_this_happen", используемую во всяких интересных ситуациях. Эту строчку рекомендовано использовать в начале воспоминаний, наряду с $ unlock_book_memory(...).

Строчка call back_to_game возвращает обратно обычную игровую музыку и плавно тушит экран. Эту строчку рекомендовано использовать в конце воспоминаний.

Устраняем побочные эффекты от повторного просмотра евента из книги

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

Для обеспечения этого есть два механизма. Во-первых, можно вручную проверить, выполняется ли евент из книги, используя переменную inbook, и завязать на это условие какую-то логику. Например, так сделано в финальном евенте, чтобы событие из книги не предлагало окончить игру:
if not inbook: call creds else: call back_to_game

Второй механизм - это подмена флагов (только флагов!), которая делается автоматически. Внутри книжных евентов set_flag() / unset_flag() / check_flag() работают с временным контекстом. Все изменения будут забыты после выхода из книги. Например, в евенте с зайчьями ушами мы не волнуясь о последствиях меняет флаги:
$ set_flag("bunnygirl_poisoned_today") $ unset_flag("catgirl_poisoned_today")
XII. Активные зоны и предметы в избушке и амбаре.
В зависимости от содержимого инвентаря и некоторых событий наполнение избушки Слави несколько отличается - поверх задника могут быть нарисованы дополнительные предметы вроде сковороды на печке или свитка на столе.



Сделано оно так:
init 1024 python: add_to_location_items("home", "scroll", home_items_scroll) image home_item_scroll: anchor (0,0) pos (910,660) "home/item_scroll.png" init python: def home_items_scroll(): if first_of_item(ITEM("SCROLL_UNREAD")) or first_of_item(ITEM("SCROLL_READ")): renpy.show("home_item_scroll")
Здесь мы зарегистрировали в локации "home" объект с id "scroll" и python-функцией home_items_scroll для отображения.

Более сложный вариант использования add_to_location_items() - добавление кликабельной зоны. Последним параметром передаётся информация о такой зоне - координаты прямоугольной области активной зоны, суффикс имени mouseover-спрайта и id локации, на которую произойдёт переход при нажатии (про локации подробнее в следующем разделе), например:
init 1024 python: add_to_location_items("home", "trapdoor", None, {"rect":(765,895,480,160), "image_home_zone":"trapdoor","location_id":"ambar"}) image home_zone trapdoor: anchor (0,0) pos (580,745) "home/home_trapdoor.png"
В данном примере третьим параметром передаётся None, то есть без mouseover не отрисовывается ничего.



Если активная зона может быть в разных местах в разное время, как в случае с кисонькой Слави, то можно последним параметром передать python-функцию, возвращающую информацию о зоне:
def home_items_cat_zones(): if check_flag("flying_cat_today"): return elif check_flag("spill_pot_today"): return elif check_flag("cat_gone_ever"): return elif current_cat_price() == -10: return {"rect":(1410,762,182,210), "image_home_zone":"cat_best", "location_id":"cat"} elif current_cat_price() == -5: return {"rect":(400,830,230,160), "image_home_zone":"cat_good", "location_id":"cat"} else: return {"rect":(700,470,205,145), "image_home_zone":"cat_angry", "location_id":"cat"} add_to_location_items("home", "cat", home_items_cat, home_items_cat_zones)



Внутри Амбара предметы и активные зоны добавляются аналогично, вот так в него добавлена лопата:
image ambar_zone shovel: anchor (0,0) pos (590,260) "ambar/shovel1h.png" image ambar_item_shovel: anchor (0,0) pos (590,260) "ambar/shovel1.png" init python: def ambar_items_shovel(): renpy.show("ambar_item_shovel") add_to_location_items("ambar", "shovel", ambar_items_shovel, {"rect":(640,290,130,450), "image_home_zone":"shovel", "location_id":"shovel"})
XIII. Локации и активности.
С точки зрения игры, Амбар, Книга, Настройки, Лопата, Игра с кисонькой, Лес - это всё объекты одной природы. Они называются локациями, характеризуются вызовом renpy-меток, и как правило могут являть собой либо новые комнаты, либо евент/интерактив в рамках других существующих локаций.

Основная функция, которая используется для регистрации их всех - add_to_game_locations(id, text, renpy_label, tags, image=icon), где id - текстовый идентификатор, text - отображаемый текст, renpy_label - метка renpy-сценария, связанного с локацией, icon - либо имя png-картинки, либо RenPy Displayable, а tags - набор тэгов, на которые завязаны различные хуки:
  • "icontransform" - автостилизация иконки локации рамочкой
  • "homeoutside" - место, куда можно уйти из избушки со сменой фона и ambient'а
  • "homedoor" - место, при переходе в которое нужно скрипнуть дверью
  • "nohikki" - место, посещение которого ломает ачивку "Затворница"
  • "nopoison" - место, в которое нельзя ходить при пищевом отравлении
  • "nomimi" - место, в которое нельзя ходить зверодевочкам
  • "noinv" - место, в котором недоступен виджет инвентаря (например, Магазин)
  • "nomenu" - место, в котором спрятаны все виджеты вообще (например, Игра с кисонькой или Книга)

Активности в существующих местах



Параметр image=icon опциональный и нужен только для локаций, видимых за дверью. Для активностей вроде игры с Кисонькой или лопаты он не нужен. Как правило такие активности указываются с одним тэгом "nomenu":
: init -65536 python: add_to_game_locations("sleep", "TEXT_IFACE_ACTION_SLEEP", "home_sleep", ["nomenu"]) label home_sleep: scene onlayer superoverlay $ scene_home_bg() show half_black with dissolve menu: "TEXT_SELECT_SLEEP_SLEEP": call home_do_actual_sleep "TEXT_SELECT_SLEEP_NOSLEEP": hide half_black with dissolve return
Теперь если у нас где-то есть активная зона, связанная с "location_id":"sleep", по клику по ней будет вызываться сон.

Локации за дверью



Локации, находящиеся прямо за дверью, нужно дополнительно регистрировать через вызов add_to_game_locations_outside(id), например:
add_to_game_locations("ambar", "TEXT_IFACE_ACTION_AMBAR", "ambar", ["homeoutside"], image="ambar/ambar_icon.png") add_to_game_locations_outside("ambar")
add_to_game_locations("forest", "TEXT_IFACE_ACTION_FOREST", "forest", ["homedoor", "homeoutside", "nohikki", "nopoison","icontransform"], image=forest_img) add_to_game_locations_outside("forest")
Регистрировать за дверью можно только локации, у которых есть иконка.

Что может быть в локации?

Что может быть в локации? В принципе, что угодно, что вы можете реализовать с использованием RenPy и Python, хоть мини-сценка с выборами, хоть пошаговая РПГ-боёвка, хоть многооконный UI, имитирующий операционную систему[lemmasoft.renai.us]. Несколько наиболее очевидных интерфейсных решений в контексте Травницы:
  • локация с активными зонами - возьмите код Амбара за основу
  • локация с инвентарями - возьмите код Магазина за основу
  • локация с окошком - возьмите код Настроек за основу
  • локация со списком локаций - используйте в своём label функцию location_catalogue_iface(text, bg, locations_list)

Для отрисовки кнопок вроде "Дальше в лес" в пользовательских интерфейсах и виджетах можно использовать такие функции:
  • overlay_degree_button(text,ret="details",mode=True,clicked=None) - левая верхняя кнопка
  • overlay_gohome_button(text="TEXT_IFACE_HINT_BACK",ret="gohome",mode=True,clicked=None) - правая верхняя кнопка
  • overlay_prev_button(text,ret="prev",mode=True,clicked=None) - левая нижняя кнопка
  • overlay_okay_button(text,ret="return",mode=True,clicked=None) - центральная нижняя кнопка
  • overlay_next_button(text,ret="next",mode=True,clicked=None) - правая нижняя кнопка

Для отображения характерного чёрного окошка вокруг вашего ui, можно использовать блок with ui_window(title) as w, например:
with ui_window("TEXT_IFACE_TESTS_MOD_LOCATION") as w: ui.vbox() for t in colored_tests_lines(linegrs): ui.label(t, text_style="smallest_style") ui.close() overlay_gohome_button()
XIV. Виджеты.
В оверлей игры можно добавлять свои виджеты для отображение каких-нибудь показателей или дополнительных кнопок.

Добавление виджета делается функцией add_to_overlays(id, ui_function), где id - строковый идентификатор, а ui_function - python-функция, отображающая виджет с использованием RenPy API ui. Функция должна принимать один параметр ttparent - объект для отображения mouseover-подсказок.

Например,
def ui_book_overlay(ttparent): tt = Tooltip(160,100,u"TEXT_IFACE_HINT_BOOK",ttparent) ui.imagebutton("overlay/book.png", "overlay/book_mo.png", clicked=ui.callsinnewcontext("book_call"), xmargin=0, ymargin=0, pos=(240,0), anchor=(0,0), hovered=tt.show, unhovered=tt.hide) add_to_overlays("book", ui_book_overlay)



Аналогичный код используется в моде, отображающем баффы:
init 1 python in buffs_mod: add_to_overlays("buffs", ui_buffs_overlay)

По умолчанию виджеты показываются только в интерактивные моменты игры и убираются во время кат-сцен и в локациях со специальными пометками. Если вы хотите сами осуществлять контроль за тем, когда отображается виджет, можно передать False третьим параметром функции add_to_overlays(), тогда ваша ui-функция будет вызываться постоянно и вам нужно будет разместить внутри if-условие, ограничивающее видимость.



Например, так сделано для подсказок в туториале, их видимость управляется вручную переменной hint_text:
def ui_hint_overlay(ttparent): if hint_text: ui.window(background=im.Flip("options/box.png",vertical=True), xpos=265, ypos=125, xanchor=0, yanchor=1.0, xminimum=1400, xmaximum=1400, yminimum=490, ymaximum=490, xmargin=0, xpadding=150, top_padding=32, bottom_padding=43) ui.label(hint_text, text_style="hint_style", ypos=1.0, yanchor=1.0, text_yalign=1.0) add_to_overlays("hint", ui_hint_overlay, False)
XV. Добавление своих Настроек.
Если вам зачем-то понадобилось добавить в меню Настроек конфигурирование какой-то переменной, например, в значение True/False, то такая возможность есть:
init -1024 python in your_mod: from renpy.store import add_to_settings init python in your_mod: if renpy.store.persistent.your_mod_param is None: renpy.store.persistent.your_mod_param = False init 1000 python in your_mod: add_to_settings("your_mod_param","TEXT_IFACE_SETTINGS_YOUR_MOD_PARAM", lambda:[ ("TEXT_IFACE_SETTINGS_YOUR_MOD_PARAM_TRUE", renpy.store.persistent, "your_mod_param", True), ("TEXT_IFACE_SETTINGS_YOUR_MOD_PARAM_FALSE", renpy.store.persistent, "your_mod_param", False), ])
XVI. Добавление страниц в Помощь.
Здесь всё просто - функция регистрации принимает 3 параметра - текстовый идентификатор секции, текст секции и её заголовок:
init 400 python: add_to_help_pages("howto4", help_txt_4, "TEXT_HELP_TITLE_HOWTO")

Текст может быть многострочной python-строкой, тогда каждая строка будет матчиться с переводами по отдельности. Пустой абзац можно задать пустой строкой. Например, четвёртая страница справки задаётся таким вот образом:
help_txt_4 = u""" TEXT_HELP_SHOP_0 TEXT_HELP_SHOP_1 TEXT_HELP_SHOP_2 TEXT_HELP_SHOP_3 """
XX. Проверка мода, его публикация и обновление.
Всего этого в принципе уже достаточно, чтобы сделать мод, в котором предметы можно получать, смешивать и использовать, случаются всякие события, есть всякие новые места и предметы и так далее. Давайте наконец разберёмся с процессом публикации.

Для начала установите модификацию "Автоматические тесты и инструменты разработки": http://steamproxy.net/sharedfiles/filedetails/?id=812571180

Проверка мода

Активировав этот мод и свой мод, перезагрузите игру, начните играть и войдите в меню, которое находится за дверью избушки. Там появится специальная кнопка, "Запуск автотестов". Запустите тесты и посмотрите отчёт (это может занять немного времени). Тесты направлены на выявление таких проблем как использование несуществующих ogg и png-файлов, отсутствие переводов для строк, типовые ошибки в локализации, jump на несуществующие метки, нарушение некоторых соглашений для моддинга и так далее. Не публикуйте моды, для которых есть проблемы, лучше постарайтесь разобраться, в чём дело, или обратитесь к разработчикам игры или другим мододелам за помощью.

Публикация мода

Если всё хорошо, то можно публиковать мод. Для этого откройте cmd.exe и перейдите в папку herbalife, которая находится внутри мода "Автоматические тесты и инструменты разработки". Вероятный путь:
"C:\Program Files (x86)\Steam\steamapps\workshop\content\542260\812571180\tests_mod\herbalife"
Далее, выполните следующую команду: "herbalife.exe <путь-до-папки-мода>". Нужно указывать путь до той папки, непосредственно внутри которой находится descript.ion, например:
herbalife.exe "C:\Program Files (x86)\Steam\steamapps\common\Herbalist\mods\123456789"
Вводить ничего не надо, инструмент возьмёт все данные из descript.ion и steam_logo.png.


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

Обновление опубликованного мода

Чтобы обновить уже загруженный в Steam мод, загруженный как "sharedfiles/filedetails/?id=XXXXXXXXX", нужно дописать к указанной выше команде ключик -u XXXXXXXXX, например, для обновления тыквенного мода я использую следующую команду:
herbalife.exe "C:\Herbalist\mods\pumpkin_mod" -u 810960137

ВНИМАНИЕ: после апдейта мода Права доступа также сбрасываются на "скрытый", пока вы не переключите их на "публичный"! Это сознательно добавленная защита от непроверенных апдейтов.
XXX. TODO
Документ ещё далёк от завершения. Планируется написать ещё как минимум следующие разделы:
  • добавление биомов в лесу
  • добавление типовых локаций - поподробнее?
  • добавление страниц и разделов в книгу
  • добавление переводов

Если у вас есть какие-то вопросы, как по написанным, так и по отсутствующим секциям, спрашивайте в комментариях или обращайтесь к автору в личку, я постараюсь помочь.
19 Comments
lolbot_iichan  [author] 24 Feb @ 12:11pm 
Nokros,
Чтобы прятать цветок можно добавить в лес новый биом, который в зависимости от флага будет выглядеть как обычная поляна или как ваша кастомная поляна с цветком. Как зарегистрировать в лесу новый биом - см. forest.rpy в Зимнем Моде. В функции-генераторе для нового биома сделать проверку на check_flag и в зависимости от него заполнить поляну так, чтобы нужный цветок был или не было.Если возникнут затруднения - напишите в личку или в телегу, помогу это сделать.
Звёздный bestrigyn 21 Feb @ 10:37pm 
У игры Есть Сюжет? Можно сделать Альт Сюжет мод И как?
Nokros 21 Oct, 2023 @ 3:07am 
Всем привет, я хотел бы сделать так чтобы после срабатывания какого-то события цветок из моего мода больше не спавнился, может кто подсказать как это провернуть
Домарощинер 11 Feb, 2022 @ 11:51am 
lolbot_iichan, есть вопрос.
Домарощинер 28 Jun, 2021 @ 11:28am 
Хорошо
lolbot_iichan  [author] 28 Jun, 2021 @ 9:52am 
Домарощинер, возможно, я перемудрил и написал слишком костноязычно?

Большинство моментов можно делать по аналогии с существующими модами, например:
https://steamproxy.net/sharedfiles/filedetails/?id=849326600
https://steamproxy.net/sharedfiles/filedetails/?id=839005624
Там есть как минимум живые примеры использования добавления предметов, нахождения предметов, рецептов, локализации, событий, счётчиков, персонажей, хуков, воспоминаний в книге.

Если нужны какие-то разъяснения - напишите в личку, постараюсь помочь и рассказать как лучше сделать задуманное.
Домарощинер 28 Jun, 2021 @ 3:34am 
Вообще, простор для модов огромный, но порог вхождения слишком высок. Жаль.
Домарощинер 28 Jun, 2021 @ 12:03am 
Почитал. Общий смысл статьи: "Не влезай, убьёт".
BloodStone SZ 19 May, 2020 @ 2:53am 
Lana, создать её вручную
Drake 22 Jan, 2017 @ 3:44am 
translation please?