Skip to content

[TM FIRST] Рассказчики#5777

Open
TeshariEnjoer wants to merge 3 commits intoFluffy-Frontier:masterfrom
TeshariEnjoer:storytellers
Open

[TM FIRST] Рассказчики#5777
TeshariEnjoer wants to merge 3 commits intoFluffy-Frontier:masterfrom
TeshariEnjoer:storytellers

Conversation

@TeshariEnjoer
Copy link
Collaborator

@TeshariEnjoer TeshariEnjoer commented Feb 8, 2026

About The Pull Request


Work in Progress

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

В отличие от статических систем событий, эта настройка создает адаптивные, специфичные для раунда истории:

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

Ключевые компоненты системы (подробно описаны ниже):

  1. Анализатор: непрерывно сканирует состояние станции, ее целостность, ресурсы, исследования, энергию и активность антагонистов.
  2. Балансировщик: вычисляет силу станции по сравнению с силой антагонистов, общее напряжение (0-100) и соотношение сил для точного балансирования.
  3. Планировщик: создает временные рамки событий, избегая недавних повторений и согласовываясь с настроением рассказчика (темп, агрессивность, изменчивость).
  4. Административные инструменты: Полный интерфейс для мониторинга показателей, настройки настроения/целей и голосования за рассказчиков.

Этот PR реализует пять уникальных рассказчиков (с возможностью расширения), каждый с отличными настроениями, личностями и конфигурациями для различных стилей игры. Полная настраиваемость через настройки сервера; портреты/логотипы для погружения в атмосферу; голосование перед раундом и административные переопределения во время раунда.


Implemented Storytellers

p_mia

Mia'Chill
Молодая исследовательница тешари, блуждающая по звездам в поисках новых миров. Ее спокойный и общительный характер привносит порядок и равновесие в окружающий хаос.

p_cas

Cas'Classic
Кэс - писатель-лиса, обожающий сбалансированные, полные приключений рассказы, в которых большая опасность всегда приводит к открытиям и росту.

p_random

Randall's Gambit
Рэндалл - азартный игрок, который доверяет все случаю. Забудьте о покое - он отправит ящики пива сразу после падения метеорита, если такова будет воля судьбы.

p_edd

Edd Catastrophe
Эдд Катастрофа родом из пустоты, где милосердие и сострадание не имеют значения. Его существование определяется разрушением и геноцидом - он не оставит от станции ничего.

p_lovers

Mia & Edd'Challenge
Настоящий парадокс - существо, закаленное в разрушении, встречает того, кто олицетворяет мир. Вместе они создают гармонию на грани катастрофы.

ЮИ:

  • Голосование за рассказчика (pre-round):
image
  • Админское ЮИ (in-round metrics/goals):
image image

Основные механизмы: как работают рассказчики

Адаптация в зависимости от настроения: настроение каждого рассказчика (темп: скорость событий; агрессивность: интенсивность угрозы; изменчивость: непредсказуемость) развивается на основе моментальных снимков станции, влияя на выбор цели.

Динамический баланс:

Показатель Описание Влияние
Сила станции Готовность экипажа, безопасность, ресурсы Высокая = меньше угроз
Сила антагониста Активность, убийства, цели Высокая = события в пользу экипажа
Общее напряжение (0-100) Взвешенное: история + вклад антагонистов + целостность/здоровье + качественные показатели (координация/скрытность/уязвимость) Определяет частоту/серьезность событий
Соотношение сил Антагонисты : Станция <1 = усиление антагонистов; >1 = ослабление

Хранилище и моментальные снимки: Все метрики хранятся в хранилище (ассоциативный список); снимки фиксируют состояния для принятия решений планировщиком.

Пороги: здоровье/раненые/болезни/смерти классифицируются (например, HEALTH_LOW_THRESHOLD, DEAD_RATIO_HIGH_THRESHOLD) для точных реакций.


Анализ станции (анализатор)

