Really complex damage tracking - coding help.

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

Post Reply
User avatar
Xaelvaen
Dragon
Posts: 498
Joined: Wed Aug 31, 2011 9:49 pm
Location: Somewhere between Heaven and Hell

Really complex damage tracking - coding help.

Post by Xaelvaen »

I have a (barely) functional code to help me track really complex damage (by my standards), and it is so ugly, and clunky, and counter-intuitive, I thought I'd put up a general question about how anyone else would handle it. There are vastly better coders than me here, so perhaps I'm overthinking/underthinking, and I'd really like to start improving my overall coding capabilities (which you guys have already helped me with immensely). Here's the damage rundown:
  • Damage is of two types: Stun, or Lethal
  • There are three types of 'Hit Points'; Armor, Stun, and Lethal
  • Most attacks deal armor damage first, but there are some that ignore armor.
  • Attacks that ignore armor have an 'AA' rating. This is a portion of the damage that ignores armor. It can equal the total damage, but not exceed (obviously).
  • There is a 'Toughness' score that reduces all damage before applying to any hit points. Some attacks ignore toughness, so this should be a checkbox.
  • Stun damage, after depleting armor, carries over into Stun hit points.
  • Lethal damage, after depleting armor, skips stun damage, and goes straight to lethal, with one exception - Hardness.
  • Hardness is a property that converts X amount of carried over lethal damage, to stun damage (after depleting armor).
  • AA damage is unaffected by Hardness; so 3 AA would go straight to lethal, regardless of Hardness.
  • Any damage delivered in excess to current Stun Hit Points carries over to Lethal.
An example damage input may look like: 5 Lethal Damage, AA 3. That would deal 5 lethal damage, 2 of which would go to armor, and the other 3 would skip armor and stun and go to Lethal.
Another might be 10 Lethal Damage, ignoring Toughness. So that'd have to skip Toughness, go to armor first, and then if there is hardness go to stun. If there is no hardness, it skips stun and goes to Lethal.

If this is too annoying for anyone to tackle, I understand - I spent an hour on my clunky spam of if statements to get it to work myself, but if anyone would have some cleaner concept or 'smart' way of doing this, I'd appreciate the insight.

EDIT: I thought I'd include my own code, so if you'd rather critique, it's here.
The Code

Code: Select all

[h, if(macro.args==""), CODE:{
	[h: tokenName=getName(getSelected())]
};{

	[h: tokenName=macro.args]

}]

[h: carryOver=0]
[h: sendToStun=0]
[h: sendToLethal=0]

[h, token(tokenName), CODE:{
	[h: current_lethal=getProperty("Health_Current")]
	[h: maximum_lethal=getProperty("Health_Maximum")]
	[h: current_armor=getProperty("Armor_Current")]
	[h: maximum_armor=getProperty("Armor_Maximum")]
	[h: current_stun=getProperty("Stun_Current")]
	[h: maximum_stun=getProperty("Stun_Maximum")]
	[h: tgt_toughness=getProperty("Toughness")]
	[h: isHealed=getState("Healed")]
	[h: isRepaired=getState("Repaired")]
	[h: isHardened=getProperty("Hardened")]
}]

[h: status=input(
"setDamage|0|Damage Value|TEXT|WIDTH=4",
"setDamageType|Stun,Lethal|Damage Type|RADIO|ORIENT=H SELECT=1",
"setArmor|1|Apply Armor|CHECK",
"setToughness|" +tgt_toughness +"|Apply Toughness (" +tgt_toughness +")|CHECK",
"setHardened|" +isHardened +"|Apply Hardness (" +isHardened +")|CHECK")]
[h: abort(status)]

[h: setDamage=if(setToughness>0, sum(setDamage,-tgt_toughness),setDamage)]
	[h: setDamage=max(0,setDamage)]

[h, if(setArmor), CODE:{
	[h: carryOver=if(setDamage>current_armor,sum(setDamage,-current_armor),0)]
	[h: carryOver=max(0,carryOver)]
	[h: current_armor=sum(current_armor,-setDamage)]
	[h: current_armor=min(current_armor,maximum_armor)]
	[h: current_armor=max(0,current_armor)]
	[h: isRepaired=0]
};{
	[h: carryOver=setDamage]
}]

[h: sendToStun=if(carryOver>0 && setHardened>0 && setDamageType==1,min(carryOver,isHardened),0)]
[h: sendToStun=if(carryOver>0 && setDamageType==0,carryOver,sendToStun)]

