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 :-)
|
// Please keep this sorted alphabetically :-)
|
||||||
#[derive(Copy, Clone, strum::EnumIter)]
|
#[derive(Copy, Clone, strum::EnumIter)]
|
||||||
pub enum ServerChatCommand {
|
pub enum ServerChatCommand {
|
||||||
@ -739,8 +772,8 @@ impl ServerChatCommand {
|
|||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ServerChatCommand::Sudo => cmd(
|
ServerChatCommand::Sudo => cmd(
|
||||||
vec![PlayerName(Required), SubCommand],
|
vec![EntityTarget(Required), SubCommand],
|
||||||
"Run command as if you were another player",
|
"Run command as if you were another entity",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ServerChatCommand::Tell => cmd(
|
ServerChatCommand::Tell => cmd(
|
||||||
@ -760,10 +793,10 @@ impl ServerChatCommand {
|
|||||||
),
|
),
|
||||||
ServerChatCommand::Tp => cmd(
|
ServerChatCommand::Tp => cmd(
|
||||||
vec![
|
vec![
|
||||||
PlayerName(Optional),
|
EntityTarget(Optional),
|
||||||
Boolean("Dismount from ship", "true".to_string(), Optional),
|
Boolean("Dismount from ship", "true".to_string(), Optional),
|
||||||
],
|
],
|
||||||
"Teleport to another player",
|
"Teleport to another entity",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ServerChatCommand::RtsimTp => cmd(
|
ServerChatCommand::RtsimTp => cmd(
|
||||||
@ -1001,6 +1034,7 @@ impl ServerChatCommand {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|arg| match arg {
|
.map(|arg| match arg {
|
||||||
ArgumentSpec::PlayerName(_) => "{}",
|
ArgumentSpec::PlayerName(_) => "{}",
|
||||||
|
ArgumentSpec::EntityTarget(_) => "{}",
|
||||||
ArgumentSpec::SiteName(_) => "{/.*/}",
|
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||||
ArgumentSpec::Float(_, _, _) => "{}",
|
ArgumentSpec::Float(_, _, _) => "{}",
|
||||||
ArgumentSpec::Integer(_, _, _) => "{d}",
|
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||||
@ -1047,6 +1081,8 @@ pub enum Requirement {
|
|||||||
pub enum ArgumentSpec {
|
pub enum ArgumentSpec {
|
||||||
/// The argument refers to a player by alias
|
/// The argument refers to a player by alias
|
||||||
PlayerName(Requirement),
|
PlayerName(Requirement),
|
||||||
|
/// The arguments refers to an entity in some way.
|
||||||
|
EntityTarget(Requirement),
|
||||||
// The argument refers to a site, by name.
|
// The argument refers to a site, by name.
|
||||||
SiteName(Requirement),
|
SiteName(Requirement),
|
||||||
/// The argument is a float. The associated values are
|
/// The argument is a float. The associated values are
|
||||||
@ -1090,6 +1126,13 @@ impl ArgumentSpec {
|
|||||||
"[player]".to_string()
|
"[player]".to_string()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ArgumentSpec::EntityTarget(req) => {
|
||||||
|
if &Requirement::Required == req {
|
||||||
|
"<entity>".to_string()
|
||||||
|
} else {
|
||||||
|
"[entity]".to_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
ArgumentSpec::SiteName(req) => {
|
ArgumentSpec::SiteName(req) => {
|
||||||
if &Requirement::Required == req {
|
if &Requirement::Required == req {
|
||||||
"<site>".to_string()
|
"<site>".to_string()
|
||||||
|
@ -21,7 +21,7 @@ use common::{
|
|||||||
assets,
|
assets,
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
cmd::{
|
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,
|
KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH,
|
||||||
},
|
},
|
||||||
comp::{
|
comp::{
|
||||||
@ -1288,9 +1288,9 @@ fn handle_tp(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
let (player, dismount_volume) = parse_cmd_args!(args, String, bool);
|
let (entity_target, dismount_volume) = parse_cmd_args!(args, EntityTarget, bool);
|
||||||
let player = if let Some(alias) = player {
|
let player = if let Some(entity_target) = entity_target {
|
||||||
find_alias(server.state.ecs(), &alias)?.0
|
get_entity_target(entity_target, server)?
|
||||||
} else if client != target {
|
} else if client != target {
|
||||||
client
|
client
|
||||||
} else {
|
} else {
|
||||||
@ -3527,10 +3527,12 @@ fn handle_skill_point(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> 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 skill_tree = parse_skill_tree(&a_skill_tree)?;
|
||||||
let player = a_alias
|
let player = entity_target
|
||||||
.map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target))
|
.map(|entity_target| get_entity_target(entity_target, server))
|
||||||
.unwrap_or(Ok(target))?;
|
.unwrap_or(Ok(target))?;
|
||||||
|
|
||||||
if let Some(mut skill_set) = server
|
if let Some(mut skill_set) = server
|
||||||
@ -3542,7 +3544,7 @@ fn handle_skill_point(
|
|||||||
skill_set.add_skill_points(skill_tree, sp);
|
skill_set.add_skill_points(skill_tree, sp);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Player has no stats!".into())
|
Err("Entity has no stats!".into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Content::Plain(action.help_string()))
|
Err(Content::Plain(action.help_string()))
|
||||||
@ -3629,6 +3631,37 @@ fn handle_remove_lights(
|
|||||||
Ok(())
|
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(
|
fn handle_sudo(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
@ -3636,23 +3669,32 @@ fn handle_sudo(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
if let (Some(player_alias), Some(cmd), cmd_args) =
|
if let (Some(entity_target), Some(cmd), cmd_args) =
|
||||||
parse_cmd_args!(args, String, String, ..Vec<String>)
|
parse_cmd_args!(args, EntityTarget, String, ..Vec<String>)
|
||||||
{
|
{
|
||||||
if let Ok(action) = cmd.parse() {
|
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")?;
|
let client_uuid = uuid(server, client, "client")?;
|
||||||
verify_above_role(
|
|
||||||
server,
|
// If the entity target is a player check if client has authority to sudo it.
|
||||||
(client, client_uuid),
|
{
|
||||||
(player, player_uuid),
|
let players = server.state.ecs().read_storage::<comp::Player>();
|
||||||
"Cannot sudo players with roles higher than your own.",
|
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
|
// 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
|
// stack overflow, although it's less of a risk coming from only mods and
|
||||||
// admins).
|
// admins).
|
||||||
do_command(server, client, player, cmd_args, &action)
|
do_command(server, client, entity, cmd_args, &action)
|
||||||
} else {
|
} else {
|
||||||
Err(Content::localized("command-unknown"))
|
Err(Content::localized("command-unknown"))
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ impl ClientChatCommand {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|arg| match arg {
|
.map(|arg| match arg {
|
||||||
ArgumentSpec::PlayerName(_) => "{}",
|
ArgumentSpec::PlayerName(_) => "{}",
|
||||||
|
ArgumentSpec::EntityTarget(_) => "{}",
|
||||||
ArgumentSpec::SiteName(_) => "{/.*/}",
|
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||||
ArgumentSpec::Float(_, _, _) => "{}",
|
ArgumentSpec::Float(_, _, _) => "{}",
|
||||||
ArgumentSpec::Integer(_, _, _) => "{d}",
|
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||||
@ -383,6 +384,7 @@ impl TabComplete for ArgumentSpec {
|
|||||||
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
||||||
match self {
|
match self {
|
||||||
ArgumentSpec::PlayerName(_) => complete_player(part, client),
|
ArgumentSpec::PlayerName(_) => complete_player(part, client),
|
||||||
|
ArgumentSpec::EntityTarget(_) => complete_player(part, client),
|
||||||
ArgumentSpec::SiteName(_) => complete_site(part, client),
|
ArgumentSpec::SiteName(_) => complete_site(part, client),
|
||||||
ArgumentSpec::Float(_, x, _) => {
|
ArgumentSpec::Float(_, x, _) => {
|
||||||
if part.is_empty() {
|
if part.is_empty() {
|
||||||
|
Loading…
Reference in New Issue
Block a user