use super::{effect::EffectType, session::Session}; use crate::{ language, regular_tasks::queued_command::QueueCommand, static_content::{ fixed_item::fixed_item_properties, possession_type::{possession_data, EatData, PossessionData, PossessionType}, room::Direction, species::SpeciesType, }, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::collections::VecDeque; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd)] pub enum BuffCause { WaitingTask { task_code: String, task_type: String, }, ByItem { item_code: String, item_type: String, }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub enum BuffImpact { ChangeStat { stat: StatType, magnitude: f64 }, ChangeSkill { skill: SkillType, magnitude: f64 }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub struct Buff { pub description: String, pub cause: BuffCause, pub impacts: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum SkillType { Appraise, Blades, Bombs, Chemistry, Climb, Clubs, Craft, Dodge, Fish, Fists, Flails, Focus, Hack, Locksmith, Medic, Persuade, Pilot, Pistols, Quickdraw, Repair, Ride, Rifles, Scavenge, Science, Share, Sneak, Spears, Swim, Teach, Throw, Track, Wrestle, Whips, } impl SkillType { pub fn values() -> Vec { use SkillType::*; vec![ Appraise, Blades, Bombs, Chemistry, Climb, Clubs, Craft, Dodge, Fish, Fists, Flails, Focus, Hack, Locksmith, Medic, Persuade, Pilot, Pistols, Quickdraw, Repair, Ride, Rifles, Scavenge, Science, Share, Sneak, Spears, Swim, Teach, Throw, Track, Wrestle, Whips, ] } pub fn display(&self) -> &'static str { use SkillType::*; match self { Appraise => "appraise", Blades => "blades", Bombs => "bombs", Chemistry => "chemistry", Climb => "climb", Clubs => "clubs", Craft => "craft", Dodge => "dodge", Fish => "fish", Fists => "fists", Flails => "flails", Focus => "focus", Hack => "hack", Locksmith => "locksmith", Medic => "medic", Persuade => "persuade", Pilot => "pilot", Pistols => "pistols", Quickdraw => "quickdraw", Repair => "repair", Ride => "ride", Rifles => "rifles", Scavenge => "scavenge", Science => "science", Share => "share", Sneak => "sneak", Spears => "spears", Swim => "swim", Teach => "teach", Throw => "throw", Track => "track", Wrestle => "wrestle", Whips => "whips", } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum StatType { Brains, Senses, Brawn, Reflexes, Endurance, Cool, } impl StatType { pub fn values() -> Vec { use StatType::*; vec![Brains, Senses, Brawn, Reflexes, Endurance, Cool] } pub fn display(&self) -> &'static str { use StatType::*; match self { Brains => "brains", Senses => "senses", Brawn => "brawn", Reflexes => "reflexes", Endurance => "endurance", Cool => "cool", } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum LiquidType { Water, } impl LiquidType { pub fn drink_data(&self) -> Option { match self { LiquidType::Water => Some(EatData { hunger_impact: 0, thirst_impact: -20, // 0.2% per mL }), } } pub fn display(&self) -> &str { match self { LiquidType::Water => "water", } } pub fn density(&self) -> f64 { match self { LiquidType::Water => 1.0, // g / mL. } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct LiquidDetails { // In mLs... pub contents: BTreeMap, } impl Default for LiquidDetails { fn default() -> Self { Self { contents: BTreeMap::::new(), } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct Pronouns { pub subject: String, pub object: String, pub intensive: String, pub possessive: String, // And some miscellaneous details to determine context pub is_plural: bool, // ... are instead of ... is pub is_proper: bool, // When naming, just ... instead of The ... } impl Pronouns { pub fn default_inanimate() -> Pronouns { Pronouns { subject: "it".to_owned(), object: "it".to_owned(), intensive: "itself".to_owned(), possessive: "its".to_owned(), is_plural: false, is_proper: true, } } pub fn default_animate() -> Pronouns { Pronouns { subject: "they".to_owned(), object: "them".to_owned(), intensive: "themselves".to_owned(), possessive: "their".to_owned(), is_plural: true, is_proper: true, } } #[allow(dead_code)] pub fn default_male() -> Pronouns { Pronouns { subject: "he".to_owned(), object: "him".to_owned(), intensive: "himself".to_owned(), possessive: "his".to_owned(), is_plural: false, is_proper: true, } } #[allow(dead_code)] pub fn default_female() -> Pronouns { Pronouns { subject: "she".to_owned(), object: "her".to_owned(), intensive: "herself".to_owned(), possessive: "her".to_owned(), is_plural: false, is_proper: true, } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Subattack { Normal, Powerattacking, Feinting, Grabbing, Wrestling, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Scavtype { Scavenge, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum LocationActionType { Normal, Sitting(Option), Reclining(Option), Worn, // Clothing etc... Wielded, Attacking(Subattack), InstalledOnDoorAsLock(Direction), Scavhidden { difficulty: u64, scavtype: Scavtype }, } impl LocationActionType { pub fn is_visible_in_look(&self) -> bool { use LocationActionType::*; match self { InstalledOnDoorAsLock(_) => false, Scavhidden { .. } => false, _ => true, } } pub fn is_in_direction(&self, dir: &Direction) -> bool { use LocationActionType::*; match self { InstalledOnDoorAsLock(d) if d == dir => true, _ => false, } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Sex { Male, Female, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ItemFlag { NoSay, NoSeeContents, DroppedItemsDontExpire, PrivatePlace, Hireable, NPCsDontAttack, CanLoad, Bench, Book, Instructions, HasUrges, NoUrgesHere, DontListInLook, AllowShare, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum AttackMode { NORMAL, POWER, FEINT, } impl Default for AttackMode { fn default() -> Self { AttackMode::NORMAL } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(default)] pub struct ActiveCombat { pub attacking: Option, pub attacked_by: Vec, pub attack_mode: AttackMode, } impl Default for ActiveCombat { fn default() -> Self { Self { attacking: None, attacked_by: vec![], attack_mode: Default::default(), } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(default)] pub struct ActiveClimb { pub height: u64, } impl Default for ActiveClimb { fn default() -> Self { Self { height: 0 } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ConversationalInterestType { Philosophy, LocalGeography, Threats, Tactics, Weather, Politics, Frivolity, } impl ConversationalInterestType { pub fn display(&self) -> &'static str { use ConversationalInterestType::*; match self { Philosophy => "philosophy", LocalGeography => "local geography", Threats => "threats", Tactics => "tactics", Weather => "weather", Politics => "politics", Frivolity => "frivolity", } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ConversationalStyle { Joking, Serious, Amicable, } impl ConversationalStyle { pub fn display(&self) -> &str { match self { ConversationalStyle::Amicable => "amicable", ConversationalStyle::Serious => "serious", ConversationalStyle::Joking => "joking", } } pub fn transitions(&self) -> Vec { use ConversationalStyle::*; vec![Amicable, Serious, Joking] .into_iter() .filter(|v| v != self) .collect() } pub fn from_name(n: &str) -> Option { use ConversationalStyle::*; match n { "amicable" => Some(Amicable), "serious" => Some(Serious), "joking" => Some(Joking), _ => None, } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ConversationTopic { ParodyKingsOffice, PlayFight, ThoughtsOnSunTzu, ThoughtsOnMachiavelli, ExploringRuins, RoamingEnemies, FishingSpots, GoodAmbushSpots, SurvivingWeather, } impl ConversationTopic { pub fn display_command(&self) -> &'static str { use ConversationTopic::*; match self { ParodyKingsOffice => "parody kings office", PlayFight => "play fight", ThoughtsOnSunTzu => "thoughts on sun tzu", ThoughtsOnMachiavelli => "thoughts on machiavelli", ExploringRuins => "exploring ruins", RoamingEnemies => "roaming enemies", FishingSpots => "fishing spots", GoodAmbushSpots => "good ambush spots", SurvivingWeather => "surviving weather", } } pub fn display_readable(&self) -> &'static str { use ConversationTopic::*; match self { ParodyKingsOffice => "parodying the kings office", PlayFight => "proposing a play fight", ThoughtsOnSunTzu => "sharing thoughts on Sun Tzu", ThoughtsOnMachiavelli => "sharing thoughts on Machiavelli", ExploringRuins => "comparing notes on exploring ruins", RoamingEnemies => "complaining about roaming enemies", FishingSpots => "sharing the best fishing spots", GoodAmbushSpots => "discussing good ambush spots", SurvivingWeather => "describing how to survive weather", } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum ConversationIntensity { Slow, Normal, Fast, } impl ConversationIntensity { pub fn to_command(&self) -> &'static str { match self { Self::Slow => "share slowly", Self::Normal => "share normally", Self::Fast => "share quickly", } } pub fn display_readable(&self) -> &'static str { match self { Self::Slow => "slowly", Self::Normal => "normally", Self::Fast => "quickly", } } pub fn from_adverb(input: &str) -> Option { let input = input.to_lowercase(); if input == "slowly" { Some(ConversationIntensity::Slow) } else if input == "normally" { Some(ConversationIntensity::Normal) } else if input == "quickly" { Some(ConversationIntensity::Fast) } else { None } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(default)] pub struct ActiveConversation { pub interest_levels: BTreeMap, pub partner_ref: String, pub style: ConversationalStyle, pub current_topic: ConversationTopic, pub current_intensity: ConversationIntensity, pub peak_total_interest: u64, pub last_change: DateTime, } impl Default for ActiveConversation { fn default() -> Self { Self { interest_levels: BTreeMap::new(), partner_ref: "unset".to_owned(), style: ConversationalStyle::Serious, current_topic: ConversationTopic::RoamingEnemies, current_intensity: ConversationIntensity::Normal, peak_total_interest: 0, last_change: DateTime::UNIX_EPOCH, } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(default)] pub struct Urge { pub last_value: u16, pub value: u16, // In hundreths of a percent (0-10000). pub growth: i16, } impl Default for Urge { fn default() -> Self { Self { last_value: 0, value: 0, growth: 42, // About 4 hours of once a minute ticks to hit 10k. } } } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(default)] pub struct Urges { pub hunger: Urge, pub thirst: Urge, pub stress: Urge, } impl Default for Urges { fn default() -> Self { Self { hunger: Default::default(), thirst: Default::default(), stress: Default::default(), } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub enum ItemSpecialData { ItemWriting { text: String, }, DynroomData { dynzone_code: String, dynroom_code: String, }, DynzoneData { zone_exit: Option, vacate_after: Option>, }, HireData { hired_by: Option, }, HanoiPuzzle { towers: [Vec; 3], }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub struct DynamicEntrance { pub direction: Direction, pub source_item: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct DoorState { pub open: bool, pub description: String, } impl Default for DoorState { fn default() -> Self { Self { open: false, description: "a solid looking wooden door".to_owned(), } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct DeathData { pub parts_remaining: Vec, } impl Default for DeathData { fn default() -> Self { Self { parts_remaining: vec![], } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub enum FollowState { // Every move is mirrored to the follower. Active, // If the followee is in the same room, mirror a movement and go to Active, // otherwise ignore and don't mirror. This happens after a mirrored move fails, // or the follower moves independently. IfSameRoom, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] pub struct FollowData { pub follow_whom: String, pub state: FollowState, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct TacticUse { pub last_pow: Option>, pub last_feint: Option>, } impl Default for TacticUse { fn default() -> Self { Self { last_pow: None, last_feint: None, } } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[serde(default)] pub struct Item { pub action_type: LocationActionType, pub action_type_started: Option>, pub active_climb: Option, pub active_combat: Option, pub active_effects: Vec<(EffectType, i64)>, pub active_conversation: Option, pub aliases: Vec, pub charges: u8, pub death_data: Option, pub details: Option, pub details_dyn_suffix: Option, pub details_less_explicit: Option, pub display: String, pub display_less_explicit: Option, pub door_states: Option>, pub dynamic_entrance: Option, pub flags: Vec, pub following: Option, pub health: u64, pub is_static: bool, pub item_code: String, pub item_type: String, pub liquid_details: Option, pub location: String, // Item reference as item_type/item_code. pub owner: Option, pub possession_type: Option, pub presence_target: Option, // e.g. what are they sitting on. pub pronouns: Pronouns, pub queue: VecDeque, pub sex: Option, pub special_data: Option, pub species: SpeciesType, pub tactic_use: TacticUse, pub temporary_buffs: Vec, pub total_skills: BTreeMap, pub total_stats: BTreeMap, pub total_xp: u64, pub urges: Option, pub weight: u64, } impl Item { pub fn display_for_sentence(&self, explicit_ok: bool, pluralise: usize, caps: bool) -> String { let mut buf = String::new(); if self.death_data.is_some() { if pluralise > 1 { buf.push_str("the bodies of "); } else { 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(language::indefinite_article(&singular)); buf.push(' '); } if pluralise > 1 { buf.push_str(&format!("{} ", pluralise)); } if pluralise > 1 { buf.push_str(&language::pluralise(singular)); } else { buf.push_str(singular); } if caps { language::caps_first(&buf) } else { buf } } pub fn display_for_session<'l>(self: &'l Self, _session: &Session) -> String { self.display_for_sentence(true, 1, false) } pub fn details_for_session<'l>(self: &'l Self, session: &Session) -> Option<&'l str> { self.details.as_ref().map(|dets| { session.explicit_if_allowed( dets.as_str(), self.details_less_explicit.as_ref().map(String::as_str), ) }) } pub fn refstr(&self) -> String { format!("{}/{}", &self.item_type, &self.item_code) } pub fn max_carry(&self) -> u64 { if self.item_type == "possession" { if let Some(container_data) = self.possession_type.as_ref().and_then(|pt| { possession_data() .get(pt) .and_then(|pd| pd.container_data.as_ref()) }) { container_data.max_weight } else { 0 } } else { (50.0 * 2.0_f64.powf(*self.total_stats.get(&StatType::Brawn).unwrap_or(&0.0))).ceil() as u64 } } pub fn static_data(&self) -> Option<&'static PossessionData> { if self.item_type == "possession" { self.possession_type .as_ref() .and_then(|pt| possession_data().get(pt)) .copied() } else if self.item_type == "fixed_item" { fixed_item_properties().get(self.item_code.as_str()) } else { None } } } impl Default for Item { fn default() -> Self { Self { action_type: LocationActionType::Normal, action_type_started: None, active_climb: None, active_combat: Some(Default::default()), active_effects: vec![], active_conversation: None, aliases: vec![], charges: 0, death_data: None, details: None, details_dyn_suffix: None, details_less_explicit: None, display: "Item".to_owned(), display_less_explicit: None, door_states: None, dynamic_entrance: None, flags: vec![], following: None, health: 24, is_static: false, item_code: "unset".to_owned(), item_type: "unset".to_owned(), liquid_details: None, location: "room/storage".to_owned(), owner: None, possession_type: None, presence_target: None, pronouns: Pronouns::default_inanimate(), queue: VecDeque::new(), sex: None, special_data: None, species: SpeciesType::Human, tactic_use: Default::default(), temporary_buffs: Vec::new(), total_skills: BTreeMap::new(), total_stats: BTreeMap::new(), total_xp: 0, urges: None, weight: 0, } } }