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
|
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
|
# TypedDict
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -72,12 +83,34 @@ class EncounterRequest(TypedDict, total=False):
|
||||||
environment: Биом.
|
environment: Биом.
|
||||||
Допустимы: arctic, coastal, desert, forest,
|
Допустимы: arctic, coastal, desert, forest,
|
||||||
grassland, hill, mountain, swamp, underdark,
|
grassland, hill, mountain, swamp, underdark,
|
||||||
underwater, urban,
|
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.
|
|
||||||
По умолчанию: forest.
|
По умолчанию: 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).
|
level: Средний уровень группы (1–20).
|
||||||
По умолчанию: 1.
|
По умолчанию: 1.
|
||||||
|
|
||||||
|
|
@ -119,6 +152,8 @@ class EncounterRequest(TypedDict, total=False):
|
||||||
По умолчанию: True.
|
По умолчанию: True.
|
||||||
"""
|
"""
|
||||||
environment: str
|
environment: str
|
||||||
|
world: str
|
||||||
|
region: str
|
||||||
level: int
|
level: int
|
||||||
encounter_type: Optional[str]
|
encounter_type: Optional[str]
|
||||||
party_surprise_mods: Dict[str, int]
|
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()
|
По схеме хука вызывает 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, {})
|
hook_data = HOOKS.get(enc_type, {}).get(hook_name, {})
|
||||||
slots = hook_data.get("subject_sources", [])
|
slots = hook_data.get("subject_sources", [])
|
||||||
subjects = []
|
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:
|
for slot in slots:
|
||||||
# Опциональный слот — 50% шанс появления
|
# Опциональный слот — 50% шанс появления
|
||||||
if slot.get("optional") and random.random() < 0.5:
|
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")
|
role = slot.get("role", "primary")
|
||||||
|
|
||||||
if source == "creatures":
|
if source == "creatures":
|
||||||
result_text = get_creature(env, level)
|
result_text = get_creature(creature_env, level)
|
||||||
elif source == "npc":
|
elif source == "npc":
|
||||||
allowed_races = get_npc_races(env)
|
# Приоритет — географический контекст (world/region).
|
||||||
result_text = get_npc(
|
# Если он задан, get_npc сам подберёт расы через REGIONAL_OVERWRITES.
|
||||||
debug_description="encounter npc",
|
# Иначе — fallback на список рас по биому.
|
||||||
constraints=[{
|
npc_constraint = {
|
||||||
"count": count,
|
"count": count,
|
||||||
"level": level,
|
"level": level,
|
||||||
"commoner_chance": 0.85,
|
"commoner_chance": 0.85,
|
||||||
"allowed_races": allowed_races,
|
}
|
||||||
}],
|
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=[npc_constraint],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result_text = f"[Неизвестный источник: {source}]"
|
result_text = f"[Неизвестный источник: {source}]"
|
||||||
|
|
@ -390,18 +440,25 @@ def generate_encounter(
|
||||||
env = req.get("environment", "forest").lower().strip()
|
env = req.get("environment", "forest").lower().strip()
|
||||||
level = int(req.get("level", 1))
|
level = int(req.get("level", 1))
|
||||||
|
|
||||||
|
# Географический / планарный контекст
|
||||||
|
world = req.get("world", None)
|
||||||
|
region = req.get("region", None)
|
||||||
|
|
||||||
# Считываем явно заданный тип или None
|
# Считываем явно заданный тип или None
|
||||||
req_type = req.get("encounter_type")
|
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_name = _pick_hook(enc_type)
|
||||||
hook_meta = HOOKS.get(enc_type, {}).get(hook_name, {})
|
hook_meta = HOOKS.get(enc_type, {}).get(hook_name, {})
|
||||||
hook_desc = hook_meta.get("description", "")
|
hook_desc = hook_meta.get("description", "")
|
||||||
latent = hook_meta.get("latent_threat", False)
|
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)
|
subjects_text = _format_subjects(subjects, latent)
|
||||||
|
|
||||||
# ── Surprise ──────────────────────────────────────────────────────────
|
# ── Surprise ──────────────────────────────────────────────────────────
|
||||||
|
|
@ -420,9 +477,16 @@ def generate_encounter(
|
||||||
reaction_text = _determine_reaction(req.get("reaction_mods", {}))
|
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 = (
|
section = (
|
||||||
f"### Встреча №{i}\n"
|
f"### Встреча №{i}\n"
|
||||||
f"- **Биом:** {env.capitalize()}\n"
|
f"- **Биом:** {env.capitalize()}\n"
|
||||||
|
f"{loc_line}"
|
||||||
f"- **Уровень группы:** {level}\n"
|
f"- **Уровень группы:** {level}\n"
|
||||||
f"- **Тип встречи:** {enc_type}\n"
|
f"- **Тип встречи:** {enc_type}\n"
|
||||||
f"- **Хук:** {hook_name} — *{hook_desc}*\n\n"
|
f"- **Хук:** {hook_name} — *{hook_desc}*\n\n"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user