forked from projectmoon/tenebrous-dicebot
Basic implementation of CoC dice rolling.
Not hooked up to parser or commands yet.
This commit is contained in:
parent
9f598541fb
commit
d5034c11dc
|
@ -0,0 +1 @@
|
||||||
|
pub mod dice;
|
|
@ -0,0 +1,203 @@
|
||||||
|
/// A planned dice roll.
|
||||||
|
pub struct DiceRoll {
|
||||||
|
target: u32,
|
||||||
|
modifier: DiceRollModifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Potential modifier on the die roll to be made.
|
||||||
|
pub enum DiceRollModifier {
|
||||||
|
/// No bonuses or penalties.
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
/// Roll one extra die and pick the lower of two results.
|
||||||
|
OneBonus,
|
||||||
|
|
||||||
|
/// Roll two extra dice and pick the lower of all results.
|
||||||
|
TwoBonus,
|
||||||
|
|
||||||
|
/// Roll one extra die and pick the higher of two results.
|
||||||
|
OnePenalty,
|
||||||
|
|
||||||
|
/// Roll two extra dice and pick the higher of all results.
|
||||||
|
TwoPenalty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The outcome of a die roll, either some kind of success or failure.
|
||||||
|
enum RollResult {
|
||||||
|
/// Basic success. The rolled number was equal to or less than the target number.
|
||||||
|
Success,
|
||||||
|
|
||||||
|
/// Hard success means the rolled number was equal to or less than
|
||||||
|
/// the target number divided by 2 (rounded down).
|
||||||
|
HardSuccess,
|
||||||
|
|
||||||
|
/// Extreme success means the rolled number was equal to or less
|
||||||
|
/// than the target number divided by 5 (rounded down).
|
||||||
|
ExtremeSuccess,
|
||||||
|
|
||||||
|
/// A critical success occurs on a roll of 1.
|
||||||
|
CriticalSuccess,
|
||||||
|
|
||||||
|
/// A basic failure means that the roll was above the target number.
|
||||||
|
Failure,
|
||||||
|
|
||||||
|
/// A fumble occurs if the target number is below 50 and the roll
|
||||||
|
/// was 96 - 100, OR if the roll result was 100. This means lower
|
||||||
|
/// target numbers are more likely to produce a fumble.
|
||||||
|
Fumble,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The outcome of a roll.
|
||||||
|
pub struct RolledDice {
|
||||||
|
/// The d100 result actually rolled.
|
||||||
|
num_rolled: u32,
|
||||||
|
|
||||||
|
/// The number we must meet for the roll to be considered a
|
||||||
|
/// success.
|
||||||
|
target: u32,
|
||||||
|
|
||||||
|
/// Stored for informational purposes in display.
|
||||||
|
modifier: DiceRollModifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RolledDice {
|
||||||
|
/// Calculate what type of success or failure this roll is.
|
||||||
|
/// Consult the RollResult enum for descriptions of what each
|
||||||
|
/// result requires.
|
||||||
|
fn result(&self) -> RollResult {
|
||||||
|
let hard_target = self.target / 2u32;
|
||||||
|
let extreme_target = self.target / 5u32;
|
||||||
|
if (self.target < 50 && self.num_rolled > 95) || self.num_rolled == 100 {
|
||||||
|
RollResult::Fumble
|
||||||
|
} else if self.num_rolled == 1 {
|
||||||
|
RollResult::CriticalSuccess
|
||||||
|
} else if self.num_rolled <= extreme_target {
|
||||||
|
RollResult::ExtremeSuccess
|
||||||
|
} else if self.num_rolled <= hard_target {
|
||||||
|
RollResult::HardSuccess
|
||||||
|
} else if self.num_rolled <= self.target {
|
||||||
|
RollResult::Success
|
||||||
|
} else {
|
||||||
|
RollResult::Failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A planned advancement roll, where the target number is the
|
||||||
|
/// existing skill amount.
|
||||||
|
pub struct AdvancementRoll {
|
||||||
|
/// The amount (0 to 100) of the existing skill. We must beat this
|
||||||
|
/// target number to advance the skill, or roll above a 95.
|
||||||
|
existing_skill: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A completed advancement roll.
|
||||||
|
pub struct RolledAdvancement {
|
||||||
|
existing_skill: u32,
|
||||||
|
advancement: u32,
|
||||||
|
successful: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RolledAdvancement {
|
||||||
|
/// The new skill amount, which will be the same if the roll was a
|
||||||
|
/// failure.
|
||||||
|
pub fn new_skill_amount(&self) -> u32 {
|
||||||
|
self.existing_skill + self.advancement
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How much the skill advanced (1 to 10). 0 if the advancement
|
||||||
|
/// roll failed.
|
||||||
|
pub fn advancement(&self) -> u32 {
|
||||||
|
self.advancement
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not the advancement roll was successful.
|
||||||
|
pub fn successful(&self) -> bool {
|
||||||
|
self.successful
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait DieRoller {
|
||||||
|
fn roll(&mut self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
///A version of DieRoller that uses a rand::Rng to roll numbers.
|
||||||
|
struct RngDieRoller<R: rand::Rng>(R);
|
||||||
|
|
||||||
|
impl<R: rand::Rng> DieRoller for RngDieRoller<R> {
|
||||||
|
fn roll(&mut self) -> u32 {
|
||||||
|
self.0.gen_range(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Roll a single percentile die according to the rules. We cannot
|
||||||
|
/// simply roll a d100 due to the way the game calculates roll results
|
||||||
|
/// with bonus/penalty dice. The unit roll (ones place) is added to
|
||||||
|
/// the tens roll, unless both results are 0, in which case the result
|
||||||
|
/// is 100.
|
||||||
|
fn roll_percentile_dice<R: DieRoller>(roller: &mut R, unit_roll: u32) -> u32 {
|
||||||
|
let tens_roll = roller.roll() * 10;
|
||||||
|
|
||||||
|
if tens_roll == 0 && unit_roll == 0 {
|
||||||
|
100
|
||||||
|
} else {
|
||||||
|
tens_roll + unit_roll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a roll with a target number and potential modifier. In a
|
||||||
|
/// normal roll, only one percentile die is rolled (1d100). With
|
||||||
|
/// bonuses or penalties, more dice are rolled, and either the lowest
|
||||||
|
/// (in case of bonus) or highest (in case of penalty) result is
|
||||||
|
/// picked. Rolls are not simply d100; the unit roll (ones place) is
|
||||||
|
/// rolled separately from the tens place, and then the unit number is
|
||||||
|
/// added to each potential roll before picking the lowest/highest
|
||||||
|
/// result.
|
||||||
|
fn roll(roll: DiceRoll) -> RolledDice {
|
||||||
|
use DiceRollModifier::*;
|
||||||
|
let num_rolls = match roll.modifier {
|
||||||
|
Normal => 1,
|
||||||
|
OneBonus | OnePenalty => 2,
|
||||||
|
TwoBonus | TwoPenalty => 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut roller = RngDieRoller(rand::thread_rng());
|
||||||
|
let unit_roll = roller.roll();
|
||||||
|
|
||||||
|
let rolls: Vec<u32> = (0..num_rolls)
|
||||||
|
.map(|_| roll_percentile_dice(&mut roller, unit_roll))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let num_rolled = match roll.modifier {
|
||||||
|
Normal => rolls.first(),
|
||||||
|
OneBonus | TwoBonus => rolls.iter().min(),
|
||||||
|
OnePenalty | TwoPenalty => rolls.iter().max(),
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
RolledDice {
|
||||||
|
modifier: roll.modifier,
|
||||||
|
num_rolled: *num_rolled,
|
||||||
|
target: roll.target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advancement_roll(roll: AdvancementRoll) -> RolledAdvancement {
|
||||||
|
let mut roller = RngDieRoller(rand::thread_rng());
|
||||||
|
let unit_roll = roller.roll();
|
||||||
|
let percentile_roll = roll_percentile_dice(&mut roller, unit_roll);
|
||||||
|
|
||||||
|
if percentile_roll < roll.existing_skill || percentile_roll > 95 {
|
||||||
|
RolledAdvancement {
|
||||||
|
existing_skill: roll.existing_skill,
|
||||||
|
advancement: roller.roll() + 1,
|
||||||
|
successful: true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RolledAdvancement {
|
||||||
|
existing_skill: roll.existing_skill,
|
||||||
|
advancement: 0,
|
||||||
|
successful: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ pub mod cofd;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod cthulhu;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod dice;
|
pub mod dice;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
Loading…
Reference in New Issue