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

Nested attributes that change dynamically

I have the following calls that work as expected (using the 3.5 sheet template): @{selected|HasteNote1} @{selected|Haste} HasteNote1 contains text I want to display when Haste is active. Haste is the flag I assign if Haste is active, it can have 0 or 1 has a value. So I want to read the value Haste has and use it with HasteNote so that I can display the content of the attribute HasteNote0 or HasteNote1 according to the situation. So I tried the following code: @{selected|HasteNote@{selected|Haste}} No go. They work seperately, but not nested. I tried replacing all ending brackets with HTML equivalents, same wiht the second @ or the nested pipe character. I tried wrapping the nested attribute ( selected|Hate )with double square brackets... Basically, I tried everything I could think of... I must be missing something obvious, I'm sure this is simple...
1677381359
timmaugh
Forum Champion
API Scripter
I think you can do that with an attribute whose value is: @{ If that is named attrstart , you can do (I think): @{selected|attrstart}@{selected|HasteNote}@{selected|Haste}} You can also do it with Fetch, if scripts are an option. I know you don't have the pro tag, but if you're playing in a game started by a pro subscriber, you can still use them.
It works has advertised! Why, though? Is it quick to explain? But, I do get an error message despite the result being as expected, I get this error message in the chat: No attribute was found for @{selected|HasteNote} Finally, yes, I play with a Pro member, what is "Fetch"?
1677383922
timmaugh
Forum Champion
API Scripter
You're dealing with a parser that looks for certain text formations (like the @{ combination). At the first pass of the parser, it will resolve what it finds. The way you were doing it (embedding one formation in the other) you were asking it to find an attribute named  HasteNote@{selected|Haste Because when the parser reaches the first } after it thinks it's found an attribute lookup, it closes the lookup and goes to search for what you've supplied. However, the Roll20 order of operations will expand attribute calls 99 times. So what we have to do is supply the text triggers in a timely way so that they aren't there to trip things up the first time the parser passes... but they ARE there the second time. Also... Fetch ( original thread and updated 2.0 thread ) is a metascript that gives you access to almost any property/feature in the game from a script input. It can retrieve data and return it to your command line... allowing you, in a simple/mundane example, to test the heading/token of a token to know if it is facing a particular portion of the map. Probably too much to go into the whole introduction to metascripts, but with Fetch, the formation wouldn't require an attrstart attribute at all...  it would simply look like: @(selected.HasteNote@{selected|Haste}) Using a Roll20 construction to retrieve one attribute, and using that in the Fetch construction. Oh, and that error you're getting is my fault. Shouldn't post solutions from memory while watching TV. I think for the solution I offered before, the proper way to do it would be: @{selected|attrstart}selected|HasteNote@{selected|Haste}} Give that a run, and see what you get.
Works like a charm! I see, basically, you are controlling the order (or slowing down) in which things are retrieved from the character page so that they pop in when it makes sense for the script, very clever! I tried steamlining the code, and I noticed something. I tried to remove all the "Selected" but I had to leave one or else I got a fatal error: TypeError: Rt is undefined I had to leave the second "Selected" to avoid that error. @{attrstart}selected|HasteNote@{Haste}} Fetch looks interesting, I'll send the information to my DM. He is a pretty good programmer so he might be interested in having more tools under his belt. Thanks so much, I really appreciate the help.
Oh, can I bother you some more? If you don't have time, it's fine, just in case you have a quick solution... Right now, I created a Mod script that changes all the paramenters for Haste (except Speed, that is a tough nut...). &{template:default} {{name=**Haste Toggle**}} !modattr --silent --charid @{selected|character_id} --dodgebonus3inuse|?{Haste|On,1|Off,-1} --reflextempmod|?{Haste} --mabtempmod|?{Haste} --rabtempmod|?{Haste} --Haste|?{Haste} !!! However, if I'm careless I might activate it twice in a row instead of decativating it the second time (increasing by 1 again instead of decreasing by 1, then I have to call the macro twice to decrease the values twice. I was wondering, can I turn this ina true toggle call by reading the value in the Haste attribute? If the attribute reads 0, activate Haste and increase the relevant parameters by 1; if it reads 1, turn off Haste and decrease all relevant parameters by 1. Is that doable?
1677419149

