Add ability to invite people to corps.
This commit is contained in:
parent
8084b020c3
commit
cb05843ee9
@ -814,6 +814,31 @@ impl DBTrans {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn match_user_corp_by_name(&self, corpname: &str, username: &str) ->
|
||||
DResult<Option<(CorpId, Corp, CorpMembership)>> {
|
||||
Ok(match self.pg_trans()?
|
||||
.query_opt("SELECT c.corp_id, c.details AS cdetails, m.details AS mdetails FROM corps c \
|
||||
JOIN corp_membership m ON m.corp_id = c.corp_id \
|
||||
WHERE LOWER(c.details->>'name') LIKE $1 AND \
|
||||
m.member_username = $2 \
|
||||
ORDER BY \
|
||||
ABS(length(c.details->>'name')-length($1)) DESC \
|
||||
LIMIT 1",
|
||||
&[&(corpname.replace("\\", "\\\\")
|
||||
.replace("_", "\\_")
|
||||
.replace("%", "")
|
||||
.to_lowercase() + "%"), &(username.to_lowercase())])
|
||||
.await? {
|
||||
None => None,
|
||||
Some(row) =>
|
||||
Some(
|
||||
(CorpId(row.get("corp_id")),
|
||||
serde_json::from_value(row.get("cdetails"))?,
|
||||
serde_json::from_value(row.get("mdetails"))?)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_corp(&self, details: &Corp) -> DResult<CorpId> {
|
||||
let id = self.pg_trans()?
|
||||
.query_one("INSERT INTO corps (details) VALUES ($1) RETURNING corp_id", &[&serde_json::to_value(details)?]).await?
|
||||
@ -821,6 +846,15 @@ impl DBTrans {
|
||||
Ok(CorpId(id))
|
||||
}
|
||||
|
||||
pub async fn expire_old_invites(&self) -> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute(
|
||||
"DELETE FROM corp_membership WHERE details->>'invited_at' <= $1",
|
||||
&[&(Utc::now() - chrono::Duration::hours(4)).to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)]
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_corp_membership(&self, corp: &CorpId, username: &str, details: &CorpMembership) -> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute("INSERT INTO corp_membership (corp_id, member_username, details) \
|
||||
@ -837,7 +871,10 @@ impl DBTrans {
|
||||
Ok(self.pg_trans()?
|
||||
.query("SELECT m.corp_id, c.details->>'name', m.details FROM corp_membership m \
|
||||
JOIN corps c ON c.corp_id = m.corp_id WHERE m.member_username = $1 \
|
||||
AND m.details->>'joined_at' IS NOT NULL", &[&username.to_lowercase()])
|
||||
AND m.details->>'joined_at' IS NOT NULL \
|
||||
ORDER BY (m.details->>'priority')::int DESC NULLS LAST, \
|
||||
(m.details->>'joined_at') :: TIMESTAMPTZ ASC",
|
||||
&[&username.to_lowercase()])
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|row|
|
||||
|
@ -15,6 +15,7 @@ mod agree;
|
||||
mod allow;
|
||||
pub mod attack;
|
||||
mod buy;
|
||||
mod corp;
|
||||
pub mod drop;
|
||||
pub mod get;
|
||||
mod describe;
|
||||
@ -118,6 +119,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
|
||||
"attack" => attack::VERB,
|
||||
"buy" => buy::VERB,
|
||||
"corp" => corp::VERB,
|
||||
"drop" => drop::VERB,
|
||||
"get" => get::VERB,
|
||||
"inventory" => inventory::VERB,
|
||||
|
142
blastmud_game/src/message_handler/user_commands/corp.rs
Normal file
142
blastmud_game/src/message_handler/user_commands/corp.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use super::{
|
||||
VerbContext,
|
||||
UserVerb,
|
||||
UserVerbRef,
|
||||
UResult,
|
||||
user_error,
|
||||
get_user_or_fail,
|
||||
get_player_item_or_fail,
|
||||
search_item_for_user,
|
||||
parsing::parse_command_name,
|
||||
};
|
||||
use crate::{
|
||||
models::corp::{CorpMembership, CorpPermission},
|
||||
db::ItemSearchParams,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use async_trait::async_trait;
|
||||
use ansi::ansi;
|
||||
|
||||
fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool {
|
||||
mem.permissions.iter().any(|p| *p == CorpPermission::Holder || *p == *perm)
|
||||
}
|
||||
|
||||
async fn corp_invite(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
||||
let (target_raw, into_raw) = match remaining.rsplit_once(" into ") {
|
||||
None => user_error(
|
||||
ansi!("Usage: <bold>corp hire<reset> username <bold>into<reset> corpname").to_owned()
|
||||
)?,
|
||||
Some(c) => c
|
||||
};
|
||||
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(into_raw.trim(), &user.username).await? {
|
||||
None => user_error("No such corp!".to_owned())?,
|
||||
Some(c) => c
|
||||
};
|
||||
if !check_corp_perm(&CorpPermission::Hire, &mem) || mem.joined_at.is_none() {
|
||||
user_error("You don't have hire 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())
|
||||
}).await?;
|
||||
|
||||
if target_user.item_type != "player" {
|
||||
user_error("Only players can be hired.".to_owned())?;
|
||||
}
|
||||
|
||||
let (their_sess, their_sess_dat) = match ctx.trans.find_session_for_player(&target_user.item_code).await? {
|
||||
None => user_error("The user needs to be logged in while you hire them.".to_owned())?,
|
||||
Some(c) => c
|
||||
};
|
||||
|
||||
ctx.trans.expire_old_invites().await?;
|
||||
|
||||
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &target_user.item_code).await? {
|
||||
None => (),
|
||||
Some((_, _, CorpMembership { invited_at: Some(_), .. })) =>
|
||||
user_error("They've already been invited.".to_owned())?,
|
||||
Some((_, _, _)) =>
|
||||
user_error("They're already hired.".to_owned())?,
|
||||
};
|
||||
|
||||
let new_mem = CorpMembership {
|
||||
invited_at: Some(Utc::now()),
|
||||
allow_combat: corp.allow_combat_required,
|
||||
..Default::default()
|
||||
};
|
||||
ctx.trans.upsert_corp_membership(&corp_id, &target_user.item_code, &new_mem).await?;
|
||||
|
||||
ctx.trans.queue_for_session(&their_sess, Some(&format!(
|
||||
ansi!("{} wants to hire you into {}! Type <bold>corp join {}<reset> to accept.{}\n"),
|
||||
&player.display_for_sentence(!their_sess_dat.less_explicit_mode, 1, true),
|
||||
&corp.name,
|
||||
&corp.name,
|
||||
if new_mem.allow_combat {
|
||||
" This corp is configured to allow the leadership to declare war on other corps; if you join, you may be attacked by members of other corps, even without your personal consent."
|
||||
} else {
|
||||
""
|
||||
}
|
||||
))).await?;
|
||||
ctx.trans.queue_for_session(
|
||||
&ctx.session,
|
||||
Some(&format!(
|
||||
"You offer to hire {} into {}.\n",
|
||||
&target_user.display_for_sentence(!their_sess_dat.less_explicit_mode, 1, false),
|
||||
&corp.name
|
||||
))).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn corp_join(_ctx: &mut VerbContext<'_>, _remaining: &str) -> UResult<()> {
|
||||
user_error("Join not implemented yet".to_owned())?
|
||||
}
|
||||
|
||||
async fn corp_list(ctx: &mut VerbContext<'_>, _remaining: &str) -> UResult<()> {
|
||||
let mut msg = String::new();
|
||||
let user = get_user_or_fail(ctx)?;
|
||||
|
||||
let corps = ctx.trans.get_corp_memberships_for_user(&user.username).await?;
|
||||
if corps.is_empty() {
|
||||
msg.push_str(ansi!(
|
||||
"You don't yet belong to any corps - try <bold>who<reset> to see the \
|
||||
corps of people online, and message someone in a corp to see if they \
|
||||
will hire you! Or buy a new corp licence at the Kings Office in Melbs.\n"
|
||||
));
|
||||
} else {
|
||||
msg.push_str("You belong to the following corps:\n");
|
||||
msg.push_str(&format!(ansi!("<bgblue><white><bold>| {:20} | {:7} | {:5} |<reset>\n"),
|
||||
"Name", "Combat?", "Order"));
|
||||
for corp in &corps {
|
||||
match corp {
|
||||
(_, name, CorpMembership { allow_combat: combat, priority: p, .. }) => {
|
||||
msg.push_str(&format!("| {:20} | {:7} | {:5} |\n",
|
||||
name, if *combat { "Y" } else { "N" }, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.trans.queue_for_session(ctx.session, Some(&msg)).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 (command, remaining) = parse_command_name(remaining);
|
||||
match command.to_lowercase().as_str() {
|
||||
"hire" | "invite" => corp_invite(ctx, remaining).await?,
|
||||
"join" => corp_join(ctx, remaining).await?,
|
||||
"" | "list" => corp_list(ctx, remaining).await?,
|
||||
_ => user_error("Unknown command".to_owned())?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1,7 +1,7 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
pub enum CorpPermission {
|
||||
Holder, // Implies all permissions.
|
||||
Hire,
|
||||
@ -12,6 +12,7 @@ pub enum CorpPermission {
|
||||
pub struct CorpId(pub i64);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Corp {
|
||||
pub name: String,
|
||||
// If true, new members get allow_combat on, and members cannot turn
|
||||
@ -32,6 +33,7 @@ impl Default for Corp {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct CorpMembership {
|
||||
pub invited_at: Option<DateTime<Utc>>,
|
||||
pub joined_at: Option<DateTime<Utc>>,
|
||||
|
@ -66,6 +66,7 @@ CREATE TABLE corp_membership (
|
||||
PRIMARY KEY (corp_id, member_username)
|
||||
);
|
||||
CREATE INDEX corp_membership_by_username ON corp_membership(member_username);
|
||||
CREATE INDEX corp_membership_by_invited ON corp_membership((details->>'invited_at'));
|
||||
|
||||
CREATE TABLE user_consent (
|
||||
consenting_user TEXT NOT NULL REFERENCES users(username),
|
||||
|
Loading…
Reference in New Issue
Block a user