Update generate_encounter tool: now it gets also world and region parameters
This commit is contained in:
parent
8d8eff6863
commit
65cbed3b88
|
|
@ -59,6 +59,17 @@ from get_npc_races import get_npc_races
|
|||
from generate_npc import generate_npc as get_npc
|
||||
|
||||
|
||||
# Множество планов существования (не прайм-миры).
|
||||
# Если world попадает сюда — монстры берутся из planar_monsters_data
|
||||
# (через ключ world), а биом environment игнорируется.
|
||||
PLANAR_WORLDS: set[str] = {
|
||||
"astral_sea", "plane_of_air", "plane_of_earth", "plane_of_fire", "plane_of_water",
|
||||
"feywild", "shadowfell",
|
||||
"lower_planes_le", "lower_planes_ne", "lower_planes_ce",
|
||||
"upper_planes_lg", "upper_planes_ng", "upper_planes_cg",
|
||||
"planes_of_chaos", "plane_of_law",
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# TypedDict
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -72,12 +83,34 @@ class EncounterRequest(TypedDict, total=False):
|
|||
environment: Биом.
|
||||
Допустимы: arctic, coastal, desert, forest,
|
||||
grassland, hill, mountain, swamp, underdark,
|
||||
underwater, urban,
|
||||
astral_sea, plane_of_air, plane_of_earth, plane_of_fire, plane_of_water,
|
||||
feywild, shadowfell, lower_planes_le, lower_planes_ne, lower_planes_ce,
|
||||
upper_planes_lg, upper_planes_ng, upper_planes_cg, planes_of_chaos, plane_of_law.
|
||||
underwater, urban.
|
||||
По умолчанию: forest.
|
||||
|
||||
world: Космологический уровень сцены (англ.). Прайм-мир
|
||||
("Toril", "Krynn", "Oerth") ЛИБО план существования ("feywild",
|
||||
"shadowfell", "lower_planes_le/ne/ce",
|
||||
"upper_planes_lg/ng/cg", "planes_of_chaos",
|
||||
"plane_of_law", "astral_sea", "plane_of_air",
|
||||
"plane_of_earth", "plane_of_fire", "plane_of_water").
|
||||
Пусто → прайм-мир, монстры по биому environment.
|
||||
|
||||
region: Регион ВНУТРИ прайм-мира (обычное англ. название,
|
||||
код нормализует через symspellpy).
|
||||
Определение СТРОГО ПО ПРИОРИТЕТУ:
|
||||
0. Никаких артиклей нигде применять нельзя, пример, nelanther_isles, а не the_nelanther_isles
|
||||
1. Если world — план, region опускается.
|
||||
2. Подземелье/пещера/канализация → "Underdark".
|
||||
3. Крупный город → название города
|
||||
(Waterdeep, Baldur's Gate, Neverwinter,
|
||||
Suzail, Athkatla, Calimport, Menzoberranzan).
|
||||
4. Дикая местность → историческая область по
|
||||
ближайшему городу/хребту/лесу
|
||||
(Western Heartlands, Silver Marches, Cormyr,
|
||||
Amn, Tethyr, Thay, Chult и т.д.).
|
||||
5. Море/побережье → ближайшая страна или
|
||||
уникальная акватория (Sea of Fallen Stars).
|
||||
6. Не определить → пусто (глобальные шансы).
|
||||
|
||||
level: Средний уровень группы (1–20).
|
||||
По умолчанию: 1.
|
||||
|
||||
|
|
@ -119,6 +152,8 @@ class EncounterRequest(TypedDict, total=False):
|
|||
По умолчанию: True.
|
||||
"""
|
||||
environment: str
|
||||
world: str
|
||||
region: str
|
||||
level: int
|
||||
encounter_type: Optional[str]
|
||||
party_surprise_mods: Dict[str, int]
|
||||
|
|
@ -219,23 +254,28 @@ def _roll_count(notation: str) -> int:
|
|||
# Вспомогательные функции — сборка субъектов
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _build_subjects(hook_name: str, enc_type: str, env: str, level: int) -> list[dict]:
|
||||
def _build_subjects(
|
||||
hook_name: str,
|
||||
enc_type: str,
|
||||
env: str,
|
||||
level: int,
|
||||
world: Optional[str] = None,
|
||||
region: Optional[str] = None,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
По схеме хука вызывает get_creature() или get_npc()
|
||||
и возвращает список субъектов с ролями.
|
||||
|
||||
Каждый элемент результата:
|
||||
{
|
||||
"source": "creatures" | "npc",
|
||||
"role": "primary" | "aggressor" | "defender" | "victim" | "bystander",
|
||||
"count": int, # сколько существ/NPC
|
||||
"result": str, # текст из таблицы или описание NPC
|
||||
}
|
||||
"""
|
||||
hook_data = HOOKS.get(enc_type, {}).get(hook_name, {})
|
||||
slots = hook_data.get("subject_sources", [])
|
||||
subjects = []
|
||||
|
||||
# Определяем ключ для таблиц монстров:
|
||||
# если world — план, монстры берутся из planar-таблиц по ключу world,
|
||||
# иначе (прайм-мир) — по биому environment.
|
||||
is_planar = bool(world) and world.lower().strip() in PLANAR_WORLDS
|
||||
creature_env = world.lower().strip() if is_planar else env
|
||||
|
||||
for slot in slots:
|
||||
# Опциональный слот — 50% шанс появления
|
||||
if slot.get("optional") and random.random() < 0.5:
|
||||
|
|
@ -246,17 +286,27 @@ def _build_subjects(hook_name: str, enc_type: str, env: str, level: int) -> list
|
|||
role = slot.get("role", "primary")
|
||||
|
||||
if source == "creatures":
|
||||
result_text = get_creature(env, level)
|
||||
result_text = get_creature(creature_env, level)
|
||||
elif source == "npc":
|
||||
allowed_races = get_npc_races(env)
|
||||
# Приоритет — географический контекст (world/region).
|
||||
# Если он задан, get_npc сам подберёт расы через REGIONAL_OVERWRITES.
|
||||
# Иначе — fallback на список рас по биому.
|
||||
npc_constraint = {
|
||||
"count": count,
|
||||
"level": level,
|
||||
"commoner_chance": 0.85,
|
||||
}
|
||||
if world:
|
||||
npc_constraint["world"] = world
|
||||
if region:
|
||||
npc_constraint["region"] = region
|
||||
else:
|
||||
# TODO: не очевидноЮ тут бы подумал, но, кажется, тут есть смысл пробрасывать только если у этого тула есть такой же параметр, чтобы можно было создать столкновения только с человеческими бандитами
|
||||
npc_constraint["allowed_races"] = get_npc_races(env)
|
||||
|
||||
result_text = get_npc(
|
||||
debug_description="encounter npc",
|
||||
constraints=[{
|
||||
"count": count,
|
||||
"level": level,
|
||||
"commoner_chance": 0.85,
|
||||
"allowed_races": allowed_races,
|
||||
}],
|
||||
constraints=[npc_constraint],
|
||||
)
|
||||
else:
|
||||
result_text = f"[Неизвестный источник: {source}]"
|
||||
|
|
@ -390,18 +440,25 @@ def generate_encounter(
|
|||
env = req.get("environment", "forest").lower().strip()
|
||||
level = int(req.get("level", 1))
|
||||
|
||||
# Географический / планарный контекст
|
||||
world = req.get("world", None)
|
||||
region = req.get("region", None)
|
||||
|
||||
# Считываем явно заданный тип или None
|
||||
req_type = req.get("encounter_type")
|
||||
|
||||
# ── Тип и хук ────────────────────────────────────────────────────────
|
||||
enc_type = _pick_encounter_type(env, req_type)
|
||||
# Для выбора весов типа встречи: если world — план, используем его,
|
||||
# иначе — биом прайм-мира.
|
||||
type_key = world.lower().strip() if (world and world.lower().strip() in PLANAR_WORLDS) else env
|
||||
enc_type = _pick_encounter_type(type_key, req_type)
|
||||
hook_name = _pick_hook(enc_type)
|
||||
hook_meta = HOOKS.get(enc_type, {}).get(hook_name, {})
|
||||
hook_desc = hook_meta.get("description", "")
|
||||
latent = hook_meta.get("latent_threat", False)
|
||||
|
||||
# ── Субъекты ──────────────────────────────────────────────────────────
|
||||
subjects = _build_subjects(hook_name, enc_type, env, level)
|
||||
subjects = _build_subjects(hook_name, enc_type, env, level, world, region)
|
||||
subjects_text = _format_subjects(subjects, latent)
|
||||
|
||||
# ── Surprise ──────────────────────────────────────────────────────────
|
||||
|
|
@ -420,9 +477,16 @@ def generate_encounter(
|
|||
reaction_text = _determine_reaction(req.get("reaction_mods", {}))
|
||||
|
||||
# ── Сборка секции ─────────────────────────────────────────────────────
|
||||
loc_line = ""
|
||||
if world:
|
||||
loc_line += f"- **Мир/План:** {world}\n"
|
||||
if region:
|
||||
loc_line += f"- **Регион:** {region}\n"
|
||||
|
||||
section = (
|
||||
f"### Встреча №{i}\n"
|
||||
f"- **Биом:** {env.capitalize()}\n"
|
||||
f"{loc_line}"
|
||||
f"- **Уровень группы:** {level}\n"
|
||||
f"- **Тип встречи:** {enc_type}\n"
|
||||
f"- **Хук:** {hook_name} — *{hook_desc}*\n\n"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user