diff --git a/blastmud_game/src/message_handler/user_commands/delete.rs b/blastmud_game/src/message_handler/user_commands/delete.rs index d16740c..8d64cdd 100644 --- a/blastmud_game/src/message_handler/user_commands/delete.rs +++ b/blastmud_game/src/message_handler/user_commands/delete.rs @@ -93,6 +93,7 @@ async fn reset_stats(ctx: &mut VerbContext<'_>) -> UResult<()> { user_dat.experience.xp_change_for_this_reroll = 0; user_dat.raw_stats = BTreeMap::new(); user_dat.raw_skills = BTreeMap::new(); + user_dat.wristpad_hacks = vec![]; calculate_total_stats_skills_for_user(&mut player_item, &user_dat); ctx.trans.save_user_model(&user_dat).await?; ctx.trans.save_item_model(&player_item).await?; diff --git a/blastmud_game/src/message_handler/user_commands/movement.rs b/blastmud_game/src/message_handler/user_commands/movement.rs index 5d21999..0e2398e 100644 --- a/blastmud_game/src/message_handler/user_commands/movement.rs +++ b/blastmud_game/src/message_handler/user_commands/movement.rs @@ -29,7 +29,7 @@ use crate::{ static_content::{ dynzone::{dynzone_by_type, DynzoneType, ExitTarget as DynExitTarget}, npc::check_for_instant_aggro, - room::{self, Direction, ExitClimb, ExitType, MaterialType}, + room::{self, check_for_enter_action, Direction, ExitClimb, ExitType, MaterialType}, }, DResult, }; @@ -659,6 +659,7 @@ async fn attempt_move_immediate( } check_for_instant_aggro(&ctx.trans, &mut ctx.item).await?; + check_for_enter_action(ctx).await?; Ok(true) } diff --git a/blastmud_game/src/models/task.rs b/blastmud_game/src/models/task.rs index 9585d0a..d495328 100644 --- a/blastmud_game/src/models/task.rs +++ b/blastmud_game/src/models/task.rs @@ -55,6 +55,9 @@ pub enum TaskDetails { TickUrges, ResetSpawns, ResetHanoi, + HospitalERSeePatient { + item: String, + }, } impl TaskDetails { pub fn name(self: &Self) -> &'static str { @@ -76,6 +79,7 @@ impl TaskDetails { TickUrges => "TickUrges", ResetSpawns => "ResetSpawns", ResetHanoi => "ResetHanoi", + HospitalERSeePatient { .. } => "HospitalERSeePatient", // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too. } } diff --git a/blastmud_game/src/regular_tasks.rs b/blastmud_game/src/regular_tasks.rs index 8826cf9..016987f 100644 --- a/blastmud_game/src/regular_tasks.rs +++ b/blastmud_game/src/regular_tasks.rs @@ -10,7 +10,10 @@ use crate::{ message_handler::user_commands::{delete, drop, hire, open, rent}, models::task::Task, services::{combat, effect, spawn, urges}, - static_content::npc::{self, computer_museum_npcs}, + static_content::{ + npc::{self, computer_museum_npcs}, + room::general_hospital, + }, DResult, }; use async_trait::async_trait; @@ -63,6 +66,10 @@ fn task_handler_registry( "ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER.clone(), ), + ( + "HospitalERSeePatient", + general_hospital::SEE_PATIENT_TASK.clone(), + ), ] .into_iter() .collect() diff --git a/blastmud_game/src/services/urges.rs b/blastmud_game/src/services/urges.rs index a88540d..a3e027c 100644 --- a/blastmud_game/src/services/urges.rs +++ b/blastmud_game/src/services/urges.rs @@ -354,7 +354,7 @@ pub async fn recalculate_urge_growth(_trans: &DBTrans, item: &mut Item) -> DResu ..old_urges.thirst }, // To do: climate based? stress: Urge { - growth: (-(cool.max(7.0) - 7.0) * 10.0 * relax_action_factor) as i16, + growth: (-(cool.max(7.0) - 6.0) * 10.0 * relax_action_factor) as i16, ..old_urges.stress }, }); diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 652270b..56aa723 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -19,6 +19,7 @@ use std::collections::{BTreeMap, BTreeSet}; mod chonkers; mod cok_murl; pub mod computer_museum; +pub mod general_hospital; mod melbs; mod repro_xv; mod special; @@ -63,6 +64,11 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> { display: "Computer Museum", outdoors: false, }, + Zone { + code: "general_hospital", + display: "General Hospital", + outdoors: false, + }, ] .into_iter() .map(|x| (x.code, x)) @@ -317,6 +323,11 @@ pub enum MaterialType { Soft { damage_modifier: f64 }, } +#[async_trait] +pub trait RoomEnterTrigger { + async fn handle_enter(self: &Self, ctx: &mut QueuedCommandContext, room: &Room) -> UResult<()>; +} + pub struct Room { pub zone: &'static str, // Other zones where it can be seen on the map and accessed. @@ -339,6 +350,7 @@ pub struct Room { pub has_power: bool, pub door_states: Option>, pub wristpad_hack_allowed: Option, + pub enter_trigger: Option<&'static (dyn RoomEnterTrigger + Sync + Send)>, } impl Default for Room { @@ -362,6 +374,7 @@ impl Default for Room { has_power: false, door_states: None, wristpad_hack_allowed: None, + enter_trigger: None, } } } @@ -375,6 +388,7 @@ pub fn room_list() -> &'static Vec { rooms.append(&mut chonkers::room_list()); rooms.append(&mut special::room_list()); rooms.append(&mut computer_museum::room_list()); + rooms.append(&mut general_hospital::room_list()); rooms.into_iter().collect() }) } @@ -488,6 +502,23 @@ pub async fn refresh_room_exits(trans: &DBTrans, template: &Item) -> DResult<()> Ok(()) } +pub async fn check_for_enter_action(ctx: &mut QueuedCommandContext<'_>) -> UResult<()> { + let room_code = match ctx.item.location.split_once("/") { + Some((loc_type, _)) if loc_type != "room" => return Ok(()), + Some((_, room_code)) => room_code, + _ => return Ok(()), + }; + let room = match room_map_by_code().get(room_code) { + Some(r) => r, + _ => return Ok(()), + }; + match room.enter_trigger { + Some(trigger) => trigger.handle_enter(ctx, room).await?, + _ => {} + } + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/blastmud_game/src/static_content/room/general_hospital.rs b/blastmud_game/src/static_content/room/general_hospital.rs new file mode 100644 index 0000000..1c551cd --- /dev/null +++ b/blastmud_game/src/static_content/room/general_hospital.rs @@ -0,0 +1,214 @@ +use crate::{ + message_handler::user_commands::UResult, + models::task::{Task, TaskDetails, TaskMeta}, + regular_tasks::{queued_command::QueuedCommandContext, TaskHandler, TaskRunContext}, + services::{combat::max_health, effect::run_effects}, + static_content::possession_type::UseEffect, + DResult, +}; + +use super::{Direction, Exit, ExitTarget, GridCoords, Room, RoomEnterTrigger, SecondaryZoneRecord}; +use ansi::ansi; +use async_trait::async_trait; +use chrono::{self, Utc}; +use log::warn; +use std::time; + +struct EnterERTrigger; +#[async_trait] +impl RoomEnterTrigger for EnterERTrigger { + async fn handle_enter( + self: &Self, + ctx: &mut QueuedCommandContext, + _room: &Room, + ) -> UResult<()> { + ctx.trans + .upsert_task(&Task { + meta: TaskMeta { + task_code: ctx.item.refstr(), + next_scheduled: Utc::now() + chrono::Duration::seconds(60), + ..Default::default() + }, + details: TaskDetails::HospitalERSeePatient { + item: ctx.item.refstr(), + }, + }) + .await?; + if let Some((sess, _)) = ctx.get_session().await? { + ctx.trans + .queue_for_session( + &sess, + Some(ansi!( + "The triage nurse says: \ + \"Luckily, we are not too busy today - at least not by usual \ + standards! Take a seat and the doctor will be with you in \ + about a minute.\"\n" + )), + ) + .await?; + } + Ok(()) + } +} +static ENTER_ER_TRIGGER: EnterERTrigger = EnterERTrigger; + +pub struct SeePatientTaskHandler; +#[async_trait] +impl TaskHandler for SeePatientTaskHandler { + async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult> { + let see_who = match ctx.task.details { + TaskDetails::HospitalERSeePatient { ref item } => item, + _ => { + warn!( + "Unexpected task dispatched to SeePatientTaskHandler: {:?}", + ctx.task + ); + return Ok(None); + } + }; + let (who_type, who_code) = match see_who.split_once("/") { + None => return Ok(None), + Some(w) => w, + }; + let who = match ctx.trans.find_item_by_type_code(who_type, who_code).await? { + None => return Ok(None), + Some(w) => w, + }; + if who.location != "room/general_hospital_waiting_room" { + return Ok(None); + } + + if who_type == "player" { + if let Some((sess, _sess_dat)) = ctx.trans.find_session_for_player(who_code).await? { + let mut msg: String = ansi!( + "The doctor says: \ + \"Stay still while I take a close look at what might be wrong.\"\n\ + The doctor looks up and down your body closely, her gaze searching for any \ + anomaly.\n" + ) + .to_owned(); + let hp_gap = max_health(&who) as i64 - who.health as i64; + let mut skip_heal = false; + if hp_gap <= 0 { + msg += ansi!("The doctor says: \"You're perfectly healthy as far \ + as I can tell! There's nothing I can do for you here. Eat healthy, try to \ + maintain a healthy weight, and stay away from radiation, and you'll stay \ + that way.\"\n"); + skip_heal = true; + } else { + msg += ansi!("The doctor says: \"You're injured. Let me stitch that \ + up, and then just take bed rest. If it stops healing and doesn't feel better, \ + come right back in to the clinic.\"\n\ + The doctor stitches you up, and applies various gels. It hurts, but you \ + persevere through the pain.\n"); + } + ctx.trans.queue_for_session(&sess, Some(&msg)).await?; + if skip_heal { + return Ok(None); + } + } + } + let mut who_mut = (*who).clone(); + run_effects( + &ctx.trans, + &vec![ + UseEffect::ChangeTargetHealth { + delay_secs: 0, + base_effect: 10, + skill_multiplier: 0.0, + max_effect: 10, + message: Box::new(|_item| { + ( + "That feels better".to_owned(), + "That feels better".to_owned(), + ) + }), + }, + UseEffect::ChangeTargetHealth { + delay_secs: 10, + base_effect: 9, + skill_multiplier: 0.0, + max_effect: 10, + message: Box::new(|_item| { + ( + "That feels better".to_owned(), + "That feels better".to_owned(), + ) + }), + }, + UseEffect::ChangeTargetHealth { + delay_secs: 20, + base_effect: 8, + skill_multiplier: 0.0, + max_effect: 10, + message: Box::new(|_item| { + ( + "That feels better".to_owned(), + "That feels better".to_owned(), + ) + }), + }, + UseEffect::ChangeTargetHealth { + delay_secs: 30, + base_effect: 7, + skill_multiplier: 0.0, + max_effect: 10, + message: Box::new(|_item| { + ( + "That feels better".to_owned(), + "That feels better".to_owned(), + ) + }), + }, + ], + &mut who_mut, + &who, + &mut None, + 0.0, + "bandages", + ) + .await?; + ctx.trans.save_item_model(&who_mut).await?; + + Ok(None) + } +} +pub static SEE_PATIENT_TASK: &'static (dyn TaskHandler + Sync + Send) = &SeePatientTaskHandler; + +pub fn room_list() -> Vec { + vec!( + Room { + zone: "general_hospital", + secondary_zones: vec!( + SecondaryZoneRecord { + zone: "melbs", + short: ansi!("++"), + grid_coords: GridCoords { x: 2, y: 10, z: 0 }, + caption: Some("General Hospital") + } + ), + code: "general_hospital_waiting_room", + name: "Emergency Waiting Room", + short: ansi!("WR"), + description: ansi!("A room apparently designed for patients to wait to seen by doctors. \ + Heavy-duty grey linoleum lines the floors, and even the tops of the walls, \ + while stainless steel metal strips cover the bottom of the walls. A line on \ + floor reserves an area of the floor for sick patients to be rushed past on \ + stretchers. It smells strongly of phenolic cleaners. At the front of the room \ + a triage nurse assures everyone coming in they will be assessed by doctors \ + a minute after arriving. Doctors pace the floor treating patients"), + description_less_explicit: None, + grid_coords: GridCoords { x: 0, y: 0, z: 0 }, + exits: vec!( + Exit { + direction: Direction::WEST, + target: ExitTarget::Custom("room/melbs_kingst_120"), + ..Default::default() + }, + ), + should_caption: true, + enter_trigger: Some(&ENTER_ER_TRIGGER), + ..Default::default() + }, + ) +} diff --git a/blastmud_game/src/static_content/room/melbs.rs b/blastmud_game/src/static_content/room/melbs.rs index a14360a..3abc8e0 100644 --- a/blastmud_game/src/static_content/room/melbs.rs +++ b/blastmud_game/src/static_content/room/melbs.rs @@ -517,7 +517,14 @@ pub fn room_list() -> Vec { }, Room { zone: "melbs", - secondary_zones: vec!(), + secondary_zones: vec![ + SecondaryZoneRecord { + zone: "general_hospital", + short: ansi!("EX"), + grid_coords: GridCoords { x: -1, y: 0, z: 0 }, + caption: Some("Melbs"), + } + ], code: "melbs_kingst_120", name: "King Street - 120 block", short: ansi!("||"), @@ -533,6 +540,11 @@ pub fn room_list() -> Vec { direction: Direction::SOUTH, ..Default::default() }, + Exit { + direction: Direction::EAST, + target: ExitTarget::Custom("room/general_hospital_waiting_room"), + ..Default::default() + }, ), should_caption: false, ..Default::default()