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:
Condorra 2023-12-27 00:34:47 +11:00
parent 706825be20
commit 0c280711e8
22 changed files with 1662 additions and 90 deletions

View File

@ -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");

View File

@ -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,

View File

@ -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,8 +820,7 @@ async fn handle_user_consent(
.await?
{
None => {}
Some((session, session_dat)) => {
if cmd.consent_type != ConsentType::Sex || !session_dat.less_explicit_mode {
Some((session, _session_dat)) => {
ctx.trans
.queue_for_session(&session, Some(&(msg + "\n")))
.await?;
@ -829,7 +828,6 @@ async fn handle_user_consent(
}
}
}
}
Ok(())
}
@ -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,
};

View File

@ -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();
}
}

View File

@ -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.

View File

@ -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

View File

@ -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(())
}

View File

@ -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" },

View 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;

View File

@ -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 {

View File

@ -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() {

View File

@ -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",
}
}
}

View File

@ -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,

View File

@ -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",

View File

@ -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),

View File

@ -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;

View File

@ -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]

View 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
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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()

View File

@ -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 \