diff --git a/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic.hde b/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic.hde
new file mode 100644
index 0000000000..b717f4148b
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic.hde
@@ -0,0 +1,5966 @@
+HeroSystem6eHeroic
HeroSystem6eHeroic
Version 2.4
Export format for the Roll20 API script HeroSystem6eHeroic_HDImporter, which imports Hero Designer characters into the HeroSystem6eHeroic character sheet.
For documentation see https://github.com/Roll20/roll20-api-scripts/tree/master/HeroSystem6eHeroic_HDImporter
By Villain In Glasses (Roll20 ID 633423)
+txt
+!hero --import {
+ "character":{
+ "character_name":"",
+ "character_title":"",
+ "height":"",
+ "weight":"",
+ "eyes":"",
+ "hair":"",
+ "backgroundText":"",
+ "historyText":"",
+ "appearance":"",
+ "tactics":"",
+ "campaignUse":"",
+ "quote":"",
+ "experience":"",
+ "experienceBenefit":"",
+ "size":"",
+ "strength":"",
+ "dexterity":"",
+ "constitution":"",
+ "intelligence":"",
+ "ego":"",
+ "presence":"",
+ "ocv":"",
+ "dcv":"",
+ "omcv":"",
+ "dmcv":"",
+ "speed":"",
+ "pd":"",
+ "ed":"",
+ "body":"",
+ "stun":"",
+ "endurance":"",
+ "recovery":"",
+ "running":"",
+ "leaping":"",
+ "swimming":"",
+ "equipment":{
+ "equipment01":{1
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 1},
+ "equipment02":{2
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 2},
+ "equipment03":{3
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 3},
+ "equipment04":{4
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 4},
+ "equipment05":{5
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 5},
+ "equipment06":{6
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 6},
+ "equipment07":{7
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 7},
+ "equipment08":{8
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 8},
+ "equipment09":{9
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 9},
+ "equipment10":{10
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 10},
+ "equipment11":{11
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 11},
+ "equipment12":{12
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 12},
+ "equipment13":{13
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 13},
+ "equipment14":{14
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 14},
+ "equipment15":{15
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 15},
+ "equipment16":{16
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 16},
+ "equipment17":{17
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 17},
+ "equipment18":{18
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 18},
+ "equipment19":{19
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 19},
+ "equipment20":{20
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 20},
+ "equipment21":{21
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 21},
+ "equipment22":{22
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 22},
+ "equipment23":{23
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 23},
+ "equipment24":{24
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 24},
+ "equipment25":{25
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 25},
+ "equipment26":{26
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 26},
+ "equipment27":{27
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 27},
+ "equipment28":{28
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 28},
+ "equipment29":{29
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 29},
+ "equipment30":{30
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 30},
+ "equipment31":{31
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 31},
+ "equipment32":{32
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 32},
+ "equipment33":{33
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 33},
+ "equipment34":{34
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 34},
+ "equipment35":{35
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 35},
+ "equipment36":{36
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 36},
+ "equipment37":{37
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 37},
+ "equipment38":{38
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 38},
+ "equipment39":{39
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 39},
+ "equipment40":{40
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 40},
+ "equipment41":{41
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 41},
+ "equipment42":{42
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 42},
+ "equipment43":{43
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 43},
+ "equipment44":{44
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 44},
+ "equipment45":{45
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 45},
+ "equipment46":{46
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 46},
+ "equipment47":{47
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 47},
+ "equipment48":{48
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 48},
+ "equipment49":{49
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 49},
+ "equipment50":{50
+
+
+ "name":"(Multipower) ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"(MPSlot ",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+
+
+ "name":"",
+ "text":"",
+ "damage":"",
+ "end":"",
+ "range":"",
+ "mass":"",
+ "attack":"true",
+ "defense":"true",
+ "notes":""
+
+
+ 50}
+ },
+ "maneuvers":{
+ "maneuver01":{
+
+ 1
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 1
+
+ },
+ "maneuver02":{
+
+ 2
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 2
+
+ },
+ "maneuver03":{
+
+ 3
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 3
+
+ },
+ "maneuver04":{
+
+ 4
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 4
+
+ },
+ "maneuver05":{
+
+ 5
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 5
+
+ },
+ "maneuver06":{
+
+ 6
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 6
+
+ },
+ "maneuver07":{
+
+ 7
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 7
+
+ },
+ "maneuver08":{
+
+ 8
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 8
+
+ },
+ "maneuver09":{
+
+ 9
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 9
+
+ },
+ "maneuver10":{
+
+ 10
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 10
+
+ },
+ "maneuver11":{
+
+ 11
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 11
+
+ },
+ "maneuver12":{
+
+ 12
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 12
+
+ },
+ "maneuver13":{
+
+
+
+ 1
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 1
+
+
+
+ },
+ "maneuver14":{
+
+
+
+ 2
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 2
+
+
+
+ },
+ "maneuver15":{
+
+
+
+ 3
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 3
+
+
+
+ },
+ "maneuver16":{
+
+
+
+ 4
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 4
+
+
+
+ },
+ "maneuver17":{
+
+
+
+ 5
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 5
+
+
+
+ },
+ "maneuver18":{
+
+
+
+ 6
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 6
+
+
+
+ },
+ "maneuver19":{
+
+
+
+ 7
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 7
+
+
+
+ },
+ "maneuver20":{
+
+
+
+ 8
+ "name":"",
+ "points":"",
+ "phase":"",
+ "ocv":"",
+ "dcv":"",
+ "effect":"",
+ "notes":""
+ 8
+
+
+
+ }
+ },
+ "perks":{
+ "perk01":{
+
+ 1
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 1
+ },
+ "perk02":{
+
+ 2
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 2
+ },
+ "perk03":{
+
+ 3
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 3
+ },
+ "perk04":{
+
+ 4
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 4
+ },
+ "perk05":{
+
+ 5
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 5
+ },
+ "perk06":{
+
+ 6
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 6
+ },
+ "perk07":{
+
+ 7
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 7
+ },
+ "perk08":{
+
+ 8
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 8
+ },
+ "perk09":{
+
+ 9
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 9
+ },
+ "perk10":{
+
+ 10
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 10
+ }
+ },
+ "talents":{
+ "talent01":{
+
+ 1
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 1
+ },
+ "talent02":{
+
+ 2
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 2
+ },
+ "talent03":{
+
+ 3
+ "type":"",
+ "points":"",
+ "text":" ",
+ "notes":""
+ 3
+ },
+ "talent04":{
+
+ 4
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 4
+ },
+ "talent05":{
+
+ 5
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 5
+ },
+ "talent06":{
+
+ 6
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 6
+ },
+ "talent07":{
+
+ 7
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 7
+ },
+ "talent08":{
+
+ 8
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 8
+ },
+ "talent09":{
+
+ 9
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 9
+ },
+ "talent10":{
+
+ 10
+ "type":"",
+ "points":"",
+ "text":"
+ ",
+ "notes":""
+ 10
+ }
+ },
+ "complications":{
+ "complication01":{
+
+ 1
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 1
+ },
+ "complication02":{
+
+ 2
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 2
+ },
+ "complication03":{
+
+ 3
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 3
+ },
+ "complication04":{
+
+ 4
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 4
+ },
+ "complication05":{
+ 5
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 5
+ },
+ "complication06":{
+ 6
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 6
+ },
+ "complication07":{
+ 7
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 7
+ },
+ "complication08":{
+ 8
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 8
+ },
+ "complication09":{
+ 9
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 9
+ },
+ "complication10":{
+ 10
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 10
+ },
+ "complication11":{
+ 11
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 11
+ },
+ "complication12":{
+ 12
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 12
+ },
+ "complication13":{
+ 13
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 13
+ },
+ "complication14":{
+ 14
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 14
+ },
+ "complication15":{
+ 15
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 15
+ },
+ "complication16":{
+ 16
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 16
+ },
+ "complication17":{
+ 17
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 17
+ },
+ "complication18":{
+ 18
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 18
+ },
+ "complication19":{
+ 19
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 19
+ },
+ "complication20":{
+ 20
+ "type":"",
+ "points":"",
+ "text":"",
+ "notes":""
+ 20
+ }
+ },
+ "powers":{
+ "power01":{
+ 1
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 1
+ },
+ "power02":{
+ 2
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 2
+ },
+ "power03":{
+ 3
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 3
+ },
+ "power04":{
+ 4
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 4
+ },
+ "power05":{
+ 5
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 5
+ },
+ "power06":{
+ 6
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 6
+ },
+ "power07":{
+ 7
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 7
+ },
+ "power08":{
+ 8
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 8
+ },
+ "power09":{
+ 9
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 9
+ },
+ "power10":{
+ 10
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 10
+ },
+ "power11":{
+ 11
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 11
+ },
+ "power12":{
+ 12
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 12
+ },
+ "power13":{
+ 13
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 13
+ },
+ "power14":{
+ 14
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 14
+ },
+ "power15":{
+ 15
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 15
+ },
+ "power16":{
+ 16
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 16
+ },
+ "power17":{
+ 17
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 17
+ },
+ "power18":{
+ 18
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 18
+ },
+ "power19":{
+ 19
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 19
+ },
+ "power20":{
+ 20
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 20
+ },
+ "power21":{
+ 21
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 21
+ },
+ "power22":{
+ 22
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 22
+ },
+ "power23":{
+ 23
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 23
+ },
+ "power24":{
+ 24
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 24
+ },
+ "power25":{
+ 25
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 25
+ },
+ "power26":{
+ 26
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 26
+ },
+ "power27":{
+ 27
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 27
+ },
+ "power28":{
+ 28
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 28
+ },
+ "power29":{
+ 29
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 29
+ },
+ "power30":{
+ 30
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 30
+ },
+ "power31":{
+ 31
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 31
+ },
+ "power32":{
+ 32
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 32
+ },
+ "power33":{
+ 33
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 33
+ },
+ "power34":{
+ 34
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 34
+ },
+ "power35":{
+ 35
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 35
+ },
+ "power36":{
+ 36
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 36
+ },
+ "power37":{
+ 37
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 37
+ },
+ "power38":{
+ 38
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 38
+ },
+ "power39":{
+ 39
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 39
+ },
+ "power40":{
+ 40
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 40
+ },
+ "power41":{
+ 41
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 41
+ },
+ "power42":{
+ 42
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 42
+ },
+ "power43":{
+ 43
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 43
+ },
+ "power44":{
+ 44
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 44
+ },
+ "power45":{
+ 45
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 45
+ },
+ "power46":{
+ 46
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 46
+ },
+ "power47":{
+ 47
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 47
+ },
+ "power48":{
+ 48
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 48
+ },
+ "power49":{
+ 49
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 49
+ },
+ "power50":{
+ 50
+
+
+ "name":"(Multipower) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"(MPSlot ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+
+ "name":"",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+
+ "name":"(VPP) ",
+ "base":"",
+ "text":"",
+ "notes":"",
+ "cost":"",
+ "endurance":"",
+ "damage":"",
+
+
+ "compound":"true"
+
+
+ "compound":"false"
+
+ 50
+ }
+ },
+ "skills": {
+ "skill01": {
+ 1
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 1
+ },
+ "skill02": {
+ 2
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 2
+ },
+ "skill03": {
+ 3
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 3
+ },
+ "skill04": {
+ 4
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 4
+ },
+ "skill05": {
+ 5
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 5
+ },
+ "skill06": {
+ 6
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 6
+ },
+ "skill07": {
+ 7
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 7
+ },
+ "skill08": {
+ 8
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 8
+ },
+ "skill09": {
+ 9
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 9
+ },
+ "skill10": {
+ 10
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 10
+ },
+ "skill11": {
+ 11
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 11
+ },
+ "skill12": {
+ 12
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 12
+ },
+ "skill13": {
+ 13
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 13
+ },
+ "skill14": {
+ 14
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 14
+ },
+ "skill15": {
+ 15
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 15
+ },
+ "skill16": {
+ 16
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 16
+ },
+ "skill17": {
+ 17
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 17
+ },
+ "skill18": {
+ 18
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 18
+ },
+ "skill19": {
+ 19
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 19
+ },
+ "skill20": {
+ 20
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 20
+ },
+ "skill21": {
+ 21
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 21
+ },
+ "skill22": {
+ 22
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 22
+ },
+ "skill23": {
+ 23
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 23
+ },
+ "skill24": {
+ 24
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 24
+ },
+ "skill25": {
+ 25
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 25
+ },
+ "skill26": {
+ 26
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 26
+ },
+ "skill27": {
+ 27
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 27
+ },
+ "skill28": {
+ 28
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 28
+ },
+ "skill29": {
+ 29
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 29
+ },
+ "skill30": {
+ 30
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 30
+ },
+ "skill31": {
+ 31
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 31
+ },
+ "skill32": {
+ 32
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 32
+ },
+ "skill33": {
+ 33
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 33
+ },
+ "skill34": {
+ 34
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 34
+ },
+ "skill35": {
+ 35
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 35
+ },
+ "skill36": {
+ 36
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 36
+ },
+ "skill37": {
+ 37
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 37
+ },
+ "skill38": {
+ 38
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 38
+ },
+ "skill39": {
+ 39
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 39
+ },
+ "skill40": {
+ 40
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 40
+ },
+ "skill41": {
+ 41
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 41
+ },
+ "skill42": {
+ 42
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 42
+ },
+ "skill43": {
+ 43
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 43
+ },
+ "skill44": {
+ 44
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 44
+ },
+ "skill45": {
+ 45
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 45
+ },
+ "skill46": {
+ 46
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 46
+ },
+ "skill47": {
+ 47
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 47
+ },
+ "skill48": {
+ 48
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 48
+ },
+ "skill49": {
+ 49
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 49
+ },
+ "skill50": {
+ 50
+ "name":"",
+ "enhancer":"true",
+ "text":"",
+ "display":"",
+ "attribute":"getCharacteristicString",
+ "base":"",
+ "levels":"",
+ "cost":""
+ 50
+ }
+ },
+ "playerName":"",
+ "gmName":"",
+ "characterFile":"",
+ "template":"templatePath",
+ "versionHD":"",
+ "timeStamp":"",
+ "genre":"",
+ "campaign":"",
+ "version":"2.4",
+ "HeroSystem6eHeroic":"true"
+ }
+}
+
+\\/
+(?<=[^\^\t:])(")(?=[^\>\}\]\:\n][^\n])''
+(\n)+
+(\t)+
+(\s)+
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic_HDImporter.js b/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic_HDImporter.js
new file mode 100644
index 0000000000..f2b11158f4
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/HeroSystem6eHeroic_HDImporter.js
@@ -0,0 +1,5434 @@
+/* HeroSystem6eHeroic_HDImporter.js
+* Hero Designer Importer for the Roll20 Hero System 6e Heroic character sheet
+* Version: 2.4
+* By Villain in Glasses
+* villaininglasses@icloud.com
+* Discord: Villain#0604
+* Roll20: https://app.roll20.net/users/633423/villain-in-glasses
+* Hero Games Forum Thread:
+* https://www.herogames.com/forums/topic/101627-new-roll20-character-sheet-hero-system-6e-heroic/
+*
+* Purpose: Imports characters created in Hero Designer into a Roll20 HeroSystem6eHeroic campaign.
+*
+* Installation: Paste this script into the API setup area of your Roll20 HeroSystem6eHeroic campaign.
+*
+* Copy "HeroSystem6eHeroic.hde" into your Hero Designer export format folder.
+*
+* Use: from Hero Designer export a character using HeroSystem6eHeroic.hde found in this repository as the selected format.
+* This will produce a text file with the name of the character (e.g., myCharacter.txt).
+*
+* Open the exported file in your favorite text editor. Select all of the contents and copy it.
+* Paste the copied text in the chat window of your Roll20 HeroSystem6eHeroic campaign. Hit enter.
+*
+* Commands:
+* Import character: "!hero --import {character text}"
+* Help: "!hero --help"
+* Config: "!hero --config"
+*
+* Based on BeyondImporter Version O.4.0 by
+* Robin Kuiper
+* Discord: Atheos#1095
+* Roll20: https://app.roll20.net/users/1226016/robin
+*
+* Matt DeKok
+* Discord: Sillvva#2532
+* Roll20: https://app.roll20.net/users/494585/sillvva
+*
+* Ammo Goettsch
+* Discord: ammo#7063
+* Roll20: https://app.roll20.net/users/2990964/ammo
+*/
+
+(function() {
+ // Constants
+ const versionMod = "2.4";
+ const versionSheet = "4.51"; // Note that a newer sheet will make upgrades as well as it can.
+ const needsExportedVersion = new Set(["1.0", "2.0", "2.1", "2.2", "2.3", "2.4"]); // HeroSystem6eHeroic.hde versions allowed.
+ const designerVersion = "20220801";
+
+ const defaultAttributes = {
+
+ // Bio
+ character_title: "hero",
+ backgroundText: "",
+ historyText: "",
+ experience: 0,
+ money: 0,
+
+ // Tally Bar
+ characteristicsCost: 0,
+
+ // Primary Attributes.
+ // We need strengthNet for weapons and vehicles.
+ size: 0,
+ strength: 10,
+ strengthNet: 10,
+ dexterity: 10,
+ constitution: 10,
+ intelligence: 10,
+ ego: 10,
+ presence:10,
+
+ // Combat Attributes
+ ocv: 3,
+ dcv: 3,
+ omcv: 3,
+ dmcv: 3,
+ speed: 2,
+ pd: 2,
+ ed: 2,
+ body: 10,
+ stun: 20,
+ endurance: 20,
+ recovery: 4,
+
+ // Movement Attributes
+ running: 12,
+ leaping: 4,
+ swimming: 4,
+
+ // Health Status Attributes
+ CurrentBODY: 10,
+ CurrentSTUN: 20,
+ CurrentEND: 20,
+ CurrentMainEND: 0,
+ CurrentAuxiliaryEND: 0,
+
+ // Make characteristic maximums default to no.
+ useCharacteristicMaximums: 0,
+ optionTakesNoSTUN: 0,
+
+ // Skill levels
+ skillLevels73: 0,
+ skillLevels74: 0,
+ skillLevels75: 0,
+ interactionLevelsCP: 0,
+ intellectLevelsCP: 0,
+ agilityLevelsCP: 0,
+ noncombatLevelsCP: 0,
+ overallLevelsCP: 0
+ }
+
+ let hero_caller = {};
+ let object; // This is the character object.
+
+
+ // Styling for the chat responses.
+ const style = "margin-left: 0px; overflow: hidden; background-color: royalblue; border: 2px solid #fff990; padding: 5px; border-radius: 5px; color: white; div#home a:link { color: #70DB93; }";
+ const buttonStyle = "background-color: dodgerblue; border: 1px solid #292929; width: 25%; border-radius: 3px; padding: 5px; color: #fff; text-align: center; float: right;";
+ const altButtonStyle = "background-color: orange; border: 1px solid #292929; border-radius: 3px; padding: 5px; color: #fff; text-align: center; float: right;";
+ const linkStyle = "color: green;"
+
+ const script_name = 'HDImporter';
+ const state_name = 'HDIMPORTER';
+ var verbose = false;
+
+
+ // Start messages
+ on('ready', function() {
+ checkInstall();
+ log(script_name + ' Ready! Command: !hero');
+ //sendChat(script_name, script_name + ' Ready!\n For help enter "!hero --help"', null, {noarchive:true});
+ sendChat(script_name, '' + script_name + ' Ready!
For help enter "!hero --help"
', null, {noarchive:true});
+ });
+
+
+ on('chat:message', (msg) => {
+ if (msg.type != 'api') return;
+
+ // Split the message into command and argument(s)
+ let args = msg.content.split(/ --(help|reset|config|imports|import) ?/g);
+ let command = args.shift().substring(1).trim();
+
+ if (command === "") {
+ return;
+ }
+
+ hero_caller = getObj('player', msg.playerid);
+
+ if (command !== 'hero') {
+ return;
+ }
+
+ let importData = "";
+ if(args.length < 1) { sendHelpMenu(hero_caller); return; }
+
+ let config = state[state_name][hero_caller.id].config;
+
+ for(let i = 0; i < args.length; i+=2) {
+ let k = args[i].trim();
+ let v = args[i+1] != null ? args[i+1].trim() : null;
+ let check;
+
+ v = cleanQuotes(v, script_name);
+
+ check = Array.from(v.replace(/\s/g, ''));
+
+ switch(k) {
+ case 'help':
+ sendHelpMenu(hero_caller);
+ return;
+
+ case 'reset':
+ state[state_name][hero_caller] = {};
+ setDefaults(true);
+ sendConfigMenu(hero_caller);
+ return;
+
+ case 'config':
+ if(args.length > 0){
+ let setting = v.split('|');
+ let key = setting.shift();
+ let value = (setting[0] === 'true') ? true : (setting[0] === 'false') ? false : (setting[0] === '[NONE]') ? '' : setting[0];
+
+ if(key === 'prefix' && value.charAt(0) !== '_' && value.length > 0) { value = value + ' ';}
+ if(key === 'suffix' && value.charAt(0) !== '_' && value.length > 0) { value = ' ' + value}
+
+ state[state_name][hero_caller.id].config[key] = value;
+ }
+
+ sendConfigMenu(hero_caller);
+ return;
+
+ case 'imports':
+ if(args.length > 0){
+ let setting = v.split('|');
+ let key = setting.shift();
+ let value = (setting[0] === 'true') ? true : (setting[0] === 'false') ? false : (setting[0] === '[NONE]') ? '' : setting[0];
+
+ state[state_name][hero_caller.id].config.imports[key] = value;
+ }
+
+ sendConfigMenu(hero_caller);
+ return;
+
+ case 'import':
+ if (check.length < 2100) {
+ // Intended character data length is safely less than the minimum character file size if exported with HDE format version 1.0.
+ // This is likely an error.
+ sendChat(script_name, 'Hero Importer finished early because the import command does not appear to contain valid character data.
' );
+ return;
+ } else if ( (check[0] !== "{") && (check[check.length - 1] !== "}")) {
+ // Improper JSON format.
+ sendChat(script_name, 'Hero Importer finished early because the import command does not appear to contain valid character data.
' );
+ return;
+ }
+
+ importData = v.replace(/[\n\r]/g, '');
+ break;
+
+ default:
+ sendHelpMenu(hero_caller);
+ return;
+ }
+ }
+
+ if ((importData === '') || (typeof importData === "undefined")) {
+ return;
+ }
+
+ var json = importData;
+ var character = null;
+
+ // Try to catch some bad input. Doesn't currently catch no input.
+ try {
+ character = JSON.parse(json).character;
+ }
+
+ catch(error) {
+ let message = "";
+ needsExportedVersion.forEach(function(value) {
+ message += value + ", ";
+ });
+
+ // Drop the last comma.
+ message = message.slice(0, -2);
+
+ sendChat(script_name, 'Hero Importer ended early due to a source content error.
' );
+ sendChat(script_name, "Please verify that the character file was exported using HeroSystem6eHeroic.hde (acceptable versions: "+message+"). For help use the command !hero --help.");
+ return;
+ }
+
+ // Verify that the character was exported with the latest version of HeroSystem6eHeroic.hde. If not, report error and abort.
+ if (needsExportedVersion.has(character.version) === false) {
+ var last;
+ needsExportedVersion.forEach(k => { last = k });
+
+ sendChat(script_name, 'Import of ' + character.character_name + ' ended early due to version mismatch error.
' );
+ sendChat(script_name, "Please download and install the latest version of HeroSystem6eHeroic.hde (version: "+last+" recommended) into your Hero Designer export formats folder. Export your character and try HD Importer again. For help use the command !hero --help." );
+
+ return;
+ }
+
+ sendChat(script_name, 'Import of ' + character.character_name + ' started.
', null, {noarchive:true});
+
+ if (character.version === "1.0") {
+ sendChat(script_name, "Exported from HERO Designer with \n HeroSystem6eHeroic.hde v1.0. \n Version 2.4 supports additional content.");
+ } else if (character.version === "2.0") {
+ sendChat(script_name, "Exported from HERO Designer with \n HeroSystem6eHeroic.hde v2.0. \n Version 2.4 supports additional content.");
+ } else if (character.version === "2.1") {
+ sendChat(script_name, "Exported from HERO Designer with \n HeroSystem6eHeroic.hde v2.1. \n Version 2.4 supports additional content.");
+ } else if (character.version === "2.2") {
+ sendChat(script_name, "Exported from HERO Designer with \n HeroSystem6eHeroic.hde v2.2. \n Version 2.4 supports additional content.");
+ } else if (character.version === "2.3") {
+ sendChat(script_name, "Exported from HERO Designer with \n HeroSystem6eHeroic.hde v2.3. \n Version 2.4 supports additional content.");
+ }
+
+ object = null;
+
+ // Assign a random name if the character doesn't have one.
+ if ((character.character_name).length === 0) {
+ character.character_name = createRandomString(7);
+ }
+
+ // Remove characters with the same name if overwrite is enabled.
+ if(state[state_name][hero_caller.id].config.overwrite) {
+ let objects = findObjs({
+ _type: "character",
+ name: state[state_name][hero_caller.id].config.prefix + character.character_name + state[state_name][hero_caller.id].config.suffix
+ }, {caseInsensitive: true});
+
+ if(objects.length > 0) {
+ object = objects[0];
+ for(let i = 1; i < objects.length; i++){
+ objects[i].remove();
+ }
+ }
+ }
+
+ if(!object) {
+ // Create character object
+ object = createObj("character", {
+ name: state[state_name][hero_caller.id].config.prefix + character.character_name + state[state_name][hero_caller.id].config.suffix,
+ inplayerjournals: playerIsGM(msg.playerid) ? state[state_name][hero_caller.id].config.inplayerjournals : msg.playerid,
+ controlledby: playerIsGM(msg.playerid) ? state[state_name][hero_caller.id].config.controlledby : msg.playerid
+ });
+ }
+
+ // Set base character sheet values.
+ setAttrs(object.id, defaultAttributes);
+
+ // Import Page 1: Characteristics and Bio
+ importCharacteristics(object, character, script_name);
+
+ // Import Page 2: Martial Arts Maneuvers
+ // Maneuvers over the sheet maximum will be prepended to excess perks and talents in the treasures field.
+ character.overflow = importManeuvers(object, character, script_name);
+
+ // Import Page 2: Equipment
+ importEquipment(object, character, script_name);
+
+ // Import Page 3: Skills
+ importAllSkills(object, character, script_name);
+
+ // Import Page 4: Powers
+ // Powers over the sheet maximum will be prepended to excess perks and talents in a text field.
+ character.overflow = importPowers(object, character, script_name);
+
+ // Import Page 5: Perks and Talents
+ importPerksAndTalents(object, character, script_name);
+
+ // Import Page 5: Complications
+ importComplications(object, character, script_name);
+
+ // Version
+ applyVersion(object, character, script_name, versionSheet);
+
+ // Finished notification
+ sendChat(script_name, 'Import of ' + character.character_name + ' finished.
', null, {noarchive:true});
+ });
+
+ // END MAIN
+
+
+/* **************************************** */
+/* *** Begin Import Functions *** */
+/* **************************************** */
+
+ var importCharacteristics = function(object, character, script_name) {
+
+ /* ************************************************* */
+ /* *** Import Function: Characteristics *** */
+ /* ************************************************* */
+
+ // Set sheet type from template type.
+ const isVehicle = (typeof character.template !== "undefined") ? ( character.template.includes("Vehicle") || ( (character.size > 0) && !character.template.includes("Base"))) : false;
+
+ let template_attributes = {
+ useVehicleAttributes: isVehicle ? "on" : 0,
+ sheetAttributes: isVehicle ? "vehicle" : "standard",
+ sheetHealthDisplay: isVehicle ? "vehicle" : "standard"
+ }
+
+ setAttrs(object.id, template_attributes);
+
+ // Set sticky note to importer details.
+ let importInfoString = "HDImporter for Roll20\n";
+ importInfoString += "Version: " + versionMod + "\n";
+ if (typeof character.playerName !== "undefined") {
+ importInfoString += "Player: " + character.playerName + "\n";
+ }
+ if (typeof character.gmName !== "undefined") {
+ importInfoString += "GM: " + character.gmName + "\n";
+ }
+ if (typeof character.genre !== "undefined") {
+ importInfoString += "Genre: " + character.genre + "\n";
+ }
+ if (typeof character.campaign !== "undefined") {
+ importInfoString += "Campaign: " + character.campaign + "\n";
+ }
+ if (typeof character.versionHD !== "undefined") {
+ importInfoString += "Hero Designer version: " + character.versionHD + "\n";
+ }
+ importInfoString += "HeroSystem6eHeroic.hde: v" + character.version + "\n";
+ if (typeof character.characterFile !== "undefined") {
+ importInfoString += "Original file: " + character.characterFile + "\n";
+ }
+ if (typeof character.template !== "undefined") {
+ importInfoString += (character.template.includes(".hdt")) ? "Template: " + character.template + "\n" : "Template: not found\n";
+ }
+ if (typeof character.timeStamp !== "undefined") {
+ importInfoString += "Export date: \n " + character.timeStamp;
+ }
+
+ setAttrs(object.id, {portraitStickyNote: importInfoString});
+
+ // Set sticky window as visible portrait.
+ setAttrs(object.id, {portraitSelection: 2});
+
+ // Set bio-type attributes and experience points.
+
+ let description = "";
+ let quote = "";
+
+ if (character.version >= 1.2) {
+ quote = character.quote;
+ quote = quote.trim();
+
+ description += ((character.appearance).length > 0) ? character.appearance : "";
+ description += ((character.backgroundText).length > 0) ? '\n' + '\n' + character.backgroundText : "";
+ description += ((character.historyText).length > 0) ? '\n' + '\n' + character.historyText : "";
+ description += ((character.tactics).length > 0) ? '\n' + '\n' + character.tactics : "";
+ description += ((character.campaignUse).length > 0) ? '\n' + '\n' + character.campaignUse : "";
+ description = description.trim();
+ description += (isVehicle) ? "" : '\n' + '\n' + character.height + " and " + Math.round(Number((character.weight).replace(/[^0-9.]/g,''))) + " kg.";
+ description = description.trim();
+ } else {
+ quote = "";
+
+ description += ((character.historyText).length > 0) ? character.historyText : "";
+ description = description.trim();
+ }
+
+ let bio_attributes = {
+ character_title: character.character_title,
+ backgroundText: quote,
+ historyText: description,
+ experience: parseInt(character.experience)||0,
+ experienceBenefit: parseInt(character.experienceBenefit)||0
+ }
+
+ setAttrs(object.id, bio_attributes);
+
+ if(verbose) {
+ sendChat(script_name, "Imported bio and experience.");
+ }
+
+ // Set primary attributes.
+ let primary_attributes = {
+ size: parseInt(character.size)||0,
+ strength: Math.max( 0, (parseInt(character.strength)||10) - 5*(parseInt(character.size)||0) ),
+ strengthMod: parseInt(character.size)||0,
+ strengthNet: parseInt(character.strength)||10,
+ dexterity: parseInt(character.dexterity)||10,
+ constitution: parseInt(character.constitution)||10,
+ intelligence: parseInt(character.intelligence)||10,
+ ego: parseInt(character.ego)||10,
+ presence: parseInt(character.presence)||10
+ }
+
+ setAttrs(object.id, primary_attributes);
+
+ if(verbose) {
+ sendChat(script_name, "Imported core attributes.");
+ }
+
+ // Set combat attributes.
+ let combat_attributes = {
+ ocv: parseInt(character.ocv)||3,
+ dcv: parseInt(character.dcv)||3,
+ omcv: parseInt(character.omcv)||3,
+ dmcv: parseInt(character.dmcv)||3,
+ speed: parseInt(character.speed)||2,
+ pd: parseInt(character.pd)||2,
+ ed: parseInt(character.ed)||2,
+ body: (parseInt(character.body)||10) - (parseInt(character.size)||0),
+ stun: parseInt(character.stun)||20,
+ endurance: parseInt(character.endurance)||20,
+ recovery: parseInt(character.recovery)||4
+ }
+
+ setAttrs(object.id, combat_attributes);
+
+ if(verbose) {
+ sendChat(script_name, "Imported combat attributes.");
+ }
+
+ // Set movement attributes.
+ let movement_attributes = {
+ running: parseInt(character.running)||0,
+ leaping: parseInt(character.leaping)||0,
+ swimming: parseInt(character.swimming)||0
+ };
+
+ setAttrs(object.id, movement_attributes);
+
+ if(verbose) {
+ sendChat(script_name, "Imported movement.");
+ }
+
+ // Set status attributes to starting values
+ let health_attributes = {
+ CurrentBODY: parseInt(character.body)||20,
+ CurrentSTUN: parseInt(character.stun)||20,
+ CurrentEND: parseInt(character.endurance)||20,
+ hiddenSTUN: parseInt(character.stun)||20
+ }
+
+ setAttrs(object.id, health_attributes);
+
+ if(verbose) {
+ sendChat(script_name, "Configured health status.");
+ }
+
+ return;
+ }
+
+
+ function createRandomString(length) {
+ // random character name from https://sentry.io/answers/generate-random-string-characters-in-javascript/
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let result = "";
+ for (let i = 0; i < length; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+ return result;
+ }
+
+
+ var applyVersion = function(object, character, script_name, version) {
+ // Set version data to avoid improper sheet auto updates.
+ let version_attributes = {
+ version: version,
+ validateMay23: 1,
+
+ // Show the Treasures slide where equipment will appear.
+ gearSlideSelection: "gearTreasures"
+ }
+
+ setAttrs(object.id, version_attributes);
+ }
+
+
+ var importManeuvers = function(object, character, script_name) {
+
+ /* ************************************************* */
+ /* *** Import Function: Import Maneuvers *** */
+ /* ************************************************* */
+
+
+ // Overall list of maneuvers
+ let maneuverArray = new Array();
+ let maneuverArrayIndex = 0;
+ let temp = 0;
+ let tempString = "";
+ let diceString = "";
+ let tempValue = 0;
+ let tempPosition = 0;
+ const maxManeuvers = 20;
+ const maneuverSlots = 10;
+ let importCount = 0;
+ let ID = "01";
+
+ // Imports twenty martial arts maneuvers, skipping empty slots.
+ // Only the first 10 are imported into sheet slots.
+
+ for (importCount = 1; importCount < maxManeuvers; importCount++) {
+
+ ID = String(importCount).padStart(2,'0');
+
+ if ((typeof character.maneuvers["maneuver"+ID] !== "undefined") && (typeof character.maneuvers["maneuver"+ID].name !== "undefined")) {
+ maneuverArray[maneuverArrayIndex] = character.maneuvers["maneuver"+ID];
+
+ maneuverArrayIndex++;
+ }
+
+ }
+
+ let importedManeuvers = {};
+ importCount = 0;
+ const nameMax = 16;
+
+ while ( (importCount < maneuverSlots) && (importCount < maneuverArrayIndex) ) {
+ if (importCount < maneuverArrayIndex) {
+ ID = String(importCount+1).padStart(2,'0');
+ tempString = maneuverArray[importCount].name;
+
+ if ( tempString.includes("Weapon Element") ) {
+ tempString = tempString.replace("Weapon Element", "WE");
+ }
+
+ if ( tempString.length > nameMax) {
+ tempString = tempString.slice(0, nameMax);
+ importedManeuvers["martialManeuverName"+ID] = tempString;
+ importedManeuvers["martialManeuverEffect"+ID] = maneuverArray[importCount].name + '\n' + maneuverArray[importCount].effect;
+ } else {
+ importedManeuvers["martialManeuverName"+ID] = tempString;
+ importedManeuvers["martialManeuverEffect"+ID] = maneuverArray[importCount].effect;
+ }
+
+ importedManeuvers["martialManeuverCP"+ID] = maneuverArray[importCount].points;
+ importedManeuvers["martialManeuverPhase"+ID] = maneuverArray[importCount].phase;
+ temp = Number(maneuverArray[importCount].ocv);
+ importedManeuvers["martialManeuverOCV"+ID] = isNaN(temp) ? 0 : temp;
+ temp = Number(maneuverArray[importCount].dcv);
+ importedManeuvers["martialManeuverDCV"+ID] = isNaN(temp) ? 0 : temp;
+
+ tempString = maneuverArray[importCount].effect.toLowerCase();
+ if ( tempString.includes("weapon") ) {
+ if ( tempString.includes("strike") ) {
+ importedManeuvers["martialManeuverType"+ID] = "weaponAttack";
+
+ tempString = maneuverArray[importCount].effect;
+
+ if ( tempString.includes("DC") ) {
+ tempPosition = tempString.indexOf("DC");
+ diceString = tempString.slice(0, tempPosition);
+ diceString = diceString.slice(-3).replace(/[^0-9.-]/g, '');
+ importedManeuvers["martialManeuverDC"+ID] = Math.max(-2, Math.min(2, parseInt(diceString)||0));
+ }
+ } else if ( tempString.includes("block") ) {
+ importedManeuvers["martialManeuverType"+ID] = "weaponBlock";
+ } else {
+ importedManeuvers["martialManeuverType"+ID] = "weaponContest";
+ }
+ } else {
+ if ( tempString.includes("strike") ) {
+ importedManeuvers["martialManeuverType"+ID] = "attack";
+ } else if ( tempString.includes("hka") ) {
+ importedManeuvers["martialManeuverType"+ID] = "attack";
+ importedManeuvers["martialManeuverNormal"+ID] = "0";
+ } else if ( tempString.includes("block") ) {
+ importedManeuvers["martialManeuverType"+ID] = "block";
+ } else if ( tempString.includes("dodge") ) {
+ importedManeuvers["martialManeuverType"+ID] = "dodge";
+ } else {
+ importedManeuvers["martialManeuverType"+ID] = "contest";
+ }
+
+ // Damage or STR adds.
+ if ( tempString.includes("d6") ) {
+ tempPosition = tempString.indexOf("d6");
+ diceString = tempString.slice(0, tempPosition);
+ diceString = diceString.slice(-2).replace(/\D/g,"") + "d6";
+
+ if (tempString.includes("d3")) {
+ diceString += "+1d3";
+ } else if (tempString.includes("+1")) {
+ diceString += "+1";
+ } else if (tempString.includes("-1")) {
+ diceString += "-1";
+ }
+
+ importedManeuvers["martialManeuverDamage"+ID] = diceString;
+ } else if ( tempString.includes("str") ) {
+ tempPosition = tempString.indexOf("str");
+ diceString = tempString.slice(0, tempPosition);
+ diceString = diceString.slice(-3).replace(/\D/g,"");
+ importedManeuvers["martialManeuverStrMod"+ID] = (parseInt(diceString)||0) - (parseInt(character.strength)||0);
+ }
+ }
+
+ importCount++;
+ }
+ }
+
+ // Import maneuvers.
+ setAttrs(object.id, importedManeuvers);
+
+ if(verbose) {
+ if (importCount === 1) {
+ sendChat(script_name, "Imported 1 maneuver.");
+ } else {
+ sendChat(script_name, "Imported " + importCount + " maneuvers.");
+ }
+ }
+
+ // Display additional maneuvers in the treasures text box.
+ if (maneuverArrayIndex > maneuverSlots) {
+ let extras = 0;
+
+ tempString = "Additional Maneuvers \n";
+
+ for (let i = maneuverSlots; i < maneuverArrayIndex; i++) {
+ tempString += maneuverArray[i].name + '\n';
+ tempString += '\t' + "CP: " + maneuverArray[i].points + " ";
+ if (maneuverArray[i].ocv !== "") {
+ tempString += "OCV: " + maneuverArray[i].ocv + " ";
+ }
+ if (maneuverArray[i].dcv !== "") {
+ tempString += "DCV: " + maneuverArray[i].dcv + " ";
+ }
+ if (maneuverArray[i].phase !== "") {
+ tempString += "Phase: " + maneuverArray[i].phase + '\n';
+ }
+ tempString += '\t' + maneuverArray[i].effect;
+ tempString += "\n \n";
+ extras++;
+ }
+
+ if(verbose) {
+ if (extras === 1) {
+ sendChat(script_name, extras + " maneuver placed in treasures.");
+ } else {
+ sendChat(script_name, extras + " maneuvers placed in treasures.");
+ }
+ }
+
+ if ( (typeof character.treasures !== "undefined") && (character.treasures !== "")) {
+ tempString = character.treasures + "\n \n" + tempString.trim();
+ } else {
+ tempString = tempString.trim();
+ }
+
+ // Place additional maneuvers in the treasures text box.
+ setAttrs(object.id, {treasures: tempString});
+ }
+
+ return tempString;
+ }
+
+
+ var importEquipment = function(object, character, script_name) {
+
+ /* ************************************************* */
+ /* *** Import Function: Import Equipment *** */
+ /* ************************************************* */
+
+ // Imports equipment and sets carried weight.
+ // Similar to the way perks and talents are handled, we will parse the imported equipment into temporary arrays.
+
+ const strength = parseInt(character.strength)||10;
+ const isVehicle = (typeof character.template !== "undefined") ? ( character.template.includes("Vehicle") || ( (character.size > 0) && !character.template.includes("Base"))) : false;
+ const characterPD = parseInt(character.pd)||2;
+ const characterED = parseInt(character.ed)||2;
+
+ let gearTextBox = "";
+ let tempString;
+ let tempPosition;
+ let secondPosition;
+ let subStringA;
+ let subStringB;
+ let sampleSize;
+ let itemNumber;
+
+ // Needed for adjusted damage.
+ let advantage = 0;
+
+ // Overall array of equipment.
+ let equipmentArray = new Array();
+ let equipmentArrayIndex = 0;
+
+ // Array of items of equipment that are not weapons, armor, or shields.
+ let equipmentListArray = new Array();
+ let equipmentListArrayIndex = 0;
+
+ // Array of items of equipment that are weapons.
+ let weaponsArray = new Array();
+ let weaponsArrayIndex = 0;
+
+ // Array of items of equipment that are weapons.
+ let armorArray = new Array();
+ let armorArrayIndex = 0;
+
+ // Array for multipowers, which need to be independent from others.
+ let multipowerArray = new Array();
+ let multipowerArrayIndex = 0;
+
+ // Weapon States
+ let weaponStates = "KKKKKNOOOOOO";
+
+ // Read equipment
+ const maxGear = (character.version >= parseFloat("2.3")||0) ? 50 : 16;
+ const maxPowers = (character.version >= parseFloat("2.4")||0) ? 50 : 30;
+ let importCount = 0;
+ let imported = 0;
+ let ID = "01";
+ let isEquipment = false;
+ let isIgnored = false;
+
+ // Power to Gear conversion
+ var tempEquipment = {};
+
+ // Imports either 42 or 16 pieces of equipment (for version < 2.3), skipping empty slots.
+
+ for (importCount = 1; importCount <= maxGear; importCount++) {
+
+ ID = String(importCount).padStart(2,'0');
+
+ if ((typeof character.equipment["equipment"+ID] !== "undefined") && (typeof character.equipment["equipment"+ID].name !== "undefined")) {
+
+ equipmentArray[equipmentArrayIndex] = character.equipment["equipment"+ID];
+
+ tempString = equipmentArray[equipmentArrayIndex].name;
+ isEquipment = equipmentArray[equipmentArrayIndex].notes.toLowerCase().includes("(equipment)");
+ isIgnored = equipmentArray[equipmentArrayIndex].notes.toLowerCase().includes("(ignore)");
+
+ if ((tempString !== "") && tempString.length) {
+ if ((equipmentArray[equipmentArrayIndex].name.includes("Multipower")) || (equipmentArray[equipmentArrayIndex].name.includes("MPSlot"))) {
+ // Then place in multipower array.
+ multipowerArray[multipowerArrayIndex] = equipmentArray[equipmentArrayIndex];
+ multipowerArrayIndex++;
+
+ } else if ((equipmentArray[equipmentArrayIndex].defense !== "") && (equipmentArray[equipmentArrayIndex].defense === "true") && (! isEquipment) && (! isIgnored) ) {
+ // If the item is a defense add it to the armor list.
+ armorArray[armorArrayIndex] = equipmentArray[equipmentArrayIndex];
+ armorArrayIndex++;
+
+ } else if ((equipmentArray[equipmentArrayIndex].attack !== "") && (equipmentArray[equipmentArrayIndex].damage !== "") && (equipmentArray[equipmentArrayIndex].attack === "true") && (! isEquipment) && (! isIgnored) ) {
+ // If the item is a damage attack add it to the weapon list.
+ weaponsArray[weaponsArrayIndex] = equipmentArray[equipmentArrayIndex];
+ weaponsArrayIndex++;
+
+ } else if (! isIgnored) {
+ // If the item is not an attack or defense add it to the equipment list.
+ equipmentListArray[equipmentListArrayIndex] = equipmentArray[equipmentArrayIndex];
+ equipmentListArrayIndex++;
+ }
+ }
+
+ equipmentArrayIndex++;
+ }
+ }
+
+ // Fill in additional equipment slots with armor, weapon, and equipment powers.
+ for (importCount = 1; importCount <= maxPowers; importCount++) {
+
+ ID = String(importCount).padStart(2,'0');
+
+ if ((typeof character.powers["power"+ID] !== "undefined") && (typeof character.powers["power"+ID].name !== "undefined")) {
+
+ isIgnored = character.powers["power"+ID].notes.toLowerCase().includes("(ignore)");
+
+ if (! isIgnored) {
+ tempEquipment = {
+ name: "power",
+ text: "none",
+ damage: "0",
+ end: "0",
+ range: "0",
+ mass: "0",
+ attack: "false",
+ defense: "false",
+ notes: ""
+ };
+
+ tempEquipment.name = (character.powers["power"+ID].name.length === 0) ? "Power" : character.powers["power"+ID].name.replace(/\([^)]*\)/g, "").trim();
+ tempEquipment.text = character.powers["power"+ID].text;
+ tempEquipment.end = character.powers["power"+ID].endurance;
+ tempString = findEffectType(tempEquipment.text, script_name);
+ tempEquipment.attack = isAttack(tempString) ? "true" : "false";
+ tempEquipment.defense = isDefense(tempString) ? "true" : "false";
+ tempEquipment.damage = getWeaponDamage(character.powers["power"+ID].damage, script_name);
+ if ( (tempEquipment.damage === "0") || (tempEquipment.damage.includes("STR")) ) {
+ tempEquipment.damage = getPowerDamage(character.powers["power"+ID].damage, tempString, character.strength, script_name);
+ }
+ tempValue = (tempEquipment.text.includes("No Range") || tempString.includes("HTH")) ? 0 : 10 * parseInt(character.powers["power"+ID].base)||0;
+ tempEquipment.range = tempValue.toString();
+ tempEquipment.notes = character.powers["power"+ID].notes;
+
+ if ((tempEquipment.defense === "true") && (tempEquipment.notes.toLowerCase().includes("equipment") !== true)) {
+ // If the item is a defense add it to the armor list.
+ armorArray[armorArrayIndex] = tempEquipment;
+ armorArrayIndex++;
+
+ } else if ((tempEquipment.damage !== "") && (tempEquipment.attack === "true") && (tempEquipment.notes.toLowerCase().includes("equipment") !== true) ) {
+ // If the item is a damage attack add it to the weapon list.
+ weaponsArray[weaponsArrayIndex] = tempEquipment;
+ weaponsArrayIndex++;
+
+ } else if ( character.powers["power"+ID].notes.toLowerCase().includes("equipment") ) {
+ // If the item is not an attack or defense add it to the equipment list.
+ equipmentListArray[equipmentListArrayIndex] = tempEquipment;
+ equipmentListArrayIndex++;
+
+ }
+ }
+ }
+ }
+
+ // Write raw details of imported equipment to the treasures slide.
+ if (equipmentArrayIndex > 0) {
+
+ // Get current contents of the treasures text box.
+ tempString = character.overflow + '\n' + '\n';
+
+ // Add equipment to treasures.
+ for (let i = 0; i < equipmentArrayIndex; i++) {
+ tempString += equipmentArray[i].name + '\n';
+ if (equipmentArray[i].damage !== "") {
+ tempString += "Damage: " + equipmentArray[i].damage + ", ";
+ }
+ if (equipmentArray[i].end !== "") {
+ tempString += "END: " + equipmentArray[i].end + ", ";
+ }
+ if (equipmentArray[i].range !== "") {
+ tempString += "Range: " + equipmentArray[i].range + ", ";
+ }
+ if (equipmentArray[i].text !== "") {
+ tempString += equipmentArray[i].text;
+ if (equipmentArray[i].notes !== "") {
+ tempString += ", " + equipmentArray[i].notes;
+ }
+ } else if (equipmentArray[i].notes !== "") {
+ tempString += ", " + equipmentArray[i].notes;
+ }
+ if (equipmentArray[i].mass !== "") {
+ tempString += ", Mass: " + equipmentArray[i].mass;
+ }
+ if (i < (equipmentArrayIndex + 2)) {
+ tempString += '\n' + '\n';
+ }
+ }
+
+ if ( (typeof character.treasures != "undefined") && (character.treasures !== "")) {
+ tempString = character.treasures + '\n' + '\n' + tempString.trim();
+ } else {
+ tempString = tempString.trim();
+ }
+
+ setAttrs(object.id, {
+ treasures: tempString
+ });
+ }
+
+ // Prepare object of items that are not weapons or armor.
+ // Assign to character sheet Equipment List.
+
+ let importedEquipment = new Array();
+ const maxEquipment = 32;
+ importCount = 0;
+ imported = 0;
+
+ // Prepare Items
+ for (importCount = 0; importCount < maxEquipment; importCount++) {
+
+ ID = String(importCount+1).padStart(2,'0');
+
+ if (importCount < equipmentListArrayIndex) {
+ imported += 1;
+
+ // Check for charges.
+ if (equipmentListArray[importCount].end != "") {
+ tempString = equipmentListArray[importCount].end;
+ if (tempString.includes("[")) {
+ tempString = " (" + parseInt(tempString.replace(/[^\d.-]/g, "")) +")";
+ } else {
+ tempString = "";
+ };
+ }
+
+ importedEquipment["equipText"+ID] = equipmentListArray[importCount].name;
+
+ // Check for additional copies in notes.
+ if (equipmentListArray[importCount].notes !== "") {
+ tempString = equipmentListArray[importCount].notes;
+
+ if (tempString.includes("number of items")) {
+ // Get the number of copies and amend the equipment text.
+ itemNumber = getItemNumber(tempString);
+ importedEquipment["equipText"+ID] += " (" + (itemNumber).toString() + ")";
+ } else {
+ itemNumber = 1;
+ }
+ } else {
+ itemNumber = 1;
+ }
+
+ // Get item mass.
+ if (equipmentListArray[importCount].mass !== "") {
+ // Multiply the base mass by the number of copies.
+ tempValue = itemNumber * getItemMass(equipmentListArray[importCount].mass, script_name, 2);
+ tempValue = Math.round(10*tempValue)/10;
+ importedEquipment["equipMass"+ID] = tempValue;
+ } else {
+ importedEquipment["equipMass"+ID] = 0;
+ }
+ }
+ }
+
+ // Import equipment.
+ setAttrs(object.id, importedEquipment);
+
+ if(verbose) {
+ if (imported === 1) {
+ sendChat(script_name, "Imported 1 piece of equipment.");
+ } else {
+ sendChat(script_name, "Imported "+ imported +" pieces of equipment.");
+ }
+ }
+
+ // Prepare objects of weapons. Assign to character sheet Weapon List.
+
+ let importedWeapons = new Array();
+ const maxAdvantage = 1;
+ const maxWeapons = 10;
+
+ importCount = 0;
+ imported = 0;
+
+ for (importCount = 0; importCount < maxWeapons; importCount++) {
+
+ // Weapons 6-10 are numbered 11-15 within the Set A/B framework of the sheet.
+ ID = (importCount < 5) ? String(importCount+1).padStart(2,'0') : String(importCount+6).padStart(2,'0');
+
+ if (importCount < weaponsArrayIndex) {
+ imported += 1;
+
+ importedWeapons["weaponName"+ID] = weaponsArray[importCount].name;
+
+ // Assign weapon base damage.
+ importedWeapons["weaponDamage"+ID] = getWeaponDamage(weaponsArray[importCount].damage, script_name);
+
+ tempString = weaponsArray[importCount].text;
+ if ((typeof tempString !== "undefined") && (tempString !== "")) {
+ // Look for weapon advantages.
+ tempValue = findDamageAdvantages(tempString, script_name);
+ if (tempValue > maxAdvantage) {
+ importedWeapons["weaponAdvantage"+ID] = maxAdvantage;
+ } else {
+ importedWeapons["weaponAdvantage"+ID] = tempValue;
+ }
+
+ // Assign weapon type
+ if (isKillingAttack( findEffectType(tempString) )) {
+ importedWeapons["weaponDamageType"+ID] = "killing";
+ importedWeapons["weaponType"+ID] = "killing";
+ weaponStates = setCharAt(weaponStates, importCount, 'K');
+ } else if (isPowerAttack( findEffectType(tempString) )) {
+ importedWeapons["weaponDamageType"+ID] = "power";
+ importedWeapons["weaponType"+ID] = "power";
+ weaponStates = setCharAt(weaponStates, importCount, 'P');
+ } else if (isMentalAttack( findEffectType(tempString) )) {
+ importedWeapons["weaponDamageType"+ID] = "mental";
+ importedWeapons["weaponType"+ID] = "power";
+ weaponStates = setCharAt(weaponStates, importCount, 'P');
+ } else {
+ // Blast, HTH Attack, Telekinesis
+ importedWeapons["weaponDamageType"+ID] = "normal";
+ importedWeapons["weaponType"+ID] = "normal";
+ weaponStates = setCharAt(weaponStates, importCount, 'N');
+ }
+
+ // Get OCV bonus or penalty.
+ importedWeapons["weaponOCV"+ID] = getOCVmodifier(tempString, script_name);
+
+ // Check for range mod adjustment.
+ importedWeapons["weaponRangeMod"+ID] = getRangeModifier(tempString, script_name);
+
+ // Check for the thrown weapon advantage.
+ importedWeapons["rangeBasedOnStr"+ID] = (tempString.includes("Range Based On STR")) ? "on" : 0;
+
+ // Check for modified STUN multiplier.
+ importedWeapons["weaponStunMod"+ID] = getStunModifier(tempString, script_name);
+
+ // Get STR minimum and apply strength.
+ tempValue = getWeaponStrMin(tempString, script_name);
+ importedWeapons["weaponStrengthMin"+ID] = tempValue;
+ importedWeapons["weaponEnhancedBySTR"+ID] = checkDamageBySTR(tempString, script_name) ? "on" : 0;
+ importedWeapons["weaponStrength"+ID] = ( importedWeapons["weaponEnhancedBySTR"+ID] === "on" ) ? getWeaponStrength(importedWeapons["weaponStrengthMin"+ID], strength, script_name) : Math.min(tempValue, character.strength);
+
+ // If the weapon does not use STR, use the listed END.
+ if ( importedWeapons["weaponEnhancedBySTR"+ID] === 0) {
+ importedWeapons["weaponEndMod"+ID] = parseInt(weaponsArray[importCount].end)||0;
+ }
+
+ // Check for AoE.
+ importedWeapons["weaponAreaEffect"+ID] = (tempString.includes("Area Of Effect")) ? "on" : 0;
+ }
+
+ // Check for charges.
+ tempString = weaponsArray[importCount].end;
+ if ((typeof tempString !== "undefined") && (tempString !== "")) {
+ if (tempString.includes("[")) {
+ importedWeapons["weaponShots"+ID] = parseInt(tempString.replace(/[^\d.-]/g, ""));
+ } else if (weaponsArray[importCount].text.includes("Charges")) {
+ tempString = weaponsArray[importCount].text;
+ tempValue = tempString.indexOf("Charges");
+ tempString = tempString.slice(tempValue-Math.min(4, tempValue), tempValue);
+ tempValue = Number(tempString.replace(/[^0-9]/g, ""));
+ importedWeapons["weaponShots"+ID] = isNaN(tempValue) ? 0 : tempValue;
+ } else {
+ importedWeapons["weaponShots"+ID] = 0;
+ }
+ }
+
+ // Calculate thrown weapon range or assign range without units.
+ importedWeapons["weaponRange"+ID] = getWeaponRange(weaponsArray[importCount].range, character.strength, importedWeapons["weaponMass"+ID], script_name);
+
+ // Check for max range in notes.
+ tempValue = (weaponsArray[importCount].notes !== "") ? getWeaponMaxRange(weaponsArray[importCount].notes, script_name) : 0;
+ importedWeapons["weaponRange"+ID] = (tempValue !== 0) ? tempValue : parseInt(weaponsArray[importCount].range)||0;
+
+ // Get weapon mass.
+ if (weaponsArray[importCount].mass !== "") {
+ tempString = weaponsArray[importCount].mass;
+ tempValue = getItemMass(tempString, script_name, 1);
+ importedWeapons["weaponMass"+ID] = tempValue;
+
+ // Check for a note on additional copies.
+ if (weaponsArray[importCount].notes !== "") {
+ tempString = weaponsArray[importCount].notes;
+
+ if (tempString.includes("number of items")) {
+ if (equipmentListArrayIndex < maxGear) {
+ itemNumber = getItemNumber(tempString);
+
+ // Fill in ammunition quantity if it hasn't already been assigned.
+ if (importedWeapons["weaponShots"+ID] === 0) {
+ importedWeapons["weaponShots"+ID] = itemNumber;
+ }
+
+ // Assign copies to an open equipment slot. ID changes to the equipment slot.
+ equipmentListArrayIndex++;
+ ID = String(equipmentListArrayIndex).padStart(2,'0');
+
+ tempString = weaponsArray[importCount].name;
+
+ if (itemNumber > 2) {
+ tempString += " (" + (itemNumber-1).toString() + ")";
+ }
+
+ importedWeapons["equipText"+ID] = tempString;
+ tempValue = (itemNumber-1) * tempValue;
+ tempValue = Math.round(10*tempValue)/10;
+ importedWeapons["equipMass"+ID] = tempValue;
+ }
+ }
+ }
+ } else {
+ importedWeapons["weaponMass"+ID] = 0;
+ }
+ }
+ }
+
+ // Import weapons.
+ importedWeapons.weaponStates = weaponStates;
+
+ setAttrs(object.id, importedWeapons);
+
+ if(verbose) {
+ if (imported === 1) {
+ sendChat(script_name, "Imported 1 weapon.");
+ } else {
+ sendChat(script_name, "Imported "+ imported +" weapons.");
+ }
+ }
+
+ // Prepare object of armor defenses. Assign to character sheet Armor List.
+
+ let importedArmor = new Array();
+ const maxArmor = 8; // The 8th piece will be overwritten if the character has resistant protection.
+
+ imported = 0;
+
+ for (importCount = 0; importCount < maxArmor; importCount++) {
+
+ // Armor pieces 5-8 are numbered 11-14 within the Set A/B framework of the sheet.
+ ID = (importCount < 4) ? String(importCount+1).padStart(2,'0') : String(importCount+7).padStart(2,'0');
+ tempString = "none";
+
+ if (importCount < armorArrayIndex) {
+ imported += 1;
+
+ importedArmor["armorName"+ID] = armorArray[importCount].name;
+
+ // Find resistant protection values.
+ // This needs to be adjusted so that it doesn't pick out other PD/ED stats from elsewhere in the text.
+ if (typeof armorArray[importCount].text !== "undefined") {
+ tempString = armorArray[importCount].text;
+
+ if (tempString.includes("Resistant Protection") || tempString.includes("Resistant")) {
+ if (tempString.includes("applied to PD")) {
+ importedArmor["armorPD"+ID] = characterPD;
+ importedArmor["totalPD"+ID] = characterED;
+ } else if (tempString.includes("PD")) {
+ tempPosition = tempString.indexOf("PD");
+ sampleSize = 4;
+ subStringA = tempString.slice(Math.max(0, tempPosition-sampleSize), tempPosition);
+ tempValue = parseInt(subStringA.replace(/[^\d.-]/g, ""))||0;
+ importedArmor["armorPD"+ID] = tempValue + (isVehicle ? characterPD : 0);
+ importedArmor["totalPD"+ID] = tempValue + characterPD;
+ } else {
+ importedArmor["armorPD"+ID] = 0;
+ importedArmor["totalPD"+ID] = characterPD;
+ }
+
+ if (tempString.includes("applied to ED")) {
+ importedArmor["armorED"+ID] = characterED;
+ importedArmor["totalED"+ID] = characterED;
+ } else if (tempString.includes("ED")) {
+ tempPosition = tempString.indexOf("ED");
+ sampleSize = 4;
+ subStringA = tempString.slice(Math.max(0, tempPosition-sampleSize), tempPosition);
+ tempValue = parseInt(subStringA.replace(/[^\d.-]/g, ""))||0;
+ importedArmor["armorED"+ID] = tempValue + (isVehicle ? characterED : 0);
+ importedArmor["totalED"+ID] = tempValue + characterED;
+ } else {
+ importedArmor["armorED"+ID] = 0;
+ importedArmor["totalED"+ID] = characterED;
+ }
+ }
+
+ // Activation roll
+ tempString = armorArray[importCount].text;
+
+ if (tempString.includes("Requires A Roll")) {
+ tempPosition = tempString.indexOf("Requires A Roll");
+
+ sampleSize = 4;
+ subStringA = tempString.substr(tempPosition+15, sampleSize);
+ subStringB = subStringA.replace(/[^\d]/g, "");
+ importedArmor["armorActivation"+ID] = parseInt(subStringB);
+ }
+
+ // Armor locations. Sometimes locations are stored in the notes field.
+ if (armorArray[importCount].notes !== "") {
+ tempString += ", " + armorArray[importCount].notes;
+ }
+ importedArmor["armorLocations"+ID] = getArmorLocations(tempString, script_name);
+ importedArmor["armorEND"+ID] = getArmorEND(tempString, script_name);
+
+ // Get armor mass.
+ if (armorArray[importCount].mass !== "") {
+ tempString = armorArray[importCount].mass;
+ importedArmor["armorMass"+ID] = getItemMass(tempString, script_name, 1);
+ } else {
+ importedArmor["armorMass"+ID] = 0;
+ }
+ }
+ }
+ }
+
+ // Add vehicle standard armor.
+ if ( isVehicle && (imported === 0) ) {
+ imported += 1;
+ importedArmor["armorName01"] = "Resistant Defense";
+ importedArmor["armorPD01"] = characterPD;
+ importedArmor["totalPD01"] = characterPD;
+ importedArmor["armorED01"] = characterED;
+ importedArmor["totalED01"] = characterED;
+ }
+
+ // Import armor.
+
+ setAttrs(object.id, importedArmor);
+
+ if(verbose) {
+ if (imported === 1) {
+ sendChat(script_name, "Imported 1 piece of armor.");
+ } else {
+ sendChat(script_name, "Imported " + imported + " pieces of armor.");
+ }
+ }
+
+ // Identify independent multipowers.
+ let equipmentMultipowers = [];
+ let shieldSearchIndex;
+
+ for (let i=0; i< multipowerArray.length; i++) {
+ //sendChat(script_name, "Multipower search "+ i +".");
+ if (multipowerArray[i].name.includes("Multipower")) {
+ equipmentMultipowers.push(i);
+ }
+ }
+
+ // Find first shield if any.
+ let shieldFound = false;
+ let importedShield = new Array();
+ let shieldID = "06";
+ let i, j;
+
+ for (i=0; i < equipmentMultipowers.length; i++) {
+ // Get next multipower index.
+ shieldSearchIndex = equipmentMultipowers[i];
+ tempString = multipowerArray[shieldSearchIndex].name;
+ tempString = tempString.toLowerCase();
+
+ if ( (tempString.includes("shield") || tempString.includes("buckler")) && !shieldFound) {
+ // Shield found
+ shieldFound = true;
+
+ if(verbose) {
+ if (equipmentMultipowers !== "undefined") {
+ sendChat(script_name, "Found shield multipower.");
+ }
+ }
+
+ // Get shield name.
+ importedShield["weaponName"+shieldID] = multipowerArray[shieldSearchIndex].name.replace("(Multipower)","");
+
+ // Get STR minimum.
+ tempString = multipowerArray[shieldSearchIndex].text;
+ importedShield["weaponStrengthMin"+shieldID] = getWeaponStrMin(tempString, script_name);
+
+ // Get weapon mass.
+ if (multipowerArray[shieldSearchIndex].mass !== "") {
+ tempString = multipowerArray[shieldSearchIndex].mass;
+ importedShield.shieldMass = getItemMass(tempString, script_name, 1);
+ } else {
+ importedShield.shieldMass = 0;
+ }
+
+ // Search for multipower slot that grants DCV.
+ let foundShieldDCV = false;
+
+ if (i+2 > equipmentMultipowers.length) {
+ // Shield is the last multipower in the list.
+ for (j = shieldSearchIndex; j equipmentMultipowers.length) {
+ // Shield is the last multipower in the list.
+ for (let j = shieldSearchIndex; j equipmentMultipowers.length) {
+ // Item is the last multipower in the list.
+ for (j = shieldSearchIndex; j 0) {
+
+ for (importCount = 0; importCount < maxImport; importCount++) {
+
+ ID = String(importCount+1).padStart(2,'0');
+
+ importedTalents["talentName"+ID] = perksAndTalentsArray[importCount].type;
+ importedTalents["talentText"+ID] = perksAndTalentsArray[importCount].text;
+ importedTalents["talentCP"+ID] = perksAndTalentsArray[importCount].points;
+
+ if (typeof importedTalents["talentText"+ID] !== "undefined") {
+
+ tempString = importedTalents["talentText"+ID];
+
+
+ // Many of the following roll chances won't be used, but are here for completeness.
+
+ if (tempString.includes("10-")) {
+ importedTalents["talentRollChance"+ID] = 10;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("11-")) {
+ importedTalents["talentRollChance"+ID] = 11;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("12-")) {
+ importedTalents["talentRollChance"+ID] = 12;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("13-")) {
+ importedTalents["talentRollChance"+ID] = 13;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("14-")) {
+ importedTalents["talentRollChance"+ID] = 14;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("15-")) {
+ importedTalents["talentRollChance"+ID] = 15;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("16-")) {
+ importedTalents["talentRollChance"+ID] = 16;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("17-")) {
+ importedTalents["talentRollChance"+ID] = 17;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("18-")) {
+ importedTalents["talentRollChance"+ID] = 18;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("19-")) {
+ importedTalents["talentRollChance"+ID] = 19;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("20-")) {
+ importedTalents["talentRollChance"+ID] = 20;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("21-")) {
+ importedTalents["talentRollChance"+ID] = 21;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("3-")) {
+ importedTalents["talentRollChance"+ID] = 3;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("4-")) {
+ importedTalents["talentRollChance"+ID] = 4;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("5-")) {
+ importedTalents["talentRollChance"+ID] = 5;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("6-")) {
+ importedTalents["talentRollChance"+ID] = 6;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("7-")) {
+ importedTalents["talentRollChance"+ID] = 7;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("8-")) {
+ importedTalents["talentRollChance"+ID] = 8;
+ importedTalents["talentActivate"+ID] = "on";
+ } else if (tempString.includes("9-")) {
+ importedTalents["talentRollChance"+ID] = 9;
+ importedTalents["talentActivate"+ID] = "on";
+ }
+
+ if ( tempString.includes("d6") ) {
+ tempPosition = tempString.indexOf("d6")
+ diceString = tempString.slice(0, tempPosition);
+ diceString = diceString.slice(-2).replace(/\D/g,"") + "d6";
+ importedTalents["talentDice"+ID] = diceString;
+ } else {
+ importedTalents["talentDice"+ID] = "0";
+ }
+ }
+ }
+
+ // Display additional perks and talents in the complications text box.
+ if (perksAndTalentsIndex > maxCombinedSheet) {
+ let i = maxCombinedSheet;
+ let extras = 0;
+
+ for (let i = maxCombinedSheet; i= parseFloat("2.4")||0) ? 50 : 30;
+ const characterPD = parseInt(character.pd)||2;
+ const characterED = parseInt(character.ed)||2;
+ let sheetCapacity = 20;
+ let overflowPowers = (typeof character.complicationsTextLeft !== "undefined") ? character.complicationsTextLeft : "";
+
+ let tempString;
+ let damageString;
+ let tempPosition;
+ let tempValue = 0;
+ let endPosition;
+ let subStringA;
+ let subStringB;
+ let subStringC;
+ let theEffect = "";
+ let sampleSize;
+ let base = 0;
+ let control = 0;
+ let active = 0;
+ let cost = 0;
+ let advantages = 0;
+ let limitations = 0;
+ let count = 0;
+ let ID = "";
+
+ let testObject = {
+ theName: "",
+ theText: "",
+ theEffect: "",
+ theEndurance: 0,
+ theBase: 0,
+ powerReducedEND : "standard"
+ };
+
+ var tempObject = new Object();
+
+ // Overall list of powers
+ var importedPowers = new Object();
+ let powerArray = new Array();
+ let powerArrayIndex = 0;
+ let extras = 0;
+
+ let importCount = 0;
+
+ // Vehicle-specific
+ let propulsionSystems = 0;
+ let reserveSystems = 0;
+ const isVehicle = (typeof character.template !== "undefined") ? ( character.template.includes("Vehicle") || ( (character.size > 0) && !character.template.includes("Base"))) : false;
+
+ /* ------------------------- */
+ /* Read All Powers */
+ /* ------------------------- */
+
+ for (importCount; importCount < maxPowers; importCount++) {
+
+ ID = String(importCount+1).padStart(2,'0');
+
+ if ((typeof character.powers["power"+ID] !== "undefined") && (typeof character.powers["power"+ID].name !== "undefined")) {
+
+ if (character.powers["power"+ID].name.length === 0) {
+ character.powers["power"+ID].name = "Power";
+ }
+
+ tempString = character.powers["power"+ID].name;
+
+ if (tempString.includes("(VPP)")) {
+ // Varriable Power Pool found.
+ // The pool needs to be split into control and base parts.
+ tempString = character.powers["power"+ID].text;
+
+ subStringA = tempString.toLowerCase();
+ if (subStringA.includes("base")) {
+ subStringA = subStringA.slice(tempString.indexOf("base")-4, tempString.indexOf("base"));
+ subStringA = subStringA.replace(/\D/g, '');
+ base = Number(subStringA);
+ } else {
+ // Error
+ base = 0;
+ }
+
+ subStringA = tempString.toLowerCase();
+ if (subStringA.includes("control cost")) {
+ subStringA = subStringA.slice(tempString.indexOf("control cost")-4, tempString.indexOf("control cost"));
+ subStringA = subStringA.replace(/\D/g, '');
+ control = Number(subStringA);
+ } else {
+ // Error
+ control = 0;
+ }
+
+ // Create entry for Control Cost
+ powerArray[powerArrayIndex]={
+ name: character.powers["power"+ID].name + "(control)",
+ base: control.toString(),
+ text: character.powers["power"+ID].text,
+ cost: control.toString(),
+ endurance: character.powers["power"+ID].endurance,
+ damage: character.powers["power"+ID].damage,
+ compound: false
+ }
+ powerArrayIndex++;
+
+ // Create entry for Pool Cost
+ powerArray[powerArrayIndex]={
+ name: character.powers["power"+ID].name + "(pool)",
+ base: base.toString(),
+ text: JSON.stringify(base) + "-point Power Pool.",
+ cost: base.toString(),
+ endurance: character.powers["power"+ID].endurance,
+ damage: character.powers["power"+ID].damage,
+ compound: false
+ }
+ powerArrayIndex++;
+
+ } else if (tempString.includes("(Multipower)") || tempString.includes("(MPSlot")) {
+ // Import multipower or multipower slot.
+ powerArray[powerArrayIndex]=character.powers["power"+ID];
+
+ powerArrayIndex++;
+ } else if (character.powers["power"+ID].compound === "true") {
+ // Check for compound power and import sub power part separately if found.
+
+ tempString = character.powers["power"+ID].text;
+ count = (tempString.match(/plus/g) || []).length+1;
+
+ // Remove total costs.
+ if (tempString.includes("(Total:")) {
+ tempString = tempString.substring(tempString.indexOf(" Real Cost)") + 12);
+ }
+
+ if(verbose) {
+ sendChat(script_name, "Compound power with " + JSON.stringify(count) + " parts.");
+ }
+
+ damageString = character.powers["power"+ID].damage
+ for (let i=0; i 0) {
+
+ importCount = 0;
+
+ while (importCount < powerArrayIndex) {
+
+ // First fix some known errors from source material.
+ powerArray[importCount].text = fixKnownSpellingErrors(powerArray[importCount].text, script_name);
+ powerArray[importCount].text = fixKnownDesignerErrors(powerArray[importCount].text, character.versionHD, script_name);
+
+ // Power descriptions
+ tempString = powerArray[importCount].text;
+ if ( (character.version >= 2.1) && (typeof powerArray[importCount].notes !== "undefined") && (powerArray[importCount].notes !== "") ) {
+ tempString += '\n'+'\n'+powerArray[importCount].notes;
+ }
+
+ testObject.theName = (powerArray[importCount].name === "") ? importedPowers["powerEffect"+ID] : powerArray[importCount].name;
+ testObject.theText = (typeof tempString !== "undefined") ? tempString.trim() : "";
+ testObject.theEndurance = powerArray[importCount].endurance;
+ testObject.theEffect = findEffectType(powerArray[importCount].text, script_name);
+ testObject.theBase = powerArray[importCount].base;
+ testObject.slotType = (powerArray[importCount].cost.includes("v")) ? "variableSlot" : ( (powerArray[importCount].cost.includes("f")) ? "fixedSlot" : "single");
+
+ if (importCount > sheetCapacity) {
+ // Add to overflow powers list
+ overflowPowers += powerArray[importCount].name + "\n";
+ overflowPowers += (powerArray[importCount].damage !== "") ? " Damage: " + powerArray[importCount].damage + "\n" : "";
+ overflowPowers += " END: " + powerArray[importCount].endurance + "\n";
+ overflowPowers += " Base CP: " + powerArray[importCount].base + ", " + " Real CP: " + powerArray[importCount].cost + "\n";
+ overflowPowers += powerArray[importCount].text + "\n" + "\n";
+ extras += 1;
+
+ } else if (isVehicle && propulsionArray.has(testObject.theEffect) && (propulsionSystems < 2)) {
+ // Import vehicle propulsion system
+ propulsionSystems += 1;
+ sheetCapacity += 1;
+
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionLabel" : "secondaryPropulsionLabel"] = testObject.theName.replace(/\([^)]*\)/g, "");
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionMode" : "secondaryPropulsionMode"] = testObject.theEffect.toLowerCase();
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionSlotType" : "secondaryPropulsionSlotType"] = testObject.slotType;
+
+ // Endurance
+ testObject = findPropulsionEndurance(testObject);
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionCostsEND" : "secondaryPropulsionCostsEND"] = testObject.powerReducedEND;
+
+ // Propulsion mode and attribute values
+ tempValuesArray = getPropulsionDetails(testObject);
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionMode" : "secondaryPropulsionMode"] = tempValuesArray[0];
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsion" : "secondaryPropulsion"] = tempValuesArray[1];
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionAdds" : "secondaryPropulsionAdds"] = tempValuesArray[2];
+ importedPowers[(propulsionSystems === 1) ? "primaryFTLvelocty" : "secondaryFTLvelocty"] = tempValuesArray[3];
+ importedPowers[(propulsionSystems === 1) ? "primaryInstantFTL" : "secondaryInstantFTL"] = (tempValuesArray[4] ? "on" : 0);
+
+ // Noncombat multiple
+ importedPowers[(propulsionSystems === 1) ? "primaryNoncombatMultiple" : "secondaryNoncombatMultiple"] = findNoncombatMultiple(testObject.theText);
+
+ // Advantages and limitations
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionAdv" : "secondaryPropulsionAdv"] = findAdvantages(testObject.theText);
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionLimitations" : "secondaryPropulsionLimitations"] = findLimitations(testObject.theText);
+
+ // Description text
+ importedPowers[(propulsionSystems === 1) ? "primaryPropulsionText" : "secondaryPropulsionText"] = testObject.theText;
+
+ } else if (isVehicle && (testObject.theEffect === "Endurance Reserve") && (reserveSystems < 2)) {
+ // Import vehicle END Reserve
+ reserveSystems += 1;
+ sheetCapacity += 1;
+
+ importedPowers[(reserveSystems === 1) ? "mainReserveLabel" : "auxiliaryReserveLabel"] = testObject.theName.replace(/\([^)]*\)/g, "");
+ tempValuesArray = getReserveParameters(testObject.theText);
+ importedPowers[(reserveSystems === 1) ? "mainReserveEND" : "auxiliaryReserveEND"] = tempValuesArray[0];
+ importedPowers[(reserveSystems === 1) ? "CurrentMainEND" : "CurrentAuxiliaryEND"] = tempValuesArray[0];
+ importedPowers[(reserveSystems === 1) ? "CurrentMainEND_max" : "CurrentAuxiliaryEND_max"] = tempValuesArray[0];
+ importedPowers[(reserveSystems === 1) ? "mainReserveREC" : "auxiliaryReserveREC"] = tempValuesArray[1];
+ importedPowers[(reserveSystems === 1) ? "mainReserveLIM" : "auxiliaryReserveLIM"] = findLimitations(testObject.theText);
+
+ // Description text
+ importedPowers[(reserveSystems === 1) ? "mainReserveText" : "auxiliaryReserveText"] = testObject.theText;
+
+ } else {
+ // All other powers
+ ID = String(importCount+1-reserveSystems-propulsionSystems).padStart(2,'0');
+
+ importedPowers["powerName"+ID] = testObject.theName;
+ importedPowers["powerEffect"+ID] = testObject.theEffect;
+
+ // Special cases or base cost.
+ tempValuesArray = getPowerBaseCost(character, powerArray[importCount].base, testObject.theEffect, testObject.theText, bonusCP, importedPowers.optionTakesNoSTUN, script_name);
+ importedPowers["powerBaseCost"+ID] = tempValuesArray[0];
+ bonusCP = tempValuesArray[1];
+
+ // Get powerReducedEND level and separate endurance limitation or advantage cost.
+
+ if (testObject.theText.includes("Multipower")) {
+ importedPowers["powerReducedEND"+ID] = "noEnd";
+ testObject.theEffect = "Multipower";
+ } else {
+ testObject = findEndurance(testObject);
+ importedPowers["powerReducedEND"+ID] = testObject.powerReducedEND;
+ }
+
+ // Find advantages and limitations values.
+ importedPowers["powerAdvantages"+ID] = findAdvantages(testObject.theText);
+ importedPowers["powerLimitations"+ID] = findLimitations(testObject.theText);
+ importedPowers["powerDamageAdvantage"+ID] = findDamageAdvantages(testObject.theText, script_name);
+ importedPowers["powerAoE"+ID] = isAoE(testObject.theText) ? "on" : 0;
+
+ // Power descriptions
+ importedPowers["powerText"+ID] = testObject.theText;
+
+ // Search for skill roll.
+ tempObject = requiresRoll(testObject.theText);
+ importedPowers["powerActivate"+ID] = tempObject.hasRoll ? "on" : 0;
+ importedPowers["powerSkillRoll"+ID] = tempObject.hasRoll ? tempObject.skillRoll : 18;
+
+ // Search for reduced DCV due to the Concentration limitation.
+ importedPowers["powerDCV"+ID] = reducedDCV(testObject.theText);
+
+ // Search for zero, half, or full range modifiers.
+ importedPowers["powerRMod"+ID] = reducedRMod(testObject.theText);
+
+ // Search for a STUNx mod.
+ importedPowers["powerStunMod"+ID] = modifiedSTUNx(testObject.theText);
+
+ // Assign effect dice.
+ importedPowers["powerDice"+ID] = getPowerDamage(powerArray[importCount].damage, testObject.theEffect, character.strength, script_name);
+
+ // Find and assign power type. Remove export notes from names.
+ tempString = powerArray[importCount].name;
+ if (tempString.includes("(Multipower)")) {
+ // Remove note from name.
+ importedPowers["powerName"+ID] = tempString.replace("(Multipower) ", "");
+ importedPowers["powerType"+ID] = "multipower";
+ importedPowers["powerEffect"+ID] = "Multipower";
+ } else if (tempString.includes("(MPSlot")) {
+ subStringA = powerArray[importCount].cost;
+ if (subStringA.includes("v")) {
+ // Remove note from name.
+ importedPowers["powerName"+ID] = tempString.replace("(MPSlot", "");
+ importedPowers["powerType"+ID] = "variableSlot";
+ } else {
+ // Remove note from name.
+ importedPowers["powerName"+ID] = tempString.replace("(MPSlot", "");
+ importedPowers["powerType"+ID] = "fixedSlot";
+ }
+ } else if (tempString.includes("(VPP)")) {
+ if (tempString.includes("control")) {
+ // Remove notes from name.
+ tempString = tempString.replace("(VPP) ", "");
+ importedPowers["powerName"+ID] = tempString.replace("(control)", " [C]");
+ importedPowers["powerType"+ID] = "powerPool";
+ importedPowers["powerEffect"+ID] = "VPP Control";
+ importedPowers["powerAction"+ID] = "false";
+ importedPowers["powerBaseCost"+ID] = powerArray[importCount].base;
+ } else {
+ // Remove note from name.
+ tempString = tempString.replace("(VPP) ", "");
+ importedPowers["powerName"+ID] = tempString.replace("(pool)", " [P]");
+ importedPowers["powerType"+ID] = "powerPool";
+ importedPowers["powerEffect"+ID] = "VPP Pool";
+ importedPowers["powerAction"+ID] = "false";
+ }
+ } else if (powerArray[importCount].compound === true) {
+ importedPowers["powerType"+ID] = "compound";
+ } else if ( (typeof powerArray[importCount].text != "undefined") && (powerArray[importCount].text != "") && ((powerArray[importCount].text).includes("Unified Power")) ) {
+ importedPowers["powerType"+ID] = "unified";
+ } else {
+ importedPowers["powerType"+ID] = "single";
+ }
+
+ // Set attack checkbox for attacks.
+ importedPowers["powerAttack"+ID] = isAttack(testObject.theEffect) ? "on" : 0;
+
+ // Set power type.
+ importedPowers["powerDamageType"+ID] = getPowerDamageType(testObject.theEffect);
+
+ // Apply characteristic mods granted by enhancement powers or movement.
+ tempString = powerArray[importCount].text;
+
+ if ( (typeof tempString !== "undefined") && (tempString !== "") ) {
+ switch (testObject.theEffect) {
+ case "Multipower": break;
+ case "Base STR Mod": if (tempString.includes("0 END")) {
+ importedPowers["optionUntiring"] = "on";
+ }
+ break;
+ case "Running": charMod.runningMod += getCharacteristicMod(tempString, "Running", script_name);
+ break;
+ case "Leaping": charMod.leapingMod += getCharacteristicMod(tempString, "Leaping", script_name);
+ break;
+ case "Swimming": charMod.swimmingMod += getCharacteristicMod(tempString, "Swimming", script_name);
+ break;
+ case "Flight": charMod.flightMod += getCharacteristicMod(tempString, "Flight", script_name);
+ break;
+ case "Enhanced STR": charMod.strengthMod += getCharacteristicMod(tempString, "STR", script_name);
+ break;
+ case "Enhanced DEX": charMod.dexterityMod += getCharacteristicMod(tempString, "DEX", script_name);
+ break;
+ case "Enhanced CON": charMod.constitutionMod += getCharacteristicMod(tempString, "CON", script_name);
+ break;
+ case "Enhanced INT": charMod.intelligenceMod += getCharacteristicMod(tempString, "INT", script_name);
+ break;
+ case "Enhanced EGO": charMod.egoMod += getCharacteristicMod(tempString, "EGO", script_name);
+ break;
+ case "Enhanced PRE": charMod.presenceMod += getCharacteristicMod(tempString, "PRE", script_name);
+ break;
+ case "Enhanced OCV": charMod.ocvMod += getCharacteristicMod(tempString, "OCV", script_name);
+ break;
+ case "Enhanced DCV": charMod.dcvMod += getCharacteristicMod(tempString, "DCV", script_name);
+ break;
+ case "Enhanced OMCV": charMod.omcvMod += getCharacteristicMod(tempString, "OMCV", script_name);
+ break;
+ case "Enhanced DMCV": charMod.dmcvMod += getCharacteristicMod(tempString, "DMCV", script_name);
+ break;
+ case "Enhanced BODY": charMod.bodyMod += getCharacteristicMod(tempString, "BODY", script_name);
+ break;
+ case "Enhanced PD": charMod.pdMod += getCharacteristicMod(tempString, "PD", script_name);
+ break;
+ case "Enhanced ED": charMod.edMod += getCharacteristicMod(tempString, "ED", script_name);
+ break;
+ case "Enhanced STUN": charMod.stunMod += getCharacteristicMod(tempString, "STUN", script_name);
+ break;
+ case "Enhanced END": charMod.enduranceMod += getCharacteristicMod(tempString, "END", script_name);
+ break;
+ case "Enhanced REC": charMod.recoveryMod += getCharacteristicMod(tempString, "REC", script_name);
+ break;
+ case "Enhanced PER": if ( tempString.includes("all Sense") ) {
+ charMod.enhancedPerceptionModifier += getCharacteristicMod(tempString, "PER", script_name);
+ if ( (tempString.includes("except Sight")) || (tempString.includes("but Sight")) ) {
+ charMod.perceptionModifierVision += -getCharacteristicMod(tempString, "PER", script_name);
+ }
+ if ( (tempString.includes("except Hearing")) || (tempString.includes("but Hearing")) ) {
+ charMod.perceptionModifierHearing += -getCharacteristicMod(tempString, "PER", script_name);
+ }
+ if ( (tempString.includes("except Smell")) || (tempString.includes("but Smell")) ) {
+ charMod.perceptionModifierSmell += -getCharacteristicMod(tempString, "PER", script_name);
+ }
+ } else {
+ charMod.perceptionModifierVision += (tempString.includes("Sight")) ? getCharacteristicMod(tempString, "PER", script_name) : 0;
+ charMod.perceptionModifierHearing += (tempString.includes("Hearing")) ? getCharacteristicMod(tempString, "PER", script_name) : 0;
+ charMod.perceptionModifierSmell += (tempString.includes("Smell")) ? getCharacteristicMod(tempString, "PER", script_name) : 0;
+ if ( !(tempString.includes("Sight")) && !(tempString.includes("Hearing")) && !(tempString.includes("Smell")) && !(tempString.includes("Touch")) ) {
+ charMod.enhancedPerceptionModifier += getCharacteristicMod(tempString, "PER", script_name);
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+ }
+
+ importCount += 1;
+ }
+ }
+
+ // Overflow powers to talent notes.
+ if ( (extras > 0) && (character.version >= 1.2) ) {
+ importedPowers.complicationsTextLeft = overflowPowers;
+
+ if(verbose) {
+ sendChat(script_name, extras + " powers placed in notes.");
+ }
+ }
+
+ // Import powers and bonus points to sheet.
+ importedPowers.bonusBenefit = bonusCP;
+
+ const importedPowersAndMods = Object.assign({}, importedPowers, charMod);
+ setAttrs(object.id, importedPowersAndMods);
+
+ if(verbose) {
+ if (powerArrayIndex === 1) {
+ sendChat(script_name, "Imported 1 power.");
+ } else {
+ sendChat(script_name, "Imported " + powerArrayIndex + " powers.");
+ }
+ }
+
+ return overflowPowers.trim();
+ };
+
+
+ var importComplications = function(object, character, script_name) {
+
+ /* ************************************************* */
+ /* *** Import Function: Import Complications *** */
+ /* ************************************************* */
+
+ // Imports the first six complications.
+ let importCount = 0;
+ let imported = 0;
+ let ID = "";
+ let tempString = "";
+ let diceString = "";
+ let tempPosition = 0;
+ const maxComplications = 10;
+ const maxOverflow = 10;
+ let overflowString = "";
+ var importedComplications = new Object();
+
+ /* ------------------------- */
+ /* Read Complications */
+ /* ------------------------- */
+
+ for (importCount = 0; importCount < maxComplications + maxOverflow; importCount++) {
+
+ ID = String(importCount+1).padStart(2,'0');
+
+ if (importCount < maxComplications) {
+ if ((typeof character.complications["complication"+ID] !== "undefined") && (typeof character.complications["complication"+ID].type !== "undefined")) {
+ importedComplications["complicationName"+ID] = character.complications["complication"+ID].type;
+ importedComplications["complicationCP"+ID] = character.complications["complication"+ID].points;
+
+ tempString = character.complications["complication"+ID].text;
+ if (typeof character.complications["complication"+ID].notes !== "undefined") {
+ tempString += '\n'+'\n'+character.complications["complication"+ID].notes;
+ }
+ importedComplications["complicationText"+ID] = (typeof tempString !== "undefined") ? tempString.trim() : "";
+
+ // Type
+ tempString = character.complications["complication"+ID].type;
+ tempString = tempString.toLowerCase();
+
+ if (tempString.includes("accidental change")) {
+ importedComplications["complicationType"+ID] = "accidental";
+ } else if (tempString.includes("dependence")) {
+ importedComplications["complicationType"+ID] = "dependence";
+ } else if (tempString.includes("dependent")) {
+ importedComplications["complicationType"+ID] = "dependent";
+ } else if (tempString.includes("distinctive")) {
+ importedComplications["complicationType"+ID] = "distinctive";
+ } else if ((tempString.includes("enraged")) || (tempString.includes("berserk"))) {
+ importedComplications["complicationType"+ID] = "enraged";
+ } else if (tempString.includes("hunted")) {
+ importedComplications["complicationType"+ID] = "hunted";
+ } else if (tempString.includes("reputation")) {
+ importedComplications["complicationType"+ID] = "reputation";
+ } else if (tempString.includes("physical")) {
+ importedComplications["complicationType"+ID] = "physical";
+ } else if (tempString.includes("psychological")) {
+ importedComplications["complicationType"+ID] = "psychological";
+ } else if (tempString.includes("rival")) {
+ importedComplications["complicationType"+ID] = "rival";
+ } else if (tempString.includes("social")) {
+ importedComplications["complicationType"+ID] = "social";
+ } else if (tempString.includes("susceptibility")) {
+ importedComplications["complicationType"+ID] = "susceptibility";
+ } else if (tempString.includes("unluck")) {
+ importedComplications["complicationType"+ID] = "unluck";
+ } else if (tempString.includes("vulnerability")) {
+ importedComplications["complicationType"+ID] = "vulnerability";
+ } else {
+ importedComplications["complicationType"+ID] = "custom";
+ }
+
+ // Activation Roll
+ tempString = character.complications["complication"+ID].text + " " + character.complications["complication"+ID].notes;
+ tempString = tempString.toLowerCase();
+
+ // Most of these roll options will never be used, but are here for completeness.
+
+ if (tempString.includes("10-")) {
+ importedComplications["complicationRollChance"+ID] = 10;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("11-")) {
+ importedComplications["complicationRollChance"+ID] = 11;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("12-")) {
+ importedComplications["complicationRollChance"+ID] = 12;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("13-")) {
+ importedComplications["complicationRollChance"+ID] = 13;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("14-")) {
+ importedComplications["complicationRollChance"+ID] = 14;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("15-")) {
+ importedComplications["complicationRollChance"+ID] = 15;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("16-")) {
+ importedComplications["complicationRollChance"+ID] = 16;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("17-")) {
+ importedComplications["complicationRollChance"+ID] = 17;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("18-")) {
+ importedComplications["complicationRollChance"+ID] = 18;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("3-")) {
+ importedComplications["complicationRollChance"+ID] = 3;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("4-")) {
+ importedComplications["complicationRollChance"+ID] = 4;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("5-")) {
+ importedComplications["complicationRollChance"+ID] = 5;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("6-")) {
+ importedComplications["complicationRollChance"+ID] = 6;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("7-")) {
+ importedComplications["complicationRollChance"+ID] = 7;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("8-")) {
+ importedComplications["complicationRollChance"+ID] = 8;
+ importedComplications["complicationActivate"+ID] = "on";
+ } else if (tempString.includes("9-")) {
+ importedComplications["complicationRollChance"+ID] = 9;
+ importedComplications["complicationActivate"+ID] = "on";
+ }
+
+ // Dice
+ if ( tempString.includes("d6") ) {
+ tempPosition = tempString.indexOf("d6")
+ diceString = tempString.slice(0, tempPosition);
+ diceString = diceString.slice(-2).replace(/\D/g,"") + "d6";
+ importedComplications["complicationDice"+ID] = diceString;
+ } else {
+ importedComplications["complicationDice"+ID] = "0";
+ }
+
+ imported += 1;
+ }
+ } else if (importCount < maxComplications + maxOverflow) {
+ if ((typeof character.complications["complication"+ID] !== "undefined") && (typeof character.complications["complication"+ID].type !== "undefined")) {
+ overflowString += character.complications["complication"+ID].type + '\n';
+ overflowString += character.complications["complication"+ID].text + '\n' + character.complications["complication"+ID].notes + '\n';
+ overflowString += "("+character.complications["complication"+ID].points + " points)\n\n";
+
+ imported += 1;
+ }
+ }
+ }
+
+ importedComplications["complicationsTextRight"] = overflowString;
+
+ if(verbose) {
+ if (imported === 1) {
+ sendChat(script_name, "Imported 1 complication.");
+ } else {
+ sendChat(script_name, "Imported " + imported + " complications.");
+ }
+ }
+
+ // Import complications to sheet.
+ setAttrs(object.id, importedComplications);
+
+ return;
+ };
+
+
+ var importAllSkills = function(object, character, script_name) {
+
+ /* ************************************************* */
+ /* *** Import Function: Import Skills *** */
+ /* ************************************************* */
+
+ // Struct for counting processed skills.
+
+ let sheetSkillIndexes={
+ skillIndex: 0,
+ generalSkillIndex: 0,
+ combatSkillIndex: 0,
+ languageSkillIndex: 0
+ }
+
+ const maxSkills = 50;
+
+ for (let importCount = 0; importCount < maxSkills; importCount++) {
+
+ ID = String(importCount+1).padStart(2,'0');
+
+ if (typeof character.skills["skill"+ID] !== "undefined") {sheetSkillIndexes = importSkill(object, character, script_name, sheetSkillIndexes, character.skills["skill"+ID]);}
+ }
+
+ return;
+ };
+
+
+ /* ------------------------- */
+ /* Import Helper Functions */
+ /* ------------------------- */
+
+ var cleanQuotes = function(inputString, script_name) {
+ // Look for double quotes in text that shouldn't be there. Remove them.
+
+ let detailString;
+ let cleanString;
+ let frontMatter;
+ let backMatter;
+ let startPosition;
+ let endPosition;
+ let count = 0;
+ let matches = 0;
+ let engagePosition = inputString.indexOf('\"backgroundText\":\"');
+ let exitPosition = inputString.indexOf('\"experience\":') - 10;
+
+ for (let i = engagePosition; i < exitPosition; i+=1) {
+ startPosition = inputString.indexOf('\":\"', i)+1;
+ endPosition = inputString.indexOf('\", \"', i);
+ detailString = inputString.slice(startPosition+2, endPosition);
+ matches = detailString.match(/["]/g);
+ count = matches ? matches.length : 0;
+
+ if (matches) {
+ frontMatter = inputString.slice(0, startPosition+2);
+ backMatter = inputString.slice(endPosition+1);
+ cleanString = detailString.replace(/["]+/g, "");
+ inputString = frontMatter + cleanString + '\"' + backMatter;
+
+ exitPosition -= count;
+ }
+
+ i += detailString.length - count;
+ }
+
+ return inputString;
+ }
+
+
+ var importSkill = function(object, character, script_name, sheetSkillIndexes, theSkill) {
+ // Assign skill to general, combat, language, enhancer, etc.
+
+ if (Object.keys(theSkill).length === 0) {
+ // Empty Skill.
+ return sheetSkillIndexes;
+
+ } else if (theSkill.enhancer === "true") {
+ // Invoke Skill Enhancer Function
+ importSkillEnhancer(object, character, script_name, theSkill.text);
+
+ } else if (theSkill.display === "Language") {
+ // Call import language function.
+ importLanguage(object, character, script_name, theSkill, sheetSkillIndexes.languageSkillIndex);
+ sheetSkillIndexes.languageSkillIndex++;
+
+ } else if (theSkill.display === "Combat Skill Levels"){
+ // Import weapon levels. The combat skill index may or may not be increased.
+ // This needs to be decided by the function as more general skill levels
+ // use prepared slots on the character sheet.
+ sheetSkillIndexes.combatSkillIndex = importWeaponSkill(object, character, script_name, theSkill, sheetSkillIndexes.combatSkillIndex);
+
+ } else if (theSkill.display === "Mental Combat Skill Levels"){
+ // Import mental CSLs. Variation of weapon CSLs.
+ sheetSkillIndexes.combatSkillIndex = importWeaponSkill(object, character, script_name, theSkill, sheetSkillIndexes.combatSkillIndex);
+
+ } else if (theSkill.display === "Penalty Skill Levels") {
+ // Import penalty skill levels.
+ sheetSkillIndexes.combatSkillIndex = importWeaponSkill(object, character, script_name, theSkill, sheetSkillIndexes.combatSkillIndex);
+
+ } else if (theSkill.display === ("Weapon Familiarity")) {
+ // Weapon familiarity skill line.
+ // There will probably be only one line since Hero Designer lumps them together.
+ // We need to break them up.
+ let tempString = theSkill.text;
+ tempString = tempString.replace(/\s\s+/g, " ");
+
+ // Special skillsets to rename.
+ tempString = tempString.replace("Thrown Knives, Axes, and Darts", "Thrown Weapons");
+ tempString = tempString.replace("Javelins and Thrown Spears", "Thrown Spears");
+ tempString = tempString.replace("Axes, Maces, Hammers, and Picks", "Hafted Weapons");
+
+ // Drop WF:
+ tempString = tempString.replace("WF: ", "");
+
+ let weaponFamArrayLength = (tempString.split(",").length - 1);
+ let weaponFamArray = new Array(weaponFamArrayLength);
+
+ for (let i = 0; i <= weaponFamArrayLength; i++) {
+ // Split up string into weapon groups.
+ if (i < weaponFamArrayLength) {
+ // Get first weapon group before a comma.
+ weaponFamArray[i] = tempString.substr(0, tempString.indexOf(","));
+
+ // Remove that weapon group from the string.
+ tempString = tempString.replace(weaponFamArray[i] + ", ", "");
+ } else {
+ // There is only one group left to get.
+ weaponFamArray[i] = tempString;
+ }
+ }
+
+ // Process the skills in weaponFamArray as individual skills.
+ // The combatSkillIndex will advance each time.
+ for (let i = 0; i <= weaponFamArrayLength; i++) {
+ let tempSkill = {
+ name: "",
+ enhancer: "",
+ text: weaponFamArray[i],
+ display:"Weapon Familiarity",
+ cost: 0
+ }
+ if (tempSkill.text.includes("Common") || tempSkill.text.includes("Small Arms") || tempSkill.text.includes("Emplaced Weapons") || tempSkill.text.includes("Beam Weapons") || tempSkill.text.includes("Energy Weapons") || tempSkill.text.includes("Early Firearms") || tempSkill.text.includes("Siege Engines")) {
+ tempSkill.cost = 2;
+ } else {
+ tempSkill.cost = 1;
+ }
+
+ sheetSkillIndexes.combatSkillIndex = importWeaponSkill(object, character, script_name, tempSkill, sheetSkillIndexes.combatSkillIndex);
+ }
+ } else if (theSkill.display === ("Transport Familiarity")) {
+ // Transport familiarity skill line.
+ // There should be only one line since Hero Designer lumps them together.
+ // We need to break them up.
+ let tempString = theSkill.text;
+ tempString = tempString.replace(/\s\s+/g, " ");
+ tempString = tempString.replace("TF: ", "");
+ let transportFamArrayLength = (tempString.split(",").length - 1);
+ let transportFamArray = new Array(transportFamArrayLength);
+
+ for (let i = 0; i <= transportFamArrayLength; i++) {
+ // Split up string into transport groups.
+ if (i < transportFamArrayLength) {
+ // Get first weapon group before a comma.
+ transportFamArray[i] = tempString.substr(0, tempString.indexOf(","));
+
+ // Remove that weapon group from the string.
+ tempString = tempString.replace(transportFamArray[i]+", ", "");
+ } else {
+ // There is only one group left to get.
+ transportFamArray[i] = tempString;
+ }
+ }
+
+ // Process the skills in transportFamArray as individual skills.
+ // The generalSkillIndex will advance each time.
+ for (let i = 0; i <= transportFamArrayLength; i++) {
+ let tempSkill = {
+ name: transportFamArray[i],
+ enhancer: "",
+ text: "TF: " + transportFamArray[i],
+ display:"Transport Familiarity",
+ cost: 1
+ }
+
+ // Find 2-point TF groups.
+ if (tempSkill.text.includes("Common") || tempSkill.text.includes("Riding") || tempSkill.text.includes("Space Vehicles") || tempSkill.text.includes("Mecha")) {
+ tempSkill.cost = 2;
+ }
+
+ sheetSkillIndexes.generalSkillIndex = importGeneralSkill(object, character, script_name, tempSkill, sheetSkillIndexes.generalSkillIndex);
+ }
+ } else if (theSkill.display === "Skill Levels") {
+ // Import non-combat skill levels.
+ // Groups of three skills will be a challenge.
+
+ if (theSkill.text.includes("three pre-defined Skills")) {
+ // A group skill level, which is recorded along with general skills.
+ sheetSkillIndexes.generalSkillIndex = importGeneralSkill(object, character, script_name, theSkill, sheetSkillIndexes.generalSkillIndex);
+ } else if (theSkill.cost/theSkill.levels <= 3) {
+ // Group or single skill level, which is recorded along with general skills.
+
+ sheetSkillIndexes.generalSkillIndex = importGeneralSkill(object, character, script_name, theSkill, sheetSkillIndexes.generalSkillIndex);
+ } else {
+ importSkillLevels(object, character, script_name, theSkill);
+ }
+ } else {
+ // Import general skill
+
+ sheetSkillIndexes.generalSkillIndex = importGeneralSkill(object, character, script_name, theSkill, sheetSkillIndexes.generalSkillIndex);
+ }
+
+ sheetSkillIndexes.skillIndex++;
+
+ return sheetSkillIndexes;
+ }
+
+
+ var importSkillEnhancer = function(object, character, script_name, enhancerString) {
+ // This function is called when a skill is identified as an enhancer.
+ // The skills' text will determine which enhancer it is.
+ let enhancer;
+
+ switch(enhancerString) {
+ case "Jack of All Trades":
+ enhancer = {
+ enhancerJack: "on",
+ enhancerJackCP: 3
+ }
+ break;
+ case "Linguist":
+ enhancer = {
+ enhancerLing: "on",
+ enhancerLingCP: 3
+ }
+ break;
+ case "Scholar":
+ enhancer = {
+ enhancerSch: "on",
+ enhancerSchCP: 3
+ }
+ break;
+ case "Scientist":
+ enhancer = {
+ enhancerSci: "on",
+ enhancerSciCP: 3
+ }
+ break;
+ case "Traveler":
+ enhancer = {
+ enhancerTrav: "on",
+ enhancerTravCP: 3
+ }
+ break;
+ default:
+ // Well-Connected
+ enhancer = {
+ enhancerWell: "on",
+ enhancerWellCP: 3
+ }
+ }
+
+ setAttrs(object.id, enhancer);
+
+ return;
+ }
+
+
+ var importLanguage = function(object, character, script_name, languageObject, languageIndex) {
+ // This function is called when a skill is identified as an enhancer.
+ // The skills' text will determine which enhancer it is.
+ // let languages;
+
+ var ID = String(languageIndex+76).padStart(2,'0');
+ var language = new Object();
+
+ let name = languageObject.name;
+ let tempString = languageObject.text;
+ if (name === "") {
+ if (tempString.includes("Language:")) {
+ name = tempString.replace("Language:", "");
+ }
+ if (name.includes("(") && name.includes(")")) {
+ let endPosition = name.indexOf("(");
+ name = name.slice(0, endPosition-1);
+ }
+ }
+
+ let fluency;
+ let literacy;
+ let cost = languageObject.cost;
+
+ // Determine fluency.
+ if (tempString.includes("native")) {
+ fluency = "native";
+ } else if ( tempString.includes("idiomatic") && (cost < 2)) {
+ fluency = "native";
+ } else if ( tempString.includes("imitate") && (cost < 3)) {
+ fluency = "nativePlus";
+ } else if (tempString.includes("basic")) {
+ fluency = "basic";
+ } else if (tempString.includes("completely")) {
+ fluency = "accent";
+ } else if (tempString.includes("fluent")) {
+ fluency = "fluent";
+ } else if (tempString.includes("idiomatic")) {
+ fluency = "idiomatic";
+ } else if (tempString.includes("imitate")) {
+ fluency = "imitate";
+ } else {
+ fluency = "none";
+ }
+
+ // Determine literacy.
+ if (tempString.includes("literate")) {
+ literacy = "on";
+ }
+ else {
+ literacy = 0;
+ }
+
+ // Assign this language to the character sheet.
+ language["skillName"+ID] = name.trim();
+ language["skillFluency"+ID] = fluency;
+ language["skillLiteracy"+ID] = literacy;
+ language["skillCP"+ID] = cost;
+
+ setAttrs(object.id, language);
+
+ return;
+ }
+
+
+ var importWeaponSkill = function(object, character, script_name, skillObject, weaponSkillIndex) {
+ // Identify and assign combat levels
+
+ let weaponSkill;
+ let name = skillObject.name;
+ let levels = parseInt(skillObject.levels);
+ let levelCost;
+ let type = 'none';
+ let cost = parseInt(skillObject.cost);
+
+ if (skillObject.text.includes("HTH Combat")) {
+ // Find the number of levels from the CP spent.
+ weaponSkill = {
+ skillLevels73: skillObject.levels
+ };
+
+ } else if (skillObject.text.includes("Ranged Combat")) {
+ // Find the number of levels from the CP spent.
+ weaponSkill = {
+ skillLevels74: skillObject.levels
+ };
+
+ } else if (skillObject.text.includes("All Attacks") || skillObject.text.includes("All Combat")) {
+ // Find the number of levels from the CP spent.
+
+ weaponSkill = {
+ skillLevels75: skillObject.levels
+ };
+
+ } else if (skillObject.text.includes("group") || skillObject.text.includes("single") || (skillObject.display === "Weapon Familiarity") || (skillObject.display === "Penalty Skill Levels") || (skillObject.display === "Combat Skill Levels") || (skillObject.display === "Mental Combat Skill Levels")) {
+ // Call import weapon skills function.
+
+ // Determine type
+ if (skillObject.display === "Weapon Familiarity") {
+ // Weapon familiarity at the moment can be common or single.
+ if (cost === 1) {
+ name = skillObject.text;
+ type = "Fam1";
+ levels = 0;
+ } else {
+ name = skillObject.text.replace("Weapons", "");
+ type = "Fam2";
+ levels = 0;
+ }
+ } else if (skillObject.display === "Penalty Skill Levels") {
+ // Determine penalty skill levels
+
+ // Try to shorten the name text.
+ name = skillObject.text.replace("versus", "vs");
+ name = name.replace("Versus", "vs");
+ name = name.replace("Location", "Loc");
+ name = name.replace("Range", "Rng");
+ name = name.replace("Modifiers ", "");
+ name = name.replace("Modifier ", "");
+ name = name.replace("with", "w/");
+ name = name.replace("the ", "");
+
+ levelCost = parseInt(cost/levels);
+ switch (levelCost) {
+ case 1: type = 'PSL1';
+ break;
+ case 2: type = 'PSL2';
+ break;
+ case 3: type = 'PSL3';
+ break;
+ default: 'none';
+ }
+ } else if (skillObject.display === "Mental Combat Skill Levels") {
+ // Determine mental combat skill levels
+ name = skillObject.text;
+
+ name = name.replace("with all", "w/");
+ name = name.replace("with a", "w/");
+ name = name.replace(" single", "");
+ name = name.replace("group of Mental Powers", "Mental Group");
+
+ levelCost = parseInt(cost/levels);
+ switch (levelCost) {
+ case 1: type = 'MCSL1';
+ break;
+ case 3: type = 'MCSL3';
+ break;
+ case 6: type = 'MCSL6';
+ break;
+ default: 'none';
+ }
+ } else {
+ // Determine combat skill levels
+ name = skillObject.text;
+
+ name = name.replace("with all", "w/");
+ name = name.replace("with any", "w/");
+ name = name.replace("with a", "w/");
+ name = name.replace("small group of attacks", "small group");
+ name = name.replace("large group of attacks", "large group");
+
+ levelCost = parseInt(cost/levels);
+ switch (levelCost) {
+ case 2: type = 'CSL2';
+ break;
+ case 3: type = 'CSL3';
+ break;
+ case 5: type = 'CSL5';
+ break;
+ case 8: type = 'CSL8';
+ break;
+ default: 'none';
+ }
+ }
+
+ // Assign skill parameters to an open weapon skill slot.
+ switch (weaponSkillIndex) {
+ case 0:
+ // Weapon skill slot 1.
+ weaponSkill = {
+ skillName61: name,
+ skillType61: type,
+ skillLevels61: levels,
+ skillCP61: cost
+ }
+ break;
+ case 1:
+ // Weapon skill slot 2.
+ weaponSkill = {
+ skillName62: name,
+ skillType62: type,
+ skillLevels62: levels,
+ skillCP62: cost
+ }
+ break;
+ case 2:
+ // Weapon skill slot 3.
+ weaponSkill = {
+ skillName63: name,
+ skillType63: type,
+ skillLevels63: levels,
+ skillCP63: cost
+ }
+ break;
+ case 3:
+ // Weapon skill slot 4.
+ weaponSkill = {
+ skillName64: name,
+ skillType64: type,
+ skillLevels64: levels,
+ skillCP64: cost
+ }
+ break;
+ case 4:
+ // Weapon skill slot 5.
+ weaponSkill = {
+ skillName65: name,
+ skillType65: type,
+ skillLevels65: levels,
+ skillCP65: cost
+ }
+ break;
+ case 5:
+ // Weapon skill slot 6.
+ weaponSkill = {
+ skillName66: name,
+ skillType66: type,
+ skillLevels66: levels,
+ skillCP66: cost
+ }
+ break;
+ case 6:
+ // Weapon skill slot 7.
+ weaponSkill = {
+ skillName67: name,
+ skillType67: type,
+ skillLevels67: levels,
+ skillCP67: cost
+ }
+ break;
+ case 7:
+ // Weapon skill slot 8. Existence triggers few languages shown sheet option.
+ weaponSkill = {
+ skillName68: name,
+ skillType68: type,
+ skillLevels68: levels,
+ skillCP68: cost,
+ optionFewerLanguages: "on",
+ combatSkillsExtension: "true",
+ languageSkillsExtension: "false"
+ }
+ break;
+ case 8:
+ // Weapon skill slot 9.
+ weaponSkill = {
+ skillName69: name,
+ skillType69: type,
+ skillLevels69: levels,
+ skillCP69: cost
+ }
+ break;
+ case 9:
+ // Weapon skill slot 10.
+ weaponSkill = {
+ skillName70: name,
+ skillType70: type,
+ skillLevels70: levels,
+ skillCP70: cost
+ }
+ break;
+ case 10:
+ // Weapon skill slot 11.
+ weaponSkill = {
+ skillName71: name,
+ skillType71: type,
+ skillLevels71: levels,
+ skillCP71: cost
+ }
+ break;
+ case 11:
+ // Last available
+ weaponSkill = {
+ skillName72: name,
+ skillType72: type,
+ skillLevels72: levels,
+ skillCP72: cost
+ }
+ break;
+ }
+ weaponSkillIndex++;
+ }
+
+ setAttrs(object.id, weaponSkill);
+
+ return weaponSkillIndex;
+ }
+
+
+ var importSkillLevels = function(object, character, script_name, skillObject) {
+
+ let skillLevel = {};
+
+ if (skillObject.text.includes("all Intellect Skills")) {
+ // The broad group skill level is ambiguous.
+ // By default we will guess intellect as the most common.
+
+ if (skillObject.name.includes("nteract")) {
+ // Look at name to see if player added interaction label.
+ skillLevel.interactionLevels = skillObject.levels;
+ skillLevel.interactionLevelsCP = skillObject.levels*4;
+
+ if(verbose) {
+ sendChat(script_name, "Found interaction group levels.");
+ }
+ } else if (skillObject.name.includes("ntellect")) {
+ // Look at name to see if player added intellect label.
+ skillLevel.intellectLevels = skillObject.levels;
+ skillLevel.intellectLevelsCP = skillObject.levels*4;
+
+ if(verbose) {
+ sendChat(script_name, "Found intellect group levels.");
+ }
+ } else {
+ // Assume intellect.
+ skillLevel.intellectLevels = skillObject.levels;
+ skillLevel.intellectLevelsCP = skillObject.levels*4;
+
+ if(verbose) {
+ sendChat(script_name, "Found broad group levels. Assuming intellect.");
+ }
+ }
+ } else if (skillObject.text.includes("all Agility Skills")) {
+ skillLevel.agilityLevels = skillObject.levels;
+ skillLevel.agilityLevelsCP = skillObject.levels*6;
+
+ } else if (skillObject.text.includes("all Non-Combat Skills")) {
+ skillLevel.noncombatLevels = skillObject.levels;
+ skillLevel.noncombatLevelsCP = skillObject.levels*10;
+
+ } else if (skillObject.text.includes("Overall")) {
+ skillLevel.overallLevels = skillObject.levels;
+ skillLevel.overallLevelsCP = skillObject.levels*12;
+ }
+
+ setAttrs(object.id, skillLevel);
+
+ return;
+ }
+
+
+ var importGeneralSkill = function(object, character, script_name, skillObject, generalSkillIndex) {
+ // Identify and import a general skill.
+
+ var theSkill = new Object();
+ let attribute = skillObject.attribute;
+ let text = skillObject.text;
+ let type = "none";
+ let base = parseInt(skillObject.base)||0;
+ let levels = parseInt(skillObject.levels)||0;
+ let cost = skillObject.cost;
+
+ if ( (skillObject.display === ("Skill Levels")) && (base/levels < 3)) {
+ // Single skill.
+ type = "other";
+ } else if (skillObject.display === ("Skill Levels")) {
+ // Three-group skill.
+ type = "group";
+ } else if (skillObject.text.includes("three pre-defined Skills")) {
+ // Three-group skill.
+ type = "group";
+ } else if (text.startsWith("PS")) {
+ // Professional skill.
+ type = (cost === "0") ? "everymanPS" : "ps";
+ } else if ((base === "0") && (cost === "0")) {
+ // Everyman skill.
+ type = "everyman";
+ } else if (text.startsWith("KS") && ((base-levels) <= 2)) {
+ // Knowledge Skill
+ type = "ks";
+ } else if (text.startsWith("KS") && ((base-levels) === 3)) {
+ // Knowledge Skill based on INT.
+ type = "intKS";
+ } else if (text.startsWith("CK") && ((base-levels) <= 2)) {
+ // City Knowledge Skill
+ type = "ck";
+ } else if (text.startsWith("CK") && ((base-levels) === 3)) {
+ // City Knowledge Skill based on INT.
+ type = "intCK";
+ } else if (text.startsWith("CuK") && ((base-levels) <= 2)) {
+ // Culture Knowledge Skill
+ type = "cuk";
+ } else if (text.startsWith("CuK") && ((base-levels) === 3)) {
+ // Culture Knowledge Skill based on INT.
+ type = "intCuK";
+ } else if (text.startsWith("Science Skill") && ((base-levels) <= 2)) {
+ // Science Skill
+ type = "ss";
+ } else if (text.startsWith("Science Skill") && ((base-levels) === 3)) {
+ // Science Skill based on INT.
+ type = "intSS";
+ } else if (text.startsWith("AK") && ((base-levels) <= 2)) {
+ // Area Knowledge.
+ type = "ak";
+ } else if (text.startsWith("AK") && ((base-levels) === 3)) {
+ // Area Knowledge Skill based on INT.
+ type = "intAK";
+ } else if (text.startsWith("TF")) {
+ // Transport familiarity.
+ type = "tf";
+ } else if (text.includes("Survival") || text.includes("Gambling") || text.includes("Forgery")) {
+ // Special skills.
+ if (text.includes("8-")) {
+ // It is not clear if a familiarity skill should be odd or even from the costs.
+ if (base === 1) {
+ type = "sp2";
+ } else if (base === 2) {
+ type = "sp4";
+ } else {
+ type = "sp6";
+ }
+ } else {
+ if ((base-levels*2) === 1) {
+ type = "sp1";
+ } else if ((base-levels*2) === 2) {
+ type = "sp2";
+ } else if ((base-levels*2) === 3) {
+ type = "sp3";
+ } else if ((base-levels*2) === 4) {
+ type = "sp4";
+ } else if ((base-levels*2) === 5) {
+ type = "sp5";
+ } else {
+ // Default of six-point skills unless there is call for higher.
+ type = "sp6";
+ }
+ }
+ } else if (attribute === "INT") {
+ // Intellect skill.
+ type = "int";
+ } else if (attribute === "DEX") {
+ // Agility skill.
+ type = "dex";
+ } else if (attribute === "PRE") {
+ // Interact skill.
+ type = "pre";
+ } else if (attribute === "EGO") {
+ // Ego skill. Probably faith.
+ type = "ego";
+ } else if (attribute === "STR") {
+ // Strength skill (unusual).
+ type = "str";
+ } else if (attribute === "CON") {
+ // Constitution skill (unusual).
+ type = "con";
+ } else if ((skillObject.display === "Cramming") || (text.toLowerCase().includes("skill"))) {
+ // A special skill or group of undetermined skills.
+ type = "other";
+ } else if (cost === "") {
+ // Empty slot.
+ type = "none";
+ } else {
+ // Best last guess is combat.
+ type = "combat";
+ }
+
+ // Remove unwanted text from skills influenced by enhancers.
+ if (text.includes("Active Points")) {
+ text = removeEnhancerText(text);
+ }
+
+ // Try to find the best name of the skill.
+ // It may be in .name, .text, or .display.
+
+ let name = skillObject.name;
+
+ if (name === "") {
+ if ((text !== "") && text.includes("AK: ")) {
+ name = text.replace("AK: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("KS: ")) {
+ name = text.replace("KS: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("CK: ")) {
+ name = text.replace("CK: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("CuK: ")) {
+ name = text.replace("CuK: ", "");
+ name = name.slice(0, -4);
+ } else if ((text !== "") && text.includes("SS: ")) {
+ name = text.replace("SS: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("Science Skill: ")) {
+ name = text.replace("Science Skill: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("PS: ")) {
+ name = text.replace("PS: ", "");
+ name = name.slice(0, -3);
+ } else if ((text !== "") && text.includes("Defense Maneuver")) {
+ name = text.replace("Defense Maneuver", "DEF Maneuver");
+ } else if ((text !== "") && text.includes("Computer Programming")) {
+ name = text.replace("Programming", "Prog.");
+ name = name.slice(0, -3);
+ } else if (skillObject.display !== "") {
+ name = skillObject.display;
+ }
+ }
+
+ // Import the skill
+ ID = String(generalSkillIndex+1).padStart(2,'0');
+ theSkill["skillName"+ID] = name.trim();
+ theSkill["skillType"+ID] = type;
+ theSkill["skillCP"+ID] = cost;
+ if (type === "everyman") {
+ theSkill["skillRollChance"+ID] = "8";
+ }
+
+ setAttrs(object.id, theSkill);
+
+ generalSkillIndex++;
+
+ return generalSkillIndex;
+ }
+
+
+ var removeEnhancerText = function(name) {
+ // Here a .replace() function doesn't work because the text to be removed is
+ // of the form "xx- (x Active Points)."
+ let startPosition;
+ let endPosition;
+ let subStringA;
+ let subStringB;
+
+ if (name.includes("Active Points")) {
+ // Split the name into two parts on each side of Active Points.
+ startPosition = name.indexOf("Active Points");
+ endPosition = startPosition+13;
+ subStringA = name.slice(0, startPosition);
+ subStringB = name.slice(endPosition);
+
+ // Remove text "XY- (Z " at the end of subStringA.
+ endPosition = subStringA.lastIndexOf('(') - 1;
+ subStringA = subStringA.slice(0,endPosition);
+
+ // Remove the first ')' in subStringB.
+ startPosition = subStringB.indexOf(')') + 1;
+ subStringB = subStringB.slice(startPosition);
+
+ // Join the subStrings to make a new name.
+ name = subStringA + subStringB;
+ }
+
+ return name;
+ }
+
+
+ var findEndurance = function(testObject) {
+ // Determine endurance type, advantages, and limitations.
+ // Remove advantage or limitation from tempString so that they aren't counted twice.
+
+ // testObject needs three items:
+ // theText, theEndurance, powerReducedEND
+
+ let tempString = testObject.theText;
+ let endString = testObject.theEndurance;
+
+ if ( ((tempString.includes("Costs Endurance (-1/4)")) || (tempString.includes("Costs Half Endurance"))) && (endString.includes("["))) {
+ testObject.powerReducedEND = "costsENDhalf";
+ tempString = tempString.replace("Costs Endurance (-1/4)", "");
+ } else if ((tempString.includes("Costs Endurance (-1/2)")) && (endString.includes("["))) {
+ testObject.powerReducedEND = "costsENDfull";
+ tempString = tempString.replace("Costs Endurance (-1/2)", "");
+ } else if (endString.includes("[")) {
+ testObject.powerReducedEND = "noEND";
+ } else if (tempString.includes("Costs Endurance (-1/4)")) {
+ testObject.powerReducedEND = "costsENDhalf";
+ tempString = tempString.replace("Costs Endurance (-1/4)", "");
+ } else if (tempString.includes("Costs Endurance (-1/2)")) {
+ testObject.powerReducedEND = "costsENDfull";
+ tempString = tempString.replace("Costs Endurance (-1/2)", "");
+ } else if ((tempString.includes("Reduced Endurance (1/2 END; +1/2)")) && (tempString.includes("Autofire"))) {
+ testObject.powerReducedEND = "reducedENDAF";
+ tempString = tempString.replace("Reduced Endurance (1/2 END; +1/2)", "");
+ } else if (tempString.includes("Reduced Endurance (0 END; +1/2)")) {
+ testObject.powerReducedEND = "zeroEND";
+ tempString = tempString.replace("Reduced Endurance (0 END; +1/2)", "");
+ } else if (tempString.includes("Reduced Endurance (0 END; +1)")) {
+ testObject.powerReducedEND = "zeroENDAF";
+ tempString = tempString.replace("Reduced Endurance (0 END; +1)", "");
+ } else if (tempString.includes("Reduced Endurance (1/2 END; +1/4)")) {
+ testObject.powerReducedEND = "reducedEND";
+ tempString = tempString.replace("Reduced Endurance (1/2 END; +1/4)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x2 END; -1/2)")) {
+ testObject.powerReducedEND = "increasedENDx2";
+ tempString = tempString.replace("Increased Endurance Cost (x2 END; -1/2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x3 END; -1)")) {
+ testObject.powerReducedEND = "increasedENDx3";
+ tempString = tempString.replace("Increased Endurance Cost (x3 END; -1)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x4 END; -1 1/2)")) {
+ testObject.powerReducedEND = "increasedENDx4";
+ tempString = tempString.replace("Increased Endurance Cost (x4 END; -1 1/2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x5 END; -2)")) {
+ testObject.powerReducedEND = "increasedENDx5";
+ tempString = tempString.replace("Increased Endurance Cost (x5 END; -2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x6 END; -2 1/2)")) {
+ testObject.powerReducedEND = "increasedENDx6";
+ tempString = tempString.replace("Increased Endurance Cost (x6 END; -2 1/2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x7 END; -3)")) {
+ testObject.powerReducedEND = "increasedENDx7";
+ tempString = tempString.replace("Increased Endurance Cost (x7 END; -3)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x8 END; -3 1/2)")) {
+ testObject.powerReducedEND = "increasedENDx8";
+ tempString = tempString.replace("Increased Endurance Cost (x8 END; -3 1/2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x9 END; -3 1/2)")) {
+ testObject.powerReducedEND3 = "increasedENDx9";
+ tempString = tempString.replace("Increased Endurance Cost (x9 END; -3 1/2)", "");
+ } else if (tempString.includes("Increased Endurance Cost (x10 END; -4)")) {
+ testObject.powerReducedEND = "increasedENDx10";
+ tempString = tempString.replace("Increased Endurance Cost (x10 END; -4)", "");
+ } else if (endString === "") {
+ testObject.powerReducedEND = "noEND";
+ } else if (endString === 0) {
+ testObject.powerReducedEND = "noEND";
+ } else {
+ testObject.powerReducedEND = "standardEND";
+ }
+
+ testObject.theText = tempString;
+
+ return testObject;
+ }
+
+
+ var findPropulsionEndurance = function(testObject) {
+ // This is a simplified version of findEndurance used only for vehicle propulsion systems.
+ let tempString = testObject.theText;
+ let endString = testObject.theEndurance;
+
+ if (tempString.includes("Costs Half Endurance (-1/4)")) {
+ testObject.powerReducedEND = "costsENDhalf";
+ tempString = tempString.replace("Costs Half Endurance (-1/4)", "");
+ } else if (tempString.includes("Costs Endurance (-1/2)")) {
+ testObject.powerReducedEND = "costsENDfull";
+ tempString = tempString.replace("Costs Endurance (-1/2)", "");
+ } else {
+ testObject.powerReducedEND = "noEND";
+ }
+
+ testObject.theText = tempString;
+
+ return testObject;
+ }
+
+
+ var getPropulsionDetails = function(testObject) {
+ const theEffect = testObject.theEffect;
+ const tempString = testObject.theText;
+ let theMode = "ground";
+ let theMove = 0;
+ let theAdds = 0;
+ let ftlVelocity = 10;
+ let isInstant = false;
+ let slotType = "single";
+
+ if (theEffect === "Running") {
+ // Might not be called
+ theMove = getVehicleMovement(tempString, "Running", script_name)[0];
+ theMode = "ground";
+ } else if (theEffect === "Ground Movement") {
+ theMove = getVehicleMovement(tempString, "Ground Movement", script_name)[0];
+ theMode = "ground";
+ } else if (theEffect === "Leaping") {
+ theMove = getVehicleMovement(tempString, "Leaping", script_name)[0];
+ theMode = "leaping";
+ } else if (theEffect === "Swimming") {
+ theMove = getVehicleMovement(tempString, "Swimming", script_name)[0];
+ theMode = "swimming";
+ } else if (theEffect === "Flight") {
+ theMove = getVehicleMovement(tempString, "Flight", script_name)[0];
+ theMode = "flight";
+ } else if (theEffect === "Swinging") {
+ theMove = getVehicleMovement(tempString, "Swinging", script_name)[0];
+ theMode = "swinging";
+ } else if (theEffect === "FTL Travel") {
+ isInstant = (tempString.includes("Instant Lightspeed")) ? true : false;
+ ftlVelocity = Math.max(10, Math.min(40, testObject.theBase - (isInstant ? 10 : 0)));
+ theMode = "ftl";
+ } else if (theEffect === "Teleportation") {
+ theMove = getVehicleMovement(tempString, "Teleportation", script_name)[0];
+ theMode = "teleport";
+ } else if (theEffect === "Extra-Dimensional MV") {
+ theAdds = Math.max(0, testObject.theBase-20);
+ theMode = "extra";
+ } else if (theEffect === "Clinging") {
+ theMove = 10;
+ theAdds = Math.max(0, testObject.theBase-10);
+ theMode = "clinging";
+ }
+
+ return [theMode, theMove, theAdds, ftlVelocity, isInstant];
+ }
+
+
+ var findNoncombatMultiple = function(inputString) {
+ // Reported as a power of 2.
+ let exponent = 1;
+
+ if (inputString.includes("no Noncombat movement")) {
+ exponent = 0;
+ } else if (inputString.includes("x4 Noncombat")) {
+ exponent = 2;
+ } else if (inputString.includes("x8 Noncombat")) {
+ exponent = 3;
+ } else if (inputString.includes("x16 Noncombat")) {
+ exponent = 4;
+ } else if (inputString.includes("x32 Noncombat")) {
+ exponent = 5;
+ }
+
+ return exponent;
+ }
+
+
+ var findEffectType = function(tempString, script_name) {
+ // Search for and return effect keywords.
+
+ const talentArray = ["absolute range sense", "absolute time sense", "ambidexterity", "animal friendship", "bump of direction", "combat luck", "combat sense", "danger sense", "deadly blow", "double jointed", "eidetic memory", "environmental movement", "lightning calculator", "lightning reflexes", "lightsleap", "off-hand defense", "perfect pitch", "resistance", "simulate death", "speed reading", "striking appearance", "universal translator", "weaponmaster"];
+
+ const skillArray = ["acrobatics", "acting", "analyze", "animal handler", "breakfall", "bribery", "bugging", "bureaucratics", "charm", "climbing", "combat driving", "combat piloting", "combat skill levels", "computer programming", "concealment", "contortionist", "conversation", "cramming", "criminology", "cryptology", "deduction", "defense maneuver", "demolitions", "disguise", "electronics", "fast draw", "forensic medicine", "forgery", "gambling", "high society", "interrogation", "inventor", "language", "lipreading", "lockpicking", "mechanics", "mimicry", "navigation", "oratory", "paramedics", "persuasion", "riding", "security systems", "shadowing", "sleight of hand", "stealth", "streetwise", "survival", "systems operations", "tactics", "teamwork", "tracking", "trading", "two-weapon fighting", "ventriloquism", "weapon familiarity", "weaponsmith", "overall"];
+
+ const senseModifierArray = ["analyze", "concealed", "adjacent", "dimensional", "discriminatory", "increased arc", "microscopic", "penetrative", "range", "rapid", "telescopic", "tracking", "transmit"];
+
+ if ( (typeof tempString != "undefined") && (tempString != "") ) {
+ let lowerCaseString = tempString.toLowerCase();
+
+ if (lowerCaseString.includes("applied to str")) {
+ return "Base STR Mod";
+ } else if (lowerCaseString.includes("range based on str") && lowerCaseString.includes("of hka")) {
+ return "HKA Mod";
+ } else if (lowerCaseString.includes("applied to running")) {
+ return "Base Running Mod";
+ } else if (lowerCaseString.includes("applied to leaping")) {
+ return "Base Leaping Mod";
+ } else if (lowerCaseString.includes("applied to swimming")) {
+ return "Base Swimming Mod";
+ } else if (lowerCaseString.includes("applied to pd")) {
+ return "Base PD Mod";
+ } else if (lowerCaseString.includes("applied to ed")) {
+ return "Base ED Mod";
+ } else if (tempString.includes("Aid")) {
+ return "Aid";
+ } else if ( (tempString.includes("Dispel")) && !(lowerCaseString.includes("difficult to dispel")) ) {
+ return "Dispel";
+ } else if (tempString.includes("Killing Attack - Hand-To-Hand")) {
+ return "HTH Killing Attack";
+ } else if (tempString.includes("HKA")) {
+ return "HTH Killing Attack";
+ } else if (tempString.includes("Killing Attack - Ranged")) {
+ return "Ranged Killing Attack";
+ } else if (tempString.includes("RKA")) {
+ return "Ranged Killing Attack";
+ } else if (tempString.includes("Absorption")) {
+ return "Absorption";
+ } else if (tempString.includes("Automaton")) {
+ return "Automaton";
+ } else if (tempString.includes("Barrier")) {
+ return "Barrier";
+ } else if (tempString.includes("Mental Blast")) {
+ return "Mental Blast";
+ } else if (tempString.includes("Blast")) {
+ return "Blast";
+ } else if (tempString.includes("Change Environment")) {
+ return "Change Environment";
+ } else if (tempString.includes("Clairsentience")) {
+ return "Clairsentience";
+ } else if (tempString.includes("Clinging")) {
+ return "Clinging";
+ } else if (tempString.includes("Damage Negation")) {
+ return "Damage Negation";
+ } else if (tempString.includes("Damage Reduction")) {
+ return "Damage Reduction";
+ } else if (tempString.includes("Darkness")) {
+ return "Darkness";
+ } else if (tempString.includes("Deflection")) {
+ return "Deflection";
+ } else if (tempString.includes("Density Increase")) {
+ return "Density Increase";
+ } else if (tempString.includes("Desolidification")) {
+ return "Desolidification";
+ } else if (tempString.includes("Does Not Bleed")) {
+ return "Does Not Bleed";
+ } else if (tempString.includes("Drain")) {
+ return "Drain";
+ } else if (tempString.includes("Duplication")) {
+ return "Duplication";
+ } else if (tempString.includes("Enhanced Senses")) {
+ return "Enhanced Senses";
+ } else if (tempString.includes("Entangle")) {
+ return "Entangle";
+ } else if (tempString.includes("Endurance Reserve")) {
+ return "Endurance Reserve";
+ } else if (tempString.includes("Extra Limb")) {
+ return "Extra Limb";
+ } else if (tempString.includes("Extra-Dimensional Movement")) {
+ return "Extra-Dimensional MV";
+ } else if (tempString.includes("Faster-Than-Light Travel")) {
+ return "FTL Travel";
+ } else if (tempString.includes("Flash Defense") && !(tempString.includes("PD")) && !(tempString.includes("ED"))) {
+ return "Flash Defense";
+ } else if (tempString.includes("Flash") && !(tempString.includes("Defense"))) {
+ return "Flash";
+ } else if (tempString.includes("Flight")) {
+ return "Flight";
+ } else if (tempString.includes("Ground Movement")) {
+ return "Ground Movement";
+ } else if (tempString.includes("Growth")) {
+ return "Growth";
+ } else if (tempString.includes("Hand-To-Hand Attack")) {
+ return "HTH Attack";
+ } else if (tempString.includes("Healing")) {
+ return "Healing";
+ } else if (tempString.includes("Invisibility")) {
+ return "Invisibility";
+ } else if ( (tempString.includes("Images")) && !(lowerCaseString.includes("only to perceive images")) ) {
+ return "Images";
+ } else if (tempString.includes("Knockback Resistance")) {
+ return "Knockback Resistance";
+ } else if (tempString.includes("Leaping")) {
+ return "Leaping";
+ } else if (tempString.includes("Life Support")) {
+ return "Life Support";
+ } else if (tempString.includes("Luck")) {
+ return "Luck";
+ } else if (tempString.includes("Transform")) {
+ return "Transform";
+ } else if (tempString.includes("Mental Defense") && !(tempString.includes("PD")) && !(tempString.includes("ED"))) {
+ return "Mental Defense";
+ } else if (tempString.includes("Mental Illusions")) {
+ return "Mental Illusions";
+ } else if (tempString.includes("Mind Control")) {
+ return "Mind Control";
+ } else if (tempString.includes("Mind Link")) {
+ return "Mind Link";
+ } else if (tempString.includes("Mind Scan")) {
+ return "Mind Scan";
+ } else if (tempString.includes("Multiform")) {
+ return "Multiform";
+ } else if (tempString.includes("No Hit Locations")) {
+ return "No Hit Locations";
+ } else if (tempString.includes("Possession")) {
+ return "Possession";
+ } else if (tempString.includes("Power Defense") && !(tempString.includes("PD")) && !(tempString.includes("ED"))) {
+ return "Power Defense";
+ } else if (tempString.includes("Reach")) {
+ return "Reach";
+ } else if (tempString.includes("Reflection")) {
+ return "Reflection";
+ } else if (tempString.includes("Regeneration")) {
+ return "Regeneration";
+ } else if (tempString.includes("Resistant")) {
+ return "Resistant Protection";
+ } else if (tempString.includes("Running")) {
+ return "Running";
+ } else if (tempString.includes("Shape Shift")) {
+ return "Shape Shift";
+ } else if (tempString.includes("Shrinking")) {
+ return "Shrinking";
+ } else if (tempString.includes("Stretching")) {
+ return "Stretching";
+ } else if (tempString.includes("Summon")) {
+ return "Summon";
+ } else if (tempString.includes("Swimming")) {
+ return "Swimming";
+ } else if (tempString.includes("Swinging")) {
+ return "Swinging";
+ } else if (tempString.includes("Takes No STUN")) {
+ return "Takes No STUN";
+ } else if (tempString.includes("Telekinesis")) {
+ return "Telekinesis";
+ } else if (tempString.includes("Telepathy")) {
+ return "Telepathy";
+ } else if (tempString.includes("Teleportation")) {
+ return "Teleportation";
+ } else if (tempString.includes("Tunneling")) {
+ return "Tunneling";
+ } else if (tempString.includes("Active Sonar")) {
+ return "Active Sonar";
+ } else if (tempString.includes("Detect")) {
+ return "Detect";
+ } else if (tempString.includes("Enhanced Perception")) {
+ return "Enhanced PER";
+ } else if ( (tempString.includes("High Range Radio")) || (tempString.includes("HRRP")) ) {
+ return "HR Radio PER";
+ } else if (tempString.includes("Infrared Perception")) {
+ return "IR Perception";
+ } else if (tempString.includes("IR Perception")) {
+ return "IR Perception";
+ } else if (tempString.includes("Mental Awareness")) {
+ return "Mental Awareness";
+ } else if (tempString.includes("Nightvision")) {
+ return "Nightvision";
+ } else if (tempString.includes("Radar")) {
+ return "Radar";
+ } else if (tempString.includes("Radio Perception/Transmission")) {
+ return "Radio PER/Trans";
+ } else if (tempString.includes("Radio Perception")) {
+ return "Radio PER";
+ } else if (tempString.includes("Spatial Awareness")) {
+ return "Spatial Awareness";
+ } else if (tempString.includes("Tracking")) {
+ return "Enhanced Sense";
+ } else if (tempString.includes("Ultrasonic Perception")) {
+ return "Ultrasonic PER";
+ } else if (tempString.includes("Ultraviolet Perception")) {
+ return "UV Perception";
+ } else if (skillArray.some(v => lowerCaseString.includes(v))) {
+ return "Skill";
+ } else if (talentArray.some(v => lowerCaseString.includes(v))) {
+ return "Talent";
+ } else if (senseModifierArray.some(v => lowerCaseString.includes(v))) {
+ return "Sense Modifier";
+ } else if (tempString.includes("Ego Attack")) {
+ return "Ego Attack";
+ } else if (tempString.includes("SPD")) {
+ return "Enhanced SPD";
+ } else if (tempString.includes("PER")) {
+ return "Enhanced PER";
+ } else if (tempString.includes("STR")) {
+ return "Enhanced STR";
+ } else if (tempString.includes("CON")) {
+ return "Enhanced CON";
+ } else if (tempString.includes("INT")) {
+ return "Enhanced INT";
+ } else if (tempString.includes("EGO")) {
+ return "Enhanced EGO";
+ } else if (tempString.includes("PRE")) {
+ return "Enhanced PRE";
+ } else if (tempString.includes("OCV")) {
+ return "Enhanced OCV";
+ } else if (tempString.includes("OMCV")) {
+ return "Enhanced OMCV";
+ } else if (tempString.includes("DMCV")) {
+ return "Enhanced DMCV";
+ } else if (tempString.includes("PD")) {
+ return "Enhanced PD";
+ } else if (tempString.includes("ED")) {
+ return "Enhanced ED";
+ } else if (tempString.includes("BODY")) {
+ return "Enhanced BODY";
+ } else if (tempString.includes("STUN")) {
+ return "Enhanced STUN";
+ } else if (tempString.includes("REC")) {
+ return "Enhanced REC";
+ } else if (tempString.includes("DEX")) {
+ return "Enhanced DEX";
+ } else if (lowerCaseString.includes("sight") || lowerCaseString.includes("hearing") || lowerCaseString.includes("smell") || lowerCaseString.includes("taste") || lowerCaseString.includes("touch") || lowerCaseString.includes("sense")) {
+ return "Enhanced PER";
+ } else if (lowerCaseString.includes("eating") || lowerCaseString.includes("immunity") || lowerCaseString.includes("longevity") || lowerCaseString.includes("safe in") || lowerCaseString.includes("breathing") || lowerCaseString.includes("sleeping")) {
+ return "Life Support";
+ } else if ( (lowerCaseString.includes("advantage")) || (lowerCaseString.includes("area of effect")) ) {
+ return "Naked Advantage";
+ } else if (lowerCaseString.includes("worth of") || lowerCaseString.includes("powers") || lowerCaseString.includes("spells") || lowerCaseString.includes("abilities")) {
+ return "To Be Determined";
+ } else if (tempString.includes("DCV")) {
+ return "Enhanced DCV";
+ } else if (tempString.includes("END")) {
+ return "Enhanced END";
+ } else {
+ return "Unknown Effect";
+ }
+ } else {
+ return "Unknown Effect";
+ }
+ }
+
+
+ var fixKnownSpellingErrors = function(theString, script_name) {
+ // Here we try to catch and correct important typos found in tested commercial sources.
+ // Add to typoList as needed.
+
+ const typoList = [
+ ["leaping", "Leaping"],
+ ["Runniing", "Running"],
+ ["Restistant", "Resistant"]
+ ];
+
+ const iMax = 1;
+ let found = false;
+ let i = 0;
+
+ if ( (typeof theString != "undefined") && (theString != "") ) {
+ while ( (i < iMax) && !found ) {
+ if (theString.includes(typoList[i][0])) {
+ theString = theString.replace(typoList[i][0], typoList[i][1]);
+ found = true;
+ }
+
+ i++;
+ }
+ }
+
+ return theString;
+ }
+
+
+ var fixKnownDesignerErrors = function(inputString, sourceVersion, scrip_name) {
+ let limitation = 0;
+ let startPosition = 0;
+ let endPosition = 0;
+ let tempString = "";
+ let replaceString = "";
+ let outputString = inputString;
+
+ let advantage = 0;
+ let multiple = "0";
+
+ if (sourceVersion === designerVersion) {
+ if (inputString.includes("Cumulative")) {
+ startPosition = inputString.indexOf("Cumulative");
+ endPosition = inputString.indexOf(')', startPosition);
+ replaceString = inputString.slice(startPosition,endPosition);
+
+ tempString = replaceString.slice(-7);
+ tempString = tempString.replace('*','');
+ advantage = findAdvantages(tempString+')');
+
+ if (advantage <= 1.75) {
+ multiple = (2 ** ((advantage-0.25)/0.25)).toString();
+ } else {
+ multiple = (125 * 2 ** ((advantage - 1.75)/0.25 - 1)).toString();
+ }
+
+ tempString = "Cumulative (" + multiple + "x; " + tempString;
+ outputString = inputString.replace(replaceString, tempString);
+ }
+ }
+
+ return outputString;
+ }
+
+
+ var isAttack = function (effect) {
+ // For setting the attack state or to identify equipment powers.
+ const attackSet = new Set(["Aid", "Blast", "Dispel", "Drain", "Entangle", "Flash", "Healing", "HTH Attack", "HTH Killing Attack", "Mental Blast", "Mental Illusions", "Mind Control", "Mind Link", "Mind Scan", "Ranged Killing Attack", "Telekinesis", "Telepathy", "Transform"]);
+
+ return attackSet.has(effect) ? true : false;
+ }
+
+
+ var isKillingAttack = function (effect) {
+ // For setting the attack state or to identify equipment powers.
+ const attackSet = new Set(["HTH Killing Attack", "Ranged Killing Attack"]);
+
+ return attackSet.has(effect) ? true : false;
+ }
+
+
+ var isMentalAttack = function (effect) {
+ // For setting the attack state or to identify equipment powers.
+ const attackSet = new Set(["Mental Blast", "Mental Illusions", "Mind Control", "Mind Link", "Mind Scan", "Telepathy"]);
+
+ return attackSet.has(effect) ? true : false;
+ }
+
+
+ var isPowerAttack = function (effect) {
+ // For setting the attack state or to identify equipment powers.
+ const attackSet = new Set(["Aid", "Dispel", "Drain", "Entangle", "Flash", "Healing", "Transform"]);
+
+ return attackSet.has(effect) ? true : false;
+ }
+
+
+ var isDefense = function (effect) {
+ // For identifying equipment powers.
+ const defenseSet = new Set(["Resistant Protection", "Resistant", "Base PD Mod", "Base ED Mod"]);
+
+ return defenseSet.has(effect) ? true : false;
+ }
+
+
+ var getResistantPD = function (inputString, script_name) {
+ // For Armor slot 4.
+ let protection = 0;
+ let endPosition = 0;
+ let tempString = inputString;
+
+ if (inputString.includes("PD/")) {
+ endPosition = inputString.indexOf("PD/");
+ tempString = inputString.slice(endPosition-Math.min(4,endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ } else if (inputString.includes("PD")) {
+ endPosition = inputString.indexOf("PD");
+ tempString = inputString.slice(endPosition-Math.min(4,endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ } else {
+ protection = 0;
+ }
+
+ return protection;
+ }
+
+
+ var getResistantED = function (inputString, script_name) {
+ // For Armor slot 4.
+ let protection = 0;
+ let startPosition = 0;
+ let endPosition = 0;
+ let tempString = inputString;
+
+ if (inputString.includes("PD/ED")) {
+ endPosition = inputString.indexOf("PD/ED");
+ tempString = inputString.slice(endPosition-Math.min(3, endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ } else if (inputString.includes("PD/")) {
+ if (inputString.includes("ED")) {
+ endPosition = inputString.indexOf("ED");
+ tempString = inputString.slice(endPosition-Math.min(4,endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ } else if (inputString.includes("ED/")) {
+ endPosition = inputString.indexOf("ED/");
+ tempString = inputString.slice(endPosition-Math.min(4,endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ }
+ } else if (inputString.includes("ED")) {
+ endPosition = inputString.indexOf("ED");
+ tempString = inputString.slice(endPosition-Math.min(4,endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ protection = (tempString !== "") ? Number(tempString) : 0;
+ protection = isNaN(protection) ? 0 : protection;
+ } else {
+ protection = 0;
+ }
+
+ return protection;
+ }
+
+
+ var getPowerDamageType = function (effect) {
+ // For setting the attack state.
+ const killingSet = new Set(["HTH Killing Attack", "Ranged Killing Attack"]);
+ const normalSet = new Set(["Blast", "HTH Attack", "Telekinesis"]);
+ const mentalSet = new Set(["Mental Blast", "Mental Illusions", "Mind Control", "Mind Link", "Mind Scan", "Telepathy"]);
+ let damageType = null;
+
+ if (killingSet.has(effect)) {
+ damageType = "killing";
+ } else if (mentalSet.has(effect)) {
+ damageType = "mental";
+ } else if (normalSet.has(effect)) {
+ damageType = "normal";
+ } else {
+ damageType = "power";
+ }
+
+ return damageType;
+ }
+
+
+ var getPowerBaseCost = function(character, base, effect, text, bonus, option, script_name) {
+ // For ordinary powers, this function simply returns the imported base cost.
+ // For stat modification powers, this function assigns a base cost determined from the characteristic
+ // and also awards those points as bonus points so that the character is not charged twice.
+ // For 'to be determined' powers the function attempts to parse the base cost from the power's text.
+
+ const characterPD = parseInt(character.pd)||2;
+ const characterED = parseInt(character.ed)||2;
+
+ let powerBaseCost = parseInt(base);
+ let bonusCP = parseInt(bonus);
+ let slicePosition = 0;
+ let tempValue = 0;
+ let tempString = "";
+
+ if (effect === "Base STR Mod") {
+ powerBaseCost = parseInt(character.strength);
+ bonusCP = bonusCP + powerBaseCost;
+ } else if (effect === "Base Running Mod") {
+ powerBaseCost = parseInt(character.running);
+ bonusCP = bonusCP + powerBaseCost;
+ } else if (effect === "Base Leaping Mod") {
+ powerBaseCost = parseInt(Math.round(character.leaping/2));
+ bonusCP = bonusCP + powerBaseCost;
+ } else if (effect === "Base Swimming Mod") {
+ powerBaseCost = parseInt(Math.round(character.swimming/2));
+ bonusCP = bonusCP + powerBaseCost;
+ } else if (effect === "Base Defense Mod") {
+ if (option === "on") {
+ // Determine pd cost. If character has takes No STUN triple cost over the base 2.
+ if ((option === "on") && (characterPD > 1)) {
+ powerBaseCost = 1 + 3*(characterPD - 1);
+ bonusCP += powerBaseCost;
+ } else {
+ powerBaseCost = characterPD;
+ bonusCP += powerBaseCost;
+ }
+
+ // Add ed cost. If character has takes No STUN triple cost over the base 2.
+ if ((option === "on") && (characterED > 1)) {
+ powerBaseCost += 1 + 3*(characterED - 1);
+ bonusCP += powerBaseCost;
+ } else {
+ powerBaseCost += characterED;
+ bonusCP += powerBaseCost;
+ }
+ } else {
+ powerBaseCost = characterPD + characterED;
+ bonusCP += powerBaseCost;
+ }
+ } else if (effect === "Base PD Mod") {
+ // If character has takes No STUN triple cost over the base 2.
+ if ((option === "on") && (characterPD > 1)) {
+ powerBaseCost = 1 + 3*(characterPD - 1);
+ bonusCP += powerBaseCost;
+ } else {
+ powerBaseCost = characterPD;
+ bonusCP += powerBaseCost;
+ }
+ } else if (effect === "Base ED Mod") {
+ // If character has takes No STUN triple cost over the base 2.
+ if ((option === "on") && (characterED > 1)) {
+ powerBaseCost = 3*(characterED - 1);
+ bonusCP += powerBaseCost;
+ } else {
+ powerBaseCost = characterED;
+ bonusCP += powerBaseCost;
+ }
+ } else if (effect === "Endurance Reserve") {
+ // Special cost due to separate END and REC purchases.
+ slicePosition = text.indexOf("END");
+ tempString = text.slice(Math.max(0, slicePosition-7), slicePosition);
+
+ tempValue = tempString.replace(/[^0-9\-]/g, '');
+ if (tempValue === "") {
+ tempValue = 0;
+ }
+ powerBaseCost = Math.round(tempValue/4);
+
+ slicePosition = text.indexOf("REC");
+ tempString = text.slice(Math.max(0, slicePosition-6), slicePosition);
+
+ tempValue = tempString.replace(/[^0-9\-]/g, '');
+ if (tempValue === "") {
+ tempValue = 0;
+ }
+ powerBaseCost += Math.round(2 * tempValue/3);
+ } else if (effect === "To Be Determined") {
+ // Workaround for when sometimes points reported as base are incorrect.
+ if ((text.match(/^\d+|\d+\b|\d+(?=\w)/g) !== null) && (text.match(/^\d+|\d+\b|\d+(?=\w)/g) !== "")) {
+ powerBaseCost = text.match(/^\d+|\d+\b|\d+(?=\w)/g)[0];
+ } else {
+ // If the array came up empty, default to base cost.
+ powerBaseCost = parseInt(base);
+ }
+ }
+
+ if (bonus != bonusCP) {
+ if(verbose) {
+ sendChat(script_name, JSON.stringify(bonusCP - bonus) + " CP added to Bonus.");
+ }
+ }
+
+ return [powerBaseCost, bonusCP];
+ }
+
+
+ var getReserveParameters = function(inputString) {
+ let endurance = 0;
+ let recovery = 0;
+ let startPosition = 0;
+ let endPosition = 0;
+ let tempString = inputString;
+
+ if (inputString.includes("END,")) {
+ endPosition = inputString.indexOf("END,");
+ tempString = inputString.slice(endPosition-Math.min(4, endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ endurance = (tempString !== "") ? Number(tempString) : 0;
+ endurance = isNaN(endurance) ? 0 : endurance;
+ }
+
+ if (inputString.includes("REC)")) {
+ endPosition = inputString.indexOf("REC)");
+ tempString = inputString.slice(endPosition-Math.min(4, endPosition), endPosition);
+ tempString = tempString.replace(/[^0-9]/g, "");
+ recovery = (tempString !== "") ? Number(tempString) : 0;
+ recovery = isNaN(recovery) ? 0 : recovery;
+ }
+
+ return [endurance, recovery];
+ }
+
+
+ var findAdvantages = function(tempString) {
+ // Determine total limitations. This will take some doing.
+
+ let advantages = 0;
+
+ // Find half-integers. Replace larger ones first.
+ advantages = advantages + ((tempString.match(/\+6 1\/2\)/g) || []).length)*6.5;
+ tempString = tempString.replace("+6 1/2)","");
+ advantages = advantages + ((tempString.match(/\+5 1\/2\)/g) || []).length)*5.5;
+ tempString = tempString.replace("+5 1/2)","");
+ advantages = advantages + ((tempString.match(/\+4 1\/2\)/g) || []).length)*4.5;
+ tempString = tempString.replace("+4 1/2)","");
+ advantages = advantages + ((tempString.match(/\+3 1\/2\)/g) || []).length)*3.5;
+ tempString = tempString.replace("+3 1/2)","");
+ advantages = advantages + ((tempString.match(/\+2 1\/2\)/g) || []).length)*2.5;
+ tempString = tempString.replace("+2 1/2)","");
+ advantages = advantages + ((tempString.match(/\+1 1\/2\)/g) || []).length)*1.5;
+ tempString = tempString.replace("+1 1/2)","");
+ advantages = advantages + ((tempString.match(/\+1\/2\)/g) || []).length)*0.5;
+ tempString = tempString.replace("+1/2)","");
+
+ // Find three-quarter integers. Replace larger ones first.
+ advantages = advantages + ((tempString.match(/\+6 3\/4\)/g) || []).length)*6.75;
+ tempString = tempString.replace("+6 3/4)","");
+ advantages = advantages + ((tempString.match(/\+5 3\/4\)/g) || []).length)*5.75;
+ tempString = tempString.replace("+5 3/4)","");
+ advantages = advantages + ((tempString.match(/\+4 3\/4\)/g) || []).length)*4.75;
+ tempString = tempString.replace("+4 3/4)","");
+ advantages = advantages + ((tempString.match(/\+3 3\/4\)/g) || []).length)*3.75;
+ tempString = tempString.replace("+3 3/4)","");
+ advantages = advantages + ((tempString.match(/\+2 3\/4\)/g) || []).length)*2.75;
+ tempString = tempString.replace("+2 3/4)","");
+ advantages = advantages + ((tempString.match(/\+1 3\/4\)/g) || []).length)*1.75;
+ tempString = tempString.replace("+1 3/4)","");
+ advantages = advantages + ((tempString.match(/\+3\/4\)/g) || []).length)*0.75;
+ tempString = tempString.replace("+3/4)","");
+
+ // Find quarter integers. Replace larger ones first.
+ advantages = advantages + ((tempString.match(/\+6 1\/4\)/g) || []).length)*6.25;
+ tempString = tempString.replace("+6 1/4)","");
+ advantages = advantages + ((tempString.match(/\+5 1\/4\)/g) || []).length)*5.25;
+ tempString = tempString.replace("+5 1/4)","");
+ advantages = advantages + ((tempString.match(/\+4 1\/4\)/g) || []).length)*4.25;
+ tempString = tempString.replace("+4 1/4)","");
+ advantages = advantages + ((tempString.match(/\+3 1\/4\)/g) || []).length)*3.25;
+ tempString = tempString.replace("+3 1/4)","");
+ advantages = advantages + ((tempString.match(/\+2 1\/4\)/g) || []).length)*2.25;
+ tempString = tempString.replace("+2 1/4)","");
+ advantages = advantages + ((tempString.match(/\+1 1\/4\)/g) || []).length)*1.25;
+ tempString = tempString.replace("+1 1/4)","");
+ advantages = advantages + ((tempString.match(/\+1\/4\)/g) || []).length)*0.25;
+ tempString = tempString.replace("+1/4)","");
+
+ // Find whole integers. Replace larger ones first.
+ advantages = advantages + ((tempString.match(/\+7\)/g) || []).length)*7;
+ tempString = tempString.replace("+7)","");
+ advantages = advantages + ((tempString.match(/\+6\)/g) || []).length)*6;
+ tempString = tempString.replace("+6)","");
+ advantages = advantages + ((tempString.match(/\+5\)/g) || []).length)*5;
+ tempString = tempString.replace("+5)","");
+ advantages = advantages + ((tempString.match(/\+4\)/g) || []).length)*4;
+ tempString = tempString.replace("+4)","");
+ advantages = advantages + ((tempString.match(/\+3\)/g) || []).length)*3;
+ tempString = tempString.replace("+3)","");
+ advantages = advantages + ((tempString.match(/\+2\)/g) || []).length)*2;
+ tempString = tempString.replace("+2)","");
+ advantages = advantages + ((tempString.match(/\+1\)/g) || []).length)*1;
+
+ return advantages;
+ }
+
+ var findLimitations = function(tempString) {
+ // Determine total limitations. This will take some doing.
+
+ let limitations = 0;
+
+ // Find half-integers. Replace larger ones first.
+ limitations = limitations + ((tempString.match(/-5 1\/2\)/g) || []).length)*5.5;
+ tempString = tempString.replace("-5 1/2)","");
+ limitations = limitations + ((tempString.match(/-4 1\/2\)/g) || []).length)*4.5;
+ tempString = tempString.replace("-4 1/2)","");
+ limitations = limitations + ((tempString.match(/-3 1\/2\)/g) || []).length)*3.5;
+ tempString = tempString.replace("-3 1/2)","");
+ limitations = limitations + ((tempString.match(/-2 1\/2\)/g) || []).length)*2.5;
+ tempString = tempString.replace("-2 1/2)","");
+ limitations = limitations + ((tempString.match(/-1 1\/2\)/g) || []).length)*1.5;
+ tempString = tempString.replace("-1 1/2)","");
+ limitations = limitations + ((tempString.match(/-1\/2\)/g) || []).length)*0.5;
+ tempString = tempString.replace("-1/2)","");
+
+ // Find three-quarter integers. Replace larger ones first.
+ limitations = limitations + ((tempString.match(/-5 3\/4\)/g) || []).length)*5.75;
+ tempString = tempString.replace("-5 3/4)","");
+ limitations = limitations + ((tempString.match(/-4 3\/4\)/g) || []).length)*4.75;
+ tempString = tempString.replace("-4 3/4)","");
+ limitations = limitations + ((tempString.match(/-3 3\/4\)/g) || []).length)*3.75;
+ tempString = tempString.replace("-3 3/4)","");
+ limitations = limitations + ((tempString.match(/-2 3\/4\)/g) || []).length)*2.75;
+ tempString = tempString.replace("-2 3/4)","");
+ limitations = limitations + ((tempString.match(/-1 3\/4\)/g) || []).length)*1.75;
+ tempString = tempString.replace("-1 3/4)","");
+ limitations = limitations + ((tempString.match(/-3\/4\)/g) || []).length)*0.75;
+ tempString = tempString.replace("-3/4)","");
+
+ // Find quarter integers. Replace larger ones first.
+ limitations = limitations + ((tempString.match(/-5 1\/4\)/g) || []).length)*5.25;
+ tempString = tempString.replace("-5 1/4)","");
+ limitations = limitations + ((tempString.match(/-4 1\/4\)/g) || []).length)*4.25;
+ tempString = tempString.replace("-4 1/4)","");
+ limitations = limitations + ((tempString.match(/-3 1\/4\)/g) || []).length)*3.25;
+ tempString = tempString.replace("-3 1/4)","");
+ limitations = limitations + ((tempString.match(/-2 1\/4\)/g) || []).length)*2.25;
+ tempString = tempString.replace("-2 1/4)","");
+ limitations = limitations + ((tempString.match(/-1 1\/4\)/g) || []).length)*1.25;
+ tempString = tempString.replace("-1 1/4)","");
+ limitations = limitations + ((tempString.match(/-1\/4\)/g) || []).length)*0.25;
+ tempString = tempString.replace("-1/4)","");
+
+ // Find whole integers. Replace larger ones first.
+ limitations = limitations + ((tempString.match(/-6\)/g) || []).length)*6;
+ tempString = tempString.replace("-6)","");
+ limitations = limitations + ((tempString.match(/-5\)/g) || []).length)*5;
+ tempString = tempString.replace("-5)","");
+ limitations = limitations + ((tempString.match(/-4\)/g) || []).length)*4;
+ tempString = tempString.replace("-4)","");
+ limitations = limitations + ((tempString.match(/-3\)/g) || []).length)*3;
+ tempString = tempString.replace("-3)","");
+ limitations = limitations + ((tempString.match(/-2\)/g) || []).length)*2;
+ tempString = tempString.replace("-2)","");
+ limitations = limitations + ((tempString.match(/-1\)/g) || []).length)*1;
+
+ return limitations;
+ }
+
+
+ var isAoE = function(inputString) {
+ // Search advantages for any that indicate an Area of Effect power.
+ // Written so as to be able to look for more than just "area" but this may be enough.
+ inputString = inputString.replace(/\W/g, " ");
+ inputString = inputString.toLowerCase();
+
+ const searchSet = new Set(["area"]);
+ let setOfWords = new Set(inputString.split(" "));
+ let intersection = new Set([...setOfWords].filter(x => searchSet.has(x)));
+ let answer = false;
+
+ if (intersection.size != 0) {
+ answer = true;
+ }
+
+ return answer;
+ }
+
+
+ var requiresRoll = function(inputString) {
+ // Determine if the power as an activation roll and find it if it is simple.
+ let lowerCaseString = inputString.toLowerCase();
+ let detailString;
+ let startPosition;
+ let endPosition;
+ let answer = false;
+ let value = 18;
+ let searchSet = new Set(["skill", "characteristic", "ps", "ks", "ss", "attack", "per"]);
+ let setOfWords;
+ let intersection;
+
+ if (lowerCaseString.includes("requires a roll")) {
+
+ answer = true;
+
+ // Attempt to obtain the skill roll needed if it is a simple activation roll. The others
+ // would require guesses, which means we need to leave the decision to the players.
+
+ startPosition = lowerCaseString.indexOf("requires a roll");
+ startPosition = lowerCaseString.indexOf("(", startPosition);
+ endPosition = lowerCaseString.indexOf(")", startPosition);
+ detailString = lowerCaseString.slice(startPosition, endPosition);
+ setOfWords = new Set((detailString.replace(/\W/g, " ").split(" ")));
+ intersection = new Set([...setOfWords].filter(x => searchSet.has(x)));
+
+ if (intersection.size === 0) {
+ endPosition = detailString.indexOf("-", 0);
+ value = detailString.slice(0, endPosition);
+ value = value.replace(/\D/g, '');
+ if (value.length !== 0) {
+ value = Number(value);
+ }
+ }
+ }
+
+ return {
+ "hasRoll": answer,
+ "skillRoll": value
+ }
+ }
+
+
+ var reducedDCV = function(inputString) {
+ // Search for the Concentration limitation.
+ inputString = inputString.toLowerCase();
+ let answer;
+
+ if (inputString.includes("0 dcv")) {
+ answer = "zero";
+ } else if (inputString.includes("1/2 dcv")) {
+ answer = "half";
+ } else {
+ answer = "full";
+ }
+
+ return answer;
+ }
+
+
+ var reducedRMod = function(inputString) {
+ // Search for half or zero range modifier advantages.
+ inputString = inputString.toLowerCase();
+ let answer;
+
+ if (inputString.includes("no range modifier")) {
+ answer = "zero";
+ } else if (inputString.includes("half range modifier")) {
+ answer = "half";
+ } else {
+ answer = "STD";
+ }
+
+ return answer;
+ }
+
+
+ var modifiedSTUNx = function(inputString) {
+ // Search for a STUNx multiplier.
+ inputString = inputString.toLowerCase();
+ let answer;
+
+ if (inputString.includes("-2 decreased stun multiplier")) {
+ answer = "-2";
+ } else if (inputString.includes("-1 decreased stun multiplier")) {
+ answer = "-1";
+ } else if (inputString.includes("+1 increased stun multiplier")) {
+ answer = "1";
+ } else if (inputString.includes("+2 increased stun multiplier")) {
+ answer = "2";
+ } else {
+ answer = "0";
+ }
+
+ return answer;
+ }
+
+
+ var getWeaponStrMin = function (weaponString, script_name) {
+ // Parse weapon text and look for one of three strings used
+ // by Hero Designer to record a weapon strength minimum.
+ let strengthMin = 0;
+ let strengthString;
+ let startParenthesis;
+ let endParenthesis;
+ let valueString;
+ let defaultStrength = 0;
+
+ if (weaponString !== "") {
+ if (weaponString.includes("STR Minimum")) {
+ tempPosition = weaponString.indexOf("STR Minimum");
+ startParenthesis = weaponString.indexOf("(", tempPosition + 11);
+ endParenthesis = weaponString.indexOf(")", tempPosition + 11);
+ strengthString = weaponString.slice(tempPosition + 11, startParenthesis);
+
+ // Get the limitation value in case no strength is available.
+ valueString = weaponString.slice(startParenthesis+1, endParenthesis);
+ valueString = valueString.replace(/\s/g, "");
+
+ switch(valueString) {
+ case "-1/4":
+ defaultStrength = 4;
+ break;
+ case "-1/2":
+ defaultStrength = 9;
+ break;
+ case "-3/4":
+ defaultStrength = 14;
+ break;
+ case "-1":
+ defaultStrength = 19;
+ break;
+ default:
+ defaultStrength = 1;
+ }
+
+ // Check to see if a strength range is used:
+ if (strengthString.includes("-")) {
+ tempPosition = strengthString.indexOf("-");
+ strengthString = strengthString.substring(0, tempPosition);
+ }
+
+ strengthMin = parseInt(strengthString.replace(/\D/g, ""))||defaultStrength;
+
+ } else if (weaponString.includes("STR Min")) {
+ tempPosition = weaponString.indexOf("STR Min");
+ startParenthesis = weaponString.indexOf("(", tempPosition + 8);
+ endParenthesis = weaponString.indexOf(")", tempPosition + 8);
+ strengthString = weaponString.slice(tempPosition + 8, startParenthesis);
+
+ // Get the limitation value in case no strength is available.
+ valueString = weaponString.slice(startParenthesis+1, endParenthesis);
+ valueString = valueString.replace(/\s/g, "");
+
+ switch(valueString) {
+ case "-1/4":
+ defaultStrength = 4;
+ break;
+ case "-1/2":
+ defaultStrength = 9;
+ break;
+ case "-3/4":
+ defaultStrength = 14;
+ break;
+ case "-1":
+ defaultStrength = 19;
+ break;
+ default:
+ defaultStrength = 1;
+ }
+
+ // Check to see if a strength range is used:
+ if (strengthString.includes("-")) {
+ tempPosition = strengthString.indexOf("-");
+ strengthString = strengthString.substring(0, tempPosition);
+ }
+
+ strengthMin = parseInt(strengthString.replace(/\D/g, ""))||defaultStrength;
+
+ } else {
+ strengthMin = 0;
+ }
+ } else {
+ strengthMin = 0;
+ }
+
+ return strengthMin;
+ }
+
+
+ var getWeaponRange = function (rangeString, strength, mass, script_name) {
+ // Parses range string for numeric characters.
+ // If "var" is found, calls the range based strength function "calculateRange".
+
+ let range = 0;
+
+ if (rangeString !== "") {
+ if (rangeString.includes("var")) {
+ range = calculateRange(strength, mass);
+ } else {
+ range = parseInt(rangeString.replace(/[^\d.-]/g, ""));
+ }
+ } else {
+ range = 0;
+ }
+
+ return range;
+ }
+
+
+ var getWeaponStrength = function (strengthMin, strengthMax, script_name) {
+ // Returns STR in increments of 5 above strengthMin up to strengthMax.
+
+ let differenceDC = 0;
+ let strength = strengthMax;
+
+ if (strengthMax >= strengthMin) {
+ differenceDC = Math.floor( (strengthMax - strengthMin)/5 );
+ strength = strengthMin + 5 * differenceDC;
+ }
+
+ return strength;
+ }
+
+
+ var getPowerDamage = function (damageString, effect, strength, script_name) {
+ // Parses damageString for damage dice.
+
+ let damage = "0";
+ let DC = 0;
+ let strDC = Math.floor(strength/5);
+ let halfDie = (((strength % 5) === 3) || ((strength % 5) === 4)) ? true : false;
+ let lastIndex = 0;
+ let detailString = "";
+ let startPosition = 0;
+ let endPosition = 0;
+ var diceSet = new Set(["Aid", "Blast", "Dispel", "Drain", "Entangle", "Flash", "HTH Attack", "HTH Killing Attack", "Ranged Killing Attack", "Healing", "Luck", "Mental Blast", "Mental Illusions", "Mind Control", "Mind Scan", "Transform", "Telepathy"]);
+
+ if (diceSet.has(effect)) {
+ if (damageString.includes("standard effect")) {
+ startPosition = damageString.indexOf("standard effect");
+ endPosition = damageString.indexOf(")", startPosition);
+ detailString = damageString.slice(startPosition+16, endPosition);
+ damage = detailString.replace("points", "CP");
+ } else {
+ if ((damageString.match(/d6/g) || []).length > 1) {
+ damageString = damageString.replace("d6", "d6+");
+ lastIndex = damageString.lastIndexOf("d6+");
+ damageString = damageString.substring(0, lastIndex) + "d6" + damageString.substring(lastIndex + 2);
+ }
+
+ // Sometimes the damage string contains extra bits after a comma. Drop them.
+ if (damageString.includes(",")) {
+ damageString = damageString.split(",")[0];
+ }
+
+ // Look for (xd6 w/STR) and use that.
+ if (damageString.includes(" w/STR")) {
+ damageString = damageString.match(/\(([^)]*)\)/)[1];
+ damageString = damageString.replace(" w/STR", "");
+ damageString = damageString.trim();
+ } else if (effect === "HTH Attack") {
+ if (damageString.includes("1/2d6")) {
+
+ damageString = damageString.replace("1/2d6", "d6");
+
+ if (halfDie) {
+ halfDie = false;
+ DC = 1;
+ } else {
+ halfDie = true;
+ }
+ }
+
+ endPosition = damageString.indexOf("d");
+ detailString = damageString.substring(0,endPosition);
+ DC += parseInt(detailString.replace(/[^0-9]/g, ""))||0;
+ DC += strDC;
+ damageString = DC.toString() + "d6";
+ damageString += halfDie ? "+d3" : "";
+ }
+
+ // Make sure the 1/2d6 is a 1d3.
+ if (damageString.includes(" 1/2d6")) {
+ damage = damageString.replace(" 1/2d6", "d6+d3");
+ } else if (damageString.includes("1/2d6")) {
+ damage = damageString.replace("1/2d6", "d3");
+ } else {
+ damage = damageString;
+ }
+ }
+ } else if (effect === "Telekinesis") {
+ damageString = damageString.replace(/[^0-9.]/g,'');
+ if ((typeof damageString !== "undefined") && (damageString !== "")) {
+ damageString = parseInt(damageString)||0;
+ damage = Math.floor(damageString/5).toString() + "d6" + (((damageString % 5) > 2) ? "+d3" : "");
+ } else {
+ damage = "0";
+ }
+ } else {
+ damage = "0";
+ }
+
+ return damage;
+ }
+
+
+ var getCharacteristicMod = function (inputString, searchString, script_name) {
+ let charMod = 0;
+ let lastIndex = 0;
+ let detailString = "";
+ let startPosition = 0;
+ let endPosition = 0;
+ const inches = String.fromCharCode(39,39);
+ let usedGameInches = false;
+ let lowerCaseString = inputString.toLowerCase();
+
+ const specialArray = ["limited", "usable by other", "real weapon", "only works", "only for", "only to", "only applies", "only when", "attacks", "requires a roll", "for up to"];
+ var leadingSet = new Set(["STR", "DEX", "CON", "INT", "EGO", "PRE", "OCV", "DCV", "OMCV", "DMCV", "PD", "ED", "BODY", "STUN", "END", "REC", "PER"]);
+ var trailingSet = new Set(["Running", "Leaping", "Swimming", "Flight", "Swinging"]);
+
+ if (specialArray.some(v => lowerCaseString.includes(v))) {
+ // We don't want to add overall modifications for special cases.
+ charMod = 0;
+ } else if (leadingSet.has(searchString)) {
+ endPosition = inputString.indexOf(searchString);
+ detailString = inputString.slice(0, endPosition);
+ startPosition = detailString.includes("+") ? detailString.indexOf("+") : 0;
+ detailString = detailString.slice(startPosition);
+ charMod = parseInt(detailString.replace(/[^0-9\-]/g, ''))||0;
+ } else if (trailingSet.has(searchString)) {
+
+ // Convert game inches to meters.
+ if (inputString.includes(inches)) {
+ inputString = inputString.replace(inches, "m");
+ usedGameInches = true;
+ }
+
+ startPosition = inputString.indexOf(searchString);
+ detailString = inputString.slice(startPosition + searchString.length);
+ endPosition = detailString.includes("m") ? detailString.indexOf("m") : detailString.length;
+ detailString = detailString.slice(0, endPosition);
+ charMod = (parseInt(detailString.replace(/[^0-9\-]/g, ''))||0) * (usedGameInches ? 2 : 1);
+ }
+
+ // Make sure we don't return something nasty.
+ charMod = ( isNaN(charMod) || (charMod === "") || (typeof charMod === "undefined")) ? 0 : Math.max(-99, Math.min(charMod, 99));
+
+ if (verbose) {
+ sendChat(script_name, "Applied characteristic mod " + searchString + " + " + charMod.toString());
+ }
+
+ return charMod;
+ }
+
+
+ var getVehicleMovement = function (inputString, searchString, script_name) {
+ // Alternate version of getCharacteristicMod,
+ let theMove = 0;
+ let theAdds = 0;
+ let lastIndex = 0;
+ let detailString = "";
+ let startPosition = 0;
+ let endPosition = 0;
+ const inches = String.fromCharCode(39,39);
+ let usedGameInches = false;
+
+ const specialString = "m total";
+ var trailingSet = new Set(["Running", "Leaping", "Swimming", "Flight", "Swinging", "Ground Movement", "Teleportation"]);
+
+ if (inputString.includes(specialString)) {
+ endPosition = inputString.indexOf(specialString);
+ detailString = inputString.slice(0, endPosition);
+ startPosition = detailString.lastIndexOf('(');
+ detailString = inputString.slice(startPosition, endPosition);
+ theMove = detailString.replace(/[^0-9\-]/g, '');
+ } else if (trailingSet.has(searchString)) {
+
+ // Convert game inches to meters.
+ if (inputString.includes(inches)) {
+ inputString = inputString.replace(inches, "m");
+ usedGameInches = true;
+ }
+
+ startPosition = inputString.indexOf(searchString);
+ detailString = inputString.slice(startPosition + searchString.length);
+ endPosition = detailString.includes("m") ? detailString.indexOf("m") : detailString.length;
+ detailString = detailString.slice(0, endPosition);
+ theMove = parseInt(detailString.replace(/[^0-9\-]/g, '')||0) * (usedGameInches ? 2 : 1);
+ }
+
+ if (verbose) {
+ sendChat(script_name, "Applied propulsion " + searchString + " of " + theMove.toString()) + " m.";
+ }
+
+ // Make sure we don't return something nasty.
+ return [isNaN(theMove) ? 0 : Math.max(0, Math.min( theMove, 999)), theAdds];
+ }
+
+
+ var getGrowthMod = function (inputString, searchString, script_name) {
+ let charMod = 0;
+ let lastIndex = 0;
+ let detailString = "";
+ let startPosition;
+ let lowerCaseString = inputString.toLowerCase();
+
+ const specialArray = ["real weapon", "only works", "only for", "only to", "only applies", "only when", "requires a roll", "for up to"];
+ var leadingSet = new Set(["STR", "DEX", "CON", "INT", "EGO", "PRE", "OCV", "DCV", "OMCV", "DMCV", "PD", "ED", "BODY", "STUN", "END", "REC", "PER","Running", "Leaping", "Swimming", "Flight"]);
+
+ if (specialArray.some(v => lowerCaseString.includes(v))) {
+ // We don't want to add overall modifications for special cases.
+ charMod = 0;
+ } else if (leadingSet.has(searchString)) {
+ endPosition = inputString.indexOf(searchString);
+ detailString = inputString.slice(0, endPosition);
+ startPosition = detailString.includes("+") ? detailString.indexOf("+") : 0;
+ detailString = detailString.slice(startPosition, endPosition);
+ charMod = detailString.replace(/[^0-9\-]/g, '');
+ if (charMod === "") {
+ charMod = 0;
+ }
+ } else {
+ charMod = 0;
+ }
+
+ return Math.max(-99, Math.min( (parseInt(charMod)||0), 99));
+ }
+
+
+ var getWeaponDamage = function (damageString, script_name) {
+ // Parses damageString for damage dice.
+
+ let damage = "0";
+ let lastIndex = 0;
+ let detailString;
+ let startPosition;
+ let endPosition;
+
+ if (damageString.includes("standard effect")) {
+ startPosition = damageString.indexOf("standard effect");
+ endPosition = damageString.indexOf(")", startPosition);
+ detailString = damageString.slice(startPosition+16, endPosition);
+ damage = detailString;
+ } else {
+ // Remove dice in w/STR since we'll calculated it.
+ if (damageString.includes(" w/STR")) {
+ damageString = damageString.replace(/\([^()]*\)/g, "");
+ }
+
+ // Separate joined dice if present.
+ if ((damageString.match(/d6/g) || []).length > 1) {
+ damageString = damageString.replace("d6", "d6+");
+ lastIndex = damageString.lastIndexOf("d6+");
+ damageString = damageString.substring(0, lastIndex) + "d6" + damageString.substring(lastIndex + 2);
+ }
+
+ // Make sure the 1/2d6 is a 1d3.
+ if (damageString.includes(" 1/2d6")) {
+ damage = damageString.replace(" 1/2d6", "d6+d3");
+ } else if (damageString.includes("1/2d6")) {
+ damage = damageString.replace("1/2d6", "d3");
+ } else {
+ damage = damageString;
+ }
+ }
+
+ return damage;
+ }
+
+
+ var checkDamageBySTR = function (inputString, script_name) {
+ damageBySTR = false;
+
+ if ( inputString.includes(" w/STR") || (findEffectType(inputString) === "HTH Attack") ) {
+ damageBySTR = true;
+ }
+
+ return damageBySTR;
+ }
+
+
+ var getWeaponMaxRange = function (inputString, script_name) {
+ let outcome = "";
+ let startPosition = 0;
+ let endPosition = 0;
+
+ inputString = inputString.toLowerCase();
+
+ if (inputString.includes("max range")) {
+ startPosition = inputString.indexOf("max range");
+ outcome = inputString.slice(startPosition);
+ if (outcome.includes(';')) {
+ endPosition = outcome.indexOf(';');
+ outcome = outcome.slice(0, endPosition);
+ } else if (outcome.includes(')')) {
+ endPosition = outcome.indexOf(')');
+ outcome = outcome.slice(0, endPosition);
+ } else {
+ endPosition = Math.min(16, outcome.length);
+ outcome = outcome.slice(0, endPosition);
+ }
+ outcome = outcome.replace(/[^\d,-]/g, "");
+ if (outcome.includes(',')) {
+ outcome = outcome.replace(',', ", ");
+ }
+ }
+
+ return parseInt(outcome.trim())||0;
+ }
+
+
+ var getItemNumber = function (inputString, script_name) {
+ let outcome = "1";
+ let startPosition = 0;
+ let endPosition = 0;
+
+ inputString = inputString.toLowerCase();
+
+ if (inputString.includes("number of items")) {
+ endPosition = inputString.indexOf("number of items");
+ startPosition = Math.max(endPosition - 3, 1);
+ outcome = inputString.slice(startPosition, endPosition);
+ outcome = outcome.replace(/[^\d,-]/g, "");
+ }
+
+ return parseInt(outcome)||1;
+ }
+
+
+ var getArmorLocations = function (inputString, script_name) {
+ let outcome = "";
+ let startPosition = 0;
+ let endPosition = 0;
+
+ inputString = inputString.toLowerCase();
+
+ if (inputString.includes("location")) {
+ startPosition = inputString.indexOf("location");
+ outcome = inputString.slice(startPosition);
+ if (outcome.includes(';')) {
+ endPosition = outcome.indexOf(';');
+ outcome = outcome.slice(0, endPosition);
+ } else if (outcome.includes(')')) {
+ endPosition = outcome.indexOf(')');
+ outcome = outcome.slice(0, endPosition);
+ } else {
+ endPosition = Math.min(28, outcome.length);
+ outcome = outcome.slice(0, endPosition);
+ }
+ outcome = outcome.replace(/[^\d,-]/g, "");
+ if (outcome.includes(',')) {
+ outcome = outcome.replace(',', ", ");
+ }
+ } else if (inputString.includes("loc")) {
+ startPosition = inputString.indexOf("loc");
+ outcome = inputString.slice(startPosition);
+ if (outcome.includes(';')) {
+ endPosition = outcome.indexOf(';');
+ outcome = outcome.slice(0, endPosition);
+ } else if (outcome.includes(')')) {
+ endPosition = outcome.indexOf(')');
+ outcome = outcome.slice(0, endPosition);
+ } else {
+ endPosition = Math.min(11, outcome.length);
+ outcome = outcome.slice(0, endPosition);
+ }
+ outcome = outcome.replace(/[^\d,-]/g, "");
+ if (outcome.includes(',')) {
+ outcome = outcome.replace(',', ", ");
+ }
+ }
+
+ return outcome.trim();
+ }
+
+
+ var getArmorEND = function (inputString, script_name) {
+ let tempString = "";
+ let endurance = 0;
+ let startPosition = 0;
+ let endPosition = 0;
+
+ inputString = inputString.toLowerCase();
+
+ if (inputString.includes("end/turn:")) {
+ startPosition = inputString.indexOf("end/turn:") + 9;
+ endPosition = Math.min(inputString.length, startPosition + 2);
+ tempString = inputString.slice(startPosition, endPosition);
+ endurance = parseInt(tempString.replace(/[^\d]/g, ""))||0;
+ } else if (inputString.includes("end/turn")) {
+ endPosition = inputString.indexOf("end/turn");
+ startPosition = Math.max(0, endPosition - 2);
+ tempString = inputString.slice(startPosition, endPosition);
+ endurance = parseInt(tempString.replace(/[^\d]/g, ""))||0;
+ } else {
+ endurance = 0;
+ }
+
+ return endurance;
+ }
+
+
+ var findDamageAdvantages = function (weaponString, script_name) {
+ // See 6E2 98 for a list of advantages that affect weapon damage.
+ let advantage = 0;
+ let temp = 0;
+ let searchString = "";
+
+ weaponString = weaponString.toLowerCase();
+
+ searchString = "area of effect";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "armor piercing";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "autofire";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "attack versus alternate defense";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "boostable";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ if (weaponString.includes("constant")) {
+ advantage += 0.5;
+ }
+
+ searchString = "cumulative";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "damage over time";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ if (weaponString.includes("does body")) {
+ advantage += 1;
+ }
+
+ if (weaponString.includes("does knockback")) {
+ advantage += 0.25;
+ }
+
+ if (weaponString.includes("double knockback")) {
+ advantage += 0.5;
+ }
+
+ if (weaponString.includes("+1 increased stun multiplier")) {
+ advantage += 0.25;
+ } else if (weaponString.includes("+2 increased stun multiplier")) {
+ advantage += 0.50;
+ }
+
+ searchString = "penetrating";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ // Check for the ranged advantage.
+ if (weaponString.includes("range based on str (+1/4)")) {
+ advantage += 0.25;
+ } else if (weaponString.includes("ranged (+1/2)")) {
+ advantage += 0.50;
+ }
+
+ if (weaponString.includes("sticky")) {
+ advantage += 0.5;
+ }
+
+ searchString = "time limit";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "transdimensional";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "trigger";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ if (weaponString.includes("uncontrolled")) {
+ advantage += 0.5;
+ }
+
+ searchString = "variable advantage";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ searchString = "variable special effects";
+ advantage += getSingleAdvantage (weaponString, searchString);
+
+ return advantage;
+ }
+
+ var getSingleAdvantage = function(weaponString, searchString) {
+ let advantage = 0;
+
+ if (weaponString.includes(searchString)) {
+ searchString = weaponString.slice(weaponString.indexOf(searchString) + searchString.length);
+ searchString = searchString.match(/\(([^)]+)\)/)[0];
+
+ advantage = findAdvantages(searchString);
+
+ if (advantage < 0) {
+ advantage = 0;
+ }
+ }
+
+ return advantage;
+ }
+
+
+ var calculateRange = function(strength, mass) {
+ // Determines range based on strength.
+ let liftCapability;
+ let freeCapability;
+ let effectiveStrength;
+ let range;
+
+ // First calculate carrying capacity.
+ switch (strength) {
+ case 0: liftCapability = 0;
+ break;
+ case 1: liftCapability = 8;
+ break;
+ case 2: liftCapability = 16;
+ break;
+ case 3: liftCapability = 25;
+ break;
+ case 4: liftCapability = 38;
+ break;
+ default: liftCapability=Math.round(25*Math.pow(2,(strength/5)));
+ }
+
+ // Subtract the thrown weight from capacity.
+ freeCapability = liftCapability-mass;
+
+ // Determine unused strength and calculate range.
+ if (freeCapability <= 0) {
+ range = 0;
+ } else {
+ if (freeCapability <= 8) {
+ effectiveStrength = 1;
+ range = 2;
+ } else if (freeCapability <= 16) {
+ effectiveStrength = 2;
+ range = 3;
+ } else if (freeCapability <= 25) {
+ effectiveStrength = 3;
+ range = 4;
+ } else if (freeCapability <= 38) {
+ effectiveStrength = 4;
+ range = 6;
+ } else {
+ effectiveStrength = 5 * Math.log2(freeCapability/25);
+ range = Math.round(8 * effectiveStrength/5);
+ }
+ }
+
+ return parseInt(Math.round(range));
+ }
+
+
+ var getItemMass = function(massString, script_name, decimalPlaces) {
+ // Remove units from mass and round to one or two decimal places.
+ let mass = 0;
+ let roundingFactor = (decimalPlaces === 1) ? 10 : 100;
+
+ if (massString !== "") {
+ massString = parseFloat(massString.replace(/[^\d.-]/g, ""));
+ mass = Math.round(roundingFactor*massString)/roundingFactor;
+ }
+
+ return mass;
+ }
+
+
+ var getStunModifier = function(itemString, script_name) {
+ // Parse string for STUN multiple.
+ let stunModifier = 0;
+ let tempPosition;
+
+ if ((typeof itemString !== "undefined") && (itemString.length !== 0)) {
+ if (itemString.includes("Increased STUN Multiplier")) {
+ tempPosition = itemString.indexOf("Increased STUN Multiplier");
+ stunModifier = parseInt(itemString.substr(tempPosition-3, 2));
+ } else if (itemString.includes("Decreased STUN Multiplier")) {
+ tempPosition = itemString.indexOf("Decreased STUN Multiplier");
+ stunModifier = parseInt(itemString.substr(tempPosition-3, 2));
+ }
+ }
+
+ return stunModifier;
+ }
+
+
+ var getOCVmodifier = function(weaponString, script_name) {
+ // Parse weapon string for OCV modifier or penalty.
+ let ocvModifier = 0;
+ let tempPosition;
+ let subString = "";
+ let searchString = "";
+ let found = true;
+
+ // First, remove Range Modifier OCV if present.
+ weaponString = weaponString.replace("OCV modifier","");
+
+ // Then search for OCV bonus.
+ if (weaponString !== "") {
+ if (weaponString.includes("with OCV")) {
+ searchString = "with OCV";
+ } else if (weaponString.includes("OCV")) {
+ searchString = "OCV";
+ } else if (weaponString.includes("with any single attack")) {
+ searchString = "with any single attack";
+ } else {
+ found = false;
+ }
+
+ if (found) {
+ tempPosition = weaponString.indexOf(searchString);
+ subString = weaponString.slice(Math.max(0, tempPosition-4), tempPosition);
+ subString = subString.replace(/[^\d-]/g, "");
+ ocvModifier = parseInt(subString)||0;
+ }
+ }
+
+ return ocvModifier;
+ }
+
+ var getRangeModifier = function(weaponString, script_name) {
+ // Parse weapon string for OCV modifier or penalty.
+ let rangeMod = 0;
+ let tempPosition;
+ let subString = "";
+ let searchString = "";
+ let found = true;
+
+ if (weaponString !== "") {
+ if (weaponString.includes("vs. Range Modifier")) {
+ searchString = "vs. Range Modifier";
+ } else if (weaponString.includes("to offset RMod")) {
+ searchString = "to offset RMod";
+ } else if (weaponString.includes("to offset Range")) {
+ searchString = "to offset Range";
+ } else {
+ found = false;
+ }
+
+ // Then search for OCV bonus.
+ if (found) {
+ tempPosition = weaponString.indexOf(searchString);
+ subString = weaponString.slice(Math.max(0, tempPosition-4), tempPosition);
+ subString = subString.replace(/[^\d-]/g, "");
+ rangeMod = parseInt(subString)||0;
+ }
+ }
+
+ return rangeMod;
+ }
+
+
+ var heroRoundUp = function(numerator, denominator) {
+
+ if (denominator > 0) {
+ const intermediate = numerator/denominator;
+ const remainder = Math.floor((numerator % denominator)*10)/10;
+
+ if (remainder < 0.5) {
+ return Math.floor(intermediate);
+ } else {
+ return Math.ceil(intermediate);
+ }
+ }
+
+ // Error. Return unmodified value.
+ return numerator;
+ }
+
+
+ var heroRoundDown = function(numerator, denominator) {
+
+ if (denominator > 0) {
+ const intermediate = numerator/denominator;
+ const remainder = Math.floor((numerator % denominator)*10)/10;
+
+ if (remainder < 0.6) {
+ return Math.floor(intermediate);
+ } else {
+ return Math.ceil(intermediate);
+ }
+ }
+
+ // Error. Return unmodified value.
+ return numerator;
+ }
+
+
+ function setCharAt(str,index,chr) {
+ // Replace a single character in a string.
+ if(index > str.length-1) return str;
+ return str.substring(0,index) + chr + str.substring(index+1);
+ }
+
+
+/* **************************************** */
+/* *** END Importing Functions *** */
+/* **************************************** */
+
+ const createSingleWriteQueue = (attributes) => {
+ // this is the list of trigger attributes that will trigger class recalculation, as of 5e OGL 2.5 October 2018
+ // (see on... handler that calls update_class in sheet html)
+ // these are written first and individually, since they trigger a lot of changes
+ let class_update_triggers = [
+ 'strength'];
+
+ // set class first, everything else is alphabetical
+ let classAttribute = class_update_triggers.shift();
+ class_update_triggers.sort();
+ class_update_triggers.unshift(classAttribute);
+
+ // write in deterministic order (class first, then alphabetical)
+
+ let items = [];
+
+ for (trigger of class_update_triggers) {
+ let value = attributes[trigger];
+ if ((value === undefined) || (value === null)) {
+ continue;
+ }
+ items.push([trigger, value]);
+ log('hero: trigger attribute ' + trigger);
+ delete attributes[trigger];
+ }
+
+ return items;
+ }
+
+
+ const reportReady = (character) => {
+ // From Beyond. Left as-is.
+ //
+ // TODO this is nonsense. we aren't actually done importing, because notifications in the character sheet are firing for quite a while
+ // after we finish changing things (especially on first import) and we have no way (?) to wait for it to be done. These are not sheet workers
+ // on which we can wait.
+ // sendChat(script_name, 'Import of ' + character.character_name + ' is ready at https://journal.roll20.net/character/' + object.id +'
', null, {noarchive:true});
+ return;
+ }
+
+
+ const blankIfNull = (input) => {
+ return (input === null)?"":input;
+ }
+
+
+ const ucFirst = (string) => {
+ if(string == null) return string;
+ return string.charAt(0).toUpperCase() + string.slice(1);
+ };
+
+
+ const sendConfigMenu = (player, first) => {
+ let playerid = player.id;
+ let prefix = (state[state_name][playerid].config.prefix !== '') ? state[state_name][playerid].config.prefix : '[NONE]';
+ let prefixButton = makeButton(prefix, '!hero --config prefix|?{Prefix}', buttonStyle);
+ let suffix = (state[state_name][playerid].config.suffix !== '') ? state[state_name][playerid].config.suffix : '[NONE]';
+ let suffixButton = makeButton(suffix, '!hero --config suffix|?{Suffix}', buttonStyle);
+ let overwriteButton = makeButton(state[state_name][playerid].config.overwrite, '!hero --config overwrite|'+!state[state_name][playerid].config.overwrite, buttonStyle);
+ let debugButton = makeButton(state[state_name][playerid].config.debug, '!hero --config debug|'+!state[state_name][playerid].config.debug, buttonStyle);
+ let optionMaximumsButton = makeButton(state[state_name][playerid].config.maximums, '!hero --config maximums|'+!state[state_name][playerid].config.maximums, buttonStyle);
+ let optionLiteracyButton = makeButton(state[state_name][playerid].config.literacy, '!hero --config literacy|'+!state[state_name][playerid].config.literacy, buttonStyle);
+ let optionSuperENDButton = makeButton(state[state_name][playerid].config.superEND, '!hero --config superEND|'+!state[state_name][playerid].config.superEND, buttonStyle);
+ let optionLocationsButton = makeButton(state[state_name][playerid].config.locations, '!hero --config locations|'+!state[state_name][playerid].config.locations, buttonStyle);
+ let optionDamageCap = makeButton(state[state_name][playerid].config.damageCap, '!hero --config damageCap|'+!state[state_name][playerid].config.damageCap, buttonStyle);
+
+ let listItems = [
+ 'Overwrite: '+overwriteButton+'
CAUTION: overwrites an existing character sheet that has a matching character name.',
+ 'Prefix: '+prefixButton,
+ 'Suffix: '+suffixButton,
+ 'Verbose Report: '+debugButton,
+ ]
+
+ let list = 'Importer'+makeList(listItems, 'overflow: hidden; list-style: none; padding: 0; margin: 0;', 'overflow: hidden; margin-top: 5px;');
+
+ let inPlayerJournalsButton = makeButton(player.get('displayname'), "", buttonStyle);
+ let controlledByButton = makeButton(player.get('displayname'), "", buttonStyle);
+ if(playerIsGM(playerid)) {
+ let players = "";
+ let playerObjects = findObjs({
+ _type: "player",
+ });
+ for(let i = 0; i < playerObjects.length; i++) {
+ players += '|'+playerObjects[i]['attributes']['_displayname']+','+playerObjects[i].id;
+ }
+
+ let ipj = state[state_name][playerid].config.inplayerjournals == "" ? '[NONE]' : state[state_name][playerid].config.inplayerjournals;
+ if(ipj != '[NONE]' && ipj != 'all') ipj = getObj('player', ipj).get('displayname');
+ inPlayerJournalsButton = makeButton(ipj, '!hero --config inplayerjournals|?{Player|None,[NONE]|All Players,all'+players+'}', buttonStyle);
+ let cb = state[state_name][playerid].config.controlledby == "" ? '[NONE]' : state[state_name][playerid].config.controlledby;
+ if(cb != '[NONE]' && cb != 'all') cb = getObj('player', cb).get('displayname');
+ controlledByButton = makeButton(cb, '!hero --config controlledby|?{Player|None,[NONE]|All Players,all'+players+'}', buttonStyle);
+ }
+
+ let sheetListItems = [
+ 'In Player Journal: ' + inPlayerJournalsButton,
+ 'Player Control: ' + controlledByButton,
+ 'Use Char Maximums: ' + optionMaximumsButton,
+ 'Literacy Costs CP: ' + optionLiteracyButton,
+ 'Super-Heroic END: ' + optionSuperENDButton,
+ 'Limit Weapon DMG: ' + optionDamageCap,
+ 'Use Hit Locations: ' + optionLocationsButton
+ ]
+
+ let sheetList = '
Character Sheet'+makeList(sheetListItems, 'overflow: hidden; list-style: none; padding: 0; margin: 0;', 'overflow: hidden; margin-top: 5px;');
+
+ // Set verbose (debug) option
+ let debug = "";
+ if(state[state_name][playerid].config.debug){
+ // The original version here would generate debug option buttons. For now, we will only change the verbose reporting state.
+ verbose = true;
+ } else {
+ verbose = false;
+ }
+
+ // Set characteristic maximums option
+ if(state[state_name][playerid].config.maximums){
+ defaultAttributes.useCharacteristicMaximums = "on";
+ } else {
+ defaultAttributes.useCharacteristicMaximums = 0;
+ }
+
+ // Set literacy cost option
+ if(state[state_name][playerid].config.literacy){
+ defaultAttributes.optionLiteracyCostsPoints = "on";
+ } else {
+ defaultAttributes.optionLiteracyCostsPoints = 0;
+ }
+
+ // Set super-heroic END option
+ if(state[state_name][playerid].config.superEND){
+ defaultAttributes.optionSuperHeroicEndurance = "on";
+ } else {
+ defaultAttributes.optionSuperHeroicEndurance = 0;
+ }
+
+ // Set hit location system option
+ if(state[state_name][playerid].config.locations){
+ defaultAttributes.optionHitLocationSystem = "on";
+ } else {
+ defaultAttributes.optionHitLocationSystem = 0;
+ }
+
+ // Set hit location system option
+ if(state[state_name][playerid].config.damageCap){
+ defaultAttributes.optionWeaponDamageCap = "on";
+ } else {
+ defaultAttributes.optionWeaponDamageCap = 0;
+ }
+
+ let resetButton = makeButton('Reset', '!hero --reset', altButtonStyle + ' margin: auto; width: 90%; display: block; float: none;');
+
+ //let title_text = (first) ? script_name + ' First Time Setup' : script_name + ' Config';
+ let title_text = (first) ? 'HD Importer First Time Setup' : 'HD Importer Configuration';
+ let text = ''+makeTitle(title_text)+list+sheetList+debug+'
'+resetButton+'';
+
+ sendChat(script_name, '/w "' + player.get('displayname') + '" ' + text, null, {noarchive:true});
+ };
+
+
+ const sendHelpMenu = (player, first) => {
+ let configButton = makeButton('Config', '!hero --config', altButtonStyle+' margin: auto; width: 90%; display: block; float: none;');
+
+ let listItems = [
+ '!hero --help
Shows this menu.',
+ '!hero --config
Shows the configuration menu. (GM only)',
+ '!hero --import [CHARACTER JSON]
Imports a character from Hero Designer.',
+ ];
+
+ let command_list = makeList(listItems, 'list-style: none; padding: 0; margin: 0;');
+
+ let text = '';
+ //text += makeTitle(script_name + ' Help');
+ text += makeTitle('HD Importer Help');
+ text += '
Export a character in Hero Designer using the HeroSystem6eHeroic.hde format.
';
+ text += '
Locate and open the exported .txt file in a text editor. Copy its entire contents and paste them into the Roll20 chat window. Hit enter.
';
+ text += '
For more information see the documentation page in the HDImporter Github repository.
';
+ text += '
';
+ text += '
Commands:'+command_list;
+ text += '
';
+ text += configButton;
+ text += '
';
+
+ sendChat(script_name, '/w "'+ player.get('displayname') + '" ' + text, null, {noarchive:true});
+ };
+
+
+ const makeTitle = (title) => {
+ return ''+title+'
';
+ };
+
+
+ const makeButton = (title, href, style) => {
+ return ''+title+'';
+ };
+
+
+ const makeList = (items, listStyle, itemStyle) => {
+ let list = '';
+ items.forEach((item) => {
+ list += '- '+item+'
';
+ });
+ list += '
';
+ return list;
+ };
+
+
+ const replaceChars = (text) => {
+ text = text.replace('\&rsquo\;', '\'').replace('\&mdash\;','—').replace('\ \;',' ').replace('\&hellip\;','…');
+ text = text.replace('\ \;', ' ');
+ text = text.replace('\û\;','û').replace('’', '\'').replace(' ', ' ');
+ text = text.replace(/]+>/gi,'• ').replace(/<\/li>/gi,'');
+ text = text.replace(/\r\n(\r\n)+/gm,'\r\n');
+ return text;
+ };
+
+
+ const getRepeatingRowIds = (section, attribute, matchValue, index) => {
+ let ids = [];
+ if(state[state_name][hero_caller.id].config.overwrite) {
+ let matches = findObjs({ type: 'attribute', characterid: object.id })
+ .filter((attr) => {
+ return attr.get('name').indexOf('repeating_'+section) !== -1 && attr.get('name').indexOf(attribute) !== -1 && attr.get('current') == matchValue;
+ });
+ for(let i in matches) {
+ let row = matches[i].get('name').replace('repeating_'+section+'_','').replace('_'+attribute,'');
+ ids.push(row);
+ }
+ if(ids.length == 0) ids.push(generateRowID());
+ }
+ else ids.push(generateRowID());
+
+ if(index == null) return ids;
+ else return ids[index] == null && index >= 0 ? generateRowID() : ids[index];
+ }
+
+
+ // Return an array of objects according to key, value, or key and value matching, optionally ignoring objects in array of names
+ const getObjects = (obj, key, val, except) => {
+ except = except || [];
+ let objects = [];
+ for (let i in obj) {
+ if (!obj.hasOwnProperty(i)) continue;
+ if (typeof obj[i] == 'object') {
+ if (except.indexOf(i) != -1) {
+ continue;
+ }
+ objects = objects.concat(getObjects(obj[i], key, val));
+ } else
+ //if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
+ if (i == key && obj[i] == val || i == key && val == "") { //
+ objects.push(obj);
+ } else if (obj[i] == val && key == ""){
+ //only add if the object is not already in the array
+ if (objects.lastIndexOf(obj) == -1){
+ objects.push(obj);
+ }
+ }
+ }
+ return objects;
+ };
+
+ // This section from Beyond is not used in HS6eH_HDImporter, but may be useful in future.
+ //
+ // Find an existing repeatable item with the same name, or generate new row ID
+ // const getOrMakeRowID = (character,repeatPrefix,name) => {
+ // // Get list of all of the character's attributes
+ // let attrObjs = findObjs({ _type: "attribute", _characterid: character.get("_id") });
+ //
+ // let i = 0;
+ // while (i < attrObjs.length)
+ // {
+ // // If this is a feat taken multiple times, strip the number of times it was taken from the name
+ // let attrName = attrObjs[i].get("current").toString();
+ // if (regexIndexOf(attrName, / x[0-9]+$/) !== -1)
+ // attrName = attrName.replace(/ x[0-9]+/,"");
+ //
+ // if (attrObjs[i].get("name").indexOf(repeatPrefix) !== -1 && attrObjs[i].get("name").indexOf("_name") !== -1 && attrName === name)
+ // return attrObjs[i].get("name").substring(repeatPrefix.length,(attrObjs[i].get("name").indexOf("_name")));
+ // i++;
+ // i++;
+ // }
+ // return generateRowID();
+ // };
+
+
+ const generateUUID = (function() {
+ let a = 0, b = [];
+ return function() {
+ let c = (new Date()).getTime() + 0, d = c === a;
+ a = c;
+ for (var e = new Array(8), f = 7; 0 <= f; f--) {
+ e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64);
+ c = Math.floor(c / 64);
+ }
+ c = e.join("");
+ if (d) {
+ for (f = 11; 0 <= f && 63 === b[f]; f--) {
+ b[f] = 0;
+ }
+ b[f]++;
+ } else {
+ for (f = 0; 12 > f; f++) {
+ b[f] = Math.floor(64 * Math.random());
+ }
+ }
+ for (f = 0; 12 > f; f++){
+ c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]);
+ }
+ return c;
+ };
+ }());
+
+
+ const generateRowID = function() {
+ "use strict";
+ return generateUUID().replace(/_/g, "Z");
+ };
+
+
+ const regexIndexOf = (str, regex, startpos) => {
+ let indexOf = str.substring(startpos || 0).search(regex);
+ return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
+ };
+
+
+ const pre_log = (message) => {
+ log('---------------------------------------------------------------------------------------------');
+ log(message);
+ log('---------------------------------------------------------------------------------------------');
+ };
+
+
+ const checkInstall = function() {
+ if(!_.has(state, state_name)){
+ state[state_name] = state[state_name] || {};
+ }
+ setDefaults();
+ };
+
+
+ const setDefaults = (reset) => {
+ const defaults = {
+ overwrite: false,
+ debug: false,
+ prefix: '',
+ suffix: '',
+ inplayerjournals: '',
+ controlledby: '',
+ maximums: false,
+ literacy: false,
+ superEND: false,
+ locations: false,
+ damageCap: false
+ };
+
+ let playerObjects = findObjs({
+ _type: "player",
+ });
+ playerObjects.forEach((player) => {
+ if(!state[state_name][player.id]) {
+ state[state_name][player.id] = {};
+ }
+
+ if(!state[state_name][player.id].config) {
+ state[state_name][player.id].config = defaults;
+ }
+
+ for(let item in defaults) {
+ if(!state[state_name][player.id].config.hasOwnProperty(item)) {
+ state[state_name][player.id].config[item] = defaults[item];
+ }
+ }
+
+ if(!state[state_name][player.id].config.hasOwnProperty('firsttime')){
+ if(!reset){
+ sendConfigMenu(player, true);
+ }
+ state[state_name][player.id].config.firsttime = false;
+ }
+ });
+ };
+
+
+})();
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.TXT b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.TXT
new file mode 100644
index 0000000000..c9aeada234
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.TXT
@@ -0,0 +1 @@
+ !hero --import { "character":{ "character_name":"Darci", "character_title":"Fae-Cursed", "height":"1.66 m", "weight":"60.00 kg", "eyes":"Brown", "hair":"Brown", "backgroundText":"Darci grew up in a small highland village, the daughter of a village healer, with no ambition save to learn her mother's trade. Her life was turned upside down when she encountered a trol while out collection herbs in the woods. The troll promised to tell her secrets of Fae magic in return for her friendship. Darci has regretted her kindness ever since. Exiled and feard by common folk and given little help by the Fae, Darci has found safety in the service of a mercenary company.", "historyText":"", "appearance":"", "tactics":"", "campaignUse":"", "quote":"Village Herbalist", "experience":"0", "experienceBenefit":"0", "size":"", "strength":"17", "dexterity":"13", "constitution":"18", "intelligence":"18", "ego":"13", "presence":"10", "ocv":"4", "dcv":"4", "omcv":"3", "dmcv":"3", "speed":"4", "pd":"4", "ed":"4", "body":"14", "stun":"26", "endurance":"40", "recovery":"9", "running":"12", "leaping":"6", "swimming":"6", "equipment":{ "equipment01":{ "name":"Bronze Maille", "text":"Resistant Protection (5 PD/5 ED) (15 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4), Requires A Roll (12- roll; Locations 7-14; -1/4)", "damage":"", "end":"0", "range":"", "mass":"11.40kg", "attack":"", "defense":"true", "notes":"(1 END/turn)" }, "equipment02":{ "name":"Bronze Cap", "text":"Resistant Protection (6 PD/6 ED) (18 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4)", "damage":"", "end":"0", "range":"", "mass":"0.56kg", "attack":"", "defense":"true", "notes":"(Locations 5)" }, "equipment03":{ "name":"High Boots, Gloves", "text":"Resistant Protection (2 PD/2 ED) (6 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4)", "damage":"", "end":"0", "range":"", "mass":"0.42kg", "attack":"", "defense":"true", "notes":"(Locations 16-18, 6-7)" }, "equipment04":{ "name":"(Multipower) Small Shield", "text":"Multipower, 5-point reserve, (5 Active Points); all slots OAF (-1), STR Min 5 (-1/4)", "damage":"", "end":"", "range":"", "mass":"2.00kg", "attack":"", "defense":"", "notes":"" }, "equipment05":{ "name":"(MPSlot1) ", "text":"+1 DCV (5 Active Points); OAF (-1), Real Armor (-1/4), STR Min 5 (-1/4)", "damage":"", "end":"", "range":"", "mass":"", "attack":"", "defense":"", "notes":"" }, "equipment06":{ "name":"(MPSlot2) Bash", "text":"Hand-To-Hand Attack +1d6 (5 Active Points); OAF (-1), Hand-To-Hand Attack (-1/2), Side Effects -1 OCV, Side Effect occurs automatically whenever Power is used (-1/2), Real Weapon (-1/4), STR Min 5 (-1/4)", "damage":"1d6", "end":"1", "range":"", "mass":"", "attack":"true", "defense":"", "notes":"" }, "equipment07":{ "name":"Bronze Battle Axe", "text":"(Total: 38 Active Cost, 13 Real Cost) Killing Attack - Hand-To-Hand 1 1/2d6 (2 1/2d6 w/STR), Reduced Endurance (0 END; +1/2) (37 Active Points); OAF (-1), STR Min: 13 (-1/2), Real Weapon (-1/4), Required Hands One-And-A-Half-Handed (-1/4) (Real Cost: 12) plus (1 Active Points) (Real Cost: 1)", "damage":"1 1/2d6 (2 1/2d6 w/STR)", "end":"0", "range":"", "mass":"1.60kg", "attack":"true", "defense":"", "notes":"" }, "equipment08":{ "name":"Bronze Dagger", "text":"Killing Attack - Hand-To-Hand 1d6-1 (1d6 w/STR), Range Based On STR (+1/4), Reduced Endurance (0 END; +1/2) (17 Active Points); OAF (-1), Real Weapon (-1/4), STR Minimum 6 (-1/4)", "damage":"1d6-1 (1d6 w/STR)", "end":"0", "range":"var.", "mass":"0.80kg", "attack":"true", "defense":"", "notes":"" }, "equipment09":{ "name":"Great Coat", "text":"Change Environment (+2 Temperature Level Adjustment), Reduced Endurance (0 END; +1/2) (9 Active Points); No Range (-1/2), Self Only (-1/2), OIF (-1/2)", "damage":"", "end":"0", "range":"", "mass":"2.00kg", "attack":"true", "defense":"", "notes":"" }, "equipment10":{ "name":"Candle", "text":"Sight Group Images, +/-4 to PER Rolls, Reduced Endurance (0 END; +1/2), Area Of Effect (2m Radius; +3/4), Mobile (1m per Phase; +1/2) (49 Active Points); Only To Create Light (-1), OAF (-1), Extra Time (1 Turn (Post-Segment 12), Only to Activate, -3/4), No Range (-1/2), Real Weapon (-1/4), 1 Continuing Fuel Charge lasting 1 Hour (-0)", "damage":"", "end":"[1 cc]", "range":"", "mass":"0.10kg", "attack":"true", "defense":"", "notes":"(x6 number of items)" }, "equipment11":{ "name":"Herbalism Kit", "text":"+1 with Herbalism (2 Active Points); OAF (-1)", "damage":"", "end":"", "range":"", "mass":"1.00kg", "attack":"", "defense":"", "notes":"" }, "equipment12":{ "name":"Survival Kit", "text":"+2 with Survival (4 Active Points); OAF (-1)", "damage":"", "end":"", "range":"", "mass":"1.00kg", "attack":"", "defense":"", "notes":"" }, "equipment13":{ "name":"Healing Potion", "text":"Healing BODY 4d6 (40 Active Points); 2 Charges which Never Recover (-3 1/2), OAF Fragile (-1 1/4), Extra Time (Full Phase, -1/2), Side Effects, Side Effect occurs automatically whenever Power is used (Side Effect only affects the recipient of the benefits of the Power; -1/2), Gestures (Drink; -1/4)", "damage":"4d6", "end":"[2 nr]", "range":"", "mass":"0.25kg", "attack":"", "defense":"", "notes":"" }, "equipment14":{}, "equipment15":{}, "equipment16":{}, "equipment17":{}, "equipment18":{}, "equipment19":{}, "equipment20":{}, "equipment21":{}, "equipment22":{}, "equipment23":{}, "equipment24":{}, "equipment25":{}, "equipment26":{}, "equipment27":{}, "equipment28":{}, "equipment29":{}, "equipment30":{}, "equipment31":{}, "equipment32":{}, "equipment33":{}, "equipment34":{}, "equipment35":{}, "equipment36":{}, "equipment37":{}, "equipment38":{}, "equipment39":{}, "equipment40":{}, "equipment41":{}, "equipment42":{}, "equipment43":{}, "equipment44":{}, "equipment45":{}, "equipment46":{}, "equipment47":{}, "equipment48":{}, "equipment49":{}, "equipment50":{} }, "maneuvers":{ "maneuver01":{ }, "maneuver02":{ }, "maneuver03":{ }, "maneuver04":{ }, "maneuver05":{ }, "maneuver06":{ }, "maneuver07":{ }, "maneuver08":{ }, "maneuver09":{ }, "maneuver10":{ }, "maneuver11":{ }, "maneuver12":{ }, "maneuver13":{ }, "maneuver14":{ }, "maneuver15":{ }, "maneuver16":{ }, "maneuver17":{ }, "maneuver18":{ }, "maneuver19":{ }, "maneuver20":{ } }, "perks":{ "perk01":{ "type":"Fringe Benefit", "points":"1", "text":"Member of the Company Fringe Benefit: Membership", "notes":"" }, "perk02":{ "type":"Fringe Benefit", "points":"1", "text":"Low-ranking member of Fae Society Fringe Benefit (0 Active Points)", "notes":"" }, "perk03":{ }, "perk04":{ }, "perk05":{ }, "perk06":{ }, "perk07":{ }, "perk08":{ }, "perk09":{ }, "perk10":{ } }, "talents":{}, "complications":{ "complication01":{ "type":"Social Complication", "points":"10", "text":"Social Complication: Regarded as fae-touched and cursed. Frequently, Minor", "notes":"" }, "complication02":{ "type":"Hunted", "points":"15", "text":"Hunted: Hunted by agents of Summer. Frequently (Mo Pow; Mildly Punish)", "notes":"" }, "complication03":{ "type":"Distinctive Features", "points":"5", "text":"Distinctive Features: Peculiar smell and hard-to-pin-down appearance. Not quite human. Trollish, to those who know of fae. (Easily Concealed; Noticed and Recognizable; Detectable By Commonly-Used Senses)", "notes":"" }, "complication04":{ "type":"Psychological Complication", "points":"20", "text":"Psychological Complication: Finds the touch of iron uncomfortable and won't wear iron armor or jewelry or use iron tools. (Very Common; Strong)", "notes":"" }, "complication05":{}, "complication06":{}, "complication07":{}, "complication08":{}, "complication09":{}, "complication10":{}, "complication11":{}, "complication12":{}, "complication13":{}, "complication14":{}, "complication15":{}, "complication16":{}, "complication17":{}, "complication18":{}, "complication19":{}, "complication20":{} }, "powers":{ "power01":{ "name":"Bile and Acid", "base":"15", "text":"Killing Attack - Ranged 1d6, Area Of Effect (4 2m Areas; +1/2), Damage Over Time, Target's defenses only apply once (3 damage increments, damage occurs every four Segments, can be negated by Water; +2 1/2) (60 Active Points); 3 Recoverable Charges (-3/4), Extra Time (Full Phase, -1/2), No Range (-1/2), Gestures (Requires both hands; -1/2), Side Effects (1d6+1d3 drain STUN; -1/4), Concentration (1/2 DCV; -1/4), Limited Power Power loses about a fourth of its effectiveness (Does not work in water; -1/4), Requires A Roll (Skill roll, -1 per 20 Active Points modifier; Magic Roll; -1/4)", "notes":"", "cost":"14", "endurance":"[3 rc]", "damage":"1d6", "compound":"false" }, "power02":{ "name":"Pneuma", "base":"30", "text":"Killing Attack - Ranged 2d6, Invisible Power Effects (Inobvious to [one Sense Group]; +1/4) (37 Active Points); Requires A Roll (Skill roll; -1/2), Gestures (-1/4), Incantations (-1/4), Beam (-1/4), Limited Power Power loses about a fourth of its effectiveness (Does not work under water; -1/4)", "notes":"", "cost":"15", "endurance":"4", "damage":"2d6", "compound":"false" }, "power03":{ "name":"Self Renewal", "base":"55", "text":"Healing BODY 5d6, Can Heal Limbs (55 Active Points); Increased Endurance Cost (x6 END; -2 1/2), Extra Time (1 Turn (Post-Segment 12), Character May Take No Other Actions, -1 1/2), Concentration, Must Concentrate throughout use of Constant Power (0 DCV; Character is totally unaware of nearby events; -1 1/2), OAF (Eat a sprig of evergreen; -1), Gestures (Requires both hands; -1/2), Life Energy Modifier Power loses about a third of its effectiveness (-1/2), Self Only Power loses about a third of its effectiveness (-1/2), Incantations (-1/4), Requires A Roll (Characteristic roll, -1 per 20 Active Points modifier; -1/4)", "notes":"", "cost":"6", "endurance":"30", "damage":"5d6", "compound":"false" }, "power04":{ "name":"Underdark Eyes", "base":"5", "text":"Nightvision (5 Active Points); Gestures (Requires both hands; -1/2), Requires A Roll (11- roll; -1/2), Incantations (-1/4)", "notes":"", "cost":"2", "endurance":"0", "damage":"", "compound":"false" }, "power05":{ "name":"Winter's Shawl", "base":"12", "text":"Life Support (Immunity All terrestrial diseases; Immunity: All terrestrial poisons; Safe in Intense Cold) (12 Active Points); Costs Endurance (-1/2), Requires A Roll (11- roll; -1/2), Incantations (-1/4)", "notes":"", "cost":"5", "endurance":"1", "damage":"", "compound":"false" }, "power06":{ "name":"Fae Sense", "base":"10", "text":"Detect Magic A Class Of Things 13- (no Sense Group), Range (10 Active Points); Increased Endurance Cost (x4 END; -3/4), Gestures (Requires both hands; -1/2), Requires A Roll (11- roll; -1/2), Incantations (-1/4), Costs Endurance (Only Costs END to Activate; -1/4)", "notes":"", "cost":"3", "endurance":"4", "damage":"13-", "compound":"false" }, "power07":{ }, "power08":{ }, "power09":{ }, "power10":{ }, "power11":{ }, "power12":{ }, "power13":{ }, "power14":{ }, "power15":{ }, "power16":{ }, "power17":{ }, "power18":{ }, "power19":{ }, "power20":{ }, "power21":{ }, "power22":{ }, "power23":{ }, "power24":{ }, "power25":{ }, "power26":{ }, "power27":{ }, "power28":{ }, "power29":{ }, "power30":{ }, "power31":{ }, "power32":{ }, "power33":{ }, "power34":{ }, "power35":{ }, "power36":{ }, "power37":{ }, "power38":{ }, "power39":{ }, "power40":{ }, "power41":{ }, "power42":{ }, "power43":{ }, "power44":{ }, "power45":{ }, "power46":{ }, "power47":{ }, "power48":{ }, "power49":{ }, "power50":{ } }, "skills": { "skill01": { "name":"Mercenary", "enhancer":"", "text":"PS 11-", "display":"Professional Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill02": { "name":"Herbalist", "enhancer":"", "text":"PS 11-", "display":"Professional Skill", "attribute":"GENERAL", "base":"0", "levels":"0", "cost":"0" }, "skill03": { "name":"", "enhancer":"", "text":"Language: Clan's Tongue (idiomatic; literate) (5 Active Points)", "display":"Language", "attribute":"GENERAL", "base":"5", "levels":"0", "cost":"1" }, "skill04": { "name":"", "enhancer":"", "text":"Language: King's Tongue (fluent conversation)", "display":"Language", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill05": { "name":"", "enhancer":"", "text":"Language: Fae (completely fluent; literate)", "display":"Language", "attribute":"GENERAL", "base":"4", "levels":"0", "cost":"4" }, "skill06": { "name":"", "enhancer":"", "text":"+3 Battle Axe", "display":"Combat Skill Levels", "attribute":"GENERAL", "base":"6", "levels":"3", "cost":"6" }, "skill07": { "name":"Fae Society", "enhancer":"", "text":"KS 11-", "display":"KS", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill08": { "name":"Clan Lands", "enhancer":"", "text":"AK 11-", "display":"Knowledge Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill09": { "name":"Common Melee", "enhancer":"", "text":"WF: Common Melee Weapons", "display":"Weapon Familiarity", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill10": { "name":"Power Skill Fae Magic", "enhancer":"", "text":"Power 15-", "display":"Power", "attribute":"INT", "base":"7", "levels":"2", "cost":"7" }, "skill11": { "name":"", "enhancer":"", "text":"Stealth 12-", "display":"Stealth", "attribute":"DEX", "base":"3", "levels":"0", "cost":"3" }, "skill12": { "name":"", "enhancer":"", "text":"Teamwork 12-", "display":"Teamwork", "attribute":"DEX", "base":"3", "levels":"0", "cost":"3" }, "skill13": { "name":"", "enhancer":"", "text":"Concealment 13-", "display":"Concealment", "attribute":"INT", "base":"3", "levels":"0", "cost":"3" }, "skill14": { "name":"", "enhancer":"", "text":"Science Skill: Herbal Medicine 11-", "display":"Science Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill15": { "name":"", "enhancer":"", "text":"Paramedics 13-", "display":"Paramedics", "attribute":"INT", "base":"3", "levels":"0", "cost":"3" }, "skill16": { "name":"", "enhancer":"", "text":"Survival (Temperate/Subtropical) 13-", "display":"Survival", "attribute":"INT", "base":"2", "levels":"0", "cost":"2" }, "skill17": { }, "skill18": { }, "skill19": { }, "skill20": { }, "skill21": { }, "skill22": { }, "skill23": { }, "skill24": { }, "skill25": { }, "skill26": { }, "skill27": { }, "skill28": { }, "skill29": { }, "skill30": { }, "skill31": { }, "skill32": { }, "skill33": { }, "skill34": { }, "skill35": { }, "skill36": { }, "skill37": { }, "skill38": { }, "skill39": { }, "skill40": { }, "skill41": { }, "skill42": { }, "skill43": { }, "skill44": { }, "skill45": { }, "skill46": { }, "skill47": { }, "skill48": { }, "skill49": { }, "skill50": { } }, "playerName":"Test PC #1", "gmName":"Villain In Glasses", "characterFile":"Sample Character 1.hdc", "template":"builtIn.Heroic6E.hdt", "versionHD":"20220801", "timeStamp":"Thu, 20 Feb 2025 23:43:48", "genre":"Fantasy HERO", "campaign":"Sample", "version":"2.4", "HeroSystem6eHeroic":"true" } }
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.hdc b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.hdc
new file mode 100644
index 0000000000..961ec9c6b9
Binary files /dev/null and b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character.hdc differ
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.TXT b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.TXT
new file mode 100644
index 0000000000..1c889ee020
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.TXT
@@ -0,0 +1 @@
+ !hero --import { "character":{ "character_name":"Henkle", "character_title":"Clan Doctor", "height":"1.76 m", "weight":"87.00 kg", "eyes":"Green", "hair":"Sandy", "backgroundText":"Henkle is a learned man, trained as a physician by the best court instructors and an expert swordsman. He once had a promising future serving clan royalty. It all fell to pieces when he misinterpreted a joke and subsequently dug himself into a ever deepening hole. Lucky to be alive, he found himself banished. The Company scooped him up after a particularly self destructive drinking binge.", "historyText":"", "appearance":"Like many a clansman, Henkle is not small and his lack of social awareness makes for an intimidating block of a man.", "tactics":"", "campaignUse":"As part of his education, Henkle dabbled in Wizardy and can cast a couple of spells, including a minor healing spell and a light spell.", "quote":"Banished aristocrat", "experience":"0", "experienceBenefit":"0", "size":"", "strength":"18", "dexterity":"15", "constitution":"13", "intelligence":"15", "ego":"14", "presence":"15", "ocv":"5", "dcv":"4", "omcv":"3", "dmcv":"4", "speed":"3", "pd":"4", "ed":"4", "body":"15", "stun":"36", "endurance":"40", "recovery":"7", "running":"12", "leaping":"4", "swimming":"0", "equipment":{ "equipment01":{ "name":"Light Maille", "text":"Resistant Protection (5 PD/5 ED) (15 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4), Requires A Roll (12- roll; Locations 7-14; -1/4)", "damage":"", "end":"0", "range":"", "mass":"11.40kg", "attack":"", "defense":"true", "notes":"(1 END/turn)" }, "equipment02":{ "name":"Open-face Helm", "text":"Resistant Protection (7 PD/7 ED) (21 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4)", "damage":"", "end":"0", "range":"", "mass":"1.17kg", "attack":"", "defense":"true", "notes":"(Locations 4-5)" }, "equipment03":{ "name":"High Boots, Gloves", "text":"Resistant Protection (2 PD/2 ED) (6 Active Points); Normal Mass (-1), OIF (-1/2), Real Armor (-1/4)", "damage":"", "end":"0", "range":"", "mass":"2.60kg", "attack":"", "defense":"true", "notes":"(Locations 16-18, 6-7)" }, "equipment04":{ "name":"Arming Sword", "text":"(Total: 31 Active Cost, 12 Real Cost) Killing Attack - Hand-To-Hand 1d6+1 (1 1/2d6 w/STR), Reduced Endurance (0 END; +1/2) (30 Active Points); OAF (-1), STR Minimum 12 (-1/2), Real Weapon (-1/4) (Real Cost: 11) plus (1 Active Points) (Real Cost: 1)", "damage":"1d6+1 (1 1/2d6 w/STR)", "end":"0", "range":"", "mass":"1.20kg", "attack":"true", "defense":"", "notes":"" }, "equipment05":{ "name":"Long Sword", "text":"(Total: 38 Active Cost, 13 Real Cost) Killing Attack - Hand-To-Hand 1 1/2d6 (2d6 w/STR), Reduced Endurance (0 END; +1/2) (37 Active Points); OAF (-1), STR Minimum 13 (-1/2), Real Weapon (-1/4), Required Hands One-And-A-Half-Handed (-1/4) (Real Cost: 12) plus (1 Active Points) (Real Cost: 1)", "damage":"1 1/2d6 (2d6 w/STR)", "end":"0", "range":"", "mass":"1.70kg", "attack":"true", "defense":"", "notes":"" }, "equipment06":{ "name":"Knife", "text":"Killing Attack - Hand-To-Hand 1/2d6 (1d6+1 w/STR), Range Based On STR (+1/4), Reduced Endurance (0 END; +1/2) (17 Active Points); OAF (-1), Real Weapon (-1/4), STR Minimum 4 (-1/4)", "damage":"1/2d6 (1d6+1 w/STR)", "end":"0", "range":"var.", "mass":"0.40kg", "attack":"true", "defense":"", "notes":"(x2 number of items)" }, "equipment07":{ "name":"Healer's Bag", "text":"+1 Paramedics (2 Active Points); OAF (-1)", "damage":"", "end":"", "range":"", "mass":"0.50kg", "attack":"", "defense":"", "notes":"" }, "equipment08":{ "name":"Cooking Kit", "text":"+1 with Cooking (2 Active Points); OAF (-1)", "damage":"", "end":"", "range":"", "mass":"1.00kg", "attack":"", "defense":"", "notes":"" }, "equipment09":{ "name":"Magnifying Glass", "text":"Microscopic ( x10) with Sight Group (5 Active Points); OAF (-1)", "damage":" x10", "end":"0", "range":"", "mass":"0.25kg", "attack":"", "defense":"", "notes":"" }, "equipment10":{ "name":"Lantern", "text":"Sight Group Images, +/-4 to PER Rolls, Reduced Endurance (0 END; +1/2), Area Of Effect (8m Radius; +1), Mobile (1m per Phase; +1/2) (55 Active Points); Only To Create Light (-1), OAF (-1), Extra Time (1 Minute, Only to Activate, -3/4), No Range (-1/2), Real Weapon (-1/4), 1 Continuing Fuel Charge lasting 6 Hours (-0), Required Hands One-Handed (-0)", "damage":"", "end":"[1 cc]", "range":"", "mass":"1.00kg", "attack":"true", "defense":"", "notes":"" }, "equipment11":{ "name":"Warm Blanket", "text":"Change Environment (+2 Temperature Level Adjustment), Reduced Endurance (0 END; +1/2) (9 Active Points); OAF (-1), No Range (-1/2), Self Only (-1/2)", "damage":"", "end":"0", "range":"", "mass":"0.50kg", "attack":"true", "defense":"", "notes":"" }, "equipment12":{}, "equipment13":{}, "equipment14":{}, "equipment15":{}, "equipment16":{}, "equipment17":{}, "equipment18":{}, "equipment19":{}, "equipment20":{}, "equipment21":{}, "equipment22":{}, "equipment23":{}, "equipment24":{}, "equipment25":{}, "equipment26":{}, "equipment27":{}, "equipment28":{}, "equipment29":{}, "equipment30":{}, "equipment31":{}, "equipment32":{}, "equipment33":{}, "equipment34":{}, "equipment35":{}, "equipment36":{}, "equipment37":{}, "equipment38":{}, "equipment39":{}, "equipment40":{}, "equipment41":{}, "equipment42":{}, "equipment43":{}, "equipment44":{}, "equipment45":{}, "equipment46":{}, "equipment47":{}, "equipment48":{}, "equipment49":{}, "equipment50":{} }, "maneuvers":{ "maneuver01":{ "name":"Slash", "points":"4", "phase":"1/2", "ocv":"+0", "dcv":"+2", "effect":"Weapon +2 DC Strike", "notes":"" }, "maneuver02":{ "name":"Parry", "points":"4", "phase":"1/2", "ocv":"+2", "dcv":"+2", "effect":"Block, Abort", "notes":"" }, "maneuver03":{ "name":"Counterstrike", "points":"4", "phase":"1/2", "ocv":"+2", "dcv":"+2", "effect":"Weapon +2 DC Strike, Must Follow Block", "notes":"" }, "maneuver04":{ "name":"Half-Sword Disarm", "points":"4", "phase":"1/2", "ocv":"-1", "dcv":"+1", "effect":"Disarm, 28 STR to Disarm roll, Requires Both Hands", "notes":"" }, "maneuver05":{ "name":"Half-Sword Trip", "points":"3", "phase":"1/2", "ocv":"+2", "dcv":"+0", "effect":"Weapon Strike, Target Falls, Requires Both Hands", "notes":"" }, "maneuver06":{ }, "maneuver07":{ }, "maneuver08":{ }, "maneuver09":{ }, "maneuver10":{ }, "maneuver11":{ }, "maneuver12":{ }, "maneuver13":{ }, "maneuver14":{ }, "maneuver15":{ }, "maneuver16":{ }, "maneuver17":{ }, "maneuver18":{ "name":"Weapon Element: Blades", "points":"0", "phase":"", "ocv":"", "dcv":"", "effect":"", "notes":"" }, "maneuver19":{ }, "maneuver20":{ } }, "perks":{ "perk01":{ "type":"Fringe Benefit", "points":"2", "text":"Fringe Benefit: Sergeant", "notes":"" }, "perk02":{ "type":"Positive Reputation", "points":"3", "text":"Positive Reputation: Brillaint Doctor (A medium-sized group) 11-, +3/+3d6", "notes":"" }, "perk03":{ "type":"Fringe Benefit", "points":"1", "text":"Company Soldier Fringe Benefit: Membership", "notes":"" }, "perk04":{ }, "perk05":{ }, "perk06":{ }, "perk07":{ }, "perk08":{ }, "perk09":{ }, "perk10":{ } }, "talents":{}, "complications":{ "complication01":{ "type":"Hunted", "points":"15", "text":"Hunted: King's Church Frequently (Mo Pow; NCI; Watching)", "notes":"" }, "complication02":{ "type":"Psychological Complication", "points":"10", "text":"Psychological Complication: Airhead (Common; Moderate)", "notes":"This character is not truly stupid, but rather a bit slower on the uptake than the average person. The character needs to have jokes explained, doesn't understand situations that call for subtlety and wit, and tends to take sarcasm literally. While this condition is hardly debilitating, it does frequently cause the character to be the target of jokes, and causes a certain skepticism regarding the character's intelligence." }, "complication03":{ "type":"Psychological Complication", "points":"10", "text":"Psychological Complication: Aristocratic Attitude (Common; Moderate)", "notes":"This character speaks and acts as if he were royalty. He is stand-offish but polite, proper at all times, speaks impeccably, and expects his commands to be followed. To other characters, he is obviously stuck up and feels is 'too good' for other people." }, "complication04":{ "type":"Physical Complication", "points":"15", "text":"Physical Complication: Horrible Hangovers (Infrequently; Greatly Impairing)", "notes":"Pounding headaches, nausea, and light sensitivity. After waking up from a night of drinking the character suffers a -4 penalty to all rolls for 6 hours." }, "complication05":{}, "complication06":{}, "complication07":{}, "complication08":{}, "complication09":{}, "complication10":{}, "complication11":{}, "complication12":{}, "complication13":{}, "complication14":{}, "complication15":{}, "complication16":{}, "complication17":{}, "complication18":{}, "complication19":{}, "complication20":{} }, "powers":{ "power01":{ "name":"Reknit Flesh", "base":"20", "text":"Healing BODY 2d6 (20 Active Points); Increased Endurance Cost (x5 END; -2), Extra Time (1 Turn (Post-Segment 12), -1 1/4), Gestures (Requires both hands; -1/2), Requires A Roll (Wizardry; -1/2), Incantations (-1/4), IIF Expendable (Herbal Ointment; Easy to obtain new Focus; -1/4)", "notes":"Magical energies, if one understands them well enough, can be set to stitching a wound or coaxing the body to more rapidly repair the trauma of a hematoma.", "cost":"3", "endurance":"10", "damage":"2d6", "compound":"false" }, "power02":{ "name":"Light", "base":"22", "text":"Sight Group Images, +/-4 to PER Rolls, Area Of Effect (4m Radius; +1/4) (27 Active Points); Only To Create Light (-1), Gestures (Requires both hands; -1/2), Requires A Roll (Wizardry; -1/2), IIF Expendable (Difficult to obtain new Focus; Charcoal coated in saltpeter; -1/2), Incantations (-1/4), Extra Time (Full Phase, Only to Activate, -1/4), 2 Continuing Charges lasting 1 Hour each (-0)", "notes":"If magic ever had a use it would be to enable one continue study late into the night.", "cost":"7", "endurance":"[2 cc]", "damage":"", "compound":"false" }, "power03":{ }, "power04":{ }, "power05":{ }, "power06":{ }, "power07":{ }, "power08":{ }, "power09":{ }, "power10":{ }, "power11":{ }, "power12":{ }, "power13":{ }, "power14":{ }, "power15":{ }, "power16":{ }, "power17":{ }, "power18":{ }, "power19":{ }, "power20":{ }, "power21":{ }, "power22":{ }, "power23":{ }, "power24":{ }, "power25":{ }, "power26":{ }, "power27":{ }, "power28":{ }, "power29":{ }, "power30":{ }, "power31":{ }, "power32":{ }, "power33":{ }, "power34":{ }, "power35":{ }, "power36":{ }, "power37":{ }, "power38":{ }, "power39":{ }, "power40":{ }, "power41":{ }, "power42":{ }, "power43":{ }, "power44":{ }, "power45":{ }, "power46":{ }, "power47":{ }, "power48":{ }, "power49":{ }, "power50":{ } }, "skills": { "skill01": { "name":"", "enhancer":"", "text":"PS: Soldier 11-", "display":"Professional Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill02": { "name":"", "enhancer":"", "text":"PS: Doctor 11-", "display":"Professional Skill", "attribute":"GENERAL", "base":"0", "levels":"0", "cost":"0" }, "skill03": { "name":"Power Skill Wizardry", "enhancer":"", "text":": Wizardry 12-", "display":"Power", "attribute":"INT", "base":"3", "levels":"0", "cost":"3" }, "skill04": { "name":"", "enhancer":"", "text":"Paramedics 13-", "display":"Paramedics", "attribute":"INT", "base":"5", "levels":"1", "cost":"5" }, "skill05": { "name":"", "enhancer":"", "text":"Science Skill: Medicine 13-", "display":"Science Skill", "attribute":"GENERAL", "base":"4", "levels":"2", "cost":"4" }, "skill06": { "name":"", "enhancer":"", "text":"Science Skill: Anatomy 11-", "display":"Science Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill07": { "name":"", "enhancer":"", "text":"KS: Herbalism 11-", "display":"KS", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill08": { "name":"", "enhancer":"", "text":"High Society 12-", "display":"High Society", "attribute":"PRE", "base":"3", "levels":"0", "cost":"3" }, "skill09": { "name":"", "enhancer":"", "text":"KS: Popular Literature 11-", "display":"KS", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill10": { "name":"", "enhancer":"", "text":"CuK: Popular Entertainment 11-", "display":"Knowledge Skill", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill11": { "name":"", "enhancer":"", "text":"Conversation 12-", "display":"Conversation", "attribute":"PRE", "base":"3", "levels":"0", "cost":"3" }, "skill12": { "name":"", "enhancer":"", "text":"Tactics 12-", "display":"Tactics", "attribute":"INT", "base":"3", "levels":"0", "cost":"3" }, "skill13": { "name":"", "enhancer":"", "text":"Teamwork 12-", "display":"Teamwork", "attribute":"DEX", "base":"3", "levels":"0", "cost":"3" }, "skill14": { "name":"", "enhancer":"true", "text":"Linguist", "display":"Linguist", "attribute":"", "base":"3", "levels":"0", "cost":"3" }, "skill15": { "name":"", "enhancer":"", "text":"Language: Ancient Elven (basic conversation; literate) (2 Active Points)", "display":"Language", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"1" }, "skill16": { "name":"", "enhancer":"", "text":"Language: Clans' Tongue (idiomatic; literate) (5 Active Points)", "display":"Language", "attribute":"GENERAL", "base":"5", "levels":"0", "cost":"0" }, "skill17": { "name":"", "enhancer":"", "text":"Language: King's Tongue (fluent conversation; literate) (3 Active Points)", "display":"Language", "attribute":"GENERAL", "base":"3", "levels":"0", "cost":"2" }, "skill18": { "name":"", "enhancer":"", "text":"Language: Southern Tongue (fluent conversation; literate) (3 Active Points)", "display":"Language", "attribute":"GENERAL", "base":"3", "levels":"0", "cost":"2" }, "skill19": { "name":"", "enhancer":"", "text":"WF: Common Melee Weapons", "display":"Weapon Familiarity", "attribute":"GENERAL", "base":"2", "levels":"0", "cost":"2" }, "skill20": { "name":"", "enhancer":"", "text":"+3 Long Sword", "display":"Combat Skill Levels", "attribute":"GENERAL", "base":"6", "levels":"3", "cost":"6" }, "skill21": { "name":"", "enhancer":"", "text":"Defense Maneuver I-II ", "display":"Defense Maneuver", "attribute":"GENERAL", "base":"5", "levels":"0", "cost":"5" }, "skill22": { }, "skill23": { }, "skill24": { }, "skill25": { }, "skill26": { }, "skill27": { }, "skill28": { }, "skill29": { }, "skill30": { }, "skill31": { }, "skill32": { }, "skill33": { }, "skill34": { }, "skill35": { }, "skill36": { }, "skill37": { }, "skill38": { }, "skill39": { }, "skill40": { }, "skill41": { }, "skill42": { }, "skill43": { }, "skill44": { }, "skill45": { }, "skill46": { }, "skill47": { }, "skill48": { }, "skill49": { }, "skill50": { } }, "playerName":"Test PC #2", "gmName":"Villain in Glasses", "characterFile":"Sample Character 2.hdc", "template":"builtIn.Heroic6E.hdt", "versionHD":"20220801", "timeStamp":"Thu, 20 Feb 2025 23:44:03", "genre":"Fantasy HERO", "campaign":"Sample", "version":"2.4", "HeroSystem6eHeroic":"true" } }
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.hdc b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.hdc
new file mode 100644
index 0000000000..69a111bb0d
Binary files /dev/null and b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Character_MA.hdc differ
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.TXT b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.TXT
new file mode 100644
index 0000000000..0353217e92
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.TXT
@@ -0,0 +1 @@
+ !hero --import { "character":{ "character_name":"Chariot", "character_title":"APC", "height":"2.00 m", "weight":"100.00 kg", "eyes":"Brown", "hair":"Brown", "backgroundText":"The Chariot is an armored car typical of armored vehicles in use on Earth and Mars as well as large moons such as Luna and Ganymede with the gravity that can support them. The Chariot has a hard point that may be used for a light weapon turret, which here is a 2-kJ Heavy Infantry Laser. Chariots have life support and environmental protection.", "historyText":"", "appearance":"", "tactics":"", "campaignUse":"", "quote":"", "experience":"0", "experienceBenefit":"22", "size":"5", "strength":"35", "dexterity":"15", "constitution":"", "intelligence":"", "ego":"", "presence":"", "ocv":"4", "dcv":"4", "omcv":"", "dmcv":"", "speed":"3", "pd":"6", "ed":"6", "body":"15", "stun":"", "endurance":"", "recovery":"", "running":"0", "leaping":"0", "swimming":"0", "equipment":{ "equipment01":{}, "equipment02":{}, "equipment03":{}, "equipment04":{}, "equipment05":{}, "equipment06":{}, "equipment07":{}, "equipment08":{}, "equipment09":{}, "equipment10":{}, "equipment11":{}, "equipment12":{}, "equipment13":{}, "equipment14":{}, "equipment15":{}, "equipment16":{}, "equipment17":{}, "equipment18":{}, "equipment19":{}, "equipment20":{}, "equipment21":{}, "equipment22":{}, "equipment23":{}, "equipment24":{}, "equipment25":{}, "equipment26":{}, "equipment27":{}, "equipment28":{}, "equipment29":{}, "equipment30":{}, "equipment31":{}, "equipment32":{}, "equipment33":{}, "equipment34":{}, "equipment35":{}, "equipment36":{}, "equipment37":{}, "equipment38":{}, "equipment39":{}, "equipment40":{}, "equipment41":{}, "equipment42":{}, "equipment43":{}, "equipment44":{}, "equipment45":{}, "equipment46":{}, "equipment47":{}, "equipment48":{}, "equipment49":{}, "equipment50":{} }, "maneuvers":{ "maneuver01":{ }, "maneuver02":{ }, "maneuver03":{ }, "maneuver04":{ }, "maneuver05":{ }, "maneuver06":{ }, "maneuver07":{ }, "maneuver08":{ }, "maneuver09":{ }, "maneuver10":{ }, "maneuver11":{ }, "maneuver12":{ }, "maneuver13":{ }, "maneuver14":{ }, "maneuver15":{ }, "maneuver16":{ }, "maneuver17":{ }, "maneuver18":{ }, "maneuver19":{ }, "maneuver20":{ } }, "perks":{}, "talents":{ "talent01":{ "type":"Bump Of Direction", "points":"2", "text":"Geolocation Computer Bump Of Direction (3 Active Points); OIF (-1/2)", "notes":"" }, "talent02":{ "type":"Absolute Time Sense", "points":"2", "text":"Vehicle Clock Absolute Time Sense (3 Active Points); OIF (-1/2)", "notes":"" }, "talent03":{ "type":"Absolute Range Sense", "points":"1", "text":"Laser Range Finder ", "notes":"" }, "talent04":{ }, "talent05":{ }, "talent06":{ }, "talent07":{ }, "talent08":{ }, "talent09":{ }, "talent10":{ } }, "complications":{ "complication01":{ "type":"Distinctive Features", "points":"25", "text":"Distinctive Features: Military or police vehicle; causes fear (Not Concealable; Extreme Reaction; Detectable By Commonly-Used Senses)", "notes":"" }, "complication02":{ }, "complication03":{ }, "complication04":{ }, "complication05":{}, "complication06":{}, "complication07":{}, "complication08":{}, "complication09":{}, "complication10":{}, "complication11":{}, "complication12":{}, "complication13":{}, "complication14":{}, "complication15":{}, "complication16":{}, "complication17":{}, "complication18":{}, "complication19":{}, "complication20":{} }, "powers":{ "power01":{ "name":"Armor Plating", "base":"18", "text":"Resistant Protection (6 PD/6 ED), Hardened (+1/4) (22 Active Points); OAF Bulky (-1 1/2), Requires A Roll (14- roll; -1/4)", "notes":"", "cost":"8", "endurance":"0", "damage":"", "compound":"false" }, "power02":{ "name":"Motorized Vehicle", "base":"50", "text":"Ground Movement +45m (45m total), x4 Noncombat (50 Active Points); OAF Bulky (-1 1/2), Only Appropriate Terrain Power loses about a fourth of its effectiveness (-1/4), 1 Continuing Fuel Charge lasting 1 Day (-0)", "notes":"", "cost":"18", "endurance":"", "damage":"", "compound":"false" }, "power03":{ "name":"Main Power", "base":"11", "text":"Endurance Reserve (44 END, 45 REC) Reserve: (41 Active Points); OAF Bulky (-1 1/2), Only for Electrical Devices Power loses about a fourth of its effectiveness (-1/4), 1 Continuing Fuel Charge lasting 1 Day (-0)", "notes":"", "cost":"34", "endurance":"[1 cc]", "damage":"", "compound":"false" }, "power04":{ "name":"Thermoelectric Generator", "base":"6", "text":"Endurance Reserve (24 END, 14 REC) Reserve: (16 Active Points); OAF Bulky (-1 1/2), Only for Electrical Devices Power loses about a fourth of its effectiveness (-1/4)", "notes":"", "cost":"12", "endurance":"0", "damage":"", "compound":"false" }, "power05":{ "name":"2-kJ Pulse Laser", "base":"51", "text":"(Total: 85 Active Cost, 27 Real Cost) Killing Attack - Ranged 3d6, +1 Increased STUN Multiplier (+1/4), Invisible Power Effects (Invisible to Sight; +1/4), Autofire (3 shots; +1/4) (79 Active Points); OAF Bulky (-1 1/2), Real Weapon (-1/4), Beam (-1/4), Limited Power Power loses about a fourth of its effectiveness (Gets Hot! Burn danger and IR liability; -1/4) (Real Cost: 24) plus +1 with any single attack (2 Active Points); OAF (-1) (Real Cost: 1) plus +4 to offset RMod (4 Active Points); OAF (-1) (Real Cost: 2)", "notes":"", "cost":"27", "endurance":"8", "damage":"3d6", "compound":"true" }, "power06":{ "name":"Radiation Shield", "base":"2", "text":"Life Support (Safe in High Radiation) (2 Active Points); OIF Bulky (-1)", "notes":"", "cost":"1", "endurance":"0", "damage":"", "compound":"false" }, "power07":{ "name":"Primary Life Support", "base":"4", "text":"Life Support (Safe in Intense Cold; Safe in Low Pressure/Vacuum) (4 Active Points); OIF Bulky (-1), Costs Half Endurance (-1/4)", "notes":"", "cost":"2", "endurance":"1", "damage":"", "compound":"false" }, "power08":{ "name":"Backup Life Support", "base":"4", "text":"Life Support (Safe in Intense Cold; Safe in Low Pressure/Vacuum) (4 Active Points); OIF Bulky (-1), 1 Continuing Charge lasting 6 Hours (-0)", "notes":"", "cost":"2", "endurance":"[1 cc]", "damage":"", "compound":"false" }, "power09":{ "name":"NBC-Proofed Seals", "base":"10", "text":"Life Support (Immunity All terrestrial poisons; Immunity: All terrestrial diseases) (10 Active Points); OIF Bulky (-1)", "notes":"", "cost":"5", "endurance":"0", "damage":"", "compound":"false" }, "power10":{ "name":"Food Supplies", "base":"3", "text":"Life Support (Eating: Character does not eat); 1 Continuing Charge lasting 1 Week (-0)", "notes":"", "cost":"3", "endurance":"[1 cc]", "damage":"", "compound":"false" }, "power11":{ "name":"Airlock", "base":"32", "text":"Change Environment (Long-Lasting 6 Hours, Suffocation), Varying Effect Very Limited Group (Restore or Remove Normal Pressure and Breathing Gases; +1/4), Area Of Effect (4 2m Areas; +1/2) (56 Active Points); OIF Bulky (-1), No Range (-1/2)", "notes":"Entire passenger compartment.", "cost":"22", "endurance":"6", "damage":"", "compound":"false" }, "power12":{ "name":"Medical Supplies", "base":"6", "text":"+3 Paramedics (6 Active Points); OAF (-1), 12 Charges (-1/4)", "notes":"", "cost":"3", "endurance":"", "damage":"", "compound":"false" }, "power13":{ "name":"MediScanner", "base":"12", "text":"Detect Vital Signs 11- (Unusual Group), Discriminatory (12 Active Points); OAF Bulky (-1 1/2), Requires A Roll (Skill roll; Paramedics or Medicine; -1/2), Costs Endurance (-1/2)", "notes":"", "cost":"3", "endurance":"1", "damage":"11-", "compound":"false" }, "power14":{ "name":"Spotlight", "base":"22", "text":"Sight Group Images, +/-4 to PER Rolls, Reduced Endurance (1/2 END; +1/4), Area Of Effect (64m Cone; +1), Thin Cone (-1/4) (44 Active Points); Only To Create Light (-1), OAF (-1), No Range (-1/2)", "notes":"", "cost":"12", "endurance":"2", "damage":"", "compound":"false" }, "power15":{ "name":"Area Lights", "base":"22", "text":"Sight Group Images, +/-4 to PER Rolls, Area Of Effect (4m Radius; +1/4), Reduced Endurance (1/2 END; +1/4) (33 Active Points); Only To Create Light (-1), OAF (-1), No Range (-1/2)", "notes":"", "cost":"9", "endurance":"1", "damage":"", "compound":"false" }, "power16":{ "name":"Nav Computer", "base":"4", "text":"+2 with Navigation (4 Active Points); OAF (-1)", "notes":"", "cost":"2", "endurance":"", "damage":"", "compound":"false" }, "power17":{ "name":"Area Cameras", "base":"10", "text":"Increased Arc Of Perception (360 Degrees) with Sight Group (10 Active Points); OAF (-1)", "notes":"", "cost":"5", "endurance":"0", "damage":"", "compound":"false" }, "power18":{ "name":"HRRP Radio", "base":"10", "text":"Radio Perception/Transmission (Radio Group) (10 Active Points); Affects sight and hearing as well as radio [very common Sense] (-1/2), Costs Half Endurance (-1/4)", "notes":"", "cost":"6", "endurance":"1", "damage":"", "compound":"false" }, "power19":{ "name":"Telescope", "base":"9", "text":"+6 versus Range Modifier for Sight Group (9 Active Points); OAF (-1), Costs Half Endurance (-1/4)", "notes":"", "cost":"4", "endurance":"1", "damage":" +6 versus Range Modifier", "compound":"false" }, "power20":{ "name":"Image Enhancer", "base":"2", "text":"+2 PER with Normal Sight (2 Active Points); OAF (-1)", "notes":"", "cost":"1", "endurance":"0", "damage":"", "compound":"false" }, "power21":{ "name":"Geiger Counter", "base":"12", "text":"Detect Radiation A Class Of Things 11- (Unusual Group), Discriminatory (12 Active Points); OAF (-1)", "notes":"", "cost":"6", "endurance":"0", "damage":"11-", "compound":"false" }, "power22":{ }, "power23":{ }, "power24":{ }, "power25":{ }, "power26":{ }, "power27":{ }, "power28":{ }, "power29":{ }, "power30":{ }, "power31":{ }, "power32":{ }, "power33":{ }, "power34":{ }, "power35":{ }, "power36":{ }, "power37":{ }, "power38":{ }, "power39":{ }, "power40":{ }, "power41":{ }, "power42":{ }, "power43":{ }, "power44":{ }, "power45":{ }, "power46":{ }, "power47":{ }, "power48":{ }, "power49":{ }, "power50":{ } }, "skills": { "skill01": { "name":"Nav Computer", "enhancer":"", "text":"+2 with Navigation", "display":"Skill Levels", "attribute":"GENERAL", "base":"4", "levels":"2", "cost":"4" }, "skill02": { }, "skill03": { }, "skill04": { }, "skill05": { }, "skill06": { }, "skill07": { }, "skill08": { }, "skill09": { }, "skill10": { }, "skill11": { }, "skill12": { }, "skill13": { }, "skill14": { }, "skill15": { }, "skill16": { }, "skill17": { }, "skill18": { }, "skill19": { }, "skill20": { }, "skill21": { }, "skill22": { }, "skill23": { }, "skill24": { }, "skill25": { }, "skill26": { }, "skill27": { }, "skill28": { }, "skill29": { }, "skill30": { }, "skill31": { }, "skill32": { }, "skill33": { }, "skill34": { }, "skill35": { }, "skill36": { }, "skill37": { }, "skill38": { }, "skill39": { }, "skill40": { }, "skill41": { }, "skill42": { }, "skill43": { }, "skill44": { }, "skill45": { }, "skill46": { }, "skill47": { }, "skill48": { }, "skill49": { }, "skill50": { } }, "playerName":"Vehicle", "gmName":"Villain in Glasses", "characterFile":"Sample_Vehicle_APC.hdc", "template":"builtIn.Vehicle6E.hdt", "versionHD":"20220801", "timeStamp":"Sun, 2 Mar 2025 22:18:34", "genre":"Star HERO", "campaign":"Sample", "version":"2.4", "HeroSystem6eHeroic":"true" } }
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.hdc b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.hdc
new file mode 100644
index 0000000000..c9df69b564
Binary files /dev/null and b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_APC.hdc differ
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.TXT b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.TXT
new file mode 100644
index 0000000000..5eb20c8cb0
--- /dev/null
+++ b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.TXT
@@ -0,0 +1 @@
+ !hero --import { "character":{ "character_name":"Blue Box", "character_title":"Type 40", "height":"2.00 m", "weight":"100.00 kg", "eyes":"Brown", "hair":"Brown", "backgroundText":"A simple blue box, which can take you anywhere in time or space. Not necessarily where you want to go, but definitely where you need to go.", "historyText":"", "appearance":"Exterior physical dimensions are 2 m tall by 1 m wide with a mass of 6400 kg (reduced by a factor of 256).", "tactics":"", "campaignUse":"", "quote":"", "experience":"0", "experienceBenefit":"0", "size":"24", "strength":"130", "dexterity":"10", "constitution":"", "intelligence":"", "ego":"", "presence":"", "ocv":"5", "dcv":"5", "omcv":"", "dmcv":"", "speed":"4", "pd":"0", "ed":"0", "body":"50", "stun":"", "endurance":"", "recovery":"", "running":"0", "leaping":"0", "swimming":"0", "equipment":{ "equipment01":{}, "equipment02":{}, "equipment03":{}, "equipment04":{}, "equipment05":{}, "equipment06":{}, "equipment07":{}, "equipment08":{}, "equipment09":{}, "equipment10":{}, "equipment11":{}, "equipment12":{}, "equipment13":{}, "equipment14":{}, "equipment15":{}, "equipment16":{}, "equipment17":{}, "equipment18":{}, "equipment19":{}, "equipment20":{}, "equipment21":{}, "equipment22":{}, "equipment23":{}, "equipment24":{}, "equipment25":{}, "equipment26":{}, "equipment27":{}, "equipment28":{}, "equipment29":{}, "equipment30":{}, "equipment31":{}, "equipment32":{}, "equipment33":{}, "equipment34":{}, "equipment35":{}, "equipment36":{}, "equipment37":{}, "equipment38":{}, "equipment39":{}, "equipment40":{}, "equipment41":{}, "equipment42":{}, "equipment43":{}, "equipment44":{}, "equipment45":{}, "equipment46":{}, "equipment47":{}, "equipment48":{}, "equipment49":{}, "equipment50":{} }, "maneuvers":{ "maneuver01":{ }, "maneuver02":{ }, "maneuver03":{ }, "maneuver04":{ }, "maneuver05":{ }, "maneuver06":{ }, "maneuver07":{ }, "maneuver08":{ }, "maneuver09":{ }, "maneuver10":{ }, "maneuver11":{ }, "maneuver12":{ }, "maneuver13":{ }, "maneuver14":{ }, "maneuver15":{ }, "maneuver16":{ }, "maneuver17":{ }, "maneuver18":{ }, "maneuver19":{ }, "maneuver20":{ } }, "perks":{}, "talents":{ "talent01":{ "type":"Absolute Range Sense", "points":"3", "text":" Absolute Range Sense", "notes":"" }, "talent02":{ "type":"Absolute Time Sense", "points":"3", "text":" Absolute Time Sense", "notes":"" }, "talent03":{ "type":"Bump Of Direction", "points":"3", "text":"", "notes":"" }, "talent04":{ "type":"Eidetic Memory", "points":"5", "text":" Eidetic Memory", "notes":"" }, "talent05":{ "type":"Danger Sense", "points":"27", "text":"Cloister Bell Danger Sense (any area, out of combat, Function as a Sense) (40 Active Points); IAF (-1/2) 14-", "notes":"" }, "talent06":{ "type":"Universal Translator", "points":"31", "text":"Translation Circuit Universal Translator 14-, Usable By Other (+1), Recipient must be within Standard Range of the Grantor for power to be granted, Grantor can take back power at any time (46 Active Points); IAF (-1/2)", "notes":"" }, "talent07":{ }, "talent08":{ }, "talent09":{ }, "talent10":{ } }, "complications":{ "complication01":{ "type":"Psychological Complication", "points":"10", "text":"Psychological Complication: Mind of its own (Common; Moderate)", "notes":"The Blue Box doesn't always do what you expect it to do." }, "complication02":{ "type":"Unluck: 1d6", "points":"5", "text":"Unluck: 1d6", "notes":"" }, "complication03":{ "type":"Distinctive Features", "points":"5", "text":"Distinctive Features: (Concealable; Noticed and Recognizable; Detectable By Commonly-Used Senses; Not Distinctive In Some Cultures)", "notes":"" }, "complication04":{ }, "complication05":{}, "complication06":{}, "complication07":{}, "complication08":{}, "complication09":{}, "complication10":{}, "complication11":{}, "complication12":{}, "complication13":{}, "complication14":{}, "complication15":{}, "complication16":{}, "complication17":{}, "complication18":{}, "complication19":{}, "complication20":{} }, "powers":{ "power01":{ "name":"Micro-Universe", "base":"48", "text":"Shrinking (0.0078 m tall, 5.96E-6 kg mass, -16 PER Rolls to perceive character, +16 DCV, takes +48m KB), Difficult To Dispel (x2 Active Points; +1/4), Persistent (+1/4), Reduced Endurance (0 END; +1/2) (96 Active Points); OIF Immobile (-1 1/2)", "notes":"", "cost":"38", "endurance":"0", "damage":"0.0078 m tall, 5.96E-6 kg mass, -16 PER Rolls to perceive character, +16 DCV, takes +48m KB", "compound":"false" }, "power02":{ "name":"(Multipower) Transdimensional Drive", "base":"195", "text":"Multipower, 195-point reserve, (195 Active Points); all slots IAF Immobile (-1 1/2)", "notes":"", "cost":"78", "endurance":"", "damage":"", "compound":"false" }, "power03":{ "name":"(MPSlot1) Spatial Shift", "base":"65", "text":"Teleportation 50m, No Relative Velocity, x4 Noncombat, Armor Piercing (+1/4), MegaScale (1m = 1,000 km; +1 3/4*) (195 Active Points); IAF Immobile (-1 1/2), Extra Time (1 Turn (Post-Segment 12), Only to Activate, -3/4), Requires A Roll (Skill roll; Transdimensional Travel; -1/2), Costs Endurance (Only Costs END to Activate; -1/4)", "notes":"", "cost":"10v", "endurance":"19", "damage":"50m", "compound":"false" }, "power04":{ "name":"(MPSlot2) Time and Space Shift", "base":"77", "text":"Extra-Dimensional Movement (Any Point in Time within 50 Billion Years, future or past, Any Physical Location) (77 Active Points); IAF Immobile (-1 1/2), Extra Time (1 Turn (Post-Segment 12), Only to Activate, -3/4), Costs Endurance (-1/2), Requires A Roll (Skill roll; Transdimensional Travel; -1/2)", "notes":"", "cost":"2f", "endurance":"8", "damage":"", "compound":"false" }, "power05":{ "name":"(MPSlot3) HADS", "base":"50", "text":"Extra-Dimensional Movement (Related Group of Points in Time, Limited Group Of Physical Locations), Trigger (Activating the Trigger requires a Zero Phase Action, Trigger requires a Turn or more to reset; Outer Shell Breach; +1/4) (62 Active Points); IAF Immobile (-1 1/2), Costs Endurance (-1/2)", "notes":"", "cost":"2f", "endurance":"6", "damage":"", "compound":"false" }, "power06":{ "name":"Prime Eye Α", "base":"26", "text":"Endurance Reserve (101 END, 100 REC) Reserve: (94 Active Points); OIF Immobile (-1 1/2)", "notes":"", "cost":"78", "endurance":"0", "damage":"", "compound":"false" }, "power07":{ "name":"Prime Eye Ω", "base":"26", "text":"Endurance Reserve (101 END, 100 REC) Reserve: (94 Active Points); OIF Immobile (-1 1/2)", "notes":"", "cost":"78", "endurance":"0", "damage":"", "compound":"false" }, "power08":{ "name":"Tractor Beam", "base":"75", "text":"Telekinesis (50 STR) (75 Active Points); IIF Bulky (-3/4), Requires A Roll (Skill roll; Systems Roll; -1/2), Affects Whole Object (-1/4)", "notes":"", "cost":"30", "endurance":"7", "damage":"(50 STR)", "compound":"false" }, "power09":{ "name":"Chameleon Circuit", "base":"25", "text":"Shape Shift (Sight, Hearing, Smell/Taste and Touch Groups, limited group of shapes), Persistent (+1/4) (31 Active Points); OAF Bulky (-1 1/2)", "notes":"", "cost":"12", "endurance":"3", "damage":"", "compound":"false" }, "power10":{ "name":"Exo-Shell", "base":"120", "text":"Resistant Protection (20 PD/20 ED/20 Mental Defense/20 Power Defense), Hardened (+1/4), Impenetrable (+1/4), Difficult To Dispel (x2 Active Points; +1/4) (210 Active Points); OAF Immobile (-2)", "notes":"", "cost":"70", "endurance":"0", "damage":"", "compound":"false" }, "power11":{ "name":"Force Field Prisms", "base":"150", "text":"Resistant Protection (20 PD/20 ED/20 Mental Defense/20 Power Defense/20 Flash Defense: Radio Group) (Impermeable), Hardened (+1/4), Difficult To Dispel (x2 Active Points; +1/4) (225 Active Points); OAF Immobile (-2), Costs Half Endurance (-1/4)", "notes":"", "cost":"69", "endurance":"11", "damage":"", "compound":"false" }, "power12":{ "name":"Main Door", "base":"50", "text":"Extra-Dimensional Movement (Related Group of Points in Time, Limited Group Of Physical Locations), Usable By Other (+1/4), Grantor can only grant the power to others (62 Active Points); OAF (Exterior Door requires Trimonic Cypher-Indent Key to use; -1), Gestures (-1/4)", "notes":"Space-time bridge between inside and outside.", "cost":"27", "endurance":"0", "damage":"", "compound":"false" }, "power13":{ "name":"Auto-Gravity", "base":"35", "text":"Telekinesis (20 STR), Alterable Origin Point (35 Active Points); OIF Immobile (-1 1/2), Limited Power Only to adjust internal gravity to normal (-1)", "notes":"(ignore)", "cost":"10", "endurance":"3", "damage":"(20 STR)", "compound":"false" }, "power14":{ "name":"Primary Life Support", "base":"22", "text":"Life Support (Eating: Character does not eat; Immunity All terrestrial poisons; Immunity: All terrestrial diseases; Safe in High Pressure; Safe in High Radiation; Safe in Intense Cold; Safe in Intense Heat; Safe in Low Pressure/Vacuum) (22 Active Points); OIF Bulky (-1), Costs Half Endurance (-1/4)", "notes":"", "cost":"10", "endurance":"1", "damage":"", "compound":"false" }, "power15":{ "name":"Backup Life Support", "base":"6", "text":"Life Support (Safe in High Radiation; Safe in Intense Cold; Safe in Low Pressure/Vacuum) (6 Active Points); OIF Bulky (-1), Costs Half Endurance (-1/4)", "notes":"", "cost":"3", "endurance":"1", "damage":"", "compound":"false" }, "power16":{ "name":"Wardrobe", "base":"20", "text":"Minor Transform 4d6 (Change of clothes for any occaision, Changing back into old clothes) (20 Active Points); Extra Time (5 Minutes, -2), OAF Immobile (-2), 16 Charges (-0)", "notes":"", "cost":"4", "endurance":"[16]", "damage":"4d6", "compound":"false" }, "power17":{ "name":"Mediscanner", "base":"13", "text":"Detect Vital Signs 14- (Unusual Group), Discriminatory (13 Active Points); OAF (-1), Requires A Roll (Paramedic or Medicine; -1/2)", "notes":"", "cost":"5", "endurance":"0", "damage":"14-", "compound":"false" }, "power18":{ "name":"Surgical Web", "base":"30", "text":"Healing BODY 3d6 (30 Active Points); Extra Time (1 Hour, -3), OAF Bulky (-1 1/2), Requires A Roll (Skill roll; Paramedics or Medicine; -1/2), Gestures (-1/4)", "notes":"", "cost":"5", "endurance":"3", "damage":"3d6", "compound":"false" }, "power19":{ "name":"Quarantine Bay", "base":"60", "text":"Major Transform 6d6 (Sick Person into Well Person, Exposure to Disease, Poison, or Radiation) (60 Active Points); Extra Time (1 Hour, -3), OAF Bulky (-1 1/2), No Range (-1/2), Gestures (-1/4)", "notes":"", "cost":"10", "endurance":"6", "damage":"6d6", "compound":"false" }, "power20":{ "name":"Workshop", "base":"12", "text":"(Total: 12 Active Cost, 4 Real Cost) +3 Mechanics (6 Active Points); OAF Bulky (-1 1/2) (Real Cost: 2) plus +3 Electronics (6 Active Points); OAF Bulky (-1 1/2) (Real Cost: 2)", "notes":"", "cost":"4", "endurance":"0", "damage":"", "compound":"true" }, "power21":{ "name":"Auto Repair System", "base":"14", "text":"Regeneration (7 BODY per Week) (14 Active Points); OIF Immobile (-1 1/2)", "notes":"", "cost":"6", "endurance":"0", "damage":"7 BODY per Week", "compound":"false" }, "power22":{ "name":"(VPP) Sensors & Comms", "base":"150", "text":"Variable Power Pool (Gadget Pool), 100 base + 100 control cost, (150 Active Points); Limited Power Can only be changed between adventures (-1/2); all slots OIF (-1/2)", "notes":"", "cost":"125", "endurance":"", "damage":"", "compound":"false" }, "power23":{ }, "power24":{ }, "power25":{ }, "power26":{ }, "power27":{ }, "power28":{ }, "power29":{ }, "power30":{ }, "power31":{ }, "power32":{ }, "power33":{ }, "power34":{ }, "power35":{ }, "power36":{ }, "power37":{ }, "power38":{ }, "power39":{ }, "power40":{ }, "power41":{ }, "power42":{ }, "power43":{ }, "power44":{ }, "power45":{ }, "power46":{ }, "power47":{ }, "power48":{ }, "power49":{ }, "power50":{ } }, "skills": { "skill01": { "name":"", "enhancer":"", "text":"Disguise 14-", "display":"Disguise", "attribute":"INT", "base":"13", "levels":"5", "cost":"13" }, "skill02": { "name":"", "enhancer":"", "text":"Concealment 14-", "display":"Concealment", "attribute":"INT", "base":"13", "levels":"5", "cost":"13" }, "skill03": { "name":"Data Core", "enhancer":"", "text":"KS 18-", "display":"KS", "attribute":"GENERAL", "base":"9", "levels":"7", "cost":"9" }, "skill04": { "name":"Navigation Charts", "enhancer":"", "text":"AK: Known Universe 18-", "display":"Knowledge Skill", "attribute":"GENERAL", "base":"9", "levels":"7", "cost":"9" }, "skill05": { "name":"Medicine", "enhancer":"", "text":"Science Skill 14-", "display":"Science Skill", "attribute":"GENERAL", "base":"5", "levels":"3", "cost":"5" }, "skill06": { "name":"", "enhancer":"", "text":"Paramedics 14-", "display":"Paramedics", "attribute":"INT", "base":"13", "levels":"5", "cost":"13" }, "skill07": { }, "skill08": { }, "skill09": { }, "skill10": { }, "skill11": { }, "skill12": { }, "skill13": { }, "skill14": { }, "skill15": { }, "skill16": { }, "skill17": { }, "skill18": { }, "skill19": { }, "skill20": { }, "skill21": { }, "skill22": { }, "skill23": { }, "skill24": { }, "skill25": { }, "skill26": { }, "skill27": { }, "skill28": { }, "skill29": { }, "skill30": { }, "skill31": { }, "skill32": { }, "skill33": { }, "skill34": { }, "skill35": { }, "skill36": { }, "skill37": { }, "skill38": { }, "skill39": { }, "skill40": { }, "skill41": { }, "skill42": { }, "skill43": { }, "skill44": { }, "skill45": { }, "skill46": { }, "skill47": { }, "skill48": { }, "skill49": { }, "skill50": { } }, "playerName":"Vehicle", "gmName":"Villain in Glasses", "characterFile":"Sample_Vehicle_BB.hdc", "template":"builtIn.Vehicle6E.hdt", "versionHD":"20220801", "timeStamp":"Wed, 19 Mar 2025 12:15:12", "genre":"Star HERO", "campaign":"Campy Sci-fi", "version":"2.4", "HeroSystem6eHeroic":"true" } }
\ No newline at end of file
diff --git a/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.hdc b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.hdc
new file mode 100644
index 0000000000..5f5677f28c
Binary files /dev/null and b/HeroSystem6eHeroic_HDImporter/2.4/Sample_Vehicle_BB.hdc differ
diff --git a/HeroSystem6eHeroic_HDImporter/README.MD b/HeroSystem6eHeroic_HDImporter/README.MD
index 457c34347d..d2cc6ce525 100644
--- a/HeroSystem6eHeroic_HDImporter/README.MD
+++ b/HeroSystem6eHeroic_HDImporter/README.MD
@@ -1,13 +1,14 @@
# Hero System 6e Heroic: HD Importer
A script for importing characters built in or acquired for HERO Designer into a Roll20 Hero System 6e Heroic game.
-HD Importer attempts to translate as much of a [HERO Designer](https://www.herogames.com/store/product/1-hero-designer/) character as possible into a Roll20 [HeroSystem6eHeroic](https://github.com/Roll20/roll20-character-sheets/blob/master/HeroSystem6eHeroic/README.md) character sheet. There will invariably be rules, options, or build styles that do not translate well from HERO Designer into Roll20. Nonetheless, this importer should import most of the details of a character.
+*HD Importer* attempts to translate as much of a [HERO Designer](https://www.herogames.com/store/product/1-hero-designer/) character as possible into a Roll20 [HeroSystem6eHeroic](https://github.com/Roll20/roll20-character-sheets/blob/master/HeroSystem6eHeroic/README.md) character sheet. There will invariably be rules, options, or build styles that do not translate well from HERO Designer into Roll20. Nonetheless, this importer should import most of the details of a character.
-This script was tested with the following commercially available character packs (almost 700 character files):
+This script was tested with the following commercially available character packs (more than 700 characters):
- [Champions Begins (free)](https://www.herogames.com/store/product/1005-champions-begins-pdfhd/)
- [Dead Orc Pass](https://www.herogames.com/store/product/910-dead-orc-pass-mini-campaign-book-pdfhdc/)
- [Dragon Bestiary 01 - Black Dragon](https://www.herogames.com/store/product/1080-dragon-bestiary-01-black-dragon-pdfhdc/)
- [Extinction Event: Hero Designer Pack](https://www.herogames.com/store/product/1045-extinction-event-hero-designer-pack/)
+- [Fantasy Hero Complete](https://www.herogames.com/store/product/507-fantasy-hero-complete-pdf/)
- [Fantasy Hero 6th Edition Character Pack](https://www.herogames.com/store/product/59-fantasy-hero-6th-edition-character-pack/)
- [Fistful of Fu: Real World Martial Artists](https://www.herogames.com/store/product/1015-fistful-of-fu-real-world-martial-artists-pdf-hdc/)
- [HERO System Bestiary 6th Edition Character Pack](https://www.herogames.com/store/product/79-hero-system-bestiary-6th-edition-character-pack/)
@@ -22,11 +23,11 @@ This script was tested with the following commercially available character packs
From the campaign's home page go to the Mods page via 'Settings -> Mod (API) Scripts.'
-From the Mods page select 'HeroSystem6eHeroic HDImporter' from the Mod Library dropdown list. Version 2.1 is the most recent and recommended version. Alternatively, create a new script and copy and paste the contents of HeroSystem6eHeroic_HDImporter.js into the text editing box. Save the script.
+From the Mods page select 'HeroSystem6eHeroic HDImporter' from the Mod Library dropdown list. Version 2.4 is the most recent and recommended version. Alternatively, create a new script and copy and paste the contents of HeroSystem6eHeroic_HDImporter.js into the text editing box. Save the script.
Launch the game.
-If HD Importer has started correctly the following notification should appear in chat when your Roll20 campaign starts up for the first time:
+If *HD Importer* has started correctly the following notification should appear in chat when your Roll20 campaign starts up for the first time:

# Import a Character:
@@ -46,35 +47,49 @@ If HD Importer has started correctly the following notification should appear in
1. Paste the copied text into the chat window of your Roll20 HeroSystem6eHeroic game.
2. Hit enter. If all goes well you should see a start and finish notification, which for the character "Alena" appears as

+3. (Optional) Open the new Roll20 character and click the `Edit` button in the upper right corner of the window to apply the character's profile image file.
## Sample Characters
-The release folder contains two test and evaluation characters for use in HERO Designer (HDC) and Roll20 (TXT).
-- `Sample_Character.hdc` is the fae wizard Darci.
-- `Sample_Character_MA.hdc` is the doctor and martial arts swordsman Henkle.
+The [release folder](/HeroSystem6eHeroic_HDImporter/2.4/) contains four test and evaluation characters for use in HERO Designer (HDC) and Roll20 (TXT).
+- `Sample_Character.hdc` is the fae wizard *Darci*.
+- `Sample_Character_MA.hdc` is the doctor and martial arts swordsman *Henkle*.
+- `Sample_Vehicle_APC.hdc` is the near-future armored personnel carrier *Chariot*.
+- `Sample_Vehicle_BB.hdc` is the time machine *Blue Box*.
-## Notes
-Double quotation marks are used to indicate JSON data values. Please take care to avoid quotation marks in Hero Designer text fields. Instead use single quotes. The exporter will attempt to remove double quotes, but this may not be foolproof.
+## Vehicles
+While twenty powers is typically sufficient for most heroic characters, it is easy to create vehicles with far more, particularly for science-fiction settings. Sensors, life support systems, and the like can really add up. To avoid running out of space, I suggest following the Star HERO source book's examples, which use Variable Power Pools for sensors and other systems. Some powers can easily be described as custom talents or skills as well. See the sample character file Sample_Vehicle_BB.hdc in this repository.
-Characters exported with older versions of HeroSystem6eHeroic.hde (1.0, 2.0, 2.1, and 2.2) will work, but those exported with the latest version (2.3) will have more content and may benefit from bug fixes.
+If you want to import more details to your vehicle sheets, consider defining various system powers as equipment. These will be imported as sheet equipment in the gear section, with power details printed in the adjacent text box.
+
+Another point to make about vehicles is that *HD Importer* ignores the default ground and swimming movement attributes. The sheet refunds these movement attributes by default. That said, HD Importer will count these starting values in the total movement value of ground or swimming powers if added to a vehicle.
+
+## General Notes
+Try to avoid double quotation marks, which JSON files use for data values. If you would like to use quotation marks in your Hero Designer files, consider using single quotes instead. During the export process, Hero Designer will attempt to fix errant double quotes, but it may not catch them all.
+
+Characters exported with older versions of HeroSystem6eHeroic.hde (1.0, 2.0, 2.1, 2.2, and 2.3) will work, but those exported with the latest version (2.4) will have more content and may benefit from bug fixes. Version 2.4 is the first to support vehicles.
Hero Designer has entries for eye color and hair color. This information is not imported since the character sheet does not have corresponding fields.
-Weapons, armor, and other equipment are imported as text and shown in the text field in the "Gear" page. HD Importer attempts to fill in the appropriate spaces in the sheet with the items it can identify, but HERO items often have additional information to track. Expect this area to require some degree of human attention.
+Attack powers and resistant defense powers will be imported as powers *and* as weapons and armor on the gear page. This feature is useful for vehicles, monsters, and superheroic characters that don't rely on equipment.
-At present, the character sheet has twenty power, ten talent, and ten complication slots. Ten additional entries of each type, if found, will be imported as text and displayed in sheet notes fields. None of the these overflow items will count in the sheet's automatic character point calculations.
+*HD Importer* attempts to fill in the appropriate spaces in the sheet with the items it can identify, but HERO items often have additional information to track. Details on weapons, armor, and other equipment appear in the text field in the "Gear" page.
+
+At present, the character sheet has twenty power, ten talent, and ten complication slots. Up to thirty additional powers and ten talents and complications, if found, will be imported as text and displayed in sheet notes fields. None of these overflow items will count in the sheet's automatic character point calculations.
Powers that add (or subtract) characteristics, movement, and perception abilities should be applied in the appropriate modifier fields.
> [!TIP]
-> HD Importer will recognize the following key phrases if added to a piece of equipment's notes and attempts to automatically apply the values given.
+> *HD Importer* will recognize the following keywords and phrases if added to power and equipment notes and attempts to automatically apply the values given.
+
+- `(locations x-y, z) or (loc x, y-z)` : If *HD Importer* finds the keyword "locations" it will assign the list "x-y, z" to a piece of armor's locations. End the list with closed parentheses ")" or a semicolon ";".
-- `(locations x-y, z) or (loc x, y-z)` : If HD Importer finds the keyword "locations" it will assign the list "x-y, z" to a piece of armor's locations. End the list with closed parentheses ")" or a semicolon ";".
+- `Max Range: XYZm or max range XYZ` : *HD Importer* will fill in a weapon's maximum range with XYZ.
-- `Equipment` : A weapon with the "equipment" keyword will be imported as a piece of equipment, not a weapon. Useful for ordinary items built with attack powers.
+- `X END/Turn or END/Turn: X` : If your campaign uses the optional rule of applying END costs to armor, *HD Importer* will automatically apply the value X.
-- `Max Range: XYZm or max range XYZ` : HD Importer will fill in a weapon's maximum range with XYZ.
+- `(Equipment)` : An item or power with the "(Equipment)" keyword will be imported to the Gear Page as a piece of equipment, not armor or as a weapon. Useful for ordinary items built with attack powers.
-- `X END/Turn or END/Turn: X` : If your campaign uses the optional rule of applying END costs to armor, HD Importer will automatically apply the value X.
+- `(Ignore)` : An item or power with the "(Ignore)" keyword will not be imported to the Gear Page.
# Help
@@ -84,11 +99,11 @@ Access the in-game help menu by entering the command "!hero --help" into the cha
# Configuration Options
-The configuration menu is accessible from the Help Screen or via the chat window command "!hero --config". HD Importer offers a number of options to help organize imported characters and to pre-configure campaign options.
+The configuration menu is accessible from the Help Screen or via the chat window command "!hero --config".* *HD Importer* offers a number of options to help organize imported characters and to pre-configure campaign options.

-- `Overwrite` : **CAUTION!** If set to true, when importing a new character, HD Importer will overwrite an existing character that has the same name.
+- `Overwrite` : **CAUTION!** If set to true, when importing a new character, *HD Importer* will overwrite an existing character that has the same name.
- `Prefix` : Adds a text label to the beginning of an imported character's name.
- `Suffix` : Adds a text label to the end of an imported character's name.
- `In Player Journal` : Presets the imported character's visibility to players.
@@ -96,11 +111,12 @@ The configuration menu is accessible from the Help Screen or via the chat window
- `Use Char Maximums` : Presets the sheet option that figures characteristic maximums into point costs.
- `Literacy Costs CP` : Presets the sheet option that literacy costs 1 CP. Most common in Fantasy Hero campaigns.
- `Super-Heroic END` : Presets the option for lower endurance costs for strength.
+- `Limit Weapon DMG` : Presets the option to limit enhanced weapon damage to twice base values.
- `Use Hit Locations` : Presets the sheet option to determine hit locations.
# Troubleshooting Import Errors
-Notepad++ and potentially other text editors may convert double quotes to pairs of single quotes. Check to see if pairs of single quotes exist in your character files. If they do exist, consider using another text editor or try replacing them with double quotes. Thank you to Jim E. for reporting this issue.
+Notepad++ and potentially other text editors may convert double quotes to pairs of single quotes. Try using your app's search function to see if pairs of single quotes exist in your character files. If they do exist, please use another text editor. Thank you to Jim E. for reporting this issue.
Please submit bug reports, feature requests, and general comments to [Villain in Glasses](https://app.roll20.net/users/633423/villain-in-glasses).
@@ -108,7 +124,7 @@ Please submit bug reports, feature requests, and general comments to [Villain in
Hero related code and modifications by [Villain in Glasses](https://app.roll20.net/users/633423/villain-in-glasses) (Discord: Villain#0604).
-Based on D&D Beyond Importer by [Robin Kuiper](https://app.roll20.net/users/1226016/robin) (Discord: Atheos#1095), [Matt DeKok](https://app.roll20.net/users/494585/sillvva) (Discord: Sillvva#2532), and [Ammo Goeetsch](https://app.roll20.net/users/2990964/ammo) (Discord: ammo#7063).
+Based on *D&D Beyond Importer* by [Robin Kuiper](https://app.roll20.net/users/1226016/robin) (Discord: Atheos#1095), [Matt DeKok](https://app.roll20.net/users/494585/sillvva) (Discord: Sillvva#2532), and [Ammo Goeetsch](https://app.roll20.net/users/2990964/ammo) (Discord: ammo#7063).
Apologies to Darci, who played the role of Duncan Idaho.
@@ -124,4 +140,6 @@ Version 2.1 -- Updated to support additional character sheet features as of shee
Version 2.2 -- Fix for compound power import failure. Recognizes Mental Combat Skill Levels and assigns levels and costs. Minor update to HeroSystem6eHeroic.hde (version 2.2). Improved formatting of text in powers, talents, and complications (September 14, 2024).
-Version 2.3 -- This version is up to date with Sheet Version 3.80. Adds special intelligence skill types with variable base cost (SPx). Adds "Native +" language skill assignment for native languages updated to imitate dialects. Doubles the number of armor, weapons, and equipment imported to account for the new "A/B" sets feature of the sheet. Recognizes additional copies of weapons purchased with the quantity feature of HD. Adds recognition of the keywords and key phrases "Max Range XYZ" and "Equipment." HeroSystem6eHeroic.hde (version 2.3) updated to 50 equipment slots (November 13, 2024).
+Version 2.3 -- This version is up to date with Sheet Version 3.80. Adds special intelligence skill types with variable base cost (SPx). Adds "Native +" language skill assignment for native languages updated to imitate dialects. Doubles the number of armor, weapons, and equipment imported to account for the new "A/B" sets feature of the sheet. Recognizes additional copies of weapons purchased with the quantity feature of HD. Adds recognition of the keywords and key phrases "Max Range XYZ" and "Equipment." HeroSystem6eHeroic.hde (version 2.3) updated to 50 equipment slots (November 13, 2024).
+
+Version 2.4 -- Supports vehicles, which were introduced in sheet version 4.42. Vehicle characters have special power sections for up to two endurance reserves and two propulsion systems, which increases imported powers up to 24. While the base capacity for general powers remains twenty, up to thirty more will be imported and displayed in the Talents text box (requires HeroSystem6eHeroic.hde v2.4). Introduces the Size attribute. Supports the expanded general and combat skill capacities of the sheet. Imports armor and weapon-like powers as equipment (March 18, 2025).
diff --git a/HeroSystem6eHeroic_HDImporter/script.json b/HeroSystem6eHeroic_HDImporter/script.json
index 528a58a4a8..4b04aac372 100644
--- a/HeroSystem6eHeroic_HDImporter/script.json
+++ b/HeroSystem6eHeroic_HDImporter/script.json
@@ -1,8 +1,8 @@
{
"name":"HeroSystem6eHeroic HDImporter",
"script":"HeroSystem6eHeroic_HDImporter.js",
- "version":"2.3",
- "description":"HDImporter imports HERO Designer-created heroes, villains, monsters, and other characters into a HeroSystem6eHeroic Roll20 campaign. The characters must be exported from Hero Designer using the format HeroSystem6eHeroic.hde, which is a companion file in the HD Importer repository. To use, open an exported character text file and paste the contents into chat and hit enter. Full sheet instructions in the [README](https://github.com/Roll20/roll20-api-scripts/tree/master/HeroSystem6eHeroic_HDImporter). Based on BeyondImporter Version O.4.0 by Robin Kuiper, Matt DeKok, and Ammo Goettsch",
+ "version":"2.4",
+ "description":"HDImporter imports HERO Designer-created heroes, villains, monsters, vehicles, and other characters into a HeroSystem6eHeroic Roll20 campaign. The characters must be exported from Hero Designer using the format HeroSystem6eHeroic.hde, which is a companion file in the HD Importer repository. To use, open an exported character text file and paste the contents into chat and hit enter. Full sheet instructions in the [README](https://github.com/Roll20/roll20-api-scripts/tree/master/HeroSystem6eHeroic_HDImporter). Based on BeyondImporter Version O.4.0 by Robin Kuiper, Matt DeKok, and Ammo Goettsch",
"authors": "Villain In Glasses",
"roll20userid":"633423",
"useroptions":[],
@@ -13,6 +13,6 @@
},
"conflicts":[],
"previousVersions":[
- "1.0, 1.1, 2.0, 2.1, 2.2"
+ "1.0, 1.1, 2.0, 2.1, 2.2, 2.3"
]
}