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;
|
pub mod stand;
|
||||||
mod status;
|
mod status;
|
||||||
mod stop;
|
mod stop;
|
||||||
|
mod turn;
|
||||||
mod uninstall;
|
mod uninstall;
|
||||||
pub mod use_cmd;
|
pub mod use_cmd;
|
||||||
mod vacate;
|
mod vacate;
|
||||||
@ -267,6 +268,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
"status" => status::VERB,
|
"status" => status::VERB,
|
||||||
|
|
||||||
"stop" => stop::VERB,
|
"stop" => stop::VERB,
|
||||||
|
"turn" => turn::VERB,
|
||||||
"uninstall" => uninstall::VERB,
|
"uninstall" => uninstall::VERB,
|
||||||
"use" => use_cmd::VERB,
|
"use" => use_cmd::VERB,
|
||||||
"vacate" => vacate::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,
|
item: String,
|
||||||
code: String,
|
code: String,
|
||||||
},
|
},
|
||||||
|
DischargeLight {
|
||||||
|
item: String,
|
||||||
|
},
|
||||||
|
ChargeItem {
|
||||||
|
item: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
impl TaskDetails {
|
impl TaskDetails {
|
||||||
pub fn name(self: &Self) -> &'static str {
|
pub fn name(self: &Self) -> &'static str {
|
||||||
@ -99,6 +105,8 @@ impl TaskDetails {
|
|||||||
ResetHanoi => "ResetHanoi",
|
ResetHanoi => "ResetHanoi",
|
||||||
HospitalERSeePatient { .. } => "HospitalERSeePatient",
|
HospitalERSeePatient { .. } => "HospitalERSeePatient",
|
||||||
ExpireBuff { .. } => "ExpireBuff",
|
ExpireBuff { .. } => "ExpireBuff",
|
||||||
|
DischargeLight { .. } => "DischargeLight",
|
||||||
|
ChargeItem { .. } => "ChargeItem",
|
||||||
// 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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,10 @@ use crate::{
|
|||||||
listener::{ListenerMap, ListenerSend},
|
listener::{ListenerMap, ListenerSend},
|
||||||
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, sharing, spawn, tempbuff, urges},
|
services::{charging, combat, effect, sharing, spawn, tempbuff, urges},
|
||||||
static_content::{
|
static_content::{
|
||||||
npc::{self, computer_museum_npcs},
|
npc::{self, computer_museum_npcs},
|
||||||
|
possession_type::lights,
|
||||||
room::general_hospital,
|
room::general_hospital,
|
||||||
},
|
},
|
||||||
DResult,
|
DResult,
|
||||||
@ -68,6 +69,8 @@ fn task_handler_registry(
|
|||||||
("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER),
|
("ResetHanoi", computer_museum_npcs::RESET_GAME_HANDLER),
|
||||||
("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK),
|
("HospitalERSeePatient", general_hospital::SEE_PATIENT_TASK),
|
||||||
("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK),
|
("ExpireBuff", tempbuff::EXPIRE_BUFF_TASK),
|
||||||
|
("DischargeLight", lights::DISCHARGE_TASK),
|
||||||
|
("ChargeItem", charging::TASK_HANDLER),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -17,6 +17,7 @@ use crate::{
|
|||||||
use mockall_double::double;
|
use mockall_double::double;
|
||||||
|
|
||||||
pub mod capacity;
|
pub mod capacity;
|
||||||
|
pub mod charging;
|
||||||
pub mod combat;
|
pub mod combat;
|
||||||
pub mod comms;
|
pub mod comms;
|
||||||
pub mod display;
|
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;
|
mod food;
|
||||||
pub mod head_armour;
|
pub mod head_armour;
|
||||||
mod junk;
|
mod junk;
|
||||||
|
pub mod lights;
|
||||||
pub mod lock;
|
pub mod lock;
|
||||||
pub mod lower_armour;
|
pub mod lower_armour;
|
||||||
mod meat;
|
mod meat;
|
||||||
@ -441,6 +442,8 @@ pub enum PossessionType {
|
|||||||
// Recipes
|
// Recipes
|
||||||
CulinaryEssentials,
|
CulinaryEssentials,
|
||||||
GrilledSteakRecipe,
|
GrilledSteakRecipe,
|
||||||
|
// Lights
|
||||||
|
ElectricLantern,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Item> for PossessionType {
|
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)),
|
.map(|v| ((*v).0.clone(), &(*v).1)),
|
||||||
)
|
)
|
||||||
.chain(books::data().iter().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()
|
.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
|
- possession_type: DrinkBottle
|
||||||
list_price: 80
|
list_price: 80
|
||||||
poverty_discount: false
|
poverty_discount: false
|
||||||
|
- possession_type: ElectricLantern
|
||||||
|
list_price: 500
|
||||||
|
poverty_discount: false
|
||||||
scavtable: CityStreet
|
scavtable: CityStreet
|
||||||
- zone: melbs
|
- zone: melbs
|
||||||
code: melbs_bourkest_190
|
code: melbs_bourkest_190
|
||||||
|
Loading…
Reference in New Issue
Block a user