Wire up regular cthulhu roll commmand (not yet parsed).

This commit is contained in:
projectmoon 2020-10-31 13:19:38 +00:00 committed by ProjectMoon
parent 7a302c4489
commit e3b819ecb0
4 changed files with 152 additions and 53 deletions

View File

@ -5,6 +5,7 @@ use thiserror::Error;
pub mod basic_rolling; pub mod basic_rolling;
pub mod cofd; pub mod cofd;
pub mod cthulhu;
pub mod misc; pub mod misc;
pub mod parser; pub mod parser;
pub mod variables; pub mod variables;

27
src/commands/cthulhu.rs Normal file
View File

@ -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!(
"<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>",
self.0, roll
);
Execution { plain, html }
}
}
pub struct CthAdvanceRoll(pub AdvancementRoll);

View File

@ -2,12 +2,14 @@ use crate::cofd::parser::{create_chance_die, parse_dice_pool};
use crate::commands::{ use crate::commands::{
basic_rolling::RollCommand, basic_rolling::RollCommand,
cofd::PoolRollCommand, cofd::PoolRollCommand,
cthulhu::CthRoll,
misc::HelpCommand, misc::HelpCommand,
variables::{ variables::{
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand, DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
}, },
Command, Command,
}; };
use crate::cthulhu::dice::{DiceRoll, DiceRollModifier};
use crate::dice::parser::parse_element_expression; use crate::dice::parser::parse_element_expression;
use crate::error::BotError; use crate::error::BotError;
use crate::help::parse_help_topic; use crate::help::parse_help_topic;
@ -47,6 +49,14 @@ fn parse_pool_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(PoolRollCommand(pool))) Ok(Box::new(PoolRollCommand(pool)))
} }
fn parse_cth_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
let roll = DiceRoll {
target: 50,
modifier: DiceRollModifier::Normal,
};
Ok(Box::new(CthRoll(roll)))
}
fn chance_die() -> Result<Box<dyn Command>, BotError> { fn chance_die() -> Result<Box<dyn Command>, BotError> {
let pool = create_chance_die()?; let pool = create_chance_die()?;
Ok(Box::new(PoolRollCommand(pool))) Ok(Box::new(PoolRollCommand(pool)))
@ -104,6 +114,7 @@ pub fn parse_command(input: &str) -> Result<Option<Box<dyn Command>>, BotError>
"del" => parse_delete_variable_command(&cmd_input).map(|command| Some(command)), "del" => parse_delete_variable_command(&cmd_input).map(|command| Some(command)),
"r" | "roll" => parse_roll(&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)), "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)), "chance" => chance_die().map(|command| Some(command)),
"help" => help(&cmd_input).map(|command| Some(command)), "help" => help(&cmd_input).map(|command| Some(command)),
// No recognized command, ignore this. // No recognized command, ignore this.

View File

@ -1,10 +1,22 @@
use std::fmt;
/// A planned dice roll. /// A planned dice roll.
#[derive(Clone, Copy)]
pub struct DiceRoll { pub struct DiceRoll {
target: u32, pub target: u32,
modifier: DiceRollModifier, 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. /// Potential modifier on the die roll to be made.
#[derive(Clone, Copy)]
pub enum DiceRollModifier { pub enum DiceRollModifier {
/// No bonuses or penalties. /// No bonuses or penalties.
Normal, Normal,
@ -22,8 +34,23 @@ pub enum DiceRollModifier {
TwoPenalty, 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. /// 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. /// Basic success. The rolled number was equal to or less than the target number.
Success, Success,
@ -47,6 +74,22 @@ enum RollResult {
Fumble, 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. /// The outcome of a roll.
pub struct RolledDice { pub struct RolledDice {
/// The d100 result actually rolled. /// The d100 result actually rolled.
@ -64,7 +107,7 @@ impl RolledDice {
/// Calculate what type of success or failure this roll is. /// Calculate what type of success or failure this roll is.
/// Consult the RollResult enum for descriptions of what each /// Consult the RollResult enum for descriptions of what each
/// result requires. /// result requires.
fn result(&self) -> RollResult { pub fn result(&self) -> RollResult {
let hard_target = self.target / 2u32; let hard_target = self.target / 2u32;
let extreme_target = self.target / 5u32; let extreme_target = self.target / 5u32;
if (self.target < 50 && self.num_rolled > 95) || self.num_rolled == 100 { 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 /// A planned advancement roll, where the target number is the
/// existing skill amount. /// existing skill amount.
pub struct AdvancementRoll { pub struct AdvancementRoll {
@ -145,59 +201,63 @@ fn roll_percentile_dice<R: DieRoller>(roller: &mut R, unit_roll: u32) -> u32 {
} }
} }
/// Make a roll with a target number and potential modifier. In a impl DiceRoll {
/// normal roll, only one percentile die is rolled (1d100). With /// Make a roll with a target number and potential modifier. In a
/// bonuses or penalties, more dice are rolled, and either the lowest /// normal roll, only one percentile die is rolled (1d100). With
/// (in case of bonus) or highest (in case of penalty) result is /// bonuses or penalties, more dice are rolled, and either the lowest
/// picked. Rolls are not simply d100; the unit roll (ones place) is /// (in case of bonus) or highest (in case of penalty) result is
/// rolled separately from the tens place, and then the unit number is /// picked. Rolls are not simply d100; the unit roll (ones place) is
/// added to each potential roll before picking the lowest/highest /// rolled separately from the tens place, and then the unit number is
/// result. /// added to each potential roll before picking the lowest/highest
fn roll(roll: DiceRoll) -> RolledDice { /// result.
use DiceRollModifier::*; pub fn roll(&self) -> RolledDice {
let num_rolls = match roll.modifier { use DiceRollModifier::*;
Normal => 1, let num_rolls = match self.modifier {
OneBonus | OnePenalty => 2, Normal => 1,
TwoBonus | TwoPenalty => 3, OneBonus | OnePenalty => 2,
}; TwoBonus | TwoPenalty => 3,
};
let mut roller = RngDieRoller(rand::thread_rng()); let mut roller = RngDieRoller(rand::thread_rng());
let unit_roll = roller.roll(); let unit_roll = roller.roll();
let rolls: Vec<u32> = (0..num_rolls) let rolls: Vec<u32> = (0..num_rolls)
.map(|_| roll_percentile_dice(&mut roller, unit_roll)) .map(|_| roll_percentile_dice(&mut roller, unit_roll))
.collect(); .collect();
let num_rolled = match roll.modifier { let num_rolled = match self.modifier {
Normal => rolls.first(), Normal => rolls.first(),
OneBonus | TwoBonus => rolls.iter().min(), OneBonus | TwoBonus => rolls.iter().min(),
OnePenalty | TwoPenalty => rolls.iter().max(), 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 { .unwrap();
RolledAdvancement {
existing_skill: roll.existing_skill, RolledDice {
advancement: 0, modifier: self.modifier,
successful: false, 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,
}
} }
} }
} }