Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Combine Multiple Table Roll Results

I want roll on three tables and concatenate them into a single variable. The following results in "undefined undefined undefined". What am I missing? sendChat("API", "/roll 1t[PO-Name1]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.N1 = a[0]; }); sendChat("API", "/roll 1t[PO-Name2]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.N2 = a[0]; }); sendChat("API", "/roll 1t[PO-Name3]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.N3 = a[0]; }); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3);
1679249635

Edited 1679249701
timmaugh
Forum Champion
API Scripter
You are running into a problem of asynchronous timing. Consider this: let content; sendChat('', 'This is the message', a => {   content = a[0].content;   log(`In the callback, the message was: ${content}`); }); log(`Outside the callback, the message was: ${content}`); What you will see in your log panel is: Outside the callback, the message was: undefined Inside the callback, the message was: This is the message That's because the synchronous code continues to run, and your variable is only filled after the synchronous code ends. To get around this, you should use a single callback, which means probably using inline roll syntax. Doing that, the rolls are not a part of the content property of the message object... they are in the inlinerolls property of the message object. Also, since you're not doing anything with the message object array after the callback, there's no real reason to re-bind your a variable... (maybe if you were going to log it?). So you can just assign it to the right property of the SCVMBuild object. If you switch to inline roll verbiage, you can do also do this in one roll/callback, so this should work: sendChat("API", "[[1t[PO-Name1]]] [[1t[PO-Name2]]] [[1t[PO-Name3]]]", function (a) {   [1,2,3].forEach(n => `SCVMBuild.N${n}  = a[0].inlinerolls[n - 1].results[0].tableItem.name.split(":")[0];   sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3); }); The other thing about the callback being asynchronous is that you need to run your your setattr chat command only after you know the data is available. Because of that, we have to put it in the callback so that it runs when the callback  reaches the processor for evaluation. Another option would be to build a continueProcessing() function outside of your callback and then call it from within the callback. That is a little syntactic sugar to fool your eye into reading the code in synchronous order, even if part of it will be delayed on the asynchronous path. This would be a good option if you had slightly more complex operations to do with the data returned (ie, there was more to come in your script processing that had to wait until the table returns were available... eg, they had to wait until the asynchronous return to your code. So, simplest for a few lines of code is to include them in the callback. If that gets to be too much, you can code another function and fire that from the callback. Or, as a third option, I think you can move to Promises and await the return from the sendChat, but I can't say I've done that, myself.
I pasted your code in, and I get the following error in the  Closure Compiler service. JSC_PARSE_ERROR: Parse error. Unterminated string literal at line 505 character 25 [1,2,3].forEach(n => 'SCVMBuild.N${n} = a[0].inlinerolls[n - 1].results[0... ^ Roll20 just says SyntaxError: Invalid or unexpected token Here is the current code block sendChat("API", "/roll 1t[PO-Unspoken-origins]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Unspoken origin: ' + SCVMBuild.EM + ''); }); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Your mind and movements are alien'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You are literate, but only in dead languages, and cannot use scrolls.'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You can intuitively use one randomly rolled Power. The Power changes each dawn, or dusk (pick one).'); sendChat("API", "/roll 1t[PO-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); sendChat("API", "[[1t[PO-Name1]]] [[1t[PO-Name2]]] [[1t[PO-Name3]]]", function (a) { [1,2,3].forEach(n => 'SCVMBuild.N${n} = a[0].inlinerolls[n - 1].results[0].tableItem.name.split(":")[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3);     });
1679257098

Edited 1679259154
timmaugh
Forum Champion
API Scripter
It's the tick character that leads off the callback of the forEach, I think. I air coded that, and must have left an artifact of the edit in the line. Sorry. Now my laptop is out of battery, so I can't confirm that's the only error, but it does account for the error message you're getting. I'll double check as soon as I can.
Where does the quote  ' SCVMBuild.N${n}...  end? sendChat("API", "[[1t[PO-Name1]]] [[1t[PO-Name2]]] [[1t[PO-Name3]]]", function (a) {   [1,2,3].forEach(n => ' SCVMBuild.N${n}  = a[0].inlinerolls[n - 1].results[0].tableItem.name.split(":")[0];   sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3); });
1679342832