Асинхронный сбор метрик (обновляется через SSstorytellers):

  • Здоровье экипажа/антагов: ранения, болезни, смерти.
  • Целостность: повреждения корпуса, пожары, энергосистема (APC/SMES).
  • Сила/ресурсы: вооружение, минералы, исследования.
  • Отслеживание противников: индивидуальная эффективность/активность.

Планирование сюжета («Мышление»)

Модульный цикл (storyteller_think.dm):

  1. Анализ → Сбор показателей хранилища.
  2. Баланс → Вычислить напряжение/соотношение/моментальный снимок.
  3. План → Выбрать глобальную цель (~30-60 минут); создать подцели/события на временной шкале.
  4. Выполнение → Запуск в соответствии с настроением; запись в журнал истории.
  5. Адаптация → Перепланирование по завершении цели/смене станции.

Предотвращение спама: штрафы за повторение; льготные периоды; ограничения на недавний урон.


Дополнительные функции

  • Замена SSevents: все события/цели теперь наративно согласованы под руководством рассказчика.
  • Дополнительная переопределение SSdynamic: передача появления антагов рассказчику для единого контроля.
  • Удобство для разработчиков: простое добавление целей/показателей; полный журнал/интерфейс.
  • Недавние доработки: исправлена логика пороговых значений, нормализовано напряжение, взвешенные вычисления антагов.

Почему это хорошо для игры

  • Бесконечная реиграбельность: 5+ рассказчиков = бесконечное количество сюжетов; масштабируется под любое количество игроков/серверов.
  • Облегчение работы администратора: автоматическая балансировка; глубокий интерфейс для настроек без необходимости в кодировании.
  • Влияние игроков: голосование определяет ход раундов; действия команды непосредственно влияют на настроение/цели рассказчика.
  • Точность RimWorld: настоящий адаптивный ИИ — раунды кажутся живыми, а не случайными.
  • Производительность: асинхронная/кэшированная; нулевая задержка.

NovaSector Ready: беспроблемная интеграция; настраиваемые переключатели.


Доказательство тестирования

Скриншоты/видео

Журнал изменений

:cl:
    добавлено: новая система рассказчиков — адаптивные нарративы, вдохновленные RimWorld, заменяющие SSevents (опционально SSdynamic).
    добавлено: 5 уникальных рассказчиков (Mia'Chill, Cas'Classic, Randall's Gambit, Edd Catastrophe, Mia&Edd'Challenge) с настроениями, настройками, голосованием/интерфейсом.
	добавлено: глубокий анализ станции (метрики/хранилище/снимки), динамическая балансировка (напряжение/соотношения), планирование целей/сроки.
    добавлено: панель администратора для управления в реальном времени; портреты/речи для погружения.
    баланс: автоматическая настройка сложности в зависимости от производительности экипажа/антагов, количества игроков, прогресса раунда.
:cl:

@Cerulyss-Aureon
Copy link

Cerulyss-Aureon commented Feb 13, 2026

Арты очень крутые.
Насчет идеи. Мне, кажется, будет не хватать рейдов как в оригинальной игре и их разнообразие. Ивенты конечно классно, но они не должны быть основой.
Выдавать антаганистов тоже не получиться, потому у ФФ сейчас в основном лоупоп. Да и экипажу запрещённою, к сожалению, с ними взаимодействовать. По сути это КСка СБ с Антагами, остальные НПС, которые не должны мешать не тем ни другим, иначе бан.
А вот отбивать рейды всем экипажем, как и в условной римке. Как по мне будет достаточно фаново, если можно будет прокачать им ИИ на уровне римки, а не просто челы которые просто бегут и бьют как сейчас. Да и может подкупать массовость, а не 1-5 агентов которые в тихую цели выполнили и дальше пьем чай с условным Edd Catastrophe. Ну может десяток метеоритов прилетит. Но это будет не особо весело.

@Cerulyss-Aureon
Copy link

