From cac8a1747834e9c7e5ef31346f8720393dd20a4c Mon Sep 17 00:00:00 2001 From: darthbeep Date: Sat, 3 Sep 2022 23:29:18 -0400 Subject: [PATCH 1/6] created a working prototype --- js/5etools-classes.js | 94 +++++++++-------- js/5etools-tool.js | 239 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 41 deletions(-) diff --git a/js/5etools-classes.js b/js/5etools-classes.js index e4a029d6..260054e0 100644 --- a/js/5etools-classes.js +++ b/js/5etools-classes.js @@ -21,6 +21,25 @@ function d20plusClass () { } }; + d20plus.classes.getDataForImport = async function (oldData) { + await d20plus.importer.pAddBrew(oldData); + const data = MiscUtil.copy(oldData); + data.class = data.class || []; + for (let i = 0; i < data.class.length; ++i) { + data.class[i] = await DataUtil.class.pGetDereferencedClassData(data.class[i]); + } + + if (data.subclass) { + for (let i = 0; i < data.subclass.length; ++i) { + data.subclass[i] = await DataUtil.class.pGetDereferencedSubclassData(data.subclass[i]); + } + } + + d20plus.classes._doAttachChildSubclasses(data); + + return data; + } + // Import Classes button was clicked d20plus.classes.button = function (forcePlayer) { const playerMode = forcePlayer || !window.is_gm; @@ -30,23 +49,9 @@ function d20plusClass () { const officialClassUrls = Object.values(classDataUrls).map(v => d20plus.formSrcUrl(CLASS_DATA_DIR, v)); DataUtil.loadJSON(url).then(async (data) => { - await d20plus.importer.pAddBrew(data); - if (!data.class) return; - data = MiscUtil.copy(data); - data.class = data.class || []; - for (let i = 0; i < data.class.length; ++i) { - data.class[i] = await DataUtil.class.pGetDereferencedClassData(data.class[i]); - } - - if (data.subclass) { - for (let i = 0; i < data.subclass.length; ++i) { - data.subclass[i] = await DataUtil.class.pGetDereferencedSubclassData(data.subclass[i]); - } - } - - d20plus.classes._doAttachChildSubclasses(data); + data = await d20plus.classes.getDataForImport(data); d20plus.importer.showImportList( "class", @@ -503,6 +508,37 @@ function d20plusClass () { source: Parser.sourceJsonToAbv(sc.source).toLowerCase(), }; }; + + d20plus.subclasses.getDataForImport = async function (data) { + await d20plus.importer.pAddBrew(data); + + data = MiscUtil.copy(data); + for (let i = 0; i < (data.class || []).length; ++i) { + data.class[i] = await DataUtil.class.pGetDereferencedClassData(data.class[i]); + } + for (let i = 0; i < (data.subclass || []).length; ++i) { + data.subclass[i] = await DataUtil.class.pGetDereferencedSubclassData(data.subclass[i]); + } + + // merge in any subclasses contained in class data + const allData = MiscUtil.copy(data.subclass || []); + (data.class || []).map(c => { + if (c.subclasses) { + // make a copy without subclasses to prevent circular references + const cpy = MiscUtil.copy(c); + delete cpy.subclasses; + c.subclasses.forEach(sc => { + sc.className = c.name; + sc.source = sc.source || c.source; + sc._baseClass = cpy; + }); + return c.subclasses; + } else return false; + }).filter(Boolean).forEach(sc => allData.push(sc)); + + return allData.flat(); + } + // Import Subclasses button was clicked d20plus.subclasses.button = function (forcePlayer) { const playerMode = forcePlayer || !window.is_gm; @@ -511,35 +547,11 @@ function d20plusClass () { const handoutBuilder = playerMode ? d20plus.subclasses.playerImportBuilder : d20plus.subclasses.handoutBuilder; DataUtil.loadJSON(url).then(async (data) => { - await d20plus.importer.pAddBrew(data); - - data = MiscUtil.copy(data); - for (let i = 0; i < (data.class || []).length; ++i) { - data.class[i] = await DataUtil.class.pGetDereferencedClassData(data.class[i]); - } - for (let i = 0; i < (data.subclass || []).length; ++i) { - data.subclass[i] = await DataUtil.class.pGetDereferencedSubclassData(data.subclass[i]); - } - - // merge in any subclasses contained in class data - const allData = MiscUtil.copy(data.subclass || []); - (data.class || []).map(c => { - if (c.subclasses) { - // make a copy without subclasses to prevent circular references - const cpy = MiscUtil.copy(c); - delete cpy.subclasses; - c.subclasses.forEach(sc => { - sc.className = c.name; - sc.source = sc.source || c.source; - sc._baseClass = cpy; - }); - return c.subclasses; - } else return false; - }).filter(Boolean).forEach(sc => allData.push(sc)); + const allData = await d20plus.subclasses.getDataForImport(data); d20plus.importer.showImportList( "subclass", - allData.flat(), + allData, handoutBuilder, { groupOptions: d20plus.subclasses._groupOptions, diff --git a/js/5etools-tool.js b/js/5etools-tool.js index 7a45d309..a175114a 100644 --- a/js/5etools-tool.js +++ b/js/5etools-tool.js @@ -1,6 +1,245 @@ function tools5eTool () { // Add the array of tools that are 5e only to the tools array d20plus.tool.tools = d20plus.tool.tools.concat([ + { + toolId: "JSON", + name: "JSON Importer", + desc: "Import 5etools JSON files from url or file upload", + html: ` +
+

+ Importer: + +

+

+
+ +

Load From URL

+

Load From File

+
+
+
+ + +
+ +
+
+
+

+
+ +
+
+ +
+ Load a file to view the contents here +
+
+
+ + +
+
+ +
+

+ +

Errors: 0

+

+
+ +
+

First, either load a module from 5etools, or upload one from a file. Then, choose the category you wish to import, and "View/Select Entries." Once you've selected everything you wish to import from the module, hit "Import Selected." This ensures entries are imported in the correct order.

+

Note: The script-wide configurable "rest time" options affect how quickly each category of entries is imported (tables and decks use the "Handout" rest time).

+

Note: Configuration options (aside from "rest time" as detailed above) do not affect the module importer. It effectively "clones" the content as-exported from the original module, including any whisper/advantage/etc settings.

