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

Making an Attribute calculate it's value based on another attribute

February 06 (11 years ago)
Hi,

I'm fiddling around with the attributes on the character pages. I noticed some of what I'm adding here is sort of redundant because it's a product of two or more numbers I entered already.

I figured, seeing as an ability can generate a value based on attributes, then why shouldn't an attribute be able to do the same? Only I don't think they are.

Just checking to make sure though.
Macros can't edit data, so you won't be able to assign the end-result to an attribute.
You can make an API script since you have Mentor access, but plain macros won't be able to do it.
February 06 (11 years ago)

Edited February 06 (11 years ago)
DXWarlock
Sheet Author
API Scripter
I was actually working on a script to do that for my game. it take HP and SDC and adds them into a new attribute called -HPSDC
You can modify it how you wish, and it assumes the attributes are already there on the character or returns doing nothing. if you need it to create it if it doesn't exist, you will need to add that part.
Also I use HPSDC as a bar on tokens for 2 stats, so Im doing it so it updates on token changes..if you need it to trigger elsewhere, change the on(whatever)..but the basics of what you need are here.

on("change:token", function (obj) {
	if(obj.get("represents") == '') return;
	var oCharacter = getObj('character', obj.get("_represents"));
	var cBy = oCharacter.get('controlledby');
	var oHP = findObjs({_type: "attribute",name: "Hitpoints",_characterid: oCharacter.id})[0];
	var oSDC = findObjs({_type: "attribute",name: "SDC",_characterid: oCharacter.id})[0];
	var oSDCHP = findObjs({_type: "attribute",name: "-HPSDC",_characterid: oCharacter.id})[0];
	if(oHP == undefined || oSDC == undefined || oSDCHP == undefined) return;
	//SDC-----------------    
	var cSDC = parseInt(oSDC.get("current"));
	var mSDC = parseInt(oSDC.get("max"));
	//HP-----------------    
	var cHP = parseInt(oHP.get("current"));
	var mHP = parseInt(oHP.get("max"));
	//SetMax-----------------------------
	var mSDCHP = +mSDC + +mHP;
	oSDCHP.set('max', mSDCHP);
	//SetCurrent----------------------
	var cSDCHP = +cSDC + +cHP;
	oSDCHP.set('current', cSDCHP);
});

February 06 (11 years ago)
Thanks a lot. I see that there are a lot of scripts out there, sadly I'm not very proficient when it comes to making use of them. This one is fairly short and may provide me with something to practice with.

I think I can make most of the basic functions I thought of from this, but it seems there are people out there who have made entire API scripts to simply generate a pathfinder sheet, which is essentially what I need. I just don't think I'd be able to make it work... So here goes nothing :) I'll try working my way from here.
If you're specifically trying to create PlayerSheet's with attributes appropriate for Pathfinder...
I've been making a point to keep a PlayerTemplate sheet and pre-populating it with attributes for everything you'd find on a PF character sheet and a few universal Token Actions (initiative, perception, etc, etc).

That lets me Duplicate that template sheet to add a new player without having to re-add every attribute and action without the need of scripts.
The player then just needs to go in and set the values, which you'd have to do anyway at some point. I write the actions and macros to do any attribute derivations needed.

Just my 2 cents.
February 06 (11 years ago)
That's basically what I'm doing now, but I'm not using any API scripts and everything is calculated manually. What I'd like to use API scripts for, if possible is to calculate some stats automatically. I could find several awesome uses for this. I just need to get better at understanding how to work with the API.
February 07 (11 years ago)

