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));
}
}
}