Add corp promote and corp info commands.
This commit is contained in:
parent
3bd0412e4a
commit
cd0f9661d1
@ -16,7 +16,10 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ansi::ansi;
|
use ansi::{ansi, ignore_special_characters};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use humantime;
|
||||||
|
|
||||||
fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool {
|
fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool {
|
||||||
mem.permissions.iter().any(|p| *p == CorpPermission::Holder || *p == *perm)
|
mem.permissions.iter().any(|p| *p == CorpPermission::Holder || *p == *perm)
|
||||||
@ -33,13 +36,13 @@ async fn corp_invite(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()>
|
|||||||
let player = get_player_item_or_fail(ctx).await?;
|
let player = get_player_item_or_fail(ctx).await?;
|
||||||
let (corp_id, corp, mem) =
|
let (corp_id, corp, mem) =
|
||||||
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &user.username).await? {
|
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &user.username).await? {
|
||||||
None => user_error("No such corp!".to_owned())?,
|
None => user_error("You don't seem to belong to a matching corp!".to_owned())?,
|
||||||
Some(c) => c
|
Some(c) => c
|
||||||
};
|
};
|
||||||
if !check_corp_perm(&CorpPermission::Hire, &mem) || mem.joined_at.is_none() {
|
if !check_corp_perm(&CorpPermission::Hire, &mem) || mem.joined_at.is_none() {
|
||||||
user_error("You don't have hiring permissions for that corp".to_owned())?;
|
user_error("You don't have hiring permissions for that corp".to_owned())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_user = search_item_for_user(ctx, &ItemSearchParams {
|
let target_user = search_item_for_user(ctx, &ItemSearchParams {
|
||||||
include_loc_contents: true,
|
include_loc_contents: true,
|
||||||
..ItemSearchParams::base(&player, target_raw.trim())
|
..ItemSearchParams::base(&player, target_raw.trim())
|
||||||
@ -54,6 +57,11 @@ async fn corp_invite(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()>
|
|||||||
Some(c) => c
|
Some(c) => c
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ctx.trans.list_corp_members(&corp_id).await?.len() > 40 {
|
||||||
|
user_error("Your corp seems a bit too big to manage already \
|
||||||
|
- fire someone first!".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.trans.expire_old_invites().await?;
|
ctx.trans.expire_old_invites().await?;
|
||||||
|
|
||||||
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &target_user.item_code).await? {
|
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &target_user.item_code).await? {
|
||||||
@ -173,7 +181,7 @@ async fn corp_leave(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
|||||||
if members.len() == 1 {
|
if members.len() == 1 {
|
||||||
delete_corp = true;
|
delete_corp = true;
|
||||||
} else if !members.iter().any(
|
} else if !members.iter().any(
|
||||||
|(name, mem)| *name != username_l &&
|
|(name, mem)| *name != username_l && mem.joined_at.is_some() &&
|
||||||
mem.permissions.contains(&CorpPermission::Holder)) {
|
mem.permissions.contains(&CorpPermission::Holder)) {
|
||||||
user_error("The last holder cannot resign from a non-empty \
|
user_error("The last holder cannot resign from a non-empty \
|
||||||
corp - fire everyone else first, or promote a \
|
corp - fire everyone else first, or promote a \
|
||||||
@ -198,7 +206,7 @@ async fn corp_fire(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
|||||||
let player = get_player_item_or_fail(ctx).await?;
|
let player = get_player_item_or_fail(ctx).await?;
|
||||||
let (corp_id, corp, mem) =
|
let (corp_id, corp, mem) =
|
||||||
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &user.username).await? {
|
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &user.username).await? {
|
||||||
None => user_error("No such corp!".to_owned())?,
|
None => user_error("You don't seem to belong to a matching corp!".to_owned())?,
|
||||||
Some(c) => c
|
Some(c) => c
|
||||||
};
|
};
|
||||||
if !check_corp_perm(&CorpPermission::Fire, &mem) || mem.joined_at.is_none() {
|
if !check_corp_perm(&CorpPermission::Fire, &mem) || mem.joined_at.is_none() {
|
||||||
@ -231,7 +239,7 @@ async fn corp_fire(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -248,6 +256,167 @@ async fn corp_fire(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn corp_promote(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
||||||
|
let usage_error = || user_error(
|
||||||
|
ansi!("Usage: <bold>corp promote<reset> username <bold>in<reset> corpname <bold>to<reset> title <bold>privileges<reset> permissions\n\
|
||||||
|
Title can be any plain text up to 20 characters long.\n\
|
||||||
|
Permissions start with + or - (to add or take away) followed immediately by a permission name (e.g. holder, hire, fire, war)").to_owned()
|
||||||
|
);
|
||||||
|
let (remaining, permissions_raw) = match remaining.rsplit_once(" privileges ") {
|
||||||
|
None => usage_error()?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
let (target_raw, remaining) = match remaining.split_once(" in ") {
|
||||||
|
None => usage_error()?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
let (corpname_raw, title_raw) = match remaining.split_once(" to ") {
|
||||||
|
None => usage_error()?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
let title = ignore_special_characters(title_raw.trim());
|
||||||
|
if title.len() > 20 {
|
||||||
|
user_error("New title must be 20 characters or less".to_owned())?;
|
||||||
|
}
|
||||||
|
let psplit = permissions_raw.split(" ");
|
||||||
|
let mut perm_add: BTreeSet<CorpPermission> = BTreeSet::new();
|
||||||
|
let mut perm_rem: BTreeSet<CorpPermission> = BTreeSet::new();
|
||||||
|
for perm in psplit {
|
||||||
|
let add =
|
||||||
|
if perm.starts_with("+") {
|
||||||
|
true
|
||||||
|
} else if perm.starts_with("-") {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
user_error(format!("Expected {} to start with + or -",
|
||||||
|
ignore_special_characters(perm)))?
|
||||||
|
};
|
||||||
|
let perm = ignore_special_characters(&perm[1..]).to_lowercase();
|
||||||
|
let perm = match CorpPermission::parse(&perm) {
|
||||||
|
None => user_error(format!("Unknown permission {}", perm))?,
|
||||||
|
Some(v) => v
|
||||||
|
};
|
||||||
|
(if add { &mut perm_add } else { &mut perm_rem }).insert(perm);
|
||||||
|
}
|
||||||
|
match perm_add.intersection(&perm_rem).next() {
|
||||||
|
Some(perm) => user_error(format!("You tried to both add and remove privilege {} - make up your mind!",
|
||||||
|
perm.display()))?,
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = get_user_or_fail(ctx)?;
|
||||||
|
let player = get_player_item_or_fail(ctx).await?;
|
||||||
|
let (corp_id, corp, mem) =
|
||||||
|
match ctx.trans.match_user_corp_by_name(corpname_raw.trim(), &user.username).await? {
|
||||||
|
None => user_error("You don't seem to belong to a matching corp!".to_owned())?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
if !check_corp_perm(&CorpPermission::Promote, &mem) || mem.joined_at.is_none() {
|
||||||
|
user_error("You don't have promote permissions for that corp".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_user = search_item_for_user(ctx, &ItemSearchParams {
|
||||||
|
include_all_players: true,
|
||||||
|
..ItemSearchParams::base(&player, target_raw.trim())
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
if target_user.item_type != "player" {
|
||||||
|
user_error("Only players can be promoted.".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut their_mem = match ctx.trans.match_user_corp_by_name(corpname_raw.trim(), &target_user.item_code).await? {
|
||||||
|
None => user_error(format!(
|
||||||
|
"{} isn't currently hired.",
|
||||||
|
&caps_first(&target_user.pronouns.subject)
|
||||||
|
))?,
|
||||||
|
Some((_, _, v)) => v
|
||||||
|
};
|
||||||
|
match their_mem {
|
||||||
|
CorpMembership { permissions: ref their_perm,
|
||||||
|
joined_at: Some(their_join), ..} => {
|
||||||
|
if their_perm.contains(&CorpPermission::Holder) {
|
||||||
|
if !mem.permissions.contains(&CorpPermission::Holder) {
|
||||||
|
user_error("I love the ambition, but only holders can promote/demote holders!".to_owned())?;
|
||||||
|
}
|
||||||
|
if their_join < mem.joined_at.unwrap_or(Utc::now()) {
|
||||||
|
user_error("Whoah there young whippersnapper, holders can't promote/demote more senior holders!".to_owned())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if target_user.item_code == player.item_code {
|
||||||
|
if !mem.permissions.contains(&CorpPermission::Holder) {
|
||||||
|
user_error("Only holders can promote / demote themselves".to_owned())?
|
||||||
|
} else if perm_rem.contains(&CorpPermission::Holder) {
|
||||||
|
let members = ctx.trans.list_corp_members(&corp_id).await?;
|
||||||
|
if !members.iter().any(
|
||||||
|
|(name, mem)| *name != player.item_code && mem.joined_at.is_some() &&
|
||||||
|
mem.permissions.contains(&CorpPermission::Holder)) {
|
||||||
|
user_error("The last holder cannot demote themselves - \
|
||||||
|
promote a successor to holder first".to_owned())?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !mem.permissions.contains(&CorpPermission::Holder) &&
|
||||||
|
!(&perm_add | &perm_rem).is_subset(&mem.permissions.clone().into_iter().collect()) {
|
||||||
|
user_error("You can only change permissions you have yourself.".to_owned())?
|
||||||
|
}
|
||||||
|
|
||||||
|
let perm_str_raw =
|
||||||
|
perm_add.iter()
|
||||||
|
.map(|v| "+".to_owned() + v.display()).join(" ") +
|
||||||
|
" " +
|
||||||
|
&perm_rem.iter().map(|v| "-".to_owned() + v.display())
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
ctx.trans.broadcast_to_corp(
|
||||||
|
&corp_id,
|
||||||
|
&CorpCommType::Notice, None,
|
||||||
|
&format!("Everyone looks up from their desk as {} changes {}'s job title in {} to {} ({}).\n",
|
||||||
|
user.username,
|
||||||
|
target_user.display_for_sentence(false, 1, false),
|
||||||
|
corp.name, &title, &perm_str_raw.trim())).await?;
|
||||||
|
|
||||||
|
their_mem.job_title = title;
|
||||||
|
their_mem.permissions = (&(&their_mem.permissions.clone().into_iter().collect::<BTreeSet<CorpPermission>>() |
|
||||||
|
&perm_add) - &perm_rem).into_iter().collect();
|
||||||
|
ctx.trans.upsert_corp_membership(&corp_id, &target_user.item_code, &their_mem).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn corp_info(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
||||||
|
let user = get_user_or_fail(ctx)?;
|
||||||
|
let (corp_id, corp, _) =
|
||||||
|
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
|
||||||
|
};
|
||||||
|
let mut msg = String::new();
|
||||||
|
let founded_ago =
|
||||||
|
humantime::format_duration(
|
||||||
|
std::time::Duration::from_secs(
|
||||||
|
(Utc::now() - corp.founded).num_seconds() as u64));
|
||||||
|
msg.push_str(&format!(ansi!("<bold>{}'s essential information<reset>\nFounded: {} ago\n"),
|
||||||
|
&corp.name, &founded_ago));
|
||||||
|
msg.push_str("Members:\n");
|
||||||
|
msg.push_str(&format!(ansi!("<bgblue><white><bold>| {:20} | {:20} | {:20} |<reset>\n"), "Name", "Title", "Permissions"
|
||||||
|
));
|
||||||
|
for (user, mem) in ctx.trans.list_corp_members(&corp_id).await? {
|
||||||
|
msg.push_str(
|
||||||
|
&format!(ansi!("| {:20} | {:20} | {:20} |\n"),
|
||||||
|
caps_first(&user),
|
||||||
|
mem.job_title,
|
||||||
|
mem.permissions.iter().map(|p| p.display())
|
||||||
|
.join(" ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.trans.queue_for_session(&ctx.session, Some(&msg)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl UserVerb for Verb {
|
impl UserVerb for Verb {
|
||||||
@ -259,6 +428,8 @@ impl UserVerb for Verb {
|
|||||||
"" | "list" => corp_list(ctx, remaining).await?,
|
"" | "list" => corp_list(ctx, remaining).await?,
|
||||||
"leave" | "resign" => corp_leave(ctx, remaining).await?,
|
"leave" | "resign" => corp_leave(ctx, remaining).await?,
|
||||||
"fire" | "dismiss" => corp_fire(ctx, remaining).await?,
|
"fire" | "dismiss" => corp_fire(ctx, remaining).await?,
|
||||||
|
"promote" | "demote" => corp_promote(ctx, remaining).await?,
|
||||||
|
"info" => corp_info(ctx, remaining).await?,
|
||||||
_ => user_error("Unknown command".to_owned())?
|
_ => user_error("Unknown command".to_owned())?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,13 +1,44 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)]
|
||||||
pub enum CorpPermission {
|
pub enum CorpPermission {
|
||||||
Holder, // Implies all permissions.
|
Holder, // Implies all permissions.
|
||||||
Hire,
|
Hire,
|
||||||
Fire,
|
Fire,
|
||||||
ChangeJobTitle,
|
Promote,
|
||||||
|
War,
|
||||||
|
Configure,
|
||||||
|
Finance,
|
||||||
}
|
}
|
||||||
|
impl CorpPermission {
|
||||||
|
pub fn parse(s: &str) -> Option<CorpPermission> {
|
||||||
|
use CorpPermission::*;
|
||||||
|
match s {
|
||||||
|
"holder" => Some(Holder),
|
||||||
|
"hire" => Some(Hire),
|
||||||
|
"fire" => Some(Fire),
|
||||||
|
"promote" => Some(Promote),
|
||||||
|
"war" => Some(War),
|
||||||
|
"config" | "configure" => Some(Configure),
|
||||||
|
"finance" | "finances" => Some(Finance),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn display(&self) -> &'static str {
|
||||||
|
use CorpPermission::*;
|
||||||
|
match self {
|
||||||
|
Holder => "holder",
|
||||||
|
Hire => "hire",
|
||||||
|
Fire => "fire",
|
||||||
|
Promote => "promote",
|
||||||
|
War => "war",
|
||||||
|
Configure => "configure",
|
||||||
|
Finance => "finance",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq)]
|
#[derive(Serialize, Deserialize, PartialEq)]
|
||||||
pub enum CorpCommType {
|
pub enum CorpCommType {
|
||||||
@ -24,6 +55,7 @@ pub struct CorpId(pub i64);
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Corp {
|
pub struct Corp {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub founded: DateTime<Utc>,
|
||||||
// If true, new members get allow_combat on, and members cannot turn
|
// If true, new members get allow_combat on, and members cannot turn
|
||||||
// allow_combat off. This will allow duly authorised corp members to
|
// allow_combat off. This will allow duly authorised corp members to
|
||||||
// consent to combat with other corps, and have it apply to members.
|
// consent to combat with other corps, and have it apply to members.
|
||||||
@ -37,6 +69,7 @@ impl Default for Corp {
|
|||||||
name: "Unset".to_owned(),
|
name: "Unset".to_owned(),
|
||||||
allow_combat_required: false,
|
allow_combat_required: false,
|
||||||
member_permissions: vec!(),
|
member_permissions: vec!(),
|
||||||
|
founded: Utc::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user