forked from blasthavers/blastmud
874 lines
35 KiB
Rust
874 lines
35 KiB
Rust
use super::{
|
|
VerbContext,
|
|
UserVerb,
|
|
UserVerbRef,
|
|
UResult,
|
|
user_error,
|
|
get_player_item_or_fail,
|
|
get_user_or_fail,
|
|
search_item_for_user,
|
|
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;
|
|
use itertools::Itertools;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum ConsentTarget<'t> {
|
|
CorpTarget { from_corp: &'t str, to_corp: &'t str },
|
|
UserTarget { to_user: &'t str }
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct ConsentDetails<'t> {
|
|
pub duration_minutes: Option<u64>,
|
|
pub until_death: bool,
|
|
pub allow_private: bool,
|
|
pub only_in: Vec<&'t str>,
|
|
pub allow_pick: bool,
|
|
pub freely_revoke: bool,
|
|
}
|
|
|
|
impl <'t>ConsentDetails<'t> {
|
|
pub fn default_for(tp: &ConsentType) -> Self {
|
|
Self {
|
|
duration_minutes: None,
|
|
until_death: false,
|
|
allow_private: tp != &ConsentType::Fight,
|
|
only_in: vec!(),
|
|
allow_pick: false,
|
|
freely_revoke: false,
|
|
}
|
|
}
|
|
|
|
pub fn as_string(&self, consent_type: &ConsentType) -> String {
|
|
let mut buf = String::new();
|
|
match self.duration_minutes {
|
|
None => {},
|
|
Some(n) => buf.push_str(&format!("{} minutes ", n))
|
|
}
|
|
if self.until_death {
|
|
buf.push_str("until death ");
|
|
}
|
|
if *consent_type == ConsentType::Fight {
|
|
if self.allow_private {
|
|
buf.push_str("allow private ");
|
|
}
|
|
if self.allow_pick {
|
|
buf.push_str("allow pick ");
|
|
}
|
|
if self.freely_revoke {
|
|
buf.push_str("allow revoke ");
|
|
}
|
|
} else {
|
|
if !self.allow_private {
|
|
buf.push_str("disallow private ");
|
|
}
|
|
}
|
|
for loc in &self.only_in {
|
|
buf.push_str(&format!("in {} ", loc));
|
|
}
|
|
buf
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct AllowCommand<'t> {
|
|
pub consent_type: ConsentType,
|
|
pub consent_target: ConsentTarget<'t>,
|
|
pub consent_details: ConsentDetails<'t>
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ConsentUpdateOutput {
|
|
pub new_consent: Option<Consent>,
|
|
pub first_party_message: Option<String>,
|
|
pub counterparty_message: Option<String>,
|
|
pub mirror_to_counterparty: bool
|
|
}
|
|
|
|
fn compute_new_consent_state(
|
|
player_display: &str,
|
|
player_possessive: &str,
|
|
counterplayer_display: &str,
|
|
counterplayer_possessive: &str,
|
|
selector: &str,
|
|
consent_type: &ConsentType,
|
|
new_consent_details: &ConsentDetails,
|
|
my_current_consent: &Option<Consent>,
|
|
their_current_consent: &Option<Consent>,
|
|
is_allow: bool) -> ConsentUpdateOutput {
|
|
if !is_allow {
|
|
if consent_type != &ConsentType::Fight {
|
|
if my_current_consent.is_none() {
|
|
return ConsentUpdateOutput {
|
|
new_consent: None,
|
|
first_party_message: Some("There was no matching consent in force to disallow".to_owned()),
|
|
counterparty_message: None,
|
|
mirror_to_counterparty: false
|
|
}
|
|
} else {
|
|
return ConsentUpdateOutput {
|
|
new_consent: None,
|
|
first_party_message:
|
|
Some(format!("You no longer allow {} from {}",
|
|
ConsentType::to_str(consent_type), counterplayer_display)),
|
|
counterparty_message:
|
|
Some(format!("{} no longer allows {} from you", player_display,
|
|
ConsentType::to_str(consent_type))),
|
|
mirror_to_counterparty: false
|
|
}
|
|
}
|
|
}
|
|
|
|
if my_current_consent.as_ref().and_then(|c| c.fight_consent.as_ref())
|
|
.map(|c| (c.status == ConsentStatus::PendingAdd)) == Some(true) {
|
|
return ConsentUpdateOutput {
|
|
new_consent: None,
|
|
first_party_message:
|
|
Some(format!("You are no longer offering to fight {}",
|
|
counterplayer_display)),
|
|
counterparty_message:
|
|
Some(format!("{} no longer wants to fight you", player_display)),
|
|
mirror_to_counterparty: false
|
|
};
|
|
}
|
|
|
|
if their_current_consent.as_ref().and_then(|c| c.fight_consent.as_ref())
|
|
.map(|c| c.freely_revoke || (c.status == ConsentStatus::PendingDelete)) == Some(true) {
|
|
return ConsentUpdateOutput {
|
|
new_consent: None,
|
|
first_party_message:
|
|
Some(format!("You no longer allow {} from {}",
|
|
ConsentType::to_str(consent_type), counterplayer_display)),
|
|
counterparty_message:
|
|
Some(format!("{} no longer allows {} from you", player_display,
|
|
ConsentType::to_str(consent_type))),
|
|
mirror_to_counterparty: true
|
|
};
|
|
}
|
|
match my_current_consent.as_ref() {
|
|
None => return ConsentUpdateOutput {
|
|
new_consent: None,
|
|
first_party_message: Some("There was no matching consent in force to disallow".to_owned()),
|
|
counterparty_message: None,
|
|
mirror_to_counterparty: false
|
|
},
|
|
Some(c) if c.fight_consent.as_ref().map(|fc| &fc.status) == Some(&ConsentStatus::PendingDelete) =>
|
|
return ConsentUpdateOutput {
|
|
new_consent: Some((*c).clone()),
|
|
first_party_message: Some(format!("You are already waiting on {} to agree to cancel consent to fight.",
|
|
counterplayer_display
|
|
)),
|
|
counterparty_message: None,
|
|
mirror_to_counterparty: false
|
|
},
|
|
Some(c) => return ConsentUpdateOutput {
|
|
new_consent: Some(Consent {
|
|
fight_consent: c.fight_consent.as_ref().map(|fc| FightConsent {
|
|
status: ConsentStatus::PendingDelete, pending_change: None, ..*fc }),
|
|
..((*c).clone()) }),
|
|
first_party_message:
|
|
Some(format!("Informing {} of your desire to withdraw consent to fight. The terms of your previous consent \
|
|
were that it was not freely revokable, so the change will only take effect on {} \
|
|
acceptance.",
|
|
counterplayer_display, counterplayer_possessive)),
|
|
counterparty_message:
|
|
Some(format!("{} wants to withdraw {} consent to fight, but only can if you agree. To agree, type disallow \
|
|
fight {}",
|
|
player_display, player_possessive, selector)),
|
|
mirror_to_counterparty: false
|
|
}
|
|
}
|
|
}
|
|
|
|
let their_target_consent = their_current_consent.as_ref().and_then(
|
|
|c| c.fight_consent.as_ref()
|
|
.map(|fc| fc.pending_change.as_ref()
|
|
.map(|c| (**c).clone()).unwrap_or(c.clone())));
|
|
let mut new_consent = Consent::default();
|
|
new_consent.only_in = new_consent_details.only_in.iter().map(|v| (*v).to_owned()).collect();
|
|
let expires_minutes = if *consent_type != ConsentType::Fight {
|
|
new_consent_details.duration_minutes
|
|
} else {
|
|
match new_consent_details.duration_minutes {
|
|
None => Some(60 * 24 * 7),
|
|
Some(n) => Some(n.min(60 * 24 * 7))
|
|
}
|
|
};
|
|
new_consent.expires = expires_minutes
|
|
.map(|n| chrono::Utc::now() +
|
|
chrono::Duration::minutes(n.min(60 * 24 * 365 * 25) as i64));
|
|
match their_target_consent.as_ref().and_then(|c| c.expires).and_then(
|
|
|t_them| new_consent.expires.map(|t_me| (t_me, t_them))) {
|
|
Some((t_me, t_them)) if (t_me - t_them).num_minutes().abs() < 5 => {
|
|
new_consent.expires = Some(t_them);
|
|
}
|
|
_ => {}
|
|
}
|
|
new_consent.allow_private = new_consent_details.allow_private;
|
|
new_consent.until_death = new_consent_details.until_death;
|
|
|
|
new_consent.fight_consent = if *consent_type == ConsentType::Fight {
|
|
Some(FightConsent {
|
|
pending_change: None,
|
|
allow_pick: new_consent_details.allow_pick,
|
|
freely_revoke: new_consent_details.freely_revoke,
|
|
..FightConsent::default()
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if *consent_type == ConsentType::Fight {
|
|
if Some(&new_consent) == their_target_consent.as_ref() {
|
|
match new_consent.fight_consent.as_mut() {
|
|
None => (),
|
|
Some(mut m) => {
|
|
m.pending_change = None;
|
|
m.status = ConsentStatus::Active;
|
|
}
|
|
}
|
|
return ConsentUpdateOutput {
|
|
new_consent: Some(new_consent),
|
|
first_party_message: Some(format!("You can now fight {} - it's on!",
|
|
counterplayer_display)),
|
|
counterparty_message: Some(format!("{} accepted your offer to fight - it's on!",
|
|
player_display)),
|
|
mirror_to_counterparty: true
|
|
};
|
|
}
|
|
match my_current_consent.as_ref() {
|
|
None => {
|
|
match new_consent.fight_consent.as_mut() {
|
|
None => (),
|
|
Some(mut m) => { m.status = ConsentStatus::PendingAdd; }
|
|
}
|
|
return ConsentUpdateOutput {
|
|
new_consent: Some(new_consent),
|
|
first_party_message: Some(format!("{} has been asked for consent to fight.", counterplayer_display)),
|
|
counterparty_message: Some(format!("{} wants to fight! To accept, type: allow fight {} {}",
|
|
player_display, selector,
|
|
&new_consent_details.as_string(consent_type))),
|
|
mirror_to_counterparty: false
|
|
};
|
|
},
|
|
Some(current) if current.fight_consent.as_ref().map(|fc| fc.status == ConsentStatus::PendingAdd) == Some(true) => {
|
|
return ConsentUpdateOutput {
|
|
new_consent: Some(new_consent),
|
|
first_party_message: Some("Waiting for the other side to accept on the new terms".to_owned()),
|
|
counterparty_message:
|
|
Some(format!("{} changed {} proposed fight terms! To accept, type allow fight {} {}",
|
|
player_display, player_possessive, selector, &new_consent_details.as_string(consent_type))),
|
|
mirror_to_counterparty: false
|
|
};
|
|
},
|
|
Some(current) => {
|
|
return ConsentUpdateOutput {
|
|
new_consent: Some(Consent {
|
|
fight_consent: current.fight_consent.as_ref().map(|fc| FightConsent {
|
|
pending_change: Some(Box::new(new_consent)),
|
|
..(*fc).clone()
|
|
}),
|
|
..(*current).clone()
|
|
}),
|
|
first_party_message: Some("Waiting for the other side to accept the change of terms".to_owned()),
|
|
counterparty_message:
|
|
Some(format!("{} wants to amend the terms of the fight! To accept, type allow fight {} {}",
|
|
player_display, selector, &new_consent_details.as_string(consent_type))),
|
|
mirror_to_counterparty: false
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
ConsentUpdateOutput {
|
|
new_consent: Some(new_consent),
|
|
first_party_message: Some(format!("You now allow {} from {}", consent_type.to_str(), counterplayer_display)),
|
|
counterparty_message: Some(format!("{} now allows {} from you", player_display, consent_type.to_str())),
|
|
mirror_to_counterparty: false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_cancels_non_fight() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Sex,
|
|
&ConsentDetails::default_for(&ConsentType::Sex),
|
|
&Some(Consent::default()),
|
|
&None,
|
|
false
|
|
);
|
|
assert_eq!(result.new_consent, None);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_disallow_puts_into_pending_delete_fight() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
false
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
assert_eq!(result.new_consent.unwrap()
|
|
.fight_consent.unwrap().status, ConsentStatus::PendingDelete);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_disallow_cancels_fight_if_freely_revoke() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
freely_revoke: true,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
freely_revoke: true,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
false
|
|
);
|
|
assert_eq!(result.new_consent, None);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
assert_eq!(result.mirror_to_counterparty, true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_disallow_cancels_pending_delete_fight() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::PendingDelete,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
false
|
|
);
|
|
assert_eq!(result.new_consent, None);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
assert_eq!(result.mirror_to_counterparty, true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_unilateral_double_disallow_doesnt_cancel() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::PendingDelete,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
false
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message, None);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_adds_nonfight_immediately() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Sex,
|
|
&ConsentDetails::default_for(&ConsentType::Sex),
|
|
&None,
|
|
&None,
|
|
true
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_creates_pending_add_for_fight() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&None,
|
|
&None,
|
|
true
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
assert_eq!(result.new_consent.unwrap().fight_consent.unwrap().status, ConsentStatus::PendingAdd);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_goes_active_if_pending_add_matches() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails::default_for(&ConsentType::Fight),
|
|
&None,
|
|
&Some(Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent::default()),
|
|
..Consent::default()
|
|
}),
|
|
true
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
assert_eq!(result.new_consent.unwrap().fight_consent.unwrap().status, ConsentStatus::Active);
|
|
assert_eq!(result.mirror_to_counterparty, true);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_creates_pending_update_to_change_fight() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails {
|
|
freely_revoke: true,
|
|
..ConsentDetails::default_for(&ConsentType::Fight)
|
|
},
|
|
&Some(Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
true
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
let fc = result.new_consent.as_ref().unwrap().fight_consent.as_ref().unwrap();
|
|
assert_eq!(fc.status, ConsentStatus::Active);
|
|
assert_eq!(fc.freely_revoke, false);
|
|
assert_eq!(fc.pending_change.is_some(), true);
|
|
assert_eq!(fc.pending_change.as_ref().unwrap()
|
|
.fight_consent.as_ref().unwrap().freely_revoke, true);
|
|
assert_eq!(result.mirror_to_counterparty, false);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn compute_new_consent_state_accepts_pending_update_to_change_fight_if_matches() {
|
|
let result =
|
|
compute_new_consent_state(
|
|
"Foo", "his", "Bar", "her", "from bar",
|
|
&ConsentType::Fight,
|
|
&ConsentDetails {
|
|
freely_revoke: true,
|
|
..ConsentDetails::default_for(&ConsentType::Fight)
|
|
},
|
|
&Some(Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
&Some(Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent {
|
|
status: ConsentStatus::Active,
|
|
pending_change: Some(Box::new(
|
|
Consent {
|
|
expires: Some(chrono::Utc::now() + chrono::Duration::minutes(60 * 24 * 7)),
|
|
fight_consent: Some(FightConsent {
|
|
freely_revoke: true,
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}
|
|
)),
|
|
..FightConsent::default()
|
|
}),
|
|
..Consent::default()
|
|
}),
|
|
true
|
|
);
|
|
assert_eq!(result.new_consent.is_some(), true);
|
|
let fc = result.new_consent.as_ref().unwrap().fight_consent.as_ref().unwrap();
|
|
assert_eq!(fc.status, ConsentStatus::Active);
|
|
assert_eq!(fc.freely_revoke, true);
|
|
assert_eq!(fc.pending_change, None);
|
|
assert_eq!(result.mirror_to_counterparty, true);
|
|
assert_eq!(result.first_party_message.is_some(), true);
|
|
assert_eq!(result.counterparty_message.is_some(), true);
|
|
}
|
|
|
|
}
|
|
|
|
async fn handle_user_consent(ctx: &mut VerbContext<'_>, source_player: &Item,
|
|
to_username: &str, is_allow: bool, cmd: &AllowCommand<'_>) -> UResult<()> {
|
|
ctx.trans.delete_expired_user_consent().await?;
|
|
let to_user_item = search_item_for_user(ctx, &ItemSearchParams {
|
|
include_all_players: true,
|
|
..ItemSearchParams::base(source_player, to_username)
|
|
}).await?;
|
|
|
|
if source_player.item_code == to_user_item.item_code {
|
|
user_error("You know that's you, right?".to_owned())?;
|
|
}
|
|
|
|
let current_consent = ctx.trans.find_user_consent_by_parties_type(
|
|
&source_player.item_code,
|
|
&to_user_item.item_code,
|
|
&cmd.consent_type
|
|
).await?;
|
|
let converse_consent = if cmd.consent_type == ConsentType::Fight {
|
|
ctx.trans.find_user_consent_by_parties_type(
|
|
&to_user_item.item_code,
|
|
&source_player.item_code,
|
|
&cmd.consent_type
|
|
).await?
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let update = compute_new_consent_state(
|
|
&source_player.display_for_sentence(false, 1, false),
|
|
&source_player.pronouns.possessive,
|
|
&to_user_item.display_for_sentence(false, 1, false),
|
|
&to_user_item.pronouns.possessive,
|
|
&("from ".to_owned() + &source_player.item_code),
|
|
&cmd.consent_type, &cmd.consent_details,
|
|
¤t_consent, &converse_consent, is_allow
|
|
);
|
|
|
|
match update.new_consent.as_ref() {
|
|
None => ctx.trans.delete_user_consent(
|
|
&source_player.item_code,
|
|
&to_user_item.item_code,
|
|
&cmd.consent_type
|
|
).await?,
|
|
Some(consent) => ctx.trans.upsert_user_consent(
|
|
&source_player.item_code,
|
|
&to_user_item.item_code,
|
|
&cmd.consent_type,
|
|
consent
|
|
).await?,
|
|
}
|
|
if update.mirror_to_counterparty {
|
|
match update.new_consent.as_ref() {
|
|
None => ctx.trans.delete_user_consent(
|
|
&to_user_item.item_code,
|
|
&source_player.item_code,
|
|
&cmd.consent_type
|
|
).await?,
|
|
Some(consent) => ctx.trans.upsert_user_consent(
|
|
&to_user_item.item_code,
|
|
&source_player.item_code,
|
|
&cmd.consent_type,
|
|
consent
|
|
).await?,
|
|
}
|
|
}
|
|
|
|
match update.first_party_message {
|
|
None => {},
|
|
Some(msg) => ctx.trans.queue_for_session(&ctx.session, Some(&(msg + "\n"))).await?
|
|
}
|
|
match update.counterparty_message {
|
|
None => {},
|
|
Some(msg) => {
|
|
match ctx.trans.find_session_for_player(&to_user_item.item_code).await? {
|
|
None => {},
|
|
Some((session, session_dat)) =>
|
|
if cmd.consent_type != ConsentType::Sex || !session_dat.less_explicit_mode {
|
|
ctx.trans.queue_for_session(&session, Some(&(msg + "\n"))).await?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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())?;
|
|
}
|
|
|
|
if is_allow {
|
|
let mut not_allowing_users = vec!();
|
|
for (name, mem) in ctx.trans.list_corp_members(&from_corp_id).await? {
|
|
if name != source_player.item_code && !mem.allow_combat {
|
|
not_allowing_users.push(name);
|
|
}
|
|
}
|
|
if !not_allowing_users.is_empty() {
|
|
user_error(format!(ansi!("War can only be declared when all corp mates allow combat for that corp. Ask them to run <bold>corp allow combat from {}<reset>, or have them fired! <bold>corp config {} allow combat required<reset> will stop anyone joining without it / turning it off. Corp mates with it off: {}"),
|
|
&from_corp.name,
|
|
&from_corp.name,
|
|
¬_allowing_users.iter().join(", ")
|
|
))?;
|
|
}
|
|
}
|
|
|
|
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(
|
|
&from_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!("<red>{} {} to fight{} against {} on behalf \
|
|
of {}<reset>, 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!("<yellow>Your wristpad buzzes with an corporate announcement to {}:<reset> {}\n"), &to_corp.name, &msg)
|
|
).await?
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_list_allows(ctx: &mut VerbContext<'_>, item: &Item) -> UResult<()> {
|
|
let consents = ctx.trans.list_consents(&item.item_code).await?;
|
|
let mut msg = String::new();
|
|
if consents.is_empty() {
|
|
msg.push_str(ansi!("You have no consents. Type <bold>help allow<reset> to learn how to consent."));
|
|
} else {
|
|
msg.push_str(ansi!("You are consenting to the following actions (type <bold>help allow<reset> to learn how to change):\n"));
|
|
msg.push_str(&format!(ansi!("<bgblue><white><bold>| {:20} | {:12} | {:40} |<reset>\n"),
|
|
"User", "Consent type", "Details"));
|
|
for (user, consent_type, details) in consents {
|
|
msg.push_str(&format!("| {:20} | {:12} | {:40} |\n",
|
|
user, consent_type.to_str(), details.to_str()));
|
|
}
|
|
}
|
|
ctx.trans.queue_for_session(&ctx.session, Some(&(msg + "\n"))).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?;
|
|
let is_allow = verb == "allow";
|
|
|
|
let remaining_trim = remaining.trim();
|
|
if remaining_trim == "" {
|
|
handle_list_allows(ctx, &player_item).await?;
|
|
} else {
|
|
let mut cmd = match parsing::parse_allow(remaining, !ctx.session_dat.less_explicit_mode) {
|
|
Err(msg) => user_error(msg)?,
|
|
Ok(cmd) => cmd
|
|
};
|
|
for place in cmd.consent_details.only_in.iter_mut() {
|
|
if place == &"here" {
|
|
let loc_code = match player_item.location.split_once("/") {
|
|
None => user_error("Can't use \"in here\" where you are now".to_owned())?,
|
|
Some((loc_type, _)) if loc_type != "room" =>
|
|
user_error("Can't use \"in here\" outside a public room".to_owned())?,
|
|
Some((_, loc_code)) => loc_code
|
|
};
|
|
*place = loc_code;
|
|
}
|
|
if room_map_by_code().get(*place).is_none() {
|
|
user_error(format!("Place {} not found", *place))?
|
|
}
|
|
}
|
|
match cmd.consent_target {
|
|
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?
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
static VERB_INT: Verb = Verb;
|
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|