Edited 1679361865
timmaugh
Forum Champion
API Scripter
Try this cleaned up version: sendChat("API", "[[1t[PO-Name1]]] [[1t[PO-Name2]]] [[1t[PO-Name3]]]", function (a) {   [1,2,3].forEach(n => SCVMBuild.N[n] = a[0].inlinerolls[n - 1].results[0].tableItem.name.split(":")[0]);   sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3); }); The SCVMBuild line isn't a quote. It should never have had the string literal opening. It's just a single operation done for each of the items in the array (or 1,2,3). That line also had a couple of other errors, so I cleaned those up, too.
I put in the revised version, and now I get this when I run that subroutine. For reference, the error message generated was:  TypeError: Cannot read properties of undefined (reading 'tableItem') TypeError: Cannot read properties of undefined (reading 'tableItem') at apiscript.js:7801:79 at Array.forEach (<anonymous>) at apiscript.js:7801:14 at checkFinishedOps (eval at <anonymous> (/home/node/d20-api-server/api.js:172:1), <anonymous>:757:7) at eval (eval at <anonymous> (/home/node/d20-api-server/api.js:172:1), <anonymous>:837:8) at Timeout._onTimeout (/home/node/d20-api-server/node_modules/underscore/underscore-node-f.cjs:1079:17) at listOnTimeout (node:internal/timers:569:17) at process.processTimers (node:internal/timers:512:7)
1679361241
timmaugh
Forum Champion
API Scripter
OK... that looks like it's somewhere else in the code. It could be related, because we're dealing with inline rolls that could reference a rollabletable, but without seeing all of the code, I can't be sure. Can you post it, or copy it into a gist or a pastebin page?
Here's the whole thing: var SCVMBuild = { version: "0.4.4", output: [], listen: function () { on("chat:message", function (b) { if ("api" == b.type) { switch (sendto = isGM(b.playerid) ? "/w gm " : "/direct ", b.content) { case "!GenClass": SCVMBuild.GenClass(b); break; case "!GenWeapon": SCVMBuild.GenWeapon(b); break; case "!GenArmor": SCVMBuild.GenArmor(b); break; case "!GenEquipment": SCVMBuild.GenEquipment(b); break; case "!GenScroll": SCVMBuild.GenScroll(b); break; case "!GenSilver": SCVMBuild.GenSilver(b); break; case "!GenAbilities": SCVMBuild.GenAbilities(b); break; case "!GenHP": SCVMBuild.GenHP(b); break; case "!GenPower": SCVMBuild.GenPower(b); break; case "!GenOmen": SCVMBuild.GenOmen(b); break; case "!GenTraits": SCVMBuild.GenTraits(b); break; case "!GenBody": SCVMBuild.GenBody(b); break; case "!GenHabit": SCVMBuild.GenHabit(b); break; case "!GenStory": SCVMBuild.GenStory(b); break; case "!GenName": SCVMBuild.GenName(b); break; case "!CharView": SCVMBuild.printSheet(b); break; case "!SaveChar": SCVMBuild.save(b); } } }); }, GenClass: function (b) { sendChat("API", "/roll 1t[Class]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split("|"); SCVMBuild.Class = a[0]; SCVMBuild.SilverDie = a[1]; SCVMBuild.OmenDie = a[2]; SCVMBuild.HPDie = a[3]; SCVMBuild.ClassDescription = a[4]; SCVMBuild.Abilities = a[5]; SCVMBuild.StrMod = a[6]; SCVMBuild.AglMod = a[7]; SCVMBuild.PreMod = a[8]; SCVMBuild.TouMod = a[9]; SCVMBuild.WpnDie = a[10]; SCVMBuild.ArmDie = a[11]; SCVMBuild.ScrDie = a[12]; SCVMBuild.output.Class = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Class&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Class + " " + SCVMBuild.SilverDie + " " + SCVMBuild.OmenDie + " " + SCVMBuild.HPDie + " " + SCVMBuild.StrMod + " " + SCVMBuild.AglMod + " " + SCVMBuild.PreMod + " " + SCVMBuild.TouMod + " " + SCVMBuild.WpnDie + " " + SCVMBuild.ArmDie + " " + SCVMBuild.ScrDie + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextA = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Class + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextA); }); }, GenWeapon: function (b) { sendChat("API", "/roll 1t[Weapon]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.attacktype = a[0]; SCVMBuild.weaponname = a[1]; SCVMBuild.weapondamage = a[2]; SCVMBuild.output.Weapon = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Weapon&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.weaponname + "/" + SCVMBuild.attacktype + "/" + SCVMBuild.weapondamage + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextB = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Weapon + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextB); }); }, GenArmor: function (b) { sendChat("API", "/roll 1t[Armor]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Armor = a[0]; SCVMBuild.ArmorTier = a[1]; SCVMBuild.output.Armor = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Armor&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Armor + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextC = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Armor + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextC); }); }, GenEquipment: function (b) { sendChat("API", "/roll 1t[Equipment1]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Equipment1 = a[0]; SCVMBuild.output.Equipment1 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment1 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextD1 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Equipment1 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextD1); }); sendChat("API", "/roll 1t[Equipment2]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Equipment2 = a[0]; if (SCVMBuild.Equipment2.includes("scroll")) { SCVMBuild.GenUScroll(b); } else { SCVMBuild.output.Equipment2 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment2 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextD2 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Equipment2 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextD2); } }); sendChat("API", "/roll 1t[Equipment3]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Equipment3 = a[0]; if (SCVMBuild.Equipment3.includes("scroll")) { SCVMBuild.GenSScroll(b); } else { SCVMBuild.output.Equipment3 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment3 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextD3 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Equipment3 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextD3); }; }); }, GenUScroll: function (b) { sendChat("API", "/roll 1t[unclean-scrolls]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split("|"); SCVMBuild.Equipment2 = a[0]; SCVMBuild.output.Equipment2 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;Scroll of " + SCVMBuild.Equipment2 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextD2 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Equipment2 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextD2); }); }, GenSScroll: function (b) { sendChat("API", "/roll 1t[sacred-scrolls]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split("|"); SCVMBuild.Equipment3 = a[0]; SCVMBuild.output.Equipment3 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;Scroll of " + SCVMBuild.Equipment3 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextD3 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Equipment3 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextD3); }); }, GenSilver: function (b) { Cash = 0; for (let x = 0; x &lt; SCVMBuild.SilverDie; x++) { Cash = Cash + randomInteger(6); } SCVMBuild.Silver = Cash; SCVMBuild.output.Silver = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Silver&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Silver + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextQ = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Silver + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextQ); }, GenAbilities: function (b) { SCVMBuild.Strength = randomInteger(7) - 4; SCVMBuild.Strength = SCVMBuild.Strength + Number(SCVMBuild.StrMod); SCVMBuild.output.Strength = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Strength&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Strength + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextE = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Strength + "&lt;/tbody&gt;&lt;/table&gt;"; SCVMBuild.Agility = randomInteger(7) - 4; SCVMBuild.Agility = SCVMBuild.Agility + Number(SCVMBuild.AglMod); SCVMBuild.output.Agility = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Agility&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Agility + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextF = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Agility + "&lt;/tbody&gt;&lt;/table&gt;"; SCVMBuild.Presence = randomInteger(7) - 4; SCVMBuild.Presence = SCVMBuild.Presence + Number(SCVMBuild.PreMod); SCVMBuild.output.Presence = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Presence&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Presence + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextG = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Presence + "&lt;/tbody&gt;&lt;/table&gt;"; SCVMBuild.Toughness = randomInteger(7) - 4; SCVMBuild.Toughness = SCVMBuild.Toughness + Number(SCVMBuild.TouMod); SCVMBuild.output.Toughness = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Toughness&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Toughness + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextH = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Toughness + "&lt;/tbody&gt;&lt;/table&gt;"; Stats = SCVMBuild.Agility + SCVMBuild.Presence + SCVMBuild.Strength + SCVMBuild.Toughness if (Stats &lt; 1) { SCVMBuild.GenAbilities(b); } else { sendChat(b.who, "/direct " + SCVMTextE); sendChat(b.who, "/direct " + SCVMTextF); sendChat(b.who, "/direct " + SCVMTextG); sendChat(b.who, "/direct " + SCVMTextH); } }, GenHP: function (b) { SCVMBuild.HP = randomInteger(SCVMBuild.HPDie); sendChat(b.who, "/direct " + SCVMBuild.HP); SCVMBuild.HP = SCVMBuild.Toughness + SCVMBuild.HP; sendChat(b.who, "/direct " + SCVMBuild.HP); 1 &gt; SCVMBuild.HP &amp;&amp; (SCVMBuild.HP = "1"); SCVMBuild.output.HP = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;HP&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.HP + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextI = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.HP + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextI); }, GenPower: function (b) { sendChat("API", "/roll 1t[Power]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Power = a[0]; SCVMBuild.output.Power = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Power&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Power + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextJ = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Power + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextJ); }); }, GenOmen: function (b) { sendChat("API", "/roll 1t[Omens]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Omen = a[0]; SCVMBuild.output.Omen = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Omen&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Omen + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextK = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Omen + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextK); }); }, GenTraits: function (b) { sendChat("API", "/roll 1t[Trait]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Trait1 = a[0]; SCVMBuild.output.Trait1 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Trait1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Trait1 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextL1 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Trait1 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextL1); }); sendChat("API", "/roll 1t[Trait]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Trait2 = a[0]; SCVMBuild.output.Trait2 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Trait2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Trait2 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextL2 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Trait2 + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextL2); }); }, GenBody: function (b) { sendChat("API", "/roll 1t[Body]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Body = a[0]; SCVMBuild.output.Body = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Broken Body&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Body + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextM = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Body + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextM); }); }, GenHabit: function (b) { sendChat("API", "/roll 1t[Habit]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Habit = a[0]; SCVMBuild.output.Habit = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Bad Habit&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Habit + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextN = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Habit + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextN); }); }, GenStory: function (b) { sendChat("API", "/roll 1t[Story]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Story = a[0]; SCVMBuild.output.Story = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Troubling Tale&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Story + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextO = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Story + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextO); }); }, GenName: function (b) { sendChat("API", "/roll 1t[Name]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.Name = a[0]; SCVMBuild.output.Name = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Name&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Name + "&lt;/td&gt;&lt;/tr&gt;"; SCVMTextP = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Name + "&lt;/tbody&gt;&lt;/table&gt;"; sendChat(b.who, "/direct " + SCVMTextP); }); }, printSheet: function (b) { SCVMBuild.output.Class = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Class&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Class + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Weapon = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Weapon&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.weaponname + "/" + SCVMBuild.attacktype + "/" + SCVMBuild.weapondamage + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Armor = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Armor&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Armor + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Equipment1 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment1&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment1 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Equipment2 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment2&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment2 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Equipment3 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Equipment3&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Equipment3 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Silver = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Silver&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Silver + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Agility = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Agility&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Agility + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Presence = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Presence&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Presence + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Strength = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Strength&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Strength + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Toughness = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Toughness&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Toughness + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.HP = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;HP&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.HP + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Power = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Power&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Power + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Omen = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Omen&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Omen + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Trait1 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Trait1&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Trait1 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Trait2 = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Trait2&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Trait2 + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Body = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Broken Body&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Body + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Habit = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Bad Habit&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Habit + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Story = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Troubling Tale&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Story + "&lt;/td&gt;&lt;/tr&gt;"; SCVMBuild.output.Name = "&lt;tr&gt;&lt;td style='font-weight: bold; padding: 5px;'&gt;Name&lt;/td&gt;&lt;td style='padding: 5px;'&gt;" + SCVMBuild.Name + "&lt;/td&gt;&lt;/tr&gt;"; SCVMText1 = "&lt;table font-size: 10px;'&gt; &lt;tbody&gt;" + SCVMBuild.output.Name + SCVMBuild.output.Class + SCVMBuild.output.Weapon + SCVMBuild.output.Armor + SCVMBuild.output.Equipment1 + SCVMBuild.output.Equipment2 + SCVMBuild.output.Equipment3 + SCVMBuild.output.Silver + SCVMBuild.output.Strength + SCVMBuild.output.Agility + SCVMBuild.output.Presence + SCVMBuild.output.Toughness + SCVMBuild.output.HP + SCVMBuild.output.Power + SCVMBuild.output.Omen + SCVMBuild.output.Trait1 + SCVMBuild.output.Trait2 + SCVMBuild.output.Body + SCVMBuild.output.Habit + SCVMBuild.output.Story + "&lt;/tbody&gt;&lt;/table&gt;"; SCVMText = SCVMText1 + "&lt;p&gt;&lt;p&gt;"; sendChat(b.who, "/direct " + SCVMText); SCVMBuild.Description = SCVMBuild.Trait1 + "\n" + SCVMBuild.Trait2 + "\n" + SCVMBuild.Body + "\n" + SCVMBuild.Habit + "\n" + SCVMBuild.Story; "function" === typeof saveCallback &amp;&amp; saveCallback(); }, save: function (b, a) { SCVMBuild.printSheet(b); c = createObj("character", { name: SCVMBuild.Name, archived: !1, inplayerjournals: "all", controlledby: "all" }); createObj("attribute", { name: "description", current: SCVMBuild.Description, _characterid: c.id }); createObj("attribute", { name: "char_name", current: SCVMBuild.Name, _characterid: c.id }); createObj("attribute", { name: "class_description", current: SCVMBuild.ClassDescription, _characterid: c.id }); createObj("attribute", { name: "class_abilities", current: SCVMBuild.Abilities, _characterid: c.id }); createObj("attribute", { name: "class", current: SCVMBuild.Class, _characterid: c.id }); createObj("attribute", { name: "armor", current: SCVMBuild.Armor, _characterid: c.id }); createObj("attribute", { name: "armortier", current: SCVMBuild.ArmorTier, _characterid: c.id }); createObj("attribute", { name: "strength", current: SCVMBuild.Strength, _characterid: c.id }); createObj("attribute", { name: "agility", current: SCVMBuild.Agility, _characterid: c.id }); createObj("attribute", { name: "presence", current: SCVMBuild.Presence, _characterid: c.id }); createObj("attribute", { name: "toughness", current: SCVMBuild.Toughness, _characterid: c.id }); createObj("attribute", { name: "hp_curr", current: SCVMBuild.HP, _characterid: c.id }); createObj("attribute", { name: "hp", current: SCVMBuild.HP, max: SCVMBuild.HP, _characterid: c.id }); createObj("attribute", { name: "silver", current: SCVMBuild.Silver, _characterid: c.id }); SCVMBuild.AddGear(b); // SCVMBuild.AddAbilities(b); sendChat("", 'Created &lt;a href="<a href="http://journal.roll20.net/character/" rel="nofollow">http://journal.roll20.net/character/</a>' + c.id + '" style="color:blue;text-decoration:underline;"&gt;SCVM ' + SCVMBuild.Name + "&lt;/a&gt;"); // }, // AddAbilities: function (b, c) { switch (SCVMBuild.Class) { case "Fanged Deserter": sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_weapons_-CREATE_weaponname|Bite Attack --repeating_weapons_-CREATE_attacktype|Melee --repeating_weapons_-CREATE_weapondamage|d6'); sendChat("API", "/roll 1t[FD-Earliest-Memories]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Earliest Memories: ' + SCVMBuild.EM + ''); }), sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Illiterate; you are incapable of understanding scrolls.'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Normal Agility tests are DR14 instead of DR12, excluding defense.'); sendChat("API", "/roll 1t[FD-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); break; case "Gutterborn Scum": sendChat("API", "/roll 1t[GS-Bad-Birth]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Bad Birth: ' + SCVMBuild.EM + ''); }), sendChat("API", "/roll 1t[GS-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Specialty:' + SCVMBuild.FA + ''); }); break; case "Esoteric Hermit": sendChat("API", "/roll 1t[EH-Eldritch-Origins]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Eldritch-Origin: ' + SCVMBuild.EM + ''); }), sendChat("API", "/roll 1t[EH-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); sendChat("API", "/roll 1t[scrolls]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.SC = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_equipment_-CREATE_equipment|' + SCVMBuild.SC + ''); }); break; case "Wretched Royalty": sendChat("API", "/roll 1t[Things-were-going-so-well-until...]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Things-were-going-so-well-until ' + SCVMBuild.EM + ''); }), sendChat("API", "/roll 1t[WR-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); sendChat("API", "/roll 1t[WR-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FB = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FB + ''); }); break; case "Heretical Priest": sendChat("API", "/roll 1t[HP-Unholy-origins]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Unholy-origins: ' + SCVMBuild.EM + ''); }) sendChat("API", "/roll 1t[HP-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.EM + ''); }) break; case "Occult Herbmaster": sendChat("API", "/roll 1t[OH-Decoctions]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Decoction Recipe: ' + SCVMBuild.FA + ''); }); sendChat("API", "/roll 1t[OH-Decoctions]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FB = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Decoction Recipe' + SCVMBuild.FB + ''); }); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You carry a portable laboratory and continually search for frequently expended ingredients. Daily you have the materials to create two decoctions and can brew a total of d4 doses. If unused they lose vitality after 24 hours.'); break; case "Sacrilegious Songbird": sendChat("API", "/roll 1t[SS-A-deal-was-struck]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|A deal was struck ' + SCVMBuild.EM + ''); }); sendChat("API", "/roll 1t[SS-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|When you made your life-and-soul altering deal, you were gifted an accursed instrument.'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You carry an aura of unnatural charm everywhere you go, capable of unsettling foes and calming friends, spend a use of your daily Powers to add or subtract d6 to any Morale check.'); break; case "Pale One": sendChat("API", "/roll 1t[PO-Unspoken-origins]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Unspoken origin: ' + SCVMBuild.EM + ''); }); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Your mind and movements are alien'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You are literate, but only in dead languages, and cannot use scrolls.'); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You can intuitively use one randomly rolled Power. The Power changes each dawn, or dusk (pick one).'); sendChat("API", "/roll 1t[PO-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); // sendChat("API", "[[1t[PO-Name1]]] [[1t[PO-Name2]]] [[1t[PO-Name3]]]", function (a) { // [1,2,3].forEach(n =&gt; SCVMBuild.N[n] = a[0].inlinerolls[n - 1].results[0].tableItem.name.split(":")[0]); // sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --char_name|'+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + ' ' + SCVMBuild.N3); // }); break; case "Dead Gods Prophet": sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You are literate and can use scrolls, but must pass a DR12 Presence test when encountering them not to immediately attempt to destroy them as words of false gods.'); sendChat("API", "/roll 1t[DG-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.EM + ''); }); sendChat("API", "/roll 1t[DG-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|' + SCVMBuild.FA + ''); }); // sendChat("API", "/roll 1t[DG-GodName1]", function (a) { // a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); // SCVMBuild.N1 = a[0]; // }); // sendChat("API", "/roll 1t[DG-GodName2]", function (a) { // a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); // SCVMBuild.N2 = a[0]; // }); // sendChat("API", "/roll 1t[DG-GodName3]", function (a) { // a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); // SCVMBuild.N3 = a[0]; // }); // sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|God: '+ SCVMBuild.N1 + ' ' + SCVMBuild.N2 + '' + SCVMBuild.N3 + ''); break; case "Cursed Skinwalker": sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Shifting your bones as such occupies a single painful round. Armor and weapons are likely unusable in your new form.'); sendChat("API", "/roll 1t[CS-First-Died]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|First Died: ' + SCVMBuild.EM + ''); }); sendChat("API", "/roll 1t[CS-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Creature shape' + SCVMBuild.FA + ''); }); break; case "Forlorn Philosopher": sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|You carry an aura of unnatural charm everywhere you go, capable of unsettling foes and calming friends, spend a use of your daily Powers to add or subtract d6 to any Morale check.'); sendChat("API", "/roll 1t[FP-Dejection]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.EM = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|The Roots of Your Dejection: ' + SCVMBuild.EM + ''); }); sendChat("API", "/roll 1t[FP-Abilities]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.FA = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|Tablet of Ochre Obscurity and ' + SCVMBuild.FA + ''); }); sendChat("API", "/roll 1t[FP-Roots]", function (a) { a = JSON.parse(a[0].content).rolls[0].results[0].tableItem.name.split(":"); SCVMBuild.RD = a[0]; sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_abilities_-CREATE_class_abilities|The Dejection of Your Roots: ' + SCVMBuild.RD + ''); }); break; } }, // }, AddGear: function (b, c) { sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_weapons_-CREATE_weaponname|' + SCVMBuild.weaponname + ' --silent --repeating_weapons_-CREATE_attacktype|' + SCVMBuild.attacktype + ' --silent --repeating_weapons_-CREATE_weapondamage|' + SCVMBuild.weapondamage + ''); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_equipment_-CREATE_equipment|' + SCVMBuild.Equipment1 + ''); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_equipment_-CREATE_equipment|' + SCVMBuild.Equipment2 + ''); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_equipment_-CREATE_equipment|' + SCVMBuild.Equipment3 + ''); sendChat('API', '!setattr --name ' + SCVMBuild.Name + ' --silent --repeating_powers_-CREATE_power|' + SCVMBuild.Power + ''); }, }; on("ready", function () { SCVMBuild.listen(); });
1679405427
timmaugh
Forum Champion
API Scripter
What is the usage of this? It looks like it is written in a way to try to get around the asynchronous problem I mentioned, but the solution arrived at makes it look like you have to run about 12 commands through chat to arrive at a randomized character. Is that what you do with it, or do you sometimes just run one of those commands? This could really be streamlined down to a single command with the option to only generate a smaller number of components, given the right line parsing. As for the specific error, sorry: I forgot there were more changes when you move from a /roll formatted message to a message with inline rolls in it. The tableitem you're looking for is in a different place. You can see this for yourself if you download the Inspector script and run a command that would display the inline roll structure, like: !inspect --inline [[1t[PO-Name1]]] I don't have a table of that name, but here it is showing the results rolling against a table of mine: So your results are going to be at: a[0].inlinerolls[0].results.rolls[0].results[0].tableItem.name When we abstract that for the forEach we're running (where we have 3 rolls in a single message), the line looks like: [1,2,3].forEach(n =&gt; SCVMBuild.N[n] = a[0].inlinerolls[n - 1].results.rolls[0].results[0].tableItem.name.split(":")[0]); But like I said, that just gets these 3 components to roll together in a single message. There are a lot of other calls which, since they are asynchronous, require a bunch of chat input: !GenClass, !GenWeapon, !GenArmor, !GenScroll, !GenEquipment, !GenSilver, etc. If you want to take a look at rolling all of that up into a single call (maybe making your life easier?), let me know. I don't know if this is your script or you're editing someone else's, so I'll wait to hear what you want to do.
This script evolved from a similar script for a different game in which I gave the players the option to reroll at each step. This game, Mork Borg, does not allow for that, so combining the subroutines into a single execution would be optimal. Additionally, however, there are two modes of play: classless and with classes. I started with the classless version, and folded in the classes. As a result: GenWeapon, GenArmor, GenEquipment, GenSilver, GenScroll GenAbilities and GenHP are all dependent on GenClass) with Classless as one of the options). GenPower, GenOmen, GenTraits, GenBody, GenHabit, and GenStory are optional GenName, CharView, SaveChar, AddGear apply to everyone.
1679425237
timmaugh
Forum Champion
API Scripter
I see in the code where there are several things derived from the random roll against the Class table, but I don't see anywhere that there's a connection between the Weapon, Armor, Equipment, etc., and the Class. What do you mean when you say, "dependent"? What does that look like in terms of your current usage of the script (as it is currently composed)?
The current script is a midway point. All the options are available for all classes. I haven't added the branching bits yet.
1679491849
timmaugh
Forum Champion
API Scripter
Right, so I would say the first order is getting the command line shaped up so you know 1) your options are covered, and 2) how you're going to need to parse it. Instead of 12 different handles, let's have 1 (since we're going to need to roll *all* components in one go). Then we have to cover our options. It sounds like if you have a class, then weapon, armor, equipment, silver, scroll, abilities, and HP are defaulted for you and we just have to get the correct data. You would only roll those randomly if you had "classless" chosen (or randomly rolled). So a base command would be to generate/supply a Name, View, Gear, and Class. If those aren't supplied, they are rolled randomly. Then, based on the result of Class, we either roll the dependent list of components or we go retrieve them to match the Class. After that, the user can optionally choose to include random generation of Power, Omen, Traits, Body, Habit, and Story (maybe even supplying values for those, but we can get to that). I'm going to assume those are individually optional (ie, I can have a Power without a Body, or a Trait and a Habit but not a Power), but tell me if that's wrong. With all of that planned out for the command line, I would see the final form looking like:&nbsp; !generate --name=Bob --habit=Nail Picker That would provide a name but roll the remaining core components randomly. The class would be random, prompting the appropriate return of the class-based components. Then we decided to include a user-specified Habit. Another example of that same form might look like: !generate --class=none That would be a base character with a specified "classless" state, prompting the generation of random class-based components. If you're good with that, then the question is whether anything can be included multiple times... for instance can there be 2 weapons? Or 2 Powers? Etc... After that, everything becomes easier to manage.
Not exactly. Depending on the class, a different die type is rolled against the individual tables. For example, for the Classless, the system should: roll 2d6 for Silver&nbsp; roll on the Omen table 2 times roll 1d8 (and add Toughness) for HP StrMod, AglMod, PreMod, and TouMod are added to 3d6 to generate the stats Roll 1d10 on the Weapon table Roll 1d4 on the Armor table Roll 0 times on the Scroll table Add (NA) to the end of the character name The parameters are as follows: Class SilverDie OmenDie HPDie Description Abilities StrMod AglMod PreMod TouMod WpnDie ArmDie ScrDie &nbsp;Suffix Classless 2 2 8 In this world there are those who seek riches or redemption. Some say the apocalypse is escapable, that it might even be stopped. And there you walk in discord and despair. One hand holds you, the other holds a waterskin and a few days worth of food. Your soul and your silver are your own and equally easy to lose. None 0 0 0 0 10 4 0 (NA) But&nbsp; for the Heretical Priest, the system should: roll 3d6 for Silver&nbsp; roll on the Omen table 4 times roll 1d8 (and add Toughness) for HP Subtract 2 from 3d6 to generate Strength Roll 3d6 to generate Agility Add 2 to 3d6 to generate Presence Roll 3d6 to generate Toughness Roll 1d8 on the Weapon table Roll 1d4 on the Armor table Roll 1 times on the Scroll table Add (NA) to the end of the character name The parameters are as follows: Class SilverDie OmenDie HPDie Description Abilities StrMod AglMod PreMod TouMod WpnDie ArmDie ScrDie &nbsp;Suffix Heretical Priest 3 4 8 Hunted by the Two-Headed Basilisks of the One True Faith, this heretic can be found raving in ruins, traipsing endlessly down dusty roads and desecrating cathedrals by night. Insightful, roll 3d6+2 for Presence. Frail, roll 3d6-2 for Strength. Roll a d8 on the weapons table and may use Powers while wearing medium armor. -2 0 2 0 8 4 1 (HP) None of the rolling by parameter die has been implemented yet.
1679844310

Edited 1680535675
timmaugh
Forum Champion
API Scripter
Ok... I wanted to do everything I'm about to discuss, but I just don't have the time when I have a pile of my own scripts requiring updating, patching, or a wholesale refactor. But here's how I would do it: Start With a Revealing Module Pattern Template A RMP template lets you enclose everything you want to code into your script, and that structure gives you a closure that will let you store "hot" data that can be recalled from usage to usage (within the same boot of the sandbox). This might be important if you want to generate a set of information, then present the option to Save or Print the character, or to Discard it. There will be a delay between those messages that will necessarily require you to store your data, but you really shouldn't use the script itself as the object to which you attach the properties since you might run into 2 people using it at the same time, or situations where not all properties are reset and you carry over data from a previous character. Also, given the asynchronous nature of the sendChat() callback, if you go that route you will be running into a situation where you will need to make sure your data persists until the asynchronous code has a chance to run. For all these reasons, I would suggest converting to a Revelaing Module Pattern. You can find Aaron's RMP template here , or mine is here (for mine, you can get rid of the logsig() function, and all references to it... that's just my sandbox signature). Create a Storage Object to Store Your Characters This will only matter if you truly intend there to be a Save or a Print function... since that implies at least a 2 command series: 1 to create the character, and 1 to Save or Print. In the meantime, your data has to exist somewhere. You can do this relatively simply: const charStorage = {}; And then use the generateUUID() function to establish your keys when you want to attach a character: &nbsp; &nbsp; const generateUUID = (() =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; let a = 0; &nbsp; &nbsp; &nbsp; &nbsp; let b = []; &nbsp; &nbsp; &nbsp; &nbsp; return () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let c = (new Date()).getTime() + 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let f = 7; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let e = new Array(8); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let d = c === a; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a = c; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (; 0 &lt;= f; f--) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = Math.floor(c / 64); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c = e.join(""); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (d) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 11; 0 &lt;= f &amp;&amp; 63 === b[f]; f--) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f] = 0; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f]++; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 0; 12 &gt; f; f++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; b[f] = Math.floor(64 * Math.random()); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (f = 0; 12 &gt; f; f++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return c; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; })(); Then you can attach a character to the charStorage object like this: let char = // ... code to create the character charStorage[generateUUID()] = char; Start With a Class that Sets Your Defaults I would start with a class that has your defaults preset for you. That simplifies things for you, and gives you an attachment point for all of the properties/items/attributes you'll be adding. In the class, you can set all of the defaults for a character... the player can, in the command line, override any of the defaults, but this way the values will be set regardless. Here is the beginning of a structure of what that might look like. Note, I don't know what data types each entry should be... for instance, I have traits as an array because I expect there will be more than one you will want to attach to the character. I have only demonstrated setting a default (other than an empty string/array) for the name property... pointing it to a function that would return a random name, but you can do the same with any others as necessary. &nbsp; &nbsp; class CharObj { &nbsp; &nbsp; &nbsp; &nbsp; constructor({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name: name = getRandomName(), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; view: view = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gear: gear = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; charClass: charClass = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; weapon: weapon = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; armor: armor = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; equipment: equipment = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; omens: omens = [], &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; traits: traits = [], &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; body: body = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; habit: habit = '', &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; story: story = '' &nbsp; &nbsp; &nbsp; &nbsp; } = {}) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.name = name; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.view = view; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.gear = gear; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.charClass = charClass; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.weapon = weapon; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.armor = armor; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.equipment = equipment; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.omens = omens; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.traits = traits; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.body = body; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.habit = habit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.story = story; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; save () { /* ... save code here... */ } &nbsp; &nbsp; &nbsp; &nbsp; print () { /* ... print code here... */ } } &nbsp;Note, too, that I attached the save and print functions to the CharObj. This lets you call it right from the character object once you have it located. Using this class in conjunction with the charStorage object would look like this for a character that had name and class overrides declared: let char = new CharObj({ name: 'Titus', class: 'FishLobber' }); // ... more code to set char data charStorage[generateUUID()] = char; Line Parsing This is the most straightforward bit, but post back if you need help with it. Basically, you'd expect a command line much like what I suggested (above, previous post), and you'd capture all of the user-defined options, and either apply them to the new CharObj as you instantiate it (with a new ), or after the fact as you modify what would have otherwise been defaulted information. Getting Your Options At this point, you could port all of your options into the script, making it more self-contained if you wanted to take it to another game. Currently, your plan is to reference tables. That can work, it just means that when you port your script from one game to the next, you will also have to port your rollable tables. However, if you still want to reference your rollable tables, we're back to your initial question of getting all of the calls to the rollable tables in a single pass (because of the asynchronous nature of the sendChat() callback. So... Using the Callback If you want to still use the sendChat() callback, I would suggest you now construct a secondary command line specifically of the things that might pass through... things like omens, and silver, etc. That command line would, itself, have arguments with handles: --silver=[[1t[SilverTable]]] --omens=[[1t[OmensTable]]] So that in your callback, you can do another line parse and capture all of the data, knowing which roll belongs to which item. This way, your command line only has to include the parameters that you derive from the user's first input. In other words, if the user didn't supply it, you don't include it, and the default value continues forward. For properties that are connected to the character class, their default value might be set only after you derive the class, but this would be the way they would be defaulted to be set up... that is, at this point you have enough data to either choose the class (if it was provided) or to generate it otherwise; that means that immediately after that you can begin to build the related properties using their default values unless they, themselves, had an override supplied by the user's initial command. Don't Use the Callback The alternative to the sendChat() callback is just to gather all of the tableitems associated with a given rollable table, and then generate a random return from them. It could be abstracted to its own function where you pass in the table name and the upper bound of potential options: const getTableItem = (n, d) =&gt; { &nbsp; let&nbsp; tbl = findObjs({ type: 'rollabletable', name: n }); &nbsp; let items = findObjs({ type: 'tableitem',&nbsp;rollabletableid: tbl.id }); &nbsp; if (!items.length) return; &nbsp; return items[randomInteger(Math.min(items.length,d))]; } Then you can just call that for each of your items... for instance, getting one of the first four entries on the SilverTable table: char.silver = getTableItem('SilverTable', 4); Or getting one of the first 8 options from the WeaponTable: char.weapon = getTableItem('WeaponTable', 8); For entries that would be an array, you'd use push() , instead: char.traits.push(getTableItems('TraitsTable',6); Presenting the Final Picture (Saving/Printing) The former script used the script itself as the attachment object for all of the character properties, but we're moving that to an individual object for each character. So, when you make a change to a character, you still want to be able to Save, Print, or Discard it. I would suggest printing all of the relevant character data for the character you just modified to the chat window. If that gets too spammy, you can use a handout, instead. But at the bottom of the informational panel (you could even use a roll template for this), you include a button for a Save option (to commit the changes and create a character) and the Print option (I didn't read too closely into this part of the code). They would each reference the ID you generated as the key for the charStorage object: !generate-save -M1234567890abcdef !generate-print -M1234567890abcdef Obviously your handleinput() function would want to be able to catch these options, parse them to get the ID that will point your script at the correct character in your storage object, and then execute the chosen function for it. You could include in that same panel an option for a Discard button (same sort of syntax), and/or you could include that in another informational panel ... this one for all of the characters in your Storage object. That panel would basically show you a row for each of your currently-stored characters (who had not been converted into an actual sheet), and for each of them the panel would present a Save button, a Print button, and a Discard button. You might even have a Clear button at the bottom, allowing the user to clear all stored characters from the storage object at once. (If you are afraid of making a mistake, you can build the listener for the Discard or Clear buttons to present a new button in chat: one that asks for confirmation (and has a command line that would include some syntax that would clue your parser in that you were working with a *confirmed* Discard or a *confirmed* Clear. Roadmap This is just a roadmap for how I would approach it... but you can tackle it however you want, of course. The important bit you were hitting a snag on initially was the asynchronous sendChat callback, so you now at least have a few options for getting around that problem. Whatever path you choose, post back if you have questions and we can get it figured out.
Thanks for your help so far. What I can't connect is how the elements of the class table get added to the&nbsp;CharObj, and how to handle multiple items (e.g., 4 rolls on the Omen table). Please explain.
1680535551