+
+ `, + dialogFn: () => { + $("#d20plus-json-importer").dialog({ + autoOpen: false, + resizable: true, + width: 750, + height: 360, + }); + $(`#d20plus-json-importer-progress`).dialog({ + autoOpen: false, + resizable: false, + }); + $("#d20plus-json-importer-help").dialog({ + autoOpen: false, + resizable: true, + width: 600, + height: 400, + }); + $("#d20plus-json-importer-list").dialog({ + autoOpen: false, + resizable: true, + width: 600, + height: 800, + }); + }, + openFn: () => { + // The types of things that can be imported with this tool + const IMPORT_NAMES = { + "adventure": "Adventures", + "background": "Backgrounds", + "class": "Classes", + "deity": "Deities", + "feat": "Feats", + "item": "Items", + "monster": "Monsters", + "object": "Objects", + "optionalfeature": "Optional Features (Invocations, etc.)", + "psionic": "Psionics", + "race": "Races", + "spell": "Spells", + "subclass": "Subclasses", + "vehicle": "Vehicles", + }; + + const $win = $("#d20plus-json-importer"); + $win.dialog("open"); + + const $winProgress = $(`#d20plus-json-importer-progress`); + const $btnCancel = $winProgress.find(".cancel").off("click"); + + const $win5etools = $(`#d20plus-json-importer-5etools`); + + const $winHelp = $(`#d20plus-json-importer-help`); + const $btnHelp = $win.find(`.readme`).off("click").click(() => $winHelp.dialog("open")); + + const $winList = $(`#d20plus-json-importer-list`); + const $wrpLst = $(`#module-json-list`); + const $lst = $winList.find(`.list`).empty(); + const $cbAll = $winList.find(`.select-all`).off("click").prop("disabled", true); + const $iptSearch = $winList.find(`.search`).prop("disabled", true); + const $btnConfirmSel = $winList.find(`[name="confirm-selection"]`).off("click"); + + const $wrpSummary = $win.find(`[name="selection-summary"]`); + const $wrpDataLoadingMessage = $win.find(`[name="data-loading-message"]`); + + const $btnImport = $win.find(`[name="import"]`).off("click").prop("disabled", true); + const $btnViewCat = $win.find(`[name="view-select-entries"]`).off("click").click(handleLoadedData).prop("disabled", true); + const $btnSelAllContent = $win.find(`[name="select-all-entries"]`).off("click").prop("disabled", true); + + const $selDataType = $win.find(`[name="data-type"]`).prop("disabled", true); + let genericFolder; + let lastLoadedData = null; + + async function handleLoadedData () { + const lastDataType = $selDataType.val(); + let optionsContainer = null; + let overrideData = null; + let extraOptions = {}; + + switch (lastDataType) { + case "adventure": + optionsContainer = d20plus.adventures; + break; + case "background": + optionsContainer = d20plus.backgrounds; + break; + case "class": + optionsContainer = d20plus.classes; + overrideData = (await d20plus.classes.getDataForImport(lastLoadedData)).class; + extraOptions["builderOptions"] = { + isHomebrew: true, + } + break; + case "deity": + optionsContainer = d20plus.deities; + break; + case "feat": + optionsContainer = d20plus.feats; + break; + case "item": + optionsContainer = d20plus.items; + break; + case "monster": + optionsContainer = d20plus.monsters; + break; + case "object": + optionsContainer = d20plus.objects; + break; + case "optionalfeature": + optionsContainer = d20plus.optionalfeatures; + break; + case "psionic": + optionsContainer = d20plus.psionics; + break; + case "race": + optionsContainer = d20plus.races; + break; + case "spell": + optionsContainer = d20plus.spells; + break; + case "subclass": + optionsContainer = d20plus.subclasses; + overrideData = await d20plus.subclasses.getDataForImport(lastLoadedData); + break; + case "vehicle": + optionsContainer = d20plus.vehicles; + break; + default: + throw new Error(`Unhandled data type: ${lastDataType}`); + } + + const handoutBuilder = optionsContainer["handoutBuilder"]; + d20plus.importer.showImportList( + lastDataType, + overrideData || lastLoadedData[lastDataType], + handoutBuilder, + { + groupOptions: optionsContainer._groupOptions, + listItemBuilder: optionsContainer._listItemBuilder, + listIndex: optionsContainer._listCols, + listIndexConverter: optionsContainer._listIndexConverter, + ...extraOptions, + }, + ); + } + + function populateDropdown (cats) { + $selDataType.empty(); + cats.forEach(c => { + $selDataType.append(``) + }); + + // Disable buttons if there are no valid catagories to import + const disable = !cats.length; + $btnViewCat.prop("disabled", disable); + $btnSelAllContent.prop("disabled", disable); + $selDataType.prop("disabled", disable); + } + + // Load from url + const $btnLoadUrl = $(`#button-json-load-url`); + $btnLoadUrl.off("click").click(async () => { + const url = $("#import-json-url").val(); + if (url && url.trim()) { + DataUtil.loadJSON(url).then(data => { + const cats = Object.keys(IMPORT_NAMES).filter(i => i in data); + populateDropdown(cats); + lastLoadedData = data; + }); + } + }); + + // Load from file + const $btnLoadFile = $(`#button-json-load-file`); + $btnLoadFile.off("click").click(async () => { + const allFiles = await DataUtil.pUserUpload(); + // Due to the new util functon, need to account for data being an array + const data = allFiles.jsons.find(Boolean); + const cats = Object.keys(IMPORT_NAMES).filter(i => i in data); + populateDropdown(cats); + lastLoadedData = data; + }); + }, + }, { name: "Shapeshifter Token Builder", desc: "Build a rollable table and related token to represent a shapeshifting creature.", From b08f72c7cbf2fd941cae46764b07795a0614c52a Mon Sep 17 00:00:00 2001 From: darthbeep Date: Tue, 6 Sep 2022 20:49:50 -0400 Subject: [PATCH 2/6] moved around macros and templates --- js/5etools-importer.js | 2 +- js/5etools-main.js | 671 +++-------------------------------------- js/5etools-monsters.js | 25 +- js/5etools-objects.js | 2 +- js/5etools-template.js | 467 ++++++++++++++++++++++++++++ js/base-css.js | 107 +++++++ js/base-journal.js | 14 +- js/base-macro.js | 33 ++ node/build-scripts.js | 4 + 9 files changed, 670 insertions(+), 655 deletions(-) create mode 100644 js/5etools-template.js create mode 100644 js/base-macro.js diff --git a/js/5etools-importer.js b/js/5etools-importer.js index c6f4c75a..c736a5d8 100644 --- a/js/5etools-importer.js +++ b/js/5etools-importer.js @@ -236,7 +236,7 @@ function d20plusImporter () { character.abilities.create({ name: `${prefix + index}: ${name}`, istokenaction: true, - action: d20plus.actionMacroAction(baseAction, index), + action: d20plus.macro.actionMacroAction(baseAction, index), }).save(); } diff --git a/js/5etools-main.js b/js/5etools-main.js index dd3af418..60bcfcf9 100644 --- a/js/5etools-main.js +++ b/js/5etools-main.js @@ -442,6 +442,7 @@ const betteR205etoolsMain = function () { if (!players.length) return difficulty; // If a player doesn't have level set, fail out. if (partyXPThreshold !== null) { + const multipliers = [1, 1.5, 2, 2.5, 3, 4, 5]; let len = npcs.length; let multiplier = 0; let adjustedxp = 0; @@ -457,7 +458,7 @@ const betteR205etoolsMain = function () { // Adjust for smaller parties if (players.length < 3) index++; // Set multiplier - multiplier = d20plus.multipliers[index]; + multiplier = multipliers[index]; // Total monster xp $.each(npcs, function (i, v) { let cr = v.attribs.find(function (a) { @@ -586,25 +587,25 @@ const betteR205etoolsMain = function () { if (window.is_gm) { const $wrpSettings = $(`#betteR20-settings`); - $wrpSettings.append(d20plus.settingsHtmlImportHeader); - $wrpSettings.append(d20plus.settingsHtmlSelector); - $wrpSettings.append(d20plus.settingsHtmlPtMonsters); - $wrpSettings.append(d20plus.settingsHtmlPtItems); - $wrpSettings.append(d20plus.settingsHtmlPtSpells); - $wrpSettings.append(d20plus.settingsHtmlPtPsionics); - $wrpSettings.append(d20plus.settingsHtmlPtRaces); - $wrpSettings.append(d20plus.settingsHtmlPtFeats); - $wrpSettings.append(d20plus.settingsHtmlPtObjects); - $wrpSettings.append(d20plus.settingsHtmlPtVehicles); - $wrpSettings.append(d20plus.settingsHtmlPtClasses); - $wrpSettings.append(d20plus.settingsHtmlPtSubclasses); - $wrpSettings.append(d20plus.settingsHtmlPtBackgrounds); - $wrpSettings.append(d20plus.settingsHtmlPtOptfeatures); - $wrpSettings.append(d20plus.settingsHtmlPtDeities); - const $ptAdventures = $(d20plus.settingsHtmlPtAdventures); + $wrpSettings.append(d20plus.template5e.settingsHtmlImportHeader); + $wrpSettings.append(d20plus.template5e.settingsHtmlSelector); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtMonsters); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtItems); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtSpells); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtPsionics); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtRaces); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtFeats); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtObjects); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtVehicles); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtClasses); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtSubclasses); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtBackgrounds); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtOptfeatures); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtDeities); + const $ptAdventures = $(d20plus.template5e.settingsHtmlPtAdventures); $wrpSettings.append($ptAdventures); $ptAdventures.find(`.Vetools-module-tool-open`).click(() => d20plus.tool.get("MODULES").openFn()); - $wrpSettings.append(d20plus.settingsHtmlPtImportFooter); + $wrpSettings.append(d20plus.template5e.settingsHtmlPtImportFooter); $("#button-monsters-load").on(window.mousedowntype, d20plus.monsters.button); $("#button-monsters-load-all").on(window.mousedowntype, d20plus.monsters.buttonAll); @@ -615,7 +616,7 @@ const betteR205etoolsMain = function () { $("#button-deities-load").on(window.mousedowntype, d20plus.deities.button); $("#bind-drop-locations").on(window.mousedowntype, d20plus.bindDropLocations); - $("#initiativewindow .characterlist").before(d20plus.initiativeHeaders); + $("#initiativewindow .characterlist").before(d20plus.template5e.initiativeHeaders); d20plus.setTurnOrderTemplate(); d20.Campaign.initiativewindow.rebuildInitiativeList(); @@ -673,19 +674,19 @@ const betteR205etoolsMain = function () { // player-only HTML if required } - $body.append(d20plus.playerImportHtml); + $body.append(d20plus.template5e.playerImportHtml); const $winPlayer = $("#d20plus-playerimport"); const $appTo = $winPlayer.find(`.append-target`); - $appTo.append(d20plus.settingsHtmlSelectorPlayer); - $appTo.append(d20plus.settingsHtmlPtItemsPlayer); - $appTo.append(d20plus.settingsHtmlPtSpellsPlayer); - $appTo.append(d20plus.settingsHtmlPtPsionicsPlayer); - $appTo.append(d20plus.settingsHtmlPtRacesPlayer); - $appTo.append(d20plus.settingsHtmlPtFeatsPlayer); - $appTo.append(d20plus.settingsHtmlPtClassesPlayer); - $appTo.append(d20plus.settingsHtmlPtSubclassesPlayer); - $appTo.append(d20plus.settingsHtmlPtBackgroundsPlayer); - $appTo.append(d20plus.settingsHtmlPtOptfeaturesPlayer); + $appTo.append(d20plus.template5e.settingsHtmlSelectorPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtItemsPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtSpellsPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtPsionicsPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtRacesPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtFeatsPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtClassesPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtSubclassesPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtBackgroundsPlayer); + $appTo.append(d20plus.template5e.settingsHtmlPtOptfeaturesPlayer); $winPlayer.dialog({ autoOpen: false, @@ -723,9 +724,9 @@ const betteR205etoolsMain = function () { $("a#import-optionalfeatures-load-player").on(window.mousedowntype, () => d20plus.optionalfeatures.button(true)); $("select#import-mode-select-player").on("change", () => d20plus.importer.importModeSwitch()); - $body.append(d20plus.importDialogHtml); - $body.append(d20plus.importListHTML); - $body.append(d20plus.importListPropsHTML); + $body.append(d20plus.template5e.importDialogHtml); + $body.append(d20plus.template5e.importListHTML); + $body.append(d20plus.template5e.importListPropsHTML); $("#d20plus-import").dialog({ autoOpen: false, resizable: false, @@ -786,7 +787,7 @@ const betteR205etoolsMain = function () { let $span = $btnPane.find("span.difficulty"); if (!$span.length) { - $btnPane.prepend(d20plus.difficultyHtml); + $btnPane.prepend(d20plus.template5e.difficultyHtml); $span = $btnPane.find("span.difficulty"); } @@ -1119,7 +1120,7 @@ const betteR205etoolsMain = function () { } d20.Campaign.initiativewindow.rebuildInitiativeList = function () { - let html = d20plus.initiativeTemplate; + let html = d20plus.template5e.initiativeTemplate; let columnsAdded = []; $(".tracker-header-extra-columns").empty(); @@ -1272,612 +1273,14 @@ const betteR205etoolsMain = function () { if (d20.Campaign.initiativewindow.model.attributes.initiativepage) d20.Campaign.initiativewindow.$el.dialog("option", "width", getTargetWidth()); }; - d20plus.miniInitStyle = ` - #initiativewindow button.initmacrobutton { - padding: 1px 4px; - } - - #initiativewindow input { - font-size: 8px; - } - - #initiativewindow ul li span.name { - font-size: 13px; - padding-top: 0; - padding-left: 4px; - margin-top: -3px; - } - - #initiativewindow ul li img { - min-height: 15px; - max-height: 15px; - } - - #initiativewindow ul li { - min-height: 15px; - } - - #initiativewindow div.header span.initiative, - #initiativewindow ul li span.initiative, - #initiativewindow ul li span.tracker-col, - #initiativewindow div.header span.tracker-col, - #initiativewindow div.header span.initmacro, - #initiativewindow ul li span.initmacro { - font-size: 10px; - font-weight: bold; - text-align: right; - float: right; - padding: 0 5px; - width: 7%; - min-height: 20px; - display: block; - overflow: hidden; - } - - #initiativewindow ul li .controls { - padding: 0 3px; - } -`; - d20plus.setInitiativeShrink = function (doShrink) { const customStyle = $(`#dynamicStyle`); if (doShrink) { - customStyle.html(d20plus.miniInitStyle); + customStyle.html(d20plus.template5e.miniInitStyle); } else { customStyle.html(""); } }; - - d20plus.difficultyHtml = ``; - - d20plus.multipliers = [1, 1.5, 2, 2.5, 3, 4, 5]; - - d20plus.playerImportHtml = `
-
- -
-
- -
-

