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_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...
|
||||
_maxInternalOpcode // Sentinel value
|
||||
)
|
||||
@ -231,6 +242,17 @@ var OpcodeNames = map[InternalOpcode]string{
|
||||
OP_EntityVerbsRequestMsg: "OP_EntityVerbsRequestMsg",
|
||||
OP_EntityVerbsReplyMsg: "OP_EntityVerbsReplyMsg",
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user