[h: sendToLethal=if(carryOver>0 && setHardened>0 && setDamageType==1,sum(carryOver,-isHardened),0)]
[h: sendToLethal=if(carryOver>0 && setDamageType==1 && setHardened==0,carryOver,sendToLethal)]

	[h: sendToLethal=max(0,sendToLethal)]
	[h: sendToStun=max(0,sendToStun)]

[h: carryOver=0]

[h, if(sendToStun>0 ), CODE:{
	[h: carryOver=if(sendToStun>current_stun,sum(sendToStun,-current_stun),0)]
	[h: current_stun=sum(current_stun,-sendToStun)]
	[h: current_stun=min(current_stun,maximum_stun)]
	[h: current_stun=max(0,current_stun)]
	[h: isHealed=0]
};{}]

[h: setLethalDamage=sum(sendToLethal,carryOver)]

[h, if(setLethalDamage>0), CODE:{
	[h: current_lethal=sum(current_lethal,-setLethalDamage)]
	[h: current_lethal=min(current_lethal,maximum_lethal)]
	[h: current_lethal=max(0,current_lethal)]
	[h: isHealed=0]
};{}]

[h: woundedCheck=if(current_lethal<maximum_lethal,1,0)]
[h: debilitatedCheck=if(current_stun<maximum_stun,1,0)]
[h: deadCheck=if(current_lethal<1,1,0)]

[h, token(tokenName), CODE:{
	[h: setProperty("Health_Current",current_lethal)]
	[h: setProperty("Stun_Current",current_stun)]
	[h: setProperty("Armor_Current",current_Armor)]
	[h: setState("Healed",isHealed)]
	[h: setState("Wounded",woundedCheck)]
	[h: setState("Debilitated",debilitatedCheck)]
	[h: setState("Repaired",isRepaired)]
	[h: setState("Dead",deadCheck)]
	[h: bar.Lethal = current_lethal / maximum_lethal]
	[h: bar.Stun = current_stun / maximum_stun]
	[h: bar.Armor = current_armor / maximum_armor]
}]
Last edited by Xaelvaen on Sat Sep 09, 2017 5:16 pm, edited 1 time in total.
"An arrogant person considers himself perfect. This is the chief harm of arrogance. It interferes with a person's main task in life - becoming a better person." - Leo Tolstoy

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Really complex damage tracking - coding help.

Post by aliasmask »

I'll put this in terms I'm more familiar with. I'll use AP for AA, stands for Armor Piercing and Pen to bypass toughness, penetrating.

First I would list all the possible types of resistance, damage and their combos:

Code: Select all

Resistances:
ArmorHP - Is reduced by non armor piercing stun and lethal damage
StunHP - Is reduced by stun damage and lethal damage equal to hardness
LethalHP - Is reduced by lethal damage
Toughness - Reduces armor, stun and lethal damage by non penetrating source
Hardness - Converts lethal to stun damage for the hardness value

Damage Types
X Stun -> X - Toughness > ArmorHP > StunHP
X Lethal -> X - Toughness > ArmorHP > StunHP(Hardness) > LethalHP
X Stun Y AP -> X - Toughness > ArmorHP - Y > StunHP
X Lethal Y AP -> X - Toughness > ArmorHP - Y > LethalHP
X Stun Pen -> X > ArmorHP > StunHP
X Lethal Pen -> X > ArmorHP > StunHP(Hardness) > LethalHP
X Stun Y AP Pen -> X > ArmorHP - Y > StunHP
X Lethal Y AP Pen -> X > ArmorHP - Y > LethalHP
What happens if you receive more stun damage than you have StunHP? Is it converted to lethal?

I would probably have a function like this:

Code: Select all

applyDamage(damage,isLethal,AP,Pen)
<!-- 
   damage - amount of basic damage
   isLethal - damage type, 0 stun, 1 lethal
   AP - Amount that is Armor Piercing (is this an additive or inclusive value?) default 0
   Pen - Is penetrating ignoring toughness, default 0, 1 for is Penetrating
-->

User avatar
Xaelvaen
Dragon
Posts: 498
Joined: Wed Aug 31, 2011 9:49 pm
Location: Somewhere between Heaven and Hell

Re: Really complex damage tracking - coding help.

Post by Xaelvaen »

I knew I forgot one in my list! Thanks Alias, corrected on the top post. Yes indeed, excess stun damage carries over to lethal, regardless of the source that sent it to the Stun Damage Track.