Edited February 07 (11 years ago)
on("change:token", function (obj) {
if(obj.get("represents") == '') return;
var oCharacter = getObj('character', obj.get("_represents"));
var cBy = oCharacter.get('controlledby');
var oStrength = findObjs({_type: "attribute",name: "Strength",_characterid: oCharacter.id})[0];
var oBAB = findObjs({_type: "attribute",name: "BAB",_characterid: oCharacter.id})[0];
var oCMB = findObjs({_type: "attribute",name: "CMB",_characterid: oCharacter.id})[0];
if(oStrength == undefined || oBAB == undefined || oCMB == undefined) return;
//BAB-----------------
var cBAB = parseInt(oBAB.get("current"));
var mBAB = parseInt(oBAB.get("max"));
//Strength-----------------
var cStrength = parseInt(oStrength.get("current"));
var mStrength = parseInt(oStrength.get("max"));
//SetMax-----------------------------
var mCMB = +mBAB + +mStrength;
oCMB.set('max', mCMB);
//SetCurrent----------------------
var cCMB = +cBAB + +cStrength;
oCMB.set('current', cCMB);
});

So, I've been trying to make it calculate the stat CMB which is made up by Strength and BAB, by changing the script you provided. I think I did it right, but... It's not working.

I assumed the script was activated by !change [token name].

Did I not get how this works?

Also, I got this error message:

ReferenceError: cCMB is not defined at evalmachine.:21:22 at eval (


February 07 (11 years ago)

Edited February 07 (11 years ago)
Your script is getting called every single time you move a token or make any kind of change to that token. What you're looking for is:

on("chat:message", function(msg) {
    // Exit if not an api command
    if (msg.type != "api") return;
    
    // Get the API Chat Command
    var n = msg.content.split(" ");
    var command = n[0].toLowerCase();
    
    // Set the temp hp value on the selected token(s) or target(s)...
    // Usage:   !setthp amount target
    if (command == "!change") {
	// Do your change attributes stuff here
    }
});
RE: Your Error Message...

var CMB = +cBAB + +cStrength;

You didn't put the c before CMB to make it cCMB.
February 07 (11 years ago)
*sigh* Every time I post something on these forums, I end up feeling stupid... Oh well, at least I end up with a script that works. Thanks!
February 07 (11 years ago)
Wait, I only just saw your added code bit.

It seems to talk about temporary HP, but I notice that's just in the commentary and has no effect on the actual script.

Basically, if I wanted to merge that with what I'm currently using, would I just add the corrected script I made before the end of the one you posted?

I'm not very good at this btw. This is my first attempt at making my own script.
February 07 (11 years ago)
DXWarlock
Sheet Author
API Scripter
Ah yea, sorry mine I posted updates on token changes, movement, opening, ect. Since I use it for a bar stat. so when they move a token any changes to the 2 stats updates then too.
So every time they touch the token it takes 'HP' and 'SDC' from the sheet, adds them and adjust the' HPSDC' stat.

But a bit confused on what you want to do, are you wanting to take 2 stats and total to another one, or use a !command to set a stat to the commands amount? like !change 40.
February 07 (11 years ago)
What I want to do, is make a script that Adds Attribute A to Attribute B to get Attribute C.

All the attributes are already on the character, so they exist already.

I either want it to do this automatically, or by using a command, but I would prefer if it did it automatically.
February 07 (11 years ago)
Note, what you sent me, did get the job done, but I understand from Honeybadger that it might be a bit big on resources? Not sure here.

I did notice that it seemed like I had to assign the various attributes I wanted to use into bars before it was willing to update...

February 07 (11 years ago)

Edited February 07 (11 years ago)
DXWarlock
Sheet Author
API Scripter
The one you have should work, anytime a token is moved or updated, the number will change automatically. Unless you want to do a timer loop or such to loop thru all tokens on the board (which isn't the best idea) you'd need to use some kinda trigger on the token for it to know to update. I use moving/looking at the token. As it always updates when its their turn to move.

I cant think of a instant way to update it, as it needs something that can be triggered for the API to know to do something.

Only thing i see wrong is what Badger said.

var CMB = +cBAB + +cStrength;
oCMB.set('current', cCMB);

should be

var cCMB = +cBAB + +cStrength;
oCMB.set('current', cCMB);
February 07 (11 years ago)
Well in that case, thanks for your help guys.

As I said, it did get the job done, it just took a while before I figured out how to get it to update.

I can find a lot of appliances for this script using it in conjunction with the already established pathfinder sheet script.
February 07 (11 years ago)

Edited February 07 (11 years ago)
DXWarlock
Sheet Author
API Scripter
NP, maybe someone else knows a sly way to get it to update auto when a variable changes on a sheet without having to touch the token to trigger a change:token. If so ill use it also :)

The only downside to combining stats to auto updates on token events, and using it on a bar is you cannot change the bar value itself. soon as you change it, the script sees it and does the math and set it back, so the sum match the total. (I just change one of the 2 base stats on the sheet and let the bar update if I need to adjust them.)

For example if HP was 10 and SDC was 10, HPSDC would be 20. You click on the token do a -2 to HPSDC and make it 18, soon as you do the API goes "oh..token change! let me do the math 10+10=20 so set HPSDC to 20!"
I have to change HP or SDC itself by -2 to get the 18.
February 07 (11 years ago)
Ahh, but seeing as I'm not using it as a bar, this shouldn't become a problem for me, right?

Just out of curiosity, what is SDC? I realize it must be some sort of value from what ever system you made this script for :)
February 07 (11 years ago)

