Improve get targeting with 'all' option.

This commit is contained in:
Condorra 2023-02-20 22:27:43 +11:00
parent ddd0f33cb5
commit d4fd71d839
8 changed files with 92 additions and 17 deletions

View File

@ -235,6 +235,8 @@ pub struct ItemSearchParams<'l> {
pub include_loc_contents: bool,
pub include_active_players: bool,
pub include_all_players: bool,
pub item_type_only: Option<&'l str>,
pub limit: u8,
pub dead_first: bool,
}
@ -247,6 +249,8 @@ impl ItemSearchParams<'_> {
include_active_players: false,
include_all_players: false,
dead_first: false,
limit: 100,
item_type_only: None,
}
}
}
@ -521,8 +525,30 @@ impl DBTrans {
let player_desig = format!("{}/{}", search.from_item.item_type,
search.from_item.item_code);
let (offset, query) = parse_offset(search.query);
let mut param_no: usize = 5;
let (offset, mut query) = parse_offset(search.query);
let mut param_no: usize = 6;
let mut extra_order: String = String::new();
let mut extra_where: String = String::new();
let mut dead_only = false;
if query.starts_with("dead") {
query = query[4..].trim();
dead_only = true;
} else if query.starts_with("corpse of") {
query = query[9..].trim();
dead_only = true;
} else if query.starts_with("corpse") {
query = query[6..].trim();
dead_only = true;
}
if dead_only {
extra_where.push_str(" AND COALESCE(CAST(details->>'is_dead' AS boolean), false) = true");
} else if search.dead_first {
extra_order.push_str(" COALESCE(CAST(details->>'is_dead' AS boolean), false) DESC,");
} else {
extra_order.push_str(" COALESCE(CAST(details->>'is_dead' AS boolean), false) ASC,");
}
let query_wildcard = query.replace("\\", "\\\\")
.replace("_", "\\_")
.replace("%", "")
@ -530,11 +556,24 @@ impl DBTrans {
let offset_sql = offset.map(|x| (if x >= 1 { x - 1 } else { x}) as i64).unwrap_or(0);
let query_json = serde_json::to_value(query.to_lowercase())?;
let query_len = query.len() as i32;
let limit = search.limit as i64;
let mut params: Vec<&(dyn ToSql + Sync)> = vec!(
&query_wildcard,
&offset_sql, &query_len, &query_json);
&offset_sql,
&query_len,
&query_json,
&limit
);
match search.item_type_only {
None => {}
Some(ref item_type) => {
extra_where.push_str(&format!(" AND details->>'item_type' = ${}", param_no));
param_no += 1;
params.push(item_type);
}
}
if search.include_contents {
ctes.push(format!("contents AS (\
SELECT details, details->'aliases' AS aliases FROM items WHERE details->>'location' = ${}\
@ -567,14 +606,15 @@ impl DBTrans {
ctes.push(format!("relevant_items AS ({})", include_tables.join(" UNION ")));
let cte_str: String = ctes.join(", ");
Ok(Arc::new(self.pg_trans()?.query(
&format!(
"WITH {} SELECT details, aliases FROM relevant_items WHERE (lower(details->>'display') LIKE $1) \
OR (lower(details ->>'display_less_explicit') LIKE $1) \
OR aliases @> $4 \
ORDER BY ABS(length(details->>'display')-$3) ASC \
LIMIT 1 OFFSET $2", &cte_str),
"WITH {} SELECT details, aliases FROM relevant_items WHERE \
((lower(details->>'display') LIKE $1) \
OR (lower(details ->>'display_less_explicit') LIKE $1) \
OR aliases @> $4) {} \
ORDER BY {} ABS(length(details->>'display')-$3) ASC \
LIMIT $5 OFFSET $2", &cte_str, &extra_where, &extra_order),
&params
).await?.into_iter()
.filter_map(|i| serde_json::from_value(i.get("details")).ok())

View File

@ -242,6 +242,14 @@ pub async fn search_item_for_user<'l>(ctx: &'l VerbContext<'l>, search: &'l Item
})
}
pub async fn search_items_for_user<'l>(ctx: &'l VerbContext<'l>, search: &'l ItemSearchParams<'l>) ->
UResult<Vec<Arc<Item>>> {
Ok(match &ctx.trans.resolve_items_by_display_name_for_player(search).await?[..] {
[] => user_error("Sorry, I couldn't find anything matching.".to_owned())?,
v => v.into_iter().map(|it| it.clone()).collect(),
})
}
#[cfg(test)] mod test {
use crate::db::MockDBTrans;

View File

@ -22,6 +22,7 @@ impl UserVerb for Verb {
let attack_whom = search_item_for_user(ctx, &ItemSearchParams {
include_loc_contents: true,
limit: 1,
..ItemSearchParams::base(&player_item, remaining)
}).await?;

View File

@ -6,7 +6,8 @@ use super::{
ItemSearchParams,
user_error,
get_player_item_or_fail,
search_item_for_user,
search_items_for_user,
parsing::parse_count
};
use crate::{
static_content::possession_type::possession_data,
@ -115,20 +116,32 @@ impl QueueCommandHandler for QueueHandler {
pub struct Verb;
#[async_trait]
impl UserVerb for Verb {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, mut remaining: &str) -> UResult<()> {
let player_item = get_player_item_or_fail(ctx).await?;
// TODO: Parse "get target from container" variant
let target = search_item_for_user(ctx, &ItemSearchParams {
let mut get_limit = Some(1);
if remaining == "all" || remaining.starts_with("all ") {
remaining = remaining[3..].trim();
get_limit = None;
} else if let (Some(n), remaining2) = parse_count(remaining) {
get_limit = Some(n);
remaining = remaining2;
}
let targets = search_items_for_user(ctx, &ItemSearchParams {
include_loc_contents: true,
item_type_only: Some("possession"),
limit: get_limit.unwrap_or(100),
..ItemSearchParams::base(&player_item, &remaining)
}).await?;
if player_item.is_dead {
user_error("You try to get it, but your ghostly hands slip through it uselessly".to_owned())?;
}
if target.item_type != "possession" {
user_error("You can't get that!".to_owned())?;
for target in targets {
if target.item_type != "possession" {
user_error("You can't get that!".to_owned())?;
}
queue_command(ctx, &QueueCommand::Get { possession_id: target.item_code.clone() }).await?;
}
queue_command(ctx, &QueueCommand::Get { possession_id: target.item_code.clone() }).await?;
Ok(())
}
}

View File

@ -219,6 +219,7 @@ impl UserVerb for Verb {
&ItemSearchParams {
include_contents: true,
include_loc_contents: true,
limit: 1,
..ItemSearchParams::base(&player_item, &rem_trim)
}
).await?

View File

@ -45,6 +45,16 @@ pub fn parse_offset(input: &str) -> (Option<u8>, &str) {
}
}
pub fn parse_count(input: &str) -> (Option<u8>, &str) {
fn parser(input: &str) -> IResult<&str, u8> {
terminated(u8, char(' '))(input)
}
match parser(input) {
Err(_) => (None, input),
Ok((rest, result)) => (Some(result), rest)
}
}
pub fn parse_username(input: &str) -> Result<(&str, &str), &'static str> {
const CATCHALL_ERROR: &'static str = "Must only contain alphanumeric characters or _";
fn parse_valid(input: &str) -> IResult<&str, (), VerboseError<&str>> {

View File

@ -22,6 +22,7 @@ impl UserVerb for Verb {
}
let to_whom = search_item_for_user(ctx, &ItemSearchParams {
include_loc_contents: true,
limit: 1,
..ItemSearchParams::base(&player_item, &to_whom_name)
}).await?;

View File

@ -117,6 +117,7 @@ impl UserVerb for Verb {
let player_item = get_player_item_or_fail(ctx).await?;
let weapon = search_item_for_user(ctx, &ItemSearchParams {
include_contents: true,
limit: 1,
..ItemSearchParams::base(&player_item, &remaining)
}).await?;
if player_item.is_dead {