simplify groups
This commit is contained in:
parent
88fd7bed4d
commit
575f94f3c1
@ -1,34 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
// Entity represents a game entity that can be part of a group
|
|
||||||
type Entity interface {
|
|
||||||
// Basic entity information
|
|
||||||
GetID() int32
|
|
||||||
GetName() string
|
|
||||||
GetLevel() int8
|
|
||||||
GetClass() int8
|
|
||||||
GetRace() int8
|
|
||||||
|
|
||||||
// Health and power
|
|
||||||
GetHP() int32
|
|
||||||
GetTotalHP() int32
|
|
||||||
GetPower() int32
|
|
||||||
GetTotalPower() int32
|
|
||||||
|
|
||||||
// Entity types
|
|
||||||
IsPlayer() bool
|
|
||||||
IsBot() bool
|
|
||||||
IsNPC() bool
|
|
||||||
IsDead() bool
|
|
||||||
|
|
||||||
// World positioning
|
|
||||||
GetZone() Zone
|
|
||||||
GetDistance(other Entity) float32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zone represents a game zone
|
|
||||||
type Zone interface {
|
|
||||||
GetZoneID() int32
|
|
||||||
GetInstanceID() int32
|
|
||||||
GetZoneName() string
|
|
||||||
}
|
|
@ -1,797 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group represents a player group with embedded database operations
|
|
||||||
type Group struct {
|
|
||||||
// Core fields
|
|
||||||
GroupID int32 `json:"group_id" db:"group_id"`
|
|
||||||
Options GroupOptions `json:"options"`
|
|
||||||
Members []*GroupMemberInfo `json:"members"`
|
|
||||||
RaidGroups []int32 `json:"raid_groups"`
|
|
||||||
CreatedTime time.Time `json:"created_time" db:"created_time"`
|
|
||||||
LastActivity time.Time `json:"last_activity" db:"last_activity"`
|
|
||||||
Disbanded bool `json:"disbanded" db:"disbanded"`
|
|
||||||
|
|
||||||
// Internal fields
|
|
||||||
membersMutex sync.RWMutex `json:"-"`
|
|
||||||
raidGroupsMutex sync.RWMutex `json:"-"`
|
|
||||||
optionsMutex sync.RWMutex `json:"-"`
|
|
||||||
activityMutex sync.RWMutex `json:"-"`
|
|
||||||
disbandMutex sync.RWMutex `json:"-"`
|
|
||||||
|
|
||||||
// Communication channels
|
|
||||||
messageQueue chan *GroupMessage `json:"-"`
|
|
||||||
updateQueue chan *GroupUpdate `json:"-"`
|
|
||||||
|
|
||||||
// Background processing
|
|
||||||
stopChan chan struct{} `json:"-"`
|
|
||||||
wg sync.WaitGroup `json:"-"`
|
|
||||||
|
|
||||||
// Database integration - embedded operations
|
|
||||||
db any `json:"-"` // Database connection
|
|
||||||
isNew bool `json:"-"` // Flag for new groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new group
|
|
||||||
func New(db any) *Group {
|
|
||||||
group := &Group{
|
|
||||||
GroupID: 0, // Will be set when saved
|
|
||||||
Options: DefaultGroupOptions(),
|
|
||||||
Members: make([]*GroupMemberInfo, 0, MAX_GROUP_SIZE),
|
|
||||||
RaidGroups: make([]int32, 0),
|
|
||||||
CreatedTime: time.Now(),
|
|
||||||
LastActivity: time.Now(),
|
|
||||||
Disbanded: false,
|
|
||||||
messageQueue: make(chan *GroupMessage, 100),
|
|
||||||
updateQueue: make(chan *GroupUpdate, 100),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
db: db,
|
|
||||||
isNew: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start background processing
|
|
||||||
group.wg.Add(1)
|
|
||||||
go group.processMessages()
|
|
||||||
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroup creates a new group with specified ID and options
|
|
||||||
func NewGroup(id int32, options *GroupOptions, db any) *Group {
|
|
||||||
if options == nil {
|
|
||||||
defaultOpts := DefaultGroupOptions()
|
|
||||||
options = &defaultOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
group := &Group{
|
|
||||||
GroupID: id,
|
|
||||||
Options: *options,
|
|
||||||
Members: make([]*GroupMemberInfo, 0, MAX_GROUP_SIZE),
|
|
||||||
RaidGroups: make([]int32, 0),
|
|
||||||
CreatedTime: time.Now(),
|
|
||||||
LastActivity: time.Now(),
|
|
||||||
Disbanded: false,
|
|
||||||
messageQueue: make(chan *GroupMessage, 100),
|
|
||||||
updateQueue: make(chan *GroupUpdate, 100),
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
db: db,
|
|
||||||
isNew: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start background processing
|
|
||||||
group.wg.Add(1)
|
|
||||||
go group.processMessages()
|
|
||||||
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID returns the group ID (implements Identifiable interface)
|
|
||||||
func (g *Group) GetID() int32 {
|
|
||||||
return g.GroupID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves the group to the database
|
|
||||||
func (g *Group) Save() error {
|
|
||||||
// TODO: Implement database save logic
|
|
||||||
// This would require integration with the actual database system
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the group from the database
|
|
||||||
func (g *Group) Delete() error {
|
|
||||||
// Disband the group first
|
|
||||||
g.Disband()
|
|
||||||
|
|
||||||
// TODO: Implement database delete logic
|
|
||||||
// This would require integration with the actual database system
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload refreshes the group from the database
|
|
||||||
func (g *Group) Reload() error {
|
|
||||||
// TODO: Implement database reload logic
|
|
||||||
// This would require integration with the actual database system
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the number of members in the group
|
|
||||||
func (g *Group) GetSize() int32 {
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
return int32(len(g.Members))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembers returns a copy of the member list
|
|
||||||
func (g *Group) GetMembers() []*GroupMemberInfo {
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
members := make([]*GroupMemberInfo, len(g.Members))
|
|
||||||
for i, member := range g.Members {
|
|
||||||
members[i] = member.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
return members
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMember adds a new member to the group
|
|
||||||
func (g *Group) AddMember(member Entity, isLeader bool) error {
|
|
||||||
if member == nil {
|
|
||||||
return fmt.Errorf("member cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.disbandMutex.RLock()
|
|
||||||
if g.Disbanded {
|
|
||||||
g.disbandMutex.RUnlock()
|
|
||||||
return fmt.Errorf("group has been disbanded")
|
|
||||||
}
|
|
||||||
g.disbandMutex.RUnlock()
|
|
||||||
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
// Check if group is full
|
|
||||||
if len(g.Members) >= MAX_GROUP_SIZE {
|
|
||||||
return fmt.Errorf("group is full")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if member is already in the group
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Member == member {
|
|
||||||
return fmt.Errorf("member is already in the group")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new group member info
|
|
||||||
gmi := &GroupMemberInfo{
|
|
||||||
GroupID: g.GroupID,
|
|
||||||
Name: member.GetName(),
|
|
||||||
Leader: isLeader,
|
|
||||||
Member: member,
|
|
||||||
IsClient: member.IsPlayer(),
|
|
||||||
JoinTime: time.Now(),
|
|
||||||
LastUpdate: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update member stats from entity
|
|
||||||
gmi.UpdateStats()
|
|
||||||
|
|
||||||
// Set client reference if it's a player
|
|
||||||
if member.IsPlayer() {
|
|
||||||
// TODO: Get client reference from player
|
|
||||||
// gmi.Client = member.GetClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update zone information
|
|
||||||
if zone := member.GetZone(); zone != nil {
|
|
||||||
gmi.ZoneID = zone.GetZoneID()
|
|
||||||
gmi.InstanceID = zone.GetInstanceID()
|
|
||||||
gmi.Zone = zone.GetZoneName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to members list
|
|
||||||
g.Members = append(g.Members, gmi)
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// Set group reference on the entity
|
|
||||||
// TODO: Set group member info on entity
|
|
||||||
// member.SetGroupMemberInfo(gmi)
|
|
||||||
|
|
||||||
// Send group update
|
|
||||||
g.sendGroupUpdate(nil, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMemberFromPeer adds a member from a peer server
|
|
||||||
func (g *Group) AddMemberFromPeer(name string, isLeader, isClient bool, classID int8,
|
|
||||||
hpCur, hpMax int32, levelCur, levelMax int16, powerCur, powerMax int32,
|
|
||||||
raceID int8, zoneName string, mentorTargetCharID int32,
|
|
||||||
zoneID, instanceID int32, peerAddress string, peerPort int16, isRaidLooter bool) error {
|
|
||||||
|
|
||||||
g.disbandMutex.RLock()
|
|
||||||
if g.Disbanded {
|
|
||||||
g.disbandMutex.RUnlock()
|
|
||||||
return fmt.Errorf("group has been disbanded")
|
|
||||||
}
|
|
||||||
g.disbandMutex.RUnlock()
|
|
||||||
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
// Check if group is full
|
|
||||||
if len(g.Members) >= MAX_GROUP_SIZE {
|
|
||||||
return fmt.Errorf("group is full")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new group member info for peer member
|
|
||||||
gmi := &GroupMemberInfo{
|
|
||||||
GroupID: g.GroupID,
|
|
||||||
Name: name,
|
|
||||||
Zone: zoneName,
|
|
||||||
HPCurrent: hpCur,
|
|
||||||
HPMax: hpMax,
|
|
||||||
PowerCurrent: powerCur,
|
|
||||||
PowerMax: powerMax,
|
|
||||||
LevelCurrent: levelCur,
|
|
||||||
LevelMax: levelMax,
|
|
||||||
RaceID: raceID,
|
|
||||||
ClassID: classID,
|
|
||||||
Leader: isLeader,
|
|
||||||
IsClient: isClient,
|
|
||||||
ZoneID: zoneID,
|
|
||||||
InstanceID: instanceID,
|
|
||||||
MentorTargetCharID: mentorTargetCharID,
|
|
||||||
ClientPeerAddress: peerAddress,
|
|
||||||
ClientPeerPort: peerPort,
|
|
||||||
IsRaidLooter: isRaidLooter,
|
|
||||||
Member: nil, // No local entity reference for peer members
|
|
||||||
Client: nil, // No local client reference for peer members
|
|
||||||
JoinTime: time.Now(),
|
|
||||||
LastUpdate: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to members list
|
|
||||||
g.Members = append(g.Members, gmi)
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// Send group update
|
|
||||||
g.sendGroupUpdate(nil, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveMember removes a member from the group
|
|
||||||
func (g *Group) RemoveMember(member Entity) error {
|
|
||||||
if member == nil {
|
|
||||||
return fmt.Errorf("member cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
// Find and remove the member
|
|
||||||
for i, gmi := range g.Members {
|
|
||||||
if gmi.Member == member {
|
|
||||||
// Clear group reference on entity
|
|
||||||
// TODO: Clear group member info on entity
|
|
||||||
// member.SetGroupMemberInfo(nil)
|
|
||||||
|
|
||||||
// Remove from slice
|
|
||||||
g.Members = append(g.Members[:i], g.Members[i+1:]...)
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// If this was a bot, camp it
|
|
||||||
// TODO: Handle bot camping
|
|
||||||
// if member.IsBot() {
|
|
||||||
// member.Camp()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Send group update
|
|
||||||
g.sendGroupUpdate(nil, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("member not found in group")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveMemberByName removes a member by name (for peer members)
|
|
||||||
func (g *Group) RemoveMemberByName(name string, isClient bool, charID int32) error {
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
// Find and remove the member
|
|
||||||
for i, gmi := range g.Members {
|
|
||||||
if gmi.Name == name && gmi.IsClient == isClient {
|
|
||||||
// Handle mentorship cleanup
|
|
||||||
if isClient && charID > 0 {
|
|
||||||
for _, otherGmi := range g.Members {
|
|
||||||
if otherGmi.MentorTargetCharID == charID {
|
|
||||||
otherGmi.MentorTargetCharID = 0
|
|
||||||
// TODO: Enable reset mentorship on client
|
|
||||||
// if otherGmi.Client != nil {
|
|
||||||
// otherGmi.Client.GetPlayer().EnableResetMentorship()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from slice
|
|
||||||
g.Members = append(g.Members[:i], g.Members[i+1:]...)
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// Send group update
|
|
||||||
g.sendGroupUpdate(nil, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("member not found in group")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disband disbands the group and removes all members
|
|
||||||
func (g *Group) Disband() {
|
|
||||||
g.disbandMutex.Lock()
|
|
||||||
if g.Disbanded {
|
|
||||||
g.disbandMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.Disbanded = true
|
|
||||||
g.disbandMutex.Unlock()
|
|
||||||
|
|
||||||
// Stop background processing first to avoid deadlock
|
|
||||||
close(g.stopChan)
|
|
||||||
g.wg.Wait()
|
|
||||||
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
// Clear raid groups
|
|
||||||
g.raidGroupsMutex.Lock()
|
|
||||||
g.RaidGroups = nil
|
|
||||||
g.raidGroupsMutex.Unlock()
|
|
||||||
|
|
||||||
// Remove all members
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Member != nil {
|
|
||||||
// Clear group reference on entity
|
|
||||||
// TODO: Clear group member info on entity
|
|
||||||
// gmi.Member.SetGroupMemberInfo(nil)
|
|
||||||
|
|
||||||
// Handle bot camping
|
|
||||||
// TODO: Handle bot camping
|
|
||||||
// if gmi.Member.IsBot() {
|
|
||||||
// gmi.Member.Camp()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle mentorship cleanup
|
|
||||||
if gmi.MentorTargetCharID > 0 {
|
|
||||||
// TODO: Enable reset mentorship on client
|
|
||||||
// if gmi.Client != nil {
|
|
||||||
// gmi.Client.GetPlayer().EnableResetMentorship()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Set character/raid sheet changed flags
|
|
||||||
// if gmi.Client != nil {
|
|
||||||
// gmi.Client.GetPlayer().SetCharSheetChanged(true)
|
|
||||||
// if isInRaid {
|
|
||||||
// gmi.Client.GetPlayer().SetRaidSheetChanged(true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear members list
|
|
||||||
g.Members = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupUpdate sends an update to all group members
|
|
||||||
func (g *Group) SendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
|
|
||||||
g.sendGroupUpdate(excludeClient, forceRaidUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendGroupUpdate internal method to send group updates
|
|
||||||
func (g *Group) sendGroupUpdate(excludeClient any, forceRaidUpdate bool) {
|
|
||||||
update := NewGroupUpdate(GROUP_UPDATE_FLAG_MEMBER_LIST, g.GroupID)
|
|
||||||
update.ExcludeClient = excludeClient
|
|
||||||
update.ForceRaidUpdate = forceRaidUpdate
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.updateQueue <- update:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the update
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleGroupMessage sends a simple message to all group members
|
|
||||||
func (g *Group) SimpleGroupMessage(message string) {
|
|
||||||
msg := NewGroupMessage(GROUP_MESSAGE_TYPE_SYSTEM, CHANNEL_GROUP_CHAT, message, "", 0)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.messageQueue <- msg:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessage sends a formatted message to all group members
|
|
||||||
func (g *Group) SendGroupMessage(msgType int8, message string) {
|
|
||||||
msg := NewGroupMessage(msgType, CHANNEL_GROUP_CHAT, message, "", 0)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.messageQueue <- msg:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupChatMessage sends a chat message from a member to the group
|
|
||||||
func (g *Group) GroupChatMessage(from Entity, language int32, message string, channel int16) {
|
|
||||||
if from == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := NewGroupMessage(GROUP_MESSAGE_TYPE_CHAT, channel, message, from.GetName(), language)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.messageQueue <- msg:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupChatMessageFromName sends a chat message from a named sender to the group
|
|
||||||
func (g *Group) GroupChatMessageFromName(fromName string, language int32, message string, channel int16) {
|
|
||||||
msg := NewGroupMessage(GROUP_MESSAGE_TYPE_CHAT, channel, message, fromName, language)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.messageQueue <- msg:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeLeader changes the group leader
|
|
||||||
func (g *Group) MakeLeader(newLeader Entity) error {
|
|
||||||
if newLeader == nil {
|
|
||||||
return fmt.Errorf("new leader cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
var newLeaderGMI *GroupMemberInfo
|
|
||||||
|
|
||||||
// Find the new leader and update leadership
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Member == newLeader {
|
|
||||||
newLeaderGMI = gmi
|
|
||||||
gmi.Leader = true
|
|
||||||
} else if gmi.Leader {
|
|
||||||
// Remove leadership from current leader
|
|
||||||
gmi.Leader = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newLeaderGMI == nil {
|
|
||||||
return fmt.Errorf("new leader not found in group")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// Send group update
|
|
||||||
g.sendGroupUpdate(nil, false)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLeaderName returns the name of the group leader
|
|
||||||
func (g *Group) GetLeaderName() string {
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Leader {
|
|
||||||
return gmi.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShareQuestWithGroup shares a quest with all group members
|
|
||||||
func (g *Group) ShareQuestWithGroup(questSharer any, quest any) bool {
|
|
||||||
// TODO: Implement quest sharing
|
|
||||||
// This would require integration with the quest system
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateGroupMemberInfo updates information for a specific member
|
|
||||||
func (g *Group) UpdateGroupMemberInfo(member Entity, groupMembersLocked bool) {
|
|
||||||
if member == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !groupMembersLocked {
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the member and update their info
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Member == member {
|
|
||||||
gmi.UpdateStats()
|
|
||||||
g.updateLastActivity()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupMemberByPosition returns a group member at a specific position
|
|
||||||
func (g *Group) GetGroupMemberByPosition(seeker Entity, mappedPosition int32) Entity {
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
if mappedPosition < 0 || int(mappedPosition) >= len(g.Members) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.Members[mappedPosition].Member
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveClientReference removes client references when a client disconnects
|
|
||||||
// This is used for cleanup when a player disconnects but stays in the group
|
|
||||||
func (g *Group) RemoveClientReference(client any) {
|
|
||||||
g.membersMutex.Lock()
|
|
||||||
defer g.membersMutex.Unlock()
|
|
||||||
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Client != nil && gmi.Client == client {
|
|
||||||
gmi.Client = nil
|
|
||||||
// Don't set Member to nil as the entity might still exist
|
|
||||||
// Only clear the client reference
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupOptions returns a copy of the group options
|
|
||||||
func (g *Group) GetGroupOptions() GroupOptions {
|
|
||||||
g.optionsMutex.RLock()
|
|
||||||
defer g.optionsMutex.RUnlock()
|
|
||||||
|
|
||||||
return g.Options.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGroupOptions sets new group options
|
|
||||||
func (g *Group) SetGroupOptions(options *GroupOptions) error {
|
|
||||||
if options == nil {
|
|
||||||
return fmt.Errorf("options cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !options.IsValid() {
|
|
||||||
return fmt.Errorf("invalid group options")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.optionsMutex.Lock()
|
|
||||||
g.Options = *options
|
|
||||||
g.optionsMutex.Unlock()
|
|
||||||
|
|
||||||
g.updateLastActivity()
|
|
||||||
|
|
||||||
// Send group update for options change
|
|
||||||
update := NewGroupUpdate(GROUP_UPDATE_FLAG_OPTIONS, g.GroupID)
|
|
||||||
update.Options = options
|
|
||||||
|
|
||||||
select {
|
|
||||||
case g.updateQueue <- update:
|
|
||||||
default:
|
|
||||||
// Queue is full, drop the update
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastLooterIndex returns the last looter index
|
|
||||||
func (g *Group) GetLastLooterIndex() int8 {
|
|
||||||
g.optionsMutex.RLock()
|
|
||||||
defer g.optionsMutex.RUnlock()
|
|
||||||
|
|
||||||
return g.Options.LastLootedIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNextLooterIndex sets the next looter index
|
|
||||||
func (g *Group) SetNextLooterIndex(newIndex int8) {
|
|
||||||
g.optionsMutex.Lock()
|
|
||||||
g.Options.LastLootedIndex = newIndex
|
|
||||||
g.optionsMutex.Unlock()
|
|
||||||
|
|
||||||
g.updateLastActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raid functionality
|
|
||||||
|
|
||||||
// GetRaidGroups returns a copy of the raid groups list
|
|
||||||
func (g *Group) GetRaidGroups() []int32 {
|
|
||||||
g.raidGroupsMutex.RLock()
|
|
||||||
defer g.raidGroupsMutex.RUnlock()
|
|
||||||
|
|
||||||
if g.RaidGroups == nil {
|
|
||||||
return []int32{}
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := make([]int32, len(g.RaidGroups))
|
|
||||||
copy(groups, g.RaidGroups)
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceRaidGroups replaces the entire raid groups list
|
|
||||||
func (g *Group) ReplaceRaidGroups(groups []int32) {
|
|
||||||
g.raidGroupsMutex.Lock()
|
|
||||||
defer g.raidGroupsMutex.Unlock()
|
|
||||||
|
|
||||||
if groups == nil {
|
|
||||||
g.RaidGroups = make([]int32, 0)
|
|
||||||
} else {
|
|
||||||
g.RaidGroups = make([]int32, len(groups))
|
|
||||||
copy(g.RaidGroups, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.updateLastActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInRaidGroup checks if this group is in a raid with the specified group
|
|
||||||
func (g *Group) IsInRaidGroup(groupID int32, isLeaderGroup bool) bool {
|
|
||||||
g.raidGroupsMutex.RLock()
|
|
||||||
defer g.raidGroupsMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, id := range g.RaidGroups {
|
|
||||||
if id == groupID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddGroupToRaid adds a group to the raid
|
|
||||||
func (g *Group) AddGroupToRaid(groupID int32) {
|
|
||||||
g.raidGroupsMutex.Lock()
|
|
||||||
defer g.raidGroupsMutex.Unlock()
|
|
||||||
|
|
||||||
// Check if already in raid
|
|
||||||
for _, id := range g.RaidGroups {
|
|
||||||
if id == groupID {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.RaidGroups = append(g.RaidGroups, groupID)
|
|
||||||
g.updateLastActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveGroupFromRaid removes a group from the raid
|
|
||||||
func (g *Group) RemoveGroupFromRaid(groupID int32) {
|
|
||||||
g.raidGroupsMutex.Lock()
|
|
||||||
defer g.raidGroupsMutex.Unlock()
|
|
||||||
|
|
||||||
for i, id := range g.RaidGroups {
|
|
||||||
if id == groupID {
|
|
||||||
g.RaidGroups = append(g.RaidGroups[:i], g.RaidGroups[i+1:]...)
|
|
||||||
g.updateLastActivity()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsGroupRaid checks if this group is part of a raid
|
|
||||||
func (g *Group) IsGroupRaid() bool {
|
|
||||||
g.raidGroupsMutex.RLock()
|
|
||||||
defer g.raidGroupsMutex.RUnlock()
|
|
||||||
|
|
||||||
return len(g.RaidGroups) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearGroupRaid clears all raid associations
|
|
||||||
func (g *Group) ClearGroupRaid() {
|
|
||||||
g.raidGroupsMutex.Lock()
|
|
||||||
defer g.raidGroupsMutex.Unlock()
|
|
||||||
|
|
||||||
g.RaidGroups = make([]int32, 0)
|
|
||||||
g.updateLastActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDisbanded checks if the group has been disbanded
|
|
||||||
func (g *Group) IsDisbanded() bool {
|
|
||||||
g.disbandMutex.RLock()
|
|
||||||
defer g.disbandMutex.RUnlock()
|
|
||||||
|
|
||||||
return g.Disbanded
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCreatedTime returns when the group was created
|
|
||||||
func (g *Group) GetCreatedTime() time.Time {
|
|
||||||
return g.CreatedTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastActivity returns the last activity time
|
|
||||||
func (g *Group) GetLastActivity() time.Time {
|
|
||||||
g.activityMutex.RLock()
|
|
||||||
defer g.activityMutex.RUnlock()
|
|
||||||
return g.LastActivity
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateLastActivity updates the last activity timestamp
|
|
||||||
func (g *Group) updateLastActivity() {
|
|
||||||
g.activityMutex.Lock()
|
|
||||||
defer g.activityMutex.Unlock()
|
|
||||||
g.LastActivity = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// processMessages processes messages and updates in the background
|
|
||||||
func (g *Group) processMessages() {
|
|
||||||
defer g.wg.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-g.messageQueue:
|
|
||||||
g.handleMessage(msg)
|
|
||||||
case update := <-g.updateQueue:
|
|
||||||
g.handleUpdate(update)
|
|
||||||
case <-g.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMessage handles a group message
|
|
||||||
func (g *Group) handleMessage(msg *GroupMessage) {
|
|
||||||
if msg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
// Send message to all group members except the excluded client
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Client != nil && gmi.Client != msg.ExcludeClient {
|
|
||||||
// TODO: Send message to client
|
|
||||||
// This would require integration with the client system
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUpdate handles a group update
|
|
||||||
func (g *Group) handleUpdate(update *GroupUpdate) {
|
|
||||||
if update == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.membersMutex.RLock()
|
|
||||||
defer g.membersMutex.RUnlock()
|
|
||||||
|
|
||||||
// Send update to all group members except the excluded client
|
|
||||||
for _, gmi := range g.Members {
|
|
||||||
if gmi.Client != nil && gmi.Client != update.ExcludeClient {
|
|
||||||
// TODO: Send update to client
|
|
||||||
// This would require integration with the client system
|
|
||||||
// if gmi.Client != nil {
|
|
||||||
// gmi.Client.GetPlayer().SetCharSheetChanged(true)
|
|
||||||
// if isInRaid || update.ForceRaidUpdate {
|
|
||||||
// gmi.Client.GetPlayer().SetRaidSheetChanged(true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1633
internal/groups/groups.go
Normal file
1633
internal/groups/groups.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,501 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GroupAware interface for entities that can be part of groups
|
|
||||||
type GroupAware interface {
|
|
||||||
// GetGroupMemberInfo returns the group member info for this entity
|
|
||||||
GetGroupMemberInfo() *GroupMemberInfo
|
|
||||||
|
|
||||||
// SetGroupMemberInfo sets the group member info for this entity
|
|
||||||
SetGroupMemberInfo(info *GroupMemberInfo)
|
|
||||||
|
|
||||||
// GetGroupID returns the current group ID
|
|
||||||
GetGroupID() int32
|
|
||||||
|
|
||||||
// SetGroupID sets the current group ID
|
|
||||||
SetGroupID(groupID int32)
|
|
||||||
|
|
||||||
// IsInGroup returns true if the entity is in a group
|
|
||||||
IsInGroup() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupManager interface for managing groups
|
|
||||||
type GroupManagerInterface interface {
|
|
||||||
// Group creation and management
|
|
||||||
NewGroup(leader Entity, options *GroupOptions, overrideGroupID int32) (int32, error)
|
|
||||||
RemoveGroup(groupID int32) error
|
|
||||||
GetGroup(groupID int32) *Group
|
|
||||||
IsGroupIDValid(groupID int32) bool
|
|
||||||
|
|
||||||
// Member management
|
|
||||||
AddGroupMember(groupID int32, member Entity, isLeader bool) error
|
|
||||||
AddGroupMemberFromPeer(groupID int32, info *GroupMemberInfo) error
|
|
||||||
RemoveGroupMember(groupID int32, member Entity) error
|
|
||||||
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
|
|
||||||
|
|
||||||
// Group updates
|
|
||||||
SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool)
|
|
||||||
|
|
||||||
// Invitations
|
|
||||||
Invite(leader Entity, member Entity) int8
|
|
||||||
AddInvite(leader Entity, member Entity) bool
|
|
||||||
AcceptInvite(member Entity, groupOverrideID *int32, autoAddGroup bool) int8
|
|
||||||
DeclineInvite(member Entity)
|
|
||||||
ClearPendingInvite(member Entity)
|
|
||||||
HasPendingInvite(member Entity) string
|
|
||||||
|
|
||||||
// Group utilities
|
|
||||||
GetGroupSize(groupID int32) int32
|
|
||||||
IsInGroup(groupID int32, member Entity) bool
|
|
||||||
IsPlayerInGroup(groupID int32, charID int32) Entity
|
|
||||||
IsSpawnInGroup(groupID int32, name string) bool
|
|
||||||
GetGroupLeader(groupID int32) Entity
|
|
||||||
MakeLeader(groupID int32, newLeader Entity) bool
|
|
||||||
|
|
||||||
// Messaging
|
|
||||||
SimpleGroupMessage(groupID int32, message string)
|
|
||||||
SendGroupMessage(groupID int32, msgType int8, message string)
|
|
||||||
GroupMessage(groupID int32, message string)
|
|
||||||
GroupChatMessage(groupID int32, from Entity, language int32, message string, channel int16)
|
|
||||||
GroupChatMessageFromName(groupID int32, fromName string, language int32, message string, channel int16)
|
|
||||||
SendGroupChatMessage(groupID int32, channel int16, message string)
|
|
||||||
|
|
||||||
// Raid functionality
|
|
||||||
ClearGroupRaid(groupID int32)
|
|
||||||
RemoveGroupFromRaid(groupID, targetGroupID int32)
|
|
||||||
IsInRaidGroup(groupID, targetGroupID int32, isLeaderGroup bool) bool
|
|
||||||
GetRaidGroups(groupID int32) []int32
|
|
||||||
ReplaceRaidGroups(groupID int32, newGroups []int32)
|
|
||||||
|
|
||||||
// Group options
|
|
||||||
GetDefaultGroupOptions(groupID int32) (GroupOptions, bool)
|
|
||||||
SetGroupOptions(groupID int32, options *GroupOptions) error
|
|
||||||
|
|
||||||
// Statistics
|
|
||||||
GetStats() GroupManagerStats
|
|
||||||
GetGroupCount() int32
|
|
||||||
GetAllGroups() []*Group
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupEventHandler interface for handling group events
|
|
||||||
type GroupEventHandler interface {
|
|
||||||
// Group lifecycle events
|
|
||||||
OnGroupCreated(group *Group, leader Entity) error
|
|
||||||
OnGroupDisbanded(group *Group) error
|
|
||||||
OnGroupMemberJoined(group *Group, member Entity) error
|
|
||||||
OnGroupMemberLeft(group *Group, member Entity) error
|
|
||||||
OnGroupLeaderChanged(group *Group, oldLeader, newLeader Entity) error
|
|
||||||
|
|
||||||
// Invitation events
|
|
||||||
OnGroupInviteSent(leader, member Entity) error
|
|
||||||
OnGroupInviteAccepted(leader, member Entity, groupID int32) error
|
|
||||||
OnGroupInviteDeclined(leader, member Entity) error
|
|
||||||
OnGroupInviteExpired(leader, member Entity) error
|
|
||||||
|
|
||||||
// Raid events
|
|
||||||
OnRaidFormed(groups []*Group) error
|
|
||||||
OnRaidDisbanded(groups []*Group) error
|
|
||||||
OnRaidInviteSent(leaderGroup *Group, targetGroup *Group) error
|
|
||||||
OnRaidInviteAccepted(leaderGroup *Group, targetGroup *Group) error
|
|
||||||
OnRaidInviteDeclined(leaderGroup *Group, targetGroup *Group) error
|
|
||||||
|
|
||||||
// Group activity events
|
|
||||||
OnGroupMessage(group *Group, from Entity, message string, channel int16) error
|
|
||||||
OnGroupOptionsChanged(group *Group, oldOptions, newOptions *GroupOptions) error
|
|
||||||
OnGroupMemberUpdate(group *Group, member *GroupMemberInfo) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupDatabase interface for database operations
|
|
||||||
type GroupDatabase interface {
|
|
||||||
// Group persistence
|
|
||||||
SaveGroup(group *Group) error
|
|
||||||
LoadGroup(groupID int32) (*Group, error)
|
|
||||||
DeleteGroup(groupID int32) error
|
|
||||||
|
|
||||||
// Group member persistence
|
|
||||||
SaveGroupMember(groupID int32, member *GroupMemberInfo) error
|
|
||||||
LoadGroupMembers(groupID int32) ([]*GroupMemberInfo, error)
|
|
||||||
DeleteGroupMember(groupID int32, memberName string) error
|
|
||||||
|
|
||||||
// Group options persistence
|
|
||||||
SaveGroupOptions(groupID int32, options *GroupOptions) error
|
|
||||||
LoadGroupOptions(groupID int32) (*GroupOptions, error)
|
|
||||||
|
|
||||||
// Raid persistence
|
|
||||||
SaveRaidGroups(groupID int32, raidGroups []int32) error
|
|
||||||
LoadRaidGroups(groupID int32) ([]int32, error)
|
|
||||||
|
|
||||||
// Statistics persistence
|
|
||||||
SaveGroupStats(stats *GroupManagerStats) error
|
|
||||||
LoadGroupStats() (*GroupManagerStats, error)
|
|
||||||
|
|
||||||
// Cleanup operations
|
|
||||||
CleanupExpiredGroups() error
|
|
||||||
CleanupOrphanedMembers() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupPacketHandler interface for handling group-related packets
|
|
||||||
type GroupPacketHandler interface {
|
|
||||||
// Group update packets
|
|
||||||
SendGroupUpdate(members []*GroupMemberInfo, excludeClient any) error
|
|
||||||
SendGroupMemberUpdate(member *GroupMemberInfo, excludeClient any) error
|
|
||||||
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient any) error
|
|
||||||
|
|
||||||
// Group invitation packets
|
|
||||||
SendGroupInvite(inviter, invitee Entity) error
|
|
||||||
SendGroupInviteResponse(inviter, invitee Entity, accepted bool) error
|
|
||||||
|
|
||||||
// Group messaging packets
|
|
||||||
SendGroupMessage(members []*GroupMemberInfo, message *GroupMessage) error
|
|
||||||
SendGroupChatMessage(members []*GroupMemberInfo, from string, message string, channel int16, language int32) error
|
|
||||||
|
|
||||||
// Raid packets
|
|
||||||
SendRaidUpdate(raidGroups []*Group, excludeClient any) error
|
|
||||||
SendRaidInvite(leaderGroup, targetGroup *Group) error
|
|
||||||
SendRaidInviteResponse(leaderGroup, targetGroup *Group, accepted bool) error
|
|
||||||
|
|
||||||
// Group UI packets
|
|
||||||
SendGroupWindowUpdate(client any, group *Group) error
|
|
||||||
SendRaidWindowUpdate(client any, raidGroups []*Group) error
|
|
||||||
|
|
||||||
// Group member packets
|
|
||||||
SendGroupMemberStats(member *GroupMemberInfo, excludeClient any) error
|
|
||||||
SendGroupMemberZoneChange(member *GroupMemberInfo, oldZoneID, newZoneID int32) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupValidator interface for validating group operations
|
|
||||||
type GroupValidator interface {
|
|
||||||
// Group creation validation
|
|
||||||
ValidateGroupCreation(leader Entity, options *GroupOptions) error
|
|
||||||
ValidateGroupJoin(group *Group, member Entity) error
|
|
||||||
ValidateGroupLeave(group *Group, member Entity) error
|
|
||||||
|
|
||||||
// Invitation validation
|
|
||||||
ValidateGroupInvite(leader, member Entity) error
|
|
||||||
ValidateRaidInvite(leaderGroup, targetGroup *Group) error
|
|
||||||
|
|
||||||
// Group operation validation
|
|
||||||
ValidateLeadershipChange(group *Group, oldLeader, newLeader Entity) error
|
|
||||||
ValidateGroupOptions(group *Group, options *GroupOptions) error
|
|
||||||
ValidateGroupMessage(group *Group, from Entity, message string) error
|
|
||||||
|
|
||||||
// Raid validation
|
|
||||||
ValidateRaidFormation(groups []*Group) error
|
|
||||||
ValidateRaidOperation(raidGroups []*Group, operation string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupNotifier interface for sending notifications
|
|
||||||
type GroupNotifier interface {
|
|
||||||
// Group notifications
|
|
||||||
NotifyGroupCreated(group *Group, leader Entity) error
|
|
||||||
NotifyGroupDisbanded(group *Group, reason string) error
|
|
||||||
NotifyGroupMemberJoined(group *Group, member Entity) error
|
|
||||||
NotifyGroupMemberLeft(group *Group, member Entity, reason string) error
|
|
||||||
NotifyGroupLeaderChanged(group *Group, oldLeader, newLeader Entity) error
|
|
||||||
|
|
||||||
// Invitation notifications
|
|
||||||
NotifyGroupInviteSent(leader, member Entity) error
|
|
||||||
NotifyGroupInviteReceived(leader, member Entity) error
|
|
||||||
NotifyGroupInviteAccepted(leader, member Entity, groupID int32) error
|
|
||||||
NotifyGroupInviteDeclined(leader, member Entity) error
|
|
||||||
NotifyGroupInviteExpired(leader, member Entity) error
|
|
||||||
|
|
||||||
// Raid notifications
|
|
||||||
NotifyRaidFormed(groups []*Group) error
|
|
||||||
NotifyRaidDisbanded(groups []*Group, reason string) error
|
|
||||||
NotifyRaidInviteSent(leaderGroup, targetGroup *Group) error
|
|
||||||
NotifyRaidInviteReceived(leaderGroup, targetGroup *Group) error
|
|
||||||
NotifyRaidInviteAccepted(leaderGroup, targetGroup *Group) error
|
|
||||||
NotifyRaidInviteDeclined(leaderGroup, targetGroup *Group) error
|
|
||||||
|
|
||||||
// System notifications
|
|
||||||
NotifyGroupSystemMessage(group *Group, message string, msgType int8) error
|
|
||||||
NotifyGroupError(group *Group, error string, errorCode int8) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupStatistics interface for tracking group statistics
|
|
||||||
type GroupStatistics interface {
|
|
||||||
// Group statistics
|
|
||||||
RecordGroupCreated(group *Group, leader Entity)
|
|
||||||
RecordGroupDisbanded(group *Group, duration int64)
|
|
||||||
RecordGroupMemberJoined(group *Group, member Entity)
|
|
||||||
RecordGroupMemberLeft(group *Group, member Entity, duration int64)
|
|
||||||
|
|
||||||
// Invitation statistics
|
|
||||||
RecordInviteSent(leader, member Entity)
|
|
||||||
RecordInviteAccepted(leader, member Entity, responseTime int64)
|
|
||||||
RecordInviteDeclined(leader, member Entity, responseTime int64)
|
|
||||||
RecordInviteExpired(leader, member Entity)
|
|
||||||
|
|
||||||
// Raid statistics
|
|
||||||
RecordRaidFormed(groups []*Group)
|
|
||||||
RecordRaidDisbanded(groups []*Group, duration int64)
|
|
||||||
|
|
||||||
// Activity statistics
|
|
||||||
RecordGroupMessage(group *Group, from Entity, messageType int8)
|
|
||||||
RecordGroupActivity(group *Group, activityType string)
|
|
||||||
|
|
||||||
// Performance statistics
|
|
||||||
RecordGroupProcessingTime(operation string, duration int64)
|
|
||||||
RecordGroupMemoryUsage(groups int32, members int32)
|
|
||||||
|
|
||||||
// Statistics retrieval
|
|
||||||
GetGroupStatistics(groupID int32) map[string]any
|
|
||||||
GetOverallStatistics() map[string]any
|
|
||||||
GetStatisticsSummary() *GroupManagerStats
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupAdapter adapts group functionality for other systems
|
|
||||||
type GroupAdapter struct {
|
|
||||||
group *Group
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroupAdapter creates a new group adapter
|
|
||||||
func NewGroupAdapter(group *Group) *GroupAdapter {
|
|
||||||
return &GroupAdapter{group: group}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup returns the wrapped group
|
|
||||||
func (ga *GroupAdapter) GetGroup() *Group {
|
|
||||||
return ga.group
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupID returns the group ID
|
|
||||||
func (ga *GroupAdapter) GetGroupID() int32 {
|
|
||||||
return ga.group.GetID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupSize returns the group size
|
|
||||||
func (ga *GroupAdapter) GetGroupSize() int32 {
|
|
||||||
return ga.group.GetSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembers returns group members
|
|
||||||
func (ga *GroupAdapter) GetMembers() []*GroupMemberInfo {
|
|
||||||
return ga.group.GetMembers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLeader returns the group leader
|
|
||||||
func (ga *GroupAdapter) GetLeader() Entity {
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Leader {
|
|
||||||
return member.Member
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLeaderName returns the group leader's name
|
|
||||||
func (ga *GroupAdapter) GetLeaderName() string {
|
|
||||||
return ga.group.GetLeaderName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInRaid returns true if the group is part of a raid
|
|
||||||
func (ga *GroupAdapter) IsInRaid() bool {
|
|
||||||
return ga.group.IsGroupRaid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRaidGroups returns the raid groups
|
|
||||||
func (ga *GroupAdapter) GetRaidGroups() []int32 {
|
|
||||||
return ga.group.GetRaidGroups()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMember checks if an entity is a member of the group
|
|
||||||
func (ga *GroupAdapter) IsMember(entity Entity) bool {
|
|
||||||
if entity == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Member == entity {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasMemberNamed checks if the group has a member with the given name
|
|
||||||
func (ga *GroupAdapter) HasMemberNamed(name string) bool {
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Name == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMemberByName returns a member by name
|
|
||||||
func (ga *GroupAdapter) GetMemberByName(name string) *GroupMemberInfo {
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Name == name {
|
|
||||||
return member
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMemberByEntity returns a member by entity
|
|
||||||
func (ga *GroupAdapter) GetMemberByEntity(entity Entity) *GroupMemberInfo {
|
|
||||||
if entity == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Member == entity {
|
|
||||||
return member
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLeader checks if an entity is the group leader
|
|
||||||
func (ga *GroupAdapter) IsLeader(entity Entity) bool {
|
|
||||||
if entity == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
members := ga.group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Member == entity && member.Leader {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOptions returns the group options
|
|
||||||
func (ga *GroupAdapter) GetOptions() GroupOptions {
|
|
||||||
return ga.group.GetGroupOptions()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDisbanded returns true if the group has been disbanded
|
|
||||||
func (ga *GroupAdapter) IsDisbanded() bool {
|
|
||||||
return ga.group.IsDisbanded()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCreatedTime returns when the group was created
|
|
||||||
func (ga *GroupAdapter) GetCreatedTime() time.Time {
|
|
||||||
return ga.group.GetCreatedTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastActivity returns the last activity time
|
|
||||||
func (ga *GroupAdapter) GetLastActivity() time.Time {
|
|
||||||
return ga.group.GetLastActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntityGroupAdapter adapts entity functionality for group systems
|
|
||||||
type EntityGroupAdapter struct {
|
|
||||||
entity Entity
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityGroupAdapter creates a new entity group adapter
|
|
||||||
func NewEntityGroupAdapter(entity Entity) *EntityGroupAdapter {
|
|
||||||
return &EntityGroupAdapter{entity: entity}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEntity returns the wrapped entity
|
|
||||||
func (ega *EntityGroupAdapter) GetEntity() Entity {
|
|
||||||
return ega.entity
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the entity name
|
|
||||||
func (ega *EntityGroupAdapter) GetName() string {
|
|
||||||
return ega.entity.GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the entity level
|
|
||||||
func (ega *EntityGroupAdapter) GetLevel() int8 {
|
|
||||||
return ega.entity.GetLevel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClass returns the entity class
|
|
||||||
func (ega *EntityGroupAdapter) GetClass() int8 {
|
|
||||||
return ega.entity.GetClass()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRace returns the entity race
|
|
||||||
func (ega *EntityGroupAdapter) GetRace() int8 {
|
|
||||||
return ega.entity.GetRace()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetZoneID returns the current zone ID
|
|
||||||
func (ega *EntityGroupAdapter) GetZoneID() int32 {
|
|
||||||
if zone := ega.entity.GetZone(); zone != nil {
|
|
||||||
return zone.GetZoneID()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInstanceID returns the current instance ID
|
|
||||||
func (ega *EntityGroupAdapter) GetInstanceID() int32 {
|
|
||||||
if zone := ega.entity.GetZone(); zone != nil {
|
|
||||||
return zone.GetInstanceID()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetZoneName returns the current zone name
|
|
||||||
func (ega *EntityGroupAdapter) GetZoneName() string {
|
|
||||||
if zone := ega.entity.GetZone(); zone != nil {
|
|
||||||
return zone.GetZoneName()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHP returns current HP
|
|
||||||
func (ega *EntityGroupAdapter) GetHP() int32 {
|
|
||||||
return ega.entity.GetHP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMaxHP returns maximum HP
|
|
||||||
func (ega *EntityGroupAdapter) GetMaxHP() int32 {
|
|
||||||
return ega.entity.GetTotalHP()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPower returns current power
|
|
||||||
func (ega *EntityGroupAdapter) GetPower() int32 {
|
|
||||||
return ega.entity.GetPower()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMaxPower returns maximum power
|
|
||||||
func (ega *EntityGroupAdapter) GetMaxPower() int32 {
|
|
||||||
return ega.entity.GetTotalPower()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPlayer returns true if the entity is a player
|
|
||||||
func (ega *EntityGroupAdapter) IsPlayer() bool {
|
|
||||||
return ega.entity.IsPlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNPC returns true if the entity is an NPC
|
|
||||||
func (ega *EntityGroupAdapter) IsNPC() bool {
|
|
||||||
return ega.entity.IsNPC()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBot returns true if the entity is a bot
|
|
||||||
func (ega *EntityGroupAdapter) IsBot() bool {
|
|
||||||
return ega.entity.IsBot()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlive returns true if the entity is alive
|
|
||||||
func (ega *EntityGroupAdapter) IsAlive() bool {
|
|
||||||
return !ega.entity.IsDead()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDead returns true if the entity is dead
|
|
||||||
func (ega *EntityGroupAdapter) IsDead() bool {
|
|
||||||
return ega.entity.IsDead()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDistance returns distance to another entity
|
|
||||||
func (ega *EntityGroupAdapter) GetDistance(other Entity) float32 {
|
|
||||||
return ega.entity.GetDistance(other)
|
|
||||||
}
|
|
@ -1,510 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,528 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Group utility methods
|
|
||||||
|
|
||||||
// GetGroupSize returns the size of a group
|
|
||||||
func (m *Manager) GetGroupSize(groupID int32) int32 {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return group.GetSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInGroup checks if an entity is in a specific group
|
|
||||||
func (m *Manager) IsInGroup(groupID int32, member Entity) bool {
|
|
||||||
group := m.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 (m *Manager) IsPlayerInGroup(groupID int32, charID int32) Entity {
|
|
||||||
group := m.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 (m *Manager) IsSpawnInGroup(groupID int32, name string) bool {
|
|
||||||
group := m.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 (m *Manager) GetGroupLeader(groupID int32) Entity {
|
|
||||||
group := m.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 (m *Manager) MakeLeader(groupID int32, newLeader Entity) bool {
|
|
||||||
group := m.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 (m *Manager) SimpleGroupMessage(groupID int32, message string) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.SimpleGroupMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupMessage sends a formatted message to all members of a group
|
|
||||||
func (m *Manager) SendGroupMessage(groupID int32, msgType int8, message string) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.SendGroupMessage(msgType, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupMessage sends a message to all members of a group (alias for SimpleGroupMessage)
|
|
||||||
func (m *Manager) GroupMessage(groupID int32, message string) {
|
|
||||||
m.SimpleGroupMessage(groupID, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupChatMessage sends a chat message from a member to the group
|
|
||||||
func (m *Manager) GroupChatMessage(groupID int32, from Entity, language int32, message string, channel int16) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.GroupChatMessage(from, language, message, channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupChatMessageFromName sends a chat message from a named sender to the group
|
|
||||||
func (m *Manager) GroupChatMessageFromName(groupID int32, fromName string, language int32, message string, channel int16) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.GroupChatMessageFromName(fromName, language, message, channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGroupChatMessage sends a formatted chat message to the group
|
|
||||||
func (m *Manager) SendGroupChatMessage(groupID int32, channel int16, message string) {
|
|
||||||
m.GroupChatMessageFromName(groupID, "System", 0, message, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raid functionality
|
|
||||||
|
|
||||||
// ClearGroupRaid clears raid associations for a group
|
|
||||||
func (m *Manager) ClearGroupRaid(groupID int32) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.ClearGroupRaid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveGroupFromRaid removes a group from a raid
|
|
||||||
func (m *Manager) RemoveGroupFromRaid(groupID, targetGroupID int32) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.RemoveGroupFromRaid(targetGroupID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInRaidGroup checks if two groups are in the same raid
|
|
||||||
func (m *Manager) IsInRaidGroup(groupID, targetGroupID int32, isLeaderGroup bool) bool {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return group.IsInRaidGroup(targetGroupID, isLeaderGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRaidGroups returns the raid groups for a specific group
|
|
||||||
func (m *Manager) GetRaidGroups(groupID int32) []int32 {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
return []int32{}
|
|
||||||
}
|
|
||||||
return group.GetRaidGroups()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceRaidGroups replaces the raid groups for a specific group
|
|
||||||
func (m *Manager) ReplaceRaidGroups(groupID int32, newGroups []int32) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group != nil {
|
|
||||||
group.ReplaceRaidGroups(newGroups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group options
|
|
||||||
|
|
||||||
// GetDefaultGroupOptions returns the default group options for a group
|
|
||||||
func (m *Manager) GetDefaultGroupOptions(groupID int32) (GroupOptions, bool) {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
return GroupOptions{}, false
|
|
||||||
}
|
|
||||||
return group.GetGroupOptions(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGroupOptions sets group options for a specific group
|
|
||||||
func (m *Manager) SetGroupOptions(groupID int32, options *GroupOptions) error {
|
|
||||||
group := m.GetGroup(groupID)
|
|
||||||
if group == nil {
|
|
||||||
return fmt.Errorf("group %d not found", groupID)
|
|
||||||
}
|
|
||||||
return group.SetGroupOptions(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background processing loops
|
|
||||||
|
|
||||||
// updateGroupsLoop periodically updates all groups
|
|
||||||
func (m *Manager) updateGroupsLoop() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(m.Config.UpdateInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
m.processGroupUpdates()
|
|
||||||
case <-m.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateBuffsLoop periodically updates group buffs
|
|
||||||
func (m *Manager) updateBuffsLoop() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(m.Config.BuffUpdateInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
m.updateGroupBuffs()
|
|
||||||
case <-m.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupExpiredInvitesLoop periodically cleans up expired invites
|
|
||||||
func (m *Manager) cleanupExpiredInvitesLoop() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(30 * time.Second) // Check every 30 seconds
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
m.cleanupExpiredInvites()
|
|
||||||
case <-m.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsLoop periodically updates statistics
|
|
||||||
func (m *Manager) updateStatsLoop() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(1 * time.Minute) // Update stats every minute
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
m.updateStatistics()
|
|
||||||
case <-m.stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processGroupUpdates processes periodic group updates
|
|
||||||
func (m *Manager) processGroupUpdates() {
|
|
||||||
groups := m.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 (m *Manager) updateGroupBuffs() {
|
|
||||||
// TODO: Implement group buff updates
|
|
||||||
// This would require integration with the spell/buff system
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupExpiredInvites removes expired invitations
|
|
||||||
func (m *Manager) cleanupExpiredInvites() {
|
|
||||||
m.invitesMutex.Lock()
|
|
||||||
defer m.invitesMutex.Unlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
expiredCount := 0
|
|
||||||
|
|
||||||
// Clean up regular invites
|
|
||||||
for key, invite := range m.PendingInvites {
|
|
||||||
if now.After(invite.ExpiresTime) {
|
|
||||||
delete(m.PendingInvites, key)
|
|
||||||
expiredCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up raid invites
|
|
||||||
for key, invite := range m.RaidPendingInvites {
|
|
||||||
if now.After(invite.ExpiresTime) {
|
|
||||||
delete(m.RaidPendingInvites, key)
|
|
||||||
expiredCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update statistics
|
|
||||||
if expiredCount > 0 {
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
m.Stats.ExpiredInvites += int64(expiredCount)
|
|
||||||
m.statsMutex.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatistics updates manager statistics
|
|
||||||
func (m *Manager) updateStatistics() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
activeGroups := m.MasterList.GetActiveGroups()
|
|
||||||
raidGroups := m.MasterList.GetRaidGroups()
|
|
||||||
|
|
||||||
var totalMembers int64
|
|
||||||
var raidMembers int64
|
|
||||||
|
|
||||||
for _, group := range activeGroups {
|
|
||||||
totalMembers += int64(group.GetSize())
|
|
||||||
if group.IsGroupRaid() {
|
|
||||||
raidMembers += int64(group.GetSize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Stats.ActiveGroups = int64(len(activeGroups))
|
|
||||||
m.Stats.ActiveRaids = int64(len(raidGroups))
|
|
||||||
|
|
||||||
if len(activeGroups) > 0 {
|
|
||||||
m.Stats.AverageGroupSize = float64(totalMembers) / float64(len(activeGroups))
|
|
||||||
} else {
|
|
||||||
m.Stats.AverageGroupSize = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Stats.LastStatsUpdate = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statistics update methods
|
|
||||||
|
|
||||||
// updateStatsForNewGroup updates statistics when a new group is created
|
|
||||||
func (m *Manager) updateStatsForNewGroup() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
m.Stats.TotalGroups++
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsForRemovedGroup updates statistics when a group is removed
|
|
||||||
func (m *Manager) updateStatsForRemovedGroup() {
|
|
||||||
// Statistics are primarily tracked in updateStatistics()
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsForInvite updates statistics when an invite is sent
|
|
||||||
func (m *Manager) updateStatsForInvite() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
m.Stats.TotalInvites++
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsForAcceptedInvite updates statistics when an invite is accepted
|
|
||||||
func (m *Manager) updateStatsForAcceptedInvite() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
m.Stats.AcceptedInvites++
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsForDeclinedInvite updates statistics when an invite is declined
|
|
||||||
func (m *Manager) updateStatsForDeclinedInvite() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
m.Stats.DeclinedInvites++
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatsForExpiredInvite updates statistics when an invite expires
|
|
||||||
func (m *Manager) updateStatsForExpiredInvite() {
|
|
||||||
if !m.Config.EnableStatistics {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.statsMutex.Lock()
|
|
||||||
defer m.statsMutex.Unlock()
|
|
||||||
|
|
||||||
m.Stats.ExpiredInvites++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event system integration
|
|
||||||
|
|
||||||
// AddEventHandler adds an event handler
|
|
||||||
func (m *Manager) AddEventHandler(handler GroupEventHandler) {
|
|
||||||
m.eventHandlersMutex.Lock()
|
|
||||||
defer m.eventHandlersMutex.Unlock()
|
|
||||||
|
|
||||||
m.EventHandlers = append(m.EventHandlers, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Integration interfaces
|
|
||||||
|
|
||||||
// SetDatabase sets the database interface
|
|
||||||
func (m *Manager) SetDatabase(db GroupDatabase) {
|
|
||||||
m.database = db
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPacketHandler sets the packet handler interface
|
|
||||||
func (m *Manager) SetPacketHandler(handler GroupPacketHandler) {
|
|
||||||
m.packetHandler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValidator sets the validator interface
|
|
||||||
func (m *Manager) SetValidator(validator GroupValidator) {
|
|
||||||
m.validator = validator
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNotifier sets the notifier interface
|
|
||||||
func (m *Manager) SetNotifier(notifier GroupNotifier) {
|
|
||||||
m.notifier = notifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event firing methods
|
|
||||||
|
|
||||||
// fireGroupCreatedEvent fires a group created event
|
|
||||||
func (m *Manager) fireGroupCreatedEvent(group *Group, leader Entity) {
|
|
||||||
m.eventHandlersMutex.RLock()
|
|
||||||
defer m.eventHandlersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, handler := range m.EventHandlers {
|
|
||||||
go handler.OnGroupCreated(group, leader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fireGroupDisbandedEvent fires a group disbanded event
|
|
||||||
func (m *Manager) fireGroupDisbandedEvent(group *Group) {
|
|
||||||
m.eventHandlersMutex.RLock()
|
|
||||||
defer m.eventHandlersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, handler := range m.EventHandlers {
|
|
||||||
go handler.OnGroupDisbanded(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fireGroupInviteSentEvent fires a group invite sent event
|
|
||||||
func (m *Manager) fireGroupInviteSentEvent(leader, member Entity) {
|
|
||||||
m.eventHandlersMutex.RLock()
|
|
||||||
defer m.eventHandlersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, handler := range m.EventHandlers {
|
|
||||||
go handler.OnGroupInviteSent(leader, member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fireGroupInviteAcceptedEvent fires a group invite accepted event
|
|
||||||
func (m *Manager) fireGroupInviteAcceptedEvent(leader, member Entity, groupID int32) {
|
|
||||||
m.eventHandlersMutex.RLock()
|
|
||||||
defer m.eventHandlersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, handler := range m.EventHandlers {
|
|
||||||
go handler.OnGroupInviteAccepted(leader, member, groupID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fireGroupInviteDeclinedEvent fires a group invite declined event
|
|
||||||
func (m *Manager) fireGroupInviteDeclinedEvent(leader, member Entity) {
|
|
||||||
m.eventHandlersMutex.RLock()
|
|
||||||
defer m.eventHandlersMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, handler := range m.EventHandlers {
|
|
||||||
go handler.OnGroupInviteDeclined(leader, member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveClientReference removes client references from all groups when a client disconnects
|
|
||||||
func (m *Manager) RemoveClientReference(client any) {
|
|
||||||
// Get all groups from the master list
|
|
||||||
groups := m.MasterList.GetAllGroups()
|
|
||||||
|
|
||||||
// Remove client reference from all groups
|
|
||||||
for _, group := range groups {
|
|
||||||
group.RemoveClientReference(client)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,848 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"maps"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MasterList manages groups with optimized lookups for:
|
|
||||||
// - Fast ID-based lookups (O(1))
|
|
||||||
// - Fast member-based lookups (indexed)
|
|
||||||
// - Fast zone-based filtering (indexed)
|
|
||||||
// - Fast size-based filtering (indexed)
|
|
||||||
// - Raid group management and lookups
|
|
||||||
// - Leader-based searching
|
|
||||||
// - Activity tracking and cleanup
|
|
||||||
type MasterList struct {
|
|
||||||
// Core storage
|
|
||||||
groups map[int32]*Group // ID -> Group
|
|
||||||
mutex sync.RWMutex
|
|
||||||
|
|
||||||
// Indices for O(1) lookups
|
|
||||||
byMember map[string]*Group // Member name -> group containing that member
|
|
||||||
byLeader map[string]*Group // Leader name -> group
|
|
||||||
byZone map[int32][]*Group // Zone ID -> groups with members in that zone
|
|
||||||
bySize map[int32][]*Group // Size -> groups of that size
|
|
||||||
activeGroups map[int32]*Group // Active (non-disbanded) groups
|
|
||||||
raidGroups map[int32]*Group // Groups that are part of raids
|
|
||||||
soloGroups map[int32]*Group // Single-member groups
|
|
||||||
fullGroups map[int32]*Group // Full groups (size = MAX_GROUP_SIZE)
|
|
||||||
|
|
||||||
// Activity tracking
|
|
||||||
byLastActivity map[time.Time][]*Group // Last activity time -> groups
|
|
||||||
|
|
||||||
// Cached metadata and slices
|
|
||||||
totalMembers int32 // Total active members across all groups
|
|
||||||
zones []int32 // Unique zones with group members
|
|
||||||
sizes []int32 // Unique group sizes
|
|
||||||
zoneStats map[int32]int // Zone ID -> group count
|
|
||||||
sizeStats map[int32]int // Size -> group count
|
|
||||||
allGroupsSlice []*Group // Cached slice of all groups
|
|
||||||
activeGroupsSlice []*Group // Cached slice of active groups
|
|
||||||
metaStale bool // Whether metadata cache needs refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMasterList creates a new group master list
|
|
||||||
func NewMasterList() *MasterList {
|
|
||||||
return &MasterList{
|
|
||||||
groups: make(map[int32]*Group),
|
|
||||||
byMember: make(map[string]*Group),
|
|
||||||
byLeader: make(map[string]*Group),
|
|
||||||
byZone: make(map[int32][]*Group),
|
|
||||||
bySize: make(map[int32][]*Group),
|
|
||||||
activeGroups: make(map[int32]*Group),
|
|
||||||
raidGroups: make(map[int32]*Group),
|
|
||||||
soloGroups: make(map[int32]*Group),
|
|
||||||
fullGroups: make(map[int32]*Group),
|
|
||||||
byLastActivity: make(map[time.Time][]*Group),
|
|
||||||
zoneStats: make(map[int32]int),
|
|
||||||
sizeStats: make(map[int32]int),
|
|
||||||
allGroupsSlice: make([]*Group, 0),
|
|
||||||
activeGroupsSlice: make([]*Group, 0),
|
|
||||||
metaStale: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// refreshMetaCache updates the cached metadata
|
|
||||||
// Note: This function assumes the caller already holds ml.mutex.Lock()
|
|
||||||
func (ml *MasterList) refreshMetaCache() {
|
|
||||||
if !ml.metaStale {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear and rebuild zone and size stats
|
|
||||||
ml.zoneStats = make(map[int32]int)
|
|
||||||
ml.sizeStats = make(map[int32]int)
|
|
||||||
zoneSet := make(map[int32]struct{})
|
|
||||||
sizeSet := make(map[int32]struct{})
|
|
||||||
ml.totalMembers = 0
|
|
||||||
|
|
||||||
// Get snapshot of active groups to avoid holding lock while calling group methods
|
|
||||||
activeGroupsSnapshot := make([]*Group, 0, len(ml.activeGroups))
|
|
||||||
for _, group := range ml.activeGroups {
|
|
||||||
activeGroupsSnapshot = append(activeGroupsSnapshot, group)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect unique values and stats
|
|
||||||
for _, group := range activeGroupsSnapshot {
|
|
||||||
size := group.GetSize()
|
|
||||||
ml.sizeStats[size]++
|
|
||||||
sizeSet[size] = struct{}{}
|
|
||||||
ml.totalMembers += size
|
|
||||||
|
|
||||||
// Collect zones from group members
|
|
||||||
members := group.GetMembers()
|
|
||||||
zoneMap := make(map[int32]struct{})
|
|
||||||
for _, member := range members {
|
|
||||||
if member.ZoneID > 0 {
|
|
||||||
zoneMap[member.ZoneID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for zoneID := range zoneMap {
|
|
||||||
ml.zoneStats[zoneID]++
|
|
||||||
zoneSet[zoneID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear and rebuild cached slices
|
|
||||||
ml.zones = ml.zones[:0]
|
|
||||||
for zoneID := range zoneSet {
|
|
||||||
ml.zones = append(ml.zones, zoneID)
|
|
||||||
}
|
|
||||||
|
|
||||||
ml.sizes = ml.sizes[:0]
|
|
||||||
for size := range sizeSet {
|
|
||||||
ml.sizes = append(ml.sizes, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild all groups slice
|
|
||||||
ml.allGroupsSlice = ml.allGroupsSlice[:0]
|
|
||||||
if cap(ml.allGroupsSlice) < len(ml.groups) {
|
|
||||||
ml.allGroupsSlice = make([]*Group, 0, len(ml.groups))
|
|
||||||
}
|
|
||||||
for _, group := range ml.groups {
|
|
||||||
ml.allGroupsSlice = append(ml.allGroupsSlice, group)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild active groups slice
|
|
||||||
ml.activeGroupsSlice = ml.activeGroupsSlice[:0]
|
|
||||||
if cap(ml.activeGroupsSlice) < len(ml.activeGroups) {
|
|
||||||
ml.activeGroupsSlice = make([]*Group, 0, len(ml.activeGroups))
|
|
||||||
}
|
|
||||||
for _, group := range ml.activeGroups {
|
|
||||||
ml.activeGroupsSlice = append(ml.activeGroupsSlice, group)
|
|
||||||
}
|
|
||||||
|
|
||||||
ml.metaStale = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateGroupIndices updates all indices for a group
|
|
||||||
func (ml *MasterList) updateGroupIndices(group *Group, add bool) {
|
|
||||||
// Get all group data in one go to minimize lock contention
|
|
||||||
// This avoids holding the master list lock while calling multiple group methods
|
|
||||||
groupID := group.GetID()
|
|
||||||
|
|
||||||
// Create a snapshot of group data to avoid repeated method calls
|
|
||||||
groupData := struct {
|
|
||||||
id int32
|
|
||||||
size int32
|
|
||||||
leaderName string
|
|
||||||
isRaid bool
|
|
||||||
isDisbanded bool
|
|
||||||
members []*GroupMemberInfo
|
|
||||||
}{
|
|
||||||
id: groupID,
|
|
||||||
size: group.GetSize(),
|
|
||||||
leaderName: group.GetLeaderName(),
|
|
||||||
isRaid: group.IsGroupRaid(),
|
|
||||||
isDisbanded: group.IsDisbanded(),
|
|
||||||
members: group.GetMembers(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the snapshot data for indexing
|
|
||||||
size := groupData.size
|
|
||||||
leaderName := groupData.leaderName
|
|
||||||
isRaid := groupData.isRaid
|
|
||||||
isDisbanded := groupData.isDisbanded
|
|
||||||
members := groupData.members
|
|
||||||
|
|
||||||
if add {
|
|
||||||
// Add to size index
|
|
||||||
ml.bySize[size] = append(ml.bySize[size], group)
|
|
||||||
|
|
||||||
// Add to leader index
|
|
||||||
if leaderName != "" {
|
|
||||||
ml.byLeader[strings.ToLower(leaderName)] = group
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to member index
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Name != "" {
|
|
||||||
ml.byMember[strings.ToLower(member.Name)] = group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to zone index
|
|
||||||
zoneMap := make(map[int32]struct{})
|
|
||||||
for _, member := range members {
|
|
||||||
if member.ZoneID > 0 {
|
|
||||||
zoneMap[member.ZoneID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for zoneID := range zoneMap {
|
|
||||||
ml.byZone[zoneID] = append(ml.byZone[zoneID], group)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to specialized indices
|
|
||||||
if !isDisbanded {
|
|
||||||
ml.activeGroups[groupID] = group
|
|
||||||
}
|
|
||||||
if isRaid {
|
|
||||||
ml.raidGroups[groupID] = group
|
|
||||||
}
|
|
||||||
if size == 1 {
|
|
||||||
ml.soloGroups[groupID] = group
|
|
||||||
}
|
|
||||||
if size == MAX_GROUP_SIZE {
|
|
||||||
ml.fullGroups[groupID] = group
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to activity index
|
|
||||||
activity := group.GetLastActivity()
|
|
||||||
ml.byLastActivity[activity] = append(ml.byLastActivity[activity], group)
|
|
||||||
} else {
|
|
||||||
// Remove from size index
|
|
||||||
sizeGroups := ml.bySize[size]
|
|
||||||
for i, g := range sizeGroups {
|
|
||||||
if g.GetID() == groupID {
|
|
||||||
ml.bySize[size] = append(sizeGroups[:i], sizeGroups[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from leader index
|
|
||||||
if leaderName != "" {
|
|
||||||
delete(ml.byLeader, strings.ToLower(leaderName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from member index
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Name != "" {
|
|
||||||
delete(ml.byMember, strings.ToLower(member.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from zone index
|
|
||||||
zoneMap := make(map[int32]struct{})
|
|
||||||
for _, member := range members {
|
|
||||||
if member.ZoneID > 0 {
|
|
||||||
zoneMap[member.ZoneID] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for zoneID := range zoneMap {
|
|
||||||
zoneGroups := ml.byZone[zoneID]
|
|
||||||
for i, g := range zoneGroups {
|
|
||||||
if g.GetID() == groupID {
|
|
||||||
ml.byZone[zoneID] = append(zoneGroups[:i], zoneGroups[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from specialized indices
|
|
||||||
delete(ml.activeGroups, groupID)
|
|
||||||
delete(ml.raidGroups, groupID)
|
|
||||||
delete(ml.soloGroups, groupID)
|
|
||||||
delete(ml.fullGroups, groupID)
|
|
||||||
|
|
||||||
// Remove from activity index
|
|
||||||
activity := group.GetLastActivity()
|
|
||||||
activityGroups := ml.byLastActivity[activity]
|
|
||||||
for i, g := range activityGroups {
|
|
||||||
if g.GetID() == groupID {
|
|
||||||
ml.byLastActivity[activity] = append(activityGroups[:i], activityGroups[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddGroup adds a group with full indexing
|
|
||||||
func (ml *MasterList) AddGroup(group *Group) bool {
|
|
||||||
if group == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Check if exists
|
|
||||||
if _, exists := ml.groups[group.GetID()]; exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to core storage
|
|
||||||
ml.groups[group.GetID()] = group
|
|
||||||
|
|
||||||
// Update all indices
|
|
||||||
ml.updateGroupIndices(group, true)
|
|
||||||
|
|
||||||
// Invalidate metadata cache
|
|
||||||
ml.metaStale = true
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup retrieves by ID (O(1))
|
|
||||||
func (ml *MasterList) GetGroup(groupID int32) *Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return ml.groups[groupID]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupSafe retrieves a group by ID with existence check
|
|
||||||
func (ml *MasterList) GetGroupSafe(groupID int32) (*Group, bool) {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
group, exists := ml.groups[groupID]
|
|
||||||
return group, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasGroup checks if a group exists by ID
|
|
||||||
func (ml *MasterList) HasGroup(groupID int32) bool {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
_, exists := ml.groups[groupID]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveGroup removes a group and updates all indices
|
|
||||||
func (ml *MasterList) RemoveGroup(groupID int32) bool {
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
group, exists := ml.groups[groupID]
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from core storage
|
|
||||||
delete(ml.groups, groupID)
|
|
||||||
|
|
||||||
// Update all indices
|
|
||||||
ml.updateGroupIndices(group, false)
|
|
||||||
|
|
||||||
// Invalidate metadata cache
|
|
||||||
ml.metaStale = true
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllGroups returns all groups as a slice
|
|
||||||
func (ml *MasterList) GetAllGroups() []*Group {
|
|
||||||
// Use read lock first to check if we need to refresh
|
|
||||||
ml.mutex.RLock()
|
|
||||||
needsRefresh := ml.metaStale
|
|
||||||
if !needsRefresh {
|
|
||||||
// Return cached result without upgrading to write lock
|
|
||||||
result := make([]*Group, len(ml.allGroupsSlice))
|
|
||||||
copy(result, ml.allGroupsSlice)
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
// Need to refresh - acquire write lock
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Double-check pattern - someone else might have refreshed while we waited
|
|
||||||
if ml.metaStale {
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]*Group, len(ml.allGroupsSlice))
|
|
||||||
copy(result, ml.allGroupsSlice)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllGroupsMap returns a copy of all groups map
|
|
||||||
func (ml *MasterList) GetAllGroupsMap() map[int32]*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make(map[int32]*Group, len(ml.groups))
|
|
||||||
maps.Copy(result, ml.groups)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupCount returns the total number of groups
|
|
||||||
func (ml *MasterList) GetGroupCount() int32 {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return int32(len(ml.groups))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the total number of groups
|
|
||||||
func (ml *MasterList) Size() int {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return len(ml.groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the master list is empty
|
|
||||||
func (ml *MasterList) IsEmpty() bool {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return len(ml.groups) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all groups from the list
|
|
||||||
func (ml *MasterList) Clear() {
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Clear all maps
|
|
||||||
ml.groups = make(map[int32]*Group)
|
|
||||||
ml.byMember = make(map[string]*Group)
|
|
||||||
ml.byLeader = make(map[string]*Group)
|
|
||||||
ml.byZone = make(map[int32][]*Group)
|
|
||||||
ml.bySize = make(map[int32][]*Group)
|
|
||||||
ml.activeGroups = make(map[int32]*Group)
|
|
||||||
ml.raidGroups = make(map[int32]*Group)
|
|
||||||
ml.soloGroups = make(map[int32]*Group)
|
|
||||||
ml.fullGroups = make(map[int32]*Group)
|
|
||||||
ml.byLastActivity = make(map[time.Time][]*Group)
|
|
||||||
|
|
||||||
// Clear cached metadata
|
|
||||||
ml.zones = ml.zones[:0]
|
|
||||||
ml.sizes = ml.sizes[:0]
|
|
||||||
ml.allGroupsSlice = ml.allGroupsSlice[:0]
|
|
||||||
ml.activeGroupsSlice = ml.activeGroupsSlice[:0]
|
|
||||||
ml.zoneStats = make(map[int32]int)
|
|
||||||
ml.sizeStats = make(map[int32]int)
|
|
||||||
ml.totalMembers = 0
|
|
||||||
ml.metaStale = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsByFilter returns groups matching the filter function
|
|
||||||
func (ml *MasterList) GetGroupsByFilter(filter func(*Group) bool) []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
var result []*Group
|
|
||||||
for _, group := range ml.groups {
|
|
||||||
if filter(group) {
|
|
||||||
result = append(result, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupByMember returns the group containing the specified member (O(1))
|
|
||||||
func (ml *MasterList) GetGroupByMember(memberName string) *Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return ml.byMember[strings.ToLower(memberName)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupByLeader returns the group led by the specified leader (O(1))
|
|
||||||
func (ml *MasterList) GetGroupByLeader(leaderName string) *Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
return ml.byLeader[strings.ToLower(leaderName)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveGroups returns all non-disbanded groups (O(1))
|
|
||||||
func (ml *MasterList) GetActiveGroups() []*Group {
|
|
||||||
// Use read lock first to check if we need to refresh
|
|
||||||
ml.mutex.RLock()
|
|
||||||
needsRefresh := ml.metaStale
|
|
||||||
if !needsRefresh {
|
|
||||||
// Return cached result without upgrading to write lock
|
|
||||||
result := make([]*Group, len(ml.activeGroupsSlice))
|
|
||||||
copy(result, ml.activeGroupsSlice)
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
// Need to refresh - acquire write lock
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Double-check pattern - someone else might have refreshed while we waited
|
|
||||||
if ml.metaStale {
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]*Group, len(ml.activeGroupsSlice))
|
|
||||||
copy(result, ml.activeGroupsSlice)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsByZone returns groups with members in the specified zone (O(1))
|
|
||||||
func (ml *MasterList) GetGroupsByZone(zoneID int32) []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
groups := ml.byZone[zoneID]
|
|
||||||
if groups == nil {
|
|
||||||
return []*Group{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]*Group, len(groups))
|
|
||||||
copy(result, groups)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsBySize returns groups of the specified size (O(1))
|
|
||||||
func (ml *MasterList) GetGroupsBySize(size int32) []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
groups := ml.bySize[size]
|
|
||||||
if groups == nil {
|
|
||||||
return []*Group{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]*Group, len(groups))
|
|
||||||
copy(result, groups)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRaidGroups returns all groups that are part of raids (O(1))
|
|
||||||
func (ml *MasterList) GetRaidGroups() []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
result := make([]*Group, 0, len(ml.raidGroups))
|
|
||||||
for _, group := range ml.raidGroups {
|
|
||||||
result = append(result, group)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSoloGroups returns all groups with only one member (O(1))
|
|
||||||
func (ml *MasterList) GetSoloGroups() []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
result := make([]*Group, 0, len(ml.soloGroups))
|
|
||||||
for _, group := range ml.soloGroups {
|
|
||||||
result = append(result, group)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFullGroups returns all groups at maximum capacity (O(1))
|
|
||||||
func (ml *MasterList) GetFullGroups() []*Group {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
result := make([]*Group, 0, len(ml.fullGroups))
|
|
||||||
for _, group := range ml.fullGroups {
|
|
||||||
result = append(result, group)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsByLeader returns groups led by entities with the specified name
|
|
||||||
func (ml *MasterList) GetGroupsByLeader(leaderName string) []*Group {
|
|
||||||
group := ml.GetGroupByLeader(leaderName)
|
|
||||||
if group != nil {
|
|
||||||
return []*Group{group}
|
|
||||||
}
|
|
||||||
return []*Group{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsByMember returns groups containing a member with the specified name
|
|
||||||
func (ml *MasterList) GetGroupsByMember(memberName string) []*Group {
|
|
||||||
group := ml.GetGroupByMember(memberName)
|
|
||||||
if group != nil {
|
|
||||||
return []*Group{group}
|
|
||||||
}
|
|
||||||
return []*Group{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetZones returns all unique zones with group members using cached results
|
|
||||||
func (ml *MasterList) GetZones() []int32 {
|
|
||||||
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]int32, len(ml.zones))
|
|
||||||
copy(result, ml.zones)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSizes returns all unique group sizes using cached results
|
|
||||||
func (ml *MasterList) GetSizes() []int32 {
|
|
||||||
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
|
|
||||||
// Return a copy to prevent external modification
|
|
||||||
result := make([]int32, len(ml.sizes))
|
|
||||||
copy(result, ml.sizes)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTotalMembers returns the total number of active members across all groups
|
|
||||||
func (ml *MasterList) GetTotalMembers() int32 {
|
|
||||||
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
return ml.totalMembers
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupStatistics returns statistics about the groups in the master list using cached data
|
|
||||||
func (ml *MasterList) GetGroupStatistics() *GroupMasterListStats {
|
|
||||||
// Use read lock first to check if we need to refresh
|
|
||||||
ml.mutex.RLock()
|
|
||||||
needsRefresh := ml.metaStale
|
|
||||||
if !needsRefresh {
|
|
||||||
// Calculate stats from cached data
|
|
||||||
var totalRaidMembers int32
|
|
||||||
for _, group := range ml.raidGroups {
|
|
||||||
totalRaidMembers += group.GetSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
var averageGroupSize float64
|
|
||||||
if len(ml.activeGroups) > 0 {
|
|
||||||
averageGroupSize = float64(ml.totalMembers) / float64(len(ml.activeGroups))
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := &GroupMasterListStats{
|
|
||||||
TotalGroups: int32(len(ml.groups)),
|
|
||||||
ActiveGroups: int32(len(ml.activeGroups)),
|
|
||||||
RaidGroups: int32(len(ml.raidGroups)),
|
|
||||||
TotalMembers: ml.totalMembers,
|
|
||||||
TotalRaidMembers: totalRaidMembers,
|
|
||||||
AverageGroupSize: averageGroupSize,
|
|
||||||
SoloGroups: int32(len(ml.soloGroups)),
|
|
||||||
FullGroups: int32(len(ml.fullGroups)),
|
|
||||||
}
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
// Need to refresh - acquire write lock
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Double-check pattern
|
|
||||||
if ml.metaStale {
|
|
||||||
ml.refreshMetaCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalRaidMembers int32
|
|
||||||
for _, group := range ml.raidGroups {
|
|
||||||
totalRaidMembers += group.GetSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
var averageGroupSize float64
|
|
||||||
if len(ml.activeGroups) > 0 {
|
|
||||||
averageGroupSize = float64(ml.totalMembers) / float64(len(ml.activeGroups))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GroupMasterListStats{
|
|
||||||
TotalGroups: int32(len(ml.groups)),
|
|
||||||
ActiveGroups: int32(len(ml.activeGroups)),
|
|
||||||
RaidGroups: int32(len(ml.raidGroups)),
|
|
||||||
TotalMembers: ml.totalMembers,
|
|
||||||
TotalRaidMembers: totalRaidMembers,
|
|
||||||
AverageGroupSize: averageGroupSize,
|
|
||||||
SoloGroups: int32(len(ml.soloGroups)),
|
|
||||||
FullGroups: int32(len(ml.fullGroups)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupMasterListStats holds statistics about the groups master list
|
|
||||||
type GroupMasterListStats struct {
|
|
||||||
TotalGroups int32 `json:"total_groups"`
|
|
||||||
ActiveGroups int32 `json:"active_groups"`
|
|
||||||
RaidGroups int32 `json:"raid_groups"`
|
|
||||||
TotalMembers int32 `json:"total_members"`
|
|
||||||
TotalRaidMembers int32 `json:"total_raid_members"`
|
|
||||||
AverageGroupSize float64 `json:"average_group_size"`
|
|
||||||
SoloGroups int32 `json:"solo_groups"`
|
|
||||||
FullGroups int32 `json:"full_groups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshGroupIndices refreshes indices for a group (used when group state changes)
|
|
||||||
func (ml *MasterList) RefreshGroupIndices(group *Group) {
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Remove from old indices
|
|
||||||
ml.updateGroupIndices(group, false)
|
|
||||||
// Add to new indices
|
|
||||||
ml.updateGroupIndices(group, true)
|
|
||||||
|
|
||||||
// Invalidate metadata cache
|
|
||||||
ml.metaStale = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateGroup updates an existing group and refreshes indices
|
|
||||||
func (ml *MasterList) UpdateGroup(group *Group) error {
|
|
||||||
if group == nil {
|
|
||||||
return fmt.Errorf("group cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
// Check if exists
|
|
||||||
old, exists := ml.groups[group.GetID()]
|
|
||||||
if !exists {
|
|
||||||
return fmt.Errorf("group %d not found", group.GetID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old group from indices (but not core storage yet)
|
|
||||||
ml.updateGroupIndices(old, false)
|
|
||||||
|
|
||||||
// Update core storage
|
|
||||||
ml.groups[group.GetID()] = group
|
|
||||||
|
|
||||||
// Add new group to indices
|
|
||||||
ml.updateGroupIndices(group, true)
|
|
||||||
|
|
||||||
// Invalidate metadata cache
|
|
||||||
ml.metaStale = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForEach executes a function for each group
|
|
||||||
func (ml *MasterList) ForEach(fn func(int32, *Group)) {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
for id, group := range ml.groups {
|
|
||||||
fn(id, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup removes disbanded groups from the master list
|
|
||||||
func (ml *MasterList) Cleanup() int32 {
|
|
||||||
ml.mutex.Lock()
|
|
||||||
defer ml.mutex.Unlock()
|
|
||||||
|
|
||||||
removed := int32(0)
|
|
||||||
for id, group := range ml.groups {
|
|
||||||
if group.IsDisbanded() {
|
|
||||||
// Remove from core storage
|
|
||||||
delete(ml.groups, id)
|
|
||||||
// Remove from indices (group is already disbanded, so activeGroups won't include it)
|
|
||||||
ml.updateGroupIndices(group, false)
|
|
||||||
removed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if removed > 0 {
|
|
||||||
// Invalidate metadata cache
|
|
||||||
ml.metaStale = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return removed
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateAll validates all groups in the master list
|
|
||||||
func (ml *MasterList) ValidateAll() []error {
|
|
||||||
ml.mutex.RLock()
|
|
||||||
defer ml.mutex.RUnlock()
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
|
|
||||||
for id, group := range ml.groups {
|
|
||||||
if group == nil {
|
|
||||||
errors = append(errors, fmt.Errorf("group ID %d is nil", id))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for basic validity
|
|
||||||
if group.GetID() != id {
|
|
||||||
errors = append(errors, fmt.Errorf("group ID mismatch: map key %d != group ID %d", id, group.GetID()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.GetID() <= 0 {
|
|
||||||
errors = append(errors, fmt.Errorf("group %d has invalid ID", group.GetID()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.GetSize() == 0 && !group.IsDisbanded() {
|
|
||||||
errors = append(errors, fmt.Errorf("group %d is empty but not disbanded", group.GetID()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.GetSize() > MAX_GROUP_SIZE {
|
|
||||||
errors = append(errors, fmt.Errorf("group %d exceeds maximum size (%d > %d)",
|
|
||||||
group.GetID(), group.GetSize(), MAX_GROUP_SIZE))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for leader
|
|
||||||
members := group.GetMembers()
|
|
||||||
hasLeader := false
|
|
||||||
leaderCount := 0
|
|
||||||
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Leader {
|
|
||||||
hasLeader = true
|
|
||||||
leaderCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasLeader && !group.IsDisbanded() {
|
|
||||||
errors = append(errors, fmt.Errorf("group %d has no leader", group.GetID()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if leaderCount > 1 {
|
|
||||||
errors = append(errors, fmt.Errorf("group %d has multiple leaders (%d)", group.GetID(), leaderCount))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate index consistency
|
|
||||||
if !group.IsDisbanded() {
|
|
||||||
if _, exists := ml.activeGroups[id]; !exists {
|
|
||||||
errors = append(errors, fmt.Errorf("active group %d not found in activeGroups index", id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.IsGroupRaid() {
|
|
||||||
if _, exists := ml.raidGroups[id]; !exists {
|
|
||||||
errors = append(errors, fmt.Errorf("raid group %d not found in raidGroups index", id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.GetSize() == 1 {
|
|
||||||
if _, exists := ml.soloGroups[id]; !exists {
|
|
||||||
errors = append(errors, fmt.Errorf("solo group %d not found in soloGroups index", id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if group.GetSize() == MAX_GROUP_SIZE {
|
|
||||||
if _, exists := ml.fullGroups[id]; !exists {
|
|
||||||
errors = append(errors, fmt.Errorf("full group %d not found in fullGroups index", id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid returns true if all groups are valid
|
|
||||||
func (ml *MasterList) IsValid() bool {
|
|
||||||
errors := ml.ValidateAll()
|
|
||||||
return len(errors) == 0
|
|
||||||
}
|
|
@ -1,257 +0,0 @@
|
|||||||
package groups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GroupOptions holds group configuration settings
|
|
||||||
type GroupOptions struct {
|
|
||||||
LootMethod int8 `json:"loot_method" db:"loot_method"`
|
|
||||||
LootItemsRarity int8 `json:"loot_items_rarity" db:"loot_items_rarity"`
|
|
||||||
AutoSplit int8 `json:"auto_split" db:"auto_split"`
|
|
||||||
DefaultYell int8 `json:"default_yell" db:"default_yell"`
|
|
||||||
GroupLockMethod int8 `json:"group_lock_method" db:"group_lock_method"`
|
|
||||||
GroupAutolock int8 `json:"group_autolock" db:"group_autolock"`
|
|
||||||
SoloAutolock int8 `json:"solo_autolock" db:"solo_autolock"`
|
|
||||||
AutoLootMethod int8 `json:"auto_loot_method" db:"auto_loot_method"`
|
|
||||||
LastLootedIndex int8 `json:"last_looted_index" db:"last_looted_index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupMemberInfo contains all information about a group member
|
|
||||||
type GroupMemberInfo struct {
|
|
||||||
// Group and member identification
|
|
||||||
GroupID int32 `json:"group_id" db:"group_id"`
|
|
||||||
Name string `json:"name" db:"name"`
|
|
||||||
Zone string `json:"zone" db:"zone"`
|
|
||||||
|
|
||||||
// Health and power stats
|
|
||||||
HPCurrent int32 `json:"hp_current" db:"hp_current"`
|
|
||||||
HPMax int32 `json:"hp_max" db:"hp_max"`
|
|
||||||
PowerCurrent int32 `json:"power_current" db:"power_current"`
|
|
||||||
PowerMax int32 `json:"power_max" db:"power_max"`
|
|
||||||
|
|
||||||
// Level and character info
|
|
||||||
LevelCurrent int16 `json:"level_current" db:"level_current"`
|
|
||||||
LevelMax int16 `json:"level_max" db:"level_max"`
|
|
||||||
RaceID int8 `json:"race_id" db:"race_id"`
|
|
||||||
ClassID int8 `json:"class_id" db:"class_id"`
|
|
||||||
|
|
||||||
// Group status
|
|
||||||
Leader bool `json:"leader" db:"leader"`
|
|
||||||
IsClient bool `json:"is_client" db:"is_client"`
|
|
||||||
IsRaidLooter bool `json:"is_raid_looter" db:"is_raid_looter"`
|
|
||||||
|
|
||||||
// Zone and instance info
|
|
||||||
ZoneID int32 `json:"zone_id" db:"zone_id"`
|
|
||||||
InstanceID int32 `json:"instance_id" db:"instance_id"`
|
|
||||||
|
|
||||||
// Mentoring
|
|
||||||
MentorTargetCharID int32 `json:"mentor_target_char_id" db:"mentor_target_char_id"`
|
|
||||||
|
|
||||||
// Network info for cross-server groups
|
|
||||||
ClientPeerAddress string `json:"client_peer_address" db:"client_peer_address"`
|
|
||||||
ClientPeerPort int16 `json:"client_peer_port" db:"client_peer_port"`
|
|
||||||
|
|
||||||
// Entity reference (local members only)
|
|
||||||
Member Entity `json:"-"`
|
|
||||||
|
|
||||||
// Client reference (players only) - interface to avoid circular deps
|
|
||||||
Client any `json:"-"`
|
|
||||||
|
|
||||||
// Timestamps
|
|
||||||
JoinTime time.Time `json:"join_time" db:"join_time"`
|
|
||||||
LastUpdate time.Time `json:"last_update" db:"last_update"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group is now defined in group.go - this type definition removed to avoid duplication
|
|
||||||
|
|
||||||
// GroupMessage represents a message sent to the group
|
|
||||||
type GroupMessage struct {
|
|
||||||
Type int8 `json:"type"`
|
|
||||||
Channel int16 `json:"channel"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
FromName string `json:"from_name"`
|
|
||||||
Language int32 `json:"language"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
ExcludeClient any `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupUpdate represents a group update event
|
|
||||||
type GroupUpdate struct {
|
|
||||||
Type int8 `json:"type"`
|
|
||||||
GroupID int32 `json:"group_id"`
|
|
||||||
MemberInfo *GroupMemberInfo `json:"member_info,omitempty"`
|
|
||||||
Options *GroupOptions `json:"options,omitempty"`
|
|
||||||
RaidGroups []int32 `json:"raid_groups,omitempty"`
|
|
||||||
ForceRaidUpdate bool `json:"force_raid_update"`
|
|
||||||
ExcludeClient any `json:"-"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupInvite represents a pending group invitation
|
|
||||||
type GroupInvite struct {
|
|
||||||
InviterName string `json:"inviter_name"`
|
|
||||||
InviteeName string `json:"invitee_name"`
|
|
||||||
GroupID int32 `json:"group_id"`
|
|
||||||
IsRaidInvite bool `json:"is_raid_invite"`
|
|
||||||
CreatedTime time.Time `json:"created_time"`
|
|
||||||
ExpiresTime time.Time `json:"expires_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupManager manages all player groups
|
|
||||||
type GroupManager struct {
|
|
||||||
// Group storage
|
|
||||||
groups map[int32]*Group
|
|
||||||
groupsMutex sync.RWMutex
|
|
||||||
|
|
||||||
// Group ID generation
|
|
||||||
nextGroupID int32
|
|
||||||
nextGroupIDMutex sync.Mutex
|
|
||||||
|
|
||||||
// Pending invitations
|
|
||||||
pendingInvites map[string]*GroupInvite
|
|
||||||
raidPendingInvites map[string]*GroupInvite
|
|
||||||
invitesMutex sync.RWMutex
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
eventHandlers []GroupEventHandler
|
|
||||||
eventHandlersMutex sync.RWMutex
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
config GroupManagerConfig
|
|
||||||
|
|
||||||
// Statistics
|
|
||||||
stats GroupManagerStats
|
|
||||||
statsMutex sync.RWMutex
|
|
||||||
|
|
||||||
// Background processing
|
|
||||||
stopChan chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Integration interfaces
|
|
||||||
database GroupDatabase
|
|
||||||
packetHandler GroupPacketHandler
|
|
||||||
validator GroupValidator
|
|
||||||
notifier GroupNotifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupManagerConfig holds configuration for the group manager
|
|
||||||
type GroupManagerConfig struct {
|
|
||||||
MaxGroups int32 `json:"max_groups"`
|
|
||||||
MaxRaidGroups int32 `json:"max_raid_groups"`
|
|
||||||
InviteTimeout time.Duration `json:"invite_timeout"`
|
|
||||||
UpdateInterval time.Duration `json:"update_interval"`
|
|
||||||
BuffUpdateInterval time.Duration `json:"buff_update_interval"`
|
|
||||||
EnableCrossServer bool `json:"enable_cross_server"`
|
|
||||||
EnableRaids bool `json:"enable_raids"`
|
|
||||||
EnableQuestSharing bool `json:"enable_quest_sharing"`
|
|
||||||
EnableAutoInvite bool `json:"enable_auto_invite"`
|
|
||||||
EnableStatistics bool `json:"enable_statistics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupManagerStats holds statistics about group management
|
|
||||||
type GroupManagerStats struct {
|
|
||||||
TotalGroups int64 `json:"total_groups"`
|
|
||||||
ActiveGroups int64 `json:"active_groups"`
|
|
||||||
TotalRaids int64 `json:"total_raids"`
|
|
||||||
ActiveRaids int64 `json:"active_raids"`
|
|
||||||
TotalInvites int64 `json:"total_invites"`
|
|
||||||
AcceptedInvites int64 `json:"accepted_invites"`
|
|
||||||
DeclinedInvites int64 `json:"declined_invites"`
|
|
||||||
ExpiredInvites int64 `json:"expired_invites"`
|
|
||||||
AverageGroupSize float64 `json:"average_group_size"`
|
|
||||||
AverageGroupDuration time.Duration `json:"average_group_duration"`
|
|
||||||
LastStatsUpdate time.Time `json:"last_stats_update"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default group options
|
|
||||||
func DefaultGroupOptions() GroupOptions {
|
|
||||||
return GroupOptions{
|
|
||||||
LootMethod: LOOT_METHOD_ROUND_ROBIN,
|
|
||||||
LootItemsRarity: LOOT_RARITY_COMMON,
|
|
||||||
AutoSplit: AUTO_SPLIT_DISABLED,
|
|
||||||
DefaultYell: DEFAULT_YELL_DISABLED,
|
|
||||||
GroupLockMethod: LOCK_METHOD_OPEN,
|
|
||||||
GroupAutolock: AUTO_LOCK_DISABLED,
|
|
||||||
SoloAutolock: AUTO_LOCK_DISABLED,
|
|
||||||
AutoLootMethod: AUTO_LOOT_DISABLED,
|
|
||||||
LastLootedIndex: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a copy of GroupMemberInfo
|
|
||||||
func (gmi *GroupMemberInfo) Copy() *GroupMemberInfo {
|
|
||||||
copy := *gmi
|
|
||||||
return ©
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid checks if the group member info is valid
|
|
||||||
func (gmi *GroupMemberInfo) IsValid() bool {
|
|
||||||
return gmi.GroupID > 0 && len(gmi.Name) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateStats updates member stats from entity
|
|
||||||
func (gmi *GroupMemberInfo) UpdateStats() {
|
|
||||||
if gmi.Member == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
entity := gmi.Member
|
|
||||||
gmi.HPCurrent = entity.GetHP()
|
|
||||||
gmi.HPMax = entity.GetTotalHP()
|
|
||||||
gmi.PowerCurrent = entity.GetPower()
|
|
||||||
gmi.PowerMax = entity.GetTotalPower()
|
|
||||||
gmi.LevelCurrent = int16(entity.GetLevel())
|
|
||||||
gmi.LevelMax = int16(entity.GetLevel()) // TODO: Get actual max level
|
|
||||||
gmi.LastUpdate = time.Now()
|
|
||||||
|
|
||||||
// Update zone info if entity has zone
|
|
||||||
if zone := entity.GetZone(); zone != nil {
|
|
||||||
gmi.ZoneID = zone.GetZoneID()
|
|
||||||
gmi.InstanceID = zone.GetInstanceID()
|
|
||||||
gmi.Zone = zone.GetZoneName()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a copy of GroupOptions
|
|
||||||
func (go_opts *GroupOptions) Copy() GroupOptions {
|
|
||||||
return *go_opts
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid checks if group options are valid
|
|
||||||
func (go_opts *GroupOptions) IsValid() bool {
|
|
||||||
return go_opts.LootMethod >= LOOT_METHOD_LEADER_ONLY && go_opts.LootMethod <= LOOT_METHOD_LOTTO &&
|
|
||||||
go_opts.LootItemsRarity >= LOOT_RARITY_COMMON && go_opts.LootItemsRarity <= LOOT_RARITY_FABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroupMessage creates a new group message
|
|
||||||
func NewGroupMessage(msgType int8, channel int16, message, fromName string, language int32) *GroupMessage {
|
|
||||||
return &GroupMessage{
|
|
||||||
Type: msgType,
|
|
||||||
Channel: channel,
|
|
||||||
Message: message,
|
|
||||||
FromName: fromName,
|
|
||||||
Language: language,
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGroupUpdate creates a new group update
|
|
||||||
func NewGroupUpdate(updateType int8, groupID int32) *GroupUpdate {
|
|
||||||
return &GroupUpdate{
|
|
||||||
Type: updateType,
|
|
||||||
GroupID: groupID,
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsExpired checks if the group invite has expired
|
|
||||||
func (gi *GroupInvite) IsExpired() bool {
|
|
||||||
return time.Now().After(gi.ExpiresTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRemaining returns the remaining time for the invite
|
|
||||||
func (gi *GroupInvite) TimeRemaining() time.Duration {
|
|
||||||
return time.Until(gi.ExpiresTime)
|
|
||||||
}
|
|
@ -136,6 +136,17 @@ const (
|
|||||||
OP_EntityVerbsReplyMsg
|
OP_EntityVerbsReplyMsg
|
||||||
OP_EntityVerbsVerbMsg
|
OP_EntityVerbsVerbMsg
|
||||||
|
|
||||||
|
// Group system opcodes
|
||||||
|
OP_GroupUpdateMsg
|
||||||
|
OP_RaidUpdateMsg
|
||||||
|
OP_GroupInviteMsg
|
||||||
|
OP_GroupDeclineMsg
|
||||||
|
OP_GroupChatMsg
|
||||||
|
OP_GroupMessageMsg
|
||||||
|
OP_GroupOptionsMsg
|
||||||
|
OP_DefaultGroupOptionsMsg
|
||||||
|
OP_DefaultGroupOptionsRequestMsg
|
||||||
|
|
||||||
// Add more opcodes as needed...
|
// Add more opcodes as needed...
|
||||||
_maxInternalOpcode // Sentinel value
|
_maxInternalOpcode // Sentinel value
|
||||||
)
|
)
|
||||||
@ -231,6 +242,17 @@ var OpcodeNames = map[InternalOpcode]string{
|
|||||||
OP_EntityVerbsRequestMsg: "OP_EntityVerbsRequestMsg",
|
OP_EntityVerbsRequestMsg: "OP_EntityVerbsRequestMsg",
|
||||||
OP_EntityVerbsReplyMsg: "OP_EntityVerbsReplyMsg",
|
OP_EntityVerbsReplyMsg: "OP_EntityVerbsReplyMsg",
|
||||||
OP_EntityVerbsVerbMsg: "OP_EntityVerbsVerbMsg",
|
OP_EntityVerbsVerbMsg: "OP_EntityVerbsVerbMsg",
|
||||||
|
|
||||||
|
// Group system opcodes
|
||||||
|
OP_GroupUpdateMsg: "OP_GroupUpdateMsg",
|
||||||
|
OP_RaidUpdateMsg: "OP_RaidUpdateMsg",
|
||||||
|
OP_GroupInviteMsg: "OP_GroupInviteMsg",
|
||||||
|
OP_GroupDeclineMsg: "OP_GroupDeclineMsg",
|
||||||
|
OP_GroupChatMsg: "OP_GroupChatMsg",
|
||||||
|
OP_GroupMessageMsg: "OP_GroupMessageMsg",
|
||||||
|
OP_GroupOptionsMsg: "OP_GroupOptionsMsg",
|
||||||
|
OP_DefaultGroupOptionsMsg: "OP_DefaultGroupOptionsMsg",
|
||||||
|
OP_DefaultGroupOptionsRequestMsg: "OP_DefaultGroupOptionsRequestMsg",
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
|
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user