Add corp promote and corp info commands.

This commit is contained in:
Condorra 2023-03-26 00:51:21 +11:00
parent 3bd0412e4a
commit cd0f9661d1
2 changed files with 212 additions and 8 deletions

View File

@ -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(())

View File

@ -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(),
}
}
}