mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
entity targets
This commit is contained in:
parent
456c0ad3e8
commit
1071fd0bca
@ -263,6 +263,39 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
pub enum EntityTarget {
|
||||
Player(String),
|
||||
RtsimNpc(u64),
|
||||
Uid(crate::uid::Uid),
|
||||
}
|
||||
|
||||
impl FromStr for EntityTarget {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// NOTE: `@` is an invalid character in usernames, so we can use it here.
|
||||
if let Some((spec, data)) = s.split_once('@') {
|
||||
match spec {
|
||||
"rtsim" => Ok(EntityTarget::RtsimNpc(u64::from_str(data).map_err(
|
||||
|_| format!("Expected a valid number after 'rtsim@' but found {data}."),
|
||||
)?)),
|
||||
"uid" => Ok(EntityTarget::Uid(
|
||||
u64::from_str(data)
|
||||
.map_err(|_| {
|
||||
format!("Expected a valid number after 'uid@' but found {data}.")
|
||||
})?
|
||||
.into(),
|
||||
)),
|
||||
_ => Err(format!(
|
||||
"Expected either 'rtsim' or 'uid' before '@' but found '{spec}'"
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(EntityTarget::Player(s.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Please keep this sorted alphabetically :-)
|
||||
#[derive(Copy, Clone, strum::EnumIter)]
|
||||
pub enum ServerChatCommand {
|
||||
@ -739,8 +772,8 @@ impl ServerChatCommand {
|
||||
Some(Admin),
|
||||
),
|
||||
ServerChatCommand::Sudo => cmd(
|
||||
vec![PlayerName(Required), SubCommand],
|
||||
"Run command as if you were another player",
|
||||
vec![EntityTarget(Required), SubCommand],
|
||||
"Run command as if you were another entity",
|
||||
Some(Moderator),
|
||||
),
|
||||
ServerChatCommand::Tell => cmd(
|
||||
@ -760,10 +793,10 @@ impl ServerChatCommand {
|
||||
),
|
||||
ServerChatCommand::Tp => cmd(
|
||||
vec![
|
||||
PlayerName(Optional),
|
||||
EntityTarget(Optional),
|
||||
Boolean("Dismount from ship", "true".to_string(), Optional),
|
||||
],
|
||||
"Teleport to another player",
|
||||
"Teleport to another entity",
|
||||
Some(Moderator),
|
||||
),
|
||||
ServerChatCommand::RtsimTp => cmd(
|
||||
@ -1001,6 +1034,7 @@ impl ServerChatCommand {
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
ArgumentSpec::PlayerName(_) => "{}",
|
||||
ArgumentSpec::EntityTarget(_) => "{}",
|
||||
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||
ArgumentSpec::Float(_, _, _) => "{}",
|
||||
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||
@ -1047,6 +1081,8 @@ pub enum Requirement {
|
||||
pub enum ArgumentSpec {
|
||||
/// The argument refers to a player by alias
|
||||
PlayerName(Requirement),
|
||||
/// The arguments refers to an entity in some way.
|
||||
EntityTarget(Requirement),
|
||||
// The argument refers to a site, by name.
|
||||
SiteName(Requirement),
|
||||
/// The argument is a float. The associated values are
|
||||
@ -1090,6 +1126,13 @@ impl ArgumentSpec {
|
||||
"[player]".to_string()
|
||||
}
|
||||
},
|
||||
ArgumentSpec::EntityTarget(req) => {
|
||||
if &Requirement::Required == req {
|
||||
"<entity>".to_string()
|
||||
} else {
|
||||
"[entity]".to_string()
|
||||
}
|
||||
},
|
||||
ArgumentSpec::SiteName(req) => {
|
||||
if &Requirement::Required == req {
|
||||
"<site>".to_string()
|
||||
|
@ -21,7 +21,7 @@ use common::{
|
||||
assets,
|
||||
calendar::Calendar,
|
||||
cmd::{
|
||||
AreaKind, KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS,
|
||||
AreaKind, EntityTarget, KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS,
|
||||
KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH,
|
||||
},
|
||||
comp::{
|
||||
@ -1288,9 +1288,9 @@ fn handle_tp(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
let (player, dismount_volume) = parse_cmd_args!(args, String, bool);
|
||||
let player = if let Some(alias) = player {
|
||||
find_alias(server.state.ecs(), &alias)?.0
|
||||
let (entity_target, dismount_volume) = parse_cmd_args!(args, EntityTarget, bool);
|
||||
let player = if let Some(entity_target) = entity_target {
|
||||
get_entity_target(entity_target, server)?
|
||||
} else if client != target {
|
||||
client
|
||||
} else {
|
||||
@ -3527,10 +3527,12 @@ fn handle_skill_point(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
if let (Some(a_skill_tree), Some(sp), a_alias) = parse_cmd_args!(args, String, u16, String) {
|
||||
if let (Some(a_skill_tree), Some(sp), entity_target) =
|
||||
parse_cmd_args!(args, String, u16, EntityTarget)
|
||||
{
|
||||
let skill_tree = parse_skill_tree(&a_skill_tree)?;
|
||||
let player = a_alias
|
||||
.map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target))
|
||||
let player = entity_target
|
||||
.map(|entity_target| get_entity_target(entity_target, server))
|
||||
.unwrap_or(Ok(target))?;
|
||||
|
||||
if let Some(mut skill_set) = server
|
||||
@ -3542,7 +3544,7 @@ fn handle_skill_point(
|
||||
skill_set.add_skill_points(skill_tree, sp);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Player has no stats!".into())
|
||||
Err("Entity has no stats!".into())
|
||||
}
|
||||
} else {
|
||||
Err(Content::Plain(action.help_string()))
|
||||
@ -3629,6 +3631,37 @@ fn handle_remove_lights(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_entity_target(entity_target: EntityTarget, server: &Server) -> CmdResult<EcsEntity> {
|
||||
match entity_target {
|
||||
EntityTarget::Player(alias) => Ok(find_alias(server.state.ecs(), &alias)?.0),
|
||||
EntityTarget::RtsimNpc(id) => {
|
||||
let (npc_id, _) = server
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<crate::rtsim::RtSim>()
|
||||
.state()
|
||||
.data()
|
||||
.npcs
|
||||
.iter()
|
||||
.find(|(_, npc)| npc.uid == id)
|
||||
.ok_or(Content::Plain(format!(
|
||||
"Could not find rtsim npc with id {id}."
|
||||
)))?;
|
||||
server
|
||||
.state()
|
||||
.ecs()
|
||||
.read_resource::<common::uid::IdMaps>()
|
||||
.rtsim_entity(common::rtsim::RtSimEntity(npc_id))
|
||||
.ok_or(Content::Plain(format!("Npc with id {id} isn't loaded.")))
|
||||
},
|
||||
EntityTarget::Uid(uid) => server
|
||||
.state
|
||||
.ecs()
|
||||
.entity_from_uid(uid)
|
||||
.ok_or(Content::Plain(format!("{uid:?} not found."))),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_sudo(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
@ -3636,23 +3669,32 @@ fn handle_sudo(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
if let (Some(player_alias), Some(cmd), cmd_args) =
|
||||
parse_cmd_args!(args, String, String, ..Vec<String>)
|
||||
if let (Some(entity_target), Some(cmd), cmd_args) =
|
||||
parse_cmd_args!(args, EntityTarget, String, ..Vec<String>)
|
||||
{
|
||||
if let Ok(action) = cmd.parse() {
|
||||
let (player, player_uuid) = find_alias(server.state.ecs(), &player_alias)?;
|
||||
let entity = get_entity_target(entity_target, server)?;
|
||||
let client_uuid = uuid(server, client, "client")?;
|
||||
verify_above_role(
|
||||
server,
|
||||
(client, client_uuid),
|
||||
(player, player_uuid),
|
||||
"Cannot sudo players with roles higher than your own.",
|
||||
)?;
|
||||
|
||||
// If the entity target is a player check if client has authority to sudo it.
|
||||
{
|
||||
let players = server.state.ecs().read_storage::<comp::Player>();
|
||||
if let Some(player) = players.get(entity) {
|
||||
let player_uuid = player.uuid();
|
||||
drop(players);
|
||||
verify_above_role(
|
||||
server,
|
||||
(client, client_uuid),
|
||||
(entity, player_uuid),
|
||||
"Cannot sudo players with roles higher than your own.",
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider making this into a tail call or loop (to avoid the potential
|
||||
// stack overflow, although it's less of a risk coming from only mods and
|
||||
// admins).
|
||||
do_command(server, client, player, cmd_args, &action)
|
||||
do_command(server, client, entity, cmd_args, &action)
|
||||
} else {
|
||||
Err(Content::localized("command-unknown"))
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ impl ClientChatCommand {
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
ArgumentSpec::PlayerName(_) => "{}",
|
||||
ArgumentSpec::EntityTarget(_) => "{}",
|
||||
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||
ArgumentSpec::Float(_, _, _) => "{}",
|
||||
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||
@ -383,6 +384,7 @@ impl TabComplete for ArgumentSpec {
|
||||
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
||||
match self {
|
||||
ArgumentSpec::PlayerName(_) => complete_player(part, client),
|
||||
ArgumentSpec::EntityTarget(_) => complete_player(part, client),
|
||||
ArgumentSpec::SiteName(_) => complete_site(part, client),
|
||||
ArgumentSpec::Float(_, x, _) => {
|
||||
if part.is_empty() {
|
||||
|
Loading…
Reference in New Issue
Block a user