package groups import ( "fmt" "sync" "time" ) // Manager provides group management with embedded database operations type Manager struct { // Core fields with embedded database operations MasterList *MasterList `json:"master_list"` Config GroupManagerConfig `json:"config"` Stats GroupManagerStats `json:"stats"` // Group ID generation nextGroupID int32 `json:"-" db:"next_group_id"` nextGroupIDMutex sync.Mutex `json:"-"` // Pending invitations PendingInvites map[string]*GroupInvite `json:"pending_invites"` RaidPendingInvites map[string]*GroupInvite `json:"raid_pending_invites"` invitesMutex sync.RWMutex `json:"-"` // Event handlers EventHandlers []GroupEventHandler `json:"-"` eventHandlersMutex sync.RWMutex `json:"-"` // Statistics statsMutex sync.RWMutex `json:"-"` // Background processing stopChan chan struct{} `json:"-"` wg sync.WaitGroup `json:"-"` // Integration interfaces database GroupDatabase `json:"-"` packetHandler GroupPacketHandler `json:"-"` validator GroupValidator `json:"-"` notifier GroupNotifier `json:"-"` // Database integration - embedded operations db any `json:"-"` // Database connection isNew bool `json:"-"` // Flag for new managers } // New creates a new group manager func NewManager(config GroupManagerConfig, db any) *Manager { manager := &Manager{ MasterList: NewMasterList(), Config: config, Stats: GroupManagerStats{}, nextGroupID: 1, PendingInvites: make(map[string]*GroupInvite), RaidPendingInvites: make(map[string]*GroupInvite), EventHandlers: make([]GroupEventHandler, 0), stopChan: make(chan struct{}), db: db, isNew: true, } return manager } // Save saves the manager state to the database func (m *Manager) Save() error { // TODO: Implement database save logic return nil } // Delete removes the manager from the database func (m *Manager) Delete() error { // Stop the manager first m.Stop() // TODO: Implement database delete logic return nil } // Reload refreshes the manager from the database func (m *Manager) Reload() error { // TODO: Implement database reload logic return nil } // Start starts the group manager background processes func (m *Manager) Start() error { // Start background processes if m.Config.UpdateInterval > 0 { m.wg.Add(1) go m.updateGroupsLoop() } if m.Config.BuffUpdateInterval > 0 { m.wg.Add(1) go m.updateBuffsLoop() } m.wg.Add(1) go m.cleanupExpiredInvitesLoop() if m.Config.EnableStatistics { m.wg.Add(1) go m.updateStatsLoop() } return nil } // Stop stops the group manager and all background processes func (m *Manager) Stop() error { close(m.stopChan) m.wg.Wait() return nil } // NewGroup creates a new group with the given leader and options func (m *Manager) NewGroup(leader 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 = m.generateNextGroupID() } // Check if group ID already exists if m.MasterList.GetGroup(groupID) != nil && overrideGroupID == 0 { return 0, fmt.Errorf("group ID %d already exists", groupID) } // Create new group group := NewGroup(groupID, options, m.db) // 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 if !m.MasterList.AddGroup(group) { group.Disband() return 0, fmt.Errorf("failed to add group to master list") } // Update statistics m.updateStatsForNewGroup() // Fire event m.fireGroupCreatedEvent(group, leader) return groupID, nil } // RemoveGroup removes a group from the manager func (m *Manager) RemoveGroup(groupID int32) error { group := m.MasterList.GetGroup(groupID) if group == nil { return fmt.Errorf("group %d not found", groupID) } // Disband the group group.Disband() // Remove from master list if !m.MasterList.RemoveGroup(groupID) { return fmt.Errorf("failed to remove group %d from master list", groupID) } // Update statistics m.updateStatsForRemovedGroup() // Fire event m.fireGroupDisbandedEvent(group) return nil } // GetGroup returns a group by ID func (m *Manager) GetGroup(groupID int32) *Group { return m.MasterList.GetGroup(groupID) } // IsGroupIDValid checks if a group ID is valid and exists func (m *Manager) IsGroupIDValid(groupID int32) bool { return m.MasterList.GetGroup(groupID) != nil } // AddGroupMember adds a member to an existing group func (m *Manager) AddGroupMember(groupID int32, member Entity, isLeader bool) error { group := m.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 (m *Manager) AddGroupMemberFromPeer(groupID int32, info *GroupMemberInfo) error { group := m.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 (m *Manager) RemoveGroupMember(groupID int32, member Entity) error { group := m.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 { m.RemoveGroup(groupID) } return nil } // RemoveGroupMemberByName removes a member by name from a group func (m *Manager) RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error { group := m.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 { m.RemoveGroup(groupID) } return nil } // SendGroupUpdate sends an update to all members of a group func (m *Manager) SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool) { group := m.GetGroup(groupID) if group != nil { group.SendGroupUpdate(excludeClient, forceRaidUpdate) } } // Invite handles inviting a player to a group func (m *Manager) Invite(leader Entity, member 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 m.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 !m.addInvite(leader, member) { return GROUP_INVITE_PERMISSION_DENIED } // Fire event m.fireGroupInviteSentEvent(leader, member) return GROUP_INVITE_SUCCESS } // AddInvite adds a group invitation func (m *Manager) AddInvite(leader Entity, member Entity) bool { return m.addInvite(leader, member) } // addInvite internal method to add an invitation func (m *Manager) addInvite(leader Entity, member 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(m.Config.InviteTimeout), } m.invitesMutex.Lock() m.PendingInvites[inviteKey] = invite m.invitesMutex.Unlock() // Update statistics m.updateStatsForInvite() return true } // AcceptInvite handles accepting of a group invite func (m *Manager) AcceptInvite(member Entity, groupOverrideID *int32, autoAddGroup bool) int8 { if member == nil { return GROUP_INVITE_TARGET_NOT_FOUND } inviteKey := member.GetName() m.invitesMutex.Lock() invite, exists := m.PendingInvites[inviteKey] if !exists { m.invitesMutex.Unlock() return GROUP_INVITE_TARGET_NOT_FOUND } // Check if invite has expired if invite.IsExpired() { delete(m.PendingInvites, inviteKey) m.invitesMutex.Unlock() m.updateStatsForExpiredInvite() return GROUP_INVITE_DECLINED } // Remove the invite delete(m.PendingInvites, inviteKey) m.invitesMutex.Unlock() if !autoAddGroup { return GROUP_INVITE_SUCCESS } // Find the leader var leader 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 = m.NewGroup(leader, nil, groupID) } else { groupID, err = m.NewGroup(leader, nil, 0) } if err != nil { return GROUP_INVITE_PERMISSION_DENIED } } else { groupID = leaderGroupID } // Add member to the group if err := m.AddGroupMember(groupID, member, false); err != nil { return GROUP_INVITE_GROUP_FULL } // Update statistics m.updateStatsForAcceptedInvite() // Fire event m.fireGroupInviteAcceptedEvent(leader, member, groupID) return GROUP_INVITE_SUCCESS } // DeclineInvite handles declining of a group invite func (m *Manager) DeclineInvite(member Entity) { if member == nil { return } inviteKey := member.GetName() m.invitesMutex.Lock() _, exists := m.PendingInvites[inviteKey] if exists { delete(m.PendingInvites, inviteKey) } m.invitesMutex.Unlock() if exists { // Update statistics m.updateStatsForDeclinedInvite() // Fire event var leader Entity // TODO: Find leader entity by name // leader = world.GetPlayerByName(invite.InviterName) m.fireGroupInviteDeclinedEvent(leader, member) } } // ClearPendingInvite clears a pending invite for a member func (m *Manager) ClearPendingInvite(member Entity) { if member == nil { return } inviteKey := member.GetName() m.invitesMutex.Lock() delete(m.PendingInvites, inviteKey) m.invitesMutex.Unlock() } // HasPendingInvite checks if a member has a pending invite and returns the inviter name func (m *Manager) HasPendingInvite(member Entity) string { if member == nil { return "" } inviteKey := member.GetName() return m.hasPendingInvite(inviteKey) } // hasPendingInvite internal method to check for pending invites func (m *Manager) hasPendingInvite(inviteKey string) string { m.invitesMutex.RLock() defer m.invitesMutex.RUnlock() if invite, exists := m.PendingInvites[inviteKey]; exists { if !invite.IsExpired() { return invite.InviterName } } return "" } // generateNextGroupID generates the next available group ID func (m *Manager) generateNextGroupID() int32 { m.nextGroupIDMutex.Lock() defer m.nextGroupIDMutex.Unlock() id := m.nextGroupID m.nextGroupID++ // Handle overflow if m.nextGroupID <= 0 { m.nextGroupID = 1 } return id } // GetGroupCount returns the number of active groups func (m *Manager) GetGroupCount() int32 { return int32(m.MasterList.Size()) } // GetAllGroups returns all active groups func (m *Manager) GetAllGroups() []*Group { return m.MasterList.GetAllGroups() } // GetStats returns current manager statistics func (m *Manager) GetStats() GroupManagerStats { m.statsMutex.RLock() defer m.statsMutex.RUnlock() return m.Stats }