Edited 1680535585
timmaugh
Forum Champion
API Scripter
So that sounds like the Omen property on the character object should be declared as an array (to take multiple items). I have corrected the class definition in my above post to reflect that. To attach the returns from the Omen table, you'd first want to get the number of "rolls" to make against it, then push each into the omen property array. Let's talk about getting the number of Omen rolls. That value is connected to the class, but your class info is more than the 1 dimensional rollable tables available in the vtt interface. That is, you have multiple properties for each "class": SilverDie, OmenDie, HPDie, etc... to have these in rollable tables would require a table for *each* class, then you'd have to know what tables were available to the script, anyway. Better if you have that in the script in a single object you can reference: const classTable = { &nbsp; classless: { name: `Classless`,&nbsp; SilverDie: 3,&nbsp; OmenDie: 4,&nbsp; HPDie: 8,&nbsp; Description: `Hunted by the...`,&nbsp; Abilities: `Insightful, roll 3d6+2...`,&nbsp; StrMod: -2,&nbsp; AglMod: 0,&nbsp; PreMod: 2,&nbsp; TouMod: 0,&nbsp; WpnDie: 8, ArmDie: 4,&nbsp; ScrDie: 1,&nbsp; Suffix: (HP) }, &nbsp; hereticalpriest: { name: `Heretical Priest`,&nbsp; SilverDie: 3,&nbsp; OmenDie: 4,&nbsp; HPDie: 8,&nbsp; Description: `Hunted by the...`,&nbsp; Abilities: `Insightful, roll 3d6+2...`,&nbsp; StrMod: -2,&nbsp; AglMod: 0,&nbsp; PreMod: 2,&nbsp; TouMod: 0,&nbsp; WpnDie: 8,&nbsp; ArmDie: 4,&nbsp; ScrDie: 1,&nbsp; Suffix: (HP) }, &nbsp; fishlobber: { /* ... */ }, &nbsp; // etc. };&nbsp;&nbsp; Then you get your new character object as an instance of the class: let char = new CharObj(); Doing it that way, you'd have to assign user parameters (like class) after the fact. As an alternative, based on how I built the CharObj class, you could parse your command line to come up with (and validate) the user parameters first, then pass them as an object to the above statement: let userparams = {}; // ... code to parse the command line and assign args to the userparams object let char = new CharObj(userparams); Either way, at some point you will either have the class as specified from the user, or not. If you don't, you'd get it via a random selection from your classTable: let randomKey = (obj) =&gt; { &nbsp; let keys = Object.keys(obj); &nbsp; return keys[randomInteger(keys.length)]; }; char.charClass = char.charClass || randomKey(classTable); Then you'd use that class to drive the number of Omen rolls to make. First, I'm going to alter the getTableItem() function, in my previous post, so that I don't have to supply a second argument if I just want all of the options to be possible returns: const getTableItem = (n, d) =&gt; { &nbsp; let tbl = findObjs({ type: 'rollabletable', name: n }); &nbsp; let items = findObjs({ type: 'tableitem', rollabletableid: tbl.id }); &nbsp; if (!items.length) return; &nbsp; if (!d) d = items.length; &nbsp; return items[randomInteger(Math.min(items.length,d))]; }; Finally, to get the Omen rolls: for (i = 0; classTable[char.charClass].OmenDie; i++) { &nbsp; char.omens.push(getTableItem('OmenTable'); }; All of this is building the character (as the object,&nbsp; char ). That character would still, at the end of the code that built it, need to be committed to the character library, and/or processed to convert these properties to the character sheet... but this hopefully answers your question about getting the table items attached to the char object.