Help with AnyDice for custom system

anydicestatistics

Can someone please provide guidance on how to make my system work within AnyDice?

The gist is: player stats dictate how many dice in a pool of d8s are rolled, if they have gear or skills proficiencies, the bonus is applied to the lowest dice roll in the pool.
If the player has a 0 in a stat and attempt to roll a check they would roll 2D8 and use the lowest value.

If at least one result is above a 5, it's a success with a setback. If an 8, it's a standard success. If more than one 8 is present it's a critical success. (Think Blades in the Dark)

I've figured out how to map the specific dice results through anydice including the gear bonuses, and I've used the BitD example to generate the chance of success/failure.
The issue arises in that when I attempt to work the bonuses into the equation for chance of success/failure/etc it doesn't seperate success from critical success.

Here's my current attempt. I've been given assistance to figure out the chance just using math, but I'd like to plot it and view it through AnyDice.

Best Answer

This would be easy to do if we could just separate the lowest roll from the rest of the rolls, add the bonus to it, merge them back together and the count the results.

Unfortunately AnyDice's sequence manipulation kind of sucks. In particular, there's no simple built-in way to extract just a part of a sequence without summing it. But we can do it with a custom helper function, like this:

function: select INDICES:s from SEQUENCE:s {
  RESULT: {}
  loop INDEX over INDICES {
    RESULT: {RESULT, INDEX@SEQUENCE}
  }
  result: RESULT
}

(Note that this function really needs to be called with actual sequences. If you try to call it with dice as either parameter, the results will just be summed together, just as if you'd simply used INDICES@SEQUENCE.)


With this helper function, everything is pretty straightforward:

function: evaluate ROLL:s {
  if (ROLL >= 8) >= 2 { result: 3 }  \ crit success \
  if (ROLL >= 8) >= 1 { result: 2 }  \ normal success \
  if (ROLL >= 5) >= 1 { result: 1 }  \ success with setback \
  result: 0  \ failure \
}

function: evaluate ROLL:s with bonus BONUS:n {
  ROLL_WITHOUT_LOWEST: [select {1 .. #ROLL-1} from ROLL]
  LOWEST: (#ROLL)@ROLL
  ROLL_WITH_BONUS: [sort {ROLL_WITHOUT_LOWEST, LOWEST + BONUS}]
  result: [evaluate ROLL_WITH_BONUS]
}

(The [sort] is technically not necessary, since nothing in [evaluate ROLL] as I've implemented it cares about how the roll is sorted. But AnyDice normally sorts dice pools from highest to lowest, and functions that expect to be called with dice rolls often assume that, so having that extra sort there eliminates a hidden "trap". As long as the code doesn't time out, I'd rather waste a bit of time with an extra sort than leave it out and risk something breaking because I forgot the sequence wasn't sorted.)


Ps. Somewhat remarkably, a bonus of less than 5 points doesn't seem to change the probability of a "success with setback" at all! That kind of makes intuitive sense, since the bonus is approximately as likely to convert a failure into a setback than a setback into a success, but the fact that the probabilities balance exactly is slightly surprising.