ну и почти 15к строк кода. Аниме-псих. И удивлен что кроме меня никто ничего не отписал =(. Хотя идея очень крутая.

Copy link
Contributor

@mogeoko mogeoko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Первая часть. Вторая хз когда.

Image

return .


/proc/is_safe_area(area/to_check)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Время нанести первый удар по 15к строкам кода и первая стрела называется "ЗАНУДСТВО".

Я бы в целом посоветовал некоторую часть файлов переименовал, как например у тебя в этом файле по сути helper'sы для сторителлеров, но функции теоретически можно применять и в других местах. Если не забуду то укажу и остальные такие файлы.

А второе, это названия функций и отсутствие документации к ним. Интуитивно не смотря в код трудно понять зачем оно и как определяется безопасность комнаты.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Забыл указать. Забыл какие нужно. Альцг.......

Comment on lines +78 to +83
for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in vents)
var/turf/open/T = get_turf(vent)
var/datum/gas_mixture/floor_gas_mixture = T.air
if(!floor_gas_mixture)
unsafe_vents += 1
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше проверяй через var/turf/T, потому что венты могут быть в стенах и насколько я понял здесь они считаются как безопасные. Мб проверять тип турфа (closed или тп) и потом уже газы

Comment on lines +90 to +97
if((floor_gas_mixture.temperature <= 270) || (floor_gas_mixture.temperature >= 360))
unsafe_vents += 1
continue

var/pressure = floor_gas_mixture.return_pressure()
if((pressure <= 20) || (pressure >= 550))
unsafe_vents += 1
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

270, 360 температуру и 20, 550 замени на дефайны BODYTEMP_COLD_DAMAGE_LIMIT, BODYTEMP_HEAT_DAMAGE_LIMIT и HAZARD_LOW_PRESSURE, HAZARD_HIGH_PRESSURE (atmos_mob_interaction.dm в помощь)

Comment on lines +99 to +100
if(!(total_vents == 1 && unsafe_vents == 0))
return !(unsafe_vents > round(total_vents * 0.5))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно я не понимаю тонкости 100 строки, но не проще было бы вписать false?

return !(unsafe_vents > round(total_vents * 0.5))
return TRUE

/proc/pick_weight_f(list/list_to_pick)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Документация. Буквально. Функция pick_weight_f вычисляет CUM предметов 😄

@@ -0,0 +1,413 @@
ADMIN_VERB(storyteller_vote, R_ADMIN | R_DEBUG, "Storyteller - Start Vote", "Start a global storyteller vote.", ADMIN_CATEGORY_STORYTELLER)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

vote_start_time = 0 // Reset vote start time
deltimer(vote_timer_id)
var/list/tallies = list()
var/list/diff_tallies = list()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут докопаюсь, без этого никак, надо повторять за другими. diff_tallies изначально трудно интерпретировать как сложность, скорее в голову придёт difference или что-то такое

if(!length(best_storytellers))
to_chat(world, span_boldnotice("No votes were cast! Random storyteller selected."))
selected_id = pick(list(storyteller_data))
selected_difficulty = 1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Локальный selected_difficulty затерялся в return'е. Возможно ты куда-то забыл его передать

Comment on lines +180 to +184
if(length(best_storytellers) == 1)
selected_id_str = best_storytellers[1]
else
selected_id_str = pick(best_storytellers)
to_chat(world, span_announce("Tie broken randomly!"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно сократить до
selected_id_str = pick(best_storytellers)
if(length(best_storytellers) > 1) to_chat(блаблабла)

Comment on lines +246 to +252
/proc/get_avg(list/nums)
if (!length(nums))
return 1.0
var/sum = 0
for (var/n in nums)
sum += n
return sum / length(nums)
Copy link
Contributor

@mogeoko mogeoko Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как будто бы этому место где-то в дефайнах/хелперах. При чём для огромной части старого кода, почему этой функции раньше не существовало лол

@Saukykouko
Copy link
Contributor

Могеоко сосал

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments