Add a hospital that can heal players who can't heal themselves
This commit is contained in:
		
							parent
							
								
									92d7b22921
								
							
						
					
					
						commit
						90a6b3f31b
					
				| @ -93,6 +93,7 @@ async fn reset_stats(ctx: &mut VerbContext<'_>) -> UResult<()> { | |||||||
|     user_dat.experience.xp_change_for_this_reroll = 0; |     user_dat.experience.xp_change_for_this_reroll = 0; | ||||||
|     user_dat.raw_stats = BTreeMap::new(); |     user_dat.raw_stats = BTreeMap::new(); | ||||||
|     user_dat.raw_skills = BTreeMap::new(); |     user_dat.raw_skills = BTreeMap::new(); | ||||||
|  |     user_dat.wristpad_hacks = vec![]; | ||||||
|     calculate_total_stats_skills_for_user(&mut player_item, &user_dat); |     calculate_total_stats_skills_for_user(&mut player_item, &user_dat); | ||||||
|     ctx.trans.save_user_model(&user_dat).await?; |     ctx.trans.save_user_model(&user_dat).await?; | ||||||
|     ctx.trans.save_item_model(&player_item).await?; |     ctx.trans.save_item_model(&player_item).await?; | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ use crate::{ | |||||||
|     static_content::{ |     static_content::{ | ||||||
|         dynzone::{dynzone_by_type, DynzoneType, ExitTarget as DynExitTarget}, |         dynzone::{dynzone_by_type, DynzoneType, ExitTarget as DynExitTarget}, | ||||||
|         npc::check_for_instant_aggro, |         npc::check_for_instant_aggro, | ||||||
|         room::{self, Direction, ExitClimb, ExitType, MaterialType}, |         room::{self, check_for_enter_action, Direction, ExitClimb, ExitType, MaterialType}, | ||||||
|     }, |     }, | ||||||
|     DResult, |     DResult, | ||||||
| }; | }; | ||||||
| @ -659,6 +659,7 @@ async fn attempt_move_immediate( | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     check_for_instant_aggro(&ctx.trans, &mut ctx.item).await?; |     check_for_instant_aggro(&ctx.trans, &mut ctx.item).await?; | ||||||
|  |     check_for_enter_action(ctx).await?; | ||||||
| 
 | 
 | ||||||
|     Ok(true) |     Ok(true) | ||||||
| } | } | ||||||
|  | |||||||
| @ -55,6 +55,9 @@ pub enum TaskDetails { | |||||||
|     TickUrges, |     TickUrges, | ||||||
|     ResetSpawns, |     ResetSpawns, | ||||||
|     ResetHanoi, |     ResetHanoi, | ||||||
|  |     HospitalERSeePatient { | ||||||
|  |         item: String, | ||||||
|  |     }, | ||||||
| } | } | ||||||
| impl TaskDetails { | impl TaskDetails { | ||||||
|     pub fn name(self: &Self) -> &'static str { |     pub fn name(self: &Self) -> &'static str { | ||||||
| @ -76,6 +79,7 @@ impl TaskDetails { | |||||||
|             TickUrges => "TickUrges", |             TickUrges => "TickUrges", | ||||||
|             ResetSpawns => "ResetSpawns", |             ResetSpawns => "ResetSpawns", | ||||||
|             ResetHanoi => "ResetHanoi", |             ResetHanoi => "ResetHanoi", | ||||||
|  |             HospitalERSeePatient { .. } => "HospitalERSeePatient", | ||||||
|             // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
 |             // Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -10,7 +10,10 @@ use crate::{ | |||||||
|     message_handler::user_commands::{delete, drop, hire, open, rent}, |     message_handler::user_commands::{delete, drop, hire, open, rent}, | ||||||
|     models::task::Task, |     models::task::Task, | ||||||
|     services::{combat, effect, spawn, urges}, |     services::{combat, effect, spawn, urges}, | ||||||
|     static_content::npc::{self, computer_museum_npcs}, |     static_content::{ | ||||||
|  |         npc::{self, computer_museum_npcs}, | ||||||
|  |         room::general_hospital, | ||||||
|  |     }, | ||||||
|     DResult, |     DResult, | ||||||
| }; | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| @ -63,6 +66,10 @@ fn task_handler_registry( | |||||||
|                 "ResetHanoi", |                 "ResetHanoi", | ||||||
|                 computer_museum_npcs::RESET_GAME_HANDLER.clone(), |                 computer_museum_npcs::RESET_GAME_HANDLER.clone(), | ||||||
|             ), |             ), | ||||||
|  |             ( | ||||||
|  |                 "HospitalERSeePatient", | ||||||
|  |                 general_hospital::SEE_PATIENT_TASK.clone(), | ||||||
|  |             ), | ||||||
|         ] |         ] | ||||||
|         .into_iter() |         .into_iter() | ||||||
|         .collect() |         .collect() | ||||||
|  | |||||||
| @ -354,7 +354,7 @@ pub async fn recalculate_urge_growth(_trans: &DBTrans, item: &mut Item) -> DResu | |||||||
|             ..old_urges.thirst |             ..old_urges.thirst | ||||||
|         }, // To do: climate based?
 |         }, // To do: climate based?
 | ||||||
