985 lines
23 KiB
Go
985 lines
23 KiB
Go
package groups
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"eq2emu/internal/entity"
|
|
)
|
|
|
|
// NewGroupManager creates a new group manager with the given configuration
|
|
func NewGroupManager(config GroupManagerConfig) *GroupManager {
|
|
manager := &GroupManager{
|
|
groups: make(map[int32]*Group),
|
|
nextGroupID: 1,
|
|
pendingInvites: make(map[string]*GroupInvite),
|
|
raidPendingInvites: make(map[string]*GroupInvite),
|
|
eventHandlers: make([]GroupEventHandler, 0),
|
|
config: config,
|
|
stopChan: make(chan struct{}),
|
|
}
|
|
|
|
return manager
|
|
}
|
|
|
|
// Start starts the group manager background processes
|
|
func (gm *GroupManager) Start() error {
|
|
// Start background processes
|
|
if gm.config.UpdateInterval > 0 {
|
|
gm.wg.Add(1)
|
|
go gm.updateGroupsLoop()
|
|
}
|
|
|
|
if gm.config.BuffUpdateInterval > 0 {
|
|
gm.wg.Add(1)
|
|
go gm.updateBuffsLoop()
|
|
}
|
|
|
|
gm.wg.Add(1)
|
|
go gm.cleanupExpiredInvitesLoop()
|
|
|
|
if gm.config.EnableStatistics {
|
|
gm.wg.Add(1)
|
|
go gm.updateStatsLoop()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the group manager and all background processes
|
|
func (gm *GroupManager) Stop() error {
|
|
close(gm.stopChan)
|
|
gm.wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
// NewGroup creates a new group with the given leader and options
|
|
func (gm *GroupManager) NewGroup(leader entity.Entity, options *GroupOptions, overrideGroupID int32) (int32, error) {
|
|
if leader == nil {
|
|
return 0, fmt.Errorf("leader cannot be nil")
|
|
}
|
|
|
|
var groupID int32
|
|
if overrideGroupID > 0 {
|
|
groupID = overrideGroupID
|
|
} else {
|
|
groupID = gm.generateNextGroupID()
|
|
}
|
|
|
|
// Check if group ID already exists
|
|
gm.groupsMutex.RLock()
|
|
if _, exists := gm.groups[groupID]; exists && overrideGroupID == 0 {
|
|
gm.groupsMutex.RUnlock()
|
|
return 0, fmt.Errorf("group ID %d already exists", groupID)
|
|
}
|
|
gm.groupsMutex.RUnlock()
|
|
|
|
// Create new group
|
|
group := NewGroup(groupID, options)
|
|
|
|
// Add leader to the group
|
|
if err := group.AddMember(leader, true); err != nil {
|
|
group.Disband()
|
|
return 0, fmt.Errorf("failed to add leader to group: %v", err)
|
|
}
|
|
|
|
// Add group to manager
|
|
gm.groupsMutex.Lock()
|
|
gm.groups[groupID] = group
|
|
gm.groupsMutex.Unlock()
|
|
|
|
// Update statistics
|
|
gm.updateStatsForNewGroup()
|
|
|
|
// Fire event
|
|
gm.fireGroupCreatedEvent(group, leader)
|
|
|
|
return groupID, nil
|
|
}
|
|
|
|
// RemoveGroup removes a group from the manager
|
|
func (gm *GroupManager) RemoveGroup(groupID int32) error {
|
|
gm.groupsMutex.Lock()
|
|
group, exists := gm.groups[groupID]
|
|
if !exists {
|
|
gm.groupsMutex.Unlock()
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
delete(gm.groups, groupID)
|
|
gm.groupsMutex.Unlock()
|
|
|
|
// Disband the group
|
|
group.Disband()
|
|
|
|
// Update statistics
|
|
gm.updateStatsForRemovedGroup()
|
|
|
|
// Fire event
|
|
gm.fireGroupDisbandedEvent(group)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetGroup returns a group by ID
|
|
func (gm *GroupManager) GetGroup(groupID int32) *Group {
|
|
gm.groupsMutex.RLock()
|
|
defer gm.groupsMutex.RUnlock()
|
|
|
|
return gm.groups[groupID]
|
|
}
|
|
|
|
// IsGroupIDValid checks if a group ID is valid and exists
|
|
func (gm *GroupManager) IsGroupIDValid(groupID int32) bool {
|
|
gm.groupsMutex.RLock()
|
|
defer gm.groupsMutex.RUnlock()
|
|
|
|
_, exists := gm.groups[groupID]
|
|
return exists
|
|
}
|
|
|
|
// AddGroupMember adds a member to an existing group
|
|
func (gm *GroupManager) AddGroupMember(groupID int32, member entity.Entity, isLeader bool) error {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
|
|
return group.AddMember(member, isLeader)
|
|
}
|
|
|
|
// AddGroupMemberFromPeer adds a member from a peer server to an existing group
|
|
func (gm *GroupManager) AddGroupMemberFromPeer(groupID int32, info *GroupMemberInfo) error {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
|
|
return group.AddMemberFromPeer(
|
|
info.Name, info.Leader, info.IsClient, info.ClassID,
|
|
info.HPCurrent, info.HPMax, info.LevelCurrent, info.LevelMax,
|
|
info.PowerCurrent, info.PowerMax, info.RaceID, info.Zone,
|
|
info.MentorTargetCharID, info.ZoneID, info.InstanceID,
|
|
info.ClientPeerAddress, info.ClientPeerPort, info.IsRaidLooter,
|
|
)
|
|
}
|
|
|
|
// RemoveGroupMember removes a member from a group
|
|
func (gm *GroupManager) RemoveGroupMember(groupID int32, member entity.Entity) error {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
|
|
err := group.RemoveMember(member)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If group is now empty, remove it
|
|
if group.GetSize() == 0 {
|
|
gm.RemoveGroup(groupID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveGroupMemberByName removes a member by name from a group
|
|
func (gm *GroupManager) RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
|
|
err := group.RemoveMemberByName(name, isClient, charID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If group is now empty, remove it
|
|
if group.GetSize() == 0 {
|
|
gm.RemoveGroup(groupID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SendGroupUpdate sends an update to all members of a group
|
|
func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.SendGroupUpdate(excludeClient, forceRaidUpdate)
|
|
}
|
|
}
|
|
|
|
// Group invitation handling
|
|
|
|
// Invite handles inviting a player to a group
|
|
func (gm *GroupManager) Invite(leader entity.Entity, member entity.Entity) int8 {
|
|
if leader == nil || member == nil {
|
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
|
}
|
|
|
|
// Check if inviting self
|
|
if leader == member {
|
|
return GROUP_INVITE_SELF_INVITE
|
|
}
|
|
|
|
// Check if member already has an invite
|
|
inviteKey := member.GetName()
|
|
if gm.hasPendingInvite(inviteKey) {
|
|
return GROUP_INVITE_ALREADY_HAS_INVITE
|
|
}
|
|
|
|
// Check if member is already in a group
|
|
// TODO: Check if member already in group
|
|
// if member.GetGroupMemberInfo() != nil {
|
|
// return GROUP_INVITE_ALREADY_IN_GROUP
|
|
// }
|
|
|
|
// Add the invite
|
|
if !gm.addInvite(leader, member) {
|
|
return GROUP_INVITE_PERMISSION_DENIED
|
|
}
|
|
|
|
// Fire event
|
|
gm.fireGroupInviteSentEvent(leader, member)
|
|
|
|
return GROUP_INVITE_SUCCESS
|
|
}
|
|
|
|
// AddInvite adds a group invitation
|
|
func (gm *GroupManager) AddInvite(leader entity.Entity, member entity.Entity) bool {
|
|
return gm.addInvite(leader, member)
|
|
}
|
|
|
|
// addInvite internal method to add an invitation
|
|
func (gm *GroupManager) addInvite(leader entity.Entity, member entity.Entity) bool {
|
|
if leader == nil || member == nil {
|
|
return false
|
|
}
|
|
|
|
inviteKey := member.GetName()
|
|
leaderName := leader.GetName()
|
|
|
|
invite := &GroupInvite{
|
|
InviterName: leaderName,
|
|
InviteeName: inviteKey,
|
|
GroupID: 0, // Will be set when group is created
|
|
IsRaidInvite: false,
|
|
CreatedTime: time.Now(),
|
|
ExpiresTime: time.Now().Add(gm.config.InviteTimeout),
|
|
}
|
|
|
|
gm.invitesMutex.Lock()
|
|
gm.pendingInvites[inviteKey] = invite
|
|
gm.invitesMutex.Unlock()
|
|
|
|
// Update statistics
|
|
gm.updateStatsForInvite()
|
|
|
|
return true
|
|
}
|
|
|
|
// AcceptInvite handles accepting of a group invite
|
|
func (gm *GroupManager) AcceptInvite(member entity.Entity, groupOverrideID *int32, autoAddGroup bool) int8 {
|
|
if member == nil {
|
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
|
}
|
|
|
|
inviteKey := member.GetName()
|
|
|
|
gm.invitesMutex.Lock()
|
|
invite, exists := gm.pendingInvites[inviteKey]
|
|
if !exists {
|
|
gm.invitesMutex.Unlock()
|
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
|
}
|
|
|
|
// Check if invite has expired
|
|
if invite.IsExpired() {
|
|
delete(gm.pendingInvites, inviteKey)
|
|
gm.invitesMutex.Unlock()
|
|
gm.updateStatsForExpiredInvite()
|
|
return GROUP_INVITE_DECLINED
|
|
}
|
|
|
|
// Remove the invite
|
|
delete(gm.pendingInvites, inviteKey)
|
|
gm.invitesMutex.Unlock()
|
|
|
|
if !autoAddGroup {
|
|
return GROUP_INVITE_SUCCESS
|
|
}
|
|
|
|
// Find the leader
|
|
var leader entity.Entity
|
|
// TODO: Find leader entity by name
|
|
// leader = world.GetPlayerByName(invite.InviterName)
|
|
|
|
if leader == nil {
|
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
|
}
|
|
|
|
var groupID int32
|
|
if groupOverrideID != nil {
|
|
groupID = *groupOverrideID
|
|
}
|
|
|
|
// Check if leader already has a group
|
|
// TODO: Get leader's group ID
|
|
// leaderGroupID := leader.GetGroupID()
|
|
|
|
leaderGroupID := int32(0) // Placeholder
|
|
|
|
if leaderGroupID == 0 {
|
|
// Create new group with leader
|
|
var err error
|
|
if groupID != 0 {
|
|
groupID, err = gm.NewGroup(leader, nil, groupID)
|
|
} else {
|
|
groupID, err = gm.NewGroup(leader, nil, 0)
|
|
}
|
|
if err != nil {
|
|
return GROUP_INVITE_PERMISSION_DENIED
|
|
}
|
|
} else {
|
|
groupID = leaderGroupID
|
|
}
|
|
|
|
// Add member to the group
|
|
if err := gm.AddGroupMember(groupID, member, false); err != nil {
|
|
return GROUP_INVITE_GROUP_FULL
|
|
}
|
|
|
|
// Update statistics
|
|
gm.updateStatsForAcceptedInvite()
|
|
|
|
// Fire event
|
|
gm.fireGroupInviteAcceptedEvent(leader, member, groupID)
|
|
|
|
return GROUP_INVITE_SUCCESS
|
|
}
|
|
|
|
// DeclineInvite handles declining of a group invite
|
|
func (gm *GroupManager) DeclineInvite(member entity.Entity) {
|
|
if member == nil {
|
|
return
|
|
}
|
|
|
|
inviteKey := member.GetName()
|
|
|
|
gm.invitesMutex.Lock()
|
|
invite, exists := gm.pendingInvites[inviteKey]
|
|
if exists {
|
|
delete(gm.pendingInvites, inviteKey)
|
|
}
|
|
gm.invitesMutex.Unlock()
|
|
|
|
if exists {
|
|
// Update statistics
|
|
gm.updateStatsForDeclinedInvite()
|
|
|
|
// Fire event
|
|
var leader entity.Entity
|
|
// TODO: Find leader entity by name
|
|
// leader = world.GetPlayerByName(invite.InviterName)
|
|
gm.fireGroupInviteDeclinedEvent(leader, member)
|
|
}
|
|
}
|
|
|
|
// ClearPendingInvite clears a pending invite for a member
|
|
func (gm *GroupManager) ClearPendingInvite(member entity.Entity) {
|
|
if member == nil {
|
|
return
|
|
}
|
|
|
|
inviteKey := member.GetName()
|
|
|
|
gm.invitesMutex.Lock()
|
|
delete(gm.pendingInvites, inviteKey)
|
|
gm.invitesMutex.Unlock()
|
|
}
|
|
|
|
// HasPendingInvite checks if a member has a pending invite and returns the inviter name
|
|
func (gm *GroupManager) HasPendingInvite(member entity.Entity) string {
|
|
if member == nil {
|
|
return ""
|
|
}
|
|
|
|
inviteKey := member.GetName()
|
|
return gm.hasPendingInvite(inviteKey)
|
|
}
|
|
|
|
// hasPendingInvite internal method to check for pending invites
|
|
func (gm *GroupManager) hasPendingInvite(inviteKey string) string {
|
|
gm.invitesMutex.RLock()
|
|
defer gm.invitesMutex.RUnlock()
|
|
|
|
if invite, exists := gm.pendingInvites[inviteKey]; exists {
|
|
if !invite.IsExpired() {
|
|
return invite.InviterName
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// Group utility methods
|
|
|
|
// GetGroupSize returns the size of a group
|
|
func (gm *GroupManager) GetGroupSize(groupID int32) int32 {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return 0
|
|
}
|
|
return group.GetSize()
|
|
}
|
|
|
|
// IsInGroup checks if an entity is in a specific group
|
|
func (gm *GroupManager) IsInGroup(groupID int32, member entity.Entity) bool {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil || member == nil {
|
|
return false
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
for _, gmi := range members {
|
|
if gmi.Member == member {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsPlayerInGroup checks if a player with the given character ID is in a group
|
|
func (gm *GroupManager) IsPlayerInGroup(groupID int32, charID int32) entity.Entity {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return nil
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
for _, gmi := range members {
|
|
if gmi.IsClient && gmi.Member != nil {
|
|
// TODO: Check character ID
|
|
// if gmi.Member.GetCharacterID() == charID {
|
|
// return gmi.Member
|
|
// }
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsSpawnInGroup checks if a spawn with the given name is in a group
|
|
func (gm *GroupManager) IsSpawnInGroup(groupID int32, name string) bool {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return false
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
for _, gmi := range members {
|
|
if gmi.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetGroupLeader returns the leader of a group
|
|
func (gm *GroupManager) GetGroupLeader(groupID int32) entity.Entity {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return nil
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
for _, gmi := range members {
|
|
if gmi.Leader {
|
|
return gmi.Member
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MakeLeader changes the leader of a group
|
|
func (gm *GroupManager) MakeLeader(groupID int32, newLeader entity.Entity) bool {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return false
|
|
}
|
|
|
|
err := group.MakeLeader(newLeader)
|
|
return err == nil
|
|
}
|
|
|
|
// Group messaging
|
|
|
|
// SimpleGroupMessage sends a simple message to all members of a group
|
|
func (gm *GroupManager) SimpleGroupMessage(groupID int32, message string) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.SimpleGroupMessage(message)
|
|
}
|
|
}
|
|
|
|
// SendGroupMessage sends a formatted message to all members of a group
|
|
func (gm *GroupManager) SendGroupMessage(groupID int32, msgType int8, message string) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.SendGroupMessage(msgType, message)
|
|
}
|
|
}
|
|
|
|
// GroupMessage sends a message to all members of a group (alias for SimpleGroupMessage)
|
|
func (gm *GroupManager) GroupMessage(groupID int32, message string) {
|
|
gm.SimpleGroupMessage(groupID, message)
|
|
}
|
|
|
|
// GroupChatMessage sends a chat message from a member to the group
|
|
func (gm *GroupManager) GroupChatMessage(groupID int32, from entity.Entity, language int32, message string, channel int16) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.GroupChatMessage(from, language, message, channel)
|
|
}
|
|
}
|
|
|
|
// GroupChatMessageFromName sends a chat message from a named sender to the group
|
|
func (gm *GroupManager) GroupChatMessageFromName(groupID int32, fromName string, language int32, message string, channel int16) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.GroupChatMessageFromName(fromName, language, message, channel)
|
|
}
|
|
}
|
|
|
|
// SendGroupChatMessage sends a formatted chat message to the group
|
|
func (gm *GroupManager) SendGroupChatMessage(groupID int32, channel int16, message string) {
|
|
gm.GroupChatMessageFromName(groupID, "System", 0, message, channel)
|
|
}
|
|
|
|
// Raid functionality
|
|
|
|
// ClearGroupRaid clears raid associations for a group
|
|
func (gm *GroupManager) ClearGroupRaid(groupID int32) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.ClearGroupRaid()
|
|
}
|
|
}
|
|
|
|
// RemoveGroupFromRaid removes a group from a raid
|
|
func (gm *GroupManager) RemoveGroupFromRaid(groupID, targetGroupID int32) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.RemoveGroupFromRaid(targetGroupID)
|
|
}
|
|
}
|
|
|
|
// IsInRaidGroup checks if two groups are in the same raid
|
|
func (gm *GroupManager) IsInRaidGroup(groupID, targetGroupID int32, isLeaderGroup bool) bool {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return false
|
|
}
|
|
return group.IsInRaidGroup(targetGroupID, isLeaderGroup)
|
|
}
|
|
|
|
// GetRaidGroups returns the raid groups for a specific group
|
|
func (gm *GroupManager) GetRaidGroups(groupID int32) []int32 {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return []int32{}
|
|
}
|
|
return group.GetRaidGroups()
|
|
}
|
|
|
|
// ReplaceRaidGroups replaces the raid groups for a specific group
|
|
func (gm *GroupManager) ReplaceRaidGroups(groupID int32, newGroups []int32) {
|
|
group := gm.GetGroup(groupID)
|
|
if group != nil {
|
|
group.ReplaceRaidGroups(newGroups)
|
|
}
|
|
}
|
|
|
|
// Group options
|
|
|
|
// GetDefaultGroupOptions returns the default group options for a group
|
|
func (gm *GroupManager) GetDefaultGroupOptions(groupID int32) (GroupOptions, bool) {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return GroupOptions{}, false
|
|
}
|
|
return group.GetGroupOptions(), true
|
|
}
|
|
|
|
// SetGroupOptions sets group options for a specific group
|
|
func (gm *GroupManager) SetGroupOptions(groupID int32, options *GroupOptions) error {
|
|
group := gm.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group %d not found", groupID)
|
|
}
|
|
return group.SetGroupOptions(options)
|
|
}
|
|
|
|
// Utility methods
|
|
|
|
// generateNextGroupID generates the next available group ID
|
|
func (gm *GroupManager) generateNextGroupID() int32 {
|
|
gm.nextGroupIDMutex.Lock()
|
|
defer gm.nextGroupIDMutex.Unlock()
|
|
|
|
id := gm.nextGroupID
|
|
gm.nextGroupID++
|
|
|
|
// Handle overflow
|
|
if gm.nextGroupID <= 0 {
|
|
gm.nextGroupID = 1
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
// GetGroupCount returns the number of active groups
|
|
func (gm *GroupManager) GetGroupCount() int32 {
|
|
gm.groupsMutex.RLock()
|
|
defer gm.groupsMutex.RUnlock()
|
|
|
|
return int32(len(gm.groups))
|
|
}
|
|
|
|
// GetAllGroups returns all active groups
|
|
func (gm *GroupManager) GetAllGroups() []*Group {
|
|
gm.groupsMutex.RLock()
|
|
defer gm.groupsMutex.RUnlock()
|
|
|
|
groups := make([]*Group, 0, len(gm.groups))
|
|
for _, group := range gm.groups {
|
|
groups = append(groups, group)
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// Background processing loops
|
|
|
|
// updateGroupsLoop periodically updates all groups
|
|
func (gm *GroupManager) updateGroupsLoop() {
|
|
defer gm.wg.Done()
|
|
|
|
ticker := time.NewTicker(gm.config.UpdateInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
gm.processGroupUpdates()
|
|
case <-gm.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateBuffsLoop periodically updates group buffs
|
|
func (gm *GroupManager) updateBuffsLoop() {
|
|
defer gm.wg.Done()
|
|
|
|
ticker := time.NewTicker(gm.config.BuffUpdateInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
gm.updateGroupBuffs()
|
|
case <-gm.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// cleanupExpiredInvitesLoop periodically cleans up expired invites
|
|
func (gm *GroupManager) cleanupExpiredInvitesLoop() {
|
|
defer gm.wg.Done()
|
|
|
|
ticker := time.NewTicker(30 * time.Second) // Check every 30 seconds
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
gm.cleanupExpiredInvites()
|
|
case <-gm.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateStatsLoop periodically updates statistics
|
|
func (gm *GroupManager) updateStatsLoop() {
|
|
defer gm.wg.Done()
|
|
|
|
ticker := time.NewTicker(1 * time.Minute) // Update stats every minute
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
gm.updateStatistics()
|
|
case <-gm.stopChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// processGroupUpdates processes periodic group updates
|
|
func (gm *GroupManager) processGroupUpdates() {
|
|
groups := gm.GetAllGroups()
|
|
|
|
for _, group := range groups {
|
|
if !group.IsDisbanded() {
|
|
// Update member information
|
|
members := group.GetMembers()
|
|
for _, gmi := range members {
|
|
if gmi.Member != nil {
|
|
group.UpdateGroupMemberInfo(gmi.Member, false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateGroupBuffs updates group buffs for all groups
|
|
func (gm *GroupManager) updateGroupBuffs() {
|
|
// TODO: Implement group buff updates
|
|
// This would require integration with the spell/buff system
|
|
}
|
|
|
|
// cleanupExpiredInvites removes expired invitations
|
|
func (gm *GroupManager) cleanupExpiredInvites() {
|
|
gm.invitesMutex.Lock()
|
|
defer gm.invitesMutex.Unlock()
|
|
|
|
now := time.Now()
|
|
expiredCount := 0
|
|
|
|
// Clean up regular invites
|
|
for key, invite := range gm.pendingInvites {
|
|
if now.After(invite.ExpiresTime) {
|
|
delete(gm.pendingInvites, key)
|
|
expiredCount++
|
|
}
|
|
}
|
|
|
|
// Clean up raid invites
|
|
for key, invite := range gm.raidPendingInvites {
|
|
if now.After(invite.ExpiresTime) {
|
|
delete(gm.raidPendingInvites, key)
|
|
expiredCount++
|
|
}
|
|
}
|
|
|
|
// Update statistics
|
|
if expiredCount > 0 {
|
|
gm.statsMutex.Lock()
|
|
gm.stats.ExpiredInvites += int64(expiredCount)
|
|
gm.statsMutex.Unlock()
|
|
}
|
|
}
|
|
|
|
// updateStatistics updates manager statistics
|
|
func (gm *GroupManager) updateStatistics() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.groupsMutex.RLock()
|
|
activeGroups := int64(len(gm.groups))
|
|
|
|
var totalMembers int64
|
|
var raidCount int64
|
|
|
|
for _, group := range gm.groups {
|
|
totalMembers += int64(group.GetSize())
|
|
if group.IsGroupRaid() {
|
|
raidCount++
|
|
}
|
|
}
|
|
gm.groupsMutex.RUnlock()
|
|
|
|
gm.stats.ActiveGroups = activeGroups
|
|
gm.stats.ActiveRaids = raidCount
|
|
|
|
if activeGroups > 0 {
|
|
gm.stats.AverageGroupSize = float64(totalMembers) / float64(activeGroups)
|
|
} else {
|
|
gm.stats.AverageGroupSize = 0
|
|
}
|
|
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
}
|
|
|
|
// Statistics update methods
|
|
|
|
// updateStatsForNewGroup updates statistics when a new group is created
|
|
func (gm *GroupManager) updateStatsForNewGroup() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.stats.TotalGroups++
|
|
}
|
|
|
|
// updateStatsForRemovedGroup updates statistics when a group is removed
|
|
func (gm *GroupManager) updateStatsForRemovedGroup() {
|
|
// Statistics are primarily tracked in updateStatistics()
|
|
}
|
|
|
|
// updateStatsForInvite updates statistics when an invite is sent
|
|
func (gm *GroupManager) updateStatsForInvite() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.stats.TotalInvites++
|
|
}
|
|
|
|
// updateStatsForAcceptedInvite updates statistics when an invite is accepted
|
|
func (gm *GroupManager) updateStatsForAcceptedInvite() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.stats.AcceptedInvites++
|
|
}
|
|
|
|
// updateStatsForDeclinedInvite updates statistics when an invite is declined
|
|
func (gm *GroupManager) updateStatsForDeclinedInvite() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.stats.DeclinedInvites++
|
|
}
|
|
|
|
// updateStatsForExpiredInvite updates statistics when an invite expires
|
|
func (gm *GroupManager) updateStatsForExpiredInvite() {
|
|
if !gm.config.EnableStatistics {
|
|
return
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
defer gm.statsMutex.Unlock()
|
|
|
|
gm.stats.ExpiredInvites++
|
|
}
|
|
|
|
// GetStats returns current manager statistics
|
|
func (gm *GroupManager) GetStats() GroupManagerStats {
|
|
gm.statsMutex.RLock()
|
|
defer gm.statsMutex.RUnlock()
|
|
|
|
return gm.stats
|
|
}
|
|
|
|
// Event system integration
|
|
|
|
// AddEventHandler adds an event handler
|
|
func (gm *GroupManager) AddEventHandler(handler GroupEventHandler) {
|
|
gm.eventHandlersMutex.Lock()
|
|
defer gm.eventHandlersMutex.Unlock()
|
|
|
|
gm.eventHandlers = append(gm.eventHandlers, handler)
|
|
}
|
|
|
|
// Integration interfaces
|
|
|
|
// SetDatabase sets the database interface
|
|
func (gm *GroupManager) SetDatabase(db GroupDatabase) {
|
|
gm.database = db
|
|
}
|
|
|
|
// SetPacketHandler sets the packet handler interface
|
|
func (gm *GroupManager) SetPacketHandler(handler GroupPacketHandler) {
|
|
gm.packetHandler = handler
|
|
}
|
|
|
|
// SetValidator sets the validator interface
|
|
func (gm *GroupManager) SetValidator(validator GroupValidator) {
|
|
gm.validator = validator
|
|
}
|
|
|
|
// SetNotifier sets the notifier interface
|
|
func (gm *GroupManager) SetNotifier(notifier GroupNotifier) {
|
|
gm.notifier = notifier
|
|
}
|
|
|
|
// Event firing methods
|
|
|
|
// fireGroupCreatedEvent fires a group created event
|
|
func (gm *GroupManager) fireGroupCreatedEvent(group *Group, leader entity.Entity) {
|
|
gm.eventHandlersMutex.RLock()
|
|
defer gm.eventHandlersMutex.RUnlock()
|
|
|
|
for _, handler := range gm.eventHandlers {
|
|
go handler.OnGroupCreated(group, leader)
|
|
}
|
|
}
|
|
|
|
// fireGroupDisbandedEvent fires a group disbanded event
|
|
func (gm *GroupManager) fireGroupDisbandedEvent(group *Group) {
|
|
gm.eventHandlersMutex.RLock()
|
|
defer gm.eventHandlersMutex.RUnlock()
|
|
|
|
for _, handler := range gm.eventHandlers {
|
|
go handler.OnGroupDisbanded(group)
|
|
}
|
|
}
|
|
|
|
// fireGroupInviteSentEvent fires a group invite sent event
|
|
func (gm *GroupManager) fireGroupInviteSentEvent(leader, member entity.Entity) {
|
|
gm.eventHandlersMutex.RLock()
|
|
defer gm.eventHandlersMutex.RUnlock()
|
|
|
|
for _, handler := range gm.eventHandlers {
|
|
go handler.OnGroupInviteSent(leader, member)
|
|
}
|
|
}
|
|
|
|
// fireGroupInviteAcceptedEvent fires a group invite accepted event
|
|
func (gm *GroupManager) fireGroupInviteAcceptedEvent(leader, member entity.Entity, groupID int32) {
|
|
gm.eventHandlersMutex.RLock()
|
|
defer gm.eventHandlersMutex.RUnlock()
|
|
|
|
for _, handler := range gm.eventHandlers {
|
|
go handler.OnGroupInviteAccepted(leader, member, groupID)
|
|
}
|
|
}
|
|
|
|
// fireGroupInviteDeclinedEvent fires a group invite declined event
|
|
func (gm *GroupManager) fireGroupInviteDeclinedEvent(leader, member entity.Entity) {
|
|
gm.eventHandlersMutex.RLock()
|
|
defer gm.eventHandlersMutex.RUnlock()
|
|
|
|
for _, handler := range gm.eventHandlers {
|
|
go handler.OnGroupInviteDeclined(leader, member)
|
|
}
|
|
}
|