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 .
×

How do you modify/set an attribute on a character with API in the D&D 2024 character sheet

1771906677

Edited 1771909010
Hey guys! So I know that the D&D 2024 character sheet is different in a lot of ways, but how do you set an attribute on a character—ANY attribute— using API ? I have been messing around to do this for a while with no success. This is kind of important to the what I want to do with my campaign. To test the weirdness and try for a work-around, I created a macro tool to send an API command. The macro reads like this: !aat!@{target|character_id}!?{Stat Name?}!?{New Value?} This calls this script in API: const getOrCreateAttribute = (name, cid) => findObjs({ type: 'attribute', characterid: cid, name: name }, {caseInsensitive: false})[0] || createObj('attribute',{ characterid: cid, name: name }); on("chat:message", function(msg) { ////////////////////////////////////////////////////////////////// // GET ID OF THE EFFECTED CHRACTER/OBJECT var sMsg = msg.content||"!!-O_yNYcqS5IfvLryXeyQ"; // <-- Safety character ID var aMsg = sMsg.split("!"); var sID = aMsg[2]||""; var sAlt = "-O_yNYcqS5IfvLryXeyQ"; if(sID == "" || sID == undefined || sID == null || sID.indexOf("character_id") > -1){sID = sAlt;} // END OF GET ID SEGMENT ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // ADJUST AN ATTRIBUTE TEST if(sMsg.indexOf("!aat!") !== -1) { let sAttr = aMsg[3]||""; let sVal = aMsg[4]||""; let nVal = parseInt(sVal)||0; let nPass = true; if(sID == "-O_yNYcqS5IfvLryXeyQ"){nPass = false;} if(sAttr == ""){nPass = false;} if(sVal == ""){nPass = false;} if(nPass == true) { //////////////////////////////////////////////////////////////////// // SET THE ATTRIBUTE /////////////////////////////////////////////// let oAttr = getOrCreateAttribute(sAttr,sID); oAttr.set("current",sVal); //////////////////////////////////////////////////////////////////// // GM ALERT THAT CHANGE WAS PROCESSED ////////////////////////////// sSay = "/w gm Stat Change Initiated. Stat: "+sAttr+", New Value: "+nVal; sendChat("API",sSay); } } // END OF ADJUST AN ATTRIBUTE TEST ////////////////////////////////////////////////////////////////// }); The resulting gm feedback tells me that the code is running all the way through,  but no attribute change occurs. So what am I doing wrong? Any help would be appreciated! Michael
1771909822
timmaugh
Roll20 Production Team
API Scripter
Unfortunately, custom attributes are not properly exposed on the sheet... it's a known issue, but of what priority I don't know. A while back there was an attempt made to update ChatSetAttr to be "beacon ready", but that uncovered a functional gap. Script-created attributes are NOT chat-renderable via @{character|attribute_name} syntax. Also, you can't update a GUI created attribute/computed via script; an attribute of that name will, instead, be created anew. What exists on the sheet (as created through the sheet creation process and via the GUI) are "computeds"... and are handled via a getSheetItem/setSheetItem pair of functions... but that will require your script going asynchronous, not to mention that the getSheetItem doesn't return an attribute object but only the datapoint of value associated with that computed. Attributes created by a script aren't visible in the sheet GUI, and GUI-created "attributes" (computeds) aren't set-able via script as attributes (they'll be recreated). Hopefully this functionality will come... but for now, it's an ersatz UX we find ourselves in.
1771910976

Edited 1771911536
So, can you tell me if what I want to do for my campaign is even do-able by any means? What I want in the end is to recreate a live tabletop campaign from years ago. The characters find themselves dead (initially), and find a path back to life. Along the way, they discover things about themselves, which builds their character (class, abilities, etc). So it would be character creation by discovery. As you can see, the normal D&D builder doesn't work too well for this. I need something that can build elements of the character as they go. Is there any way to do that in the constraints of the system for that character sheet as it stands now?  If not, I guess I build a custom sheet. *sigh* ADDENDUM It occurs to me that at this point I suppose my question more properly belongs under that character sheets forum category. My apologies. I will move it there. timmaugh said: Unfortunately, custom attributes are not properly exposed on the sheet... it's a known issue, but of what priority I don't know. A while back there was an attempt made to update ChatSetAttr to be "beacon ready", but that uncovered a functional gap. Script-created attributes are NOT chat-renderable via @{character|attribute_name} syntax. Also, you can't update a GUI created attribute/computed via script; an attribute of that name will, instead, be created anew. What exists on the sheet (as created through the sheet creation process and via the GUI) are "computeds"... and are handled via a getSheetItem/setSheetItem pair of functions... but that will require your script going asynchronous, not to mention that the getSheetItem doesn't return an attribute object but only the datapoint of value associated with that computed. Attributes created by a script aren't visible in the sheet GUI, and GUI-created "attributes" (computeds) aren't set-able via script as attributes (they'll be recreated). Hopefully this functionality will come... but for now, it's an ersatz UX we find ourselves in.
1771936979
timmaugh
Pro
API Scripter
I don't think you have to go to a wholly new sheet. You could use the 2014 sheet...?
True. I dislike that sheet on a number of other points, but yes. That's an option. I actually love the new sheet other than the inaccessibility of the attributes. So I was really hoping for a solution within that framework. But you can't always get what you want. :-( timmaugh said: I don't think you have to go to a wholly new sheet. You could use the 2014 sheet...?
1772086985

Edited 1772088401
I just tested and was able to get my custom attributes with @{character|customer_attr} so maybe they fixed something timmaugh was referencing? Anyways for the past month or so I've been using custom attributes to store "spellbooks" for my spellcasters, and then creating macros for characters to send that info to chat. They also show up under the attributes tab on the sheet as "user.whatever_attr_name" though referencing them anywhere except the API (chat or macros) you just do "whatever_attr_name" but in the api with getSheetItem and setSheetItem the "user." part is required. To better answer the original question... First, set your sandbox to Experimental (under your game's mod settings you can swap between default and sandbox) Second,  findObjs does not work with Beacon sheets, we now have to use getSheetItem and setSheetItem which are much more limited in how they work but they do work. You can no longer grab the entire character basically you have to grab individual attributes like so...  const beaconAttr = await getSheetItem ( characterId , "user.customer_attr", "current" ); // "current" could also be "max" if you're using max values and setSheetItem(characterId, "user.customer_attr", "some value")
1772110715
timmaugh
Pro
API Scripter
Aikepah said: I just tested and was able to get my custom attributes with @{character|customer_attr} so maybe they fixed something timmaugh was referencing? Anyways for the past month or so I've been using custom attributes to store "spellbooks" for my spellcasters, and then creating macros for characters to send that info to chat. They also show up under the attributes tab on the sheet as "user.whatever_attr_name" though referencing them anywhere except the API (chat or macros) you just do "whatever_attr_name" but in the api with getSheetItem and setSheetItem the "user." part is required. I'm not saying that custom attributes don't work on the sheet. You can create, manually and through the GUI, a custom attribute and it will get prepended with the "user." text. That will respond to chat recall. What I am saying is... You can't create a custom attribute via the API and have it recalled through chat. Nor can you see that API-created custom attribute on the sheet GUI. Nor can you, via the API, create a sheet-visible "computed" (ie, the class of thing created by manual creation of a custom attribute via the GUI). Nor can you, via the API, get a GUI-created custom attribute and set the value *as an attribute*... you have to, as you say, use setSheetItem Nor can you link a token bar to an API-created attribute on the 2024 sheet. I'm happy to be wrong on any of these... it's been a minute since I mocked up the tests I ran to figure this out. Just trying to share the difference of what is (or was) impossible versus what is natively available on any other non-beacon sheet. Has this changed?
timmaugh said: Has this changed? Yeah that's what I mean... My attribute are created via the API, I can see them just fine in the GUI and edit them there. I can also access my API created attribute via chat
1772163947
timmaugh
Roll20 Production Team
API Scripter
Welp. Looks like I'm going to have to go test again. =D
1772164863

Edited 1772164880
Hey, I notice that  Aikepah  used a "user" prefix in front of the attribute (user.custom_attr). Is that how we need to get attribute objects now with API on the 2024 sheet? Put that prefix in front? That could be my problem. Aikepah said: const beaconAttr = await getSheetItem ( characterId , "user.customer_attr", "current" ); // "current" could also be "max" if you're using max values and setSheetItem(characterId, "user.customer_attr", "some value") timmaugh said: Welp. Looks like I'm going to have to go test again. =D
1772170660

Edited 1772171207
Yeah with getSheetItem and setSheetItem you have to prefix all custom attributes with `user.` but anytime you reference those attributes with macros or in chat you don't use the prefix. I will note also in my custom script where I'm writing attributes I've been using libSmartAttributes,&nbsp; <a href="https://github.com/Roll20/roll20-api-scripts/blob/master/libSmartAttributes/0.0.2/libSmartAttributes.js" rel="nofollow">https://github.com/Roll20/roll20-api-scripts/blob/master/libSmartAttributes/0.0.2/libSmartAttributes.js</a> It's what the unrelease beacon version of chatSetAttribute is built on top of, so it has a lot of good info to reference even if you don't use the library.
1772204750
timmaugh
Roll20 Production Team
API Scripter
@Aikepah... just to make sure we're talking about the same thing, can you provide an example of your syntax around creating an attribute that doesn't exist? Just want to make sure of something...
1772211289

Edited 1772211662
@timmaugh sure thing! Updating or creating a new one all goes through the same writeMenuAttr call. Also I have it try with libSmartAttributes first, but in the 2 games I've been testing the script in I don't have libSmartAttributes installed so it just falls back to my manual calls to setSheetItem const writeMenuAttr = async (charId, attrName, value) =&gt; { &nbsp; try { &nbsp; &nbsp; if (typeof libSmartAttributes !== 'undefined' &amp;&amp; libSmartAttributes.setAttribute) { &nbsp; &nbsp; &nbsp; await libSmartAttributes.setAttribute(charId, attrName, value); &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; if (typeof setSheetItem === 'function') { &nbsp; &nbsp; &nbsp; setSheetItem(charId, `user.${attrName}`, value, 'current'); &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; // fallback: legacy attribute &nbsp; &nbsp; const a = findObjs({ type: 'attribute', characterid: charId, name: attrName })[0]; &nbsp; &nbsp; if (a) { &nbsp; &nbsp; &nbsp; a.set('current', value); &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; createObj('attribute', { characterid: charId, name: attrName, current: value }); &nbsp; } catch (e) { &nbsp; &nbsp; log(`SpellBookBuilder: Failed to write menu attr ${attrName}: ${e}`); &nbsp; } }; And that gets called a couple places, but the main place where it's dynamic and can vary the number of attributes it creates depending on the character... here levelsToWrite is dynamically determined and can be "cantrip" or any number from 1 through 9. First time running on a character it creates the attributes and writes the values. If I rerun (say after a level up or long rest in my case) it updates the attribute. for (let lvl of levelsToWrite) { const menu = perLevel[lvl]; let title = (lvl === 'cantrip') ? 'Cantrips' : `Lvl ${lvl}`; const action = `/w @{selected|token_name} &amp;{template:default} {{name=Spell Book - ${title}}} ${menu}`; const attrName = (lvl === 'cantrip') ? `${MENU_ATTR}_cantrip` : `${MENU_ATTR}_lvl${lvl}`; await writeMenuAttr(charId, attrName, action); }
1772231150
timmaugh
Pro
API Scripter
Ahh... OK. Yes. The difference between what I was saying and what your code represents is that by "attributes" I'm referring ONLY to the firebase object represented as an object with&nbsp; type: 'attribute' , not the computeds that can be created via&nbsp; setSheetItem() . I was referring to the fact that you can still call&nbsp; createObj() &nbsp;from the sandbox for a beacon sheet and have it be created, but that won't be retrievable via chat, etc. However, to your point... I believe you're correct that the OP's original need could likely be answered with your approach!