forked from blasthavers/blastmud
401 lines
13 KiB
Rust
401 lines
13 KiB
Rust
#[double]
|
|
use crate::db::DBTrans;
|
|
use crate::{
|
|
message_handler::user_commands::{
|
|
say::say_to_room,
|
|
CommandHandlingError::{SystemError, UserError},
|
|
},
|
|
models::{
|
|
item::{Item, ItemFlag, LocationActionType, StatType, Urge, Urges},
|
|
task::{Task, TaskDetails, TaskMeta, TaskRecurrence},
|
|
},
|
|
regular_tasks::{TaskHandler, TaskRunContext},
|
|
static_content::{species::SpeciesType, StaticTask},
|
|
DResult,
|
|
};
|
|
use async_trait::async_trait;
|
|
use chrono::{self, Utc};
|
|
use mockall_double::double;
|
|
use std::time;
|
|
|
|
fn urge_threshold_check(urge: &Urge) -> bool {
|
|
(urge.last_value < 2500 && urge.value >= 2500)
|
|
|| (urge.last_value >= 2500 && urge.value < 2500)
|
|
|| (urge.last_value < 5000 && urge.value >= 5000)
|
|
|| (urge.last_value >= 5000 && urge.value < 5000)
|
|
|| (urge.last_value < 7500 && urge.value >= 7500)
|
|
|| (urge.last_value >= 7500 && urge.value < 7500)
|
|
|| (urge.last_value == 10000 && urge.value != 10000)
|
|
|| (urge.last_value != 10000 && urge.value == 10000)
|
|
}
|
|
|
|
pub async fn hunger_changed(trans: &DBTrans, who: &Item) -> DResult<()> {
|
|
let urge = match who.urges.as_ref().map(|urg| &urg.hunger) {
|
|
None => return Ok(()),
|
|
Some(u) => u,
|
|
};
|
|
if !urge_threshold_check(&urge) {
|
|
return Ok(());
|
|
}
|
|
let is_player = who.item_type == "player";
|
|
let msg = if urge.last_value < urge.value {
|
|
// Rising
|
|
if urge.value < 5000 {
|
|
if is_player {
|
|
"You're starting to feel a bit peckish."
|
|
} else {
|
|
"I'm getting a bit hungry, you know."
|
|
}
|
|
} else if urge.value < 7500 {
|
|
if is_player {
|
|
"You feel sharp pangs of hunger."
|
|
} else {
|
|
"I really wish I had something to eat."
|
|
}
|
|
} else if urge.value < 10000 {
|
|
if is_player {
|
|
"You are absolutely starving!"
|
|
} else {
|
|
"I'm SO hungry!"
|
|
}
|
|
} else {
|
|
if is_player {
|
|
"You're literally famished and can barely move."
|
|
} else {
|
|
"I'm literally starving."
|
|
}
|
|
}
|
|
} else {
|
|
if urge.value < 2500 {
|
|
if is_player {
|
|
"You're not that hungry now."
|
|
} else {
|
|
"I don't feel so hungry anymore!"
|
|
}
|
|
} else if urge.value < 5000 {
|
|
if is_player {
|
|
"You're only a bit hungry now."
|
|
} else {
|
|
"I'm still a bit hungry, you know."
|
|
}
|
|
} else if urge.value < 7500 {
|
|
if is_player {
|
|
"You're still pretty hungry."
|
|
} else {
|
|
"I still feel pretty hungry."
|
|
}
|
|
} else {
|
|
if is_player {
|
|
"You're still really hungry."
|
|
} else {
|
|
"I'm still really hungry!"
|
|
}
|
|
}
|
|
};
|
|
|
|
if is_player {
|
|
if let Some((sess, _sess_dat)) = trans.find_session_for_player(&who.item_code).await? {
|
|
trans
|
|
.queue_for_session(&sess, Some(&format!("{}\n", msg)))
|
|
.await?;
|
|
}
|
|
} else {
|
|
if who.species == SpeciesType::Human {
|
|
match say_to_room(&trans, who, &who.location, msg, false).await {
|
|
Ok(_) => Ok(()),
|
|
Err(SystemError(e)) => Err(e),
|
|
Err(UserError(_)) => Ok(()),
|
|
}?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn bladder_changed(trans: &DBTrans, who: &Item) -> DResult<()> {
|
|
let urge = match who.urges.as_ref().map(|urg| &urg.bladder) {
|
|
None => return Ok(()),
|
|
Some(u) => u,
|
|
};
|
|
if !urge_threshold_check(&urge) {
|
|
return Ok(());
|
|
}
|
|
if who.item_type == "player" && urge.value > urge.last_value {
|
|
let msg = if urge.value < 5000 {
|
|
"You feel a slight pressure building in your bladder."
|
|
} else if urge.value < 7500 {
|
|
"You've really got to find a toilet soon."
|
|
} else if urge.value < 10000 {
|
|
"You're absolutely busting!"
|
|
} else {
|
|
"You can't hold your bladder any longer."
|
|
};
|
|
if let Some((sess, _sess_dat)) = trans.find_session_for_player(&who.item_code).await? {
|
|
trans
|
|
.queue_for_session(&sess, Some(&format!("{}\n", msg)))
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// TODO soil the room
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn thirst_changed(trans: &DBTrans, who: &Item) -> DResult<()> {
|
|
let urge = match who.urges.as_ref().map(|urg| &urg.thirst) {
|
|
None => return Ok(()),
|
|
Some(u) => u,
|
|
};
|
|
if !urge_threshold_check(&urge) {
|
|
return Ok(());
|
|
}
|
|
let is_player = who.item_type == "player";
|
|
let msg = if urge.last_value < urge.value {
|
|
// Rising
|
|
if urge.value < 5000 {
|
|
if is_player {
|
|
"You're starting to feel a bit thirsty."
|
|
} else {
|
|
"I'm getting a bit thirsty, you know."
|
|
}
|
|
} else if urge.value < 7500 {
|
|
if is_player {
|
|
"Your through feels really dry."
|
|
} else {
|
|
"I really wish I had something to drink."
|
|
}
|
|
} else if urge.value < 10000 {
|
|
if is_player {
|
|
"You're throat is absolutely dry with thirst!"
|
|
} else {
|
|
"I'm SO thirsty!"
|
|
}
|
|
} else {
|
|
if is_player {
|
|
"You're literally dehydrated and can barely move."
|
|
} else {
|
|
"I'm literally dehydrated."
|
|
}
|
|
}
|
|
} else {
|
|
if urge.value < 2500 {
|
|
if is_player {
|
|
"You're not that thirsty now."
|
|
} else {
|
|
"I don't feel so thirsty anymore!"
|
|
}
|
|
} else if urge.value < 5000 {
|
|
if is_player {
|
|
"You're only a bit thirsty now."
|
|
} else {
|
|
"I'm still a bit thirsty, you know."
|
|
}
|
|
} else if urge.value < 7500 {
|
|
if is_player {
|
|
"You're still pretty thirsty."
|
|
} else {
|
|
"I still feel pretty thirsty."
|
|
}
|
|
} else {
|
|
if is_player {
|
|
"You're still really thirsty."
|
|
} else {
|
|
"I'm still really thirsty!"
|
|
}
|
|
}
|
|
};
|
|
|
|
if is_player {
|
|
if let Some((sess, _sess_dat)) = trans.find_session_for_player(&who.item_code).await? {
|
|
trans
|
|
.queue_for_session(&sess, Some(&format!("{}\n", msg)))
|
|
.await?;
|
|
}
|
|
} else {
|
|
match say_to_room(&trans, who, &who.location, msg, false).await {
|
|
Ok(_) => Ok(()),
|
|
Err(SystemError(e)) => Err(e),
|
|
Err(UserError(_)) => Ok(()),
|
|
}?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn stress_changed(trans: &DBTrans, who: &Item) -> DResult<()> {
|
|
let urge = match who.urges.as_ref().map(|urg| &urg.stress) {
|
|
None => return Ok(()),
|
|
Some(u) => u,
|
|
};
|
|
if !urge_threshold_check(&urge) {
|
|
return Ok(());
|
|
}
|
|
if who.item_type == "player" {
|
|
let msg = if urge.value > urge.last_value {
|
|
if urge.value < 5000 {
|
|
"You're getting a bit stressed."
|
|
} else if urge.value < 7500 {
|
|
"You're pretty strssed out."
|
|
} else if urge.value < 10000 {
|
|
"You're so stressed you'd really better rest!"
|
|
} else {
|
|
"You're so stressed you can't move, and need to sleep now."
|
|
}
|
|
} else {
|
|
if urge.value < 2500 {
|
|
"You're no longer stressed."
|
|
} else if urge.value < 5000 {
|
|
"You're only a bit stressed now."
|
|
} else if urge.value < 7500 {
|
|
"You're still pretty stressed."
|
|
} else {
|
|
"You're still really stressed."
|
|
}
|
|
};
|
|
if let Some((sess, _sess_dat)) = trans.find_session_for_player(&who.item_code).await? {
|
|
trans
|
|
.queue_for_session(&sess, Some(&format!("{}\n", msg)))
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub struct TickUrgesTaskHandler;
|
|
#[async_trait]
|
|
impl TaskHandler for TickUrgesTaskHandler {
|
|
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
|
ctx.trans.stop_urges_for_sessionless().await?;
|
|
ctx.trans.apply_urge_tick("hunger").await?;
|
|
for item in ctx.trans.get_urges_crossed_milestones("hunger").await? {
|
|
hunger_changed(&ctx.trans, &item).await?;
|
|
}
|
|
ctx.trans.apply_urge_tick("bladder").await?;
|
|
for item in ctx.trans.get_urges_crossed_milestones("bladder").await? {
|
|
bladder_changed(&ctx.trans, &item).await?;
|
|
}
|
|
ctx.trans.apply_urge_tick("thirst").await?;
|
|
for item in ctx.trans.get_urges_crossed_milestones("thirst").await? {
|
|
thirst_changed(&ctx.trans, &item).await?;
|
|
}
|
|
ctx.trans.apply_urge_tick("stress").await?;
|
|
for item in ctx.trans.get_urges_crossed_milestones("stress").await? {
|
|
stress_changed(&ctx.trans, &item).await?;
|
|
}
|
|
Ok(Some(time::Duration::from_secs(60)))
|
|
}
|
|
}
|
|
pub static TICK_URGES_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &TickUrgesTaskHandler;
|
|
|
|
pub fn urge_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
|
|
Box::new(
|
|
vec![StaticTask {
|
|
task_code: "tick_urges".to_owned(),
|
|
initial_task: Box::new(|| Task {
|
|
meta: TaskMeta {
|
|
task_code: "tick_urges".to_owned(),
|
|
is_static: true,
|
|
recurrence: Some(TaskRecurrence::FixedDuration { seconds: 60 }),
|
|
next_scheduled: Utc::now() + chrono::Duration::seconds(60),
|
|
..TaskMeta::default()
|
|
},
|
|
details: TaskDetails::TickUrges,
|
|
}),
|
|
}]
|
|
.into_iter(),
|
|
)
|
|
}
|
|
|
|
pub async fn set_has_urges_if_needed(trans: &DBTrans, player_item: &mut Item) -> DResult<()> {
|
|
let mut has_urges = player_item.death_data.is_none()
|
|
&& match player_item.urges {
|
|
None => false,
|
|
Some(Urges {
|
|
hunger: Urge { growth: hunger, .. },
|
|
stress: Urge { growth: stress, .. },
|
|
thirst: Urge { growth: thirst, .. },
|
|
..
|
|
}) => hunger != 0 || stress != 0 || thirst != 0,
|
|
};
|
|
if has_urges {
|
|
match player_item.location.split_once("/") {
|
|
None => {}
|
|
Some((loc_type, loc_code)) => {
|
|
has_urges |= trans
|
|
.find_item_by_type_code(loc_type, loc_code)
|
|
.await?
|
|
.map(|it| !it.flags.contains(&ItemFlag::NoUrgesHere))
|
|
.unwrap_or(true);
|
|
}
|
|
}
|
|
}
|
|
if has_urges {
|
|
player_item.flags.push(ItemFlag::HasUrges);
|
|
} else {
|
|
player_item.flags = player_item
|
|
.flags
|
|
.clone()
|
|
.into_iter()
|
|
.filter(|f| f != &ItemFlag::HasUrges)
|
|
.collect();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn change_stress_considering_cool(
|
|
trans: &DBTrans,
|
|
who: &mut Item,
|
|
max_magnitude: i64,
|
|
) -> DResult<()> {
|
|
if !who.flags.contains(&ItemFlag::HasUrges) {
|
|
return Ok(());
|
|
}
|
|
let cool = who.total_stats.get(&StatType::Cool).unwrap_or(&8.0);
|
|
let stress_factor = 1.0 - 1.0 / (1.0 + (-0.7 * (cool - 8.0)).exp());
|
|
match who.urges.as_mut() {
|
|
None => {}
|
|
Some(urges) => {
|
|
urges.stress.last_value = urges.stress.value;
|
|
urges.stress.value = (urges.stress.value as i64
|
|
+ (max_magnitude as f64 * stress_factor) as i64)
|
|
.max(0)
|
|
.min(10000) as u16;
|
|
}
|
|
}
|
|
stress_changed(trans, who).await
|
|
}
|
|
|
|
pub async fn recalculate_urge_growth(_trans: &DBTrans, item: &mut Item) -> DResult<()> {
|
|
let cool = item.total_stats.get(&StatType::Cool).unwrap_or(&8.0);
|
|
let relax_action_factor = match item.action_type {
|
|
LocationActionType::Sitting(_) => 100.0,
|
|
LocationActionType::Reclining(_) => 150.0,
|
|
LocationActionType::Attacking(_) => 0.5,
|
|
_ => 1.0,
|
|
};
|
|
|
|
let old_urges = item.urges.clone().unwrap_or_else(|| Default::default());
|
|
item.urges = Some(Urges {
|
|
hunger: Urge {
|
|
growth: 42,
|
|
..old_urges.hunger
|
|
},
|
|
thirst: Urge {
|
|
growth: 0,
|
|
..old_urges.thirst
|
|
}, // To do: climate based?
|
|
bladder: Urge {
|
|
growth: 42,
|
|
..old_urges.bladder
|
|
},
|
|
stress: Urge {
|
|
growth: (-(cool.max(7.0) - 7.0) * 4.0 * relax_action_factor) as i16,
|
|
..old_urges.stress
|
|
},
|
|
});
|
|
Ok(())
|
|
}
|