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

Martial script with drop downs and accounting for class abilities (superiority dice, rage, etc)

so I am trying to modify the customspellbook script Nick Olivo posted to his youtube to marital abilities (code as it is now below).  I want the code to do the following:      account for how many hands are used to utilize the weapon      drop down to apply class ability (yes or no answer - are you raging?)     check if class ability is available (if character has any rage instances available or if they have all been expended)             message if none are available     roll attack adding any class ability modifiers (superiority dice) or global modifiers (bless) to the roll     roll damage adding any class ability modifiers (superiority dice) or global modifiers (bless) to the roll     Whisper attack roll, damage roll (including critical strike damage), and any DC to the DM     Adjust remaining number of class ability (if used) Issues: What is the proper way to address checking for a critical strike?  Where does the script look? Where would the script look for class ability?   where would the script look for global attack or damage modifier? Here is the script so far.  Warhammer is kind of done.  I stopped on longsword when I realized that I had no idea how to pull superiority dice from the character sheet. on("chat:message", function(msg) { if (msg.type == "api" && msg.content.indexOf("!custommartial") == 0){ var args = msg.content.split(/\s+/); var weapon = args[1].toLowerCase(); var numberhands = Number(args[2]); var tokenid = msg.selected[0]._id; var token = getObj("graphic",tokenid); var charID = token.get("represents"); var tokenName = token.get("name"); switch(weapon){ case "warhammer": Warhammer(numberhands,tokenName); break; case "longsword": longSword(numberhands,tokenName); break; } } }); function longSword(numberhands,tokenName){ var myAttack=""; var myDmg=""; switch(numberhands){ case "1": myDice=""; break; case "2": myDice=""; break; } myAttack+=`{{Attack [[1d20]]}}` } function Warhammer(numberhands,tokenName){ var Myattack=""; var Mydmg=""; //var Mydice = Number(args[1]); switch(numberhands){ case "1": Mydice="8"; break; case "2": Mydice="10"; break; } /* DmgOutput+=`{{Attack [[1d20+@{${tokenName}|spell_attack_bonus} ]] ` + `for [[1d8 + @{selected|spellcasting_modifier}]] + [[1d8]] Force Damage}}`; =[[@{I am Cleric test|d20}cs>20 + 3[STR] + 2[PROF]]]}} */ Myattack+=`{{Attack [[1d20cs>20+@{${tokenName}|Strength_mod}+@{${tokenName}|PB|}[Prof Bonus]+@{${tokenName}|Global_Attack_mod}[Global_Attack_mod_name]]]}}` //Mydmg+=` for {{Damage= 1d@{${mydice}}+@{${tokenName}|Strength_mod}+@{${tokenName}|Global_Damage_mod}]]}}`; var outputMessage = `&{template:default} {{name=Warhammer}} ${Myattack} + ${Mydmg}`; sendChat("API",outputMessage); } Any and all help is appreciated. Thanks,
1599264386
timmaugh
Pro
API Scripter
Hey, RC... Here are a couple of thoughts... Your functions, as written, are in the global namespace. Nothing wrong with that in general, unless someone else came along and wrote a new Warhammer function in the global namespace and you tried to incorporate it beside yours. I believe with the function invocation pattern you are using, the sandbox won't break but the LAST one to register to the compiler is going to be the one that is called when you ask for the function Warhammer. If you moved them into a namespace for your custom script, then they will have access to variables declared/derived in the main body of the function, which can help with not having to pass all of the variables they might need... they'll just to be able to directly access "parent" level variables. That makes it easier to do things like utilize the charID that you derive from the token's "represents" property. Once you have that, you need to use either: getObj() findObjs({})         ...or, if it is specifically an attribute you're looking for... getAttrByName() ...to get the value you need. Do you know where the value you need (for instance, "superiority") is on the character sheet? Or what sort of object it is (attribute, repeating attribute, or ability)? If it isn't clear from the character sheet, you can use my xray script to walk the character sheet and try to figure out where the data is that you need. When you have that piece of info, post back and we can work through the specifics of how to get it, how to deal with it, and where to put it in your script. If you're not sure how to convert what you have to a namespace, we can help with that, too.
Yeah lets start with the namespace.    I get the concept of what you are saying, but not sure how to write the script to the namespace rather than the global. As for the rest, I know where the ability is in the character sheet visually.  Not sure if that is what you mean by where it is on the character sheet.  It does not show up on the attributes section of the attributes and abilities sheet.  The first fighter ability "Second Wind" occupies the class_resource_name line, and the ammo for the ranged weapon occupies the other_resource_name line.  There is no other line for the other two abilities for that item.  I would record the use of the ability, but there is no link to push on the character sheet that is sent to chat to see how/where it is written.   This may need to wait a week or so as I am going on vacation Monday an will be out of pocket for a week.  So no rush on the solve.
1599354607
timmaugh
Pro
API Scripter
Your namespace is a way to limit your footprint to the global namespace to 1 total entry. Everything else that you code up, build as a function, etc, is going to be within that namespace. It can change the availability of functions, and it can change the way you call them. If your Warhammer function is in your namespace, "brickhouse", then pretty much everywhere within brickhouse you can call Warhammer as you do, now: Warhammer(...) ...but it would be unavailable to a function from OUTSIDE your brickhouse. If you only had a Warhammer function in the brickhouse namespace (and nothing in the global namespace), then a function from another namespace that tried to call Warhammer as we just did would throw an error. The only way for a function from another namespace (even the global namespace) to call your Warhammer function (built within your brickhouse namespace) would be if the brickhouse namespace exposed the function as part of its interface (I'll get to that in a minute). In that case, a exo-brickhouse function would have to invoke the Warhammer function more like this: brickhouse.Warhammer(...) Make sense? The trade-off of extra steps to arrive at the proper location of the function is paid for in terms of the protection of making sure that people intend to call YOUR Warhammer function, and when they do, they GET your Warhammer script. Here are the basics of carving out your own namespace (I'll use fat arrow syntax, but you can do the function invocation pattern, the way you did) const brickhouse = (() => {                    // this will be your namespace declaration; see note from last line for more info     const handleInput = (msg) => {             // this is where the api handle is evaluated; see registerEventHandlers function     };     const registerEventHandlers = () => {      // this establishes any event listeners we need (chat, graphic changes, handout changes, etc.)         on('chat:message', handleInput);       // declare handleInput as the function to run when a chat comes through     };      on('ready',() => {                          // when ready, we need to add those event listeners         registerEventHanders();     };     return {                                   // public interface of the namespace; this is where you'd expose the internal functions, if necessary     }; })();                                          // the parentheses turn this into an immediately invoked function expression (IIFE)                                                // giving you access to the return interface anytime you reference the namespace So, with your code added in, it would look something like... const brickhouse = (() => {     const handleInput = (msg) => {         if (msg.type == "api" && msg.content.indexOf("!custommartial") == 0){             var args = msg.content.split(/\s+/);             var weapon = args[1].toLowerCase();             var numberhands = Number(args[2]);             var tokenid = msg.selected[0]._id;             var token = getObj("graphic",tokenid);             var charID = token.get("represents");             var tokenName = token.get("name");             switch(weapon){                 case "warhammer":                     Warhammer(numberhands,tokenName);                     break;                 case "longsword":                     longSword(numberhands,tokenName);                     break;             }         }     };     const Warhammer = (numberhands,tokenName) => {         // ... Warhammer code here     };     const longSword = (numberhands,tokenName) => {         // ... longSword code here     };     const registerEventHandlers = () => {         on('chat:message', handleInput);     };     on('ready',() => {         registerEventHanders();     };     return {     }; })(); Now that I've gone through that... a couple of thoughts. I said your Warhammer and Longsword functions could access the variables you've already determined by the use of a namespace. I was short-handing things a bit. They can use those variables directly if they are children of the handleInput function where the variables are derived/declared (so the declarations of warhammer and longsword happen WITHIN the handleInput function). Putting them outside, as I have, you would still have to pass the variables the way you are. (But you still get the other protections of the namespace model.) Putting them outside of the handleInput function also makes it more straightforward to expose them to an exo-brickhouse function. To expose a function (or object, or variable, etc.), put it in the returned object (between the {}). A good model to follow here is to have internally lowercase or camelCase function names, but use TitleCase for the exposed version: return {     WarHammer: warHammer,     LongSword: longSword }; I don't know that you will need to expose either function, but it's good to know how if you need to. One more point... your capitalization standard is a bit off... Warhammer is leading with a capital, while longSword is camelCase. OK, one more/more point. =D I find long if-blocks that could otherwise be exits to create too much whitespace in my code. You have to indent everything until that api check finishes. If you reverse your logic, you can exit the function at a failed 'if' statement, and move on if it passes: if (msg.type !== "api" || msg.content.indexOf("!custommartial") !== 0) return; Now, when you're back from vacay, let's settle the other problem you were having, too!
ok I think I get the namespace concept.  It is kind of like using private, public and just normal objects in VBA.  Not exactly the same, but close. The capitalization changed as I learned about camelCase.  I am self-taught in VBA so I have a bunch of bad habits or I am ignorant of some best practices.  I change as I learn and I just found out about camelCase. Not sure I completely get the return or the comments on the If.  Keep in mind that I got this (the base code) from someone else and I am changing it to include the functionality that I want.  If there are better ways then I will definitely try and incorporate them or hell I am open to re-writing the whole thing if needed.   Last point then I gotta go pack.   I watched your YT videos on your script.  DUDE! that thing is awesome.  I see so many things I can use that for.  Will take a few passes to completely get how to use it, but def something I am gonna be playing with.  I was already able to use xray to locate the class abilities.  They are not listed on the attributes page at all, but xray found them.  I am not certain how we will be affecting them, but learning their names is a step in the right direction. Thanks for the help.
1599398895
timmaugh
Pro
API Scripter
rcbricker said: ok I think I get the namespace concept.  It is kind of like using private, public and just normal objects in VBA.  Not exactly the same, but close. The capitalization changed as I learned about camelCase.  I am self-taught in VBA so I have a bunch of bad habits or I am ignorant of some best practices.  I change as I learn and I just found out about camelCase. It seems you can't throw a rock on these forums but that you hit an old VBA programmer! I cut my teeth on Access Databases before moving on to Office interoperability and automation. Eventually I dabbled in web design and php, but mostly back-end stuff, so I didn't have a ton of use for javascript. Then I found Roll20. =D And thanks for the comments on the script and videos. That was definitely a labor of love, and it has a learning curve for sure (something I'm looking for ways to flatten), so it's good to hear if it has helped someone! So, really quickly on your issue...the line about the if statement and returning is just a matter of preference. The way you wrote it will work, for sure. But here's what I meant... if (msg.type == "api" && msg.content.indexOf("!custommartial") == 0){        // <======== This block doesn't close... var args = msg.content.split(/\s+/); var weapon = args[1].toLowerCase(); var numberhands = Number(args[2]); var tokenid = msg.selected[0]._id; var token = getObj("graphic",tokenid); var charID = token.get("represents"); var tokenName = token.get("name"); switch(weapon){ case "warhammer": Warhammer(numberhands,tokenName); break; case "longsword": longSword(numberhands,tokenName); break; } }                                                                            // <======== until way down here Since you're "carrying" that block closure all the way through, all of the lines between my two comments are indented. That can start to eat up screen real-estate, and can indent a LOT of lines if there is a significant amount of processing between. The thing is, your if statement is an all or nothing: if the api handle doesn't match, you don't want to do anything... you just want to return (end the script processing). By reversing the logic of your tests, I was able to handle that on one line, so everything else could remain at its normal level of indentation. If you can't figure out modifying the attribute when you get back from vacation, post back and we'll keep working through it! But it sounds as if you have a good start already, so you might be halfway home. Enjoy your vacation!
1599405876

Edited 1599405904
GiGs
Pro
Sheet Author
API Scripter
rcbricker said: I was already able to use xray to locate the class abilities.  They are not listed on the attributes page at all, but xray found them.   The Attributes page doesnt hold all the attributes. It won't show those inside repeating sections, autocalc attributes, or attributes that are still at their default values. The Attributes page dates back to the time before we had the Character Sheet feature, and you should generally not use it if you are using a character sheet. If you need to manually create attributes that you know definitely do not exist in the character sheet, it'll be fine - but you should otherwise leave that section alone. And definitely dont worry when it doesnt show all the attributes.
GiGs said: rcbricker said: I was already able to use xray to locate the class abilities.  They are not listed on the attributes page at all, but xray found them.   The Attributes page doesnt hold all the attributes. It won't show those inside repeating sections, autocalc attributes, or attributes that are still at their default values. The Attributes page dates back to the time before we had the Character Sheet feature, and you should generally not use it if you are using a character sheet. If you need to manually create attributes that you know definitely do not exist in the character sheet, it'll be fine - but you should otherwise leave that section alone. And definitely dont worry when it doesnt show all the attributes. thanks Gigs for the info on the attributes page.  Right now I use it as it gives the proper names for items I want to use.  Not the best way to learn them but it is a good place to start (unless there is a better way to find the name of an object).
timmaugh said: rcbricker said: ok I think I get the namespace concept.  It is kind of like using private, public and just normal objects in VBA.  Not exactly the same, but close. The capitalization changed as I learned about camelCase.  I am self-taught in VBA so I have a bunch of bad habits or I am ignorant of some best practices.  I change as I learn and I just found out about camelCase. It seems you can't throw a rock on these forums but that you hit an old VBA programmer! I cut my teeth on Access Databases before moving on to Office interoperability and automation. Eventually I dabbled in web design and php, but mostly back-end stuff, so I didn't have a ton of use for javascript. Then I found Roll20. =D And thanks for the comments on the script and videos. That was definitely a labor of love, and it has a learning curve for sure (something I'm looking for ways to flatten), so it's good to hear if it has helped someone! So, really quickly on your issue...the line about the if statement and returning is just a matter of preference. The way you wrote it will work, for sure. But here's what I meant... if (msg.type == "api" && msg.content.indexOf("!custommartial") == 0){        // <======== This block doesn't close... var args = msg.content.split(/\s+/); var weapon = args[1].toLowerCase(); var numberhands = Number(args[2]); var tokenid = msg.selected[0]._id; var token = getObj("graphic",tokenid); var charID = token.get("represents"); var tokenName = token.get("name"); switch(weapon){ case "warhammer": Warhammer(numberhands,tokenName); break; case "longsword": longSword(numberhands,tokenName); break; } }                                                                            // <======== until way down here Since you're "carrying" that block closure all the way through, all of the lines between my two comments are indented. That can start to eat up screen real-estate, and can indent a LOT of lines if there is a significant amount of processing between. The thing is, your if statement is an all or nothing: if the api handle doesn't match, you don't want to do anything... you just want to return (end the script processing). By reversing the logic of your tests, I was able to handle that on one line, so everything else could remain at its normal level of indentation. If you can't figure out modifying the attribute when you get back from vacation, post back and we'll keep working through it! But it sounds as if you have a good start already, so you might be halfway home. Enjoy your vacation! I think i see what you are saying.  Just write an IF that checks if it is API and if not end/return out of the process.  Then carry on with the script.