From 5fad95a16f6807d408626ccf84c23b2162517f20 Mon Sep 17 00:00:00 2001 From: oldewyrm <79656029+oldewyrm@users.noreply.github.com> Date: Sat, 1 Jan 2022 14:56:44 -0700 Subject: [PATCH 1/5] Add option to skip --- js/base-ui.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/js/base-ui.js b/js/base-ui.js index fd40eb4d..8ad0fe99 100644 --- a/js/base-ui.js +++ b/js/base-ui.js @@ -143,9 +143,10 @@ function baseUi () { * @param random show button for random choices * @param randomMax Enforce max random choices * @param totallyRandom select randomly number of items between countMin and countMax. Requires count to be null. This has higher priority than randomMax + * @param skip show skip button to allow no choices * @return {Promise} */ - d20plus.ui.chooseCheckboxList = async function (dataArray, dataTitle, {displayFormatter = null, count = null, countMin = null, countMax = null, additionalHTML = null, note = null, messageCountIncomplete = null , random = null, randomMax = null, totallyRandom = null} = {}) { + d20plus.ui.chooseCheckboxList = async function (dataArray, dataTitle, {displayFormatter = null, count = null, countMin = null, countMax = null, additionalHTML = null, note = null, messageCountIncomplete = null , random = null, randomMax = null, totallyRandom = null, skip = null} = {}) { return new Promise((resolve, reject) => { // Ensure count, countMin, and countMax don't mess up // Note if(var) is false if the number is 0. countMin is the only count allowed to be 0 @@ -196,6 +197,14 @@ function baseUi () { $dialog.dialog({ dialogClass: "no-close", buttons: [ + (skip ? { + text: "Skip", + click: function () { + $(this).dialog("close"); + $dialog.remove(); + resolve(null); + }, + } : null), { text: "Cancel", click: function () { @@ -273,9 +282,10 @@ function baseUi () { * @param additionalHTML additional html code, such as a button * @param note add a note at the bottom of the window * @param messageCountIncomplete message when user does not choose correct number of choices + * @param skip show skip button to allow no choices * @return {Promise} */ - d20plus.ui.chooseRadioList = async function (dataArray, dataTitle, {displayFormatter = null, random = null, additionalHTML = null, note = null, messageCountIncomplete = null} = {}) { + d20plus.ui.chooseRadioList = async function (dataArray, dataTitle, {displayFormatter = null, random = null, additionalHTML = null, note = null, messageCountIncomplete = null, skip = null} = {}) { return new Promise((resolve, reject) => { @@ -302,6 +312,14 @@ function baseUi () { $dialog.dialog({ dialogClass: "no-close", buttons: [ + (skip ? { + text: "Skip", + click: function () { + $(this).dialog("close"); + $dialog.remove(); + resolve(null); + }, + } : null), { text: "Cancel", click: function () { From 56765bf9e9a60a388908e908705c4c2099129b35 Mon Sep 17 00:00:00 2001 From: oldewyrm <79656029+oldewyrm@users.noreply.github.com> Date: Sun, 2 Jan 2022 11:07:03 -0700 Subject: [PATCH 2/5] Background fixes and additions Add skip to background importer. Add missing backgrounds features. Remove formatting from traits. --- js/5etools-backgrounds.js | 160 +++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 29 deletions(-) diff --git a/js/5etools-backgrounds.js b/js/5etools-backgrounds.js index 41dc3d84..2b6744be 100644 --- a/js/5etools-backgrounds.js +++ b/js/5etools-backgrounds.js @@ -86,20 +86,22 @@ function d20plusBackgrounds () { const ptraitargs = { countMin: 1, countMax: 2, - random:true, - totallyRandom:true, + random: true, + totallyRandom: true, + skip: true } const args = { countMin: 1, countMax: 1, - random:true, + random: true, + skip: true } - // Call the menu + // Call the menus, allowing for null sets const pt = await d20plus.ui.chooseCheckboxList(ptrait, "Personality Trait", ptraitargs); - const id = await d20plus.ui.chooseRadioList(ideal, "Ideal", args); - const bd = await d20plus.ui.chooseRadioList(bond, "Bond", args); - const fl = await d20plus.ui.chooseRadioList(flaw, "Flaw", args); + const id = !ideal ? null : await d20plus.ui.chooseRadioList(ideal, "Ideal", args); + const bd = !bond ? null : await d20plus.ui.chooseRadioList(bond, "Bond", args); + const fl = !flaw ? null : await d20plus.ui.chooseRadioList(flaw, "Flaw", args); // Return return { @@ -115,16 +117,49 @@ function d20plusBackgrounds () { const renderer = new Renderer(); renderer.setBaseUrl(BASE_SITE_URL); - const renderStack = []; - let feature = {}; + const featureSet = [ + "Cultural Chameleon", + "Dust Digger", + "Favored Event", + "Specialty", + "Contacts", + "How Do I Fit In?", + "Favorite Schemes", + "Faceless Persona", + "Why Are You Here?", + "Fey Mark", + "Feywild Visitor", + "Fishing Tale", + "Harrowing Event", + "Role", + "Path to Mystery", + "Hardship Endured", + "Variant Noble (Knight)", + "A Flair for the Dramatic", + "Life at Sea", + "Claim to Fame", + "Carnival Companion" + ]; + let features = []; bg.entries.forEach(e => { + let feature = {}; if (e.name && e.name.includes("Feature:")) { feature = JSON.parse(JSON.stringify(e)); feature.name = feature.name.replace("Feature:", "").trim(); - } + } else if (e.name && ( + e.name.includes("Guild Spells") || // covers all GGR backgrounds + e.name.includes("Origins") // Criminal, Folk Hero, Hermit, Outlander, etc + )) { + feature = JSON.parse(JSON.stringify(e)); + } else if (e.name && featureSet.includes(e.name)) { + feature = JSON.parse(JSON.stringify(e)); + } else return; + + const renderStack = []; + renderer.recursiveRender({entries: feature.entries}, renderStack); + feature.text = renderStack.length ? d20plus.importer.getCleanText(renderStack.join("")) : ""; + features.push(feature); }); - if (feature) renderer.recursiveRender({entries: feature.entries}, renderStack); - feature.text = renderStack.length ? d20plus.importer.getCleanText(renderStack.join("")) : ""; // Add skills @@ -424,11 +459,28 @@ function d20plusBackgrounds () { let ideal = null; let bond = null; let flaw = null; + const matchCharacteristics = [ + "Suggested Characteristics", // Most backgrounds + "Horror Characteristics" // 'Haunted One' and 'Investigator' + ]; // Get the JSON for all the tables if (bg.entries) { for (const ent of bg.entries) { - if (ent.name && ent.name === "Suggested Characteristics") { + if (ent.name && matchCharacteristics.includes(ent.name)) { traits = ent; + } else if (ent.entries) { + for (const entItem of ent.entries) { + // look for embedded characteristics + if (entItem.name && entItem.name === "Suggested Characteristics") { + traits = entItem; + // look for embedded trinkets, and move to features + } else if (entItem.name && entItem.name.includes("Trinket")) { + const renderStack = []; + renderer.recursiveRender({entries: entItem.entries}, renderStack); + entItem.text = renderStack.length ? d20plus.importer.getCleanText(renderStack.join("")) : ""; + features.push(entItem); + } + } } } } @@ -436,22 +488,67 @@ function d20plusBackgrounds () { // Fill the rows if (traits !== null && traits.entries?.length) { for (let i = 0; i < traits.entries.length; i++) { - ent = traits.entries[i]; + const ent = traits.entries[i]; // This seems to be the best way to parse the information with some room for errors // It seems like the schema is based on on the website, which is why colLabels is where the identifier is if (ent.colLabels && ent.colLabels.length === 2 && ent.rows) { + + /** + * Clean up the formatting and reference syntaxes to get clean text. + * + * @param entry entry to clean up + * @return cleaned text + */ + const _cleanText = function (entry) { + let cleanedText = ""; + let closeIndex = 0; + while (true) { + const firstIndex = entry.indexOf("{@", closeIndex); + if (firstIndex === -1) { + // grab whatever is left in the entry + cleanedText += entry.substring(closeIndex); + break; + } + cleanedText += entry.substring(closeIndex, firstIndex); + const spaceIndex = entry.indexOf(" ", firstIndex); + if (spaceIndex === -1) { + // parse error, just grab the rest and bail + cleanedText += entry.substring(firstIndex); + break; + } + closeIndex = entry.indexOf("}", spaceIndex); + if (closeIndex === -1) { + // parse error, just grab the rest and bail + cleanedText += entry.substring(firstIndex); + break; + } + // look for any reference syntax between the space and the closure + const pipeIndex = entry.substring(spaceIndex + 1, closeIndex).indexOf("|"); + if (pipeIndex === -1) { + // no reference, copy one past space up to closure + cleanedText += entry.substring(spaceIndex + 1, closeIndex); + } else { + // got a reference, copy one past space for number of characters up to pipe + // note that pipeIndex is zero-based + cleanedText += entry.substr(spaceIndex + 1, pipeIndex); + } + closeIndex++; + } + return cleanedText; + }; + switch (ent.colLabels[1]) { case "Personality Trait": - ptrait = ent.rows.map(r => r[1]); + ptrait = ent.rows.map(r => _cleanText(r[1])); break; case "Ideal": - ideal = ent.rows.map(r => r[1]); + ideal = ent.rows.map(r => _cleanText(r[1])); break; case "Bond": - bond = ent.rows.map(r => r[1]); + bond = ent.rows.map(r => _cleanText(r[1])); break; case "Flaw": - flaw = ent.rows.map(r => r[1]); + flaw = ent.rows.map(r => _cleanText(r[1])); break; } } @@ -464,18 +561,20 @@ function d20plusBackgrounds () { // Update Sheet const attrs = new d20plus.importer.CharacterAttributesProxy(character); - const fRowId = d20plus.ut.generateRowId(); if (d20plus.sheet === "ogl") { attrs.addOrUpdate("background", bg.name); attrs.addOrUpdate("gp", startingGold); - attrs.add(`repeating_traits_${fRowId}_name`, feature.name); - attrs.add(`repeating_traits_${fRowId}_source`, "Background"); - attrs.add(`repeating_traits_${fRowId}_source_type`, bg.name); - attrs.add(`repeating_traits_${fRowId}_options-flag`, "0"); - if (feature.text) { - attrs.add(`repeating_traits_${fRowId}_description`, feature.text); + for (const feature of features) { + const fRowId = d20plus.ut.generateRowId(); + attrs.add(`repeating_traits_${fRowId}_name`, feature.name); + attrs.add(`repeating_traits_${fRowId}_source`, "Background"); + attrs.add(`repeating_traits_${fRowId}_source_type`, bg.name); + attrs.add(`repeating_traits_${fRowId}_options-flag`, "0"); + if (feature.text) { + attrs.add(`repeating_traits_${fRowId}_description`, feature.text); + } } skills.map(s => s.toLowerCase().replace(/ /g, "_")).forEach(s => { @@ -512,10 +611,13 @@ function d20plusBackgrounds () { if (flaws?.length === 1) attrs.addOrUpdate(`flaws`, flaws[0]); } else if (d20plus.sheet === "shaped") { attrs.addOrUpdate("background", bg.name); - attrs.add(`repeating_trait_${fRowId}_name`, `${feature.name} (${bg.name})`); - if (feature.text) { - attrs.add(`repeating_trait_${fRowId}_content`, feature.text); - attrs.add(`repeating_trait_${fRowId}_content_toggle`, "1"); + for (const feature of features) { + const fRowId = d20plus.ut.generateRowId(); + attrs.add(`repeating_trait_${fRowId}_name`, `${feature.name} (${bg.name})`); + if (feature.text) { + attrs.add(`repeating_trait_${fRowId}_content`, feature.text); + attrs.add(`repeating_trait_${fRowId}_content_toggle`, "1"); + } } skills.map(s => s.toUpperCase().replace(/ /g, "")).forEach(s => { From 70daa79e2d2f47573587adef7f2b71712d6b04b5 Mon Sep 17 00:00:00 2001 From: oldewyrm <79656029+oldewyrm@users.noreply.github.com> Date: Sat, 15 Jan 2022 09:38:54 -0700 Subject: [PATCH 3/5] Explicitly tag entries as features --- data/backgrounds.json | 295 +++++++++++++++++++++++++++++++++--------- 1 file changed, 236 insertions(+), 59 deletions(-) diff --git a/data/backgrounds.json b/data/backgrounds.json index 10df569f..0039699e 100644 --- a/data/backgrounds.json +++ b/data/backgrounds.json @@ -363,7 +363,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Feature: Adept Linguist", @@ -659,7 +662,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Feature: Historical Knowledge", @@ -981,7 +987,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -1305,7 +1314,10 @@ ] }, "Your magic often takes the form of blue or golden runes floating and glowing in the air in circular patterns or of shimmering azure barriers of magical energy. If you cast {@spell ensnaring strike}, for example, the vines created by the spell might appear as rune-inscribed glowing bands that wrap around the target and hold it in place." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -1580,7 +1592,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -1588,7 +1603,10 @@ "entries": [ "As a member of the Azorius Senate, you are probably engaged in the work of law enforcement (even if your background involved the legislative or judicial aspects of the senate's activities). Legislative aides and judges' clerks find little reason to venture beyond the Azorius guildhalls, but soldiers and lawmages patrol the streets daily.", "An Azorius soldier or lawmage is a force for order, charged with fighting crime on the streets\u2014and in the halls of power. You might spend your time foiling thefts, putting a stop to Orzhov extortion, rooting out Dimir spies, or hunting down Golgari assassins. Perhaps you take your orders from a precognitive mage (or you are one yourself) who receives unpredictable and cryptic visions of future crimes that you and your allies must try to prevent." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -1740,7 +1758,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ] } @@ -1854,7 +1875,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ] } @@ -1967,7 +1991,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ] } @@ -2080,7 +2107,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ] } @@ -2670,7 +2700,10 @@ ] }, "Your magic often features dramatic bursts of flame or radiance. When you cast beneficial spells on your allies, they appear momentarily surrounded with halos of bright fire." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -2945,7 +2978,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -2960,7 +2996,10 @@ "Tajic is a firefist who carries the exalted title of Blade of the Legion, putting him just below the angels in rank. He maintains close communication with Aurelia, though recent events in the city have set them at odds. Tajic believes that the Boros can trust only the Boros. He is convinced that any effort at peace among the guilds is doomed to failure without the Guildpact. The Boros, he argues, would be better off spending their energy to make themselves stronger so they can uphold the fragile balance that exists now\u2014and protect the innocent when the balance tilts. Aurelia feels that his negative attitude runs the risk of poisoning the hearts of the other Boros and undermining any peace efforts. For the most part, in deference to the angel, Tajic keeps his views to himself." ] } - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -3578,7 +3617,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -4450,7 +4492,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -4817,7 +4862,10 @@ ] }, "Your magic is meant to be subtle and undetectable, but it might pull shadows or clouds of mist around you as you cast your spells. Using the {@spell encode thoughts|GGR} cantrip described below, you can turn a creature's thoughts (including your own) into a thought strand that others can potentially read, share, or steal. These thought strands are treated as valuable currency among the Dimir." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -5038,7 +5086,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -5046,7 +5097,10 @@ "entries": [ "As a Dimir adventurer, you are a member of the guild's network of spies, thieves, assassins, and mind mages that lurks behind the facade of the public guild. On the surface, House Dimir presents the appearance of a network of couriers, investigators, media reporters, and archivists, dealing in information and spreading news. But you and your peers trade in secrets. You use secret symbols, runes, and signals to surreptitiously communicate with other Dimir agents, often in plain sight.", "Like any good spy, you have multiple identities: your true face as an agent of House Dimir; a guildless identity; and a role as a member of another guild. Within that secondary guild, you might already be on a mission for House Dimir, assigned to spy on the guild, collect information about a person, or recruit another spy from the ranks of the guild. Or that guild could be a launching point for your real mission. Perhaps, for example, you were ordered to infiltrate the Azorius in hopes of gaining access to a notorious inmate in an Azorius prison." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -5871,7 +5925,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -6157,7 +6214,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Feature: Dual Personalities", @@ -6799,7 +6859,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -7092,7 +7155,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -7145,7 +7211,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true, @@ -7273,7 +7342,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -7568,7 +7640,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -8406,7 +8481,10 @@ ] }, "Golgari magic is often accompanied by a sickly green glow and a rotting stench." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -8681,7 +8759,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -8691,7 +8772,10 @@ "A classic adventuring role for a member of the Golgari involves crawling through dungeon-like environments\u2014the sewers and ancient vaults of the undercity\u2014in search of treasures left behind by the dead. Sometimes you might be sent to find a specific item believed lost in a dangerous part of the undercity. At other times, you could be asked to collect samples of a specific fungus, retrieve a body floating in the muck of the sewers, or bring back whatever booty you can to help fill the swarm's coffers.", "You might gain enough renown to become a member of the Ochran, assigned to a variety of tasks concerning thievery, assassination, or the protection of important figures in your guild. You might steal something because the guild needs it, or because its loss will bring harm to another guild, hastening that group's decline. You could be assigned to kill an outspoken and active enemy of the Golgari, such as an overzealous Boros captain whose raids into the undercity have approached dangerously close to the swarm's inner sanctum. Or you could serve as a bodyguard to one of Guildmaster Jarad's high chancellors, escorting this figure through the undercity while being ready to intervene at a moment's notice if things go wrong.", "The shamans of the Golgari use their magic to accelerate the cycle of decay and regrowth. You might be sent to spread spores throughout an area that the Golgari want to claim as their territory or to convince the inhabitants of such a territory to abandon it. You might also contend with the ever-present threat of hostile monsters encroaching into Golgari-controlled regions." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -9091,7 +9175,10 @@ ] }, "Fueled by the fire of rage burning in your heart, your magic is almost always accompanied by fiery effects, such as flames smoldering behind your eyes or dancing over your hands." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -9366,7 +9453,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -9375,7 +9465,10 @@ "In service of a simple goal, you have a simple part to play: Fight. Unleash your rage. Flatten buildings and defeat those who stand in your way. Be Gruul, in your own way.", "You will frequently be summoned to participate in a raid your clan is launching against the city or against a group of its defenders. Your clan leader might also send you on a special mission, though it would almost certainly still qualify as a raid. You might join a small group of Gruul warriors on a dangerous charge deep into the settled streets to plunder a certain location, retrieve an item stolen from your clan, or assault a Boros garrison.", "Sometimes your objective might be more esoteric. With prophecies of the return of Ilharg the Raze-Boar spreading like wildfire among the Gruul druids, you might be asked to carry out some task that the druids believe will speed his coming. Such a task might involve collecting a sacred relic held in an Orzhov vault or collecting sacrifices for a grand ceremony in the Raze-Boar's honor." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -9554,7 +9647,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -10114,7 +10210,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "section", @@ -10503,7 +10602,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -11407,7 +11509,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Feature: House Connections", @@ -12193,7 +12298,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "section", @@ -12864,7 +12972,10 @@ "Your spells tend to be loud, flashy, or explosive, even when the effect is unremarkable. For example, when you open the portal of a {@spell rope trick} spell, the portal might be outlined by harmless, showy sparkles.", "If you use an arcane focus, it probably takes the form of an intricate device that could include metal gauntlets, glass canisters, copper tubing, and leather straps attaching it to your body. The {@item mizzium apparatus|GGR} described in {@book chapter 5|GGR|4|Mizzium Apparatus} is a magical version of this gear.", "The {@spell chaos bolt|XGE} spell is a favorite of Izzet spellcasters because of its unpredictable nature." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -13139,14 +13250,20 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", "name": "How Do I Fit In?", "entries": [ "Whatever your role in the Izzet League, you are expected to contribute to its research in some way. That contribution might involve participating in tests, whether as an assistant, a researcher, or a subject. You might be one of the soldiers who protects a laboratory, or a laborer responsible for lifting heavy pieces of equipment into place. Everyone's contribution matters, even if the Izzet know that some matter more than others." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -13604,7 +13721,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -14498,7 +14618,10 @@ ] }, "Your magic tends to manifest as swirling shadows, brilliant light, or sometimes the momentary appearance of shadowy spirit forms. Your spells might draw the blood of your enemies, or even directly touch their souls." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -14773,7 +14896,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -14781,7 +14907,10 @@ "entries": [ "The structure of the Orzhov Syndicate means that you are always doing the bidding of someone higher up the ladder than you are. Ultimately, your role in the guild is defined by whatever the people (and spirits) above you decide for you.", "For most of your career, you can expect to engage in some aspect of the day-to-day criminal operations of the guild. That can mean throwing your weight around to enforce the will of the guild or using religious authority to extort offerings from the people. But it can also mean doing various errands for your superiors, from bearing messages to carrying out assassinations." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -14917,7 +15046,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -16632,7 +16764,10 @@ ] }, "Your magic often produces a flashy spectacle, wreathing you or your targets in a mixture of harmless flame and shadowy shapes. When you manipulate an opponent's mind, a flaming symbol of Rakdos might momentarily appear like a mask over the target's face." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -16907,7 +17042,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -16915,7 +17053,10 @@ "entries": [ "The Cult of Rakdos encourages independent action on the part of its members. Its goal is fomenting chaos, and it firmly believes in putting its own house in disorder before carrying that mission into the larger city. So your role is to execute your vision of grand satire and disruptive performance art, as you aspire to outdo your guild mates and attract the attention of Rakdos himself.", "You work as part of a troupe, with your artistic talents used in service to a ringmaster's vision. But your performance is your own, and no one expects you to follow a script. In fact, if you go through a performance without doing something you haven't done before, you're clearly not trying hard enough." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -17289,7 +17430,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -18096,7 +18240,10 @@ ] }, "Members of the Selesnya Conclave refer to their magic as \"doruvati,\" a Sylvan word meaning \"gift.\" When you use these gifts of Mat'Selesnya, graceful swirls of green and silver light dance in the air around you, and phantasmal green leaves might waft through the air. A sensation of gentle warmth and the smell of spring flowers or autumn leaves might accompany your spells." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -18371,7 +18518,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -18379,7 +18529,10 @@ "entries": [ "Although the guild teaches the importance of subjugating the individual will to the good of the conclave, it also celebrates the diversity of individuals, in the same sense that a field that produces different crops is healthier than one that yields a single crop. That principle applies to your skills as an adventurer. As long as your efforts are directed toward advancing the goals of the guild rather than your private agenda, you're allowed to put your talents to work in your unique way.", "That said, you must never lose sight of the fact that you are part of a larger community. That includes the whole guild, of course, but your ties to community start with your vernadi (enclave) and its voda (dryad leader). The dryads want to know what you're doing and what purpose it serves, and they act to curtail your actions\u2014or even expel you from the guild\u2014if they determine that you aren't serving Selesnya's best interests." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -18747,7 +18900,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -19366,7 +19522,10 @@ ] }, "When your magic causes physical alterations in yourself or others, the result often displays the characteristics of fish, amphibians, or other water-dwelling creatures. Blue-green eddies of magical energy sometimes accompany your spellcasting, forming spirals that reflect the mathematical perfection of nature." - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -19641,7 +19800,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -19649,7 +19811,10 @@ "entries": [ "As a Simic adventurer, your mission likely aligns with the Adaptationist philosophy; the disagreements and tensions among the guilds will soon erupt into open conflict, and your guild needs your help to ensure that the Simic survive. That help might come in the form of defending against Golgari incursions into Simic zonots or shielding Simic research from Azorius intrusion. It could also involve more subtle, diplomatic work to maintain balance among the guilds, or subterfuge aimed at undermining another guild's grab for power.", "Self-improvement is also an important part of your mission. Anything you can do to make yourself more capable\u2014whether learning a new spell or adopting a new hybridizing mutation\u2014gives the Simic a stronger weapon in its arsenal. The combine must change to survive, and that means individual members of the guild must grow and adapt as well." - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true @@ -19766,7 +19931,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "type": "entries", @@ -20065,7 +20233,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } }, { "name": "Suggested Characteristics", @@ -21661,7 +21832,10 @@ "entries": [ "A knighthood is among the lowest noble titles in most societies, but it can be a path to higher status. If you wish to be a knight, choose the Retainers feature instead of the Position of Privilege feature.", "As an emblem of chivalry and the ideals of courtly love, you might include among your equipment a banner or other token from a noble lord or lady to whom you have given your heart-in a chaste sort of devotion." - ] + ], + "data": { + "isFeature": true + } } } ] @@ -22403,7 +22577,10 @@ ] ] } - ] + ], + "data": { + "isFeature": true + } } ], "hasFluff": true, From 85b56f1064394a349fe179ac542e95b285d4d6b6 Mon Sep 17 00:00:00 2001 From: oldewyrm <79656029+oldewyrm@users.noreply.github.com> Date: Sat, 15 Jan 2022 09:39:23 -0700 Subject: [PATCH 4/5] Address comments --- js/5etools-backgrounds.js | 97 ++++----------------------------------- 1 file changed, 9 insertions(+), 88 deletions(-) diff --git a/js/5etools-backgrounds.js b/js/5etools-backgrounds.js index 2b6744be..6784d6a5 100644 --- a/js/5etools-backgrounds.js +++ b/js/5etools-backgrounds.js @@ -117,42 +117,12 @@ function d20plusBackgrounds () { const renderer = new Renderer(); renderer.setBaseUrl(BASE_SITE_URL); - const featureSet = [ - "Cultural Chameleon", - "Dust Digger", - "Favored Event", - "Specialty", - "Contacts", - "How Do I Fit In?", - "Favorite Schemes", - "Faceless Persona", - "Why Are You Here?", - "Fey Mark", - "Feywild Visitor", - "Fishing Tale", - "Harrowing Event", - "Role", - "Path to Mystery", - "Hardship Endured", - "Variant Noble (Knight)", - "A Flair for the Dramatic", - "Life at Sea", - "Claim to Fame", - "Carnival Companion" - ]; let features = []; bg.entries.forEach(e => { let feature = {}; - if (e.name && e.name.includes("Feature:")) { - feature = JSON.parse(JSON.stringify(e)); - feature.name = feature.name.replace("Feature:", "").trim(); - } else if (e.name && ( - e.name.includes("Guild Spells") || // covers all GGR backgrounds - e.name.includes("Origins") // Criminal, Folk Hero, Hermit, Outlander, etc - )) { - feature = JSON.parse(JSON.stringify(e)); - } else if (e.name && featureSet.includes(e.name)) { - feature = JSON.parse(JSON.stringify(e)); + if (e.name && e.data && e.data.isFeature) { + feature = MiscUtil.copy(e); + feature.name = feature.name.replace("/^.*Feature:/", "").trim(); } else return; const renderStack = []; @@ -459,19 +429,15 @@ function d20plusBackgrounds () { let ideal = null; let bond = null; let flaw = null; - const matchCharacteristics = [ - "Suggested Characteristics", // Most backgrounds - "Horror Characteristics" // 'Haunted One' and 'Investigator' - ]; // Get the JSON for all the tables if (bg.entries) { for (const ent of bg.entries) { - if (ent.name && matchCharacteristics.includes(ent.name)) { + if (ent.name && ent.name.includes("Characteristics")) { traits = ent; } else if (ent.entries) { for (const entItem of ent.entries) { // look for embedded characteristics - if (entItem.name && entItem.name === "Suggested Characteristics") { + if (entItem.name && entItem.name.includes("Characteristics")) { traits = entItem; // look for embedded trinkets, and move to features } else if (entItem.name && entItem.name.includes("Trinket")) { @@ -492,63 +458,18 @@ function d20plusBackgrounds () { // This seems to be the best way to parse the information with some room for errors // It seems like the schema is based on on the website, which is why colLabels is where the identifier is if (ent.colLabels && ent.colLabels.length === 2 && ent.rows) { - - /** - * Clean up the formatting and reference syntaxes to get clean text. - * - * @param entry entry to clean up - * @return cleaned text - */ - const _cleanText = function (entry) { - let cleanedText = ""; - let closeIndex = 0; - while (true) { - const firstIndex = entry.indexOf("{@", closeIndex); - if (firstIndex === -1) { - // grab whatever is left in the entry - cleanedText += entry.substring(closeIndex); - break; - } - cleanedText += entry.substring(closeIndex, firstIndex); - const spaceIndex = entry.indexOf(" ", firstIndex); - if (spaceIndex === -1) { - // parse error, just grab the rest and bail - cleanedText += entry.substring(firstIndex); - break; - } - closeIndex = entry.indexOf("}", spaceIndex); - if (closeIndex === -1) { - // parse error, just grab the rest and bail - cleanedText += entry.substring(firstIndex); - break; - } - // look for any reference syntax between the space and the closure - const pipeIndex = entry.substring(spaceIndex + 1, closeIndex).indexOf("|"); - if (pipeIndex === -1) { - // no reference, copy one past space up to closure - cleanedText += entry.substring(spaceIndex + 1, closeIndex); - } else { - // got a reference, copy one past space for number of characters up to pipe - // note that pipeIndex is zero-based - cleanedText += entry.substr(spaceIndex + 1, pipeIndex); - } - closeIndex++; - } - return cleanedText; - }; - switch (ent.colLabels[1]) { case "Personality Trait": - ptrait = ent.rows.map(r => _cleanText(r[1])); + ptrait = ent.rows.map(r => Renderer.stripTags(r[1])); break; case "Ideal": - ideal = ent.rows.map(r => _cleanText(r[1])); + ideal = ent.rows.map(r => Renderer.stripTags(r[1])); break; case "Bond": - bond = ent.rows.map(r => _cleanText(r[1])); + bond = ent.rows.map(r => Renderer.stripTags(r[1])); break; case "Flaw": - flaw = ent.rows.map(r => _cleanText(r[1])); + flaw = ent.rows.map(r => Renderer.stripTags(r[1])); break; } } From 5fbd420dd436ca96ebfe58ab5a90a099b5dad8e5 Mon Sep 17 00:00:00 2001 From: oldewyrm <79656029+oldewyrm@users.noreply.github.com> Date: Sat, 15 Jan 2022 10:25:40 -0700 Subject: [PATCH 5/5] Update 5etools-backgrounds.js --- js/5etools-backgrounds.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/5etools-backgrounds.js b/js/5etools-backgrounds.js index 6784d6a5..2d48703a 100644 --- a/js/5etools-backgrounds.js +++ b/js/5etools-backgrounds.js @@ -122,7 +122,7 @@ function d20plusBackgrounds () { let feature = {}; if (e.name && e.data && e.data.isFeature) { feature = MiscUtil.copy(e); - feature.name = feature.name.replace("/^.*Feature:/", "").trim(); + feature.name = feature.name.replace(/^.*Feature:/, "").trim(); } else return; const renderStack = [];