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",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"slab",
|
||||||
"specs",
|
"specs",
|
||||||
"specs-idvs",
|
"specs-idvs",
|
||||||
"sum_type",
|
"sum_type",
|
||||||
|
@ -17,8 +17,8 @@ use byteorder::{ByteOrder, LittleEndian};
|
|||||||
use common::{
|
use common::{
|
||||||
character::CharacterItem,
|
character::CharacterItem,
|
||||||
comp::{
|
comp::{
|
||||||
self, ControlAction, ControlEvent, Controller, ControllerInputs, InventoryManip,
|
self, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
||||||
InventoryUpdateEvent,
|
InventoryManip, InventoryUpdateEvent,
|
||||||
},
|
},
|
||||||
msg::{
|
msg::{
|
||||||
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
|
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
|
||||||
@ -74,11 +74,15 @@ pub struct Client {
|
|||||||
pub server_info: ServerInfo,
|
pub server_info: ServerInfo,
|
||||||
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
|
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
|
||||||
pub player_list: HashMap<Uid, PlayerInfo>,
|
pub player_list: HashMap<Uid, PlayerInfo>,
|
||||||
|
pub group_members: HashSet<Uid>,
|
||||||
pub character_list: CharacterList,
|
pub character_list: CharacterList,
|
||||||
pub active_character_id: Option<i32>,
|
pub active_character_id: Option<i32>,
|
||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
available_recipes: HashSet<String>,
|
available_recipes: HashSet<String>,
|
||||||
|
|
||||||
|
group_invite: Option<Uid>,
|
||||||
|
group_leader: Option<Uid>,
|
||||||
|
|
||||||
_network: Network,
|
_network: Network,
|
||||||
participant: Option<Participant>,
|
participant: Option<Participant>,
|
||||||
singleton_stream: Stream,
|
singleton_stream: Stream,
|
||||||
@ -208,11 +212,15 @@ impl Client {
|
|||||||
server_info,
|
server_info,
|
||||||
world_map,
|
world_map,
|
||||||
player_list: HashMap::new(),
|
player_list: HashMap::new(),
|
||||||
|
group_members: HashSet::new(),
|
||||||
character_list: CharacterList::default(),
|
character_list: CharacterList::default(),
|
||||||
active_character_id: None,
|
active_character_id: None,
|
||||||
recipe_book,
|
recipe_book,
|
||||||
available_recipes: HashSet::default(),
|
available_recipes: HashSet::default(),
|
||||||
|
|
||||||
|
group_invite: None,
|
||||||
|
group_leader: None,
|
||||||
|
|
||||||
_network: network,
|
_network: network,
|
||||||
participant: Some(participant),
|
participant: Some(participant),
|
||||||
singleton_stream: stream,
|
singleton_stream: stream,
|
||||||
@ -424,6 +432,53 @@ impl Client {
|
|||||||
.unwrap();
|
.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 {
|
pub fn is_mounted(&self) -> bool {
|
||||||
self.state
|
self.state
|
||||||
.ecs()
|
.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 => {
|
ServerMsg::Ping => {
|
||||||
self.singleton_stream.send(ClientMsg::Pong)?;
|
self.singleton_stream.send(ClientMsg::Pong)?;
|
||||||
},
|
},
|
||||||
|
@ -29,7 +29,8 @@ crossbeam = "0.7"
|
|||||||
notify = "5.0.0-pre.3"
|
notify = "5.0.0-pre.3"
|
||||||
indexmap = "1.3.0"
|
indexmap = "1.3.0"
|
||||||
sum_type = "0.2.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]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::{path::Chaser, sync::Uid};
|
use crate::{path::Chaser, sync::Uid};
|
||||||
use serde::{Deserialize, Serialize};
|
use specs::{Component, Entity as EcsEntity};
|
||||||
use specs::{Component, Entity as EcsEntity, FlaggedStorage};
|
|
||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
/// Wild animals and gentle giants
|
/// Wild animals and gentle giants
|
||||||
Wild,
|
Wild,
|
||||||
@ -52,7 +51,7 @@ impl Alignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Alignment {
|
impl Component for Alignment {
|
||||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -18,12 +18,23 @@ pub enum InventoryManip {
|
|||||||
CraftRecipe(String),
|
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)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum ControlEvent {
|
pub enum ControlEvent {
|
||||||
ToggleLantern,
|
ToggleLantern,
|
||||||
Mount(Uid),
|
Mount(Uid),
|
||||||
Unmount,
|
Unmount,
|
||||||
InventoryManip(InventoryManip),
|
InventoryManip(InventoryManip),
|
||||||
|
GroupManip(GroupManip),
|
||||||
Respawn,
|
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 controller;
|
||||||
mod damage;
|
mod damage;
|
||||||
mod energy;
|
mod energy;
|
||||||
|
pub mod group;
|
||||||
mod inputs;
|
mod inputs;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
mod last;
|
mod last;
|
||||||
@ -28,13 +29,17 @@ pub use body::{
|
|||||||
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
|
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
|
||||||
};
|
};
|
||||||
pub use character_state::{Attacking, CharacterState, StateUpdate};
|
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::{
|
pub use controller::{
|
||||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, Input, InventoryManip,
|
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||||
MountState, Mounting,
|
InventoryManip, MountState, Mounting,
|
||||||
};
|
};
|
||||||
pub use damage::{Damage, DamageSource};
|
pub use damage::{Damage, DamageSource};
|
||||||
pub use energy::{Energy, EnergySource};
|
pub use energy::{Energy, EnergySource};
|
||||||
|
pub use group::Group;
|
||||||
pub use inputs::CanBuild;
|
pub use inputs::CanBuild;
|
||||||
pub use inventory::{
|
pub use inventory::{
|
||||||
item,
|
item,
|
||||||
|
@ -35,6 +35,7 @@ pub enum ServerEvent {
|
|||||||
cause: comp::HealthSource,
|
cause: comp::HealthSource,
|
||||||
},
|
},
|
||||||
InventoryManip(EcsEntity, comp::InventoryManip),
|
InventoryManip(EcsEntity, comp::InventoryManip),
|
||||||
|
GroupManip(EcsEntity, comp::GroupManip),
|
||||||
Respawn(EcsEntity),
|
Respawn(EcsEntity),
|
||||||
Shoot {
|
Shoot {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
|
@ -17,7 +17,7 @@ sum_type! {
|
|||||||
LightEmitter(comp::LightEmitter),
|
LightEmitter(comp::LightEmitter),
|
||||||
Item(comp::Item),
|
Item(comp::Item),
|
||||||
Scale(comp::Scale),
|
Scale(comp::Scale),
|
||||||
Alignment(comp::Alignment),
|
Group(comp::Group),
|
||||||
MountState(comp::MountState),
|
MountState(comp::MountState),
|
||||||
Mounting(comp::Mounting),
|
Mounting(comp::Mounting),
|
||||||
Mass(comp::Mass),
|
Mass(comp::Mass),
|
||||||
@ -44,7 +44,7 @@ sum_type! {
|
|||||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||||
Item(PhantomData<comp::Item>),
|
Item(PhantomData<comp::Item>),
|
||||||
Scale(PhantomData<comp::Scale>),
|
Scale(PhantomData<comp::Scale>),
|
||||||
Alignment(PhantomData<comp::Alignment>),
|
Group(PhantomData<comp::Group>),
|
||||||
MountState(PhantomData<comp::MountState>),
|
MountState(PhantomData<comp::MountState>),
|
||||||
Mounting(PhantomData<comp::Mounting>),
|
Mounting(PhantomData<comp::Mounting>),
|
||||||
Mass(PhantomData<comp::Mass>),
|
Mass(PhantomData<comp::Mass>),
|
||||||
@ -71,7 +71,7 @@ impl sync::CompPacket for EcsCompPacket {
|
|||||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Item(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::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::MountState(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Mounting(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),
|
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::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Item(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::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::MountState(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Mounting(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),
|
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::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(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::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
|
||||||
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
||||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(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
|
/// An error occured while creating or deleting a character
|
||||||
CharacterActionError(String),
|
CharacterActionError(String),
|
||||||
PlayerListUpdate(PlayerListUpdate),
|
PlayerListUpdate(PlayerListUpdate),
|
||||||
|
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
|
||||||
|
GroupInvite(sync::Uid),
|
||||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||||
/// from an ingame state
|
/// from an ingame state
|
||||||
|
@ -123,7 +123,7 @@ impl State {
|
|||||||
ecs.register::<comp::Gravity>();
|
ecs.register::<comp::Gravity>();
|
||||||
ecs.register::<comp::CharacterState>();
|
ecs.register::<comp::CharacterState>();
|
||||||
ecs.register::<comp::Object>();
|
ecs.register::<comp::Object>();
|
||||||
ecs.register::<comp::Alignment>();
|
ecs.register::<comp::Group>();
|
||||||
|
|
||||||
// Register components send from clients -> server
|
// Register components send from clients -> server
|
||||||
ecs.register::<comp::Controller>();
|
ecs.register::<comp::Controller>();
|
||||||
@ -146,6 +146,7 @@ impl State {
|
|||||||
ecs.register::<comp::Last<comp::Pos>>();
|
ecs.register::<comp::Last<comp::Pos>>();
|
||||||
ecs.register::<comp::Last<comp::Vel>>();
|
ecs.register::<comp::Last<comp::Vel>>();
|
||||||
ecs.register::<comp::Last<comp::Ori>>();
|
ecs.register::<comp::Last<comp::Ori>>();
|
||||||
|
ecs.register::<comp::Alignment>();
|
||||||
ecs.register::<comp::Agent>();
|
ecs.register::<comp::Agent>();
|
||||||
ecs.register::<comp::WaypointArea>();
|
ecs.register::<comp::WaypointArea>();
|
||||||
ecs.register::<comp::ForceUpdate>();
|
ecs.register::<comp::ForceUpdate>();
|
||||||
@ -156,7 +157,7 @@ impl State {
|
|||||||
ecs.register::<comp::Attacking>();
|
ecs.register::<comp::Attacking>();
|
||||||
ecs.register::<comp::ItemDrop>();
|
ecs.register::<comp::ItemDrop>();
|
||||||
ecs.register::<comp::ChatMode>();
|
ecs.register::<comp::ChatMode>();
|
||||||
ecs.register::<comp::Group>();
|
ecs.register::<comp::ChatGroup>();
|
||||||
ecs.register::<comp::Faction>();
|
ecs.register::<comp::Faction>();
|
||||||
|
|
||||||
// Register synced resources used by the ECS.
|
// Register synced resources used by the ECS.
|
||||||
@ -168,9 +169,10 @@ impl State {
|
|||||||
ecs.insert(TerrainGrid::new().unwrap());
|
ecs.insert(TerrainGrid::new().unwrap());
|
||||||
ecs.insert(BlockChange::default());
|
ecs.insert(BlockChange::default());
|
||||||
ecs.insert(TerrainChanges::default());
|
ecs.insert(TerrainChanges::default());
|
||||||
|
ecs.insert(EventBus::<LocalEvent>::default());
|
||||||
// TODO: only register on the server
|
// TODO: only register on the server
|
||||||
ecs.insert(EventBus::<ServerEvent>::default());
|
ecs.insert(EventBus::<ServerEvent>::default());
|
||||||
ecs.insert(EventBus::<LocalEvent>::default());
|
ecs.insert(comp::group::GroupManager::default());
|
||||||
ecs.insert(RegionMap::new());
|
ecs.insert(RegionMap::new());
|
||||||
|
|
||||||
ecs
|
ecs
|
||||||
|
@ -2,6 +2,7 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::Activity,
|
agent::Activity,
|
||||||
|
group,
|
||||||
item::{tool::ToolKind, ItemKind},
|
item::{tool::ToolKind, ItemKind},
|
||||||
Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout,
|
Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout,
|
||||||
MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel,
|
MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel,
|
||||||
@ -29,6 +30,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Read<'a, UidAllocator>,
|
Read<'a, UidAllocator>,
|
||||||
Read<'a, Time>,
|
Read<'a, Time>,
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
|
Read<'a, group::GroupManager>,
|
||||||
Write<'a, EventBus<ServerEvent>>,
|
Write<'a, EventBus<ServerEvent>>,
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
@ -40,6 +42,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, CharacterState>,
|
ReadStorage<'a, CharacterState>,
|
||||||
ReadStorage<'a, PhysicsState>,
|
ReadStorage<'a, PhysicsState>,
|
||||||
ReadStorage<'a, Uid>,
|
ReadStorage<'a, Uid>,
|
||||||
|
ReadStorage<'a, group::Group>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
ReadStorage<'a, Alignment>,
|
ReadStorage<'a, Alignment>,
|
||||||
ReadStorage<'a, Body>,
|
ReadStorage<'a, Body>,
|
||||||
@ -55,6 +58,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
uid_allocator,
|
uid_allocator,
|
||||||
time,
|
time,
|
||||||
dt,
|
dt,
|
||||||
|
group_manager,
|
||||||
event_bus,
|
event_bus,
|
||||||
entities,
|
entities,
|
||||||
positions,
|
positions,
|
||||||
@ -66,6 +70,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
character_states,
|
character_states,
|
||||||
physics_states,
|
physics_states,
|
||||||
uids,
|
uids,
|
||||||
|
groups,
|
||||||
terrain,
|
terrain,
|
||||||
alignments,
|
alignments,
|
||||||
bodies,
|
bodies,
|
||||||
@ -88,6 +93,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
agent,
|
agent,
|
||||||
controller,
|
controller,
|
||||||
mount_state,
|
mount_state,
|
||||||
|
group,
|
||||||
) in (
|
) in (
|
||||||
&entities,
|
&entities,
|
||||||
&positions,
|
&positions,
|
||||||
@ -102,9 +108,19 @@ impl<'a> System<'a> for Sys {
|
|||||||
&mut agents,
|
&mut agents,
|
||||||
&mut controllers,
|
&mut controllers,
|
||||||
mount_states.maybe(),
|
mount_states.maybe(),
|
||||||
|
groups.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.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
|
// Skip mounted entities
|
||||||
if mount_state
|
if mount_state
|
||||||
.map(|ms| *ms != MountState::Unmounted)
|
.map(|ms| *ms != MountState::Unmounted)
|
||||||
@ -117,7 +133,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut inputs = &mut controller.inputs;
|
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;
|
inputs.look_dir = ori.0;
|
||||||
|
|
||||||
const AVG_FOLLOW_DIST: f32 = 6.0;
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
||||||
@ -148,11 +164,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
thread_rng().gen::<f32>() - 0.5,
|
thread_rng().gen::<f32>() - 0.5,
|
||||||
) * 0.1
|
) * 0.1
|
||||||
- *bearing * 0.003
|
- *bearing * 0.003
|
||||||
- if let Some(patrol_origin) = agent.patrol_origin {
|
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
||||||
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
|
(pos.0 - patrol_origin).xy() * 0.0002
|
||||||
} else {
|
});
|
||||||
Vec2::zero()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stop if we're too close to a wall
|
// Stop if we're too close to a wall
|
||||||
*bearing *= 0.1
|
*bearing *= 0.1
|
||||||
@ -169,8 +183,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
.until(|block| block.is_solid())
|
.until(|block| block.is_solid())
|
||||||
.cast()
|
.cast()
|
||||||
.1
|
.1
|
||||||
.map(|b| b.is_none())
|
.map_or(true, |b| b.is_none())
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
{
|
||||||
0.9
|
0.9
|
||||||
} else {
|
} else {
|
||||||
@ -269,8 +282,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Don't attack entities we are passive towards
|
// Don't attack entities we are passive towards
|
||||||
// TODO: This is here, it's a bit of a hack
|
// TODO: This is here, it's a bit of a hack
|
||||||
if let Some(alignment) = alignment {
|
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;
|
do_idle = true;
|
||||||
break 'activity;
|
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
|
// 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())?;
|
let owner = uid_allocator.retrieve_entity_internal(owner.id())?;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
Alignment, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange,
|
group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
|
||||||
HealthSource, Loadout, Ori, Pos, Scale, Stats,
|
Loadout, Ori, Pos, Scale, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, ServerEvent},
|
event::{EventBus, LocalEvent, ServerEvent},
|
||||||
sync::Uid,
|
sync::Uid,
|
||||||
@ -26,10 +26,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, Ori>,
|
ReadStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Scale>,
|
ReadStorage<'a, Scale>,
|
||||||
ReadStorage<'a, Alignment>,
|
|
||||||
ReadStorage<'a, Body>,
|
ReadStorage<'a, Body>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
ReadStorage<'a, Loadout>,
|
ReadStorage<'a, Loadout>,
|
||||||
|
ReadStorage<'a, group::Group>,
|
||||||
WriteStorage<'a, Attacking>,
|
WriteStorage<'a, Attacking>,
|
||||||
WriteStorage<'a, CharacterState>,
|
WriteStorage<'a, CharacterState>,
|
||||||
);
|
);
|
||||||
@ -44,10 +44,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
positions,
|
positions,
|
||||||
orientations,
|
orientations,
|
||||||
scales,
|
scales,
|
||||||
alignments,
|
|
||||||
bodies,
|
bodies,
|
||||||
stats,
|
stats,
|
||||||
loadouts,
|
loadouts,
|
||||||
|
groups,
|
||||||
mut attacking_storage,
|
mut attacking_storage,
|
||||||
character_states,
|
character_states,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
@ -71,23 +71,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
attack.applied = true;
|
attack.applied = true;
|
||||||
|
|
||||||
// Go through all other entities
|
// Go through all other entities
|
||||||
for (
|
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
|
||||||
b,
|
|
||||||
uid_b,
|
|
||||||
pos_b,
|
|
||||||
ori_b,
|
|
||||||
scale_b_maybe,
|
|
||||||
alignment_b_maybe,
|
|
||||||
character_b,
|
|
||||||
stats_b,
|
|
||||||
body_b,
|
|
||||||
) in (
|
|
||||||
&entities,
|
&entities,
|
||||||
&uids,
|
&uids,
|
||||||
&positions,
|
&positions,
|
||||||
&orientations,
|
&orientations,
|
||||||
scales.maybe(),
|
scales.maybe(),
|
||||||
alignments.maybe(),
|
|
||||||
character_states.maybe(),
|
character_states.maybe(),
|
||||||
&stats,
|
&stats,
|
||||||
&bodies,
|
&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)
|
&& 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()
|
&& 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
|
// Weapon gives base damage
|
||||||
let source = if attack.base_healthchange > 0 {
|
let source = if attack.base_healthchange > 0 {
|
||||||
DamageSource::Healing
|
DamageSource::Healing
|
||||||
@ -121,28 +121,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
healthchange: attack.base_healthchange as f32,
|
healthchange: attack.base_healthchange as f32,
|
||||||
source,
|
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)
|
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;
|
&& 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 {
|
local_emitter.emit(LocalEvent::ApplyForce {
|
||||||
entity: b,
|
entity: b,
|
||||||
force: knockback
|
force: attack.knockback
|
||||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
* *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))
|
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)),
|
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ pub struct Client {
|
|||||||
pub network_error: AtomicBool,
|
pub network_error: AtomicBool,
|
||||||
pub last_ping: f64,
|
pub last_ping: f64,
|
||||||
pub login_msg_sent: bool,
|
pub login_msg_sent: bool,
|
||||||
|
pub invited_to_group: Option<specs::Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Client {
|
impl Component for Client {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//! To implement a new command, add an instance of `ChatCommand` to
|
//! To implement a new command, add an instance of `ChatCommand` to
|
||||||
//! `CHAT_COMMANDS` and provide a handler function.
|
//! `CHAT_COMMANDS` and provide a handler function.
|
||||||
|
|
||||||
use crate::{Server, StateExt};
|
use crate::{client::Client, Server, StateExt};
|
||||||
use chrono::{NaiveTime, Timelike};
|
use chrono::{NaiveTime, Timelike};
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
@ -557,6 +557,42 @@ fn handle_spawn(
|
|||||||
|
|
||||||
let new_entity = entity_base.build();
|
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) {
|
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
@ -1161,7 +1197,7 @@ fn handle_group(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ecs = server.state.ecs();
|
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 mode = comp::ChatMode::Group(group.to_string());
|
||||||
let _ = ecs.write_storage().insert(client, mode.clone());
|
let _ = ecs.write_storage().insert(client, mode.clone());
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
@ -1352,7 +1388,7 @@ fn handle_join_group(
|
|||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
.insert(client, comp::Group(group.clone()))
|
.insert(client, comp::ChatGroup(group.clone()))
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|f| f.0);
|
.map(|f| f.0);
|
||||||
@ -1369,7 +1405,7 @@ fn handle_join_group(
|
|||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
.remove(client)
|
.remove(client)
|
||||||
.map(|comp::Group(f)| f)
|
.map(|comp::ChatGroup(f)| f)
|
||||||
};
|
};
|
||||||
if let Some(group) = group_leave {
|
if let Some(group) = group_leave {
|
||||||
server.state.send_chat(
|
server.state.send_chat(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{sys, Server, StateExt};
|
use crate::{sys, Server, StateExt};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
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,
|
Projectile, Scale, Stats, Vel, WaypointArea,
|
||||||
},
|
},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
@ -36,12 +36,26 @@ pub fn handle_create_npc(
|
|||||||
scale: Scale,
|
scale: Scale,
|
||||||
drop_item: Option<Item>,
|
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
|
let entity = server
|
||||||
.state
|
.state
|
||||||
.create_npc(pos, stats, loadout, body)
|
.create_npc(pos, stats, loadout, body)
|
||||||
.with(scale)
|
.with(scale)
|
||||||
.with(alignment);
|
.with(alignment);
|
||||||
|
|
||||||
|
let entity = if let Some(group) = group {
|
||||||
|
entity.with(group)
|
||||||
|
} else {
|
||||||
|
entity
|
||||||
|
};
|
||||||
|
|
||||||
let entity = if let Some(agent) = agent.into() {
|
let entity = if let Some(agent) = agent.into() {
|
||||||
entity.with(agent)
|
entity.with(agent)
|
||||||
} else {
|
} 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::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, item,
|
self, item,
|
||||||
slot::{self, Slot},
|
slot::{self, Slot},
|
||||||
Pos, MAX_PICKUP_RANGE_SQR,
|
Pos, MAX_PICKUP_RANGE_SQR,
|
||||||
},
|
},
|
||||||
|
msg::ServerMsg,
|
||||||
recipe::default_recipe_book,
|
recipe::default_recipe_book,
|
||||||
sync::{Uid, WorldSyncExt},
|
sync::{Uid, WorldSyncExt},
|
||||||
terrain::block::Block,
|
terrain::block::Block,
|
||||||
@ -222,6 +223,33 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
.insert(tameable_entity, comp::Alignment::Owned(uid));
|
.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
|
let _ = state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
|
@ -8,6 +8,7 @@ use entity_manipulation::{
|
|||||||
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
||||||
handle_respawn,
|
handle_respawn,
|
||||||
};
|
};
|
||||||
|
use group_manip::handle_group;
|
||||||
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
||||||
use inventory_manip::handle_inventory;
|
use inventory_manip::handle_inventory;
|
||||||
use player::{handle_client_disconnect, handle_exit_ingame};
|
use player::{handle_client_disconnect, handle_exit_ingame};
|
||||||
@ -15,6 +16,7 @@ use specs::{Entity as EcsEntity, WorldExt};
|
|||||||
|
|
||||||
mod entity_creation;
|
mod entity_creation;
|
||||||
mod entity_manipulation;
|
mod entity_manipulation;
|
||||||
|
mod group_manip;
|
||||||
mod interaction;
|
mod interaction;
|
||||||
mod inventory_manip;
|
mod inventory_manip;
|
||||||
mod player;
|
mod player;
|
||||||
@ -62,6 +64,7 @@ impl Server {
|
|||||||
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
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::Respawn(entity) => handle_respawn(&self, entity),
|
||||||
ServerEvent::LandOnGround { entity, vel } => {
|
ServerEvent::LandOnGround { entity, vel } => {
|
||||||
handle_land_on_ground(&self, entity, vel)
|
handle_land_on_ground(&self, entity, vel)
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
comp::Player,
|
comp::{group, Player},
|
||||||
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
||||||
sync::{Uid, UidAllocator},
|
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_client = state.ecs().write_storage::<Client>().remove(entity);
|
||||||
let maybe_uid = state.read_component_cloned::<Uid>(entity);
|
let maybe_uid = state.read_component_cloned::<Uid>(entity);
|
||||||
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(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) {
|
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
|
||||||
// Tell client its request was successful
|
// Tell client its request was successful
|
||||||
client.allow_state(ClientState::Registered);
|
client.allow_state(ClientState::Registered);
|
||||||
@ -29,13 +34,37 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
|||||||
client.notify(ServerMsg::ExitIngameCleanup);
|
client.notify(ServerMsg::ExitIngameCleanup);
|
||||||
|
|
||||||
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
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
|
// Ensure UidAllocator maps this uid to the new entity
|
||||||
let uid = entity_builder
|
let uid = entity_builder
|
||||||
.world
|
.world
|
||||||
.write_resource::<UidAllocator>()
|
.write_resource::<UidAllocator>()
|
||||||
.allocate(entity_builder.entity, Some(uid.into()));
|
.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
|
// Delete old entity
|
||||||
if let Err(e) = state.delete_entity_recorded(entity) {
|
if let Err(e) = state.delete_entity_recorded(entity) {
|
||||||
error!(
|
error!(
|
||||||
|
@ -656,6 +656,7 @@ impl Server {
|
|||||||
network_error: std::sync::atomic::AtomicBool::new(false),
|
network_error: std::sync::atomic::AtomicBool::new(false),
|
||||||
last_ping: self.state.get_time(),
|
last_ping: self.state.get_time(),
|
||||||
login_msg_sent: false,
|
login_msg_sent: false,
|
||||||
|
invited_to_group: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.settings().max_players
|
if self.settings().max_players
|
||||||
|
@ -320,7 +320,7 @@ impl StateExt for State {
|
|||||||
comp::ChatType::GroupMeta(s) | comp::ChatType::Group(_, s) => {
|
comp::ChatType::GroupMeta(s) | comp::ChatType::Group(_, s) => {
|
||||||
for (client, group) in (
|
for (client, group) in (
|
||||||
&mut ecs.write_storage::<Client>(),
|
&mut ecs.write_storage::<Client>(),
|
||||||
&ecs.read_storage::<comp::Group>(),
|
&ecs.read_storage::<comp::ChatGroup>(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
@ -346,6 +346,30 @@ impl StateExt for State {
|
|||||||
&mut self,
|
&mut self,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
) -> Result<(), specs::error::WrongGeneration> {
|
) -> 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) = (
|
let (maybe_uid, maybe_pos) = (
|
||||||
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
||||||
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::SysTimer;
|
use super::SysTimer;
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
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,
|
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
|
||||||
},
|
},
|
||||||
msg::EcsCompPacket,
|
msg::EcsCompPacket,
|
||||||
@ -48,7 +48,7 @@ pub struct TrackedComps<'a> {
|
|||||||
pub scale: ReadStorage<'a, Scale>,
|
pub scale: ReadStorage<'a, Scale>,
|
||||||
pub mounting: ReadStorage<'a, Mounting>,
|
pub mounting: ReadStorage<'a, Mounting>,
|
||||||
pub mount_state: ReadStorage<'a, MountState>,
|
pub mount_state: ReadStorage<'a, MountState>,
|
||||||
pub alignment: ReadStorage<'a, Alignment>,
|
pub group: ReadStorage<'a, Group>,
|
||||||
pub mass: ReadStorage<'a, Mass>,
|
pub mass: ReadStorage<'a, Mass>,
|
||||||
pub collider: ReadStorage<'a, Collider>,
|
pub collider: ReadStorage<'a, Collider>,
|
||||||
pub sticky: ReadStorage<'a, Sticky>,
|
pub sticky: ReadStorage<'a, Sticky>,
|
||||||
@ -105,7 +105,7 @@ impl<'a> TrackedComps<'a> {
|
|||||||
.get(entity)
|
.get(entity)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|c| comps.push(c.into()));
|
.map(|c| comps.push(c.into()));
|
||||||
self.alignment
|
self.group
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|c| comps.push(c.into()));
|
.map(|c| comps.push(c.into()));
|
||||||
@ -151,7 +151,7 @@ pub struct ReadTrackers<'a> {
|
|||||||
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
||||||
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
|
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
|
||||||
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
|
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 mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
||||||
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
||||||
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
|
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.scale, &comps.scale, filter)
|
||||||
.with_component(&comps.uid, &*self.mounting, &comps.mounting, 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.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.mass, &comps.mass, filter)
|
||||||
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
|
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
|
||||||
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
||||||
@ -214,7 +214,7 @@ pub struct WriteTrackers<'a> {
|
|||||||
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
||||||
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
||||||
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
||||||
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
|
group: WriteExpect<'a, UpdateTracker<Group>>,
|
||||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||||
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
||||||
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
||||||
@ -236,7 +236,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
|||||||
trackers.scale.record_changes(&comps.scale);
|
trackers.scale.record_changes(&comps.scale);
|
||||||
trackers.mounting.record_changes(&comps.mounting);
|
trackers.mounting.record_changes(&comps.mounting);
|
||||||
trackers.mount_state.record_changes(&comps.mount_state);
|
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.mass.record_changes(&comps.mass);
|
||||||
trackers.collider.record_changes(&comps.collider);
|
trackers.collider.record_changes(&comps.collider);
|
||||||
trackers.sticky.record_changes(&comps.sticky);
|
trackers.sticky.record_changes(&comps.sticky);
|
||||||
@ -291,7 +291,7 @@ pub fn register_trackers(world: &mut World) {
|
|||||||
world.register_tracker::<Scale>();
|
world.register_tracker::<Scale>();
|
||||||
world.register_tracker::<Mounting>();
|
world.register_tracker::<Mounting>();
|
||||||
world.register_tracker::<MountState>();
|
world.register_tracker::<MountState>();
|
||||||
world.register_tracker::<Alignment>();
|
world.register_tracker::<Group>();
|
||||||
world.register_tracker::<Mass>();
|
world.register_tracker::<Mass>();
|
||||||
world.register_tracker::<Collider>();
|
world.register_tracker::<Collider>();
|
||||||
world.register_tracker::<Sticky>();
|
world.register_tracker::<Sticky>();
|
||||||
|
Loading…
Reference in New Issue
Block a user