From e3b819ecb03b46e7d23c208560d307a0d501e81a Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sat, 31 Oct 2020 13:19:38 +0000 Subject: [PATCH] Wire up regular cthulhu roll commmand (not yet parsed). --- src/commands.rs | 1 + src/commands/cthulhu.rs | 27 +++++++ src/commands/parser.rs | 11 +++ src/cthulhu/dice.rs | 166 +++++++++++++++++++++++++++------------- 4 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 src/commands/cthulhu.rs diff --git a/src/commands.rs b/src/commands.rs index 9468ff0..7320d88 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,6 +5,7 @@ use thiserror::Error; pub mod basic_rolling; pub mod cofd; +pub mod cthulhu; pub mod misc; pub mod parser; pub mod variables; diff --git a/src/commands/cthulhu.rs b/src/commands/cthulhu.rs new file mode 100644 index 0000000..508f626 --- /dev/null +++ b/src/commands/cthulhu.rs @@ -0,0 +1,27 @@ +use super::{Command, Execution}; +use crate::context::Context; +use crate::cthulhu::dice::{AdvancementRoll, DiceRoll}; +use async_trait::async_trait; + +pub struct CthRoll(pub DiceRoll); + +#[async_trait] +impl Command for CthRoll { + fn name(&self) -> &'static str { + "roll percentile pool" + } + + async fn execute(&self, ctx: &Context) -> Execution { + //TODO this will be converted to a result when supporting variables. + let roll = self.0.roll(); + let plain = format!("Roll: {}\nResult: {}", self.0, roll); + let html = format!( + "

Roll: {}

Result: {}

", + self.0, roll + ); + + Execution { plain, html } + } +} + +pub struct CthAdvanceRoll(pub AdvancementRoll); diff --git a/src/commands/parser.rs b/src/commands/parser.rs index 6194320..88bf3f9 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -2,12 +2,14 @@ use crate::cofd::parser::{create_chance_die, parse_dice_pool}; use crate::commands::{ basic_rolling::RollCommand, cofd::PoolRollCommand, + cthulhu::CthRoll, misc::HelpCommand, variables::{ DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand, }, Command, }; +use crate::cthulhu::dice::{DiceRoll, DiceRollModifier}; use crate::dice::parser::parse_element_expression; use crate::error::BotError; use crate::help::parse_help_topic; @@ -47,6 +49,14 @@ fn parse_pool_roll(input: &str) -> Result, BotError> { Ok(Box::new(PoolRollCommand(pool))) } +fn parse_cth_roll(input: &str) -> Result, BotError> { + let roll = DiceRoll { + target: 50, + modifier: DiceRollModifier::Normal, + }; + Ok(Box::new(CthRoll(roll))) +} + fn chance_die() -> Result, BotError> { let pool = create_chance_die()?; Ok(Box::new(PoolRollCommand(pool))) @@ -104,6 +114,7 @@ pub fn parse_command(input: &str) -> Result>, BotError> "del" => parse_delete_variable_command(&cmd_input).map(|command| Some(command)), "r" | "roll" => parse_roll(&cmd_input).map(|command| Some(command)), "rp" | "pool" => parse_pool_roll(&cmd_input).map(|command| Some(command)), + "cthroll" => parse_cth_roll(&cmd_input).map(|command| Some(command)), "chance" => chance_die().map(|command| Some(command)), "help" => help(&cmd_input).map(|command| Some(command)), // No recognized command, ignore this. diff --git a/src/cthulhu/dice.rs b/src/cthulhu/dice.rs index 805e9af..329561f 100644 --- a/src/cthulhu/dice.rs +++ b/src/cthulhu/dice.rs @@ -1,10 +1,22 @@ +use std::fmt; + /// A planned dice roll. +#[derive(Clone, Copy)] pub struct DiceRoll { - target: u32, - modifier: DiceRollModifier, + pub target: u32, + pub modifier: DiceRollModifier, +} + +impl fmt::Display for DiceRoll { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = format!("target: {}, modifiers: {}", self.target, self.modifier); + write!(f, "{}", message)?; + Ok(()) + } } /// Potential modifier on the die roll to be made. +#[derive(Clone, Copy)] pub enum DiceRollModifier { /// No bonuses or penalties. Normal, @@ -22,8 +34,23 @@ pub enum DiceRollModifier { TwoPenalty, } +impl fmt::Display for DiceRollModifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::Normal => "none", + Self::OneBonus => "one bonus", + Self::TwoBonus => "two bonus", + Self::OnePenalty => "one penalty", + Self::TwoPenalty => "two penalty", + }; + + write!(f, "{}", message)?; + Ok(()) + } +} + /// The outcome of a die roll, either some kind of success or failure. -enum RollResult { +pub enum RollResult { /// Basic success. The rolled number was equal to or less than the target number. Success, @@ -47,6 +74,22 @@ enum RollResult { Fumble, } +impl fmt::Display for RollResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + Self::Success => "success!", + Self::HardSuccess => "hard success!", + Self::ExtremeSuccess => "extreme success!", + Self::CriticalSuccess => "critical success!", + Self::Failure => "failure!", + Self::Fumble => "fumble!", + }; + + write!(f, "{}", message)?; + Ok(()) + } +} + /// The outcome of a roll. pub struct RolledDice { /// The d100 result actually rolled. @@ -64,7 +107,7 @@ 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 { + pub 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 { @@ -83,6 +126,19 @@ impl RolledDice { } } +impl fmt::Display for RolledDice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = format!( + "{} against {}: {}", + self.num_rolled, + self.target, + self.result() + ); + write!(f, "{}", message)?; + Ok(()) + } +} + /// A planned advancement roll, where the target number is the /// existing skill amount. pub struct AdvancementRoll { @@ -145,59 +201,63 @@ fn roll_percentile_dice(roller: &mut R, unit_roll: u32) -> u32 { } } -/// 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, - }; +impl DiceRoll { + /// 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. + pub fn roll(&self) -> RolledDice { + use DiceRollModifier::*; + let num_rolls = match self.modifier { + Normal => 1, + OneBonus | OnePenalty => 2, + TwoBonus | TwoPenalty => 3, + }; - let mut roller = RngDieRoller(rand::thread_rng()); - let unit_roll = roller.roll(); + let mut roller = RngDieRoller(rand::thread_rng()); + let unit_roll = roller.roll(); - let rolls: Vec = (0..num_rolls) - .map(|_| roll_percentile_dice(&mut roller, unit_roll)) - .collect(); + let rolls: Vec = (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, + let num_rolled = match self.modifier { + Normal => rolls.first(), + OneBonus | TwoBonus => rolls.iter().min(), + OnePenalty | TwoPenalty => rolls.iter().max(), } - } else { - RolledAdvancement { - existing_skill: roll.existing_skill, - advancement: 0, - successful: false, + .unwrap(); + + RolledDice { + modifier: self.modifier, + num_rolled: *num_rolled, + target: self.target, + } + } +} + +impl AdvancementRoll { + pub fn roll(&self) -> 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 < self.existing_skill || percentile_roll > 95 { + RolledAdvancement { + existing_skill: self.existing_skill, + advancement: roller.roll() + 1, + successful: true, + } + } else { + RolledAdvancement { + existing_skill: self.existing_skill, + advancement: 0, + successful: false, + } } } }