import random from typing import Dict, List, Optional from typing_extensions import TypedDict # ---------------------------------------------------------------------- # TypedDict для параметров запроса # ---------------------------------------------------------------------- class TrapRequest(TypedDict, total=False): """ Параметры одного запроса на генерацию ловушки. Поля: environment: Тип локации. Допустимы: dungeon, forest, urban, tomb, cave, arctic, swamp, ruins, castle, sewer, laboratory. По умолчанию: dungeon. level: Средний уровень группы (1–20). По умолчанию: 1. trap_type: Тип ловушки по природе. Допустимы: mechanical (механическая), magical (магическая), haunted (призрачная), living (живая). По умолчанию: случайно. complexity: Сложность поведения. Допустимы: simple (простая), complex (сложная). По умолчанию: simple. severity: Серьёзность угрозы. Допустимы: easy (лёгкая), moderate (средняя), dangerous (опасная), deadly (смертельная). По умолчанию: moderate. trigger_type: Как активируется ловушка. Допустимы: pressure_plate, trip_wire, door_open, lock_pick, touch, time, magical_ward, manual, force, enter_area. По умолчанию: случайно. trigger_location: Где находится триггер. Допустимы: floor, wall, ceiling, door, chest, object, corridor. По умолчанию: floor. trigger_range_ft: Расстояние, на котором триггер срабатывает. 0 = касание, положительное число = футы до цели. По умолчанию: 0. effect_type: Основной эффект ловушки. Допустимы: damage, condition, restrain, imprison, teleport, summon, alarm, delay, environment, curse, flooding, illusion. По умолчанию: damage (случайно из damage/condition/restrain). damage_type: Тип урона (если effect_type=damage). Допустимы: poison, fire, cold, acid, lightning, bludgeoning, piercing, slashing, psychic, necrotic, radiant, thunder. По умолчанию: выбирается случайно. area_shape: Форма области поражения. Допустимы: single_target, cone, cube, line, sphere, room, pit, net. По умолчанию: single_target (случайно cone/cube). duration: Продолжительность эффекта. Допустимы: instant, persistent, repeating, until_escape. По умолчанию: instant. detection_skill: Навык для обнаружения ловушки. Допустимы: perception, investigation, arcana, none. По умолчанию: perception. disarm_skill: Навык/способ обезвреживания. Допустимы: thieves_tools, athletics, arcana, religion, simple_action, none. По умолчанию: thieves_tools. reset: Перезарядка ловушки. Допустимы: once (одноразовая), manual (ручная), automatic (автоматическая). По умолчанию: once. avoidable_by: Особые условия избегания. Допустимы: password, specific_item, specific_creature_type. По умолчанию: []. trap_race: Раса/культура создателей ловушки (влияет на визуальные/звуковые подсказки). Допустимы: dwarven, elven, humanoid, goblinoid, giant, draconic, elemental, undead, fey, lizardfolk, drow, aberration, thief_guild. Если не указана, выбирается случайно на основе environment. summon_type: Тип существа, которое призывается (если effect_type=summon). Допустимы: aberration, giant, humanoid, dragon, beast, fiend, construct, monstrosity, celestial, undead, plant, ooze, fey, elemental. Если не указан, выбирается случайно на основе environment. disarm_fail_threshold: При попытке обезвредить, если результат проверки меньше Сл на это значение или более, ловушка срабатывает. По умолчанию 0. Для deadly рекомендуется 5. flooding_medium: Тип жидкости при затоплении (если effect_type=flooding). Допустимы: water, acid, lava. По умолчанию: water. illusion_type: Тип иллюзии (если effect_type=illusion). Допустимы: visual, auditory, tactile. По умолчанию: visual. illusion_save: Спасбросок против иллюзии. Допустимы: wisdom, intelligence. По умолчанию: wisdom. """ environment: str level: int trap_type: str complexity: str severity: str trigger_type: str trigger_location: str trigger_range_ft: int # 0 = касание, иначе расстояние в футах effect_type: str damage_type: str area_shape: str duration: str detection_skill: str disarm_skill: str reset: str avoidable_by: List[str] trap_race: str summon_type: str disarm_fail_threshold: int flooding_medium: str illusion_type: str illusion_save: str DAMAGE_SAVE_MAP = { "poison": "Телосложение", "fire": "Ловкость", "cold": "Телосложение", "acid": "Ловкость", "lightning": "Ловкость", "bludgeoning": "Ловкость", "piercing": "Ловкость", "slashing": "Ловкость", "psychic": "Интеллект", "necrotic": "Мудрость", "radiant": "Мудрость", "thunder": "Телосложение" } def _get_save_ability(damage_type: str) -> str: return DAMAGE_SAVE_MAP.get(damage_type, "Ловкость") AREA_SIZES = { "cone": 15, # 15-футовый конус "cube": 10, # 10-футовый куб "line": 30, # 30-футовая линия "sphere": 20, # 20-футовый радиус "room": 30, # 30x30 комната (или всё помещение) "pit": 10, # 10 футов глубины "net": 10, # 10x10 футов сеть "single_target": 0 # нет размера } # ---------------------------------------------------------------------- # Словари соответствий (environment -> возможные расы ловушек) # ---------------------------------------------------------------------- TRAP_RACES_BY_ENVIRONMENT = { "arctic": ["dwarven", "giant", "draconic"], "coastal": ["elven", "dwarven", "humanoid"], "desert": ["dwarven", "draconic", "elemental"], "forest": ["elven", "goblinoid", "fey"], "grassland": ["humanoid", "goblinoid"], "hill": ["dwarven", "giant"], "mountain": ["dwarven", "draconic", "giant"], "swamp": ["undead", "lizardfolk", "goblinoid"], "underdark": ["drow", "dwarven", "aberration"], "underwater": ["elven", "dwarven", "draconic"], "urban": ["humanoid", "dwarven", "thief_guild"], "plane_of_air": ["elemental", "draconic", "humanoid"], "plane_of_earth": ["dwarven", "elemental", "giant"], "plane_of_fire": ["draconic", "elemental", "humanoid"], "plane_of_water": ["elven", "elemental", "lizardfolk"], } # Визуальные и звуковые подсказки для каждой расы TRAP_RACE_CLUES = { "dwarven": { "visual": ["gears", "rune_carvings", "stone_brick_pattern", "soot", "oil_stains"], "audible": ["click", "clank", "grinding_stone"] }, "elven": { "visual": ["delicate_lines", "leaves", "moon_motif", "faint_glow"], "audible": ["soft_chime", "whisper"] }, "humanoid": { "visual": ["scorch_marks", "loose_flagstone", "rope_fibers"], "audible": ["squeak", "creak"] }, "goblinoid": { "visual": ["wooden_spikes", "bones", "crude_writing"], "audible": ["jingle_bell", "twang"] }, "giant": { "visual": ["massive_dents", "broken_columns", "scratch_marks_high"], "audible": ["heavy_thud", "groaning_metal"] }, "draconic": { "visual": ["scaly_pattern", "claw_marks", "bas-relief_dragon"], "audible": ["hiss", "roar_echo"] }, "elemental": { "visual": ["glowing_veins", "crackling_energy", "charred_floor"], "audible": ["hum", "crackle", "whoosh"] }, "undead": { "visual": ["cobwebs", "dust", "hidden_bones", "decayed_fabric"], "audible": ["moan", "skittering"] }, "fey": { "visual": ["glowing_mushrooms", "twisted_vines", "sparkling_dust"], "audible": ["giggle", "chime"] }, "lizardfolk": { "visual": ["scales", "eggshells", "claw_scratches"], "audible": ["hiss", "scraping"] }, "drow": { "visual": ["spider_motif", "web_strands", "poison_droplets"], "audible": ["skittering", "faint_scream"] }, "aberration": { "visual": ["organic_growth", "wrong_angles", "eyeball_patterns"], "audible": ["whisper_gibberish", "squelch"] }, "thief_guild": { "visual": ["tool_marks", "hidden_catch", "false_bottom"], "audible": ["spring_coil", "tiny_click"] } } # environment -> возможные типы призываемых существ SUMMON_TYPE_BY_ENVIRONMENT = { "arctic": ["giant", "beast", "elemental"], "coastal": ["beast", "elemental", "dragon"], "desert": ["elemental", "undead", "beast"], "forest": ["beast", "fey", "plant"], "grassland": ["beast", "humanoid", "giant"], "hill": ["giant", "beast", "humanoid"], "mountain": ["giant", "dragon", "elemental"], "swamp": ["undead", "plant", "beast"], "underdark": ["aberration", "humanoid", "monstrosity"], "underwater": ["beast", "elemental", "dragon"], "urban": ["humanoid", "construct", "undead"], "plane_of_air": ["elemental", "dragon", "beast", "giant"], "plane_of_earth": ["elemental", "humanoid", "monstrosity", "construct"], "plane_of_fire": ["elemental", "fiend", "dragon"], "plane_of_water": ["elemental", "dragon", "beast", "humanoid"], } # ---------------------------------------------------------------------- # Таблицы CR → характеристики (из таблицы монстров) # ---------------------------------------------------------------------- MONSTER_STATS = { 0.125: {"pb": 2, "dc": 13, "attack": 3, "hp": 21, "dmg_low": 2, "dmg_high": 3}, 0.25: {"pb": 2, "dc": 13, "attack": 3, "hp": 43, "dmg_low": 4, "dmg_high": 5}, 0.5: {"pb": 2, "dc": 13, "attack": 3, "hp": 60, "dmg_low": 6, "dmg_high": 8}, 1: {"pb": 2, "dc": 13, "attack": 3, "hp": 78, "dmg_low": 9, "dmg_high": 14}, 2: {"pb": 2, "dc": 13, "attack": 3, "hp": 93, "dmg_low": 15, "dmg_high": 20}, 3: {"pb": 2, "dc": 13, "attack": 4, "hp": 108, "dmg_low": 21, "dmg_high": 26}, 4: {"pb": 2, "dc": 14, "attack": 5, "hp": 123, "dmg_low": 27, "dmg_high": 32}, 5: {"pb": 3, "dc": 15, "attack": 6, "hp": 138, "dmg_low": 33, "dmg_high": 38}, 6: {"pb": 3, "dc": 15, "attack": 6, "hp": 153, "dmg_low": 39, "dmg_high": 44}, 7: {"pb": 3, "dc": 15, "attack": 6, "hp": 168, "dmg_low": 45, "dmg_high": 50}, 8: {"pb": 3, "dc": 16, "attack": 7, "hp": 183, "dmg_low": 51, "dmg_high": 56}, 9: {"pb": 4, "dc": 16, "attack": 7, "hp": 198, "dmg_low": 57, "dmg_high": 62}, 10: {"pb": 4, "dc": 16, "attack": 7, "hp": 213, "dmg_low": 63, "dmg_high": 68}, 11: {"pb": 4, "dc": 17, "attack": 8, "hp": 228, "dmg_low": 69, "dmg_high": 74}, 12: {"pb": 4, "dc": 17, "attack": 8, "hp": 243, "dmg_low": 75, "dmg_high": 80}, 13: {"pb": 5, "dc": 18, "attack": 8, "hp": 258, "dmg_low": 81, "dmg_high": 86}, 14: {"pb": 5, "dc": 18, "attack": 8, "hp": 273, "dmg_low": 87, "dmg_high": 92}, 15: {"pb": 5, "dc": 18, "attack": 8, "hp": 288, "dmg_low": 93, "dmg_high": 98}, 16: {"pb": 5, "dc": 18, "attack": 9, "hp": 303, "dmg_low": 99, "dmg_high": 104}, 17: {"pb": 6, "dc": 19, "attack": 10, "hp": 318, "dmg_low": 105, "dmg_high": 110}, 18: {"pb": 6, "dc": 19, "attack": 10, "hp": 333, "dmg_low": 111, "dmg_high": 116}, 19: {"pb": 6, "dc": 19, "attack": 10, "hp": 348, "dmg_low": 117, "dmg_high": 122}, 20: {"pb": 6, "dc": 19, "attack": 10, "hp": 363, "dmg_low": 123, "dmg_high": 140}, } VALID_CRS = sorted(MONSTER_STATS.keys()) # ---------------------------------------------------------------------- # Вспомогательные функции # ---------------------------------------------------------------------- def _get_cr(level: int, severity: str) -> float: mult = {"easy": 0.25, "moderate": 0.5, "dangerous": 0.75, "deadly": 1.0}[severity] cr_raw = level * mult if cr_raw < 0.125: return 0.125 if cr_raw < 1.0: if cr_raw < 0.1875: return 0.125 if cr_raw < 0.375: return 0.25 else: return 0.5 cr_int = int(cr_raw) if cr_int not in MONSTER_STATS: available = [c for c in VALID_CRS if c <= cr_int] return max(available) if available else 1 return cr_int def _get_monster_stats(cr: float) -> Dict: return MONSTER_STATS.get(cr, MONSTER_STATS[20]) def _get_damage(level: int, severity: str, area: bool = False) -> str: cr = _get_cr(level, severity) stats = _get_monster_stats(cr) avg_damage = (stats["dmg_low"] + stats["dmg_high"]) // 2 if avg_damage < 1: avg_damage = 1 num_dice = max(1, round(avg_damage / 5.5)) if area: return f"{num_dice}d6" else: return f"{num_dice}d10" def _derive_escape_requirements(area_shape: str, effect_type: str) -> List[str]: if area_shape == "pit": return ["climb_speed", "climbing_gear", "spider_climb"] if effect_type == "restrain" and area_shape == "net": return ["strength_check", "slashing_damage"] if effect_type == "imprison": return ["strength_check", "lockpick", "magic"] return [] def _get_trap_race(environment: str, user_trap_race: Optional[str]) -> str: if user_trap_race and user_trap_race in TRAP_RACE_CLUES: return user_trap_race possible = TRAP_RACES_BY_ENVIRONMENT.get(environment, ["humanoid"]) return random.choice(possible) def _get_visual_audible_clues(trap_race: str): clues = TRAP_RACE_CLUES.get(trap_race, TRAP_RACE_CLUES["humanoid"]) return clues["visual"], clues["audible"] def _get_summon_type(environment: str, user_summon_type: Optional[str]) -> str: if user_summon_type: return user_summon_type possible = SUMMON_TYPE_BY_ENVIRONMENT.get(environment, ["humanoid"]) return random.choice(possible) # ---------------------------------------------------------------------- # Основная функция генерации # ---------------------------------------------------------------------- def generate_trap(debug_description: str, requests: List[TrapRequest]) -> str: if not requests: requests = [{}] results = [] for idx, req in enumerate(requests, 1): # --- Параметры по умолчанию --- env = req.get("environment", "dungeon").lower() level = max(1, min(20, int(req.get("level", 1)))) trap_type = req.get("trap_type") if trap_type not in ["mechanical", "magical", "haunted", "living"]: trap_type = random.choice( ["mechanical", "magical", "haunted", "living"]) complexity = req.get("complexity", "simple") if complexity not in ["simple", "complex"]: complexity = "simple" severity = req.get("severity", "moderate") if severity not in ["easy", "moderate", "dangerous", "deadly"]: severity = "moderate" trigger_type = req.get("trigger_type") valid_triggers = [ "pressure_plate", "trip_wire", "door_open", "lock_pick", "touch", "time", "magical_ward", "manual", "force", "enter_area", "looked_at" ] if not trigger_type or trigger_type not in valid_triggers: trigger_type = random.choice(valid_triggers) trigger_location = req.get("trigger_location") valid_locs = [ "floor", "wall", "ceiling", "door", "chest", "object", "corridor" ] if not trigger_location or trigger_location not in valid_locs: trigger_location = random.choice(valid_locs) effect_type = req.get("effect_type") valid_effects = [ "damage", "condition", "restrain", "imprison", "teleport", "summon", "alarm", "delay", "environment", "curse", "flooding", "illusion" ] if not effect_type or effect_type not in valid_effects: effect_type = random.choice(["damage", "condition", "restrain"]) # TODO: Можно поддержать ещё secondary effect для complex ловушек, с шансом, например, 10 или 20%, в виде: # secondary_effect_type (те же значения, что effect_type: damage, condition, restrain, imprison, teleport, summon, alarm, delay, environment, curse, flooding, illusion) # secondary_damage_type (если secondary_effect_type = damage) # secondary_severity_factor (float, по умолчанию 0.5 — множитель к урону/Сл) damage_type = req.get("damage_type") valid_damage = [ "poison", "fire", "cold", "acid", "lightning", "bludgeoning", "piercing", "slashing", "psychic", "necrotic", "radiant", "thunder" ] if effect_type == "damage" and (not damage_type or damage_type not in valid_damage): damage_type = random.choice(valid_damage) area_shape = req.get("area_shape") valid_shapes = [ "single_target", "cone", "cube", "line", "sphere", "room", "pit", "net" ] if not area_shape or area_shape not in valid_shapes: area_shape = random.choice(["single_target", "cone", "cube"]) duration = req.get("duration") valid_durations = [ "instant", "persistent", "repeating", "until_escape" ] if not duration or duration not in valid_durations: duration = "instant" detection_skill = req.get("detection_skill") valid_detection = ["perception", "investigation", "arcana", "none"] if not detection_skill or detection_skill not in valid_detection: detection_skill = "perception" disarm_skill = req.get("disarm_skill") valid_disarm = [ "thieves_tools", "athletics", "arcana", "religion", "simple_action", "none" ] if not disarm_skill or disarm_skill not in valid_disarm: disarm_skill = "thieves_tools" reset = req.get("reset") valid_reset = ["once", "manual", "automatic"] if not reset or reset not in valid_reset: reset = "once" avoidable_by = req.get("avoidable_by", []) if not isinstance(avoidable_by, list): avoidable_by = [] valid_avoid = ["password", "specific_item", "specific_creature_type"] avoidable_by = [a for a in avoidable_by if a in valid_avoid] # Новые поля trap_race = _get_trap_race(env, req.get("trap_race")) summon_type = _get_summon_type(env, req.get("summon_type")) disarm_fail_threshold = req.get("disarm_fail_threshold", 5 if severity == "deadly" else 0) flooding_medium = req.get( "flooding_medium", "water") if effect_type == "flooding" else None illusion_type = req.get( "illusion_type", "visual") if effect_type == "illusion" else None illusion_save = req.get( "illusion_save", "wisdom") if effect_type == "illusion" else None # Рассчёт характеристик cr = _get_cr(level, severity) stats = _get_monster_stats(cr) save_dc = stats["dc"] attack_bonus = stats["attack"] hp = stats["hp"] damage_single = _get_damage(level, severity, area=False) damage_area = _get_damage( level, severity, area=True) if area_shape != "single_target" else damage_single init_bonus = stats["pb"] if complexity == "complex" else None escape_req = _derive_escape_requirements(area_shape, effect_type) visual_clues, audible_clues = _get_visual_audible_clues(trap_race) # --- Генерация Markdown --- lines = [] lines.append(f"### 🪤 Ловушка №{idx}") lines.append(f"- **Локация:** {env.capitalize()}") lines.append( f"- **Уровень группы:** {level} | **Угроза:** {severity.capitalize()} | **Сложность:** {complexity.capitalize()}" ) lines.append( f"- **Тип ловушки:** {trap_type.capitalize()} | **Раса создателей:** {trap_race.capitalize()}" ) # Триггер с указанием дальности trigger_range = req.get("trigger_range_ft", 0) if trigger_range > 0: trigger_text = f"{trigger_type.replace('_', ' ').capitalize()} на расстоянии {trigger_range} футов ({trigger_location})" else: trigger_text = f"{trigger_type.replace('_', ' ').capitalize()} ({trigger_location})" lines.append(f"- **Триггер:** {trigger_text}") lines.append( f"- **Эффект:** {effect_type.capitalize()} | **Область:** {area_shape.replace('_', ' ').capitalize()}" ) # Размер области area_size = AREA_SIZES.get(area_shape, 0) if area_size > 0 and area_shape != "single_target": lines.append(f"- **Размер области:** {area_size} футов") lines.append(f"- **Длительность:** {duration.capitalize()}") lines.append("") lines.append("**Описание:**") lines.append( f"*{visual_clues[0].replace('_', ' ').capitalize()} и {audible_clues[0]} выдают скрытую угрозу.*" ) lines.append("") lines.append( f"🔍 **Обнаружение:** Успешная проверка Мудрости ({detection_skill.capitalize()}) Сл {save_dc} позволяет заметить признаки ловушки." ) disarm_text = "" if disarm_skill == "thieves_tools": disarm_text = f"Успешная проверка Ловкости (Воровские инструменты) Сл {save_dc} обезвреживает ловушку. При провале на {disarm_fail_threshold} или более ловушка срабатывает." elif disarm_skill == "athletics": disarm_text = f"Успешная проверка Силы (Атлетика) Сл {save_dc} позволяет сломать или заблокировать механизм." elif disarm_skill == "arcana": disarm_text = f"Успешная проверка Интеллекта (Магия) Сл {save_dc} или заклинание `dispel magic` (Сл {save_dc}) отключает магическую ловушку." elif disarm_skill == "religion": disarm_text = f"Успешная проверка Интеллекта (Религия) Сл {save_dc} позволяет стереть священную руну." elif disarm_skill == "simple_action": disarm_text = "Достаточно простого действия: перешагнуть, забить клин или перерезать верёвку." else: disarm_text = "Ловушку невозможно обезвредить; её можно только избежать." lines.append(f"🛠️ **Обезвреживание:** {disarm_text}") reset_text = { "once": "Ловушка одноразовая.", "manual": "Требуется ручная перезарядка (10 минут).", "automatic": "Перезаряжается автоматически через 1d10 минут." }.get(reset, "") lines.append(f"⏳ **Перезарядка:** {reset_text}") if avoidable_by: lines.append( f"✨ **Избегание:** Можно полностью избежать, если {', '.join(avoidable_by)}." ) if escape_req: lines.append(f"🏃 **Побег:** Требуется: {', '.join(escape_req)}.") if complexity == "complex": lines.append( f"⚙️ **Сложная ловушка:** Действует на инициативе (бонус {init_bonus:+d}). В свой ход повторяет эффект. Требуется 3 успешные проверки для отключения." ) if effect_type == "damage": save_ability = _get_save_ability(damage_type) if area_shape == "single_target": lines.append( f"💥 **Урон:** {damage_single} ({damage_type.capitalize()}) + спасбросок {save_ability} Сл {save_dc} для половины." ) else: lines.append( f"💥 **Урон по области:** {damage_area} ({damage_type.capitalize()}) + спасбросок {save_ability} Сл {save_dc} для половины." ) if damage_type in ["fire", "acid"]: lines.append("🔥 Легковоспламеняющиеся предметы загораются.") elif effect_type == "condition": cond = random.choice([ "отравлен", "ослеплён", "оглушён", "ошеломлён", "напуган", "парализован" ]) lines.append( f"😵 **Состояние:** {cond} на 1 минуту (спасбросок Мудрости Сл {save_dc})." ) elif effect_type == "restrain": lines.append( f"⛓️ **Опутывание:** Цель должна преуспеть в спасброске Ловкости Сл {save_dc}, иначе становится опутанной. Для освобождения требуется проверка Силы (Сл 15)." ) elif effect_type == "imprison": lines.append( f"🔒 **Заточение:** Цель должна преуспеть в спасброске Ловкости Сл {save_dc}, иначе оказывается запертой. Для освобождения требуется проверка Силы (Сл 15) или магия." ) elif effect_type == "flooding": lines.append( f"🌊 **Затопление:** Каждый раунд уровень {flooding_medium} поднимается на 1 фут. Существа в зоне должны делать спасброски Силы Сл {save_dc} или быть сметёнными." ) elif effect_type == "illusion": lines.append( f"👁️ **Иллюзия ({illusion_type}):** Спасбросок {illusion_save.upper()} Сл {save_dc} иначе ложное восприятие на 1 минуту." ) elif effect_type == "summon": lines.append( f"📯 **Призыв:** Появляется существо типа {summon_type} (CR {cr})." ) elif effect_type == "teleport": lines.append( f"🌀 **Телепортация:** Цель переносится в другое место (спасбросок Мудрости Сл {save_dc} избегает)." ) elif effect_type == "alarm": lines.append(f"🔔 **Тревога:** Громкий звук оповещает обитателей.") elif effect_type == "delay": lines.append( f"🚧 **Задержка:** Проход блокируется (требуется проверка Силы Сл {save_dc} чтобы пробиться)." ) elif effect_type == "environment": lines.append( f"🌪️ **Изменение среды:** Пол становится скользким, появляется труднопроходимая местность и т.п." ) elif effect_type == "curse": lines.append( f"☠️ **Проклятие:** Цель должна преуспеть в спасброске Мудрости Сл {save_dc} иначе получает проклятие (снимается только `remove curse`)." ) lines.append("") lines.append( f"*💬 Реплика Мастера: Эта ловушка {'смертельно опасна' if severity=='deadly' else 'серьёзна' if severity=='dangerous' else 'доставит неприятности' if severity=='moderate' else 'скорее досадная помеха'} для группы {level} уровня.*" ) results.append("\n".join(lines)) return "\n\n---\n\n".join(results) # ---------------------------------------------------------------------- # Пример использования # ---------------------------------------------------------------------- if __name__ == "__main__": test_req = [ {"environment": "dungeon", "level": 5, "severity": "dangerous", "effect_type": "damage", "area_shape": "cone"}, {"environment": "swamp", "level": 12, "severity": "deadly", "complexity": "complex", "trap_type": "haunted", "effect_type": "illusion"} ] print(generate_trap("Тест", test_req))