700 lines
16 KiB
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 interface{}, forceRaidUpdate bool) {
|
|
g.sendGroupUpdate(excludeClient, forceRaidUpdate)
|
|
}
|
|
|
|
// sendGroupUpdate internal method to send group updates
|
|
func (g *Group) sendGroupUpdate(excludeClient interface{}, 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 interface{}, quest interface{}) 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)
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
}
|