veloren_common/comp/
group.rs

1use crate::{comp::Alignment, uid::Uid};
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4use slab::Slab;
5use specs::{Component, DerefFlaggedStorage, Join, LendJoin, storage::GenericReadStorage};
6use std::iter;
7use tracing::{error, warn};
8
9// Primitive group system
10// Shortcomings include:
11//  - no support for more complex group structures
12//  - lack of npc group integration
13//  - relies on careful management of groups to maintain a valid state
14//  - the possession rod could probably wreck this
15//  - clients don't know which pets are theirs (could be easy to solve by
16//    putting owner uid in Role::Pet)
17
18#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
19pub struct Group(u32);
20
21// TODO: Hack
22// Corresponds to Alignment::Enemy
23pub const ENEMY: Group = Group(u32::MAX);
24// Corresponds to Alignment::Npc | Alignment::Tame
25pub const NPC: Group = Group(u32::MAX - 1);
26
27impl Component for Group {
28    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
29}
30
31#[derive(Clone, Debug)]
32pub struct GroupInfo {
33    // TODO: what about enemy groups, either the leader will constantly change because they have to
34    // be loaded or we create a dummy entity or this needs to be optional
35    pub leader: specs::Entity,
36    // Number of group members (excluding pets)
37    pub num_members: u32,
38    // Name of the group
39    pub name: String,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum Role {
44    Member,
45    Pet,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub enum ChangeNotification<E> {
50    // :D
51    Added(E, Role),
52    // :(
53    Removed(E),
54    NewLeader(E),
55    // Use to put in a group overwriting existing group
56    NewGroup { leader: E, members: Vec<(E, Role)> },
57    // No longer in a group
58    NoGroup,
59}
60// Note: now that we are dipping into uids here consider just using
61// ChangeNotification<Uid> everywhere
62// Also note when the same notification is sent to multiple destinations the
63// mapping might be duplicated effort
64impl<E> ChangeNotification<E> {
65    pub fn try_map_ref<T>(&self, f: impl Fn(&E) -> Option<T>) -> Option<ChangeNotification<T>> {
66        match self {
67            Self::Added(e, r) => f(e).map(|t| ChangeNotification::Added(t, *r)),
68            Self::Removed(e) => f(e).map(ChangeNotification::Removed),
69            Self::NewLeader(e) => f(e).map(ChangeNotification::NewLeader),
70            // Note just discards members that fail map
71            Self::NewGroup { leader, members } => {
72                f(leader).map(|leader| ChangeNotification::NewGroup {
73                    leader,
74                    members: members
75                        .iter()
76                        .filter_map(|(e, r)| f(e).map(|t| (t, *r)))
77                        .collect(),
78                })
79            },
80            Self::NoGroup => Some(ChangeNotification::NoGroup),
81        }
82    }
83}
84
85type GroupsMut<'a> = specs::WriteStorage<'a, Group>;
86type Alignments<'a> = specs::ReadStorage<'a, Alignment>;
87type Uids<'a> = specs::ReadStorage<'a, Uid>;
88
89#[derive(Debug, Default)]
90pub struct GroupManager {
91    groups: Slab<GroupInfo>,
92}
93
94// Gather list of pets of the group member
95// Note: iterating through all entities here could become slow at higher entity
96// counts
97fn pets(
98    entity: specs::Entity,
99    uid: Uid,
100    alignments: &Alignments,
101    entities: &specs::world::EntitiesRes,
102) -> Vec<specs::Entity> {
103    (entities, alignments)
104        .join()
105        .filter_map(|(e, a)| {
106            matches!(a, Alignment::Owned(owner) if *owner == uid && e != entity).then_some(e)
107        })
108        .collect::<Vec<_>>()
109}
110
111/// Returns list of current members of a group
112pub fn members<'a>(
113    group: Group,
114    groups: impl Join<Type = &'a Group> + 'a,
115    entities: &'a specs::world::EntitiesRes,
116    alignments: &'a Alignments,
117    uids: &'a Uids,
118) -> impl Iterator<Item = (specs::Entity, Role)> + 'a {
119    (entities, groups, alignments, uids)
120        .join()
121        .filter(move |&(_e, g, _a, _u)| (*g == group))
122        .map(|(e, _g, a, u)| {
123            (
124                e,
125                if matches!(a, Alignment::Owned(owner) if owner != u) {
126                    Role::Pet
127                } else {
128                    Role::Member
129                },
130            )
131        })
132}
133
134// TODO: optimize add/remove for massive NPC groups
135impl GroupManager {
136    pub fn group_info(&self, group: Group) -> Option<&GroupInfo> {
137        self.groups.get(group.0 as usize)
138    }
139
140    fn group_info_mut(&mut self, group: Group) -> Option<&mut GroupInfo> {
141        self.groups.get_mut(group.0 as usize)
142    }
143
144    fn create_group(&mut self, leader: specs::Entity, num_members: u32) -> Group {
145        Group(self.groups.insert(GroupInfo {
146            leader,
147            num_members,
148            name: "Group".into(),
149        }) as u32)
150    }
151
152    fn remove_group(&mut self, group: Group) { self.groups.remove(group.0 as usize); }
153
154    // Add someone to a group
155    // Also used to create new groups
156    pub fn add_group_member(
157        &mut self,
158        leader: specs::Entity,
159        new_member: specs::Entity,
160        entities: &specs::Entities,
161        groups: &mut GroupsMut,
162        alignments: &Alignments,
163        uids: &Uids,
164        mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
165    ) {
166        // Ensure leader is not inviting themselves
167        if leader == new_member {
168            warn!("Attempt to form group with leader as the only member (this is disallowed)");
169            return;
170        }
171
172        // Get uid
173        let new_member_uid = if let Some(uid) = uids.get(new_member) {
174            *uid
175        } else {
176            error!("Failed to retrieve uid for the new group member");
177            return;
178        };
179
180        // If new member is a member of a different group remove that
181        if groups
182            .get(new_member)
183            .and_then(|g| self.group_info(*g))
184            .is_some()
185        {
186            self.leave_group(
187                new_member,
188                groups,
189                alignments,
190                uids,
191                entities,
192                &mut notifier,
193            )
194        }
195
196        let group = match groups.get(leader).copied() {
197            Some(id)
198                if self
199                    .group_info(id)
200                    .map(|info| info.leader == leader)
201                    .unwrap_or(false) =>
202            {
203                Some(id)
204            },
205            // Member of an existing group can't be a leader
206            // If the lead is a member of another group leave that group first
207            Some(_) => {
208                self.leave_group(leader, groups, alignments, uids, entities, &mut notifier);
209                None
210            },
211            None => None,
212        };
213
214        let group = if let Some(group) = group {
215            // Increment group size
216            // Note: unwrap won't fail since we just retrieved the group successfully above
217            self.group_info_mut(group).unwrap().num_members += 1;
218            group
219        } else {
220            let new_group = self.create_group(leader, 2);
221            // Unwrap should not fail since we just found these entities and they should
222            // still exist Note: if there is an issue replace with a warn
223            groups.insert(leader, new_group).unwrap();
224            // Inform
225            notifier(leader, ChangeNotification::NewLeader(leader));
226            new_group
227        };
228
229        let new_pets = pets(new_member, new_member_uid, alignments, entities);
230
231        // Inform
232        members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| match role {
233            Role::Member => {
234                notifier(e, ChangeNotification::Added(new_member, Role::Member));
235                notifier(new_member, ChangeNotification::Added(e, Role::Member));
236
237                new_pets.iter().for_each(|p| {
238                    notifier(e, ChangeNotification::Added(*p, Role::Pet));
239                })
240            },
241            Role::Pet => {
242                notifier(new_member, ChangeNotification::Added(e, Role::Pet));
243            },
244        });
245        notifier(new_member, ChangeNotification::NewLeader(leader));
246
247        // Add group id for new member and pets
248        // Unwrap should not fail since we just found these entities and they should
249        // still exist
250        // Note: if there is an issue replace with a warn
251        let _ = groups.insert(new_member, group).unwrap();
252        new_pets.iter().for_each(|e| {
253            let _ = groups.insert(*e, group).unwrap();
254        });
255    }
256
257    pub fn new_pet(
258        &mut self,
259        pet: specs::Entity,
260        owner: specs::Entity,
261        groups: &mut GroupsMut,
262        entities: &specs::Entities,
263        alignments: &Alignments,
264        uids: &Uids,
265        notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
266    ) {
267        if !entities.is_alive(owner) {
268            warn!("Tried to create new pet for non-existent owner {owner:?}");
269        } else if !entities.is_alive(pet) {
270            warn!("Tried to create new pet for non-existent pet {pet:?}");
271        } else {
272            let group = match groups.get(owner).copied() {
273                Some(group) => group,
274                None => {
275                    let new_group = self.create_group(owner, 1);
276                    // Unwrap can't fail, we checked that `owner` is alive above
277                    groups.insert(owner, new_group).unwrap();
278                    // Inform
279                    notifier(owner, ChangeNotification::NewLeader(owner));
280                    new_group
281                },
282            };
283
284            // Inform
285            members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| match role {
286                Role::Member => {
287                    notifier(e, ChangeNotification::Added(pet, Role::Pet));
288                },
289                Role::Pet => {},
290            });
291
292            // Add
293            // Unwrap can't fail, we checked that `pet` is alive above
294            groups.insert(pet, group).unwrap();
295        }
296    }
297
298    pub fn leave_group(
299        &mut self,
300        member: specs::Entity,
301        groups: &mut GroupsMut,
302        alignments: &Alignments,
303        uids: &Uids,
304        entities: &specs::Entities,
305        notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
306    ) {
307        // Pets can't leave
308        if matches!(alignments.get(member), Some(Alignment::Owned(uid)) if uids.get(member) != Some(uid))
309        {
310            return;
311        }
312        self.remove_from_group(member, groups, alignments, uids, entities, notifier, false);
313    }
314
315    pub fn entity_deleted(
316        &mut self,
317        member: specs::Entity,
318        groups: &mut GroupsMut,
319        alignments: &Alignments,
320        uids: &Uids,
321        entities: &specs::world::EntitiesRes,
322        notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
323    ) {
324        self.remove_from_group(member, groups, alignments, uids, entities, notifier, true);
325    }
326
327    // Remove someone from a group if they are in one
328    // Don't need to check if they are in a group before calling this
329    // Also removes pets (ie call this if the pet no longer exists)
330    fn remove_from_group(
331        &mut self,
332        member: specs::Entity,
333        groups: &mut GroupsMut,
334        alignments: &Alignments,
335        uids: &Uids,
336        entities: &specs::world::EntitiesRes,
337        notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
338        to_be_deleted: bool,
339    ) {
340        let group = match groups.get(member) {
341            Some(group) => *group,
342            None => return,
343        };
344
345        // If leaving entity was the leader disband the group
346        if self
347            .group_info(group)
348            .map(|info| info.leader == member)
349            .unwrap_or(false)
350        {
351            // Remove group
352            self.remove_group(group);
353
354            (entities, uids, &*groups, alignments.maybe())
355                .join()
356                .filter(|(e, _, g, _)| **g == group && !(to_be_deleted && *e == member))
357                .fold(
358                    HashMap::<Uid, (Option<specs::Entity>, Option<Alignment>, Vec<specs::Entity>)>::new(),
359                    |mut acc, (e, uid, _, alignment)| {
360                        if let Some(owner) = alignment.and_then(|a| match a {
361                            Alignment::Owned(owner) if uid != owner => Some(owner),
362                            _ => None,
363                        }) {
364                            // A pet
365                            // Assumes owner will be in the group
366                            acc.entry(*owner).or_default().2.push(e);
367                        } else {
368                            // Not a pet
369                            //
370                            // Entry may already exist from inserting pets
371                            let entry = acc.entry(*uid).or_default();
372                            entry.0 = Some(e);
373                            entry.1 = alignment.copied();
374                        }
375
376                        acc
377                    },
378                )
379                .into_iter()
380                .map(|(_, v)| v)
381                .for_each(|(owner, alignment, pets)| {
382                    if let Some(owner) = owner {
383                        if let Some(special_group) = alignment.and_then(|a| a.group()) {
384                            // Set NPC and pets back to their special alignment based group
385                            for entity in iter::once(owner).chain(pets) {
386                                groups.insert(entity, special_group)
387                                    .expect("entity from join above so it must exist");
388                            }
389                        } else if !pets.is_empty() {
390                            let mut members =
391                                pets.iter().map(|e| (*e, Role::Pet)).collect::<Vec<_>>();
392                            members.push((owner, Role::Member));
393
394                            // New group
395                            let new_group = self.create_group(owner, 1);
396                            for (member, _) in &members {
397                                groups.insert(*member, new_group).unwrap();
398                            }
399
400                            notifier(owner, ChangeNotification::NewGroup {
401                                leader: owner,
402                                members,
403                            });
404                        } else {
405                            // If no pets just remove group
406                            groups.remove(owner);
407                            notifier(owner, ChangeNotification::NoGroup)
408                        }
409                    } else {
410                        // Owner not found, potentially the were removed from the world
411                        pets.into_iter().for_each(|pet| {
412                            groups.remove(pet);
413                        });
414                    }
415                });
416        // Not leader
417        } else {
418            let leaving_member_uid = if let Some(uid) = uids.get(member) {
419                *uid
420            } else {
421                error!("Failed to retrieve uid for the leaving member");
422                return;
423            };
424
425            let leaving_pets = pets(member, leaving_member_uid, alignments, entities);
426
427            // Handle updates for the leaving group member and their pets.
428            if let Some(special_group) = alignments.get(member).and_then(|a| a.group())
429                && !to_be_deleted
430            {
431                // Set NPC and pets back to their the special alignment based group
432                for entity in iter::once(member).chain(leaving_pets.iter().copied()) {
433                    groups
434                        .insert(entity, special_group)
435                        .expect("entity from join above so it must exist");
436                }
437            // Form new group if these conditions are met:
438            // * Alignment not for a special npc group (handled above)
439            // * The entity has pets.
440            // * The entity isn't about to be deleted.
441            } else if !leaving_pets.is_empty()
442                && !to_be_deleted
443                && alignments.get(member).and_then(Alignment::group).is_none()
444            {
445                let new_group = self.create_group(member, 1);
446
447                notifier(member, ChangeNotification::NewGroup {
448                    leader: member,
449                    members: leaving_pets
450                        .iter()
451                        .map(|p| (*p, Role::Pet))
452                        .chain(iter::once((member, Role::Member)))
453                        .collect(),
454                });
455
456                let _ = groups.insert(member, new_group).unwrap();
457                leaving_pets.iter().for_each(|&e| {
458                    let _ = groups.insert(e, new_group).unwrap();
459                });
460            } else {
461                let _ = groups.remove(member);
462                notifier(member, ChangeNotification::NoGroup);
463                leaving_pets.iter().for_each(|&e| {
464                    let _ = groups.remove(e);
465                });
466            }
467
468            // Handle updates for the rest of the group, potentially disbanding it if there
469            // is now only one member left.
470            if let Some(info) = self.group_info_mut(group) {
471                // If not pet, decrement number of members
472                if !matches!(alignments.get(member), Some(Alignment::Owned(owner)) if uids.get(member) != Some(owner))
473                {
474                    if info.num_members > 0 {
475                        info.num_members -= 1;
476                    } else {
477                        error!("Group with invalid number of members")
478                    }
479                }
480
481                let mut remaining_count = 0; // includes pets
482                // Inform remaining members
483                members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| {
484                    remaining_count += 1;
485                    match role {
486                        Role::Member => {
487                            notifier(e, ChangeNotification::Removed(member));
488                            leaving_pets.iter().for_each(|p| {
489                                notifier(e, ChangeNotification::Removed(*p));
490                            })
491                        },
492                        Role::Pet => {},
493                    }
494                });
495                // If leader is the last one left then disband the group
496                // Assumes last member is the leader
497                if remaining_count == 1 {
498                    let leader = info.leader;
499                    self.remove_group(group);
500                    groups.remove(leader);
501                    notifier(leader, ChangeNotification::NoGroup);
502                } else if remaining_count == 0 {
503                    error!("Somehow group has no members")
504                }
505            }
506        }
507    }
508
509    // Assign new group leader
510    // Does nothing if new leader is not part of a group
511    pub fn assign_leader<'a>(
512        &mut self,
513        new_leader: specs::Entity,
514        groups: impl GenericReadStorage<Component = Group> + Join<Type = &'a Group> + 'a,
515        entities: &'a specs::Entities,
516        alignments: &'a Alignments,
517        uids: &'a Uids,
518        mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
519    ) {
520        let group = match groups.get(new_leader) {
521            Some(group) => *group,
522            None => return,
523        };
524
525        // Set new leader
526        self.groups[group.0 as usize].leader = new_leader;
527
528        // Point to new leader
529        members(group, groups, entities, alignments, uids).for_each(|(e, role)| match role {
530            Role::Member => notifier(e, ChangeNotification::NewLeader(new_leader)),
531            Role::Pet => {},
532        });
533    }
534}
535
536impl Group {
537    /// Returns whether this is one of the special npc or enemy groups
538    // FIXME: These groups are a HACK
539    pub fn is_special(&self) -> bool { *self == NPC || *self == ENEMY }
540}