Edited 1677419979
timmaugh
Forum Champion
API Scripter
Right... About ditching the references to selected , Roll20 gives you the ability to use short references (no selected or character name) when you're working from the character sheet. In that case, all of your short references automatically point to the sheet you're on. Using the trick I showed, you're only working from the sheet for the first cycle of attribute retrieval... After that you're working from the chat  æ ther, and you have to fully qualify your references. For your other macro, testing the value of Haste, Roll20 doesn't expose a standard syntax for conditionals. Some tricks can be accomplished with math in an inline roll, but not one where you want to dispatch a ChatSetAttr call. That's the bad news... the good news is the lack of a conditional syntax is why I wrote APILogic, another metascript. You can do what you want to do using APILogic and ZeroFrame). I'll type up an example when I'm back in front of my computer, so I don't have to peck out a bunch of specific syntax on my phone. =D
1677420726
timmaugh
Forum Champion
API Scripter
OK, I think this should work. If it doesn't, I'll mock something up in my test game that simulates what you're trying to do. !{{   &{template:default} {{name=**Haste Toggle**}} {{Result=Haste has been turned ***{\&if hastechange = 1}on{\&else}off{\&end}***.}}   !modattr --silent  --charid @{selected|character_id}  --dodgebonus3inuse| hastechange  --reflextempmod| hastechange   --mabtempmod| hastechange  --rabtempmod| hastechange  --Haste| hastechange  {\&define ([hastechange] {&if @{selected|Haste} = 0}1{&else}-1{&end})} }} Note I had to move the ChatSetAttr call all to one line... that's the consequence of using the ZeroFrame batching ability (which we need in order to impose a metascript loop on ChatSetAttr (it would have been skipped because CSA was responding to a normal chat message. That's deep in the meta-sauce, but I can explain more, if you're confused. Point is, it should work. =D SCRIPTS REQUIRED: ZeroFrame Beta , APILogic (one-click), ChatSetAttr (one-click)
1677439195

Edited 1677439415
Wow... I had no idea that so much work had been done to improve the basic Roll20 engine, that is amazing and very impressive. Thank you for the time you took to explain everything and suggest a possible toogle switch, I've learned alot! To make the toggle work we would need to install ZeroFrame and APILogic (I think we already have CSA, I've ben using it, right?). APILogic is neeed to instantiate the If blocks, and ZeroFrame is needed to connect the APILogic script with the CSA script, correct? Thanks again and have a great day!
1677505792
timmaugh
Forum Champion
API Scripter
Pretty much... APILogic gives you the IF blocks. ZeroFrame does a couple of things... including allowing us to structure the message in such a way that CSA will pass through a metascript loop. Using the !{{ }} structure for the message turns multiple commands into a single command, getting us around a known bug where Roll20 will sometimes drop random lines from a multi-command line macro. It also lets us control the order when metascript constructions resolve (using deferrals -- backslashes). In this case, definitions are part of APILogic, and they run before the conditional checks. That means that at the end of the message, when we're defining the hastechange term, since it relies on a conditional, we have to delay the defining until the conditional resolves. One deferral = one backslash (to break up the text pattern so it is missed by the parser). {\&define ([hastechange] {&if @{selected|Haste} = 0}1{&else}-1{&end})} Since the conditional in the Result portion of the template then relies on the defined term, it also has to be deferred. It doesn't have to double-defer (waiting on the term definition which is waiting on a conditional check), because, remember, it runs after the definition. So we should only have to defer it one time, and it will have the definition available to it when it needs to make its conditional check. {\&if hastechange = 1}on{\&else}off{\&end}
Thanks for the explanation, I think I understand the gist of what is happening. I tought I'd try something simple first, to get my feet wet with APILogic on its own, so I tried a simple piece of code, but I cannot manage to get the IF block to do its job, it is either ignored or just rendered in the chat as it is written... Here is my code: &{template:default} {{name=Test Attack}} {{attack= [[1d20+3]]}} ! {{note={&if @|Réjean|Haste = 0 } Haste is not active {&else } Haste is active {&end}. }} {{Saving Throw= vs Will}} I'm sure it is something simple I missed about the "!"...
1677637071
timmaugh
Forum Champion
API Scripter
Bangsy messages (intended for the Script Moderator) have to *begin* with the exclamation point. Then, because that message won't hit the chat output, you have to include the {&simple} tag from ZeroFrame. Also, I think the documentation for APILogic that demonstrated that form of attribute retrieval might be outdated... you can just use a straight Roll20 construction there (or, if it gets more complicated, Fetch). Altogether, your command line would look more like: !&{template:default} {{name=Test Attack}} {{attack= [[1d20+3]]}} {{note={&if @{Réjean|Haste} = 0 } Haste is not active {&else } Haste is active {&end}. }} {{Saving Throw= vs Will}}{&simple} (Requires ZeroFrame and APILogic) Just for perspective, you can do even simpler messages to test (these also use ZeroFrame and APILogic): ! Réjean's Haste is {&if @{ Réjean|Haste} = 0}off{&else}on{&end}.{&simple}
Thanks for the feedback. Ok. I'm confused here. I read the APILogic documentation. I noticed that at the start it said that there was a dependency on liblnline . So we installed that dependency. After installing it, none of the Mod macros worked anymore. Stand alone regular macros still worked, but not the Mod ones. Something so simple as: /w Réjean &{template:default} {{name=**HP Modification**}} !modattr --silent --charid @{selected|character_id} --hitpoints|?{HP (Plus or Minus)|0} !!! {{Result=Modify HP value for @{selected|character_name} by ?{HP (Plus or Minus)}.}} didn't work anymore. It was working with only APILogic installed, but not when we installed liblnline on top of APILogic . So we remove liblnline and all the Mod macros started to work again. Also, I didn't see anything about ZeroFrame in the APILogic documentation page, we need ZeroFrame for APILogic to work? Sorry to bother you with such newbie questions...
1677684441
timmaugh
Forum Champion
API Scripter
No worries, Jean-Guy... The reference to libInline being required I think is a relic of the nascent days of the metascript-o-verse, before they big-banged themselves into many different scripts with different responsibilities. APILogic does not require libInline... I have corrected the documentation to that effect. ZeroFrame requires libInline, so we'll still want to get to the bottom of what is going on, but really quickly, a metascript primer: When your sandbox boots up, the various scripts you have installed are collected into a single, long js file... listed one after the other. When that file is together and run, functions that are built to be executed immediately are executed, from the top of the file to the bottom. Importantly, this includes event registrations - those functions that want to listen to a particular kind of event in the game: token changes, character changes, chat events, etc. So, from top down in your script deck (or, from your historically first script installed to your last), the scripts have a chance to register for events, forming a queue of scripts waiting to take action when that event occurs. Imagine those listening functions like workers on an assembly line. In the case of a chat event, the message comes down the assembly line, and the first worker gets a chance to act on the message... but then the message keeps going to the next worker, etc., in the order the workers registered. In the case of chat events, most standard functions (most of our workers) will do nothing to the message, because we structure our messages to trigger particular workers: TokenMod will act on messages intended for TokenMod; ChatSetAttr on ChatSetAttr  messages; SmartAoE on SmartAoE... etc. These are standard scripts. Next, imagine that every worker on the left side of the assembly line are metascript workers. They're going to use a trick to register early (sooner to see the message as it comes down the assembly line). All of those workers walk forward (still in their same order) until they fill all of the first stations along the assembly line. Workers on the right side (our standard workers), move back (also maintaining their internal order) until the first standard worker is immediately after the last meta-worker. Metascript workers don't intend to own the message as it moves down the assembly line... they just look for work they need to do to it (like evaluating conditionals to change the command line, "virtually" selecting tokens, looking up game information not available through standard Roll20 syntax, etc.). This is great... it doesn't matter when you install your metascripts, they'll jump to the front of the queue and do their thing. But... ...their internal order is always the same: the order in which you install them. That means that, depending on how you install them, you could use one to drive a change that a second would react to, but not the reverse. For instance, Fetch returns data from the game. APILogic evaluates conditionals. Installing Fetch first would mean that you could use a Fetch construction inside a conditional... ie (pseudo-syntax): if (fetch: the token's top property) is greater than 200 then do ... this... But you could not use a conditional to choose which fetch construction to return...: @(selected.{&if a = b}attribute_1{&else}attribute_2{&end}) ...because the conditional wouldn't have the time to process by the time Fetch went to look for an attribute, meaning Fetch would be using all of that APILogic syntax as the name it was looking for. The same works in reverse if you instead install APILogic before Fetch... now the second case would work, but not the first. This is where ZeroFrame comes in. In the big-bang split of different functionalities from the original APILogic, ZeroFrame got roll management and metascript ordering. The metascripts will work without ZeroFrame, but they will always work in the order you install them... one to the next. With ZeroFrame, no matter the order you install them, it will... 1) run them in a default order that satisfies 90% of use cases, 2) loop over the installed scripts in case one meta-operation exposes a text construction that another metascript, higher in the order, would want to act on, 3) allow you to configure the metascripts in a different order for all messages, 4) allow you to configure the metascripts in a different order for a single message, and 5) allow you to "defer" individual text constructions (breaking up the pattern that might otherwise be detected) so that the construction isn't detected for 1 or more cycles of the ZeroFrame loop. A recent (beta) change to ZeroFrame also gives it the ability to "batch" up commands, allowing you to share die rolls between individual commands in a multi-command message, and to avoid a known Roll20 bug where random commands are sometimes dropped from a multi-command macro. That's probably more information about the metascripts than you probably need, but it explains why APILogic will work with or without ZeroFrame, and what the benefit is of having both installed. Regarding libInline... Can you share the scripts your game has installed? (A screen-shot will do, if there are a lot of them) I have the one-click version installed in my various test games, and they are working. If you want ZeroFrame, then when you install it from the one-click it will pull down libInline, as well. If the problem persists at that point, I would look at the scripts you have installed. There is a bug where each script requires a blank line at the bottom (so that things don't break with the lot of them are concatenated into that single large file I mentioned). Typically this affects scripts installed after that problematic script, so some might work and others might not. If you can't figure out which is causing the problem, have your gm shoot me an invite and promote me to GM, and I can try to figure it out.
Thanks for the detailed explanation. We loaded liblnline before APILogic and all our macros worked again, including those using Chatsetattr . So all I need is to get APILogic to work, it seems I must also load ZeroFrame , even if this is't mentioned in the ReadMe file for APILogic , correct? Or else we need ZeroFrame because we are using Chatsetattr together with APILogic ? If we need ZeroFrame , does it matter when it is loaded? Before or after the other three ( Chatsetattr , APILogic and liblnline )? If APILogic can run without ZeroFrame , what do I need to change to the code you posted in a previous reponse so that a single simple conditional would work? Here's the code again: !&{template:default} {{name=Test Attack}} {{attack= [[1d20+3]]}} {{note={&if @{Réjean|Haste} = 0 } Haste is not active {&else } Haste is active {&end}. }} {{Saving Throw= vs Will}}{&simple} Thanks for everything.
1677725892
timmaugh
Forum Champion
API Scripter
For what you currently have, you need ZeroFrame not because it will make APILogic work, but because you want to output your roll template message to the chat. You could use APILogic in a command line that was intended straight for ChatSetAttr... or straight for TokenMod... and it would work. By the time that script saw the message, APILogic would have done its thing. The conditional would be evaluated. That part would work without ZeroFrame. However, when you use {&simple}, you're telling ZeroFrame, "Whatever the command line looks like at the end of the metascript processing (when there is no more meta-work to do), send that to the chat." Since your message isn't intended for a script but rather for the chat output (using a roll template), we have to use the {&simple}, which means we have to use ZeroFrame. Hope that makes sense.
Ha, ok, yes, it makes sense. Can you give me a simple example of an APILogic script intended straight for ChatSetAttr With that I think I'll really see the full picture. Thanks again!
1677854837

