Merge branch 'desttinghim/build-areas' into 'master'

Build Areas

See merge request veloren/veloren!1978
This commit is contained in:
Imbris 2021-03-27 15:26:35 +00:00
commit 973a2af753
8 changed files with 375 additions and 31 deletions

View File

@ -14,9 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- You can now jump out of rolls for a slight jump boost - You can now jump out of rolls for a slight jump boost
- Dungeons now have multiple kinds of stairs. - Dungeons now have multiple kinds of stairs.
- Trades now display item prices in tooltips. - Trades now display item prices in tooltips.
- Admin designated build areas
### Changed ### Changed
- Permission to build is no longer tied to being an admin
### Removed ### Removed
### Fixed ### Fixed

View File

@ -40,6 +40,9 @@ pub enum ChatCommand {
Alias, Alias,
Ban, Ban,
Build, Build,
BuildAreaAdd,
BuildAreaList,
BuildAreaRemove,
Campfire, Campfire,
Debug, Debug,
DebugColumn, DebugColumn,
@ -68,9 +71,12 @@ pub enum ChatCommand {
MakeSprite, MakeSprite,
Motd, Motd,
Object, Object,
PermitBuild,
Players, Players,
Region, Region,
RemoveLights, RemoveLights,
RevokeBuild,
RevokeBuildAll,
Safezone, Safezone,
Say, Say,
SetMotd, SetMotd,
@ -94,6 +100,9 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Alias, ChatCommand::Alias,
ChatCommand::Ban, ChatCommand::Ban,
ChatCommand::Build, ChatCommand::Build,
ChatCommand::BuildAreaAdd,
ChatCommand::BuildAreaList,
ChatCommand::BuildAreaRemove,
ChatCommand::Campfire, ChatCommand::Campfire,
ChatCommand::Debug, ChatCommand::Debug,
ChatCommand::DebugColumn, ChatCommand::DebugColumn,
@ -122,9 +131,12 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::MakeSprite, ChatCommand::MakeSprite,
ChatCommand::Motd, ChatCommand::Motd,
ChatCommand::Object, ChatCommand::Object,
ChatCommand::PermitBuild,
ChatCommand::Players, ChatCommand::Players,
ChatCommand::Region, ChatCommand::Region,
ChatCommand::RemoveLights, ChatCommand::RemoveLights,
ChatCommand::RevokeBuild,
ChatCommand::RevokeBuildAll,
ChatCommand::Safezone, ChatCommand::Safezone,
ChatCommand::Say, ChatCommand::Say,
ChatCommand::SetMotd, ChatCommand::SetMotd,
@ -235,7 +247,26 @@ impl ChatCommand {
"Ban a player with a given username", "Ban a player with a given username",
Admin, Admin,
), ),
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin), ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", NoAdmin),
ChatCommand::BuildAreaAdd => cmd(
vec![
Any("name", Required),
Integer("xlo", 0, Required),
Integer("xhi", 10, Required),
Integer("ylo", 0, Required),
Integer("yhi", 10, Required),
Integer("zlo", 0, Required),
Integer("zhi", 10, Required),
],
"Adds a new build area",
Admin,
),
ChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Admin),
ChatCommand::BuildAreaRemove => cmd(
vec![Any("name", Required)],
"Removes specified build area",
Admin,
),
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin), ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin),
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin), ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
ChatCommand::DebugColumn => cmd( ChatCommand::DebugColumn => cmd(
@ -368,12 +399,27 @@ impl ChatCommand {
"Spawn an object", "Spawn an object",
Admin, Admin,
), ),
ChatCommand::PermitBuild => cmd(
vec![Any("area_name", Required)],
"Grants player a bounded box they can build in",
Admin,
),
ChatCommand::Players => cmd(vec![], "Lists players currently online", NoAdmin), ChatCommand::Players => cmd(vec![], "Lists players currently online", NoAdmin),
ChatCommand::RemoveLights => cmd( ChatCommand::RemoveLights => cmd(
vec![Float("radius", 20.0, Optional)], vec![Float("radius", 20.0, Optional)],
"Removes all lights spawned by players", "Removes all lights spawned by players",
Admin, Admin,
), ),
ChatCommand::RevokeBuild => cmd(
vec![Any("area_name", Required)],
"Revokes build area permission for player",
Admin,
),
ChatCommand::RevokeBuildAll => cmd(
vec![],
"Revokes all build area permissions for player",
Admin,
),
ChatCommand::Region => cmd( ChatCommand::Region => cmd(
vec![Message(Optional)], vec![Message(Optional)],
"Send messages to everyone in your region of the world", "Send messages to everyone in your region of the world",
@ -460,6 +506,9 @@ impl ChatCommand {
ChatCommand::Alias => "alias", ChatCommand::Alias => "alias",
ChatCommand::Ban => "ban", ChatCommand::Ban => "ban",
ChatCommand::Build => "build", ChatCommand::Build => "build",
ChatCommand::BuildAreaAdd => "build_area_add",
ChatCommand::BuildAreaList => "build_area_list",
ChatCommand::BuildAreaRemove => "build_area_remove",
ChatCommand::Campfire => "campfire", ChatCommand::Campfire => "campfire",
ChatCommand::Debug => "debug", ChatCommand::Debug => "debug",
ChatCommand::DebugColumn => "debug_column", ChatCommand::DebugColumn => "debug_column",
@ -488,9 +537,12 @@ impl ChatCommand {
ChatCommand::MakeSprite => "make_sprite", ChatCommand::MakeSprite => "make_sprite",
ChatCommand::Motd => "motd", ChatCommand::Motd => "motd",
ChatCommand::Object => "object", ChatCommand::Object => "object",
ChatCommand::PermitBuild => "permit_build",
ChatCommand::Players => "players", ChatCommand::Players => "players",
ChatCommand::Region => "region", ChatCommand::Region => "region",
ChatCommand::RemoveLights => "remove_lights", ChatCommand::RemoveLights => "remove_lights",
ChatCommand::RevokeBuild => "revoke_build",
ChatCommand::RevokeBuildAll => "revoke_build_all",
ChatCommand::Safezone => "safezone", ChatCommand::Safezone => "safezone",
ChatCommand::Say => "say", ChatCommand::Say => "say",
ChatCommand::SetMotd => "set_motd", ChatCommand::SetMotd => "set_motd",

View File

@ -1,8 +1,15 @@
use crate::depot::Id;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, NullStorage}; use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::collections::HashSet;
use vek::geom::Aabb;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct CanBuild; pub struct CanBuild {
impl Component for CanBuild { pub enabled: bool,
type Storage = DerefFlaggedStorage<Self, NullStorage<Self>>; pub build_areas: HashSet<Id<Aabb<i32>>>,
}
impl Component for CanBuild {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
} }

View File

@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use std::{ use std::{
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
fmt, hash, fmt, hash,
@ -6,6 +7,7 @@ use std::{
}; };
/// Type safe index into Depot /// Type safe index into Depot
#[derive(Deserialize, Serialize)]
pub struct Id<T> { pub struct Id<T> {
idx: u32, idx: u32,
gen: u32, gen: u32,

View File

@ -6,6 +6,7 @@ use crate::plugin::PluginMgr;
use common::uid::UidAllocator; use common::uid::UidAllocator;
use common::{ use common::{
comp, comp,
depot::{Depot, Id},
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
region::RegionMap, region::RegionMap,
resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay}, resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay},
@ -39,6 +40,21 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// avoid such a situation. /// avoid such a situation.
const MAX_DELTA_TIME: f32 = 1.0; const MAX_DELTA_TIME: f32 = 1.0;
#[derive(Default)]
pub struct BuildAreas {
pub areas: Depot<geom::Aabb<i32>>,
pub area_names: HashMap<String, Id<Aabb<i32>>>,
}
impl BuildAreas {
pub fn new() -> Self {
Self {
areas: Depot::default(),
area_names: HashMap::new(),
}
}
}
#[derive(Default)] #[derive(Default)]
pub struct BlockChange { pub struct BlockChange {
blocks: HashMap<Vec3<i32>, Block>, blocks: HashMap<Vec3<i32>, Block>,
@ -204,6 +220,7 @@ impl State {
ecs.insert(PlayerEntity(None)); ecs.insert(PlayerEntity(None));
ecs.insert(TerrainGrid::new().unwrap()); ecs.insert(TerrainGrid::new().unwrap());
ecs.insert(BlockChange::default()); ecs.insert(BlockChange::default());
ecs.insert(BuildAreas::new());
ecs.insert(TerrainChanges::default()); ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default()); ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(game_mode); ecs.insert(game_mode);

View File

@ -30,9 +30,15 @@ use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral}, msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt, sync::WorldSyncExt,
}; };
use common_sys::state::BuildAreas;
use rand::Rng; use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use std::{convert::TryFrom, time::Duration}; use std::{
collections::HashSet,
convert::TryFrom,
ops::{Deref, DerefMut},
time::Duration,
};
use vek::*; use vek::*;
use world::util::Sampler; use world::util::Sampler;
@ -81,6 +87,9 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Alias => handle_alias, ChatCommand::Alias => handle_alias,
ChatCommand::Ban => handle_ban, ChatCommand::Ban => handle_ban,
ChatCommand::Build => handle_build, ChatCommand::Build => handle_build,
ChatCommand::BuildAreaAdd => handle_build_area_add,
ChatCommand::BuildAreaList => handle_build_area_list,
ChatCommand::BuildAreaRemove => handle_build_area_remove,
ChatCommand::Campfire => handle_spawn_campfire, ChatCommand::Campfire => handle_spawn_campfire,
ChatCommand::Debug => handle_debug, ChatCommand::Debug => handle_debug,
ChatCommand::DebugColumn => handle_debug_column, ChatCommand::DebugColumn => handle_debug_column,
@ -109,9 +118,12 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::MakeSprite => handle_make_sprite, ChatCommand::MakeSprite => handle_make_sprite,
ChatCommand::Motd => handle_motd, ChatCommand::Motd => handle_motd,
ChatCommand::Object => handle_object, ChatCommand::Object => handle_object,
ChatCommand::PermitBuild => handle_permit_build,
ChatCommand::Players => handle_players, ChatCommand::Players => handle_players,
ChatCommand::Region => handle_region, ChatCommand::Region => handle_region,
ChatCommand::RemoveLights => handle_remove_lights, ChatCommand::RemoveLights => handle_remove_lights,
ChatCommand::RevokeBuild => handle_revoke_build,
ChatCommand::RevokeBuildAll => handle_revoke_build_all,
ChatCommand::Safezone => handle_safezone, ChatCommand::Safezone => handle_safezone,
ChatCommand::Say => handle_say, ChatCommand::Say => handle_say,
ChatCommand::SetMotd => handle_set_motd, ChatCommand::SetMotd => handle_set_motd,
@ -1116,6 +1128,105 @@ fn handle_safezone(
} }
} }
fn handle_permit_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
if server
.state
.read_storage::<comp::CanBuild>()
.get(target)
.is_none()
{
let _ = ecs
.write_storage::<comp::CanBuild>()
.insert(target, comp::CanBuild {
enabled: false,
build_areas: HashSet::new(),
});
}
if let Some(bb_id) = ecs
.read_resource::<BuildAreas>()
.deref()
.area_names
.get(&area_name)
{
if let Some(mut comp_can_build) = ecs.write_storage::<comp::CanBuild>().get_mut(target)
{
comp_can_build.build_areas.insert(*bb_id);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Permission to build in {} granted", area_name),
),
);
}
}
} else {
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandError, action.help_string()),
);
}
}
fn handle_revoke_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
if let Some(bb_id) = ecs
.read_resource::<BuildAreas>()
.deref()
.area_names
.get(&area_name)
{
if let Some(mut comp_can_build) = ecs.write_storage::<comp::CanBuild>().get_mut(target)
{
comp_can_build.build_areas.retain(|&x| x != *bb_id);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Permission to build in {} revoked", area_name),
),
);
}
}
} else {
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandError, action.help_string()),
);
}
}
fn handle_revoke_build_all(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_action: &ChatCommand,
) {
let ecs = server.state.ecs();
ecs.write_storage::<comp::CanBuild>().remove(target);
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "All build permissions revoked"),
);
}
fn handle_players( fn handle_players(
server: &mut Server, server: &mut Server,
client: EcsEntity, client: EcsEntity,
@ -1150,30 +1261,152 @@ fn handle_build(
_args: String, _args: String,
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
if server if let Some(mut can_build) = server
.state .state
.read_storage::<comp::CanBuild>() .ecs()
.get(target) .write_storage::<comp::CanBuild>()
.is_some() .get_mut(target)
{ {
server if can_build.enabled {
.state can_build.enabled = false;
.ecs() server.notify_client(
.write_storage::<comp::CanBuild>() client,
.remove(target); ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled off build mode!"),
server.notify_client( );
client, } else {
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled off build mode!"), can_build.enabled = true;
); server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled on build mode!"),
);
}
} else { } else {
let _ = server
.state
.ecs()
.write_storage::<comp::CanBuild>()
.insert(target, comp::CanBuild);
server.notify_client( server.notify_client(
client, client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled on build mode!"), ServerGeneral::server_msg(
ChatType::CommandInfo,
"You do not have permission to build.",
),
);
}
}
fn handle_build_area_add(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) = scan_fmt_some!(
&args,
&action.arg_fmt(),
String,
i32,
i32,
i32,
i32,
i32,
i32
) {
let ecs = server.state.ecs();
if ecs
.read_resource::<BuildAreas>()
.deref()
.area_names
.contains_key(&area_name)
{
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandError,
format!("Build zone {} already exists!", area_name),
),
);
return;
}
let bb_id = ecs.write_resource::<BuildAreas>().deref_mut().areas.insert(
Aabb {
min: Vec3::new(xlo, ylo, zlo),
max: Vec3::new(xhi, yhi, zhi),
}
.made_valid(),
);
ecs.write_resource::<BuildAreas>()
.deref_mut()
.area_names
.insert(area_name.clone(), bb_id);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Created build zone {}", area_name),
),
);
}
}
fn handle_build_area_list(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_action: &ChatCommand,
) {
let ecs = server.state.ecs();
let build_areas = ecs.read_resource::<BuildAreas>();
server.notify_client(
client,
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
}
},
),
),
);
}
fn handle_build_area_remove(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
let mut build_areas = ecs.write_resource::<BuildAreas>();
let bb_id = match build_areas.area_names.get(&area_name) {
Some(x) => *x,
None => {
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandError,
format!("No such build area '{}'", area_name),
),
);
return;
},
};
let _ = build_areas.area_names.remove(&area_name);
let _ = build_areas.areas.remove(bb_id);
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Removed build zone {}", area_name),
),
); );
} }
} }