Your list is basically the approach I took, my coding just seems really clunky, but I'm really not sure there's many more ways to do it - a whole mega-ton of if statements.
"An arrogant person considers himself perfect. This is the chief harm of arrogance. It interferes with a person's main task in life - becoming a better person." - Leo Tolstoy

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Really complex damage tracking - coding help.

Post by aliasmask »

Xaelvaen wrote:Your list is basically the approach I took, my coding just seems really clunky, but I'm really not sure there's many more ways to do it - a whole mega-ton of if statements.
With so many conditions, lots of if statements is hard to avoid. One thing you're missing is comments. Comments are really important to understand what's going on in the code. You may know it inside out right now, but in 2 weeks you'll be scratching your head when looking at it again. It also helps organize your thoughts and code.

Looking at your code it looks like armor piercing is all or nothing, you can't have some of the damage be AA. So, in your example 5 Lethal, 3 AA couldn't be applied. I'll write up how I would approach this code, as an example.

User avatar
Xaelvaen
Dragon
Posts: 498
Joined: Wed Aug 31, 2011 9:49 pm
Location: Somewhere between Heaven and Hell

Re: Really complex damage tracking - coding help.

Post by Xaelvaen »

What's the best approach to commenting? I've seen <!----> as well as [h: "<!---->"] and so forth - is there any one particularly better (cleaner) way to do this? All of my output is always through [h: broadcast()] so I send nothing to chat unformatted, if that matters for commenting.

Secondly, yeah - armor (at the moment) is all or nothing and I just run the macro twice to apply the piercing portion. I'm working on the coding separately for armor piercing being a variable - just finished a game session tonight, so had to get a quicker code functional.

Thanks for your time, by the way, much appreciated.
"An arrogant person considers himself perfect. This is the chief harm of arrogance. It interferes with a person's main task in life - becoming a better person." - Leo Tolstoy

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Really complex damage tracking - coding help.

Post by aliasmask »

<!-- comment --> is an html comment. The [H: "<!-- comment -->"] is a work around I came up with to put in a comment but not have it go to chat. When clicking a macro button all the <!-- comments --> go to chat, so if your macro is supposed to have no output then it will show a blank line because of the comment. Using the H: makes it hidden but is much slower to do. This really only matters if you're working with big data and loops where you have a comment in the loop. You can run out of stack that way too.

The solution is to use UDFs and put in normal HTML comments. When defining the UDF specify it to suppress output. Your broadcast() will post to chat no matter what.

Here's what I have so far for a UDF. The user input and damage output would be in another macro that calls this one:

Code: Select all

<!-- applyDamage(token,damage,isLethal,damageAA,isPenetrating): output
   token - token name or id to apply damage to
   damage - amount of basic damage
   isLethal - damage type: 0 stun, 1 lethal
   damageAA - Amount of basic that is armor piercing. default 0, -1 all of basic, 1+ value of AA
   isPenetrating - ignores toughness
   output - html output of damage taken
   
   This function will apply attack damage to a token and set token states based on damage.
-->

[H: token = arg(0)]
[H: damage = arg(1)]
[H: isLethal = arg(2)]
[H, if(argCount() >= 4): damageAA = arg(3); damageAA = 0]
[H, if(argCount() >= 5): isPenetrating = arg(4); isPenetrating = 0]

<!-- get token info -->
[H: tokenId = findToken(token)]
[H, if(tokenId), code: {
   [H: switchToken(tokenId)]
   [H: tokenName = getName()]
   
   [H: maxArmor = getProperty("Armor_Maximum")]
   [H: curArmor = getProperty("Armor_Current")]
   [H: maxStun = getProperty("Stun_Maximum")]
   [H: curStun = getProperty("Stun_Current")]
   [H: maxHealth = getProperty("Health_Maximum")]
   [H: curHealth = getProperty("Health_Current")]
   
   <!-- amount of resistance that could be applied due to toughness -->
   [H, if(isPenetrating): toughnessResist = 0; toughnessResist = getProperty("Toughness")]
   <!-- amount of damage that could bypass armor -->
   [H, if(damageAA), code: {
      [H, if(damageAA < 0): amountAA = damage; amountAA = damageAA]
   };{
      [H: amountAA = 0]
   }]
   <!-- amount of hardness that could be applied to lethal damage -->
   [H, if(! amountAA && isLethal): hardnessResist = getProperty("Hardened"); hardnessResist = 0]
};{
   <!-- token not on current map -->
   [H: abort(0)]
}]