Player-imported items are temporary, as players can't make handouts. GMs may also use this functionality to avoid cluttering the journal. Once imported, items can be drag-dropped to character sheets.

-
`; - - d20plus.importListHTML = `
-

- - - - -

-

- - - - [?] -
- -
-

-

- - - - -

- -
`; - - d20plus.importListPropsHTML = `
-
- -
-

- Warning: this feature is highly experimental, and disabling properties which are assumed to always exist is not recommended. -
- -

-
`; - - d20plus.importDialogHtml = `
-

-

-

- remaining -

-Errors: 0 -

-

- -

-
`; - - d20plus.settingsHtmlImportHeader = ` -

Import By Category

-

We strongly recommend the OGL sheet for importing. You can switch afterwards.

-`; - d20plus.settingsHtmlSelector = ` - -`; - d20plus.settingsHtmlSelectorPlayer = ` - -`; - d20plus.settingsHtmlPtMonsters = ` -
-

Monster Importing

- - - -

Import Monsters

-

Import Monsters From All Sources

-

Import Monsters From File

-

-WARNING: Importing huge numbers of character sheets slows the game down. We recommend you import them as needed.
-The "Import Monsters From All Sources" button presents a list containing monsters from official sources only.
-To import from third-party sources, either individually select one available in the list, enter a custom URL, or upload a custom file, and "Import Monsters." -

-
-`; - - d20plus.settingsHtmlPtItems = ` -
-

Item Importing

- - - -Import Items -
-`; - - d20plus.settingsHtmlPtItemsPlayer = ` -
-

Item Importing

- - - -Import Items -
-`; - - d20plus.settingsHtmlPtSpells = ` -
-

Spell Importing

- - - -

Import Spells

-

Import Spells From All Sources

-

-The "Import Spells From All Sources" button presents a list containing spells from official sources only.
-To import from third-party sources, either individually select one available in the list or enter a custom URL, and "Import Spells." -

-
-`; - - d20plus.settingsHtmlPtSpellsPlayer = ` -
-

Spell Importing

- - - -

Import Spells

-

Import Spells From All Sources

-

-The "Import Spells From All Sources" button presents a list containing spells from official sources only.
-To import from third-party sources, either individually select one available in the list or enter a custom URL, and "Import Spells." -

-
-`; - - d20plus.settingsHtmlPtPsionics = ` -
-

Psionic Importing

- - - -Import Psionics -
-`; - - d20plus.settingsHtmlPtPsionicsPlayer = ` -
-

Psionic Importing

- - - -Import Psionics -
-`; - - d20plus.settingsHtmlPtFeats = ` -
-

Feat Importing

- - - -Import Feats -
-`; - - d20plus.settingsHtmlPtFeatsPlayer = ` -
-

Feat Importing

- - - -Import Feats -
-`; - - d20plus.settingsHtmlPtObjects = ` -
-

Object Importing

- - - -Import Objects -
-`; - - d20plus.settingsHtmlPtVehicles = ` -
-

Vehicle Importing

- - - -Import Vehicles -
-`; - - d20plus.settingsHtmlPtRaces = ` -
-

Race Importing

- - - -Import Races -
-`; - - d20plus.settingsHtmlPtRacesPlayer = ` -
-

Race Importing

- - - -Import Races -
-`; - - d20plus.settingsHtmlPtClasses = ` -
-

Class Importing

-

Import Classes from 5etools

- - - -

Import Classes from URL

-

-`; - - d20plus.settingsHtmlPtClassesPlayer = ` -
-

Class Importing

-

Import Classes from 5etools

- - - -

Import Classes from URL

-

-`; - - d20plus.settingsHtmlPtSubclasses = ` -
-

Subclass Importing

- - - -Import Subclasses -
-`; - - d20plus.settingsHtmlPtSubclassesPlayer = ` -
-

Subclass Importing

- - - -Import Subclasses -
-`; - - d20plus.settingsHtmlPtBackgrounds = ` -
-

Background Importing

- - - -Import Backgrounds -
-`; - - d20plus.settingsHtmlPtBackgroundsPlayer = ` -
-

Background Importing

- - - -Import Backgrounds -
-`; - - d20plus.settingsHtmlPtOptfeatures = ` -
-

Optional Feature (Invocations, etc.) Importing

- - - -Import Optional Features -
-`; - - d20plus.settingsHtmlPtOptfeaturesPlayer = ` -
-

Optional Feature (Invocations, etc.) Importing

- - - -Import Optional Features -
-`; - - d20plus.settingsHtmlPtAdventures = ` -
-Please note that this importer has been superceded by the Module Importer tool, found in the Tools List, or by clicking here. -

Adventure Importing

- - - -

Import Adventure

-

-

-
-`; - - d20plus.settingsHtmlPtDeities = ` -
-

Deity Importing

- - - -Import Deities -
-`; - - d20plus.settingsHtmlPtImportFooter = ` -

Bind Drag-n-Drop

-

Readme

