510 lines
12 KiB
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
|
|
} |