<!-- ### APPLY DAMAGE ### -->

<!-- apply toughness resistance -->
[H: curDamage = damage - toughnessResist]
 

User avatar
Xaelvaen
Dragon
Posts: 498
Joined: Wed Aug 31, 2011 9:49 pm
Location: Somewhere between Heaven and Hell

Re: Really complex damage tracking - coding help.

Post by Xaelvaen »

My knowledge of UDFs is limited to me having been studying the wiki this past week, so I know the calling macro would send args. These args would be token, damage, isLethal, damageAA (if present), and isPenetrating (if present), if I read your script right. Being very new at this, could you tell me about the following lines?

Code: Select all

[H, if(argCount() >= 4): damageAA = arg(3); damageAA = 0]
[H, if(argCount() >= 5): isPenetrating = arg(4); isPenetrating = 0]
I am -guessing- it's because the arguments start at 0 (like a list), so even though it would be argument 3 and 4, the argCount would return 4 and 5?

Nice trick with the semi-colons on the ifs to make it a single line instead of using code as well, haven't seen that before. (Haven't actually looked at a lot of code, either, but that'll be seriously useful for me).

I just can't say thank you enough, this will turn my code around big time, and much more easily read.
"An arrogant person considers himself perfect. This is the chief harm of arrogance. It interferes with a person's main task in life - becoming a better person." - Leo Tolstoy

User avatar
aliasmask
RPTools Team
Posts: 9023
Joined: Tue Nov 10, 2009 6:11 pm
Location: Bay Area

Re: Really complex damage tracking - coding help.

Post by aliasmask »

Right on all accounts. arg() is an array and starts at 0 but argCount gives you the number of arguments which starts at 1.

Life will be much easier for you once you start using UDFs, but one thing to watch out for is function creep. You may want to jam a bunch of stuff in to a function but functions should be very task oriented. They do one thing and one thing well. Another advantage to functions is when you reference them from many other places and you need to make a slight change then you only change the code in one spot, the function.

That's why I defined the function as applyDamage. It's not going to ask for user input, it's not going to send output to the chat, but does return it to the calling function. I wouldn't normally apply state updates but the nature of the code kind of needs you too. You could return the state changes from the function to have it update elsewhere but that kind of goes against the do one thing rule, ie return output and state changes. Since the state changes being made are solely dependent upon the damage done, it's okay to add that to the function.

I have some basic coding best practices here along with some MT specific tips. It's a short read: http://forums.rptools.net/viewtopic.php ... 81#p233681

User avatar
Xaelvaen
Dragon
Posts: 498
Joined: Wed Aug 31, 2011 9:49 pm
Location: Somewhere between Heaven and Hell

Re: Really complex damage tracking - coding help.

Post by Xaelvaen »

Read your best practices again - read it once long ago, thus why I have the lowercaseUppercase variable naming convention, just never got in the habit of commenting - I should do that.

I'll keep in mind the UDF creep, though it'll be a very slow implementation - I've done so much with just calling lib:token macros, it'll just slowly be replacing everything.

I have a slightly related question - is there a function to extract numbers and letters from combined input? In example, if I label a damage value as 5L (5 Lethal), is there a way to extract the numbers from the letters and get two distinct variables out of that? I'm on the wiki blindly clicking things that sound like they -might- work for that, if I stumble upon it myself I'll update here.

EDIT: I found 'endsWith' and wrote a code to choose the damage type based on an S or an L appearing at the end of the string. Now I just need a way to remove the number portion and use that in a math function.

EDIT 2: I also found 'replace' and got the code working hehe.

Should anyone be looking in the future using a search for this criteria, I'll write the code (in brief) that I used.

Code: Select all

[h: tDamage=json.get(macro.args,"tDamage")]
[h: damageType=if(endsWith(tDamage,"L"),"Lethal","Stun")]
[h: trueDamage=if(endsWith(tDamage,"L"),replace(tDamage,"L",""),tDamage)]
[h: trueDamage=if(endsWith(trueDamage,"S"),replace(trueDamage,"S",""),trueDamage)]
[h: trueDamage=number(trueDamage)]
[h: "<!---- I added the number section just in case the previous coding made it not act like a number - really dunno if it is necessary ---->"]
"An arrogant person considers himself perfect. This is the chief harm of arrogance. It interferes with a person's main task in life - becoming a better person." - Leo Tolstoy

Post Reply

Return to “Macros”