Wire up regular cthulhu roll commmand (not yet parsed).
This commit is contained in:
parent
7a302c4489
commit
e3b819ecb0
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue