Commit 5b46dd

2026-10-30 22:47:01 iociveteres: Выгрузка материалов по столбцу «База» Таблица материалов теперь называет базу материала вместо типа, и выгрузка падала на отсутствующем столбце «Тип». entryType берётся из раздела `##` записи (Металл, Кожа, Ткань…), как и раньше показывалось в автодополнении, а «База» выгружается в отдельное поле base и сверяется с меткой в записи. Лист не знает, из чего обычно делают предмет, поэтому для материалов с базой Дерево выгружается ещё и вариант «вместо стали»: цена ×0.1, вес ×0.5, Pen −1, «−3 R и P Dmg» в кратком описании.
.scripts/gamedata/materials.py ..
@@ 1,7 1,8 @@
"""Материалы, камни инкрустации и зачарования предметов (Pathfinder Crusade).
materials.json — материалы из таблицы и записей `8 материалы.md` (entryType —
-колонка «Тип») и камни из таблицы «Инкрустация» того же файла (entryType
+раздел `##` записи, base — колонка «База»), варианты деревянных материалов
+«вместо стали» и камни из таблицы «Инкрустация» того же файла (entryType
«Инкрустация»); enchantments.json — зачарования: таблица `7 зачарования.md`
(категория → entryType, «Кратко» → short) и записи `7 зачарования/*.md`
(уровни). Формат записей — `_prd/item_materials/plan.md` чарника.
@@ 26,7 27,17 @@
GEMS_EN = {"Кварц": "Quartz", "Оникс": "Onyx", "Рубин": "Ruby", "Изумруд": "Emerald", "Алмаз": "Diamond"}
MATERIAL_STATS = ("Редкость", "Цена", "Банк", "Вес", "Прочность", "Ёмкость", "AP", "Pen")
-MATERIAL_TEXTS = ("Применение", "Особое", "Описание")
+MATERIAL_TEXTS = ("Применение", "База", "Особое", "Описание")
+# Раздел `##` → entryType; остальные разделы — как есть.
+SECTION_TYPES = {"Металлы": "Металл"}
+
+WOOD_BASE = "Дерево"
+# Дерево вместо стали («## Дерево»): отдельная запись автодополнения, у листа нет
+# базы предмета. Множители — к множителям материала, Pen — к его Pen.
+WOOD_FOR_STEEL = {"priceFactor": 0.1, "weightFactor": 0.5, "pen": -1}
+WOOD_FOR_STEEL_SHORT = "−3 R и P Dmg"
+WOOD_FOR_STEEL_SPECIAL = ("Вместо стали: цена ×0.1, вес ×0.5 от стальной версии, профили с R и P Dmg "
+ "наносят на 3 Dmg меньше, Pen оружия −1.")
# Поле записи → колонка таблицы и ключ JSON.
MATERIAL_FIELDS = {
"Редкость": ("R", "rarity"),
@@ 148,7 159,14 @@
for lab in extra:
report.entry("label", e, f"неизвестная метка «{lab}»")
- rec = {**names(e), "entryType": cell(row["Тип"]), "use": fields.get("Применение") or cell(row["Применение"])}
+ section = e.groups.get(2, "")
+ if not section:
+ report.entry("format", e, "материал вне раздела `##`: нет entryType")
+ base = cell(row.get("База", ""))
+ if fields.get("База", base) != base:
+ report.entry("value", e, f"База: в записи {fields['База']}, в таблице {base}")
+ rec = {**names(e), "entryType": SECTION_TYPES.get(section, section), "base": base,
+ "use": fields.get("Применение") or cell(row["Применение"])}
for label_, (column, key) in MATERIAL_FIELDS.items():
raw = fields.get(label_, row.get(column, ""))
try:
@@ 165,6 183,8 @@
rec["description"] = fields.get("Описание", "")
rec["conditions"] = MATERIAL_CONDITIONS.get(rec["name"], [])
out.append(rec)
+ if base == WOOD_BASE:
+ out.append(wood_for_steel(rec))
for e in wiki.entries(doc, 4):
if e.index not in seen:
@@ 175,6 195,22 @@
return out + inlays(doc, report)
+def _round(x):
+ x = round(x, 6)
+ return int(x) if float(x).is_integer() else x
+
+
+def wood_for_steel(rec):
+ """Вариант деревянного материала для предмета, который обычно делают из стали."""
+ out = {**rec, "name": f"{rec['name']} (instead of Steel)", "name_ru": f"{rec['name_ru']} (вместо стали)",
+ "base": "Сталь", "use": "Оружие", "short": WOOD_FOR_STEEL_SHORT,
+ "special": "\n".join(t for t in (WOOD_FOR_STEEL_SPECIAL, rec["special"]) if t)}
+ for key in ("priceFactor", "weightFactor"):
+ out[key] = _round(rec[key] * WOOD_FOR_STEEL[key]) if rec[key] is not None else None
+ out["pen"] = (rec["pen"] or 0) + WOOD_FOR_STEEL["pen"]
+ return out
+
+
def inlays(doc, report):
"""Камни таблицы «Инкрустация»: имя, редкость, цена (как в вики, без единицы), бонус Ёмкости."""
table = _table(doc, "Камень")
.scripts/gamedata/test_materials.py ..
@@ 20,25 20,38 @@
MATERIALS = """
# Материалы
- | Материал | Тип | Применение | R | Цена | Банк | Вес | Прочность | Ёмкость | AP | Pen | Особое |
+ | Материал | База | Применение | R | Цена | Банк | Вес | Прочность | Ёмкость | AP | Pen | Особое |
| --- | --- | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | --- |
- | [Мифрил](#mithral-) | Металл | Универсальный | 3 | ×15 | ×1.5 | ×0.5 | 2 | ×1 | +1 | +1 | +20 Max.A |
+ | [Мифрил](#mithral-) | Сталь | Универсальный | 3 | ×15 | ×1.5 | ×0.5 | 2 | ×1 | +1 | +1 | +20 Max.A |
| [Кожа](#leather-) | Кожа | Броня | −3 | ×0.3 | ×0.5 | ×1 | −1 | ×1 | 0 | — | — |
+ | [Живое Дерево](#livewood-) | Дерево | Оружие, Щиты | 1 | ×20 | ×1 | ×1 | 0 | ×2 | 0 | 0 | Может заменять сталь |
+
+ ## Металлы
#### Mithral / Мифрил
**Редкость:** 3 | **Цена:** ×15 | **Банк:** ×1.5 | **Вес:** ×0.5 | **Прочность:** 2 | **Ёмкость:** ×1 | **AP:** +1 | **Pen:** +1
- **Применение:** Универсальный
+ **Применение:** Универсальный | **База:** Сталь
**Особое:**
- Броня получает +20 Max.A.
- Клинки острее.
**Описание:** Лёгкий металл.
+ ## Кожа
+
#### Leather / Кожа
**Редкость:** −3 | **Цена:** ×0.3 | **Банк:** ×0.5 | **Вес:** ×1 | **Прочность:** −2 | **Ёмкость:** ×1 | **AP:** 0 | **Pen:** —
- **Применение:** Броня
+ **Применение:** Броня | **База:** Лён
**Описание:** Выделанная кожа.
+ ## Дерево
+
+ #### Livewood / Живое Дерево
+ **Редкость:** 1 | **Цена:** ×20 | **Банк:** ×1 | **Вес:** ×1 | **Прочность:** 0 | **Ёмкость:** ×2 | **AP:** 0 | **Pen:** 0
+ **Применение:** Оружие, Щиты | **База:** Дерево
+ **Особое:** Самовосстановление.
+ **Описание:** Живая древесина.
+
#### Tin / Олово
**Редкость:** 0
@@ 66,6 79,24 @@
self.assertEqual(m["special"], "- Броня получает +20 Max.A.\n- Клинки острее.")
self.assertEqual(m["description"], "Лёгкий металл.")
+ def test_section_and_base(self):
+ m = self.by_name["Mithral"]
+ self.assertEqual((m["entryType"], m["base"]), ("Металл", "Сталь"))
+ self.assertEqual(self.by_name["Leather"]["entryType"], "Кожа")
+ self.assertTrue(any("База: в записи Лён, в таблице Кожа" in m for m in messages(self.r, "value")))
+ self.assertFalse(messages(self.r, "label"))
+
+ def test_wood_for_steel(self):
+ wood = self.by_name["Livewood (instead of Steel)"]
+ self.assertEqual((wood["name_ru"], wood["entryType"], wood["base"], wood["use"]),
+ ("Живое Дерево (вместо стали)", "Дерево", "Сталь", "Оружие"))
+ self.assertEqual((wood["priceFactor"], wood["weightFactor"], wood["pen"], wood["capacityFactor"]), (2, 0.5, -1, 2))
+ self.assertEqual(wood["short"], "−3 R и P Dmg")
+ self.assertTrue(wood["special"].endswith("\nСамовосстановление."))
+ names = [m["name"] for m in self.out]
+ self.assertEqual(names.index("Livewood (instead of Steel)"), names.index("Livewood") + 1)
+ self.assertNotIn("Mithral (instead of Steel)", names)
+
def test_dash_is_none(self):
leather = self.by_name["Leather"]
self.assertEqual((leather["rarity"], leather["pen"], leather["short"]), (-3, None, ""))
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