Implement /join_group and /join_faction commands

This commit is contained in:
CapsizeGlimmer 2020-06-04 05:40:05 -04:00 committed by Forest Anderson
parent 289ef5d6b2
commit 5cbecb29e6
6 changed files with 207 additions and 43 deletions

View File

@ -43,6 +43,8 @@ pub enum ChatCommand {
Group,
Health,
Help,
JoinFaction,
JoinGroup,
Jump,
Kill,
KillNpcs,
@ -81,6 +83,8 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Group,
ChatCommand::Health,
ChatCommand::Help,
ChatCommand::JoinFaction,
ChatCommand::JoinGroup,
ChatCommand::Jump,
ChatCommand::Kill,
ChatCommand::KillNpcs,
@ -218,6 +222,16 @@ impl ChatCommand {
"Display information about commands",
NoAdmin,
),
ChatCommand::JoinFaction => ChatCommandData::new(
vec![Any("faction", Optional)],
"Join/leave the specified faction",
NoAdmin,
),
ChatCommand::JoinGroup => ChatCommandData::new(
vec![Any("group", Optional)],
"Join/leave the specified group",
NoAdmin,
),
ChatCommand::Jump => cmd(
vec![
Float("x", 0.0, Required),
@ -336,6 +350,8 @@ impl ChatCommand {
ChatCommand::Goto => "goto",
ChatCommand::Group => "group",
ChatCommand::Health => "health",
ChatCommand::JoinFaction => "join_faction",
ChatCommand::JoinGroup => "join_group",
ChatCommand::Help => "help",
ChatCommand::Jump => "jump",
ChatCommand::Kill => "kill",

View File

@ -4,7 +4,7 @@ use specs_idvs::IDVStorage;
use std::time::{Duration, Instant};
/// A player's current chat mode.
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub enum ChatMode {
/// Private message to another player (by uuid)
Tell(Uid),
@ -13,9 +13,9 @@ pub enum ChatMode {
/// Talk to players in your region of the world
Region,
/// Talk to your current group of players
Group,
Group(String),
/// Talk to your faction
Faction,
Faction(String),
/// Talk to every player on the server
World,
}
@ -31,18 +31,22 @@ impl ChatMode {
ChatMode::Tell(to) => ChatType::Tell(from, *to),
ChatMode::Say => ChatType::Say(from),
ChatMode::Region => ChatType::Region(from),
ChatMode::Group => ChatType::Group(from),
ChatMode::Faction => ChatType::Faction(from),
ChatMode::Group(name) => ChatType::Group(from, name.to_string()),
ChatMode::Faction(name) => ChatType::Faction(from, name.to_string()),
ChatMode::World => ChatType::World(from),
};
ChatMsg { chat_type, message }
}
}
impl Default for ChatMode {
fn default() -> Self { Self::World }
}
/// List of chat types. Note that this is a superset of `ChatMode`; this is
/// because `ChatType::Kill`, `ChatType::Broadcast`, and `ChatType::Private`
/// cannot be sent by players.
#[derive(Copy, Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChatType {
/// Tell all players something (such as players connecting or alias changes)
Broadcast,
@ -55,9 +59,9 @@ pub enum ChatType {
/// Chat with nearby players
Say(Uid),
/// Group chat
Group(Uid),
Group(Uid, String),
/// Factional chat
Faction(Uid),
Faction(Uid, String),
/// Regional chat
Region(Uid),
/// World chat
@ -81,23 +85,23 @@ impl ChatMsg {
}
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
let tuple = match self.chat_type {
let tuple = match &self.chat_type {
ChatType::Broadcast => None,
ChatType::Private => None,
ChatType::Kill => None,
ChatType::Tell(u, _) => Some((SpeechBubbleIcon::Tell, u, None)),
ChatType::Say(u) => Some((SpeechBubbleIcon::Say, u, None)),
ChatType::Group(u) => Some((SpeechBubbleIcon::Group, u, None)),
ChatType::Faction(u) => Some((SpeechBubbleIcon::Faction, u, None)),
ChatType::Group(u, _s) => Some((SpeechBubbleIcon::Group, u, None)),
ChatType::Faction(u, _s) => Some((SpeechBubbleIcon::Faction, u, None)),
ChatType::Region(u) => Some((SpeechBubbleIcon::Region, u, None)),
ChatType::World(u) => Some((SpeechBubbleIcon::World, u, None)),
ChatType::Npc(u, r) => Some((SpeechBubbleIcon::None, u, Some(r))),
};
tuple.map(|(icon, from, npc_rand)| {
if let Some(r) = npc_rand {
(SpeechBubble::npc_new(self.message.clone(), r, icon), from)
(SpeechBubble::npc_new(self.message.clone(), *r, icon), *from)
} else {
(SpeechBubble::player_new(self.message.clone(), icon), from)
(SpeechBubble::player_new(self.message.clone(), icon), *from)
}
})
}
@ -107,19 +111,27 @@ impl ChatMsg {
/// gameplay.
///
/// Groups are currently just an associated String (the group's name)
pub struct Group(String);
#[derive(Clone, Debug)]
pub struct Group(pub String);
impl Component for Group {
type Storage = IDVStorage<Self>;
}
impl From<String> for Group {
fn from(s: String) -> Self { Group(s) }
}
/// Player factions are used to coordinate pvp vs hostile factions or segment
/// chat from the world
///
/// Factions are currently just an associated String (the faction's name)
pub struct Faction(String);
#[derive(Clone, Debug)]
pub struct Faction(pub String);
impl Component for Faction {
type Storage = IDVStorage<Self>;
}
impl From<String> for Faction {
fn from(s: String) -> Self { Faction(s) }
}
/// The contents of a speech bubble
pub enum SpeechBubbleMessage {

View File

@ -155,6 +155,8 @@ impl State {
ecs.register::<comp::Attacking>();
ecs.register::<comp::ItemDrop>();
ecs.register::<comp::ChatMode>();
ecs.register::<comp::Group>();
ecs.register::<comp::Faction>();
// Register synced resources used by the ECS.
ecs.insert(TimeOfDay(0.0));

View File

@ -8,6 +8,7 @@ use common::{
assets,
cmd::{ChatCommand, CHAT_COMMANDS},
comp,
comp::Item,
event::{EventBus, ServerEvent},
msg::{Notification, PlayerListUpdate, ServerMsg},
npc::{self, get_npc_name},
@ -75,6 +76,8 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Group => handle_group,
ChatCommand::Health => handle_health,
ChatCommand::Help => handle_help,
ChatCommand::JoinFaction => handle_join_faction,
ChatCommand::JoinGroup => handle_join_group,
ChatCommand::Jump => handle_jump,
ChatCommand::Kill => handle_kill,
ChatCommand::KillNpcs => handle_kill_npcs,
@ -1020,13 +1023,20 @@ fn handle_tell(
.get(player)
.expect("Player must have uid");
let mode = comp::ChatMode::Tell(player_uid);
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, mode.clone());
let msg = if msg.is_empty() {
format!("{} wants to talk to you.", alias)
} else {
msg.to_string()
};
server.notify_client(player, ServerMsg::chat(mode, client_uid, msg.clone()));
server.notify_client(
player,
ServerMsg::chat(mode.clone(), client_uid, msg.clone()),
);
server.notify_client(client, ServerMsg::chat(mode, client_uid, msg));
} else {
server.notify_client(
@ -1057,14 +1067,24 @@ fn handle_faction(
);
return;
}
let mode = comp::ChatMode::Faction;
let _ = server.state.ecs().write_storage().insert(client, mode);
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
.state
.notify_registered_clients(ServerMsg::chat(mode, *uid, msg.to_string()));
let ecs = server.state.ecs();
if let Some(comp::Faction(faction)) = ecs.read_storage().get(client) {
let mode = comp::ChatMode::Faction(faction.to_string());
let _ = ecs.write_storage().insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = ecs.read_storage().get(client) {
server.state.notify_registered_clients(ServerMsg::chat(
mode,
*uid,
msg.to_string(),
));
}
}
} else {
server.notify_client(
client,
ServerMsg::private(String::from("Please join a faction with /join_faction")),
);
}
}
@ -1083,14 +1103,24 @@ fn handle_group(
);
return;
}
let mode = comp::ChatMode::Group;
let _ = server.state.ecs().write_storage().insert(client, mode);
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
.state
.notify_registered_clients(ServerMsg::chat(mode, *uid, msg.to_string()));
let ecs = server.state.ecs();
if let Some(comp::Group(group)) = ecs.read_storage().get(client) {
let mode = comp::ChatMode::Group(group.to_string());
let _ = ecs.write_storage().insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = ecs.read_storage().get(client) {
server.state.notify_registered_clients(ServerMsg::chat(
mode,
*uid,
msg.to_string(),
));
}
}
} else {
server.notify_client(
client,
ServerMsg::private(String::from("Please join a group with /join_group")),
);
}
}
@ -1110,7 +1140,11 @@ fn handle_region(
return;
}
let mode = comp::ChatMode::Region;
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
@ -1136,7 +1170,11 @@ fn handle_say(
return;
}
let mode = comp::ChatMode::Say;
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
@ -1162,7 +1200,11 @@ fn handle_world(
return;
}
let mode = comp::ChatMode::World;
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
@ -1172,6 +1214,88 @@ fn handle_world(
}
}
fn handle_join_faction(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if client != target {
// This happens when [ab]using /sudo
server.notify_client(
client,
ServerMsg::private(String::from("It's rude to impersonate people")),
);
return;
}
if let Ok(faction) = scan_fmt!(&args, &action.arg_fmt(), String) {
let mode = comp::ChatMode::Faction(faction.clone());
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, comp::Faction(faction.clone()));
// TODO notify faction
server.notify_client(
client,
ServerMsg::private(format!("Joined faction {{{}}}", faction)),
);
} else {
let mode = comp::ChatMode::default();
let _ = server.state.ecs().write_storage().insert(client, mode);
if let Some(comp::Faction(faction)) = server.state.ecs().write_storage().remove(client) {
// TODO notify faction
server.notify_client(
client,
ServerMsg::private(format!("Left faction {{{}}}", faction)),
);
}
}
}
fn handle_join_group(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if client != target {
// This happens when [ab]using /sudo
server.notify_client(
client,
ServerMsg::private(String::from("It's rude to impersonate people")),
);
return;
}
if let Ok(group) = scan_fmt!(&args, &action.arg_fmt(), String) {
let mode = comp::ChatMode::Group(group.clone());
let _ = server.state.ecs().write_storage().insert(client, mode);
let _ = server
.state
.ecs()
.write_storage()
.insert(client, comp::Group(group.clone()));
// TODO notify group
server.notify_client(
client,
ServerMsg::private(format!("Joined group {{{}}}", group)),
);
} else {
let mode = comp::ChatMode::default();
let _ = server.state.ecs().write_storage().insert(client, mode);
if let Some(comp::Group(group)) = server.state.ecs().write_storage().remove(client) {
// TODO notify group
server.notify_client(
client,
ServerMsg::private(format!("Left group {{{}}}", group)),
);
}
}
}
#[cfg(not(feature = "worldgen"))]
fn handle_debug_column(
server: &mut Server,
@ -1375,7 +1499,6 @@ fn handle_set_level(
}
}
use common::comp::Item;
fn handle_debug(
server: &mut Server,
client: EcsEntity,

View File

@ -328,8 +328,11 @@ impl<'a> System<'a> for Sys {
| ClientState::Character => match validate_chat_msg(&message) {
Ok(()) => {
if let Some(from) = uids.get(entity) {
let mode = chat_modes.get(entity).unwrap_or(&ChatMode::World);
let msg = ServerMsg::chat(*mode, *from, message);
let mode = chat_modes
.get(entity)
.map(Clone::clone)
.unwrap_or(ChatMode::default());
let msg = ServerMsg::chat(mode, *from, message);
new_chat_msgs.push((Some(entity), msg));
} else {
tracing::error!("Could not send message. Missing player uid");

View File

@ -321,7 +321,13 @@ impl<'a> Widget for Chat<'a> {
}
})
};
let message_format = |uid, message| format!("[{}]: {}", alias_of_uid(uid), message);
let message_format = |uid, message, group| {
if let Some(group) = group {
format!("{{{}}} [{}]: {}", group, alias_of_uid(uid), message)
} else {
format!("[{}]: {}", alias_of_uid(uid), message)
}
};
// Message box
Rectangle::fill([470.0, 174.0])
.rgba(0.0, 0.0, 0.0, transp)
@ -362,11 +368,13 @@ impl<'a> Widget for Chat<'a> {
(TELL_COLOR, format!("From [{}]: {}", from_alias, message))
}
},
ChatType::Say(uid) => (SAY_COLOR, message_format(uid, message)),
ChatType::Group(uid) => (GROUP_COLOR, message_format(uid, message)),
ChatType::Faction(uid) => (FACTION_COLOR, message_format(uid, message)),
ChatType::Region(uid) => (REGION_COLOR, message_format(uid, message)),
ChatType::World(uid) => (WORLD_COLOR, message_format(uid, message)),
ChatType::Say(uid) => (SAY_COLOR, message_format(uid, message, None)),
ChatType::Group(uid, s) => (GROUP_COLOR, message_format(uid, message, Some(s))),
ChatType::Faction(uid, s) => {
(FACTION_COLOR, message_format(uid, message, Some(s)))
},
ChatType::Region(uid) => (REGION_COLOR, message_format(uid, message, None)),
ChatType::World(uid) => (WORLD_COLOR, message_format(uid, message, None)),
ChatType::Npc(_uid, _r) => continue, // Should be filtered by hud/mod.rs
};
let text = Text::new(&msg)