-

-You can drag-and-drop imported handouts to character sheets.
-If a handout is glowing green in the journal, it's draggable. This breaks when Roll20 decides to hard-refresh the journal.
-To restore this functionality, press the "Bind Drag-n-Drop" button.
-Note: to drag a handout to a character sheet, you need to drag the name, and not the handout icon. -

-`; - - d20plus.css.cssRules = d20plus.css.cssRules.concat([ - { - s: ".no-shrink", - r: "flex-shrink: 0;", - }, - { - s: "#initiativewindow ul li span.initiative,#initiativewindow ul li span.tracker-col,#initiativewindow ul li span.initmacro", - r: "font-size: 25px;font-weight: bold;text-align: right;float: right;padding: 2px 5px;width: 10%;min-height: 20px;display: block;", - }, - { - s: "#initiativewindow ul li span.editable input", - r: "width: 100%; box-sizing: border-box;height: 100%;", - }, - { - s: "#initiativewindow div.header", - r: "height: 30px;", - }, - { - s: "#initiativewindow div.header span", - r: "cursor: default;font-size: 15px;font-weight: bold;text-align: right;float: right;width: 10%;min-height: 20px;padding: 5px;", - }, - { - s: ".ui-dialog-buttonpane span.difficulty", - r: "display: inline-block;padding: 5px 4px 6px;margin: .5em .4em .5em 0;font-size: 18px;", - }, - { - s: ".ui-dialog-buttonpane.buttonpane-absolute-position", - r: "position: absolute;bottom: 0;box-sizing: border-box;width: 100%;", - }, - { - s: ".ui-dialog.dialog-collapsed .ui-dialog-buttonpane", - r: "position: initial;", - }, - { - s: ".token .cr,.header .cr", - r: "display: none!important;", - }, - { - s: "li.handout.compendium-item .namecontainer", - r: "box-shadow: inset 0px 0px 25px 2px rgb(195, 239, 184);", - }, - { - s: ".bind-drop-locations:active", - r: "box-shadow: inset 0px 0px 25px 2px rgb(195, 239, 184);", - }, - { - s: "del.userscript-hidden", - r: "display: none;", - }, - { - s: ".importer-section", - r: "display: none;", - }, - { - s: ".userscript-rd__h", - r: "font-weight: bold;", - }, - { - s: ".userscript-rd__h--0", - r: "font-weight: bold; font-size: 1.5em;", - }, - { - s: ".userscript-rd__h--2", - r: "font-weight: bold; font-size: 1.3em;", - }, - { - s: ".userscript-rd__h--3, .userscript-rd__h--4", - r: "font-style: italic", - }, - { - s: ".userscript-rd__b-inset--readaloud", - r: "background: #cbd6c688 !important", - }, - // "No character sheet" message - { - s: ".ve-nosheet__body", - r: "overflow: hidden !important;", - }, - { - s: ".ve-nosheet__overlay", - r: ` - background: darkred; - position: fixed; - z-index: 99999; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100vw; - height: 100vh; - color: white; - font-family: monospace;`, - }, - { - s: ".ve-nosheet__title", - r: "font-size: 72px;", - }, - { - s: ".ve-nosheet__btn-close", - r: `position: absolute; - top: 8px; - right: 8px; - font-size: 16px;`, - }, - ]); - - d20plus.initiativeHeaders = `
- -Init -CR -
-
`; - - d20plus.initiativeTemplate = ``; - - d20plus.actionMacroPerception = "%{Selected|npc_perception} @{selected|wtype} &{template:default} {{name=Senses}} @{selected|wtype} @{Selected|npc_senses} "; - d20plus.actionMacroInit = "%{selected|npc_init}"; - d20plus.actionMacroDrImmunities = "@{selected|wtype} &{template:default} {{name=DR/Immunities}} {{Damage Resistance= @{selected|npc_resistances}}} {{Damage Vulnerability= @{selected|npc_vulnerabilities}}} {{Damage Immunity= @{selected|npc_immunities}}} {{Condition Immunity= @{selected|npc_condition_immunities}}} "; - d20plus.actionMacroStats = "@{selected|wtype} &{template:default} {{name=Stats}} {{Armor Class= @{selected|npc_AC}}} {{Hit Dice= @{selected|npc_hpformula}}} {{Speed= @{selected|npc_speed}}} {{Senses= @{selected|npc_senses}}} {{Languages= @{selected|npc_languages}}} {{Challenge= @{selected|npc_challenge}(@{selected|npc_xp}xp)}}"; - d20plus.actionMacroSaves = "@{selected|wtype} &{template:simple}{{always=1}}?{Saving Throw?|STR,{{rname=Strength Save}}{{mod=@{npc_str_save}}} {{r1=[[1d20+@{npc_str_save}]]}}{{r2=[[1d20+@{npc_str_save}]]}}|DEX,{{rname=Dexterity Save}}{{mod=@{npc_dex_save}}} {{r1=[[1d20+@{npc_dex_save}]]}}{{r2=[[1d20+@{npc_dex_save}]]}}|CON,{{rname=Constitution Save}}{{mod=@{npc_con_save}}} {{r1=[[1d20+@{npc_con_save}]]}}{{r2=[[1d20+@{npc_con_save}]]}}|INT,{{rname=Intelligence Save}}{{mod=@{npc_int_save}}} {{r1=[[1d20+@{npc_int_save}]]}}{{r2=[[1d20+@{npc_int_save}]]}}|WIS,{{rname=Wisdom Save}}{{mod=@{npc_wis_save}}} {{r1=[[1d20+@{npc_wis_save}]]}}{{r2=[[1d20+@{npc_wis_save}]]}}|CHA,{{rname=Charisma Save}}{{mod=@{npc_cha_save}}} {{r1=[[1d20+@{npc_cha_save}]]}}{{r2=[[1d20+@{npc_cha_save}]]}}}{{charname=@{character_name}}} "; - d20plus.actionMacroSkillCheck = "@{selected|wtype} &{template:simple}{{always=1}}?{Ability?|Acrobatics,{{rname=Acrobatics}}{{mod=@{npc_acrobatics}}} {{r1=[[1d20+@{npc_acrobatics}]]}}{{r2=[[1d20+@{npc_acrobatics}]]}}|Animal Handling,{{rname=Animal Handling}}{{mod=@{npc_animal_handling}}} {{r1=[[1d20+@{npc_animal_handling}]]}}{{r2=[[1d20+@{npc_animal_handling}]]}}|Arcana,{{rname=Arcana}}{{mod=@{npc_arcana}}} {{r1=[[1d20+@{npc_arcana}]]}}{{r2=[[1d20+@{npc_arcana}]]}}|Athletics,{{rname=Athletics}}{{mod=@{npc_athletics}}} {{r1=[[1d20+@{npc_athletics}]]}}{{r2=[[1d20+@{npc_athletics}]]}}|Deception,{{rname=Deception}}{{mod=@{npc_deception}}} {{r1=[[1d20+@{npc_deception}]]}}{{r2=[[1d20+@{npc_deception}]]}}|History,{{rname=History}}{{mod=@{npc_history}}} {{r1=[[1d20+@{npc_history}]]}}{{r2=[[1d20+@{npc_history}]]}}|Insight,{{rname=Insight}}{{mod=@{npc_insight}}} {{r1=[[1d20+@{npc_insight}]]}}{{r2=[[1d20+@{npc_insight}]]}}|Intimidation,{{rname=Intimidation}}{{mod=@{npc_intimidation}}} {{r1=[[1d20+@{npc_intimidation}]]}}{{r2=[[1d20+@{npc_intimidation}]]}}|Investigation,{{rname=Investigation}}{{mod=@{npc_investigation}}} {{r1=[[1d20+@{npc_investigation}]]}}{{r2=[[1d20+@{npc_investigation}]]}}|Medicine,{{rname=Medicine}}{{mod=@{npc_medicine}}} {{r1=[[1d20+@{npc_medicine}]]}}{{r2=[[1d20+@{npc_medicine}]]}}|Nature,{{rname=Nature}}{{mod=@{npc_nature}}} {{r1=[[1d20+@{npc_nature}]]}}{{r2=[[1d20+@{npc_nature}]]}}|Perception,{{rname=Perception}}{{mod=@{npc_perception}}} {{r1=[[1d20+@{npc_perception}]]}}{{r2=[[1d20+@{npc_perception}]]}}|Performance,{{rname=Performance}}{{mod=@{npc_performance}}} {{r1=[[1d20+@{npc_performance}]]}}{{r2=[[1d20+@{npc_performance}]]}}|Persuasion,{{rname=Persuasion}}{{mod=@{npc_persuasion}}} {{r1=[[1d20+@{npc_persuasion}]]}}{{r2=[[1d20+@{npc_persuasion}]]}}|Religion,{{rname=Religion}}{{mod=@{npc_religion}}} {{r1=[[1d20+@{npc_religion}]]}}{{r2=[[1d20+@{npc_religion}]]}}|Sleight of Hand,{{rname=Sleight of Hand}}{{mod=@{npc_sleight_of_hand}}} {{r1=[[1d20+@{npc_sleight_of_hand}]]}}{{r2=[[1d20+@{npc_sleight_of_hand}]]}}|Stealth,{{rname=Stealth}}{{mod=@{npc_stealth}}} {{r1=[[1d20+@{npc_stealth}]]}}{{r2=[[1d20+@{npc_stealth}]]}}|Survival,{{rname=Survival}}{{mod=@{npc_survival}}} {{r1=[[1d20+@{npc_survival}]]}}{{r2=[[1d20+@{npc_survival}]]}}}{{charname=@{character_name}}} "; - d20plus.actionMacroAbilityCheck = "@{selected|wtype} &{template:simple}{{always=1}}?{Ability?|STR,{{rname=Strength}}{{mod=@{strength_mod}}} {{r1=[[1d20+@{strength_mod}]]}}{{r2=[[1d20+@{strength_mod}]]}}|DEX,{{rname=Dexterity}}{{mod=@{dexterity_mod}}} {{r1=[[1d20+@{dexterity_mod}]]}}{{r2=[[1d20+@{dexterity_mod}]]}}|CON,{{rname=Constitution}}{{mod=@{constitution_mod}}} {{r1=[[1d20+@{constitution_mod}]]}}{{r2=[[1d20+@{constitution_mod}]]}}|INT,{{rname=Intelligence}}{{mod=@{intelligence_mod}}} {{r1=[[1d20+@{intelligence_mod}]]}}{{r2=[[1d20+@{intelligence_mod}]]}}|WIS,{{rname=Wisdom}}{{mod=@{wisdom_mod}}} {{r1=[[1d20+@{wisdom_mod}]]}}{{r2=[[1d20+@{wisdom_mod}]]}}|CHA,{{rname=Charisma}}{{mod=@{charisma_mod}}} {{r1=[[1d20+@{charisma_mod}]]}}{{r2=[[1d20+@{charisma_mod}]]}}}{{charname=@{character_name}}} "; - - d20plus.actionMacroTrait = function (index) { - return `@{selected|wtype} &{template:npcaction} {{name=@{selected|npc_name}}} {{rname=@{selected|repeating_npctrait_$${index}_name}}} {{description=@{selected|repeating_npctrait_$${index}_desc} }}`; - }; - - d20plus.actionMacroAction = function (baseAction, index) { - return `%{selected|${baseAction}_$${index}_npc_action}`; - }; - - d20plus.actionMacroReaction = function (index) { - return `@{selected|wtype} &{template:npcaction} {{name=@{selected|npc_name}}} {{rname=@{selected|repeating_npcreaction_$${index}_name}}} {{description=@{selected|repeating_npcreaction_$${index}_desc} }} `; - }; - - d20plus.actionMacroLegendary = function (tokenactiontext) { - return `@{selected|wtype} @{selected|wtype}&{template:npcaction} {{name=@{selected|npc_name}}} {{rname=Legendary Actions}} {{description=The @{selected|npc_name} can take @{selected|npc_legendary_actions} legendary actions, choosing from the options below. Only one legendary option can be used at a time and only at the end of another creature's turn. The @{selected|npc_name} regains spent legendary actions at the start of its turn.\n\r${tokenactiontext}}} `; - } - - d20plus.actionMacroMythic = function (tokenactiontext) { - return `@{selected|wtype} @{selected|wtype}&{template:npcaction} {{name=@{selected|npc_name}}} {{rname=Mythic Actions}} {{description=The @{selected|npc_name} can take @{selected|npc_legendary_actions} mythic actions, choosing from the options below. Only one mythic option can be used at a time and only at the end of another creature's turn. The @{selected|npc_name} regains spent mythic actions at the start of its turn.\n\r${tokenactiontext}}} `; - } }; SCRIPT_EXTENSIONS.push(betteR205etoolsMain); diff --git a/js/5etools-monsters.js b/js/5etools-monsters.js index c37a42de..66ab6f56 100644 --- a/js/5etools-monsters.js +++ b/js/5etools-monsters.js @@ -511,6 +511,7 @@ function d20plusMonsters () { } // add Tokenaction Macros + // TODO: Can probably be shortened using a generic add macro function if (d20plus.cfg.getOrDefault("import", "tokenactionsSkills")) { if (d20plus.sheet === "shaped") { // Do nothing (room for future expansion!) @@ -518,7 +519,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Skill-Check", istokenaction: true, - action: d20plus.actionMacroSkillCheck, + action: d20plus.macro.actionMacroSkillCheck, }); } } @@ -529,7 +530,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Perception", istokenaction: true, - action: d20plus.actionMacroPerception, + action: d20plus.macro.actionMacroPerception, }); } } @@ -544,7 +545,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Saves", istokenaction: true, - action: d20plus.actionMacroSaves, + action: d20plus.macro.actionMacroSaves, }); } } @@ -559,7 +560,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Init", istokenaction: true, - action: d20plus.actionMacroInit, + action: d20plus.macro.actionMacroInit, }); } } @@ -574,7 +575,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Ability-Check", istokenaction: true, - action: d20plus.actionMacroAbilityCheck, + action: d20plus.macro.actionMacroAbilityCheck, }); } } @@ -585,12 +586,12 @@ function d20plusMonsters () { character.abilities.create({ name: "DR/Immunities", istokenaction: true, - action: d20plus.actionMacroDrImmunities, + action: d20plus.macro.actionMacroDrImmunities, }); character.abilities.create({ name: "Stats", istokenaction: true, - action: d20plus.actionMacroStats, + action: d20plus.macro.actionMacroStats, }); } } @@ -1151,7 +1152,7 @@ function d20plusMonsters () { character.abilities.create({ name: `Trait${offsetIndex}: ${v.name}`, istokenaction: true, - action: d20plus.actionMacroTrait(offsetIndex), + action: d20plus.macro.actionMacroTrait(offsetIndex), }); } @@ -1172,7 +1173,7 @@ function d20plusMonsters () { character.abilities.create({ name: `${offsetIndex}: ${v.name}`, istokenaction: true, - action: d20plus.actionMacroTrait(offsetIndex), + action: d20plus.macro.actionMacroTrait(offsetIndex), }); } @@ -1265,7 +1266,7 @@ function d20plusMonsters () { character.abilities.create({ name: `Reaction: ${v.name}`, istokenaction: true, - action: d20plus.actionMacroReaction(i), + action: d20plus.macro.actionMacroReaction(i), }); } @@ -1309,7 +1310,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Legendary Actions", istokenaction: true, - action: d20plus.actionMacroLegendary(tokenactiontext), + action: d20plus.macro.actionMacroLegendary(tokenactiontext), }); } } @@ -1333,7 +1334,7 @@ function d20plusMonsters () { character.abilities.create({ name: "Mythic Actions", istokenaction: true, - action: d20plus.actionMacroMythic(tokenactiontext), + action: d20plus.macro.actionMacroMythic(tokenactiontext), }); } } diff --git a/js/5etools-objects.js b/js/5etools-objects.js index 73dc2bd7..ebbb59f2 100644 --- a/js/5etools-objects.js +++ b/js/5etools-objects.js @@ -97,7 +97,7 @@ function d20plusObjects () { character.abilities.create({ name: `Information: ${name}`, istokenaction: true, - action: d20plus.actionMacroTrait(0), + action: d20plus.macro.actionMacroTrait(0), }); } } diff --git a/js/5etools-template.js b/js/5etools-template.js new file mode 100644 index 00000000..dbe094b5 --- /dev/null +++ b/js/5etools-template.js @@ -0,0 +1,467 @@ +const d20plusTemplate = function () { + d20plus.template5e = {}; + + d20plus.template5e.miniInitStyle = ` +#initiativewindow button.initmacrobutton { + padding: 1px 4px; +} + +#initiativewindow input { + font-size: 8px; +} + +#initiativewindow ul li span.name { + font-size: 13px; + padding-top: 0; + padding-left: 4px; + margin-top: -3px; +} + +#initiativewindow ul li img { + min-height: 15px; + max-height: 15px; +} + +#initiativewindow ul li { + min-height: 15px; +} + +#initiativewindow div.header span.initiative, +#initiativewindow ul li span.initiative, +#initiativewindow ul li span.tracker-col, +#initiativewindow div.header span.tracker-col, +#initiativewindow div.header span.initmacro, +#initiativewindow ul li span.initmacro { + font-size: 10px; + font-weight: bold; + text-align: right; + float: right; + padding: 0 5px; + width: 7%; + min-height: 20px; + display: block; + overflow: hidden; +} + +#initiativewindow ul li .controls { + padding: 0 3px; +} +`; + + d20plus.template5e.difficultyHtml = ``; + + d20plus.template5e.playerImportHtml = `
+
+ +
+
+ +
+

