diff --git a/blastmud_game/src/db.rs b/blastmud_game/src/db.rs index 391d667..eaa74fc 100644 --- a/blastmud_game/src/db.rs +++ b/blastmud_game/src/db.rs @@ -756,6 +756,18 @@ impl DBTrans { } } + pub async fn find_corp_consent_by_parties_type(&self, consenting: &CorpId, consented: &CorpId, + consent_type: &ConsentType) -> DResult> { + match self.pg_trans()?.query_opt( + "SELECT details FROM corp_consent WHERE consenting_corp = $1 AND \ + consented_corp = $2 AND consent_type = $3", + &[&consenting.0, &consented.0, &ConsentType::to_str(consent_type)] + ).await? { + None => Ok(None), + Some(row) => Ok(Some(serde_json::from_value(row.get(0))?)) + } + } + pub async fn revoke_until_death_consent(&self, party: &str) -> DResult<()> { self.pg_trans()?.execute( "DELETE FROM user_consent WHERE (consenting_user = $1 OR \ @@ -763,6 +775,16 @@ impl DBTrans { details->>'until_death'='true'", &[&party] ).await?; + self.pg_trans()?.execute( + "DELETE FROM corp_consent cc USING + corp_membership cm + WHERE (cc.consenting_corp = cm.corp_id OR cc.consented_corp = cm.corp_id) AND \ + cm.member_username = $1 AND \ + cm.details->>'joined_at' IS NOT NULL AND \ + cc.consent_type = 'fight' AND \ + cc.details->>'until_death'='true'", + &[&party] + ).await?; Ok(()) } @@ -774,6 +796,14 @@ impl DBTrans { Ok(()) } + pub async fn delete_expired_corp_consent(&self) -> DResult<()> { + self.pg_trans()?.execute( + "DELETE FROM corp_consent WHERE details->>'expires' < $1", + &[&Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)] + ).await?; + Ok(()) + } + pub async fn delete_user_consent(&self, consenting: &str, consented: &str, @@ -786,6 +816,18 @@ impl DBTrans { Ok(()) } + pub async fn delete_corp_consent(&self, + consenting: &CorpId, + consented: &CorpId, + consent_type: &ConsentType) -> DResult<()> { + self.pg_trans()?.execute( + "DELETE FROM corp_consent WHERE consenting_corp = $1 AND \ + consented_corp = $2 AND consent_type = $3", + &[&consenting.0, &consented.0, &ConsentType::to_str(consent_type)] + ).await?; + Ok(()) + } + pub async fn upsert_user_consent(&self, consenting: &str, consented: &str, @@ -800,7 +842,22 @@ impl DBTrans { &serde_json::to_value(details)?]).await?; Ok(()) } - + + pub async fn upsert_corp_consent(&self, + consenting: &CorpId, + consented: &CorpId, + consent_type: &ConsentType, + details: &Consent + ) -> DResult<()> { + self.pg_trans()? + .execute("INSERT INTO corp_consent (consenting_corp, consented_corp, consent_type, details) VALUES ($1, $2, $3, $4) \ + ON CONFLICT (consenting_corp, consented_corp, consent_type) DO UPDATE SET \ + details = EXCLUDED.details", + &[&consenting.0, &consented.0, &ConsentType::to_str(consent_type), + &serde_json::to_value(details)?]).await?; + Ok(()) + } + pub async fn find_corp_by_name(&self, name: &str) -> DResult> { Ok(match self.pg_trans()? .query_opt("SELECT corp_id, details FROM corps WHERE LOWER(details->>'name') = $1", diff --git a/blastmud_game/src/message_handler/user_commands.rs b/blastmud_game/src/message_handler/user_commands.rs index a5d6c7f..e14ded5 100644 --- a/blastmud_game/src/message_handler/user_commands.rs +++ b/blastmud_game/src/message_handler/user_commands.rs @@ -16,7 +16,7 @@ mod allow; pub mod attack; mod buy; mod c; -mod corp; +pub mod corp; pub mod drop; pub mod get; mod describe; diff --git a/blastmud_game/src/message_handler/user_commands/allow.rs b/blastmud_game/src/message_handler/user_commands/allow.rs index e9cd7be..596874d 100644 --- a/blastmud_game/src/message_handler/user_commands/allow.rs +++ b/blastmud_game/src/message_handler/user_commands/allow.rs @@ -5,18 +5,22 @@ use super::{ UResult, user_error, get_player_item_or_fail, + get_user_or_fail, search_item_for_user, - parsing + parsing, + corp::check_corp_perm, }; use async_trait::async_trait; use crate::{ models::{ consent::{Consent, ConsentType, ConsentStatus, FightConsent}, + corp::{CorpPermission, CorpCommType}, item::Item }, db::ItemSearchParams, static_content::room::room_map_by_code, }; +use ansi::ansi; #[derive(Debug, PartialEq)] pub enum ConsentTarget<'t> { @@ -669,6 +673,127 @@ async fn handle_user_consent(ctx: &mut VerbContext<'_>, source_player: &Item, Ok(()) } +async fn handle_corp_consent(ctx: &mut VerbContext<'_>, source_player: &Item, + from_corp: &str, + to_corp: &str, + is_allow: bool, + cmd: &AllowCommand<'_> +) -> UResult<()> { + let user = get_user_or_fail(ctx)?; + let (from_corp_id, from_corp, mem) = + match ctx.trans.match_user_corp_by_name(from_corp, &user.username).await? { + None => user_error("You don't seem to belong to a matching corp!".to_owned())?, + Some(c) => c + }; + let (to_corp_id, to_corp) = match ctx.trans.find_corp_by_name(to_corp).await? { + None => user_error("I didn't find the corp you want to fight against.".to_owned())?, + Some(c) => c + }; + if !check_corp_perm(&CorpPermission::War, &mem) { + user_error("You don't have permission to declare war on behalf of that corp.".to_owned())?; + } + + ctx.trans.delete_expired_corp_consent().await?; + + let current_consent = ctx.trans.find_corp_consent_by_parties_type( + &from_corp_id, + &to_corp_id, + &cmd.consent_type + ).await?; + let converse_consent = if cmd.consent_type == ConsentType::Fight { + ctx.trans.find_corp_consent_by_parties_type( + &to_corp_id, + &from_corp_id, + &cmd.consent_type + ).await? + } else { + None + }; + + let update = compute_new_consent_state( + &to_corp.name, + "their", + &to_corp.name, + "their", + &("against ".to_owned() + &to_corp.name + " by " + &from_corp.name), + &cmd.consent_type, &cmd.consent_details, + ¤t_consent, &converse_consent, is_allow + ); + + match update.new_consent.as_ref() { + None => ctx.trans.delete_corp_consent( + &from_corp_id, + &to_corp_id, + &cmd.consent_type + ).await?, + Some(consent) => ctx.trans.upsert_corp_consent( + &from_corp_id, + &to_corp_id, + &cmd.consent_type, + consent + ).await?, + } + if update.mirror_to_counterparty { + match update.new_consent.as_ref() { + None => ctx.trans.delete_corp_consent( + &to_corp_id, + &from_corp_id, + &cmd.consent_type + ).await?, + Some(consent) => ctx.trans.upsert_corp_consent( + &to_corp_id, + &from_corp_id, + &cmd.consent_type, + consent + ).await?, + } + } + + match update.first_party_message { + None => {}, + Some(msg) => + if update.counterparty_message.is_some() { + let details_str = cmd.consent_details.as_string(&ConsentType::Fight); + let details_str = if details_str != "" { + format!(" ({})", &details_str.trim()) + } else { + "".to_owned() + }; + let action_str = if is_allow { "consented" } else { "withdrew consent" }; + // If it goes to both corps, it is a diplomatic message so goes to whole source corp. + ctx.trans.broadcast_to_corp( + &from_corp_id, + &CorpCommType::Consent, + None, + &format!(ansi!("{} {} to fight{} against {} on behalf \ + of {}, with result: {}\n"), + &source_player.display_for_sentence(false, 1, true), + action_str, + &details_str, + &to_corp.name, + &from_corp.name, + &msg + ) + ).await?; + } else { + ctx.trans.queue_for_session(&ctx.session, Some(&(msg + "\n"))).await?; + } + } + match update.counterparty_message { + None => {}, + Some(msg) => { + ctx.trans.broadcast_to_corp( + &to_corp_id, + &CorpCommType::Consent, + None, + &format!(ansi!("Your wristpad buzzes with an corporate announcement to {}: {}\n"), &to_corp.name, &msg) + ).await? + } + } + + Ok(()) +} + pub struct Verb; #[async_trait] impl UserVerb for Verb { @@ -699,8 +824,8 @@ impl UserVerb for Verb { } } match cmd.consent_target { - ConsentTarget::CorpTarget { .. } => user_error( - "Corporate allow/disallow not implemented yet".to_owned())?, + ConsentTarget::CorpTarget { from_corp, to_corp } => + handle_corp_consent(ctx, &player_item, from_corp, to_corp, is_allow, &cmd).await?, ConsentTarget::UserTarget { to_user } => handle_user_consent(ctx, &player_item, to_user, is_allow, &cmd).await? } diff --git a/blastmud_game/src/message_handler/user_commands/corp.rs b/blastmud_game/src/message_handler/user_commands/corp.rs index a5c0faf..da659b7 100644 --- a/blastmud_game/src/message_handler/user_commands/corp.rs +++ b/blastmud_game/src/message_handler/user_commands/corp.rs @@ -21,7 +21,7 @@ use std::collections::BTreeSet; use itertools::Itertools; use humantime; -fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool { +pub fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool { mem.permissions.iter().any(|p| *p == CorpPermission::Holder || *p == *perm) } @@ -617,7 +617,7 @@ async fn corp_subscribe(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<( 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())?, + commtypes: chat notice connect reward death consent".to_owned())?, Some(v) => v }; let mut subs_del = BTreeSet::::new(); diff --git a/blastmud_game/src/models/corp.rs b/blastmud_game/src/models/corp.rs index a2db63e..f4aa8c1 100644 --- a/blastmud_game/src/models/corp.rs +++ b/blastmud_game/src/models/corp.rs @@ -47,6 +47,7 @@ pub enum CorpCommType { Connect, Reward, Death, + Consent, } impl CorpCommType { @@ -58,6 +59,7 @@ impl CorpCommType { "connect" => Some(Connect), "reward" => Some(Reward), "death" => Some(Death), + "consent" => Some(Consent), _ => None } } @@ -69,6 +71,7 @@ impl CorpCommType { Connect => "connect", Reward => "reward", Death => "death", + Consent => "consent", } } } @@ -126,6 +129,7 @@ impl Default for CorpMembership { Connect, Reward, Death, + Consent, ), } }