|         stress: Urge { |         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 |             ..old_urges.stress | ||||||
|         }, |         }, | ||||||
|     }); |     }); | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ use std::collections::{BTreeMap, BTreeSet}; | |||||||
| mod chonkers; | mod chonkers; | ||||||
| mod cok_murl; | mod cok_murl; | ||||||
| pub mod computer_museum; | pub mod computer_museum; | ||||||
|  | pub mod general_hospital; | ||||||
| mod melbs; | mod melbs; | ||||||
| mod repro_xv; | mod repro_xv; | ||||||
| mod special; | mod special; | ||||||
| @ -63,6 +64,11 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> { | |||||||
|                 display: "Computer Museum", |                 display: "Computer Museum", | ||||||
|                 outdoors: false, |                 outdoors: false, | ||||||
|             }, |             }, | ||||||
|  |             Zone { | ||||||
|  |                 code: "general_hospital", | ||||||
|  |                 display: "General Hospital", | ||||||
|  |                 outdoors: false, | ||||||
|  |             }, | ||||||
|         ] |         ] | ||||||
|         .into_iter() |         .into_iter() | ||||||
|         .map(|x| (x.code, x)) |         .map(|x| (x.code, x)) | ||||||
| @ -317,6 +323,11 @@ pub enum MaterialType { | |||||||
|     Soft { damage_modifier: f64 }, |     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 struct Room { | ||||||
|     pub zone: &'static str, |     pub zone: &'static str, | ||||||
|     // Other zones where it can be seen on the map and accessed.
 |     // Other zones where it can be seen on the map and accessed.
 | ||||||
| @ -339,6 +350,7 @@ pub struct Room { | |||||||
|     pub has_power: bool, |     pub has_power: bool, | ||||||
|     pub door_states: Option<BTreeMap<Direction, DoorState>>, |     pub door_states: Option<BTreeMap<Direction, DoorState>>, | ||||||
|     pub wristpad_hack_allowed: Option<WristpadHack>, |     pub wristpad_hack_allowed: Option<WristpadHack>, | ||||||
|  |     pub enter_trigger: Option<&'static (dyn RoomEnterTrigger + Sync + Send)>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for Room { | impl Default for Room { | ||||||
| @ -362,6 +374,7 @@ impl Default for Room { | |||||||
|             has_power: false, |             has_power: false, | ||||||
|             door_states: None, |             door_states: None, | ||||||
|             wristpad_hack_allowed: None, |             wristpad_hack_allowed: None, | ||||||
|  |             enter_trigger: None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -375,6 +388,7 @@ pub fn room_list() -> &'static Vec<Room> { | |||||||
|         rooms.append(&mut chonkers::room_list()); |         rooms.append(&mut chonkers::room_list()); | ||||||
|         rooms.append(&mut special::room_list()); |         rooms.append(&mut special::room_list()); | ||||||
|         rooms.append(&mut computer_museum::room_list()); |         rooms.append(&mut computer_museum::room_list()); | ||||||
|  |         rooms.append(&mut general_hospital::room_list()); | ||||||
|         rooms.into_iter().collect() |         rooms.into_iter().collect() | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
| @ -488,6 +502,23 @@ pub async fn refresh_room_exits(trans: &DBTrans, template: &Item) -> DResult<()> | |||||||
|     Ok(()) |     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)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|     use super::*; |     use super::*; | ||||||
|  | |||||||
							
								
								
									
										214
									
								
								blastmud_game/src/static_content/room/general_hospital.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								blastmud_game/src/static_content/room/general_hospital.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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!( | ||||||
|  |                         "<yellow>The triage nurse says: <reset><bold>\ | ||||||
|  |                             \"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.\"<reset>\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<Option<time::Duration>> { | ||||||
|  |         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!( | ||||||
|  |                     "<yellow>The doctor says: <reset><bold>\ | ||||||
|  |                      \"Stay still while I take a close look at what might be wrong.\"<reset>\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!("<yellow>The doctor says: <reset><bold>\"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.\"<reset>\n"); | ||||||
|  |                     skip_heal = true; | ||||||
|  |                 } else { | ||||||
|  |                     msg += ansi!("<yellow>The doctor says: <reset><bold>\"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.\"<reset>\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<Room> { | ||||||
|  |     vec!( | ||||||
|  |         Room { | ||||||
|  |             zone: "general_hospital", | ||||||
|  |             secondary_zones: vec!( | ||||||
|  |                 SecondaryZoneRecord { | ||||||
|  |                     zone: "melbs", | ||||||
|  |                     short: ansi!("<bgwhite><red>++<reset>"), | ||||||
|  |                     grid_coords: GridCoords { x: 2, y: 10, z: 0 }, | ||||||
|  |                     caption: Some("General Hospital") | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             code: "general_hospital_waiting_room", | ||||||
|  |             name: "Emergency Waiting Room", | ||||||
|  |             short: ansi!("<bgwhite><red>WR<reset>"), | ||||||
|  |             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() | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  | } | ||||||
| @ -517,7 +517,14 @@ pub fn room_list() -> Vec<Room> { | |||||||
|       }, |       }, | ||||||
|       Room { |       Room { | ||||||
|           zone: "melbs", |           zone: "melbs", | ||||||
|           secondary_zones: vec!(), |           secondary_zones: vec![ | ||||||
|  |               SecondaryZoneRecord { | ||||||
|  |                   zone: "general_hospital", | ||||||
|  |                   short: ansi!("<bggreen><white>EX<reset>"), | ||||||
|  |                   grid_coords: GridCoords { x: -1, y: 0, z: 0 }, | ||||||
|  |                   caption: Some("Melbs"), | ||||||
|  |               } | ||||||
|  |           ], | ||||||
|           code: "melbs_kingst_120", |           code: "melbs_kingst_120", | ||||||
|           name: "King Street - 120 block", |           name: "King Street - 120 block", | ||||||
|           short: ansi!("<yellow>||<reset>"), |           short: ansi!("<yellow>||<reset>"), | ||||||
| @ -533,6 +540,11 @@ pub fn room_list() -> Vec<Room> { | |||||||
|                   direction: Direction::SOUTH, |                   direction: Direction::SOUTH, | ||||||
|                   ..Default::default() |                   ..Default::default() | ||||||
|               }, |               }, | ||||||
|  |               Exit { | ||||||
|  |                   direction: Direction::EAST, | ||||||
|  |                   target: ExitTarget::Custom("room/general_hospital_waiting_room"), | ||||||
|  |                   ..Default::default() | ||||||
|  |               }, | ||||||
|           ), |           ), | ||||||
|           should_caption: false, |           should_caption: false, | ||||||
|           ..Default::default() |           ..Default::default() | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user