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
- Dungeons now have multiple kinds of stairs.
- Trades now display item prices in tooltips.
- Admin designated build areas
### Changed
- Permission to build is no longer tied to being an admin
### Removed
### Fixed

View File

@ -40,6 +40,9 @@ pub enum ChatCommand {
Alias,
Ban,
Build,
BuildAreaAdd,
BuildAreaList,
BuildAreaRemove,
Campfire,
Debug,
DebugColumn,
@ -68,9 +71,12 @@ pub enum ChatCommand {
MakeSprite,
Motd,
Object,
PermitBuild,
Players,
Region,
RemoveLights,
RevokeBuild,
RevokeBuildAll,
Safezone,
Say,
SetMotd,
@ -94,6 +100,9 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::Alias,
ChatCommand::Ban,
ChatCommand::Build,
ChatCommand::BuildAreaAdd,
ChatCommand::BuildAreaList,
ChatCommand::BuildAreaRemove,
ChatCommand::Campfire,
ChatCommand::Debug,
ChatCommand::DebugColumn,
@ -122,9 +131,12 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
ChatCommand::MakeSprite,
ChatCommand::Motd,
ChatCommand::Object,
ChatCommand::PermitBuild,
ChatCommand::Players,
ChatCommand::Region,
ChatCommand::RemoveLights,
ChatCommand::RevokeBuild,
ChatCommand::RevokeBuildAll,
ChatCommand::Safezone,
ChatCommand::Say,
ChatCommand::SetMotd,
@ -235,7 +247,26 @@ impl ChatCommand {
"Ban a player with a given username",
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::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
ChatCommand::DebugColumn => cmd(
@ -368,12 +399,27 @@ impl ChatCommand {
"Spawn an object",
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::RemoveLights => cmd(
vec![Float("radius", 20.0, Optional)],
"Removes all lights spawned by players",
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(
vec![Message(Optional)],
"Send messages to everyone in your region of the world",
@ -460,6 +506,9 @@ impl ChatCommand {
ChatCommand::Alias => "alias",
ChatCommand::Ban => "ban",
ChatCommand::Build => "build",
ChatCommand::BuildAreaAdd => "build_area_add",
ChatCommand::BuildAreaList => "build_area_list",
ChatCommand::BuildAreaRemove => "build_area_remove",
ChatCommand::Campfire => "campfire",
ChatCommand::Debug => "debug",
ChatCommand::DebugColumn => "debug_column",
@ -488,9 +537,12 @@ impl ChatCommand {
ChatCommand::MakeSprite => "make_sprite",
ChatCommand::Motd => "motd",
ChatCommand::Object => "object",
ChatCommand::PermitBuild => "permit_build",
ChatCommand::Players => "players",
ChatCommand::Region => "region",
ChatCommand::RemoveLights => "remove_lights",
ChatCommand::RevokeBuild => "revoke_build",
ChatCommand::RevokeBuildAll => "revoke_build_all",
ChatCommand::Safezone => "safezone",
ChatCommand::Say => "say",
ChatCommand::SetMotd => "set_motd",

View File

@ -1,8 +1,15 @@
use crate::depot::Id;
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)]
pub struct CanBuild;
impl Component for CanBuild {
type Storage = DerefFlaggedStorage<Self, NullStorage<Self>>;
pub struct CanBuild {
pub enabled: bool,
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::{
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
fmt, hash,
@ -6,6 +7,7 @@ use std::{
};
/// Type safe index into Depot
#[derive(Deserialize, Serialize)]
pub struct Id<T> {
idx: u32,
gen: u32,

View File

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

View File

@ -30,9 +30,15 @@ use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt,
};
use common_sys::state::BuildAreas;
use rand::Rng;
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 world::util::Sampler;
@ -81,6 +87,9 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::Alias => handle_alias,
ChatCommand::Ban => handle_ban,
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::Debug => handle_debug,
ChatCommand::DebugColumn => handle_debug_column,
@ -109,9 +118,12 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
ChatCommand::MakeSprite => handle_make_sprite,
ChatCommand::Motd => handle_motd,
ChatCommand::Object => handle_object,
ChatCommand::PermitBuild => handle_permit_build,
ChatCommand::Players => handle_players,
ChatCommand::Region => handle_region,
ChatCommand::RemoveLights => handle_remove_lights,
ChatCommand::RevokeBuild => handle_revoke_build,
ChatCommand::RevokeBuildAll => handle_revoke_build_all,
ChatCommand::Safezone => handle_safezone,
ChatCommand::Say => handle_say,
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(
server: &mut Server,
client: EcsEntity,
@ -1150,30 +1261,152 @@ fn handle_build(
_args: String,
_action: &ChatCommand,
) {
if server
if let Some(mut can_build) = server
.state
.read_storage::<comp::CanBuild>()
.get(target)
.is_some()
.ecs()
.write_storage::<comp::CanBuild>()
.get_mut(target)
{
server
.state
.ecs()
.write_storage::<comp::CanBuild>()
.remove(target);
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled off build mode!"),
);
if can_build.enabled {
can_build.enabled = false;
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled off build mode!"),
);
} else {
can_build.enabled = true;
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Toggled on build mode!"),
);
}
} else {
let _ = server
.state
.ecs()
.write_storage::<comp::CanBuild>()
.insert(target, comp::CanBuild);
server.notify_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_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 tracing::{debug, trace};
@ -29,6 +29,7 @@ impl Sys {
orientations: &mut WriteStorage<'_, Ori>,
controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, Settings>,
build_areas: &Read<'_, BuildAreas>,
msg: ClientGeneral,
) -> Result<(), crate::error::Error> {
let presence = match maybe_presence {
@ -102,13 +103,39 @@ impl Sys {
}
},
ClientGeneral::BreakBlock(pos) => {
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
block_changes.set(pos, block.into_vacant());
if let Some(comp_can_build) = can_build.get(entity) {
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) => {
if can_build.get(entity).is_some() {
block_changes.try_set(pos, block);
if let Some(comp_can_build) = can_build.get(entity) {
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) => {
@ -156,6 +183,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>,
WriteStorage<'a, Controller>,
Read<'a, Settings>,
Read<'a, BuildAreas>,
);
const NAME: &'static str = "msg::in_game";
@ -180,6 +208,7 @@ impl<'a> System<'a> for Sys {
mut clients,
mut controllers,
settings,
build_areas,
): Self::SystemData,
) {
let mut server_emitter = server_event_bus.emitter();
@ -204,6 +233,7 @@ impl<'a> System<'a> for Sys {
&mut orientations,
&mut controllers,
&settings,
&build_areas,
msg,
)
});

View File

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