Allow buying a lantern that lights up the sewer.
It will be rechargeable but that's not fully implemented yet.
This commit is contained in:
parent
6126bc984b
commit
15b96c1b50
@ -74,6 +74,7 @@ mod staff_show;
|
||||
pub mod stand;
|
||||
mod status;
|
||||
mod stop;
|
||||
mod turn;
|
||||
mod uninstall;
|
||||
pub mod use_cmd;
|
||||
mod vacate;
|
||||
@ -267,6 +268,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"status" => status::VERB,
|
||||
|
||||
"stop" => stop::VERB,
|
||||
"turn" => turn::VERB,
|
||||
"uninstall" => uninstall::VERB,
|
||||
"use" => use_cmd::VERB,
|
||||
"vacate" => vacate::VERB,
|
||||
|
73
blastmud_game/src/message_handler/user_commands/turn.rs
Normal file
73
blastmud_game/src/message_handler/user_commands/turn.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use crate::{db::ItemSearchParams, static_content::possession_type::possession_data};
|
||||
|
||||
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 player_item = get_player_item_or_fail(ctx).await?;
|
||||
if player_item.death_data.is_some() {
|
||||
user_error("You can't figure out to turn things on/off whilst dead.".to_owned())?;
|
||||
}
|
||||
|
||||
let (state, item_name) = remaining.split_once(" ").ok_or_else(|| {
|
||||
UserError(
|
||||
ansi!("Try <bold>turn on something<reset> or <bold>turn off something<reset>.")
|
||||
.to_owned(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let state = state.trim().to_lowercase();
|
||||
let to_state = if state == "on" {
|
||||
true
|
||||
} else if state == "off" {
|
||||
false
|
||||
} else {
|
||||
user_error(
|
||||
ansi!("Try <bold>turn on something<reset> or <bold>turn off something<reset>.")
|
||||
.to_owned(),
|
||||
)?
|
||||
};
|
||||
|
||||
let item_name = item_name.trim();
|
||||
|
||||
let item = search_item_for_user(
|
||||
ctx,
|
||||
&ItemSearchParams {
|
||||
include_contents: true,
|
||||
include_loc_contents: true,
|
||||
limit: 1,
|
||||
..ItemSearchParams::base(&player_item, item_name)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let handler = item
|
||||
.possession_type
|
||||
.as_ref()
|
||||
.and_then(|pt| possession_data().get(pt))
|
||||
.and_then(|pd| pd.turn_toggle_handler)
|
||||
.ok_or_else(|| {
|
||||
UserError(format!(
|
||||
"You can't turn that {}!",
|
||||
if to_state { "on" } else { "off" }
|
||||
))
|
||||
})?;
|
||||
handler.turn_cmd(ctx, &player_item, &item, to_state).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -73,6 +73,12 @@ pub enum TaskDetails {
|
||||
item: String,
|
||||
code: String,
|
||||
},
|
||||
DischargeLight {
|
||||
item: String,
|
||||
},
|
||||
ChargeItem {
|
||||
item: String,
|
||||
},
|
||||
}
|
||||
impl TaskDetails {
|
||||
pub fn name(self: &Self) -> &'static str {
|
||||
@ -99,6 +105,8 @@ impl TaskDetails {
|
||||
ResetHanoi => "ResetHanoi",
|
||||
HospitalERSeePatient { .. } => "HospitalERSeePatient",
|
||||
ExpireBuff { .. } => "ExpireBuff",
|
||||
DischargeLight { .. } => "DischargeLight",
|
||||
ChargeItem { .. } => "ChargeItem",
|
||||
// Don't forget to add to TASK_HANDLER_REGISTRY in regular_tasks.rs too.
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,10 @@ use crate::{
|
||||
listener::{ListenerMap, ListenerSend},
|
||||
message_handler::user_commands::{delete, drop, hire, open, rent},
|
||||
models::task::Task,
|
||||
services::{combat, effect, sharing, spawn, tempbuff, urges},
|
||||
services::{charging, combat, effect, sharing, spawn, tempbuff, urges},
|
||||
static_content::{
|
||||
npc::{self, computer_museum_npcs},
|
||||
possession_type::lights,
|
||||
room::general_hospital,
|
||||
},
|
||||
DResult,
|
||||
@ -68,6 +69,8 @@ fn task_handler_registry(
|
||||
("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER),
|
||||
("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK),
|
||||
("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK),
|
||||
("DischargeLight", lights::DISCHARGE_TASK),
|
||||
("ChargeItem", charging::TASK_HANDLER),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
|
@ -17,6 +17,7 @@ use crate::{
|
||||
use mockall_double::double;
|
||||
|
||||
pub mod capacity;
|
||||
pub mod charging;
|
||||
pub mod combat;
|
||||
pub mod comms;
|
||||
pub mod display;
|
||||
|
72
blastmud_game/src/services/charging.rs
Normal file
72
blastmud_game/src/services/charging.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
|
||||
use crate::{
|
||||
models::task::TaskDetails,
|
||||
regular_tasks::{TaskHandler, TaskRunContext},
|
||||
static_content::possession_type::possession_data,
|
||||
DResult,
|
||||
};
|
||||
use std::time;
|
||||
|
||||
use super::comms::broadcast_to_room;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RechargeTaskHandler;
|
||||
#[async_trait]
|
||||
impl TaskHandler for RechargeTaskHandler {
|
||||
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||
let item_ref = match &ctx.task.details {
|
||||
TaskDetails::ChargeItem { ref item } => item,
|
||||
t => {
|
||||
warn!("RechargeTaskHandler got unexpected task {:#?}", t);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let (item_type, item_code) = match item_ref.split_once("/") {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
warn!("RechargeTaskHandler got unexpected item ref {}", item_ref);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let item = match ctx
|
||||
.trans
|
||||
.find_item_by_type_code(item_type, item_code)
|
||||
.await?
|
||||
{
|
||||
None => return Ok(None),
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
let charge_data = match item
|
||||
.possession_type
|
||||
.as_ref()
|
||||
.and_then(|pt| possession_data().get(pt))
|
||||
.and_then(|pd| pd.charge_data.as_ref())
|
||||
{
|
||||
None => return Ok(None),
|
||||
Some(v) => v,
|
||||
};
|
||||
let mut item = (*item).clone();
|
||||
|
||||
item.charges = (item.charges + 1).min(charge_data.max_charges);
|
||||
ctx.trans.save_item_model(&item).await?;
|
||||
if item.charges == charge_data.max_charges {
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&item.location,
|
||||
None,
|
||||
&format!(
|
||||
"{} beeps to indicate it is fully charged.\n",
|
||||
item.display_for_sentence(1, true)
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(time::Duration::from_secs(60)))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub static TASK_HANDLER: &(dyn TaskHandler + Sync + Send) = &RechargeTaskHandler;
|
@ -28,6 +28,7 @@ mod fangs;
|
||||
mod food;
|
||||
pub mod head_armour;
|
||||
mod junk;
|
||||
pub mod lights;
|
||||
pub mod lock;
|
||||
pub mod lower_armour;
|
||||
mod meat;
|
||||
@ -441,6 +442,8 @@ pub enum PossessionType {
|
||||
// Recipes
|
||||
CulinaryEssentials,
|
||||
GrilledSteakRecipe,
|
||||
// Lights
|
||||
ElectricLantern,
|
||||
}
|
||||
|
||||
impl Into<Item> for PossessionType {
|
||||
@ -561,6 +564,7 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, &'static Possessio
|
||||
.map(|v| ((*v).0.clone(), &(*v).1)),
|
||||
)
|
||||
.chain(books::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.chain(lights::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
181
blastmud_game/src/static_content/possession_type/lights.rs
Normal file
181
blastmud_game/src/static_content/possession_type/lights.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::{
|
||||
message_handler::user_commands::{user_error, UResult, VerbContext},
|
||||
models::{
|
||||
item::{Item, ItemFlag, ItemSpecialData},
|
||||
task::{Task, TaskDetails, TaskMeta},
|
||||
},
|
||||
regular_tasks::{TaskHandler, TaskRunContext},
|
||||
services::comms::broadcast_to_room,
|
||||
static_content::possession_type::ChargeData,
|
||||
DResult,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use log::warn;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::time;
|
||||
|
||||
use super::{PossessionData, PossessionType, TurnToggleHandler};
|
||||
|
||||
pub struct LanternHandler {}
|
||||
|
||||
#[async_trait]
|
||||
impl TurnToggleHandler for LanternHandler {
|
||||
async fn turn_cmd(
|
||||
&self,
|
||||
ctx: &mut VerbContext,
|
||||
player: &Item,
|
||||
what: &Item,
|
||||
turn_on: bool,
|
||||
) -> UResult<()> {
|
||||
let mut item_mut = (*what).clone();
|
||||
let old_state = match item_mut.special_data.as_ref() {
|
||||
Some(ItemSpecialData::ToggleData { turned_on }) => *turned_on,
|
||||
_ => false,
|
||||
};
|
||||
if old_state == turn_on {
|
||||
user_error(format!(
|
||||
"It's already turned {}!",
|
||||
if turn_on { "on" } else { "off" }
|
||||
))?;
|
||||
}
|
||||
if turn_on && item_mut.charges <= 0 {
|
||||
user_error("It won't turn on! It probably needs charging.".to_owned())?;
|
||||
}
|
||||
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&player.location,
|
||||
None,
|
||||
&format!(
|
||||
"{} flicks {} a lantern.\n",
|
||||
&player.display_for_sentence(1, true),
|
||||
if turn_on { "on" } else { "off" }
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
item_mut.special_data = Some(ItemSpecialData::ToggleData { turned_on: turn_on });
|
||||
if turn_on {
|
||||
ctx.trans
|
||||
.upsert_task(&Task {
|
||||
meta: TaskMeta {
|
||||
task_code: item_mut.refstr(),
|
||||
next_scheduled: Utc::now() + chrono::Duration::minutes(5),
|
||||
..Default::default()
|
||||
},
|
||||
details: TaskDetails::DischargeLight {
|
||||
item: item_mut.refstr(),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
item_mut.flags.push(ItemFlag::IlluminatesRoom);
|
||||
// To make it a bit harder to use well-timed periods of darkness to
|
||||
// avoid drawing any charge, use one charge to turn it on (but if they
|
||||
// had exactly one left, don't immediately turn it off).
|
||||
item_mut.charges -= 1;
|
||||
} else {
|
||||
ctx.trans
|
||||
.delete_task("DischargeLight", &item_mut.refstr())
|
||||
.await?;
|
||||
item_mut.flags.retain(|f| f != &ItemFlag::IlluminatesRoom);
|
||||
}
|
||||
ctx.trans.save_item_model(&item_mut).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static LANTERN_HANDLER: LanternHandler = LanternHandler {};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DischargeTaskHandler;
|
||||
#[async_trait]
|
||||
impl TaskHandler for DischargeTaskHandler {
|
||||
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
|
||||
let item_ref = match &ctx.task.details {
|
||||
TaskDetails::DischargeLight { ref item } => item,
|
||||
t => {
|
||||
warn!("DischargeTaskHandler got unexpected task {:#?}", t);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let (item_type, item_code) = match item_ref.split_once("/") {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
warn!("DischargeTaskHandler got unexpected item ref {}", item_ref);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let item = match ctx
|
||||
.trans
|
||||
.find_item_by_type_code(item_type, item_code)
|
||||
.await?
|
||||
{
|
||||
None => return Ok(None),
|
||||
Some(v) => v,
|
||||
};
|
||||
let mut item = (*item).clone();
|
||||
item.charges = item.charges.max(1) - 1;
|
||||
let lit_location = match item.location.split_once("/") {
|
||||
Some((loc_type, loc_code)) if loc_type == "player" || loc_type == "npc" => {
|
||||
match ctx.trans.find_item_by_type_code(loc_type, loc_code).await? {
|
||||
None => item.location.clone(),
|
||||
Some(pl) => pl.location.clone(),
|
||||
}
|
||||
}
|
||||
_ => item.location.clone(),
|
||||
};
|
||||
if item.charges == 1 {
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&lit_location,
|
||||
None,
|
||||
&format!(
|
||||
"{} dims noticeably as if it was running out of power.\n",
|
||||
item.display_for_sentence(1, true)
|
||||
),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
if item.charges == 0 {
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&lit_location,
|
||||
None,
|
||||
&format!(
|
||||
"{} dims briefly before completely going out.\n",
|
||||
item.display_for_sentence(1, true)
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
item.flags.retain(|fl| {
|
||||
fl != &ItemFlag::IlluminatesRoom && fl != &ItemFlag::IlluminatesAdjacentRoom
|
||||
});
|
||||
}
|
||||
ctx.trans.save_item_model(&item).await?;
|
||||
if item.charges == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(time::Duration::from_secs(300)))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub static DISCHARGE_TASK: &(dyn TaskHandler + Sync + Send) = &DischargeTaskHandler;
|
||||
|
||||
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||
static D: OnceCell<Vec<(PossessionType, PossessionData)>> = OnceCell::new();
|
||||
&D.get_or_init(|| vec![(PossessionType::ElectricLantern, PossessionData {
|
||||
display: "electric lantern",
|
||||
aliases: vec!["lantern"],
|
||||
details: "A rechargeable electric lantern. It is made of yellow plastic, interspersed with banks of tiny flat white LEDs. It looks like it would illuminate a dark room well, although it probably isn't bright enough to let you see anything in the next room over.",
|
||||
weight: 300,
|
||||
turn_toggle_handler: Some(&LANTERN_HANDLER),
|
||||
charge_data: Some(ChargeData {
|
||||
max_charges: 20,
|
||||
charge_name_prefix: "bar",
|
||||
charge_name_suffix: "of power",
|
||||
}),
|
||||
..Default::default()
|
||||
})])
|
||||
}
|
@ -1624,6 +1624,9 @@
|
||||
- possession_type: DrinkBottle
|
||||
list_price: 80
|
||||
poverty_discount: false
|
||||
- possession_type: ElectricLantern
|
||||
list_price: 500
|
||||
poverty_discount: false
|
||||
scavtable: CityStreet
|
||||
- zone: melbs
|
||||
code: melbs_bourkest_190
|
||||
|
Loading…
Reference in New Issue
Block a user