This commit is contained in:
TheGiddyLimit
2024-03-24 23:47:02 +00:00
parent 84065a027d
commit 12f34a38f8
153 changed files with 90818 additions and 1900 deletions

View File

@@ -1553,7 +1553,7 @@ class CreatureParser extends BaseParser {
// regular creatures
// region Size
const reSize = new RegExp(`(${Object.values(Parser.SIZE_ABV_TO_FULL).join("|")})`, "i");
const reSize = new RegExp(`\\b(${Object.values(Parser.SIZE_ABV_TO_FULL).join("|")})\\b`, "i");
const reSizeGlobal = new RegExp(reSize, "gi");
const tks = meta.curLine.split(reSizeGlobal);

View File

@@ -2,6 +2,12 @@
class AcConvert {
static tryPostProcessAc (mon, cbMan, cbErr) {
const traitNames = new Set(
(mon.trait || [])
.map(it => it.name ? it.name.toLowerCase() : null)
.filter(Boolean),
);
if (this._tryPostProcessAc_special(mon, cbMan, cbErr)) return;
const nuAc = [];
@@ -114,14 +120,14 @@ class AcConvert {
// everything else
default: {
const simpleFrom = this._getSimpleFrom(fromLow);
const simpleFrom = this._getSimpleFrom({fromLow, traitNames});
if (simpleFrom) return froms.push(simpleFrom);
// Special parsing for barding, as the pre-barding armor type might not exactly match our known
// barding names (e.g. "chainmail barding")
const mWithBarding = /^(?<ac>\d+) with (?<name>(?<type>.*?) barding)$/.exec(fromLow);
if (mWithBarding) {
let simpleFromBarding = this._getSimpleFrom(mWithBarding.groups.type);
let simpleFromBarding = this._getSimpleFrom({fromLow: mWithBarding.groups.type, traitNames});
if (simpleFromBarding) {
simpleFromBarding = simpleFromBarding
.replace(/{@item ([^}]+)}/, (...m) => {
@@ -203,7 +209,7 @@ class AcConvert {
return false;
}
static _getSimpleFrom (fromLow) {
static _getSimpleFrom ({fromLow, traitNames}) {
switch (fromLow) {
// region unhandled/other
case "unarmored defense":
@@ -323,6 +329,10 @@ class AcConvert {
if (/scraps of .*?armor/i.test(fromLow)) { // e.g. "scraps of hide armor"
return fromLow;
}
if (traitNames.has(fromLow)) {
return fromLow;
}
}
}
}
@@ -371,9 +381,19 @@ class _CreatureImmunityResistanceVulnerabilityConverterBase {
static _getSplitInput ({ipt}) {
return ipt
.toLowerCase()
.split(";")
// Split e.g.
// "Bludgeoning and Piercing from nonmagical attacks, Acid, Fire, Lightning"
.split(/(.*\b(?:from|by)\b[^,;.!?]+)(?:[,;] ?)?/g)
.map(it => it.trim())
.filter(Boolean);
.filter(Boolean)
// Split e.g.
// "poison; bludgeoning, piercing, and slashing from nonmagical attacks"
.flatMap(pt => pt.split(";"))
.map(it => it.trim())
.filter(Boolean)
;
}
/**
@@ -1343,9 +1363,14 @@ class MiscTag {
globalThis.MiscTag = MiscTag;
class SpellcastingTraitConvert {
static SPELL_SRC_MAP = {};
static SPELL_SRD_MAP = {};
static init (spellData) {
// reversed so official sources take precedence over 3pp
spellData.forEach(s => SpellcastingTraitConvert.SPELL_SRC_MAP[s.name.toLowerCase()] = s.source);
spellData.forEach(s => {
this.SPELL_SRC_MAP[s.name.toLowerCase()] = s.source;
if (typeof s.srd === "string") this.SPELL_SRD_MAP[s.srd.toLowerCase()] = s.name;
});
}
static tryParseSpellcasting (ent, {isMarkdown, cbErr, displayAs, actions, reactions}) {
@@ -1490,6 +1515,8 @@ class SpellcastingTraitConvert {
str = str.substring(0, ixParenOpen);
}
str = this._parseSpell_getNonSrdSpellName(str);
return [
`{@spell ${str}${this._parseSpell_getSourcePart(str)}}`,
ptsSuffix.join(" "),
@@ -1498,6 +1525,16 @@ class SpellcastingTraitConvert {
.join(" ");
}
static _parseSpell_getNonSrdSpellName (spellName) {
const nonSrdName = SpellcastingTraitConvert.SPELL_SRD_MAP[spellName.toLowerCase().trim()];
if (!nonSrdName) return spellName;
if (spellName.toSpellCase() === spellName) return nonSrdName.toSpellCase();
if (spellName.toLowerCase() === spellName) return nonSrdName.toLowerCase();
if (spellName.toTitleCase() === spellName) return nonSrdName.toTitleCase();
return spellName;
}
static _parseSpell_getSourcePart (spellName) {
const source = SpellcastingTraitConvert._getSpellSource(spellName);
return `${source && source !== Parser.SRC_PHB ? `|${source}` : ""}`;
@@ -1583,7 +1620,6 @@ class SpellcastingTraitConvert {
});
}
}
SpellcastingTraitConvert.SPELL_SRC_MAP = {};
globalThis.SpellcastingTraitConvert = SpellcastingTraitConvert;

View File

@@ -1,12 +1,6 @@
"use strict";
class LanguagesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "sublanguages",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -70,8 +64,6 @@ class LanguagesPage extends ListPage {
pageFilter,
listClass: "languages",
dataProps: ["language"],
isMarkdownPopout: true,

View File

@@ -107,7 +107,6 @@ class SublistManager {
/**
* @param [opts]
* @param [opts.sublistClass] Sublist class.
* @param [opts.sublistListOptions] Other sublist options.
* @param [opts.isSublistItemsCountable] If the sublist items should be countable, i.e. have a quantity.
* @param [opts.shiftCountAddSubtract] If the sublist items should be countable, i.e. have a quantity.
@@ -115,7 +114,6 @@ class SublistManager {
constructor (opts) {
opts = opts || {};
this._sublistClass = opts.sublistClass; // TODO(PageGen) remove once all pages transitioned
this._sublistListOptions = opts.sublistListOptions || {};
this._isSublistItemsCountable = !!opts.isSublistItemsCountable;
this._shiftCountAddSubtract = opts.shiftCountAddSubtract ?? 20;
@@ -160,7 +158,7 @@ class SublistManager {
this._listSub = new List({
...this._sublistListOptions,
$wrpList: this._sublistClass ? $(`.${this._sublistClass}`) : $(`#sublist`),
$wrpList: $(`#sublist`),
isUseJquery: true,
});
@@ -987,7 +985,6 @@ class ListPage {
* `pageFilter` must be specified.)
* @param [opts.pageFilter] PageFilter implementation for this page. (Either `filters` and `filterSource` or
* `pageFilter` must be specified.)
* @param [opts.listClass] List class.
* @param opts.listOptions Other list options.
* @param opts.dataProps JSON data propert(y/ies).
*
@@ -1017,7 +1014,6 @@ class ListPage {
this._filters = opts.filters;
this._filterSource = opts.filterSource;
this._pageFilter = opts.pageFilter;
this._listClass = opts.listClass; // TODO(PageGen) remove once all pages transitioned
this._listOptions = opts.listOptions || {};
this._dataProps = opts.dataProps;
this._bookViewOptions = opts.bookViewOptions;
@@ -1152,7 +1148,7 @@ class ListPage {
const $btnReset = $("#reset");
this._list = this._initList({
$iptSearch,
$wrpList: this._listClass ? $(`.list.${this._listClass}`) : $(`#list`),
$wrpList: $(`#list`),
$btnReset,
$btnClear: $(`#lst__search-glass`),
dispPageTagline: document.getElementById(`page__subtitle`),

View File

@@ -1,12 +1,6 @@
"use strict";
class ObjectsSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subobjects",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -64,8 +58,6 @@ class ObjectsPage extends ListPage {
pageFilter,
listClass: "objects",
dataProps: ["object"],
listSyntax: new ListSyntaxObjects({fnGetDataList: () => this._dataList, pFnGetFluff}),

View File

@@ -3,7 +3,6 @@
class OptionalFeaturesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "suboptfeatures",
sublistListOptions: {
fnSort: PageFilterOptionalFeatures.sortOptionalFeatures,
},
@@ -84,7 +83,6 @@ class OptionalFeaturesPage extends ListPage {
pageFilter,
listClass: "optfeatures",
listOptions: {
fnSort: PageFilterOptionalFeatures.sortOptionalFeatures,
},

View File

@@ -645,7 +645,7 @@ Parser.sourceJsonToDate = function (source) {
};
Parser.sourceJsonToColor = function (source) {
return `source${Parser.sourceJsonToAbv(source)}`;
return `source__${source}`;
};
Parser.sourceJsonToStyle = function (source) {
@@ -2536,7 +2536,7 @@ Parser.vehicleTypeToFull = function (vehicleType) {
// SOURCES =============================================================================================================
Parser.SRC_5ETOOLS_TMP = "Parser.SRC_5ETOOLS_TMP"; // Temp source, used as a placeholder value
Parser.SRC_5ETOOLS_TMP = "SRC_5ETOOLS_TMP"; // Temp source, used as a placeholder value
Parser.SRC_CoS = "CoS";
Parser.SRC_DMG = "DMG";
@@ -2645,6 +2645,7 @@ Parser.SRC_GHLoE = "GHLoE";
Parser.SRC_DoDk = "DoDk";
Parser.SRC_HWCS = "HWCS";
Parser.SRC_HWAitW = "HWAitW";
Parser.SRC_ToB1_2023 = "ToB1-2023";
Parser.SRC_TD = "TD";
Parser.SRC_SCREEN = "Screen";
Parser.SRC_SCREEN_WILDERNESS_KIT = "ScreenWildernessKit";
@@ -2821,6 +2822,7 @@ Parser.SOURCE_JSON_TO_FULL[Parser.SRC_GHLoE] = "Grim Hollow: Lairs of Etharis";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_DoDk] = "Dungeons of Drakkenheim";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_HWCS] = "Humblewood Campaign Setting";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_HWAitW] = "Humblewood: Adventure in the Wood";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_ToB1_2023] = "Tome of Beasts 1 (2023 Edition)";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_TD] = "Tarot Deck";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_SCREEN] = "Dungeon Master's Screen";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_SCREEN_WILDERNESS_KIT] = "Dungeon Master's Screen: Wilderness Kit";
@@ -2972,6 +2974,7 @@ Parser.SOURCE_JSON_TO_ABV[Parser.SRC_GHLoE] = "GHLoE";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_DoDk] = "DoDk";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_HWCS] = "HWCS";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_HWAitW] = "HWAitW";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_ToB1_2023] = "ToB1'23";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_TD] = "TD";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_SCREEN] = "Screen";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_SCREEN_WILDERNESS_KIT] = "ScWild";
@@ -3122,6 +3125,7 @@ Parser.SOURCE_JSON_TO_DATE[Parser.SRC_GHLoE] = "2023-11-30";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_DoDk] = "2023-12-21";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_HWCS] = "2019-06-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_HWAitW] = "2019-06-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_ToB1_2023] = "2023-05-31";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_TD] = "2022-05-24";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_SCREEN] = "2015-01-20";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_SCREEN_WILDERNESS_KIT] = "2020-11-17";
@@ -3311,6 +3315,7 @@ Parser.SOURCES_PARTNERED_WOTC = new Set([
Parser.SRC_DoDk,
Parser.SRC_HWCS,
Parser.SRC_HWAitW,
Parser.SRC_ToB1_2023,
Parser.SRC_TD,
]);
// region Source categories
@@ -3397,6 +3402,7 @@ Parser.SOURCES_NON_FR = new Set([
Parser.SRC_DoDk,
Parser.SRC_HWCS,
Parser.SRC_HWAitW,
Parser.SRC_ToB1_2023,
]);
// endregion
@@ -3439,6 +3445,7 @@ Parser.SOURCES_AVAILABLE_DOCS_BOOK = {};
Parser.SRC_BMT,
Parser.SRC_DMTCRG,
Parser.SRC_HWCS,
Parser.SRC_ToB1_2023,
Parser.SRC_TD,
].forEach(src => {
Parser.SOURCES_AVAILABLE_DOCS_BOOK[src] = src;

View File

@@ -1,12 +1,6 @@
"use strict";
class PsionicsSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subpsionics",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -65,10 +59,6 @@ class PsionicsPage extends ListPage {
pageFilter,
listClass: "psionics",
sublistClass: "subpsionics",
dataProps: ["psionic"],
isMarkdownPopout: true,

View File

@@ -1,12 +1,6 @@
"use strict";
class RacesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subraces",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -73,8 +67,6 @@ class RacesPage extends ListPage {
pageFilter,
listClass: "races",
dataProps: ["race"],
isMarkdownPopout: true,

View File

@@ -1,12 +1,6 @@
"use strict";
class RecipesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subrecipes",
});
}
_getCustomHashId ({entity}) {
return Renderer.recipe.getCustomHashId(entity);
}
@@ -71,8 +65,6 @@ class RecipesPage extends ListPage {
pageFilter,
listClass: "recipes",
dataProps: ["recipe"],
isMarkdownPopout: true,

View File

@@ -1689,6 +1689,8 @@ RendererMarkdown.vehicle = class {
renderer.render(entriesMetaShip.entrySizeDimensions),
RendererMarkdown.vehicle.ship.getCrewCargoPaceSection_(ent, {entriesMetaShip}),
RendererMarkdown.utils.compact.getRenderedAbilityScores(ent),
entriesMeta.entryDamageVulnerabilities ? renderer.render(entriesMeta.entryDamageVulnerabilities) : null,
entriesMeta.entryDamageResistances ? renderer.render(entriesMeta.entryDamageResistances) : null,
entriesMeta.entryDamageImmunities ? renderer.render(entriesMeta.entryDamageImmunities) : null,
entriesMeta.entryConditionImmunities ? renderer.render(entriesMeta.entryConditionImmunities) : null,
ent.action ? "### Actions" : null,
@@ -1770,6 +1772,8 @@ RendererMarkdown.vehicle = class {
.map(prop => renderer.render(entriesMetaInfwar[prop])),
renderer.render(entriesMetaInfwar.entrySpeedNote),
RendererMarkdown.utils.compact.getRenderedAbilityScores(ent),
entriesMeta.entryDamageVulnerabilities ? renderer.render(entriesMeta.entryDamageVulnerabilities) : null,
entriesMeta.entryDamageResistances ? renderer.render(entriesMeta.entryDamageResistances) : null,
entriesMeta.entryDamageImmunities ? renderer.render(entriesMeta.entryDamageImmunities) : null,
entriesMeta.entryConditionImmunities ? renderer.render(entriesMeta.entryConditionImmunities) : null,
...this._getLinesRendered_traits({ent, renderer}),

View File

@@ -260,6 +260,25 @@ globalThis.Renderer = function () {
this._getPlugins = function (pluginType) { return this._plugins[pluginType] ||= []; };
// TODO(Future) refactor to use this
this._applyPlugins_useFirst = function (pluginType, commonArgs, pluginArgs) {
for (const plugin of this._getPlugins(pluginType)) {
const out = plugin(commonArgs, pluginArgs);
if (out) return out;
}
};
this._applyPlugins_useAll = function (pluginType, commonArgs, pluginArgs) {
const plugins = this._getPlugins(pluginType);
if (!plugins?.length) return null;
let input = pluginArgs.input;
for (const plugin of plugins) {
input = plugin(commonArgs, pluginArgs) ?? input;
}
return input;
};
/** Run a function with the given plugin active. */
this.withPlugin = function ({pluginTypes, fnPlugin, fn}) {
for (const pt of pluginTypes) this.addPlugin(pt, fnPlugin);
@@ -1547,7 +1566,9 @@ globalThis.Renderer = function () {
};
this._renderString = function (entry, textStack, meta, options) {
const tagSplit = Renderer.splitByTags(entry);
const str = this._applyPlugins_useAll("string_preprocess", {textStack, meta, options}, {input: entry}) ?? entry;
const tagSplit = Renderer.splitByTags(str);
const len = tagSplit.length;
for (let i = 0; i < len; ++i) {
const s = tagSplit[i];
@@ -9605,6 +9626,12 @@ Renderer.vehicle = class {
static getVehicleRenderableEntriesMeta (ent) {
return {
entryDamageVulnerabilities: ent.vulnerable
? `{@b Damage Vulnerabilities} ${Parser.getFullImmRes(ent.vulnerable)}`
: null,
entryDamageResistances: ent.resist
? `{@b Damage Resistances} ${Parser.getFullImmRes(ent.resist)}`
: null,
entryDamageImmunities: ent.immune
? `{@b Damage Immunities} ${Parser.getFullImmRes(ent.immune)}`
: null,
@@ -9940,6 +9967,8 @@ Renderer.vehicle = class {
entriesMeta ||= Renderer.vehicle.getVehicleRenderableEntriesMeta(ent);
const props = [
"entryDamageVulnerabilities",
"entryDamageResistances",
"entryDamageImmunities",
"entryConditionImmunities",
];

View File

@@ -1,12 +1,6 @@
"use strict";
class RewardsSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subrewards",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -61,8 +55,6 @@ class RewardsPage extends ListPage {
pageFilter,
listClass: "rewards",
dataProps: ["reward"],
isPreviewable: true,

View File

@@ -104,10 +104,10 @@ class SearchPage {
SearchPage._$wrpResults = $(`<div class="ve-flex-col w-100">${this._getWrpResult_message("Loading...")}</div>`);
$$(SearchPage._$wrp)`<div class="ve-flex-col w-100 pg-search__wrp">
<div class="ve-flex-v-center mb-2 mobile-ish__ve-flex-col">
<div class="ve-flex-v-center input-group btn-group mr-2 w-100 mobile-ish__mb-2">${$iptSearch}${$btnSearch}</div>
<div class="ve-flex-v-center mb-2 mobile-lg__ve-flex-col">
<div class="ve-flex-v-center input-group btn-group mr-2 w-100 mobile-lg__mb-2">${$iptSearch}${$btnSearch}</div>
<div class="ve-flex-v-center mobile__ve-flex-col mobile-ish__ve-flex-ai-start mobile-ish__w-100">
<div class="ve-flex-v-center mobile__ve-flex-col mobile-lg__ve-flex-ai-start mobile-lg__w-100">
${$btnHelp}
<div class="ve-flex-v-center btn-group mr-2 mobile__mb-2 mobile__mr-0">
${$btnToggleBrew}

View File

@@ -1,15 +1,6 @@
"use strict";
class SpellsSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subspells",
sublistListOptions: {
fnSort: PageFilterSpells.sortSpells,
},
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -223,7 +214,6 @@ class SpellsPage extends ListPageMultiSource {
super({
pageFilter: new PageFilterSpells(),
listClass: "spells",
listOptions: {
fnSort: PageFilterSpells.sortSpells,
},

View File

@@ -661,8 +661,8 @@ class StatGenUi extends BaseComponent {
// region Point Buy stages
const $stgPbHeader = this._render_$getStgPbHeader();
const $stgPbCustom = this._render_$getStgPbCustom();
const $vrPbCustom = $(`<div class="vr-5 mobile-ish__hidden"></div>`);
const $hrPbCustom = $(`<hr class="hr-5 mobile-ish__visible">`);
const $vrPbCustom = $(`<div class="vr-5 mobile-lg__hidden"></div>`);
const $hrPbCustom = $(`<hr class="hr-5 mobile-lg__visible">`);
const hkStgPb = () => {
$stgPbHeader.toggleVe(this.ixActiveTab === this._IX_TAB_PB);
$stgPbCustom.toggleVe(this.ixActiveTab === this._IX_TAB_PB);
@@ -859,7 +859,7 @@ class StatGenUi extends BaseComponent {
${$stgArrayHeader}
${$stgManualHeader}
<div class="ve-flex mobile-ish__ve-flex-col w-100 px-3">
<div class="ve-flex mobile-lg__ve-flex-col w-100 px-3">
<div class="ve-flex-col">
${$stgPbHeader}
@@ -1363,7 +1363,7 @@ class StatGenUi extends BaseComponent {
const $wrpAsi = this._render_$getWrpAsi();
$$($wrpTab)`
<div class="ve-flex mobile-ish__ve-flex-col w-100 px-3">
<div class="ve-flex mobile-lg__ve-flex-col w-100 px-3">
<div class="ve-flex-col">
<div class="ve-flex">
<div class="ve-flex-col mr-3">

View File

@@ -3,7 +3,6 @@
class TablesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subtablesdata",
sublistListOptions: {
sortByInitial: "sortName",
},
@@ -55,7 +54,6 @@ class TablesPage extends ListPage {
pageFilter,
listClass: "tablesdata",
listOptions: {
sortByInitial: "sortName",
},

View File

@@ -2119,7 +2119,7 @@ class ManageBrewUi {
const btnViewJson = e_({
tag: "button",
clazz: `btn btn-default btn-xs mobile-ish__hidden w-24p`,
clazz: `btn btn-default btn-xs mobile-lg__hidden w-24p`,
title: `${this._LBL_LIST_VIEW_JSON}: ${this.constructor._getBrewJsonTitle({brew, brewName})}`,
children: [
e_({

View File

@@ -78,11 +78,16 @@ PropOrder._ObjectKey = class {
this.order = opts.order;
}
static getCopyKey ({fnGetModOrder}) {
static getCopyKey ({identKeys = null, fnGetModOrder}) {
return new this("_copy", {
order: [
"name",
"source",
...(
identKeys
|| [
"name",
"source",
]
),
"_templates",
new PropOrder._ObjectKey("_mod", {
fnGetOrder: fnGetModOrder,
@@ -112,6 +117,63 @@ PropOrder._ArrayKey = class {
}
};
PropOrder._META = [
"sources",
"dependencies",
"includes",
"internalCopies",
"otherSources",
"spellSchools",
"spellDistanceUnits",
"optionalFeatureTypes",
"psionicTypes",
"currencyConversions",
"fonts",
"status",
"unlisted",
"dateAdded",
"dateLastModified",
"_dateLastModifiedHash",
];
PropOrder._FOUNDRY_GENERIC = [
"name",
"source",
"type",
"system",
"effects",
"flags",
"img",
"_merge",
];
PropOrder._FOUNDRY_GENERIC_FEATURE = [
"name",
"source",
"isIgnored",
"type",
"system",
"actorDataMod",
"effects",
"ignoreSrdEffects",
"flags",
"img",
"entries",
new PropOrder._ObjectKey("entryData", {
fnGetOrder: () => PropOrder._ENTRY_DATA_OBJECT,
}),
"_merge",
];
PropOrder._MONSTER = [
"name",
"shortName",
@@ -335,6 +397,7 @@ PropOrder._FOUNDRY_MONSTER = [
"source",
"system",
"prototypeToken",
"effects",
"flags",
"img",
@@ -450,15 +513,6 @@ PropOrder._ROLL20_SPELL = [
}),
"shapedData",
];
PropOrder._FOUNDRY_SPELL = [
"name",
"source",
"system",
"effects",
"flags",
"img",
];
PropOrder._SPELL__COPY_MOD = [
"*",
"_",
@@ -739,6 +793,21 @@ PropOrder._SUBCLASS__COPY_MOD = [
"_",
...PropOrder._SUBCLASS,
];
PropOrder._FOUNDRY_SUBCLASS = [
"name",
"source",
"className",
"classSource",
"advancement",
"chooseSystem",
"isChooseSystemRenderEntries",
"isChooseFlagsRenderEntries",
"isIgnored",
"ignoreSrdEffects",
"actorDataMod",
"actorTokenMod",
];
PropOrder._ENTRY_DATA_OBJECT = [
"languageProficiencies",
"skillProficiencies",
@@ -1110,15 +1179,6 @@ PropOrder._FEAT__COPY_MOD = [
"_",
...PropOrder._FEAT,
];
PropOrder._FOUNDRY_FEAT = [
"name",
"source",
"system",
"effects",
"flags",
"img",
];
PropOrder._VEHICLE = [
"name",
@@ -1158,7 +1218,9 @@ PropOrder._VEHICLE = [
"hp",
"resist",
"immune",
"vulnerable",
"conditionImmune",
"hull",
@@ -1319,6 +1381,7 @@ PropOrder._ITEM = [
"bonusSavingThrow",
"bonusAbilityCheck",
"bonusProficiencyBonus",
"bonusSavingThrowConcentration",
"modifySpeed",
"reach",
"critThreshold",
@@ -1679,11 +1742,26 @@ PropOrder._FOUNDRY_RACE_FEATURE = [
"raceName",
"raceSource",
PropOrder._ObjectKey.getCopyKey({
identKeys: [
"name",
"source",
"raceName",
"raceSource",
],
fnGetModOrder: () => PropOrder._FOUNDRY_RACE_FEATURE__COPY_MOD,
}),
"system",
"effects",
"flags",
"img",
];
PropOrder._FOUNDRY_RACE_FEATURE__COPY_MOD = [
"*",
"_",
...PropOrder._FOUNDRY_RACE_FEATURE,
];
PropOrder._TABLE = [
"name",
@@ -1915,6 +1993,7 @@ PropOrder._CITATION = [
];
PropOrder._PROP_TO_LIST = {
"_meta": PropOrder._META,
"monster": PropOrder._MONSTER,
"foundryMonster": PropOrder._FOUNDRY_MONSTER,
"monsterFluff": PropOrder._GENERIC_FLUFF,
@@ -1935,9 +2014,10 @@ PropOrder._PROP_TO_LIST = {
"hazardFluff": PropOrder._GENERIC_FLUFF,
"spell": PropOrder._SPELL,
"roll20Spell": PropOrder._ROLL20_SPELL,
"foundrySpell": PropOrder._FOUNDRY_SPELL,
"foundrySpell": PropOrder._FOUNDRY_GENERIC,
"spellList": PropOrder._SPELL_LIST,
"action": PropOrder._ACTION,
"foundryAction": PropOrder._FOUNDRY_GENERIC,
"adventure": PropOrder._ADVENTURE,
"adventureData": PropOrder._ADVENTURE_DATA,
"book": PropOrder._BOOK,
@@ -1947,6 +2027,7 @@ PropOrder._PROP_TO_LIST = {
"class": PropOrder._CLASS,
"foundryClass": PropOrder._FOUNDRY_CLASS,
"subclass": PropOrder._SUBCLASS,
"foundrySubclass": PropOrder._FOUNDRY_SUBCLASS,
"classFeature": PropOrder._CLASS_FEATURE,
"subclassFeature": PropOrder._SUBCLASS_FEATURE,
"foundryClassFeature": PropOrder._FOUNDRY_CLASS_FEATURE,
@@ -1961,21 +2042,28 @@ PropOrder._PROP_TO_LIST = {
"boon": PropOrder._BOON,
"deity": PropOrder._DEITY,
"feat": PropOrder._FEAT,
"foundryFeat": PropOrder._FOUNDRY_FEAT,
"foundryFeat": PropOrder._FOUNDRY_GENERIC_FEATURE,
"vehicle": PropOrder._VEHICLE,
"vehicleUpgrade": PropOrder._VEHICLE_UPGRADE,
"foundryVehicleUpgrade": PropOrder._FOUNDRY_GENERIC_FEATURE,
"item": PropOrder._ITEM,
"foundryItem": PropOrder._FOUNDRY_GENERIC,
"baseitem": PropOrder._ITEM,
"magicvariant": PropOrder._MAGICVARIANT,
"foundryMagicvariant": PropOrder._FOUNDRY_GENERIC,
"itemGroup": PropOrder._ITEM,
"itemMastery": PropOrder._ITEM_MASTERY,
"object": PropOrder._OBJECT,
"optionalfeature": PropOrder._OPTIONALFEATURE,
"foundryOptionalfeature": PropOrder._FOUNDRY_GENERIC_FEATURE,
"psionic": PropOrder._PSIONIC,
"foundryPsionic": PropOrder._FOUNDRY_GENERIC_FEATURE,
"reward": PropOrder._REWARD,
"foundryReward": PropOrder._FOUNDRY_GENERIC_FEATURE,
"variantrule": PropOrder._VARIANTRULE,
"spellFluff": PropOrder._GENERIC_FLUFF,
"race": PropOrder._RACE,
"foundryRace": PropOrder._FOUNDRY_GENERIC_FEATURE,
"subrace": PropOrder._SUBRACE,
"foundryRaceFeature": PropOrder._FOUNDRY_RACE_FEATURE,
"table": PropOrder._TABLE,

View File

@@ -1406,7 +1406,7 @@ class TabUiUtilSide extends TabUiUtilBase {
super.decorate(obj, {isInitMeta});
obj.__$getBtnTab = function ({isSingleTab, tabMeta, _propProxy, propActive, ixTab}) {
return isSingleTab ? null : $(`<button class="btn btn-default btn-sm ui-tab-side__btn-tab mb-2 br-0 btr-0 bbr-0 text-left ve-flex-v-center" title="${tabMeta.name.qq()}"><div class="${tabMeta.icon} ui-tab-side__icon-tab mr-2 mobile-ish__mr-0 ve-text-center"></div><div class="mobile-ish__hidden">${tabMeta.name.qq()}</div></button>`)
return isSingleTab ? null : $(`<button class="btn btn-default btn-sm ui-tab-side__btn-tab mb-2 br-0 btr-0 bbr-0 text-left ve-flex-v-center" title="${tabMeta.name.qq()}"><div class="${tabMeta.icon} ui-tab-side__icon-tab mr-2 mobile-lg__mr-0 ve-text-center"></div><div class="mobile-lg__hidden">${tabMeta.name.qq()}</div></button>`)
.click(() => this[_propProxy][propActive] = ixTab);
};
@@ -3496,11 +3496,14 @@ class SourceUiUtil {
$iptJson.removeClass("form-control--error");
});
if (options.source) $iptJson.val(options.source.json);
const $iptVersion = $(`<input class="form-control ui-source__ipt-named">`)
.keydown(evt => { if (evt.key === "Escape") $iptUrl.blur(); });
if (options.source) $iptVersion.val(options.source.version);
let hasColor = false;
const $iptColor = $(`<input type="color" class="w-100 b-0">`)
.keydown(evt => { if (evt.key === "Escape") $iptColor.blur(); })
.change(() => hasColor = true);
if (options.source?.color != null) { hasColor = true; $iptColor.val(options.source.color); }
if (options.source?.color != null) { hasColor = true; $iptColor.val(`#${options.source.color}`); }
const $iptUrl = $(`<input class="form-control ui-source__ipt-named">`)
.keydown(evt => { if (evt.key === "Escape") $iptUrl.blur(); });
if (options.source) $iptUrl.val(options.source.url);
@@ -3531,11 +3534,19 @@ class SourceUiUtil {
json: jsonVal,
abbreviation: $iptAbv.val().trim(),
full: $iptName.val().trim(),
url: $iptUrl.val().trim(),
authors: $iptAuthors.val().trim().split(",").map(it => it.trim()).filter(Boolean),
convertedBy: $iptConverters.val().trim().split(",").map(it => it.trim()).filter(Boolean),
version: $iptVersion.val().trim() || "1.0.0",
};
if (hasColor) source.color = $iptColor.val().trim();
const url = $iptUrl.val().trim();
if (url) source.url = url;
const authors = $iptAuthors.val().trim().split(",").map(it => it.trim()).filter(Boolean);
if (authors.length) source.authors = authors;
const convertedBy = $iptConverters.val().trim().split(",").map(it => it.trim()).filter(Boolean);
if (convertedBy.length) source.convertedBy = convertedBy;
if (hasColor) source.color = $iptColor.val().trim().replace(/^#/, "");
await options.cbConfirm(source, options.mode !== "edit");
});
@@ -3567,6 +3578,10 @@ class SourceUiUtil {
<span class="mr-2 ui-source__name help" title="This will be used to identify your homebrew universally, so should be unique to you and you alone">JSON Identifier</span>
${$iptJson}
</div></div>
<div class="ui-source__row mb-2"><div class="ve-col-12 ve-flex-v-center">
<span class="mr-2 ui-source__name help" title="A version identifier, e.g. &quot;1.0.0&quot; or &quot;draft 1&quot;">Version</span>
${$iptVersion}
</div></div>
<div class="ui-source__row mb-2"><div class="ve-col-12 ve-flex-v-center">
<span class="mr-2 ui-source__name help" title="A color which should be used when displaying the source abbreviation">Color</span>
${$iptColor}

View File

@@ -2,7 +2,7 @@
// in deployment, `IS_DEPLOYED = "<version number>";` should be set below.
globalThis.IS_DEPLOYED = undefined;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.201.1"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.202.0"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.DEPLOYED_IMG_ROOT = undefined;
// for the roll20 script to set
globalThis.IS_VTT = false;
@@ -1293,12 +1293,12 @@ globalThis.ObjUtil = {
},
};
// TODO refactor other misc utils into this
globalThis.MiscUtil = {
COLOR_HEALTHY: "#00bb20",
COLOR_HURT: "#c5ca00",
COLOR_BLOODIED: "#f7a100",
COLOR_DEFEATED: "#cc0000",
// TODO refactor specific utils out of this
globalThis.MiscUtil = class {
static COLOR_HEALTHY = "#00bb20";
static COLOR_HURT = "#c5ca00";
static COLOR_BLOODIED = "#f7a100";
static COLOR_DEFEATED = "#cc0000";
/**
* @param obj
@@ -1306,12 +1306,12 @@ globalThis.MiscUtil = {
* @param isPreserveUndefinedValueKeys Otherwise, drops the keys of `undefined` values
* (e.g. `{a: undefined}` -> `{}`).
*/
copy (obj, {isSafe = false, isPreserveUndefinedValueKeys = false} = {}) {
static copy (obj, {isSafe = false, isPreserveUndefinedValueKeys = false} = {}) {
if (isSafe && obj === undefined) return undefined; // Generally use "unsafe," as this helps identify bugs.
return JSON.parse(JSON.stringify(obj));
},
}
copyFast (obj) {
static copyFast (obj) {
if ((typeof obj !== "object") || obj == null) return obj;
if (obj instanceof Array) return obj.map(MiscUtil.copyFast);
@@ -1319,9 +1319,9 @@ globalThis.MiscUtil = {
const cpy = {};
for (const k of Object.keys(obj)) cpy[k] = MiscUtil.copyFast(obj[k]);
return cpy;
},
}
async pCopyTextToClipboard (text) {
static async pCopyTextToClipboard (text) {
function doCompatibilityCopy () {
const $iptTemp = $(`<textarea class="clp__wrp-temp"></textarea>`)
.appendTo(document.body)
@@ -1339,26 +1339,26 @@ globalThis.MiscUtil = {
} else doCompatibilityCopy();
} catch (e) { doCompatibilityCopy(); }
} else doCompatibilityCopy();
},
}
checkProperty (object, ...path) {
static checkProperty (object, ...path) {
for (let i = 0; i < path.length; ++i) {
object = object[path[i]];
if (object == null) return false;
}
return true;
},
}
get (object, ...path) {
static get (object, ...path) {
if (object == null) return null;
for (let i = 0; i < path.length; ++i) {
object = object[path[i]];
if (object == null) return object;
}
return object;
},
}
set (object, ...pathAndVal) {
static set (object, ...pathAndVal) {
if (object == null) return null;
const val = pathAndVal.pop();
@@ -1372,31 +1372,31 @@ globalThis.MiscUtil = {
}
return val;
},
}
getOrSet (object, ...pathAndVal) {
static getOrSet (object, ...pathAndVal) {
if (pathAndVal.length < 2) return null;
const existing = MiscUtil.get(object, ...pathAndVal.slice(0, -1));
if (existing != null) return existing;
return MiscUtil.set(object, ...pathAndVal);
},
}
getThenSetCopy (object1, object2, ...path) {
static getThenSetCopy (object1, object2, ...path) {
const val = MiscUtil.get(object1, ...path);
return MiscUtil.set(object2, ...path, MiscUtil.copyFast(val, {isSafe: true}));
},
}
delete (object, ...path) {
static delete (object, ...path) {
if (object == null) return object;
for (let i = 0; i < path.length - 1; ++i) {
object = object[path[i]];
if (object == null) return object;
}
return delete object[path.last()];
},
}
/** Delete a prop from a nested object, then all now-empty objects backwards from that point. */
deleteObjectPath (object, ...path) {
static deleteObjectPath (object, ...path) {
const stack = [object];
if (object == null) return object;
@@ -1412,9 +1412,9 @@ globalThis.MiscUtil = {
}
return out;
},
}
merge (obj1, obj2) {
static merge (obj1, obj2) {
obj2 = MiscUtil.copyFast(obj2);
Object.entries(obj2)
@@ -1438,21 +1438,21 @@ globalThis.MiscUtil = {
});
return obj1;
},
}
/**
* @deprecated
*/
mix: (superclass) => new MiscUtil._MixinBuilder(superclass),
_MixinBuilder: function (superclass) {
static mix = (superclass) => new MiscUtil._MixinBuilder(superclass);
static _MixinBuilder = function (superclass) {
this.superclass = superclass;
this.with = function (...mixins) {
return mixins.reduce((c, mixin) => mixin(c), this.superclass);
};
},
};
clearSelection () {
static clearSelection () {
if (document.getSelection) {
document.getSelection().removeAllRanges();
document.getSelection().addRange(document.createRange());
@@ -1466,9 +1466,9 @@ globalThis.MiscUtil = {
} else if (document.selection) {
document.selection.empty();
}
},
}
randomColor () {
static randomColor () {
let r; let g; let b;
const h = RollerUtil.randomise(30, 0) / 30;
const i = ~~(h * 6);
@@ -1483,7 +1483,7 @@ globalThis.MiscUtil = {
case 5: r = 1; g = 0; b = q; break;
}
return `#${`00${(~~(r * 255)).toString(16)}`.slice(-2)}${`00${(~~(g * 255)).toString(16)}`.slice(-2)}${`00${(~~(b * 255)).toString(16)}`.slice(-2)}`;
},
}
/**
* @param hex Original hex color.
@@ -1492,7 +1492,7 @@ globalThis.MiscUtil = {
* @param [opts.dark] Color to return if a "dark" color would contrast best.
* @param [opts.light] Color to return if a "light" color would contrast best.
*/
invertColor (hex, opts) {
static invertColor (hex, opts) {
opts = opts || {};
hex = hex.slice(1); // remove #
@@ -1508,18 +1508,18 @@ globalThis.MiscUtil = {
r = (255 - r).toString(16); g = (255 - g).toString(16); b = (255 - b).toString(16);
return `#${[r, g, b].map(it => it.padStart(2, "0")).join("")}`;
},
}
scrollPageTop () {
static scrollPageTop () {
document.body.scrollTop = document.documentElement.scrollTop = 0;
},
}
expEval (str) {
static expEval (str) {
// eslint-disable-next-line no-new-func
return new Function(`return ${str.replace(/[^-()\d/*+.]/g, "")}`)();
},
}
parseNumberRange (input, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
static parseNumberRange (input, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
if (!input || !input.trim()) return null;
const errInvalid = input => { throw new Error(`Could not parse range input "${input}"`); };
@@ -1563,9 +1563,9 @@ globalThis.MiscUtil = {
}
return out;
},
}
findCommonPrefix (strArr, {isRespectWordBoundaries} = {}) {
static findCommonPrefix (strArr, {isRespectWordBoundaries} = {}) {
if (isRespectWordBoundaries) {
return MiscUtil._findCommonPrefixSuffixWords({strArr});
}
@@ -1588,15 +1588,15 @@ globalThis.MiscUtil = {
}
});
return prefix;
},
}
findCommonSuffix (strArr, {isRespectWordBoundaries} = {}) {
static findCommonSuffix (strArr, {isRespectWordBoundaries} = {}) {
if (!isRespectWordBoundaries) throw new Error(`Unimplemented!`);
return MiscUtil._findCommonPrefixSuffixWords({strArr, isSuffix: true});
},
}
_findCommonPrefixSuffixWords ({strArr, isSuffix}) {
static _findCommonPrefixSuffixWords ({strArr, isSuffix}) {
let prefixTks = null;
let lenMax = -1;
@@ -1633,18 +1633,18 @@ globalThis.MiscUtil = {
return isSuffix
? ` ${prefixTks.join(" ")}`
: `${prefixTks.join(" ")} `;
},
}
/**
* @param fgHexTarget Target/resultant color for the foreground item
* @param fgOpacity Desired foreground transparency (0-1 inclusive)
* @param bgHex Background color
*/
calculateBlendedColor (fgHexTarget, fgOpacity, bgHex) {
static calculateBlendedColor (fgHexTarget, fgOpacity, bgHex) {
const fgDcTarget = CryptUtil.hex2Dec(fgHexTarget);
const bgDc = CryptUtil.hex2Dec(bgHex);
return ((fgDcTarget - ((1 - fgOpacity) * bgDc)) / fgOpacity).toString(16);
},
}
/**
* Borrowed from lodash.
@@ -1654,7 +1654,7 @@ globalThis.MiscUtil = {
* @param options Options object.
* @return {Function} The debounced function.
*/
debounce (func, wait, options) {
static debounce (func, wait, options) {
let lastArgs; let lastThis; let maxWait; let result; let timerId; let lastCallTime; let lastInvokeTime = 0; let leading = false; let maxing = false; let trailing = true;
wait = Number(wait) || 0;
@@ -1739,10 +1739,10 @@ globalThis.MiscUtil = {
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
},
}
// from lodash
throttle (func, wait, options) {
static throttle (func, wait, options) {
let leading = true; let trailing = true;
if (typeof options === "object") {
@@ -1751,13 +1751,13 @@ globalThis.MiscUtil = {
}
return this.debounce(func, wait, {leading, maxWait: wait, trailing});
},
}
pDelay (msecs, resolveAs) {
static pDelay (msecs, resolveAs) {
return new Promise(resolve => setTimeout(() => resolve(resolveAs), msecs));
},
}
GENERIC_WALKER_ENTRIES_KEY_BLOCKLIST: new Set(["caption", "type", "colLabels", "colLabelGroups", "name", "colStyles", "style", "shortName", "subclassShortName", "id", "path"]),
static GENERIC_WALKER_ENTRIES_KEY_BLOCKLIST = new Set(["caption", "type", "colLabels", "colLabelGroups", "name", "colStyles", "style", "shortName", "subclassShortName", "id", "path"]);
/**
* @param [opts]
@@ -1771,7 +1771,7 @@ globalThis.MiscUtil = {
* @param [opts.isNoModification] If the walker should not attempt to modify the data.
* @param [opts.isBreakOnReturn] If the walker should fast-exist on any handler returning a value.
*/
getWalker (opts) {
static getWalker (opts) {
opts = opts || {};
if (opts.isBreakOnReturn && !opts.isNoModification) throw new Error(`"isBreakOnReturn" may only be used in "isNoModification" mode!`);
@@ -1893,9 +1893,9 @@ globalThis.MiscUtil = {
};
return {walk: fn};
},
}
_getWalker_applyHandlers ({opts, handlers, obj, lastKey, stack}) {
static _getWalker_applyHandlers ({opts, handlers, obj, lastKey, stack}) {
handlers = handlers instanceof Array ? handlers : [handlers];
const didBreak = handlers.some(h => {
const out = h(obj, lastKey, stack);
@@ -1904,12 +1904,12 @@ globalThis.MiscUtil = {
});
if (didBreak) return VeCt.SYM_WALKER_BREAK;
return obj;
},
}
_getWalker_runHandlers ({handlers, obj, lastKey, stack}) {
static _getWalker_runHandlers ({handlers, obj, lastKey, stack}) {
handlers = handlers instanceof Array ? handlers : [handlers];
handlers.forEach(h => h(obj, lastKey, stack));
},
}
/**
* TODO refresh to match sync version
@@ -1923,7 +1923,7 @@ globalThis.MiscUtil = {
* @param [opts.isDepthFirst] If array/object recursion should occur before array/object primitive handling.
* @param [opts.isNoModification] If the walker should not attempt to modify the data.
*/
getAsyncWalker (opts) {
static getAsyncWalker (opts) {
opts = opts || {};
const keyBlocklist = opts.keyBlocklist || new Set();
@@ -2040,25 +2040,32 @@ globalThis.MiscUtil = {
};
return {pWalk: pFn};
},
}
async _getAsyncWalker_pApplyHandlers ({opts, handlers, obj, lastKey, stack}) {
static async _getAsyncWalker_pApplyHandlers ({opts, handlers, obj, lastKey, stack}) {
handlers = handlers instanceof Array ? handlers : [handlers];
await handlers.pSerialAwaitMap(async pH => {
const out = await pH(obj, lastKey, stack);
if (!opts.isNoModification) obj = out;
});
return obj;
},
}
async _getAsyncWalker_pRunHandlers ({handlers, obj, lastKey, stack}) {
static async _getAsyncWalker_pRunHandlers ({handlers, obj, lastKey, stack}) {
handlers = handlers instanceof Array ? handlers : [handlers];
await handlers.pSerialAwaitMap(pH => pH(obj, lastKey, stack));
},
}
pDefer (fn) {
static pDefer (fn) {
return (async () => fn())();
},
}
static isNearStrictlyEqual (a, b) {
if (a == null && b == null) return true;
if (a == null && b != null) return false;
if (a != null && b == null) return false;
return a === b;
}
};
// EVENT HANDLERS ======================================================================================================

View File

@@ -1,12 +1,6 @@
"use strict";
class VariantRulesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subvariantrules",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -58,8 +52,6 @@ class VariantRulesPage extends ListPage {
pageFilter,
listClass: "variantrules",
dataProps: ["variantrule"],
isMarkdownPopout: true,

View File

@@ -1,12 +1,6 @@
"use strict";
class VehiclesSublistManager extends SublistManager {
constructor () {
super({
sublistClass: "subvehicles",
});
}
static get _ROW_TEMPLATE () {
return [
new SublistCellTemplate({
@@ -66,8 +60,6 @@ class VehiclesPage extends ListPage {
pageFilter,
listClass: "vehicles",
dataProps: ["vehicle", "vehicleUpgrade"],
isMarkdownPopout: true,