This commit is contained in:
TheGiddyLimit
2024-02-19 22:18:48 +00:00
parent 661a119c6d
commit b323d4123e
157 changed files with 43972 additions and 697 deletions

View File

@@ -1363,10 +1363,14 @@ class CreatureParser extends BaseParser {
static _doStatblockPostProcess (stats, isMarkdown, options) {
this._doFilterAddSpellcasting(stats, "trait", isMarkdown, options);
this._doFilterAddSpellcasting(stats, "action", isMarkdown, options);
if (stats.trait) stats.trait.forEach(it => RechargeConvert.tryConvertRecharge(it, () => {}, () => options.cbWarning(`${stats.name ? `(${stats.name}) ` : ""}Manual recharge tagging required for trait "${it.name}"`)));
if (stats.action) stats.action.forEach(it => RechargeConvert.tryConvertRecharge(it, () => {}, () => options.cbWarning(`${stats.name ? `(${stats.name}) ` : ""}Manual recharge tagging required for action "${it.name}"`)));
if (stats.bonus) stats.bonus.forEach(it => RechargeConvert.tryConvertRecharge(it, () => {}, () => options.cbWarning(`${stats.name ? `(${stats.name}) ` : ""}Manual recharge tagging required for bonus action "${it.name}"`)));
CreatureParser._PROPS_ENTRIES.filter(prop => stats[prop]).forEach(prop => SpellTag.tryRun(stats[prop]));
CreatureParser._PROPS_ENTRIES
.filter(prop => stats[prop])
.forEach(prop => {
stats[prop].forEach(it => RechargeConvert.tryConvertRecharge(it, () => {}, () => options.cbWarning(`${stats.name ? `(${stats.name}) ` : ""}Manual recharge tagging required for ${prop} "${it.name}"`)));
});
CreatureParser._PROPS_ENTRIES
.filter(prop => stats[prop])
.forEach(prop => SpellTag.tryRun(stats[prop]));
AcConvert.tryPostProcessAc(
stats,
(ac) => options.cbWarning(`${stats.name ? `(${stats.name}) ` : ""}AC "${ac}" requires manual conversion`),
@@ -1390,6 +1394,7 @@ class CreatureParser extends BaseParser {
isTagInflicted: true,
},
);
CreatureSpecialEquipmentTagger.tryRun(stats);
TraitActionTag.tryRun(stats);
LanguageTag.tryRun(stats);
SenseFilterTag.tryRun(stats);
@@ -1400,6 +1405,7 @@ class CreatureParser extends BaseParser {
CreatureSavingThrowTagger.tryRun(stats);
CreatureSavingThrowTagger.tryRunSpells(stats);
CreatureSavingThrowTagger.tryRunRegionalsLairs(stats);
SkillTag.tryRunProps(stats, {props: Renderer.monster.CHILD_PROPS_EXTENDED});
MiscTag.tryRun(stats);
DetectNamedCreature.tryRun(stats);
TagImmResVulnConditional.tryRun(stats);
@@ -1831,13 +1837,7 @@ class CreatureParser extends BaseParser {
}
// endregion
}
CreatureParser._PROPS_ENTRIES = [
"trait",
"action",
"bonus",
"reaction",
"legendary",
"mythic",
];
CreatureParser._PROPS_ENTRIES = Renderer.monster.CHILD_PROPS_EXTENDED.filter(it => it !== "spellcasting");
globalThis.CreatureParser = CreatureParser;

View File

@@ -1908,3 +1908,16 @@ class CreatureSavingThrowTagger extends _PrimaryLegendarySpellsTaggerBase {
}
globalThis.CreatureSavingThrowTagger = CreatureSavingThrowTagger;
class CreatureSpecialEquipmentTagger {
static tryRun (mon) {
if (!mon.trait) return;
mon.trait = mon.trait
.map(ent => {
if (!/\bEquipment\b/.test(ent.name || "")) return ent;
return ItemTag.tryRun(ent);
});
}
}
globalThis.CreatureSpecialEquipmentTagger = CreatureSpecialEquipmentTagger;

View File

@@ -196,15 +196,19 @@ class ItemTag {
// endregion
// region Other items
const otherItems = standardItems.filter(it => {
if (toolTypes.has(it.type)) return false;
// Disallow specific items
if (it.name === "Wave" && it.source === Parser.SRC_DMG) return false;
// Allow all non-specific-variant DMG items
if (it.source === Parser.SRC_DMG && !Renderer.item.isMundane(it) && it._category !== "Specific Variant") return true;
// Allow "sufficiently complex name" items
return it.name.split(" ").length > 2;
});
const otherItems = standardItems
.filter(it => {
if (toolTypes.has(it.type)) return false;
// Disallow specific items
if (it.name === "Wave" && it.source === Parser.SRC_DMG) return false;
// Allow all non-specific-variant DMG items
if (it.source === Parser.SRC_DMG && !Renderer.item.isMundane(it) && it._category !== "Specific Variant") return true;
// Allow "sufficiently complex name" items
return it.name.split(" ").length > 2;
})
// Prefer specific variants first, as they have longer names
.sort((itemA, itemB) => Number(itemB._category === "Specific Variant") - Number(itemA._category === "Specific Variant") || SortUtil.ascSortLower(itemA.name, itemB.name))
;
otherItems.forEach(it => {
this._ITEM_NAMES[it.name.toLowerCase()] = {name: it.name, source: it.source};
});
@@ -236,7 +240,7 @@ class ItemTag {
0,
str,
{
fnTag: this._fnTag,
fnTag: this._fnTag.bind(this),
},
);
return ptrStack._;
@@ -273,7 +277,7 @@ class ItemTag {
0,
str,
{
fnTag: this._fnTagBasicEquipment,
fnTag: this._fnTagBasicEquipment.bind(this),
},
);
return ptrStack._;

View File

@@ -809,6 +809,12 @@ class SkillTag {
static _fnTag (strMod) {
return strMod.replace(/\b(Acrobatics|Animal Handling|Arcana|Athletics|Deception|History|Insight|Intimidation|Investigation|Medicine|Nature|Perception|Performance|Persuasion|Religion|Sleight of Hand|Stealth|Survival)\b/g, (...m) => `{@skill ${m[1]}}`);
}
static tryRunProps (ent, {props} = {}) {
props
.filter(prop => ent[prop])
.forEach(prop => this.tryRun(ent[prop]));
}
}
class ActionTag {

View File

@@ -1816,75 +1816,69 @@ Parser.charCreationOptionTypeToFull = function (type) {
return type;
};
Parser._ALIGNMENT_ABV_TO_FULL = {
"L": "lawful",
"N": "neutral",
"NX": "neutral (law/chaos axis)",
"NY": "neutral (good/evil axis)",
"C": "chaotic",
"G": "good",
"E": "evil",
// "special" values
"U": "unaligned",
"A": "any alignment",
};
Parser.alignmentAbvToFull = function (alignment) {
if (!alignment) return null; // used in sidekicks
if (typeof alignment === "object") {
if (alignment.special != null) {
// use in MTF Sacred Statue
return alignment.special;
} else {
// e.g. `{alignment: ["N", "G"], chance: 50}` or `{alignment: ["N", "G"]}`
return `${alignment.alignment.map(a => Parser.alignmentAbvToFull(a)).join(" ")}${alignment.chance ? ` (${alignment.chance}%)` : ""}${alignment.note ? ` (${alignment.note})` : ""}`;
}
} else {
alignment = alignment.toUpperCase();
switch (alignment) {
case "L":
return "lawful";
case "N":
return "neutral";
case "NX":
return "neutral (law/chaos axis)";
case "NY":
return "neutral (good/evil axis)";
case "C":
return "chaotic";
case "G":
return "good";
case "E":
return "evil";
// "special" values
case "U":
return "unaligned";
case "A":
return "any alignment";
}
return alignment;
// use in MTF Sacred Statue
if (alignment.special != null) return alignment.special;
// e.g. `{alignment: ["N", "G"], chance: 50}` or `{alignment: ["N", "G"]}`
return `${Parser.alignmentListToFull(alignment.alignment)}${alignment.chance ? ` (${alignment.chance}%)` : ""}${alignment.note ? ` (${alignment.note})` : ""}`;
}
alignment = alignment.toUpperCase();
return Parser._ALIGNMENT_ABV_TO_FULL[alignment] ?? alignment;
};
Parser.alignmentListToFull = function (alignList) {
if (!alignList) return "";
if (alignList.some(it => typeof it !== "string")) {
if (alignList.some(it => typeof it === "string")) throw new Error(`Mixed alignment types: ${JSON.stringify(alignList)}`);
// filter out any nonexistent alignments, as we don't care about "alignment does not exist" if there are other alignments
alignList = alignList.filter(it => it.alignment === undefined || it.alignment != null);
return alignList.map(it => it.special != null || it.chance != null || it.note != null ? Parser.alignmentAbvToFull(it) : Parser.alignmentListToFull(it.alignment)).join(" or ");
} else {
// assume all single-length arrays can be simply parsed
if (alignList.length === 1) return Parser.alignmentAbvToFull(alignList[0]);
// a pair of abv's, e.g. "L" "G"
if (alignList.length === 2) {
return alignList.map(a => Parser.alignmentAbvToFull(a)).join(" ");
}
if (alignList.length === 3) {
if (alignList.includes("NX") && alignList.includes("NY") && alignList.includes("N")) return "any neutral alignment";
}
// longer arrays should have a custom mapping
if (alignList.length === 5) {
if (!alignList.includes("G")) return "any non-good alignment";
if (!alignList.includes("E")) return "any non-evil alignment";
if (!alignList.includes("L")) return "any non-lawful alignment";
if (!alignList.includes("C")) return "any non-chaotic alignment";
}
if (alignList.length === 4) {
if (!alignList.includes("L") && !alignList.includes("NX")) return "any chaotic alignment";
if (!alignList.includes("G") && !alignList.includes("NY")) return "any evil alignment";
if (!alignList.includes("C") && !alignList.includes("NX")) return "any lawful alignment";
if (!alignList.includes("E") && !alignList.includes("NY")) return "any good alignment";
}
throw new Error(`Unmapped alignment: ${JSON.stringify(alignList)}`);
return alignList
.filter(it => it.alignment === undefined || it.alignment != null)
.map(it => it.special != null || it.chance != null || it.note != null ? Parser.alignmentAbvToFull(it) : Parser.alignmentListToFull(it.alignment)).join(" or ");
}
// assume all single-length arrays can be simply parsed
if (alignList.length === 1) return Parser.alignmentAbvToFull(alignList[0]);
// a pair of abv's, e.g. "L" "G"
if (alignList.length === 2) {
return alignList.map(a => Parser.alignmentAbvToFull(a)).join(" ");
}
if (alignList.length === 3) {
if (alignList.includes("NX") && alignList.includes("NY") && alignList.includes("N")) return "any neutral alignment";
}
// longer arrays should have a custom mapping
if (alignList.length === 5) {
if (!alignList.includes("G")) return "any non-good alignment";
if (!alignList.includes("E")) return "any non-evil alignment";
if (!alignList.includes("L")) return "any non-lawful alignment";
if (!alignList.includes("C")) return "any non-chaotic alignment";
}
if (alignList.length === 4) {
if (!alignList.includes("L") && !alignList.includes("NX")) return "any chaotic alignment";
if (!alignList.includes("G") && !alignList.includes("NY")) return "any evil alignment";
if (!alignList.includes("C") && !alignList.includes("NX")) return "any lawful alignment";
if (!alignList.includes("E") && !alignList.includes("NY")) return "any good alignment";
}
throw new Error(`Unmapped alignment: ${JSON.stringify(alignList)}`);
};
Parser.weightToFull = function (lbs, isSmallUnit) {
@@ -2646,8 +2640,11 @@ Parser.SRC_SatO = "SatO";
Parser.SRC_ToFW = "ToFW";
Parser.SRC_MPP = "MPP";
Parser.SRC_BMT = "BMT";
Parser.SRC_DMTCRG = "DMTCRG";
Parser.SRC_GHLoE = "GHLoE";
Parser.SRC_DoDk = "DoDk";
Parser.SRC_HWCS = "HWCS";
Parser.SRC_HWAitW = "HWAitW";
Parser.SRC_SCREEN = "Screen";
Parser.SRC_SCREEN_WILDERNESS_KIT = "ScreenWildernessKit";
Parser.SRC_SCREEN_DUNGEON_KIT = "ScreenDungeonKit";
@@ -2818,8 +2815,11 @@ Parser.SOURCE_JSON_TO_FULL[Parser.SRC_SatO] = "Sigil and the Outlands";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_ToFW] = "Turn of Fortune's Wheel";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_MPP] = "Morte's Planar Parade";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_BMT] = "The Book of Many Things";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_DMTCRG] = "The Deck of Many Things: Card Reference Guide";
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_SCREEN] = "Dungeon Master's Screen";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_SCREEN_WILDERNESS_KIT] = "Dungeon Master's Screen: Wilderness Kit";
Parser.SOURCE_JSON_TO_FULL[Parser.SRC_SCREEN_DUNGEON_KIT] = "Dungeon Master's Screen: Dungeon Kit";
@@ -2965,8 +2965,11 @@ Parser.SOURCE_JSON_TO_ABV[Parser.SRC_SatO] = "SatO";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_ToFW] = "ToFW";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_MPP] = "MPP";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_BMT] = "BMT";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_DMTCRG] = "DMTCRG";
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_SCREEN] = "Screen";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_SCREEN_WILDERNESS_KIT] = "ScWild";
Parser.SOURCE_JSON_TO_ABV[Parser.SRC_SCREEN_DUNGEON_KIT] = "ScDun";
@@ -3111,8 +3114,11 @@ Parser.SOURCE_JSON_TO_DATE[Parser.SRC_SatO] = "2023-10-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_ToFW] = "2023-10-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_MPP] = "2023-10-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_BMT] = "2023-11-14";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_DMTCRG] = "2023-11-14";
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_SCREEN] = "2015-01-20";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_SCREEN_WILDERNESS_KIT] = "2020-11-17";
Parser.SOURCE_JSON_TO_DATE[Parser.SRC_SCREEN_DUNGEON_KIT] = "2020-09-21";
@@ -3237,6 +3243,7 @@ Parser.SOURCES_ADVENTURES = new Set([
Parser.SRC_HFStCM,
Parser.SRC_GHLoE,
Parser.SRC_DoDk,
Parser.SRC_HWAitW,
Parser.SRC_AWM,
]);
@@ -3298,6 +3305,8 @@ Parser.SOURCES_PARTNERED_WOTC = new Set([
Parser.SRC_HftT,
Parser.SRC_GHLoE,
Parser.SRC_DoDk,
Parser.SRC_HWCS,
Parser.SRC_HWAitW,
]);
// region Source categories
@@ -3326,6 +3335,7 @@ Parser.SOURCES_VANILLA = new Set([
Parser.SRC_MaBJoV,
Parser.SRC_CoA,
Parser.SRC_BMT,
Parser.SRC_DMTCRG,
]);
// Any opinionated set of sources that are """hilarious, dude"""
@@ -3380,6 +3390,8 @@ Parser.SOURCES_NON_FR = new Set([
Parser.SRC_LK,
Parser.SRC_GHLoE,
Parser.SRC_DoDk,
Parser.SRC_HWCS,
Parser.SRC_HWAitW,
]);
// endregion
@@ -3420,6 +3432,8 @@ Parser.SOURCES_AVAILABLE_DOCS_BOOK = {};
Parser.SRC_HF,
Parser.SRC_HFFotM,
Parser.SRC_BMT,
Parser.SRC_DMTCRG,
Parser.SRC_HWCS,
].forEach(src => {
Parser.SOURCES_AVAILABLE_DOCS_BOOK[src] = src;
Parser.SOURCES_AVAILABLE_DOCS_BOOK[src.toLowerCase()] = src;
@@ -3513,6 +3527,7 @@ Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE = {};
Parser.SRC_HFStCM,
Parser.SRC_GHLoE,
Parser.SRC_DoDk,
Parser.SRC_HWAitW,
].forEach(src => {
Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE[src] = src;
Parser.SOURCES_AVAILABLE_DOCS_ADVENTURE[src.toLowerCase()] = src;

View File

@@ -6682,6 +6682,9 @@ Renderer.deity = class {
"province": {
name: "Province",
},
"dogma": {
name: "Dogma",
},
"altNames": {
name: "Alternate Names",
displayFn: (it) => it.join(", "),
@@ -9237,7 +9240,7 @@ Renderer.item = class {
static _pPopulatePropertyAndTypeReference = null;
static pPopulatePropertyAndTypeReference () {
return Renderer.item._pPopulatePropertyAndTypeReference || (async () => {
Renderer.item._pPopulatePropertyAndTypeReference ||= (async () => {
const data = await DataUtil.loadJSON(`${Renderer.get().baseUrl}data/items-base.json`);
Object.entries(Parser.ITEM_TYPE_JSON_TO_ABV).forEach(([abv, name]) => Renderer.item._addType({abbreviation: abv, name}));
@@ -9248,6 +9251,8 @@ Renderer.item = class {
await Renderer.item._pAddPrereleaseBrewPropertiesAndTypes();
})();
return Renderer.item._pPopulatePropertyAndTypeReference;
}
// fetch every possible indexable item from official data

View File

@@ -505,6 +505,7 @@ PropOrder._ADVENTURE = [
"coverUrl",
"published",
"publishedOrder",
"author",
"storyline",
"level",
@@ -1033,6 +1034,7 @@ PropOrder._DEITY = [
"category",
"domains",
"province",
"dogma",
"symbol",
"symbolImg",

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.199.3"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.200.0"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.DEPLOYED_IMG_ROOT = undefined;
// for the roll20 script to set
globalThis.IS_VTT = false;