Add a mini-game around sharing knowledge
It still needs to apply buffs at the end and a few other details!
This commit is contained in:
parent
706825be20
commit
0c280711e8
@ -281,6 +281,14 @@ pub fn join_words(words: &[&str]) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join_words_or(words: &[&str]) -> String {
|
||||
match words.split_last() {
|
||||
None => "".to_string(),
|
||||
Some((last, [])) => last.to_string(),
|
||||
Some((last, rest)) => rest.join(", ") + " or " + last,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weight(grams: u64) -> String {
|
||||
if grams > 999 {
|
||||
format!(
|
||||
@ -380,6 +388,19 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_words_or_works() {
|
||||
for (inp, outp) in vec![
|
||||
(vec![], ""),
|
||||
(vec!["cat"], "cat"),
|
||||
(vec!["cat", "dog"], "cat or dog"),
|
||||
(vec!["cat", "dog", "fish"], "cat, dog or fish"),
|
||||
(vec!["wolf", "cat", "dog", "fish"], "wolf, cat, dog or fish"),
|
||||
] {
|
||||
assert_eq!(super::join_words_or(&inp[..]), outp);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_works() {
|
||||
assert_eq!(super::weight(100), "100 g");
|
||||
|
@ -68,6 +68,7 @@ mod reset_spawns;
|
||||
pub mod say;
|
||||
pub mod scavenge;
|
||||
mod score;
|
||||
mod share;
|
||||
mod sign;
|
||||
pub mod sit;
|
||||
mod staff_show;
|
||||
@ -241,6 +242,22 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"sc" => score::VERB,
|
||||
"score" => score::VERB,
|
||||
|
||||
"share" => share::VERB,
|
||||
"serious" => share::VERB,
|
||||
"amicable" => share::VERB,
|
||||
"joking" => share::VERB,
|
||||
"parody" => share::VERB,
|
||||
"play" => share::VERB,
|
||||
"thoughts" => share::VERB,
|
||||
"exploring" => share::VERB,
|
||||
"roaming" => share::VERB,
|
||||
"fishing" => share::VERB,
|
||||
"good" => share::VERB,
|
||||
"surviving" => share::VERB,
|
||||
"slow" => share::VERB,
|
||||
"normal" => share::VERB,
|
||||
"intense" => share::VERB,
|
||||
|
||||
"sign" => sign::VERB,
|
||||
"sit" => sit::VERB,
|
||||
|
||||
|
@ -373,8 +373,8 @@ mod tests {
|
||||
"Bar",
|
||||
"her",
|
||||
"from bar",
|
||||
&ConsentType::Sex,
|
||||
&ConsentDetails::default_for(&ConsentType::Sex),
|
||||
&ConsentType::Share,
|
||||
&ConsentDetails::default_for(&ConsentType::Share),
|
||||
&Some(Consent::default()),
|
||||
&None,
|
||||
false,
|
||||
@ -527,8 +527,8 @@ mod tests {
|
||||
"Bar",
|
||||
"her",
|
||||
"from bar",
|
||||
&ConsentType::Sex,
|
||||
&ConsentDetails::default_for(&ConsentType::Sex),
|
||||
&ConsentType::Share,
|
||||
&ConsentDetails::default_for(&ConsentType::Share),
|
||||
&None,
|
||||
&None,
|
||||
true,
|
||||
@ -820,12 +820,10 @@ async fn handle_user_consent(
|
||||
.await?
|
||||
{
|
||||
None => {}
|
||||
Some((session, session_dat)) => {
|
||||
if cmd.consent_type != ConsentType::Sex || !session_dat.less_explicit_mode {
|
||||
ctx.trans
|
||||
.queue_for_session(&session, Some(&(msg + "\n")))
|
||||
.await?;
|
||||
}
|
||||
Some((session, _session_dat)) => {
|
||||
ctx.trans
|
||||
.queue_for_session(&session, Some(&(msg + "\n")))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1030,8 +1028,7 @@ impl UserVerb for Verb {
|
||||
if remaining_trim == "" {
|
||||
handle_list_allows(ctx, &player_item).await?;
|
||||
} else {
|
||||
let mut cmd = match parsing::parse_allow(remaining, !ctx.session_dat.less_explicit_mode)
|
||||
{
|
||||
let mut cmd = match parsing::parse_allow(remaining) {
|
||||
Err(msg) => user_error(msg)?,
|
||||
Ok(cmd) => cmd,
|
||||
};
|
||||
|
@ -32,12 +32,6 @@ fn unregistered_help_pages() -> &'static BTreeMap<String, String> {
|
||||
CELL.get_or_init(|| load_help_yaml(include_str!("help/unregistered.yaml")))
|
||||
}
|
||||
|
||||
fn explicit_help_pages() -> &'static BTreeMap<String, String> {
|
||||
static CELL: OnceCell<BTreeMap<String, String>> = OnceCell::new();
|
||||
|
||||
CELL.get_or_init(|| load_help_yaml(include_str!("help/explicit.yaml")))
|
||||
}
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
@ -57,9 +51,6 @@ impl UserVerb for Verb {
|
||||
help = help.or_else(|| unregistered_help_pages().get(remaining));
|
||||
} else {
|
||||
help = help.or_else(|| registered_help_pages().get(remaining));
|
||||
if !ctx.session_dat.less_explicit_mode {
|
||||
help = explicit_help_pages().get(remaining).or(help);
|
||||
}
|
||||
}
|
||||
help = help.or_else(|| always_help_pages().get(remaining).map(|v| v));
|
||||
let help_final = help.ok_or(UserError("No help available on that".to_string()))?;
|
||||
@ -90,9 +81,4 @@ mod tests {
|
||||
fn always_help_ok() {
|
||||
always_help_pages();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_help_ok() {
|
||||
explicit_help_pages();
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
fuck: "Type <bold>fuck <lt>name><reset> to fuck someone. It only works if they have consented."
|
||||
allow: |-
|
||||
<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in Blastmud let you choose how you want to play with other players (it only affects players, not NPCs). There are 5 types of consent: <bold>fight<reset> (for hostile actions like attack or pick), <bold>medicine<reset> (for medical actions, including those that might crit fail and do harm), <bold>gifts<reset> (lets them give you things), <bold>visit<reset> (lets them on to a tile owned by you legally), and <bold>fuck<reset> (lets them do fun but dirty things to you).
|
||||
|
||||
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options
|
||||
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options
|
||||
Options can be blank to use defaults, or can be one or more of the following, separated by spaces:
|
||||
<bold>until <reset> n <bold>minutes<reset> - replace n with a number. You can use hours, days, or weeks instead of minutes. This makes the consent expire. Fight expires after a week if you don't give a shorter period, and all other consent types have no expiry unless you specify one.
|
||||
<bold>until death<reset> - makes the consent valid only until you next die.
|
||||
<bold>allow private<reset> - makes the consent valid even in privately owned places. This is the default for anything except fight.
|
||||
<bold>disallow private<reset> - the opposite of allow private.
|
||||
<bold>in<reset> place - limits where fighting can happen to selected public places. You can use <bold>here<reset>, or if you know the code, a place name. You can use this option more than once to allow any place, and if you don't use the option, it means anywhere (subject to allow private).
|
||||
<bold>allow pick<reset> - fight only - include picking in your consent.
|
||||
<bold>allow revoke<reset> - fight only - allows the player to revoke any time with disallow.
|
||||
|
||||
Consents for anything except fight take effect immediately to let the other player do the action.
|
||||
Consents for fight take effect when the other player executes a reciprocal allow command.
|
||||
Consents for anything except than fight can be revoked instantly with:
|
||||
<bold>disallow<reset> action <bold>from<reset> player
|
||||
Consent for fight can be revoked similarly if the consent used the <bold>allow revoke<reset> option. Otherwise, attempting to revoke informs the other player, and it is revoked when they also issue a disallow command.
|
@ -93,7 +93,7 @@ list: *possessions
|
||||
wield: *possessions
|
||||
gear: *possessions
|
||||
allow: |-
|
||||
<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in Blastmud let you choose how you want to play with other players (it only affects players, not NPCs). There are 4 types of consent: <bold>fight<reset> (for hostile actions like attack or pick), <bold>medicine<reset> (for medical actions, including those that might crit fail and do harm), <bold>gifts<reset> (lets them give you things), and <bold>visit<reset> (lets them on to a tile owned by you legally).
|
||||
<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in Blastmud let you choose how you want to play with other players (it only affects players, not NPCs). There are 5 types of consent: <bold>fight<reset> (for hostile actions like attack or pick), <bold>medicine<reset> (for medical actions, including those that might crit fail and do harm), <bold>gifts<reset> (lets them give you things), <bold>visit<reset> (lets them on to a tile owned by you legally), and <bold>share<reset> (lets them local share knowledge with you, making both parties stronger).
|
||||
|
||||
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options
|
||||
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options
|
||||
|
@ -24,6 +24,7 @@ use crate::{
|
||||
check_consent, check_one_consent,
|
||||
combat::{change_health, handle_resurrect, stop_attacking_mut},
|
||||
comms::broadcast_to_room,
|
||||
sharing::stop_conversation_mut,
|
||||
skills::skill_check_and_grind,
|
||||
urges::{recalculate_urge_growth, thirst_changed},
|
||||
},
|
||||
@ -892,6 +893,14 @@ impl UserVerb for Verb {
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
if player_item.active_conversation.is_some() {
|
||||
stop_conversation_mut(
|
||||
&ctx.trans,
|
||||
&mut player_item,
|
||||
"walks away from sharing knowledge with",
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ctx.trans.save_item_model(&player_item).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ pub fn parse_duration_mins<'l>(input: &'l str) -> Result<(u64, &'l str), String>
|
||||
}, input))
|
||||
}
|
||||
|
||||
pub fn parse_allow<'l>(input: &'l str, is_explicit: bool) -> Result<AllowCommand, String> {
|
||||
pub fn parse_allow<'l>(input: &'l str) -> Result<AllowCommand, String> {
|
||||
let usage: &'static str =
|
||||
ansi!("Usage: allow <lt>action> from <lt>user> <lt>options> | allow <lt>action> against <lt>corp> by <lt>corp> <lt>options>. Try <bold>help allow<reset> for more.");
|
||||
let (consent_type_s, input) = match input.trim_start().split_once(" ") {
|
||||
@ -126,11 +126,7 @@ pub fn parse_allow<'l>(input: &'l str, is_explicit: bool) -> Result<AllowCommand
|
||||
Some(v) => Ok(v),
|
||||
}?;
|
||||
let consent_type = match ConsentType::from_str(&consent_type_s.trim().to_lowercase()) {
|
||||
None => Err(if is_explicit {
|
||||
"Invalid consent type - options are fight, medicine, gifts, visit and sex"
|
||||
} else {
|
||||
"Invalid consent type - options are fight, medicine, gifts and visit"
|
||||
}),
|
||||
None => Err("Invalid consent type - options are fight, medicine, gifts, visit and share"),
|
||||
Some(ct) => Ok(ct),
|
||||
}?;
|
||||
|
||||
@ -434,7 +430,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_consent_works_default_options_user() {
|
||||
assert_eq!(
|
||||
super::parse_allow("medicine From Athorina", false),
|
||||
super::parse_allow("medicine From Athorina"),
|
||||
Ok(AllowCommand {
|
||||
consent_type: ConsentType::Medicine,
|
||||
consent_target: ConsentTarget::UserTarget {
|
||||
@ -448,7 +444,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_consent_works_default_options_corp() {
|
||||
assert_eq!(
|
||||
super::parse_allow("Fight Against megacorp By supercorp", false),
|
||||
super::parse_allow("Fight Against megacorp By supercorp"),
|
||||
Ok(AllowCommand {
|
||||
consent_type: ConsentType::Fight,
|
||||
consent_target: ConsentTarget::CorpTarget {
|
||||
@ -462,7 +458,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_consent_handles_options() {
|
||||
assert_eq!(super::parse_allow("fighT fRom athorina For 2 hOurs unTil deAth allOw priVate Disallow pIck alLow revoKe iN here in pit", false),
|
||||
assert_eq!(super::parse_allow("fighT fRom athorina For 2 hOurs unTil deAth allOw priVate Disallow pIck alLow revoKe iN here in pit"),
|
||||
Ok(AllowCommand {
|
||||
consent_type: ConsentType::Fight,
|
||||
consent_target: ConsentTarget::UserTarget { to_user: "athorina" },
|
||||
|
112
blastmud_game/src/message_handler/user_commands/share.rs
Normal file
112
blastmud_game/src/message_handler/user_commands/share.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use crate::{
|
||||
db::ItemSearchParams,
|
||||
models::item::{ConversationIntensity, ConversationalStyle},
|
||||
services::sharing::{
|
||||
change_conversation_intensity, change_conversation_topic, change_conversational_style,
|
||||
display_conversation_status, parse_conversation_topic, start_conversation,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
get_player_item_or_fail, search_item_for_user, user_error, UResult, UserError, UserVerb,
|
||||
UserVerbRef, VerbContext,
|
||||
};
|
||||
use ansi::ansi;
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(
|
||||
self: &Self,
|
||||
ctx: &mut VerbContext,
|
||||
verb: &str,
|
||||
remaining: &str,
|
||||
) -> UResult<()> {
|
||||
let mut player_item = (*(get_player_item_or_fail(ctx).await?)).clone();
|
||||
if player_item.death_data.is_some() {
|
||||
user_error("You can't do that, you're dead!".to_string())?;
|
||||
}
|
||||
|
||||
let remaining = remaining.trim();
|
||||
if let Some(ac) = player_item.active_conversation.as_ref() {
|
||||
match ConversationalStyle::from_name(verb) {
|
||||
None => {}
|
||||
Some(style) => {
|
||||
change_conversational_style(ctx, &mut player_item, style).await?;
|
||||
ctx.trans.save_item_model(&player_item).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
match parse_conversation_topic(&format!("{} {}", verb, remaining))
|
||||
.map_err(|e| UserError(e.to_owned()))?
|
||||
{
|
||||
None => {}
|
||||
Some(topic) => {
|
||||
change_conversation_topic(ctx, &mut player_item, topic).await?;
|
||||
ctx.trans.save_item_model(&player_item).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if verb == "share" {
|
||||
match ConversationIntensity::from_adverb(remaining) {
|
||||
None => {}
|
||||
Some(intensity) => {
|
||||
change_conversation_intensity(ctx, &mut player_item, intensity).await?;
|
||||
ctx.trans.save_item_model(&player_item).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if remaining == "status" {
|
||||
let (partner_type, partner_code) = ac
|
||||
.partner_ref
|
||||
.split_once("/")
|
||||
.ok_or_else(|| UserError("Bad share partner".to_owned()))?;
|
||||
if let Some(partner) = ctx
|
||||
.trans
|
||||
.find_item_by_type_code(partner_type, partner_code)
|
||||
.await?
|
||||
{
|
||||
display_conversation_status(&ctx.trans, &player_item, &partner).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user_error("You're already sharing knowledge!".to_owned())?;
|
||||
}
|
||||
|
||||
let (word2, remaining) = remaining.split_once(" ").ok_or_else(|| {
|
||||
UserError(
|
||||
ansi!("Start your encounter with the <bold>share knowledge with<reset> command first.")
|
||||
.to_owned(),
|
||||
)
|
||||
})?;
|
||||
let (word3, remaining) = remaining.trim().split_once(" ").ok_or_else(|| {
|
||||
UserError(ansi!("<bold>share knowledge with<reset> command first.").to_owned())
|
||||
})?;
|
||||
if verb != "share" || word2.trim() != "knowledge" || word3.trim() != "with" {
|
||||
user_error(
|
||||
ansi!("Start your encounter with the <bold>share knowledge with<reset> command first.")
|
||||
.to_owned(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let with_whom = search_item_for_user(
|
||||
ctx,
|
||||
&ItemSearchParams {
|
||||
include_loc_contents: true,
|
||||
limit: 1,
|
||||
..ItemSearchParams::base(&player_item, remaining)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
start_conversation(&ctx.trans, &player_item, &with_whom).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1,22 +1,11 @@
|
||||
use super::{get_player_item_or_fail, user_error, UResult, UserVerb, UserVerbRef, VerbContext};
|
||||
use crate::{models::item::Urges, services::combat::max_health};
|
||||
use crate::{
|
||||
models::item::Urges,
|
||||
services::{combat::max_health, display::bar_n_of_m},
|
||||
};
|
||||
use ansi::ansi;
|
||||
use async_trait::async_trait;
|
||||
|
||||
fn bar_n_of_m(mut actual: u64, max: u64) -> String {
|
||||
if actual > max {
|
||||
actual = max;
|
||||
}
|
||||
let mut r = String::new();
|
||||
for _i in 0..actual {
|
||||
r += "|";
|
||||
}
|
||||
for _i in actual..max {
|
||||
r += " ";
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
|
@ -2,6 +2,7 @@ use super::{
|
||||
get_player_item_or_fail, movement::reverse_climb, user_error, UResult, UserVerb, UserVerbRef,
|
||||
VerbContext,
|
||||
};
|
||||
use crate::services::sharing::stop_conversation_mut;
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Verb;
|
||||
@ -21,6 +22,20 @@ impl UserVerb for Verb {
|
||||
let mut player_item_mut = (*player_item).clone();
|
||||
let mut queue_head = player_item_mut.queue.pop_front();
|
||||
|
||||
if player_item_mut.active_conversation.is_some() {
|
||||
stop_conversation_mut(
|
||||
&ctx.trans,
|
||||
&mut player_item_mut,
|
||||
&format!(
|
||||
"holds up {} hand to stop the conversation with",
|
||||
&player_item.pronouns.possessive
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
ctx.trans.save_item_model(&player_item_mut).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if player_item.active_combat.is_some() {
|
||||
// Otherwise, we assume they wanted to stop escaping etc...
|
||||
if queue_head.is_none() {
|
||||
|
@ -8,7 +8,7 @@ pub enum ConsentType {
|
||||
Medicine,
|
||||
Gifts,
|
||||
Visit,
|
||||
Sex,
|
||||
Share,
|
||||
}
|
||||
|
||||
impl ConsentType {
|
||||
@ -19,7 +19,7 @@ impl ConsentType {
|
||||
"medicine" => Some(Medicine),
|
||||
"gifts" => Some(Gifts),
|
||||
"visit" => Some(Visit),
|
||||
"sex" => Some(Sex),
|
||||
"share" => Some(Share),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,7 @@ impl ConsentType {
|
||||
Medicine => "medicine",
|
||||
Gifts => "gifts",
|
||||
Visit => "visit",
|
||||
Sex => "sex",
|
||||
Share => "share",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,6 @@ pub enum SkillType {
|
||||
Fists,
|
||||
Flails,
|
||||
Focus,
|
||||
Fuck,
|
||||
Hack,
|
||||
Locksmith,
|
||||
Medic,
|
||||
@ -66,6 +65,7 @@ pub enum SkillType {
|
||||
Rifles,
|
||||
Scavenge,
|
||||
Science,
|
||||
Share,
|
||||
Sneak,
|
||||
Spears,
|
||||
Swim,
|
||||
@ -81,8 +81,9 @@ impl SkillType {
|
||||
use SkillType::*;
|
||||
vec![
|
||||
Appraise, Blades, Bombs, Chemistry, Climb, Clubs, Craft, Dodge, Fish, Fists, Flails,
|
||||
Focus, Fuck, Hack, Locksmith, Medic, Persuade, Pilot, Pistols, Quickdraw, Repair, Ride,
|
||||
Rifles, Scavenge, Science, Sneak, Spears, Swim, Teach, Throw, Track, Wrestle, Whips,
|
||||
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 {
|
||||
@ -100,7 +101,6 @@ impl SkillType {
|
||||
Fists => "fists",
|
||||
Flails => "flails",
|
||||
Focus => "focus",
|
||||
Fuck => "fuck",
|
||||
Hack => "hack",
|
||||
Locksmith => "locksmith",
|
||||
Medic => "medic",
|
||||
@ -113,6 +113,7 @@ impl SkillType {
|
||||
Rifles => "rifles",
|
||||
Scavenge => "scavenge",
|
||||
Science => "science",
|
||||
Share => "share",
|
||||
Sneak => "sneak",
|
||||
Spears => "spears",
|
||||
Swim => "swim",
|
||||
@ -317,6 +318,7 @@ pub enum ItemFlag {
|
||||
HasUrges,
|
||||
NoUrgesHere,
|
||||
DontListInLook,
|
||||
AllowShare,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
@ -362,6 +364,175 @@ impl Default for ActiveClimb {
|
||||
}
|
||||
}
|
||||
|
||||
#[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<ConversationalStyle> {
|
||||
use ConversationalStyle::*;
|
||||
vec![Amicable, Serious, Joking]
|
||||
.into_iter()
|
||||
.filter(|v| v != self)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_name(n: &str) -> Option<ConversationalStyle> {
|
||||
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<ConversationIntensity> {
|
||||
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<ConversationalInterestType, u64>,
|
||||
pub partner_ref: String,
|
||||
pub style: ConversationalStyle,
|
||||
pub current_topic: ConversationTopic,
|
||||
pub current_intensity: ConversationIntensity,
|
||||
pub peak_total_interest: u64,
|
||||
pub last_change: DateTime<Utc>,
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -495,6 +666,7 @@ pub struct Item {
|
||||
pub active_climb: Option<ActiveClimb>,
|
||||
pub active_combat: Option<ActiveCombat>,
|
||||
pub active_effects: Vec<(EffectType, i64)>,
|
||||
pub active_conversation: Option<ActiveConversation>,
|
||||
pub aliases: Vec<String>,
|
||||
pub charges: u8,
|
||||
pub death_data: Option<DeathData>,
|
||||
@ -620,6 +792,7 @@ impl Default for Item {
|
||||
active_climb: None,
|
||||
active_combat: Some(Default::default()),
|
||||
active_effects: vec![],
|
||||
active_conversation: None,
|
||||
aliases: vec![],
|
||||
charges: 0,
|
||||
death_data: None,
|
||||
|
@ -27,6 +27,7 @@ pub enum TaskDetails {
|
||||
npc_code: String,
|
||||
},
|
||||
AttackTick,
|
||||
ShareTick,
|
||||
RecloneNPC {
|
||||
npc_code: String,
|
||||
},
|
||||
@ -78,6 +79,7 @@ impl TaskDetails {
|
||||
NPCWander { .. } => "NPCWander",
|
||||
NPCAggro { .. } => "NPCAggro",
|
||||
AttackTick => "AttackTick",
|
||||
ShareTick => "ShareTick",
|
||||
RecloneNPC { .. } => "RecloneNPC",
|
||||
RotCorpse { .. } => "RotCorpse",
|
||||
DelayedHealth { .. } => "DelayedHealth",
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
listener::{ListenerMap, ListenerSend},
|
||||
message_handler::user_commands::{delete, drop, hire, open, rent},
|
||||
models::task::Task,
|
||||
services::{combat, effect, spawn, urges},
|
||||
services::{combat, effect, sharing, spawn, urges},
|
||||
static_content::{
|
||||
npc::{self, computer_museum_npcs},
|
||||
room::general_hospital,
|
||||
@ -52,6 +52,7 @@ fn task_handler_registry(
|
||||
("NPCWander", npc::WANDER_HANDLER),
|
||||
("NPCAggro", npc::AGGRO_HANDLER),
|
||||
("AttackTick", combat::TASK_HANDLER),
|
||||
("ShareTick", sharing::TASK_HANDLER),
|
||||
("RecloneNPC", npc::RECLONE_HANDLER),
|
||||
("RotCorpse", combat::ROT_CORPSE_HANDLER),
|
||||
("DelayedHealth", effect::DELAYED_HEALTH_HANDLER),
|
||||
|
@ -19,7 +19,9 @@ use mockall_double::double;
|
||||
pub mod capacity;
|
||||
pub mod combat;
|
||||
pub mod comms;
|
||||
pub mod display;
|
||||
pub mod effect;
|
||||
pub mod sharing;
|
||||
pub mod skills;
|
||||
pub mod spawn;
|
||||
pub mod urges;
|
||||
|
@ -1361,7 +1361,7 @@ impl TaskHandler for RotCorpseTaskHandler {
|
||||
pub static ROT_CORPSE_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &RotCorpseTaskHandler;
|
||||
|
||||
#[cfg(test)]
|
||||
mod Tests {
|
||||
mod tests {
|
||||
use crate::{models::effect::EffectType, services::effect::default_effects_for_type};
|
||||
|
||||
#[test]
|
||||
|
13
blastmud_game/src/services/display.rs
Normal file
13
blastmud_game/src/services/display.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub fn bar_n_of_m(mut actual: u64, max: u64) -> String {
|
||||
if actual > max {
|
||||
actual = max;
|
||||
}
|
||||
let mut r = String::new();
|
||||
for _i in 0..actual {
|
||||
r += "|";
|
||||
}
|
||||
for _i in actual..max {
|
||||
r += " ";
|
||||
}
|
||||
r
|
||||
}
|
1260
blastmud_game/src/services/sharing.rs
Normal file
1260
blastmud_game/src/services/sharing.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -183,12 +183,12 @@ pub fn calculate_total_stats_skills_for_user(target_item: &mut Item, user: &User
|
||||
.or_insert(end * 0.5);
|
||||
target_item
|
||||
.total_skills
|
||||
.entry(SkillType::Fuck)
|
||||
.entry(SkillType::Share)
|
||||
.and_modify(|sk| *sk += sen * 0.5)
|
||||
.or_insert(sen * 0.5);
|
||||
target_item
|
||||
.total_skills
|
||||
.entry(SkillType::Fuck)
|
||||
.entry(SkillType::Share)
|
||||
.and_modify(|sk| *sk += end * 0.5)
|
||||
.or_insert(end * 0.5);
|
||||
target_item
|
||||
|
@ -56,7 +56,7 @@ pub fn npc_list() -> Vec<NPC> {
|
||||
message_handler: None,
|
||||
wander_zones: vec!("melbs".to_owned()),
|
||||
says: vec!(melbs_citizen_stdsay.clone()),
|
||||
player_consents: vec!(ConsentType::Medicine, ConsentType::Sex),
|
||||
player_consents: vec!(ConsentType::Medicine, ConsentType::Share),
|
||||
..Default::default()
|
||||
}
|
||||
).collect()
|
||||
|
@ -104,7 +104,7 @@ fn points_left(user: &User) -> f64 {
|
||||
(62 - (brn + sen + brw + refl + end + col) as i16).max(0) as f64
|
||||
}
|
||||
|
||||
fn next_action_text(session: &Session, user: &User, item: &Item) -> String {
|
||||
fn next_action_text(_session: &Session, user: &User, item: &Item) -> String {
|
||||
let brn = user
|
||||
.raw_stats
|
||||
.get(&StatType::Brains)
|
||||
@ -156,13 +156,12 @@ fn next_action_text(session: &Session, user: &User, item: &Item) -> String {
|
||||
are: ").to_owned() + &summary,
|
||||
StatbotState::Senses => format!(ansi!(
|
||||
"Your next job is to choose how good your senses will be. Senses help your \
|
||||
appraise, dodge, focus,{} scavenge, sneak, throw, track and whips skills.\n\
|
||||
appraise, dodge, focus, scavenge, share knowledge, sneak, throw, track and \
|
||||
whips skills.\n\
|
||||
\tType <green><bold>-statbot senses 8<reset><blue> (or any other number) to \
|
||||
set your senses to that number. You will be able to adjust your stats by \
|
||||
sending me the new value, up until you leave here. Your stats now are: {}"),
|
||||
if session.less_explicit_mode {
|
||||
""
|
||||
} else { " fuck,"}, &summary),
|
||||
&summary),
|
||||
StatbotState::Brawn => ansi!(
|
||||
"Your next job is to choose how strong you will be. Brawn helps your \
|
||||
clubs, fists, and throw skills.\n\
|
||||
@ -180,11 +179,11 @@ fn next_action_text(session: &Session, user: &User, item: &Item) -> String {
|
||||
).to_owned() + &summary,
|
||||
StatbotState::Endurance => format!(ansi!(
|
||||
"Your next job is to choose how much stamina you will have. Endurance helps \
|
||||
your climb, fish, fists, focus,{} scavenge, spears and swim skills.\n\
|
||||
your climb, fish, fists, focus, scavenge, share knowledge, spears and swim skills.\n\
|
||||
\tType <green><bold>-statbot endurance 8<reset><blue> (or any other number) to \
|
||||
set your endurance to that number. You will be able to adjust your stats by \
|
||||
sending me the new value, up until you leave here. Your stats now are: {}"
|
||||
), if session.less_explicit_mode { "" } else { " fuck,"}, &summary),
|
||||
), &summary),
|
||||
StatbotState::Cool => ansi!(
|
||||
"Your next job is to choose how much you keep your cool under pressure. \
|
||||
Cool helps your blades, bombs, fish, pistols, quickdraw, rifles, sneak \
|
||||
|
Loading…
Reference in New Issue
Block a user