Add a hospital that can heal players who can't heal themselves

This commit is contained in:
Condorra 2023-09-26 22:33:41 +10:00
parent 92d7b22921
commit 90a6b3f31b
8 changed files with 274 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<BTreeMap<Direction, DoorState>>,
pub wristpad_hack_allowed: Option<WristpadHack>,
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<Room> {
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::*;

View 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()
},
)
}

View File

@ -517,7 +517,14 @@ pub fn room_list() -> Vec<Room> {
},
Room {
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",
name: "King Street - 120 block",
short: ansi!("<yellow>||<reset>"),
@ -533,6 +540,11 @@ pub fn room_list() -> Vec<Room> {
direction: Direction::SOUTH,
..Default::default()
},
Exit {
direction: Direction::EAST,
target: ExitTarget::Custom("room/general_hospital_waiting_room"),
..Default::default()
},
),
should_caption: false,
..Default::default()