From d35bbbad532c7dafacab9f3e151aeba7f2f0a46f Mon Sep 17 00:00:00 2001 From: Condorra Date: Sun, 26 Mar 2023 22:29:39 +1100 Subject: [PATCH] Implement messaging to corps. --- blastmud_game/src/db.rs | 18 ++++ .../src/message_handler/user_commands.rs | 2 + .../src/message_handler/user_commands/c.rs | 55 +++++++++++ .../src/message_handler/user_commands/corp.rs | 96 ++++++++++++++++++- .../message_handler/user_commands/register.rs | 3 +- blastmud_game/src/models/corp.rs | 26 ++++- 6 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 blastmud_game/src/message_handler/user_commands/c.rs diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 4dcd1acf..391d667e 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -893,6 +893,24 @@ impl DBTrans { }) .collect()) } + + pub async fn get_default_corp_for_user(&self, username: &str) -> DResult> { + Ok(match self.pg_trans()? + .query_opt("SELECT m.corp_id, c.details FROM corp_membership m \ + JOIN corps c ON c.corp_id = m.corp_id WHERE m.member_username = $1 \ + AND m.details->>'joined_at' IS NOT NULL \ + ORDER BY (m.details->>'priority')::int ASC NULLS LAST, \ + (m.details->>'joined_at') :: TIMESTAMPTZ ASC LIMIT 1", + &[&username.to_lowercase()]) + .await? { + None => None, + Some(row) => match serde_json::from_value(row.get(1)) { + Err(_) => None, + Ok(j) => + Some((CorpId(row.get(0)), j)) + } + }) + } pub async fn broadcast_to_corp<'a>(self: &'a Self, corp_id: &'a CorpId, diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index db4a4533..a5d6c7fa 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -15,6 +15,7 @@ mod agree; mod allow; pub mod attack; mod buy; +mod c; mod corp; pub mod drop; pub mod get; @@ -119,6 +120,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! { "attack" => attack::VERB, "buy" => buy::VERB, + "c" => c::VERB, "corp" => corp::VERB, "drop" => drop::VERB, "get" => get::VERB, diff --git a/blastmud_game/src/message_handler/user_commands/c.rs b/blastmud_game/src/message_handler/user_commands/c.rs new file mode 100644 index 00000000..e220aed8 --- /dev/null +++ b/blastmud_game/src/message_handler/user_commands/c.rs @@ -0,0 +1,55 @@ +use super::{VerbContext, UserVerb, UserVerbRef, UResult, + get_user_or_fail, get_player_item_or_fail, user_error}; +use async_trait::async_trait; +use crate::{ + models::corp::CorpCommType +}; +use ansi::{ansi, ignore_special_characters}; + +pub struct Verb; +#[async_trait] +impl UserVerb for Verb { + async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { + let user = get_user_or_fail(ctx)?; + + let (corp_id, corp, msg) = if remaining.starts_with("@") { + match remaining[1..].split_once(" ") { + None => user_error("Usage: c message (lowest ordered corp) or c @corpname message" + .to_owned())?, + Some((corpname, msg)) => { + let (corp_id, corp, _) = + match ctx.trans.match_user_corp_by_name(corpname.trim(), &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + (corp_id, corp, msg.trim()) + } + } + } else { + let (corp_id, corp) = + match ctx.trans.get_default_corp_for_user(&user.username).await? { + None => user_error("You're not a member of any corps.".to_owned())?, + Some(v) => v + }; + (corp_id, corp, remaining.trim()) + }; + + let msg = ignore_special_characters(msg); + + if msg.trim() == "" { + user_error("Message required.".to_owned())?; + } + + let player = get_player_item_or_fail(ctx).await?; + ctx.trans.broadcast_to_corp(&corp_id, &CorpCommType::Chat, + Some(&user.username), + &format!(ansi!("{} (to {}): \"{}\"\n"), + &player.display_for_sentence(false, 1, true), + &corp.name, + &msg)).await?; + + Ok(()) + } +} +static VERB_INT: Verb = Verb; +pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef; diff --git a/blastmud_game/src/message_handler/user_commands/corp.rs b/blastmud_game/src/message_handler/user_commands/corp.rs index fe1470e5..a5c0faf1 100644 --- a/blastmud_game/src/message_handler/user_commands/corp.rs +++ b/blastmud_game/src/message_handler/user_commands/corp.rs @@ -576,6 +576,91 @@ async fn corp_config(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> Ok(()) } +async fn corp_subscribe(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> { + let user = get_user_or_fail(ctx)?; + let (subs, remaining) = match remaining.split_once(" from ") { + None => { + let (_, corp, mem) = + match ctx.trans.match_user_corp_by_name(remaining.trim(), &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + ctx.trans.queue_for_session( + &ctx.session, + Some(&format!("Subscriptions for {}: {}\n", + &corp.name, + &mem.comms_on.iter().map(|m| m.display()).join(" "))) + ).await?; + return Ok(()); + } + Some(v) => v + }; + let mut subs_add = BTreeSet::::new(); + for sub in subs.trim().split(" ") { + let sub = ignore_special_characters(&sub.trim()); + match CorpCommType::parse(&sub) { + None => user_error(format!("Invalid commtype: {}", sub))?, + Some(t) => subs_add.insert(t) + }; + } + let (corp_id, _, mut mem) = + match ctx.trans.match_user_corp_by_name(remaining.trim(), &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + mem.comms_on = (&mem.comms_on.into_iter().collect::>() | &subs_add).into_iter().collect(); + ctx.trans.upsert_corp_membership(&corp_id, &user.username, &mem).await?; + ctx.trans.queue_for_session(&ctx.session, Some("Subscriptions updated.\n")).await?; + Ok(()) +} + +async fn corp_unsubscribe(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> { + let (subs, remaining) = match remaining.split_once(" from ") { + None => user_error("Usage: corp unsubscribe commtype commtype ... from corpname\n\ + commtypes: chat notice connect reward death".to_owned())?, + Some(v) => v + }; + let mut subs_del = BTreeSet::::new(); + for sub in subs.trim().split(" ") { + let sub = ignore_special_characters(&sub.trim()); + match CorpCommType::parse(&sub) { + None => user_error(format!("Invalid commtype: {}", sub))?, + Some(t) => subs_del.insert(t) + }; + } + let user = get_user_or_fail(ctx)?; + let (corp_id, _, mut mem) = + match ctx.trans.match_user_corp_by_name(remaining.trim(), &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + mem.comms_on = (&mem.comms_on.into_iter().collect::>() - &subs_del).into_iter().collect(); + ctx.trans.upsert_corp_membership(&corp_id, &user.username, &mem).await?; + ctx.trans.queue_for_session(&ctx.session, Some("Subscriptions updated.\n")).await?; + Ok(()) +} + +async fn corp_order(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> { + let (corpname, number_s) = match remaining.split_once(" as ") { + None => user_error("Usage: corp order corpname as number".to_owned())?, + Some(v) => v + }; + let number = match nom::character::complete::i64::<&str, ()>(number_s) { + Ok((rest, num)) if rest.trim() == "" => num, + _ => user_error("Usage: corp order corpname as number".to_owned())? + }; + let user = get_user_or_fail(ctx)?; + let (corp_id, _, mut mem) = + match ctx.trans.match_user_corp_by_name(corpname.trim(), &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + mem.priority = number; + ctx.trans.upsert_corp_membership(&corp_id, &user.username, &mem).await?; + ctx.trans.queue_for_session(&ctx.session, Some("Updated!\n")).await?; + Ok(()) +} + pub struct Verb; #[async_trait] impl UserVerb for Verb { @@ -607,7 +692,16 @@ impl UserVerb for Verb { }, "configure" | "config" => { corp_config(ctx, remaining).await?; - } + }, + "sub" | "subscribe" => { + corp_subscribe(ctx, remaining).await?; + }, + "unsub" | "unsubscribe" => { + corp_unsubscribe(ctx, remaining).await?; + }, + "order" => { + corp_order(ctx, remaining).await?; + }, _ => user_error("Unknown command".to_owned())? } Ok(()) diff --git a/blastmud_game/src/message_handler/user_commands/register.rs b/blastmud_game/src/message_handler/user_commands/register.rs index bd9ae7f2..591285b7 100644 --- a/blastmud_game/src/message_handler/user_commands/register.rs +++ b/blastmud_game/src/message_handler/user_commands/register.rs @@ -19,7 +19,8 @@ pub fn is_invalid_username(name: &str) -> bool { "bot" )); let invalid_words = INVALID_WORDS.get_or_init(|| HashSet::from( - ["corp", "to", "from", "dog", "bot"] + ["corp", "to", "from", "dog", "bot", "for", "against", "on", + "privileges", "as"] )); if invalid_words.contains(name) { return true; diff --git a/blastmud_game/src/models/corp.rs b/blastmud_game/src/models/corp.rs index 87e745d5..a2db63e8 100644 --- a/blastmud_game/src/models/corp.rs +++ b/blastmud_game/src/models/corp.rs @@ -40,7 +40,7 @@ impl CorpPermission { } -#[derive(Serialize, Deserialize, PartialEq)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)] pub enum CorpCommType { Chat, Notice, @@ -49,6 +49,30 @@ pub enum CorpCommType { Death, } +impl CorpCommType { + pub fn parse(s: &str) -> Option { + use CorpCommType::*; + match s { + "chat" => Some(Chat), + "notice" => Some(Notice), + "connect" => Some(Connect), + "reward" => Some(Reward), + "death" => Some(Death), + _ => None + } + } + pub fn display(&self) -> &'static str { + use CorpCommType::*; + match self { + Chat => "chat", + Notice => "notice", + Connect => "connect", + Reward => "reward", + Death => "death", + } + } +} + pub struct CorpId(pub i64); #[derive(Serialize, Deserialize)]