Player-imported items are temporary, as players can't make handouts. GMs may also use this functionality to avoid cluttering the journal. Once imported, items can be drag-dropped to character sheets.

+
`; + + d20plus.template5e.importListHTML = `
+

+ + + + +

+

+ + + +[?] +
+ +
+

+

+ + + + +

+ +
`; + + d20plus.template5e.importListPropsHTML = `
+
+ +
+

+ Warning: this feature is highly experimental, and disabling properties which are assumed to always exist is not recommended. +
+ +

+
`; + + d20plus.template5e.importDialogHtml = `
+

+

+

+ remaining +

+Errors: 0 +

+

+ +

+
`; + + d20plus.template5e.settingsHtmlImportHeader = ` +

Import By Category

+

We strongly recommend the OGL sheet for importing. You can switch afterwards.

+`; + d20plus.template5e.settingsHtmlSelector = ` + +`; + d20plus.template5e.settingsHtmlSelectorPlayer = ` + +`; + d20plus.template5e.settingsHtmlPtMonsters = ` +
+

Monster Importing

+ + + +

Import Monsters

+

Import Monsters From All Sources

+

Import Monsters From File

+

+WARNING: Importing huge numbers of character sheets slows the game down. We recommend you import them as needed.
+The "Import Monsters From All Sources" button presents a list containing monsters from official sources only.
+To import from third-party sources, either individually select one available in the list, enter a custom URL, or upload a custom file, and "Import Monsters." +

