Pathfinder Crusade Wiki
Changelog
About An Otter Wiki
Help
Toggle dark mode
Login
Home
A - Z
Create page
Page Index
Home
I Создание Персонажа
Архетипы
Archetype Template
Алхимик
Бард
Варвар
Воин
Волшебник
Друид
Дикие Формы
Легат
Монах
0 Пути
1 Кулак
2 Иглоукалывание
3 Хмель
4 Яды
5 Отшельник
Паладин
Клятвы
Разбойник
Священник
Домены
Следопыт
Чародей
Расы
Race Template
Дворф
Кобольд
Лизардфолк
Орк
Полуорк
Полурослик
Полуэльф
Троль
Человек
Эльф
Склонности
Ii Механика
01 Тесты
02 Вес И Разгрузка
03 Движение
04 Тактическая Карта
05 Бой
1 Ход И Инициатива
2 Движение
3 Разное
4 Избегание
5 Атака
6 Стрельба
7 Ближний Бой
1 Базы
2 Приёмы
3 Стойки
4 Хваты
5 Длина Оружия
6 Типы Оружия
8 Безоружный Бой
8 Борьба
06 Раны И Урон
1 Статусы
07 Критические Эффекты
1 Ударные
2 Режущие
3 Взрывные
4_1 Энергетические E, E(Fl)
4_2 Энергетические E(Co)
4_3 Энергетические E(Pos) И E(Neg)
5 Химические
08 Лечение
09 Смерть
10 Командование
11 Страх
12 Орды
13 Крафт
Iii Продвижение
Домены
Навыки
Таланты
01 Общие
02 Скорость
03 Внимательность
04 Избегание
05 Стойкость
06 Медик
07 Рукопашные
08 Разбойник
09 Пугилист
10 Берсерк
11 Стрелок
12 Огнемётчик
13 Метание
14 Оружейник
15 Два Оружия
16 Арсенал
17 Всадник
18 Пилот
19 Лидерство
20 Социальные
21 Миньоны
22 Смелость
23 Мудрец
24 Магия
25 Оккультист
26 Магическая Стойкость
27 Ремесло
28 Искусства
_Talent Template
Трейты
Характеристики
Элитные Архетипы
Elite Archetype Template
Осадный Маг
Iv Арсенал
0 Редкость И Качество
0 Свойства Оружия
1 Рукопашное Оружие
1 Базовое
2 Особое
3 Щиты
4 Модификации
2 Дальнобойное Оружие
1 Базовое
2 Особое
3 Модификации
4 Гранаты И Бомбы
5 Боеприпасы
3 Броня
1 Базовая
2 Модификации
4 Экипировка
1 Разгрузка
2 Мобильность
3 Разное
4 Мистическое
5 Инструмент
2 Наборы Инструментов
3 Стационарные
4 Расходники
5 Мистические
6 Химия
1 Медицина
2 Наркотики
3 Эликсиры
4 Яды
7 Зачарования
1 Универсальные
2 Оружие
3 Дальнобойное Оружие
4 Рукопашное Оружие И Щиты
5 Боеприпасы
6 Броня
7 Поножи
8 Голова
9 Наручи
8 Бионика И Кибернетика
1 Протезы
8 Материалы
9 Товары И Услуги
01 Еда И Напитки
02 Проживание И Услуги
03 Лагерь
04 Навигация И Связь
05 Освещение
06 Верёвки И Инструменты
07 Контейнеры
08 Письмо И Знания
09 Быт И Гигиена
10 Замки И Уловки
11 Развлечения
12 Товары И Сырьё
13 Животные И Транспорт
Техника
V Магия
1 Механика
2 Пути Силы
3 Феномены
4 Прорывы
5 Заклинания
00 Без Дисциплины
1 Колдовство
01 Малые Заклинания
02 Колдовство
03 Тауматургия
04 Высшее Колдовство
05 Чистая Демонология
06 Пагубная Демонология
07 Амальгамы
2 Большие Дисциплины
01 Телекинез
02 Телепатия
03 Прорицание
04 Биомантия
05 Пиромантия
3 Малые Дисциплины
01 Хрономантия
02 Криомантия
03 Техномантия
04 Геомантия
05 Фульминация
06 Умбрамантия
07 Люминен
08 Либрариум
09 Магия Крови
10 Природа
11 Виаторика
12 Некромантия
13 Теургия
4 Дисциплины Аспектов
01 Магия Искушений
02 Магия Увядания
03 Магия Перемен
_Spell Template
To Do
Искусства
Искусства Барда
Искусства Паладина
Искусства Следопыта
Vi Прочее
1 Очки Судьбы
X Об Этом Диске
Божества
Дизайн идеи
Хоумрулы на рассмотрение
Commit
d2fa1a
2026-10-24 03:16:05
iociveteres
: feat(gamedata): клятвы паладина и пути монаха в выгрузке Клятвы и их способности — трейты, способности путей — таланты со склонностью Cul, обеты Отшельника — трейты. split_fields понимает, где кончается блок меток.
.scripts/gamedata/build.py
..
@@ 46,8 46,9 @@
skill_list = character.skills(report)
skill_names = character.skill_groups(skill_list)
-
talent_list = character.talents(report)
-
trait_list = character.traits(report)
+
path_abilities, vows = character.monk_paths(report)
+
talent_list = character.talents(report) + path_abilities
+
trait_list = character.traits(report) + character.oaths(report) + vows
spell_list = spells.spells(report, skill_names)
elites = spells.elite_archetypes(report, skill_names)
for el in elites:
.scripts/gamedata/character.py
..
@@ 15,7 15,13 @@
SKILLS_FILE = "iii продвижение/навыки.md"
CHARACTERISTICS_FILE = "iii продвижение/характеристики.md"
-
TALENT_LABELS = ("Уровень", "Скл.", "Требования", "Специализации", "Действие", "Тип")
+
OATHS_FILE = "i создание персонажа/архетипы/паладин/клятвы.md"
+
MONK_PATHS_GLOB = "i создание персонажа/архетипы/монах/*.md"
+
+
# «Свершение» — у способностей путей монаха: поступок, без которого способность не открыть.
+
TALENT_LABELS = ("Уровень", "Скл.", "Требования", "Специализации", "Действие", "Тип", "Свершение")
+
# Метки, которые идут в начало описания таланта.
+
TALENT_TEXT_LABELS = ("Свершение", "Специализации", "Действие", "Тип")
SKILL_CHAR_RE = re.compile(r"\s*\((WS|BS|S|T|A|I|P|W|F)\)\s*$")
@@ 56,7 62,7 @@
req = fmap.get("Требования")
self.requirements = None if req is None or pf.is_empty(req.value) else wiki.plain_inline(req.value).strip()
-
extra = [f"{f.label}: {wiki.plain_inline(f.value)}" for f in fields if f.label in ("Специализации", "Действие", "Тип")]
+
extra = [f"{f.label}: {wiki.plain_inline(f.value)}" for f in fields if f.label in TALENT_TEXT_LABELS]
self.description = "\n".join(extra + ([text] if text else []))
def record(self):
@@ 157,6 163,76 @@
return out
+
# ─── Клятвы паладина ────────────────────────────────────────────────────────
+
+
+
def oaths(report, doc=None):
+
"""Клятвы `паладин/клятвы.md` как трейты: [(запись, wiki.Entry)].
+
+
Клятва — раздел `## Oath of X` (кредо, способность, Искусство клятвы),
+
её способность — запись `####` в разделе. Обе — трейты: клятву ищут по
+
имени клятвы, способность — по своему. doc — вместо файла (тесты).
+
"""
+
doc = doc or wiki.read_document(wiki.ROOT / OATHS_FILE)
+
out = []
+
for oath in wiki.sections(doc, 2):
+
check_names(oath, report, need_ru=False)
+
abilities = oath.children(4)
+
if not abilities:
+
report.entry("required", oath, "нет способности клятвы (`#### English / Русское`)")
+
lines, traits = [wiki.plain_text(oath.own_body()), ""], []
+
for a in abilities:
+
check_names(a, report)
+
text = wiki.plain_text(a.body())
+
traits.append(({**names(a), "description": f"Способность клятвы {oath.title.text}.\n{text}"}, a))
+
lines.append(f"Способность клятвы: {a.title.text}")
+
art = next((f for n, t in a.body() for f in wiki.line_fields(t, n) if f.label == "Искусство клятвы"), None)
+
if art is not None:
+
lines.append(f"Искусство клятвы: {wiki.plain_inline(art.value).strip()}")
+
out.append(({**names(oath), "description": "\n".join(lines).strip()}, oath))
+
out += traits
+
return out
+
+
+
# ─── Пути монаха ────────────────────────────────────────────────────────────
+
+
+
class PathAbility(Talent):
+
"""Способность пути культивации: покупается как талант; без уровня (черновик) — без записи опыта."""
+
+
def __init__(self, e, path, report):
+
check_names(e, report)
+
fields, rest = wiki.split_fields(e.body(), known=TALENT_LABELS)
+
super().__init__(e, fields, wiki.plain_text(rest), report)
+
self.description = f"Способность пути культивации «{path}».\n{self.description}"
+
+
def advancements(self):
+
return [] if self.level is None else super().advancements()
+
+
+
def monk_paths(report, docs=None):
+
"""Пути `монах/*.md` (страницы с `**Характеристика:**`): (способности как таланты, обеты как трейты).
+
+
Способности — записи `####` прямо под `# Путь`; записи в `## Обеты` (Отшельник) — трейты.
+
docs — вместо файлов (тесты).
+
"""
+
abilities, vows = [], []
+
for doc in docs or map(wiki.read_document, wiki.md_files(MONK_PATHS_GLOB)):
+
top = wiki.sections(doc, 1)
+
if not top:
+
continue
+
fields, _ = wiki.split_fields(top[0].own_body())
+
if not any(f.label == "Характеристика" for f in fields):
+
continue # общие правила путей, не путь
+
name = top[0].title.text
+
for e in wiki.entries(doc, 4):
+
if e.groups.get(2) == "Обеты":
+
vows.append(parse_trait(e, report))
+
elif 2 not in e.groups:
+
abilities.append(PathAbility(e, name, report))
+
return abilities, vows
+
+
# ─── Навыки ─────────────────────────────────────────────────────────────────
.scripts/gamedata/pf.py
..
@@ 5,7 5,8 @@
# Характеристики PF (без Inf, Cor, Fa) в порядке листа.
CHARACTERISTICS = ("WS", "BS", "S", "T", "A", "I", "P", "W", "F")
-
APTITUDES = CHARACTERISTICS + ("Off", "Fin", "Def", "Mag", "Tec", "Knw", "Soc", "FC", "Gen")
+
# Cul — склонность путей культивации монаха (`архетипы/монах/0 пути.md`), в `склонности.md` её нет.
+
APTITUDES = CHARACTERISTICS + ("Off", "Fin", "Def", "Mag", "Tec", "Knw", "Soc", "FC", "Gen", "Cul")
# Уровни продвижения как в BC (`EXPERIENCE_LEVELS_BY_TYPE` листа).
SKILL_LEVELS = ("- -> +0", "+0 -> +10", "+10 -> +20", "+20 -> +30")
.scripts/gamedata/test_wiki.py
..
@@ 75,6 75,12 @@
self.assertEqual([f.label for f in fields], ["Стоимость", "Эффект"])
self.assertEqual(rest, [(4, "**Раны:** 10"), (5, "Хвост")])
+
def test_split_fields_stops_at_unknown_label(self):
+
lines = [(1, "**Уровень:** 1, **Скл.:** W, Cul"), (2, "**Пассивно:** Текст."), (3, "**Активно (1 Ки):** Ещё.")]
+
fields, rest = wiki.split_fields(lines, known=("Уровень", "Скл."))
+
self.assertEqual([f.label for f in fields], ["Уровень", "Скл."])
+
self.assertEqual([n for n, _ in rest], [2, 3])
+
def test_split_fields_stops_at_text(self):
fields, rest = wiki.split_fields([(1, "**Скл.:** A, Gen"), (2, "Текст."), (3, "**Действие:** Полное")])
self.assertEqual([f.label for f in fields], ["Скл."])
@@ 334,6 340,70 @@
self.assertEqual((recs[0]["type"], recs[0]["aptitudes"]), ("talent", "Gen"))
+
class OathsTest(unittest.TestCase):
+
def test_oath_and_its_ability_are_traits(self):
+
r = Report()
+
[(oath, _), (ability, _)] = character.oaths(r, doc("""
+
# Клятвы
+
## Oath of the Crown
+
Справедливость.
+
+
Я поддерживаю закон.
+
+
#### Order on the Battlefield / Порядок на Поле Боя
+
Паладин командует.
+
+
**Искусство клятвы:** [Achilles Heel / Ахиллесова Пята](/V%20Магия/Искусства#achilles-heel-)
+
"""))
+
self.assertEqual(oath, {"name": "Oath of the Crown", "description":
+
"Справедливость.\n\nЯ поддерживаю закон.\n\n"
+
"Способность клятвы: Order on the Battlefield / Порядок на Поле Боя\n"
+
"Искусство клятвы: Achilles Heel / Ахиллесова Пята"})
+
self.assertEqual(ability, {"name": "Order on the Battlefield", "name_ru": "Порядок на Поле Боя", "description":
+
"Способность клятвы Oath of the Crown.\nПаладин командует.\n\n"
+
"Искусство клятвы: Achilles Heel / Ахиллесова Пята"})
+
self.assertEqual(dict(r.items), {})
+
+
+
class MonkPathsTest(unittest.TestCase):
+
def test_abilities_are_talents_and_vows_traits(self):
+
r = Report()
+
rules = doc("""
+
# Пути культивации
+
#### Навык пути
+
Общие правила.
+
""")
+
path = doc("""
+
# Отшельник
+
**Характеристика:** W
+
#### Way of Vows / Путь Обетов
+
**Уровень:** 1, **Скл.:** W, Cul
+
+
**Пассивно:** Монах даёт обеты.
+
#### Geas / Зарок
+
**Уровень:** 3, **Скл.:** W, Cul
+
**Требования:** Way of Vows
+
**Свершение:** отпустить врага.
+
**Активно (2 Ки):** Накладывает обет.
+
#### Needle / Игла
+
**Активно (1 Ки):** Черновик без уровня.
+
## Обеты
+
#### Обет Молчания
+
**Ограничение:** Молчит.
+
""")
+
abilities, vows = character.monk_paths(r, [rules, path])
+
way, geas, needle = abilities
+
self.assertEqual(way.record()["description"], "Способность пути культивации «Отшельник».\nПассивно: Монах даёт обеты.")
+
self.assertEqual(geas.record()["description"],
+
"Способность пути культивации «Отшельник».\nСвершение: отпустить врага.\nАктивно (2 Ки): Накладывает обет.")
+
self.assertEqual(geas.advancements(), [{"type": "talent", "name": "Geas", "name_ru": "Зарок", "level": 3,
+
"aptitudes": "W, Cul", "requirements": "Way of Vows"}])
+
self.assertEqual(needle.advancements(), [])
+
self.assertEqual(vows, [({"name": "Обет Молчания", "description": "Ограничение: Молчит."}, vows[0][1])])
+
self.assertEqual(sorted(m for _, m, _ in r.items["required"]), ["Needle: нет «Скл.»", "Needle: нет «Уровень»"])
+
self.assertEqual([m for _, m, _ in r.items["name"]], ["Обет Молчания: нет английского имени (нужно `English / Русское`)"])
+
self.assertNotIn("label", r.items)
+
class ReportTest(unittest.TestCase):
def test_collapses_groups(self):
r = Report()
.scripts/gamedata/wiki.py
..
@@ 299,13 299,14 @@
return out
-
def split_fields(lines, open_labels=()):
+
def split_fields(lines, open_labels=(), known=None):
"""Блок полей в начале записи и остаток.
lines — пары (номер, текст). Блок — подряд идущие строки с метками в
начале (пустые строки между ними пропускаются). Метка из open_labels
(например «Эффект») заканчивает блок: её значение продолжается текстом
-
ниже, и он попадает в остаток.
+
ниже, и он попадает в остаток. known — метки блока: строка, которая
+
начинается с другой метки («**Пассивно:**»), — уже текст записи.
Возвращает (поля, остаток строк).
"""
@@ 317,7 318,7 @@
k += 1
continue
fs = line_fields(text, n)
-
if not fs:
+
if not fs or (known is not None and fs[0].label not in known):
break
fields.extend(fs)
k += 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9