In general, you can do this by having each function call the next one. That is, instead of writing your [hit ATTACKS]
function like this:
function: hit ATTACKS:n {
DICE: ATTACKS d6
if(WS_FOE > WS * 2) { result: [count {5-WS_MODIFIER..6} in DICE] }
if(WS_FOE >= WS & WS_FOE <= WS * 2) { result: [count {4-WS_MODIFIER..6} in DICE]}
if(WS_FOE < WS) { result: [count {3-WS_MODIFIER..6} in DICE]}
}
and then passing the result of that function to [wound HITS]
to get the number of wounds, you can instead write it like this to make it call [wound HITS]
directly:
function: hit ATTACKS:n {
DICE: ATTACKS d6
if(WS_FOE > WS * 2) { HITS: [count {5-WS_MODIFIER..6} in DICE] }
if(WS_FOE >= WS & WS_FOE <= WS * 2) { HITS: [count {4-WS_MODIFIER..6} in DICE]}
if(WS_FOE < WS) { HITS: [count {3-WS_MODIFIER..6} in DICE]}
result: [wound HITS]
}
Then you can similarly modify your [wound HITS]
function to have it call [armor save WOUNDS]
directly:
function: wound HITS:n {
DICE: HITS d6
DIFF: T_FOE - S
if(DIFF > 3) { WOUNDS: 0 }
if(DIFF = 3 | DIFF = 2) { WOUNDS: [count {6} in DICE]}
if(DIFF = 1) { WOUNDS: [count {5..6} in DICE]}
if(DIFF = 0) { WOUNDS: [count {4..6} in DICE]}
if(DIFF = -1) { WOUNDS: [count {3..6} in DICE]}
if(DIFF <= -2) { WOUNDS: [count {2..6} in DICE]}
result: [armor save WOUNDS]
}
And then you should change your output
statement from:
output [armor save [wound [hit A]]]
to just:
output [hit A]
since now your [hit ATTACKS]
function calls the other two functions automatically. And if you did all that right, you should get exactly the same output as before.
OK, so why does that help, if it just gives the same output anyway?
It helps because now you can pass more than just one parameter to the inner functions. So for example, in your [hit ATTACKS]
function, you could also pass [count 6 in DICE]
as a second parameter to your next function (now redefined e.g. as [wounds HITS with SIXES]
) and use it to tweak the wounds and/or armor save calculations.
However, before you do that, you should first make one more change to your [hits ATTACK]
function and split it into two functions:
function: hit ATTACKS:n {
DICE: ATTACKS d6
result: [hit roll DICE]
}
function: hit roll DICE:s {
if(WS_FOE > WS * 2) { HITS: [count {5-WS_MODIFIER..6} in DICE] }
if(WS_FOE >= WS & WS_FOE <= WS * 2) { HITS: [count {4-WS_MODIFIER..6} in DICE]}
if(WS_FOE < WS) { HITS: [count {3-WS_MODIFIER..6} in DICE]}
result: [wound HITS]
}
OK, what difference does that make? Nothing, if you just run this code and look at the results. But there's a subtle difference that you can't see directly: DICE
inside the [hit roll DICE]
helper function has been "frozen" into a sequence containing a fixed sequence of rolled numbers. That matters if you ever try to use it more than once in the function, e.g. to count both hits and sixes separately.
…wait, what?
OK, let me demonstrate with a quick example:
DICE: 5d6
HITS: [count {4..6} in DICE]
SIXES: [count 6 in DICE]
output HITS - SIXES named "this should never be negative, right?"
Actually, if you run the code above, you can see that the output can be negative! Why? Because DICE
above has not been "frozen" by passing it into a function (as a sequence parameter), and thus has no fixed value. Every time you use DICE
in an expression, AnyDice treats it as a new, independent 5d6 roll. And it's quite possible to roll fewer hits on one 5d6 roll than sixes on another 5d6 roll.
(Also, even the HITS
and SIXES
variables themselves above are "custom dice", and so have no fixed value either. So even e.g. output HITS - HITS
can and will output non-zero results!)
We can fix this by passing the 5d6 "collection of dice" into a function that takes a sequence parameter (i.e. one tagged with :s
):
function: test DICE:s {
HITS: [count {4..6} in DICE]
SIXES: [count 6 in DICE]
result: HITS - SIXES
}
output [test 5d6] named "this really is always non-negative"
OK, sorry for that digression. Let's get back to your code. With the [hit ATTACKS]
function split as suggested above to freeze the attack rolls, you can now tweak your code e.g. like this:
function: hit roll DICE:s {
if(WS_FOE > WS * 2) { HITS: [count {5-WS_MODIFIER..6} in DICE] }
if(WS_FOE >= WS & WS_FOE <= WS * 2) { HITS: [count {4-WS_MODIFIER..6} in DICE]}
if(WS_FOE < WS) { HITS: [count {3-WS_MODIFIER..6} in DICE]}
SIXES: [count 6 in DICE]
result: [wound HITS with SIXES]
}
function: wound HITS:n with SIXES:n {
DICE: (HITS - SIXES)d6
DIFF: T_FOE - S
if(DIFF > 3) { WOUNDS: 0 }
if(DIFF = 3 | DIFF = 2) { WOUNDS: [count {6} in DICE]}
if(DIFF = 1) { WOUNDS: [count {5..6} in DICE]}
if(DIFF = 0) { WOUNDS: [count {4..6} in DICE]}
if(DIFF = -1) { WOUNDS: [count {3..6} in DICE]}
if(DIFF <= -2) { WOUNDS: [count {2..6} in DICE]}
result: [armor save WOUNDS + SIXES]
}
Now, any natural "6" rolls on the hit roll count as automatic wounds (so we exclude them from the normal wound roll calculation and then add them back at the end before calculating the armor saves).
Ps. A word of warning: while nested functions and "freezing" dice like this are powerful tools, they can also make your program very slow if you overuse them. If that happens, you'll need to find some alternative approach to avoid timeouts.
For example, in your mechanic, the hit/wound/save rolls for each attack are independent. So, instead of calculating the distribution of successful attacks out of A attempts directly (using [hit A]
), you can get the same result faster (possibly much faster) by first calculating the success rate of a single attack (using [hit 1]
) as a custom die, and then rolling A of these custom dice (with A d [hit 1]
) to simulate A independent attacks, like this.
roll to hit -> roll to wound -> armour save -> ward save
\$\endgroup\$