eq2go/internal/groups/manager.go

510 lines
12 KiB
Go

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
}