Edited 1677855943
timmaugh
Forum Champion
API Scripter
Sure. In a system where health was reflected in a mix of HP and Constitution... let's say that Ruprecht the Fish Lobber (that was the last time the party let Ruprecht hand out nicknames) has an ability to heal people by lobbing a fish at them. If he lobs a carp, he can add 5pts to HP. If he lobs a trout, he can add 2pts to Constitution and a +3 to HP. And if he lobs a herring, it's a +1 to both Constitution and HP. Assuming the recipient of the lobbed fish is selected, here is the command line for the carp: !modattr --sel --HP|5 ...and here is the command for the trout: !modattr --sel --Constitution|2 --HP|3 ...and for the herring: !modattr --sel --Constitution|1 --HP|1 So here are ways that could be connected in a single command line using APILogic... !modattr --sel {&if ?{Fish|Carp|Trout|Herring} = Carp}--HP|5{&elseif ?{Fish}=Trout}--Constitution|2 --HP|3{&else}--Constitution|1 --HP|1{&end} ...or, using a nested IF in the Constitution section, and a logical OR in the parent conditional of that nesting... !modattr --sel --HP|{&if ?{Fish|Carp|Trout|Herring} = Carp}5{&elseif ?{Fish}=Trout}3{&else}1{&end} {&if ?{Fish} = Trout || ?{Fish} = Herring}--Constitution|{&if ?{Fish} = Trout}2{&else}1{&end} {&end} Now, let's say that Ruprecht has leveled-up his fish lobbery, and he's gained the ability that (specifically for herring) when lobs said fish, if Ruprecht is wearing his mackerel slippers, he can also boost the Morale of the recipient of the tossed herring by +1 (he is a much more stunning sight in those slippers, to be sure). We'll assume that Ruprecht's character sheet has a set of four attributes for equipment he might have equipped: equipped1 equipped2 equipped3 equipped4 And, to keep it simple, we'll assume that these would be filled with text, and that Ruprecht only has one pair of slippers, so that if he notes "slippers" in any of these attributes, the mackerel slippers are equipped. In order to apply the morale bonus, we have to know that the fish is a herring AND that one of those attributes contains the text "slippers". We can do that with a logical AND, grouping, and a series of logical ORs: {&if ?{Fish} = Herring && ("@{Ruprecht|equipped1}" ~ slippers || " @{Ruprecht|equipped2}" ~ slippers || " @{Ruprecht|equipped3}" ~ slippers || " @{Ruprecht|equipped4}" ~ slippers )}--Morale|1{&end} You can tack that on the end of any of the above formations, and the Morale boost will be included if the criteria are met. ==== EDIT ==== Edited to include quotation marks around the equipped checks, just above. If there are spaces in what is returned, that would break the parser unless we wrapped them in quotation marks. Also, now that I've done that, I realize that because of the nature of the test I was applying to the four attributes, I could have skipped the logical OR and the grouping altogether and just treated them as one long string: {&if ?{Fish} = Herring && "@{Ruprecht|equipped1}  @{Ruprecht|equipped2}  @{Ruprecht|equipped3}  @{Ruprecht|equipped4} " ~ slippers }--Morale|1{&end} Point is, there are lots of ways to structure your conditionals based around the tests you need to run, and lots of ways to construct your command line to make those conditionals make your life easier.
Lol, I'm so glad I asked for a simple example... The good news is that I managed to get all of that to work, and I even, maybe, fixed a typo. For some reason the last attribute ( Morale , whihc I replaced with dex-temp ) didn't work. I thought that it was because I was testing with repeating elements of a repeating section... I was worried my slippers weren't bright enough! So I tried with my golden backpack instead! But eventually I added a space just between the } and the - of " )}--dex-temp " (see my code below). Then everything worked. Why is a space needed there, but not in the previous section of the code? !modattr --sel {&if ?{Fish|Carp|Trout|Herring} = Carp}--hitpoints|5{&elseif ?{Fish}=Trout}--con-temp|2 --hitpoints|3{&else} --con-temp|1 --hitpoints|1{&end}{&if ?{Fish} = Herring && ("@{Réjean|repeating_equipment_1_equipmentname}" ~ Backpack || "@{Réjean|repeating_equipment_2_equipmentname}" ~ Backpack || "@{Réjean|repeating_equipment_3_equipmentname}" ~ Backpack || "@{Réjean|repeating_equipment_4_equipmentname}" ~ Backpack )} --dex-temp|1{&end} Thank you so much for the examples, you went above and beyond! I'm sending you a virtual beer!