Add basic group functionality (no voxygen wiring yet)

This commit is contained in:
Imbris 2020-04-26 13:03:19 -04:00 committed by Monty Marz
parent 1741384d00
commit 10c3d01466
24 changed files with 1028 additions and 87 deletions

1
Cargo.lock generated
View File

@ -4638,6 +4638,7 @@ dependencies = [
"ron",
"serde",
"serde_json",
"slab",
"specs",
"specs-idvs",
"sum_type",

View File

@ -17,8 +17,8 @@ use byteorder::{ByteOrder, LittleEndian};
use common::{
character::CharacterItem,
comp::{
self, ControlAction, ControlEvent, Controller, ControllerInputs, InventoryManip,
InventoryUpdateEvent,
self, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
InventoryManip, InventoryUpdateEvent,
},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
@ -74,11 +74,15 @@ pub struct Client {
pub server_info: ServerInfo,
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
pub player_list: HashMap<Uid, PlayerInfo>,
pub group_members: HashSet<Uid>,
pub character_list: CharacterList,
pub active_character_id: Option<i32>,
recipe_book: RecipeBook,
available_recipes: HashSet<String>,
group_invite: Option<Uid>,
group_leader: Option<Uid>,
_network: Network,
participant: Option<Participant>,
singleton_stream: Stream,
@ -208,11 +212,15 @@ impl Client {
server_info,
world_map,
player_list: HashMap::new(),
group_members: HashSet::new(),
character_list: CharacterList::default(),
active_character_id: None,
recipe_book,
available_recipes: HashSet::default(),
group_invite: None,
group_leader: None,
_network: network,
participant: Some(participant),
singleton_stream: stream,
@ -424,6 +432,53 @@ impl Client {
.unwrap();
}
pub fn group_invite(&self) -> Option<Uid> { self.group_invite }
pub fn group_leader(&self) -> Option<Uid> { self.group_leader }
pub fn accept_group_invite(&mut self) {
// Clear invite
self.group_invite.take();
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Accept,
))).unwrap();
}
pub fn reject_group_invite(&mut self) {
// Clear invite
self.group_invite.take();
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Reject,
))).unwrap();
}
pub fn leave_group(&mut self) {
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Leave,
))).unwrap();
}
pub fn kick_from_group(&mut self, entity: specs::Entity) {
if let Some(uid) = self.state.ecs().read_storage::<Uid>().get(entity).copied() {
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Kick(uid),
))).unwrap();
}
}
pub fn assign_group_leader(&mut self, entity: specs::Entity) {
if let Some(uid) = self.state.ecs().read_storage::<Uid>().get(entity).copied() {
self.singleton_stream
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::AssignLeader(uid),
))).unwrap();
}
}
pub fn is_mounted(&self) -> bool {
self.state
.ecs()
@ -935,7 +990,46 @@ impl Client {
);
}
},
ServerMsg::GroupUpdate(change_notification) => {
use comp::group::ChangeNotification::*;
// Note: we use a hashmap since this would not work with entities outside
// the view distance
match change_notification {
Added(uid) => {
warn!("message to add: {}", uid);
if !self.group_members.insert(uid) {
warn!(
"Received msg to add uid {} to the group members but they \
were already there",
uid
);
}
},
Removed(uid) => {
if !self.group_members.remove(&uid) {
warn!(
"Received msg to remove uid {} from group members but by \
they weren't in there!",
uid
);
}
},
NewLeader(leader) => {
self.group_leader = Some(leader);
},
NewGroup { leader, members } => {
self.group_leader = Some(leader);
self.group_members = members.into_iter().collect();
},
NoGroup => {
self.group_leader = None;
self.group_members = HashSet::new();
}
}
},
ServerMsg::GroupInvite(uid) => {
self.group_invite = Some(uid);
},
ServerMsg::Ping => {
self.singleton_stream.send(ClientMsg::Pong)?;
},

View File

@ -29,7 +29,8 @@ crossbeam = "0.7"
notify = "5.0.0-pre.3"
indexmap = "1.3.0"
sum_type = "0.2.0"
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "223a4097f7ebc8d451936dccb5e6517194bbf086" }
slab = "0.4.2"
[dev-dependencies]
criterion = "0.3"

View File

