Лист персонажа

Экспорт персонажа

const exportNote = dv.current();
const pcLink = exportNote["персонаж"];
const pc = pcLink ? dv.page(pcLink) : null;
 
if (!pc) {
  dv.paragraph("⚠️ Не найдена заметка персонажа. Проверь поле `персонаж` в YAML этой заметки.");
} else if (!pc["классы"] || !Array.isArray(pc["классы"]) || pc["классы"].length === 0) {
  dv.paragraph("⚠️ В заметке персонажа не заполнен массив `классы`.");
} else {
  const excluded = new Set(
    (pc["исключённые_умения"] || [])
      .map(function (link) {
        return link && link.path ? link.path : String(link);
      })
  );
 
  const extraPaths = (pc["дополнительные_умения"] || [])
    .map(function (link) {
      return link && link.path ? link.path : String(link);
    });
 
  function getLevels(page) {
    const raw = page["уровни"] || page["уровень"] || ;
    const values = Array.isArray(raw) ? raw : [raw];
 
    return values
      .map(function (value) {
        return Number(value);
      })
      .filter(function (value) {
        return !Number.isNaN(value);
      });
  }
 
  function isAvailable(page, classLevel) {
    return getLevels(page).some(function (featureLevel) {
      return featureLevel <= classLevel;
    });
  }
 
  function hasExactTag(page, tag) {
    const tags = page.file.etags || [];
    return tags.includes(tag);
  }
 
  function hasTreeTag(page, tag) {
    const tags = page.file.tags || [];
    return tags.includes(tag);
  }
 
  function isVisible(page) {
    const isExcluded = excluded.has(page.file.path);
    const isGMOnly = hasExactTag(page, "#export/gm-only");
 
    return page["экспортировать"] !== false &&
      !isExcluded &&
      !isGMOnly;
  }
 
  function minLevel(page) {
    const levels = getLevels(page);
    return levels.length > 0 ? Math.min.apply(null, levels) : 1;
  }
 
  function uniquePages(pages) {
    const seen = new Set();
 
    return pages.filter(function (page) {
      if (seen.has(page.file.path)) {
        return false;
      }
 
      seen.add(page.file.path);
      return true;
    });
  }
 
  function sortFeatures(a, b) {
    const levelDifference = minLevel(a) - minLevel(b);
 
    if (levelDifference !== 0) {
      return levelDifference;
    }
 
    const orderDifference =
      Number(a["порядок_экспорта"] || 999) -
      Number(b["порядок_экспорта"] || 999);
 
    if (orderDifference !== 0) {
      return orderDifference;
    }
 
    const aName = a["название"] || a.file.name;
    const bName = b["название"] || b.file.name;
 
    return aName.localeCompare(bName, "ru");
  }
 
  function getCurrentValue(page, classLevel) {
    const rows = page["значения_по_уровню"] || [];
 
    if (!Array.isArray(rows)) {
      return null;
    }
 
    for (const row of rows) {
      const from = Number(row["от"] || 1);
      const to = Number(row["до"] || 999);
 
      if (from <= classLevel && to >= classLevel) {
        return row;
      }
    }
 
    return null;
  }
 
  function renderCurrentValue(page, classLevel) {
    const currentValue = getCurrentValue(page, classLevel);
 
    if (!currentValue) {
      return;
    }
 
    const fields = Object.entries(currentValue)
      .filter(function (entry) {
        return entry !== "от" && entry !== "до";
      })
      .map(function (entry) {
        return entry + ": " + entry;
      });
 
    if (fields.length > 0) {
      dv.paragraph(
        "**Текущее значение на уровне класса " +
        classLevel +
        ":** " +
        fields.join("; ")
      );
    }
  }
 
  async function renderFeature(page, classLevel) {
    const title = page["название"] || page.file.name;
    const levels = getLevels(page).join(", ");
 
    dv.header(4, title + " · получено на уровне: " + levels);
 
    const details = [];
 
    if (page["активация"]) {
      details.push("**Активация:** " + page["активация"]);
    }
 
    if (page["восстановление"]) {
      details.push("**Восстановление:** " + page["восстановление"]);
    }
 
    const exportSettings = pc["экспорт"] || {};
 
    if (
      page["источник"] &&
      exportSettings["показывать_источники"] !== false
    ) {
      details.push("**Источник:** " + page["источник"]);
    }
 
    if (details.length > 0) {
      dv.paragraph(details.join("  \n"));
    }
 
    renderCurrentValue(page, classLevel);
 
    if (exportSettings["полные_описания"] !== false) {
      const raw = await dv.io.load(page.file.path);
 
      const body = raw
        .replace(/^---[\s\S]*?---\s*/m, "")
        .replace(/^# .*\n/m, "")
        .trim();
 
      if (body.length > 0) {
        dv.paragraph(body);
      }
    }
 
    dv.el("hr");
  }
 
  async function renderSection(title, pages, classLevel) {
    const sortedPages = uniquePages(pages).sort(sortFeatures);
 
    if (sortedPages.length === 0) {
      return;
    }
 
    dv.header(3, title);
 
    for (const page of sortedPages) {
      await renderFeature(page, classLevel);
    }
  }
 
  const characterName = pc["имя"] || pc.file.name;
  const totalLevel = pc["уровень_персонажа"];
 
  dv.header(1, characterName);
 
  if (totalLevel) {
    dv.paragraph("**Общий уровень:** " + totalLevel);
  }
 
  for (const characterClass of pc["классы"]) {
    const slug = characterClass["слаг"];
    const classLevel = Number(characterClass["уровень"] || 0);
    const className = characterClass["название"] || slug;
 
    if (!slug || !classLevel) {
      dv.paragraph(
        "⚠️ У одной из записей в `классы` не заполнен `слаг` или `уровень`."
      );
      continue;
    }
 
    const classTag = "#feature/class/" + slug;
    const archetype = characterClass["архетип"] || null;
    const archetypeSlug = archetype ? archetype["слаг"] : null;
    const archetypeName = archetype ? archetype["название"] : null;
 
    const archetypeTag = archetypeSlug
      ? "#feature/archetype/" + slug + "/" + archetypeSlug
      : null;
 
    const allPages = dv.pages()
      .where(function (page) {
        return isVisible(page);
      })
      .array();
 
    const classFeatures = allPages
      .filter(function (page) {
        return hasExactTag(page, classTag);
      })
      .filter(function (page) {
        return page["тип_вывода"] !== "ресурс";
      })
      .filter(function (page) {
        return !hasExactTag(page, "#resource");
      })
      .filter(function (page) {
        return isAvailable(page, classLevel);
      });
 
    let archetypeFeatures = [];
 
    if (archetypeTag) {
      archetypeFeatures = allPages
        .filter(function (page) {
          return hasTreeTag(page, archetypeTag);
        })
        .filter(function (page) {
          return page["тип_вывода"] !== "ресурс";
        })
        .filter(function (page) {
          return !hasExactTag(page, "#resource");
        })
        .filter(function (page) {
          return isAvailable(page, classLevel);
        });
    }
 
    const resources = allPages
      .filter(function (page) {
        return hasExactTag(page, "#resource") ||
          page["тип_вывода"] === "ресурс";
      })
      .filter(function (page) {
        const fromClass = hasTreeTag(page, classTag);
        const fromArchetype = archetypeTag &&
          hasTreeTag(page, archetypeTag);
 
        return fromClass || fromArchetype;
      })
      .filter(function (page) {
        return isAvailable(page, classLevel);
      });
 
    dv.header(2, className + " · уровень " + classLevel);
 
    await renderSection(
      "Классовые умения",
      classFeatures,
      classLevel
    );
 
    const exportSettings = pc["экспорт"] || {};
 
    if (
      archetypeTag &&
      exportSettings["включать_архетипы"] !== false
    ) {
      await renderSection(
        "Архетип: " + (archetypeName || archetypeSlug),
        archetypeFeatures,
        classLevel
      );
    }
 
    await renderSection(
      "Ресурсы и лимиты",
      resources,
      classLevel
    );
  }
 
  const extraFeatures = extraPaths
    .map(function (path) {
      return dv.page(path);
    })
    .filter(function (page) {
      return page !== null && page !== undefined;
    })
    .filter(function (page) {
      return isVisible(page);
    });
 
  if (extraFeatures.length > 0) {
    dv.header(2, "Дополнительные умения");
 
    for (const page of uniquePages(extraFeatures).sort(sortFeatures)) {
      await renderFeature(page, Number(totalLevel || 1));
    }
  }
}