Refactor to move command queue to item.

This is the start of being able to implement following in a way that
works for NPCs, but it isn't finished yet. It does mean NPCs can do
things like climb immediately, and will make it far simpler for NPCs
to do other player-like actions in the future.
This commit is contained in:
Condorra 2023-06-20 22:53:46 +10:00
parent 61b40a9000
commit 261151881d
30 changed files with 1985 additions and 1413 deletions

View File

@ -26,6 +26,7 @@ pub mod cut;
pub mod delete; pub mod delete;
mod describe; mod describe;
pub mod drop; pub mod drop;
mod follow;
mod gear; mod gear;
pub mod get; pub mod get;
mod help; mod help;
@ -146,6 +147,10 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
"cut" => cut::VERB, "cut" => cut::VERB,
"delete" => delete::VERB, "delete" => delete::VERB,
"drop" => drop::VERB, "drop" => drop::VERB,
"follow" => follow::VERB,
"unfollow" => follow::VERB,
"gear" => gear::VERB, "gear" => gear::VERB,
"get" => get::VERB, "get" => get::VERB,

View File

@ -67,6 +67,7 @@ impl UserVerb for Verb {
ensure_has_butcher_tool(&ctx.trans, &player_item).await?; ensure_has_butcher_tool(&ctx.trans, &player_item).await?;
let mut player_item_mut = (*player_item).clone();
for possession_type in possession_types { for possession_type in possession_types {
let possession_data = possession_data() let possession_data = possession_data()
.get(&possession_type) .get(&possession_type)
@ -74,6 +75,7 @@ impl UserVerb for Verb {
queue_command( queue_command(
ctx, ctx,
&mut player_item_mut,
&QueueCommand::Cut { &QueueCommand::Cut {
from_corpse: corpse.item_code.clone(), from_corpse: corpse.item_code.clone(),
what_part: possession_data.display.to_owned(), what_part: possession_data.display.to_owned(),
@ -81,6 +83,7 @@ impl UserVerb for Verb {
) )
.await?; .await?;
} }
ctx.trans.save_item_model(&player_item_mut).await?;
Ok(()) Ok(())
} }
} }

View File

@ -5,7 +5,9 @@ use super::{
}; };
use crate::{ use crate::{
models::item::DoorState, models::item::DoorState,
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::comms::broadcast_to_room, services::comms::broadcast_to_room,
static_content::room::Direction, static_content::room::Direction,
}; };
@ -15,20 +17,15 @@ use std::time;
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, let direction = match ctx.command {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let direction = match command {
QueueCommand::CloseDoor { direction } => direction, QueueCommand::CloseDoor { direction } => direction,
_ => user_error("Unexpected queued command".to_owned())?, _ => user_error("Unexpected queued command".to_owned())?,
}; };
let player_item = get_player_item_or_fail(ctx).await?; let use_location = if ctx.item.death_data.is_some() {
let use_location = if player_item.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())? user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else { } else {
&player_item.location &ctx.item.location
}; };
match is_door_in_direction(&ctx.trans, &direction, use_location).await? { match is_door_in_direction(&ctx.trans, &direction, use_location).await? {
DoorSituation::NoDoor => user_error("There is no door to close.".to_owned())?, DoorSituation::NoDoor => user_error("There is no door to close.".to_owned())?,
@ -47,20 +44,15 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, let direction = match ctx.command {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let direction = match command {
QueueCommand::CloseDoor { direction } => direction, QueueCommand::CloseDoor { direction } => direction,
_ => user_error("Unexpected queued command".to_owned())?, _ => user_error("Unexpected queued command".to_owned())?,
}; };
let player_item = get_player_item_or_fail(ctx).await?; let use_location = if ctx.item.death_data.is_some() {
let use_location = if player_item.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())? user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else { } else {
&player_item.location &ctx.item.location
}; };
let (room_1, dir_in_room, room_2) = let (room_1, dir_in_room, room_2) =
match is_door_in_direction(&ctx.trans, &direction, use_location).await? { match is_door_in_direction(&ctx.trans, &direction, use_location).await? {
@ -123,12 +115,12 @@ impl QueueCommandHandler for QueueHandler {
None, None,
&format!( &format!(
"{} closes the door to the {}.\n", "{} closes the door to the {}.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
dir dir
), ),
Some(&format!( Some(&format!(
"{} closes the door to the {}.\n", "{} closes the door to the {}.\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
dir dir
)), )),
) )
@ -157,8 +149,10 @@ impl UserVerb for Verb {
) -> UResult<()> { ) -> UResult<()> {
let dir = let dir =
Direction::parse(remaining).ok_or_else(|| UserError("Unknown direction".to_owned()))?; Direction::parse(remaining).ok_or_else(|| UserError("Unknown direction".to_owned()))?;
queue_command( let player_item = get_player_item_or_fail(ctx).await?;
queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::CloseDoor { &QueueCommand::CloseDoor {
direction: dir.clone(), direction: dir.clone(),
}, },

View File

@ -8,7 +8,9 @@ use crate::{
db::ItemSearchParams, db::ItemSearchParams,
language::join_words, language::join_words,
models::item::{DeathData, Item, SkillType}, models::item::{DeathData, Item, SkillType},
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{ services::{
capacity::{check_item_capacity, CapacityLevel}, capacity::{check_item_capacity, CapacityLevel},
combat::corpsify_item, combat::corpsify_item,
@ -26,18 +28,13 @@ use std::{sync::Arc, time};
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You butcher things while they are dead, not while YOU are dead!".to_owned(), "You butcher things while they are dead, not while YOU are dead!".to_owned(),
)?; )?;
} }
let (from_corpse_id, what_part) = match command { let (from_corpse_id, what_part) = match ctx.command {
QueueCommand::Cut { QueueCommand::Cut {
from_corpse, from_corpse,
what_part, what_part,
@ -52,13 +49,15 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("The corpse seems to be gone".to_owned())?, None => user_error("The corpse seems to be gone".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if corpse.location != player_item.location {
let explicit = ctx.explicit().await?;
if corpse.location != ctx.item.location {
user_error(format!( user_error(format!(
"You try to cut {} but realise it is no longer there.", "You try to cut {} but realise it is no longer there.",
corpse.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) corpse.display_for_sentence(explicit, 1, false)
))? ))?
} }
ensure_has_butcher_tool(&ctx.trans, &player_item).await?; ensure_has_butcher_tool(&ctx.trans, &ctx.item).await?;
match corpse.death_data.as_ref() { match corpse.death_data.as_ref() {
None => user_error(format!( None => user_error(format!(
"You can't do that while {} is still alive!", "You can't do that while {} is still alive!",
@ -74,7 +73,8 @@ impl QueueCommandHandler for QueueHandler {
== Some(true) == Some(true)
}) { }) {
user_error(format!( user_error(format!(
"That part is now gone. Parts you can cut: {}", "That part ({}) is now gone. Parts you can cut: {}",
&what_part,
&join_words( &join_words(
&parts_remaining &parts_remaining
.iter() .iter()
@ -89,19 +89,19 @@ impl QueueCommandHandler for QueueHandler {
let msg_exp = format!( let msg_exp = format!(
"{} prepares to cut {} from {}\n", "{} prepares to cut {} from {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&what_part, &what_part,
&corpse.display_for_sentence(true, 1, false) &corpse.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} prepares to cut {} from {}\n", "{} prepares to cut {} from {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&what_part, &what_part,
&corpse.display_for_sentence(false, 1, false) &corpse.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -111,25 +111,20 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You butcher things while they are dead, not while YOU are dead!".to_owned(), "You butcher things while they are dead, not while YOU are dead!".to_owned(),
)?; )?;
} }
let (from_corpse_id, what_part) = match command { let (from_corpse_id, what_part) = match ctx.command {
QueueCommand::Cut { QueueCommand::Cut {
from_corpse, from_corpse,
what_part, what_part,
} => (from_corpse, what_part), } => (from_corpse, what_part),
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
ensure_has_butcher_tool(&ctx.trans, &player_item).await?; ensure_has_butcher_tool(&ctx.trans, &ctx.item).await?;
let corpse = match ctx let corpse = match ctx
.trans .trans
.find_item_by_type_code("corpse", &from_corpse_id) .find_item_by_type_code("corpse", &from_corpse_id)
@ -138,10 +133,12 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("The corpse seems to be gone".to_owned())?, None => user_error("The corpse seems to be gone".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if corpse.location != player_item.location {
let explicit = ctx.explicit().await?;
if corpse.location != ctx.item.location {
user_error(format!( user_error(format!(
"You try to cut {} but realise it is no longer there.", "You try to cut {} but realise it is no longer there.",
corpse.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) corpse.display_for_sentence(explicit, 1, false)
))? ))?
} }
@ -197,8 +194,7 @@ impl QueueCommandHandler for QueueHandler {
} }
} }
match check_item_capacity(&ctx.trans, &player_item.refstr(), possession_data.weight).await? match check_item_capacity(&ctx.trans, &ctx.item.refstr(), possession_data.weight).await? {
{
CapacityLevel::AboveItemLimit | CapacityLevel::OverBurdened => { CapacityLevel::AboveItemLimit | CapacityLevel::OverBurdened => {
user_error("You have too much stuff to take that on!".to_owned())? user_error("You have too much stuff to take that on!".to_owned())?
} }
@ -216,23 +212,20 @@ impl QueueCommandHandler for QueueHandler {
ctx.trans.save_item_model(&corpse_mut).await?; ctx.trans.save_item_model(&corpse_mut).await?;
} }
let mut player_item_mut = (*player_item).clone(); if skill_check_and_grind(&ctx.trans, ctx.item, &SkillType::Craft, 10.0).await? < 0.0 {
if skill_check_and_grind(&ctx.trans, &mut player_item_mut, &SkillType::Craft, 10.0).await?
< 0.0
{
broadcast_to_room( broadcast_to_room(
&ctx.trans, &ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&format!( &format!(
"{} tries to cut the {} from {}, but only leaves a mutilated mess.\n", "{} tries to cut the {} from {}, but only leaves a mutilated mess.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
possession_data.display, possession_data.display,
corpse.display_for_sentence(true, 1, false) corpse.display_for_sentence(true, 1, false)
), ),
Some(&format!( Some(&format!(
"{} tries to cut the {} from {}, but only leaves a mutilated mess.\n", "{} tries to cut the {} from {}, but only leaves a mutilated mess.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
possession_data.display, possession_data.display,
corpse.display_for_sentence(true, 1, false) corpse.display_for_sentence(true, 1, false)
)), )),
@ -241,22 +234,22 @@ impl QueueCommandHandler for QueueHandler {
} else { } else {
let mut new_item: Item = (*possession_type).clone().into(); let mut new_item: Item = (*possession_type).clone().into();
new_item.item_code = format!("{}", ctx.trans.alloc_item_code().await?); new_item.item_code = format!("{}", ctx.trans.alloc_item_code().await?);
new_item.location = player_item.refstr(); new_item.location = ctx.item.refstr();
ctx.trans.save_item_model(&new_item).await?; ctx.trans.save_item_model(&new_item).await?;
broadcast_to_room( broadcast_to_room(
&ctx.trans, &ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&format!( &format!(
"{} expertly cuts the {} from {}.\n", "{} expertly cuts the {} from {}.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
possession_data.display, possession_data.display,
corpse.display_for_sentence(true, 1, false) corpse.display_for_sentence(true, 1, false)
), ),
Some(&format!( Some(&format!(
"{} expertly cuts the {} from {}.\n", "{} expertly cuts the {} from {}.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
possession_data.display, possession_data.display,
corpse.display_for_sentence(true, 1, false) corpse.display_for_sentence(true, 1, false)
)), )),
@ -264,8 +257,6 @@ impl QueueCommandHandler for QueueHandler {
.await?; .await?;
} }
ctx.trans.save_item_model(&player_item_mut).await?;
Ok(()) Ok(())
} }
} }
@ -367,8 +358,9 @@ impl UserVerb for Verb {
ensure_has_butcher_tool(&ctx.trans, &player_item).await?; ensure_has_butcher_tool(&ctx.trans, &player_item).await?;
queue_command( queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::Cut { &QueueCommand::Cut {
from_corpse: corpse.item_code.clone(), from_corpse: corpse.item_code.clone(),
what_part: possession_data.display.to_owned(), what_part: possession_data.display.to_owned(),

View File

@ -10,7 +10,7 @@ use crate::{
task::{Task, TaskDetails, TaskMeta}, task::{Task, TaskDetails, TaskMeta},
}, },
regular_tasks::{ regular_tasks::{
queued_command::{queue_command, QueueCommand, QueueCommandHandler}, queued_command::{queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext},
TaskHandler, TaskRunContext, TaskHandler, TaskRunContext,
}, },
services::{ services::{
@ -107,18 +107,13 @@ pub async fn consider_expire_job_for_item(trans: &DBTrans, item: &Item) -> DResu
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to drop it, but your ghostly hands slip through it uselessly".to_owned(), "You try to drop it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Drop { possession_id } => possession_id, QueueCommand::Drop { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -130,10 +125,10 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("{}/{}", &player_item.item_type, &player_item.item_code) { if item.location != format!("{}/{}", &ctx.item.item_type, &ctx.item.item_code) {
user_error(format!( user_error(format!(
"You try to drop {} but realise you no longer have it", "You try to drop {} but realise you no longer have it",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
if item.action_type == LocationActionType::Worn { if item.action_type == LocationActionType::Worn {
@ -143,17 +138,17 @@ impl QueueCommandHandler for QueueHandler {
} }
let msg_exp = format!( let msg_exp = format!(
"{} prepares to drop {}\n", "{} prepares to drop {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} prepares to drop {}\n", "{} prepares to drop {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -163,18 +158,13 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to get it, but your ghostly hands slip through it uselessly".to_owned(), "You try to get it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Drop { possession_id } => possession_id, QueueCommand::Drop { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -187,10 +177,10 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("{}/{}", &player_item.item_type, &player_item.item_code) { if item.location != format!("{}/{}", &ctx.item.item_type, &ctx.item.item_code) {
user_error(format!( user_error(format!(
"You try to drop {} but realise you no longer have it!", "You try to drop {} but realise you no longer have it!",
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) &item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
if item.action_type == LocationActionType::Worn { if item.action_type == LocationActionType::Worn {
@ -210,34 +200,34 @@ impl QueueCommandHandler for QueueHandler {
Some(pd) => pd, Some(pd) => pd,
}; };
match check_item_capacity(ctx.trans, &player_item.location, possession_data.weight).await? { match check_item_capacity(ctx.trans, &ctx.item.location, possession_data.weight).await? {
CapacityLevel::AboveItemLimit => user_error(format!( CapacityLevel::AboveItemLimit => user_error(format!(
"You can't drop {}, because it is so cluttered here there is no where to put it!", "You can't drop {}, because it is so cluttered here there is no where to put it!",
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) &item.display_for_sentence(ctx.explicit().await?, 1, false)
))?, ))?,
_ => (), _ => (),
} }
let msg_exp = format!( let msg_exp = format!(
"{} drops {}\n", "{} drops {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} drops {}\n", "{} drops {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
) )
.await?; .await?;
let mut item_mut = (*item).clone(); let mut item_mut = (*item).clone();
item_mut.location = player_item.location.clone(); item_mut.location = ctx.item.location.clone();
consider_expire_job_for_item(ctx.trans, &item_mut).await?; consider_expire_job_for_item(ctx.trans, &item_mut).await?;
item_mut.action_type = LocationActionType::Normal; item_mut.action_type = LocationActionType::Normal;
ctx.trans.save_item_model(&item_mut).await?; ctx.trans.save_item_model(&item_mut).await?;
@ -281,18 +271,21 @@ impl UserVerb for Verb {
)?; )?;
} }
let mut player_item_mut = (*player_item).clone();
for target in targets { for target in targets {
if target.item_type != "possession" { if target.item_type != "possession" {
user_error("You can't drop that!".to_owned())?; user_error("You can't drop that!".to_owned())?;
} }
queue_command( queue_command(
ctx, ctx,
&mut player_item_mut,
&QueueCommand::Drop { &QueueCommand::Drop {
possession_id: target.item_code.clone(), possession_id: target.item_code.clone(),
}, },
) )
.await?; .await?;
} }
ctx.trans.save_item_model(&player_item_mut).await?;
Ok(()) Ok(())
} }
} }

View File

@ -0,0 +1,46 @@
use super::{
get_player_item_or_fail, search_item_for_user, user_error, UResult, UserVerb, UserVerbRef,
VerbContext,
};
use crate::db::ItemSearchParams;
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?)).clone();
if verb == "follow" {
let follow_who = search_item_for_user(
ctx,
&ItemSearchParams {
include_loc_contents: true,
..ItemSearchParams::base(&player_item, remaining.trim())
},
)
.await?;
if follow_who.item_type != "player" && follow_who.item_type != "npc" {
user_error("Only characters (player / NPC) can be followed.".to_owned())?;
}
if let Some(follow) = player_item.following.as_ref() {
if follow.follow_whom == player_item.refstr() {
user_error(format!(
"You're already following {}!",
follow_who.pronouns.possessive
))?;
}
// sess_dets.queue.filter()
}
} else {
}
user_error("Sorry, we're still building the follow command!".to_owned())?;
Ok(())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;

View File

@ -4,7 +4,9 @@ use super::{
}; };
use crate::{ use crate::{
models::item::LocationActionType, models::item::LocationActionType,
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{ services::{
capacity::{check_item_capacity, CapacityLevel}, capacity::{check_item_capacity, CapacityLevel},
comms::broadcast_to_room, comms::broadcast_to_room,
@ -17,18 +19,13 @@ use std::time;
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to get it, but your ghostly hands slip through it uselessly".to_owned(), "You try to get it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Get { possession_id } => possession_id, QueueCommand::Get { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -40,25 +37,25 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.location { if item.location != ctx.item.location {
user_error(format!( user_error(format!(
"You try to get {} but realise it is no longer there", "You try to get {} but realise it is no longer there",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
let msg_exp = format!( let msg_exp = format!(
"{} fumbles around trying to pick up {}\n", "{} fumbles around trying to pick up {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} fumbles around trying to pick up {}\n", "{} fumbles around trying to pick up {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -68,18 +65,13 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to get it, but your ghostly hands slip through it uselessly".to_owned(), "You try to get it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Get { possession_id } => possession_id, QueueCommand::Get { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -91,10 +83,10 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.location { if item.location != ctx.item.location {
user_error(format!( user_error(format!(
"You try to get {} but realise it is no longer there", "You try to get {} but realise it is no longer there",
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) &item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
@ -109,36 +101,35 @@ impl QueueCommandHandler for QueueHandler {
Some(pd) => pd, Some(pd) => pd,
}; };
let player_as_loc = format!("{}/{}", &player_item.item_type, &player_item.item_code); let player_as_loc = format!("{}/{}", &ctx.item.item_type, &ctx.item.item_code);
match check_item_capacity(ctx.trans, &player_as_loc, possession_data.weight).await? { match check_item_capacity(ctx.trans, &player_as_loc, possession_data.weight).await? {
CapacityLevel::AboveItemLimit => { CapacityLevel::AboveItemLimit => {
user_error("You just can't hold that many things!".to_owned())? user_error("You just can't hold that many things!".to_owned())?
} }
CapacityLevel::OverBurdened => user_error(format!( CapacityLevel::OverBurdened => {
let explicit = ctx.explicit().await?;
user_error(format!(
"{} You drop {} because it is too heavy!", "{} You drop {} because it is too heavy!",
if ctx.session_dat.less_explicit_mode { if explicit { "Fuck!" } else { "Rats!" },
"Rats!" &ctx.item.display_for_sentence(explicit, 1, false)
} else { ))?
"Fuck!" }
},
&player_item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false)
))?,
_ => (), _ => (),
} }
let msg_exp = format!( let msg_exp = format!(
"{} picks up {}\n", "{} picks up {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} picks up {}\n", "{} picks up {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -188,6 +179,7 @@ impl UserVerb for Verb {
} }
let mut did_anything: bool = false; let mut did_anything: bool = false;
let mut player_item_mut = (*player_item).clone();
for target in targets for target in targets
.iter() .iter()
.filter(|t| t.action_type.is_visible_in_look()) .filter(|t| t.action_type.is_visible_in_look())
@ -198,6 +190,7 @@ impl UserVerb for Verb {
did_anything = true; did_anything = true;
queue_command( queue_command(
ctx, ctx,
&mut player_item_mut,
&QueueCommand::Get { &QueueCommand::Get {
possession_id: target.item_code.clone(), possession_id: target.item_code.clone(),
}, },
@ -206,6 +199,8 @@ impl UserVerb for Verb {
} }
if !did_anything { if !did_anything {
user_error("I didn't find anything matching.".to_owned())?; user_error("I didn't find anything matching.".to_owned())?;
} else {
ctx.trans.save_item_model(&player_item_mut).await?;
} }
Ok(()) Ok(())
} }

View File

@ -5,7 +5,9 @@ use super::{
use crate::{ use crate::{
language::{self, indefinite_article}, language::{self, indefinite_article},
models::item::Item, models::item::Item,
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{ services::{
comms::broadcast_to_room, comms::broadcast_to_room,
destroy_container, destroy_container,
@ -27,16 +29,11 @@ use std::time;
pub struct WithQueueHandler; pub struct WithQueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for WithQueueHandler { impl QueueCommandHandler for WithQueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error("The dead aren't very good at improvisation.".to_owned())?; user_error("The dead aren't very good at improvisation.".to_owned())?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::ImprovWith { possession_id } => possession_id, QueueCommand::ImprovWith { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -48,23 +45,23 @@ impl QueueCommandHandler for WithQueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.refstr() { if item.location != ctx.item.refstr() {
user_error("You try improvising but realise you no longer have it.".to_owned())?; user_error("You try improvising but realise you no longer have it.".to_owned())?;
} }
broadcast_to_room( broadcast_to_room(
&ctx.trans, &ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&format!( &format!(
"{} tries to work out what {} can make from {}.\n", "{} tries to work out what {} can make from {}.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&player_item.pronouns.subject, &ctx.item.pronouns.subject,
&item.display_for_sentence(true, 1, false), &item.display_for_sentence(true, 1, false),
), ),
Some(&format!( Some(&format!(
"{} tries to work out what {} can make from {}.\n", "{} tries to work out what {} can make from {}.\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&player_item.pronouns.subject, &ctx.item.pronouns.subject,
&item.display_for_sentence(false, 1, false), &item.display_for_sentence(false, 1, false),
)), )),
) )
@ -73,16 +70,11 @@ impl QueueCommandHandler for WithQueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error("The dead aren't very good at improvisation.".to_owned())?; user_error("The dead aren't very good at improvisation.".to_owned())?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::ImprovWith { possession_id } => possession_id, QueueCommand::ImprovWith { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -94,9 +86,20 @@ impl QueueCommandHandler for WithQueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.refstr() { if item.location != ctx.item.refstr() {
user_error("You try improvising but realise you no longer have it.".to_owned())?; user_error("You try improvising but realise you no longer have it.".to_owned())?;
} }
let session = if ctx.item.item_type == "player" {
ctx.trans
.find_session_for_player(&ctx.item.item_code)
.await?
} else {
None
};
let explicit = session
.as_ref()
.map(|s| !s.1.less_explicit_mode)
.unwrap_or(false);
let opts: Vec<&'static PossessionData> = improv_by_ingredient() let opts: Vec<&'static PossessionData> = improv_by_ingredient()
.get( .get(
item.possession_type item.possession_type
@ -106,12 +109,12 @@ impl QueueCommandHandler for WithQueueHandler {
.ok_or_else(|| { .ok_or_else(|| {
UserError(format!( UserError(format!(
"You can't think of anything you could make with {}", "You can't think of anything you could make with {}",
item.display_for_session(&ctx.session_dat) item.display_for_sentence(explicit, 1, false)
)) ))
})? })?
.iter() .iter()
.filter_map(|it| possession_data().get(&it.output).map(|v| *v)) .filter_map(|it| possession_data().get(&it.output).map(|v| *v))
.filter(|pd| !ctx.session_dat.less_explicit_mode || pd.display_less_explicit.is_none()) .filter(|pd| explicit || pd.display_less_explicit.is_none())
.collect(); .collect();
let result_data = opts let result_data = opts
.as_slice() .as_slice()
@ -119,20 +122,22 @@ impl QueueCommandHandler for WithQueueHandler {
.ok_or_else(|| { .ok_or_else(|| {
UserError(format!( UserError(format!(
"You can't think of anything you could make with {}", "You can't think of anything you could make with {}",
item.display_for_session(&ctx.session_dat) item.display_for_sentence(explicit, 1, false)
)) ))
})?; })?;
if let Some((sess, _)) = session {
ctx.trans ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, &sess,
Some(&format!( Some(&format!(
"You think you could make {} {} from {}\n", "You think you could make {} {} from {}\n",
indefinite_article(result_data.display), indefinite_article(result_data.display),
result_data.display, result_data.display,
item.display_for_session(&ctx.session_dat) item.display_for_sentence(explicit, 1, false)
)), )),
) )
.await?; .await?;
}
Ok(()) Ok(())
} }
} }
@ -140,16 +145,11 @@ impl QueueCommandHandler for WithQueueHandler {
pub struct FromQueueHandler; pub struct FromQueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for FromQueueHandler { impl QueueCommandHandler for FromQueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error("The dead aren't very good at improvisation.".to_owned())?; user_error("The dead aren't very good at improvisation.".to_owned())?;
} }
let (already_used, item_ids) = match command { let (already_used, item_ids) = match ctx.command {
QueueCommand::ImprovFrom { QueueCommand::ImprovFrom {
possession_ids, possession_ids,
already_used, already_used,
@ -169,7 +169,7 @@ impl QueueCommandHandler for FromQueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.refstr() { if item.location != ctx.item.refstr() {
user_error("You try improvising but realise you no longer have the things you'd planned to use." user_error("You try improvising but realise you no longer have the things you'd planned to use."
.to_owned())?; .to_owned())?;
} }
@ -178,16 +178,11 @@ impl QueueCommandHandler for FromQueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error("The dead aren't very good at improvisation.".to_owned())?; user_error("The dead aren't very good at improvisation.".to_owned())?;
} }
let (output, possession_ids, already_used) = match command { let (output, possession_ids, already_used) = match ctx.command {
QueueCommand::ImprovFrom { QueueCommand::ImprovFrom {
output, output,
possession_ids, possession_ids,
@ -218,6 +213,17 @@ impl QueueCommandHandler for FromQueueHandler {
} }
let mut possession_id_iter = possession_ids.iter(); let mut possession_id_iter = possession_ids.iter();
let session = if ctx.item.item_type == "player" {
ctx.trans
.find_session_for_player(&ctx.item.item_code)
.await?
} else {
None
};
let explicit = session
.as_ref()
.map(|s| !s.1.less_explicit_mode)
.unwrap_or(false);
match possession_id_iter.next() { match possession_id_iter.next() {
None => { None => {
let choice = ingredients_left let choice = ingredients_left
@ -232,23 +238,23 @@ impl QueueCommandHandler for FromQueueHandler {
} }
let mut new_item: Item = craft_data.output.clone().into(); let mut new_item: Item = craft_data.output.clone().into();
new_item.item_code = ctx.trans.alloc_item_code().await?.to_string(); new_item.item_code = ctx.trans.alloc_item_code().await?.to_string();
new_item.location = player_item.refstr(); new_item.location = ctx.item.refstr();
ctx.trans.create_item(&new_item).await?; ctx.trans.create_item(&new_item).await?;
broadcast_to_room( broadcast_to_room(
&ctx.trans, &ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&format!( &format!(
"{} proudly holds up the {} {} just made.\n", "{} proudly holds up the {} {} just made.\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&new_item.display_for_sentence(true, 1, false), &new_item.display_for_sentence(true, 1, false),
&player_item.pronouns.subject &ctx.item.pronouns.subject
), ),
Some(&format!( Some(&format!(
"{} proudly holds up the {} {} just made.\n", "{} proudly holds up the {} {} just made.\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&new_item.display_for_sentence(false, 1, false), &new_item.display_for_sentence(false, 1, false),
&player_item.pronouns.subject &ctx.item.pronouns.subject
)), )),
) )
.await?; .await?;
@ -287,67 +293,66 @@ impl QueueCommandHandler for FromQueueHandler {
) { ) {
user_error(format!( user_error(format!(
"You try adding {}, but it doesn't really seem to fit right.", "You try adding {}, but it doesn't really seem to fit right.",
&item.display_for_session(&ctx.session_dat) &item.display_for_sentence(explicit, 1, false)
))?; ))?;
} }
let mut player_item_mut = (*player_item).clone();
let skill_result = skill_check_and_grind( let skill_result = skill_check_and_grind(
&ctx.trans, &ctx.trans,
&mut player_item_mut, ctx.item,
&craft_data.skill, &craft_data.skill,
craft_data.difficulty, craft_data.difficulty,
) )
.await?; .await?;
if skill_result <= -0.5 { if skill_result <= -0.5 {
crit_fail_penalty_for_skill( crit_fail_penalty_for_skill(&ctx.trans, ctx.item, &craft_data.skill).await?;
&ctx.trans,
&mut player_item_mut,
&craft_data.skill,
)
.await?;
ctx.trans ctx.trans
.delete_item(&item.item_type, &item.item_code) .delete_item(&item.item_type, &item.item_code)
.await?; .await?;
if let Some((sess, _)) = session {
ctx.trans ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, &sess,
Some(&format!( Some(&format!(
"You try adding {}, but it goes badly and you waste it.\n", "You try adding {}, but it goes badly and you waste it.\n",
&item.display_for_session(&ctx.session_dat) &item.display_for_sentence(explicit, 1, false)
)), )),
) )
.await?; .await?;
}
} else if skill_result <= 0.0 { } else if skill_result <= 0.0 {
if let Some((sess, _)) = session {
ctx.trans ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, &sess,
Some(&format!( Some(&format!(
"You try and fail at adding {}.\n", "You try and fail at adding {}.\n",
&item.display_for_session(&ctx.session_dat) &item.display_for_sentence(explicit, 1, false)
)), )),
) )
.await?; .await?;
}
} else { } else {
if let Some((sess, _)) = session {
ctx.trans ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, &sess,
Some(&format!( Some(&format!(
"You try adding {}.\n", "You try adding {}.\n",
&item.display_for_session(&ctx.session_dat), &item.display_for_sentence(explicit, 1, false),
)), )),
) )
.await?; .await?;
}
let mut new_possession_ids = possession_ids.clone(); let mut new_possession_ids = possession_ids.clone();
new_possession_ids.remove(possession_id); new_possession_ids.remove(possession_id);
let mut new_already_used = already_used.clone(); let mut new_already_used = already_used.clone();
new_already_used.insert(possession_id.clone()); new_already_used.insert(possession_id.clone());
ctx.session_dat.queue.push_front(QueueCommand::ImprovFrom { ctx.item.queue.push_front(QueueCommand::ImprovFrom {
output: output.clone(), output: output.clone(),
possession_ids: new_possession_ids, possession_ids: new_possession_ids,
already_used: new_already_used, already_used: new_already_used,
}); });
} }
ctx.trans.save_item_model(&player_item_mut).await?;
} }
} }
Ok(()) Ok(())
@ -371,8 +376,9 @@ async fn improv_query(
if item.item_type != "possession" { if item.item_type != "possession" {
user_error("You can't improvise with that!".to_owned())? user_error("You can't improvise with that!".to_owned())?
} }
queue_command( queue_command_and_save(
ctx, ctx,
player_item,
&QueueCommand::ImprovWith { &QueueCommand::ImprovWith {
possession_id: item.item_code.clone(), possession_id: item.item_code.clone(),
}, },
@ -444,8 +450,9 @@ impl UserVerb for Verb {
input_ids.insert(item.item_code.to_owned()); input_ids.insert(item.item_code.to_owned());
} }
} }
queue_command( queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::ImprovFrom { &QueueCommand::ImprovFrom {
output: output_type, output: output_type,
possession_ids: input_ids, possession_ids: input_ids,

View File

@ -11,7 +11,10 @@ use crate::{
consent::ConsentType, consent::ConsentType,
item::{ActiveClimb, DoorState, Item, ItemSpecialData, LocationActionType, SkillType}, item::{ActiveClimb, DoorState, Item, ItemSpecialData, LocationActionType, SkillType},
}, },
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, MovementSource, QueueCommand, QueueCommandHandler,
QueuedCommandContext,
},
services::{ services::{
check_consent, check_consent,
combat::{change_health, handle_resurrect, stop_attacking_mut}, combat::{change_health, handle_resurrect, stop_attacking_mut},
@ -84,14 +87,16 @@ pub async fn announce_move(
} }
async fn move_to_where( async fn move_to_where(
trans: &DBTrans,
use_location: &str, use_location: &str,
direction: &Direction, direction: &Direction,
mover_for_exit_check: Option<&mut Item>, ctx: &mut QueuedCommandContext<'_>,
player_ctx: &mut Option<&mut VerbContext<'_>>,
) -> UResult<(String, Option<Item>, Option<&'static ExitClimb>)> { ) -> UResult<(String, Option<Item>, Option<&'static ExitClimb>)> {
// Firstly check dynamic exits, since they apply to rooms and dynrooms... // Firstly check dynamic exits, since they apply to rooms and dynrooms...
if let Some(dynroom_result) = trans.find_exact_dyn_exit(use_location, direction).await? { if let Some(dynroom_result) = ctx
.trans
.find_exact_dyn_exit(use_location, direction)
.await?
{
return Ok(( return Ok((
format!( format!(
"{}/{}", "{}/{}",
@ -107,7 +112,7 @@ async fn move_to_where(
.unwrap_or(("room", "repro_xv_chargen")); .unwrap_or(("room", "repro_xv_chargen"));
if heretype == "dynroom" { if heretype == "dynroom" {
let old_dynroom_item = match trans.find_item_by_type_code(heretype, herecode).await? { let old_dynroom_item = match ctx.trans.find_item_by_type_code(heretype, herecode).await? {
None => user_error("Your current room has vanished!".to_owned())?, None => user_error("Your current room has vanished!".to_owned())?,
Some(v) => v, Some(v) => v,
}; };
@ -140,7 +145,8 @@ async fn move_to_where(
.location .location
.split_once("/") .split_once("/")
.ok_or_else(|| UserError("Invalid zone for your room".to_owned()))?; .ok_or_else(|| UserError("Invalid zone for your room".to_owned()))?;
let zoneitem = trans let zoneitem = ctx
.trans
.find_item_by_type_code(zonetype, zonecode) .find_item_by_type_code(zonetype, zonecode)
.await? .await?
.ok_or_else(|| UserError("Can't find your zone".to_owned()))?; .ok_or_else(|| UserError("Can't find your zone".to_owned()))?;
@ -159,7 +165,8 @@ async fn move_to_where(
Ok((zone_exit.to_string(), None, None)) Ok((zone_exit.to_string(), None, None))
} }
DynExitTarget::Intrazone { subcode } => { DynExitTarget::Intrazone { subcode } => {
let to_item = trans let to_item = ctx
.trans
.find_item_by_location_dynroom_code(&old_dynroom_item.location, &subcode) .find_item_by_location_dynroom_code(&old_dynroom_item.location, &subcode)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
@ -188,15 +195,11 @@ async fn move_to_where(
match exit.exit_type { match exit.exit_type {
ExitType::Free => {} ExitType::Free => {}
ExitType::Blocked(blocker) => { ExitType::Blocked(blocker) => {
if let Some(mover) = mover_for_exit_check { if !blocker.attempt_exit(ctx, exit).await? {
if let Some(ctx) = player_ctx {
if !blocker.attempt_exit(*ctx, mover, exit).await? {
user_error("Stopping movement".to_owned())?; user_error("Stopping movement".to_owned())?;
} }
} }
} }
}
}
let new_room = room::resolve_exit(room, exit) let new_room = room::resolve_exit(room, exit)
.ok_or_else(|| UserError("Can't find that room".to_owned()))?; .ok_or_else(|| UserError("Can't find that room".to_owned()))?;
@ -295,24 +298,23 @@ pub async fn handle_fall(trans: &DBTrans, faller: &mut Item, fall_dist: u64) ->
Ok(descriptor.to_owned()) Ok(descriptor.to_owned())
} }
pub async fn attempt_move_immediate( async fn attempt_move_immediate(
trans: &DBTrans,
orig_mover: &Item,
direction: &Direction, direction: &Direction,
// player_ctx should only be Some if called from queue_handler finish_command mut ctx: &mut QueuedCommandContext<'_>,
// for the orig_mover's queue, because might re-queue a move command. source: &MovementSource,
mut player_ctx: &mut Option<&mut VerbContext<'_>>,
) -> UResult<()> { ) -> UResult<()> {
let use_location = if orig_mover.death_data.is_some() { let use_location = if ctx.item.death_data.is_some() {
if orig_mover.item_type != "player" { if ctx.item.item_type != "player" {
user_error("Dead players don't move".to_owned())?; user_error("Dead players don't move".to_owned())?;
} }
"room/repro_xv_respawn" "room/repro_xv_respawn".to_owned()
} else { } else {
&orig_mover.location ctx.item.location.clone()
}; };
match is_door_in_direction(trans, direction, use_location).await? { let session = ctx.get_session().await?;
match is_door_in_direction(ctx.trans, direction, &use_location).await? {
DoorSituation::NoDoor DoorSituation::NoDoor
| DoorSituation::DoorOutOfRoom { | DoorSituation::DoorOutOfRoom {
state: DoorState { open: true, .. }, state: DoorState { open: true, .. },
@ -323,49 +325,30 @@ pub async fn attempt_move_immediate(
room_with_door, room_with_door,
.. ..
} => { } => {
check_room_access(trans, orig_mover, &room_with_door).await?; check_room_access(ctx.trans, ctx.item, &room_with_door).await?;
} }
_ => { _ => {
attempt_open_immediate(trans, player_ctx, orig_mover, direction).await?; attempt_open_immediate(ctx, direction).await?;
match player_ctx.as_mut() {
None => {
// NPCs etc... open and move in one step, but can't unlock.
}
Some(actual_player_ctx) => {
// Players take an extra step. So tell them to come back. // Players take an extra step. So tell them to come back.
actual_player_ctx ctx.item.queue.push_front(QueueCommand::Movement {
.session_dat
.queue
.push_front(QueueCommand::Movement {
direction: direction.clone(), direction: direction.clone(),
source: source.clone(),
}); });
return Ok(()); return Ok(());
} }
} }
}
}
let mut mover = (*orig_mover).clone(); let (new_loc, new_loc_item, climb_opt) = move_to_where(&use_location, direction, ctx).await?;
let (new_loc, new_loc_item, climb_opt) = move_to_where(
trans,
use_location,
direction,
Some(&mut mover),
&mut player_ctx,
)
.await?;
let mut skip_escape_check: bool = false; let mut skip_escape_check: bool = false;
let mut escape_check_only: bool = false; let mut escape_check_only: bool = false;
if let Some(climb) = climb_opt { if let Some(climb) = climb_opt {
// We need to think about if NPCs climb at all. if let Some(active_climb) = ctx.item.active_climb.clone() {
if let Some(ctx) = player_ctx {
if let Some(active_climb) = mover.active_climb.clone() {
skip_escape_check = true; // Already done if we get here. skip_escape_check = true; // Already done if we get here.
let skills = skill_check_and_grind( let skills = skill_check_and_grind(
trans, ctx.trans,
&mut mover, ctx.item,
&SkillType::Climb, &SkillType::Climb,
climb.difficulty as f64, climb.difficulty as f64,
) )
@ -375,40 +358,38 @@ pub async fn attempt_move_immediate(
// Crit fail - they have fallen. // Crit fail - they have fallen.
let (fall_dist, from_room, to_room) = if climb.height < 0 { let (fall_dist, from_room, to_room) = if climb.height < 0 {
// At least they get to where they want to go! // At least they get to where they want to go!
mover.location = new_loc.clone(); ctx.item.location = new_loc.clone();
( (
climb.height.abs() as u64 - active_climb.height, climb.height.abs() as u64 - active_climb.height,
new_loc.to_owned(), new_loc.to_owned(),
use_location.to_owned(), use_location.clone(),
) )
} else { } else {
( (
active_climb.height, active_climb.height,
use_location.to_owned(), use_location.clone(),
new_loc.to_owned(), new_loc.to_owned(),
) )
}; };
mover.active_climb = None; ctx.item.active_climb = None;
let descriptor = handle_fall(&trans, &mut mover, fall_dist).await?; let descriptor = handle_fall(&ctx.trans, ctx.item, fall_dist).await?;
let msg_exp = format!( let msg_exp = format!(
"{} loses {} grip from {} metres up and {}!\n", "{} loses {} grip from {} metres up and {}!\n",
&mover.display_for_sentence(true, 1, true), ctx.item.display_for_sentence(true, 1, true),
&mover.pronouns.possessive, ctx.item.pronouns.possessive,
fall_dist, fall_dist,
&descriptor &descriptor
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} loses {} grip from {} metres up and {}!\n", "{} loses {} grip from {} metres up and {}!\n",
&mover.display_for_sentence(true, 1, false), ctx.item.display_for_sentence(false, 1, true),
&mover.pronouns.possessive, &ctx.item.pronouns.possessive,
fall_dist, fall_dist,
&descriptor &descriptor
); );
trans.save_item_model(&mover).await?; broadcast_to_room(ctx.trans, &from_room, None, &msg_exp, Some(&msg_nonexp)).await?;
broadcast_to_room(&trans, &from_room, None, &msg_exp, Some(&msg_nonexp)) broadcast_to_room(ctx.trans, &to_room, None, &msg_exp, Some(&msg_nonexp)).await?;
.await?; ctx.item.queue.truncate(0);
broadcast_to_room(&trans, &to_room, None, &msg_exp, Some(&msg_nonexp)).await?;
ctx.session_dat.queue.truncate(0);
return Ok(()); return Ok(());
} else if skills <= 0.0 { } else if skills <= 0.0 {
if climb.height >= 0 { if climb.height >= 0 {
@ -418,7 +399,7 @@ pub async fn attempt_move_immediate(
"You struggle to find a foothold and reluctantly climb a metre back up", "You struggle to find a foothold and reluctantly climb a metre back up",
); );
} }
if let Some(ac) = mover.active_climb.as_mut() { if let Some(ac) = ctx.item.active_climb.as_mut() {
if ac.height > 0 { if ac.height > 0 {
ac.height -= 1; ac.height -= 1;
} }
@ -429,78 +410,83 @@ pub async fn attempt_move_immediate(
} else { } else {
narrative.push_str("You climb up another metre"); narrative.push_str("You climb up another metre");
} }
if let Some(ac) = mover.active_climb.as_mut() { if let Some(ac) = ctx.item.active_climb.as_mut() {
ac.height += 1; ac.height += 1;
} }
} }
if let Some(ac) = mover.active_climb.as_ref() { if let Some(ac) = ctx.item.active_climb.as_ref() {
if climb.height >= 0 && ac.height >= climb.height as u64 { if climb.height >= 0 && ac.height >= climb.height as u64 {
trans if let Some((sess, _)) = session.as_ref() {
ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, sess,
Some( Some(
"You brush yourself off and finish climbing - you \ "You brush yourself off and finish climbing - you \
made it to the top!\n", made it to the top!\n",
), ),
) )
.await?; .await?;
mover.active_climb = None; }
ctx.item.active_climb = None;
} else if climb.height < 0 && ac.height >= (-climb.height) as u64 { } else if climb.height < 0 && ac.height >= (-climb.height) as u64 {
trans if let Some((sess, _)) = session.as_ref() {
ctx.trans
.queue_for_session( .queue_for_session(
&ctx.session, sess,
Some( Some(
"You brush yourself off and finish climbing - you \ "You brush yourself off and finish climbing - you \
made it down!\n", made it down!\n",
), ),
) )
.await?; .await?;
mover.active_climb = None; }
ctx.item.active_climb = None;
} else { } else {
let progress_quant = let progress_quant =
(((ac.height as f64) / (climb.height.abs() as f64)) * 10.0) as u64; (((ac.height as f64) / (climb.height.abs() as f64)) * 10.0) as u64;
trans.queue_for_session( if let Some((sess, _)) = session {
&ctx.session, ctx.trans.queue_for_session(
&sess,
Some(&format!(ansi!("<bold>[<reset><cyan>{}{}<reset><bold>] [<reset>{}/{} m<bold>]<reset> {}\n"), Some(&format!(ansi!("<bold>[<reset><cyan>{}{}<reset><bold>] [<reset>{}/{} m<bold>]<reset> {}\n"),
"=".repeat(progress_quant as usize), " ".repeat((10 - progress_quant) as usize), "=".repeat(progress_quant as usize), " ".repeat((10 - progress_quant) as usize),
ac.height, climb.height.abs(), &narrative ac.height, climb.height.abs(), &narrative
))).await?; ))).await?;
ctx.session_dat.queue.push_front(QueueCommand::Movement { }
ctx.item.queue.push_front(QueueCommand::Movement {
direction: direction.clone(), direction: direction.clone(),
source: source.clone(),
}); });
trans.save_item_model(&mover).await?;
return Ok(()); return Ok(());
} }
} }
} else { } else {
let msg_exp = format!( let msg_exp = format!(
"{} starts climbing {}\n", "{} starts climbing {}\n",
&orig_mover.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&direction.describe_climb(if climb.height > 0 { "up" } else { "down" }) &direction.describe_climb(if climb.height > 0 { "up" } else { "down" })
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} starts climbing {}\n", "{} starts climbing {}\n",
&orig_mover.display_for_sentence(true, 1, false), &ctx.item.display_for_sentence(true, 1, false),
&direction.describe_climb(if climb.height > 0 { "up" } else { "down" }) &direction.describe_climb(if climb.height > 0 { "up" } else { "down" })
); );
broadcast_to_room(&trans, &use_location, None, &msg_exp, Some(&msg_nonexp)).await?; broadcast_to_room(&ctx.trans, &use_location, None, &msg_exp, Some(&msg_nonexp)).await?;
mover.active_climb = Some(ActiveClimb { ctx.item.active_climb = Some(ActiveClimb {
..Default::default() ..Default::default()
}); });
ctx.session_dat.queue.push_front(QueueCommand::Movement { ctx.item.queue.push_front(QueueCommand::Movement {
direction: direction.clone(), direction: direction.clone(),
source: source.clone(),
}); });
escape_check_only = true; escape_check_only = true;
} }
} else {
user_error("NPC climbing not supported yet.".to_owned())?;
}
} }
if !skip_escape_check { if !skip_escape_check {
match mover match ctx
.item
.active_combat .active_combat
.as_ref() .as_ref()
.and_then(|ac| ac.attacking.clone()) .and_then(|ac| ac.attacking.clone())
@ -508,15 +494,16 @@ pub async fn attempt_move_immediate(
None => {} None => {}
Some(old_victim) => { Some(old_victim) => {
if let Some((vcode, vtype)) = old_victim.split_once("/") { if let Some((vcode, vtype)) = old_victim.split_once("/") {
if let Some(vitem) = trans.find_item_by_type_code(vcode, vtype).await? { if let Some(vitem) = ctx.trans.find_item_by_type_code(vcode, vtype).await? {
let mut vitem_mut = (*vitem).clone(); let mut vitem_mut = (*vitem).clone();
stop_attacking_mut(trans, &mut mover, &mut vitem_mut, false).await?; stop_attacking_mut(ctx.trans, ctx.item, &mut vitem_mut, false).await?;
trans.save_item_model(&vitem_mut).await? ctx.trans.save_item_model(&vitem_mut).await?
} }
} }
} }
} }
match mover match ctx
.item
.active_combat .active_combat
.clone() .clone()
.as_ref() .as_ref()
@ -526,11 +513,13 @@ pub async fn attempt_move_immediate(
Some(attackers) => { Some(attackers) => {
let mut attacker_names = Vec::new(); let mut attacker_names = Vec::new();
let mut attacker_items = Vec::new(); let mut attacker_items = Vec::new();
if let Some(ctx) = player_ctx.as_ref() { if let Some((_, session_dat)) = session.as_ref() {
for attacker in &attackers[..] { for attacker in &attackers[..] {
if let Some((acode, atype)) = attacker.split_once("/") { if let Some((acode, atype)) = attacker.split_once("/") {
if let Some(aitem) = trans.find_item_by_type_code(acode, atype).await? { if let Some(aitem) =
attacker_names.push(aitem.display_for_session(ctx.session_dat)); ctx.trans.find_item_by_type_code(acode, atype).await?
{
attacker_names.push(aitem.display_for_session(session_dat));
attacker_items.push(aitem); attacker_items.push(aitem);
} }
} }
@ -542,18 +531,18 @@ pub async fn attempt_move_immediate(
.collect::<Vec<&str>>(); .collect::<Vec<&str>>();
let attacker_names_str = language::join_words(&attacker_names_ref[..]); let attacker_names_str = language::join_words(&attacker_names_ref[..]);
if skill_check_and_grind( if skill_check_and_grind(
trans, ctx.trans,
&mut mover, ctx.item,
&SkillType::Dodge, &SkillType::Dodge,
attackers.len() as f64 + 8.0, attackers.len() as f64 + 8.0,
) )
.await? .await?
>= 0.0 >= 0.0
{ {
if let Some(ctx) = player_ctx.as_ref() { if let Some((sess, _)) = session.as_ref() {
trans ctx.trans
.queue_for_session( .queue_for_session(
ctx.session, sess,
Some(&format!( Some(&format!(
"You successfully get away from {}\n", "You successfully get away from {}\n",
&attacker_names_str &attacker_names_str
@ -563,14 +552,14 @@ pub async fn attempt_move_immediate(
} }
for item in &attacker_items[..] { for item in &attacker_items[..] {
let mut item_mut = (**item).clone(); let mut item_mut = (**item).clone();
stop_attacking_mut(trans, &mut item_mut, &mut mover, true).await?; stop_attacking_mut(ctx.trans, &mut item_mut, ctx.item, true).await?;
trans.save_item_model(&item_mut).await?; ctx.trans.save_item_model(&item_mut).await?;
} }
} else { } else {
if let Some(ctx) = player_ctx.as_ref() { if let Some((sess, _)) = session.as_ref() {
trans ctx.trans
.queue_for_session( .queue_for_session(
ctx.session, sess,
Some(&format!( Some(&format!(
"You try and fail to run past {}\n", "You try and fail to run past {}\n",
&attacker_names_str &attacker_names_str
@ -578,48 +567,59 @@ pub async fn attempt_move_immediate(
) )
.await?; .await?;
} }
trans.save_item_model(&mover).await?;
return Ok(()); return Ok(());
} }
} }
} }
} }
if escape_check_only { if escape_check_only {
trans.save_item_model(&mover).await?;
return Ok(()); return Ok(());
} }
if mover.death_data.is_some() { if ctx.item.death_data.is_some() {
if !handle_resurrect(trans, &mut mover).await? { if !handle_resurrect(ctx.trans, ctx.item).await? {
user_error("You couldn't be resurrected.".to_string())?; user_error("You couldn't be resurrected.".to_string())?;
} }
} }
mover.location = new_loc.clone(); ctx.item.location = new_loc.clone();
mover.action_type = LocationActionType::Normal; ctx.item.action_type = LocationActionType::Normal;
mover.active_combat = None; ctx.item.active_combat = None;
trans.save_item_model(&mover).await?; if let Some((sess, mut session_dat)) = session {
let mut user = ctx.trans.find_by_username(&ctx.item.item_code).await?;
if let Some(ctx) = player_ctx { // Look reads it, so we ensure we save it first.
look::VERB.handle(ctx, "look", "").await?; ctx.trans.save_item_model(&ctx.item).await?;
look::VERB
.handle(
&mut VerbContext {
session: &sess,
session_dat: &mut session_dat,
trans: ctx.trans,
user_dat: &mut user,
},
"look",
"",
)
.await?;
} }
if let Some((old_loc_type, old_loc_code)) = use_location.split_once("/") { if let Some((old_loc_type, old_loc_code)) = use_location.split_once("/") {
if let Some(old_room_item) = trans if let Some(old_room_item) = ctx
.trans
.find_item_by_type_code(old_loc_type, old_loc_code) .find_item_by_type_code(old_loc_type, old_loc_code)
.await? .await?
{ {
if let Some((new_loc_type, new_loc_code)) = new_loc.split_once("/") { if let Some((new_loc_type, new_loc_code)) = new_loc.split_once("/") {
if let Some(new_room_item) = match new_loc_item { if let Some(new_room_item) = match new_loc_item {
None => { None => {
trans ctx.trans
.find_item_by_type_code(new_loc_type, new_loc_code) .find_item_by_type_code(new_loc_type, new_loc_code)
.await? .await?
} }
v => v.map(Arc::new), v => v.map(Arc::new),
} { } {
announce_move(&trans, &mover, &old_room_item, &new_room_item).await?; announce_move(&ctx.trans, ctx.item, &old_room_item, &new_room_item).await?;
} }
} }
} }
@ -631,26 +631,17 @@ pub async fn attempt_move_immediate(
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, _ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self,
_ctx: &mut VerbContext<'_>,
_command: &QueueCommand,
) -> UResult<time::Duration> {
Ok(time::Duration::from_secs(1)) Ok(time::Duration::from_secs(1))
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, let (direction, source) = match ctx.command {
ctx: &mut VerbContext<'_>, QueueCommand::Movement { direction, source } => (direction, source),
command: &QueueCommand,
) -> UResult<()> {
let direction = match command {
QueueCommand::Movement { direction } => direction,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
let player_item = get_player_item_or_fail(ctx).await?; attempt_move_immediate(direction, ctx, source).await?;
attempt_move_immediate(ctx.trans, &player_item, direction, &mut Some(ctx)).await?;
Ok(()) Ok(())
} }
} }
@ -667,10 +658,13 @@ impl UserVerb for Verb {
) -> UResult<()> { ) -> UResult<()> {
let dir = Direction::parse(&(verb.to_owned() + " " + remaining.trim()).trim()) let dir = Direction::parse(&(verb.to_owned() + " " + remaining.trim()).trim())
.ok_or_else(|| UserError("Unknown direction".to_owned()))?; .ok_or_else(|| UserError("Unknown direction".to_owned()))?;
queue_command( let player_item = get_player_item_or_fail(ctx).await?;
queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::Movement { &QueueCommand::Movement {
direction: dir.clone(), direction: dir.clone(),
source: MovementSource::Command,
}, },
) )
.await .await

View File

@ -10,7 +10,9 @@ use crate::{
task::{Task, TaskDetails, TaskMeta}, task::{Task, TaskDetails, TaskMeta},
}, },
regular_tasks::{ regular_tasks::{
queued_command::{queue_command, QueueCommand, QueueCommandHandler}, queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
TaskHandler, TaskRunContext, TaskHandler, TaskRunContext,
}, },
services::comms::broadcast_to_room, services::comms::broadcast_to_room,
@ -92,18 +94,16 @@ impl TaskHandler for SwingShutHandler {
} }
pub async fn attempt_open_immediate( pub async fn attempt_open_immediate(
trans: &DBTrans, ctx: &mut QueuedCommandContext<'_>,
ctx_opt: &mut Option<&mut VerbContext<'_>>,
who: &Item,
direction: &Direction, direction: &Direction,
) -> UResult<()> { ) -> UResult<()> {
let use_location = if who.death_data.is_some() { let use_location = if ctx.item.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())? user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else { } else {
&who.location &ctx.item.location
}; };
let (room_1, dir_in_room, room_2) = let (room_1, dir_in_room, room_2) =
match is_door_in_direction(trans, &direction, use_location).await? { match is_door_in_direction(ctx.trans, &direction, use_location).await? {
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?, DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
DoorSituation::DoorIntoRoom { DoorSituation::DoorIntoRoom {
state: DoorState { open: true, .. }, state: DoorState { open: true, .. },
@ -120,7 +120,8 @@ pub async fn attempt_open_immediate(
} => { } => {
let entering_room_loc = room_with_door.refstr(); let entering_room_loc = room_with_door.refstr();
if let Some(revdir) = direction.reverse() { if let Some(revdir) = direction.reverse() {
if let Some(lock) = trans if let Some(lock) = ctx
.trans
.find_by_action_and_location( .find_by_action_and_location(
&entering_room_loc, &entering_room_loc,
&LocationActionType::InstalledOnDoorAsLock(revdir.clone()), &LocationActionType::InstalledOnDoorAsLock(revdir.clone()),
@ -128,18 +129,13 @@ pub async fn attempt_open_immediate(
.await? .await?
.first() .first()
{ {
if let Some(ctx) = ctx_opt {
if let Some(lockcheck) = lock if let Some(lockcheck) = lock
.possession_type .possession_type
.as_ref() .as_ref()
.and_then(|pt| possession_data().get(pt)) .and_then(|pt| possession_data().get(pt))
.and_then(|pd| pd.lockcheck_handler) .and_then(|pd| pd.lockcheck_handler)
{ {
lockcheck.cmd(ctx, &who, &lock).await? lockcheck.cmd(ctx, &lock).await?
}
} else {
// NPCs don't unlock doors.
user_error("Can't get through locked doors".to_owned())?;
} }
} }
let mut entering_room_mut = (*room_with_door).clone(); let mut entering_room_mut = (*room_with_door).clone();
@ -148,7 +144,7 @@ pub async fn attempt_open_immediate(
(*door).open = true; (*door).open = true;
} }
} }
trans.save_item_model(&entering_room_mut).await?; ctx.trans.save_item_model(&entering_room_mut).await?;
(room_with_door, revdir, current_room) (room_with_door, revdir, current_room)
} else { } else {
user_error("There's no door possible there.".to_owned())? user_error("There's no door possible there.".to_owned())?
@ -165,7 +161,7 @@ pub async fn attempt_open_immediate(
(*door).open = true; (*door).open = true;
} }
} }
trans.save_item_model(&entering_room_mut).await?; ctx.trans.save_item_model(&entering_room_mut).await?;
(room_with_door, direction.clone(), new_room) (room_with_door, direction.clone(), new_room)
} }
}; };
@ -181,24 +177,24 @@ pub async fn attempt_open_immediate(
), ),
] { ] {
broadcast_to_room( broadcast_to_room(
&trans, &ctx.trans,
loc, loc,
None, None,
&format!( &format!(
"{} opens the door to the {}.\n", "{} opens the door to the {}.\n",
&who.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
dir dir
), ),
Some(&format!( Some(&format!(
"{} opens the door to the {}.\n", "{} opens the door to the {}.\n",
&who.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
dir dir
)), )),
) )
.await?; .await?;
} }
trans ctx.trans
.upsert_task(&Task { .upsert_task(&Task {
meta: TaskMeta { meta: TaskMeta {
task_code: format!("{}/{}", &room_1.refstr(), &direction.describe()), task_code: format!("{}/{}", &room_1.refstr(), &direction.describe()),
@ -218,20 +214,15 @@ pub async fn attempt_open_immediate(
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, let direction = match ctx.command {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let direction = match command {
QueueCommand::OpenDoor { direction } => direction, QueueCommand::OpenDoor { direction } => direction,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
let player_item = get_player_item_or_fail(ctx).await?; let use_location = if ctx.item.death_data.is_some() {
let use_location = if player_item.death_data.is_some() {
user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())? user_error("Your ethereal hands don't seem to be able to move the door.".to_owned())?
} else { } else {
&player_item.location &ctx.item.location
}; };
match is_door_in_direction(&ctx.trans, &direction, use_location).await? { match is_door_in_direction(&ctx.trans, &direction, use_location).await? {
DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?, DoorSituation::NoDoor => user_error("There is no door to open.".to_owned())?,
@ -264,7 +255,7 @@ impl QueueCommandHandler for QueueHandler {
.and_then(|pt| possession_data().get(pt)) .and_then(|pt| possession_data().get(pt))
.and_then(|pd| pd.lockcheck_handler) .and_then(|pd| pd.lockcheck_handler)
{ {
lockcheck.cmd(ctx, &player_item, &lock).await? lockcheck.cmd(ctx, &lock).await?
} }
} }
} }
@ -276,17 +267,12 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, let direction = match ctx.command {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let direction = match command {
QueueCommand::OpenDoor { direction } => direction, QueueCommand::OpenDoor { direction } => direction,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
let player_item = get_player_item_or_fail(ctx).await?; attempt_open_immediate(ctx, &direction).await?;
attempt_open_immediate(&ctx.trans, &mut Some(ctx), &player_item, &direction).await?;
Ok(()) Ok(())
} }
@ -359,8 +345,10 @@ impl UserVerb for Verb {
) -> UResult<()> { ) -> UResult<()> {
let dir = let dir =
Direction::parse(remaining).ok_or_else(|| UserError("Unknown direction".to_owned()))?; Direction::parse(remaining).ok_or_else(|| UserError("Unknown direction".to_owned()))?;
queue_command( let player_item = get_player_item_or_fail(ctx).await?;
queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::OpenDoor { &QueueCommand::OpenDoor {
direction: dir.clone(), direction: dir.clone(),
}, },

View File

@ -4,22 +4,20 @@ use super::{
}; };
use crate::{ use crate::{
models::item::{BuffCause, Item, LocationActionType}, models::item::{BuffCause, Item, LocationActionType},
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{comms::broadcast_to_room, skills::calculate_total_stats_skills_for_user}, services::{comms::broadcast_to_room, skills::calculate_total_stats_skills_for_user},
static_content::possession_type::{possession_data, WearData}, static_content::possession_type::{possession_data, WearData},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use std::time; use std::time;
async fn check_removeable( async fn check_removeable(ctx: &mut QueuedCommandContext<'_>, item: &Item) -> UResult<()> {
ctx: &mut VerbContext<'_>, if item.location != ctx.item.refstr() {
item: &Item,
player_item: &Item,
) -> UResult<()> {
if item.location != player_item.refstr() {
user_error(format!( user_error(format!(
"You try to remove {} but realise you no longer have it.", "You try to remove {} but realise you no longer have it.",
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) &item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
@ -46,7 +44,7 @@ async fn check_removeable(
let other_clothes = ctx let other_clothes = ctx
.trans .trans
.find_by_action_and_location(&player_item.refstr(), &LocationActionType::Worn) .find_by_action_and_location(&ctx.item.refstr(), &LocationActionType::Worn)
.await?; .await?;
if let Some(my_worn_since) = item.action_type_started { if let Some(my_worn_since) = item.action_type_started {
@ -70,8 +68,8 @@ async fn check_removeable(
}) { }) {
user_error(format!( user_error(format!(
"You can't do that without first removing your {} from your {}.", "You can't do that without first removing your {} from your {}.",
&other_item.display_for_session(&ctx.session_dat), &other_item.display_for_sentence(ctx.explicit().await?, 1, false),
part.display(player_item.sex.clone()) part.display(ctx.item.sex.clone())
))?; ))?;
} }
} }
@ -82,18 +80,13 @@ async fn check_removeable(
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to remove it, but your ghostly hands slip through it uselessly".to_owned(), "You try to remove it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Remove { possession_id } => possession_id, QueueCommand::Remove { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -106,21 +99,21 @@ impl QueueCommandHandler for QueueHandler {
Some(it) => it, Some(it) => it,
}; };
check_removeable(ctx, &item, &player_item).await?; check_removeable(ctx, &item).await?;
let msg_exp = format!( let msg_exp = format!(
"{} fumbles around trying to take off {}\n", "{} fumbles around trying to take off {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} fumbles around trying to take off {}\n", "{} fumbles around trying to take off {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -130,18 +123,13 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to remove it, but your ghostly hands slip through it uselessly".to_owned(), "You try to remove it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Remove { possession_id } => possession_id, QueueCommand::Remove { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -154,21 +142,21 @@ impl QueueCommandHandler for QueueHandler {
Some(it) => it, Some(it) => it,
}; };
check_removeable(ctx, &item, &player_item).await?; check_removeable(ctx, &item).await?;
let msg_exp = format!( let msg_exp = format!(
"{} removes {}\n", "{} removes {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} removes {}\n", "{} removes {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -189,10 +177,12 @@ impl QueueCommandHandler for QueueHandler {
.to_owned(), .to_owned(),
) )
})?; })?;
ctx.trans.save_item_model(&item_mut).await?;
if wear_data.dodge_penalty != 0.0 { if wear_data.dodge_penalty != 0.0 {
let mut player_item_mut = (*player_item).clone(); ctx.item.temporary_buffs = ctx
player_item_mut.temporary_buffs = player_item_mut .item
.temporary_buffs .temporary_buffs
.clone()
.into_iter() .into_iter()
.filter(|buf| { .filter(|buf| {
buf.cause buf.cause
@ -202,13 +192,12 @@ impl QueueCommandHandler for QueueHandler {
} }
}) })
.collect(); .collect();
if let Some(ref usr) = ctx.user_dat { if ctx.item.item_type == "player" {
calculate_total_stats_skills_for_user(&mut player_item_mut, usr); if let Some(usr) = ctx.trans.find_by_username(&ctx.item.item_code).await? {
calculate_total_stats_skills_for_user(ctx.item, &usr);
}
} }
ctx.trans.save_item_model(&player_item_mut).await?;
} }
ctx.trans.save_item_model(&item_mut).await?;
Ok(()) Ok(())
} }
} }
@ -247,6 +236,7 @@ impl UserVerb for Verb {
} }
let mut did_anything: bool = false; let mut did_anything: bool = false;
let mut player_item_mut = (*player_item).clone();
for target in targets for target in targets
.iter() .iter()
.filter(|t| t.action_type.is_visible_in_look()) .filter(|t| t.action_type.is_visible_in_look())
@ -257,6 +247,7 @@ impl UserVerb for Verb {
did_anything = true; did_anything = true;
queue_command( queue_command(
ctx, ctx,
&mut player_item_mut,
&QueueCommand::Remove { &QueueCommand::Remove {
possession_id: target.item_code.clone(), possession_id: target.item_code.clone(),
}, },
@ -265,6 +256,8 @@ impl UserVerb for Verb {
} }
if !did_anything { if !did_anything {
user_error("I didn't find anything matching.".to_owned())?; user_error("I didn't find anything matching.".to_owned())?;
} else {
ctx.trans.save_item_model(&player_item_mut).await?;
} }
Ok(()) Ok(())
} }

View File

@ -5,30 +5,28 @@ use super::{
use crate::{ use crate::{
language, language,
models::item::SkillType, models::item::SkillType,
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{ services::{
check_consent, comms::broadcast_to_room, effect::run_effects, skills::skill_check_and_grind, check_consent, comms::broadcast_to_room, effect::run_effects, skills::skill_check_and_grind,
}, },
static_content::possession_type::possession_data, static_content::possession_type::possession_data,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use std::sync::Arc;
use std::time; use std::time;
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to use it, but your ghostly hands slip through it uselessly".to_owned(), "You try to use it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let (item_id, target_type_code) = match command { let (item_id, target_type_code) = match ctx.command {
QueueCommand::Use { QueueCommand::Use {
possession_id, possession_id,
target_id, target_id,
@ -43,19 +41,19 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("player/{}", player_item.item_code) { if item.location != format!("player/{}", ctx.item.item_code) {
user_error(format!( user_error(format!(
"You try to use {} but realise you no longer have it", "You try to use {} but realise you no longer have it",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
let (target_type, target_code) = match target_type_code.split_once("/") { let (target_type, target_code) = match target_type_code.split_once("/") {
None => user_error("Couldn't handle use command (invalid target)".to_owned())?, None => user_error("Couldn't handle use command (invalid target)".to_owned())?,
Some(spl) => spl, Some(spl) => spl,
}; };
let is_self_use = target_type == "player" && target_code == player_item.item_code; let is_self_use = target_type == "player" && target_code == ctx.item.item_code;
let target = if is_self_use { let target = if is_self_use {
player_item.clone() Arc::new(ctx.item.clone())
} else { } else {
match ctx match ctx
.trans .trans
@ -70,62 +68,57 @@ impl QueueCommandHandler for QueueHandler {
} }
}; };
if !is_self_use if !is_self_use
&& target.location != player_item.location && target.location != ctx.item.location
&& target.location != format!("player/{}", player_item.item_code) && target.location != format!("player/{}", ctx.item.item_code)
{ {
let target_name = let explicit = ctx.explicit().await?;
target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false); let target_name = target.display_for_sentence(explicit, 1, false);
user_error(format!( user_error(format!(
"You try to use {} on {}, but realise {} is no longer here", "You try to use {} on {}, but realise {} is no longer here",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false), item.display_for_sentence(explicit, 1, false),
target_name, target_name,
target_name target_name
))? ))?
} }
let msg_exp = format!( let msg_exp = format!(
"{} prepares to use {} {} on {}\n", "{} prepares to use {} {} on {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&player_item.pronouns.possessive, &ctx.item.pronouns.possessive,
&item.display_for_sentence(true, 1, false), &item.display_for_sentence(true, 1, false),
&if is_self_use { &if is_self_use {
player_item.pronouns.intensive.clone() ctx.item.pronouns.intensive.clone()
} else { } else {
player_item.display_for_sentence(true, 1, false) ctx.item.display_for_sentence(true, 1, false)
} }
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} prepares to use {} {} on {}\n", "{} prepares to use {} {} on {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&player_item.pronouns.possessive, &ctx.item.pronouns.possessive,
&item.display_for_sentence(false, 1, false), &item.display_for_sentence(false, 1, false),
&if is_self_use { &if is_self_use {
player_item.pronouns.intensive.clone() ctx.item.pronouns.intensive.clone()
} else { } else {
player_item.display_for_sentence(true, 1, false) ctx.item.display_for_sentence(true, 1, false)
} }
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
) )
.await?; .await?;
let mut draw_level: f64 = *player_item let mut draw_level: f64 = *ctx
.item
.total_skills .total_skills
.get(&SkillType::Quickdraw) .get(&SkillType::Quickdraw)
.to_owned() .to_owned()
.unwrap_or(&8.0); .unwrap_or(&8.0);
let mut player_item_mut = (*player_item).clone();
let skill_result = skill_check_and_grind( let skill_result =
ctx.trans, skill_check_and_grind(ctx.trans, ctx.item, &SkillType::Quickdraw, draw_level).await?;
&mut player_item_mut,
&SkillType::Quickdraw,
draw_level,
)
.await?;
if skill_result < -0.5 { if skill_result < -0.5 {
draw_level -= 2.0; draw_level -= 2.0;
} else if skill_result < -0.25 { } else if skill_result < -0.25 {
@ -135,7 +128,6 @@ impl QueueCommandHandler for QueueHandler {
} else if skill_result > 0.25 { } else if skill_result > 0.25 {
draw_level += 1.0; draw_level += 1.0;
} }
ctx.trans.save_item_model(&player_item_mut).await?;
let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0); let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0);
Ok(time::Duration::from_millis( Ok(time::Duration::from_millis(
@ -144,19 +136,14 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to use it, but your ghostly hands slip through it uselessly".to_owned(), "You try to use it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let (ref item_id, ref target_type_code) = match command { let (ref item_id, ref target_type_code) = match ctx.command {
QueueCommand::Use { QueueCommand::Use {
possession_id, possession_id,
target_id, target_id,
@ -171,10 +158,10 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("player/{}", player_item.item_code) { if item.location != format!("player/{}", ctx.item.item_code) {
user_error(format!( user_error(format!(
"You try to use {} but realise you no longer have it", "You try to use {} but realise you no longer have it",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) item.display_for_sentence(ctx.explicit().await?, 1, false)
))? ))?
} }
let (ref target_type, ref target_code) = match target_type_code.split_once("/") { let (ref target_type, ref target_code) = match target_type_code.split_once("/") {
@ -189,14 +176,14 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Couldn't handle use command (target missing)".to_owned())?, None => user_error("Couldn't handle use command (target missing)".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if target.location != player_item.location if target.location != ctx.item.location
&& target.location != format!("player/{}", player_item.item_code) && target.location != format!("player/{}", ctx.item.item_code)
{ {
let target_name = let explicit = ctx.explicit().await?;
target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false); let target_name = target.display_for_sentence(explicit, 1, false);
user_error(format!( user_error(format!(
"You try to use {} on {}, but realise {} is no longer here", "You try to use {} on {}, but realise {} is no longer here",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false), item.display_for_sentence(explicit, 1, false),
target_name, target_name,
target_name target_name
))? ))?
@ -211,10 +198,10 @@ impl QueueCommandHandler for QueueHandler {
Some(d) => d, Some(d) => d,
}; };
if let Some(consent_type) = use_data.needs_consent_check.as_ref() { if let Some(consent_type) = use_data.needs_consent_check.as_ref() {
if !check_consent(ctx.trans, "use", consent_type, &player_item, &target).await? { if !check_consent(ctx.trans, "use", consent_type, &ctx.item, &target).await? {
user_error(format!( user_error(format!(
"{} doesn't allow {} from you", "{} doesn't allow {} from you",
&target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, true), &target.display_for_sentence(ctx.explicit().await?, 1, true),
consent_type.to_str() consent_type.to_str()
))? ))?
} }
@ -228,7 +215,7 @@ impl QueueCommandHandler for QueueHandler {
if item.charges < 1 { if item.charges < 1 {
user_error(format!( user_error(format!(
"{} has no {} {} left", "{} has no {} {} left",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, true), item.display_for_sentence(ctx.explicit().await?, 1, true),
&language::pluralise(charge_data.charge_name_prefix), &language::pluralise(charge_data.charge_name_prefix),
charge_data.charge_name_suffix charge_data.charge_name_suffix
))?; ))?;
@ -248,17 +235,17 @@ impl QueueCommandHandler for QueueHandler {
) )
.await? .await?
{ {
let explicit = ctx.explicit().await?;
user_error(format!( user_error(format!(
"You see no reason to use {} on {}", "You see no reason to use {} on {}",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false), item.display_for_sentence(explicit, 1, false),
target.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) target.display_for_sentence(explicit, 1, false)
))?; ))?;
} }
let is_self_use = target_type == &"player" && target_code == &player_item.item_code; let is_self_use = target_type == &"player" && target_code == &ctx.item.item_code;
let mut player_mut = (*player_item).clone();
let skillcheck = skill_check_and_grind( let skillcheck = skill_check_and_grind(
&ctx.trans, &ctx.trans,
&mut player_mut, ctx.item,
&use_data.uses_skill, &use_data.uses_skill,
use_data.diff_level, use_data.diff_level,
) )
@ -280,7 +267,7 @@ impl QueueCommandHandler for QueueHandler {
run_effects( run_effects(
ctx.trans, ctx.trans,
&effects, &effects,
&mut player_mut, ctx.item,
&item, &item,
&mut target_mut, &mut target_mut,
skilllvl, skilllvl,
@ -290,7 +277,6 @@ impl QueueCommandHandler for QueueHandler {
if let Some(target_mut_save) = target_mut { if let Some(target_mut_save) = target_mut {
ctx.trans.save_item_model(&target_mut_save).await?; ctx.trans.save_item_model(&target_mut_save).await?;
} }
ctx.trans.save_item_model(&player_mut).await?;
let mut item_mut = (*item).clone(); let mut item_mut = (*item).clone();
let mut save_item = false; let mut save_item = false;
@ -396,8 +382,9 @@ impl UserVerb for Verb {
if let Some(err) = (use_data.errorf)(&item, &target) { if let Some(err) = (use_data.errorf)(&item, &target) {
user_error(err)?; user_error(err)?;
} }
queue_command( queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::Use { &QueueCommand::Use {
possession_id: item.item_code.clone(), possession_id: item.item_code.clone(),
target_id: format!("{}/{}", target.item_type, target.item_code), target_id: format!("{}/{}", target.item_type, target.item_code),

View File

@ -4,7 +4,9 @@ use super::{
}; };
use crate::{ use crate::{
models::item::{Buff, BuffCause, BuffImpact, LocationActionType, SkillType}, models::item::{Buff, BuffCause, BuffImpact, LocationActionType, SkillType},
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{comms::broadcast_to_room, skills::calculate_total_stats_skills_for_user}, services::{comms::broadcast_to_room, skills::calculate_total_stats_skills_for_user},
static_content::possession_type::possession_data, static_content::possession_type::possession_data,
}; };
@ -15,18 +17,13 @@ use std::time;
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to wear it, but your ghostly hands slip through it uselessly".to_owned(), "You try to wear it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Wear { possession_id } => possession_id, QueueCommand::Wear { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -38,10 +35,11 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.refstr() { let explicit = ctx.explicit().await?;
if item.location != ctx.item.refstr() {
user_error(format!( user_error(format!(
"You try to wear {} but realise you no longer have it", "You try to wear {} but realise you no longer have it",
item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) item.display_for_sentence(explicit, 1, false)
))? ))?
} }
@ -64,17 +62,17 @@ impl QueueCommandHandler for QueueHandler {
let msg_exp = format!( let msg_exp = format!(
"{} fumbles around trying to put on {}\n", "{} fumbles around trying to put on {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} fumbles around trying to put on {}\n", "{} fumbles around trying to put on {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -84,18 +82,13 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to wear it, but your ghostly hands slip through it uselessly".to_owned(), "You try to wear it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Wear { possession_id } => possession_id, QueueCommand::Wear { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -107,10 +100,11 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != player_item.refstr() { let explicit = ctx.explicit().await?;
if item.location != ctx.item.refstr() {
user_error(format!( user_error(format!(
"You try to wear {} but realise it is no longer there.", "You try to wear {} but realise it is no longer there.",
&item.display_for_sentence(!ctx.session_dat.less_explicit_mode, 1, false) &item.display_for_sentence(explicit, 1, false)
))? ))?
} }
@ -133,7 +127,7 @@ impl QueueCommandHandler for QueueHandler {
let other_clothes = ctx let other_clothes = ctx
.trans .trans
.find_by_action_and_location(&player_item.refstr(), &LocationActionType::Worn) .find_by_action_and_location(&ctx.item.refstr(), &LocationActionType::Worn)
.await?; .await?;
for part in &wear_data.covers_parts { for part in &wear_data.covers_parts {
let thickness: f64 = let thickness: f64 =
@ -153,24 +147,24 @@ impl QueueCommandHandler for QueueHandler {
if thickness > 12.0 { if thickness > 12.0 {
user_error(format!( user_error(format!(
"You're wearing too much on your {} already.", "You're wearing too much on your {} already.",
part.display(player_item.sex.clone()) part.display(ctx.item.sex.clone())
))?; ))?;
} }
} }
let msg_exp = format!( let msg_exp = format!(
"{} wears {}\n", "{} wears {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} wears {}\n", "{} wears {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -181,8 +175,7 @@ impl QueueCommandHandler for QueueHandler {
item_mut.action_type_started = Some(Utc::now()); item_mut.action_type_started = Some(Utc::now());
if wear_data.dodge_penalty != 0.0 { if wear_data.dodge_penalty != 0.0 {
let mut player_item_mut = (*player_item).clone(); ctx.item.temporary_buffs.push(Buff {
player_item_mut.temporary_buffs.push(Buff {
description: "Dodge penalty".to_owned(), description: "Dodge penalty".to_owned(),
cause: BuffCause::ByItem { cause: BuffCause::ByItem {
item_type: item_mut.item_type.clone(), item_type: item_mut.item_type.clone(),
@ -193,10 +186,11 @@ impl QueueCommandHandler for QueueHandler {
magnitude: -wear_data.dodge_penalty, magnitude: -wear_data.dodge_penalty,
}], }],
}); });
if let Some(ref usr) = ctx.user_dat { if ctx.item.item_type == "player" {
calculate_total_stats_skills_for_user(&mut player_item_mut, usr); if let Some(usr) = ctx.trans.find_by_username(&ctx.item.item_code).await? {
calculate_total_stats_skills_for_user(ctx.item, &usr);
}
} }
ctx.trans.save_item_model(&player_item_mut).await?;
} }
ctx.trans.save_item_model(&item_mut).await?; ctx.trans.save_item_model(&item_mut).await?;
@ -238,6 +232,7 @@ impl UserVerb for Verb {
} }
let mut did_anything: bool = false; let mut did_anything: bool = false;
let mut player_item_mut = (*player_item).clone();
for target in targets for target in targets
.iter() .iter()
.filter(|t| t.action_type.is_visible_in_look()) .filter(|t| t.action_type.is_visible_in_look())
@ -248,6 +243,7 @@ impl UserVerb for Verb {
did_anything = true; did_anything = true;
queue_command( queue_command(
ctx, ctx,
&mut player_item_mut,
&QueueCommand::Wear { &QueueCommand::Wear {
possession_id: target.item_code.clone(), possession_id: target.item_code.clone(),
}, },
@ -256,6 +252,8 @@ impl UserVerb for Verb {
} }
if !did_anything { if !did_anything {
user_error("I didn't find anything matching.".to_owned())?; user_error("I didn't find anything matching.".to_owned())?;
} else {
ctx.trans.save_item_model(&player_item_mut).await?;
} }
Ok(()) Ok(())
} }

View File

@ -4,7 +4,9 @@ use super::{
}; };
use crate::{ use crate::{
models::item::{LocationActionType, SkillType}, models::item::{LocationActionType, SkillType},
regular_tasks::queued_command::{queue_command, QueueCommand, QueueCommandHandler}, regular_tasks::queued_command::{
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
},
services::{comms::broadcast_to_room, skills::skill_check_and_grind}, services::{comms::broadcast_to_room, skills::skill_check_and_grind},
static_content::possession_type::possession_data, static_content::possession_type::possession_data,
}; };
@ -14,18 +16,13 @@ use std::time;
pub struct QueueHandler; pub struct QueueHandler;
#[async_trait] #[async_trait]
impl QueueCommandHandler for QueueHandler { impl QueueCommandHandler for QueueHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to wield it, but your ghostly hands slip through it uselessly".to_owned(), "You try to wield it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Wield { possession_id } => possession_id, QueueCommand::Wield { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -37,43 +34,39 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("player/{}", player_item.item_code) { if item.location != format!("player/{}", ctx.item.item_code) {
user_error("You try to wield it but realise you no longer have it".to_owned())? user_error("You try to wield it but realise you no longer have it".to_owned())?
} }
let msg_exp = format!( let msg_exp = format!(
"{} fumbles around with {} {}\n", "{} fumbles around with {} {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&player_item.pronouns.possessive, &ctx.item.pronouns.possessive,
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} fumbles around with {} {}\n", "{} fumbles around with {} {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&player_item.pronouns.possessive, &ctx.item.pronouns.possessive,
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
) )
.await?; .await?;
let mut draw_level: f64 = *player_item let mut draw_level: f64 = ctx
.item
.total_skills .total_skills
.get(&SkillType::Quickdraw) .get(&SkillType::Quickdraw)
.to_owned() .to_owned()
.unwrap_or(&8.0); .unwrap_or(&8.0)
let mut player_item_mut = (*player_item).clone(); .clone();
let skill_result = skill_check_and_grind( let skill_result =
ctx.trans, skill_check_and_grind(ctx.trans, ctx.item, &SkillType::Quickdraw, draw_level).await?;
&mut player_item_mut,
&SkillType::Quickdraw,
draw_level,
)
.await?;
if skill_result < -0.5 { if skill_result < -0.5 {
draw_level -= 2.0; draw_level -= 2.0;
} else if skill_result < -0.25 { } else if skill_result < -0.25 {
@ -83,7 +76,6 @@ impl QueueCommandHandler for QueueHandler {
} else if skill_result > 0.25 { } else if skill_result > 0.25 {
draw_level += 1.0; draw_level += 1.0;
} }
ctx.trans.save_item_model(&player_item_mut).await?;
let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0); let wait_ticks = (12.0 - (draw_level / 2.0)).min(8.0).max(1.0);
Ok(time::Duration::from_millis( Ok(time::Duration::from_millis(
@ -92,18 +84,13 @@ impl QueueCommandHandler for QueueHandler {
} }
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
async fn finish_command( async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
&self, if ctx.item.death_data.is_some() {
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
if player_item.death_data.is_some() {
user_error( user_error(
"You try to wield it, but your ghostly hands slip through it uselessly".to_owned(), "You try to wield it, but your ghostly hands slip through it uselessly".to_owned(),
)?; )?;
} }
let item_id = match command { let item_id = match ctx.command {
QueueCommand::Wield { possession_id } => possession_id, QueueCommand::Wield { possession_id } => possession_id,
_ => user_error("Unexpected command".to_owned())?, _ => user_error("Unexpected command".to_owned())?,
}; };
@ -115,22 +102,22 @@ impl QueueCommandHandler for QueueHandler {
None => user_error("Item not found".to_owned())?, None => user_error("Item not found".to_owned())?,
Some(it) => it, Some(it) => it,
}; };
if item.location != format!("player/{}", player_item.item_code) { if item.location != format!("player/{}", ctx.item.item_code) {
user_error("You try to wield it but realise you no longer have it".to_owned())? user_error("You try to wield it but realise you no longer have it".to_owned())?
} }
let msg_exp = format!( let msg_exp = format!(
"{} wields {}\n", "{} wields {}\n",
&player_item.display_for_sentence(true, 1, true), &ctx.item.display_for_sentence(true, 1, true),
&item.display_for_sentence(true, 1, false) &item.display_for_sentence(true, 1, false)
); );
let msg_nonexp = format!( let msg_nonexp = format!(
"{} wields {}\n", "{} wields {}\n",
&player_item.display_for_sentence(false, 1, true), &ctx.item.display_for_sentence(false, 1, true),
&item.display_for_sentence(false, 1, false) &item.display_for_sentence(false, 1, false)
); );
broadcast_to_room( broadcast_to_room(
ctx.trans, ctx.trans,
&player_item.location, &ctx.item.location,
None, None,
&msg_exp, &msg_exp,
Some(&msg_nonexp), Some(&msg_nonexp),
@ -184,8 +171,9 @@ impl UserVerb for Verb {
{ {
user_error("You can't wield that!".to_owned())?; user_error("You can't wield that!".to_owned())?;
} }
queue_command( queue_command_and_save(
ctx, ctx,
&player_item,
&QueueCommand::Wield { &QueueCommand::Wield {
possession_id: weapon.item_code.clone(), possession_id: weapon.item_code.clone(),
}, },

View File

@ -1,31 +1,37 @@
use serde::{Serialize, Deserialize};
use std::collections::BTreeMap;
use crate::{
language,
static_content::species::SpeciesType,
static_content::possession_type::PossessionType,
static_content::room::Direction,
};
use super::session::Session; use super::session::Session;
use crate::{
language, regular_tasks::queued_command::QueueCommand,
static_content::possession_type::PossessionType, static_content::room::Direction,
static_content::species::SpeciesType,
};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::collections::VecDeque;
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd)]
pub enum BuffCause { pub enum BuffCause {
WaitingTask { task_code: String, task_type: String }, WaitingTask {
ByItem { item_code: String, item_type: String } task_code: String,
task_type: String,
},
ByItem {
item_code: String,
item_type: String,
},
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub enum BuffImpact { pub enum BuffImpact {
ChangeStat { stat: StatType, magnitude: f64 }, ChangeStat { stat: StatType, magnitude: f64 },
ChangeSkill { skill: SkillType, magnitude: f64 } ChangeSkill { skill: SkillType, magnitude: f64 },
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub struct Buff { pub struct Buff {
pub description: String, pub description: String,
pub cause: BuffCause, pub cause: BuffCause,
pub impacts: Vec<BuffImpact> pub impacts: Vec<BuffImpact>,
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
@ -62,47 +68,17 @@ pub enum SkillType {
Throw, Throw,
Track, Track,
Wrestle, Wrestle,
Whips Whips,
} }
impl SkillType { impl SkillType {
pub fn values() -> Vec<SkillType> { pub fn values() -> Vec<SkillType> {
use SkillType::*; use SkillType::*;
vec!( vec![
Appraise, Appraise, Blades, Bombs, Chemistry, Climb, Clubs, Craft, Dodge, Fish, Fists, Flails,
Blades, Focus, Fuck, Hack, Locksmith, Medic, Persuade, Pilot, Pistols, Quickdraw, Repair, Ride,
Bombs, Rifles, Scavenge, Science, Sneak, Spears, Swim, Teach, Throw, Track, Wrestle, Whips,
Chemistry, ]
Climb,
Clubs,
Craft,
Dodge,
Fish,
Fists,
Flails,
Focus,
Fuck,
Hack,
Locksmith,
Medic,
Persuade,
Pilot,
Pistols,
Quickdraw,
Repair,
Ride,
Rifles,
Scavenge,
Science,
Sneak,
Spears,
Swim,
Teach,
Throw,
Track,
Wrestle,
Whips
)
} }
pub fn display(&self) -> &'static str { pub fn display(&self) -> &'static str {
use SkillType::*; use SkillType::*;
@ -139,12 +115,11 @@ impl SkillType {
Throw => "throw", Throw => "throw",
Track => "track", Track => "track",
Wrestle => "wrestle", Wrestle => "wrestle",
Whips => "whips" Whips => "whips",
} }
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum StatType { pub enum StatType {
Brains, Brains,
@ -152,20 +127,13 @@ pub enum StatType {
Brawn, Brawn,
Reflexes, Reflexes,
Endurance, Endurance,
Cool Cool,
} }
impl StatType { impl StatType {
pub fn values() -> Vec<Self> { pub fn values() -> Vec<Self> {
use StatType::*; use StatType::*;
vec!( vec![Brains, Senses, Brawn, Reflexes, Endurance, Cool]
Brains,
Senses,
Brawn,
Reflexes,
Endurance,
Cool
)
} }
pub fn display(&self) -> &'static str { pub fn display(&self) -> &'static str {
use StatType::*; use StatType::*;
@ -175,7 +143,7 @@ impl StatType {
Brawn => "brawn", Brawn => "brawn",
Reflexes => "reflexes", Reflexes => "reflexes",
Endurance => "endurance", Endurance => "endurance",
Cool => "cool" Cool => "cool",
} }
} }
} }
@ -226,7 +194,6 @@ impl Pronouns {
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn default_female() -> Pronouns { pub fn default_female() -> Pronouns {
Pronouns { Pronouns {
@ -246,7 +213,7 @@ pub enum Subattack {
Powerattacking, Powerattacking,
Feinting, Feinting,
Grabbing, Grabbing,
Wrestling Wrestling,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
@ -265,14 +232,14 @@ impl LocationActionType {
use LocationActionType::*; use LocationActionType::*;
match self { match self {
InstalledOnDoorAsLock(_) => false, InstalledOnDoorAsLock(_) => false,
_ => true _ => true,
} }
} }
pub fn is_in_direction(&self, dir: &Direction) -> bool { pub fn is_in_direction(&self, dir: &Direction) -> bool {
use LocationActionType::*; use LocationActionType::*;
match self { match self {
InstalledOnDoorAsLock(d) if d == dir => true, InstalledOnDoorAsLock(d) if d == dir => true,
_ => false _ => false,
} }
} }
} }
@ -295,14 +262,14 @@ pub enum ItemFlag {
#[serde(default)] #[serde(default)]
pub struct ActiveCombat { pub struct ActiveCombat {
pub attacking: Option<String>, pub attacking: Option<String>,
pub attacked_by: Vec<String> pub attacked_by: Vec<String>,
} }
impl Default for ActiveCombat { impl Default for ActiveCombat {
fn default() -> Self { fn default() -> Self {
Self { Self {
attacking: None, attacking: None,
attacked_by: vec!() attacked_by: vec![],
} }
} }
} }
@ -310,27 +277,27 @@ impl Default for ActiveCombat {
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[serde(default)] #[serde(default)]
pub struct ActiveClimb { pub struct ActiveClimb {
pub height: u64 pub height: u64,
} }
impl Default for ActiveClimb { impl Default for ActiveClimb {
fn default() -> Self { fn default() -> Self {
Self { Self { height: 0 }
height: 0
}
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub enum ItemSpecialData { pub enum ItemSpecialData {
ItemWriting { text: String }, ItemWriting {
text: String,
},
DynroomData { DynroomData {
dynzone_code: String, dynzone_code: String,
dynroom_code: String, dynroom_code: String,
}, },
DynzoneData { DynzoneData {
zone_exit: Option<String>, zone_exit: Option<String>,
vacate_after: Option<DateTime<Utc>> vacate_after: Option<DateTime<Utc>>,
}, },
} }
@ -344,7 +311,7 @@ pub struct DynamicEntrance {
#[serde(default)] #[serde(default)]
pub struct DoorState { pub struct DoorState {
pub open: bool, pub open: bool,
pub description: String pub description: String,
} }
impl Default for DoorState { impl Default for DoorState {
@ -359,17 +326,33 @@ impl Default for DoorState {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
#[serde(default)] #[serde(default)]
pub struct DeathData { pub struct DeathData {
pub parts_remaining: Vec<PossessionType> pub parts_remaining: Vec<PossessionType>,
} }
impl Default for DeathData { impl Default for DeathData {
fn default() -> Self { fn default() -> Self {
Self { Self {
parts_remaining: vec!() parts_remaining: vec![],
} }
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub enum FollowState {
// Every move is mirrored to the follower.
Active,
// If the followee is in the same room, mirror a movement and go to Active,
// otherwise ignore and don't mirror. This happens after a mirrored move fails,
// or the follower moves independently.
IfSameRoom,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
pub struct FollowData {
pub follow_whom: String,
pub state: FollowState,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
#[serde(default)] #[serde(default)]
pub struct Item { pub struct Item {
@ -404,6 +387,8 @@ pub struct Item {
pub dynamic_entrance: Option<DynamicEntrance>, pub dynamic_entrance: Option<DynamicEntrance>,
pub owner: Option<String>, pub owner: Option<String>,
pub door_states: Option<BTreeMap<Direction, DoorState>>, pub door_states: Option<BTreeMap<Direction, DoorState>>,
pub following: Option<FollowData>,
pub queue: VecDeque<QueueCommand>,
} }
impl Item { impl Item {
@ -416,8 +401,11 @@ impl Item {
buf.push_str("the body of "); buf.push_str("the body of ");
} }
} }
let singular = if explicit_ok { &self.display } else { let singular = if explicit_ok {
self.display_less_explicit.as_ref().unwrap_or(&self.display) }; &self.display
} else {
self.display_less_explicit.as_ref().unwrap_or(&self.display)
};
if !self.pronouns.is_proper && pluralise == 1 { if !self.pronouns.is_proper && pluralise == 1 {
buf.push_str(language::indefinite_article(&singular)); buf.push_str(language::indefinite_article(&singular));
buf.push(' '); buf.push(' ');
@ -442,13 +430,12 @@ impl Item {
} }
pub fn details_for_session<'l>(self: &'l Self, session: &Session) -> Option<&'l str> { pub fn details_for_session<'l>(self: &'l Self, session: &Session) -> Option<&'l str> {
self.details.as_ref() self.details.as_ref().map(|dets| {
.map(|dets|
session.explicit_if_allowed( session.explicit_if_allowed(
dets.as_str(), dets.as_str(),
self.details_less_explicit.as_ref().map(String::as_str) self.details_less_explicit.as_ref().map(String::as_str),
)
) )
})
} }
pub fn refstr(&self) -> String { pub fn refstr(&self) -> String {
@ -466,7 +453,7 @@ impl Default for Item {
display_less_explicit: None, display_less_explicit: None,
details: None, details: None,
details_less_explicit: None, details_less_explicit: None,
aliases: vec!(), aliases: vec![],
location: "room/storage".to_owned(), location: "room/storage".to_owned(),
action_type: LocationActionType::Normal, action_type: LocationActionType::Normal,
action_type_started: None, action_type_started: None,
@ -480,7 +467,7 @@ impl Default for Item {
total_skills: BTreeMap::new(), total_skills: BTreeMap::new(),
temporary_buffs: Vec::new(), temporary_buffs: Vec::new(),
pronouns: Pronouns::default_inanimate(), pronouns: Pronouns::default_inanimate(),
flags: vec!(), flags: vec![],
sex: None, sex: None,
active_combat: Some(Default::default()), active_combat: Some(Default::default()),
active_climb: None, active_climb: None,
@ -490,6 +477,8 @@ impl Default for Item {
dynamic_entrance: None, dynamic_entrance: None,
owner: None, owner: None,
door_states: None, door_states: None,
following: None,
queue: VecDeque::new(),
} }
} }
} }

View File

@ -1,14 +1,11 @@
use serde::{Serialize, Deserialize};
use std::collections::VecDeque;
use crate::regular_tasks::queued_command::QueueCommand;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(default)] #[serde(default)]
pub struct Session { pub struct Session {
pub source: String, pub source: String,
pub less_explicit_mode: bool, pub less_explicit_mode: bool,
pub queue: VecDeque<QueueCommand>,
pub last_active: Option<DateTime<Utc>>, pub last_active: Option<DateTime<Utc>>,
// Reminder: Consider backwards compatibility when updating this. New fields should generally // Reminder: Consider backwards compatibility when updating this. New fields should generally
// be an Option, or you should ensure the default value is sensible, or things will // be an Option, or you should ensure the default value is sensible, or things will
@ -16,7 +13,11 @@ pub struct Session {
} }
impl Session { impl Session {
pub fn explicit_if_allowed<'l>(self: &Self, explicit: &'l str, non_explicit: Option<&'l str>) -> &'l str { pub fn explicit_if_allowed<'l>(
self: &Self,
explicit: &'l str,
non_explicit: Option<&'l str>,
) -> &'l str {
if self.less_explicit_mode { if self.less_explicit_mode {
non_explicit.unwrap_or(explicit) non_explicit.unwrap_or(explicit)
} else { } else {
@ -27,7 +28,10 @@ impl Session {
impl Default for Session { impl Default for Session {
fn default() -> Self { fn default() -> Self {
Session { source: "unknown".to_owned(), less_explicit_mode: false, Session {
queue: VecDeque::new(), last_active: None } source: "unknown".to_owned(),
less_explicit_mode: false,
last_active: None,
}
} }
} }

View File

@ -1,19 +1,33 @@
use super::{TaskHandler, TaskRunContext}; use super::{TaskHandler, TaskRunContext};
#[double]
use crate::db::DBTrans;
use crate::message_handler::user_commands::{ use crate::message_handler::user_commands::{
close, cut, drop, get, get_user_or_fail, improvise, movement, open, remove, use_cmd, close, cut, drop, get, improvise, movement, open, remove, use_cmd, user_error, wear, wield,
user_error, wear, wield, CommandHandlingError, UResult, VerbContext, CommandHandlingError, UResult, VerbContext,
};
use crate::message_handler::ListenerSession;
use crate::models::session::Session;
use crate::models::{
item::Item,
task::{Task, TaskDetails, TaskMeta},
}; };
use crate::models::task::{Task, TaskDetails, TaskMeta};
use crate::static_content::{possession_type::PossessionType, room::Direction}; use crate::static_content::{possession_type::PossessionType, room::Direction};
use crate::DResult; use crate::DResult;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use mockall_double::double;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::time; use std::time;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)]
pub enum MovementSource {
Command,
Follow,
}
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum QueueCommand { pub enum QueueCommand {
CloseDoor { CloseDoor {
direction: Direction, direction: Direction,
@ -30,6 +44,7 @@ pub enum QueueCommand {
}, },
Movement { Movement {
direction: Direction, direction: Direction,
source: MovementSource,
}, },
OpenDoor { OpenDoor {
direction: Direction, direction: Direction,
@ -76,18 +91,36 @@ impl QueueCommand {
} }
} }
pub struct QueuedCommandContext<'l> {
pub trans: &'l DBTrans,
pub command: &'l QueueCommand,
pub item: &'l mut Item,
}
impl<'l> QueuedCommandContext<'l> {
pub async fn get_session(&self) -> UResult<Option<(ListenerSession, Session)>> {
Ok(if self.item.item_type != "player" {
None
} else {
self.trans
.find_session_for_player(&self.item.item_code)
.await?
})
}
pub async fn explicit(&self) -> UResult<bool> {
Ok(self
.trans
.find_session_for_player(&self.item.item_code)
.await?
.map(|(_, sess)| !sess.less_explicit_mode)
.unwrap_or(false))
}
}
#[async_trait] #[async_trait]
pub trait QueueCommandHandler { pub trait QueueCommandHandler {
async fn start_command( async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration>;
&self, async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()>;
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<time::Duration>;
async fn finish_command(
&self,
ctx: &mut VerbContext<'_>,
command: &QueueCommand,
) -> UResult<()>;
} }
fn queue_command_registry( fn queue_command_registry(
@ -151,38 +184,64 @@ fn queue_command_registry(
}) })
} }
pub async fn queue_command(ctx: &mut VerbContext<'_>, command: &QueueCommand) -> UResult<()> { // Note: Saves item so you can return after calling.
let was_empty = ctx.session_dat.queue.is_empty(); pub async fn queue_command_and_save(
let username = get_user_or_fail(ctx)?.username.to_lowercase(); ctx: &mut VerbContext<'_>,
if ctx.session_dat.queue.len() >= 20 { item: &Item,
command: &QueueCommand,
) -> UResult<()> {
let mut item_mut = item.clone();
queue_command(ctx, &mut item_mut, command).await?;
ctx.trans.save_item_model(&item_mut).await?;
Ok(())
}
// Note: Saves item so you can return after calling.
pub async fn queue_command_for_npc_and_save(
trans: &DBTrans,
item: &Item,
command: &QueueCommand,
) -> DResult<()> {
let mut item_mut = item.clone();
queue_command_for_npc(trans, &mut item_mut, command).await?;
trans.save_item_model(&item_mut).await?;
Ok(())
}
// Caller must save item or it won't actually work.
pub async fn queue_command(
ctx: &mut VerbContext<'_>,
item: &mut Item,
command: &QueueCommand,
) -> UResult<()> {
let was_empty = item.queue.is_empty();
if item.queue.len() >= 20 {
user_error("Can't queue more than 20 actions\n".to_owned())?; user_error("Can't queue more than 20 actions\n".to_owned())?;
} }
ctx.session_dat.queue.push_back(command.clone()); item.queue.push_back(command.clone());
if was_empty { if was_empty {
match queue_command_registry() match queue_command_registry()
.get(&command.name()) .get(&command.name())
.expect("QueueCommand to have been registered") .expect("QueueCommand to have been registered")
.start_command(ctx, &command) .start_command(&mut QueuedCommandContext {
trans: ctx.trans,
command,
item,
})
.await .await
{ {
Err(CommandHandlingError::UserError(err_msg)) => { Err(CommandHandlingError::UserError(err_msg)) => {
ctx.session_dat.queue.truncate(0); item.queue.truncate(0);
ctx.trans
.save_session_model(ctx.session, ctx.session_dat)
.await?;
ctx.trans ctx.trans
.queue_for_session(&ctx.session, Some(&(err_msg + "\r\n"))) .queue_for_session(&ctx.session, Some(&(err_msg + "\r\n")))
.await?; .await?;
} }
Err(CommandHandlingError::SystemError(e)) => Err(e)?, Err(CommandHandlingError::SystemError(e)) => Err(e)?,
Ok(dur) => { Ok(dur) => {
ctx.trans
.save_session_model(ctx.session, ctx.session_dat)
.await?;
ctx.trans ctx.trans
.upsert_task(&Task { .upsert_task(&Task {
meta: TaskMeta { meta: TaskMeta {
task_code: username, task_code: item.refstr(),
next_scheduled: Utc::now() + chrono::Duration::from_std(dur)?, next_scheduled: Utc::now() + chrono::Duration::from_std(dur)?,
..Default::default() ..Default::default()
}, },
@ -195,10 +254,50 @@ pub async fn queue_command(ctx: &mut VerbContext<'_>, command: &QueueCommand) ->
ctx.trans ctx.trans
.queue_for_session(ctx.session, Some("[queued]\n")) .queue_for_session(ctx.session, Some("[queued]\n"))
.await?; .await?;
ctx.trans }
.save_session_model(ctx.session, ctx.session_dat) Ok(())
}
// Caller must save item or it won't actually work.
pub async fn queue_command_for_npc(
trans: &DBTrans,
item: &mut Item,
command: &QueueCommand,
) -> DResult<()> {
let was_empty = item.queue.is_empty();
if item.queue.len() >= 20 {
Err("Can't queue more than 20 actions\n")?;
}
item.queue.push_back(command.clone());
if was_empty {
match queue_command_registry()
.get(&command.name())
.expect("QueueCommand to have been registered")
.start_command(&mut QueuedCommandContext {
trans,
command,
item,
})
.await
{
Err(CommandHandlingError::UserError(_err_msg)) => {
item.queue.truncate(0);
}
Err(CommandHandlingError::SystemError(e)) => Err(e)?,
Ok(dur) => {
trans
.upsert_task(&Task {
meta: TaskMeta {
task_code: item.refstr(),
next_scheduled: Utc::now() + chrono::Duration::from_std(dur)?,
..Default::default()
},
details: TaskDetails::RunQueuedCommand,
})
.await?; .await?;
} }
}
}
Ok(()) Ok(())
} }
@ -206,67 +305,81 @@ pub struct RunQueuedCommandTaskHandler;
#[async_trait] #[async_trait]
impl TaskHandler for RunQueuedCommandTaskHandler { impl TaskHandler for RunQueuedCommandTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let username: &str = ctx.task.meta.task_code.as_str(); let (item_type, item_code) = ctx
let (listener_sess, mut sess_dets) = .task
match ctx.trans.find_session_for_player(username).await? { .meta
None => { .task_code
// Queue is gone if session is gone, and don't schedule another .as_str()
// job, but otherwise this is a successful run. .split_once("/")
return Ok(None); .ok_or("QueuedCommandHandler has bad item refstr")?;
} let mut item = (*(ctx
Some(x) => x, .trans
}; .find_item_by_type_code(item_type, item_code)
let queue_command = match sess_dets.queue.pop_front() { .await?
.ok_or("Can't find player to process QueuedCommand")?))
.clone();
let queue_command = match item.queue.pop_front() {
None => { None => {
return Ok(None); return Ok(None);
} }
Some(x) => x, Some(x) => x,
}; };
let mut user = ctx.trans.find_by_username(username).await?; let mut qcontext = QueuedCommandContext {
let mut verbcontext = VerbContext {
session: &listener_sess,
session_dat: &mut sess_dets,
user_dat: &mut user,
trans: ctx.trans, trans: ctx.trans,
item: &mut item,
command: &queue_command,
}; };
let uresult_finish = queue_command_registry() let uresult_finish = queue_command_registry()
.get(&queue_command.name()) .get(&queue_command.name())
.expect("QueueCommand to have been registered") .expect("QueueCommand to have been registered")
.finish_command(&mut verbcontext, &queue_command) .finish_command(&mut qcontext)
.await; .await;
match uresult_finish { match uresult_finish {
Ok(()) => {} Ok(()) => {}
Err(CommandHandlingError::UserError(err_msg)) => { Err(CommandHandlingError::UserError(err_msg)) => {
if item.item_type == "player" {
if let Some((listener_sess, _)) =
ctx.trans.find_session_for_player(&item.item_code).await?
{
ctx.trans ctx.trans
.queue_for_session(&listener_sess, Some(&(err_msg + "\r\n"))) .queue_for_session(&listener_sess, Some(&(err_msg + "\r\n")))
.await?; .await?;
sess_dets.queue.truncate(0); }
ctx.trans }
.save_session_model(&listener_sess, &sess_dets) item.queue.truncate(0);
.await?; ctx.trans.save_item_model(&item).await?;
return Ok(None); return Ok(None);
} }
Err(CommandHandlingError::SystemError(e)) => Err(e)?, Err(CommandHandlingError::SystemError(e)) => Err(e)?,
}; };
let next_command_opt = verbcontext.session_dat.queue.front().cloned(); let next_command_opt = item.queue.front().cloned();
let result = match next_command_opt { let result = match next_command_opt {
None => None, None => None,
Some(next_command) => { Some(next_command) => {
let mut qcontext = QueuedCommandContext {
trans: ctx.trans,
item: &mut item,
command: &next_command,
};
match queue_command_registry() match queue_command_registry()
.get(&next_command.name()) .get(&next_command.name())
.expect("QueueCommand to have been registered") .expect("QueueCommand to have been registered")
.start_command(&mut verbcontext, &next_command) .start_command(&mut qcontext)
.await .await
{ {
Err(CommandHandlingError::UserError(err_msg)) => { Err(CommandHandlingError::UserError(err_msg)) => {
if item.item_type == "player" {
if let Some((listener_sess, _)) =
ctx.trans.find_session_for_player(&item.item_code).await?
{
ctx.trans ctx.trans
.queue_for_session(&listener_sess, Some(&(err_msg + "\r\n"))) .queue_for_session(&listener_sess, Some(&(err_msg + "\r\n")))
.await?; .await?;
sess_dets.queue.truncate(0); }
ctx.trans }
.save_session_model(&listener_sess, &sess_dets) item.queue.truncate(0);
.await?; ctx.trans.save_item_model(&item).await?;
None None
} }
Err(CommandHandlingError::SystemError(e)) => Err(e)?, Err(CommandHandlingError::SystemError(e)) => Err(e)?,
@ -274,9 +387,7 @@ impl TaskHandler for RunQueuedCommandTaskHandler {
} }
} }
}; };
ctx.trans ctx.trans.save_item_model(&item).await?;
.save_session_model(&listener_sess, &sess_dets)
.await?;
Ok(result) Ok(result)
} }

View File

@ -1,42 +1,36 @@
use super::{ use super::{
StaticItem,
StaticTask,
possession_type::PossessionType, possession_type::PossessionType,
room::{resolve_exit, room_map_by_code},
species::SpeciesType, species::SpeciesType,
room::{ StaticItem, StaticTask,
room_map_by_code,
resolve_exit
}
}; };
use crate::models::{ use crate::{
message_handler::user_commands::{
say::say_to_room, CommandHandlingError, UResult, VerbContext,
},
models::{
consent::ConsentType,
item::{Item, Pronouns, SkillType}, item::{Item, Pronouns, SkillType},
task::{Task, TaskMeta, TaskRecurrence, TaskDetails}, task::{Task, TaskDetails, TaskMeta, TaskRecurrence},
consent::{ConsentType}, },
regular_tasks::{
queued_command::{queue_command_for_npc_and_save, MovementSource, QueueCommand},
TaskHandler, TaskRunContext,
},
services::combat::{corpsify_item, start_attack},
DResult,
}; };
use crate::services::{
combat::{
corpsify_item,
start_attack,
}
};
use once_cell::sync::OnceCell;
use std::collections::BTreeMap;
use crate::message_handler::user_commands::{
VerbContext, UResult, CommandHandlingError,
say::say_to_room,
movement::attempt_move_immediate,
};
use crate::DResult;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Utc; use chrono::Utc;
use rand::{thread_rng, Rng, prelude::*};
use crate::regular_tasks::{TaskHandler, TaskRunContext};
use log::info; use log::info;
use once_cell::sync::OnceCell;
use rand::{prelude::*, thread_rng, Rng};
use std::collections::BTreeMap;
use std::time; use std::time;
pub mod statbot;
mod melbs_citizen; mod melbs_citizen;
mod melbs_dog; mod melbs_dog;
pub mod statbot;
#[async_trait] #[async_trait]
pub trait NPCMessageHandler { pub trait NPCMessageHandler {
@ -45,21 +39,21 @@ pub trait NPCMessageHandler {
ctx: &mut VerbContext, ctx: &mut VerbContext,
source: &Item, source: &Item,
target: &Item, target: &Item,
message: &str message: &str,
) -> UResult<()>; ) -> UResult<()>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum NPCSayType { pub enum NPCSayType {
// Bool is true if it should be filtered for less-explicit. // Bool is true if it should be filtered for less-explicit.
FromFixedList(Vec<(bool, &'static str)>) FromFixedList(Vec<(bool, &'static str)>),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NPCSayInfo { pub struct NPCSayInfo {
pub say_code: &'static str, pub say_code: &'static str,
pub frequency_secs: u64, pub frequency_secs: u64,
pub talk_type: NPCSayType pub talk_type: NPCSayType,
} }
pub struct KillBonus { pub struct KillBonus {
@ -96,37 +90,42 @@ impl Default for NPC {
description: "default", description: "default",
spawn_location: "default", spawn_location: "default",
message_handler: None, message_handler: None,
aliases: vec!(), aliases: vec![],
says: vec!(), says: vec![],
total_xp: 1000, total_xp: 1000,
total_skills: SkillType::values().into_iter() total_skills: SkillType::values()
.map(|sk| (sk.clone(), if &sk == &SkillType::Dodge { 8.0 } else { 10.0 })).collect(), .into_iter()
.map(|sk| {
(
sk.clone(),
if &sk == &SkillType::Dodge { 8.0 } else { 10.0 },
)
})
.collect(),
aggression: 0, aggression: 0,
max_health: 24, max_health: 24,
intrinsic_weapon: None, intrinsic_weapon: None,
species: SpeciesType::Human, species: SpeciesType::Human,
wander_zones: vec!(), wander_zones: vec![],
kill_bonus: None, kill_bonus: None,
player_consents: vec!(), player_consents: vec![],
} }
} }
} }
pub fn npc_list() -> &'static Vec<NPC> { pub fn npc_list() -> &'static Vec<NPC> {
static NPC_LIST: OnceCell<Vec<NPC>> = OnceCell::new(); static NPC_LIST: OnceCell<Vec<NPC>> = OnceCell::new();
NPC_LIST.get_or_init( NPC_LIST.get_or_init(|| {
|| { let mut npcs = vec![NPC {
let mut npcs = vec!(
NPC {
code: "repro_xv_chargen_statbot", code: "repro_xv_chargen_statbot",
name: "Statbot", name: "Statbot",
description: "A silvery shiny metal mechanical being. It lets out a whirring sound as it moves.", description:
"A silvery shiny metal mechanical being. It lets out a whirring sound as it moves.",
spawn_location: "room/repro_xv_chargen", spawn_location: "room/repro_xv_chargen",
message_handler: Some(&statbot::StatbotMessageHandler), message_handler: Some(&statbot::StatbotMessageHandler),
says: vec!(), says: vec![],
..Default::default() ..Default::default()
}, }];
);
npcs.append(&mut melbs_citizen::npc_list()); npcs.append(&mut melbs_citizen::npc_list());
npcs.append(&mut melbs_dog::npc_list()); npcs.append(&mut melbs_dog::npc_list());
npcs npcs
@ -135,28 +134,30 @@ pub fn npc_list() -> &'static Vec<NPC> {
pub fn npc_by_code() -> &'static BTreeMap<&'static str, &'static NPC> { pub fn npc_by_code() -> &'static BTreeMap<&'static str, &'static NPC> {
static NPC_CODE_MAP: OnceCell<BTreeMap<&'static str, &'static NPC>> = OnceCell::new(); static NPC_CODE_MAP: OnceCell<BTreeMap<&'static str, &'static NPC>> = OnceCell::new();
NPC_CODE_MAP.get_or_init( NPC_CODE_MAP.get_or_init(|| npc_list().iter().map(|npc| (npc.code, npc)).collect())
|| npc_list().iter()
.map(|npc| (npc.code, npc))
.collect())
} }
pub fn npc_say_info_by_npc_code_say_code() -> &'static BTreeMap<(&'static str, &'static str), pub fn npc_say_info_by_npc_code_say_code(
&'static NPCSayInfo> { ) -> &'static BTreeMap<(&'static str, &'static str), &'static NPCSayInfo> {
static NPC_SAYINFO_MAP: OnceCell<BTreeMap<(&'static str, &'static str), static NPC_SAYINFO_MAP: OnceCell<BTreeMap<(&'static str, &'static str), &'static NPCSayInfo>> =
&'static NPCSayInfo>> = OnceCell::new(); OnceCell::new();
NPC_SAYINFO_MAP.get_or_init( NPC_SAYINFO_MAP.get_or_init(|| {
|| npc_list().iter().flat_map( npc_list()
|npc| npc.says.iter().map( .iter()
|says| ((npc.code, says.say_code), says) .flat_map(|npc| {
) npc.says
).collect()) .iter()
.map(|says| ((npc.code, says.say_code), says))
})
.collect()
})
} }
pub fn npc_static_items() -> Box<dyn Iterator<Item = StaticItem>> { pub fn npc_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
Box::new(npc_list().iter().map(|c| StaticItem { Box::new(npc_list().iter().map(|c| StaticItem {
item_code: c.code, item_code: c.code,
initial_item: Box::new(|| Item { initial_item: Box::new(|| {
Item {
item_code: c.code.to_owned(), item_code: c.code.to_owned(),
item_type: "npc".to_owned(), item_type: "npc".to_owned(),
display: c.name.to_owned(), display: c.name.to_owned(),
@ -168,106 +169,130 @@ pub fn npc_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
total_skills: c.total_skills.clone(), total_skills: c.total_skills.clone(),
species: c.species.clone(), species: c.species.clone(),
health: c.max_health.clone(), health: c.max_health.clone(),
aliases: c.aliases.iter().map(|a| (*a).to_owned()).collect::<Vec<String>>(), aliases: c
.aliases
.iter()
.map(|a| (*a).to_owned())
.collect::<Vec<String>>(),
..Item::default() ..Item::default()
}) }
}),
})) }))
} }
pub fn npc_say_tasks() -> Box<dyn Iterator<Item = StaticTask>> { pub fn npc_say_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
Box::new(npc_list().iter().flat_map(|c| c.says.iter().map(|say| StaticTask { Box::new(npc_list().iter().flat_map(|c| {
c.says.iter().map(|say| StaticTask {
task_code: c.code.to_owned() + "_" + say.say_code, task_code: c.code.to_owned() + "_" + say.say_code,
initial_task: Box::new( initial_task: Box::new(|| {
|| {
let mut rng = thread_rng(); let mut rng = thread_rng();
Task { Task {
meta: TaskMeta { meta: TaskMeta {
task_code: c.code.to_owned() + "_" + say.say_code, task_code: c.code.to_owned() + "_" + say.say_code,
is_static: true, is_static: true,
recurrence: Some(TaskRecurrence::FixedDuration { seconds: say.frequency_secs as u32 }), recurrence: Some(TaskRecurrence::FixedDuration {
next_scheduled: Utc::now() + chrono::Duration::seconds(rng.gen_range(0..say.frequency_secs) as i64), seconds: say.frequency_secs as u32,
}),
next_scheduled: Utc::now()
+ chrono::Duration::seconds(
rng.gen_range(0..say.frequency_secs) as i64
),
..TaskMeta::default() ..TaskMeta::default()
}, },
details: TaskDetails::NPCSay { details: TaskDetails::NPCSay {
npc_code: c.code.to_owned(), npc_code: c.code.to_owned(),
say_code: say.say_code.to_owned() say_code: say.say_code.to_owned(),
}, },
} }
}),
}) })
}))) }))
} }
pub fn npc_wander_tasks() -> Box<dyn Iterator<Item = StaticTask>> { pub fn npc_wander_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
Box::new(npc_list().iter().filter(|c| !c.wander_zones.is_empty()) Box::new(
npc_list()
.iter()
.filter(|c| !c.wander_zones.is_empty())
.map(|c| StaticTask { .map(|c| StaticTask {
task_code: c.code.to_owned(), task_code: c.code.to_owned(),
initial_task: Box::new( initial_task: Box::new(|| {
|| {
let mut rng = thread_rng(); let mut rng = thread_rng();
Task { Task {
meta: TaskMeta { meta: TaskMeta {
task_code: c.code.to_owned(), task_code: c.code.to_owned(),
is_static: true, is_static: true,
recurrence: Some(TaskRecurrence::FixedDuration { seconds: rng.gen_range(250..350) as u32 }), recurrence: Some(TaskRecurrence::FixedDuration {
next_scheduled: Utc::now() + chrono::Duration::seconds(rng.gen_range(0..300) as i64), seconds: rng.gen_range(250..350) as u32,
}),
next_scheduled: Utc::now()
+ chrono::Duration::seconds(rng.gen_range(0..300) as i64),
..TaskMeta::default() ..TaskMeta::default()
}, },
details: TaskDetails::NPCWander { details: TaskDetails::NPCWander {
npc_code: c.code.to_owned(), npc_code: c.code.to_owned(),
}, },
} }
}) }),
})) }),
)
} }
pub fn npc_aggro_tasks() -> Box<dyn Iterator<Item = StaticTask>> { pub fn npc_aggro_tasks() -> Box<dyn Iterator<Item = StaticTask>> {
Box::new(npc_list().iter().filter(|c| c.aggression != 0) Box::new(
npc_list()
.iter()
.filter(|c| c.aggression != 0)
.map(|c| StaticTask { .map(|c| StaticTask {
task_code: c.code.to_owned(), task_code: c.code.to_owned(),
initial_task: Box::new( initial_task: Box::new(|| {
|| {
let mut rng = thread_rng(); let mut rng = thread_rng();
let aggro_time = (rng.gen_range(450..550) as u64) / c.aggression; let aggro_time = (rng.gen_range(450..550) as u64) / c.aggression;
Task { Task {
meta: TaskMeta { meta: TaskMeta {
task_code: c.code.to_owned(), task_code: c.code.to_owned(),
is_static: true, is_static: true,
recurrence: Some(TaskRecurrence::FixedDuration { seconds: aggro_time as u32 }), recurrence: Some(TaskRecurrence::FixedDuration {
next_scheduled: Utc::now() + chrono::Duration::seconds(rng.gen_range(0..aggro_time) as i64), seconds: aggro_time as u32,
}),
next_scheduled: Utc::now()
+ chrono::Duration::seconds(rng.gen_range(0..aggro_time) as i64),
..TaskMeta::default() ..TaskMeta::default()
}, },
details: TaskDetails::NPCAggro { details: TaskDetails::NPCAggro {
npc_code: c.code.to_owned(), npc_code: c.code.to_owned(),
}, },
} }
}) }),
})) }),
)
} }
pub struct NPCSayTaskHandler; pub struct NPCSayTaskHandler;
#[async_trait] #[async_trait]
impl TaskHandler for NPCSayTaskHandler { impl TaskHandler for NPCSayTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let (npc_code, say_code) = match &ctx.task.details { let (npc_code, say_code) = match &ctx.task.details {
TaskDetails::NPCSay { npc_code, say_code } => (npc_code.clone(), say_code.clone()), TaskDetails::NPCSay { npc_code, say_code } => (npc_code.clone(), say_code.clone()),
_ => Err("Expected NPC say task to be NPCSay type")? _ => Err("Expected NPC say task to be NPCSay type")?,
}; };
let say_info = match npc_say_info_by_npc_code_say_code().get(&(&npc_code, &say_code)) { let say_info = match npc_say_info_by_npc_code_say_code().get(&(&npc_code, &say_code)) {
None => { None => {
info!("NPCSayTaskHandler can't find NPCSayInfo for npc {} say_code {}", info!(
npc_code, say_code); "NPCSayTaskHandler can't find NPCSayInfo for npc {} say_code {}",
npc_code, say_code
);
return Ok(None); return Ok(None);
} }
Some(r) => r Some(r) => r,
}; };
let npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { let npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
None => { None => {
info!("NPCSayTaskHandler can't find NPC {}", npc_code); info!("NPCSayTaskHandler can't find NPC {}", npc_code);
return Ok(None); return Ok(None);
} }
Some(r) => r Some(r) => r,
}; };
if npc_item.death_data.is_some() { if npc_item.death_data.is_some() {
@ -279,22 +304,34 @@ impl TaskHandler for NPCSayTaskHandler {
let mut rng = thread_rng(); let mut rng = thread_rng();
match l[..].choose(&mut rng) { match l[..].choose(&mut rng) {
None => { None => {
info!("NPCSayTaskHandler NPCSayInfo for npc {} say_code {} has no choices", info!(
npc_code, say_code); "NPCSayTaskHandler NPCSayInfo for npc {} say_code {} has no choices",
npc_code, say_code
);
return Ok(None); return Ok(None);
} }
Some(r) => r.clone() Some(r) => r.clone(),
} }
} }
}; };
match say_to_room(ctx.trans, &npc_item, &npc_item.location, say_what, is_explicit).await { match say_to_room(
ctx.trans,
&npc_item,
&npc_item.location,
say_what,
is_explicit,
)
.await
{
Ok(()) => {} Ok(()) => {}
Err(CommandHandlingError::UserError(e)) => { Err(CommandHandlingError::UserError(e)) => {
info!("NPCSayHandler couldn't send for npc {} say_code {}: {}", info!(
npc_code, say_code, e); "NPCSayHandler couldn't send for npc {} say_code {}: {}",
npc_code, say_code, e
);
} }
Err(CommandHandlingError::SystemError(e)) => Err(e)? Err(CommandHandlingError::SystemError(e)) => Err(e)?,
} }
Ok(None) Ok(None)
} }
@ -307,28 +344,34 @@ impl TaskHandler for NPCWanderTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let npc_code = match &ctx.task.details { let npc_code = match &ctx.task.details {
TaskDetails::NPCWander { npc_code } => npc_code.clone(), TaskDetails::NPCWander { npc_code } => npc_code.clone(),
_ => Err("Expected NPCWander type")? _ => Err("Expected NPCWander type")?,
}; };
let npc = match npc_by_code().get(npc_code.as_str()) { let npc = match npc_by_code().get(npc_code.as_str()) {
None => { None => {
info!("NPC {} is gone / not yet in static items, ignoring in wander handler", &npc_code); info!(
return Ok(None) "NPC {} is gone / not yet in static items, ignoring in wander handler",
}, &npc_code
Some(r) => r );
return Ok(None);
}
Some(r) => r,
}; };
let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
None => { None => {
info!("NPC {} is gone / not yet in DB, ignoring in wander handler", &npc_code); info!(
return Ok(None) "NPC {} is gone / not yet in DB, ignoring in wander handler",
}, &npc_code
Some(r) => r );
return Ok(None);
}
Some(r) => r,
}; };
if item.death_data.is_some() { if item.death_data.is_some() {
return Ok(None) return Ok(None);
} }
let (ltype, lcode) = match item.location.split_once("/") { let (ltype, lcode) = match item.location.split_once("/") {
None => return Ok(None), None => return Ok(None),
Some(r) => r Some(r) => r,
}; };
if ltype != "room" { if ltype != "room" {
let mut new_item = (*item).clone(); let mut new_item = (*item).clone();
@ -342,22 +385,31 @@ impl TaskHandler for NPCWanderTaskHandler {
new_item.location = npc.spawn_location.to_owned(); new_item.location = npc.spawn_location.to_owned();
ctx.trans.save_item_model(&new_item).await?; ctx.trans.save_item_model(&new_item).await?;
return Ok(None); return Ok(None);
},
Some(r) => r
};
let ex_iter = room.exits
.iter()
.filter(
|ex| resolve_exit(room, ex).map(
|new_room| npc.wander_zones.contains(&new_room.zone) &&
!new_room.repel_npc).unwrap_or(false)
);
let dir_opt = ex_iter.choose(&mut thread_rng()).map(|ex| ex.direction.clone()).clone();
if let Some(dir) = dir_opt {
match attempt_move_immediate(ctx.trans, &item, &dir, &mut None).await {
Ok(()) | Err(CommandHandlingError::UserError(_)) => {},
Err(CommandHandlingError::SystemError(e)) => Err(e)?
} }
Some(r) => r,
};
if !item.queue.is_empty() {
return Ok(None);
}
let ex_iter = room.exits.iter().filter(|ex| {
resolve_exit(room, ex)
.map(|new_room| npc.wander_zones.contains(&new_room.zone) && !new_room.repel_npc)
.unwrap_or(false)
});
let dir_opt = ex_iter
.choose(&mut thread_rng())
.map(|ex| ex.direction.clone())
.clone();
if let Some(dir) = dir_opt {
queue_command_for_npc_and_save(
&ctx.trans,
&item,
&QueueCommand::Movement {
direction: dir.clone(),
source: MovementSource::Command,
},
)
.await?;
} }
Ok(None) Ok(None)
} }
@ -370,28 +422,40 @@ impl TaskHandler for NPCAggroTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let npc_code = match &ctx.task.details { let npc_code = match &ctx.task.details {
TaskDetails::NPCAggro { npc_code } => npc_code.clone(), TaskDetails::NPCAggro { npc_code } => npc_code.clone(),
_ => Err("Expected NPCAggro type")? _ => Err("Expected NPCAggro type")?,
}; };
let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { let item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
None => { None => {
info!("NPC {} is gone / not yet in DB, ignoring in aggro handler", &npc_code); info!(
return Ok(None) "NPC {} is gone / not yet in DB, ignoring in aggro handler",
}, &npc_code
Some(r) => r );
return Ok(None);
}
Some(r) => r,
}; };
if item.death_data.is_some() || item.active_combat.as_ref().map(|ac| ac.attacking.is_some()).unwrap_or(false) { if item.death_data.is_some()
|| item
.active_combat
.as_ref()
.map(|ac| ac.attacking.is_some())
.unwrap_or(false)
{
return Ok(None); return Ok(None);
} }
let items_loc = ctx.trans.find_items_by_location(&item.location).await?; let items_loc = ctx.trans.find_items_by_location(&item.location).await?;
let vic_opt = items_loc let vic_opt = items_loc
.iter() .iter()
.filter(|it| (it.item_type == "player" || it.item_type == "npc") && .filter(|it| {
it.death_data.is_none() && (it.item_type != item.item_type || it.item_code != item.item_code)) (it.item_type == "player" || it.item_type == "npc")
&& it.death_data.is_none()
&& (it.item_type != item.item_type || it.item_code != item.item_code)
})
.choose(&mut thread_rng()); .choose(&mut thread_rng());
if let Some(victim) = vic_opt { if let Some(victim) = vic_opt {
match start_attack(ctx.trans, &item, victim).await { match start_attack(ctx.trans, &item, victim).await {
Ok(()) | Err(CommandHandlingError::UserError(_)) => {} Ok(()) | Err(CommandHandlingError::UserError(_)) => {}
Err(CommandHandlingError::SystemError(e)) => Err(e)? Err(CommandHandlingError::SystemError(e)) => Err(e)?,
} }
} }
@ -406,16 +470,16 @@ impl TaskHandler for NPCRecloneTaskHandler {
async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> { async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
let npc_code = match &ctx.task.details { let npc_code = match &ctx.task.details {
TaskDetails::RecloneNPC { npc_code } => npc_code.clone(), TaskDetails::RecloneNPC { npc_code } => npc_code.clone(),
_ => Err("Expected RecloneNPC type")? _ => Err("Expected RecloneNPC type")?,
}; };
let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? { let mut npc_item = match ctx.trans.find_item_by_type_code("npc", &npc_code).await? {
None => { return Ok(None) }, None => return Ok(None),
Some(r) => (*r).clone() Some(r) => (*r).clone(),
}; };
let npc = match npc_by_code().get(npc_code.as_str()) { let npc = match npc_by_code().get(npc_code.as_str()) {
None => { return Ok(None) }, None => return Ok(None),
Some(r) => r Some(r) => r,
}; };
if npc_item.death_data.is_none() { if npc_item.death_data.is_none() {

View File

@ -1,8 +1,5 @@
use super::{NPC, NPCSayInfo, NPCSayType}; use super::{NPCSayInfo, NPCSayType, NPC};
use crate::models::{ use crate::models::{consent::ConsentType, item::Pronouns};
item::Pronouns,
consent::ConsentType,
};
pub fn npc_list() -> Vec<NPC> { pub fn npc_list() -> Vec<NPC> {
use NPCSayType::FromFixedList; use NPCSayType::FromFixedList;
@ -38,66 +35,341 @@ pub fn npc_list() -> Vec<NPC> {
} }
} }
vec!( vec![
citizen!("1", "Matthew Thomas", "kingst_latrobest", Pronouns::default_male()), citizen!(
"1",
"Matthew Thomas",
"kingst_latrobest",
Pronouns::default_male()
),
citizen!("2", "Matthew Perez", "kingst_20", Pronouns::default_male()), citizen!("2", "Matthew Perez", "kingst_20", Pronouns::default_male()),
citizen!("3", "Kimberly Jackson", "kingst_40", Pronouns::default_female()), citizen!(
citizen!("4", "Michael Sanchez", "kingst_50", Pronouns::default_male()), "3",
citizen!("5", "Jessica Davis", "kingst_bourkest", Pronouns::default_female()), "Kimberly Jackson",
"kingst_40",
Pronouns::default_female()
),
citizen!(
"4",
"Michael Sanchez",
"kingst_50",
Pronouns::default_male()
),
citizen!(
"5",
"Jessica Davis",
"kingst_bourkest",
Pronouns::default_female()
),
citizen!("6", "Robert Davis", "kingst_70", Pronouns::default_male()), citizen!("6", "Robert Davis", "kingst_70", Pronouns::default_male()),
citizen!("7", "Paul Lewis", "kingst_90", Pronouns::default_male()), citizen!("7", "Paul Lewis", "kingst_90", Pronouns::default_male()),
citizen!("8", "Andrew Moore", "kingst_collinsst", Pronouns::default_male()), citizen!(
citizen!("9", "Betty Thomas", "kingst_100", Pronouns::default_female()), "8",
citizen!("10", "Mary Robinson", "kingst_110", Pronouns::default_female()), "Andrew Moore",
citizen!("11", "Lisa Lopez", "kingst_flinderst", Pronouns::default_female()), "kingst_collinsst",
citizen!("12", "Kimberly Martinez", "flindersst_200", Pronouns::default_female()), Pronouns::default_male()
citizen!("13", "Anthony Nguyen", "flindersst_190", Pronouns::default_male()), ),
citizen!("14", "Joshua Green", "flindersst_180", Pronouns::default_male()), citizen!(
citizen!("15", "Emily Wright", "flindersst_170", Pronouns::default_female()), "9",
citizen!("16", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()), "Betty Thomas",
citizen!("17", "Jessica Miller", "kingst_80", Pronouns::default_female()), "kingst_100",
citizen!("18", "Anthony Lopez", "lonsdalest_140", Pronouns::default_male()), Pronouns::default_female()
citizen!("19", "John Lopez", "elizabethst_lonsdalest", Pronouns::default_male()), ),
citizen!("20", "Thomas Garcia", "williamsst_120", Pronouns::default_male()), citizen!(
citizen!("21", "Donna Thompson", "elizabethst_60", Pronouns::default_female()), "10",
citizen!("22", "Matthew Davis", "williamsst_100", Pronouns::default_male()), "Mary Robinson",
citizen!("23", "Steven Jones", "swanstonst_120", Pronouns::default_male()), "kingst_110",
citizen!("24", "Linda Smith", "swanstonst_lonsdalest", Pronouns::default_male()), Pronouns::default_female()
citizen!("25", "Karen Rodriguez", "bourkest_180", Pronouns::default_female()), ),
citizen!("26", "Paul Scott", "swanstonst_70", Pronouns::default_male()), citizen!(
citizen!("27", "Ashley Thomas", "lonsdalest_130", Pronouns::default_male()), "11",
citizen!("28", "Sandra Scott", "elizabethst_30", Pronouns::default_female()), "Lisa Lopez",
citizen!("29", "Michael Rodriguez", "swanstonst_70", Pronouns::default_male()), "kingst_flinderst",
citizen!("30", "Donald Miller", "elizabethst_30", Pronouns::default_male()), Pronouns::default_female()
citizen!("31", "Charles Moore", "lonsdalest_160", Pronouns::default_male()), ),
citizen!("32", "Ashley Sanchez", "kingst_100", Pronouns::default_male()), citizen!(
citizen!("33", "Margaret Lewis", "flindersst_180", Pronouns::default_female()), "12",
citizen!("34", "Sandra Thompson", "swanstonst_80", Pronouns::default_female()), "Kimberly Martinez",
citizen!("35", "Sandra King", "lonsdalest_150", Pronouns::default_female()), "flindersst_200",
citizen!("36", "Lisa Anderson", "lonsdalest_210", Pronouns::default_female()), Pronouns::default_female()
citizen!("37", "Kimberly Martin", "kingst_80", Pronouns::default_female()), ),
citizen!("38", "Susan Smith", "latrobest_190", Pronouns::default_female()), citizen!(
citizen!("39", "Susan Martin", "collinsst_150", Pronouns::default_female()), "13",
citizen!("40", "Linda Scott", "williamsst_30", Pronouns::default_female()), "Anthony Nguyen",
citizen!("41", "Donald Miller", "elizabethst_80", Pronouns::default_male()), "flindersst_190",
Pronouns::default_male()
),
citizen!(
"14",
"Joshua Green",
"flindersst_180",
Pronouns::default_male()
),
citizen!(
"15",
"Emily Wright",
"flindersst_170",
Pronouns::default_female()
),
citizen!(
"16",
"Ashley Thomas",
"lonsdalest_130",
Pronouns::default_male()
),
citizen!(
"17",
"Jessica Miller",
"kingst_80",
Pronouns::default_female()
),
citizen!(
"18",
"Anthony Lopez",
"lonsdalest_140",
Pronouns::default_male()
),
citizen!(
"19",
"John Lopez",
"elizabethst_lonsdalest",
Pronouns::default_male()
),
citizen!(
"20",
"Thomas Garcia",
"williamsst_120",
Pronouns::default_male()
),
citizen!(
"21",
"Donna Thompson",
"elizabethst_60",
Pronouns::default_female()
),
citizen!(
"22",
"Matthew Davis",
"williamsst_100",
Pronouns::default_male()
),
citizen!(
"23",
"Steven Jones",
"swanstonst_120",
Pronouns::default_male()
),
citizen!(
"24",
"Linda Smith",
"swanstonst_lonsdalest",
Pronouns::default_male()
),
citizen!(
"25",
"Karen Rodriguez",
"bourkest_180",
Pronouns::default_female()
),
citizen!(
"26",
"Paul Scott",
"swanstonst_70",
Pronouns::default_male()
),
citizen!(
"27",
"Ashley Thomas",
"lonsdalest_130",
Pronouns::default_male()
),
citizen!(
"28",
"Sandra Scott",
"elizabethst_30",
Pronouns::default_female()
),
citizen!(
"29",
"Michael Rodriguez",
"swanstonst_70",
Pronouns::default_male()
),
citizen!(
"30",
"Donald Miller",
"elizabethst_30",
Pronouns::default_male()
),
citizen!(
"31",
"Charles Moore",
"lonsdalest_160",
Pronouns::default_male()
),
citizen!(
"32",
"Ashley Sanchez",
"kingst_100",
Pronouns::default_male()
),
citizen!(
"33",
"Margaret Lewis",
"flindersst_180",
Pronouns::default_female()
),
citizen!(
"34",
"Sandra Thompson",
"swanstonst_80",
Pronouns::default_female()
),
citizen!(
"35",
"Sandra King",
"lonsdalest_150",
Pronouns::default_female()
),
citizen!(
"36",
"Lisa Anderson",
"lonsdalest_210",
Pronouns::default_female()
),
citizen!(
"37",
"Kimberly Martin",
"kingst_80",
Pronouns::default_female()
),
citizen!(
"38",
"Susan Smith",
"latrobest_190",
Pronouns::default_female()
),
citizen!(
"39",
"Susan Martin",
"collinsst_150",
Pronouns::default_female()
),
citizen!(
"40",
"Linda Scott",
"williamsst_30",
Pronouns::default_female()
),
citizen!(
"41",
"Donald Miller",
"elizabethst_80",
Pronouns::default_male()
),
citizen!("42", "Mark Hill", "collinsst_120", Pronouns::default_male()), citizen!("42", "Mark Hill", "collinsst_120", Pronouns::default_male()),
citizen!("43", "William Perez", "queenst_90", Pronouns::default_male()), citizen!(
citizen!("44", "Donald Perez", "queenst_lonsdalest", Pronouns::default_male()), "43",
citizen!("45", "Lisa Rodriguez", "collinsst_100", Pronouns::default_female()), "William Perez",
citizen!("46", "James Adams", "latrobest_150", Pronouns::default_male()), "queenst_90",
citizen!("47", "James Moore", "latrobest_130", Pronouns::default_male()), Pronouns::default_male()
citizen!("48", "Joseph Martin", "bourkest_150", Pronouns::default_male()), ),
citizen!(
"44",
"Donald Perez",
"queenst_lonsdalest",
Pronouns::default_male()
),
citizen!(
"45",
"Lisa Rodriguez",
"collinsst_100",
Pronouns::default_female()
),
citizen!(
"46",
"James Adams",
"latrobest_150",
Pronouns::default_male()
),
citizen!(
"47",
"James Moore",
"latrobest_130",
Pronouns::default_male()
),
citizen!(
"48",
"Joseph Martin",
"bourkest_150",
Pronouns::default_male()
),
citizen!("49", "Matthew Jones", "kingst_60", Pronouns::default_male()), citizen!("49", "Matthew Jones", "kingst_60", Pronouns::default_male()),
citizen!("50", "Michael Sanchez", "queenst_100", Pronouns::default_male()), citizen!(
citizen!("51", "Donna Torres", "flindersst_150", Pronouns::default_female()), "50",
citizen!("52", "Barbara Garcia", "swanstonst_50", Pronouns::default_female()), "Michael Sanchez",
citizen!("53", "Daniel Miller", "bourkest_110", Pronouns::default_male()), "queenst_100",
citizen!("54", "Robert Young", "kingst_collinsst", Pronouns::default_male()), Pronouns::default_male()
citizen!("55", "Donald Flores", "swanstonst_40", Pronouns::default_male()), ),
citizen!("56", "Charles Thomas", "flindersst_110", Pronouns::default_male()), citizen!(
citizen!("57", "William Torres", "swanstonst_60", Pronouns::default_male()), "51",
citizen!("58", "Barbara Gonzalez", "collinsst_190", Pronouns::default_female()), "Donna Torres",
citizen!("59", "Mary Smith", "bourkest_180", Pronouns::default_female()), "flindersst_150",
citizen!("60", "Michael John", "williamsst_110", Pronouns::default_male()), Pronouns::default_female()
) ),
citizen!(
"52",
"Barbara Garcia",
"swanstonst_50",
Pronouns::default_female()
),
citizen!(
"53",
"Daniel Miller",
"bourkest_110",
Pronouns::default_male()
),
citizen!(
"54",
"Robert Young",
"kingst_collinsst",
Pronouns::default_male()
),
citizen!(
"55",
"Donald Flores",
"swanstonst_40",
Pronouns::default_male()
),
citizen!(
"56",
"Charles Thomas",
"flindersst_110",
Pronouns::default_male()
),
citizen!(
"57",
"William Torres",
"swanstonst_60",
Pronouns::default_male()
),
citizen!(
"58",
"Barbara Gonzalez",
"collinsst_190",
Pronouns::default_female()
),
citizen!(
"59",
"Mary Smith",
"bourkest_180",
Pronouns::default_female()
),
citizen!(
"60",
"Michael John",
"williamsst_110",
Pronouns::default_male()
),
]
} }

View File

@ -1,12 +1,6 @@
use super::{NPC, KillBonus}; use super::{KillBonus, NPC};
use crate::models::{ use crate::models::{consent::ConsentType, item::Pronouns};
item::Pronouns, use crate::static_content::{possession_type::PossessionType, species::SpeciesType};
consent::ConsentType,
};
use crate::static_content::{
possession_type::PossessionType,
species::SpeciesType
};
macro_rules! dog { macro_rules! dog {
($code:expr, $adj:expr, $spawn: expr) => { ($code:expr, $adj:expr, $spawn: expr) => {
@ -32,7 +26,7 @@ macro_rules! dog {
} }
pub fn npc_list() -> Vec<NPC> { pub fn npc_list() -> Vec<NPC> {
vec!( vec![
dog!("1", "smelly black", "melbs_williamsst_80"), dog!("1", "smelly black", "melbs_williamsst_80"),
dog!("2", "howling black", "melbs_swanstonst_100"), dog!("2", "howling black", "melbs_swanstonst_100"),
dog!("3", "smelly black", "melbs_collinsst_160"), dog!("3", "smelly black", "melbs_collinsst_160"),
@ -93,5 +87,5 @@ pub fn npc_list() -> Vec<NPC> {
dog!("58", "reeking brown", "melbs_bourkest_130"), dog!("58", "reeking brown", "melbs_bourkest_130"),
dog!("59", "mangy light brown", "melbs_queenst_50"), dog!("59", "mangy light brown", "melbs_queenst_50"),
dog!("60", "growling white", "melbs_kingst_110"), dog!("60", "growling white", "melbs_kingst_110"),
) ]
} }

View File

@ -1,19 +1,20 @@
use super::super::room::{Exit, ExitBlocker};
use super::NPCMessageHandler; use super::NPCMessageHandler;
use super::super::room::{ExitBlocker, Exit};
use crate::message_handler::user_commands::{
VerbContext, UResult,
get_user_or_fail,
get_user_or_fail_mut,
parsing::parse_to_space
};
use async_trait::async_trait;
use crate::models::{
item::{Item, Sex, Pronouns, StatType},
user::{User},
session::Session
};
use crate::services::skills::calculate_total_stats_skills_for_user; use crate::services::skills::calculate_total_stats_skills_for_user;
use crate::{
message_handler::user_commands::{
get_user_or_fail, get_user_or_fail_mut, parsing::parse_to_space, CommandHandlingError,
UResult, VerbContext,
},
models::{
item::{Item, Pronouns, Sex, StatType},
session::Session,
user::User,
},
regular_tasks::queued_command::QueuedCommandContext,
};
use ansi::ansi; use ansi::ansi;
use async_trait::async_trait;
use nom::character::complete::u8; use nom::character::complete::u8;
pub struct StatbotMessageHandler; pub struct StatbotMessageHandler;
@ -29,24 +30,19 @@ pub enum StatbotState {
FixTotals, FixTotals,
AssignSex, AssignSex,
SetDescription, SetDescription,
Done Done,
} }
async fn reply(ctx: &VerbContext<'_>, msg: &str) -> UResult<()> { async fn reply(ctx: &VerbContext<'_>, msg: &str) -> UResult<()> {
ctx.trans.queue_for_session( ctx.trans
.queue_for_session(
ctx.session, ctx.session,
Some(&format!(ansi!("Statbot replies in a mechanical voice: <blue>\"{}\"<reset>\n"), Some(&format!(
msg)) ansi!("Statbot replies in a mechanical voice: <blue>\"{}\"<reset>\n"),
).await?; msg
Ok(()) )),
} )
.await?;
async fn shout(ctx: &VerbContext<'_>, msg: &str) -> UResult<()> {
ctx.trans.queue_for_session(
ctx.session,
Some(&format!(ansi!("Statbot shouts in a stern mechanical voice: <red>\"{}\"<reset>\n"),
msg))
).await?;
Ok(()) Ok(())
} }
@ -82,23 +78,64 @@ fn work_out_state(user: &User, item: &Item) -> StatbotState {
} }
fn points_left(user: &User) -> f64 { fn points_left(user: &User) -> f64 {
let brn = user.raw_stats.get(&StatType::Brains).cloned().unwrap_or(8.0); let brn = user
let sen = user.raw_stats.get(&StatType::Senses).cloned().unwrap_or(8.0); .raw_stats
.get(&StatType::Brains)
.cloned()
.unwrap_or(8.0);
let sen = user
.raw_stats
.get(&StatType::Senses)
.cloned()
.unwrap_or(8.0);
let brw = user.raw_stats.get(&StatType::Brawn).cloned().unwrap_or(8.0); let brw = user.raw_stats.get(&StatType::Brawn).cloned().unwrap_or(8.0);
let refl = user.raw_stats.get(&StatType::Reflexes).cloned().unwrap_or(8.0); let refl = user
let end = user.raw_stats.get(&StatType::Endurance).cloned().unwrap_or(8.0); .raw_stats
.get(&StatType::Reflexes)
.cloned()
.unwrap_or(8.0);
let end = user
.raw_stats
.get(&StatType::Endurance)
.cloned()
.unwrap_or(8.0);
let col = user.raw_stats.get(&StatType::Cool).cloned().unwrap_or(8.0); let col = user.raw_stats.get(&StatType::Cool).cloned().unwrap_or(8.0);
(62 - (brn + sen + brw + refl + end + col) as i16).max(0) as f64 (62 - (brn + sen + brw + refl + end + col) as i16).max(0) as f64
} }
fn next_action_text(session: &Session, user: &User, item: &Item) -> String { fn next_action_text(session: &Session, user: &User, item: &Item) -> String {
let brn = user.raw_stats.get(&StatType::Brains).cloned().unwrap_or(8.0); let brn = user
let sen = user.raw_stats.get(&StatType::Senses).cloned().unwrap_or(8.0); .raw_stats
.get(&StatType::Brains)
.cloned()
.unwrap_or(8.0);
let sen = user
.raw_stats
.get(&StatType::Senses)
.cloned()
.unwrap_or(8.0);
let brw = user.raw_stats.get(&StatType::Brawn).cloned().unwrap_or(8.0); let brw = user.raw_stats.get(&StatType::Brawn).cloned().unwrap_or(8.0);
let refl = user.raw_stats.get(&StatType::Reflexes).cloned().unwrap_or(8.0); let refl = user
let end = user.raw_stats.get(&StatType::Endurance).cloned().unwrap_or(8.0); .raw_stats
.get(&StatType::Reflexes)
.cloned()
.unwrap_or(8.0);
let end = user
.raw_stats
.get(&StatType::Endurance)
.cloned()
.unwrap_or(8.0);
let col = user.raw_stats.get(&StatType::Cool).cloned().unwrap_or(8.0); let col = user.raw_stats.get(&StatType::Cool).cloned().unwrap_or(8.0);
let summary = format!("Brains: {}, Senses: {}, Brawn: {}, Reflexes: {}, Endurance: {}, Cool: {}. To spend: {}", brn, sen, brw, refl, end, col, points_left(user)); let summary = format!(
"Brains: {}, Senses: {}, Brawn: {}, Reflexes: {}, Endurance: {}, Cool: {}. To spend: {}",
brn,
sen,
brw,
refl,
end,
col,
points_left(user)
);
let st = work_out_state(user, item); let st = work_out_state(user, item);
@ -184,28 +221,53 @@ fn next_action_text(session: &Session, user: &User, item: &Item) -> String {
} }
} }
async fn stat_command(ctx: &mut VerbContext<'_>, item: &Item, async fn stat_command(
stat: &StatType, arg: &str) -> UResult<()> { ctx: &mut VerbContext<'_>,
item: &Item,
stat: &StatType,
arg: &str,
) -> UResult<()> {
match u8::<&str, nom::error::Error<&str>>(arg) { match u8::<&str, nom::error::Error<&str>>(arg) {
Err(_) => { reply(ctx, "I'll need a number after the stat name.").await?; } Err(_) => {
reply(ctx, "I'll need a number after the stat name.").await?;
}
Ok((rest, _)) if rest.trim() != "" => { Ok((rest, _)) if rest.trim() != "" => {
reply(ctx, "SYNTAX ERROR - who dares to put extra text after the stat number!").await?; reply(
ctx,
"SYNTAX ERROR - who dares to put extra text after the stat number!",
)
.await?;
} }
Ok((_, statno)) if statno < 8 => { Ok((_, statno)) if statno < 8 => {
reply(ctx, "8 is the minimum, you can't go lower").await?; reply(ctx, "8 is the minimum, you can't go lower").await?;
} }
Ok((_, statno)) if statno > 15 => { Ok((_, statno)) if statno > 15 => {
reply(ctx, "15 is the maximum, you can't go higher even if you have points").await?; reply(
ctx,
"15 is the maximum, you can't go higher even if you have points",
)
.await?;
} }
Ok((_, statno)) => { Ok((_, statno)) => {
let points = { let points = {
let user = get_user_or_fail(ctx)?; let user = get_user_or_fail(ctx)?;
points_left(get_user_or_fail(ctx)?) + (user.raw_stats.get(stat).cloned().unwrap_or(8.0) - 8.0) points_left(get_user_or_fail(ctx)?)
+ (user.raw_stats.get(stat).cloned().unwrap_or(8.0) - 8.0)
}; };
if (statno as f64 - 8.0) > points { if (statno as f64 - 8.0) > points {
reply(ctx, &if points == 0.0 { "You have no points left".to_owned() } else { reply(
format!("You only have {} point{} left", points, if points == 1.0 { "" } else { "s" }) ctx,
}).await?; &if points == 0.0 {
"You have no points left".to_owned()
} else {
format!(
"You only have {} point{} left",
points,
if points == 1.0 { "" } else { "s" }
)
},
)
.await?;
return Ok(()); return Ok(());
} }
{ {
@ -217,34 +279,45 @@ async fn stat_command(ctx: &mut VerbContext<'_>, item: &Item,
let mut item_updated = item.clone(); let mut item_updated = item.clone();
item_updated.total_stats = user.raw_stats.clone(); item_updated.total_stats = user.raw_stats.clone();
ctx.trans.save_item_model(&item_updated).await?; ctx.trans.save_item_model(&item_updated).await?;
reply(ctx, &next_action_text(&ctx.session_dat, user, &item_updated)).await?; reply(
ctx,
&next_action_text(&ctx.session_dat, user, &item_updated),
)
.await?;
} }
} }
Ok(()) Ok(())
} }
async fn sex_command(ctx: &mut VerbContext<'_>, item: &Item, async fn sex_command(ctx: &mut VerbContext<'_>, item: &Item, arg: &str) -> UResult<()> {
arg: &str) -> UResult<()> {
let choice = match arg.trim().to_lowercase().as_str() { let choice = match arg.trim().to_lowercase().as_str() {
"male" | "man" => Sex::Male, "male" | "man" => Sex::Male,
"female" | "woman" => Sex::Female, "female" | "woman" => Sex::Female,
_ => { _ => {
reply(ctx, "You want to be a what? The empire values all its subjects, \ reply(
ctx,
"You want to be a what? The empire values all its subjects, \
but the body factory makes only male and female. Pick one \ but the body factory makes only male and female. Pick one \
of those.").await?; of those.",
)
.await?;
return Ok(()); return Ok(());
} }
}; };
let mut item_updated = item.clone(); let mut item_updated = item.clone();
item_updated.pronouns = match choice { item_updated.pronouns = match choice {
Sex::Male => Pronouns::default_male(), Sex::Male => Pronouns::default_male(),
Sex::Female => Pronouns::default_female() Sex::Female => Pronouns::default_female(),
}; };
item_updated.sex = Some(choice); item_updated.sex = Some(choice);
let user: &User = get_user_or_fail(ctx)?; let user: &User = get_user_or_fail(ctx)?;
ctx.trans.save_item_model(&item_updated).await?; ctx.trans.save_item_model(&item_updated).await?;
reply(ctx, &next_action_text(&ctx.session_dat, user, &item_updated)).await?; reply(
ctx,
&next_action_text(&ctx.session_dat, user, &item_updated),
)
.await?;
Ok(()) Ok(())
} }
@ -255,19 +328,29 @@ impl NPCMessageHandler for StatbotMessageHandler {
ctx: &mut VerbContext, ctx: &mut VerbContext,
source: &Item, source: &Item,
_target: &Item, _target: &Item,
message: &str message: &str,
) -> UResult<()> { ) -> UResult<()> {
let (command, arg) = parse_to_space(message); let (command, arg) = parse_to_space(message);
match command.to_lowercase().as_str() { match command.to_lowercase().as_str() {
"brains" | "brn" | "brain" => stat_command(ctx, source, &StatType::Brains, &arg).await?, "brains" | "brn" | "brain" => {
"senses" | "sen" | "sense" => stat_command(ctx, source, &StatType::Senses, &arg).await?, stat_command(ctx, source, &StatType::Brains, &arg).await?
}
"senses" | "sen" | "sense" => {
stat_command(ctx, source, &StatType::Senses, &arg).await?
}
"brawn" | "brw" => stat_command(ctx, source, &StatType::Brawn, &arg).await?, "brawn" | "brw" => stat_command(ctx, source, &StatType::Brawn, &arg).await?,
"reflexes" | "ref" | "reflex" => stat_command(ctx, source, &StatType::Reflexes, &arg).await?, "reflexes" | "ref" | "reflex" => {
stat_command(ctx, source, &StatType::Reflexes, &arg).await?
}
"endurance" | "end" => stat_command(ctx, source, &StatType::Endurance, &arg).await?, "endurance" | "end" => stat_command(ctx, source, &StatType::Endurance, &arg).await?,
"cool" | "col" => stat_command(ctx, source, &StatType::Cool, &arg).await?, "cool" | "col" => stat_command(ctx, source, &StatType::Cool, &arg).await?,
"sex" => sex_command(ctx, source, &arg).await?, "sex" => sex_command(ctx, source, &arg).await?,
_ => { _ => {
reply(ctx, &next_action_text(&ctx.session_dat, get_user_or_fail(ctx)?, source)).await?; reply(
ctx,
&next_action_text(&ctx.session_dat, get_user_or_fail(ctx)?, source),
)
.await?;
} }
} }
Ok(()) Ok(())
@ -280,17 +363,36 @@ impl ExitBlocker for ChoiceRoomBlocker {
// True if they will be allowed to pass the exit, false otherwise. // True if they will be allowed to pass the exit, false otherwise.
async fn attempt_exit( async fn attempt_exit(
self: &Self, self: &Self,
ctx: &mut VerbContext, ctx: &mut QueuedCommandContext,
player: &mut Item, _exit: &Exit,
_exit: &Exit
) -> UResult<bool> { ) -> UResult<bool> {
let user = get_user_or_fail(ctx)?; if ctx.item.item_type != "player" {
if work_out_state(user, player) == StatbotState::Done { return Ok(false);
calculate_total_stats_skills_for_user(player, &user); }
let user = ctx
.trans
.find_by_username(&ctx.item.item_code)
.await?
.ok_or_else(|| CommandHandlingError::UserError("No user exists".to_owned()))?;
if work_out_state(&user, ctx.item) == StatbotState::Done {
calculate_total_stats_skills_for_user(ctx.item, &user);
Ok(true) Ok(true)
} else { } else {
shout(ctx, &format!(ansi!("YOU SHALL NOT PASS UNTIL YOU DO AS I SAY! <blue>{}"), if let Some((sess, sess_dat)) = ctx
&next_action_text(&ctx.session_dat, user, player))).await?; .trans
.find_session_for_player(&ctx.item.item_code)
.await?
{
ctx.trans
.queue_for_session(
&sess,
Some(&format!(
ansi!("Statbot shouts in a stern mechanical voice: <red>\"YOU SHALL NOT PASS UNTIL YOU DO AS I SAY! <blue>{}\"<reset>\n"),
&next_action_text(&sess_dat, &user, ctx.item)
)),
)
.await?;
}
Ok(false) Ok(false)
} }
} }

View File

@ -2,6 +2,7 @@ use crate::{
message_handler::user_commands::{UResult, VerbContext}, message_handler::user_commands::{UResult, VerbContext},
models::consent::ConsentType, models::consent::ConsentType,
models::item::{Item, Pronouns, SkillType}, models::item::{Item, Pronouns, SkillType},
regular_tasks::queued_command::QueuedCommandContext,
static_content::{room::Direction, species::BodyPart}, static_content::{room::Direction, species::BodyPart},
}; };
use async_trait::async_trait; use async_trait::async_trait;
@ -217,6 +218,11 @@ pub trait ArglessHandler {
async fn cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item) -> UResult<()>; async fn cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item) -> UResult<()>;
} }
#[async_trait]
pub trait LockcheckHandler {
async fn cmd(&self, ctx: &mut QueuedCommandContext, what: &Item) -> UResult<()>;
}
#[async_trait] #[async_trait]
pub trait InstallHandler { pub trait InstallHandler {
async fn install_cmd( async fn install_cmd(
@ -250,7 +256,7 @@ pub struct PossessionData {
pub becomes_on_spent: Option<PossessionType>, pub becomes_on_spent: Option<PossessionType>,
pub weight: u64, pub weight: u64,
pub install_handler: Option<&'static (dyn InstallHandler + Sync + Send)>, pub install_handler: Option<&'static (dyn InstallHandler + Sync + Send)>,
pub lockcheck_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, pub lockcheck_handler: Option<&'static (dyn LockcheckHandler + Sync + Send)>,
pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>, pub sign_handler: Option<&'static (dyn ArglessHandler + Sync + Send)>,
pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>, pub write_handler: Option<&'static (dyn WriteHandler + Sync + Send)>,
pub can_butcher: bool, pub can_butcher: bool,

View File

@ -1,7 +1,8 @@
use super::{ArglessHandler, PossessionData, PossessionType}; use super::{LockcheckHandler, PossessionData, PossessionType};
use crate::{ use crate::{
message_handler::user_commands::{user_error, UResult, VerbContext}, message_handler::user_commands::{user_error, UResult, VerbContext},
models::item::{Item, LocationActionType}, models::item::{Item, LocationActionType},
regular_tasks::queued_command::QueuedCommandContext,
services::{ services::{
capacity::{check_item_capacity, CapacityLevel}, capacity::{check_item_capacity, CapacityLevel},
comms::broadcast_to_room, comms::broadcast_to_room,
@ -13,11 +14,13 @@ use once_cell::sync::OnceCell;
struct ScanLockLockcheck; struct ScanLockLockcheck;
#[async_trait] #[async_trait]
impl ArglessHandler for ScanLockLockcheck { impl LockcheckHandler for ScanLockLockcheck {
async fn cmd(&self, ctx: &mut VerbContext, player: &Item, what: &Item) -> UResult<()> { async fn cmd(&self, ctx: &mut QueuedCommandContext, what: &Item) -> UResult<()> {
if what.owner == Some(player.refstr()) { if what.owner == Some(ctx.item.refstr()) {
ctx.trans.queue_for_session(&ctx.session, Some( if let Some((session, _)) = ctx.get_session().await? {
ctx.trans.queue_for_session(&session, Some(
"The scanlock in the door emits a single high-pitched beep as it unlocks.\n")).await?; "The scanlock in the door emits a single high-pitched beep as it unlocks.\n")).await?;
}
} else { } else {
user_error( user_error(
"The scanlock in the door emits a medium-pitched tone followed by a low tone, and remains locked.".to_owned())?; "The scanlock in the door emits a medium-pitched tone followed by a low tone, and remains locked.".to_owned())?;

View File

@ -1,23 +1,19 @@
use super::{ use super::{possession_type::PossessionType, StaticItem};
StaticItem,
possession_type::PossessionType,
};
use once_cell::sync::OnceCell;
use std::collections::BTreeMap;
use async_trait::async_trait;
use serde::{Serialize, Deserialize};
use crate::message_handler::user_commands::{
UResult, VerbContext
};
use crate::{ use crate::{
models::item::{Item, ItemFlag} message_handler::user_commands::UResult,
models::item::{Item, ItemFlag},
regular_tasks::queued_command::QueuedCommandContext,
}; };
use async_trait::async_trait;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
mod special;
mod repro_xv;
mod melbs;
mod cok_murl;
mod chonkers; mod chonkers;
mod cok_murl;
mod melbs;
mod repro_xv;
mod special;
pub struct Zone { pub struct Zone {
pub code: &'static str, pub code: &'static str,
@ -27,47 +23,95 @@ pub struct Zone {
static STATIC_ZONE_DETAILS: OnceCell<BTreeMap<&'static str, Zone>> = OnceCell::new(); static STATIC_ZONE_DETAILS: OnceCell<BTreeMap<&'static str, Zone>> = OnceCell::new();
pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> { pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> {
STATIC_ZONE_DETAILS.get_or_init( STATIC_ZONE_DETAILS.get_or_init(|| {
|| vec!( vec![
Zone { code: "special", Zone {
code: "special",
display: "Outside of Time", display: "Outside of Time",
outdoors: false }, outdoors: false,
Zone { code: "melbs", },
Zone {
code: "melbs",
display: "Melbs", display: "Melbs",
outdoors: true }, outdoors: true,
Zone { code: "repro_xv", },
Zone {
code: "repro_xv",
display: "Reprolabs XV", display: "Reprolabs XV",
outdoors: false }, outdoors: false,
Zone { code: "cok_murl", },
Zone {
code: "cok_murl",
display: "CoK-Murlison Complex", display: "CoK-Murlison Complex",
outdoors: false }, outdoors: false,
Zone { code: "chonkers", },
Zone {
code: "chonkers",
display: "Chonker's Gym", display: "Chonker's Gym",
outdoors: false }, outdoors: false,
).into_iter().map(|x|(x.code, x)).collect()) },
]
.into_iter()
.map(|x| (x.code, x))
.collect()
})
} }
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)] #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub struct GridCoords { pub struct GridCoords {
pub x: i64, pub x: i64,
pub y: i64, pub y: i64,
pub z: i64 pub z: i64,
} }
impl GridCoords { impl GridCoords {
pub fn apply(self: &GridCoords, dir: &Direction) -> GridCoords { pub fn apply(self: &GridCoords, dir: &Direction) -> GridCoords {
match dir { match dir {
Direction::NORTH => GridCoords {y: self.y - 1, ..*self}, Direction::NORTH => GridCoords {
Direction::SOUTH => GridCoords {y: self.y + 1, ..*self}, y: self.y - 1,
Direction::EAST => GridCoords {x: self.x + 1, ..*self}, ..*self
Direction::WEST => GridCoords {x: self.x - 1, ..*self}, },
Direction::NORTHEAST => GridCoords {x: self.x + 1, y: self.y - 1, ..*self}, Direction::SOUTH => GridCoords {
Direction::SOUTHEAST => GridCoords {x: self.x + 1, y: self.y + 1, ..*self}, y: self.y + 1,
Direction::NORTHWEST => GridCoords {x: self.x - 1, y: self.y - 1, ..*self}, ..*self
Direction::SOUTHWEST => GridCoords {x: self.x - 1, y: self.y + 1, ..*self}, },
Direction::UP => GridCoords {z: self.z + 1, ..*self}, Direction::EAST => GridCoords {
Direction::DOWN => GridCoords {z: self.z - 1, ..*self}, x: self.x + 1,
Direction::IN { .. } => self.clone() ..*self
},
Direction::WEST => GridCoords {
x: self.x - 1,
..*self
},
Direction::NORTHEAST => GridCoords {
x: self.x + 1,
y: self.y - 1,
..*self
},
Direction::SOUTHEAST => GridCoords {
x: self.x + 1,
y: self.y + 1,
..*self
},
Direction::NORTHWEST => GridCoords {
x: self.x - 1,
y: self.y - 1,
..*self
},
Direction::SOUTHWEST => GridCoords {
x: self.x - 1,
y: self.y + 1,
..*self
},
Direction::UP => GridCoords {
z: self.z + 1,
..*self
},
Direction::DOWN => GridCoords {
z: self.z - 1,
..*self
},
Direction::IN { .. } => self.clone(),
} }
} }
} }
@ -77,9 +121,8 @@ pub trait ExitBlocker {
// True if they will be allowed to pass the exit, false otherwise. // True if they will be allowed to pass the exit, false otherwise.
async fn attempt_exit( async fn attempt_exit(
self: &Self, self: &Self,
ctx: &mut VerbContext, ctx: &mut QueuedCommandContext,
player: &mut Item, exit: &Exit,
exit: &Exit
) -> UResult<bool>; ) -> UResult<bool>;
} }
@ -101,21 +144,24 @@ pub enum Direction {
SOUTHWEST, SOUTHWEST,
UP, UP,
DOWN, DOWN,
IN { item: String } IN { item: String },
} }
impl Serialize for Direction { impl Serialize for Direction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer { where
S: serde::Serializer,
{
Serialize::serialize(&self.describe(), serializer) Serialize::serialize(&self.describe(), serializer)
} }
} }
impl<'de> Deserialize<'de> for Direction { impl<'de> Deserialize<'de> for Direction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> { where
Deserialize::deserialize(deserializer) D: serde::Deserializer<'de>,
.and_then(|s: String| Self::parse(s.as_str()) {
.ok_or( Deserialize::deserialize(deserializer).and_then(|s: String| {
serde::de::Error::custom("Invalid direction"))) Self::parse(s.as_str()).ok_or(serde::de::Error::custom("Invalid direction"))
})
} }
} }
@ -132,7 +178,7 @@ impl Direction {
Direction::SOUTHWEST => "southwest".to_owned(), Direction::SOUTHWEST => "southwest".to_owned(),
Direction::UP => "up".to_owned(), Direction::UP => "up".to_owned(),
Direction::DOWN => "down".to_owned(), Direction::DOWN => "down".to_owned(),
Direction::IN { item } => "in ".to_owned() + item Direction::IN { item } => "in ".to_owned() + item,
} }
} }
@ -148,13 +194,15 @@ impl Direction {
Direction::SOUTHWEST => format!("{} to the southwest", climb_dir), Direction::SOUTHWEST => format!("{} to the southwest", climb_dir),
Direction::UP => "upwards".to_owned(), Direction::UP => "upwards".to_owned(),
Direction::DOWN => "downwards".to_owned(), Direction::DOWN => "downwards".to_owned(),
Direction::IN { item } => format!("{} and in ", item) Direction::IN { item } => format!("{} and in ", item),
} }
} }
pub fn parse(input: &str) -> Option<Direction> { pub fn parse(input: &str) -> Option<Direction> {
if input.starts_with("in ") { if input.starts_with("in ") {
return Some(Direction::IN { item: input["in ".len()..].trim().to_owned() }) return Some(Direction::IN {
item: input["in ".len()..].trim().to_owned(),
});
} }
match input { match input {
"north" | "n" => Some(Direction::NORTH), "north" | "n" => Some(Direction::NORTH),
@ -167,7 +215,7 @@ impl Direction {
"southwest" | "sw" => Some(Direction::SOUTHWEST), "southwest" | "sw" => Some(Direction::SOUTHWEST),
"up" => Some(Direction::UP), "up" => Some(Direction::UP),
"down" => Some(Direction::DOWN), "down" => Some(Direction::DOWN),
_ => None _ => None,
} }
} }
@ -183,7 +231,7 @@ impl Direction {
Direction::SOUTHWEST => Some(Direction::NORTHEAST), Direction::SOUTHWEST => Some(Direction::NORTHEAST),
Direction::UP => Some(Direction::DOWN), Direction::UP => Some(Direction::DOWN),
Direction::DOWN => Some(Direction::UP), Direction::DOWN => Some(Direction::UP),
Direction::IN { .. } => None Direction::IN { .. } => None,
} }
} }
} }
@ -191,7 +239,7 @@ impl Direction {
#[derive(Eq, Ord, Debug, PartialEq, PartialOrd, Clone)] #[derive(Eq, Ord, Debug, PartialEq, PartialOrd, Clone)]
pub enum ExitTarget { pub enum ExitTarget {
UseGPS, UseGPS,
Custom(&'static str) Custom(&'static str),
} }
pub struct ExitClimb { pub struct ExitClimb {
@ -222,19 +270,19 @@ pub struct SecondaryZoneRecord {
pub zone: &'static str, pub zone: &'static str,
pub short: &'static str, pub short: &'static str,
pub grid_coords: GridCoords, pub grid_coords: GridCoords,
pub caption: Option<&'static str> pub caption: Option<&'static str>,
} }
pub struct RoomStock { pub struct RoomStock {
pub possession_type: PossessionType, pub possession_type: PossessionType,
pub list_price: u64 pub list_price: u64,
} }
impl Default for RoomStock { impl Default for RoomStock {
fn default() -> Self { fn default() -> Self {
Self { Self {
possession_type: PossessionType::AntennaWhip, possession_type: PossessionType::AntennaWhip,
list_price: 1000000000 list_price: 1000000000,
} }
} }
} }
@ -251,7 +299,7 @@ pub enum MaterialType {
Normal, Normal,
WaterSurface, WaterSurface,
Underwater, Underwater,
Soft { damage_modifier: f64 } Soft { damage_modifier: f64 },
} }
pub struct Room { pub struct Room {
@ -272,36 +320,34 @@ pub struct Room {
pub stock_list: Vec<RoomStock>, pub stock_list: Vec<RoomStock>,
// What can be rented here... // What can be rented here...
pub rentable_dynzone: Vec<RentInfo>, pub rentable_dynzone: Vec<RentInfo>,
pub material_type: MaterialType pub material_type: MaterialType,
} }
impl Default for Room { impl Default for Room {
fn default() -> Self { fn default() -> Self {
Self { Self {
zone: "default", zone: "default",
secondary_zones: vec!(), secondary_zones: vec![],
code: "default", code: "default",
name: "default", name: "default",
short: "DF", short: "DF",
grid_coords: GridCoords { x: 0, y: 0, z: 0 }, grid_coords: GridCoords { x: 0, y: 0, z: 0 },
description: "default", description: "default",
description_less_explicit: None, description_less_explicit: None,
exits: vec!(), exits: vec![],
should_caption: true, should_caption: true,
repel_npc: false, repel_npc: false,
item_flags: vec!(), item_flags: vec![],
stock_list: vec!(), stock_list: vec![],
rentable_dynzone: vec!(), rentable_dynzone: vec![],
material_type: MaterialType::Normal, material_type: MaterialType::Normal,
} }
} }
} }
static STATIC_ROOM_LIST: OnceCell<Vec<Room>> = OnceCell::new(); static STATIC_ROOM_LIST: OnceCell<Vec<Room>> = OnceCell::new();
pub fn room_list() -> &'static Vec<Room> { pub fn room_list() -> &'static Vec<Room> {
STATIC_ROOM_LIST.get_or_init( STATIC_ROOM_LIST.get_or_init(|| {
|| {
let mut rooms = repro_xv::room_list(); let mut rooms = repro_xv::room_list();
rooms.append(&mut melbs::room_list()); rooms.append(&mut melbs::room_list());
rooms.append(&mut cok_murl::room_list()); rooms.append(&mut cok_murl::room_list());
@ -313,21 +359,25 @@ pub fn room_list() -> &'static Vec<Room> {
static STATIC_ROOM_MAP_BY_CODE: OnceCell<BTreeMap<&'static str, &'static Room>> = OnceCell::new(); static STATIC_ROOM_MAP_BY_CODE: OnceCell<BTreeMap<&'static str, &'static Room>> = OnceCell::new();
pub fn room_map_by_code() -> &'static BTreeMap<&'static str, &'static Room> { pub fn room_map_by_code() -> &'static BTreeMap<&'static str, &'static Room> {
STATIC_ROOM_MAP_BY_CODE.get_or_init( STATIC_ROOM_MAP_BY_CODE.get_or_init(|| room_list().iter().map(|r| (r.code, r)).collect())
|| room_list().iter().map(|r| (r.code, r)).collect())
} }
static STATIC_ROOM_MAP_BY_ZLOC: OnceCell<BTreeMap<(&'static str, &'static GridCoords), static STATIC_ROOM_MAP_BY_ZLOC: OnceCell<
&'static Room>> = OnceCell::new(); BTreeMap<(&'static str, &'static GridCoords), &'static Room>,
> = OnceCell::new();
pub fn room_map_by_zloc() -> &'static BTreeMap<(&'static str, &'static GridCoords), &'static Room> { pub fn room_map_by_zloc() -> &'static BTreeMap<(&'static str, &'static GridCoords), &'static Room> {
STATIC_ROOM_MAP_BY_ZLOC.get_or_init( STATIC_ROOM_MAP_BY_ZLOC.get_or_init(|| {
|| room_list().iter() room_list()
.iter()
.map(|r| ((r.zone, &r.grid_coords), r)) .map(|r| ((r.zone, &r.grid_coords), r))
.chain(room_list().iter() .chain(room_list().iter().flat_map(|r| {
.flat_map(|r| r.secondary_zones.iter() r.secondary_zones
.iter()
.map(|sz| ((sz.zone, &sz.grid_coords), r)) .map(|sz| ((sz.zone, &sz.grid_coords), r))
.collect::<Vec<((&'static str, &'static GridCoords), &'static Room)>>())) .collect::<Vec<((&'static str, &'static GridCoords), &'static Room)>>()
.collect()) }))
.collect()
})
} }
pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> { pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
@ -343,64 +393,69 @@ pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
is_static: true, is_static: true,
flags: r.item_flags.clone(), flags: r.item_flags.clone(),
..Item::default() ..Item::default()
}) }),
})) }))
} }
pub fn resolve_exit(room: &Room, exit: &Exit) -> Option<&'static Room> { pub fn resolve_exit(room: &Room, exit: &Exit) -> Option<&'static Room> {
match exit.target { match exit.target {
ExitTarget::Custom(t) => t.split_once("/").and_then( ExitTarget::Custom(t) => t.split_once("/").and_then(|(t, c)| {
|(t,c)|
if t != "room" { if t != "room" {
None None
} else { } else {
room_map_by_code().get(c).map(|r| *r) room_map_by_code().get(c).map(|r| *r)
}
}), }),
ExitTarget::UseGPS => ExitTarget::UseGPS => room_map_by_zloc()
room_map_by_zloc().get(&(room.zone, &room.grid_coords.apply(&exit.direction))).map(|r|*r) .get(&(room.zone, &room.grid_coords.apply(&exit.direction)))
.map(|r| *r),
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use itertools::Itertools;
use super::*; use super::*;
use itertools::Itertools;
#[test] #[test]
fn room_zones_should_exist() { fn room_zones_should_exist() {
for room in room_list() { for room in room_list() {
zone_details().get(room.zone).expect( zone_details().get(room.zone).expect(&format!(
&format!("zone {} for room {} should exist", room.zone, room.code)); "zone {} for room {} should exist",
room.zone, room.code
));
} }
} }
#[test] #[test]
fn room_map_by_code_should_have_repro_xv_chargen() { fn room_map_by_code_should_have_repro_xv_chargen() {
assert_eq!(room_map_by_code().get("repro_xv_chargen").expect("repro_xv_chargen to exist").code, assert_eq!(
"repro_xv_chargen"); room_map_by_code()
.get("repro_xv_chargen")
.expect("repro_xv_chargen to exist")
.code,
"repro_xv_chargen"
);
} }
#[test] #[test]
fn grid_coords_should_be_unique_in_zone() { fn grid_coords_should_be_unique_in_zone() {
let mut roomlist: Vec<&'static Room> = room_list().iter().collect(); let mut roomlist: Vec<&'static Room> = room_list().iter().collect();
roomlist.sort_unstable_by( roomlist
|a,b| .sort_unstable_by(|a, b| a.grid_coords.cmp(&b.grid_coords).then(a.zone.cmp(&b.zone)));
a.grid_coords.cmp(&b.grid_coords) let dups: Vec<Vec<(&'static str, &GridCoords, &'static str)>> = roomlist
.then(a.zone.cmp(&b.zone))); .iter()
let dups : Vec<Vec<(&'static str, &GridCoords, &'static str)>> =
roomlist.iter()
.group_by(|x| (&x.grid_coords, x.zone)) .group_by(|x| (&x.grid_coords, x.zone))
.into_iter() .into_iter()
.map(|((coord, zone), rg)| .map(|((coord, zone), rg)| {
rg.map(|r| (r.name, coord, zone)) rg.map(|r| (r.name, coord, zone))
.collect::<Vec<(&str, &GridCoords, &str)>>()) .collect::<Vec<(&str, &GridCoords, &str)>>()
})
.filter(|x| x.len() > 1) .filter(|x| x.len() > 1)
.collect(); .collect();
assert_eq!(dups, assert_eq!(dups, Vec::<Vec<(&str, &GridCoords, &str)>>::new());
Vec::<Vec<(&str, &GridCoords, &str)>>::new());
} }
#[test] #[test]
fn parse_direction_should_work() { fn parse_direction_should_work() {
assert_eq!(Direction::parse("southeast"), Some(Direction::SOUTHEAST)); assert_eq!(Direction::parse("southeast"), Some(Direction::SOUTHEAST));
@ -408,7 +463,7 @@ mod test {
#[test] #[test]
fn parse_and_describe_direction_should_roundtrip() { fn parse_and_describe_direction_should_roundtrip() {
let examples = vec!( let examples = vec![
"north", "north",
"south", "south",
"east", "east",
@ -419,11 +474,13 @@ mod test {
"southwest", "southwest",
"up", "up",
"down", "down",
"in there" "in there",
); ];
for example in examples { for example in examples {
assert_eq!(Direction::parse(example).map(|v| v.describe()), Some(example.to_owned())); assert_eq!(
Direction::parse(example).map(|v| v.describe()),
Some(example.to_owned())
);
} }
} }
} }

View File

@ -1,6 +1,5 @@
use super::{ use super::{
Room, GridCoords, Exit, Direction, ExitTarget, MaterialType, Direction, Exit, ExitClimb, ExitTarget, GridCoords, MaterialType, Room, SecondaryZoneRecord,
SecondaryZoneRecord, ExitClimb
}; };
use ansi::ansi; use ansi::ansi;
pub fn room_list() -> Vec<Room> { pub fn room_list() -> Vec<Room> {

View File

@ -1,7 +1,4 @@
use super::{ use super::{Direction, Exit, ExitTarget, GridCoords, RentInfo, Room, SecondaryZoneRecord};
Room, GridCoords, Exit, Direction, ExitTarget,
SecondaryZoneRecord, RentInfo,
};
use crate::static_content::dynzone::DynzoneType; use crate::static_content::dynzone::DynzoneType;
use ansi::ansi; use ansi::ansi;
pub fn room_list() -> Vec<Room> { pub fn room_list() -> Vec<Room> {

View File

@ -1,11 +1,7 @@
use super::{ use super::{
Room, RoomStock, GridCoords, Exit, Direction, ExitTarget, ExitClimb, Direction, Exit, ExitClimb, ExitTarget, GridCoords, Room, RoomStock, SecondaryZoneRecord,
SecondaryZoneRecord
};
use crate::{
models::item::ItemFlag,
static_content::possession_type::PossessionType
}; };
use crate::{models::item::ItemFlag, static_content::possession_type::PossessionType};
use ansi::ansi; use ansi::ansi;
pub fn room_list() -> Vec<Room> { pub fn room_list() -> Vec<Room> {

View File

@ -1,7 +1,4 @@
use super::{ use super::{Direction, Exit, ExitTarget, ExitType, GridCoords, Room, SecondaryZoneRecord};
Room, GridCoords, Exit, Direction, ExitTarget, ExitType,
SecondaryZoneRecord
};
use crate::static_content::npc; use crate::static_content::npc;
use ansi::ansi; use ansi::ansi;

View File

@ -1,12 +1,10 @@
use super::{ use super::{GridCoords, Room};
Room, GridCoords,
};
use ansi::ansi; use ansi::ansi;
// None of these are reachable except when the game or an admin puts something there. // None of these are reachable except when the game or an admin puts something there.
pub fn room_list() -> Vec<Room> { pub fn room_list() -> Vec<Room> {
let holding_desc: &'static str = "The inside of a small pen or cage, with thick steel bars, suitable for holding an animal - or a person - securely, with no chance of escape. It is dimly lit and smells like urine, and is very cramped and indignifying. It looks like the only way out would be with the help of whoever locked you in here. [OOC: consider emailing staff@blastmud.org to discuss your situation]"; let holding_desc: &'static str = "The inside of a small pen or cage, with thick steel bars, suitable for holding an animal - or a person - securely, with no chance of escape. It is dimly lit and smells like urine, and is very cramped and indignifying. It looks like the only way out would be with the help of whoever locked you in here. [OOC: consider emailing staff@blastmud.org to discuss your situation]";
vec!( vec![
Room { Room {
zone: "special", zone: "special",
code: "valhalla", code: "valhalla",
@ -15,7 +13,7 @@ pub fn room_list() -> Vec<Room> {
description: "Where the valiant dead NPCs go to wait recloning", description: "Where the valiant dead NPCs go to wait recloning",
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 0, y: 0, z: 0 }, grid_coords: GridCoords { x: 0, y: 0, z: 0 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -26,7 +24,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 0, y: 0, z: -1 }, grid_coords: GridCoords { x: 0, y: 0, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -37,7 +35,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 1, y: 0, z: -1 }, grid_coords: GridCoords { x: 1, y: 0, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -48,7 +46,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 2, y: 0, z: -1 }, grid_coords: GridCoords { x: 2, y: 0, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -59,7 +57,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 3, y: 0, z: -1 }, grid_coords: GridCoords { x: 3, y: 0, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -70,7 +68,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 0, y: 1, z: -1 }, grid_coords: GridCoords { x: 0, y: 1, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -81,7 +79,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 1, y: 1, z: -1 }, grid_coords: GridCoords { x: 1, y: 1, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -92,7 +90,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 2, y: 1, z: -1 }, grid_coords: GridCoords { x: 2, y: 1, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -103,7 +101,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 3, y: 1, z: -1 }, grid_coords: GridCoords { x: 3, y: 1, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -114,7 +112,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 0, y: 2, z: -1 }, grid_coords: GridCoords { x: 0, y: 2, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -125,7 +123,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 1, y: 2, z: -1 }, grid_coords: GridCoords { x: 1, y: 2, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -136,7 +134,7 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 2, y: 2, z: -1 }, grid_coords: GridCoords { x: 2, y: 2, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
Room { Room {
@ -147,8 +145,8 @@ pub fn room_list() -> Vec<Room> {
description: holding_desc, description: holding_desc,
description_less_explicit: None, description_less_explicit: None,
grid_coords: GridCoords { x: 3, y: 2, z: -1 }, grid_coords: GridCoords { x: 3, y: 2, z: -1 },
exits: vec!(), exits: vec![],
..Default::default() ..Default::default()
}, },
) ]
} }

0
queued_command.rs Normal file
View File