+
+`; + + d20plus.template5e.settingsHtmlPtItems = ` +
+

Item Importing

+ + + +Import Items +
+`; + + d20plus.template5e.settingsHtmlPtItemsPlayer = ` +
+

Item Importing

+ + + +Import Items +
+`; + + d20plus.template5e.settingsHtmlPtSpells = ` +
+

Spell Importing

+ + + +

Import Spells

+

Import Spells From All Sources

+

+The "Import Spells From All Sources" button presents a list containing spells from official sources only.
+To import from third-party sources, either individually select one available in the list or enter a custom URL, and "Import Spells." +

+
+`; + + d20plus.template5e.settingsHtmlPtSpellsPlayer = ` +
+

Spell Importing

+ + + +

Import Spells

+

Import Spells From All Sources

+

+The "Import Spells From All Sources" button presents a list containing spells from official sources only.
+To import from third-party sources, either individually select one available in the list or enter a custom URL, and "Import Spells." +

+
+`; + + d20plus.template5e.settingsHtmlPtPsionics = ` +
+

Psionic Importing

+ + + +Import Psionics +
+`; + + d20plus.template5e.settingsHtmlPtPsionicsPlayer = ` +
+

Psionic Importing

+ + + +Import Psionics +
+`; + + d20plus.template5e.settingsHtmlPtFeats = ` +
+

Feat Importing

+ + + +Import Feats +
+`; + + d20plus.template5e.settingsHtmlPtFeatsPlayer = ` +
+

Feat Importing

+ + + +Import Feats +
+`; + + d20plus.template5e.settingsHtmlPtObjects = ` +
+

Object Importing

+ + + +Import Objects +
+`; + + d20plus.template5e.settingsHtmlPtVehicles = ` +
+

Vehicle Importing

+ + + +Import Vehicles +
+`; + + d20plus.template5e.settingsHtmlPtRaces = ` +
+

Race Importing

+ + + +Import Races +
+`; + + d20plus.template5e.settingsHtmlPtRacesPlayer = ` +
+

Race Importing

+ + + +Import Races +
+`; + + d20plus.template5e.settingsHtmlPtClasses = ` +
+

Class Importing

+

Import Classes from 5etools

+ + + +

Import Classes from URL

+

+`; + + d20plus.template5e.settingsHtmlPtClassesPlayer = ` +
+

Class Importing

+

Import Classes from 5etools

+ + + +

Import Classes from URL

+

+`; + + d20plus.template5e.settingsHtmlPtSubclasses = ` +
+

Subclass Importing

+ + + +Import Subclasses +
+`; + + d20plus.template5e.settingsHtmlPtSubclassesPlayer = ` +
+

Subclass Importing

+ + + +Import Subclasses +
+`; + + d20plus.template5e.settingsHtmlPtBackgrounds = ` +
+

Background Importing

+ + + +Import Backgrounds +
+`; + + d20plus.template5e.settingsHtmlPtBackgroundsPlayer = ` +
+

Background Importing

+ + + +Import Backgrounds +
+`; + + d20plus.template5e.settingsHtmlPtOptfeatures = ` +
+

Optional Feature (Invocations, etc.) Importing

+ + + +Import Optional Features +
+`; + + d20plus.template5e.settingsHtmlPtOptfeaturesPlayer = ` +
+

Optional Feature (Invocations, etc.) Importing

+ + + +Import Optional Features +
+`; + + d20plus.template5e.settingsHtmlPtAdventures = ` +
+Please note that this importer has been superceded by the Module Importer tool, found in the Tools List, or by clicking here. +

Adventure Importing

+ + + +

Import Adventure

+

+

+
+`; + + d20plus.template5e.settingsHtmlPtDeities = ` +
+

Deity Importing

+ + + +Import Deities +
+`; + + d20plus.template5e.settingsHtmlPtImportFooter = ` +

Bind Drag-n-Drop

+

Readme

+

+You can drag-and-drop imported handouts to character sheets.
+If a handout is glowing green in the journal, it's draggable. This breaks when Roll20 decides to hard-refresh the journal.
+To restore this functionality, press the "Bind Drag-n-Drop" button.
+Note: to drag a handout to a character sheet, you need to drag the name, and not the handout icon. +

