This is a massively revised and improved version of my PowerShell tool to convert background definitions exported from 5etools into things compatible with Rod's great framework.
There's a bunch of notes in the opening comments. Check and adjust the path definitions around line 75 for your environment. To prepare your source JSON, select the background(s) you want in 5etools, right-click and Pin them. Then go to your Pin list, right-click, and choose "Download JSON Data". THAT file becomes the input used by my tool. The tool is fast - on a decent PC it will process several dozen backgrounds in just a few seconds. Most of the effort is a lot of copy/paste of text from tool output into the FW.
I have run this tool against several dozen backgrounds, but I have NOT exhaustively tested every one of them inside the FW. I tested about a dozen to ensure that the basic class of functions worked as expected - or, in some cases, to determine that my tool is not yet capable of creating a small handful of the backgrounds available. If anyone comes across a problem that's not already discussed in the tool's comments please let me know so I can look into it!
Code: Select all
#
# Import-Background.ps1
#
# This tool is designed to work in support of Rod's D&D 5e Framework for
# Maptool, found at https://forums.rptools.net/viewtopic.php?f=85&t=28435.
#
# It takes as input a file containing D&D 5e Background descriptions
# exported from 5eTools as JSON definitions. It will generate as output
# the following:
#
# 1) For EACH background in the JSON source, a file called <background>.mt.
# The contents of this file are meant to be copy/pasted into the
# framework as a background defined at Lib:Character Creation.
# 2) A single file called parsed-backgrounds.json.
# The contents of this file are meant to be copy/pasted into the
# Menu->Settings->Feats (compendium) Import field. What you are
# pasting in are all the text descriptions of the Backgrounds plus
# the special Feature associated with each Background.
# 3) A single (potentially LONG) line of text is printed to the screen
# after the tool runs. It is a list of all the Background=Feature
# pairs just processed. You should copy/paste this into the
# Menu->Settings->Backgrounds link of the framework.
# Things this tool currently does NOT do:
# o Doesn't check for duplicate Features, which, when imported, will cause
# at least one of the related Backgrounds to generate errors in MT.
# Example: Charlatan, Dimir Operative, and Baldur's Gate Charlatan all
# have a Feature called "False Identity". As a result, these three
# backgrounds could result in three DIFFERENT JSON entries, all duping
# the key of "False Identity". SOLUTION: The user will need to manually
# modify the input file to differentiate these better. PROBABLY Note
# GOING TO FIX THIS IN THE TOOL. THE PROBLEM IS THAT THE SOURCES ARE
# NON-UNIQUE.
#
# o At least one Background has a Language description of "Dwarvish, or one
# lanuage of choice is Dwarvish is already known". I don't know what the
# MT macro code would need to look like to a) figure out if a language is
# already selected and b) perform an appropriate "if <langknown> then
# <select language>". (I know how to do the <select language> portion. If
# someone could help me with a) and the syntax for "if x then" I'll add it.
#
# o For starting equipment, we can only recognize gear that is tagged in the
# JSON sources. For example, the Acolyte's background shows this starting
# gear:
# A {@item holy symbol|phb} (a gift to you when you entered the priesthood),
# a prayer book or prayer wheel, 5 sticks of incense, vestments, a set of
# {@item common clothes|phb}, and a belt {@item pouch|phb} containing 15 gp
#
# The prayer book or prayer wheel, the incense, and the vestments are not
# tagged as @item in the source, and are therefore not added to the
# inventory of the Acolyte. (I will be adding support for defining
# supplemental equipment later.) ON THE LIST TO FIX
#
# o For starting equipment, only grants Qty 1 of items regardless of what the
# background text might say. EXCEPTION: Always grants 20 arrows or
# 10 torches when the item is spec'ed in the background. (So far, these
# are the only exceptions I've encountered in backgrounds.)
#
# o At least one background (Archaeologist) grants ONE tool proficiency, but
# it is from a choice of two options. I don't yet handle this - and any
# .mt file generated will not work correctly. ON THE LIST TO FIX
#
# o At least one background (Far Traveler) requires a Tool Proficiency choice
# of Musical Instrument OR Gaming Set. I don't currently handle this and
# the output .mt file that is created will NOT work. ON THE LIST TO FIX.
#
# o Backgrounds that have 's in them don't display correctly in the list
# when selecting a background. Everything prior to the 's is dropped.
# As a result, choosing these options results in the matching macro to
# not be found. I have a query out to Rod re: whether I can solve this
# by better string quoting. I'll wait to see if this is a bug in the FW
# that can be fixed. If not, the only solution will be to manually rename
# things to avoid use of 's.
$devPath = "C:\Users\dstei\OneDrive\Projects\MapTool\Rod's 5E Framework\Tools\"
$srcFile = $devPath + "ALL-backgrounds.json"
$dstFile = $devPath + "parsed-backgrounds.json"
$jsondata = Get-Content -RAW -Path $srcFile -Encoding UTF8 | ConvertFrom-Json -NoEnumerate
$TextInfo = (Get-Culture).TextInfo
$q = '"' # literal quote
function Make-Macro {
# Let's see if this background grants any kind of tools proficiencies
if ($tools.count -ne 0) {
# Common tools preamble for all backgrounds
$tp = "`r`n`r`n<!-----------------Tools------------------->`r`n"
$tp = $tp + '[h:tools=getLibProperty("Tools","Lib:Character Creation")]' + "`r`n`r`n"
$tp = $tp + '[h:atr=getProperty("Tool Proficiency")]' + "`r`n"
$tp = $tp + '[h:value=getStrProp(atr,"value")]' + "`r`n`r`n"
# Check to see if there's a list of choices that we'll have to query the PC about (example: Clasp Member)
# For each item that the PC can choose we'll also have to make sure that the macro can process their choices
# in the case of generic selections. For example, if there's a choice between "musical instrument" and "gaming set"
# the macro not only has to offer this choice, but will also have to include code to process EITHER choice after selection.
#
# NOT YET IMPLEMENTED
#
#
# Let's look for GENERIC proficiencies and add macro code for each one found
for ($i=0; $i -lt $tools.count; $i++) {
$t = $tools[$i].ToLower()
if ($t -eq "musical instrument") {
$tp = $tp + "<!-----------------Choose Instrument Proficiency------------------->`r`n"
$tp = $tp + "[h:res=input(" + $q + "skill|Background Instrument Proficiency||label|span=true" +$q + ",`r`n"
$tp = $tp + $q + "music|Bagpipes,Drum,Ducimer,Flute,Horn,Lute,Lyre,Pan Flute,Sharm,Viol|Instrument Choice|list|value=string" + $q + ")]`r`n"
$tp = $tp + "[h,if(listfind(value,music)==-1):value=listappend(value,music)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' +"`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
} elseif ($t -eq "gaming set") {
$tp = $tp + "<!-----------------Choose Gaming Proficiency------------------->`r`n"
$tp = $tp + "[h:res=input(" + $q + "skill|Background Gaming Set Proficiency||label|span=true" +$q + ",`r`n"
$tp = $tp + $q + "game|Dice Set,Dragonchess Set,Playing Card Set,Three-Dragon Ante Set|Gaming Set Choice|list|value=string" + $q + ")]`r`n"
$tp = $tp + "[h,if(listfind(value,game)==-1):value=listappend(value,game)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' + "`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
} elseif ($t -eq "artisan's tools") {
$tp = $tp + "<!-----------------Choose Artisan's Proficiency------------------->`r`n"
$tp = $tp + "[h:res=input(" + $q + "skill|Background Artisan Tool Proficiency||label|span=true" +$q + ",`r`n"
$tp = $tp + $q + "artisan|Alchemist's Supplies,Brewer's Supplies,Calligrapher's Supplies,Carprenter's Tools,Cartographer's Tools,Cobbler's Tools,Cook's Tools,Glassblower's Tools,Jeweler's Tools,Leatherworker's Tools,Mason's Tools,Painter's Tools,Smith's Tools,Tinker's Tools,Weaver's Tools,Woodcarver's Tools|Tool Choice|list|value=string" + $q + ")]`r`n"
$tp = $tp + "[h,if(listfind(value,artisan)==-1):value=listappend(value,artisan)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' + "`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
} elseif ($t -eq "vehicles (land)") {
$tp = $tp + "<!-----------------Choose Vehicle Proficiency------------------->`r`n"
$tp = $tp + "[h:res=input(" + $q + "skill|Background Land Vehicle Proficiency||label|span=true" +$q + ",`r`n"
$tp = $tp + $q + "skillchoice|Carriage,Cart,Chariot,Sled,Wagon|Vehicle Choice|list|value=string" + $q + ")]`r`n"
$tp = $tp + "[h,if(listfind(value,skillchoice)==-1):value=listappend(value,skillchoice)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' + "`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
} elseif ($t -eq "vehicles (water)") {
$tp = $tp + "<!-----------------Choose Vehicle Proficiency------------------->`r`n"
$tp = $tp + "[h:res=input(" + $q + "skill|Background Water Vehicle Proficiency||label|span=true" +$q + ",`r`n"
$tp = $tp + $q + "skillchoice|Galley,Keelboat,Longship,Rowboat,Sailing Ship,Warship|Vehicle Choice|list|value=string" + $q + ")]`r`n"
$tp = $tp + "[h,if(listfind(value,skillchoice)==-1):value=listappend(value,skillchoice)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' + "`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
} else {
# This tool is NOT a generic tool, so let's compile macro code that grants SPECIFIC proficiency
$tp = $tp + "<!-----------------" + $t + "Proficiency------------------->`r`n"
$tp = $tp + "[h:tool=" + $q + $t + $q +"]`r`n"
$tp = $tp + "[h,if(listfind(value,tool)==-1):value=listappend(value,tool)]`r`n"
$tp = $tp + '[h:atr=setStrProp(atr,"value",value)]' + "`r`n"
$tp = $tp + '[h:setProperty("Tool Proficiency",atr)]' +"`r`n"
$tp = $tp + "`r`n"
}
}
}
if ($langChoiceCount -gt 0) {
$addSave = $true
$lc = $lc + "`r`n<!-----------------Choose any Standard------------------->`r`n"
$lc = $lc + "[h:res=input(" + $q +"lang|Background Languages||label|span=true" + $q + ",`r`n"
for ($i=1; $i -le $langChoiceCount ; $i++) {
$lc = $lc + $q + "language" + $i + "|Choose one," +$q + "+languages+" +$q + "|Language " + $i + "|list|value=string" + $q +",`r`n"
}
$lc = $lc.TrimEnd(",`r`n") + ")]`r`n"
$lc = $lc + "[h:abort(res)]`r`n`r`n"
for ($i=1; $i -le $langChoiceCount ; $i++) {
$lc = $lc + "[h,if(listfind(value,language" + $i +")==-1):value=listappend(value,language" +$i + ")]`r`n"
}
$lc = $lc + "`r`n"
}
if ($langChoiceList.count -gt 0) {
$addSave = $true
$lc = $lc + "<!-----------------Choose from a Subset of Languages------------------->`r`n"
$lc = $lc + "[h:res=input(" + $q + "lang|Background Languages||label|span=true" +$q + ",`r`n"
$lc = $lc + $q + "langchoice|LANGCHOICELIST|Language Choices|list|value=string" + $q + ")]`r`n"
$lc = $lc + "[h,if(listfind(value,langchoice)==-1):value=listappend(value,langchoice)]`r`n"
$lc = $lc + "`r`n"
}
if ($forcedLangName -ne "") {
$lc = $lc + "<!-----------------Know a SPECIFIC Language------------------->`r`n"
$lc = $lc + "[h,if(listfind(value," + $q + "FORCEDLANGNAME" + $q + ")==-1):value=listappend(value," + $q + "FORCEDLANGNAME" + $q +")]`r`n"
$lc = $lc + "`r`n"
}
if ($addSave) {
$lc = $lc + "<!-----------------Save Languages------------------->`r`n"
$lc = $lc + "[h:atr=setStrProp(atr," + $q + "value" + $q + ",value)]`r`n`r`n"
$lc = $lc + "[h:setProperty(" + $q + "Language Proficiency" + $q + ",atr)]`r`n"
}
# Let's see if this background grants any kind of starting equipment
if ($equiplist.count -gt 0) {
# Common equipment preamble for all backgrounds
$ec = "`r`n<!-----------------Equipment------------------->`r`n"
$ec = $ec + '[h:group="Equipment"]' + "`r`n"
$ec = $ec + '[h:inputList=getLibProperty(group,"Lib:Character")]' + "`r`n"
$ec = $ec + "[h:inputList=json.fields(inputList)]`r`n"
$ec = $ec + '[h:inputList=listSort(inputList,"N")]' + "`r`n"
$ec = $ec + "[h:Property=getProperty(group)]`r`n`r`n"
$ec = $ec + '[h,if(json.type(Property)=="UNKNOWN"):Property="{}";""]' + "`r`n`r`n"
$ec = $ec + '[h:AddItem="Add Item@Lib:Character Creation"]' + "`r`n`r`n"
# Iterate through the list of equipment. When necessary, create drop-down lists when a choice is required.
for ($i=0; $i -lt $equiplist.count; $i++) {
$it = $TextInfo.ToTitleCase($equiplist[$i])
$custom = ""
if ($it -eq "holy symbol") {
# Pick what kind of holy symbol to have
$ec = $ec + '[h:res=input("holysym|Background Equipment||label|span=true",' + "`r`n"
$ec = $ec + '"holy|Amulet,Emblem,Reliquary|Holy Symbol|list|value=string")]' + "`r`n"
$ec = $ec + "[h:abort(res)]`r`n"
$ec = $ec + '[macro(AddItem):"tokenName="+tokenName+";item="+holy+";Quantity=1;customName="]' + "`r`n"
} elseif ($it -eq "artisan's tools") {
# Grant the tool that we previously took a proficiency in
$ec = $ec + '[macro(AddItem):"tokenName="+tokenName+";item="+artisan+";Quantity=1;customName="]' + "`r`n"
} elseif ($it -eq "musical instrument") {
# Grant the tool that we previously took a proficiency in
$ec = $ec + '[macro(AddItem):"tokenName="+tokenName+";item="+music+";Quantity=1;customName="]' + "`r`n"
} elseif ($it -eq "gaming set") {
# Grant the tool that we previously took a proficiency in
$ec = $ec + '[macro(AddItem):"tokenName="+tokenName+";item="+game+";Quantity=1;customName="]' + "`r`n"
} else {
# Add a specific named item to inventory
# We're going to kludge up some code to handle items typically granted in quantity. We don't have any
# knowledge of how many to grant, so we'll assume it defaults to 1 but then call out some well-known
# overrides here.
$qty = 1
if ($it.ToLower() -eq "arrow") { $qty = 20 }
if ($it.ToLower() -eq "torches") { $qty = 10 }
$ec = $ec + '[macro(AddItem):"tokenName="+tokenName+";item=' + $it + ';Quantity=' + $qty + ';customName="]' + "`r`n"
}
}
}
$sc = $sc + "`r`n<!-----------------Skill------------------->`r`n"
$sc = $sc + '[h:attributeList=getLibProperty("Skills", "Lib:Character")]' + "`r`n"
$sc = $sc + "[h:repeat=countStrProp(attributeList)]`r`n"
$sc = $sc + '[h:skillList=""]' + "`r`n"
$sc = $sc + '[h,count(repeat,""),code:{' + "`r`n"
$sc = $sc + " [h:skillList=listappend(skillList,indexKeyStrProp(attributeList,roll.count))]`r`n"
$sc = $sc + "}]`r`n`r`n"
if ($skillChoiceList -ne "") {
# We have to CHOOSE one or more of our skills
# $skill1 is always (assumed to be) a single, named skill and therefore isn't relevant for preparing our selection code
# $skill2 will contain the number of selections to be made (aka the # of linkes in our input statement)
# $skillChoiceList will contain the CSV list of choices to select from
$sc = $sc + "<!-----------------Skill Selection------------------->`r`n"
$sc = $sc + '[h:res=input("skill|Skills||label|span=true",' + "`r`n"
$sc = $sc + '"skillchoice2|Choose one,LISTOFSKILLCHOICES|Skill 2|list|value=string"'
if ($skill2 -eq 2) {
# Handle the need to select TWO skills at time of background selection
$sc = $sc + ",`r`n" + '"skillchoice3|Choose one,"+LISTOFSKILLCHOICES+"|Skill 3|list|value=string")]' + "`r`n"
$sc = $sc + "[h:abort(res)]`r`n"
if ($skill1 -eq "") {
# There were ZERO pre-assigned skills, so we're adding only the two chosen skills
$sc = $sc + '[h:skill="skillchoice2+","+skillchoice3]' + "`r`n"
} else {
# There was ONE pre-assigned skill plus our two choices
$sc = $sc + '[h:skill="SKILL1,"+skillchoice2+","+skillchoice3]' + "`r`n"
}
} else {
# Handle the need to select ONE skill at time of background selection
$sc = $sc + ")]`r`n"
$sc = $sc + "[h:abort(res)]`r`n"
if ($skill1 -eq "") {
# There were ZERO pre-assigned skills, so we're adding only the one chosen skill
$sc = $sc + '[h:skill=skillchoice2]' + "`r`n"
} else {
# There was ONE pre-assigned skill plus our one choice
$sc = $sc + '[h:skill="SKILL1," +skillchoice2]' + "`r`n"
}
}
} else {
# ALL of our background skills are explicitly named
if ($skill2 -eq "" ) {
# We are pre-assigned ONE skill. (Haven't yet come across this IRL, but safety valve...)
$sc = $sc + '[h:skill="SKILL1"]' + "`r`n"
} else {
# We were pre-assigned TWO skills.
$sc = $sc + '[h:skill="SKILL1,SKILL2"]' + "`r`n"
}
}
$mt = $macro
$mt = $mt -replace "BGNAME", $bgName
$mt = $mt -replace "BGFEATURE", $bgFeature
$mt = $mt -replace "SKILLCODE", $sc
$mt = $mt -replace "SKILL1", $skill1
$mt = $mt -replace "SKILL2", $skill2
$mt = $mt -replace "LISTOFSKILLCHOICES", $skillChoiceList
$mt = $mt -replace "LANGCODE", $lc
$mt = $mt -replace "LANGCHOICELIST", $langChoiceList
$mt = $mt -replace "FORCEDLANGNAME", $TextInfo.ToTitleCase($forcedLangName)
$mt = $mt -replace "SKILLCODE", $skillCode
$mt = $mt -replace "EQUIPCODE", $ec
$mt = $mt -replace "TOOLCODE", $tp
$mt = $mt -replace "GOLD", $money
$fname = $devPath,$bgName,".mt" -join ""
$mt | Out-File "$fname"
}
$macro = @'
[h:tokenName=macro.args]
[h:id=findToken(tokenName)]
[h:switchToken(id)]
<!-----------------Background------------------->
[h:atr=setStrProp("","value","BGNAME")]
[h:setProperty("Background",atr)]
<!-----------------Set Skills if empty------------------->
[h:skillList=getLibProperty("Skills", "Lib:Character")]
[h:SkillObject=getProperty("Skills")]
[h:array=json.fromList(skillList,";")]
[h:object=""]
[h,if(json.type(SkillObject)=="UNKNOWN"),count(countStrProp(skillList),"<br><br>"),code:{
[h:skillName=indexKeyStrProp(skillList,roll.count)]
[h:skillAttribute=indexValueStrProp(skillList,roll.count)]
[h:object=json.set(object,"name",skillName)]
[h:object=json.set(object,"prof",0)]
[h:object=json.set(object,"attribute",skillAttribute)]
[h:object=json.set(object,"other",0)]
[r:array=json.set(array,roll.count,object)]
};{}]
[h,if(json.type(SkillObject)=="UNKNOWN"),code:{
[h:setProperty("Skills",array)]
[h:SkillObject=array]
};{}]
SKILLCODE
[h:skills=getProperty("Skills")]
[h,count(listcount(skill)),code:{
[h:currentSkill=listget(skill,roll.count)]
[h:index=listfind(skillList,currentSkill)]
[h:chosenskill=json.get(skills,index)]
[h:value=json.get(chosenskill,"prof")]
[h:chosenskill=json.set(chosenskill,"prof",if(value>1,value,1))]
[h:skills=json.set(skills,index,chosenskill)]
}]
[h:setProperty("Skills",skills)]
<!-----------------FEATURE------------------->
[h:group="Feats"]
[h:inputList=getLibProperty(group,"Lib:Character")]
[h:inputList=json.fields(inputList)]
[h:inputList=listSort(inputList,"N")]
[h:Property=getProperty(group)]
[h:Property=json.set(Property,"BGNAME","Background")]
[h:Property=json.set(Property,"BGFEATURE","Background")]
[h:setProperty(group,Property)]
TOOLCODE
<!-----------------Languages------------------->
[h:atr=getProperty("Language Proficiency")]
[h:value=getStrProp(atr,"value")]
[h:languages=getLibProperty("Languages","Lib:Character Creation")]
[h,count(listcount(value),""),code:{
[h:item=listget(value,roll.count)]
[h:itemFind=listfind(languages,item)]
[h,if(itemFind==-1):"";languages=listdelete(languages,listfind(languages,item))]
}]
LANGCODE
EQUIPCODE
<!-----------------Currency------------------->
[h:currentmoney=getProperty("Currency")]
[h:gp=getStrProp(currentmoney,"GP")]
[h:gp=if(gp=="",0,gp)]
[h:currentmoney=setStrProp(currentmoney,"GP",gp+GOLD)]
[h:setProperty("Currency",currentmoney)]
'@
function Get-EquipmentList {
param ([string]$txt)
# Given a string with embedded equipment as in this form:
# "A set of {@item fine clothes|phb}, a {@item disguise kit|phb}, tools of the con of your choice (ten stoppered bottles filled with colored liquid, a set of weighted dice, a deck of marked cards, or a signet ring of an imaginary duke), and a belt {@item pouch|phb} containing 15 gp"
# return an array of all equipment granted. Note that the item MUST be represented in $txt with the @item tag. SO in the example
# above, the liquid/dice/cards/ring will NOT be part of the array.
# This fancy regex basically strips out all the text OUTSIDE of {}, plus all the @<tag><space> text. It still leaves the |<tag>
# text because I can't figure out the secret regex to do that...so I simply brute-force the removal of that at the end.
## $el = [Regex]::Matches($txt, '(?<={@item )(.*?)(?=\}') | Select -ExpandProperty Value
$el = @($txt | Select-String '(?<={@\w+ )(.*?)(?=})' -AllMatches |% matches)
for ($i=0; $i -lt $el.count; $i++) {
$el[$i] = $el[$i] -replace "\|.*$", "" # remove the trailing (e.g.) |phb tag
}
return ,$el #NOTE: the comma before $el forces the function to return arrays ALWAYS, even if only a single element
}
function Get-Money {
param ([string]$txt)
# Given a string with embedded equipment as in this form:
# "A set of {@item fine clothes|phb}, a {@item disguise kit|phb}, tools of the con of your choice (ten stoppered bottles filled with colored liquid, a set of weighted dice, a deck of marked cards, or a signet ring of an imaginary duke), and a belt {@item pouch|phb} containing 15 gp"
# return the number of GP granted.
#$g = [Regex]::Matches($txt, '(?<=containing )(\d+)(?= gp)') | Select -ExpandProperty Value
$g = ($txt | Select-String '(?<=containing )(\d+)(?= gp)' |% matches)
return $g.value
}
function Key-Exists {
# Test to see if the specified key exists in our object
param ( [object]$obj)
[bool]($obj.psobject.properties.Count -ne 0)
}
function Quote-Literals {
# Encode any string literals that have to be protected
param( [string]$txt )
$txt = $txt -replace '"', '\"' # protect embedded quotes
$txt = $txt -replace " d(\d+?)", ' 1d$1' # replace " dN" with "1dN"
$txt = $txt -replace " (\d+?d\d+?)", '[$1](roll \"$1\")' # encode die rolls
$txt = $txt -replace "^Prerequisite:(.*)",'*Prerequisite: $1*' # KLUDGE: not sure why the replacement in Remove-EncodedText not working?
return $txt
}
function Remove-EncodedText {
param( [string]$link )
# Remove @<text> through to space (inclusive)
# Remove { and }
# Remove anything from first | through to } (inclusive)
$link = $link -replace "\|\D+?}","" # Remove ALL lists from <pipe> through end bracket (inclusive)
$link = $link -replace "{@\D+? ","" # Remove ALL {@<tag><space>
$link = $link -replace "}+?",""
$link = $link -replace "\)\|.*$", ")" # KLUDGE: handles things like "pole (10-foot)|<blah" by removing from just after the ) to the end
$link = $link -replace "{@i (.*)}", '*$1*' # Convert italic directive to italic markup
return ($link)
}
$bgout = "{"
$otherFeatures = "" # holds the collection of Features defined in all the backgrounds
$bgList = @() # list of background=feature; entries
foreach ($bg in $jsondata) {
# BUG: "Dissenter" has a totally mangled source json record. Just drop it quietly from our output.
if ($bg.name -eq "Dissenter") {
continue
}
$bg.name
$bgName = $bg.name
$equiplist = @()
$langChoiceCount = 0 # number of languages this background can select
$langChoiceList = @() # in cases where the list of choices is constrained, these are the options
$forcedLangName = "" # A specific language we must choose (unless we already know it)
$tools = @() # A list of any tools we gain proficiency in as a result of this background
$toolsChoiceList = @() # A list of tools we have to choose from
$skill1 = ""
$skill2 = ""
$skillChoiceList = @()
$money = 0
$out = $q + $bg.name + $q + ":" + $q + "---\n"
# Code doesn't currently handle backgrounds that have _copy keys, so we're going to skip all those entries
if ([bool]($bg.PSobject.Properties.name -match "_copy")) {
$copy = $true
} else {
foreach ($item in $bg.entries[0].items) {
# Sigh. Once again, the TalDorei json sources are a mess. Everyone else places "{@skill skilla},{@skill skillb}" into
# $bg.entries[0].items...but TDCS puts tons of extraneous text here. So going to use skillProficiencies instead (which
# I should have done from the start, anyway.
if ($item.name -eq "Skill Proficiencies") {
$skills = $bg.skillProficiencies | gm -membertype NoteProperty | foreach-object { "$_" }
# If one of the skills do NOT start with "bool" then is is some kind of choice string
if ($skills.count -gt 1) {
if (($skills[0].StartsWith("bool")) -and ($skills[1].StartsWith("bool"))) {
# a normal record with two specified skills
$skill1 = $(Remove-EncodedText $skills[0])
$skill2 = $(Remove-EncodedText $skills[1])
$skill2 = $skill2 -replace "bool ", ""
$skill2 = $skill2 -replace "=true", ""
$skill2 = $skill2.Trim()
$skill2 = $TextInfo.ToTitleCase($skill2)
} elseif ($skills[0].StartsWith("bool")) {
# $skills[1] is some kind of non-standard format
$skill1 = $(Remove-EncodedText $skills[0])
$skill2 = 1
} elseif ($skills[1].StartsWith("bool")) {
# $skills[0] is some kind of non-standard format
$skill1 = $(Remove-EncodedText $skills[1])
$skill2 = 1
}
} else {
if ($skills.StartsWith("bool")) {
# OK - there was only a SINGLE explicit skills assignment.
$skill1 = $(Remove-EncodedText $skills[0])
} else {
# Wow. The only thing that came back is some kind of list that we're supposed to select from.
# This is something like a really braindead entry for "Lyceum Student", where we're supposed
# to select TWO choices and have NONE granted outright.
$skill1 = ""
$skill2 = 2
}
}
if ($skill1 -ne "") {
# We for back (at least one) explicit skill grant, so clean it up
$skill1 = $skill1 -replace "bool ", ""
$skill1 = $skill1 -replace "=true", ""
$skill1 = $skill1.Trim()
$skill1 = $TextInfo.ToTitleCase($skill1)
}
if ($skill2 -ge 1) {
# If skill2 is "", then we only had a single explicit skill grant.
# If skill2 is a number, then at least one of our skill choices must be made at runtime
if (Key-Exists $bg.skillProficiencies[0].choose.from) {
# This is a mangled TalDorei-style entry. In fact, it is so stupid that we can't detect how many
# choices are to be made because they called the key "count", and anytime we do <path>.count we don't get
# the count attribute, we instead get the count of elements under the "from" key. So HORRIBLE
# brute force here.
$skillChoiceList = $bg.skillProficiencies[0].choose.from
$skillChoiceList = $TextInfo.ToTitleCase($skillChoiceList -join ",")
}
}
$skillList = $skill1
if ($skillChoiceList -ne "") {
# If we've got a list, then $skill2 = [int]# of choices to be made...
# skillList is a text string printed next to Skill Proficiencies in the background header
# skillChoiceList is a text string containing CSV values used to construct dropdown lists in the macro
$skillList = $skillList + " plus your choice of "
if ($skill2 -eq 2) {
$skillList = $skillList + "two from "
} else {
$skillList = $skillList + "one from "
}
$skillList = $skillList + $skillChoiceList
} else {
#...otherwise, $skill2 is a single, named skill
$skillList = $skillList + $skill2
}
$out = $out + "**Skill Proficiencies** " + $skillList + " \n"
}
if ($item.name -eq "Tool Proficiencies") {
$out = $out + "**Tool Proficiencies** " + $(Remove-EncodedText $item.entry ) + " \n"
# Figure out what our proficiencies are
$tools = @($bg.toolProficiencies | gm -membertype NoteProperty | foreach-object { "$_" })
for ($i=0; $i -lt $tools.count; $i++) {
# Ugh. This is SO fuggly...but I don't yet understand how to better deal with NoteProperties, so
# I'm just brute-forcing my way to an answer.
$temp = $tools[$i]
$temp = $temp -replace "bool ", ""
$temp = $temp -replace "=true", ""
$tools[$i] = $temp
}
# Check to see whether this background has a list of tools you can choose from
$toolsChoiceList = $bg.toolProficiencies.choose.from
}
if ($item.name -eq "Languages") {
$out = $out + "**Language** " + $(Remove-EncodedText $item.entry ) + " \n"
# Do we have any forced languages?
# Some entries (notably the TDCS ones like Clasp Member) are missing LanguageProficiencies keys.
if (-not(Key-Exists($bg.languageProficiencies))) {
if ($bg.source -eq "TalDorei") {
for ($i=0; $i -lt $bg.entries[0].count; $i++) {
# Let's hunt for a Languages tag
if(Key-Exists $bg.entries[0].items[$i] -and $bg.entries[0].items[$i] -eq "Languages") {
$temp = $bg.entries[0].items[$i].entry
if ($temp -eq "Thieves' Cant") { $forcedLangName = "Thieves' Cant" }
if ($temp -eq "One of your choice") { $langChoiceCount = 1 }
if ($temp -eq "Two of your choice") { $langChoiceCount = 2 }
}
}
}
} else {
$forcedLangName = $bg.languageProficiencies[0].psobject.properties.name
if ($forcedLangName -eq "anyStandard") {
# No - we can pick N number of languages. Example: Acolyte
$forcedLangName = ""
$langChoiceCount = $bg.languageProficiencies[0].anyStandard
} elseif ($bg.languageProficiencies[1].anyStandard -gt 0) {
# Yes - we can pick N language IF we already know the forced language. Example: Clan Crafter (24)
$langChoiceCount = $bg.languageProficiencies[1].anyStandard
} elseif ($bg.languageProficiencies.choose.from.count -ne 0) {
# We have a small list from which we can choose languages: Example: Izzet Engineer
$forcedLangName = ""
$langChoiceList = $bg.languageProficiencies.choose.from
$langChoiceList = $TextInfo.ToTitleCase($langChoiceList -join ",")
}
}
}
if ($item.name -eq "Equipment") {
$out = $out + "**Equipment** " + $(Remove-EncodedText $item.entry ) + "\n\n"
$equiplist = Get-EquipmentList ($item.entry)
$money = Get-Money ($item.entry)
if ($money -le 0) { $money = 0 }
}
}
for($node=1;$node -lt $bg.entries.count;$node++) {
# Node 0: Proficiencies, languages, equipment
# Node 1-n: Features, tables, etc.
foreach ($item in $bg.entries[$node]) {
if (-not (Key-Exists($item.name))) {
# Issue: House Agent has a node that doesn't have a .name entry that will be encountered BEFORE
# getting to the Feature node. This causes a "null-valued expression" error but this error is benign.
continue
}
if ($item.name.StartsWith("Feature: ")) {
# Issue: House Agent has a node that doesn't have a .name entry that will be encountered BEFORE
# getting to the Feature node. This causes a "null-valued expression" error but this error is benign.
# Create a separate JSON entry for features
# All entries of this node are part of the feature
$bgFeature = $item.name.Replace("Feature: ","")
$otherFeatures = $otherFeatures + $q + $bgFeature + $q + ':"'
$bgList += ($bg.name + "=" + $bgFeature)
for($subnode=0;$subnode -lt $item.entries.count;$subnode++) {
$otherFeatures = $otherFeatures + $(Quote-Literals $item.entries[$subnode]) + "\n\n"
}
$otherFeatures = $otherFeatures + $q + ",`r`n"
#BUG: "Inheritance" has a table in the Features section which isn't correctly handled here. This
# makes both Inheritor and Inheritance unusable.
# BUG: "Trail of the Five Gods" feature has a bunch of fancy stuff...not handled here.
} else {
# ...otherwise, compile section header...
$out = $out + "#### " + $item.name + "\n\n"
# Huge kludge. $item is often a mix of raw strings + complex objects (like tables). The
# raw strings are hard to figure out, so basically we look at all items here and capture those
# that are of type string, leaving the things like tables for other processes.
# BUG: Izzet Engineer has an entry node with text, table, and text. The 2nd batch of text is
# *supposed* to appear AFTER the table is rendered, but currently appears BEFORE (and also messes
# up the table). May need to stop looking for more strings as soon as we find our first table,
# and then continue looking for strings after we render each table.
for ($i=0 ; $i -lt $item.entries.count; $i++) {
if ($item.entries[$i] -is [string]) {
$out = $out + $(Quote-Literals $(Remove-EncodedText($item.entries[$i]))) + "\n\n"
}
}
# ...and then check for table entries
$HAkludge = $false
foreach ($tbl in $bg.entries[$node].entries) {
# Kludge: House Agent has a table in a non-standard place that we don't normally look at
# If doing house agent, the first time through this loop look at our non-standard table
# NOTE: This table will appear later in our output than in the original source material, but
# the diff is minor and I can live with it.
if ($bg.name -eq "House Agent" -and -not $HAkludge) {
$tbl = $bg.entries[1]
$HAkludge = $true
}
if ($tbl.type -eq "table") {
if ($tbl.caption -ne $null) {
$out = $out + "##### " + $tbl.caption + "\n\n"
}
# Column headers
$out = $out + "|[" + $tbl.colLabels[0] + "](roll \" + $q + "1" + $tbl.colLabels[0] + "\" + $q + ")|" + $tbl.colLabels[1] + "|\n"
$out = $out + "|:---:|---|\n"
# Table rows
foreach ($row in $tbl.rows) {
$out = $out + "|" + $row[0] + "|" + $(Quote-Literals($(Remove-EncodedText $row[1]))) + "|\n"
}
$out = $out + "\n"
}
}
}
}
}
$bgout = $bgout + $out + $q + ","
$bgout = $bgout + "`r`n"
}
Make-Macro
}
$bgout = $bgout + $otherFeatures.TrimEnd(",`r`n") + "`r`n}"
$bgout | Out-File $dstFile
Write-Host "`r`n"
Write-Host "Done! Copy/paste the following string of text into the framework Settings-->Background entry:`r`n"
$(($bgList | Sort-Object) -join ";")