summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Gay <eapoems@riseup.net>2024-01-31 03:14:53 -0500
committerDavid Gay <eapoems@riseup.net>2024-01-31 03:14:53 -0500
commite63dbbde68f81f8a65b48608d41ef6bf6be799e6 (patch)
treeb6c9a200278974d6bc70ccdb9e3960b9e871344f
parent05a13c4c3232f10ade9a2cc75de84e93278d22c6 (diff)
`random npc` accepts level argumentHEADmaster
-rw-r--r--CHANGELOG.md14
-rw-r--r--src/cli.rs8
-rw-r--r--src/main.rs31
-rw-r--r--src/rules/npcs.rs13
4 files changed, 56 insertions, 10 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7087ec0..76f9946 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file.
+## [0.0.3] - Unreleased
+
+### Added
+
+- New `random npc` command can check for which magic items the NPC has based on
+ their `--class` and `--level`. (This uses my preferred method, the Appendix C
+ city encounters table.)
+- NPCs now have a level. (Multiclass and dualclass support is be a future
+ feature.)
+
+### Changed
+
+- Alignments now always output as abbreviations.
+
## [0.0.2] - 2023-10-16
### Added
diff --git a/src/cli.rs b/src/cli.rs
index bc6c3a8..0c58461 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -33,6 +33,14 @@ pub fn cli() -> Command {
.short('c')
.long("class")
.help("The class of the NPC, e.g. fighter"),
+ )
+ .arg(
+ Arg::new("level")
+ .short('l')
+ .long("level")
+ .help("The level of the NPC, e.g 18")
+ .default_value("1")
+ .value_parser(clap::value_parser!(i32)),
),
),
)
diff --git a/src/main.rs b/src/main.rs
index 44c162c..85c90fe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,10 +27,19 @@ fn main() {
std::process::exit(1);
});
+ let level = npc_matches
+ .get_one::<i32>("level")
+ .unwrap_or_else(|| {
+ eprintln!("Error: Couldn't parse level.");
+ std::process::exit(1);
+ })
+ .clone();
+
let mut npc = Npc::new(
Some(RANDOM_TABLES.roll_table("npc_alignment")),
None,
Some(class_ref),
+ level,
None,
None,
Vec::new(),
@@ -39,9 +48,14 @@ fn main() {
npc.add_random_magic_items();
println!(
- "{} {}. {}",
- npc.alignment.unwrap(),
+ "{} {} {}. {}",
+ npc.alignment
+ .unwrap()
+ .split_whitespace()
+ .map(|word| word.chars().next().unwrap())
+ .collect::<String>(),
npc.class.unwrap().name,
+ npc.level,
npc.magic_items
.iter()
.map(|item| item.name.clone())
@@ -70,6 +84,7 @@ fn main() {
Some(RANDOM_TABLES.roll_table("npc_alignment")),
Some(race_ref),
Some(class_ref),
+ 1,
None,
None,
Vec::new(),
@@ -86,7 +101,7 @@ fn main() {
// string literal at compile time.
if output_csv {
println!(
- "{},{},{},{},{},{},{},{},{},\"{}\"",
+ "{},{},{},{},{},{},{},{},{},{},\"{}\"",
npc.alignment
.unwrap()
.split_whitespace()
@@ -94,6 +109,7 @@ fn main() {
.collect::<String>(),
npc.race.unwrap().name,
npc.class.unwrap().name,
+ npc.level,
ability_scores.get_score(AbilityScore::Strength).unwrap(),
ability_scores
.get_score(AbilityScore::Intelligence)
@@ -108,10 +124,15 @@ fn main() {
);
} else {
println!(
- "{} {} {}. STR {}, INT {}, WIS {}, CON {}, DEX {}, CHA {}. {}",
- npc.alignment.unwrap(),
+ "{} {} {} {}. STR {}, INT {}, WIS {}, CON {}, DEX {}, CHA {}. {}",
+ npc.alignment
+ .unwrap()
+ .split_whitespace()
+ .map(|word| word.chars().next().unwrap())
+ .collect::<String>(),
npc.race.unwrap().name,
npc.class.unwrap().name,
+ npc.level,
ability_scores.get_score(AbilityScore::Strength).unwrap(),
ability_scores
.get_score(AbilityScore::Intelligence)
diff --git a/src/rules/npcs.rs b/src/rules/npcs.rs
index fe87e76..5477306 100644
--- a/src/rules/npcs.rs
+++ b/src/rules/npcs.rs
@@ -13,6 +13,7 @@ pub struct Npc {
pub alignment: Option<String>,
pub race: Option<&'static Race>,
pub class: Option<&'static Class>,
+ pub level: i32,
pub ability_scores: Option<AbilityScoreCollection>,
pub persona: Option<String>,
pub magic_items: Vec<&'static MagicItem>,
@@ -23,6 +24,7 @@ impl Npc {
alignment: Option<String>,
race: Option<&'static Race>,
class: Option<&'static Class>,
+ level: i32, // TODO: Multiclass and dualclass.
ability_scores: Option<AbilityScoreCollection>,
persona: Option<String>,
magic_items: Vec<&'static MagicItem>,
@@ -31,6 +33,7 @@ impl Npc {
alignment,
race,
class,
+ level,
ability_scores,
persona,
magic_items,
@@ -120,7 +123,6 @@ impl Npc {
// This uses the Appendix C method provided in the city/town section.
// I prefer it to the Monster Manual method and the NPC party method
// because it provides more variance.
- // TODO: Support other levels than 1st.
pub fn add_random_magic_items(&mut self) {
let mut rng = rand::thread_rng();
let class_ref = self.class.unwrap();
@@ -139,13 +141,13 @@ impl Npc {
];
for &kind_string in kind_strings_for_chances.iter() {
- if class_ref
+ let chance_per_level = class_ref
.chances_for_magic
.get(kind_string)
.copied()
- .unwrap_or(0)
- >= rng.gen_range(1..=100)
- {
+ .unwrap_or(0);
+ let chance = chance_per_level * self.level;
+ if chance >= rng.gen_range(1..=100) {
// For now, just get a dummy item named after the item type.
// Later, we'll add the actual magic items.
self.magic_items.push(
@@ -202,6 +204,7 @@ mod tests {
alignment: Some(String::from("Lawful Good")),
race: Some(race_ref),
class: Some(class_ref),
+ level: 1,
ability_scores: None,
persona: None,
magic_items: Vec::new(),