eq2go/internal/groups/group.go

782 lines
19 KiB
Go

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
}
// 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)
// }
// }
}
}
}