Implement scavenge command
This commit is contained in:
parent
64b96f48ab
commit
925deba57e
@ -3,7 +3,7 @@ use crate::message_handler::ListenerSession;
|
||||
use crate::models::{
|
||||
consent::{Consent, ConsentType},
|
||||
corp::{Corp, CorpCommType, CorpId, CorpMembership},
|
||||
item::{Item, ItemFlag, LocationActionType},
|
||||
item::{Item, ItemFlag, LocationActionType, Scavtype},
|
||||
session::Session,
|
||||
task::{Task, TaskParse},
|
||||
user::User,
|
||||
@ -788,6 +788,29 @@ impl DBTrans {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_scavhidden_item_by_location<'a>(
|
||||
self: &'a Self,
|
||||
location: &'a str,
|
||||
scavtype: &Scavtype,
|
||||
max_difficulty: i64,
|
||||
) -> DResult<Option<Item>> {
|
||||
match self
|
||||
.pg_trans()?
|
||||
.query_opt(
|
||||
"SELECT details FROM items WHERE details->>'location' = $1 AND \
|
||||
details->'action_type'->'Scavhidden'->'scavtype' = $2::JSONB AND \
|
||||
(details->'action_type'->'Scavhidden'->>'difficulty')::INT8 <= $3 \
|
||||
ORDER BY details->>'display'
|
||||
LIMIT 1",
|
||||
&[&location, &serde_json::to_value(scavtype)?, &max_difficulty],
|
||||
)
|
||||
.await?
|
||||
{
|
||||
None => Ok(None),
|
||||
Some(i) => Ok(Some(serde_json::from_value(i.get("details"))?)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save_item_model(self: &Self, details: &Item) -> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute(
|
||||
@ -817,6 +840,17 @@ impl DBTrans {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clean_scavhidden<'a>(self: &'a Self) -> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute(
|
||||
"DELETE FROM items WHERE \
|
||||
details->'action_type'->'Scavhidden' IS NOT NULL",
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_session_for_player<'a>(
|
||||
self: &'a Self,
|
||||
item_code: &'a str,
|
||||
|
@ -64,6 +64,7 @@ pub mod rent;
|
||||
mod report;
|
||||
mod reset_spawns;
|
||||
pub mod say;
|
||||
pub mod scavenge;
|
||||
mod score;
|
||||
mod sign;
|
||||
pub mod sit;
|
||||
@ -225,6 +226,9 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"\'" => say::VERB,
|
||||
"say" => say::VERB,
|
||||
|
||||
"scavenge" => scavenge::VERB,
|
||||
"search" => scavenge::VERB,
|
||||
|
||||
"sc" => score::VERB,
|
||||
"score" => score::VERB,
|
||||
|
||||
|
147
blastmud_game/src/message_handler/user_commands/scavenge.rs
Normal file
147
blastmud_game/src/message_handler/user_commands/scavenge.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use super::{
|
||||
drop::consider_expire_job_for_item, get_player_item_or_fail, user_error, UResult, UserVerb,
|
||||
UserVerbRef, VerbContext,
|
||||
};
|
||||
use crate::{
|
||||
models::item::{LocationActionType, Scavtype, SkillType},
|
||||
regular_tasks::queued_command::{
|
||||
queue_command_and_save, QueueCommand, QueueCommandHandler, QueuedCommandContext,
|
||||
},
|
||||
services::{
|
||||
capacity::{check_item_capacity, CapacityLevel},
|
||||
comms::broadcast_to_room,
|
||||
skills::skill_check_and_grind,
|
||||
},
|
||||
};
|
||||
use ansi::ansi;
|
||||
use async_trait::async_trait;
|
||||
use std::time;
|
||||
|
||||
pub struct QueueHandler;
|
||||
#[async_trait]
|
||||
impl QueueCommandHandler for QueueHandler {
|
||||
async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
|
||||
if ctx.item.death_data.is_some() {
|
||||
user_error(
|
||||
"You try to search the area, but you realise your ghost hands aren't good for searching".to_owned(),
|
||||
)?;
|
||||
}
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&ctx.item.location,
|
||||
None,
|
||||
&format!(
|
||||
ansi!("<blue>{} starts methodically searching the area.<reset>\n"),
|
||||
ctx.item.display_for_sentence(true, 1, true),
|
||||
),
|
||||
Some(&format!(
|
||||
ansi!("<blue>{} starts methodically searching the area.<reset>\n"),
|
||||
ctx.item.display_for_sentence(false, 1, true),
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
Ok(time::Duration::from_secs(3))
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
|
||||
if ctx.item.death_data.is_some() {
|
||||
user_error(
|
||||
"You try to search the area, but you realise your ghost hands aren't good for searching".to_owned(),
|
||||
)?;
|
||||
}
|
||||
let scav = ctx
|
||||
.item
|
||||
.total_skills
|
||||
.get(&SkillType::Scavenge)
|
||||
.unwrap_or(&0.0)
|
||||
.clone();
|
||||
let found_opt = ctx
|
||||
.trans
|
||||
.find_scavhidden_item_by_location(
|
||||
&ctx.item.location,
|
||||
&Scavtype::Scavenge,
|
||||
(scav * 100.0).max(0.0) as i64,
|
||||
)
|
||||
.await?;
|
||||
let mut found = match found_opt {
|
||||
None => user_error("You have a look, and you're pretty sure there's nothing to find here. [Try searching elsewhere, or come back later]".to_owned())?,
|
||||
Some(v) => v,
|
||||
};
|
||||
let diff: f64 = (match found.action_type {
|
||||
LocationActionType::Scavhidden { difficulty, .. } => difficulty,
|
||||
_ => 800,
|
||||
}) as f64
|
||||
/ 100.0;
|
||||
|
||||
if skill_check_and_grind(&ctx.trans, &mut ctx.item, &SkillType::Scavenge, diff).await? < 0.0
|
||||
{
|
||||
// No crit fail for now, it is either yes or no.
|
||||
match ctx.get_session().await? {
|
||||
None => {},
|
||||
Some((sess, _)) =>
|
||||
ctx.trans.queue_for_session(&sess, Some("You give up searching, but you still have a feeling there is something here.\n")).await?
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
found.action_type = LocationActionType::Normal;
|
||||
match check_item_capacity(&ctx.trans, &ctx.item, found.weight).await? {
|
||||
CapacityLevel::OverBurdened | CapacityLevel::AboveItemLimit => {
|
||||
consider_expire_job_for_item(&ctx.trans, &found).await?;
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&ctx.item.location,
|
||||
None,
|
||||
&format!("{} seems to have found {} - but can't carry it so drops it on the ground.\n",
|
||||
ctx.item.display_for_sentence(true, 1, true),
|
||||
found.display_for_sentence(true, 1, false),
|
||||
),
|
||||
Some(&format!(
|
||||
"{} seems to have found {} - but can't carry it so drops it on the ground.\n",
|
||||
ctx.item.display_for_sentence(false, 1, true),
|
||||
found.display_for_sentence(false, 1, false),)),
|
||||
).await?;
|
||||
}
|
||||
_ => {
|
||||
broadcast_to_room(
|
||||
&ctx.trans,
|
||||
&ctx.item.location,
|
||||
None,
|
||||
&format!(
|
||||
"{} seems to have found {}.\n",
|
||||
ctx.item.display_for_sentence(true, 1, true),
|
||||
found.display_for_sentence(true, 1, false),
|
||||
),
|
||||
Some(&format!(
|
||||
"{} seems to have found {}.\n",
|
||||
ctx.item.display_for_sentence(false, 1, true),
|
||||
found.display_for_sentence(false, 1, false),
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
found.location = ctx.item.refstr();
|
||||
}
|
||||
}
|
||||
ctx.trans.save_item_model(&found).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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?;
|
||||
queue_command_and_save(ctx, &player_item, &QueueCommand::Scavenge).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -261,6 +261,11 @@ pub enum Subattack {
|
||||
Wrestling,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Scavtype {
|
||||
Scavenge,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum LocationActionType {
|
||||
Normal,
|
||||
@ -270,6 +275,7 @@ pub enum LocationActionType {
|
||||
Wielded,
|
||||
Attacking(Subattack),
|
||||
InstalledOnDoorAsLock(Direction),
|
||||
Scavhidden { difficulty: u64, scavtype: Scavtype },
|
||||
}
|
||||
|
||||
impl LocationActionType {
|
||||
@ -277,6 +283,7 @@ impl LocationActionType {
|
||||
use LocationActionType::*;
|
||||
match self {
|
||||
InstalledOnDoorAsLock(_) => false,
|
||||
Scavhidden { .. } => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ use super::{TaskHandler, TaskRunContext};
|
||||
use crate::db::DBTrans;
|
||||
use crate::message_handler::user_commands::{
|
||||
close, cut, drink, drop, eat, fill, get, improvise, make, movement, open, put, recline, remove,
|
||||
sit, stand, use_cmd, user_error, wear, wield, CommandHandlingError, UResult, VerbContext,
|
||||
scavenge, sit, stand, use_cmd, user_error, wear, wield, CommandHandlingError, UResult,
|
||||
VerbContext,
|
||||
};
|
||||
use crate::message_handler::ListenerSession;
|
||||
use crate::models::session::Session;
|
||||
@ -102,6 +103,7 @@ pub enum QueueCommand {
|
||||
Remove {
|
||||
possession_id: String,
|
||||
},
|
||||
Scavenge,
|
||||
Sit {
|
||||
item: Option<String>,
|
||||
},
|
||||
@ -143,6 +145,7 @@ impl QueueCommand {
|
||||
Put { .. } => "Put",
|
||||
Recline { .. } => "Recline",
|
||||
Remove { .. } => "Remove",
|
||||
Scavenge => "Scavenge",
|
||||
Sit { .. } => "Sit",
|
||||
Stand { .. } => "Stand",
|
||||
Use { .. } => "Use",
|
||||
@ -249,6 +252,10 @@ fn queue_command_registry(
|
||||
"Remove",
|
||||
&remove::QueueHandler as &(dyn QueueCommandHandler + Sync + Send),
|
||||
),
|
||||
(
|
||||
"Scavenge",
|
||||
&scavenge::QueueHandler as &(dyn QueueCommandHandler + Sync + Send),
|
||||
),
|
||||
(
|
||||
"Sit",
|
||||
&sit::QueueHandler as &(dyn QueueCommandHandler + Sync + Send),
|
||||
|
@ -3,12 +3,12 @@ use crate::db::DBTrans;
|
||||
use crate::{
|
||||
message_handler::user_commands::rent::recursively_destroy_or_move_item,
|
||||
models::{
|
||||
item::{LiquidDetails, LiquidType},
|
||||
item::{LiquidDetails, LiquidType, LocationActionType},
|
||||
task::{Task, TaskDetails, TaskMeta, TaskRecurrence},
|
||||
},
|
||||
regular_tasks::{TaskHandler, TaskRunContext},
|
||||
services::Item,
|
||||
static_content::{possession_type::PossessionType, StaticTask},
|
||||
static_content::{possession_type::PossessionType, room::room_list, StaticTask},
|
||||
DResult,
|
||||
};
|
||||
use async_recursion::async_recursion;
|
||||
@ -17,6 +17,7 @@ use log::{info, warn};
|
||||
use mockall_double::double;
|
||||
use once_cell::sync::OnceCell;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand_distr::Distribution;
|
||||
use std::collections::BTreeMap;
|
||||
use std::time;
|
||||
|
||||
@ -168,6 +169,31 @@ pub async fn refresh_all_spawn_points(trans: &DBTrans) -> DResult<()> {
|
||||
trans.save_item_model(&location_mut).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Also all scav spawns...
|
||||
trans.clean_scavhidden().await?;
|
||||
for room in room_list().iter() {
|
||||
for scav in &room.scavtable {
|
||||
if thread_rng().gen_bool(scav.p_present) {
|
||||
let difflevel = {
|
||||
let mut rng = thread_rng();
|
||||
(rand_distr::Normal::new(scav.difficulty_mean, scav.difficulty_stdev)?
|
||||
.sample(&mut rng)
|
||||
* 100.0)
|
||||
.max(0.0) as u64
|
||||
};
|
||||
let mut item: Item = scav.possession_type.clone().into();
|
||||
item.item_code = format!("{}", trans.alloc_item_code().await?);
|
||||
item.location = format!("room/{}", room.code);
|
||||
item.action_type = LocationActionType::Scavhidden {
|
||||
difficulty: difflevel,
|
||||
scavtype: scav.scavtype.clone(),
|
||||
};
|
||||
trans.save_item_model(&item).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ mod corp_licence;
|
||||
mod fangs;
|
||||
mod food;
|
||||
pub mod head_armour;
|
||||
mod junk;
|
||||
pub mod lock;
|
||||
pub mod lower_armour;
|
||||
mod meat;
|
||||
@ -442,6 +443,7 @@ pub enum PossessionType {
|
||||
Steak,
|
||||
AnimalSkin,
|
||||
SeveredHead,
|
||||
RustySpike,
|
||||
// Craft benches
|
||||
KitchenStove,
|
||||
// Recipes
|
||||
@ -532,6 +534,7 @@ pub fn possession_data() -> &'static BTreeMap<PossessionType, &'static Possessio
|
||||
.chain(lock::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.chain(meat::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.chain(food::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.chain(junk::data().iter().map(|v| ((*v).0.clone(), &(*v).1)))
|
||||
.chain(
|
||||
head_armour::data()
|
||||
.iter()
|
||||
|
19
blastmud_game/src/static_content/possession_type/junk.rs
Normal file
19
blastmud_game/src/static_content/possession_type/junk.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use super::{PossessionData, PossessionType};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
||||
static D: OnceCell<Vec<(PossessionType, PossessionData)>> = OnceCell::new();
|
||||
&D.get_or_init(|| {
|
||||
vec![(
|
||||
PossessionType::RustySpike,
|
||||
PossessionData {
|
||||
display: "rusty metal spike",
|
||||
aliases: vec!["spike"],
|
||||
details:
|
||||
"A sharp, rusty spike of metal. You might be able to make something with this, but it is otherwise useless",
|
||||
weight: 250,
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
})
|
||||
}
|
@ -4,7 +4,7 @@ use crate::db::DBTrans;
|
||||
use crate::{
|
||||
message_handler::user_commands::UResult,
|
||||
models::{
|
||||
item::{DoorState, Item, ItemFlag},
|
||||
item::{DoorState, Item, ItemFlag, Scavtype},
|
||||
journal::JournalType,
|
||||
user::WristpadHack,
|
||||
},
|
||||
@ -335,6 +335,14 @@ pub trait RoomEnterTrigger {
|
||||
async fn handle_enter(self: &Self, ctx: &mut QueuedCommandContext, room: &Room) -> UResult<()>;
|
||||
}
|
||||
|
||||
pub struct Scavinfo {
|
||||
pub possession_type: PossessionType,
|
||||
pub p_present: f64, // probability it is there.
|
||||
pub difficulty_mean: f64,
|
||||
pub difficulty_stdev: f64,
|
||||
pub scavtype: Scavtype,
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
pub zone: &'static str,
|
||||
// Other zones where it can be seen on the map and accessed.
|
||||
@ -359,6 +367,7 @@ pub struct Room {
|
||||
pub wristpad_hack_allowed: Option<WristpadHack>,
|
||||
pub journal: Option<JournalType>,
|
||||
pub enter_trigger: Option<&'static (dyn RoomEnterTrigger + Sync + Send)>,
|
||||
pub scavtable: Vec<Scavinfo>,
|
||||
}
|
||||
|
||||
impl Default for Room {
|
||||
@ -384,6 +393,7 @@ impl Default for Room {
|
||||
wristpad_hack_allowed: None,
|
||||
journal: None,
|
||||
enter_trigger: None,
|
||||
scavtable: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -555,6 +565,18 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_shorts_should_be_display_length_2() {
|
||||
assert_eq!(
|
||||
room_list()
|
||||
.iter()
|
||||
.map(|r| (r.code, ansi::strip_special_characters(r.short)))
|
||||
.filter(|(_c, s)| s.len() != 2)
|
||||
.collect::<Vec<(&'static str, String)>>(),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn room_map_by_code_should_have_repro_xv_chargen() {
|
||||
assert_eq!(
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user