veloren/server/src/cmd.rs

3598 lines
117 KiB
Rust
Raw Normal View History

//! # Implementing new commands.
2021-08-15 11:45:50 +00:00
//! To implement a new command provide a handler function
//! in [do_command].
use crate::{
client::Client,
location::Locations,
login_provider::LoginProvider,
settings::{
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
},
2021-08-15 14:09:52 +00:00
sys::terrain::NpcData,
wiring,
wiring::OutputFormula,
Server, Settings, SpawnPoint, StateExt,
};
use assets::AssetExt;
use authc::Uuid;
use chrono::{NaiveTime, Timelike, Utc};
use common::{
assets,
calendar::Calendar,
cmd::{
2022-06-14 20:35:01 +00:00
KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH,
2021-09-04 02:06:15 +00:00
PRESET_MANIFEST_PATH,
},
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},
invite::InviteKind,
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea,
2020-12-04 22:24:56 +00:00
},
depot,
2020-11-01 17:15:46 +00:00
effect::Effect,
2019-08-25 16:48:12 +00:00
event::{EventBus, ServerEvent},
2022-01-04 23:07:33 +00:00
generation::{EntityConfig, EntityInfo},
link::Is,
mounting::Rider,
npc::{self, get_npc_name},
2022-06-14 20:35:01 +00:00
parse_cmd_args,
resources::{BattleMode, PlayerPhysicsSettings, Time, TimeOfDay},
2021-11-07 17:13:20 +00:00
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
uid::{Uid, UidAllocator},
2021-11-04 12:45:08 +00:00
vol::{ReadVol, RectVolSize},
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
};
use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt,
};
2021-04-06 15:47:03 +00:00
use common_state::{BuildAreaError, BuildAreas};
use core::{cmp::Ordering, convert::TryFrom, time::Duration};
2021-05-04 13:22:10 +00:00
use hashbrown::{HashMap, HashSet};
use humantime::Duration as HumanDuration;
use rand::{thread_rng, Rng};
use specs::{
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
};
2021-11-04 12:45:08 +00:00
use std::{str::FromStr, sync::Arc};
use vek::*;
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
use world::util::Sampler;
use common::comp::Alignment;
2021-09-06 17:33:08 +00:00
use tracing::{error, info, warn};
2019-07-26 14:38:31 +00:00
pub trait ChatCommandExt {
fn execute(&self, server: &mut Server, entity: EcsEntity, args: Vec<String>);
}
2022-06-14 20:35:01 +00:00
impl ChatCommandExt for ServerChatCommand {
fn execute(&self, server: &mut Server, entity: EcsEntity, args: Vec<String>) {
if let Err(err) = do_command(server, entity, entity, args, self) {
server.notify_client(
entity,
ServerGeneral::server_msg(ChatType::CommandError, err),
);
}
}
}
type CmdResult<T> = Result<T, String>;
2021-08-15 11:45:50 +00:00
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
2021-07-23 16:33:31 +00:00
/// * `Vec<String>` - a `Vec<String>` containing the arguments of the command
/// after the keyword.
2020-05-11 22:02:21 +00:00
/// * `&ChatCommand` - the command to execute with the above arguments.
/// Handler functions must parse arguments from the the given `String`
2021-07-23 16:33:31 +00:00
/// (`parse_args!` exists for this purpose).
///
/// # 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.
2021-07-23 16:33:31 +00:00
type CommandHandler =
2022-06-14 20:35:01 +00:00
fn(&mut Server, EcsEntity, EcsEntity, Vec<String>, &ServerChatCommand) -> CmdResult<()>;
fn do_command(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
cmd: &ServerChatCommand,
) -> 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 {
2022-06-14 20:35:01 +00:00
ServerChatCommand::Adminify => handle_adminify,
ServerChatCommand::Airship => handle_spawn_airship,
ServerChatCommand::Alias => handle_alias,
ServerChatCommand::ApplyBuff => handle_apply_buff,
ServerChatCommand::Ban => handle_ban,
ServerChatCommand::BattleMode => handle_battlemode,
ServerChatCommand::BattleModeForce => handle_battlemode_force,
ServerChatCommand::Build => handle_build,
ServerChatCommand::BuildAreaAdd => handle_build_area_add,
ServerChatCommand::BuildAreaList => handle_build_area_list,
ServerChatCommand::BuildAreaRemove => handle_build_area_remove,
ServerChatCommand::Campfire => handle_spawn_campfire,
ServerChatCommand::DebugColumn => handle_debug_column,
ServerChatCommand::DisconnectAllPlayers => handle_disconnect_all_players,
ServerChatCommand::DropAll => handle_drop_all,
ServerChatCommand::Dummy => handle_spawn_training_dummy,
ServerChatCommand::Explosion => handle_explosion,
ServerChatCommand::Faction => handle_faction,
ServerChatCommand::GiveItem => handle_give_item,
ServerChatCommand::Goto => handle_goto,
ServerChatCommand::Group => handle_group,
ServerChatCommand::GroupInvite => handle_group_invite,
ServerChatCommand::GroupKick => handle_group_kick,
ServerChatCommand::GroupLeave => handle_group_leave,
ServerChatCommand::GroupPromote => handle_group_promote,
ServerChatCommand::Health => handle_health,
ServerChatCommand::Help => handle_help,
ServerChatCommand::Home => handle_home,
ServerChatCommand::JoinFaction => handle_join_faction,
ServerChatCommand::Jump => handle_jump,
ServerChatCommand::Kick => handle_kick,
ServerChatCommand::Kill => handle_kill,
ServerChatCommand::KillNpcs => handle_kill_npcs,
ServerChatCommand::Kit => handle_kit,
ServerChatCommand::Lantern => handle_lantern,
ServerChatCommand::Light => handle_light,
ServerChatCommand::MakeBlock => handle_make_block,
ServerChatCommand::MakeNpc => handle_make_npc,
ServerChatCommand::MakeSprite => handle_make_sprite,
ServerChatCommand::Motd => handle_motd,
ServerChatCommand::Object => handle_object,
ServerChatCommand::PermitBuild => handle_permit_build,
ServerChatCommand::Players => handle_players,
ServerChatCommand::Region => handle_region,
ServerChatCommand::ReloadChunks => handle_reload_chunks,
ServerChatCommand::RemoveLights => handle_remove_lights,
ServerChatCommand::RevokeBuild => handle_revoke_build,
ServerChatCommand::RevokeBuildAll => handle_revoke_build_all,
ServerChatCommand::Safezone => handle_safezone,
ServerChatCommand::Say => handle_say,
ServerChatCommand::ServerPhysics => handle_server_physics,
ServerChatCommand::SetMotd => handle_set_motd,
ServerChatCommand::Ship => handle_spawn_ship,
ServerChatCommand::Site => handle_site,
ServerChatCommand::SkillPoint => handle_skill_point,
ServerChatCommand::SkillPreset => handle_skill_preset,
ServerChatCommand::Spawn => handle_spawn,
ServerChatCommand::Sudo => handle_sudo,
ServerChatCommand::Tell => handle_tell,
ServerChatCommand::Time => handle_time,
ServerChatCommand::Tp => handle_tp,
ServerChatCommand::Unban => handle_unban,
ServerChatCommand::Version => handle_version,
ServerChatCommand::Waypoint => handle_waypoint,
ServerChatCommand::Wiring => handle_spawn_wiring,
ServerChatCommand::Whitelist => handle_whitelist,
ServerChatCommand::World => handle_world,
ServerChatCommand::MakeVolume => handle_make_volume,
ServerChatCommand::Location => handle_location,
ServerChatCommand::CreateLocation => handle_create_location,
ServerChatCommand::DeleteLocation => handle_delete_location,
};
handler(server, client, target, args, cmd)
}
// 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 entity = server
.state
.ecs()
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| {
server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into())
})
.unwrap_or(entity);
let res = server
.state
.ecs()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(f)
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
if res.is_ok() {
let _ = server
.state
.ecs()
.write_storage::<comp::ForceUpdate>()
.insert(entity, comp::ForceUpdate);
}
res
}
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))
}
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))
}
// 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())
}
}
/// 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
.editable_settings()
.admins
.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())
}
}
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)
.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
)),
}
}
fn handle_drop_all(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
let mut items = Vec::new();
if let Some(mut inventory) = server
.state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(target)
{
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);
server
.state
.create_item_drop(Default::default(), item)
.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),
pos.0.z + 5.0,
)))
.with(comp::Vel(vel))
.build();
}
Ok(())
}
fn handle_give_item(
2020-04-24 02:36:19 +00:00
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(item_name), give_amount_opt) = parse_cmd_args!(args, String, u32) {
2020-05-07 18:39:48 +00:00
let give_amount = give_amount_opt.unwrap_or(1);
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;
let mut res = Ok(());
const MAX_GIVE_AMOUNT: u32 = 2000;
// Cap give_amount for non-stackable items
let give_amount = if item.is_stackable() {
give_amount
} else {
give_amount.min(MAX_GIVE_AMOUNT)
};
if let Ok(()) = item.set_amount(give_amount) {
2020-05-07 18:39:48 +00:00
server
.state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(target)
.map(|mut inv| {
// 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>();
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)
.map(|mut inv| {
2020-05-07 18:39:48 +00:00
for i in 0..give_amount {
// 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() {
res = Err(format!(
"Player inventory full. Gave {} of {} items.",
i, give_amount
));
2020-05-07 18:39:48 +00:00
break;
}
}
});
}
insert_or_replace_component(
server,
target,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
"target",
)?;
res
2020-05-07 18:39:48 +00:00
} else {
Err(format!("Invalid item: {}", item_name))
2020-05-07 18:39:48 +00:00
}
2019-10-24 21:05:10 +00:00
} else {
Err(action.help_string())
2019-10-24 21:05:10 +00:00
}
}
2020-06-27 18:39:16 +00:00
fn handle_make_block(
server: &mut Server,
_client: EcsEntity,
2020-06-27 18:39:16 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(block_name), r, g, b) = parse_cmd_args!(args, String, u8, u8, u8) {
if let Ok(bk) = BlockKind::from_str(block_name.as_str()) {
let pos = position(server, target, "target")?;
2021-07-23 12:04:16 +00:00
let new_block = Block::new(bk, Rgb::new(r, g, b).map(|e| e.unwrap_or(255)));
let pos = pos.0.map(|e| e.floor() as i32);
server.state.set_block(pos, new_block);
#[cfg(feature = "persistent_world")]
2021-07-23 12:04:16 +00:00
if let Some(terrain_persistence) = server
.state
.ecs()
.try_fetch_mut::<crate::TerrainPersistence>()
.as_mut()
{
terrain_persistence.set_block(pos, new_block);
}
Ok(())
} else {
Err(format!("Invalid block kind: {}", block_name))
2020-06-27 18:39:16 +00:00
}
} else {
Err(action.help_string())
2020-06-27 18:39:16 +00:00
}
}
2021-08-15 11:45:50 +00:00
fn handle_make_npc(
2021-08-15 14:09:52 +00:00
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
2021-08-15 11:45:50 +00:00
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let (entity_config, number) = parse_cmd_args!(args, String, i8);
2021-08-15 14:09:52 +00:00
let entity_config = entity_config.ok_or_else(|| action.help_string())?;
let number = match number {
Some(i8::MIN..=0) => {
return Err("Number of entities should be at least 1".to_owned());
},
Some(50..=i8::MAX) => {
return Err("Number of entities should be less than 50".to_owned());
},
Some(number) => number,
None => 1,
};
2022-01-04 23:07:33 +00:00
let config = match EntityConfig::load(&entity_config) {
Ok(asset) => asset.read(),
Err(_err) => return Err(format!("Failed to load entity config: {}", entity_config)),
};
2022-01-27 12:47:46 +00:00
let mut loadout_rng = rand::thread_rng();
2021-08-15 14:09:52 +00:00
for _ in 0..number {
let comp::Pos(pos) = position(server, target, "target")?;
let entity_info = EntityInfo::at(pos).with_entity_config(
config.clone(),
Some(&entity_config),
2022-01-27 12:47:46 +00:00
&mut loadout_rng,
);
match NpcData::from_entity_info(entity_info) {
2021-08-15 14:09:52 +00:00
NpcData::Waypoint(_) => {
return Err("Waypoint spawning is not implemented".to_owned());
},
NpcData::Data {
inventory,
2021-08-15 14:09:52 +00:00
pos,
stats,
skill_set,
poise,
health,
body,
agent,
alignment,
scale,
loot,
2021-08-15 14:09:52 +00:00
} => {
let mut entity_builder = server
.state
.create_npc(pos, stats, skill_set, health, poise, inventory, body)
.with(alignment)
.with(scale)
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
2021-08-15 14:09:52 +00:00
if let Some(agent) = agent {
entity_builder = entity_builder.with(agent);
}
if let Some(drop_item) = loot.to_item() {
2021-08-15 14:09:52 +00:00
entity_builder = entity_builder.with(comp::ItemDrop(drop_item));
}
// Some would say it's a hack, some would say it's incomplete
// simulation. But this is what we do to avoid PvP between npc.
let npc_group = match alignment {
Alignment::Enemy => Some(comp::group::ENEMY),
Alignment::Npc | Alignment::Tame => Some(comp::group::NPC),
Alignment::Wild | Alignment::Passive | Alignment::Owned(_) => None,
};
if let Some(group) = npc_group {
entity_builder = entity_builder.with(group);
}
entity_builder.build();
},
};
}
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Spawned {} entities from config: {}", number, entity_config),
),
);
Ok(())
2021-08-15 11:45:50 +00:00
}
2020-09-21 15:39:20 +00:00
fn handle_make_sprite(
server: &mut Server,
_client: EcsEntity,
2020-09-21 15:39:20 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(sprite_name) = parse_cmd_args!(args, String) {
2020-09-21 15:39:20 +00:00
if let Ok(sk) = SpriteKind::try_from(sprite_name.as_str()) {
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);
#[cfg(feature = "persistent_world")]
2021-07-23 12:04:16 +00:00
if let Some(terrain_persistence) = server
.state
.ecs()
.try_fetch_mut::<crate::TerrainPersistence>()
.as_mut()
{
terrain_persistence.set_block(pos, new_block);
}
Ok(())
2020-09-21 15:39:20 +00:00
} else {
Err(format!("Invalid sprite kind: {}", sprite_name))
2020-09-21 15:39:20 +00:00
}
} else {
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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
(*server.editable_settings().server_description).clone(),
),
);
Ok(())
}
fn handle_set_motd(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let data_dir = server.data_dir();
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")?;
2022-06-14 20:35:01 +00:00
match parse_cmd_args!(args, String) {
2021-07-23 16:33:31 +00:00
Some(msg) => {
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
},
2021-07-23 16:33:31 +00:00
None => {
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 12:07:01 +00:00
}
}
2020-04-24 02:36:19 +00:00
fn handle_jump(
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(x), Some(y), Some(z)) = parse_cmd_args!(args, f32, f32, f32) {
position_mut(server, target, "target", |current_pos| {
current_pos.0 += Vec3::new(x, y, z)
})
} else {
Err(action.help_string())
}
}
2020-04-24 02:36:19 +00:00
fn handle_goto(
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(x), Some(y), Some(z)) = parse_cmd_args!(args, f32, f32, f32) {
position_mut(server, target, "target", |current_pos| {
current_pos.0 = Vec3::new(x, y, z)
})
} else {
Err(action.help_string())
}
}
/// TODO: Add autocompletion if possible (might require modifying enum to handle
/// dynamic values).
fn handle_site(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
#[cfg(feature = "worldgen")]
2022-06-14 20:35:01 +00:00
if let Some(dest_name) = parse_cmd_args!(args, String) {
let site = server
.world
.civs()
.sites()
.find(|site| {
site.site_tmp
.map_or(false, |id| server.index.sites[id].name() == dest_name)
})
.ok_or_else(|| "Site not found".to_string())?;
let site_pos = server.world.find_accessible_pos(
server.index.as_index_ref(),
TerrainChunkSize::center_wpos(site.center),
false,
);
position_mut(server, target, "target", |current_pos| {
current_pos.0 = site_pos
})
} else {
Err(action.help_string())
}
#[cfg(not(feature = "worldgen"))]
Ok(())
}
2020-11-03 23:53:46 +00:00
fn handle_home(
server: &mut Server,
_client: EcsEntity,
2020-11-03 23:53:46 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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",
)
2020-11-03 23:53:46 +00:00
}
2020-04-24 02:36:19 +00:00
fn handle_kill(
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
server
.state
.ecs_mut()
.write_storage::<comp::Health>()
2020-04-24 02:36:19 +00:00
.get_mut(target)
.map(|mut h| h.kill());
Ok(())
}
2020-04-24 02:36:19 +00:00
fn handle_time(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
const DAY: u64 = 86400;
let time_in_seconds = server.state.mut_resource::<TimeOfDay>().0;
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
}
};
2022-06-14 20:35:01 +00:00
let time = parse_cmd_args!(args, String);
2020-06-30 14:56:49 +00:00
let new_time = match time.as_deref() {
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)
},
Some(n) => match n.parse() {
Ok(n) => n,
Err(_) => match NaiveTime::parse_from_str(n, "%H:%M") {
// 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
{
// Absolute time (i.e: since in-game epoch)
2020-11-16 22:46:31 +00:00
Some(n) => n as f64,
None => {
return Err(format!("{:?} is not a valid time.", n));
2020-11-16 22:46:31 +00:00
},
},
2019-07-26 14:38:31 +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 ;)
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,
0,
);
let msg = match current_time {
Some(time) => format!("It is {}", time.format("%H:%M")),
None => String::from("Unknown Time"),
};
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, msg),
);
return Ok(());
},
};
server.state.mut_resource::<TimeOfDay>().0 = new_time;
// 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>();
let calendar = server.state.ecs().read_resource::<Calendar>();
for client in (&clients).join() {
let msg = tod_lazymsg.unwrap_or_else(|| {
client.prepare(ServerGeneral::TimeOfDay(
TimeOfDay(new_time),
(*calendar).clone(),
))
});
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,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Time changed to: {}", new_time.format("%H:%M")),
),
2020-11-16 22:46:31 +00:00
);
}
Ok(())
}
2020-04-24 02:36:19 +00:00
fn handle_health(
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(hp) = parse_cmd_args!(args, f32) {
if let Some(mut health) = server
.state
.ecs()
.write_storage::<comp::Health>()
2020-04-24 02:36:19 +00:00
.get_mut(target)
{
let time = server.state.ecs().read_resource::<Time>();
let change = comp::HealthChange {
amount: hp - health.current(),
by: None,
cause: None,
2022-05-17 19:30:51 +00:00
crit: false,
time: *time,
2022-01-26 10:16:45 +00:00
instance: rand::random(),
};
health.change_by(change);
Ok(())
2019-07-30 08:10:58 +00:00
} else {
Err("You have no health".into())
2019-07-30 08:10:58 +00:00
}
} else {
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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(alias) = parse_cmd_args!(args, String) {
// Prevent silly aliases
comp::Player::alias_validate(&alias).map_err(|e| e.to_string())?;
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)
.map(|mut player| std::mem::replace(&mut player.alias, alias));
// Update name on client player lists
let ecs = server.state.ecs();
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),
old_alias_optional,
) {
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(
*uid,
player.alias.clone(),
));
server.state.notify_players(msg);
// Announce alias change if target has a Body.
if ecs.read_storage::<comp::Body>().get(target).is_some() {
server.state.notify_players(ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("{} is now known as {}.", old_alias, player.alias),
));
}
}
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 {
Err(action.help_string())
}
}
2020-04-24 02:36:19 +00:00
fn handle_tp(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let player = if let Some(alias) = parse_cmd_args!(args, String) {
find_alias(server.state.ecs(), &alias)?.0
} else if client != target {
client
} else {
return Err(action.help_string());
};
let player_pos = position(server, player, "player")?;
position_mut(server, target, "target", |target_pos| {
*target_pos = player_pos
})
}
2020-04-24 02:36:19 +00:00
fn handle_spawn(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
match parse_cmd_args!(args, 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) => {
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")?;
2021-08-02 12:08:39 +00:00
let mut agent = comp::Agent::from_body(&body());
// If unowned, the agent should stay in a particular place
if !matches!(alignment, comp::Alignment::Owned(_)) {
agent = agent.with_patrol_origin(pos.0);
}
2020-07-02 21:53:01 +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,
);
let body = body();
Split LodoutBuilder::build_loadout LoadoutBuilder::build_loadout is a function which has four parameters and 3 of them are Option<>, and although fourth (body) isn't Option<>, it's optional too because it is used only in some combinations of another arguments. Because these combinations produces quirky code flow, it will be better to split it to different methods. So we did following changes to remove it and rewrite code that was using it to use better methods. * Introduce LoadoutPreset as new LoadoutConfig, currently it's only used in Summon ability, because SummonInfo uses Copy and we can't specify String for specifying asset path for loadout. Everything else is rewritten to use asset path to create loadouts. * More builder methods for LoadoutBuilder. Namely: - from_default which is used in server/src/cmd.rs in "/spawn" command. - with_default_equipment, with_default_maintool to use default loadout for specific body - with_preset to use LoadoutPreset * Add new make_loadout field with `fn (loadout_builder, trading_info) -> loadout_builder` to EntityInfo which allows to lazily construct loadout without modifying LoadoutBuilder code * Fix Merchants not having trade site We had heuristic that if something has Merchant LoadoutConfig - it's merchant, which can be false, especially if we create Merchant loadout lazily As side note, we do same check for Guards and it fails too. Too fix it, we introduce new agent::Mark, which explicitly specifies kind of agent for entity * `LoadoutBuilder::build_loadout` was written in a such way that depending on main_tool you will have different loadout. Turns out it was this way only for Adlets though and this behaviour is reproduced by specifying different loadouts directly in world code.
2021-06-05 18:05:31 +00:00
let loadout = LoadoutBuilder::from_default(&body).build();
let inventory = Inventory::with_loadout(loadout, body);
let mut entity_base = server
.state
.create_npc(
pos,
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
comp::SkillSet::default(),
2021-06-03 22:42:50 +00:00
Some(comp::Health::new(body, 1)),
comp::Poise::new(body),
inventory,
body,
)
.with(comp::Vel(vel))
.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 server_eventbus =
server.state.ecs().read_resource::<EventBus<ServerEvent>>();
server_eventbus.emit_now(ServerEvent::TamePet {
owner_entity: target,
pet_entity: new_entity,
});
} 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,
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,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Spawned {} entities", amount),
),
2020-06-12 17:44:29 +00:00
);
Ok(())
},
_ => Err(action.help_string()),
}
}
2020-07-02 21:53:01 +00:00
fn handle_spawn_training_dummy(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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
let body = comp::Body::Object(comp::object::Body::TrainingDummy);
2020-07-02 21:53:01 +00:00
let stats = comp::Stats::new("Training Dummy".to_string());
let skill_set = comp::SkillSet::default();
let health = comp::Health::new(body, 0);
let poise = comp::Poise::new(body);
server
.state
.create_npc(
pos,
stats,
skill_set,
2021-06-03 22:42:50 +00:00
Some(health),
poise,
Inventory::with_empty(),
body,
)
.with(comp::Vel(vel))
.build();
2020-07-02 21:53:01 +00:00
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a training dummy"),
);
Ok(())
2020-07-31 09:34:26 +00:00
}
fn handle_spawn_airship(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let angle = parse_cmd_args!(args, f32);
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,
)
});
let mut rng = rand::thread_rng();
let ship = comp::ship::Body::random_airship_with(&mut rng);
let mut builder = server
.state
2021-11-04 12:45:08 +00:00
.create_ship(pos, ship, |ship| ship.make_collider(), true)
.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-30 15:38:47 +00:00
let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship));
fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
2021-08-02 12:08:39 +00:00
let agent = comp::Agent::from_body(&comp::Body::Ship(ship))
.with_destination(pos)
.with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z));
builder = builder.with(agent);
}
builder.build();
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned an airship"),
);
Ok(())
}
fn handle_spawn_ship(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let angle = parse_cmd_args!(args, f32);
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,
)
});
let mut rng = rand::thread_rng();
let ship = comp::ship::Body::random_ship_with(&mut rng);
let mut builder = server
.state
.create_ship(pos, ship, |ship| ship.make_collider(), true)
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
flicker: 1.0,
animated: true,
});
if let Some(pos) = destination {
let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship));
fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
let agent = comp::Agent::from_body(&comp::Body::Ship(ship))
.with_destination(pos)
.with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z));
builder = builder.with(agent);
}
builder.build();
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a ship"),
);
Ok(())
}
2021-11-04 12:45:08 +00:00
fn handle_make_volume(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
2021-11-04 12:45:08 +00:00
) -> CmdResult<()> {
use comp::body::ship::figuredata::VoxelCollider;
//let () = parse_args!(args);
2021-11-07 17:13:20 +00:00
let pos = position(server, target, "target")?;
2021-11-04 12:45:08 +00:00
let ship = comp::ship::Body::Volume;
let sz = Vec3::new(15, 15, 15);
let collider = {
let terrain = server.state().terrain();
comp::Collider::Volume(Arc::new(VoxelCollider::from_fn(sz, |rpos| {
terrain
.get(pos.0.map(|e| e.floor() as i32) + rpos - sz.map(|e| e as i32) / 2)
.ok()
.copied()
.unwrap_or_else(Block::empty)
})))
};
server
.state
.create_ship(
comp::Pos(pos.0 + Vec3::unit_z() * 50.0),
ship,
move |_| collider,
true,
)
.build();
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Created a volume"),
);
Ok(())
}
2020-07-31 09:34:26 +00:00
fn handle_spawn_campfire(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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())
.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(2.0, Some(Duration::from_secs(10))),
category: BuffCategory::Natural,
source: BuffSource::World,
},
0.7,
None,
AuraTarget::All,
),
]))
.build();
2020-07-31 09:34:26 +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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let range = parse_cmd_args!(args, f32);
let pos = position(server, target, "target")?;
server.state.create_safezone(range, pos).build();
2021-02-28 23:14:59 +00:00
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned a safe zone"),
);
Ok(())
2021-02-28 23:14:59 +00:00
}
fn handle_permit_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(area_name) = parse_cmd_args!(args, String) {
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,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Permission to build in {} granted", area_name),
),
2021-03-24 07:58:42 +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,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(area_name) = parse_cmd_args!(args, String) {
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 {
server.notify_client(
target,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Your permission to build in {} has been revoked", area_name),
),
);
}
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())
}
} else {
Err(action.help_string())
}
}
2021-03-24 07:58:42 +00:00
fn handle_revoke_build_all(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let ecs = server.state.ecs();
2021-03-24 07:58:42 +00:00
ecs.write_storage::<comp::CanBuild>().remove(target);
if client != target {
server.notify_client(
target,
ServerGeneral::server_msg(
ChatType::CommandInfo,
"Your build permissions have been revoked.",
),
);
}
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "All build permissions revoked"),
);
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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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,
ServerGeneral::server_msg(
ChatType::CommandInfo,
entity_tuples.join().fold(
format!("{} online players:", entity_tuples.join().count()),
|s, (_, player, stat)| format!("{}\n[{}]{}", s, player.alias, stat.name,),
),
),
2020-06-01 13:01:10 +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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
if let Some(mut can_build) = server
.state
.ecs()
.write_storage::<comp::CanBuild>()
.get_mut(target)
{
can_build.enabled ^= true;
2021-07-23 12:04:16 +00:00
let toggle_string = if can_build.enabled { "on" } else { "off" };
let msg = format!(
"Toggled build mode {}.{}",
toggle_string,
if !can_build.enabled {
""
} else if server.settings().experimental_terrain_persistence {
" Experimental terrain persistence is enabled. The server will attempt to persist \
changes, but this is not guaranteed."
} else {
" Changes will not be persisted when a chunk unloads."
},
);
let chat_msg = ServerGeneral::server_msg(ChatType::CommandInfo, msg);
if client != target {
server.notify_client(target, chat_msg.clone());
}
server.notify_client(client, chat_msg);
Ok(())
} else {
Err("You do not have permission to build.".into())
}
}
2021-03-24 07:58:42 +00:00
fn handle_build_area_add(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2021-07-23 16:33:31 +00:00
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) =
2022-06-14 20:35:01 +00:00
parse_cmd_args!(args, String, i32, i32, i32, i32, i32, i32)
2021-07-23 16:33:31 +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),
})
.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
}
}
fn handle_build_area_list(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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
}
},
),
);
server.notify_client(client, msg);
Ok(())
}
2021-03-24 07:58:42 +00:00
fn handle_build_area_remove(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(area_name) = parse_cmd_args!(args, String) {
let build_areas = server.state.mut_resource::<BuildAreas>();
2021-03-24 07:58:42 +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),
),
);
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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(cmd) = parse_cmd_args!(args, ServerChatCommand) {
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, cmd.help_string()),
)
} else {
let mut message = String::new();
let entity_role = server.entity_admin_role(client);
// Iterate through all commands you have permission to use.
2022-06-14 20:35:01 +00:00
ServerChatCommand::iter()
.filter(|cmd| cmd.needs_role() <= entity_role)
.for_each(|cmd| {
message += &cmd.help_string();
message += "\n";
});
message += "Additionally, you can use the following shortcuts:";
2022-06-14 20:35:01 +00:00
ServerChatCommand::iter()
.filter_map(|cmd| cmd.short_keyword().map(|k| (k, cmd)))
.for_each(|(k, cmd)| {
message += &format!(" /{} => /{}", k, cmd.keyword());
});
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, message),
)
}
Ok(())
}
fn parse_alignment(owner: Uid, alignment: &str) -> CmdResult<comp::Alignment> {
2019-06-15 07:54:47 +00:00
match alignment {
"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)),
}
}
fn handle_kill_npcs(
2020-04-24 02:36:19 +00:00
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let kill_pets = if let Some(kill_option) = parse_cmd_args!(args, String) {
kill_option.contains("--also-pets")
} else {
false
};
let ecs = server.state.ecs();
let mut healths = ecs.write_storage::<comp::Health>();
let players = ecs.read_storage::<comp::Player>();
let alignments = ecs.read_storage::<comp::Alignment>();
let mut count = 0;
2021-09-06 17:33:08 +00:00
for (mut health, (), alignment) in (&mut healths, !&players, alignments.maybe()).join() {
let should_kill = kill_pets
|| if let Some(Alignment::Owned(owned)) = alignment {
ecs.entity_from_uid(owned.0)
.map_or(true, |owner| !players.contains(owner))
} else {
true
};
if should_kill {
count += 1;
health.kill();
}
2019-07-12 21:16:07 +00:00
}
let text = if count > 0 {
format!("Destroyed {} NPCs.", count)
} else {
"No NPCs on server.".to_string()
};
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, text),
);
Ok(())
2019-07-12 21:16:07 +00:00
}
fn handle_kit(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
use common::cmd::KitManifest;
let notify = |server: &mut Server, kit_name: &str| {
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, format!("Gave kit: {}", kit_name)),
);
};
2022-06-14 20:35:01 +00:00
let name = parse_cmd_args!(args, String).ok_or_else(|| action.help_string())?;
match name.as_str() {
"all" => {
// TODO: we will probably want to handle modular items here too
let items = &ITEM_SPECS;
let res = push_kit(
2021-09-04 02:06:15 +00:00
items
.iter()
.map(|item_id| (KitSpec::Item(item_id.to_string()), 1)),
items.len(),
server,
target,
);
if res.is_ok() {
notify(server, "all");
}
res
},
kit_name => {
let kits = KitManifest::load(KIT_MANIFEST_PATH)
.map(|kits| kits.read())
.map_err(|_| format!("Could not load manifest file {}", KIT_MANIFEST_PATH))?;
let kit = kits
.0
.get(kit_name)
.ok_or(format!("Kit '{}' not found", kit_name))?;
let res = push_kit(
kit.iter()
2021-09-04 02:06:15 +00:00
.map(|(item_id, quantity)| (item_id.clone(), *quantity)),
kit.len(),
server,
target,
);
if res.is_ok() {
notify(server, kit_name);
}
res
},
}
}
2021-09-04 02:06:15 +00:00
fn push_kit<I>(kit: I, count: usize, server: &mut Server, target: EcsEntity) -> CmdResult<()>
where
2021-09-04 02:06:15 +00:00
I: Iterator<Item = (KitSpec, u32)>,
{
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>(),
) {
// TODO: implement atomic `insert_all_or_nothing` on Inventory
if target_inventory.free_slots() < count {
return Err("Inventory doesn't have enough slots".to_owned());
}
let mut rng = thread_rng();
for (item_id, quantity) in kit {
2021-09-04 02:06:15 +00:00
let mut item = match &item_id {
KitSpec::Item(item_id) => comp::Item::new_from_asset(item_id)
.map_err(|_| format!("Unknown item: {:#?}", item_id))?,
KitSpec::ModularWeapon { tool, material } => {
comp::item::modular::random_weapon(*tool, *material, None, &mut rng)
.map_err(|err| format!("{:#?}", err))?
2021-09-04 02:06:15 +00:00
},
};
let mut res = Ok(());
// Either push stack or push one by one.
if item.is_stackable() {
// FIXME: in theory, this can fail,
// but we don't have stack sizes yet.
let _ = item.set_amount(quantity);
res = target_inventory.push(item);
let _ = target_inv_update.insert(
target,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug),
);
} else {
let ability_map = server.state.ecs().read_resource::<AbilityMap>();
let msm = server.state.ecs().read_resource::<MaterialStatManifest>();
for _ in 0..quantity {
res = target_inventory.push(item.duplicate(&ability_map, &msm));
let _ = target_inv_update.insert(
target,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug),
);
}
}
// I think it's possible to pick-up item during this loop
// and fail into case where you had space but now you don't?
if res.is_err() {
return Err("Can't fit item to inventory".to_owned());
}
}
Ok(())
} else {
Err("Could not get inventory".to_string())
}
}
2020-04-24 02:36:19 +00:00
fn handle_object(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let obj_type = parse_cmd_args!(args, String);
let pos = position(server, target, "target")?;
let ori = server
.state
.ecs()
.read_storage::<comp::Ori>()
2020-04-24 02:36:19 +00:00
.get(target)
.copied()
.ok_or_else(|| "Cannot get orientation for target".to_string())?;
/*let builder = server.state
.create_object(pos, ori, obj_type)
.with(ori);*/
2021-07-23 16:33:31 +00:00
let obj_str_res = obj_type.as_deref();
if let Some(obj_type) = comp::object::ALL_OBJECTS
.iter()
2021-07-23 16:33:31 +00:00
.find(|o| Some(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
)
.unwrap_or_default(),
)
.build();
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Spawned: {}", obj_str_res.unwrap_or("<Unknown object>")),
),
);
Ok(())
} else {
Err("Object not found!".into())
2019-07-21 12:42:45 +00:00
}
}
2020-04-24 02:36:19 +00:00
fn handle_light(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let (opt_r, opt_g, opt_b, opt_x, opt_y, opt_z, opt_s) =
2022-06-14 20:35:01 +00:00
parse_cmd_args!(args, f32, f32, f32, f32, f32, f32, f32);
let mut light_emitter = comp::LightEmitter::default();
let mut light_offset_opt = None;
if let (Some(r), Some(g), Some(b)) = (opt_r, opt_g, opt_b) {
if r < 0.0 || g < 0.0 || b < 0.0 {
return Err("cr, cg and cb values mustn't be negative.".into());
}
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) {
light_offset_opt = Some(comp::LightAnimation {
offset: Vec3::new(x, y, z),
col: light_emitter.col,
strength: 0.0,
})
};
if let Some(s) = opt_s {
light_emitter.strength = s.max(0.0)
};
let pos = position(server, target, "target")?;
let builder = server
.state
.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();
} else {
builder.build();
2019-07-25 20:51:20 +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-04-24 02:36:19 +00:00
fn handle_lantern(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(s), r, g, b) = parse_cmd_args!(args, f32, f32, f32, f32) {
if let Some(mut light) = server
.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,
ServerGeneral::server_msg(
ChatType::CommandInfo,
"You adjusted flame strength and color.",
),
)
} else {
server.notify_client(
2020-04-24 02:36:19 +00:00
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
"You adjusted flame strength.",
),
)
2019-07-28 11:36:35 +00:00
}
Ok(())
2019-07-28 11:36:35 +00:00
} else {
Err("Please equip a lantern first".into())
2019-07-28 11:36:35 +00:00
}
2019-08-12 14:05:58 +00:00
} else {
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,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let power = parse_cmd_args!(args, f32).unwrap_or(8.0);
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
));
} else if power <= 0.0 {
return Err(format!(
"Explosion power must be more than {:?}.",
MIN_POWER
));
}
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,
kind: DamageKind::Energy,
value: 100.0 * power,
})),
RadiusEffect::TerrainDestruction(power),
],
radius: 3.0 * power,
reagent: None,
2021-10-27 19:42:11 +00:00
min_falloff: 0.0,
},
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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
2021-04-15 16:28:16 +00:00
) -> CmdResult<()> {
let mut pos = position(server, target, "target")?;
pos.0.x += 3.0;
let mut outputs1 = HashMap::new();
outputs1.insert("button".to_string(), OutputFormula::OnCollide {
value: 1.0,
2021-04-15 16:28:16 +00:00
});
// Create the first element of the circuit.
// This is a coin body. This element does not have any inputs or actions.
// Instead there is one output. When there is a collision with this element the
// value of 1.0 will be sent as an input with the "button" label. Any
// element with an `Input` for the name "button" can use this value as an
// input. The threshold does not matter as there are no effects for this
// element.
2021-04-15 16:28:16 +00:00
let builder1 = server
.state
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
inputs: HashMap::new(),
outputs: outputs1,
actions: Vec::new(),
})
.with(comp::Density(100_f32));
2021-04-15 16:28:16 +00:00
let ent1 = builder1.build();
pos.0.x += 3.0;
// The second element has no elements in the `inputs` field to start with. When
// the circuit runs, the input as specified by the `Input` OutputFormula is
// added to the inputs. The next tick the effect(s) are applied based on the
// input value.
2021-04-15 16:28:16 +00:00
let builder2 = server
.state
.create_wiring(pos, comp::object::Body::Coins, WiringElement {
inputs: HashMap::new(),
outputs: HashMap::new(),
actions: vec![WiringAction {
formula: OutputFormula::Input {
name: String::from("button"),
},
threshold: 0.0,
effects: vec![WiringActionEffect::SetLight {
r: OutputFormula::Input {
name: String::from("button"),
},
g: OutputFormula::Input {
name: String::from("button"),
},
b: OutputFormula::Input {
name: String::from("button"),
},
}],
}],
})
.with(comp::Density(100_f32));
2021-04-15 16:28:16 +00:00
let ent2 = builder2.build();
pos.0.x += 3.0;
let builder3 = server
.state
.create_wiring(pos, comp::object::Body::TrainingDummy, WiringElement {
inputs: HashMap::new(),
outputs: HashMap::new(),
actions: Vec::new(),
2021-04-15 16:28:16 +00:00
})
.with(comp::Density(comp::object::Body::TrainingDummy.density().0))
.with(Circuit::new(vec![Wire {
input: WireNode::new(ent1, "button".to_string()),
output: WireNode::new(ent2, "button".to_string()),
}]));
2021-04-15 16:28:16 +00:00
builder3.build();
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Wire"),
);
Ok(())
}
2020-04-24 02:36:19 +00:00
fn handle_adminify(
server: &mut Server,
client: EcsEntity,
2020-04-24 02:36:19 +00:00
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(alias), desired_role) = parse_cmd_args!(args, 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")?;
let uid = uid(server, player, "player")?;
// 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(
server,
(client, client_uuid),
(player, player_uuid),
"Cannot reassign a role for anyone with your role or higher.",
)?;
// 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) {
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),
),
);
},
};
// 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));
server.state.notify_players(msg);
Ok(())
} else {
Err(action.help_string())
}
}
2020-04-24 02:36:19 +00:00
fn handle_tell(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
2022-06-14 20:35:01 +00:00
if let (Some(alias), message_opt) = parse_cmd_args!(args, String, ..Vec<String>) {
2019-07-30 08:10:58 +00:00
let ecs = server.state.ecs();
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
}
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")?;
2021-07-23 16:33:31 +00:00
let msg = if message_opt.is_empty() {
format!("{} wants to talk to you.", alias)
} else {
message_opt.join(" ")
};
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 {
Err(action.help_string())
2019-07-13 04:25:44 +00:00
}
}
fn handle_faction(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
let factions = server.state.ecs().read_storage();
if let Some(comp::Faction(faction)) = factions.get(target) {
let mode = comp::ChatMode::Faction(faction.to_string());
drop(factions);
insert_or_replace_component(server, target, mode.clone(), "target")?;
2021-07-23 16:33:31 +00:00
let msg = args.join(" ");
if !msg.is_empty() {
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
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
} else {
Err("Please join a faction with /join_faction".into())
}
}
fn handle_group(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> 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);
drop(groups);
insert_or_replace_component(server, target, mode.clone(), "target")?;
2021-07-23 16:33:31 +00:00
let msg = args.join(" ");
if !msg.is_empty() {
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
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
} else {
Err("Please create a group first".into())
}
}
2020-12-04 02:18:42 +00:00
fn handle_group_invite(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(target_alias) = parse_cmd_args!(args, String) {
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
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::InitiateInvite(target, uid, InviteKind::Group));
2020-12-04 02:18:42 +00:00
if client != target {
2020-12-04 02:18:42 +00:00
server.notify_client(
target,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("{} has been invited to your group.", target_alias),
),
2020-12-04 02:18:42 +00:00
);
}
2020-12-04 02:18:42 +00:00
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Invited {} to the group.", target_alias),
),
2020-12-04 02:18:42 +00:00
);
Ok(())
} else {
Err(action.help_string())
2020-12-04 02:18:42 +00:00
}
}
fn handle_group_kick(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2020-12-04 02:18:42 +00:00
// Checking if leader is already done in group_manip
2022-06-14 20:35:01 +00:00
if let Some(target_alias) = parse_cmd_args!(args, String) {
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 {
Err(action.help_string())
2020-12-04 02:18:42 +00:00
}
}
fn handle_group_leave(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2020-12-04 02:18:42 +00:00
server
.state
.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,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2020-12-04 02:18:42 +00:00
// Checking if leader is already done in group_manip
2022-06-14 20:35:01 +00:00
if let Some(target_alias) = parse_cmd_args!(args, String) {
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 {
Err(action.help_string())
2020-12-04 02:18:42 +00:00
}
}
fn handle_region(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
2020-06-02 02:42:26 +00:00
let mode = comp::ChatMode::Region;
insert_or_replace_component(server, target, mode.clone(), "target")?;
2021-07-23 16:33:31 +00:00
let msg = args.join(" ");
if !msg.is_empty() {
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
}
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
}
fn handle_say(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
2020-06-02 02:42:26 +00:00
let mode = comp::ChatMode::Say;
insert_or_replace_component(server, target, mode.clone(), "target")?;
2021-07-23 16:33:31 +00:00
let msg = args.join(" ");
if !msg.is_empty() {
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
}
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
}
fn handle_world(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
2020-06-02 02:42:26 +00:00
let mode = comp::ChatMode::World;
insert_or_replace_component(server, target, mode.clone(), "target")?;
2021-07-23 16:33:31 +00:00
let msg = args.join(" ");
if !msg.is_empty() {
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
}
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
}
fn handle_join_faction(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let players = server.state.ecs().read_storage::<comp::Player>();
if let Some(alias) = players.get(target).map(|player| player.alias.clone()) {
drop(players);
2022-06-14 20:35:01 +00:00
let (faction_leave, mode) = if let Some(faction) = parse_cmd_args!(args, String) {
let mode = comp::ChatMode::Faction(faction.clone());
insert_or_replace_component(server, target, mode.clone(), "target")?;
let faction_join = server
.state
.ecs()
.write_storage()
.insert(target, comp::Faction(faction.clone()))
.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)),
);
(faction_join, mode)
} else {
let mode = comp::ChatMode::default();
insert_or_replace_component(server, target, mode.clone(), "target")?;
let faction_leave = server
.state
.ecs()
.write_storage()
.remove(target)
.map(|comp::Faction(f)| f);
(faction_leave, mode)
};
if let Some(faction) = faction_leave {
server.state.send_chat(
ChatType::FactionMeta(faction.clone())
.chat_msg(format!("[{}] left faction ({})", alias, faction)),
);
}
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
} else {
Err("Could not find your player alias".into())
}
}
#[cfg(not(feature = "worldgen"))]
fn handle_debug_column(
server: &mut Server,
2020-04-24 02:36:19 +00:00
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
Err("Unsupported without worldgen enabled".into())
}
#[cfg(feature = "worldgen")]
2020-04-24 02:36:19 +00:00
fn handle_debug_column(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
let sim = server.world.sim();
2021-12-06 23:06:48 +00:00
let calendar = (*server.state.ecs().read_resource::<Calendar>()).clone();
let sampler = server.world.sample_columns();
2022-06-14 20:35:01 +00:00
let wpos = if let (Some(x), Some(y)) = parse_cmd_args!(args, i32, i32) {
Vec2::new(x, y)
2020-11-21 12:33:52 +00:00
} else {
let pos = position(server, target, "target")?;
// FIXME: Deal with overflow, if needed.
pos.0.xy().map(|x| x as i32)
};
2021-12-06 23:06:48 +00:00
let msg_generator = |calendar| {
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)?;
2021-12-06 23:06:48 +00:00
let col = sampler.get((wpos, server.index.as_index_ref(), Some(calendar)))?;
let gradient = sim.get_gradient_approx(chunk_pos)?;
let downhill = chunk.downhill;
let river = &chunk.river;
let flux = chunk.flux;
Some(format!(
r#"wpos: {:?}
alt {:?} ({:?})
water_alt {:?} ({:?})
2019-11-19 18:34:52 +00:00
basement {:?}
river {:?}
gradient {:?}
downhill {:?}
chaos {:?}
flux {:?}
temp {:?}
humidity {:?}
rockiness {:?}
tree_density {:?}
spawn_rate {:?} "#,
wpos,
alt,
col.alt,
water_alt,
col.water_level,
basement,
river,
gradient,
downhill,
chaos,
flux,
temp,
humidity,
rockiness,
tree_density,
spawn_rate
))
};
2021-12-06 23:06:48 +00:00
if let Some(s) = msg_generator(&calendar) {
server.notify_client(client, ServerGeneral::server_msg(ChatType::CommandInfo, s));
Ok(())
} else {
Err("Not a pregenerated chunk.".into())
2020-02-13 20:32:24 +00:00
}
}
fn handle_disconnect_all_players(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
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")?;
2022-06-14 20:35:01 +00:00
if parse_cmd_args!(args, String).as_deref() != Some("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.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(())
}
fn handle_skill_point(
2020-04-24 02:36:19 +00:00
server: &mut Server,
_client: EcsEntity,
2020-04-24 02:36:19 +00:00
target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(a_skill_tree), Some(sp), a_alias) = parse_cmd_args!(args, 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))?;
if let Some(mut skill_set) = server
.state
.ecs_mut()
.write_storage::<comp::SkillSet>()
.get_mut(player)
{
skill_set.add_skill_points(skill_tree, sp);
Ok(())
} else {
Err("Player has no stats!".into())
}
} else {
Err(action.help_string())
2019-10-04 15:48:14 +00:00
}
}
2019-10-07 03:19:46 +00:00
fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skillset::SkillGroupKind> {
use comp::{item::tool::ToolKind, skillset::SkillGroupKind};
match skill_tree {
"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)),
"mining" => Ok(SkillGroupKind::Weapon(ToolKind::Pick)),
_ => Err(format!("{} is not a skill group!", skill_tree)),
2020-02-13 21:15:54 +00:00
}
}
2022-03-19 08:43:38 +00:00
fn handle_reload_chunks(
server: &mut Server,
_client: EcsEntity,
_target: EcsEntity,
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
2022-03-19 08:43:38 +00:00
) -> CmdResult<()> {
server.state.clear_terrain();
Ok(())
}
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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
let opt_radius = parse_cmd_args!(args, f32);
let player_pos = position(server, target, "target")?;
2019-10-07 03:19:46 +00:00
let mut to_delete = vec![];
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();
for entity in to_delete {
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
}
server.notify_client(
2020-04-24 02:36:19 +00:00
client,
ServerGeneral::server_msg(ChatType::CommandInfo, format!("Removed {} lights!", size)),
2019-10-07 03:19:46 +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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2020-04-25 17:18:29 +00:00
if let (Some(player_alias), Some(cmd), cmd_args) =
2022-06-14 20:35:01 +00:00
parse_cmd_args!(args, String, String, ..Vec<String>)
2020-04-24 02:36:19 +00:00
{
if let Ok(action) = cmd.parse() {
let (player, player_uuid) = find_alias(server.state.ecs(), &player_alias)?;
let client_uuid = uuid(server, client, "client")?;
verify_above_role(
server,
(client, client_uuid),
(player, player_uuid),
"Cannot sudo players with roles higher than your own.",
)?;
// 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 {
Err(format!("Unknown command: /{}", cmd))
2020-04-24 02:36:19 +00:00
}
} else {
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,
2021-07-23 16:33:31 +00:00
_args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2020-05-04 15:59:53 +00:00
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!(
"Server is running {}[{}]",
*common::util::GIT_HASH,
*common::util::GIT_DATE,
),
),
2020-05-04 15:59:53 +00:00
);
Ok(())
2020-05-04 15:59:53 +00:00
}
fn handle_whitelist(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
let now = Utc::now();
2022-06-14 20:35:01 +00:00
if let (Some(whitelist_action), Some(username)) = parse_cmd_args!(args, String, String) {
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")?;
if whitelist_action.eq_ignore_ascii_case("add") {
let uuid = find_username(server, &username)?;
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)
})
} else if whitelist_action.eq_ignore_ascii_case("remove") {
let client_uuid = uuid(server, client, "client")?;
let client_role = real_role(server, client_uuid, "client")?;
let uuid = find_username(server, &username)?;
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))
} else {
Err(action.help_string())
}
} else {
Err(action.help_string())
}
}
2020-07-16 00:48:37 +00:00
fn kick_player(
server: &mut Server,
(client, client_uuid): (EcsEntity, Uuid),
(target_player, target_player_uuid): (EcsEntity, Uuid),
reason: &str,
) -> CmdResult<()> {
verify_above_role(
server,
(client, client_uuid),
(target_player, target_player_uuid),
"Cannot kick players with roles higher than your own.",
)?;
2020-09-14 06:16:09 +00:00
server.notify_client(
target_player,
ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())),
2020-09-14 06:16:09 +00:00
);
server
.state
.mut_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::ClientDisconnect(
target_player,
common::comp::DisconnectReason::Kicked,
));
Ok(())
}
2020-07-16 00:48:37 +00:00
fn handle_kick(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(target_alias), reason_opt) = parse_cmd_args!(args, String, String) {
let client_uuid = uuid(server, client, "client")?;
let reason = reason_opt.unwrap_or_default();
2020-07-16 00:48:37 +00:00
let ecs = server.state.ecs();
let target_player = find_alias(ecs, &target_alias)?;
2020-07-16 00:48:37 +00:00
kick_player(server, (client, client_uuid), target_player, &reason)?;
2020-07-16 00:48:37 +00:00
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!(
"Kicked {} from the server with reason: {}",
target_alias, reason
),
),
2020-07-16 00:48:37 +00:00
);
Ok(())
} else {
Err(action.help_string())
2020-07-16 00:48:37 +00:00
}
}
fn handle_ban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2021-07-23 16:33:31 +00:00
if let (Some(username), overwrite, parse_duration, reason_opt) =
2022-06-14 20:35:01 +00:00
parse_cmd_args!(args, String, bool, HumanDuration, String)
2021-07-23 16:33:31 +00:00
{
let reason = reason_opt.unwrap_or_default();
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(),
};
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| {
(
format!("Added {} to the banlist with reason: {}", username, reason),
result,
)
});
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
}
Ok(())
2020-07-16 20:56:11 +00:00
} else {
Err(action.help_string())
2020-07-16 20:56:11 +00:00
}
2020-07-16 00:48:37 +00:00
}
fn handle_battlemode(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2021-09-03 21:30:31 +00:00
// TODO: discuss time
const COOLDOWN: f64 = 60.0 * 5.0;
let ecs = server.state.ecs();
2021-09-03 21:30:31 +00:00
let time = ecs.read_resource::<Time>();
let settings = ecs.read_resource::<Settings>();
2022-06-14 20:35:01 +00:00
if let Some(mode) = parse_cmd_args!(args, String) {
if !settings.gameplay.battle_mode.allow_choosing() {
2021-08-28 13:32:18 +00:00
return Err("Command disabled in server settings".to_owned());
}
#[cfg(feature = "worldgen")]
let in_town = {
2021-08-28 13:32:18 +00:00
// get chunk position
let pos = position(server, target, "target")?;
2021-08-28 13:32:18 +00:00
let wpos = pos.0.xy().map(|x| x as i32);
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |wpos, size: u32| {
wpos / size as i32
2021-08-28 13:32:18 +00:00
});
server.world.civs().sites().any(|site| {
// empirical
const RADIUS: f32 = 9.0;
let delta = site
.center
.map(|x| x as f32)
.distance(chunk_pos.map(|x| x as f32));
delta < RADIUS
})
};
// just skip this check, if worldgen is disabled
#[cfg(not(feature = "worldgen"))]
let in_town = true;
if !in_town {
return Err("You need to be in town to change battle mode!".to_owned());
}
let mut players = ecs.write_storage::<comp::Player>();
let mut player_info = players.get_mut(target).ok_or_else(|| {
error!("Can't get player component for player");
"Error!"
})?;
if let Some(Time(last_change)) = player_info.last_battlemode_change {
let Time(time) = *time;
let elapsed = time - last_change;
if elapsed < COOLDOWN {
let msg = format!(
2021-09-04 17:56:55 +00:00
"Cooldown period active. Try again in {:.0} seconds",
COOLDOWN - elapsed,
);
return Err(msg);
}
}
let mode = match mode.as_str() {
"pvp" => BattleMode::PvP,
"pve" => BattleMode::PvE,
_ => return Err("Available modes: pvp, pve".to_owned()),
};
2021-09-03 21:30:31 +00:00
if player_info.battle_mode == mode {
return Err("Attempted to set the same battlemode".to_owned());
}
player_info.battle_mode = mode;
player_info.last_battlemode_change = Some(*time);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("New battle mode: {:?}", mode),
),
);
Ok(())
} else {
let players = ecs.read_storage::<comp::Player>();
let player = players.get(target).ok_or_else(|| {
error!("Can't get player component for player");
"Error!"
})?;
2021-09-03 21:30:31 +00:00
let mut msg = format!("Current battle mode: {:?}.", player.battle_mode);
if settings.gameplay.battle_mode.allow_choosing() {
2021-09-03 21:30:31 +00:00
msg.push_str(" Possible to change.");
} else {
msg.push_str(" Global.");
}
if let Some(change) = player.last_battlemode_change {
let Time(time) = *time;
let Time(change) = change;
let elapsed = time - change;
let next = COOLDOWN - elapsed;
let notice = format!(" Next change will be available in: {:.0} seconds", next);
msg.push_str(&notice);
}
server.notify_client(
client,
2021-09-03 21:30:31 +00:00
ServerGeneral::server_msg(ChatType::CommandInfo, msg),
);
Ok(())
}
}
fn handle_battlemode_force(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
let ecs = server.state.ecs();
let settings = ecs.read_resource::<Settings>();
if !settings.gameplay.battle_mode.allow_choosing() {
return Err("Command disabled in server settings".to_owned());
}
2022-06-14 20:35:01 +00:00
let mode = parse_cmd_args!(args, String).ok_or_else(|| action.help_string())?;
let mode = match mode.as_str() {
"pvp" => BattleMode::PvP,
"pve" => BattleMode::PvE,
_ => return Err("Available modes: pvp, pve".to_owned()),
};
let mut players = ecs.write_storage::<comp::Player>();
let mut player_info = players
.get_mut(target)
.ok_or("Cannot get player component for target")?;
player_info.battle_mode = mode;
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Set battle mode to: {:?}", mode),
),
);
Ok(())
}
2020-07-16 00:48:37 +00:00
fn handle_unban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(username) = parse_cmd_args!(args, String) {
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 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
.editable_settings_mut()
.banlist
.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)
})
} 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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
2021-04-17 17:44:22 +00:00
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(username), enabled_opt) = parse_cmd_args!(args, String, bool) {
2021-04-17 17:44:22 +00:00
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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
2021-05-02 21:42:54 +00:00
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let (Some(buff), strength, duration) = parse_cmd_args!(args, String, f32, f64) {
2021-05-02 21:42:54 +00:00
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,
2021-07-23 16:33:31 +00:00
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
2021-05-08 15:47:09 +00:00
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(preset) = parse_cmd_args!(args, String) {
2021-05-08 15:47:09 +00:00
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<()> {
let presets = match common::cmd::SkillPresetManifest::load(PRESET_MANIFEST_PATH) {
2021-05-22 17:41:47 +00:00
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);
match skill_set.unlock_skill(*skill) {
Ok(_) | Err(comp::skillset::SkillUnlockError::SkillAlreadyUnlocked) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}?;
2021-05-08 15:47:09 +00:00
}
}
Ok(())
} else {
Err("Such preset doesn't exist".to_owned())
}
}
fn handle_location(
server: &mut Server,
2022-02-17 16:01:18 +00:00
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
_action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(name) = parse_cmd_args!(args, String) {
2022-02-17 16:01:18 +00:00
let loc = server.state.ecs().read_resource::<Locations>().get(&name);
match loc {
Ok(loc) => position_mut(server, target, "target", |target_pos| {
target_pos.0 = loc;
}),
Err(e) => Err(e.to_string()),
}
} else {
2022-02-17 16:01:18 +00:00
let locations = server.state.ecs().read_resource::<Locations>();
let mut locations = locations.iter().map(|s| s.as_str()).collect::<Vec<_>>();
locations.sort_unstable();
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
if locations.is_empty() {
"No locations currently exist".to_owned()
} else {
format!("Available locations:\n{}", locations.join(", "))
},
),
);
Ok(())
}
}
fn handle_create_location(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(name) = parse_cmd_args!(args, String) {
let target_pos = position(server, target, "target")?;
let res = server
.state
.ecs_mut()
.write_resource::<Locations>()
.insert(name.clone(), target_pos.0);
match res {
Ok(()) => {
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Created location '{}'", name),
),
);
Ok(())
},
Err(e) => Err(e.to_string()),
}
} else {
Err(action.help_string())
}
}
fn handle_delete_location(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: Vec<String>,
2022-06-14 20:35:01 +00:00
action: &ServerChatCommand,
) -> CmdResult<()> {
2022-06-14 20:35:01 +00:00
if let Some(name) = parse_cmd_args!(args, String) {
let res = server
.state
.ecs_mut()
.write_resource::<Locations>()
.remove(&name);
match res {
Ok(()) => {
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Deleted location '{}'", name),
),
);
Ok(())
},
Err(e) => Err(e.to_string()),
}
} else {
Err(action.help_string())
}
}