Merge branch 'maxicarlos08/dura-free-area' into 'master'

Durability free areas

See merge request veloren/veloren!3936
This commit is contained in:
Joshua Barretto 2023-05-23 13:56:32 +00:00
commit 19342daa8d
10 changed files with 178 additions and 68 deletions

View File

@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Some sprites can be sat on.
- Pet birds can now sit on the player's shoulder as they explore the world.
- Adlet caves
- Durability free areas (`/area_add <area_name> no_durability ...`)
### Changed
@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The game now starts in fullscreen by default
- Default audio volume should be less likely to destroy ear drums
- Creatures flee less quickly when low on health
- All `/build_area_*` commands have been renamed to `/area_*`, and you will have to pass an additional area type
### Removed

View File

@ -12,7 +12,7 @@ use std::{
fmt::{self, Display},
str::FromStr,
};
use strum::IntoEnumIterator;
use strum::{AsRefStr, EnumIter, EnumString, IntoEnumIterator};
use tracing::warn;
/// Struct representing a command that a user can run from server chat.
@ -66,6 +66,15 @@ impl assets::Asset for SkillPresetManifest {
pub const KIT_MANIFEST_PATH: &str = "server.manifests.kits";
pub const PRESET_MANIFEST_PATH: &str = "server.manifests.presets";
/// Enum for all possible area types
#[derive(Debug, Clone, EnumIter, EnumString, AsRefStr)]
pub enum AreaKind {
#[strum(serialize = "build")]
Build,
#[strum(serialize = "no_durability")]
NoDurability,
}
lazy_static! {
static ref ALIGNMENTS: Vec<String> = vec!["wild", "enemy", "npc", "pet"]
.iter()
@ -114,6 +123,7 @@ lazy_static! {
souls
};
static ref AREA_KINDS: Vec<String> = AreaKind::iter().map(|kind| kind.as_ref().to_string()).collect();
static ref OBJECTS: Vec<String> = comp::object::ALL_OBJECTS
.iter()
.map(|o| o.to_string().to_string())
@ -247,15 +257,15 @@ pub enum ServerChatCommand {
Adminify,
Airship,
Alias,
AreaAdd,
AreaList,
AreaRemove,
Ban,
BattleMode,
BattleModeForce,
Body,
Buff,
Build,
BuildAreaAdd,
BuildAreaList,
BuildAreaRemove,
Campfire,
CreateLocation,
DebugColumn,
@ -400,9 +410,10 @@ impl ServerChatCommand {
Some(Admin),
),
ServerChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
ServerChatCommand::BuildAreaAdd => cmd(
ServerChatCommand::AreaAdd => cmd(
vec![
Any("name", Required),
Enum("kind", AREA_KINDS.clone(), Required),
Integer("xlo", 0, Required),
Integer("xhi", 10, Required),
Integer("ylo", 0, Required),
@ -413,9 +424,12 @@ impl ServerChatCommand {
"Adds a new build area",
Some(Admin),
),
ServerChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
ServerChatCommand::BuildAreaRemove => cmd(
vec![Any("name", Required)],
ServerChatCommand::AreaList => cmd(vec![], "List all build areas", Some(Admin)),
ServerChatCommand::AreaRemove => cmd(
vec![
Any("name", Required),
Enum("kind", AREA_KINDS.clone(), Required),
],
"Removes specified build area",
Some(Admin),
),
@ -807,9 +821,9 @@ impl ServerChatCommand {
ServerChatCommand::Body => "body",
ServerChatCommand::Buff => "buff",
ServerChatCommand::Build => "build",
ServerChatCommand::BuildAreaAdd => "build_area_add",
ServerChatCommand::BuildAreaList => "build_area_list",
ServerChatCommand::BuildAreaRemove => "build_area_remove",
ServerChatCommand::AreaAdd => "area_add",
ServerChatCommand::AreaList => "area_list",
ServerChatCommand::AreaRemove => "area_remove",
ServerChatCommand::Campfire => "campfire",
ServerChatCommand::DebugColumn => "debug_column",
ServerChatCommand::DebugWays => "debug_ways",

View File

@ -1,9 +1,9 @@
//! This crate contains the [`State`] and shared between
//! server (`veloren-server`) and the client (`veloren-client`)
mod build_areas;
#[cfg(feature = "plugins")] pub mod plugin;
mod special_areas;
mod state;
// TODO: breakup state module and remove glob
pub use build_areas::{BuildAreaError, BuildAreas};
pub use special_areas::*;
pub use state::{BlockChange, BlockDiff, State, TerrainChanges};

View File

@ -1,17 +1,30 @@
use common::depot::{Depot, Id};
use hashbrown::{hash_map, HashMap};
use std::{
marker::PhantomData,
ops::{Deref, DerefMut},
};
use vek::*;
#[derive(Default)]
pub struct AreasContainer<Kind>(Areas, PhantomData<Kind>);
#[derive(Default)]
pub struct BuildArea;
#[derive(Default)]
pub struct NoDurabilityArea;
/// NOTE: Please don't add `Deserialize` without checking to make sure we
/// can guarantee the invariant that every entry in `area_names` points to a
/// valid id in `areas`.
#[derive(Default)]
pub struct BuildAreas {
pub struct Areas {
areas: Depot<Aabb<i32>>,
area_names: HashMap<String, Id<Aabb<i32>>>,
}
pub enum BuildAreaError {
pub enum SpecialAreaError {
/// This build area name is reserved by the system.
Reserved,
/// The build area name was not found.
@ -21,10 +34,10 @@ pub enum BuildAreaError {
/// Build area names that can only be inserted, not removed.
const RESERVED_BUILD_AREA_NAMES: &[&str] = &["world"];
impl BuildAreas {
impl Areas {
pub fn areas(&self) -> &Depot<Aabb<i32>> { &self.areas }
pub fn area_names(&self) -> &HashMap<String, Id<Aabb<i32>>> { &self.area_names }
pub fn area_metas(&self) -> &HashMap<String, Id<Aabb<i32>>> { &self.area_names }
/// If the area_name is already in the map, returns Err(area_name).
pub fn insert(&mut self, area_name: String, area: Aabb<i32>) -> Result<Id<Aabb<i32>>, String> {
@ -37,14 +50,14 @@ impl BuildAreas {
Ok(bb_id)
}
pub fn remove(&mut self, area_name: &str) -> Result<Aabb<i32>, BuildAreaError> {
pub fn remove(&mut self, area_name: &str) -> Result<Aabb<i32>, SpecialAreaError> {
if RESERVED_BUILD_AREA_NAMES.contains(&area_name) {
return Err(BuildAreaError::Reserved);
return Err(SpecialAreaError::Reserved);
}
let bb_id = self
.area_names
.remove(area_name)
.ok_or(BuildAreaError::NotFound)?;
.ok_or(SpecialAreaError::NotFound)?;
let area = self.areas.remove(bb_id).expect(
"Entries in `areas` are added before entries in `area_names` in `insert`, and that is \
the only exposed way to add elements to `area_names`.",
@ -52,3 +65,31 @@ impl BuildAreas {
Ok(area)
}
}
impl<Kind> Deref for AreasContainer<Kind>
where
Kind: AreaKind,
{
type Target = Areas;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<Kind> DerefMut for AreasContainer<Kind>
where
Kind: AreaKind,
{
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
pub trait AreaKind {
fn display() -> &'static str;
}
impl AreaKind for BuildArea {
fn display() -> &'static str { "build" }
}
impl AreaKind for NoDurabilityArea {
fn display() -> &'static str { "durability free" }
}

View File

@ -2,6 +2,7 @@
use crate::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")]
use crate::plugin::PluginMgr;
use crate::{BuildArea, NoDurabilityArea};
#[cfg(feature = "plugins")]
use common::uid::UidAllocator;
use common::{
@ -275,7 +276,8 @@ impl State {
ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
ecs.insert(BlockChange::default());
ecs.insert(ScheduledBlockChange::default());
ecs.insert(crate::build_areas::BuildAreas::default());
ecs.insert(crate::special_areas::AreasContainer::<BuildArea>::default());
ecs.insert(crate::special_areas::AreasContainer::<NoDurabilityArea>::default());
ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(game_mode);

View File

@ -21,8 +21,8 @@ use common::{
assets,
calendar::Calendar,
cmd::{
KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH,
PRESET_MANIFEST_PATH,
AreaKind, KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS,
KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH,
},
comp::{
self,
@ -55,7 +55,7 @@ use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt,
};
use common_state::{BuildAreaError, BuildAreas};
use common_state::{Areas, AreasContainer, BuildArea, NoDurabilityArea, SpecialAreaError, State};
use core::{cmp::Ordering, convert::TryFrom};
use hashbrown::{HashMap, HashSet};
use humantime::Duration as HumanDuration;
@ -63,7 +63,7 @@ use rand::{thread_rng, Rng};
use specs::{
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
};
use std::{fmt::Write, str::FromStr, sync::Arc};
use std::{fmt::Write, ops::DerefMut, str::FromStr, sync::Arc};
use vek::*;
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
use world::util::{Sampler, LOCALITY};
@ -133,9 +133,9 @@ fn do_command(
ServerChatCommand::Body => handle_body,
ServerChatCommand::Buff => handle_buff,
ServerChatCommand::Build => handle_build,
ServerChatCommand::BuildAreaAdd => handle_build_area_add,
ServerChatCommand::BuildAreaList => handle_build_area_list,
ServerChatCommand::BuildAreaRemove => handle_build_area_remove,
ServerChatCommand::AreaAdd => handle_area_add,
ServerChatCommand::AreaList => handle_area_list,
ServerChatCommand::AreaRemove => handle_area_remove,
ServerChatCommand::Campfire => handle_spawn_campfire,
ServerChatCommand::DebugColumn => handle_debug_column,
ServerChatCommand::DebugWays => handle_debug_ways,
@ -351,11 +351,9 @@ fn uid(server: &Server, target: EcsEntity, descriptor: &str) -> CmdResult<Uid> {
.ok_or_else(|| format!("Cannot get uid for {:?}", descriptor))
}
fn area(server: &mut Server, area_name: &str) -> CmdResult<depot::Id<Aabb<i32>>> {
server
.state
.mut_resource::<BuildAreas>()
.area_names()
fn area(server: &mut Server, area_name: &str, kind: &str) -> CmdResult<depot::Id<Aabb<i32>>> {
get_areas_mut(kind, &mut server.state)?
.area_metas()
.get(area_name)
.copied()
.ok_or_else(|| format!("Area name not found: {}", area_name))
@ -1841,7 +1839,7 @@ fn handle_permit_build(
action: &ServerChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = parse_cmd_args!(args, String) {
let bb_id = area(server, &area_name)?;
let bb_id = area(server, &area_name, "build")?;
let mut can_build = server.state.ecs().write_storage::<comp::CanBuild>();
let entry = can_build
.entry(target)
@ -1882,7 +1880,7 @@ fn handle_revoke_build(
action: &ServerChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = parse_cmd_args!(args, String) {
let bb_id = area(server, &area_name)?;
let bb_id = area(server, &area_name, "build")?;
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);
@ -2006,27 +2004,47 @@ fn handle_build(
}
}
fn handle_build_area_add(
fn get_areas_mut<'l>(kind: &str, state: &'l mut State) -> CmdResult<&'l mut Areas> {
Ok(match AreaKind::from_str(kind).ok() {
Some(AreaKind::Build) => state
.mut_resource::<AreasContainer<BuildArea>>()
.deref_mut(),
Some(AreaKind::NoDurability) => state
.mut_resource::<AreasContainer<NoDurabilityArea>>()
.deref_mut(),
None => Err(format!("Invalid area type '{kind}'"))?,
})
}
fn handle_area_add(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) =
parse_cmd_args!(args, String, i32, i32, i32, i32, i32, i32)
if let (
Some(area_name),
Some(kind),
Some(xlo),
Some(xhi),
Some(ylo),
Some(yhi),
Some(zlo),
Some(zhi),
) = parse_cmd_args!(args, String, String, i32, i32, i32, i32, i32, i32)
{
let build_areas = server.state.mut_resource::<BuildAreas>();
let special_areas = get_areas_mut(&kind, &mut server.state)?;
let msg = ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Created build zone {}", area_name),
format!("Created {kind} zone {}", area_name),
);
build_areas
special_areas
.insert(area_name, Aabb {
min: Vec3::new(xlo, ylo, zlo),
max: Vec3::new(xhi, yhi, zhi),
})
.map_err(|area_name| format!("Build zone {} already exists!", area_name))?;
.map_err(|area_name| format!("{kind} zone {} already exists!", area_name))?;
server.notify_client(client, msg);
Ok(())
} else {
@ -2034,54 +2052,67 @@ fn handle_build_area_add(
}
}
fn handle_build_area_list(
fn handle_area_list(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: Vec<String>,
_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)
let format_areas = |areas: &Areas, kind: &str| {
areas
.area_metas()
.iter()
.fold(format!("{kind} areas:"), |acc, (area_name, bb_id)| {
if let Some(aabb) = areas.areas().get(*bb_id) {
format!("{}\n{}: {} to {} ()", acc, area_name, aabb.min, aabb.max,)
} else {
acc
}
},
),
})
};
let build_message = format_areas(
server.state.mut_resource::<AreasContainer<BuildArea>>(),
"Build",
);
let no_dura_message = format_areas(
server
.state
.mut_resource::<AreasContainer<NoDurabilityArea>>(),
"Durability free",
);
let msg = ServerGeneral::server_msg(
ChatType::CommandInfo,
[build_message, no_dura_message].join("\n"),
);
server.notify_client(client, msg);
Ok(())
}
fn handle_build_area_remove(
fn handle_area_remove(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = parse_cmd_args!(args, String) {
let build_areas = server.state.mut_resource::<BuildAreas>();
if let (Some(area_name), Some(kind)) = parse_cmd_args!(args, String, String) {
let areas = get_areas_mut(&kind, &mut server.state)?;
build_areas.remove(&area_name).map_err(|err| match err {
BuildAreaError::Reserved => format!(
"Build area is reserved and cannot be removed: {}",
areas.remove(&area_name).map_err(|err| match err {
SpecialAreaError::Reserved => format!(
"Special area is reserved and cannot be removed: {}",
area_name
),
BuildAreaError::NotFound => format!("No such build area {}", area_name),
SpecialAreaError::NotFound => format!("No such build area {}", area_name),
})?;
server.notify_client(
client,
ServerGeneral::server_msg(
ChatType::CommandInfo,
format!("Removed build zone {}", area_name),
format!("Removed {kind} zone {area_name}"),
),
);
Ok(())

View File

@ -38,7 +38,7 @@ use common::{
Damage, DamageKind, DamageSource, Explosion, GroupTarget, RadiusEffect,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use common_state::BlockChange;
use common_state::{AreasContainer, BlockChange, NoDurabilityArea};
use hashbrown::HashSet;
use rand::Rng;
use specs::{
@ -521,9 +521,28 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
true
};
let resists_durability = state
.ecs()
.read_storage::<Pos>()
.get(entity)
.cloned()
.map_or(false, |our_pos| {
let areas_container = state
.ecs()
.read_resource::<AreasContainer<NoDurabilityArea>>();
let our_pos = our_pos.0.map(|i| i as i32);
let is_in_area = areas_container
.areas()
.iter()
.any(|(_, area)| area.contains_point(our_pos));
is_in_area
});
// TODO: Do we need to do this if `should_delete` is true?
// Modify durability on all equipped items
if let Some(mut inventory) = state.ecs().write_storage::<Inventory>().get_mut(entity) {
if !resists_durability && let Some(mut inventory) = state.ecs().write_storage::<Inventory>().get_mut(entity) {
let ecs = state.ecs();
let ability_map = ecs.read_resource::<AbilityMap>();
let msm = ecs.read_resource::<MaterialStatManifest>();

View File

@ -89,7 +89,7 @@ use common_net::{
msg::{ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerMsg},
sync::WorldSyncExt,
};
use common_state::{BlockDiff, BuildAreas, State};
use common_state::{AreasContainer, BlockDiff, BuildArea, State};
use common_systems::add_local_systems;
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
use network::{ListenAddr, Network, Pid};
@ -447,7 +447,7 @@ impl Server {
state
.ecs()
.write_resource::<BuildAreas>()
.write_resource::<AreasContainer<BuildArea>>()
.insert("world".to_string(), world_aabb)
.expect("The initial insert should always work.");
}

View File

@ -496,6 +496,7 @@ impl StateExt for State {
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
};
let time = self.get_time();
// TODO: Consider using the area system for this
self.ecs_mut()
.create_entity_synced()
.with(pos)

View File

@ -16,7 +16,7 @@ use common::{
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, ServerGeneral};
use common_state::{BlockChange, BuildAreas};
use common_state::{AreasContainer, BlockChange, BuildArea};
use core::mem;
use rayon::prelude::*;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
@ -62,7 +62,7 @@ impl Sys {
orientation: Option<&mut Ori>,
controller: Option<&mut Controller>,
settings: &Read<'_, Settings>,
build_areas: &Read<'_, BuildAreas>,
build_areas: &Read<'_, AreasContainer<BuildArea>>,
player_physics_setting: Option<&mut PlayerPhysicsSetting>,
maybe_admin: &Option<&Admin>,
time_for_vd_changes: Instant,
@ -335,7 +335,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>,
WriteStorage<'a, Controller>,
Read<'a, Settings>,
Read<'a, BuildAreas>,
Read<'a, AreasContainer<BuildArea>>,
Write<'a, PlayerPhysicsSettings>,
TerrainPersistenceData<'a>,
ReadStorage<'a, Player>,