@ -1,10 +1,9 @@
use crate::{path::Chaser, sync::Uid};
use serde::{Deserialize, Serialize};
use specs::{Component, Entity as EcsEntity, FlaggedStorage};
use specs::{Component, Entity as EcsEntity};
use specs_idvs::IdvStorage;
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Alignment {
/// Wild animals and gentle giants
Wild,
@ -52,7 +51,7 @@ impl Alignment {
}
impl Component for Alignment {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
type Storage = IdvStorage<Self>;
}
#[derive(Clone, Debug, Default)]

View File

@ -18,12 +18,23 @@ pub enum InventoryManip {
CraftRecipe(String),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupManip {
Invite(Uid),
Accept,
Reject,
Leave,
Kick(Uid),
AssignLeader(Uid),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ControlEvent {
ToggleLantern,
Mount(Uid),
Unmount,
InventoryManip(InventoryManip),
GroupManip(GroupManip),
Respawn,
}

392
common/src/comp/group.rs Normal file
View File

@ -0,0 +1,392 @@
use crate::{comp::Alignment, sync::Uid};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use slab::Slab;
use specs::{Component, FlaggedStorage, Join};
use specs_idvs::IdvStorage;
use tracing::{error, warn};
// Primitive group system
// Shortcomings include:
// - no support for more complex group structures
// - lack of complex enemy npc integration
// - relies on careful management of groups to maintain a valid state
// - clients don't know what entities are their pets
// - the possesion rod could probably wreck this
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Group(u32);
// TODO: Hack
// Corresponds to Alignment::Enemy
pub const ENEMY: Group = Group(u32::MAX);
// Corresponds to Alignment::Npc | Alignment::Tame
pub const NPC: Group = Group(u32::MAX - 1);
impl Component for Group {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
#[derive(Copy, Clone, Debug)]
pub struct GroupInfo {
// TODO: what about enemy groups, either the leader will constantly change because they have to
// be loaded or we create a dummy entity or this needs to be optional
pub leader: specs::Entity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeNotification<E> {
// :D
Added(E),
// :(
Removed(E),
NewLeader(E),
// Use to put in a group overwriting existing group
NewGroup { leader: E, members: Vec<E> },
// No longer in a group
NoGroup,
}
// Note: now that we are dipping into uids here consider just using
// ChangeNotification<Uid> everywhere
// Also note when the same notification is sent to multiple destinations the
// maping might be duplicated effort
impl<E> ChangeNotification<E> {
pub fn try_map<T>(self, f: impl Fn(E) -> Option<T>) -> Option<ChangeNotification<T>> {
match self {
Self::Added(e) => f(e).map(ChangeNotification::Added),
Self::Removed(e) => f(e).map(ChangeNotification::Removed),
Self::NewLeader(e) => f(e).map(ChangeNotification::NewLeader),
// Note just discards members that fail map
Self::NewGroup { leader, members } => {
f(leader).map(|leader| ChangeNotification::NewGroup {
leader,
members: members.into_iter().filter_map(f).collect(),
})
},
Self::NoGroup => Some(ChangeNotification::NoGroup),
}
}
}
type GroupsMut<'a> = specs::WriteStorage<'a, Group>;
type Groups<'a> = specs::ReadStorage<'a, Group>;
type Alignments<'a> = specs::ReadStorage<'a, Alignment>;
type Uids<'a> = specs::ReadStorage<'a, Uid>;
#[derive(Debug, Default)]
pub struct GroupManager {
groups: Slab<GroupInfo>,
}
// Gather list of pets of the group member + member themselves
// Note: iterating through all entities here could become slow at higher entity
// counts
fn with_pets(
entity: specs::Entity,
uid: Uid,
alignments: &Alignments,
entities: &specs::Entities,
) -> Vec<specs::Entity> {
let mut list = (entities, alignments)
.join()
.filter_map(|(e, a)| matches!(a, Alignment::Owned(owner) if *owner == uid).then_some(e))
.collect::<Vec<_>>();
list.push(entity);
list
}
/// Returns list of current members of a group
pub fn members<'a>(
group: Group,
groups: impl Join<Type = &'a Group> + 'a,
entities: &'a specs::Entities,
) -> impl Iterator<Item = specs::Entity> + 'a {
(entities, groups)
.join()
.filter_map(move |(e, g)| (*g == group).then_some(e))
}
// TODO: optimize add/remove for massive NPC groups
impl GroupManager {
pub fn group_info(&self, group: Group) -> Option<GroupInfo> {
self.groups.get(group.0 as usize).copied()
}
fn create_group(&mut self, leader: specs::Entity) -> Group {
Group(self.groups.insert(GroupInfo { leader }) as u32)
}
fn remove_group(&mut self, group: Group) { self.groups.remove(group.0 as usize); }
// Add someone to a group
// Also used to create new groups
pub fn add_group_member(
&mut self,
leader: specs::Entity,
new_member: specs::Entity,
entities: &specs::Entities,
groups: &mut GroupsMut,
alignments: &Alignments,
uids: &Uids,
mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
// Get uid
let new_member_uid = if let Some(uid) = uids.get(new_member) {
*uid
} else {
error!("Failed to retrieve uid for the new group member");
return;
};
// If new member is a member of a different group remove that
if groups.get(new_member).is_some() {
self.remove_from_group(
new_member,
groups,
alignments,
uids,
entities,
&mut notifier,
)
}
let group = match groups.get(leader).copied() {
Some(id)
if self
.group_info(id)
.map(|info| info.leader == leader)
.unwrap_or(false) =>
{
Some(id)
},
// Member of an existing group can't be a leader
// If the lead is a member of another group leave that group first
Some(_) => {
self.remove_from_group(leader, groups, alignments, uids, entities, &mut notifier);
None
},
None => None,
};
let group = group.unwrap_or_else(|| {
let new_group = self.create_group(leader);
// Unwrap should not fail since we just found these entities and they should
// still exist Note: if there is an issue replace with a warn
groups.insert(leader, new_group).unwrap();
// Inform
notifier(leader, ChangeNotification::NewLeader(leader));
new_group
});
let member_plus_pets = with_pets(new_member, new_member_uid, alignments, entities);
// Inform
members(group, &*groups, entities).for_each(|a| {
member_plus_pets.iter().for_each(|b| {
notifier(a, ChangeNotification::Added(*b));
notifier(*b, ChangeNotification::Added(a));
})
});
// Note: pets not informed
notifier(new_member, ChangeNotification::NewLeader(leader));
// Add group id for new member and pets
// Unwrap should not fail since we just found these entities and they should
// still exist
// Note: if there is an issue replace with a warn
member_plus_pets.iter().for_each(|e| {
let _ = groups.insert(*e, group).unwrap();
});
}
pub fn new_pet(
&mut self,
pet: specs::Entity,
owner: specs::Entity,
groups: &mut GroupsMut,
entities: &specs::Entities,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
let group = match groups.get(owner).copied() {
Some(group) => group,
None => {
let new_group = self.create_group(owner);
groups.insert(owner, new_group).unwrap();
// Inform
notifier(owner, ChangeNotification::NewLeader(owner));
new_group
},
};
// Inform
members(group, &*groups, entities).for_each(|a| {
notifier(a, ChangeNotification::Added(pet));
notifier(pet, ChangeNotification::Added(a));
});
// Add
groups.insert(pet, group).unwrap();
if let Some(info) = self.group_info(group) {
notifier(pet, ChangeNotification::NewLeader(info.leader));
}
}
// Remove someone from a group if they are in one
// Don't need to check if they are in a group before calling this
// Also removes pets (ie call this if the pet no longer exists)
pub fn remove_from_group(
&mut self,
member: specs::Entity,
groups: &mut GroupsMut,
alignments: &Alignments,
uids: &Uids,
entities: &specs::Entities,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
let group = match groups.get(member) {
Some(group) => *group,
None => return,
};
// If leaving entity was the leader disband the group
if self
.group_info(group)
.map(|info| info.leader == member)
.unwrap_or(false)
{
// Remove group
self.remove_group(group);
(entities, uids, &*groups, alignments.maybe())
.join()
.filter(|(_, _, g, _)| **g == group)
.fold(
HashMap::<Uid, (Option<specs::Entity>, Vec<specs::Entity>)>::new(),
|mut acc, (e, uid, _, alignment)| {
if let Some(Alignment::Owned(owner)) = alignment {
// Assumes owner will be in the group
acc.entry(*owner).or_default().1.push(e);
} else {
acc.entry(*uid).or_default().0 = Some(e);
}
acc
},
)
.into_iter()
.map(|(_, v)| v)
.for_each(|(owner, pets)| {
if let Some(owner) = owner {
if !pets.is_empty() {
let mut members = pets.clone();
members.push(owner);
// New group
let new_group = self.create_group(owner);
for &member in &members {
groups.insert(member, new_group).unwrap();
}
let notification = ChangeNotification::NewGroup {
leader: owner,
members,
};
// TODO: don't clone
notifier(owner, notification.clone());
pets.into_iter()
.for_each(|pet| notifier(pet, notification.clone()));
} else {
// If no pets just remove group
groups.remove(owner);
notifier(owner, ChangeNotification::NoGroup)
}
} else {
warn!(
"Something went wrong! The pet owner is missing from a group that the \
pet is in"
);
pets.into_iter()
.for_each(|pet| notifier(pet, ChangeNotification::NoGroup));
}
});
} else {
// Not leader
let leaving_member_uid = if let Some(uid) = uids.get(member) {
*uid
} else {
error!("Failed to retrieve uid for the new group member");
return;
};
let leaving = with_pets(member, leaving_member_uid, alignments, entities);
// If pets form new group
if leaving.len() > 1 {
let new_group = self.create_group(member);
leaving.iter().for_each(|e| {
let _ = groups.insert(*e, new_group).unwrap();
});
let notification = ChangeNotification::NewGroup {
leader: member,
members: leaving.clone(),
};
leaving
.iter()
.for_each(|e| notifier(*e, notification.clone()));
} else {
groups.remove(member);
notifier(member, ChangeNotification::NoGroup)
}
// Inform remaining members
let mut num_members = 0;
members(group, &*groups, entities).for_each(|a| {
num_members += 1;
leaving.iter().for_each(|b| {
notifier(a, ChangeNotification::Removed(*b));
})
});
// If leader is the last one left then disband the group
// Assumes last member is the leader
if num_members == 1 {
if let Some(info) = self.group_info(group) {
let leader = info.leader;
self.remove_group(group);
groups.remove(leader);
notifier(leader, ChangeNotification::NoGroup);
}
} else if num_members == 0 {
error!("Somehow group has no members")
}
}
}
// Assign new group leader
// Does nothing if new leader is not part of a group
pub fn assign_leader(
&mut self,
new_leader: specs::Entity,
groups: &Groups,
entities: &specs::Entities,
mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
let group = match groups.get(new_leader) {
Some(group) => *group,
None => return,
};
// Set new leader
self.groups[group.0 as usize].leader = new_leader;
// Point to new leader
members(group, groups, entities).for_each(|e| {
notifier(e, ChangeNotification::NewLeader(e));
});
}
}

View File

@ -7,6 +7,7 @@ mod chat;
mod controller;
mod damage;
mod energy;
pub mod group;
mod inputs;
mod inventory;
mod last;
@ -28,13 +29,17 @@ pub use body::{
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
};
pub use character_state::{Attacking, CharacterState, StateUpdate};
pub use chat::{ChatMode, ChatMsg, ChatType, Faction, Group, SpeechBubble, SpeechBubbleType};
// TODO: replace chat::Group functionality with group::Group
pub use chat::{
ChatMode, ChatMsg, ChatType, Faction, Group as ChatGroup, SpeechBubble, SpeechBubbleType,
};
pub use controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, Input, InventoryManip,
MountState, Mounting,
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
InventoryManip, MountState, Mounting,
};
pub use damage::{Damage, DamageSource};
pub use energy::{Energy, EnergySource};
pub use group::Group;
pub use inputs::CanBuild;
pub use inventory::{
item,

View File

@ -35,6 +35,7 @@ pub enum ServerEvent {
cause: comp::HealthSource,
},
InventoryManip(EcsEntity, comp::InventoryManip),
GroupManip(EcsEntity, comp::GroupManip),
Respawn(EcsEntity),
Shoot {
entity: EcsEntity,

View File

@ -17,7 +17,7 @@ sum_type! {
LightEmitter(comp::LightEmitter),
Item(comp::Item),
Scale(comp::Scale),
Alignment(comp::Alignment),
Group(comp::Group),
MountState(comp::MountState),
Mounting(comp::Mounting),
Mass(comp::Mass),
@ -44,7 +44,7 @@ sum_type! {
LightEmitter(PhantomData<comp::LightEmitter>),
Item(PhantomData<comp::Item>),
Scale(PhantomData<comp::Scale>),
Alignment(PhantomData<comp::Alignment>),
Group(PhantomData<comp::Group>),
MountState(PhantomData<comp::MountState>),
Mounting(PhantomData<comp::Mounting>),
Mass(PhantomData<comp::Mass>),
@ -71,7 +71,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Alignment(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
@ -96,7 +96,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Alignment(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
@ -123,7 +123,7 @@ impl sync::CompPacket for EcsCompPacket {
},
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),

View File

@ -69,6 +69,8 @@ pub enum ServerMsg {
/// An error occured while creating or deleting a character
CharacterActionError(String),
PlayerListUpdate(PlayerListUpdate),
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
GroupInvite(sync::Uid),
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
/// Trigger cleanup for when the client goes back to the `Registered` state
/// from an ingame state

View File

@ -123,7 +123,7 @@ impl State {
ecs.register::<comp::Gravity>();
ecs.register::<comp::CharacterState>();
ecs.register::<comp::Object>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::Group>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
@ -146,6 +146,7 @@ impl State {
ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::Agent>();
ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>();
@ -156,7 +157,7 @@ impl State {
ecs.register::<comp::Attacking>();
ecs.register::<comp::ItemDrop>();
ecs.register::<comp::ChatMode>();
ecs.register::<comp::Group>();
ecs.register::<comp::ChatGroup>();
ecs.register::<comp::Faction>();
// Register synced resources used by the ECS.
@ -168,9 +169,10 @@ impl State {
ecs.insert(TerrainGrid::new().unwrap());
ecs.insert(BlockChange::default());
ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default());
// TODO: only register on the server
ecs.insert(EventBus::<ServerEvent>::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(comp::group::GroupManager::default());
ecs.insert(RegionMap::new());
ecs

View File

@ -2,6 +2,7 @@ use crate::{
comp::{
self,
agent::Activity,
group,
item::{tool::ToolKind, ItemKind},
Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout,
MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel,
@ -29,6 +30,7 @@ impl<'a> System<'a> for Sys {
Read<'a, UidAllocator>,
Read<'a, Time>,
Read<'a, DeltaTime>,
Read<'a, group::GroupManager>,
Write<'a, EventBus<ServerEvent>>,
Entities<'a>,
ReadStorage<'a, Pos>,
@ -40,6 +42,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, CharacterState>,
ReadStorage<'a, PhysicsState>,
ReadStorage<'a, Uid>,
ReadStorage<'a, group::Group>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Alignment>,
ReadStorage<'a, Body>,
@ -55,6 +58,7 @@ impl<'a> System<'a> for Sys {
uid_allocator,
time,
dt,
group_manager,
event_bus,
entities,
positions,
@ -66,6 +70,7 @@ impl<'a> System<'a> for Sys {
character_states,
physics_states,
uids,
groups,
terrain,
alignments,
bodies,
@ -88,6 +93,7 @@ impl<'a> System<'a> for Sys {
agent,
controller,
mount_state,
group,
) in (
&entities,
&positions,
@ -102,9 +108,19 @@ impl<'a> System<'a> for Sys {
&mut agents,
&mut controllers,
mount_states.maybe(),
groups.maybe(),
)
.join()
{
// Hack, replace with better system when groups are more sophisticated
// Override alignment if in a group
let alignment = group
.and_then(|g| group_manager.group_info(*g))
.and_then(|info| uids.get(info.leader))
.copied()
.map(Alignment::Owned)
.or(alignment.copied());
// Skip mounted entities
if mount_state
.map(|ms| *ms != MountState::Unmounted)
@ -117,7 +133,7 @@ impl<'a> System<'a> for Sys {
let mut inputs = &mut controller.inputs;
// Default to looking in orientation direction
// Default to looking in orientation direction (can be overriden below)
inputs.look_dir = ori.0;
const AVG_FOLLOW_DIST: f32 = 6.0;
@ -148,11 +164,9 @@ impl<'a> System<'a> for Sys {
thread_rng().gen::<f32>() - 0.5,
) * 0.1
- *bearing * 0.003
- if let Some(patrol_origin) = agent.patrol_origin {
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
} else {
Vec2::zero()
};
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
(pos.0 - patrol_origin).xy() * 0.0002
});
// Stop if we're too close to a wall
*bearing *= 0.1
@ -169,8 +183,7 @@ impl<'a> System<'a> for Sys {
.until(|block| block.is_solid())
.cast()
.1
.map(|b| b.is_none())
.unwrap_or(true)
.map_or(true, |b| b.is_none())
{
0.9
} else {
@ -269,8 +282,7 @@ impl<'a> System<'a> for Sys {
// Don't attack entities we are passive towards
// TODO: This is here, it's a bit of a hack
if let Some(alignment) = alignment {
if (*alignment).passive_towards(tgt_alignment) || tgt_stats.is_dead
{
if alignment.passive_towards(tgt_alignment) || tgt_stats.is_dead {
do_idle = true;
break 'activity;
}
@ -437,7 +449,7 @@ impl<'a> System<'a> for Sys {
}
// Follow owner if we're too far, or if they're under attack
if let Some(Alignment::Owned(owner)) = alignment.copied() {
if let Some(Alignment::Owned(owner)) = alignment {
(|| {
let owner = uid_allocator.retrieve_entity_internal(owner.id())?;

View File

@ -1,7 +1,7 @@
use crate::{
comp::{
Alignment, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange,
HealthSource, Loadout, Ori, Pos, Scale, Stats,
group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
Loadout, Ori, Pos, Scale, Stats,
},
event::{EventBus, LocalEvent, ServerEvent},
sync::Uid,
@ -26,10 +26,10 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Alignment>,
ReadStorage<'a, Body>,
ReadStorage<'a, Stats>,
ReadStorage<'a, Loadout>,
ReadStorage<'a, group::Group>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, CharacterState>,
);
@ -44,10 +44,10 @@ impl<'a> System<'a> for Sys {
positions,
orientations,
scales,
alignments,
bodies,
stats,
loadouts,
groups,
mut attacking_storage,
character_states,
): Self::SystemData,
@ -71,23 +71,12 @@ impl<'a> System<'a> for Sys {
attack.applied = true;
// Go through all other entities
for (
b,
uid_b,
pos_b,
ori_b,
scale_b_maybe,
alignment_b_maybe,
character_b,
stats_b,
body_b,
) in (
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
&entities,
&uids,
&positions,
&orientations,
scales.maybe(),
alignments.maybe(),
character_states.maybe(),
&stats,
&bodies,
@ -111,6 +100,17 @@ impl<'a> System<'a> for Sys {
&& pos.0.distance_squared(pos_b.0) < (rad_b + scale * attack.range).powi(2)
&& ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan()
{
// See if entities are in the same group
let same_group = groups
.get(entity)
.map(|group_a| Some(group_a) == groups.get(b))
.unwrap_or(false);
// Don't heal if outside group
// Don't damage in the same group
if same_group != (attack.base_healthchange > 0) {
continue;
}
// Weapon gives base damage
let source = if attack.base_healthchange > 0 {
DamageSource::Healing
@ -121,28 +121,6 @@ impl<'a> System<'a> for Sys {
healthchange: attack.base_healthchange as f32,
source,
};
let mut knockback = attack.knockback;
// TODO: remove this, either it will remain unused or be used as a temporary
// gameplay balance
//// NPCs do less damage
//if agent_maybe.is_some() {
// healthchange = (healthchange / 1.5).min(-1.0);
//}
// TODO: remove this when there is a better way to deal with alignment
// Don't heal NPCs
if (damage.healthchange > 0.0 && alignment_b_maybe
.map(|a| !a.is_friendly_to_players())
.unwrap_or(true))
// Don't hurt pets
|| (damage.healthchange < 0.0 && alignment_b_maybe
.map(|b| Alignment::Owned(*uid).passive_towards(*b))
.unwrap_or(false))
{
damage.healthchange = 0.0;
knockback = 0.0;
}
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
@ -160,10 +138,10 @@ impl<'a> System<'a> for Sys {
},
});
}
if knockback != 0.0 {
if attack.knockback != 0.0 {
local_emitter.emit(LocalEvent::ApplyForce {
entity: b,
force: knockback
force: attack.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
});
}

View File

@ -96,6 +96,9 @@ impl<'a> System<'a> for Sys {
}
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
},
ControlEvent::GroupManip(manip) => {
server_emitter.emit(ServerEvent::GroupManip(entity, manip))
},
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
}
}

View File

@ -18,6 +18,7 @@ pub struct Client {
pub network_error: AtomicBool,
pub last_ping: f64,
pub login_msg_sent: bool,
pub invited_to_group: Option<specs::Entity>,
}
impl Component for Client {

View File

@ -2,7 +2,7 @@
//! To implement a new command, add an instance of `ChatCommand` to
//! `CHAT_COMMANDS` and provide a handler function.
use crate::{Server, StateExt};
use crate::{client::Client, Server, StateExt};
use chrono::{NaiveTime, Timelike};
use common::{
assets,
@ -557,6 +557,42 @@ fn handle_spawn(
let new_entity = entity_base.build();
// Add to group system if a pet
if matches!(alignment, comp::Alignment::Owned { .. }) {
let state = server.state();
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager =
state.ecs().write_resource::<comp::group::GroupManager>();
group_manager.new_pet(
new_entity,
target,
&mut state.ecs().write_storage(),
&state.ecs().entities(),
&mut |entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
} else if let Some(group) = match alignment {
comp::Alignment::Wild => None,
comp::Alignment::Enemy => Some(comp::group::ENEMY),
comp::Alignment::Npc | comp::Alignment::Tame => {
Some(comp::group::NPC)
},
// TODO: handle
comp::Alignment::Owned(_) => unreachable!(),
} {
let _ =
server.state.ecs().write_storage().insert(new_entity, group);
}
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
server.notify_client(
client,
@ -1161,7 +1197,7 @@ fn handle_group(
return;
}
let ecs = server.state.ecs();
if let Some(comp::Group(group)) = ecs.read_storage().get(client) {
if let Some(comp::ChatGroup(group)) = ecs.read_storage().get(client) {
let mode = comp::ChatMode::Group(group.to_string());
let _ = ecs.write_storage().insert(client, mode.clone());
if !msg.is_empty() {
@ -1352,7 +1388,7 @@ fn handle_join_group(
.state
.ecs()
.write_storage()
.insert(client, comp::Group(group.clone()))
.insert(client, comp::ChatGroup(group.clone()))
.ok()
.flatten()
.map(|f| f.0);
@ -1369,7 +1405,7 @@ fn handle_join_group(
.ecs()
.write_storage()
.remove(client)
.map(|comp::Group(f)| f)
.map(|comp::ChatGroup(f)| f)
};
if let Some(group) = group_leave {
server.state.send_chat(

View File

@ -1,7 +1,7 @@
use crate::{sys, Server, StateExt};
use common::{
comp::{
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
self, group, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
Projectile, Scale, Stats, Vel, WaypointArea,
},
util::Dir,
@ -36,12 +36,26 @@ pub fn handle_create_npc(
scale: Scale,
drop_item: Option<Item>,
) {
let group = match alignment {
Alignment::Wild => None,
Alignment::Enemy => Some(group::ENEMY),
Alignment::Npc | Alignment::Tame => Some(group::NPC),
// TODO: handle
Alignment::Owned(_) => None,
};
let entity = server
.state
.create_npc(pos, stats, loadout, body)
.with(scale)
.with(alignment);
let entity = if let Some(group) = group {
entity.with(group)
} else {
entity
};
let entity = if let Some(agent) = agent.into() {
entity.with(agent)
} else {

View File

@ -0,0 +1,304 @@
use crate::{client::Client, Server};
use common::{
comp::{
self,
group::{self, GroupManager},
ChatType, GroupManip,
},
msg::ServerMsg,
sync,
sync::WorldSyncExt,
};
use specs::world::WorldExt;
// TODO: turn chat messages into enums
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
let state = server.state_mut();
match manip {
GroupManip::Invite(uid) => {
let mut clients = state.ecs().write_storage::<Client>();
let invitee = match state.ecs().entity_from_uid(uid.into()) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Leadership transfer failed, target does not exist".to_owned(),
));
}
return;
},
};
let uids = state.ecs().read_storage::<sync::Uid>();
let alignments = state.ecs().read_storage::<comp::Alignment>();
let agents = state.ecs().read_storage::<comp::Agent>();
let mut already_has_invite = false;
let mut add_to_group = false;
// If client comp
if let (Some(client), Some(inviter_uid)) = (clients.get_mut(invitee), uids.get(entity))
{
if client.invited_to_group.is_some() {
already_has_invite = true;
} else {
client.notify(ServerMsg::GroupInvite((*inviter_uid).into()));
client.invited_to_group = Some(entity);
}
// Would be cool to do this in agent system (e.g. add an invited
// component to replace the field on Client)
// TODO: move invites to component and make them time out
} else if matches!(
(alignments.get(invitee), agents.get(invitee)),
(Some(comp::Alignment::Npc), Some(_))
) {
add_to_group = true;
// Wipe agent state
let _ = state
.ecs()
.write_storage()
.insert(invitee, comp::Agent::default());
}
if already_has_invite {
// Inform inviter that there is already an invite
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Invite failed target already has a pending invite".to_owned(),
));
}
}
if add_to_group {
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
entity,
invitee,
&state.ecs().entities(),
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
|entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
}
},
GroupManip::Accept => {
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<sync::Uid>();
if let Some(inviter) = clients
.get_mut(entity)
.and_then(|c| c.invited_to_group.take())
{
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
inviter,
entity,
&state.ecs().entities(),
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
|entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
}
},
GroupManip::Reject => {
let mut clients = state.ecs().write_storage::<Client>();
if let Some(inviter) = clients
.get_mut(entity)
.and_then(|c| c.invited_to_group.take())
{
// Inform inviter of rejection
if let Some(client) = clients.get_mut(inviter) {
// TODO: say who rejected the invite
client.notify(ChatType::Meta.server_msg("Invite rejected".to_owned()));
}
}
},
GroupManip::Leave => {
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<sync::Uid>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.remove_from_group(
entity,
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
&state.ecs().entities(),
&mut |entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
},
GroupManip::Kick(uid) => {
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<sync::Uid>();
let target = match state.ecs().entity_from_uid(uid.into()) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Leadership transfer failed, target does not exist".to_owned(),
));
}
return;
},
};
let mut groups = state.ecs().write_storage::<group::Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
// Make sure kicker is the group leader
match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Remove target from group
group_manager.remove_from_group(
target,
&mut groups,
&state.ecs().read_storage(),
&uids,
&state.ecs().entities(),
&mut |entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
// Tell them the have been kicked
if let Some(client) = clients.get_mut(target) {
client.notify(
ChatType::Meta.server_msg("The group leader kicked you".to_owned()),
);
}
// Tell kicker that they were succesful
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg("Kick complete".to_owned()));
}
},
Some(_) => {
// Inform kicker that they are not the leader
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Kick failed: you are not the leader of the target's group".to_owned(),
));
}
},
None => {
// Inform kicker that the target is not in a group
if let Some(client) = clients.get_mut(entity) {
client.notify(
ChatType::Meta.server_msg(
"Kick failed: your target is not in a group".to_owned(),
),
);
}
},
}
},
GroupManip::AssignLeader(uid) => {
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<sync::Uid>();
let target = match state.ecs().entity_from_uid(uid.into()) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Leadership transfer failed, target does not exist".to_owned(),
));
}
return;
},
};
let groups = state.ecs().read_storage::<group::Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
// Make sure assigner is the group leader
match groups
.get(target)
.and_then(|group| group_manager.group_info(*group))
{
Some(info) if info.leader == entity => {
// Assign target as group leader
group_manager.assign_leader(
target,
&groups,
&state.ecs().entities(),
|entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
// Tell them they are the leader
if let Some(client) = clients.get_mut(target) {
client.notify(ChatType::Meta.server_msg(
"The group leader has passed leadership to you".to_owned(),
));
}
// Tell the old leader that the transfer was succesful
if let Some(client) = clients.get_mut(target) {
client
.notify(ChatType::Meta.server_msg("Leadership transferred".to_owned()));
}
},
Some(_) => {
// Inform transferer that they are not the leader
if let Some(client) = clients.get_mut(entity) {
client.notify(
ChatType::Meta.server_msg(
"Transfer failed: you are not the leader of the target's group"
.to_owned(),
),
);
}
},
None => {
// Inform transferer that the target is not in a group
if let Some(client) = clients.get_mut(entity) {
client.notify(ChatType::Meta.server_msg(
"Transfer failed: your target is not in a group".to_owned(),
));
}
},
}
},
}
}

View File

@ -1,10 +1,11 @@
use crate::{Server, StateExt};
use crate::{client::Client, Server, StateExt};
use common::{
comp::{
self, item,
slot::{self, Slot},
Pos, MAX_PICKUP_RANGE_SQR,
},
msg::ServerMsg,
recipe::default_recipe_book,
sync::{Uid, WorldSyncExt},
terrain::block::Block,
@ -222,6 +223,33 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.ecs()
.write_storage()
.insert(tameable_entity, comp::Alignment::Owned(uid));
// Add to group system
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state
.ecs()
.write_resource::<comp::group::GroupManager>(
);
group_manager.new_pet(
tameable_entity,
entity,
&mut state.ecs().write_storage(),
&state.ecs().entities(),
&mut |entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
c.notify(ServerMsg::GroupUpdate(g))
});
},
);
let _ = state
.ecs()
.write_storage()

View File

@ -8,6 +8,7 @@ use entity_manipulation::{
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
handle_respawn,
};
use group_manip::handle_group;
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
use inventory_manip::handle_inventory;
use player::{handle_client_disconnect, handle_exit_ingame};
@ -15,6 +16,7 @@ use specs::{Entity as EcsEntity, WorldExt};
mod entity_creation;
mod entity_manipulation;
mod group_manip;
mod interaction;
mod inventory_manip;
mod player;
@ -62,6 +64,7 @@ impl Server {
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip),
ServerEvent::Respawn(entity) => handle_respawn(&self, entity),
ServerEvent::LandOnGround { entity, vel } => {
handle_land_on_ground(&self, entity, vel)

View File

@ -4,7 +4,7 @@ use crate::{
};
use common::{
comp,
comp::Player,
comp::{group, Player},
msg::{ClientState, PlayerListUpdate, ServerMsg},
sync::{Uid, UidAllocator},
};
@ -22,6 +22,11 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
let maybe_group = state
.ecs()
.write_storage::<group::Group>()
.get(entity)
.cloned();
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
// Tell client its request was successful
client.allow_state(ClientState::Registered);
@ -29,13 +34,37 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
client.notify(ServerMsg::ExitIngameCleanup);
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
let entity_builder = match maybe_group {
Some(group) => entity_builder.with(group),
None => entity_builder,
};
// Ensure UidAllocator maps this uid to the new entity
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(uid.into()));
entity_builder.with(uid).build();
let new_entity = entity_builder.with(uid).build();
if let Some(group) = maybe_group {
let mut group_manager = state.ecs().write_resource::<group::GroupManager>();
if group_manager
.group_info(group)
.map(|info| info.leader == entity)
.unwrap_or(false)
{
group_manager.assign_leader(
new_entity,
&state.ecs().read_storage(),
&state.ecs().entities(),
// Nothing actually changing
|_, _| {},
);
}
}
}
// Erase group component to avoid group restructure when deleting the entity
state.ecs().write_storage::<group::Group>().remove(entity);
// Delete old entity
if let Err(e) = state.delete_entity_recorded(entity) {
error!(

View File

@ -656,6 +656,7 @@ impl Server {
network_error: std::sync::atomic::AtomicBool::new(false),
last_ping: self.state.get_time(),
login_msg_sent: false,
invited_to_group: None,
};
if self.settings().max_players

View File

@ -320,7 +320,7 @@ impl StateExt for State {
comp::ChatType::GroupMeta(s) | comp::ChatType::Group(_, s) => {
for (client, group) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Group>(),
&ecs.read_storage::<comp::ChatGroup>(),
)
.join()
{
@ -346,6 +346,30 @@ impl StateExt for State {
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration> {
// Remove entity from a group if they are in one
{
let mut clients = self.ecs().write_storage::<Client>();
let uids = self.ecs().read_storage::<Uid>();
let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
group_manager.remove_from_group(
entity,
&mut self.ecs().write_storage(),
&self.ecs().read_storage(),
&uids,
&self.ecs().entities(),
&mut |entity, group_change| {
clients
.get_mut(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
},
);
}
let (maybe_uid, maybe_pos) = (
self.ecs().read_storage::<Uid>().get(entity).copied(),
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),

View File

@ -1,7 +1,7 @@
use super::SysTimer;
use common::{
comp::{
Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter,
Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter,
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
},
msg::EcsCompPacket,
@ -48,7 +48,7 @@ pub struct TrackedComps<'a> {
pub scale: ReadStorage<'a, Scale>,
pub mounting: ReadStorage<'a, Mounting>,
pub mount_state: ReadStorage<'a, MountState>,
pub alignment: ReadStorage<'a, Alignment>,
pub group: ReadStorage<'a, Group>,
pub mass: ReadStorage<'a, Mass>,
pub collider: ReadStorage<'a, Collider>,
pub sticky: ReadStorage<'a, Sticky>,
@ -105,7 +105,7 @@ impl<'a> TrackedComps<'a> {
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.alignment
self.group
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
@ -151,7 +151,7 @@ pub struct ReadTrackers<'a> {
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
pub alignment: ReadExpect<'a, UpdateTracker<Alignment>>,
pub group: ReadExpect<'a, UpdateTracker<Group>>,
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
@ -184,7 +184,7 @@ impl<'a> ReadTrackers<'a> {
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
.with_component(&comps.uid, &*self.alignment, &comps.alignment, filter)
.with_component(&comps.uid, &*self.group, &comps.group, filter)
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
@ -214,7 +214,7 @@ pub struct WriteTrackers<'a> {
scale: WriteExpect<'a, UpdateTracker<Scale>>,
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
group: WriteExpect<'a, UpdateTracker<Group>>,
mass: WriteExpect<'a, UpdateTracker<Mass>>,
collider: WriteExpect<'a, UpdateTracker<Collider>>,
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
@ -236,7 +236,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
trackers.scale.record_changes(&comps.scale);
trackers.mounting.record_changes(&comps.mounting);
trackers.mount_state.record_changes(&comps.mount_state);
trackers.alignment.record_changes(&comps.alignment);
trackers.group.record_changes(&comps.group);
trackers.mass.record_changes(&comps.mass);
trackers.collider.record_changes(&comps.collider);
trackers.sticky.record_changes(&comps.sticky);
@ -291,7 +291,7 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<Scale>();
world.register_tracker::<Mounting>();
world.register_tracker::<MountState>();
world.register_tracker::<Alignment>();
world.register_tracker::<Group>();
world.register_tracker::<Mass>();
world.register_tracker::<Collider>();
world.register_tracker::<Sticky>();