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

[Help] Trying to create a Script for tracking downtime activities.

1653897321
Julexar
Pro
API Scripter
Hello there, it is I again. This time I am trying to create a Script for tracking downtime activities for each Player. I intend for this to include Brewing, Crafting, Working and a few other things. However, I am having Problems actually getting it to work, I haven't been able to figure out how to make it take the values from the sheets of the characters. Could you maybe help me or give me a link where I can find the functions and expressions? You can find my current code here: GitHub Gist
1653911895

Edited 1653911909
Julexar
Pro
API Scripter
So I researched a little bit but I still can't seem to get it working. The findObjs doesn't seem to work... GitHub Gist
1653955835

Edited 1653956023
Oosh
Sheet Author
API Scripter
A few things on a brief skim through: You're passing through the String "GM" from handleInput on some of the switch paths, and the rest of the time the message object. You're not handling this on the other end - you're trying to grab user.playerid which won't work for the String. Be careful using terms like 'player' 'character' in your variables, and that they actually reference the right thing. For example: let playerName = msg.who msg.who contains the value of the "Speak as..." select in the chat bar. If a player hasn't selected a character, it will be their Player displayname. If they have selected a character to Speak As, it will be an actual Character name. You can handle this however you like, but a couple of ways: 1. Make sure the correct Character id is retrievable from the Player input. A roll template is good since you can grab the character id without displaying it: charname=@{selected|name} --or-- charid=@{selected|character_id} This gives you a reliable way to grab the correct character name and be sure all the data in the rolltemplate are from the same sheet. If Players control more than one character this is the most reliable method. Players don't need to do anything, apart from make sure they're using the correct macro to send their downtime activity. The way your script is currently receiving Player input would require you to build the Character ID into the CLI command your Players will use. The simplest way is to add a flag in there, like --charid @{selected|character_id}, which obviously means the Player can't use the script without a token selected. But the benefits are that it can handle identical character names (since it runs off id), Players with multiple Character sheets (since they manually select the token they're downtiming), and it's extremely simple. 2. Handle all possible cases yourself. First, try to find a character from the msg.who name: const chars = findObjs({ type: 'character', name: msg.who }); if (chars.length === 1) { // found a single character with that name // proceed with the rest of the script } else if (chars.length > 1) { // uh oh, you have identical character sheet names. This is generally not good. } else { // no character exists const playerChars = findObjs({ type: 'character', controlledby: msg.playerid }); // then do the same checks on the length of playerChars.... You can probably see the issue with doing it this way - it's fine if Players all only control a single Character, otherwise you're making more work for yourself. If playerChars.length is more than one, you've just forced yourself to write more script, a command-button UI for the chatbar so the player can be prompted to pick which one of their characters the activity was for - and  you'll need to then cycle through the script again with the charId scraped from the API command button. This is a little unwieldy (hot tip to save you some time - you cannot prompt for a Query from the sandbox), but is a method used in plenty of scripts. Finally - there's little to no debug logging going on. When your findObjs isn't working, the first thing you want to do is add some logging lines: let playerName = msg.who; log(`Attempting to find player: ${playerName}`); let user = findObjs({ _type: 'character', name: playerName }, {caseInsensitive: true}); log(user); In this case, you're attempting to find a Character , not a Player , from a chat message where the name could refer to either of those things. But logging the playerName lets  you know whether you're even searching for the right name, and if it's followed by an empty Array, you know immediately that either the name is wrong, or there's a problem with the values supplied to findObjs. In this case, it looks like you want to search for a user, which would be: let user = findObjs({ type: 'player', displayname: playerName }, {caseInsensitive: true}); However, a few lines later you're attempting to use user.id to find a Character - this clearly won't work. Players are humans, Characters are fictional characters with character sheets. If you get more logging in there, it'll help you understand when you're looking for the wrong one!
1653979081
Julexar
Pro
API Scripter
Oh, awesome, that really helps a lot. Thank you! I don't know too much about how to grab information from the character sheets and the documentation I found wasn't too clear.. Is there somewhere where you can look up the different attribute names etc?
1653981076
Julexar
Pro
API Scripter
How would I make use of charid though? I think the Ideal way would be to work with the ID of the character that the Player uses to run the command as. (They select their character under speaking as and then run the command) I tried around a bit but can't seem to be able to grab the id from the findObjs let char=findObjs({             _type: 'character',             name: msg.who         }, {caseInsensitive: true}); log(char); How can I get the id from there?
1653981405
Julexar
Pro
API Scripter
Nevermind, I figured it out. let char=findObjs({             _type: 'character',             name: msg.who         }, {caseInsensitive: true});         let charid = char[0].get("_id");         log(charid);
1653982584
Julexar
Pro
API Scripter
Alright.. - so, how would I be able to read/get the Attributes (such as the current bonus for skills or the current amount of money) the character has? A function that works with the character id would be ideal
1653982692
Julexar
Pro
API Scripter
Like maybe sth like getObj(charid, attrname); Does something like that exist?
1653996524
David M.
Pro
API Scripter
There's the built-in getAttrByName function. Taken from the API Objects wiki :  __________________________ Working with Character Sheets The  Character Sheets  affects the usage of the Attributes object type, because the sheets have the capability of specifying a default value for each attribute on the sheet. However, if the attribute is set to the default value, there is not yet an actual Attribute object created in the game for that Character. We provide a convenience function which hides this complexity from you. You should use this function to get the value of an attribute going forward, especially if you know that a game is using a Character Sheet. More about editing character sheets and how they work:  Building Character Sheets getAttrByName(character_id, attribute_name, value_type) Simply specify the character's ID, the  name  (not ID) of the attribute (e.g. "HP" or "Str"), and then if you want the "current" or "max" for value_type. Here's an example: var character = getObj ( "character" , "-JMGkBaMgMWiQdNDwjjS" ); getAttrByName (character.id, "str" ); // the current value of str, for example "12" getAttrByName (character.id, "str" , "max" ); //the max value of str, for example "[[floor(@{STR}/2-5)]]" Note that fields which have  auto-calculated values  will return the formula rather than the result of the value. You can then pass that formula to  sendChat()  to use the dice engine to calculate the result for you automatically. Be sure to also look at the  Building Character Sheets  documentation for more information on how they interact with the API. getAttrByName  will  only  get the value of the attribute, not the attribute object itself. If you wish to reference properties of the attribute other than "current" or "max", or if you wish to change properties of the attribute, you must use one of the other functions above, such as  findObjs . In the case that the requested attribute does not exist,  getAttrByName()  will return  undefined .
1654005392
GiGs
Pro
Sheet Author
API Scripter
That page is worth a lok in general. Attributes are objects like characters, so you can also get them the same way you got the character id - using findObjs.
1654006463
Julexar
Pro
API Scripter
Yeah, I managed it with findObjs({     _type: 'attribute',     _characterid: charid,     _name: type })[0];
1654006773
GiGs
Pro
Sheet Author
API Scripter
Note that if you just want the value, and have set a default value in the character sheet, the method suggested by David is better - it handles situations where players havent manually entered a value more elegantly.
1654058971

Edited 1654059022
Victor B.
Pro
Sheet Author
API Scripter
There's downtime activities in the source books.  What are you selecting random one?  You want characters to select from a list?  And have all the additional rules behind it?  Probably a popular API if you do that.  But you have to carry it through to all the rules behind downtime.  Then you'll have a winner
1654059084
Victor B.
Pro
Sheet Author
API Scripter
People here have given you the link between the charsheet and the api.  But you need to do the api and build in all the various rules.   Hope you do.  I'll subscribe
1654086586
Julexar
Pro
API Scripter
Alright, so I tried to have the current downtime get saved in the gmnotes of each character but when I try to get the notes with: let char=findObjs({             _type: 'character',             name: msg.who         }, {caseInsensitive: true})[0];         if (char) {             let notes=char.get('gmnotes');             log(notes);         } Then it throws the following Error: "Error: You must pass a callback function to .get() when getting the bio, notes, defaulttoken, or gmnotes of a Character or Handout."
1654087455
Julexar
Pro
API Scripter
Alright, so apparently I need to do it with char.get("gmnotes",function(gmnotes) {     }) But I don't seem to be able to save the information from gmnotes anywhere. Like if I try: let notes; char.get("gmnotes",function(gmnotes) {     notes=gmnotes; }) It won't work and just gives me "undefined"... How can I get what's in gmnotes into another variable?
1654089414
Julexar
Pro
API Scripter
Nevermind, I found a way: char.get("gmnotes",function(gmnotes) {                 let time;                 time=gmnotes.replace(" Days","");                                  if (!(Number(time)<amount)) {                     time=Number(time)-amount;                 }                 let nnotes="";                 if (time && time!=0) {                     nnotes+=String(time)+" Days";                 }                 char.set("gmnotes",nnotes);             });
1654090373
Julexar
Pro
API Scripter
Alrighty, updated my Script a little bit: GitHub Gist
1654110634
timmaugh
Pro
API Scripter
Julexar said: Alright, so apparently I need to do it with char.get("gmnotes",function(gmnotes) {     }) But I don't seem to be able to save the information from gmnotes anywhere. Like if I try: let notes; char.get("gmnotes",function(gmnotes) {     notes=gmnotes; }) It won't work and just gives me "undefined"... How can I get what's in gmnotes into another variable? Yep... you're running into asynchronous code execution, there. You fire your callback, and then linear code continues (where you might reference the notes variable. Then the callback resolves and returns a value. To nothing. Because nothing is waiting for it, anymore. Handling it in your callback (the way you did it in your next post) is one way to get around that. =D
1654146723
Julexar
Pro
API Scripter
timmaugh said: Julexar said: Alright, so apparently I need to do it with char.get("gmnotes",function(gmnotes) {     }) But I don't seem to be able to save the information from gmnotes anywhere. Like if I try: let notes; char.get("gmnotes",function(gmnotes) {     notes=gmnotes; }) It won't work and just gives me "undefined"... How can I get what's in gmnotes into another variable? Yep... you're running into asynchronous code execution, there. You fire your callback, and then linear code continues (where you might reference the notes variable. Then the callback resolves and returns a value. To nothing. Because nothing is waiting for it, anymore. Handling it in your callback (the way you did it in your next post) is one way to get around that. =D Yeah, I didn't know that, took me some experimenting to eventually get it to work
1654174871
Julexar
Pro
API Scripter
So.. - is there a way to get what you would roll with a tool? I couldn't find it in the attributes.. I'm trying to use Thieves' Tools for example and have either the result or the bonus to use in the script
1654249783
Julexar
Pro
API Scripter
From what I could find, it isn't possible, taking the ability mod and pb instead
1654249844
Julexar
Pro
API Scripter
Is there a way to give a player proficiency/expertise with a tool/skill through the API?
1654499221

Edited 1654499431
Victor B.
Pro
Sheet Author
API Scripter
Yes but you have to build it.  You need to pull from char sheet.  I've had to go into what Roll20 is publishing, which you can do through an API, and figure out where I need to get info.  Log the entire API message.  
you thinking for the training thing julexar?
there are api's that can adjust things like that might be worth looking at how token mod and token action maker do stuff
1654507555
Julexar
Pro
API Scripter
sam L. said: you thinking for the training thing julexar? Yeah, I am - wanna try and make that
1654843241
Julexar
Pro
API Scripter
So, is there some way to give a character a Tool Proficiency? If so, how? I'm trying to make the training Option, where one can gain Proficiency or Expertise with Skills, Weapons and Tools.
1654864397
Julexar
Pro
API Scripter
Alright, so.. - I figured out how to look for the tools.. - Now, I have the following Question: Are the Tool IDs unique? Like does each tool (such as Herbalism Kit, Thieves' Tools etc) have it's own ID that stays consistent no matter what? What I'm trying to get at is, that I need something that will let me add an attribute to the character that corresponds to the Tool. Or just something that lets the add the Tool to the Character Does something like that exist?
1654864552
Julexar
Pro
API Scripter
This is what I have so far: GitHub Gist
1654866205

Edited 1654867983
The Aaron
Roll20 Production Team
API Scripter
To make your script more user friendly, I'd suggest adjusting some things about your command parsing.  Right now, you expect a command like: !train,Tool,healer's kit But will reject: !train,tool,healer's kit Anything that you'll be inspecting in code, I would suggest sanitizing to either lowercase or uppercase before passing it on to your functions. Furthermore, this will fail: !train, Tool, healer's kit People are used to typing spaces after commas and won't understand why that doesn't work.  At a minimum, I'd suggest accepting spaces around the commas that are splitting up the arguments of your commands: var args = msg.content.split(/\s*,\s*/); However, there is something of an informal standard with API scripts, where arguments are separated with " --": !train --tool --healer's kit which you can do using: var args = msg.content.split(/\s+--/); If you aren't overly attached to using , for a separator, I'd suggest adopting that style.  It has the added benefit that " --" doesn't appear in english naturally, so you can use it with taking whole descriptions without issues: !train --weapon --martial --note|Studied with Master Torin, 3rd knight of his Majesty. In the interest of more maintainable code, I'd suggest organizing your GM-only commands more like this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 handleInput = function (msg) { if (msg.type !== "api" ) { return ; } let args = msg.content.split( /\s+--/ ); if (playerIsGM(msg.playerid)) { switch (args[ 0 ]) { case '!setdown' : setdown(args[ 1 ],args[ 2 ],msg); downmenu(msg); return ; case '!setdc' : state.down.now.dc = args[ 1 ]; return ; case '!setchar' : state.down.now.character = args[ 1 ]; return ; case '!settrain' : state.down.now.train = args[ 1 ]; trainmenu( "Tool" ,msg); return ; } } switch (args[ 0 ]) { case '!down' : downmenu(msg); break ; case '!brew' : brew(args[ 1 ],args[ 2 ],msg); break ; case '!craft' : craft(args[ 1 ],args[ 2 ],msg); break ; case '!work' : work(args[ 1 ],args[ 2 ],msg); break ; case '!trainmenu' : trainmenu(args[ 1 ],msg); break ; case '!crime' : crimemenu(args[ 1 ],args[ 2 ],msg); break ; case '!research' : research(args[ 1 ],msg); break ; case '!commit' : crime(args[ 1 ],args[ 2 ],args[ 3 ],args[ 4 ]); break ; case '!setvalue' : state.down.now.crimeval = Number (args[ 1 ]); crimemenu(args[ 2 ],args[ 3 ],args[ 4 ]) break ; case '!settrain' : state.down.now.train = args[ 1 ]; trainmenu( "Tool" ,msg); break ; case '!train' : train(args[ 1 ],args[ 2 ],msg); downmenu(msg); break ; } }, That will let you execute GM-only commands if the player is a gm and if those commands were specified, but not duplicate calling all the any player commands.
1654936684
Julexar
Pro
API Scripter
Thanks, I will definitely take that into consideration when designing the final version of the script. Do you perhaps know if the Zools and Languages all have unique IDs and how I would be able to get those, no matter which player executes the command? Or rather: Is there a way to add Tool/Language proficiencies through some function or something? If so, how? I couldn't find much on that subject
1654978652
The Aaron
Roll20 Production Team
API Scripter
The IDs for attributes are all unique on a per attribute basis, so if you add Tool/Herbalism kit to two characters, the iDs will be different.  Furthermore, if you delete and re-add it, the ID will be different for the new instance of it. Adding a tool to a character requires adding attributes for a Repeating Row.  I think there is a function for getting a new Repeating Row ID, but the basic idea is that Repeating Sections have a name ("repeating_tool" for the Tools section of the 5e character sheet) and all the attributes that make up a Row in that Repeating Section begin with that name, followed by an ID, followed by the row field ID.  So the name a Tool, the name attribute might be: repeating_tool_-N4JNOpnxLm6HGaDQQl5_toolname You can find these names in the code by exploring the character sheet's HTML with developer tools: To add a new row, you simply create new attributes with a new Repeating Row ID.
1655027212
Julexar
Pro
API Scripter
I see.. - more complicated than I had thought then.. - would probably just be easier to give the Players a message that they can go ahead and add that Proficiency..
1655042636
The Aaron
Roll20 Production Team
API Scripter
It's probably 12 lines of code, assuming there's already a function for Row IDs. 
1655062523
Julexar
Pro
API Scripter
Hm, I see, I sadly don't know the function that could get me the Row IDs.
1655131680
Julexar
Pro
API Scripter
Alright, so I got the problem that now that I am trying to make a brewing system, when I want to remove the amount of time from the character, it removes all the days one after the other. brew = function(rarity,potion,amount,msg) {         let price;         let neededtime;         switch (rarity) {             case 'Common':                 neededtime=1;                 price=25;                 break;             case 'Uncommon':                 neededtime=5;                 price=100;                 break;             case 'Rare':                 neededtime=25;                 neededprice=1000;                 break;             case 'Very Rare':                 neededtime=60;                 price=10000;                 break;             case 'Legendary':                 neededtime=125;                 price=50000;                 break;         }         let char=findObjs({             _type: 'character',             name: msg.who         }, {caseInsensitive: true})[0];         let charid=char.get('_id');         neededtime*=amount;         price*=amount;         char.get("gmnotes",function(gmnotes) {             let time;             time=gmnotes.replace(" Days","");                              if (!(Number(time)<amount)) {                 time=Number(time)-amount;             }             let nnotes="";             if (time && time!=0) {                 nnotes+=String(time)+" Days";             }             char.set("gmnotes",nnotes);         });     }, Does anyone have an Idea why it does that?
1655136298
The Aaron
Roll20 Production Team
API Scripter
Julexar said: Hm, I see, I sadly don't know the function that could get me the Row IDs. Looks like the function is only exposed for sheetworkers.  You can use these functions: const generateUUID = (() => { let a = 0; let b = []; return () => { let c = (new Date()).getTime() + 0; let f = 7; let e = new Array(8); let d = c === a; a = c; for (; 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 = () => generateUUID().replace(/_/g, "Z"); Just call generateRowID() to get a RowID.
1655136545
The Aaron
Roll20 Production Team
API Scripter
I'm not quite sure what you mean by this, if you can go into more detail, I might be able to help. Julexar said: ...it removes all the days one after the other.
1655138248
Julexar
Pro
API Scripter
Well.. - one sec, I'll upload the new version.. Essentially what it does.. - you set a certain amount of downtime days. You select the character you want and speak as it. You issue the command and click the brew option. From there you select the Rarity and the amount. Afterwards should come a menu where you can select a Potion and then begin the process. But when you click it, it just removes all the downtime days. Not all at once though but eventually it shows the message that you don't have enough time to brew as you don't have downtime days left. i have literally no idea why this happens as it's the exact same thing I've used in other functions and it worked there...
1655138462
Julexar
Pro
API Scripter
GitHub Gist
1655138543
Julexar
Pro
API Scripter
Also, I am using ChatSetAttr for setting the Attributes
1655178495
The Aaron
Roll20 Production Team
API Scripter
If you're in the API anyway, you should just set the attributes directly. 
1655182914
Julexar
Pro
API Scripter
True, but I'm lazy and this is way simpler
1655198626
Julexar
Pro
API Scripter
Alright, I managed to fix the Problem somehow. I'm still not sure why it was doing that in the first place but I guess we shall never know
1655359946
Julexar
Pro
API Scripter
Is there a way to make the API toggle Advantage/Disadvantage if you have enabled "Advantage Toggle" on the character sheet? If so, how?
1655376565
Julexar
Pro
API Scripter
Alright, I have the system for the Potions done (don't mind the state of the code too much, I will do some clean up when I get to the point that I can make the release version) GitHub Gist
1655386819

Edited 1655386838
Julexar
Pro
API Scripter
(Small Update since I fucked something up in the previous one) GitHub Gist
1655882901
Julexar
Pro
API Scripter
Quick little Update as I had screwed up something in the Crime Function GitHub Gist
1655893972
Julexar
Pro
API Scripter
Alright, I have now changed the commands to be in the suggested format of !down --sel/name/charid GitHub Gist
1655898060
Julexar
Pro
API Scripter
UPDATE Changed the entire format of the commands It is now possible to edit the downtime of individual characters. When you issue the command you will now see the amount of downtime you have left GitHub Gist