# races.py import random from languages import ( Common, Celestial, Elvish, Dwarvish, Draconic, GiantLanguage, GnomishLanguage, GoblinLanguage, HalflingLanguage, OrcLanguage, Infernal, Primordial, Sylvan, Abyssal, GithLanguage, LeoninLanguage, Undercommon, Alzhedo, Chondathan, Damaran, Illuskan, KoryoLanguage, Mulani, Rashemi, Shou, living_languages_except_common, ) # ─── Расы ──────────────────────────────────────────────────────────────────── RACES: dict = { "Aasimar": { "chance": 1.00, "subraces": { "Protector": {"chance": 50.00}, "Scourge": {"chance": 30.00}, "Fallen": {"chance": 20.00}, }, "names": "Human", "languages": [Common, Celestial], }, "Bugbear": { "chance": 0.50, "names": "Goblin", "languages": [Common, GoblinLanguage], }, "Dragonborn": { "chance": 1.00, "subraces": {color: {"chance": 10.00} for color in ["Black","Blue","Brass","Bronze","Copper", "Gold","Green","Red","Silver","White"]}, "names": { "M": ["Арджхан","Баласар","Бхараш","Гхеш","Донаар","Крив","Медраш","Мехен", "Надарр","Панджед","Патрин","Рхогар","Тархун","Торинн","Хескан","Шамаш","Шединн"], "F": ["Акра","Бири","Даар","Джхери","Кава","Коринн","Мисханн","Нала", "Перра","Райанн","Сора","Сурина","Тхава","Уаджит","Фаридэ","Хавилар","Харанн"], }, "languages": [Common, Draconic], }, "Dwarf": { "chance": 10.00, "subraces": { "Gold/Hill": {"chance": 50.00, "languages": [Common, Dwarvish]}, "Shield/Mountain":{"chance": 40.00, "languages": [Common, Dwarvish]}, "Duergar": {"chance": 10.00, "languages": [Common, Dwarvish, Undercommon]}, }, "names": { "M": ["Адрик","Альберих","Баренд","Барундар","Баэрн","Броттор","Бруенор","Вондал", "Вулгар","Вэйт","Гардаин","Даин","Даррак","Делг","Джойин","Дорн","Килдрак", "Моргран","Орсик","Оскар","Рангрим","Рорин","Рюрик","Сторн","Таклинн", "Торадин","Тордек","Торин","Торик","Травок","Траубон","Ульфгар","Фаргрим", "Флинт","Харбек","Хондар","Эберк","Эйнкиль"], "F": ["Артин","Бардрин","Белмара","Вистра","Гуннлода","Гурдис","Дагнал","Джойлин", "Диеза","Дорна","Илде","Катра","Киира","Кристид","Лифтраса","Мардред", "Одхильд","Рисвин","Самбрил","Саннл","Тейс","Торбера","Торгга","Умиль", "Фалкрунн","Финеллен","Хельджа","Хлин","Эльдет","Эмбер"], "S": ["Балдерк","Блейтбайт","Боевой Молот","Гордривер","Горунн","Данкил", "Железный Кулак","Каменный Щит","Крауншилд","Крепкая Наковальня", "Ледяная Борода","Лодерр","Лютгер","Огненная Кузня","Рамнахейм", "Рог","Скуллдарк","Стракелн","Торунн","Унгарт","Холдерхек"], }, "languages": [Common, Dwarvish], }, "Elf": { "chance": 10.00, "subraces": { "Avariel": {"chance": 0.50, "names": "Moon", "languages": [Common, Elvish]}, "Drow": { "chance": 10.00, "names": { "M": ["Алак","Дриззт","Ильмрин","Меринид","Фарон","Риззон","Тебрин","Закнафейн"], "F": ["Акордия","Чалитра","Эклавдра","Джаэлрина","Недилен","Квилуэ","СиНафай","Влондрил"], "S": ["Абафейр","Колоара","Гланнат","Иллистин","Фарн","Сиреар","Вринн","Ксилтин"], }, "languages": [Common, Elvish, Undercommon], }, "Eladrin": {"chance": 2.50, "names": "Moon", "languages": [Common, Elvish]}, "Moon": { "chance": 34.50, "names": { "M": ["Адран","Аравилар","Арамиль","Араннис","Ауст","Аэлар","Бейро","Берриан", "Варис","Галинндан","Ивеллиос","Иммераль","Каррик","Куарион","Лаусиан", "Миндартис","Морн","Ним","Орлпар","Паэлиас","Перен","Респен","Риардон", "Ристель","Ролен","Саэвел","Совелисс","Тамиорн","Таривол","Терен", "Тэген","Фейлар","Хадарай","Химо","Хэйян","Эниалис","Эрдан","Эреван"], "F": ["Адрие","Альтеа","Амра","Анастрианна","Андрасте","Антинуа","Бетринна", "Бирель","Вадания","Валанте","Вестеле","Джаумрит","Джеленетт","Друсилиа", "Имизаэль","Йелениа","Каэлинн","Квамара","Квеленна","Квиласи","Кейлет", "Ксанафия","Лешанна","Лиа","Миали","Мэриэль","Найвара","Сариэль","Силакви", "Талиндра","Теирастра","Тиа","Фелосиаль","Хакатра","Шава","Шанайра","Энна","Ястра"], "S": ["Амакиир (Сверкающий Цветок)","Амалит","Амастасия (Звёздный Цветок)", "Брэген","Галанодель (Лунный Шёпот)","Ивнингфолл","Ильфелкиир (Сверкающий Бутон)", "Калаудра","Ксилосент (Золотой Лепесток)","Лаэлитар","Лиадон (Серебряный Лист)", "Мундаун","Найло (Ночной Бриз)","Сианодель (Лунный Ручей)","Тарнрут", "Холимион (Алмазная Роса)"], }, "languages": [Common, Elvish], }, "Shadar-kai":{"chance": 2.50, "names": "Moon", "languages": [Common, Elvish]}, "Sun": {"chance": 25.00, "names": "Moon", "languages": [Common, Elvish]}, "Wood": {"chance": 25.00, "names": "Moon", "languages": [Common, Elvish]}, }, "languages": [Common, Elvish], }, "Firbolg": { "chance": 0.20, "names": "Elf", "languages": [Common, Elvish, GiantLanguage], }, "Genasi": { "chance": 1.00, "subraces": { "Air": {"chance": 25.00}, "Earth": {"chance": 25.00}, "Fire": {"chance": 25.00}, "Water": {"chance": 25.00}, }, "names": "Human", "languages": [Common, Primordial], }, "Gith": { "chance": 0.20, "names": { "M": ["Al'chaia","Dak'kon","Lae'zel","Vhostym","Yrlakka"], "F": ["Al'chaia","Dak'kon","Lae'zel","Vhostym","Yrlakka"], }, "languages": [Common, GithLanguage], }, "Gnome": { "chance": 2.00, "subraces": { "Forest": {"chance": 50.00}, "Rock": {"chance": 50.00}, }, "names": { "M": ["Алвин","Алстон","Боддинок","Брок","Бургелл","Бюргел","Варрин","Вренн","Гербо", "Гимбл","Глим","Джебеддо","Димбл","Дорган","Зук","Келлен","Колмарр","Намфудл", "Орламм","Оррин","Ронделл","Рундар","Сибо","Синдри","Столиг","Фалринн","Фонкин", "Фрюг","Халбринн","Элдон","Эрки"], "F": ["Бимпноттин","Брина","Вейуокет","Джари","Донелла","Дувамил","Занна","Зелазадда", "Ийисс","Каландра","Карамип","Карлин","Лилли","Лисса","Лорилла","Лупмоттин", "Маднаб","Мери","Нати","Никс","Нисса","Ода","Орла","Ройвин","Тана","Шамил", "Эливик","Элиджобелл","Элла","Эрисс"], "S": ["Берен","Блэкрок","Блимс","Гаррик","Грейторм","Даергел","Мёрниг","Накл", "Нингел","Раулнор","Ривенстоун","Тавартарр","Тимберс","Турен","Уайтхорн", "Уварикк","Фолькор","Шеппен"], }, "languages": [Common, GnomishLanguage], }, "Goblin": { "chance": 1.00, "names": { "M": ["Аругет","Вании","Ванон","Вуударадж","Гудруун","Даавн","Дабрак","Дагии", "Джазаал","Древдуул","Дуулан","Каллаад","Кракуул","Кроотад","Мазаан","Мунта", "Насаар","Ракари","Рексиит","Тарик","Тарууж","Туун","Феник","Четин","Халуун", "Харуук","Аззинакс","Баболакс","Бликсаникс","Вазозав","Вексин","Даззаз", "Зиззикс","Калузакс","Криксизикс","Лизакса","Миззикс","Мизнар","Никсиспикс", "Паксизаз","Равиксиз","Стиксил","Сунникс","Тозинокс","Уксивози","Эстрикс", "Финизикс","Юзба"], "F": ["Ааспар","Агуус","Белалуур","Вали","Вуун","Грааль","Гадуул","Денаал","Джелуум", "Драраар","Дууша","Келаал","Мулаан","Насри","Разу","Ралин","Рексин","Сенен", "Таджин","Тюнер","Хашак","Шедруор","Эхаас","Элуун"], }, "languages": [Common, GoblinLanguage], }, "Goliath": { "chance": 0.50, "names": { "M": ["Аукан","Ваунеа","Вимак","Гае-Ал","Иглат","Иликан","Кеотхи","Куори", "Ло-Каг","Мавейт","Маннео","Налла","Орило","Пааву","Петани","Талай","Тхотам","Утхал"], "F": ["Аукан","Ваунеа","Вимак","Гае-Ал","Иглат","Иликан","Кеотхи","Куори", "Ло-Каг","Мавейт","Маннео","Налла","Орило","Пааву","Петани","Талай","Тхотам","Утхал"], }, "languages": [Common, GiantLanguage], }, "Half-Elf": { "chance": 6.00, "subraces": { "Drow": {"chance": 3.50}, "Eladrin": {"chance": 2.50}, "Moon": {"chance": 56.50}, "Shadar-kai":{"chance": 2.50}, "Sun": {"chance": 10.00}, "Wood": {"chance": 25.00}, }, "names": ["Human", "Elf"], "languages": [Common, Elvish, living_languages_except_common], }, "Half-Orc": { "chance": 3.00, "names": { "M": ["Гел","Денч","Имш","Кет","Краск","Муррен","Ронт","Токк","Фенг","Хенк","Холг","Шамп"], "F": ["Багги","Вола","Волен","Евельда","Кансиф","Мев","Нига","Овак","Оунка","Сута","Шаута","Эмен","Энгонг"], }, "languages": [Common, OrcLanguage], }, "Halfling": { "chance": 3.00, "subraces": { "Ghostwise": {"chance": 10.00}, "Lightfoot": {"chance": 60.00}, "Stout": {"chance": 30.00}, }, "names": { "M": ["Альтон","Андер","Блазанар","Вилимак","Гаррет","Далабрак","Кейд","Коррин", "Коркаури","Лайл","Линдал","Майло","Меррик","Омберт","Осборн","Перрин","Рид", "Роберк","Роско","Тирраури","Уэллби","Финнан","Халандар","Элдон","Эррих"], "F": ["Алонирра","Бри","Вани","Венинда","Верна","Делдира","Джиллиан","Калатра", "Китри","Кора","Кэлли","Лавиния","Лидда","Мелинден","Мерла","Недда","Олпара", "Паэла","Портия","Росинден","Серафина","Тара","Трим","Шаэна","Эндри","Юфемия"], "S": ["Аумбль","Брамблфут","Вверхтормашкин","Высокохолм","Галькоброс","Дардаргон", "Добробочка","Зеленофляг","Кустосбор","Лугодуг","Мерримар","Подветкин", "Репейник","Старнхап","Хардингдейл","Чайнолист"], }, "languages": [Common, HalflingLanguage], }, "Hobgoblin": { "chance": 0.50, "names": "Goblin", "languages": [Common, GoblinLanguage], }, "Human": { "chance": 54.70, "subraces": { "Calishite": { "chance": 20.00, "names": {"M":["Асеир","Бардеид","Зашеир","Кхемед","Мехмен","Судейман","Хасеид"], "F":["Атала","Джасмаль","Зашеида","Мейлиль","Сейдиль","Сейпора","Хама","Яшеира"], "S":["Баша","Джассан","Думеин","Кхалид","Мостана","Пашар","Рейн"]}, "languages": [Common, Alzhedo], }, "Chondathan": { "chance": 20.00, "names": {"M":["Горстаг","Грим","Дарвин","Дорн","Маларк","Морн","Рэндал","Стедд","Хельм","Эвендур"], "F":["Арвин","Джессаиль","Керри","Лурин","Мири","Рован","Тесселе","Шандри","Эсвель"], "S":["Бакмэн","Грэйкасл","Дандрэгон","Толстаг","Эвенвуд","Эмблкроун"]}, "languages": [Common, Chondathan], }, "Damaran": { "chance": 15.00, "names": {"M":["Айвор","Бор","Глэр","Григор","Иган","Козеф","Миваль","Орел","Павел","Сергор","Фодель"], "F":["Алетра","Зора","Кара","Катернин","Мара","Натали","Ольма","Тана"], "S":["Берск","Дотск","Куленов","Марск","Немецк","Стараг","Чернин","Шемов"]}, "languages": [Common, Damaran], }, "Illuskan": { "chance": 15.00, "names": {"M":["Блас","Бран","Гет","Ландер","Лют","Малсер","Стор","Таман","Урт","Фрат","Эндер"], "F":["Амафрея","Бетха","Вестра","Кетра","Мара","Ольга","Силифрей","Цефрея"], "S":["Брайтвуд","Виндривер","Лакмэн","Хелдер","Хорнрейвен","Штормвинд"]}, "languages": [Common, Illuskan], }, "Koryoan": { "chance": 2.50, "names": "Shou", "languages": [Common, KoryoLanguage], }, "Mulan": { "chance": 10.00, "names": {"M":["Аот","Барерис","Кетот","Мумед","Рамас","Со-Кехур","Тхазар-Де","Урхур","ЭхпутКи"], "F":["Аризима","Золис","Мурити","Нефис","Нулара","Сефрис","Тола","Умара","Чатхи"], "S":["Анкхалаб","Анскульд","Натандем","Серпет","Уутракт","Фезим","Хахпет"]}, "languages": [Common, Mulani], }, "Rashemi": { "chance": 7.50, "names": {"M":["Боривик","Владислак","Джандар","Канитар","Мадислак","Ральмевик","Фаургар","Шаумар"], "F":["Имзель","Иммит","Наварра","Таммит","Файварра","Хулмарра","Шеварра","Юльдра"], "S":["Дайрнина","Илтазяра","Мурнитара","Стаянога","Улмокина","Чергоба"]}, "languages": [Common, Rashemi], }, "Shou": { "chance": 2.50, "names": {"M":["Ан","Вэнь","Лонг","Лянь","Менг","Он","Фай","Цзюн","Цзянь","Чен","Чи","Шань","Шуй"], "F":["Бай","Ксяо","Лей","Мей","Тай","Цзя","Чао","Шуй"], "S":["Вань","Као","Кунг","Лао","Линг","Мей","Пинь","Сум","Тань","Хуан","Чиень","Шин"]}, "languages": [Common, Shou], }, "Tethyrian": { "chance": 7.50, "names": "Chondathan", "languages": [Common, Chondathan], }, }, }, "Kenku": { "chance": 0.50, "names": { "M": ["Разбиватель","Лязгун","Разрезатель","Грохотун","Крысиный Скрежет","Свистун","Мышатник","Рычун"], "F": ["Свистун","Мышатник","Рычун"], }, "languages": [Common, Primordial], }, "Kobold": {"chance": 0.50, "names": [], "languages": [Common, Draconic]}, "Leonin": { "chance": 0.10, "names": { "M": ["Апто","Ат","Барагон","Брыг","Горий","Грекс","Земн","Зиор","Орий","Пиксатор","Тей","Эрем"], "F": ["Алета","Атагона","Грагонда","Демна","Доксия","Зиора","Иадма","Койла","Орамна","Сеза","Эката","Эриз"], }, "languages": [Common, LeoninLanguage], }, "Lizardfolk": {"chance": 0.50, "names": [], "languages": [Common, Draconic]}, "Orc": { "chance": 2.00, "names": { "M": ["Абзуг","Бажок","Беск","Готог","Груток","Джахрук","Дурт","Кеск","Лортар","Мобад", "Мугруб","Орруск","Рук","Рхог","Тараг","Тог","Угурт","Урзул","Фанг","Харл","Шамок","Шугог"], "F": ["Багги","Бетарра","Варра","Вола","Волен","Дуваега","Имен","Йескарра","Кансиф","Креске", "Муай","Неске","Ниига","Овак","Орваега","Оунка","Сута","Шаута","Эдарреске","Энгонг"], "S": ["Думник","Хортор","Ламмар","Сормужик","Турнчерпал","Улькруннар","Зоргар"], }, "languages": [Common, OrcLanguage], }, "Satyr": {"chance": 0.10, "names": [], "languages": [Common, Sylvan]}, "Tabaxi": {"chance": 0.50, "names": [], "languages": [Common, living_languages_except_common]}, "Tiefling": { "chance": 1.00, "names": { "M": ["Акменос","Амнон","Баракас","Дамакос","Йадос","Кайрон","Люцис","Мелех","Мордай", "Мортос","Пелайос","Скамос","Терай","Экемон"], "F": ["Акта","Анакис","Брисеис","Дамая","Каллиста","Криелла","Лерисса","Макария", "Немея","Орианна","Риета","Фелая","Эа"], "S": ["Амезис","Барбатос","Дракос","Инфернус","Мефисто","Фирнас","Хекатон","Харгрейв","Эбони"], }, "languages": [Common, Infernal], }, "Triton": { "chance": 0.10, "names": { "M": ["Водос","Делнис","Джимас","Зунис","Керос","Корус","Молос","Налос"], "F": ["Белтин","Вуолин","Влэрин","Дьютин","Отанин","Филорин","Шелрин","Эрин"], }, "languages": [Common, Primordial], }, "Yuan-ti Pureblood": { "chance": 0.10, "names": [], "languages": [Common, Abyssal, Draconic], }, } # ─── Вспомогательные функции ───────────────────────────────────────────────── def _weighted_choice(items: list[tuple[str, float]]) -> str: """items = [(name, chance), ...]. Возвращает выбранное name.""" total = sum(c for _, c in items) r = random.random() * total cumulative = 0.0 for name, chance in items: cumulative += chance if r <= cumulative: return name return items[-1][0] def select_random_race_and_subrace() -> dict: """Возвращает {"race": str, "subrace": str | None}.""" race_items = [(r, d["chance"]) for r, d in RACES.items()] race_name = _weighted_choice(race_items) race_data = RACES[race_name] subrace_name = None if "subraces" in race_data and race_data["subraces"]: sub_items = [(s, d["chance"]) for s, d in race_data["subraces"].items()] subrace_name = _weighted_choice(sub_items) return {"race": race_name, "subrace": subrace_name} def get_name(race_obj: dict, gender: str) -> dict: """Возвращает {"name": str, "race": str, "subrace": str | None}.""" gender_key = gender.upper() race_key = race_obj["race"] subrace_key = race_obj.get("subrace") def resolve_names(r_key: str, s_key: str | None): """Рекурсивно разрешает ссылки на имена, возвращает (nameData, source_race, source_subrace).""" race_data = RACES.get(r_key) if not race_data: return None, None, None # Определяем имена names = None is_race_level_ref = False if s_key and "subraces" in race_data and s_key in race_data["subraces"]: sub_data = race_data["subraces"][s_key] if "names" in sub_data: names = sub_data["names"] else: names = race_data.get("names") is_race_level_ref = True else: if "subraces" in race_data and race_data["subraces"]: # Выбираем случайную подрасу с именами for candidate in random.sample(list(race_data["subraces"].keys()), len(race_data["subraces"])): if "names" in race_data["subraces"][candidate]: names = race_data["subraces"][candidate]["names"] s_key = candidate break if names is None: names = race_data.get("names") is_race_level_ref = True else: names = race_data.get("names") is_race_level_ref = True if names is None: return None, None, None # Строка-ссылка if isinstance(names, str): if is_race_level_ref: return resolve_names(names, None) else: return resolve_names(r_key, names) # Список ссылок (Half-Elf: ["Human", "Elf"]) if isinstance(names, list) and names and isinstance(names[0], str): ref = random.choice(names) return resolve_names(ref, None) # Прямой словарь {"M":[], "F":[], "S":[]} if isinstance(names, dict): name_data: dict[str, list] = {"M": [], "F": [], "S": []} for g in ("M", "F", "S"): if g in names: name_data[g] = list(names[g]) return name_data, r_key, s_key return None, None, None name_data, src_race, src_subrace = resolve_names(race_key, subrace_key) if not name_data: return {"name": "", "race": race_key, "subrace": subrace_key} first_names = name_data.get(gender_key, []) if not first_names: first_names = name_data.get("F" if gender_key == "M" else "M", []) if not first_names: return {"name": "", "race": src_race, "subrace": src_subrace} surname_list = name_data.get("S", []) first_name = random.choice(first_names) surname = random.choice(surname_list) if surname_list else "" return { "name": f"{first_name} {surname}".strip(), "race": src_race, "subrace": src_subrace, } def get_languages(race_obj: dict, full_name_obj: dict) -> list[dict]: """Возвращает список языков для расы/подрасы персонажа.""" race_key = race_obj["race"] subrace_key = race_obj.get("subrace") def get_lang_list(r_key: str, s_key: str | None) -> list: r_data = RACES.get(r_key) if not r_data: return [] if s_key and "subraces" in r_data and s_key in r_data["subraces"]: sub = r_data["subraces"][s_key] if "languages" in sub: return sub["languages"] return r_data.get("languages", []) lang_list = get_lang_list(race_key, subrace_key) languages: list[dict] = [] for item in lang_list: if isinstance(item, list): # Массив языков на выбор — выбираем один случайный, не совпадающий с уже добавленными available = [l for l in item if l not in languages] if available: languages.append(random.choice(available)) else: languages.append(item) # Дедупликация с сохранением порядка seen = [] for lang in languages: if lang not in seen: seen.append(lang) return seen