diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 987357bf..3e01bdfc 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -234,7 +234,8 @@ pub struct ItemSearchParams<'l> { pub include_contents: bool, pub include_loc_contents: bool, pub include_active_players: bool, - pub include_all_players: bool + pub include_all_players: bool, + pub dead_first: bool, } impl ItemSearchParams<'_> { @@ -244,7 +245,8 @@ impl ItemSearchParams<'_> { include_contents: false, include_loc_contents: false, include_active_players: false, - include_all_players: false + include_all_players: false, + dead_first: false, } } } diff --git a/blastmud_game/src/language.rs b/blastmud_game/src/language.rs index 547a8637..79dcc124 100644 --- a/blastmud_game/src/language.rs +++ b/blastmud_game/src/language.rs @@ -54,6 +54,60 @@ pub fn pluralise(input: &str) -> String { input.to_owned() + "s" } +pub fn indefinite_article(countable_word: &str) -> &'static str { + if countable_word.is_empty() { + return ""; + } + let vowels = ["a", "e", "i", "o", "u"]; + if !vowels.contains(&&countable_word[0..1]) { + if countable_word.starts_with("honor") || countable_word.starts_with("honour") || + countable_word.starts_with("honest") || countable_word.starts_with("hour") || + countable_word.starts_with("heir") { + return "an"; + } + return "a"; + } + if countable_word.starts_with("eu") || countable_word.starts_with("one") || + countable_word.starts_with("once") { + return "a"; + } + if countable_word.starts_with("e") { + if countable_word.starts_with("ewe") { + return "a"; + } + return "an"; + } + if countable_word.starts_with("u") { + if countable_word.len() < 3 { + return "an"; + } + if countable_word.starts_with("uni") { + if countable_word.starts_with("unid") || + countable_word.starts_with("unim") || + countable_word.starts_with("unin") { + // unidentified, unimaginable, uninhabited etc... + return "an"; + } + // Words like unilateral + return "a"; + } + if countable_word.starts_with("unani") || countable_word.starts_with("ubiq") { + return "a"; + } + if ["r", "s", "t"].contains(&&countable_word[1..2]) { + if vowels.contains(&&countable_word[2..3]) { + // All u[rst][aeiou] words, e.g. usury, need "a" + return "a"; + } + return "an"; + } + if countable_word.starts_with("ubiq") || countable_word.starts_with("uku") || countable_word.starts_with("ukr") { + return "a" + } + } + return "an"; +} + pub fn caps_first(inp: &str) -> String { if inp.is_empty() { "".to_string() @@ -102,6 +156,36 @@ mod test { } } + #[test] + fn indefinite_article_should_follow_english_rules() { + for (article, word) in vec!( + ("a", "cat"), + ("a", "human"), + ("an", "apple"), + ("an", "easter egg"), + ("an", "indigo orb"), + ("an", "orange"), + ("an", "urchin"), + ("an", "hour"), + ("a", "once-in-a-lifetime opportunity"), + ("a", "uranium-covered field"), + ("a", "usurper to the throne"), + ("a", "Ukrainian hero"), + ("a", "universal truth"), + ("an", "uninvited guest"), + ("a", "unanimous decision"), + ("a", "European getaway"), + ("an", "utter disaster"), + ("a", "uterus"), + ("a", "user"), + ("a", "ubiquitous hazard"), + ("a", "unitary plan"), + ) { + let result = super::indefinite_article(&word.to_lowercase()); + assert_eq!(format!("{} {}", result, word), format!("{} {}", article, word)); + } + } + #[test] fn caps_first_works() { for (inp, outp) in vec!( diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index d6c2348e..160d13f2 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -254,6 +254,7 @@ pub async fn search_item_for_user<'l>(ctx: &'l VerbContext<'l>, search: &'l Item trans: &trans, session_dat: &mut session_dat, user_dat: &mut user_dat }; - resolve_handler(&ctx, "less_explicit_mode"); + assert_eq!(resolve_handler(&ctx, "less_explicit_mode").is_some(), + true); } } diff --git a/blastmud_game/src/message_handler/user_commands/list.rs b/blastmud_game/src/message_handler/user_commands/list.rs index 493413fd..7f162058 100644 --- a/blastmud_game/src/message_handler/user_commands/list.rs +++ b/blastmud_game/src/message_handler/user_commands/list.rs @@ -3,6 +3,7 @@ use super::{VerbContext, UserVerb, UserVerbRef, UResult, user_error, use crate::{ static_content::room, static_content::possession_type::possession_data, + language }; use async_trait::async_trait; use ansi::ansi; @@ -38,7 +39,8 @@ impl UserVerb for Verb { let display = if ctx.session_dat.less_explicit_mode { possession_type.display_less_explicit.as_ref().unwrap_or(&possession_type.display) } else { &possession_type.display }; - msg.push_str(&format!("| {:20} | {:15.2} |\n", display, &stock.list_price)) + msg.push_str(&format!("| {:20} | {:15.2} |\n", + &language::caps_first(&display), &stock.list_price)) } } msg.push_str(ansi!("\nUse buy item to purchase something.\n")); diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index cc428c8a..76aa236b 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -314,14 +314,15 @@ impl Item { buf.push_str("the body of "); } } + let singular = if explicit_ok { &self.display } else { + self.display_less_explicit.as_ref().unwrap_or(&self.display) }; if !self.pronouns.is_proper && pluralise == 1 { - buf.push_str("a "); + buf.push_str(language::indefinite_article(&singular)); + buf.push(' '); } if pluralise > 1 { buf.push_str(&format!("{} ", pluralise)); } - let singular = if explicit_ok { &self.display } else { - self.display_less_explicit.as_ref().unwrap_or(&self.display) }; if pluralise > 1 { buf.push_str(&language::pluralise(singular)); } else { diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index b0013f01..02033884 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -1,6 +1,6 @@ use serde::{Serialize, Deserialize}; use crate::{ - models::item::{SkillType, Item} + models::item::{SkillType, Item, Pronouns} }; use once_cell::sync::OnceCell; use std::collections::BTreeMap; @@ -123,6 +123,10 @@ impl Into for PossessionType { aliases: possession_dat.aliases.iter().map(|al| (*al).to_owned()).collect(), health: possession_dat.max_health, weight: possession_dat.weight, + pronouns: Pronouns { + is_proper: false, + ..Pronouns::default_inanimate() + }, ..Default::default() } } @@ -191,7 +195,7 @@ pub fn possession_data() -> &'static BTreeMap { ..Default::default() }), (AntennaWhip, PossessionData { - display: "Antenna whip", + display: "antenna whip", details: "A crudely fashioned whip made from a broken metal antenna. It looks a bit flimsy, but it \ might do you until you get a better weapon!", aliases: vec!("whip"),