Edited February 07 (11 years ago)
DXWarlock
Sheet Author
API Scripter
It is Structural Damage Capacity, for Rifts.
its pretty much HP of a sort. a player has SDC and HP, both are players taking damage. but SDC goes down first its a sort of "non lethal damage". When you run out of them and get into HP your starting to get negatives and need medical attention.

So i combine them into one bar, and just have it put a status icon up when your at 0SDC and only HPs left. letting them know 'you've taken all the beating you can, now its getting into your life threatening damage.'
February 08 (11 years ago)
https://wiki.roll20.net/API:Events

Event Ordering

Events are fired synchronously (meaning each function won't start until the previous one has finished) in order from first-bound to last-bound, and also from specific property to general object. So given the following:
on("change:graphic", function1); on("change:graphic", function2); on("change:graphic:left", function3);
If the objects left property changed, then the order would be function3, then function1, then function2.

Somehow you might be able to use this to get what you want. Riley would know more though.
February 08 (11 years ago)
I tried using the script you gave me to make a script that combines more than two attributes.

It didn't work, leading me to believe there might be something very basic I failed at...

Basically, having no skills when it comes to scripting, I tried just following my basic logic which is not my strongest suit!

on("change:token", function (obj) {
if(obj.get("represents") == '') return;
var oCharacter = getObj('character', obj.get("_represents"));
var cBy = oCharacter.get('controlledby');
var oBase-AC-Value = findObjs({_type: "attribute",name: "Base-AC-Value",_characterid: oCharacter.id})[0];
var oArmor-Bonus = findObjs({_type: "attribute",name: "Armor-Bonus",_characterid: oCharacter.id})[0];
var oDexterity = findObjs({_type: "attribute",name: "Dexterity",_characterid: oCharacter.id})[0];
var oAC = findObjs({_type: "attribute",name: "AC",_characterid: oCharacter.id})[0];
var oDodge-Bonus = findObjs({_type: "attribute",name: "Dodge-Bonus",_characterid: oCharacter.id})[0];
var oNatural-AC = findObjs({_type: "attribute",name: "Natural-AC",_characterid: oCharacter.id})[0];
var oShield-Bonus = findObjs({_type: "attribute",name: "Shield-Bonus",_characterid: oCharacter.id})[0];
var oDeflection = findObjs({_type: "attribute",name: "Deflection",_characterid: oCharacter.id})[0];
var oAC-Penalty = findObjs({_type: "attribute",name: "AC-Penalty",_characterid: oCharacter.id})[0];
var oSpecial-AC-Wis = findObjs({_type: "attribute",name: "Special-AC-Wis",_characterid: oCharacter.id})[0];
var o oSpecial-AC-Int = findObjs({_type: "attribute",name: " oSpecial-AC-Int",_characterid: oCharacter.id})[0];
if(oBase-AC-Value == undefined || oDexterity == undefined || oAC == undefined || oDodge-Bonus || oNatural-AC == undefined || oDeflection == undefined || oShield == undefined || oArmor-Bonus == undefined || oAC-Penalty== undefined || oSpecial-AC-Wis== undefined || oSpecial-AC-Int== undefined) return;
//Dexterity-----------------
var cDexterity = parseInt(oDexterity.get("current"));
var mDexterity = parseInt(oDexterity.get("max"));
//Base-AC-Value-----------------
var cBase-AC-Value = parseInt(oBase-AC-Value.get("current"));
var mBase-AC-Value = parseInt(oBase-AC-Value.get("max"));
//Deflection-----------------
var cDeflection = parseInt(oDeflection.get("current"));
var mDeflection = parseInt(oDeflection.get("max"));
//Natural-AC-----------------
var cNatural-AC = parseInt(oNatural-AC.get("current"));
var mNatural-AC = parseInt(oNatural-AC.get("max"));
//Dodge-Bonus-----------------
var cDodge-Bonus = parseInt(oDodge-Bonus.get("current"));
var mDodge-Bonus = parseInt(oDodge-Bonus.get("max"));
//Shield-Bonus-----------------
var cShield-Bonus = parseInt(oShield-Bonus.get("current"));
var mShield-Bonus = parseInt(oShield-Bonus.get("max"));
//Armor-Bonus-----------------
var cArmor-Bonus = parseInt(oArmor-Bonus.get("current"));
var mArmor-Bonus = parseInt(oArmor-Bonus.get("max"));
//Special-AC-Wis-----------------
var cSpecial-AC-Wis = parseInt(oSpecial-AC-Wis.get("current"));
var mSpecial-AC-Wis = parseInt(oSpecial-AC-Wis.get("max"));
//Special-AC-Int-----------------
var cSpecial-AC-Int = parseInt(oSpecial-AC-Int.get("current"));
var mSpecial-AC-Int = parseInt(oSpecial-AC-Int.get("max"));
//AC-Penalty-----------------
var cAC-Penalty = parseInt(oAC-Penalty.get("current"));
var mAC-Penalty = parseInt(oAC-Penalty.get("max"));
//SetMax-----------------------------
var mAC = +mDexterity + +mBase-AC-Value+ +mSpecial-AC-Int+ +mAC-Penalty+ +mSpecial-AC-Wis+ +mShield-Bonus+ +mDodge-Bonus+ +mNatural-AC+ +mDeflection;
oAC.set('max', mAC);
//SetCurrent----------------------
var cAC = +cDexterity + +cBase-AC-Value+ +cSpecial-AC-Int+ +cAC-Penalty+ +cSpecial-AC-Wis+ +cShield-Bonus+ +cDodge-Bonus+ +cNatural-AC+ +cDeflection;
oAC.set('current', cAC);
});
February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
Hold on, lot easier way to do it than all that, if its always those added together.

Can just set the names of the attributes in a config and have it do a foreach loop and add them all, then set the max/current.
Gimme a bit, Ill give you a base to start with. be a lot less redundant.
February 08 (11 years ago)

Edited February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
Try this.
Im assuming AC is the one you want to set, and there is 10 your reading from.
so this should work.
I made a character and gave him all the Att's and set them 10,9,8,7,6,5,4,3,2,1 and it set AC to 55.

What it does is goes thru each of the 'CFG' up top and looks for that att on the character, and adds it to a total and looks for the next one.
I noticed you had Special-AC-Int as oSpecial-AC-Int to look for, and assumed it was wrong so I fixed it. If not change it back.

(I noticed a bug/hiccup where it wasnt showing the AC after I tried the first time, I refreshed the roll20 page and looked, it was there from then on everytime I changed it..not sure why...or cAC being undefined, so I forced it to check and set 0 if it is just incase.)

var CFG = [
    {Att: 'Base-AC-Value',},
    {Att: 'Armor-Bonus',},
    {Att: 'Dexterity',},
    {Att: 'Dodge-Bonus',},
    {Att: 'Natural-AC',},
    {Att: 'Shield-Bonus',},
    {Att: 'Deflection',},
    {Att: 'AC-Penalty',},
    {Att: 'Special-AC-Wis',},
    {Att: 'Special-AC-Int',}
    ]; 
on("change:token", function (obj) {
  var cAC = 0;
  var mAC = 0;
  if(obj.get("represents") == '') return;
  var oCharacter = getObj('character', obj.get("_represents"));
  CFG.forEach(function (opts) {
    if (cAC == null) cAC = 0;
    if (mAC == null) mAC = 0;
    var oAtt = findObjs({_type: "attribute",name: opts.Att,_characterid: oCharacter.id})[0];
    if(oAtt == undefined) return;
    var cAtt = parseInt(oAtt.get("current"));
    var mAtt = parseInt(oAtt.get("max"));
    cAC = cAC + cAtt;
    mAC = mAC + mAtt;
  });  
  var oAC = findObjs({_type: "attribute",name: 'AC',_characterid: oCharacter.id})[0];
  oAC.set('current', cAC);
  oAC.set('max', mAC);
});
February 08 (11 years ago)
Hi first of all, thanks for taking the time to try and help me with this :)