View File

@ -7,7 +7,7 @@ use common::{
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral}; use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral};
use common_sys::state::BlockChange; use common_sys::state::{BlockChange, BuildAreas};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -29,6 +29,7 @@ impl Sys {
orientations: &mut WriteStorage<'_, Ori>, orientations: &mut WriteStorage<'_, Ori>,
controllers: &mut WriteStorage<'_, Controller>, controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, Settings>, settings: &Read<'_, Settings>,
build_areas: &Read<'_, BuildAreas>,
msg: ClientGeneral, msg: ClientGeneral,
) -> Result<(), crate::error::Error> { ) -> Result<(), crate::error::Error> {
let presence = match maybe_presence { let presence = match maybe_presence {
@ -102,13 +103,39 @@ impl Sys {
} }
}, },
ClientGeneral::BreakBlock(pos) => { ClientGeneral::BreakBlock(pos) => {
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { if let Some(comp_can_build) = can_build.get(entity) {
block_changes.set(pos, block.into_vacant()); if comp_can_build.enabled {
for area in comp_can_build.build_areas.iter() {
if let Some(block) = build_areas
.areas
.get(*area)
// TODO: Make this an exclusive check on the upper bound of the AABB
// Vek defaults to inclusive which is not optimal
.filter(|aabb| aabb.contains_point(pos))
.and_then(|_| terrain.get(pos).ok())
{
block_changes.set(pos, block.into_vacant());
}
}
}
} }
}, },
ClientGeneral::PlaceBlock(pos, block) => { ClientGeneral::PlaceBlock(pos, block) => {
if can_build.get(entity).is_some() { if let Some(comp_can_build) = can_build.get(entity) {
block_changes.try_set(pos, block); if comp_can_build.enabled {
for area in comp_can_build.build_areas.iter() {
if build_areas
.areas
.get(*area)
// TODO: Make this an exclusive check on the upper bound of the AABB
// Vek defaults to inclusive which is not optimal
.filter(|aabb| aabb.contains_point(pos))
.is_some()
{
block_changes.try_set(pos, block);
}
}
}
} }
}, },
ClientGeneral::UnlockSkill(skill) => { ClientGeneral::UnlockSkill(skill) => {
@ -156,6 +183,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
Read<'a, Settings>, Read<'a, Settings>,
Read<'a, BuildAreas>,
); );
const NAME: &'static str = "msg::in_game"; const NAME: &'static str = "msg::in_game";
@ -180,6 +208,7 @@ impl<'a> System<'a> for Sys {
mut clients, mut clients,
mut controllers, mut controllers,
settings, settings,
build_areas,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_event_bus.emitter(); let mut server_emitter = server_event_bus.emitter();
@ -204,6 +233,7 @@ impl<'a> System<'a> for Sys {
&mut orientations, &mut orientations,
&mut controllers, &mut controllers,
&settings, &settings,
&build_areas,
msg, msg,
) )
}); });

View File

@ -317,7 +317,7 @@ impl PlayState for SessionState {
.state() .state()
.read_storage::<comp::CanBuild>() .read_storage::<comp::CanBuild>()
.get(player_entity) .get(player_entity)
.is_some(); .map_or_else(|| false, |cb| cb.enabled);
let is_mining = self let is_mining = self
.client .client