dnd5-scripts/generate_encounter/hooks_data.py
2026-06-17 22:32:48 +03:00

323 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
hooks_data.py — данные о хуках и весах типов встреч.
Содержит:
- ENCOUNTER_TYPE_WEIGHTS: веса типов встреч по биому.
- HOOK_WEIGHTS: веса хуков внутри каждого типа (универсальные).
- HOOKS: схема субъектов для каждого хука.
"""
# ──────────────────────────────────────────────────────────────────────────────
# Веса ТИПОВ встреч по биому/плану.
# Добавление нового плана = одна новая строка здесь.
# ──────────────────────────────────────────────────────────────────────────────
ENCOUNTER_TYPE_WEIGHTS: dict[str, dict[str, int]] = {
"arctic": {"PvP": 50, "PvE": 25, "PvX": 20, "PvSelf": 5},
"coastal": {"PvP": 45, "PvE": 20, "PvX": 30, "PvSelf": 5}, # 10
"desert": {"PvP": 45, "PvE": 30, "PvX": 20, "PvSelf": 5}, # 10
"forest": {"PvP": 50, "PvE": 20, "PvX": 25, "PvSelf": 5}, # 10
"grassland": {"PvP": 50, "PvE": 20, "PvX": 25, "PvSelf": 5}, # 10
"hill": {"PvP": 50, "PvE": 20, "PvX": 25, "PvSelf": 5}, # 10
"mountain": {"PvP": 40, "PvE": 30, "PvX": 25, "PvSelf": 5},
"swamp": {"PvP": 35, "PvE": 25, "PvX": 35, "PvSelf": 5},
"underdark": {"PvP": 50, "PvE": 15, "PvX": 30, "PvSelf": 5},
"underwater": {"PvP": 30, "PvE": 35, "PvX": 30, "PvSelf": 5},
"urban": {"PvP": 40, "PvE": 15, "PvX": 25, "PvSelf": 20}, # 45
# Планы — новые строки:
"nine_hells": {"PvP": 55, "PvE": 25, "PvX": 15, "PvSelf": 5},
"astral_sea": {"PvP": 20, "PvE": 40, "PvX": 35, "PvSelf": 5},
"feywild": {"PvP": 35, "PvE": 25, "PvX": 30, "PvSelf": 10}, # 25
"plane_of_air": {"PvP": 35, "PvE": 30, "PvX": 30, "PvSelf": 5},
"plane_of_earth": {"PvP": 40, "PvE": 30, "PvX": 25, "PvSelf": 5},
"plane_of_fire": {"PvP": 45, "PvE": 25, "PvX": 25, "PvSelf": 5},
"plane_of_water": {"PvP": 35, "PvE": 30, "PvX": 30, "PvSelf": 5},
}
# ──────────────────────────────────────────────────────────────────────────────
# Веса ХУКОВ внутри каждого типа.
# Универсальны — не зависят от биома или плана.
# ──────────────────────────────────────────────────────────────────────────────
HOOK_WEIGHTS: dict[str, dict[str, int]] = {
"PvP": {
"The Hunt": 18, # Охота, засада, набег
"The Routine": 20, # Патруль, миграция, фуражирование
"The Claim": 15, # Охрана территории/чека
"The Social Event": 12, # Процессия, ритуал, состязание
"The Interaction": 12, # Поиск контакта (торговля, помощь)
"The Internal Conflict": 10, # Две группы дерутся
"The Shadow": 7, # Скрытое наблюдение, шпионаж
"The Chase": 5,
"The Challenge": 1,
},
"PvE": {
"The Sudden Crisis": 28, # Сбежавшие животные, пожар, телега
"The Obstacle": 25, # Блок пути (завал, айсберг, водоросли)
"The Hazard": 18, # Опасная зона (водоворот, гейзер)
"The Anomaly": 17, # Дикая магия, корабль-призрак
"The Weather": 12,
},
"PvX": {
"The Landmark": 20, # Пустые руины, статуя, кораблекрушение
"The Remnant": 25, # Трупы, следы, обломки
"The Cache": 25, # Сокровище, жемчуг, магические ягоды
"The Lair": 20, # Обитаемая пещера, логово
"The Omen": 10, # Звуки, предзнаменования, эльфийская песня
},
"PvSelf": {
"The Plea": 30, # Выживший в воде, сирота
"The Moral Crossroads": 35, # Задиры, произвол стражи, призыв
"The Temptation": 22, # Теневая сделка, бесхозный лут
"The Betrayal": 13,
},
}
# ──────────────────────────────────────────────────────────────────────────────
# Схема субъектов для каждого хука.
#
# subject_sources — список слотов субъектов. Каждый слот:
# source: "creatures" → get_monsters_or_animals()
# "npc" → get_npc()
# count: строка с dice-нотацией или "1"
# role: роль в сцене (для нейросети): "primary", "aggressor",
# "defender", "victim", "bystander"
# optional: если True — слот появляется с вероятностью 50%
#
# latent_threat: True → существа могут появиться как следствие
# провала броска Surprise (решается в encounter_generator)
# ──────────────────────────────────────────────────────────────────────────────
HOOKS: dict[str, dict[str, dict]] = {
"PvP": {
"The Hunt": {
"description": "Целенаправленная атака, засада или поиск добычи (хищники, пираты, бандиты).",
"subject_sources": [
{"source": "creatures", "count": "1d4+1", "role": "aggressor"},
],
"latent_threat": False,
},
"The Routine": {
"description": "Существа заняты своей жизнью: миграция, патруль, фуражирование, прогулка.",
"subject_sources": [
{"source": "creatures", "count": "1d4+1", "role": "primary"},
],
"latent_threat": False,
},
"The Claim": {
"description": "Существа активно охраняют логово, проход, предмет или территорию.",
"subject_sources": [
{"source": "creatures", "count": "1d4+1", "role": "defender"},
],
"latent_threat": False,
},
"The Social Event": {
"description": (
"Массовое или социальное действие существ. "
"Мирные варианты: процессия, ярмарка, состязание, представление, "
"глашатай зачитывает указ, свадьба гоблинов. "
"Тёмные варианты: культисты призывают демона через ритуал жертвоприношения, "
"кобольды пытаются оживить каменного идола, "
"друиды проводят обряд плодородия — прервать можно, но опасно."
),
"subject_sources": [
{"source": "npc", "count": "1d4+2", "role": "bystander"},
],
"latent_threat": False,
},
"The Internal Conflict": {
"description": "Две группы существ конфликтуют между собой: бунт, драка за добычу, политический протест/митинг, разборки между бандами, хищник - напавший на патруль.",
"subject_sources": [
{"source": "creatures", "count": "1d4", "role": "aggressor"},
{"source": "creatures", "count": "1d4", "role": "defender"},
],
"latent_threat": False,
},
"The Shadow": {
"description": "Существа скрытно наблюдают, крадутся следом, пытаются обокрасть кого-то или совершают тайное действие.",
"subject_sources": [
{"source": "creatures", "count": "1d3", "role": "primary"},
],
"latent_threat": False,
},
"The Interaction": {
"description": (
"Существа или NPC инициативно ищут контакта с героями. "
"Диапазон от мирного (торговцы, проводники, пьяный с байками) "
"до условного (медуза предлагает пропуск в обмен на историю, "
"хаг торгуется за услугу, дьявол — за душу, "
"культист вербует в орден)."
),
"subject_sources": [
{"source": "npc", "count": "1d3", "role": "primary"},
],
"latent_threat": False,
},
"The Chase": {
"description": (
"Динамичное преследование или гонка, требующие проверок навыков, "
"а не прямого боя. "
"Примеры: конная погоня за курьером с секретным письмом, "
"скачки на грифонах над городом, гонки на лодках по подземной реке, "
"беглый преступник петляет по рыночным рядам, "
"гарпии преследуют торговый корабль."
),
"subject_sources": [
{"source": "creatures", "count": "1d3", "role": "aggressor"},
{"source": "npc", "count": "1", "role": "victim"},
],
"latent_threat": False,
},
"The Challenge": {
"description": (
"Прямой вызов на состязание, дуэль или испытание навыков без цели убить. "
"Примеры: дворянин требует сатисфакции за оскорбление, варвар вызывает на "
"кулачный бой у костра, эльфийский следопыт предлагает пари на точность стрельбы, "
"монах вызывает на испытание воли (кто дольше простоит под ледяным водопадом)."
),
"subject_sources": [
{"source": "npc", "count": "1", "role": "aggressor"},
{"source": "npc", "count": "1d4", "role": "bystander", "optional": True},
],
"latent_threat": False,
},
},
"PvE": {
"The Sudden Crisis": {
"description": "Динамичная, срочная проблема среды (пожар, сбежавшая телега/животное, обвал, обрушение конструкции, землетрясение, тонущее судно, паника на рынке, т.п.).",
"subject_sources": [
{"source": "npc", "count": "1", "role": "victim", "optional": True},
],
"latent_threat": False,
},
"The Obstacle": {
"description": "Масштабная преграда, блокирующая путь (завал на лесной тропе, айсберг, заросли, пропасть, запертые ворота).",
"subject_sources": [],
"latent_threat": False,
},
"The Hazard": {
"description": "Пассивная опасная зона или ловушка (водоворот, гейзер, ядовитые споры, зыбучие пески).",
"subject_sources": [],
"latent_threat": True,
},
"The Anomaly": {
"description": "Сложное природное или магическое явление (мираж, корабль-призрак, зона дикой магии, парящие острова, светящиеся туманы, портал).",
"subject_sources": [],
"latent_threat": True,
},
"The Weather": {
"description": (
"Экстремальное погодное явление, меняющее условия всей зоны встречи. "
"Примеры: внезапная метель обнуляет видимость и замедляет движение, "
"пылевая буря на равнине, ураган у побережья, "
"кислотный дождь в Nine Hells разъедает снаряжение, "
"густой туман скрывает всё дальше 10 футов, "
"магическая гроза бьёт молниями случайно."
),
"subject_sources": [],
"latent_threat": True, # в буре может скрываться хищник
},
},
"PvX": {
"The Landmark": {
"description": (
"Заметный объект инфраструктуры, древности или магии. "
"Пассивные: пустые руины, статуя героя, монолит с надписями, "
"кораблекрушение, каменный круг. "
"Интерактивные (Puzzle): древняя дверь с загадкой на трёх языках, "
"магический замок с шифром из созвездий, "
"каменная плита с рунами, требующими правильного порядка нажатий, "
"зеркало, отражающее иное место."
),
"subject_sources": [],
"latent_threat": True,
},
"The Remnant": {
"description": (
"Свидетельства недавних или давних событий. "
"Краткосрочные следы: трупы, сломанные повозки, плавающие обломки, "
"остатки лагеря, брошенные вещи, сожжённая роща. "
"Долгосрочные последствия (Fallout): отравленная река после битвы армий, "
"выжженная земля проклятием личей, "
"зона некротической энергии — земля мертва уже десять лет, "
"пепелище деревни с ловушками мародёров."
),
"subject_sources": [],
"latent_threat": True,
},
"The Cache": {
"description": "Находка, имеющая ценность (магическое растение, утерянный груз, схрон, жемчуг, сундук с сокровищами, полезный предмет на тротуаре).",
"subject_sources": [],
"latent_threat": True,
},
"The Lair": {
"description": "Место обитания монстров, героев может ждать хозяин дома (грот осьминога, заселенные руины, гнездо грифона, бандитский притон, т.п.).",
"subject_sources": [
{"source": "creatures", "count": "1d6", "role": "primary"},
],
"latent_threat": False,
},
"The Omen": {
"description": (
"Информационный сигнал из среды — от неясного до конкретного. "
"Знамения: странная мелодия из тумана, дым трёх цветов на горизонте, "
"все птицы разом умолкли, мертвая рыба плывёт по реке вверх. "
"Сообщения (Message): послание в бутылке с зашифрованными координатами, "
"магический камень с записью голоса мёртвого мага, "
"надпись на стене древним языком («они уже внутри»), "
"застывший в янтаре свиток с предупреждением."
),
"subject_sources": [],
"latent_threat": True,
},
},
"PvSelf": {
"The Plea": {
"description": (
"Кто-то молит о помощи или ситуация требует самопожертвования. "
"Просьба о действии: выживший на обломках судна, "
"раненый рыцарь у дороги, пленник в клетке, "
"умоляющий купец («они убьют мою семью»). "
"Просьба об отказе (Sacrifice): сломай магический меч — снимешь проклятие с деревни, "
"сожги эти записи — они единственное доказательство твоей невиновности, "
"потеряй память о прошлом, чтобы пройти через врата богов."
),
"subject_sources": [
{"source": "npc", "count": "1", "role": "victim"},
],
"latent_threat": False,
},
"The Moral Crossroads": {
"description": "Столкновение с явной несправедливостью или злоупотреблением (задиры, произвол стражи или налоговика, несправедливый арест, жестокое обращение с животными, лорд приказывает выдать беглого слугу, т.п.).",
"subject_sources": [
{"source": "npc", "count": "1d3+", "role": "aggressor"},
{"source": "npc", "count": "1", "role": "victim"},
],
"latent_threat": False,
},
"The Temptation": {
"description": "Соблазн получить выгоду сомнительным путем (теневая сделка, бесхозный кошелёк или груз на дороге, возможность украсть у пьяного, забытые в храме деньги, т.п.).",
"subject_sources": [
{"source": "npc", "count": "1", "role": "primary", "optional": True},
],
"latent_threat": False,
},
"The Betrayal": {
"description": (
"Герой обнаруживает, что союзник, друг или заказчик предал его. "
"Примеры: контрактник сбежал с авансом и уликами на героев, "
"NPC которого спасли — оказывается осведомителем врага, "
"заказчик нанимал, зная что посылает на верную смерть, "
"проводник ведёт прямо в засаду, "
"союзный маг тайно работает на гильдию убийц."
),
"subject_sources": [
{"source": "npc", "count": "1", "role": "traitor"},
],
"latent_threat": False,
},
},
}