It returned to me with this message first:

/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:1 orts, require, module, __filename, __dirname) { function f(a){throw a;}var j=v ^ Error: Firebase.update failed: First argument contains NaN in property 'current' at Error () at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:9:186) at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:196) at za (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:8:468) at Da (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:392) at G.W.update (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:128:318) at TrackedObj._doSave (


But then I tried turning off ALL my other scripts (I have 2 others <.<), and instead it now says "on is not defined". I found where it says on, but I don't see the error, obviously. but then I tried turning off ALL my other scripts (I have 2 others <.<), and instead it now says "on is not defined". I found where it says on, but I don't see the error, obviously.

February 08 (11 years ago)

Edited February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
....no idea, the on part is right. Maybe its something with you copying it from here?
try this:
http://pastebin.com/raw.php?i=hPNJgxBU

are you putting it into its own script? not trying to paste it in the middle of another one are you?
February 08 (11 years ago)
No, I made a new script. Copypasting it again took care of the old error, but prompted a new (old) one. Basically, the same happened as it did prior to me turning off my other scripts, but now that doesn't solve the problem.

/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:1 orts, require, module, __filename, __dirname) { function f(a){throw a;}var j=v ^ Error: Firebase.update failed: First argument contains NaN in property 'current' at Error () at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:9:186) at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:196) at za (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:8:468) at Da (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:392) at G.W.update (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:128:318) at TrackedObj._doSave (
February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
I see why, if I delete all the attribute numbers, it does it. it has no value to set current as.
Let me change it a bit. I didnt put enough checks in.

change these 2 lines to this:
//oAC.set('current', cAC);
//oAC.set('max', mAC);


so they go grey, and it stops fussing. it doesn't work, but it stops complaining :)
February 08 (11 years ago)
Exactly as you said. :p
February 08 (11 years ago)

Edited February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
OK...should be more cautious now with missing attributes
If one is missing, or blank it will fuss and tell you which ones is missing in the API log.
If either cAC or mAC (the totals to make it) are blank it will tell you also.

var CFG = [
    {Att: 'Base-AC-Value',},
    {Att: 'Armor-Bonus',},
    {Att: 'Dexterity',},
    {Att: 'Dodge-Bonus',},
    {Att: 'Natural-AC',},
    {Att: 'Shield-Bonus',},
    {Att: 'Deflection',},
    {Att: 'AC-Penalty',},
    {Att: 'Special-AC-Wis',},
    {Att: 'Special-AC-Int',}
    ]; 
on("change:token", function (obj) {
  var cAC = 0;
  var mAC = 0;
  if(obj.get("represents") == '') return;
  var oCharacter = getObj('character', obj.get("_represents"));
  CFG.forEach(function (opts) {
    if (cAC == null) cAC = 0;
    if (mAC == null) mAC = 0;
    var oAtt = findObjs({_type: "attribute",name: opts.Att,_characterid: oCharacter.id})[0];
    if(oAtt == undefined || oAtt.get("current") == '' || oAtt.get("max") == '') {
        log('cannot find Stat numbers for '+ opts.Att);
        return;
    }
    var cAtt = parseInt(oAtt.get("current"));
    var mAtt = parseInt(oAtt.get("max"));
    cAC = cAC + cAtt;
    mAC = mAC + mAtt;
  });
   if (isNaN(cAC) || isNaN(mAC)) {
     Log('Either cAC or mAC total is blank, cannot set');
     return;   
   }
   var oAC = findObjs({_type: "attribute",name: 'AC',_characterid: oCharacter.id})[0];  
    oAC.set('current', cAC);
    oAC.set('max', mAC);
});
February 08 (11 years ago)
Oookay... This.... Works !! I'm a bit surprised, as it took a bit of time to get it working, but now it runs smoothly.

This will also be incredibly useful if I try to modify it for further uses. Thanks a ton!
I don't understand half of what you did. This is a bit more complex seen from a laymans perspective, than the first script you offered me.
February 08 (11 years ago)

Edited February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
Maybe this will help Ill comment it out

//-----set attributes to check---------
var CFG = [
    {Att: 'Base-AC-Value',},
    {Att: 'Armor-Bonus',},
    {Att: 'Dexterity',},
    {Att: 'Dodge-Bonus',},
    {Att: 'Natural-AC',},
    {Att: 'Shield-Bonus',},
    {Att: 'Deflection',},
    {Att: 'AC-Penalty',},
    {Att: 'Special-AC-Wis',},
    {Att: 'Special-AC-Int',}
    ]; 
//-----Look for token changes with change:token---------
on("change:token", function (obj) {
  var cAC = 0;
  var mAC = 0;
//-----check to make sure it has a sheet for it---------
  if(obj.get("represents") == '') return;
  var oCharacter = getObj('character', obj.get("_represents"));
//-----Do a 'for each' of the attributes by calling opts.Att (which turns into the attr name)-
  CFG.forEach(function (opts) {
    if (cAC == null) cAC = 0;
    if (mAC == null) mAC = 0;
//-----Look for that attribute---------
    var oAtt = findObjs({_type: "attribute",name: opts.Att,_characterid: oCharacter.id})[0];
//-----If that attributes blank or missing, fuss about it and goto next one-------
    if(oAtt == undefined || oAtt.get("current") == '' || oAtt.get("max") == '') {
        log('cannot find Stat numbers for '+ opts.Att);
        return;
    }
//-----Get current and max of that Attribute and add it to the total-------
    var cAtt = parseInt(oAtt.get("current"));
    var mAtt = parseInt(oAtt.get("max"));
    cAC = cAC + cAtt;
    mAC = mAC + mAtt;
  });
//-----If either AC max or AC current is not there tell you-------
   if (isNaN(cAC) || isNaN(mAC)) {
     Log('Either cAC or mAC total is blank, cannot set');
     return;   
   }
//-----set AC values-------
   var oAC = findObjs({_type: "attribute",name: 'AC',_characterid: oCharacter.id})[0];  
    oAC.set('current', cAC);
    oAC.set('max', mAC);
});
February 08 (11 years ago)
That helped a lot! I think it will be considerably easier to make a successful modification of this for the next thing I try out. You've been an enormous help!
February 08 (11 years ago)
DXWarlock
Sheet Author
API Scripter
Sorry for the hassles, I was 'lazy coding' for testing it, wasn't checking to see if some of the attr was missing or blank it was looking for. I was just assuming they was there for test purposes :P