in the beginning there was code
commit
b7c5e7a37a
@ -0,0 +1,16 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
.vscode
|
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = 'simrust'
|
||||
version = '0.1.0'
|
||||
edition = '2021'
|
||||
|
||||
[lib]
|
||||
crate-type = [ 'lib' ]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
rand = '0.8.4'
|
||||
rand_distr = '0.4.3'
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
rayon = "1.7.0"
|
||||
|
@ -0,0 +1,127 @@
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
iter,
|
||||
};
|
||||
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
// use rayon::prelude::*;
|
||||
use simrust::{
|
||||
character::Character,
|
||||
combat::{make_them_fight, EncounterType},
|
||||
random_character::random_character,
|
||||
stats::Stat,
|
||||
};
|
||||
|
||||
fn print_chars<'a, L>(characters: L)
|
||||
where
|
||||
L: IntoIterator<Item = &'a Character>,
|
||||
{
|
||||
println!("Name | Classes | HP MP AC DP | STR DEX CON STB | INT KND CMP CHA | Armor | Weapon");
|
||||
for ch in characters {
|
||||
let mut c = ch.to_owned();
|
||||
c.init_encounter(EncounterType::Physical);
|
||||
println!(
|
||||
"{name:<8} | {class1:<12} {class2:<12} | {hp:>2} {mp:>2} {ac:>2} {dp:>2} | {str:>3} {dex:>3} {con:>3} {stb:>3} | {int:>3} {knd:>3} {cmp:>3} {cha:>3} | {armor:<6} | {weapon:<15}",
|
||||
name = c.name,
|
||||
class1 = c.class1,
|
||||
class2 = c.class2,
|
||||
hp=c.hp,
|
||||
mp = c.mp,
|
||||
ac=c.ac(),
|
||||
dp = c.dice_pool(),
|
||||
str=c.stat(Stat::STR),
|
||||
dex=c.stat(Stat::DEX),
|
||||
con=c.stat(Stat::CON),
|
||||
stb=c.stat(Stat::STB),
|
||||
int=c.stat(Stat::INT),
|
||||
knd=c.stat(Stat::KND),
|
||||
cmp=c.stat(Stat::CMP),
|
||||
cha=c.stat(Stat::CHA),
|
||||
armor = c.armor,
|
||||
weapon = c.weapon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let n = 100;
|
||||
let keep = 20;
|
||||
let gen = 50000;
|
||||
|
||||
let mut characters: Vec<Character> = iter::repeat_with(random_character).take(n).collect();
|
||||
print_chars(characters.iter().take(keep).collect::<Vec<&Character>>());
|
||||
// print_chars(&characters1);
|
||||
// print_chars(&characters2);
|
||||
|
||||
for g in 0..gen {
|
||||
characters.shuffle(&mut rng);
|
||||
|
||||
let teams: (Vec<(usize, Character)>, Vec<(usize, Character)>) = characters
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.partition(|(i, _)| *i % 2 == 0);
|
||||
let matches: Vec<(Character, Character)> = iter::zip(teams.0.iter(), teams.1.iter())
|
||||
.map(|((_, c1), (_, c2))| (c1.to_owned(), c2.to_owned()))
|
||||
.collect();
|
||||
|
||||
let mut winners: Vec<(i64, Character)> = matches
|
||||
.iter()
|
||||
.map(|(c1, c2)| {
|
||||
let n_rounds = 50;
|
||||
let mut w: i64 = 0;
|
||||
|
||||
for _i in 0..n_rounds {
|
||||
let mut c1l = c1.to_owned();
|
||||
let mut c2l = c2.to_owned();
|
||||
let t = make_them_fight(&mut c1l, &mut c2l);
|
||||
|
||||
match t {
|
||||
Some(_turns) => {
|
||||
if c2l.hp <= 0 {
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
if w >= n_rounds / 2 {
|
||||
(-w, c1.to_owned())
|
||||
} else {
|
||||
(-(n_rounds - w), c2.to_owned())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
winners.sort_by_key(|(i, _)| *i);
|
||||
|
||||
characters = winners
|
||||
.iter()
|
||||
.take(keep)
|
||||
.map(|(_, c)| c.clone())
|
||||
.chain(
|
||||
(0..(n - keep))
|
||||
.into_iter()
|
||||
.map(|_| random_character())
|
||||
.take(n - keep),
|
||||
)
|
||||
.collect();
|
||||
|
||||
if g % (gen / 100) == 0 {
|
||||
print!(
|
||||
"\r{:#<c$}{:.<r$}",
|
||||
"",
|
||||
"",
|
||||
c = (((g as f32) / (gen as f32)) * 100.0) as usize,
|
||||
r = 100 - (((g as f32) / (gen as f32)) * 100.0) as usize
|
||||
);
|
||||
io::stdout().flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
println!("");
|
||||
|
||||
print_chars(characters.iter().take(keep).collect::<Vec<&Character>>());
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
use simrust::{
|
||||
character::create_character,
|
||||
class::Class,
|
||||
combat::EncounterType,
|
||||
equipment::{Armor, WeaponType},
|
||||
stats::StatBlock,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut bob = create_character(
|
||||
String::from("Bob"),
|
||||
Class::Guard,
|
||||
Class::Brawler,
|
||||
StatBlock::from((1, 0, 1, 0, 0, 0, 0, 0)),
|
||||
StatBlock::from((2, 0, 2, 0, -1, -2, 0, -1)),
|
||||
Armor::Medium,
|
||||
WeaponType::BladedWeapon.create_weapon("Longsword".to_owned()),
|
||||
);
|
||||
|
||||
let mut drub = create_character(
|
||||
String::from("Drub"),
|
||||
Class::Hunter,
|
||||
Class::Thief,
|
||||
StatBlock::from((0, 1, 1, 0, 0, 0, 0, 0)),
|
||||
StatBlock::from((0, 2, 2, 0, -2, 0, 0, -2)),
|
||||
Armor::Light,
|
||||
WeaponType::RangedWeapon.create_weapon(String::from("Shortbow")),
|
||||
);
|
||||
|
||||
bob.init_dice_pool(EncounterType::Physical);
|
||||
drub.init_dice_pool(EncounterType::Physical);
|
||||
|
||||
println!("{}", bob);
|
||||
println!("{}", drub);
|
||||
|
||||
bob.attacks(&mut drub);
|
||||
|
||||
println!("{}", bob);
|
||||
println!("{}", drub);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
use simrust::{
|
||||
character::create_character,
|
||||
class::Class,
|
||||
combat::EncounterType,
|
||||
equipment::{Armor, WeaponType},
|
||||
stats::StatBlock,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let mut bob = create_character(
|
||||
String::from("Bob"),
|
||||
Class::Hunter,
|
||||
Class::Knight,
|
||||
StatBlock::from((1, 0, 1, 0, 0, 0, 0, 0)),
|
||||
StatBlock::from((2, 0, 2, 0, -1, -2, 0, -1)),
|
||||
Armor::Medium,
|
||||
WeaponType::BladedWeapon.create_weapon("Longsword".to_owned()),
|
||||
);
|
||||
|
||||
let mut glob = create_character(
|
||||
String::from("Glob"),
|
||||
Class::Guard,
|
||||
Class::Brawler,
|
||||
StatBlock::from((1, 0, 1, 0, 0, 0, 0, 0)),
|
||||
StatBlock::from((2, 0, 2, 0, -1, -2, 0, -1)),
|
||||
Armor::Medium,
|
||||
WeaponType::BladedWeapon.create_weapon("Longsword".to_owned()),
|
||||
);
|
||||
|
||||
bob.init_dice_pool(EncounterType::Physical);
|
||||
glob.init_dice_pool(EncounterType::Physical);
|
||||
|
||||
println!("{}", bob);
|
||||
println!("{}", glob);
|
||||
|
||||
bob.attacks(&mut glob);
|
||||
|
||||
println!("{}", bob);
|
||||
println!("{}", glob);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
use simrust::{
|
||||
character::create_character,
|
||||
class::Class,
|
||||
equipment::{Armor, WeaponType},
|
||||
random_character::{random_stat_adjust, random_stat_choice},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let c = create_character(
|
||||
"Callypigian".into(),
|
||||
Class::Swashbuckler,
|
||||
Class::Survivalist,
|
||||
random_stat_choice(),
|
||||
random_stat_adjust(),
|
||||
Armor::Medium,
|
||||
WeaponType::RangedWeapon.create_weapon(WeaponType::RangedWeapon.to_string()),
|
||||
);
|
||||
|
||||
println!("{}", c)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
use simrust::{random_character::{random_character}, combat::EncounterType};
|
||||
|
||||
fn main() {
|
||||
// for _i in 0..10 {
|
||||
// let sb = random_stat_adjust();
|
||||
// println!("{:?}", sb);
|
||||
// println!(
|
||||
// "{:?}",
|
||||
// Vec::from(sb).iter().map(|x| { x.abs() }).sum::<i64>() / 2
|
||||
// );
|
||||
// }
|
||||
|
||||
for _i in 0..3 {
|
||||
let mut c = random_character();
|
||||
c.init_dice_pool(EncounterType::Physical);
|
||||
println!("{}", c);
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
class::Class,
|
||||
combat::EncounterType,
|
||||
equipment::{Armor, Weapon, WeaponType},
|
||||
skills::{Proficiencies, ToProficiencies},
|
||||
stats::{Stat, StatBlock, ToStatBlock},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Character {
|
||||
pub name: String,
|
||||
pub class1: Class,
|
||||
pub class2: Class,
|
||||
pub stat_block: StatBlock,
|
||||
pub hp: i64,
|
||||
pub mp: i64,
|
||||
pub proficiency: i64,
|
||||
pub proficiencies: Proficiencies,
|
||||
pub armor: Armor,
|
||||
pub weapon: Weapon,
|
||||
dice_pool: i64,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
pub fn ac(&self) -> i64 {
|
||||
self.stat(Stat::STB) + self.stat(Stat::DEX) + self.armor_value()
|
||||
}
|
||||
|
||||
pub fn weapon_proficiency(&self) -> i64 {
|
||||
if match self.weapon.weapon_type {
|
||||
WeaponType::SimpleWeapon => self.proficiencies.simple_weapons,
|
||||
WeaponType::BladedWeapon => self.proficiencies.bladed_weapons,
|
||||
WeaponType::BluntWeapon => self.proficiencies.blunt_weapons,
|
||||
WeaponType::PoleWeapon => self.proficiencies.pole_weapons,
|
||||
WeaponType::ThrownWeapon => self.proficiencies.thrown_weapons,
|
||||
WeaponType::RangedWeapon => self.proficiencies.ranged_weapons,
|
||||
} {
|
||||
self.proficiency
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn armor_value(&self) -> i64 {
|
||||
if match self.armor {
|
||||
Armor::Heavy => self.proficiencies.heavy_armor,
|
||||
Armor::Medium => self.proficiencies.medium_armor,
|
||||
Armor::Light => self.proficiencies.light_armor,
|
||||
Armor::None => true,
|
||||
} {
|
||||
self.armor.armor()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_damage(&self, dice: i64) -> i64 {
|
||||
(dice + self.weapon_proficiency()).max(0) * self.weapon.damage_dice
|
||||
+ if let Some(damage_stat) = self.weapon.stat_damage {
|
||||
self.stat(damage_stat)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dice_pool(&self) -> i64 {
|
||||
self.dice_pool
|
||||
}
|
||||
|
||||
pub fn init_dice_pool(&mut self, encounter_type: EncounterType) {
|
||||
self.dice_pool = match encounter_type {
|
||||
EncounterType::Physical => {
|
||||
self.stat_block[self.weapon.weapon_stat] + self.weapon_proficiency()
|
||||
}
|
||||
EncounterType::Mental => self.stat_block.INT,
|
||||
}
|
||||
.max(0)
|
||||
}
|
||||
|
||||
pub fn adjust_dice_pool(&mut self, adjustment: i64, encounter_type: EncounterType) {
|
||||
self.dice_pool = (self.dice_pool
|
||||
+ adjustment
|
||||
+ match encounter_type {
|
||||
EncounterType::Physical => self.stat_block.CON,
|
||||
EncounterType::Mental => self.stat_block.CMP,
|
||||
})
|
||||
.max(0)
|
||||
}
|
||||
|
||||
pub fn init_hp_mp(&mut self, encounter_type: EncounterType) {
|
||||
if encounter_type == EncounterType::Physical {
|
||||
self.hp = (self.stat(Stat::CON) + self.stat(Stat::STB)) * 3;
|
||||
} else {
|
||||
self.mp = (self.stat(Stat::CMP) + self.stat(Stat::CHA)) * 3;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_encounter(&mut self, encounter_type: EncounterType) {
|
||||
self.init_dice_pool(encounter_type);
|
||||
self.init_hp_mp(encounter_type);
|
||||
}
|
||||
|
||||
pub fn stat(&self, stat: Stat) -> i64 {
|
||||
let adjusted_stat_block = self.stat_block + self.armor.stat_adjust();
|
||||
|
||||
adjusted_stat_block[stat]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_character(
|
||||
name: String,
|
||||
class1: Class,
|
||||
class2: Class,
|
||||
stat_choice: StatBlock,
|
||||
stat_adjust: StatBlock,
|
||||
armor: Armor,
|
||||
weapon: Weapon,
|
||||
) -> Character {
|
||||
if !(Vec::from(stat_choice).iter().all(|s| *s >= 0)
|
||||
&& Vec::from(stat_choice).iter().sum::<i64>() == 2i64)
|
||||
{
|
||||
panic!("Invalid stat choice: {:?}", stat_choice)
|
||||
}
|
||||
if Vec::from(stat_adjust).iter().sum::<i64>() != 0 {
|
||||
panic!("Invalid stat adjust: {:?}", stat_adjust)
|
||||
}
|
||||
let stat_block =
|
||||
StatBlock::ones() + class1.stat_block() + class2.stat_block() + stat_choice + stat_adjust;
|
||||
Character {
|
||||
name,
|
||||
class1,
|
||||
class2,
|
||||
stat_block,
|
||||
hp: (stat_block.CON + stat_block.STB) * 3,
|
||||
mp: (stat_block.CMP + stat_block.CHA) * 3,
|
||||
proficiency: 1,
|
||||
proficiencies: class1.proficiencies() | class2.proficiencies(),
|
||||
armor,
|
||||
weapon,
|
||||
dice_pool: 0,
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Character {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"╔═══════════════════════════════╤═══════╗
|
||||
║ {name:<15} {class1:>12} │ HP {hp:>2} ║
|
||||
║ {class2:>12} │ MP {mp:>2} ║
|
||||
╠═══════════════════╤═══════════╧═══════╣
|
||||
║ STR {str:>2} │ {int:>2} INT ║
|
||||
║ DEX {dex:>2} │ {knd:>2} KND ║
|
||||
║ CON {con:>2} │ {cmp:>2} CMP ║
|
||||
║ STB {stb:>2} │ {cha:>2} CHA ║
|
||||
╠═══════════════════╧═══════════════════╣
|
||||
║ Armor {armor:<15} AC {ac:>2} ║
|
||||
║ Weapon {weapon:<15} ║
|
||||
╠═══════════════════════════════════════╣
|
||||
║ Dice pool {dp:>2} ║
|
||||
╚═══════════════════════════════════════╝"#,
|
||||
name = self.name,
|
||||
class1 = self.class1,
|
||||
class2 = self.class2,
|
||||
hp = self.hp,
|
||||
mp = self.mp,
|
||||
ac = self.ac(),
|
||||
str = self.stat_block.STR,
|
||||
dex = self.stat_block.DEX,
|
||||
con = self.stat_block.CON,
|
||||
stb = self.stat_block.STB,
|
||||
int = self.stat_block.INT,
|
||||
knd = self.stat_block.KND,
|
||||
cmp = self.stat_block.CMP,
|
||||
cha = self.stat_block.CHA,
|
||||
armor = self.armor,
|
||||
weapon = self.weapon,
|
||||
dp = self.dice_pool()
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
use rand::seq::IteratorRandom;
|
||||
use rand_distr::{Distribution, Standard};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{Display, EnumIter};
|
||||
|
||||
use crate::{
|
||||
skills::{Proficiencies, ToProficiencies},
|
||||
stats::{StatBlock, ToStatBlock},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, EnumIter, Clone, Copy, Display)]
|
||||
pub enum Class {
|
||||
Guard,
|
||||
Knight,
|
||||
Brawler,
|
||||
Thief,
|
||||
Swashbuckler,
|
||||
Survivalist,
|
||||
Hunter,
|
||||
Witch,
|
||||
Wizard,
|
||||
}
|
||||
|
||||
impl ToStatBlock for Class {
|
||||
fn stat_block(&self) -> StatBlock {
|
||||
match self {
|
||||
Class::Guard => StatBlock::from((1, 0, 1, 1, 0, 0, 0, 0)),
|
||||
Class::Knight => StatBlock::from((1, 0, 0, 1, 0, 0, 0, 1)),
|
||||
Class::Brawler => StatBlock::from((1, 1, 1, 0, 0, 0, 0, 0)),
|
||||
// -----
|
||||
Class::Thief => StatBlock::from((0, 1, 0, 0, 1, 0, 1, 0)),
|
||||
Class::Swashbuckler => StatBlock::from((1, 1, 0, 0, 0, 0, 0, 1)),
|
||||
// -----
|
||||
Class::Survivalist => StatBlock::from((0, 1, 1, 0, 1, 0, 0, 0)),
|
||||
Class::Hunter => StatBlock::from((0, 1, 1, 0, 0, 1, 0, 0)),
|
||||
// -----
|
||||
Class::Witch => StatBlock::from((0, 0, 0, 0, 1, 1, 1, 0)),
|
||||
Class::Wizard => StatBlock::from((0, 0, 0, 0, 1, 1, 0, 1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToProficiencies for Class {
|
||||
fn proficiencies(&self) -> Proficiencies {
|
||||
match self {
|
||||
Class::Guard => Proficiencies {
|
||||
light_armor: true,
|
||||
medium_armor: true,
|
||||
heavy_armor: true,
|
||||
// -----
|
||||
// bladed_weapons: true,
|
||||
simple_weapons: true,
|
||||
pole_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Knight => Proficiencies {
|
||||
light_armor: true,
|
||||
medium_armor: true,
|
||||
heavy_armor: true,
|
||||
// -----
|
||||
bladed_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Brawler => Proficiencies {
|
||||
light_armor: true,
|
||||
medium_armor: true,
|
||||
// -----
|
||||
simple_weapons: true,
|
||||
blunt_weapons: true,
|
||||
thrown_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Thief => Proficiencies {
|
||||
simple_weapons: true,
|
||||
thrown_weapons: true,
|
||||
ranged_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Swashbuckler => Proficiencies {
|
||||
light_armor: true,
|
||||
simple_weapons: true,
|
||||
bladed_weapons: true,
|
||||
thrown_weapons: true,
|
||||
ranged_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Survivalist => Proficiencies {
|
||||
// -----
|
||||
simple_weapons: true,
|
||||
thrown_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Hunter => Proficiencies {
|
||||
light_armor: true,
|
||||
// -----
|
||||
simple_weapons: true,
|
||||
pole_weapons: true,
|
||||
ranged_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Witch => Proficiencies {
|
||||
simple_weapons: true,
|
||||
..Default::default()
|
||||
},
|
||||
Class::Wizard => Proficiencies::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Class> for Standard {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Class {
|
||||
Class::iter().choose(rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod class_tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{class::Class, stats::ToStatBlock};
|
||||
|
||||
// const CLASSES: [Class; 9] = [
|
||||
// Class::Guard,
|
||||
// Class::Knight,
|
||||
// Class::Brawler,
|
||||
// Class::Thief,
|
||||
// Class::Swashbuckler,
|
||||
// Class::Survivalist,
|
||||
// Class::Hunter,
|
||||
// Class::Witch,
|
||||
// Class::Wizard,
|
||||
// ];
|
||||
|
||||
#[test]
|
||||
fn stat_sums() {
|
||||
for class in Class::iter() {
|
||||
let stat_block = class.stat_block();
|
||||
assert_eq!(
|
||||
3,
|
||||
stat_block.STR
|
||||
+ stat_block.DEX
|
||||
+ stat_block.CON
|
||||
+ stat_block.STB
|
||||
+ stat_block.INT
|
||||
+ stat_block.KND
|
||||
+ stat_block.CMP
|
||||
+ stat_block.CHA
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_unique() {
|
||||
for class1 in Class::iter() {
|
||||
for class2 in Class::iter() {
|
||||
assert!(class1 == class2 || class1.stat_block() != class2.stat_block())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
use crate::{character::Character, dice::successes};
|
||||
|
||||
#[derive(Debug,PartialEq, Eq, Clone, Copy)]
|
||||
pub enum EncounterType {
|
||||
Physical,
|
||||
Mental,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
pub fn attacks(&mut self, opponent: &mut Character) {
|
||||
let (margin, _succ, _fail, _succ_dice, fail_dice) = successes(
|
||||
self.dice_pool().max(0) as usize,
|
||||
(opponent.ac() - self.weapon_proficiency() as i64).clamp(1, 12),
|
||||
);
|
||||
|
||||
let damage = self.compute_damage(margin);//(margin + self.weapon_proficiency()).max(0) * self.weapon.damage_dice;
|
||||
|
||||
opponent.hp = opponent.hp - damage as i64;
|
||||
|
||||
self.adjust_dice_pool((-fail_dice).min(0), EncounterType::Physical);
|
||||
|
||||
// println!(
|
||||
// "{} attacks {} getting a margin of {} (s: {}, f: {}) and dealing {} damage.",
|
||||
// self.name, opponent.name, margin, succ, fail, damage
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_them_fight(character1: &mut Character, character2: &mut Character) -> Option<i64> {
|
||||
character1.init_encounter(EncounterType::Physical);
|
||||
character2.init_encounter(EncounterType::Physical);
|
||||
|
||||
let mut turn = 0;
|
||||
|
||||
loop {
|
||||
turn += 1;
|
||||
character1.attacks(character2);
|
||||
if character2.hp <= 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
character2.attacks(character1);
|
||||
if character1.hp <= 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if turn > 15 {
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
Some(turn)
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
use rand::Rng;
|
||||
use std::ops;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Dice {
|
||||
d4,
|
||||
d6,
|
||||
d8,
|
||||
d10,
|
||||
d12,
|
||||
d20,
|
||||
d100,
|
||||
}
|
||||
|
||||
impl Iterator for Dice {
|
||||
type Item = i64;
|
||||
|
||||
fn next(self: &mut Dice) -> Option<Self::Item> {
|
||||
let mut rng = rand::thread_rng();
|
||||
match self {
|
||||
Dice::d4 => Some(rng.gen_range(1..4)),
|
||||
Dice::d6 => Some(rng.gen_range(1..6)),
|
||||
Dice::d8 => Some(rng.gen_range(1..8)),
|
||||
Dice::d10 => Some(rng.gen_range(1..10)),
|
||||
Dice::d12 => Some(rng.gen_range(1..12)),
|
||||
Dice::d20 => Some(rng.gen_range(1..20)),
|
||||
Dice::d100 => Some(rng.gen_range(1..100)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<Dice> for i64 {
|
||||
type Output = i64;
|
||||
|
||||
fn mul(self, rhs: Dice) -> Self::Output {
|
||||
rhs.take(self as usize).sum()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn successes(n: usize, threshold: i64) -> (i64, i64, i64, i64, i64) {
|
||||
if threshold < 1 || threshold > 12 {
|
||||
panic!("Invalid threshold: {}", threshold)
|
||||
}
|
||||
|
||||
let rolls: Vec<i64> = Dice::d12.take(n).collect();
|
||||
|
||||
let succ_dice = rolls.iter().filter(|roll| **roll > threshold).count() as i64;
|
||||
let crit_succ = rolls.iter().filter(|roll| **roll == 12).count() as i64;
|
||||
|
||||
let fail_dice = rolls.iter().filter(|roll| **roll <= threshold).count() as i64;
|
||||
let crit_fail = rolls.iter().filter(|roll| **roll == 1).count() as i64;
|
||||
|
||||
(
|
||||
succ_dice + crit_succ - fail_dice - crit_fail,
|
||||
succ_dice + crit_succ,
|
||||
fail_dice + crit_fail,
|
||||
succ_dice,
|
||||
fail_dice,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dice_tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn in_range() {
|
||||
assert!(Dice::d4.take(1000).all(|roll| roll >= 1 && roll <= 4));
|
||||
assert!(Dice::d6.take(1000).all(|roll| roll >= 1 && roll <= 6));
|
||||
assert!(Dice::d8.take(1000).all(|roll| roll >= 1 && roll <= 8));
|
||||
assert!(Dice::d10.take(1000).all(|roll| roll >= 1 && roll <= 10));
|
||||
assert!(Dice::d12.take(1000).all(|roll| roll >= 1 && roll <= 12));
|
||||
assert!(Dice::d20.take(1000).all(|roll| roll >= 1 && roll <= 20));
|
||||
assert!(Dice::d100.take(1000).all(|roll| roll >= 1 && roll <= 100))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn wrong_threshold() {
|
||||
successes(10, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Clampable: Ord + Copy {
|
||||
fn clamp(&self, lb: &Self, ub: &Self) -> Self {
|
||||
*self.min(ub).max(lb)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clampable for i64 {}
|
@ -0,0 +1,150 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
dice::Dice,
|
||||
stats::{Stat, StatBlock},
|
||||
};
|
||||
use rand::seq::IteratorRandom;
|
||||
use rand_distr::{Distribution, Standard};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{Display, EnumIter};
|
||||
|
||||
#[derive(Debug, Clone, Copy, EnumIter, Display)]
|
||||
pub enum Armor {
|
||||
Heavy,
|
||||
Medium,
|
||||
Light,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Armor {
|
||||
pub fn armor(&self) -> i64 {
|
||||
match self {
|
||||
Armor::Heavy => 5,
|
||||
Armor::Medium => 3,
|
||||
Armor::Light => 1,
|
||||
Armor::None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stat_adjust(&self) -> StatBlock {
|
||||
match self {
|
||||
Armor::Heavy => StatBlock {
|
||||
DEX: -1,
|
||||
..Default::default()
|
||||
},
|
||||
Armor::Medium => Default::default(),
|
||||
Armor::Light => Default::default(),
|
||||
Armor::None => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Armor> for Standard {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Armor {
|
||||
Armor::iter().choose(rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, EnumIter)]
|
||||
pub enum WeaponType {
|
||||
SimpleWeapon,
|
||||
BladedWeapon,
|
||||
BluntWeapon,
|
||||
PoleWeapon,
|
||||
ThrownWeapon,
|
||||
RangedWeapon,
|
||||
}
|
||||
|
||||
impl WeaponType {
|
||||
pub fn create_weapon(&self, name: String) -> Weapon {
|
||||
Weapon {
|
||||
name,
|
||||
weapon_type: *self,
|
||||
damage_dice: self.damage_dice(),
|
||||
weapon_stat: self.weapon_stat(),
|
||||
stat_damage: self.stat_damage(),
|
||||
}
|
||||
}
|
||||
pub fn damage_dice(&self) -> Dice {
|
||||
match self {
|
||||
WeaponType::SimpleWeapon => Dice::d4,
|
||||
WeaponType::BladedWeapon => Dice::d6,
|
||||
WeaponType::BluntWeapon => Dice::d10,
|
||||
WeaponType::PoleWeapon => Dice::d4,
|
||||
WeaponType::ThrownWeapon => Dice::d4,
|
||||
WeaponType::RangedWeapon => Dice::d4,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weapon_stat(&self) -> Stat {
|
||||
match self {
|
||||
WeaponType::SimpleWeapon => Stat::STR,
|
||||
WeaponType::BladedWeapon => Stat::STR,
|
||||
WeaponType::BluntWeapon => Stat::STR,
|
||||
WeaponType::PoleWeapon => Stat::DEX,
|
||||
WeaponType::ThrownWeapon => Stat::DEX,
|
||||
WeaponType::RangedWeapon => Stat::DEX,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stat_damage(&self) -> Option<Stat> {
|
||||
match self {
|
||||
WeaponType::SimpleWeapon => None,
|
||||
WeaponType::BladedWeapon => None,
|
||||
WeaponType::BluntWeapon => Some(Stat::STR),
|
||||
WeaponType::PoleWeapon => Some(Stat::DEX),
|
||||
WeaponType::ThrownWeapon => Some(Stat::STR),
|
||||
WeaponType::RangedWeapon => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stat_adjust(&self) -> StatBlock {
|
||||
match self {
|
||||
WeaponType::SimpleWeapon => StatBlock::default(),
|
||||
WeaponType::BladedWeapon => StatBlock::default(),
|
||||
WeaponType::BluntWeapon => StatBlock {
|
||||
DEX: -1,
|
||||
..Default::default()
|
||||
},
|
||||
WeaponType::PoleWeapon => StatBlock::default(),
|
||||
WeaponType::ThrownWeapon => StatBlock::default(),
|
||||
WeaponType::RangedWeapon => StatBlock::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WeaponType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WeaponType::SimpleWeapon => write!(f, "Simple Weapon"),
|
||||
WeaponType::BladedWeapon => write!(f, "Bladed Weapon"),
|
||||
WeaponType::BluntWeapon => write!(f, "Blunt Weapon"),
|
||||
WeaponType::PoleWeapon => write!(f, "Pole Weapon"),
|
||||
WeaponType::ThrownWeapon => write!(f, "Thrown Weapon"),
|
||||
WeaponType::RangedWeapon => write!(f, "Ranged Weapon"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Weapon {
|
||||
pub name: String,
|
||||
pub weapon_type: WeaponType,
|
||||
pub damage_dice: Dice,
|
||||
pub weapon_stat: Stat,
|
||||
pub stat_damage: Option<Stat>,
|
||||
}
|
||||
|
||||
impl Display for Weapon {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.pad(&self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Weapon> for Standard {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Weapon {
|
||||
let wt = WeaponType::iter().choose(rng).unwrap();
|
||||
wt.create_weapon(wt.to_string())
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod character;
|
||||
pub mod class;
|
||||
pub mod combat;
|
||||
pub mod dice;
|
||||
pub mod equipment;
|
||||
pub mod skills;
|
||||
pub mod stats;
|
||||
|
||||
pub mod random_character;
|
@ -0,0 +1,99 @@
|
||||
use rand::{
|
||||
seq::{IteratorRandom, SliceRandom},
|
||||
thread_rng, Rng,
|
||||
};
|
||||
use rand_distr::{Distribution, Poisson, Standard};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
character::{create_character, Character},
|
||||
stats::{Stat, StatBlock},
|
||||
};
|
||||
|
||||
pub fn random_stat_choice() -> StatBlock {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let stat1: Stat = rng.gen();
|
||||
let stat2: Stat = rng.gen();
|
||||
|
||||
stat1 + stat2
|
||||
}
|
||||
|
||||
pub fn random_stat_adjust() -> StatBlock {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let total_adjusts: usize = (Poisson::new(2.0).unwrap().sample(&mut rng) as usize).min(7);
|
||||
let n_adjust_stats = rng.gen_range(1..=4);
|
||||
let adjust_stats = Stat::iter().choose_multiple(&mut rng, n_adjust_stats as usize);
|
||||
|
||||
let stat_up: Vec<Stat> = (&mut rng)
|
||||
.sample_iter(Standard)
|
||||
.filter(|stat| adjust_stats.contains(stat))
|
||||
.take(total_adjusts)
|
||||
.collect();
|
||||
|
||||
let stat_down: Vec<Stat> = rng
|
||||
.sample_iter::<Stat, Standard>(Standard)
|
||||
.filter(|stat| !adjust_stats.contains(stat))
|
||||
.take(total_adjusts)
|
||||
.collect();
|
||||
|
||||
stat_up.iter().fold(StatBlock::default(), |x, y| x + *y)
|
||||
- stat_down.iter().fold(StatBlock::default(), |x, y| x + *y)
|
||||
}
|
||||
|
||||
const NAMES: [&str; 20] = [
|
||||
"Garry", "Brad", "Rob", "Adrian", "Jeff", "Larry", "Chad", "Fred", "Jack", "Jerry", "Mitch",
|
||||
"Bob", "Billy", "Johnny", "Terry", "Berk", "Dave", "Steve", "Joe", "Bort",
|
||||
];
|
||||
|
||||
pub fn random_character() -> Character {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let class1 = rng.gen();
|
||||
|
||||
create_character(
|
||||
NAMES.choose(&mut rng).unwrap().clone().into(),
|
||||
class1,
|
||||
(&mut rng)
|
||||
.sample_iter(Standard)
|
||||
.filter(|c| *c != class1)
|
||||
.next()
|
||||
.unwrap(),
|
||||
random_stat_choice(),
|
||||
random_stat_adjust(),
|
||||
rng.gen(),
|
||||
rng.gen(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod random_character_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_choice() {
|
||||
for _i in 0..1000 {
|
||||
assert_eq!(Vec::from(random_stat_choice()).iter().sum::<i64>(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adjust() {
|
||||
for _i in 0..1000 {
|
||||
let adjust = Vec::from(random_stat_adjust());
|
||||
println!("{:?}", adjust);
|
||||
assert_eq!(adjust.iter().sum::<i64>(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_stats() {
|
||||
for _i in 0..1000 {
|
||||
assert_eq!(
|
||||
Vec::from(random_character().stat_block).iter().sum::<i64>(),
|
||||
16
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
use std::ops;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Proficiencies {
|
||||
// Armor
|
||||
pub light_armor: bool,
|
||||
pub medium_armor: bool,
|
||||
pub heavy_armor: bool,
|
||||
// Weapon
|
||||
pub simple_weapons: bool,
|
||||
pub bladed_weapons: bool,
|
||||
pub blunt_weapons: bool,
|
||||
pub pole_weapons: bool,
|
||||
pub thrown_weapons: bool,
|
||||
pub ranged_weapons: bool,
|
||||
}
|
||||
|
||||
pub trait ToProficiencies {
|
||||
fn proficiencies(&self) -> Proficiencies;
|
||||
}
|
||||
|
||||
impl ops::BitOr for Proficiencies {
|
||||
type Output = Proficiencies;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Proficiencies {
|
||||
light_armor: self.light_armor || rhs.light_armor,
|
||||
medium_armor: self.medium_armor || rhs.medium_armor,
|
||||
heavy_armor: self.heavy_armor || rhs.heavy_armor,
|
||||
simple_weapons: self.simple_weapons || rhs.simple_weapons,
|
||||
bladed_weapons: self.bladed_weapons || rhs.bladed_weapons,
|
||||
blunt_weapons: self.blunt_weapons || rhs.blunt_weapons,
|
||||
pole_weapons: self.pole_weapons || rhs.pole_weapons,
|
||||
thrown_weapons: self.thrown_weapons || rhs.thrown_weapons,
|
||||
ranged_weapons: self.ranged_weapons || rhs.ranged_weapons,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod skills_tests {
|
||||
use crate::{
|
||||
class::Class,
|
||||
skills::{Proficiencies, ToProficiencies},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn defaults() {
|
||||
assert_eq!(
|
||||
Proficiencies::default() | Proficiencies::default(),
|
||||
Proficiencies::default()
|
||||
);
|
||||
assert_eq!(
|
||||
Class::Brawler.proficiencies() | Proficiencies::default(),
|
||||
Class::Brawler.proficiencies()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combine() {
|
||||
assert_eq!(
|
||||
Proficiencies {
|
||||
light_armor: true,
|
||||
..Default::default()
|
||||
} | Proficiencies {
|
||||
light_armor: true,
|
||||
..Default::default()
|
||||
},
|
||||
Proficiencies {
|
||||
light_armor: true,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Proficiencies {
|
||||
light_armor: true,
|
||||
..Default::default()
|
||||
} | Proficiencies {
|
||||
heavy_armor: true,
|
||||
..Default::default()
|
||||
},
|
||||
Proficiencies {
|
||||
light_armor: true,
|
||||
heavy_armor: true,
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
use std::{
|
||||
convert,
|
||||
ops::{self, Index},
|
||||
};
|
||||
|
||||
use rand::{distributions::Standard, prelude::Distribution, seq::IteratorRandom};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
|
||||
pub enum Stat {
|
||||
STR,
|
||||
DEX,
|
||||
CON,
|
||||
STB,
|
||||
|
||||
INT,
|
||||
KND,
|
||||
CMP,
|
||||
CHA,
|
||||
}
|
||||
|
||||
impl ops::Add<Stat> for Stat {
|
||||
type Output = StatBlock;
|
||||
|
||||
fn add(self, rhs: Stat) -> Self::Output {
|
||||
StatBlock::from(self) + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Stat> for Standard {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Stat {
|
||||
Stat::iter().choose(rng).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
|
||||
pub struct StatBlock {
|
||||
// Physical
|
||||
pub STR: i64,
|
||||
pub DEX: i64,
|
||||
pub CON: i64,
|
||||
pub STB: i64,
|
||||
// Mental
|
||||
pub INT: i64,
|
||||
pub KND: i64,
|
||||
pub CMP: i64,
|
||||
pub CHA: i64,
|
||||
}
|
||||
|
||||
impl StatBlock {
|
||||
pub fn ones() -> StatBlock {
|
||||
StatBlock::from((1, 1, 1, 1, 1, 1, 1, 1))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToStatBlock {
|
||||
fn stat_block(&self) -> StatBlock;
|
||||
}
|
||||
|
||||
impl ops::Add<StatBlock> for StatBlock {
|
||||
type Output = StatBlock;
|
||||
|
||||
fn add(self, rhs: StatBlock) -> Self::Output {
|
||||
StatBlock {
|
||||
STR: self.STR + rhs.STR,
|
||||
DEX: self.DEX + rhs.DEX,
|
||||
CON: self.CON + rhs.CON,
|
||||
STB: self.STB + rhs.STB,
|
||||
INT: self.INT + rhs.INT,
|
||||
KND: self.KND + rhs.KND,
|
||||
CMP: self.CMP + rhs.CMP,
|
||||
CHA: self.CHA + rhs.CHA,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<StatBlock> for StatBlock {
|
||||
type Output = StatBlock;
|
||||
|
||||
fn sub(self, rhs: StatBlock) -> Self::Output {
|
||||
self + -1 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<StatBlock> for i64 {
|
||||
type Output = StatBlock;
|
||||
|
||||
fn mul(self, rhs: StatBlock) -> Self::Output {
|
||||
StatBlock {
|
||||
STR: self * rhs.STR,
|
||||
DEX: self * rhs.DEX,
|
||||
CON: self * rhs.CON,
|
||||
STB: self * rhs.STB,
|
||||
INT: self * rhs.INT,
|
||||
KND: self * rhs.KND,
|
||||
CMP: self * rhs.CMP,
|
||||
CHA: self * rhs.CHA,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Stat> for StatBlock {
|
||||
type Output = StatBlock;
|
||||
|
||||
fn add(self, rhs: Stat) -> Self::Output {
|
||||
self + StatBlock::from(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<(i64, i64, i64, i64, i64, i64, i64, i64)> for StatBlock {
|
||||
#[allow(non_snake_case)]
|
||||
fn from(
|
||||
(STR, DEX, CON, STB, INT, KND, CMP, CHA): (i64, i64, i64, i64, i64, i64, i64, i64),
|
||||
) -> Self {
|
||||
StatBlock {
|
||||
STR,
|
||||
DEX,
|
||||
CON,
|
||||
STB,
|
||||
INT,
|
||||
KND,
|
||||
CMP,
|
||||
CHA,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Stat> for StatBlock {
|
||||
fn from(value: Stat) -> Self {
|
||||
match value {
|
||||
Stat::STR => StatBlock::from((1, 0, 0, 0, 0, 0, 0, 0)),
|
||||
Stat::DEX => StatBlock::from((0, 1, 0, 0, 0, 0, 0, 0)),
|
||||
Stat::CON => StatBlock::from((0, 0, 1, 0, 0, 0, 0, 0)),
|
||||
Stat::STB => StatBlock::from((0, 0, 0, 1, 0, 0, 0, 0)),
|
||||
Stat::INT => StatBlock::from((0, 0, 0, 0, 1, 0, 0, 0)),
|
||||
Stat::KND => StatBlock::from((0, 0, 0, 0, 0, 1, 0, 0)),
|
||||
Stat::CMP => StatBlock::from((0, 0, 0, 0, 0, 0, 1, 0)),
|
||||
Stat::CHA => StatBlock::from((0, 0, 0, 0, 0, 0, 0, 1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Stat> for StatBlock {
|
||||
type Output = i64;
|
||||
|
||||
fn index(&self, index: Stat) -> &Self::Output {
|
||||
match index {
|
||||
Stat::STR => &self.STR,
|
||||
Stat::DEX => &self.DEX,
|
||||
Stat::CON => &self.CON,
|
||||
Stat::STB => &self.STB,
|
||||
Stat::INT => &self.INT,
|
||||
Stat::KND => &self.KND,
|
||||
Stat::CMP => &self.CMP,
|
||||
Stat::CHA => &self.CHA,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<StatBlock> for Vec<i64> {
|
||||
fn from(value: StatBlock) -> Self {
|
||||
vec![
|
||||
value.STR, value.DEX, value.CON, value.STB, value.INT, value.KND, value.CMP, value.CHA,
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue