# [[Sugarcube 2]] Tabletop Style Stat Checks

+1 vote
So far I have a working stat allocation system for my story. All that remains is coming up with a way to make the choices you make a little more randomized. What I would like to do is have options that lead down branching paths. So for instance you could use your strength to move an object out of the way, or your dexterity to scale the object. Clicking one option will trigger a roll. Against NPC's it will compete with the NPC's roll, simple objects will have a flat value to beat. Below is how I want the rolls to work. How would I go about writing this.

Player Stat + 1d8 (random 1-8) vs Enemy Stat +1d8 or Player Stat + 1d8 vs Enemy Stat

by (63.1k points)

Making a dice roller/random number generator is simple enough using the random() function.

``````<<set _check to random(1, 8) + \$stat>>

<<if _check gte 6>>
success
<<else>>
fail
<</if>>``````

You can create an enemy roll the same way and compare the rolls in an <<if>>.

by (820 points)
Quick simple and clean, though I always like to name my random number variables \$dice so that I can more easily remember them and what they're for. =^^=
by (8.6k points)

I like to gather all the relevant methods into a (static) object, typically called window.Stat or window.Stats or similar, and use it to access the values and write my stat/skill check methods. This way, I can later on refactor how I store the stats in the game or add things like stat-modifying effects without worrying that anything which uses the stat values or makes stat checks needs to be changed. A typical implementation (cut down to just the Stat.check(...) method and its helpers) looks about like this.

``````window.Stat = {};

/*
* Returns the statistic value (a finite number) of the given stat,
* modified by currently active effects and whatnot,
* for the given character, or undefined if the character doesn't
* possess the stat or the arguments are invalid.
*
* The value for "stat" can be anything the game recognises as a
* valued statistic, so attributes, skills, levels, height
* or any computed value; or it can be limited to "real" statistics
* or any subset of the above. The only important consideration is
* that calling Stat.get(...) works the same for everything;
* PCs, NPCs and mechanic traps alike.
*/
Stat.get = function(character, stat) {
/* Implementation is game specific */
};

/*
* What the Stat.check(...) call returns when some check
* automatically fails (for example, because the character
* doesn't have the stat).
*/
Stat.RESULT_AUTOFAILURE = {
roll1: 0, roll2: 0, result: -100, success: false, failure: true,
};

/*
* What the Stat.check(...) call returns when some check
* automatically succeeds (for example, because the character
* isn't actually opposed by anyone).
*/
Stat.RESULT_AUTOSUCCESS = {
roll1: 0, roll2: 0, result: 100, success: true, failure: false,
};

/*
* The Stat.check(...) function can be called in two different ways:
*
* Stat.check(character, character's stat, opponent, opponent's counter-stat)
* - Opposed check against an opponent
*
* Stat.check(character, character's stat, flat value)
* - Unopposed check against some flat value to beat
*
* It returns an object with the following properties:
*
* # result
*   - positive if the character's roll+stat value is better
*     than the opponent's (or the flat value), negative if
*     it's worse
* # roll1, roll2
*   - the two rolls used to determine the value; roll2
*     is 0 if there is only a flat value to beat;
*     both values will be 0 if there is nothing to roll
* # success
*   - a boolean value; true if the check succeeded for the character
* # failure
*   - a boolean value; true if the check failed for the character
*
* This object can be further enhanced with crits and fumbles
* or other data gathered during the check process, as required.
*/
Stat.check = function(character, stat, opponent, counter) {
var dice = {}; /* this will be our return value */
var characterStat = Stat.get(character, stat);
if(!_.isDefined(characterStat)) {
return Stat.RESULT_AUTOFAILURE;
}
dice.roll1 = random(1, 8);

var counterStat = undefined;
if(!_.isDefined(counter) && _.isFinite(opponent)) {
/* We were called with a simple number - a check against a static value */
dice.roll2 = 0;
counterStat = opponent;
} else {
/* We were likely called with a counter-character and their stat */
dice.roll2 = random(1, 8);
counterStat = Stat.get(opponent, counter);
}
if(!_.isDefined(counterStat)) {
return Stat.RESULT_AUTOSUCCESS;
}

dice.result = (characterStat + dice.roll1) - (counterStat + dice.roll2);
/* This makes it so a tie is neither a success nor a failure */
dice.success = (dice.result > 0);
dice.failure = (dice.result < 0);
return dice;
};
``````

And you can now call the method and use its result, either right away or saving it in a temporary variable and checking what the values are. So to move a boulder ...

``````<<link "Try moving the boulder [STR]">>
<<if Stat.check(\$PC, "STR", 10).success>>
<<goto "Boulder moved">>
<<else>>
<<goto "Boulder doesn't bulge">>
<</if>>

And to attack an enemy, keeping the result in a variable so we can reference it in the next passages and do something with it ...

``````<<link "Hit her with your axe">>
<<set \$lastAttackCheck = Stat.check(\$PC, "DEX", \$enemy, "DEX")>>
<<if \$lastAttackCheck.success>>
<<goto "Attack connects">>
<<elseif \$lastAttackCheck.failure>>
<<goto "Attack misses">>
<<else>>
<<goto "No opening for an attack found">>
<</if>>