+`; + + d20plus.template5e.initiativeHeaders = `
+ +Init +CR +
+
`; + + d20plus.template5e.initiativeTemplate = ``; +}; + +SCRIPT_EXTENSIONS.push(d20plusTemplate); diff --git a/js/base-css.js b/js/base-css.js index 6096805a..1bd0ab9f 100644 --- a/js/base-css.js +++ b/js/base-css.js @@ -1009,6 +1009,113 @@ function baseCss () { `, }, ]); + + // Technecally 5e css rules, but they aren't hurting anything and there aren't enough to justify a separate file + d20plus.css.cssRules = d20plus.css.cssRules.concat([ + { + s: ".no-shrink", + r: "flex-shrink: 0;", + }, + { + s: "#initiativewindow ul li span.initiative,#initiativewindow ul li span.tracker-col,#initiativewindow ul li span.initmacro", + r: "font-size: 25px;font-weight: bold;text-align: right;float: right;padding: 2px 5px;width: 10%;min-height: 20px;display: block;", + }, + { + s: "#initiativewindow ul li span.editable input", + r: "width: 100%; box-sizing: border-box;height: 100%;", + }, + { + s: "#initiativewindow div.header", + r: "height: 30px;", + }, + { + s: "#initiativewindow div.header span", + r: "cursor: default;font-size: 15px;font-weight: bold;text-align: right;float: right;width: 10%;min-height: 20px;padding: 5px;", + }, + { + s: ".ui-dialog-buttonpane span.difficulty", + r: "display: inline-block;padding: 5px 4px 6px;margin: .5em .4em .5em 0;font-size: 18px;", + }, + { + s: ".ui-dialog-buttonpane.buttonpane-absolute-position", + r: "position: absolute;bottom: 0;box-sizing: border-box;width: 100%;", + }, + { + s: ".ui-dialog.dialog-collapsed .ui-dialog-buttonpane", + r: "position: initial;", + }, + { + s: ".token .cr,.header .cr", + r: "display: none!important;", + }, + { + s: "li.handout.compendium-item .namecontainer", + r: "box-shadow: inset 0px 0px 25px 2px rgb(195, 239, 184);", + }, + { + s: ".bind-drop-locations:active", + r: "box-shadow: inset 0px 0px 25px 2px rgb(195, 239, 184);", + }, + { + s: "del.userscript-hidden", + r: "display: none;", + }, + { + s: ".importer-section", + r: "display: none;", + }, + { + s: ".userscript-rd__h", + r: "font-weight: bold;", + }, + { + s: ".userscript-rd__h--0", + r: "font-weight: bold; font-size: 1.5em;", + }, + { + s: ".userscript-rd__h--2", + r: "font-weight: bold; font-size: 1.3em;", + }, + { + s: ".userscript-rd__h--3, .userscript-rd__h--4", + r: "font-style: italic", + }, + { + s: ".userscript-rd__b-inset--readaloud", + r: "background: #cbd6c688 !important", + }, + // "No character sheet" message + { + s: ".ve-nosheet__body", + r: "overflow: hidden !important;", + }, + { + s: ".ve-nosheet__overlay", + r: ` + background: darkred; + position: fixed; + z-index: 99999; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100vw; + height: 100vh; + color: white; + font-family: monospace;`, + }, + { + s: ".ve-nosheet__title", + r: "font-size: 72px;", + }, + { + s: ".ve-nosheet__btn-close", + r: `position: absolute; + top: 8px; + right: 8px; + font-size: 16px;`, + }, + ]); } SCRIPT_EXTENSIONS.push(baseCss); diff --git a/js/base-journal.js b/js/base-journal.js index 50e9b714..73189b05 100644 --- a/js/base-journal.js +++ b/js/base-journal.js @@ -36,32 +36,32 @@ function d20plusJournal () { character.abilities.create({ name: "Perception", istokenaction: true, - action: d20plus.actionMacroPerception, + action: d20plus.macro.actionMacroPerception, }); character.abilities.create({ name: "DR/Immunities", istokenaction: true, - action: d20plus.actionMacroDrImmunities, + action: d20plus.macro.actionMacroDrImmunities, }); character.abilities.create({ name: "Stats", istokenaction: true, - action: d20plus.actionMacroStats, + action: d20plus.macro.actionMacroStats, }); character.abilities.create({ name: "Saves", istokenaction: true, - action: d20plus.actionMacroSaves, + action: d20plus.macro.actionMacroSaves, }); character.abilities.create({ name: "Skill-Check", istokenaction: true, - action: d20plus.actionMacroSkillCheck, + action: d20plus.macro.actionMacroSkillCheck, }); character.abilities.create({ name: "Ability-Check", istokenaction: true, - action: d20plus.actionMacroAbilityCheck, + action: d20plus.macro.actionMacroAbilityCheck, }); } else { // player specific tokenactions @@ -117,7 +117,7 @@ function d20plusJournal () { character.abilities.create({ name: "Initiative", istokenaction: true, - action: d20plus.actionMacroInit, + action: d20plus.macro.actionMacroInit, }); } }); diff --git a/js/base-macro.js b/js/base-macro.js new file mode 100644 index 00000000..3bd481bc --- /dev/null +++ b/js/base-macro.js @@ -0,0 +1,33 @@ +const baseMacro = function () { + d20plus.macro = {}; + + d20plus.macro.actionMacroTrait = function (index) { + return `@{selected|wtype} &{template:npcaction} {{name=@{selected|npc_name}}} {{rname=@{selected|repeating_npctrait_$${index}_name}}} {{description=@{selected|repeating_npctrait_$${index}_desc} }}`; + }; + + d20plus.macro.actionMacroAction = function (baseAction, index) { + return `%{selected|${baseAction}_$${index}_npc_action}`; + }; + + d20plus.macro.actionMacroReaction = function (index) { + return `@{selected|wtype} &{template:npcaction} {{name=@{selected|npc_name}}} {{rname=@{selected|repeating_npcreaction_$${index}_name}}} {{description=@{selected|repeating_npcreaction_$${index}_desc} }} `; + }; + + d20plus.macro.actionMacroLegendary = function (tokenactiontext) { + return `@{selected|wtype} @{selected|wtype}&{template:npcaction} {{name=@{selected|npc_name}}} {{rname=Legendary Actions}} {{description=The @{selected|npc_name} can take @{selected|npc_legendary_actions} legendary actions, choosing from the options below. Only one legendary option can be used at a time and only at the end of another creature's turn. The @{selected|npc_name} regains spent legendary actions at the start of its turn.\n\r${tokenactiontext}}} `; + } + + d20plus.macro.actionMacroMythic = function (tokenactiontext) { + return `@{selected|wtype} @{selected|wtype}&{template:npcaction} {{name=@{selected|npc_name}}} {{rname=Mythic Actions}} {{description=The @{selected|npc_name} can take @{selected|npc_legendary_actions} mythic actions, choosing from the options below. Only one mythic option can be used at a time and only at the end of another creature's turn. The @{selected|npc_name} regains spent mythic actions at the start of its turn.\n\r${tokenactiontext}}} `; + } + + d20plus.macro.actionMacroPerception = "%{Selected|npc_perception} @{selected|wtype} &{template:default} {{name=Senses}} @{selected|wtype} @{Selected|npc_senses} "; + d20plus.macro.actionMacroInit = "%{selected|npc_init}"; + d20plus.macro.actionMacroDrImmunities = "@{selected|wtype} &{template:default} {{name=DR/Immunities}} {{Damage Resistance= @{selected|npc_resistances}}} {{Damage Vulnerability= @{selected|npc_vulnerabilities}}} {{Damage Immunity= @{selected|npc_immunities}}} {{Condition Immunity= @{selected|npc_condition_immunities}}} "; + d20plus.macro.actionMacroStats = "@{selected|wtype} &{template:default} {{name=Stats}} {{Armor Class= @{selected|npc_AC}}} {{Hit Dice= @{selected|npc_hpformula}}} {{Speed= @{selected|npc_speed}}} {{Senses= @{selected|npc_senses}}} {{Languages= @{selected|npc_languages}}} {{Challenge= @{selected|npc_challenge}(@{selected|npc_xp}xp)}}"; + d20plus.macro.actionMacroSaves = "@{selected|wtype} &{template:simple}{{always=1}}?{Saving Throw?|STR,{{rname=Strength Save}}{{mod=@{npc_str_save}}} {{r1=[[1d20+@{npc_str_save}]]}}{{r2=[[1d20+@{npc_str_save}]]}}|DEX,{{rname=Dexterity Save}}{{mod=@{npc_dex_save}}} {{r1=[[1d20+@{npc_dex_save}]]}}{{r2=[[1d20+@{npc_dex_save}]]}}|CON,{{rname=Constitution Save}}{{mod=@{npc_con_save}}} {{r1=[[1d20+@{npc_con_save}]]}}{{r2=[[1d20+@{npc_con_save}]]}}|INT,{{rname=Intelligence Save}}{{mod=@{npc_int_save}}} {{r1=[[1d20+@{npc_int_save}]]}}{{r2=[[1d20+@{npc_int_save}]]}}|WIS,{{rname=Wisdom Save}}{{mod=@{npc_wis_save}}} {{r1=[[1d20+@{npc_wis_save}]]}}{{r2=[[1d20+@{npc_wis_save}]]}}|CHA,{{rname=Charisma Save}}{{mod=@{npc_cha_save}}} {{r1=[[1d20+@{npc_cha_save}]]}}{{r2=[[1d20+@{npc_cha_save}]]}}}{{charname=@{character_name}}} "; + d20plus.macro.actionMacroSkillCheck = "@{selected|wtype} &{template:simple}{{always=1}}?{Ability?|Acrobatics,{{rname=Acrobatics}}{{mod=@{npc_acrobatics}}} {{r1=[[1d20+@{npc_acrobatics}]]}}{{r2=[[1d20+@{npc_acrobatics}]]}}|Animal Handling,{{rname=Animal Handling}}{{mod=@{npc_animal_handling}}} {{r1=[[1d20+@{npc_animal_handling}]]}}{{r2=[[1d20+@{npc_animal_handling}]]}}|Arcana,{{rname=Arcana}}{{mod=@{npc_arcana}}} {{r1=[[1d20+@{npc_arcana}]]}}{{r2=[[1d20+@{npc_arcana}]]}}|Athletics,{{rname=Athletics}}{{mod=@{npc_athletics}}} {{r1=[[1d20+@{npc_athletics}]]}}{{r2=[[1d20+@{npc_athletics}]]}}|Deception,{{rname=Deception}}{{mod=@{npc_deception}}} {{r1=[[1d20+@{npc_deception}]]}}{{r2=[[1d20+@{npc_deception}]]}}|History,{{rname=History}}{{mod=@{npc_history}}} {{r1=[[1d20+@{npc_history}]]}}{{r2=[[1d20+@{npc_history}]]}}|Insight,{{rname=Insight}}{{mod=@{npc_insight}}} {{r1=[[1d20+@{npc_insight}]]}}{{r2=[[1d20+@{npc_insight}]]}}|Intimidation,{{rname=Intimidation}}{{mod=@{npc_intimidation}}} {{r1=[[1d20+@{npc_intimidation}]]}}{{r2=[[1d20+@{npc_intimidation}]]}}|Investigation,{{rname=Investigation}}{{mod=@{npc_investigation}}} {{r1=[[1d20+@{npc_investigation}]]}}{{r2=[[1d20+@{npc_investigation}]]}}|Medicine,{{rname=Medicine}}{{mod=@{npc_medicine}}} {{r1=[[1d20+@{npc_medicine}]]}}{{r2=[[1d20+@{npc_medicine}]]}}|Nature,{{rname=Nature}}{{mod=@{npc_nature}}} {{r1=[[1d20+@{npc_nature}]]}}{{r2=[[1d20+@{npc_nature}]]}}|Perception,{{rname=Perception}}{{mod=@{npc_perception}}} {{r1=[[1d20+@{npc_perception}]]}}{{r2=[[1d20+@{npc_perception}]]}}|Performance,{{rname=Performance}}{{mod=@{npc_performance}}} {{r1=[[1d20+@{npc_performance}]]}}{{r2=[[1d20+@{npc_performance}]]}}|Persuasion,{{rname=Persuasion}}{{mod=@{npc_persuasion}}} {{r1=[[1d20+@{npc_persuasion}]]}}{{r2=[[1d20+@{npc_persuasion}]]}}|Religion,{{rname=Religion}}{{mod=@{npc_religion}}} {{r1=[[1d20+@{npc_religion}]]}}{{r2=[[1d20+@{npc_religion}]]}}|Sleight of Hand,{{rname=Sleight of Hand}}{{mod=@{npc_sleight_of_hand}}} {{r1=[[1d20+@{npc_sleight_of_hand}]]}}{{r2=[[1d20+@{npc_sleight_of_hand}]]}}|Stealth,{{rname=Stealth}}{{mod=@{npc_stealth}}} {{r1=[[1d20+@{npc_stealth}]]}}{{r2=[[1d20+@{npc_stealth}]]}}|Survival,{{rname=Survival}}{{mod=@{npc_survival}}} {{r1=[[1d20+@{npc_survival}]]}}{{r2=[[1d20+@{npc_survival}]]}}}{{charname=@{character_name}}} "; + d20plus.macro.actionMacroAbilityCheck = "@{selected|wtype} &{template:simple}{{always=1}}?{Ability?|STR,{{rname=Strength}}{{mod=@{strength_mod}}} {{r1=[[1d20+@{strength_mod}]]}}{{r2=[[1d20+@{strength_mod}]]}}|DEX,{{rname=Dexterity}}{{mod=@{dexterity_mod}}} {{r1=[[1d20+@{dexterity_mod}]]}}{{r2=[[1d20+@{dexterity_mod}]]}}|CON,{{rname=Constitution}}{{mod=@{constitution_mod}}} {{r1=[[1d20+@{constitution_mod}]]}}{{r2=[[1d20+@{constitution_mod}]]}}|INT,{{rname=Intelligence}}{{mod=@{intelligence_mod}}} {{r1=[[1d20+@{intelligence_mod}]]}}{{r2=[[1d20+@{intelligence_mod}]]}}|WIS,{{rname=Wisdom}}{{mod=@{wisdom_mod}}} {{r1=[[1d20+@{wisdom_mod}]]}}{{r2=[[1d20+@{wisdom_mod}]]}}|CHA,{{rname=Charisma}}{{mod=@{charisma_mod}}} {{r1=[[1d20+@{charisma_mod}]]}}{{r2=[[1d20+@{charisma_mod}]]}}}{{charname=@{character_name}}} "; +}; + +SCRIPT_EXTENSIONS.push(baseMacro); diff --git a/node/build-scripts.js b/node/build-scripts.js index 7ad19525..e1a84769 100644 --- a/node/build-scripts.js +++ b/node/build-scripts.js @@ -165,6 +165,7 @@ const SCRIPTS = { "templates/template-token-editor", "templates/template-page-settings", "base-template", + "base-macro", "base-emoji", "base-remote-libre", "base-jukebox-widget", @@ -202,6 +203,7 @@ const SCRIPTS = { "templates/template-token-editor", "templates/template-page-settings", "base-template", + "base-macro", "base-emoji", "base-remote-libre", "base-jukebox-widget", @@ -225,6 +227,7 @@ const SCRIPTS = { "5etools-adventures", "5etools-deities", "5etools-vehicles", + "5etools-template", "base", ], @@ -252,4 +255,5 @@ Object.entries(SCRIPTS).forEach(([k, v]) => { fs.writeFileSync(`${BUILD_DIR}/betteR20-version`, `${SCRIPT_VERSION}`); +// eslint-disable-next-line no-console console.log(`v${SCRIPT_VERSION}: Build completed at ${(new Date()).toJSON().slice(11, 19)}`); From 2978ffa7f31a4b0d108dc964374ccaf620d7686b Mon Sep 17 00:00:00 2001 From: darthbeep Date: Wed, 7 Sep 2022 00:23:57 -0400 Subject: [PATCH 3/6] automated html setup --- js/5etools-bootstrap.js | 2 +- js/5etools-main.js | 377 ++++++-------------------- js/5etools-monsters.js | 2 +- js/5etools-spells.js | 2 +- js/5etools-template.js | 581 +++++++++++++++++++++------------------- 5 files changed, 399 insertions(+), 565 deletions(-) diff --git a/js/5etools-bootstrap.js b/js/5etools-bootstrap.js index e3c885f5..764f594e 100644 --- a/js/5etools-bootstrap.js +++ b/js/5etools-bootstrap.js @@ -39,7 +39,7 @@ const betteR205etools = function () { d20plus.bindDropLocations(); d20plus.ui.addHtmlHeader(); - d20plus.addCustomHTML(); + d20plus.template5e.addCustomHTML(); d20plus.ui.addHtmlFooter(); d20plus.engine.enhanceMarkdown(); d20plus.engine.addProFeatures(); diff --git a/js/5etools-main.js b/js/5etools-main.js index 60bcfcf9..d86307e6 100644 --- a/js/5etools-main.js +++ b/js/5etools-main.js @@ -121,6 +121,94 @@ const betteR205etoolsMain = function () { ], }; + IMPORT_CATEGORIES = [ + { + name: "background", + plural: "backgrounds", + playerImport: true, + baseUrl: BACKGROUND_DATA_URL, + }, + { + name: "class", + plural: "classes", + playerImport: true, + allImport: true, + baseUrl: CLASS_DATA_DIR, + defaultSource: "", + }, + { + name: "deity", + plural: "deities", + baseUrl: DEITY_DATA_URL, + }, + { + name: "feat", + plural: "feats", + playerImport: true, + baseUrl: FEAT_DATA_URL, + }, + { + name: "item", + plural: "items", + playerImport: true, + baseUrl: ITEM_DATA_URL, + }, + { + name: "monster", + plural: "monsters", + allImport: true, + fileImport: true, + baseUrl: MONSTER_DATA_DIR, + defaultSource: "MM", + finalText: ` WARNING: Importing huge numbers of character sheets slows the game down. We recommend you import them as needed.
The "Import Monsters From All Sources" button presents a list containing monsters from official sources only.
To import from third-party sources, either individually select one available in the list, enter a custom URL, or upload a custom file, and "Import Monsters."`, + }, + { + name: "object", + plural: "objects", + baseUrl: OBJECT_DATA_URL, + }, + { + name: "optionalfeature", + plural: "optionalfeatures", + titleSing: "Optional Feature (Invocations, etc.)", + titlePl: "Optional Features (Invocations, etc.)", + playerImport: true, + baseUrl: OPT_FEATURE_DATA_URL, + }, + { + name: "psionic", + plural: "psionics", + playerImport: true, + baseUrl: PSIONIC_DATA_URL, + }, + { + name: "race", + plural: "races", + playerImport: true, + baseUrl: RACE_DATA_URL, + }, + { + name: "spell", + plural: "spells", + playerImport: true, + allImport: true, + baseUrl: SPELL_DATA_DIR, + defaultSource: "PHB", + finalText: `The "Import Spells From All Sources" button presents a list containing spells from official sources only.
To import from third-party sources, either individually select one available in the list or enter a custom URL, and "Import Spells."`, + }, + { + name: "subclass", + plural: "subclasses", + playerImport: true, + baseUrl: "", + }, + { + name: "vehicle", + plural: "vehicles", + baseUrl: VEHICLE_DATA_URL, + }, + ] + let spellDataUrls = {}; let spellMetaData = {}; let monsterDataUrls = {}; @@ -490,295 +578,6 @@ const betteR205etoolsMain = function () { return dataDir + fileName; }; - d20plus.addCustomHTML = function () { - function populateDropdown (dropdownId, inputFieldId, baseUrl, srcUrlObject, defaultSel, brewProps) { - const defaultUrl = defaultSel ? d20plus.formSrcUrl(baseUrl, srcUrlObject[defaultSel]) : ""; - $(inputFieldId).val(defaultUrl); - const dropdown = $(dropdownId); - $.each(Object.keys(srcUrlObject), function (i, src) { - dropdown.append($("