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 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 {
|
||||
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 (corp_id, corp, mem) =
|
||||
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
|
||||
};
|
||||
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())?;
|
||||
}
|
||||
|
||||
|
||||
let target_user = search_item_for_user(ctx, &ItemSearchParams {
|
||||
include_loc_contents: true,
|
||||
..ItemSearchParams::base(&player, target_raw.trim())
|
||||
@ -54,6 +57,11 @@ async fn corp_invite(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()>
|
||||
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?;
|
||||
|
||||
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 {
|
||||
delete_corp = true;
|
||||
} 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)) {
|
||||
user_error("The last holder cannot resign from a non-empty \
|
||||
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 (corp_id, corp, mem) =
|
||||
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
|
||||
};
|
||||
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(())
|
||||
}
|
||||
|
||||
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;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
@ -259,6 +428,8 @@ impl UserVerb for Verb {
|
||||
"" | "list" => corp_list(ctx, remaining).await?,
|
||||
"leave" | "resign" => corp_leave(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())?
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1,13 +1,44 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Clone)]
|
||||
pub enum CorpPermission {
|
||||
Holder, // Implies all permissions.
|
||||
Hire,
|
||||
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)]
|
||||
pub enum CorpCommType {
|
||||
@ -24,6 +55,7 @@ pub struct CorpId(pub i64);
|
||||
#[serde(default)]
|
||||
pub struct Corp {
|
||||
pub name: String,
|
||||
pub founded: DateTime<Utc>,
|
||||
// If true, new members get allow_combat on, and members cannot turn
|
||||
// allow_combat off. This will allow duly authorised corp members to
|
||||
// consent to combat with other corps, and have it apply to members.
|
||||
@ -37,6 +69,7 @@ impl Default for Corp {
|
||||
name: "Unset".to_owned(),
|
||||
allow_combat_required: false,
|
||||
member_permissions: vec!(),
|
||||
founded: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user