package groups import ( "fmt" "time" ) // 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, 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) 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, 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.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() // 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) // } // } } } }