mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add basic group functionality (no voxygen wiring yet)
This commit is contained in:
parent
1741384d00
commit
10c3d01466
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4638,6 +4638,7 @@ dependencies = [
|
||||
"ron",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slab",
|
||||
"specs",
|
||||
"specs-idvs",
|
||||
"sum_type",
|
||||
|
@ -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)?;
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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)]
|
||||
|
@ -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
392
common/src/comp/group.rs
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -35,6 +35,7 @@ pub enum ServerEvent {
|
||||
cause: comp::HealthSource,
|
||||
},
|
||||
InventoryManip(EcsEntity, comp::InventoryManip),
|
||||
GroupManip(EcsEntity, comp::GroupManip),
|
||||
Respawn(EcsEntity),
|
||||
Shoot {
|
||||
entity: EcsEntity,
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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())?;
|
||||
|
||||
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
304
server/src/events/group_manip.rs
Normal file
304
server/src/events/group_manip.rs
Normal 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(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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!(
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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>();
|
||||
|
Loading…
Reference in New Issue
Block a user