2019-05-17 09:22:32 +00:00
|
|
|
//! # Implementing new commands.
|
2020-02-01 20:39:39 +00:00
|
|
|
//! To implement a new command, add an instance of `ChatCommand` to
|
|
|
|
//! `CHAT_COMMANDS` and provide a handler function.
|
2019-04-16 16:08:36 +00:00
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
use crate::{
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
settings::{
|
|
|
|
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
|
|
|
},
|
2021-05-06 16:50:15 +00:00
|
|
|
wiring::{Logic, OutputFormula},
|
2020-11-03 23:53:46 +00:00
|
|
|
Server, SpawnPoint, StateExt,
|
2020-10-06 02:59:47 +00:00
|
|
|
};
|
2021-04-12 16:49:08 +00:00
|
|
|
use assets::AssetExt;
|
2021-04-09 07:34:58 +00:00
|
|
|
use authc::Uuid;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
use chrono::{NaiveTime, Timelike, Utc};
|
2019-06-01 19:49:34 +00:00
|
|
|
use common::{
|
2021-04-12 16:49:08 +00:00
|
|
|
assets,
|
2021-05-04 13:22:10 +00:00
|
|
|
cmd::{ChatCommand, BUFF_PACK, BUFF_PARSER, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
2020-12-04 22:24:56 +00:00
|
|
|
comp::{
|
|
|
|
self,
|
2021-01-18 22:58:56 +00:00
|
|
|
aura::{Aura, AuraKind, AuraTarget},
|
2021-05-02 21:42:54 +00:00
|
|
|
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
|
2021-05-05 20:35:57 +00:00
|
|
|
inventory::item::{tool::AbilityMap, MaterialStatManifest, Quality},
|
2021-02-13 23:32:55 +00:00
|
|
|
invite::InviteKind,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea,
|
2020-12-04 22:24:56 +00:00
|
|
|
},
|
2021-04-09 07:34:58 +00:00
|
|
|
depot,
|
2020-11-01 17:15:46 +00:00
|
|
|
effect::Effect,
|
2019-08-25 16:48:12 +00:00
|
|
|
event::{EventBus, ServerEvent},
|
2020-01-29 16:56:28 +00:00
|
|
|
npc::{self, get_npc_name},
|
2021-04-17 17:44:22 +00:00
|
|
|
resources::{PlayerPhysicsSettings, TimeOfDay},
|
2020-09-21 15:39:20 +00:00
|
|
|
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
|
2020-12-13 17:40:15 +00:00
|
|
|
uid::Uid,
|
2020-09-26 13:55:01 +00:00
|
|
|
vol::RectVolSize,
|
2021-05-06 18:50:16 +00:00
|
|
|
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
2019-06-01 19:49:34 +00:00
|
|
|
};
|
2020-12-13 17:11:55 +00:00
|
|
|
use common_net::{
|
|
|
|
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
|
|
|
sync::WorldSyncExt,
|
|
|
|
};
|
2021-04-06 15:47:03 +00:00
|
|
|
use common_state::{BuildAreaError, BuildAreas};
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
use core::{cmp::Ordering, convert::TryFrom, time::Duration};
|
2021-05-04 13:22:10 +00:00
|
|
|
use hashbrown::{HashMap, HashSet};
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
use humantime::Duration as HumanDuration;
|
2019-07-21 18:22:13 +00:00
|
|
|
use rand::Rng;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt};
|
2019-04-16 15:38:01 +00:00
|
|
|
use vek::*;
|
2021-05-04 13:22:10 +00:00
|
|
|
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
|
2019-10-16 11:39:41 +00:00
|
|
|
use world::util::Sampler;
|
2019-04-16 15:38:01 +00:00
|
|
|
|
2021-04-15 16:28:16 +00:00
|
|
|
use crate::{client::Client, login_provider::LoginProvider, wiring};
|
2019-07-29 13:42:26 +00:00
|
|
|
use scan_fmt::{scan_fmt, scan_fmt_some};
|
2021-04-13 22:05:47 +00:00
|
|
|
use tracing::{error, info, warn};
|
2019-07-26 14:38:31 +00:00
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
pub trait ChatCommandExt {
|
|
|
|
fn execute(&self, server: &mut Server, entity: EcsEntity, args: String);
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
2020-05-05 22:33:16 +00:00
|
|
|
impl ChatCommandExt for ChatCommand {
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::needless_return)] // TODO: Pending review in #587
|
2020-05-05 22:33:16 +00:00
|
|
|
fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
if let Err(err) = do_command(server, entity, entity, args, self) {
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
entity,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandError, err),
|
|
|
|
);
|
2019-08-15 14:33:00 +00:00
|
|
|
}
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
type CmdResult<T> = Result<T, String>;
|
2020-05-11 22:02:21 +00:00
|
|
|
/// Handler function called when the command is executed.
|
|
|
|
/// # Arguments
|
|
|
|
/// * `&mut Server` - the `Server` instance executing the command.
|
|
|
|
/// * `EcsEntity` - an `Entity` corresponding to the player that invoked the
|
|
|
|
/// command.
|
|
|
|
/// * `EcsEntity` - an `Entity` for the player on whom the command is invoked.
|
|
|
|
/// This differs from the previous argument when using /sudo
|
|
|
|
/// * `String` - a `String` containing the part of the command after the
|
|
|
|
/// keyword.
|
|
|
|
/// * `&ChatCommand` - the command to execute with the above arguments.
|
|
|
|
/// Handler functions must parse arguments from the the given `String`
|
|
|
|
/// (`scan_fmt!` is included for this purpose).
|
2021-04-09 07:34:58 +00:00
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
///
|
|
|
|
/// A `Result` that is `Ok` if the command went smoothly, and `Err` if it
|
|
|
|
/// failed; on failure, the string is sent to the client who initiated the
|
|
|
|
/// command.
|
|
|
|
type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand) -> CmdResult<()>;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
|
|
|
|
fn do_command(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
cmd: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
// Make sure your role is at least high enough to execute this command.
|
|
|
|
if cmd.needs_role() > server.entity_admin_role(client) {
|
|
|
|
return Err(format!(
|
|
|
|
"You don't have permission to use '/{}'.",
|
|
|
|
cmd.keyword()
|
|
|
|
));
|
|
|
|
}
|
|
|
|
let handler: CommandHandler = match cmd {
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Adminify => handle_adminify,
|
2021-03-11 16:48:59 +00:00
|
|
|
ChatCommand::Airship => handle_spawn_airship,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Alias => handle_alias,
|
2021-05-02 21:42:54 +00:00
|
|
|
ChatCommand::ApplyBuff => handle_apply_buff,
|
2020-07-16 00:48:37 +00:00
|
|
|
ChatCommand::Ban => handle_ban,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Build => handle_build,
|
2021-03-24 07:58:42 +00:00
|
|
|
ChatCommand::BuildAreaAdd => handle_build_area_add,
|
2021-03-27 05:53:33 +00:00
|
|
|
ChatCommand::BuildAreaList => handle_build_area_list,
|
2021-03-24 07:58:42 +00:00
|
|
|
ChatCommand::BuildAreaRemove => handle_build_area_remove,
|
2020-07-31 09:34:26 +00:00
|
|
|
ChatCommand::Campfire => handle_spawn_campfire,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::DebugColumn => handle_debug_column,
|
2021-04-13 22:05:47 +00:00
|
|
|
ChatCommand::DisconnectAllPlayers => handle_disconnect_all_players,
|
2021-01-08 19:12:09 +00:00
|
|
|
ChatCommand::DropAll => handle_drop_all,
|
2020-07-02 21:53:01 +00:00
|
|
|
ChatCommand::Dummy => handle_spawn_training_dummy,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Explosion => handle_explosion,
|
2020-06-01 04:33:39 +00:00
|
|
|
ChatCommand::Faction => handle_faction,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::GiveItem => handle_give_item,
|
|
|
|
ChatCommand::Goto => handle_goto,
|
2020-06-01 04:33:39 +00:00
|
|
|
ChatCommand::Group => handle_group,
|
2020-12-04 02:18:42 +00:00
|
|
|
ChatCommand::GroupInvite => handle_group_invite,
|
|
|
|
ChatCommand::GroupKick => handle_group_kick,
|
|
|
|
ChatCommand::GroupLeave => handle_group_leave,
|
|
|
|
ChatCommand::GroupPromote => handle_group_promote,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Health => handle_health,
|
|
|
|
ChatCommand::Help => handle_help,
|
2020-11-03 23:53:46 +00:00
|
|
|
ChatCommand::Home => handle_home,
|
2020-06-04 09:40:05 +00:00
|
|
|
ChatCommand::JoinFaction => handle_join_faction,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Jump => handle_jump,
|
2020-07-16 00:48:37 +00:00
|
|
|
ChatCommand::Kick => handle_kick,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Kill => handle_kill,
|
|
|
|
ChatCommand::KillNpcs => handle_kill_npcs,
|
2021-04-12 16:49:08 +00:00
|
|
|
ChatCommand::Kit => handle_kit,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Lantern => handle_lantern,
|
|
|
|
ChatCommand::Light => handle_light,
|
2020-06-27 18:39:16 +00:00
|
|
|
ChatCommand::MakeBlock => handle_make_block,
|
2020-09-21 15:39:20 +00:00
|
|
|
ChatCommand::MakeSprite => handle_make_sprite,
|
2020-06-25 12:07:01 +00:00
|
|
|
ChatCommand::Motd => handle_motd,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Object => handle_object,
|
2021-03-23 08:37:29 +00:00
|
|
|
ChatCommand::PermitBuild => handle_permit_build,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Players => handle_players,
|
2020-06-01 04:33:39 +00:00
|
|
|
ChatCommand::Region => handle_region,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::RemoveLights => handle_remove_lights,
|
2021-03-24 07:58:42 +00:00
|
|
|
ChatCommand::RevokeBuild => handle_revoke_build,
|
|
|
|
ChatCommand::RevokeBuildAll => handle_revoke_build_all,
|
2021-02-28 23:14:59 +00:00
|
|
|
ChatCommand::Safezone => handle_safezone,
|
2020-06-01 04:33:39 +00:00
|
|
|
ChatCommand::Say => handle_say,
|
2021-04-17 17:44:22 +00:00
|
|
|
ChatCommand::ServerPhysics => handle_server_physics,
|
2020-06-25 13:56:21 +00:00
|
|
|
ChatCommand::SetMotd => handle_set_motd,
|
2021-03-27 00:38:56 +00:00
|
|
|
ChatCommand::Site => handle_site,
|
2021-01-06 01:26:21 +00:00
|
|
|
ChatCommand::SkillPoint => handle_skill_point,
|
2021-05-08 15:47:09 +00:00
|
|
|
ChatCommand::SkillPreset => handle_skill_preset,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Spawn => handle_spawn,
|
|
|
|
ChatCommand::Sudo => handle_sudo,
|
|
|
|
ChatCommand::Tell => handle_tell,
|
|
|
|
ChatCommand::Time => handle_time,
|
|
|
|
ChatCommand::Tp => handle_tp,
|
2020-07-16 00:48:37 +00:00
|
|
|
ChatCommand::Unban => handle_unban,
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Version => handle_version,
|
|
|
|
ChatCommand::Waypoint => handle_waypoint,
|
2021-04-15 16:28:16 +00:00
|
|
|
ChatCommand::Wiring => handle_spawn_wiring,
|
2020-06-30 12:21:36 +00:00
|
|
|
ChatCommand::Whitelist => handle_whitelist,
|
2020-06-01 04:33:39 +00:00
|
|
|
ChatCommand::World => handle_world,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
handler(server, client, target, args, cmd)
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
2020-05-09 02:42:51 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
// Fallibly get position of entity with the given descriptor (used for error
|
|
|
|
// message).
|
|
|
|
fn position(server: &Server, entity: EcsEntity, descriptor: &str) -> CmdResult<comp::Pos> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Pos>()
|
|
|
|
.get(entity)
|
|
|
|
.copied()
|
|
|
|
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn position_mut<T>(
|
|
|
|
server: &mut Server,
|
|
|
|
entity: EcsEntity,
|
|
|
|
descriptor: &str,
|
|
|
|
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
|
|
|
|
) -> CmdResult<T> {
|
|
|
|
let mut pos_storage = server.state.ecs_mut().write_storage::<comp::Pos>();
|
|
|
|
pos_storage
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(f)
|
|
|
|
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn insert_or_replace_component<C: specs::Component>(
|
|
|
|
server: &mut Server,
|
|
|
|
entity: EcsEntity,
|
|
|
|
component: C,
|
|
|
|
descriptor: &str,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage()
|
|
|
|
.insert(entity, component)
|
|
|
|
.and(Ok(()))
|
|
|
|
.map_err(|_| format!("Entity {:?} is dead!", descriptor))
|
|
|
|
}
|
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
fn uuid(server: &Server, entity: EcsEntity, descriptor: &str) -> CmdResult<Uuid> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Player>()
|
|
|
|
.get(entity)
|
|
|
|
.map(|player| player.uuid())
|
|
|
|
.ok_or_else(|| format!("Cannot get player information for {:?}", descriptor))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn real_role(server: &Server, uuid: Uuid, descriptor: &str) -> CmdResult<comp::AdminRole> {
|
|
|
|
server
|
|
|
|
.editable_settings()
|
|
|
|
.admins
|
|
|
|
.get(&uuid)
|
|
|
|
.map(|record| record.role.into())
|
|
|
|
.ok_or_else(|| format!("Cannot get administrator roles for {:?} uuid", descriptor))
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
// Fallibly get uid of entity with the given descriptor (used for error
|
|
|
|
// message).
|
|
|
|
fn uid(server: &Server, target: EcsEntity, descriptor: &str) -> CmdResult<Uid> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<Uid>()
|
|
|
|
.get(target)
|
|
|
|
.copied()
|
|
|
|
.ok_or_else(|| format!("Cannot get uid for {:?}", descriptor))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn area(server: &mut Server, area_name: &str) -> CmdResult<depot::Id<vek::Aabb<i32>>> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<BuildAreas>()
|
|
|
|
.area_names()
|
|
|
|
.get(area_name)
|
|
|
|
.copied()
|
|
|
|
.ok_or_else(|| format!("Area name not found: {}", area_name))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prevent use through sudo.
|
|
|
|
fn no_sudo(client: EcsEntity, target: EcsEntity) -> CmdResult<()> {
|
|
|
|
if client == target {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
// This happens when [ab]using /sudo
|
|
|
|
Err("It's rude to impersonate people".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
/// Ensure that client role is above target role, for the purpose of performing
|
|
|
|
/// some (often permanent) administrative action on the target. Note that this
|
|
|
|
/// function is *not* a replacement for actually verifying that the client
|
|
|
|
/// should be able to execute the command at all, which still needs to be
|
|
|
|
/// rechecked, nor does it guarantee that either the client or the target
|
|
|
|
/// actually have an entry in the admin settings file.
|
|
|
|
///
|
|
|
|
/// For our purposes, there are *two* roles--temporary role, and permanent role.
|
|
|
|
/// For the purpose of these checks, currently *any* permanent role overrides
|
|
|
|
/// *any* temporary role (this may change if more roles are added that aren't
|
|
|
|
/// moderator or administrator). If the permanent roles match, the temporary
|
|
|
|
/// roles are used as a tiebreaker. /adminify should ensure that no one's
|
|
|
|
/// temporary role can be different from their permanent role without someone
|
|
|
|
/// with a higher role than their permanent role allowing it, and only permanent
|
|
|
|
/// roles should be recorded in the settings files.
|
|
|
|
fn verify_above_role(
|
|
|
|
server: &mut Server,
|
|
|
|
(client, client_uuid): (EcsEntity, Uuid),
|
|
|
|
(player, player_uuid): (EcsEntity, Uuid),
|
|
|
|
reason: &str,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
let client_temp = server.entity_admin_role(client);
|
|
|
|
let client_perm = server
|
|
|
|
.editable_settings()
|
|
|
|
.admins
|
|
|
|
.get(&client_uuid)
|
|
|
|
.map(|record| record.role);
|
|
|
|
|
|
|
|
let player_temp = server.entity_admin_role(player);
|
|
|
|
let player_perm = server
|
2021-04-09 07:34:58 +00:00
|
|
|
.editable_settings()
|
|
|
|
.admins
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
.get(&player_uuid)
|
|
|
|
.map(|record| record.role);
|
|
|
|
|
|
|
|
if client_perm > player_perm || client_perm == player_perm && client_temp > player_temp {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(reason.into())
|
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn find_alias(ecs: &specs::World, alias: &str) -> CmdResult<(EcsEntity, Uuid)> {
|
|
|
|
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
|
|
|
.join()
|
|
|
|
.find(|(_, player)| player.alias == alias)
|
|
|
|
.map(|(entity, player)| (entity, player.uuid()))
|
|
|
|
.ok_or_else(|| format!("Player {:?} not found!", alias))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_uuid(ecs: &specs::World, uuid: Uuid) -> CmdResult<EcsEntity> {
|
|
|
|
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
|
|
|
.join()
|
|
|
|
.find(|(_, player)| player.uuid() == uuid)
|
|
|
|
.map(|(entity, _)| entity)
|
|
|
|
.ok_or_else(|| format!("Player with UUID {:?} not found!", uuid))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_username(server: &mut Server, username: &str) -> CmdResult<Uuid> {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<LoginProvider>()
|
|
|
|
.username_to_uuid(username)
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
.map_err(|_| format!("Unable to determine UUID for username {:?}", username))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// NOTE: Intended to be run only on logged-in clients.
|
|
|
|
fn uuid_to_username(
|
|
|
|
server: &mut Server,
|
|
|
|
fallback_entity: EcsEntity,
|
|
|
|
uuid: Uuid,
|
|
|
|
) -> CmdResult<String> {
|
|
|
|
let make_err = || format!("Unable to determine username for UUID {:?}", uuid);
|
|
|
|
let player_storage = server.state.ecs().read_storage::<comp::Player>();
|
|
|
|
|
|
|
|
let fallback_alias = &player_storage
|
|
|
|
.get(fallback_entity)
|
|
|
|
.ok_or_else(make_err)?
|
|
|
|
.alias;
|
|
|
|
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_resource::<LoginProvider>()
|
|
|
|
.uuid_to_username(uuid, fallback_alias)
|
|
|
|
.map_err(|_| make_err())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn edit_setting_feedback<S: EditableSetting>(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
result: Option<(String, Result<(), SettingError<S>>)>,
|
|
|
|
failure: impl FnOnce() -> String,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
let (info, result) = result.ok_or_else(failure)?;
|
|
|
|
match result {
|
|
|
|
Ok(()) => {
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, info),
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
Err(SettingError::Io(err)) => {
|
|
|
|
warn!(
|
|
|
|
?err,
|
|
|
|
"Failed to write settings file to disk, but succeeded in memory (success message: \
|
|
|
|
{})",
|
|
|
|
info,
|
|
|
|
);
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandError,
|
|
|
|
format!(
|
|
|
|
"Failed to write settings file to disk, but succeeded in memory.\n
|
|
|
|
Error (storage): {:?}\n
|
|
|
|
Success (memory): {}",
|
|
|
|
err, info
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
Err(SettingError::Integrity(err)) => Err(format!(
|
|
|
|
"Encountered an error while validating the request: {:?}",
|
|
|
|
err
|
|
|
|
)),
|
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 19:12:09 +00:00
|
|
|
fn handle_drop_all(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2021-01-08 19:12:09 +00:00
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let pos = position(server, target, "target")?;
|
2021-01-08 19:12:09 +00:00
|
|
|
|
|
|
|
let mut items = Vec::new();
|
|
|
|
if let Some(mut inventory) = server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
2021-04-09 07:34:58 +00:00
|
|
|
.get_mut(target)
|
2021-01-08 19:12:09 +00:00
|
|
|
{
|
|
|
|
items = inventory.drain().collect();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
2021-05-05 20:35:57 +00:00
|
|
|
let item_to_place = items
|
|
|
|
.into_iter()
|
|
|
|
.filter(|i| !matches!(i.quality(), Quality::Debug));
|
|
|
|
for item in item_to_place {
|
2021-01-26 20:23:18 +00:00
|
|
|
let vel = Vec3::new(rng.gen_range(-0.1..0.1), rng.gen_range(-0.1..0.1), 0.5);
|
2021-01-08 19:12:09 +00:00
|
|
|
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.create_object(Default::default(), comp::object::Body::Pouch)
|
|
|
|
.with(comp::Pos(Vec3::new(
|
2021-01-26 20:23:18 +00:00
|
|
|
pos.0.x + rng.gen_range(5.0..10.0),
|
|
|
|
pos.0.y + rng.gen_range(5.0..10.0),
|
2021-01-08 19:12:09 +00:00
|
|
|
pos.0.z + 5.0,
|
|
|
|
)))
|
|
|
|
.with(item)
|
|
|
|
.with(comp::Vel(vel))
|
|
|
|
.build();
|
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
|
|
|
|
Ok(())
|
2021-01-08 19:12:09 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 06:52:04 +00:00
|
|
|
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
2020-05-05 22:33:16 +00:00
|
|
|
fn handle_give_item(
|
2020-04-24 02:36:19 +00:00
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
2020-05-07 18:39:48 +00:00
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-10 01:17:03 +00:00
|
|
|
if let (Some(item_name), give_amount_opt) =
|
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), String, u32)
|
|
|
|
{
|
2020-05-07 18:39:48 +00:00
|
|
|
let give_amount = give_amount_opt.unwrap_or(1);
|
2020-11-21 04:08:11 +00:00
|
|
|
if let Ok(item) = Item::new_from_asset(&item_name.replace('/', ".").replace("\\", ".")) {
|
2020-05-07 18:39:48 +00:00
|
|
|
let mut item: Item = item;
|
2021-04-09 07:34:58 +00:00
|
|
|
let mut res = Ok(());
|
2020-05-07 18:39:48 +00:00
|
|
|
if let Ok(()) = item.set_amount(give_amount.min(2000)) {
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(target)
|
2021-01-08 19:12:09 +00:00
|
|
|
.map(|mut inv| {
|
2021-04-09 07:34:58 +00:00
|
|
|
// NOTE: Deliberately ignores items that couldn't be pushed.
|
|
|
|
if inv.push(item).is_err() {
|
|
|
|
res = Err(format!(
|
|
|
|
"Player inventory full. Gave 0 of {} items.",
|
|
|
|
give_amount
|
|
|
|
));
|
2020-05-07 18:39:48 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2021-04-29 23:34:14 +00:00
|
|
|
let ability_map = server.state.ecs().read_resource::<AbilityMap>();
|
2021-02-23 20:29:27 +00:00
|
|
|
let msm = server.state.ecs().read_resource::<MaterialStatManifest>();
|
2020-05-07 18:39:48 +00:00
|
|
|
// This item can't stack. Give each item in a loop.
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(target)
|
2021-01-08 19:12:09 +00:00
|
|
|
.map(|mut inv| {
|
2020-05-07 18:39:48 +00:00
|
|
|
for i in 0..give_amount {
|
2021-04-09 07:34:58 +00:00
|
|
|
// NOTE: Deliberately ignores items that couldn't be pushed.
|
2021-04-29 23:34:14 +00:00
|
|
|
if inv.push(item.duplicate(&ability_map, &msm)).is_err() {
|
2021-04-09 07:34:58 +00:00
|
|
|
res = Err(format!(
|
|
|
|
"Player inventory full. Gave {} of {} items.",
|
|
|
|
i, give_amount
|
|
|
|
));
|
2020-05-07 18:39:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(
|
|
|
|
server,
|
|
|
|
target,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
|
|
|
|
"target",
|
|
|
|
)?;
|
|
|
|
res
|
2020-05-07 18:39:48 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(format!("Invalid item: {}", item_name))
|
2020-05-07 18:39:48 +00:00
|
|
|
}
|
2019-10-24 21:05:10 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-10-24 21:05:10 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-31 04:48:57 +00:00
|
|
|
|
2020-06-27 18:39:16 +00:00
|
|
|
fn handle_make_block(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-06-27 18:39:16 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-06-27 18:39:16 +00:00
|
|
|
if let Some(block_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
|
|
|
if let Ok(bk) = BlockKind::try_from(block_name.as_str()) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
server.state.set_block(
|
|
|
|
pos.0.map(|e| e.floor() as i32),
|
|
|
|
Block::new(bk, Rgb::broadcast(255)),
|
2020-06-27 18:39:16 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(format!("Invalid block kind: {}", block_name))
|
2020-06-27 18:39:16 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-06-27 18:39:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 15:39:20 +00:00
|
|
|
fn handle_make_sprite(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-09-21 15:39:20 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-09-21 15:39:20 +00:00
|
|
|
if let Some(sprite_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
|
|
|
if let Ok(sk) = SpriteKind::try_from(sprite_name.as_str()) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let pos = pos.0.map(|e| e.floor() as i32);
|
|
|
|
let new_block = server
|
|
|
|
.state
|
|
|
|
.get_block(pos)
|
|
|
|
// TODO: Make more principled.
|
|
|
|
.unwrap_or_else(|| Block::air(SpriteKind::Empty))
|
|
|
|
.with_sprite(sk);
|
|
|
|
server.state.set_block(pos, new_block);
|
|
|
|
Ok(())
|
2020-09-21 15:39:20 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(format!("Invalid sprite kind: {}", sprite_name))
|
2020-09-21 15:39:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-09-21 15:39:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 12:07:01 +00:00
|
|
|
fn handle_motd(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
2020-06-25 18:50:04 +00:00
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-06-25 13:56:21 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
2021-04-09 07:34:58 +00:00
|
|
|
ChatType::CommandInfo,
|
2020-12-13 17:40:15 +00:00
|
|
|
(*server.editable_settings().server_description).clone(),
|
|
|
|
),
|
2020-06-25 13:56:21 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2020-06-25 13:56:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_set_motd(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-10-05 07:41:58 +00:00
|
|
|
let data_dir = server.data_dir();
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
// Ensure the person setting this has a real role in the settings file, since
|
|
|
|
// it's persistent.
|
|
|
|
let _client_real_role = real_role(server, client_uuid, "client")?;
|
2020-06-25 12:07:01 +00:00
|
|
|
match scan_fmt!(&args, &action.arg_fmt(), String) {
|
|
|
|
Ok(msg) => {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let edit =
|
|
|
|
server
|
|
|
|
.editable_settings_mut()
|
|
|
|
.server_description
|
|
|
|
.edit(data_dir.as_ref(), |d| {
|
|
|
|
let info = format!("Server description set to {:?}", msg);
|
|
|
|
**d = msg;
|
|
|
|
Some(info)
|
|
|
|
});
|
|
|
|
drop(data_dir);
|
|
|
|
edit_setting_feedback(server, client, edit, || {
|
|
|
|
unreachable!("edit always returns Some")
|
|
|
|
})
|
2020-06-25 12:07:01 +00:00
|
|
|
},
|
2020-06-25 13:56:21 +00:00
|
|
|
Err(_) => {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let edit =
|
|
|
|
server
|
|
|
|
.editable_settings_mut()
|
|
|
|
.server_description
|
|
|
|
.edit(data_dir.as_ref(), |d| {
|
|
|
|
d.clear();
|
|
|
|
Some("Removed server description".to_string())
|
|
|
|
});
|
|
|
|
drop(data_dir);
|
|
|
|
edit_setting_feedback(server, client, edit, || {
|
|
|
|
unreachable!("edit always returns Some")
|
|
|
|
})
|
2020-06-25 13:56:21 +00:00
|
|
|
},
|
2020-06-25 12:07:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_jump(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
2021-04-09 07:34:58 +00:00
|
|
|
position_mut(server, target, "target", |current_pos| {
|
|
|
|
current_pos.0 += Vec3::new(x, y, z)
|
|
|
|
})?;
|
|
|
|
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_goto(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
2021-04-09 07:34:58 +00:00
|
|
|
position_mut(server, target, "target", |current_pos| {
|
|
|
|
current_pos.0 = Vec3::new(x, y, z)
|
|
|
|
})?;
|
|
|
|
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
2019-08-15 14:33:00 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
/// TODO: Add autocompletion if possible (might require modifying enum to handle
|
|
|
|
/// dynamic values).
|
2021-03-27 00:38:56 +00:00
|
|
|
fn handle_site(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2021-03-27 00:38:56 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-04-15 18:07:46 +00:00
|
|
|
#[cfg(feature = "worldgen")]
|
2021-03-27 00:38:56 +00:00
|
|
|
if let Ok(dest_name) = scan_fmt!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let site = server
|
|
|
|
.world
|
|
|
|
.civs()
|
|
|
|
.sites()
|
|
|
|
.find(|site| {
|
2021-03-27 00:38:56 +00:00
|
|
|
site.site_tmp
|
|
|
|
.map_or(false, |id| server.index.sites[id].name() == dest_name)
|
2021-04-09 07:34:58 +00:00
|
|
|
})
|
|
|
|
.ok_or_else(|| "Site not found".to_string())?;
|
|
|
|
|
|
|
|
let site_pos = server
|
|
|
|
.world
|
|
|
|
.find_lowest_accessible_pos(server.index.as_index_ref(), site.center);
|
|
|
|
|
|
|
|
position_mut(server, target, "target", |current_pos| {
|
|
|
|
current_pos.0 = site_pos
|
|
|
|
})?;
|
|
|
|
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
2021-03-27 00:38:56 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2021-03-27 00:38:56 +00:00
|
|
|
}
|
2021-04-15 18:07:46 +00:00
|
|
|
|
|
|
|
#[cfg(not(feature = "worldgen"))]
|
|
|
|
Ok(())
|
2021-03-27 00:38:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 23:53:46 +00:00
|
|
|
fn handle_home(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-11-03 23:53:46 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let home_pos = server.state.mut_resource::<SpawnPoint>().0;
|
|
|
|
let time = *server.state.mut_resource::<common::resources::Time>();
|
|
|
|
|
|
|
|
position_mut(server, target, "target", |current_pos| {
|
|
|
|
current_pos.0 = home_pos
|
|
|
|
})?;
|
|
|
|
insert_or_replace_component(
|
|
|
|
server,
|
|
|
|
target,
|
|
|
|
comp::Waypoint::temp_new(home_pos, time),
|
|
|
|
"target",
|
|
|
|
)?;
|
|
|
|
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
2020-11-03 23:53:46 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_kill(
|
|
|
|
server: &mut Server,
|
2020-04-24 16:17:56 +00:00
|
|
|
client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-04-24 16:17:56 +00:00
|
|
|
let reason = if client == target {
|
|
|
|
comp::HealthSource::Suicide
|
2020-06-11 17:06:11 +00:00
|
|
|
} else if let Some(uid) = server.state.read_storage::<Uid>().get(client) {
|
2020-11-05 01:21:42 +00:00
|
|
|
comp::HealthSource::Damage {
|
|
|
|
kind: DamageSource::Other,
|
|
|
|
by: Some(*uid),
|
|
|
|
}
|
2020-04-24 16:17:56 +00:00
|
|
|
} else {
|
2020-06-11 17:06:11 +00:00
|
|
|
comp::HealthSource::Command
|
2020-04-24 16:17:56 +00:00
|
|
|
};
|
2019-05-19 21:54:09 +00:00
|
|
|
server
|
|
|
|
.state
|
2019-05-25 21:13:38 +00:00
|
|
|
.ecs_mut()
|
2020-10-31 22:34:08 +00:00
|
|
|
.write_storage::<comp::Health>()
|
2020-04-24 02:36:19 +00:00
|
|
|
.get_mut(target)
|
2021-01-07 20:25:12 +00:00
|
|
|
.map(|mut h| h.set_to(0, reason));
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-05-19 21:54:09 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_time(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-11 19:13:19 +00:00
|
|
|
const DAY: u64 = 86400;
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let time_in_seconds = server.state.mut_resource::<TimeOfDay>().0;
|
2021-03-11 19:13:19 +00:00
|
|
|
let current_day = time_in_seconds as u64 / DAY;
|
|
|
|
let day_start = (current_day * DAY) as f64;
|
|
|
|
|
|
|
|
// Find the next occurence of the given time in the day/night cycle
|
|
|
|
let next_cycle = |time| {
|
|
|
|
let new_time = day_start + time;
|
|
|
|
new_time
|
|
|
|
+ if new_time < time_in_seconds {
|
|
|
|
DAY as f64
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
let time = scan_fmt_some!(&args, &action.arg_fmt(), String);
|
2020-06-30 14:56:49 +00:00
|
|
|
let new_time = match time.as_deref() {
|
2021-03-11 19:13:19 +00:00
|
|
|
Some("midnight") => {
|
|
|
|
next_cycle(NaiveTime::from_hms(0, 0, 0).num_seconds_from_midnight() as f64)
|
|
|
|
},
|
|
|
|
Some("night") => {
|
|
|
|
next_cycle(NaiveTime::from_hms(20, 0, 0).num_seconds_from_midnight() as f64)
|
|
|
|
},
|
|
|
|
Some("dawn") => next_cycle(NaiveTime::from_hms(5, 0, 0).num_seconds_from_midnight() as f64),
|
|
|
|
Some("morning") => {
|
|
|
|
next_cycle(NaiveTime::from_hms(8, 0, 0).num_seconds_from_midnight() as f64)
|
|
|
|
},
|
|
|
|
Some("day") => next_cycle(NaiveTime::from_hms(10, 0, 0).num_seconds_from_midnight() as f64),
|
|
|
|
Some("noon") => {
|
|
|
|
next_cycle(NaiveTime::from_hms(12, 0, 0).num_seconds_from_midnight() as f64)
|
|
|
|
},
|
|
|
|
Some("dusk") => {
|
|
|
|
next_cycle(NaiveTime::from_hms(17, 0, 0).num_seconds_from_midnight() as f64)
|
|
|
|
},
|
2019-08-15 14:33:00 +00:00
|
|
|
Some(n) => match n.parse() {
|
|
|
|
Ok(n) => n,
|
|
|
|
Err(_) => match NaiveTime::parse_from_str(n, "%H:%M") {
|
2021-03-11 19:13:19 +00:00
|
|
|
// Relative to current day
|
|
|
|
Ok(time) => next_cycle(time.num_seconds_from_midnight() as f64),
|
2020-11-21 21:31:49 +00:00
|
|
|
// Accept `u12345`, seconds since midnight day 0
|
2020-11-16 22:46:31 +00:00
|
|
|
Err(_) => match n
|
|
|
|
.get(1..)
|
2020-11-23 15:39:03 +00:00
|
|
|
.filter(|_| n.starts_with('u'))
|
|
|
|
.and_then(|n| n.trim_start_matches('u').parse::<u64>().ok())
|
2020-11-16 22:46:31 +00:00
|
|
|
{
|
2021-03-11 19:13:19 +00:00
|
|
|
// Absolute time (i.e: since in-game epoch)
|
2020-11-16 22:46:31 +00:00
|
|
|
Some(n) => n as f64,
|
|
|
|
None => {
|
2021-04-09 07:34:58 +00:00
|
|
|
return Err(format!("{:?} is not a valid time.", n));
|
2020-11-16 22:46:31 +00:00
|
|
|
},
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-07-26 14:38:31 +00:00
|
|
|
},
|
2019-08-15 14:33:00 +00:00
|
|
|
},
|
|
|
|
None => {
|
2020-11-23 15:39:03 +00:00
|
|
|
// Would this ever change? Perhaps in a few hundred thousand years some
|
|
|
|
// game archeologists of the future will resurrect the best game of all
|
|
|
|
// time which, obviously, would be Veloren. By that time, the inescapable
|
|
|
|
// laws of thermodynamics will mean that the earth's rotation period
|
|
|
|
// would be slower. Of course, a few hundred thousand years is enough
|
|
|
|
// for the circadian rhythm of human biology to have shifted to account
|
|
|
|
// accordingly. When booting up Veloren for the first time in 337,241
|
|
|
|
// years, they might feel a touch of anguish at the fact that their
|
|
|
|
// earth days and the days within the game do not neatly divide into
|
|
|
|
// one-another. Understandably, they'll want to change this. Who
|
|
|
|
// wouldn't? It would be like turning the TV volume up to an odd number
|
|
|
|
// or having a slightly untuned radio (assuming they haven't begun
|
|
|
|
// broadcasting information directly into their brains). Totally
|
|
|
|
// unacceptable. No, the correct and proper thing to do would be to
|
|
|
|
// release a retroactive definitive edition DLC for $99 with the very
|
|
|
|
// welcome addition of shorter day periods and a complementary
|
|
|
|
// 'developer commentary' mode created by digging up the long-decayed
|
|
|
|
// skeletons of the Veloren team, measuring various attributes of their
|
|
|
|
// jawlines, and using them to recreate their voices. But how to go about
|
|
|
|
// this Herculean task? This code is jibberish! The last of the core Rust
|
|
|
|
// dev team died exactly 337,194 years ago! Rust is now a long-forgotten
|
|
|
|
// dialect of the ancient ones, lost to the sands of time. Ashes to ashes,
|
|
|
|
// dust to dust. When all hope is lost, one particularly intrepid
|
|
|
|
// post-human hominid exployed by the 'Veloren Revival Corp' (no doubt we
|
|
|
|
// still won't have gotted rid of this blasted 'capitalism' thing by then)
|
|
|
|
// might notice, after years of searching, a particularly curious
|
|
|
|
// inscription within the code. The letters `D`, `A`, `Y`. Curious! She
|
|
|
|
// consults the post-human hominid scholars of the old. Care to empathise
|
|
|
|
// with her shock when she discovers that these symbols, as alien as they
|
|
|
|
// may seem, correspond exactly to the word `ⓕя𝐢ᵇᵇ𝔩E`, the word for
|
|
|
|
// 'day' in the post-human hominid language, which is of course universal.
|
|
|
|
// Imagine also her surprise when, after much further translating, she
|
|
|
|
// finds a comment predicting her very existence and her struggle to
|
|
|
|
// decode this great mystery. Rejoyce! The Veloren Revival Corp. may now
|
|
|
|
// persist with their great Ultimate Edition DLC because the day period
|
|
|
|
// might now be changed because they have found the constant that controls
|
|
|
|
// it! Everybody was henceforth happy until the end of time.
|
|
|
|
//
|
|
|
|
// This one's for you, xMac ;)
|
2019-08-15 14:33:00 +00:00
|
|
|
let current_time = NaiveTime::from_num_seconds_from_midnight_opt(
|
|
|
|
// Wraps around back to 0s if it exceeds 24 hours (24 hours = 86400s)
|
2020-11-23 15:39:03 +00:00
|
|
|
(time_in_seconds as u64 % DAY) as u32,
|
2019-08-15 14:33:00 +00:00
|
|
|
0,
|
|
|
|
);
|
|
|
|
let msg = match current_time {
|
2019-08-18 18:07:21 +00:00
|
|
|
Some(time) => format!("It is {}", time.format("%H:%M").to_string()),
|
2019-08-15 14:33:00 +00:00
|
|
|
None => String::from("Unknown Time"),
|
|
|
|
};
|
2020-12-13 17:40:15 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, msg),
|
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
return Ok(());
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-08-15 14:33:00 +00:00
|
|
|
};
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server.state.mut_resource::<TimeOfDay>().0 = new_time;
|
2019-08-15 14:33:00 +00:00
|
|
|
|
2021-04-28 21:26:09 +00:00
|
|
|
// Update all clients with the new TimeOfDay (without this they would have to
|
|
|
|
// wait for the next 100th tick to receive the update).
|
|
|
|
let mut tod_lazymsg = None;
|
|
|
|
let clients = server.state.ecs().read_storage::<Client>();
|
|
|
|
for client in (&clients).join() {
|
|
|
|
let msg = tod_lazymsg
|
|
|
|
.unwrap_or_else(|| client.prepare(ServerGeneral::TimeOfDay(TimeOfDay(new_time))));
|
|
|
|
let _ = client.send_prepared(&msg);
|
|
|
|
tod_lazymsg = Some(msg);
|
|
|
|
}
|
|
|
|
|
2020-11-23 15:39:03 +00:00
|
|
|
if let Some(new_time) =
|
|
|
|
NaiveTime::from_num_seconds_from_midnight_opt(((new_time as u64) % 86400) as u32, 0)
|
|
|
|
{
|
2020-11-16 22:46:31 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Time changed to: {}", new_time.format("%H:%M").to_string(),),
|
|
|
|
),
|
2020-11-16 22:46:31 +00:00
|
|
|
);
|
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-06-21 08:42:16 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_health(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) {
|
2021-01-07 20:25:12 +00:00
|
|
|
if let Some(mut health) = server
|
2019-08-15 14:33:00 +00:00
|
|
|
.state
|
2019-10-15 04:06:14 +00:00
|
|
|
.ecs()
|
2020-10-31 22:34:08 +00:00
|
|
|
.write_storage::<comp::Health>()
|
2020-04-24 02:36:19 +00:00
|
|
|
.get_mut(target)
|
2019-08-15 14:33:00 +00:00
|
|
|
{
|
2020-10-31 22:34:08 +00:00
|
|
|
health.set_to(hp * 10, comp::HealthSource::Command);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-07-30 08:10:58 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("You have no health".into())
|
2019-07-30 08:10:58 +00:00
|
|
|
}
|
2019-08-15 14:33:00 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("You must specify health amount!".into())
|
2019-07-01 20:07:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_alias(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
// Prevent silly aliases
|
|
|
|
comp::Player::alias_validate(&alias).map_err(|e| e.to_string())?;
|
|
|
|
|
2020-05-23 07:13:51 +00:00
|
|
|
let old_alias_optional = server
|
2019-07-30 08:10:58 +00:00
|
|
|
.state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::Player>()
|
2020-04-24 02:36:19 +00:00
|
|
|
.get_mut(target)
|
2021-01-07 20:25:12 +00:00
|
|
|
.map(|mut player| std::mem::replace(&mut player.alias, alias));
|
2019-12-23 06:02:00 +00:00
|
|
|
|
|
|
|
// Update name on client player lists
|
|
|
|
let ecs = server.state.ecs();
|
2020-05-23 07:13:51 +00:00
|
|
|
if let (Some(uid), Some(player), Some(old_alias)) = (
|
2020-04-24 02:36:19 +00:00
|
|
|
ecs.read_storage::<Uid>().get(target),
|
|
|
|
ecs.read_storage::<comp::Player>().get(target),
|
2020-05-23 07:13:51 +00:00
|
|
|
old_alias_optional,
|
2019-12-23 06:02:00 +00:00
|
|
|
) {
|
2020-10-07 10:31:49 +00:00
|
|
|
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(
|
2020-10-05 10:44:33 +00:00
|
|
|
*uid,
|
|
|
|
player.alias.clone(),
|
|
|
|
));
|
2020-10-30 16:39:53 +00:00
|
|
|
server.state.notify_players(msg);
|
2020-05-30 01:36:52 +00:00
|
|
|
|
|
|
|
// Announce alias change if target has a Body.
|
|
|
|
if ecs.read_storage::<comp::Body>().get(target).is_some() {
|
2020-12-13 17:40:15 +00:00
|
|
|
server.state.notify_players(ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("{} is now known as {}.", old_alias, player.alias),
|
|
|
|
));
|
2020-05-30 01:36:52 +00:00
|
|
|
}
|
2019-12-23 06:02:00 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
if client != target {
|
|
|
|
// Notify target that an admin changed the alias due to /sudo
|
|
|
|
server.notify_client(
|
|
|
|
target,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "An admin changed your alias."),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(())
|
2019-07-30 08:10:58 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_tp(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let player = if let Some(alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
|
|
|
find_alias(server.state.ecs(), &alias)?.0
|
2020-06-11 17:06:11 +00:00
|
|
|
} else if client != target {
|
2021-04-09 07:34:58 +00:00
|
|
|
client
|
2020-05-10 01:17:03 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
return Err(action.help_string());
|
2020-05-10 01:17:03 +00:00
|
|
|
};
|
2021-04-09 07:34:58 +00:00
|
|
|
let player_pos = position(server, player, "player")?;
|
|
|
|
position_mut(server, target, "target", |target_pos| {
|
|
|
|
*target_pos = player_pos
|
|
|
|
})?;
|
|
|
|
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
2019-04-16 15:38:01 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_spawn(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
match scan_fmt_some!(&args, &action.arg_fmt(), String, npc::NpcBody, u32, bool) {
|
2020-07-02 21:53:01 +00:00
|
|
|
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
|
2021-04-09 07:34:58 +00:00
|
|
|
let uid = uid(server, target, "target")?;
|
|
|
|
let alignment = parse_alignment(uid, &opt_align)?;
|
|
|
|
let amount = opt_amount.filter(|x| *x > 0).unwrap_or(1).min(50);
|
|
|
|
|
|
|
|
let ai = opt_ai.unwrap_or(true);
|
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let agent = if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
|
|
|
comp::Agent::default()
|
|
|
|
} else {
|
|
|
|
comp::Agent::default().with_patrol_origin(pos.0)
|
|
|
|
};
|
2020-07-02 21:53:01 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
for _ in 0..amount {
|
|
|
|
let vel = Vec3::new(
|
|
|
|
rand::thread_rng().gen_range(-2.0..3.0),
|
|
|
|
rand::thread_rng().gen_range(-2.0..3.0),
|
|
|
|
10.0,
|
|
|
|
);
|
2020-04-26 17:03:19 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let body = body();
|
|
|
|
let loadout = LoadoutBuilder::build_loadout(body, None, None, None).build();
|
|
|
|
let inventory = Inventory::new_with_loadout(loadout);
|
|
|
|
|
|
|
|
let mut entity_base = server
|
|
|
|
.state
|
|
|
|
.create_npc(
|
|
|
|
pos,
|
|
|
|
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
|
2021-04-14 15:35:34 +00:00
|
|
|
comp::SkillSet::default(),
|
2021-04-09 07:34:58 +00:00
|
|
|
comp::Health::new(body, 1),
|
|
|
|
comp::Poise::new(body),
|
|
|
|
inventory,
|
|
|
|
body,
|
|
|
|
)
|
|
|
|
.with(comp::Vel(vel))
|
|
|
|
.with(comp::MountState::Unmounted)
|
|
|
|
.with(alignment);
|
|
|
|
|
|
|
|
if ai {
|
|
|
|
entity_base = entity_base.with(agent.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
let new_entity = entity_base.build();
|
|
|
|
|
|
|
|
// Add to group system if a pet
|
|
|
|
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
|
|
|
let state = server.state();
|
|
|
|
let clients = state.ecs().read_storage::<Client>();
|
|
|
|
let uids = state.ecs().read_storage::<Uid>();
|
|
|
|
let mut group_manager =
|
|
|
|
state.ecs().write_resource::<comp::group::GroupManager>();
|
|
|
|
group_manager.new_pet(
|
|
|
|
new_entity,
|
|
|
|
target,
|
|
|
|
&mut state.ecs().write_storage(),
|
|
|
|
&state.ecs().entities(),
|
|
|
|
&state.ecs().read_storage(),
|
|
|
|
&uids,
|
|
|
|
&mut |entity, group_change| {
|
|
|
|
clients
|
|
|
|
.get(entity)
|
|
|
|
.and_then(|c| {
|
|
|
|
group_change
|
|
|
|
.try_map(|e| uids.get(e).copied())
|
|
|
|
.map(|g| (g, c))
|
|
|
|
})
|
|
|
|
.map(|(g, c)| {
|
|
|
|
c.send_fallible(ServerGeneral::GroupUpdate(g));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} else if let Some(group) = match alignment {
|
|
|
|
comp::Alignment::Wild => None,
|
|
|
|
comp::Alignment::Passive => None,
|
|
|
|
comp::Alignment::Enemy => Some(comp::group::ENEMY),
|
|
|
|
comp::Alignment::Npc | comp::Alignment::Tame => Some(comp::group::NPC),
|
|
|
|
comp::Alignment::Owned(_) => unreachable!(),
|
|
|
|
} {
|
|
|
|
insert_or_replace_component(server, new_entity, group, "new entity")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
|
|
|
server.notify_client(
|
2020-04-24 02:36:19 +00:00
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Spawned entity with ID: {}", uid),
|
|
|
|
),
|
|
|
|
);
|
2019-06-15 07:54:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-12 17:44:29 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Spawned {} entities", amount),
|
|
|
|
),
|
2020-06-12 17:44:29 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2021-04-09 07:34:58 +00:00
|
|
|
_ => Err(action.help_string()),
|
2019-05-11 12:43:19 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-31 03:01:52 +00:00
|
|
|
|
2020-07-02 21:53:01 +00:00
|
|
|
fn handle_spawn_training_dummy(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let vel = Vec3::new(
|
|
|
|
rand::thread_rng().gen_range(-2.0..3.0),
|
|
|
|
rand::thread_rng().gen_range(-2.0..3.0),
|
|
|
|
10.0,
|
|
|
|
);
|
2020-07-02 21:53:01 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let body = comp::Body::Object(comp::object::Body::TrainingDummy);
|
2020-07-02 21:53:01 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let stats = comp::Stats::new("Training Dummy".to_string());
|
2021-04-14 15:35:34 +00:00
|
|
|
let skill_set = comp::SkillSet::default();
|
2021-04-09 07:34:58 +00:00
|
|
|
let health = comp::Health::new(body, 0);
|
|
|
|
let poise = comp::Poise::new(body);
|
2020-10-31 22:34:08 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server
|
|
|
|
.state
|
2021-04-14 15:35:34 +00:00
|
|
|
.create_npc(
|
|
|
|
pos,
|
|
|
|
stats,
|
|
|
|
skill_set,
|
|
|
|
health,
|
|
|
|
poise,
|
|
|
|
Inventory::new_empty(),
|
|
|
|
body,
|
|
|
|
)
|
2021-04-09 07:34:58 +00:00
|
|
|
.with(comp::Vel(vel))
|
|
|
|
.with(comp::MountState::Unmounted)
|
|
|
|
.build();
|
2020-07-02 21:53:01 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a training dummy"),
|
|
|
|
);
|
|
|
|
Ok(())
|
2020-07-31 09:34:26 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 16:48:59 +00:00
|
|
|
fn handle_spawn_airship(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2021-03-12 02:58:57 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-12 18:53:06 +00:00
|
|
|
let angle = scan_fmt!(&args, &action.arg_fmt(), f32).ok();
|
2021-04-09 07:34:58 +00:00
|
|
|
let mut pos = position(server, target, "target")?;
|
|
|
|
pos.0.z += 50.0;
|
|
|
|
const DESTINATION_RADIUS: f32 = 2000.0;
|
|
|
|
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
|
|
|
|
let destination = angle.map(|a| {
|
|
|
|
pos.0
|
|
|
|
+ Vec3::new(
|
|
|
|
DESTINATION_RADIUS * a.cos(),
|
|
|
|
DESTINATION_RADIUS * a.sin(),
|
|
|
|
200.0,
|
|
|
|
)
|
|
|
|
});
|
2021-05-29 18:45:46 +00:00
|
|
|
let ship = comp::ship::Body::DefaultAirship;
|
2021-04-09 07:34:58 +00:00
|
|
|
let mut builder = server
|
|
|
|
.state
|
2021-05-29 18:45:46 +00:00
|
|
|
.create_ship(pos, ship, true)
|
2021-04-09 07:34:58 +00:00
|
|
|
.with(LightEmitter {
|
|
|
|
col: Rgb::new(1.0, 0.65, 0.2),
|
|
|
|
strength: 2.0,
|
|
|
|
flicker: 1.0,
|
|
|
|
animated: true,
|
|
|
|
});
|
|
|
|
if let Some(pos) = destination {
|
2021-05-29 18:45:46 +00:00
|
|
|
let (kp, ki, kd) = ship.pid_coefficients();
|
|
|
|
fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
|
|
|
|
let agent = comp::Agent::default()
|
|
|
|
.with_destination(pos)
|
|
|
|
.with_pid_controller(comp::PidController::new(
|
|
|
|
kp,
|
|
|
|
ki,
|
|
|
|
kd,
|
|
|
|
Vec3::zero(),
|
|
|
|
0.0,
|
|
|
|
pure_z,
|
|
|
|
));
|
|
|
|
builder = builder.with(agent);
|
2021-04-09 07:34:58 +00:00
|
|
|
}
|
|
|
|
builder.build();
|
2021-03-11 16:48:59 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned an airship"),
|
|
|
|
);
|
|
|
|
Ok(())
|
2021-03-11 16:48:59 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:34:26 +00:00
|
|
|
fn handle_spawn_campfire(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.create_object(pos, comp::object::Body::CampfireLit)
|
|
|
|
.with(LightEmitter {
|
|
|
|
col: Rgb::new(1.0, 0.65, 0.2),
|
|
|
|
strength: 2.0,
|
|
|
|
flicker: 1.0,
|
|
|
|
animated: true,
|
|
|
|
})
|
|
|
|
.with(WaypointArea::default())
|
2021-05-13 05:34:51 +00:00
|
|
|
.with(comp::Auras::new(vec![
|
|
|
|
Aura::new(
|
|
|
|
AuraKind::Buff {
|
|
|
|
kind: BuffKind::CampfireHeal,
|
|
|
|
data: BuffData::new(0.02, Some(Duration::from_secs(1))),
|
|
|
|
category: BuffCategory::Natural,
|
|
|
|
source: BuffSource::World,
|
|
|
|
},
|
|
|
|
5.0,
|
|
|
|
None,
|
|
|
|
AuraTarget::All,
|
|
|
|
),
|
|
|
|
Aura::new(
|
|
|
|
AuraKind::Buff {
|
|
|
|
kind: BuffKind::Burning,
|
|
|
|
data: BuffData::new(20.0, Some(Duration::from_secs(10))),
|
|
|
|
category: BuffCategory::Natural,
|
|
|
|
source: BuffSource::World,
|
|
|
|
},
|
|
|
|
0.7,
|
|
|
|
None,
|
|
|
|
AuraTarget::All,
|
|
|
|
),
|
|
|
|
]))
|
2021-04-09 07:34:58 +00:00
|
|
|
.build();
|
2020-07-31 09:34:26 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a campfire"),
|
|
|
|
);
|
|
|
|
Ok(())
|
2020-07-02 21:53:01 +00:00
|
|
|
}
|
|
|
|
|
2021-02-28 23:14:59 +00:00
|
|
|
fn handle_safezone(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-02-28 23:14:59 +00:00
|
|
|
let range = scan_fmt_some!(&args, &action.arg_fmt(), f32);
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
2021-04-17 21:33:54 +00:00
|
|
|
server.state.create_safezone(range, pos).build();
|
2021-02-28 23:14:59 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a safe zone"),
|
|
|
|
);
|
|
|
|
Ok(())
|
2021-02-28 23:14:59 +00:00
|
|
|
}
|
|
|
|
|
2021-03-23 08:37:29 +00:00
|
|
|
fn handle_permit_build(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2021-03-27 05:53:33 +00:00
|
|
|
target: EcsEntity,
|
2021-03-23 08:37:29 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-27 05:53:33 +00:00
|
|
|
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let bb_id = area(server, &area_name)?;
|
|
|
|
let mut can_build = server.state.ecs().write_storage::<comp::CanBuild>();
|
|
|
|
let entry = can_build
|
|
|
|
.entry(target)
|
|
|
|
.map_err(|_| "Cannot find target entity!".to_string())?;
|
|
|
|
let mut comp_can_build = entry.or_insert(comp::CanBuild {
|
|
|
|
enabled: false,
|
|
|
|
build_areas: HashSet::new(),
|
|
|
|
});
|
|
|
|
comp_can_build.build_areas.insert(bb_id);
|
|
|
|
drop(can_build);
|
|
|
|
if client != target {
|
|
|
|
server.notify_client(
|
|
|
|
target,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("You are now permitted to build in {}", area_name),
|
|
|
|
),
|
|
|
|
);
|
2021-03-24 07:58:42 +00:00
|
|
|
}
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Permission to build in {} granted", area_name),
|
|
|
|
),
|
2021-03-24 07:58:42 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2021-03-24 07:58:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_revoke_build(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2021-03-27 05:53:33 +00:00
|
|
|
target: EcsEntity,
|
2021-03-24 07:58:42 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-27 05:53:33 +00:00
|
|
|
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let bb_id = area(server, &area_name)?;
|
|
|
|
let mut can_build = server.state.ecs_mut().write_storage::<comp::CanBuild>();
|
|
|
|
if let Some(mut comp_can_build) = can_build.get_mut(target) {
|
|
|
|
comp_can_build.build_areas.retain(|&x| x != bb_id);
|
|
|
|
drop(can_build);
|
|
|
|
if client != target {
|
2021-03-27 05:53:33 +00:00
|
|
|
server.notify_client(
|
2021-04-09 07:34:58 +00:00
|
|
|
target,
|
2021-03-27 05:53:33 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
2021-04-09 07:34:58 +00:00
|
|
|
format!("Your permission to build in {} has been revoked", area_name),
|
2021-03-27 05:53:33 +00:00
|
|
|
),
|
|
|
|
);
|
2021-03-23 08:37:29 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Permission to build in {} revoked", area_name),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("You do not have permission to build.".into())
|
2021-03-23 08:37:29 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2021-03-23 08:37:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 07:58:42 +00:00
|
|
|
fn handle_revoke_build_all(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2021-03-27 05:53:33 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-27 05:53:33 +00:00
|
|
|
let ecs = server.state.ecs();
|
2021-03-24 07:58:42 +00:00
|
|
|
|
2021-03-27 05:53:33 +00:00
|
|
|
ecs.write_storage::<comp::CanBuild>().remove(target);
|
2021-04-09 07:34:58 +00:00
|
|
|
if client != target {
|
|
|
|
server.notify_client(
|
|
|
|
target,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
"Your build permissions have been revoked.",
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2021-03-27 05:53:33 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "All build permissions revoked"),
|
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2021-03-24 07:58:42 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_players(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2019-06-11 04:24:35 +00:00
|
|
|
let ecs = server.state.ecs();
|
2019-06-29 22:16:16 +00:00
|
|
|
|
2020-06-01 13:01:10 +00:00
|
|
|
let entity_tuples = (
|
|
|
|
&ecs.entities(),
|
|
|
|
&ecs.read_storage::<comp::Player>(),
|
|
|
|
&ecs.read_storage::<comp::Stats>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
entity_tuples.join().fold(
|
|
|
|
format!("{} online players:", entity_tuples.join().count()),
|
2021-01-03 04:44:54 +00:00
|
|
|
|s, (_, player, stat)| format!("{}\n[{}]{}", s, player.alias, stat.name,),
|
2020-12-13 17:40:15 +00:00
|
|
|
),
|
|
|
|
),
|
2020-06-01 13:01:10 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-06-11 04:24:35 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_build(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-23 08:37:29 +00:00
|
|
|
if let Some(mut can_build) = server
|
2019-08-15 14:33:00 +00:00
|
|
|
.state
|
2021-03-23 08:37:29 +00:00
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::CanBuild>()
|
|
|
|
.get_mut(target)
|
2019-08-15 14:33:00 +00:00
|
|
|
{
|
2021-04-09 07:34:58 +00:00
|
|
|
let toggle_string = if can_build.enabled { "off" } else { "on" };
|
|
|
|
let chat_msg = ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Toggled {:?} build mode!", toggle_string),
|
|
|
|
);
|
|
|
|
can_build.enabled ^= true;
|
|
|
|
if client != target {
|
|
|
|
server.notify_client(target, chat_msg.clone());
|
2021-03-23 08:37:29 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(client, chat_msg);
|
|
|
|
Ok(())
|
2019-08-14 15:51:59 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("You do not have permission to build.".into())
|
2019-07-02 18:19:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 07:58:42 +00:00
|
|
|
fn handle_build_area_add(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-24 07:58:42 +00:00
|
|
|
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) = scan_fmt_some!(
|
|
|
|
&args,
|
|
|
|
&action.arg_fmt(),
|
|
|
|
String,
|
|
|
|
i32,
|
|
|
|
i32,
|
|
|
|
i32,
|
|
|
|
i32,
|
|
|
|
i32,
|
|
|
|
i32
|
|
|
|
) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let build_areas = server.state.mut_resource::<BuildAreas>();
|
|
|
|
let msg = ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Created build zone {}", area_name),
|
|
|
|
);
|
|
|
|
build_areas
|
|
|
|
.insert(area_name, Aabb {
|
2021-03-27 14:16:26 +00:00
|
|
|
min: Vec3::new(xlo, ylo, zlo),
|
|
|
|
max: Vec3::new(xhi, yhi, zhi),
|
2021-04-09 07:34:58 +00:00
|
|
|
})
|
|
|
|
.map_err(|area_name| format!("Build zone {} already exists!", area_name))?;
|
|
|
|
server.notify_client(client, msg);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2021-03-24 07:58:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-27 05:53:33 +00:00
|
|
|
fn handle_build_area_list(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let build_areas = server.state.mut_resource::<BuildAreas>();
|
|
|
|
let msg = ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
build_areas.area_names().iter().fold(
|
|
|
|
"Build Areas:".to_string(),
|
|
|
|
|acc, (area_name, bb_id)| {
|
|
|
|
if let Some(aabb) = build_areas.areas().get(*bb_id) {
|
|
|
|
format!("{}\n{}: {} to {}", acc, area_name, aabb.min, aabb.max)
|
|
|
|
} else {
|
|
|
|
acc
|
|
|
|
}
|
|
|
|
},
|
2021-03-27 05:53:33 +00:00
|
|
|
),
|
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
|
|
|
|
server.notify_client(client, msg);
|
|
|
|
Ok(())
|
2021-03-27 05:53:33 +00:00
|
|
|
}
|
|
|
|
|
2021-03-24 07:58:42 +00:00
|
|
|
fn handle_build_area_remove(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2021-03-24 07:58:42 +00:00
|
|
|
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let build_areas = server.state.mut_resource::<BuildAreas>();
|
2021-03-24 07:58:42 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
build_areas.remove(&area_name).map_err(|err| match err {
|
|
|
|
BuildAreaError::Reserved => format!(
|
|
|
|
"Build area is reserved and cannot be removed: {}",
|
|
|
|
area_name
|
|
|
|
),
|
|
|
|
BuildAreaError::NotFound => format!("No such build area {}", area_name),
|
|
|
|
})?;
|
2021-03-24 07:58:42 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Removed build zone {}", area_name),
|
|
|
|
),
|
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2021-03-24 07:58:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_help(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
2020-05-05 22:33:16 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let Some(cmd) = scan_fmt_some!(&args, &action.arg_fmt(), ChatCommand) {
|
2020-12-13 17:40:15 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, cmd.help_string()),
|
2021-04-09 07:34:58 +00:00
|
|
|
)
|
2020-05-05 22:33:16 +00:00
|
|
|
} else {
|
2020-06-05 22:36:31 +00:00
|
|
|
let mut message = String::new();
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let entity_role = server.entity_admin_role(client);
|
|
|
|
|
|
|
|
// Iterate through all commands you have permission to use.
|
|
|
|
CHAT_COMMANDS
|
|
|
|
.iter()
|
|
|
|
.filter(|cmd| cmd.needs_role() <= entity_role)
|
|
|
|
.for_each(|cmd| {
|
2020-06-05 22:36:31 +00:00
|
|
|
message += &cmd.help_string();
|
|
|
|
message += "\n";
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
});
|
2020-06-05 22:36:31 +00:00
|
|
|
message += "Additionally, you can use the following shortcuts:";
|
|
|
|
for (k, v) in CHAT_SHORTCUTS.iter() {
|
|
|
|
message += &format!(" /{} => /{}", k, v.keyword());
|
|
|
|
}
|
2020-12-13 17:40:15 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, message),
|
2021-04-09 07:34:58 +00:00
|
|
|
)
|
2019-05-26 03:31:41 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-05-26 03:31:41 +00:00
|
|
|
}
|
2019-05-31 03:01:52 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
fn parse_alignment(owner: Uid, alignment: &str) -> CmdResult<comp::Alignment> {
|
2019-06-15 07:54:47 +00:00
|
|
|
match alignment {
|
2021-04-09 07:34:58 +00:00
|
|
|
"wild" => Ok(comp::Alignment::Wild),
|
|
|
|
"enemy" => Ok(comp::Alignment::Enemy),
|
|
|
|
"npc" => Ok(comp::Alignment::Npc),
|
|
|
|
"pet" => Ok(comp::Alignment::Owned(owner)),
|
|
|
|
_ => Err(format!("Invalid alignment: {:?}", alignment)),
|
2019-05-27 11:18:14 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-31 03:01:52 +00:00
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
fn handle_kill_npcs(
|
2020-04-24 02:36:19 +00:00
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2019-08-15 14:33:00 +00:00
|
|
|
let ecs = server.state.ecs();
|
2020-10-31 22:34:08 +00:00
|
|
|
let mut healths = ecs.write_storage::<comp::Health>();
|
2019-08-15 14:33:00 +00:00
|
|
|
let players = ecs.read_storage::<comp::Player>();
|
|
|
|
let mut count = 0;
|
2021-01-07 20:25:12 +00:00
|
|
|
for (mut health, ()) in (&mut healths, !&players).join() {
|
2019-08-15 14:33:00 +00:00
|
|
|
count += 1;
|
2020-10-31 22:34:08 +00:00
|
|
|
health.set_to(0, comp::HealthSource::Command);
|
2019-07-12 21:16:07 +00:00
|
|
|
}
|
2019-08-15 14:33:00 +00:00
|
|
|
let text = if count > 0 {
|
|
|
|
format!("Destroyed {} NPCs.", count)
|
|
|
|
} else {
|
|
|
|
"No NPCs on server.".to_string()
|
|
|
|
};
|
2020-12-13 17:40:15 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, text),
|
|
|
|
);
|
2021-04-12 16:49:08 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-07-12 21:16:07 +00:00
|
|
|
}
|
2019-07-17 17:53:10 +00:00
|
|
|
|
2021-04-12 16:49:08 +00:00
|
|
|
fn handle_kit(
|
|
|
|
server: &mut Server,
|
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
let kit_name = scan_fmt!(&args, &action.arg_fmt(), String);
|
|
|
|
if let Ok(name) = kit_name {
|
2021-04-12 18:07:34 +00:00
|
|
|
if let Ok(kits) = common::cmd::KitManifest::load("server.manifests.kits") {
|
2021-04-12 16:49:08 +00:00
|
|
|
let kits = kits.read();
|
|
|
|
if let Some(kit) = kits.0.get(&name) {
|
2021-04-12 18:07:34 +00:00
|
|
|
if let (Some(mut target_inventory), mut target_inv_update) = (
|
|
|
|
server
|
|
|
|
.state()
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(target),
|
|
|
|
server.state.ecs().write_storage::<comp::InventoryUpdate>(),
|
|
|
|
) {
|
|
|
|
for (item_id, quantity) in kit.iter() {
|
|
|
|
if let Ok(mut item) = comp::Item::new_from_asset(item_id) {
|
|
|
|
let _ = item.set_amount(*quantity);
|
|
|
|
let (_, _) = (
|
|
|
|
target_inventory.push(item),
|
|
|
|
target_inv_update.insert(
|
|
|
|
target,
|
|
|
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug),
|
|
|
|
),
|
2021-04-12 16:49:08 +00:00
|
|
|
);
|
2021-04-12 18:07:34 +00:00
|
|
|
}
|
2021-04-12 16:49:08 +00:00
|
|
|
}
|
2021-04-12 18:07:34 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Could not get inventory".to_string())
|
2021-04-12 16:49:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(format!("Kit '{}' not found", name))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err("Could not load manifest file 'server.manifests.kits'".to_string())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
|
|
|
#[allow(clippy::needless_return)] // TODO: Pending review in #587
|
|
|
|
#[allow(clippy::useless_format)] // TODO: Pending review in #587
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_object(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
2020-05-05 22:33:16 +00:00
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
let obj_type = scan_fmt!(&args, &action.arg_fmt(), String);
|
2019-07-28 09:21:17 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
2019-08-15 14:33:00 +00:00
|
|
|
let ori = server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Ori>()
|
2020-04-24 02:36:19 +00:00
|
|
|
.get(target)
|
2021-04-09 07:34:58 +00:00
|
|
|
.copied()
|
|
|
|
.ok_or_else(|| "Cannot get orientation for target".to_string())?;
|
2020-03-10 02:27:32 +00:00
|
|
|
/*let builder = server.state
|
2019-08-15 14:33:00 +00:00
|
|
|
.create_object(pos, ori, obj_type)
|
|
|
|
.with(ori);*/
|
2021-04-09 07:34:58 +00:00
|
|
|
let obj_str_res = obj_type.as_ref().map(String::as_str);
|
|
|
|
if let Some(obj_type) = comp::object::ALL_OBJECTS
|
|
|
|
.iter()
|
|
|
|
.find(|o| Ok(o.to_string()) == obj_str_res)
|
|
|
|
{
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.create_object(pos, *obj_type)
|
|
|
|
.with(
|
|
|
|
comp::Ori::from_unnormalized_vec(
|
|
|
|
// converts player orientation into a 90° rotation for the object by using
|
|
|
|
// the axis with the highest value
|
|
|
|
{
|
|
|
|
let look_dir = ori.look_dir();
|
|
|
|
look_dir.map(|e| {
|
|
|
|
if e.abs() == look_dir.map(|e| e.abs()).reduce_partial_max() {
|
|
|
|
e
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
2021-02-04 09:17:38 +00:00
|
|
|
)
|
2021-04-09 07:34:58 +00:00
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
.build();
|
2020-06-12 07:43:20 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Spawned: {}", obj_str_res.unwrap_or("<Unknown object>")),
|
|
|
|
),
|
2020-06-12 07:43:20 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Object not found!".into())
|
2019-07-21 12:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::useless_format)] // TODO: Pending review in #587
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_light(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2019-08-15 14:33:00 +00:00
|
|
|
let (opt_r, opt_g, opt_b, opt_x, opt_y, opt_z, opt_s) =
|
2020-05-05 22:33:16 +00:00
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32, f32, f32, f32);
|
2019-08-15 14:33:00 +00:00
|
|
|
|
|
|
|
let mut light_emitter = comp::LightEmitter::default();
|
2020-05-04 15:15:31 +00:00
|
|
|
let mut light_offset_opt = None;
|
2019-08-15 14:33:00 +00:00
|
|
|
|
|
|
|
if let (Some(r), Some(g), Some(b)) = (opt_r, opt_g, opt_b) {
|
2020-05-04 09:50:58 +00:00
|
|
|
if r < 0.0 || g < 0.0 || b < 0.0 {
|
2021-04-09 07:34:58 +00:00
|
|
|
return Err("cr, cg and cb values mustn't be negative.".into());
|
2020-05-04 09:50:58 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 14:33:00 +00:00
|
|
|
let r = r.max(0.0).min(1.0);
|
|
|
|
let g = g.max(0.0).min(1.0);
|
|
|
|
let b = b.max(0.0).min(1.0);
|
|
|
|
light_emitter.col = Rgb::new(r, g, b)
|
|
|
|
};
|
|
|
|
if let (Some(x), Some(y), Some(z)) = (opt_x, opt_y, opt_z) {
|
2020-05-04 15:15:31 +00:00
|
|
|
light_offset_opt = Some(comp::LightAnimation {
|
|
|
|
offset: Vec3::new(x, y, z),
|
|
|
|
col: light_emitter.col,
|
|
|
|
strength: 0.0,
|
|
|
|
})
|
2019-08-15 14:33:00 +00:00
|
|
|
};
|
|
|
|
if let Some(s) = opt_s {
|
|
|
|
light_emitter.strength = s.max(0.0)
|
|
|
|
};
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let builder = server
|
2019-08-15 14:33:00 +00:00
|
|
|
.state
|
2021-04-09 07:34:58 +00:00
|
|
|
.ecs_mut()
|
|
|
|
.create_entity_synced()
|
|
|
|
.with(pos)
|
|
|
|
.with(comp::ForceUpdate)
|
|
|
|
.with(light_emitter);
|
|
|
|
if let Some(light_offset) = light_offset_opt {
|
|
|
|
builder.with(light_offset).build();
|
2019-08-15 14:33:00 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
builder.build();
|
2019-07-25 20:51:20 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned object."),
|
|
|
|
);
|
|
|
|
Ok(())
|
2019-07-25 20:51:20 +00:00
|
|
|
}
|
2019-07-26 14:38:31 +00:00
|
|
|
|
2020-06-23 06:52:04 +00:00
|
|
|
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_lantern(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
if let (Some(s), r, g, b) = scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32) {
|
2021-01-07 20:25:12 +00:00
|
|
|
if let Some(mut light) = server
|
2020-05-04 15:15:31 +00:00
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::LightEmitter>()
|
|
|
|
.get_mut(target)
|
|
|
|
{
|
|
|
|
light.strength = s.max(0.1).min(10.0);
|
|
|
|
if let (Some(r), Some(g), Some(b)) = (r, g, b) {
|
|
|
|
light.col = (
|
|
|
|
r.max(0.0).min(1.0),
|
|
|
|
g.max(0.0).min(1.0),
|
|
|
|
b.max(0.0).min(1.0),
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
"You adjusted flame strength and color.",
|
|
|
|
),
|
2021-04-09 07:34:58 +00:00
|
|
|
)
|
2020-05-04 15:15:31 +00:00
|
|
|
} else {
|
2019-10-15 04:06:14 +00:00
|
|
|
server.notify_client(
|
2020-04-24 02:36:19 +00:00
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
"You adjusted flame strength.",
|
|
|
|
),
|
2021-04-09 07:34:58 +00:00
|
|
|
)
|
2019-07-28 11:36:35 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-07-28 11:36:35 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("Please equip a lantern first".into())
|
2019-07-28 11:36:35 +00:00
|
|
|
}
|
2019-08-12 14:05:58 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-08-12 14:05:58 +00:00
|
|
|
}
|
2019-07-25 20:51:20 +00:00
|
|
|
}
|
2019-07-26 14:38:31 +00:00
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_explosion(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
let power = scan_fmt!(&args, &action.arg_fmt(), f32).unwrap_or(8.0);
|
2020-05-04 09:50:58 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
const MIN_POWER: f32 = 0.0;
|
|
|
|
const MAX_POWER: f32 = 512.0;
|
|
|
|
|
|
|
|
if power > MAX_POWER {
|
|
|
|
return Err(format!(
|
|
|
|
"Explosion power mustn't be more than {:?}.",
|
|
|
|
MAX_POWER
|
|
|
|
));
|
2020-05-04 09:50:58 +00:00
|
|
|
} else if power <= 0.0 {
|
2021-04-09 07:34:58 +00:00
|
|
|
return Err(format!(
|
|
|
|
"Explosion power must be more than {:?}.",
|
|
|
|
MIN_POWER
|
|
|
|
));
|
2020-05-04 09:50:58 +00:00
|
|
|
}
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let owner = server
|
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<Uid>()
|
|
|
|
.get(target)
|
|
|
|
.copied();
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
|
|
|
.emit_now(ServerEvent::Explosion {
|
|
|
|
pos: pos.0,
|
|
|
|
explosion: Explosion {
|
|
|
|
effects: vec![
|
|
|
|
RadiusEffect::Entity(Effect::Damage(Damage {
|
|
|
|
source: DamageSource::Explosion,
|
2021-05-06 18:50:16 +00:00
|
|
|
kind: DamageKind::Energy,
|
2021-04-09 07:34:58 +00:00
|
|
|
value: 100.0 * power,
|
|
|
|
})),
|
|
|
|
RadiusEffect::TerrainDestruction(power),
|
|
|
|
],
|
|
|
|
radius: 3.0 * power,
|
|
|
|
reagent: None,
|
|
|
|
},
|
|
|
|
owner,
|
|
|
|
});
|
|
|
|
Ok(())
|
2019-09-25 20:22:39 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_waypoint(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
let time = *server.state.mut_resource::<common::resources::Time>();
|
|
|
|
insert_or_replace_component(
|
|
|
|
server,
|
|
|
|
target,
|
|
|
|
comp::Waypoint::temp_new(pos.0, time),
|
|
|
|
"target",
|
|
|
|
)?;
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Waypoint saved!"),
|
|
|
|
);
|
|
|
|
server.notify_client(
|
|
|
|
target,
|
|
|
|
ServerGeneral::Notification(Notification::WaypointSaved),
|
|
|
|
);
|
|
|
|
Ok(())
|
2019-08-07 17:17:04 +00:00
|
|
|
}
|
|
|
|
|
2021-04-15 16:28:16 +00:00
|
|
|
fn handle_spawn_wiring(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
// Obviously it is a WIP - use it for debug
|
|
|
|
|
|
|
|
let mut pos = position(server, target, "target")?;
|
|
|
|
pos.0.x += 3.0;
|
|
|
|
|
|
|
|
let mut outputs1 = HashMap::new();
|
2021-05-06 16:50:15 +00:00
|
|
|
outputs1.insert(
|
|
|
|
"deaths_last_tick".to_string(),
|
|
|
|
wiring::OutputFormula::OnDeath {
|
|
|
|
value: 1.0,
|
|
|
|
radius: 30.0,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
outputs1.insert(
|
|
|
|
"deaths_accumulated".to_string(),
|
|
|
|
OutputFormula::Logic(Box::new(Logic {
|
|
|
|
kind: wiring::LogicKind::Sum,
|
|
|
|
left: OutputFormula::Logic(Box::new(Logic {
|
|
|
|
kind: wiring::LogicKind::Sub,
|
|
|
|
left: OutputFormula::Input {
|
|
|
|
name: "deaths_accumulated".to_string(),
|
|
|
|
},
|
|
|
|
right: OutputFormula::Logic(Box::new(Logic {
|
|
|
|
kind: wiring::LogicKind::Min,
|
|
|
|
left: OutputFormula::Input {
|
|
|
|
name: "pressed".to_string(),
|
|
|
|
},
|
|
|
|
right: OutputFormula::Input {
|
|
|
|
name: "deaths_accumulated".to_string(),
|
|
|
|
},
|
|
|
|
})),
|
|
|
|
})),
|
|
|
|
right: OutputFormula::Input {
|
|
|
|
name: "deaths_last_tick".to_string(),
|
|
|
|
},
|
|
|
|
})),
|
|
|
|
);
|
|
|
|
outputs1.insert("pressed".to_string(), OutputFormula::OnCollide {
|
|
|
|
value: f32::MAX,
|
2021-04-15 16:28:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
let builder1 = server
|
|
|
|
.state
|
|
|
|
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
|
|
|
|
actions: vec![WiringAction {
|
|
|
|
formula: wiring::OutputFormula::Constant { value: 1.0 },
|
|
|
|
threshold: 1.0,
|
|
|
|
effects: vec![WiringActionEffect::SetLight {
|
|
|
|
r: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
g: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
b: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
}],
|
|
|
|
inputs: HashMap::new(),
|
|
|
|
outputs: outputs1,
|
2021-05-03 07:50:24 +00:00
|
|
|
})
|
|
|
|
.with(comp::Sticky);
|
2021-04-15 16:28:16 +00:00
|
|
|
let ent1 = builder1.build();
|
|
|
|
|
|
|
|
pos.0.x += 3.0;
|
|
|
|
let builder2 = server
|
|
|
|
.state
|
|
|
|
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
|
2021-05-05 14:13:38 +00:00
|
|
|
actions: vec![
|
|
|
|
WiringAction {
|
|
|
|
formula: wiring::OutputFormula::Input {
|
2021-05-06 16:50:15 +00:00
|
|
|
name: String::from("deaths_accumulated"),
|
2021-05-05 14:13:38 +00:00
|
|
|
},
|
2021-05-06 16:50:15 +00:00
|
|
|
threshold: 5.0,
|
2021-05-05 14:13:38 +00:00
|
|
|
effects: vec![WiringActionEffect::SpawnProjectile {
|
2021-04-15 16:28:16 +00:00
|
|
|
constr: comp::ProjectileConstructor::Arrow {
|
|
|
|
damage: 1.0,
|
|
|
|
energy_regen: 0.0,
|
|
|
|
knockback: 0.0,
|
|
|
|
},
|
2021-05-05 14:13:38 +00:00
|
|
|
}],
|
|
|
|
},
|
|
|
|
WiringAction {
|
|
|
|
formula: wiring::OutputFormula::Constant { value: 1.0 },
|
|
|
|
threshold: 1.0,
|
|
|
|
effects: vec![WiringActionEffect::SetLight {
|
|
|
|
r: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
g: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
b: wiring::OutputFormula::Input {
|
|
|
|
name: String::from("color"),
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
},
|
|
|
|
],
|
2021-04-15 16:28:16 +00:00
|
|
|
inputs: HashMap::new(),
|
|
|
|
outputs: HashMap::new(),
|
|
|
|
});
|
|
|
|
let ent2 = builder2.build();
|
|
|
|
|
|
|
|
pos.0.x += 3.0;
|
|
|
|
let builder3 = server
|
|
|
|
.state
|
|
|
|
.create_wiring(pos, comp::object::Body::TrainingDummy, WiringElement {
|
|
|
|
actions: vec![],
|
|
|
|
inputs: HashMap::new(),
|
|
|
|
outputs: HashMap::new(),
|
|
|
|
})
|
|
|
|
.with(Circuit {
|
2021-05-06 16:50:15 +00:00
|
|
|
wires: vec![
|
|
|
|
Wire {
|
|
|
|
input_entity: ent1,
|
|
|
|
input_field: String::from("deaths_last_tick"),
|
|
|
|
output_entity: ent1,
|
|
|
|
output_field: String::from("deaths_last_tick"),
|
|
|
|
},
|
|
|
|
Wire {
|
|
|
|
input_entity: ent1,
|
|
|
|
input_field: String::from("deaths_accumulated"),
|
|
|
|
output_entity: ent1,
|
|
|
|
output_field: String::from("deaths_accumulated"),
|
|
|
|
},
|
|
|
|
Wire {
|
|
|
|
input_entity: ent1,
|
|
|
|
input_field: String::from("pressed"),
|
|
|
|
output_entity: ent1,
|
|
|
|
output_field: String::from("pressed"),
|
|
|
|
},
|
|
|
|
Wire {
|
|
|
|
input_entity: ent1,
|
|
|
|
input_field: String::from("deaths_accumulated"),
|
|
|
|
output_entity: ent2,
|
|
|
|
output_field: String::from("deaths_accumulated"),
|
|
|
|
},
|
|
|
|
],
|
2021-04-15 16:28:16 +00:00
|
|
|
});
|
|
|
|
builder3.build();
|
|
|
|
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, "Wire"),
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-23 06:52:04 +00:00
|
|
|
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_adminify(
|
|
|
|
server: &mut Server,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
if let (Some(alias), desired_role) = scan_fmt_some!(&args, &action.arg_fmt(), String, String) {
|
|
|
|
let desired_role = if let Some(mut desired_role) = desired_role {
|
|
|
|
desired_role.make_ascii_lowercase();
|
|
|
|
Some(match &*desired_role {
|
|
|
|
"admin" => AdminRole::Admin,
|
|
|
|
"moderator" => AdminRole::Moderator,
|
|
|
|
_ => {
|
|
|
|
return Err(action.help_string());
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let (player, player_uuid) = find_alias(server.state.ecs(), &alias)?;
|
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
2021-04-09 07:34:58 +00:00
|
|
|
let uid = uid(server, player, "player")?;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
|
|
|
|
// Your permanent role, not your temporary role, is what's used to determine
|
|
|
|
// what temporary roles you can grant.
|
|
|
|
let client_real_role = real_role(server, client_uuid, "client")?;
|
|
|
|
|
|
|
|
// This appears to prevent de-mod / de-admin for mods / admins with access to
|
|
|
|
// this command, but it does not in the case where the target is
|
|
|
|
// temporary, because `verify_above_role` always values permanent roles
|
|
|
|
// above temporary ones.
|
|
|
|
verify_above_role(
|
2021-04-09 07:34:58 +00:00
|
|
|
server,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
(client, client_uuid),
|
|
|
|
(player, player_uuid),
|
|
|
|
"Cannot reassign a role for anyone with your role or higher.",
|
2021-04-09 07:34:58 +00:00
|
|
|
)?;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
|
|
|
|
// Ensure that it's not possible to assign someone a higher role than your own
|
|
|
|
// (i.e. even if mods had the ability to create temporary mods, they
|
|
|
|
// wouldn't be able to create temporary admins).
|
|
|
|
//
|
|
|
|
// Also note that we perform no more permissions checks after this point based
|
|
|
|
// on the assignee's temporary role--even if the player's temporary role
|
|
|
|
// is higher than the client's, we still allow the role to be reduced to
|
|
|
|
// the selected role, as long as they would have permission to assign it
|
|
|
|
// in the first place. This is consistent with our
|
|
|
|
// policy on bans--banning or lengthening a ban (decreasing player permissions)
|
|
|
|
// can be done even after an unban or ban shortening (increasing player
|
|
|
|
// permissions) by someone with a higher role than the person doing the
|
|
|
|
// ban. So if we change how bans work, we should change how things work
|
|
|
|
// here, too, for consistency.
|
|
|
|
if desired_role > Some(client_real_role.into()) {
|
|
|
|
return Err(
|
|
|
|
"Cannot assign someone a temporary role higher than your own permanent one".into(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut admin_storage = server.state.ecs().write_storage::<comp::Admin>();
|
|
|
|
let entry = admin_storage
|
|
|
|
.entry(player)
|
|
|
|
.map_err(|_| "Cannot find player entity!".to_string())?;
|
|
|
|
match (entry, desired_role) {
|
|
|
|
(StorageEntry::Vacant(_), None) => {
|
|
|
|
return Err("Player already has no role!".into());
|
|
|
|
},
|
|
|
|
(StorageEntry::Occupied(o), None) => {
|
|
|
|
let old_role = o.remove().0;
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Role removed from player {}: {:?}", alias, old_role),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
(entry, Some(desired_role)) => {
|
|
|
|
let verb = match entry
|
|
|
|
.replace(comp::Admin(desired_role))
|
|
|
|
.map(|old_admin| old_admin.0.cmp(&desired_role))
|
|
|
|
{
|
|
|
|
Some(Ordering::Equal) => {
|
|
|
|
return Err("Player already has that role!".into());
|
|
|
|
},
|
|
|
|
Some(Ordering::Greater) => "downgraded",
|
|
|
|
Some(Ordering::Less) | None => "upgraded",
|
|
|
|
};
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Role for player {} {} to {:?}", alias, verb, desired_role),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
2021-04-09 07:34:58 +00:00
|
|
|
};
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
// Update player list so the player shows up as moderator in client chat.
|
|
|
|
//
|
|
|
|
// NOTE: We deliberately choose not to differentiate between moderators and
|
|
|
|
// administrators in the player list.
|
|
|
|
let is_moderator = desired_role.is_some();
|
|
|
|
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Moderator(uid, is_moderator));
|
2021-04-09 07:34:58 +00:00
|
|
|
server.state.notify_players(msg);
|
|
|
|
Ok(())
|
2019-08-17 18:16:58 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-08-17 18:16:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 06:52:04 +00:00
|
|
|
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::useless_format)] // TODO: Pending review in #587
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_tell(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
2020-06-12 07:43:20 +00:00
|
|
|
if let (Some(alias), message_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, String) {
|
2019-07-30 08:10:58 +00:00
|
|
|
let ecs = server.state.ecs();
|
2021-04-09 07:34:58 +00:00
|
|
|
let player = find_alias(ecs, &alias)?.0;
|
|
|
|
|
|
|
|
if player == target {
|
|
|
|
return Err("You can't /tell yourself.".into());
|
2019-07-13 04:25:44 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
let target_uid = uid(server, target, "target")?;
|
|
|
|
let player_uid = uid(server, player, "player")?;
|
|
|
|
let mode = comp::ChatMode::Tell(player_uid);
|
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
|
|
|
let msg = message_opt.unwrap_or_else(|| format!("{} wants to talk to you.", alias));
|
|
|
|
server.state.send_chat(mode.new_message(target_uid, msg));
|
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2019-07-30 08:10:58 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2019-07-13 04:25:44 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-26 09:49:14 +00:00
|
|
|
|
2020-06-01 04:33:39 +00:00
|
|
|
fn handle_faction(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
msg: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
|
|
|
let factions = server.state.ecs().read_storage();
|
|
|
|
if let Some(comp::Faction(faction)) = factions.get(target) {
|
2020-06-04 09:40:05 +00:00
|
|
|
let mode = comp::ChatMode::Faction(faction.to_string());
|
2021-04-09 07:34:58 +00:00
|
|
|
drop(factions);
|
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2020-06-04 09:40:05 +00:00
|
|
|
if !msg.is_empty() {
|
2021-04-09 07:34:58 +00:00
|
|
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
2020-06-24 06:29:39 +00:00
|
|
|
server.state.send_chat(mode.new_message(*uid, msg));
|
2020-06-04 09:40:05 +00:00
|
|
|
}
|
2020-06-02 02:42:26 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-04 09:40:05 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("Please join a faction with /join_faction".into())
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_group(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
msg: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
|
|
|
let groups = server.state.ecs().read_storage::<comp::Group>();
|
|
|
|
if let Some(group) = groups.get(target) {
|
2020-07-12 20:18:57 +00:00
|
|
|
let mode = comp::ChatMode::Group(*group);
|
2021-04-09 07:34:58 +00:00
|
|
|
drop(groups);
|
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2020-06-04 09:40:05 +00:00
|
|
|
if !msg.is_empty() {
|
2021-04-09 07:34:58 +00:00
|
|
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
2020-06-24 06:29:39 +00:00
|
|
|
server.state.send_chat(mode.new_message(*uid, msg));
|
2020-06-04 09:40:05 +00:00
|
|
|
}
|
2020-06-02 02:42:26 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-04 09:40:05 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("Please create a group first".into())
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 02:18:42 +00:00
|
|
|
fn handle_group_invite(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2021-04-09 07:34:58 +00:00
|
|
|
target: EcsEntity,
|
2020-12-04 02:18:42 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-12-04 02:18:42 +00:00
|
|
|
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
|
|
|
|
let uid = uid(server, target_player, "player")?;
|
2020-12-04 02:18:42 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
|
|
|
.emit_now(ServerEvent::InitiateInvite(target, uid, InviteKind::Group));
|
2020-12-04 02:18:42 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
if client != target {
|
2020-12-04 02:18:42 +00:00
|
|
|
server.notify_client(
|
2021-04-09 07:34:58 +00:00
|
|
|
target,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
2021-04-09 07:34:58 +00:00
|
|
|
format!("{} has been invited to your group.", target_alias),
|
2020-12-13 17:40:15 +00:00
|
|
|
),
|
2020-12-04 02:18:42 +00:00
|
|
|
);
|
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
|
2020-12-04 02:18:42 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!("Invited {} to the group.", target_alias),
|
|
|
|
),
|
2020-12-04 02:18:42 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2020-12-04 02:18:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_group_kick(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2020-12-04 02:18:42 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-12-04 02:18:42 +00:00
|
|
|
// Checking if leader is already done in group_manip
|
|
|
|
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
|
|
|
|
let uid = uid(server, target_player, "player")?;
|
|
|
|
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
|
|
|
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Kick(uid)));
|
|
|
|
Ok(())
|
2020-12-04 02:18:42 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-12-04 02:18:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_group_leave(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2020-12-04 02:18:42 +00:00
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-12-04 02:18:42 +00:00
|
|
|
server
|
|
|
|
.state
|
2021-04-09 07:34:58 +00:00
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
|
|
|
.emit_now(ServerEvent::GroupManip(target, comp::GroupManip::Leave));
|
|
|
|
Ok(())
|
2020-12-04 02:18:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_group_promote(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2020-12-04 02:18:42 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-12-04 02:18:42 +00:00
|
|
|
// Checking if leader is already done in group_manip
|
|
|
|
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
2021-04-09 07:34:58 +00:00
|
|
|
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
|
|
|
|
let uid = uid(server, target_player, "player")?;
|
|
|
|
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
|
|
|
.emit_now(ServerEvent::GroupManip(
|
|
|
|
target,
|
|
|
|
comp::GroupManip::AssignLeader(uid),
|
|
|
|
));
|
|
|
|
Ok(())
|
2020-12-04 02:18:42 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-12-04 02:18:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 04:33:39 +00:00
|
|
|
fn handle_region(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
msg: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
2020-06-02 02:42:26 +00:00
|
|
|
let mode = comp::ChatMode::Region;
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2020-06-01 04:33:39 +00:00
|
|
|
if !msg.is_empty() {
|
2021-04-09 07:34:58 +00:00
|
|
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
2020-06-24 06:29:39 +00:00
|
|
|
server.state.send_chat(mode.new_message(*uid, msg));
|
2020-06-02 02:42:26 +00:00
|
|
|
}
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_say(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
msg: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
2020-06-02 02:42:26 +00:00
|
|
|
let mode = comp::ChatMode::Say;
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2020-06-01 04:33:39 +00:00
|
|
|
if !msg.is_empty() {
|
2021-04-09 07:34:58 +00:00
|
|
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
2020-06-24 06:29:39 +00:00
|
|
|
server.state.send_chat(mode.new_message(*uid, msg));
|
2020-06-02 02:42:26 +00:00
|
|
|
}
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_world(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
msg: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
no_sudo(client, target)?;
|
|
|
|
|
2020-06-02 02:42:26 +00:00
|
|
|
let mode = comp::ChatMode::World;
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2020-06-01 04:33:39 +00:00
|
|
|
if !msg.is_empty() {
|
2021-04-09 07:34:58 +00:00
|
|
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
2020-06-24 06:29:39 +00:00
|
|
|
server.state.send_chat(mode.new_message(*uid, msg));
|
2020-06-02 02:42:26 +00:00
|
|
|
}
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-01 04:33:39 +00:00
|
|
|
}
|
|
|
|
|
2020-06-04 09:40:05 +00:00
|
|
|
fn handle_join_faction(
|
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-06-04 09:40:05 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
let players = server.state.ecs().read_storage::<comp::Player>();
|
|
|
|
if let Some(alias) = players.get(target).map(|player| player.alias.clone()) {
|
|
|
|
drop(players);
|
2021-02-10 19:42:59 +00:00
|
|
|
let (faction_leave, mode) = if let Ok(faction) = scan_fmt!(&args, &action.arg_fmt(), String)
|
|
|
|
{
|
2020-06-12 07:43:20 +00:00
|
|
|
let mode = comp::ChatMode::Faction(faction.clone());
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
|
|
|
let faction_join = server
|
2021-02-10 19:42:59 +00:00
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
2021-04-09 07:34:58 +00:00
|
|
|
.insert(target, comp::Faction(faction.clone()))
|
2020-06-12 22:37:15 +00:00
|
|
|
.ok()
|
|
|
|
.flatten()
|
|
|
|
.map(|f| f.0);
|
2020-06-12 17:44:29 +00:00
|
|
|
server.state.send_chat(
|
|
|
|
ChatType::FactionMeta(faction.clone())
|
|
|
|
.chat_msg(format!("[{}] joined faction ({})", alias, faction)),
|
2020-06-04 09:40:05 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
(faction_join, mode)
|
2020-06-12 07:43:20 +00:00
|
|
|
} else {
|
|
|
|
let mode = comp::ChatMode::default();
|
2021-04-09 07:34:58 +00:00
|
|
|
insert_or_replace_component(server, target, mode.clone(), "target")?;
|
2021-02-10 19:42:59 +00:00
|
|
|
let faction_leave = server
|
2020-06-12 22:37:15 +00:00
|
|
|
.state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
2021-04-09 07:34:58 +00:00
|
|
|
.remove(target)
|
2021-02-10 19:42:59 +00:00
|
|
|
.map(|comp::Faction(f)| f);
|
|
|
|
(faction_leave, mode)
|
2020-06-12 22:37:15 +00:00
|
|
|
};
|
|
|
|
if let Some(faction) = faction_leave {
|
|
|
|
server.state.send_chat(
|
|
|
|
ChatType::FactionMeta(faction.clone())
|
|
|
|
.chat_msg(format!("[{}] left faction ({})", alias, faction)),
|
|
|
|
);
|
2020-06-04 09:40:05 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
|
|
|
Ok(())
|
2020-06-12 07:43:20 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("Could not find your player alias".into())
|
2020-06-04 09:40:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-22 03:12:17 +00:00
|
|
|
#[cfg(not(feature = "worldgen"))]
|
|
|
|
fn handle_debug_column(
|
|
|
|
server: &mut Server,
|
2020-04-24 02:36:19 +00:00
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2020-01-22 03:12:17 +00:00
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
Err("Unsupported without worldgen enabled".into())
|
2020-01-22 03:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "worldgen")]
|
2020-04-24 02:36:19 +00:00
|
|
|
fn handle_debug_column(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2020-10-14 21:02:58 +00:00
|
|
|
target: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2019-08-26 09:49:14 +00:00
|
|
|
let sim = server.world.sim();
|
2019-10-16 11:39:41 +00:00
|
|
|
let sampler = server.world.sample_columns();
|
2021-04-09 07:34:58 +00:00
|
|
|
let wpos = if let Ok((x, y)) = scan_fmt!(&args, &action.arg_fmt(), i32, i32) {
|
|
|
|
Vec2::new(x, y)
|
2020-11-21 12:33:52 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
let pos = position(server, target, "target")?;
|
|
|
|
// FIXME: Deal with overflow, if needed.
|
|
|
|
pos.0.xy().map(|x| x as i32)
|
|
|
|
};
|
2020-10-14 21:02:58 +00:00
|
|
|
let msg_generator = || {
|
|
|
|
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?;
|
|
|
|
let basement = sim.get_interpolated(wpos, |chunk| chunk.basement)?;
|
|
|
|
let water_alt = sim.get_interpolated(wpos, |chunk| chunk.water_alt)?;
|
|
|
|
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
|
|
|
|
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
|
|
|
|
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
|
|
|
|
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
|
|
|
|
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
|
|
|
|
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
|
|
|
|
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
|
|
|
|
let chunk = sim.get(chunk_pos)?;
|
|
|
|
let col = sampler.get((wpos, server.index.as_index_ref()))?;
|
2020-10-24 17:57:46 +00:00
|
|
|
let gradient = sim.get_gradient_approx(chunk_pos)?;
|
2020-10-14 21:02:58 +00:00
|
|
|
let downhill = chunk.downhill;
|
|
|
|
let river = &chunk.river;
|
|
|
|
let flux = chunk.flux;
|
|
|
|
|
|
|
|
Some(format!(
|
|
|
|
r#"wpos: {:?}
|
2019-10-16 11:39:41 +00:00
|
|
|
alt {:?} ({:?})
|
|
|
|
water_alt {:?} ({:?})
|
2019-11-19 18:34:52 +00:00
|
|
|
basement {:?}
|
2019-10-16 11:39:41 +00:00
|
|
|
river {:?}
|
2020-10-24 17:57:46 +00:00
|
|
|
gradient {:?}
|
2019-10-16 11:39:41 +00:00
|
|
|
downhill {:?}
|
2019-08-26 09:49:14 +00:00
|
|
|
chaos {:?}
|
2019-10-16 11:39:41 +00:00
|
|
|
flux {:?}
|
2019-08-26 09:49:14 +00:00
|
|
|
temp {:?}
|
|
|
|
humidity {:?}
|
|
|
|
rockiness {:?}
|
|
|
|
tree_density {:?}
|
|
|
|
spawn_rate {:?} "#,
|
2020-10-14 21:02:58 +00:00
|
|
|
wpos,
|
|
|
|
alt,
|
|
|
|
col.alt,
|
|
|
|
water_alt,
|
|
|
|
col.water_level,
|
|
|
|
basement,
|
|
|
|
river,
|
2020-10-24 17:57:46 +00:00
|
|
|
gradient,
|
2020-10-14 21:02:58 +00:00
|
|
|
downhill,
|
|
|
|
chaos,
|
|
|
|
flux,
|
|
|
|
temp,
|
|
|
|
humidity,
|
|
|
|
rockiness,
|
|
|
|
tree_density,
|
|
|
|
spawn_rate
|
|
|
|
))
|
|
|
|
};
|
|
|
|
if let Some(s) = msg_generator() {
|
2020-12-13 17:11:55 +00:00
|
|
|
server.notify_client(client, ServerGeneral::server_msg(ChatType::CommandInfo, s));
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-08-26 09:49:14 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err("Not a pregenerated chunk.".into())
|
2020-02-13 20:32:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
fn handle_disconnect_all_players(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
_action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
// Make sure temporary mods/admins can't run this command.
|
|
|
|
let _role = real_role(server, client_uuid, "role")?;
|
|
|
|
|
2021-04-13 22:05:47 +00:00
|
|
|
if args != *"confirm" {
|
|
|
|
return Err(
|
|
|
|
"Please run the command again with the second argument of \"confirm\" to confirm that \
|
|
|
|
you really want to disconnect all players from the server"
|
|
|
|
.to_string(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let ecs = server.state.ecs();
|
|
|
|
let players = &ecs.read_storage::<comp::Player>();
|
|
|
|
|
|
|
|
// TODO: This logging and verification of admin commands would be better moved
|
|
|
|
// to a more generic method used for auditing -all- admin commands.
|
|
|
|
let player_name;
|
|
|
|
if let Some(player) = players.get(client) {
|
|
|
|
player_name = &*player.alias;
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"Failed to get player name for admin who used /disconnect_all_players - ignoring \
|
|
|
|
command."
|
|
|
|
);
|
|
|
|
return Err("You do not exist, so you cannot use this command".to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
info!(
|
|
|
|
"Disconnecting all clients due to admin command from {}",
|
|
|
|
player_name
|
|
|
|
);
|
|
|
|
server.disconnect_all_clients_requested = true;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-06 01:26:21 +00:00
|
|
|
fn handle_skill_point(
|
2020-04-24 02:36:19 +00:00
|
|
|
server: &mut Server,
|
2021-04-09 07:34:58 +00:00
|
|
|
_client: EcsEntity,
|
2020-04-24 02:36:19 +00:00
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
|
|
|
if let (Some(a_skill_tree), Some(sp), a_alias) =
|
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), String, u16, String)
|
|
|
|
{
|
|
|
|
let skill_tree = parse_skill_tree(&a_skill_tree)?;
|
|
|
|
let player = a_alias
|
|
|
|
.map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target))
|
|
|
|
.unwrap_or(Ok(target))?;
|
2019-10-15 04:06:14 +00:00
|
|
|
|
2021-04-14 15:35:34 +00:00
|
|
|
if let Some(mut skill_set) = server
|
2021-04-09 07:34:58 +00:00
|
|
|
.state
|
|
|
|
.ecs_mut()
|
2021-04-14 15:35:34 +00:00
|
|
|
.write_storage::<comp::SkillSet>()
|
2021-04-09 07:34:58 +00:00
|
|
|
.get_mut(player)
|
|
|
|
{
|
2021-04-14 15:35:34 +00:00
|
|
|
skill_set.add_skill_points(skill_tree, sp);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Player has no stats!".into())
|
2019-10-15 04:06:14 +00:00
|
|
|
}
|
2021-04-09 07:34:58 +00:00
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2019-10-04 15:48:14 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-07 03:19:46 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skills::SkillGroupKind> {
|
2021-01-18 19:08:13 +00:00
|
|
|
use comp::{item::tool::ToolKind, skills::SkillGroupKind};
|
2021-01-06 01:26:21 +00:00
|
|
|
match skill_tree {
|
2021-04-09 07:34:58 +00:00
|
|
|
"general" => Ok(SkillGroupKind::General),
|
|
|
|
"sword" => Ok(SkillGroupKind::Weapon(ToolKind::Sword)),
|
|
|
|
"axe" => Ok(SkillGroupKind::Weapon(ToolKind::Axe)),
|
|
|
|
"hammer" => Ok(SkillGroupKind::Weapon(ToolKind::Hammer)),
|
|
|
|
"bow" => Ok(SkillGroupKind::Weapon(ToolKind::Bow)),
|
|
|
|
"staff" => Ok(SkillGroupKind::Weapon(ToolKind::Staff)),
|
|
|
|
"sceptre" => Ok(SkillGroupKind::Weapon(ToolKind::Sceptre)),
|
|
|
|
_ => Err(format!("{} is not a skill group!", skill_tree)),
|
2020-02-13 21:15:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-07 04:38:28 +00:00
|
|
|
fn handle_remove_lights(
|
|
|
|
server: &mut Server,
|
2020-04-24 02:36:19 +00:00
|
|
|
client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
2019-10-07 04:38:28 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-05 22:33:16 +00:00
|
|
|
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
|
2021-04-09 07:34:58 +00:00
|
|
|
let player_pos = position(server, target, "target")?;
|
2019-10-07 03:19:46 +00:00
|
|
|
let mut to_delete = vec![];
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let ecs = server.state.ecs();
|
|
|
|
for (entity, pos, _, _, _) in (
|
|
|
|
&ecs.entities(),
|
|
|
|
&ecs.read_storage::<comp::Pos>(),
|
|
|
|
&ecs.read_storage::<comp::LightEmitter>(),
|
|
|
|
!&ecs.read_storage::<comp::WaypointArea>(),
|
|
|
|
!&ecs.read_storage::<comp::Player>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
|
|
|
if opt_radius
|
|
|
|
.map(|r| pos.0.distance(player_pos.0) < r)
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
to_delete.push(entity);
|
|
|
|
}
|
2019-10-07 03:19:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let size = to_delete.len();
|
|
|
|
|
2019-10-07 04:49:56 +00:00
|
|
|
for entity in to_delete {
|
2020-06-21 21:47:49 +00:00
|
|
|
if let Err(e) = server.state.delete_entity_recorded(entity) {
|
|
|
|
error!(?e, "Failed to delete light: {:?}", e);
|
2019-11-29 06:04:37 +00:00
|
|
|
}
|
2019-10-07 03:19:46 +00:00
|
|
|
}
|
|
|
|
|
2019-10-15 04:06:14 +00:00
|
|
|
server.notify_client(
|
2020-04-24 02:36:19 +00:00
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(ChatType::CommandInfo, format!("Removed {} lights!", size)),
|
2019-10-07 03:19:46 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2019-10-07 03:19:46 +00:00
|
|
|
}
|
2020-04-24 02:36:19 +00:00
|
|
|
|
|
|
|
fn handle_sudo(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-04-25 17:18:29 +00:00
|
|
|
if let (Some(player_alias), Some(cmd), cmd_args) =
|
2020-05-05 22:33:16 +00:00
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), String, String, String)
|
2020-04-24 02:36:19 +00:00
|
|
|
{
|
2020-06-30 14:56:49 +00:00
|
|
|
let cmd_args = cmd_args.unwrap_or_else(|| String::from(""));
|
2020-06-05 18:12:18 +00:00
|
|
|
if let Ok(action) = cmd.parse() {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let (player, player_uuid) = find_alias(server.state.ecs(), &player_alias)?;
|
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
verify_above_role(
|
2021-04-09 07:34:58 +00:00
|
|
|
server,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
(client, client_uuid),
|
|
|
|
(player, player_uuid),
|
|
|
|
"Cannot sudo players with roles higher than your own.",
|
2021-04-09 07:34:58 +00:00
|
|
|
)?;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
|
|
|
|
// 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)
|
2020-04-24 02:36:19 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(format!("Unknown command: /{}", cmd))
|
2020-04-24 02:36:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-04-24 02:36:19 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-04 15:59:53 +00:00
|
|
|
|
|
|
|
fn handle_version(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
_args: String,
|
|
|
|
_action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-05-04 15:59:53 +00:00
|
|
|
server.notify_client(
|
|
|
|
client,
|
2020-12-13 17:40:15 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!(
|
|
|
|
"Server is running {}[{}]",
|
|
|
|
common::util::GIT_HASH.to_string(),
|
|
|
|
common::util::GIT_DATE.to_string(),
|
|
|
|
),
|
|
|
|
),
|
2020-05-04 15:59:53 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2020-05-04 15:59:53 +00:00
|
|
|
}
|
2020-06-30 12:21:36 +00:00
|
|
|
|
|
|
|
fn handle_whitelist(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let now = Utc::now();
|
|
|
|
|
2020-06-30 12:21:36 +00:00
|
|
|
if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
let client_username = uuid_to_username(server, client, client_uuid)?;
|
|
|
|
let client_role = real_role(server, client_uuid, "client")?;
|
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
if whitelist_action.eq_ignore_ascii_case("add") {
|
2021-04-09 07:34:58 +00:00
|
|
|
let uuid = find_username(server, &username)?;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
|
|
|
|
let record = WhitelistRecord {
|
|
|
|
date: now,
|
|
|
|
info: Some(WhitelistInfo {
|
|
|
|
username_when_whitelisted: username.clone(),
|
|
|
|
whitelisted_by: client_uuid,
|
|
|
|
whitelisted_by_username: client_username,
|
|
|
|
whitelisted_by_role: client_role.into(),
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
let edit =
|
|
|
|
server
|
|
|
|
.editable_settings_mut()
|
|
|
|
.whitelist
|
|
|
|
.edit(server.data_dir().as_ref(), |w| {
|
|
|
|
if w.insert(uuid, record).is_some() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(format!("added to whitelist: {}", username))
|
|
|
|
}
|
|
|
|
});
|
|
|
|
edit_setting_feedback(server, client, edit, || {
|
|
|
|
format!("already in whitelist: {}!", username)
|
|
|
|
})
|
2020-06-30 12:21:36 +00:00
|
|
|
} else if whitelist_action.eq_ignore_ascii_case("remove") {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
let client_role = real_role(server, client_uuid, "client")?;
|
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
let uuid = find_username(server, &username)?;
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let mut err_info = "not part of whitelist: ";
|
|
|
|
let edit =
|
|
|
|
server
|
|
|
|
.editable_settings_mut()
|
|
|
|
.whitelist
|
|
|
|
.edit(server.data_dir().as_ref(), |w| {
|
|
|
|
w.remove(&uuid)
|
|
|
|
.filter(|record| {
|
|
|
|
if record.whitelisted_by_role() <= client_role.into() {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
err_info = "permission denied to remove user: ";
|
|
|
|
false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map(|_| format!("removed from whitelist: {}", username))
|
|
|
|
});
|
|
|
|
edit_setting_feedback(server, client, edit, || format!("{}{}", err_info, username))
|
2021-04-09 07:34:58 +00:00
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2020-06-30 12:21:36 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-06-30 12:21:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-16 00:48:37 +00:00
|
|
|
|
2021-04-09 07:34:58 +00:00
|
|
|
fn kick_player(
|
|
|
|
server: &mut Server,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
(client, client_uuid): (EcsEntity, Uuid),
|
|
|
|
(target_player, target_player_uuid): (EcsEntity, Uuid),
|
2021-04-09 07:34:58 +00:00
|
|
|
reason: &str,
|
|
|
|
) -> CmdResult<()> {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
verify_above_role(
|
2021-04-09 07:34:58 +00:00
|
|
|
server,
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
(client, client_uuid),
|
|
|
|
(target_player, target_player_uuid),
|
|
|
|
"Cannot kick players with roles higher than your own.",
|
2021-04-09 07:34:58 +00:00
|
|
|
)?;
|
2020-09-14 06:16:09 +00:00
|
|
|
server.notify_client(
|
|
|
|
target_player,
|
2020-10-07 10:31:49 +00:00
|
|
|
ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())),
|
2020-09-14 06:16:09 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
server
|
|
|
|
.state
|
|
|
|
.mut_resource::<EventBus<ServerEvent>>()
|
2021-05-06 09:43:10 +00:00
|
|
|
.emit_now(ServerEvent::ClientDisconnect(
|
|
|
|
target_player,
|
|
|
|
common::comp::DisconnectReason::Kicked,
|
|
|
|
));
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
2020-08-09 15:14:44 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 00:48:37 +00:00
|
|
|
fn handle_kick(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-08-09 15:14:44 +00:00
|
|
|
if let (Some(target_alias), reason_opt) =
|
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), String, String)
|
|
|
|
{
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
2020-08-09 15:14:44 +00:00
|
|
|
let reason = reason_opt.unwrap_or_default();
|
2020-07-16 00:48:37 +00:00
|
|
|
let ecs = server.state.ecs();
|
2021-04-09 07:34:58 +00:00
|
|
|
let target_player = find_alias(ecs, &target_alias)?;
|
2020-07-16 00:48:37 +00:00
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
kick_player(server, (client, client_uuid), target_player, &reason)?;
|
2020-07-16 00:48:37 +00:00
|
|
|
server.notify_client(
|
2020-08-09 15:14:44 +00:00
|
|
|
client,
|
2021-04-09 07:34:58 +00:00
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!(
|
|
|
|
"Kicked {} from the server with reason: {}",
|
|
|
|
target_alias, reason
|
|
|
|
),
|
|
|
|
),
|
2020-07-16 00:48:37 +00:00
|
|
|
);
|
2021-04-09 07:34:58 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2020-07-16 00:48:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_ban(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
2020-08-09 15:14:44 +00:00
|
|
|
_target: EcsEntity,
|
2020-07-16 20:56:11 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
if let (Some(username), overwrite, parse_duration, reason_opt) = scan_fmt_some!(
|
|
|
|
&args,
|
|
|
|
&action.arg_fmt(),
|
|
|
|
String,
|
|
|
|
bool,
|
|
|
|
HumanDuration,
|
|
|
|
String
|
|
|
|
) {
|
2020-08-09 15:14:44 +00:00
|
|
|
let reason = reason_opt.unwrap_or_default();
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let overwrite = overwrite.unwrap_or(false);
|
|
|
|
|
|
|
|
let player_uuid = find_username(server, &username)?;
|
|
|
|
|
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
let client_username = uuid_to_username(server, client, client_uuid)?;
|
|
|
|
let client_role = real_role(server, client_uuid, "client")?;
|
|
|
|
|
|
|
|
let now = Utc::now();
|
|
|
|
let end_date = parse_duration
|
|
|
|
.map(|duration| chrono::Duration::from_std(duration.into()))
|
|
|
|
.transpose()
|
|
|
|
.map_err(|err| format!("Error converting to duration: {}", err))?
|
|
|
|
// On overflow (someone adding some ridiculous timespan), just make the ban infinite.
|
|
|
|
.and_then(|duration| now.checked_add_signed(duration));
|
|
|
|
|
|
|
|
let ban_info = BanInfo {
|
|
|
|
performed_by: client_uuid,
|
|
|
|
performed_by_username: client_username,
|
|
|
|
performed_by_role: client_role.into(),
|
|
|
|
};
|
2020-08-11 16:44:30 +00:00
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let ban = Ban {
|
|
|
|
reason: reason.clone(),
|
|
|
|
info: Some(ban_info),
|
|
|
|
end_date,
|
|
|
|
};
|
|
|
|
|
|
|
|
let edit = server
|
|
|
|
.editable_settings_mut()
|
|
|
|
.banlist
|
|
|
|
.ban_action(
|
|
|
|
server.data_dir().as_ref(),
|
|
|
|
now,
|
|
|
|
player_uuid,
|
|
|
|
username.clone(),
|
|
|
|
BanAction::Ban(ban),
|
|
|
|
overwrite,
|
|
|
|
)
|
|
|
|
.map(|result| {
|
|
|
|
(
|
2021-04-09 07:34:58 +00:00
|
|
|
format!("Added {} to the banlist with reason: {}", username, reason),
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
result,
|
|
|
|
)
|
|
|
|
});
|
2021-04-09 07:34:58 +00:00
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
edit_setting_feedback(server, client, edit, || {
|
|
|
|
format!("{} is already on the banlist", username)
|
|
|
|
})?;
|
|
|
|
// If the player is online kick them (this may fail if the player is a hardcoded
|
|
|
|
// admin; we don't care about that case because hardcoded admins can log on even
|
|
|
|
// if they're on the ban list).
|
|
|
|
let ecs = server.state.ecs();
|
|
|
|
if let Ok(target_player) = find_uuid(ecs, player_uuid) {
|
|
|
|
let _ = kick_player(
|
|
|
|
server,
|
|
|
|
(client, client_uuid),
|
|
|
|
(target_player, player_uuid),
|
|
|
|
&reason,
|
|
|
|
);
|
2020-07-16 21:34:54 +00:00
|
|
|
}
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
Ok(())
|
2020-07-16 20:56:11 +00:00
|
|
|
} else {
|
2021-04-09 07:34:58 +00:00
|
|
|
Err(action.help_string())
|
2020-07-16 20:56:11 +00:00
|
|
|
}
|
2020-07-16 00:48:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_unban(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
2020-07-16 22:27:04 +00:00
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
2021-04-09 07:34:58 +00:00
|
|
|
) -> CmdResult<()> {
|
2020-07-16 22:27:04 +00:00
|
|
|
if let Ok(username) = scan_fmt!(&args, &action.arg_fmt(), String) {
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let player_uuid = find_username(server, &username)?;
|
2020-08-11 16:44:30 +00:00
|
|
|
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
let client_uuid = uuid(server, client, "client")?;
|
|
|
|
let client_username = uuid_to_username(server, client, client_uuid)?;
|
|
|
|
let client_role = real_role(server, client_uuid, "client")?;
|
|
|
|
|
|
|
|
let now = Utc::now();
|
|
|
|
|
|
|
|
let ban_info = BanInfo {
|
|
|
|
performed_by: client_uuid,
|
|
|
|
performed_by_username: client_username,
|
|
|
|
performed_by_role: client_role.into(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let unban = BanAction::Unban(ban_info);
|
|
|
|
|
|
|
|
let edit = server
|
2021-04-09 07:34:58 +00:00
|
|
|
.editable_settings_mut()
|
|
|
|
.banlist
|
Added non-admin moderators and timed bans.
The security model has been updated to reflect this change (for example,
moderators cannot revert a ban by an administrator). Ban history is
also now recorded in the ban file, and much more information about the
ban is stored (whitelists and administrators also have extra
information).
To support the new information without losing important information,
this commit also introduces a new migration path for editable settings
(both from legacy to the new format, and between versions). Examples
of how to do this correctly, and migrate to new versions of a settings
file, are in the settings/ subdirectory.
As part of this effort, editable settings have been revamped to
guarantee atomic saves (due to the increased amount of information in
each file), some latent bugs in networking were fixed, and server-cli
has been updated to go through StructOpt for both calls through TUI
and argv, greatly simplifying parsing logic.
2021-05-08 18:22:21 +00:00
|
|
|
.ban_action(
|
|
|
|
server.data_dir().as_ref(),
|
|
|
|
now,
|
|
|
|
player_uuid,
|
|
|
|
username.clone(),
|
|
|
|
unban,
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
.map(|result| (format!("{} was successfully unbanned", username), result));
|
|
|
|
|
|
|
|
edit_setting_feedback(server, client, edit, || {
|
|
|
|
format!("{} was already unbanned", username)
|
|
|
|
})
|
2021-04-09 07:34:58 +00:00
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
2020-07-16 22:27:04 +00:00
|
|
|
}
|
2020-07-16 00:48:37 +00:00
|
|
|
}
|
2021-04-17 17:44:22 +00:00
|
|
|
|
|
|
|
fn handle_server_physics(
|
|
|
|
server: &mut Server,
|
|
|
|
client: EcsEntity,
|
|
|
|
_target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
if let (Some(username), enabled_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, bool) {
|
|
|
|
let uuid = find_username(server, &username)?;
|
|
|
|
let server_force = enabled_opt.unwrap_or(true);
|
|
|
|
|
|
|
|
let mut player_physics_settings =
|
|
|
|
server.state.ecs().write_resource::<PlayerPhysicsSettings>();
|
|
|
|
let entry = player_physics_settings.settings.entry(uuid).or_default();
|
|
|
|
entry.server_force = server_force;
|
|
|
|
|
|
|
|
server.notify_client(
|
|
|
|
client,
|
|
|
|
ServerGeneral::server_msg(
|
|
|
|
ChatType::CommandInfo,
|
|
|
|
format!(
|
|
|
|
"Updated physics settings for {} ({}): {:?}",
|
|
|
|
username, uuid, entry
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
|
|
|
}
|
|
|
|
}
|
2021-05-02 21:42:54 +00:00
|
|
|
|
|
|
|
fn handle_apply_buff(
|
|
|
|
server: &mut Server,
|
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
if let (Some(buff), strength, duration) =
|
|
|
|
scan_fmt_some!(&args, &action.arg_fmt(), String, f32, f64)
|
|
|
|
{
|
|
|
|
let strength = strength.unwrap_or(0.01);
|
|
|
|
let duration = Duration::from_secs_f64(duration.unwrap_or(1.0));
|
|
|
|
let buffdata = BuffData::new(strength, Some(duration));
|
|
|
|
if buff != "all" {
|
|
|
|
cast_buff(&buff, buffdata, server, target)
|
|
|
|
} else {
|
2021-05-04 13:22:10 +00:00
|
|
|
for kind in BUFF_PACK.iter() {
|
2021-05-02 22:18:40 +00:00
|
|
|
cast_buff(kind, buffdata, server, target)?;
|
|
|
|
}
|
2021-05-02 21:42:54 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-02 22:18:40 +00:00
|
|
|
fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity) -> CmdResult<()> {
|
2021-05-02 21:42:54 +00:00
|
|
|
if let Some(buffkind) = parse_buffkind(kind) {
|
|
|
|
let ecs = &server.state.ecs();
|
|
|
|
let mut buffs_all = ecs.write_storage::<comp::Buffs>();
|
|
|
|
if let Some(mut buffs) = buffs_all.get_mut(target) {
|
|
|
|
buffs.insert(Buff::new(buffkind, data, vec![], BuffSource::Command));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(format!("unknown buff: {}", kind))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 13:22:10 +00:00
|
|
|
fn parse_buffkind(buff: &str) -> Option<BuffKind> { BUFF_PARSER.get(buff).copied() }
|
2021-05-08 15:47:09 +00:00
|
|
|
|
|
|
|
fn handle_skill_preset(
|
|
|
|
server: &mut Server,
|
|
|
|
_client: EcsEntity,
|
|
|
|
target: EcsEntity,
|
|
|
|
args: String,
|
|
|
|
action: &ChatCommand,
|
|
|
|
) -> CmdResult<()> {
|
|
|
|
if let Some(preset) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
|
|
|
|
if let Some(mut skill_set) = server
|
|
|
|
.state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::SkillSet>()
|
|
|
|
.get_mut(target)
|
|
|
|
{
|
|
|
|
match preset.as_str() {
|
|
|
|
"clear" => {
|
|
|
|
clear_skillset(&mut skill_set);
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
preset => set_skills(&mut skill_set, preset),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err("Player has no stats!".into())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(action.help_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_skillset(skill_set: &mut comp::SkillSet) { *skill_set = comp::SkillSet::default(); }
|
|
|
|
|
|
|
|
fn set_skills(skill_set: &mut comp::SkillSet, preset: &str) -> CmdResult<()> {
|
2021-05-22 17:41:47 +00:00
|
|
|
let presets = match common::cmd::SkillPresetManifest::load("server.manifests.presets") {
|
|
|
|
Ok(presets) => presets.read().0.clone(),
|
|
|
|
Err(err) => {
|
|
|
|
warn!("Error in preset: {}", err);
|
|
|
|
return Err("Error while loading presets".to_owned());
|
|
|
|
},
|
|
|
|
};
|
2021-05-08 15:47:09 +00:00
|
|
|
if let Some(preset) = presets.get(preset) {
|
|
|
|
for (skill, level) in preset {
|
|
|
|
let group = if let Some(group) = skill.skill_group_kind() {
|
|
|
|
group
|
|
|
|
} else {
|
|
|
|
warn!("Skill in preset doesn't exist in any group");
|
|
|
|
return Err("Preset is broken".to_owned());
|
|
|
|
};
|
|
|
|
for _ in 0..*level {
|
|
|
|
let cost = skill_set.skill_cost(*skill);
|
|
|
|
skill_set.add_skill_points(group, cost);
|
|
|
|
skill_set.unlock_skill(*skill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Such preset doesn't exist".to_owned())
|
|
|
|
}
|
|
|
|
}
|