Fixes and tweaks for groups

This commit is contained in:
Imbris 2020-07-11 23:12:03 -04:00 committed by Monty Marz
parent d9e3937a82
commit 0a8f148559
7 changed files with 195 additions and 67 deletions

View File

@ -1021,6 +1021,13 @@ impl Client {
NewGroup { leader, members } => {
self.group_leader = Some(leader);
self.group_members = members.into_iter().collect();
// Currently add/remove messages treat client as an implicit member
// of the group whereas this message explicitly included them so to
// be consistent for now we will remove the client from the
// received hashset
if let Some(uid) = self.uid() {
self.group_members.remove(&uid);
}
},
NoGroup => {
self.group_leader = None;

View File

@ -89,7 +89,9 @@ fn with_pets(
) -> Vec<specs::Entity> {
let mut list = (entities, alignments)
.join()
.filter_map(|(e, a)| matches!(a, Alignment::Owned(owner) if *owner == uid).then_some(e))
.filter_map(|(e, a)| {
matches!(a, Alignment::Owned(owner) if *owner == uid && e != entity).then_some(e)
})
.collect::<Vec<_>>();
list.push(entity);
list
@ -145,8 +147,12 @@ impl GroupManager {
};
// If new member is a member of a different group remove that
if groups.get(new_member).is_some() {
self.remove_from_group(
if groups
.get(new_member)
.and_then(|g| self.group_info(*g))
.is_some()
{
self.leave_group(
new_member,
groups,
alignments,
@ -168,7 +174,7 @@ impl GroupManager {
// 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);
self.leave_group(leader, groups, alignments, uids, entities, &mut notifier);
None
},
None => None,
@ -238,10 +244,7 @@ impl GroupManager {
}
}
// 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(
pub fn leave_group(
&mut self,
member: specs::Entity,
groups: &mut GroupsMut,
@ -249,6 +252,52 @@ impl GroupManager {
uids: &Uids,
entities: &specs::Entities,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
// Pets can't leave
if matches!(alignments.get(member), Some(Alignment::Owned(uid)) if uids.get(member).map_or(true, |u| u != uid))
{
return;
}
self.remove_from_group(member, groups, alignments, uids, entities, notifier, false);
// Set NPC back to their group
if let Some(alignment) = alignments.get(member) {
match alignment {
Alignment::Npc => {
let _ = groups.insert(member, NPC);
},
Alignment::Enemy => {
let _ = groups.insert(member, ENEMY);
},
_ => {},
}
}
}
pub fn entity_deleted(
&mut self,
member: specs::Entity,
groups: &mut GroupsMut,
alignments: &Alignments,
uids: &Uids,
entities: &specs::Entities,
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
) {
self.remove_from_group(member, groups, alignments, uids, entities, notifier, true);
}
// 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)
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>),
to_be_deleted: bool,
) {
let group = match groups.get(member) {
Some(group) => *group,
@ -266,11 +315,14 @@ impl GroupManager {
(entities, uids, &*groups, alignments.maybe())
.join()
.filter(|(_, _, g, _)| **g == group)
.filter(|(e, _, g, _)| **g == group && (!to_be_deleted || *e == member))
.fold(
HashMap::<Uid, (Option<specs::Entity>, Vec<specs::Entity>)>::new(),
|mut acc, (e, uid, _, alignment)| {
if let Some(Alignment::Owned(owner)) = alignment {
if let Some(owner) = alignment.and_then(|a| match a {
Alignment::Owned(owner) if uid != owner => Some(owner),
_ => None,
}) {
// Assumes owner will be in the group
acc.entry(*owner).or_default().1.push(e);
} else {
@ -309,10 +361,6 @@ impl GroupManager {
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));
}
@ -328,47 +376,45 @@ impl GroupManager {
let leaving = with_pets(member, leaving_member_uid, alignments, entities);
// If pets form new group
if leaving.len() > 1 {
// If pets and not about to be deleted form new group
if leaving.len() > 1 && !to_be_deleted {
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()));
leaving.iter().for_each(|&e| {
let _ = groups.insert(e, new_group).unwrap();
notifier(e, notification.clone());
});
} else {
groups.remove(member);
notifier(member, ChangeNotification::NoGroup)
leaving.iter().for_each(|&e| {
let _ = groups.remove(e);
notifier(e, 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) {
if let Some(info) = self.group_info(group) {
// 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 {
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")
}
} else if num_members == 0 {
error!("Somehow group has no members")
}
}
}
@ -392,7 +438,7 @@ impl GroupManager {
// Point to new leader
members(group, groups, entities).for_each(|e| {
notifier(e, ChangeNotification::NewLeader(e));
notifier(e, ChangeNotification::NewLeader(new_leader));
});
}
}

View File

@ -153,7 +153,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
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(
group_manager.leave_group(
entity,
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
@ -174,6 +174,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
GroupManip::Kick(uid) => {
let mut clients = state.ecs().write_storage::<Client>();
let uids = state.ecs().read_storage::<sync::Uid>();
let alignments = state.ecs().read_storage::<comp::Alignment>();
let target = match state.ecs().entity_from_uid(uid.into()) {
Some(t) => t,
@ -188,6 +189,27 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
return;
},
};
// Can't kick pet
if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner))
{
if let Some(client) = clients.get_mut(entity) {
client.notify(
ChatType::Meta.server_msg("Kick failed, can't kick pet".to_owned()),
);
}
return;
}
// Can't kick yourself
if uids.get(entity).map_or(false, |u| *u == uid) {
if let Some(client) = clients.get_mut(entity) {
client.notify(
ChatType::Meta.server_msg("Kick failed, can't kick yourself".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
@ -197,7 +219,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
{
Some(info) if info.leader == entity => {
// Remove target from group
group_manager.remove_from_group(
group_manager.leave_group(
target,
&mut groups,
&state.ecs().read_storage(),

View File

@ -351,7 +351,7 @@ impl StateExt for State {
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(
group_manager.entity_deleted(
entity,
&mut self.ecs().write_storage(),
&self.ecs().read_storage(),

View File

@ -117,6 +117,8 @@ const UI_MAIN: Color = Color::Rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue
const UI_HIGHLIGHT_0: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0);
//const UI_DARK_0: Color = Color::Rgba(0.25, 0.37, 0.37, 1.0);
/// Distance at which nametags are visible for group members
const NAMETAG_GROUP_RANGE: f32 = 300.0;
/// Distance at which nametags are visible
const NAMETAG_RANGE: f32 = 40.0;
/// Time nametags stay visible after doing damage even if they are out of range
@ -1052,7 +1054,7 @@ impl Hud {
}
// Render overhead name tags and health bars
for (pos, name, stats, energy, height_offset, hpfl, uid) in (
for (pos, name, stats, energy, height_offset, hpfl, uid, in_group) in (
&entities,
&pos,
interpolated.maybe(),
@ -1065,15 +1067,34 @@ impl Hud {
&uids,
)
.join()
.filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead
.map(|(a, b, c, d, e, f, g, h, i, uid)| {
(
a,
b,
c,
d,
e,
f,
g,
h,
i,
uid,
client.group_members.contains(uid),
)
})
.filter(|(entity, pos, _, stats, _, _, _, _, hpfl, uid, in_group)| {
*entity != me && !stats.is_dead
&& (stats.health.current() != stats.health.maximum()
|| info.target_entity.map_or(false, |e| e == *entity)
|| info.selected_entity.map_or(false, |s| s.0 == *entity)
))
// Don't show outside a certain range
.filter(|(_, pos, _, _, _, _, _, _, hpfl, _)| {
pos.0.distance_squared(player_pos)
< (if hpfl
|| *in_group
)
// Don't show outside a certain range
&& pos.0.distance_squared(player_pos)
< (if *in_group
{
NAMETAG_GROUP_RANGE
} else if hpfl
.time_since_last_dmg_by_me
.map_or(false, |t| t < NAMETAG_DMG_TIME)
{
@ -1083,25 +1104,40 @@ impl Hud {
})
.powi(2)
})
.map(|(_, pos, interpolated, stats, energy, player, scale, body, hpfl, uid)| {
// TODO: This is temporary
// If the player used the default character name display their name instead
let name = if stats.name == "Character Name" {
player.map_or(&stats.name, |p| &p.alias)
} else {
&stats.name
};
(
interpolated.map_or(pos.0, |i| i.pos),
name,
.map(
|(
_,
pos,
interpolated,
stats,
energy,
// TODO: when body.height() is more accurate remove the 2.0
body.height() * 2.0 * scale.map_or(1.0, |s| s.0),
player,
scale,
body,
hpfl,
uid,
)
})
in_group,
)| {
// TODO: This is temporary
// If the player used the default character name display their name instead
let name = if stats.name == "Character Name" {
player.map_or(&stats.name, |p| &p.alias)
} else {
&stats.name
};
(
interpolated.map_or(pos.0, |i| i.pos),
name,
stats,
energy,
// TODO: when body.height() is more accurate remove the 2.0
body.height() * 2.0 * scale.map_or(1.0, |s| s.0),
hpfl,
uid,
in_group,
)
},
)
{
let bubble = self.speech_bubbles.get(uid);
@ -1118,6 +1154,7 @@ impl Hud {
stats,
energy,
own_level,
in_group,
&global_state.settings.gameplay,
self.pulse,
&self.voxygen_i18n,

View File

@ -56,6 +56,7 @@ pub struct Overhead<'a> {
stats: &'a Stats,
energy: Option<&'a Energy>,
own_level: u32,
in_group: bool,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
@ -73,6 +74,7 @@ impl<'a> Overhead<'a> {
stats: &'a Stats,
energy: Option<&'a Energy>,
own_level: u32,
in_group: bool,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
@ -85,6 +87,7 @@ impl<'a> Overhead<'a> {
stats,
energy,
own_level,
in_group,
settings,
pulse,
voxygen_i18n,
@ -145,7 +148,11 @@ impl<'a> Widget for Overhead<'a> {
Text::new(&self.name)
.font_id(self.fonts.cyri.conrod_id)
.font_size(30)
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
.color(if self.in_group {
Color::Rgba(1.0, 0.5, 0.6, 1.0)
} else {
Color::Rgba(0.61, 0.61, 0.89, 1.0)
})
.x_y(0.0, MANA_BAR_Y + 50.0)
.set(state.ids.name, ui);

View File

@ -375,6 +375,9 @@ impl<'a> Widget for Social<'a> {
{
if let Some(uid) = selected {
events.push(Event::Invite(uid));
state.update(|s| {
s.selected_uid = None;
});
}
}
}
@ -452,6 +455,9 @@ impl<'a> Widget for Social<'a> {
{
if let Some(uid) = selected {
events.push(Event::Kick(uid));
state.update(|s| {
s.selected_member = None;
});
}
}
// Assign leader
@ -474,6 +480,9 @@ impl<'a> Widget for Social<'a> {
{
if let Some(uid) = selected {
events.push(Event::AssignLeader(uid));
state.update(|s| {
s.selected_member = None;
});
}
}
}