eq2go/internal/groups/group.go

700 lines
16 KiB
Go

package groups
import (
"fmt"
"time"
"eq2emu/internal/entity"
)
// NewGroup creates a new group with the given ID and options
func NewGroup(id int32, options *GroupOptions) *Group {
if options == nil {
defaultOpts := DefaultGroupOptions()
options = &defaultOpts
}
group := &Group{
id: 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{}),
}
// Start background processing
group.wg.Add(1)
go group.processMessages()
return group
}
// GetID returns the group ID
func (g *Group) GetID() int32 {
return g.id
}
// 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.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.id,
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.id,
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.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()
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
// Stop background processing
close(g.stopChan)
g.wg.Wait()
}
// 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.id)
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.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.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.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.Entity, mappedPosition int32) entity.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.id)
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 {
return g.lastActivity
}
// updateLastActivity updates the last activity timestamp (not thread-safe)
func (g *Group) updateLastActivity() {
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()
isInRaid := g.IsGroupRaid()
// 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)
// }
// }
}
}
}