first pass on group tests
This commit is contained in:
parent
0534c49610
commit
e35e41f643
42
internal/groups/entity_interface.go
Normal file
42
internal/groups/entity_interface.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package groups
|
||||||
|
|
||||||
|
// Entity is the interface for entities that can be part of groups
|
||||||
|
// This interface is implemented by Player, NPC, and Bot types
|
||||||
|
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 type checks
|
||||||
|
IsPlayer() bool
|
||||||
|
IsNPC() bool
|
||||||
|
IsBot() bool
|
||||||
|
IsDead() bool
|
||||||
|
|
||||||
|
// Zone information
|
||||||
|
GetZone() Zone
|
||||||
|
|
||||||
|
// Distance calculation
|
||||||
|
GetDistance(other Entity) float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone interface for zone information
|
||||||
|
type Zone interface {
|
||||||
|
GetZoneID() int32
|
||||||
|
GetInstanceID() int32
|
||||||
|
GetZoneName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn interface for distance calculations
|
||||||
|
type Spawn interface {
|
||||||
|
// Minimal spawn interface for distance calculations
|
||||||
|
}
|
@ -3,8 +3,6 @@ package groups
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eq2emu/internal/entity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewGroup creates a new group with the given ID and options
|
// NewGroup creates a new group with the given ID and options
|
||||||
@ -61,7 +59,7 @@ func (g *Group) GetMembers() []*GroupMemberInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddMember adds a new member to the group
|
// AddMember adds a new member to the group
|
||||||
func (g *Group) AddMember(member entity.Entity, isLeader bool) error {
|
func (g *Group) AddMember(member Entity, isLeader bool) error {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return fmt.Errorf("member cannot be nil")
|
return fmt.Errorf("member cannot be nil")
|
||||||
}
|
}
|
||||||
@ -188,7 +186,7 @@ func (g *Group) AddMemberFromPeer(name string, isLeader, isClient bool, classID
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveMember removes a member from the group
|
// RemoveMember removes a member from the group
|
||||||
func (g *Group) RemoveMember(member entity.Entity) error {
|
func (g *Group) RemoveMember(member Entity) error {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return fmt.Errorf("member cannot be nil")
|
return fmt.Errorf("member cannot be nil")
|
||||||
}
|
}
|
||||||
@ -356,7 +354,7 @@ func (g *Group) SendGroupMessage(msgType int8, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GroupChatMessage sends a chat message from a member to the group
|
// GroupChatMessage sends a chat message from a member to the group
|
||||||
func (g *Group) GroupChatMessage(from entity.Entity, language int32, message string, channel int16) {
|
func (g *Group) GroupChatMessage(from Entity, language int32, message string, channel int16) {
|
||||||
if from == nil {
|
if from == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -382,7 +380,7 @@ func (g *Group) GroupChatMessageFromName(fromName string, language int32, messag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MakeLeader changes the group leader
|
// MakeLeader changes the group leader
|
||||||
func (g *Group) MakeLeader(newLeader entity.Entity) error {
|
func (g *Group) MakeLeader(newLeader Entity) error {
|
||||||
if newLeader == nil {
|
if newLeader == nil {
|
||||||
return fmt.Errorf("new leader cannot be nil")
|
return fmt.Errorf("new leader cannot be nil")
|
||||||
}
|
}
|
||||||
@ -437,7 +435,7 @@ func (g *Group) ShareQuestWithGroup(questSharer any, quest any) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGroupMemberInfo updates information for a specific member
|
// UpdateGroupMemberInfo updates information for a specific member
|
||||||
func (g *Group) UpdateGroupMemberInfo(member entity.Entity, groupMembersLocked bool) {
|
func (g *Group) UpdateGroupMemberInfo(member Entity, groupMembersLocked bool) {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -458,7 +456,7 @@ func (g *Group) UpdateGroupMemberInfo(member entity.Entity, groupMembersLocked b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupMemberByPosition returns a group member at a specific position
|
// GetGroupMemberByPosition returns a group member at a specific position
|
||||||
func (g *Group) GetGroupMemberByPosition(seeker entity.Entity, mappedPosition int32) entity.Entity {
|
func (g *Group) GetGroupMemberByPosition(seeker Entity, mappedPosition int32) Entity {
|
||||||
g.membersMutex.RLock()
|
g.membersMutex.RLock()
|
||||||
defer g.membersMutex.RUnlock()
|
defer g.membersMutex.RUnlock()
|
||||||
|
|
||||||
@ -681,8 +679,6 @@ func (g *Group) handleUpdate(update *GroupUpdate) {
|
|||||||
g.membersMutex.RLock()
|
g.membersMutex.RLock()
|
||||||
defer g.membersMutex.RUnlock()
|
defer g.membersMutex.RUnlock()
|
||||||
|
|
||||||
isInRaid := g.IsGroupRaid()
|
|
||||||
|
|
||||||
// Send update to all group members except the excluded client
|
// Send update to all group members except the excluded client
|
||||||
for _, gmi := range g.members {
|
for _, gmi := range g.members {
|
||||||
if gmi.Client != nil && gmi.Client != update.ExcludeClient {
|
if gmi.Client != nil && gmi.Client != update.ExcludeClient {
|
||||||
|
863
internal/groups/groups_test.go
Normal file
863
internal/groups/groups_test.go
Normal file
@ -0,0 +1,863 @@
|
|||||||
|
package groups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock entity implementation for testing
|
||||||
|
type mockEntity struct {
|
||||||
|
id int32
|
||||||
|
name string
|
||||||
|
level int8
|
||||||
|
class int8
|
||||||
|
race int8
|
||||||
|
hp int32
|
||||||
|
maxHP int32
|
||||||
|
power int32
|
||||||
|
maxPower int32
|
||||||
|
isPlayer bool
|
||||||
|
isNPC bool
|
||||||
|
isBot bool
|
||||||
|
isDead bool
|
||||||
|
zone *mockZone
|
||||||
|
groupID int32
|
||||||
|
groupInfo *GroupMemberInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEntity) GetID() int32 { return m.id }
|
||||||
|
func (m *mockEntity) GetName() string { return m.name }
|
||||||
|
func (m *mockEntity) GetLevel() int8 { return m.level }
|
||||||
|
func (m *mockEntity) GetClass() int8 { return m.class }
|
||||||
|
func (m *mockEntity) GetRace() int8 { return m.race }
|
||||||
|
func (m *mockEntity) GetHP() int32 { return m.hp }
|
||||||
|
func (m *mockEntity) GetTotalHP() int32 { return m.maxHP }
|
||||||
|
func (m *mockEntity) GetPower() int32 { return m.power }
|
||||||
|
func (m *mockEntity) GetTotalPower() int32 { return m.maxPower }
|
||||||
|
func (m *mockEntity) IsPlayer() bool { return m.isPlayer }
|
||||||
|
func (m *mockEntity) IsNPC() bool { return m.isNPC }
|
||||||
|
func (m *mockEntity) IsBot() bool { return m.isBot }
|
||||||
|
func (m *mockEntity) IsDead() bool { return m.isDead }
|
||||||
|
func (m *mockEntity) GetZone() Zone { return m.zone }
|
||||||
|
func (m *mockEntity) GetDistance(other Entity) float32 { return 10.0 }
|
||||||
|
|
||||||
|
// GroupAware implementation
|
||||||
|
func (m *mockEntity) GetGroupMemberInfo() *GroupMemberInfo { return m.groupInfo }
|
||||||
|
func (m *mockEntity) SetGroupMemberInfo(info *GroupMemberInfo) { m.groupInfo = info }
|
||||||
|
func (m *mockEntity) GetGroupID() int32 { return m.groupID }
|
||||||
|
func (m *mockEntity) SetGroupID(groupID int32) { m.groupID = groupID }
|
||||||
|
func (m *mockEntity) IsInGroup() bool { return m.groupID > 0 }
|
||||||
|
|
||||||
|
// Mock zone implementation
|
||||||
|
type mockZone struct {
|
||||||
|
zoneID int32
|
||||||
|
instanceID int32
|
||||||
|
zoneName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockZone) GetZoneID() int32 { return m.zoneID }
|
||||||
|
func (m *mockZone) GetInstanceID() int32 { return m.instanceID }
|
||||||
|
func (m *mockZone) GetZoneName() string { return m.zoneName }
|
||||||
|
|
||||||
|
// Helper function to create mock entities
|
||||||
|
func createMockEntity(id int32, name string, isPlayer bool) *mockEntity {
|
||||||
|
return &mockEntity{
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
level: 50,
|
||||||
|
class: 1,
|
||||||
|
race: 0,
|
||||||
|
hp: 1500,
|
||||||
|
maxHP: 1500,
|
||||||
|
power: 800,
|
||||||
|
maxPower: 800,
|
||||||
|
isPlayer: isPlayer,
|
||||||
|
zone: &mockZone{
|
||||||
|
zoneID: 220,
|
||||||
|
instanceID: 1,
|
||||||
|
zoneName: "commonlands",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupCreation tests basic group creation
|
||||||
|
func TestGroupCreation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
groupID int32
|
||||||
|
options *GroupOptions
|
||||||
|
expectNil bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Create group with default options",
|
||||||
|
groupID: 1,
|
||||||
|
options: nil,
|
||||||
|
expectNil: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Create group with custom options",
|
||||||
|
groupID: 2,
|
||||||
|
options: &GroupOptions{
|
||||||
|
LootMethod: LOOT_METHOD_NEED_BEFORE_GREED,
|
||||||
|
LootItemsRarity: LOOT_RARITY_RARE,
|
||||||
|
AutoSplit: AUTO_SPLIT_ENABLED,
|
||||||
|
},
|
||||||
|
expectNil: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
group := NewGroup(tt.groupID, tt.options)
|
||||||
|
|
||||||
|
if (group == nil) != tt.expectNil {
|
||||||
|
t.Errorf("NewGroup() returned nil = %v, want %v", group == nil, tt.expectNil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetID() != tt.groupID {
|
||||||
|
t.Errorf("Group ID = %d, want %d", group.GetID(), tt.groupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetSize() != 0 {
|
||||||
|
t.Errorf("Initial group size = %d, want 0", group.GetSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
group.Disband()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupMemberManagement tests adding and removing members
|
||||||
|
func TestGroupMemberManagement(t *testing.T) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
leader := createMockEntity(1, "Leader", true)
|
||||||
|
member1 := createMockEntity(2, "Member1", true)
|
||||||
|
member2 := createMockEntity(3, "Member2", true)
|
||||||
|
|
||||||
|
// Test adding leader
|
||||||
|
err := group.AddMember(leader, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add leader: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetSize() != 1 {
|
||||||
|
t.Errorf("Group size after adding leader = %d, want 1", group.GetSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test adding members
|
||||||
|
err = group.AddMember(member1, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add member1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = group.AddMember(member2, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add member2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetSize() != 3 {
|
||||||
|
t.Errorf("Group size after adding members = %d, want 3", group.GetSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test duplicate member
|
||||||
|
err = group.AddMember(member1, false)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when adding duplicate member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test member removal
|
||||||
|
err = group.RemoveMember(member1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove member1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetSize() != 2 {
|
||||||
|
t.Errorf("Group size after removing member = %d, want 2", group.GetSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing non-existent member
|
||||||
|
err = group.RemoveMember(member1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when removing non-existent member")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupLeadership tests leadership transfer
|
||||||
|
func TestGroupLeadership(t *testing.T) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
leader := createMockEntity(1, "Leader", true)
|
||||||
|
member1 := createMockEntity(2, "Member1", true)
|
||||||
|
member2 := createMockEntity(3, "Member2", true)
|
||||||
|
|
||||||
|
// Add members
|
||||||
|
group.AddMember(leader, true)
|
||||||
|
group.AddMember(member1, false)
|
||||||
|
group.AddMember(member2, false)
|
||||||
|
|
||||||
|
// Test initial leader
|
||||||
|
if group.GetLeaderName() != "Leader" {
|
||||||
|
t.Errorf("Initial leader name = %s, want Leader", group.GetLeaderName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer leadership
|
||||||
|
err := group.MakeLeader(member1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to transfer leadership: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GetLeaderName() != "Member1" {
|
||||||
|
t.Errorf("New leader name = %s, want Member1", group.GetLeaderName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test invalid leadership transfer
|
||||||
|
nonMember := createMockEntity(4, "NonMember", true)
|
||||||
|
err = group.MakeLeader(nonMember)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected failure when making non-member leader")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupOptions tests group options management
|
||||||
|
func TestGroupOptions(t *testing.T) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
// Test default options
|
||||||
|
options := group.GetGroupOptions()
|
||||||
|
if options.LootMethod != LOOT_METHOD_ROUND_ROBIN {
|
||||||
|
t.Errorf("Default loot method = %d, want %d", options.LootMethod, LOOT_METHOD_ROUND_ROBIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test setting options
|
||||||
|
newOptions := GroupOptions{
|
||||||
|
LootMethod: LOOT_METHOD_NEED_BEFORE_GREED,
|
||||||
|
LootItemsRarity: LOOT_RARITY_RARE,
|
||||||
|
AutoSplit: AUTO_SPLIT_ENABLED,
|
||||||
|
GroupLockMethod: LOCK_METHOD_INVITE_ONLY,
|
||||||
|
}
|
||||||
|
|
||||||
|
group.SetGroupOptions(&newOptions)
|
||||||
|
|
||||||
|
options = group.GetGroupOptions()
|
||||||
|
if options.LootMethod != LOOT_METHOD_NEED_BEFORE_GREED {
|
||||||
|
t.Errorf("Updated loot method = %d, want %d", options.LootMethod, LOOT_METHOD_NEED_BEFORE_GREED)
|
||||||
|
}
|
||||||
|
if options.AutoSplit != AUTO_SPLIT_ENABLED {
|
||||||
|
t.Errorf("Updated auto split = %d, want %d", options.AutoSplit, AUTO_SPLIT_ENABLED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupRaidFunctionality tests raid-related functionality
|
||||||
|
func TestGroupRaidFunctionality(t *testing.T) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
// Initially not a raid
|
||||||
|
if group.IsGroupRaid() {
|
||||||
|
t.Error("New group should not be a raid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add raid groups
|
||||||
|
raidGroups := []int32{1, 2, 3, 4}
|
||||||
|
group.ReplaceRaidGroups(raidGroups)
|
||||||
|
|
||||||
|
if !group.IsGroupRaid() {
|
||||||
|
t.Error("Group should be a raid after setting raid groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test raid group retrieval
|
||||||
|
retrievedGroups := group.GetRaidGroups()
|
||||||
|
if len(retrievedGroups) != len(raidGroups) {
|
||||||
|
t.Errorf("Retrieved raid groups length = %d, want %d", len(retrievedGroups), len(raidGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear raid
|
||||||
|
group.ClearGroupRaid()
|
||||||
|
if group.IsGroupRaid() {
|
||||||
|
t.Error("Group should not be a raid after clearing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupConcurrency tests concurrent access to group operations
|
||||||
|
func TestGroupConcurrency(t *testing.T) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
const numGoroutines = 100
|
||||||
|
const operationsPerGoroutine = 100
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Test concurrent member additions and removals
|
||||||
|
t.Run("ConcurrentMemberOperations", func(t *testing.T) {
|
||||||
|
// Add initial members
|
||||||
|
for i := 0; i < MAX_GROUP_SIZE-1; i++ {
|
||||||
|
member := createMockEntity(int32(i+1), fmt.Sprintf("Member%d", i+1), true)
|
||||||
|
group.AddMember(member, i == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
member := createMockEntity(int32(100+goroutineID), fmt.Sprintf("Temp%d", goroutineID), true)
|
||||||
|
|
||||||
|
for j := range operationsPerGoroutine {
|
||||||
|
if j%2 == 0 {
|
||||||
|
// Try to add member (will mostly fail due to full group)
|
||||||
|
_ = group.AddMember(member, false)
|
||||||
|
} else {
|
||||||
|
// Remove and re-add existing member
|
||||||
|
members := group.GetMembers()
|
||||||
|
if len(members) > 0 {
|
||||||
|
memberIdx := goroutineID % len(members)
|
||||||
|
existingMember := members[memberIdx]
|
||||||
|
_ = group.RemoveMember(existingMember.Member)
|
||||||
|
_ = group.AddMember(existingMember.Member, existingMember.Leader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test concurrent option updates
|
||||||
|
t.Run("ConcurrentOptionUpdates", func(t *testing.T) {
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := range operationsPerGoroutine {
|
||||||
|
if j%2 == 0 {
|
||||||
|
// Read options
|
||||||
|
_ = group.GetGroupOptions()
|
||||||
|
} else {
|
||||||
|
// Write options
|
||||||
|
options := GroupOptions{
|
||||||
|
LootMethod: int8(goroutineID % 4),
|
||||||
|
LootItemsRarity: int8(goroutineID % 5),
|
||||||
|
AutoSplit: int8(goroutineID % 2),
|
||||||
|
}
|
||||||
|
group.SetGroupOptions(&options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test concurrent raid operations
|
||||||
|
t.Run("ConcurrentRaidOperations", func(t *testing.T) {
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := range operationsPerGoroutine {
|
||||||
|
switch j % 4 {
|
||||||
|
case 0:
|
||||||
|
_ = group.IsGroupRaid()
|
||||||
|
case 1:
|
||||||
|
_ = group.GetRaidGroups()
|
||||||
|
case 2:
|
||||||
|
raidGroups := []int32{int32(goroutineID%4 + 1)}
|
||||||
|
group.ReplaceRaidGroups(raidGroups)
|
||||||
|
case 3:
|
||||||
|
group.ClearGroupRaid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test concurrent member info updates
|
||||||
|
t.Run("ConcurrentMemberInfoUpdates", func(t *testing.T) {
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for range operationsPerGoroutine {
|
||||||
|
members := group.GetMembers()
|
||||||
|
if len(members) > 0 {
|
||||||
|
// Update member stats
|
||||||
|
memberIdx := goroutineID % len(members)
|
||||||
|
members[memberIdx].UpdateStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupManagerCreation tests group manager creation
|
||||||
|
func TestGroupManagerCreation(t *testing.T) {
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 30 * time.Second,
|
||||||
|
UpdateInterval: 1 * time.Second,
|
||||||
|
BuffUpdateInterval: 5 * time.Second,
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
if manager == nil {
|
||||||
|
t.Fatal("NewGroupManager returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := manager.GetStats()
|
||||||
|
if stats.ActiveGroups != 0 {
|
||||||
|
t.Errorf("Initial active groups = %d, want 0", stats.ActiveGroups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupManagerGroupOperations tests group operations through manager
|
||||||
|
func TestGroupManagerGroupOperations(t *testing.T) {
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 30 * time.Second,
|
||||||
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: false, // Disable statistics for testing
|
||||||
|
}
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
leader := createMockEntity(1, "Leader", true)
|
||||||
|
member1 := createMockEntity(2, "Member1", true)
|
||||||
|
member2 := createMockEntity(3, "Member2", true)
|
||||||
|
|
||||||
|
// Create group
|
||||||
|
groupID, err := manager.NewGroup(leader, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create group: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupID <= 0 {
|
||||||
|
t.Errorf("Invalid group ID: %d", groupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add members
|
||||||
|
err = manager.AddGroupMember(groupID, member1, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add member1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.AddGroupMember(groupID, member2, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to add member2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check group size
|
||||||
|
size := manager.GetGroupSize(groupID)
|
||||||
|
if size != 3 {
|
||||||
|
t.Errorf("Group size = %d, want 3", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test member checks
|
||||||
|
if !manager.IsInGroup(groupID, leader) {
|
||||||
|
t.Error("Leader should be in group")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !manager.IsInGroup(groupID, member1) {
|
||||||
|
t.Error("Member1 should be in group")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove member
|
||||||
|
err = manager.RemoveGroupMember(groupID, member1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove member1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.IsInGroup(groupID, member1) {
|
||||||
|
t.Error("Member1 should not be in group after removal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove group
|
||||||
|
err = manager.RemoveGroup(groupID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to remove group: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manager.IsGroupIDValid(groupID) {
|
||||||
|
t.Error("Group should not be valid after removal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupManagerInvitations tests invitation system
|
||||||
|
func TestGroupManagerInvitations(t *testing.T) {
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 2 * time.Second, // Short timeout for testing
|
||||||
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: false, // Disable statistics for testing
|
||||||
|
}
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
leader := createMockEntity(1, "Leader", true)
|
||||||
|
member := createMockEntity(2, "Member", true)
|
||||||
|
|
||||||
|
// Create group
|
||||||
|
groupID, _ := manager.NewGroup(leader, nil, 0)
|
||||||
|
|
||||||
|
// Send invitation
|
||||||
|
result := manager.Invite(leader, member)
|
||||||
|
if result != GROUP_INVITE_SUCCESS {
|
||||||
|
t.Errorf("Invite result = %d, want %d", result, GROUP_INVITE_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pending invite
|
||||||
|
inviterName := manager.HasPendingInvite(member)
|
||||||
|
if inviterName != "Leader" {
|
||||||
|
t.Errorf("Pending invite from = %s, want Leader", inviterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept invitation (will fail due to missing leader lookup, but that's expected in tests)
|
||||||
|
acceptResult := manager.AcceptInvite(member, nil, true)
|
||||||
|
if acceptResult != GROUP_INVITE_TARGET_NOT_FOUND {
|
||||||
|
t.Logf("Accept invite result = %d (expected due to missing leader lookup in test)", acceptResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since invite acceptance failed due to missing world integration,
|
||||||
|
// let's manually add the member to test the group functionality
|
||||||
|
err := manager.AddGroupMember(groupID, member, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to manually add member: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify member is in group
|
||||||
|
if !manager.IsInGroup(groupID, member) {
|
||||||
|
t.Error("Member should be in group after adding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test invitation timeout
|
||||||
|
member2 := createMockEntity(3, "Member2", true)
|
||||||
|
manager.Invite(leader, member2)
|
||||||
|
|
||||||
|
// Wait for timeout
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// Try to accept after timeout (will fail due to missing leader lookup,
|
||||||
|
// but we're mainly testing that the invite was cleaned up)
|
||||||
|
acceptResult = manager.AcceptInvite(member2, nil, true)
|
||||||
|
if acceptResult == GROUP_INVITE_SUCCESS {
|
||||||
|
t.Error("Should not be able to accept expired invitation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the invite was cleaned up by checking it no longer exists
|
||||||
|
if manager.HasPendingInvite(member2) != "" {
|
||||||
|
t.Error("Expired invitation should have been cleaned up")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGroupManagerConcurrency tests concurrent manager operations
|
||||||
|
func TestGroupManagerConcurrency(t *testing.T) {
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 30 * time.Second,
|
||||||
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: false, // Disable statistics for testing
|
||||||
|
}
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
const numGoroutines = 50
|
||||||
|
const groupsPerGoroutine = 10
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Test concurrent group creation and removal
|
||||||
|
t.Run("ConcurrentGroupCreation", func(t *testing.T) {
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := range groupsPerGoroutine {
|
||||||
|
leader := createMockEntity(int32(goroutineID*1000+j), fmt.Sprintf("Leader%d_%d", goroutineID, j), true)
|
||||||
|
|
||||||
|
// Create group
|
||||||
|
groupID, err := manager.NewGroup(leader, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some members
|
||||||
|
for k := range 3 {
|
||||||
|
member := createMockEntity(int32(goroutineID*1000+j*10+k), fmt.Sprintf("Member%d_%d_%d", goroutineID, j, k), true)
|
||||||
|
_ = manager.AddGroupMember(groupID, member, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes remove the group
|
||||||
|
if j%2 == 0 {
|
||||||
|
_ = manager.RemoveGroup(groupID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test concurrent invitations
|
||||||
|
t.Run("ConcurrentInvitations", func(t *testing.T) {
|
||||||
|
// Create some groups
|
||||||
|
groups := make([]int32, 10)
|
||||||
|
leaders := make([]*mockEntity, 10)
|
||||||
|
|
||||||
|
for i := range 10 {
|
||||||
|
leader := createMockEntity(int32(10000+i), fmt.Sprintf("InviteLeader%d", i), true)
|
||||||
|
leaders[i] = leader
|
||||||
|
groupID, _ := manager.NewGroup(leader, nil, 0)
|
||||||
|
groups[i] = groupID
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := range 100 {
|
||||||
|
leaderIdx := goroutineID % len(leaders)
|
||||||
|
leader := leaders[leaderIdx]
|
||||||
|
|
||||||
|
member := createMockEntity(int32(20000+goroutineID*100+j), fmt.Sprintf("InviteMember%d_%d", goroutineID, j), true)
|
||||||
|
|
||||||
|
// Send invite
|
||||||
|
_ = manager.Invite(leader, member)
|
||||||
|
|
||||||
|
// Sometimes accept, sometimes decline
|
||||||
|
if j%3 == 0 {
|
||||||
|
_ = manager.AcceptInvite(member, nil, false)
|
||||||
|
} else if j%3 == 1 {
|
||||||
|
manager.DeclineInvite(member)
|
||||||
|
}
|
||||||
|
// Otherwise let it expire
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Cleanup groups
|
||||||
|
for _, groupID := range groups {
|
||||||
|
_ = manager.RemoveGroup(groupID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test concurrent statistics updates
|
||||||
|
t.Run("ConcurrentStatistics", func(t *testing.T) {
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for range 1000 {
|
||||||
|
_ = manager.GetStats()
|
||||||
|
_ = manager.GetGroupCount()
|
||||||
|
_ = manager.GetAllGroups()
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRaceConditions tests for race conditions with -race flag
|
||||||
|
func TestRaceConditions(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping race condition test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 30 * time.Second,
|
||||||
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: false, // Disable statistics for testing
|
||||||
|
}
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
const numGoroutines = 100
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Create a shared group
|
||||||
|
leader := createMockEntity(1, "RaceLeader", true)
|
||||||
|
groupID, _ := manager.NewGroup(leader, nil, 0)
|
||||||
|
|
||||||
|
// Add some initial members
|
||||||
|
for i := range 5 {
|
||||||
|
member := createMockEntity(int32(i+2), fmt.Sprintf("RaceMember%d", i+1), true)
|
||||||
|
_ = manager.AddGroupMember(groupID, member, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
|
for i := range numGoroutines {
|
||||||
|
go func(goroutineID int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
for j := range 50 {
|
||||||
|
switch j % 10 {
|
||||||
|
case 0:
|
||||||
|
// Get group
|
||||||
|
_ = manager.GetGroup(groupID)
|
||||||
|
case 1:
|
||||||
|
// Get size
|
||||||
|
_ = manager.GetGroupSize(groupID)
|
||||||
|
case 2:
|
||||||
|
// Check membership
|
||||||
|
_ = manager.IsInGroup(groupID, leader)
|
||||||
|
case 3:
|
||||||
|
// Get leader
|
||||||
|
_ = manager.GetGroupLeader(groupID)
|
||||||
|
case 4:
|
||||||
|
// Send message
|
||||||
|
manager.SimpleGroupMessage(groupID, fmt.Sprintf("Message %d", goroutineID))
|
||||||
|
case 5:
|
||||||
|
// Update options
|
||||||
|
options := DefaultGroupOptions()
|
||||||
|
options.LootMethod = int8(goroutineID % 4)
|
||||||
|
_ = manager.SetGroupOptions(groupID, &options)
|
||||||
|
case 6:
|
||||||
|
// Get options
|
||||||
|
_, _ = manager.GetDefaultGroupOptions(groupID)
|
||||||
|
case 7:
|
||||||
|
// Send group update
|
||||||
|
manager.SendGroupUpdate(groupID, nil, false)
|
||||||
|
case 8:
|
||||||
|
// Check raid status
|
||||||
|
_ = manager.IsInRaidGroup(groupID, groupID+1, false)
|
||||||
|
case 9:
|
||||||
|
// Get stats
|
||||||
|
_ = manager.GetStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark tests
|
||||||
|
func BenchmarkGroupOperations(b *testing.B) {
|
||||||
|
b.Run("GroupCreation", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
group := NewGroup(int32(i), nil)
|
||||||
|
group.Disband()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("MemberAddition", func(b *testing.B) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
member := createMockEntity(int32(i), fmt.Sprintf("Member%d", i), true)
|
||||||
|
_ = group.AddMember(member, false)
|
||||||
|
_ = group.RemoveMember(member)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("ConcurrentMemberAccess", func(b *testing.B) {
|
||||||
|
group := NewGroup(1, nil)
|
||||||
|
defer group.Disband()
|
||||||
|
|
||||||
|
// Add some members
|
||||||
|
for i := range MAX_GROUP_SIZE {
|
||||||
|
member := createMockEntity(int32(i+1), fmt.Sprintf("Member%d", i+1), true)
|
||||||
|
group.AddMember(member, i == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_ = group.GetMembers()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("ManagerGroupLookup", func(b *testing.B) {
|
||||||
|
config := GroupManagerConfig{
|
||||||
|
MaxGroups: 1000,
|
||||||
|
MaxRaidGroups: 4,
|
||||||
|
InviteTimeout: 30 * time.Second,
|
||||||
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
|
EnableCrossServer: true,
|
||||||
|
EnableRaids: true,
|
||||||
|
EnableQuestSharing: true,
|
||||||
|
EnableStatistics: false, // Disable statistics for testing
|
||||||
|
}
|
||||||
|
manager := NewGroupManager(config)
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// Create some groups
|
||||||
|
for i := range 100 {
|
||||||
|
leader := createMockEntity(int32(i+1), fmt.Sprintf("Leader%d", i+1), true)
|
||||||
|
manager.NewGroup(leader, nil, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
i := 0
|
||||||
|
for pb.Next() {
|
||||||
|
groupID := int32((i % 100) + 1)
|
||||||
|
_ = manager.GetGroup(groupID)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package groups
|
package groups
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"eq2emu/internal/entity"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,41 +25,41 @@ type GroupAware interface {
|
|||||||
// GroupManager interface for managing groups
|
// GroupManager interface for managing groups
|
||||||
type GroupManagerInterface interface {
|
type GroupManagerInterface interface {
|
||||||
// Group creation and management
|
// Group creation and management
|
||||||
NewGroup(leader entity.Entity, options *GroupOptions, overrideGroupID int32) (int32, error)
|
NewGroup(leader Entity, options *GroupOptions, overrideGroupID int32) (int32, error)
|
||||||
RemoveGroup(groupID int32) error
|
RemoveGroup(groupID int32) error
|
||||||
GetGroup(groupID int32) *Group
|
GetGroup(groupID int32) *Group
|
||||||
IsGroupIDValid(groupID int32) bool
|
IsGroupIDValid(groupID int32) bool
|
||||||
|
|
||||||
// Member management
|
// Member management
|
||||||
AddGroupMember(groupID int32, member entity.Entity, isLeader bool) error
|
AddGroupMember(groupID int32, member Entity, isLeader bool) error
|
||||||
AddGroupMemberFromPeer(groupID int32, info *GroupMemberInfo) error
|
AddGroupMemberFromPeer(groupID int32, info *GroupMemberInfo) error
|
||||||
RemoveGroupMember(groupID int32, member entity.Entity) error
|
RemoveGroupMember(groupID int32, member Entity) error
|
||||||
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
|
RemoveGroupMemberByName(groupID int32, name string, isClient bool, charID int32) error
|
||||||
|
|
||||||
// Group updates
|
// Group updates
|
||||||
SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool)
|
SendGroupUpdate(groupID int32, excludeClient any, forceRaidUpdate bool)
|
||||||
|
|
||||||
// Invitations
|
// Invitations
|
||||||
Invite(leader entity.Entity, member entity.Entity) int8
|
Invite(leader Entity, member Entity) int8
|
||||||
AddInvite(leader entity.Entity, member entity.Entity) bool
|
AddInvite(leader Entity, member Entity) bool
|
||||||
AcceptInvite(member entity.Entity, groupOverrideID *int32, autoAddGroup bool) int8
|
AcceptInvite(member Entity, groupOverrideID *int32, autoAddGroup bool) int8
|
||||||
DeclineInvite(member entity.Entity)
|
DeclineInvite(member Entity)
|
||||||
ClearPendingInvite(member entity.Entity)
|
ClearPendingInvite(member Entity)
|
||||||
HasPendingInvite(member entity.Entity) string
|
HasPendingInvite(member Entity) string
|
||||||
|
|
||||||
// Group utilities
|
// Group utilities
|
||||||
GetGroupSize(groupID int32) int32
|
GetGroupSize(groupID int32) int32
|
||||||
IsInGroup(groupID int32, member entity.Entity) bool
|
IsInGroup(groupID int32, member Entity) bool
|
||||||
IsPlayerInGroup(groupID int32, charID int32) entity.Entity
|
IsPlayerInGroup(groupID int32, charID int32) Entity
|
||||||
IsSpawnInGroup(groupID int32, name string) bool
|
IsSpawnInGroup(groupID int32, name string) bool
|
||||||
GetGroupLeader(groupID int32) entity.Entity
|
GetGroupLeader(groupID int32) Entity
|
||||||
MakeLeader(groupID int32, newLeader entity.Entity) bool
|
MakeLeader(groupID int32, newLeader Entity) bool
|
||||||
|
|
||||||
// Messaging
|
// Messaging
|
||||||
SimpleGroupMessage(groupID int32, message string)
|
SimpleGroupMessage(groupID int32, message string)
|
||||||
SendGroupMessage(groupID int32, msgType int8, message string)
|
SendGroupMessage(groupID int32, msgType int8, message string)
|
||||||
GroupMessage(groupID int32, message string)
|
GroupMessage(groupID int32, message string)
|
||||||
GroupChatMessage(groupID int32, from entity.Entity, language int32, message string, channel int16)
|
GroupChatMessage(groupID int32, from Entity, language int32, message string, channel int16)
|
||||||
GroupChatMessageFromName(groupID int32, fromName string, language int32, message string, channel int16)
|
GroupChatMessageFromName(groupID int32, fromName string, language int32, message string, channel int16)
|
||||||
SendGroupChatMessage(groupID int32, channel int16, message string)
|
SendGroupChatMessage(groupID int32, channel int16, message string)
|
||||||
|
|
||||||
@ -84,17 +83,17 @@ type GroupManagerInterface interface {
|
|||||||
// GroupEventHandler interface for handling group events
|
// GroupEventHandler interface for handling group events
|
||||||
type GroupEventHandler interface {
|
type GroupEventHandler interface {
|
||||||
// Group lifecycle events
|
// Group lifecycle events
|
||||||
OnGroupCreated(group *Group, leader entity.Entity) error
|
OnGroupCreated(group *Group, leader Entity) error
|
||||||
OnGroupDisbanded(group *Group) error
|
OnGroupDisbanded(group *Group) error
|
||||||
OnGroupMemberJoined(group *Group, member entity.Entity) error
|
OnGroupMemberJoined(group *Group, member Entity) error
|
||||||
OnGroupMemberLeft(group *Group, member entity.Entity) error
|
OnGroupMemberLeft(group *Group, member Entity) error
|
||||||
OnGroupLeaderChanged(group *Group, oldLeader, newLeader entity.Entity) error
|
OnGroupLeaderChanged(group *Group, oldLeader, newLeader Entity) error
|
||||||
|
|
||||||
// Invitation events
|
// Invitation events
|
||||||
OnGroupInviteSent(leader, member entity.Entity) error
|
OnGroupInviteSent(leader, member Entity) error
|
||||||
OnGroupInviteAccepted(leader, member entity.Entity, groupID int32) error
|
OnGroupInviteAccepted(leader, member Entity, groupID int32) error
|
||||||
OnGroupInviteDeclined(leader, member entity.Entity) error
|
OnGroupInviteDeclined(leader, member Entity) error
|
||||||
OnGroupInviteExpired(leader, member entity.Entity) error
|
OnGroupInviteExpired(leader, member Entity) error
|
||||||
|
|
||||||
// Raid events
|
// Raid events
|
||||||
OnRaidFormed(groups []*Group) error
|
OnRaidFormed(groups []*Group) error
|
||||||
@ -104,7 +103,7 @@ type GroupEventHandler interface {
|
|||||||
OnRaidInviteDeclined(leaderGroup *Group, targetGroup *Group) error
|
OnRaidInviteDeclined(leaderGroup *Group, targetGroup *Group) error
|
||||||
|
|
||||||
// Group activity events
|
// Group activity events
|
||||||
OnGroupMessage(group *Group, from entity.Entity, message string, channel int16) error
|
OnGroupMessage(group *Group, from Entity, message string, channel int16) error
|
||||||
OnGroupOptionsChanged(group *Group, oldOptions, newOptions *GroupOptions) error
|
OnGroupOptionsChanged(group *Group, oldOptions, newOptions *GroupOptions) error
|
||||||
OnGroupMemberUpdate(group *Group, member *GroupMemberInfo) error
|
OnGroupMemberUpdate(group *Group, member *GroupMemberInfo) error
|
||||||
}
|
}
|
||||||
@ -146,8 +145,8 @@ type GroupPacketHandler interface {
|
|||||||
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient any) error
|
SendGroupOptionsUpdate(groupID int32, options *GroupOptions, excludeClient any) error
|
||||||
|
|
||||||
// Group invitation packets
|
// Group invitation packets
|
||||||
SendGroupInvite(inviter, invitee entity.Entity) error
|
SendGroupInvite(inviter, invitee Entity) error
|
||||||
SendGroupInviteResponse(inviter, invitee entity.Entity, accepted bool) error
|
SendGroupInviteResponse(inviter, invitee Entity, accepted bool) error
|
||||||
|
|
||||||
// Group messaging packets
|
// Group messaging packets
|
||||||
SendGroupMessage(members []*GroupMemberInfo, message *GroupMessage) error
|
SendGroupMessage(members []*GroupMemberInfo, message *GroupMessage) error
|
||||||
@ -170,18 +169,18 @@ type GroupPacketHandler interface {
|
|||||||
// GroupValidator interface for validating group operations
|
// GroupValidator interface for validating group operations
|
||||||
type GroupValidator interface {
|
type GroupValidator interface {
|
||||||
// Group creation validation
|
// Group creation validation
|
||||||
ValidateGroupCreation(leader entity.Entity, options *GroupOptions) error
|
ValidateGroupCreation(leader Entity, options *GroupOptions) error
|
||||||
ValidateGroupJoin(group *Group, member entity.Entity) error
|
ValidateGroupJoin(group *Group, member Entity) error
|
||||||
ValidateGroupLeave(group *Group, member entity.Entity) error
|
ValidateGroupLeave(group *Group, member Entity) error
|
||||||
|
|
||||||
// Invitation validation
|
// Invitation validation
|
||||||
ValidateGroupInvite(leader, member entity.Entity) error
|
ValidateGroupInvite(leader, member Entity) error
|
||||||
ValidateRaidInvite(leaderGroup, targetGroup *Group) error
|
ValidateRaidInvite(leaderGroup, targetGroup *Group) error
|
||||||
|
|
||||||
// Group operation validation
|
// Group operation validation
|
||||||
ValidateLeadershipChange(group *Group, oldLeader, newLeader entity.Entity) error
|
ValidateLeadershipChange(group *Group, oldLeader, newLeader Entity) error
|
||||||
ValidateGroupOptions(group *Group, options *GroupOptions) error
|
ValidateGroupOptions(group *Group, options *GroupOptions) error
|
||||||
ValidateGroupMessage(group *Group, from entity.Entity, message string) error
|
ValidateGroupMessage(group *Group, from Entity, message string) error
|
||||||
|
|
||||||
// Raid validation
|
// Raid validation
|
||||||
ValidateRaidFormation(groups []*Group) error
|
ValidateRaidFormation(groups []*Group) error
|
||||||
@ -191,18 +190,18 @@ type GroupValidator interface {
|
|||||||
// GroupNotifier interface for sending notifications
|
// GroupNotifier interface for sending notifications
|
||||||
type GroupNotifier interface {
|
type GroupNotifier interface {
|
||||||
// Group notifications
|
// Group notifications
|
||||||
NotifyGroupCreated(group *Group, leader entity.Entity) error
|
NotifyGroupCreated(group *Group, leader Entity) error
|
||||||
NotifyGroupDisbanded(group *Group, reason string) error
|
NotifyGroupDisbanded(group *Group, reason string) error
|
||||||
NotifyGroupMemberJoined(group *Group, member entity.Entity) error
|
NotifyGroupMemberJoined(group *Group, member Entity) error
|
||||||
NotifyGroupMemberLeft(group *Group, member entity.Entity, reason string) error
|
NotifyGroupMemberLeft(group *Group, member Entity, reason string) error
|
||||||
NotifyGroupLeaderChanged(group *Group, oldLeader, newLeader entity.Entity) error
|
NotifyGroupLeaderChanged(group *Group, oldLeader, newLeader Entity) error
|
||||||
|
|
||||||
// Invitation notifications
|
// Invitation notifications
|
||||||
NotifyGroupInviteSent(leader, member entity.Entity) error
|
NotifyGroupInviteSent(leader, member Entity) error
|
||||||
NotifyGroupInviteReceived(leader, member entity.Entity) error
|
NotifyGroupInviteReceived(leader, member Entity) error
|
||||||
NotifyGroupInviteAccepted(leader, member entity.Entity, groupID int32) error
|
NotifyGroupInviteAccepted(leader, member Entity, groupID int32) error
|
||||||
NotifyGroupInviteDeclined(leader, member entity.Entity) error
|
NotifyGroupInviteDeclined(leader, member Entity) error
|
||||||
NotifyGroupInviteExpired(leader, member entity.Entity) error
|
NotifyGroupInviteExpired(leader, member Entity) error
|
||||||
|
|
||||||
// Raid notifications
|
// Raid notifications
|
||||||
NotifyRaidFormed(groups []*Group) error
|
NotifyRaidFormed(groups []*Group) error
|
||||||
@ -220,23 +219,23 @@ type GroupNotifier interface {
|
|||||||
// GroupStatistics interface for tracking group statistics
|
// GroupStatistics interface for tracking group statistics
|
||||||
type GroupStatistics interface {
|
type GroupStatistics interface {
|
||||||
// Group statistics
|
// Group statistics
|
||||||
RecordGroupCreated(group *Group, leader entity.Entity)
|
RecordGroupCreated(group *Group, leader Entity)
|
||||||
RecordGroupDisbanded(group *Group, duration int64)
|
RecordGroupDisbanded(group *Group, duration int64)
|
||||||
RecordGroupMemberJoined(group *Group, member entity.Entity)
|
RecordGroupMemberJoined(group *Group, member Entity)
|
||||||
RecordGroupMemberLeft(group *Group, member entity.Entity, duration int64)
|
RecordGroupMemberLeft(group *Group, member Entity, duration int64)
|
||||||
|
|
||||||
// Invitation statistics
|
// Invitation statistics
|
||||||
RecordInviteSent(leader, member entity.Entity)
|
RecordInviteSent(leader, member Entity)
|
||||||
RecordInviteAccepted(leader, member entity.Entity, responseTime int64)
|
RecordInviteAccepted(leader, member Entity, responseTime int64)
|
||||||
RecordInviteDeclined(leader, member entity.Entity, responseTime int64)
|
RecordInviteDeclined(leader, member Entity, responseTime int64)
|
||||||
RecordInviteExpired(leader, member entity.Entity)
|
RecordInviteExpired(leader, member Entity)
|
||||||
|
|
||||||
// Raid statistics
|
// Raid statistics
|
||||||
RecordRaidFormed(groups []*Group)
|
RecordRaidFormed(groups []*Group)
|
||||||
RecordRaidDisbanded(groups []*Group, duration int64)
|
RecordRaidDisbanded(groups []*Group, duration int64)
|
||||||
|
|
||||||
// Activity statistics
|
// Activity statistics
|
||||||
RecordGroupMessage(group *Group, from entity.Entity, messageType int8)
|
RecordGroupMessage(group *Group, from Entity, messageType int8)
|
||||||
RecordGroupActivity(group *Group, activityType string)
|
RecordGroupActivity(group *Group, activityType string)
|
||||||
|
|
||||||
// Performance statistics
|
// Performance statistics
|
||||||
@ -280,7 +279,7 @@ func (ga *GroupAdapter) GetMembers() []*GroupMemberInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetLeader returns the group leader
|
// GetLeader returns the group leader
|
||||||
func (ga *GroupAdapter) GetLeader() entity.Entity {
|
func (ga *GroupAdapter) GetLeader() Entity {
|
||||||
members := ga.group.GetMembers()
|
members := ga.group.GetMembers()
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
if member.Leader {
|
if member.Leader {
|
||||||
@ -306,7 +305,7 @@ func (ga *GroupAdapter) GetRaidGroups() []int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsMember checks if an entity is a member of the group
|
// IsMember checks if an entity is a member of the group
|
||||||
func (ga *GroupAdapter) IsMember(entity entity.Entity) bool {
|
func (ga *GroupAdapter) IsMember(entity Entity) bool {
|
||||||
if entity == nil {
|
if entity == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -343,7 +342,7 @@ func (ga *GroupAdapter) GetMemberByName(name string) *GroupMemberInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMemberByEntity returns a member by entity
|
// GetMemberByEntity returns a member by entity
|
||||||
func (ga *GroupAdapter) GetMemberByEntity(entity entity.Entity) *GroupMemberInfo {
|
func (ga *GroupAdapter) GetMemberByEntity(entity Entity) *GroupMemberInfo {
|
||||||
if entity == nil {
|
if entity == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -358,7 +357,7 @@ func (ga *GroupAdapter) GetMemberByEntity(entity entity.Entity) *GroupMemberInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsLeader checks if an entity is the group leader
|
// IsLeader checks if an entity is the group leader
|
||||||
func (ga *GroupAdapter) IsLeader(entity entity.Entity) bool {
|
func (ga *GroupAdapter) IsLeader(entity Entity) bool {
|
||||||
if entity == nil {
|
if entity == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -394,16 +393,16 @@ func (ga *GroupAdapter) GetLastActivity() time.Time {
|
|||||||
|
|
||||||
// EntityGroupAdapter adapts entity functionality for group systems
|
// EntityGroupAdapter adapts entity functionality for group systems
|
||||||
type EntityGroupAdapter struct {
|
type EntityGroupAdapter struct {
|
||||||
entity entity.Entity
|
entity Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEntityGroupAdapter creates a new entity group adapter
|
// NewEntityGroupAdapter creates a new entity group adapter
|
||||||
func NewEntityGroupAdapter(entity entity.Entity) *EntityGroupAdapter {
|
func NewEntityGroupAdapter(entity Entity) *EntityGroupAdapter {
|
||||||
return &EntityGroupAdapter{entity: entity}
|
return &EntityGroupAdapter{entity: entity}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEntity returns the wrapped entity
|
// GetEntity returns the wrapped entity
|
||||||
func (ega *EntityGroupAdapter) GetEntity() entity.Entity {
|
func (ega *EntityGroupAdapter) GetEntity() Entity {
|
||||||
return ega.entity
|
return ega.entity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,6 +496,6 @@ func (ega *EntityGroupAdapter) IsDead() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDistance returns distance to another entity
|
// GetDistance returns distance to another entity
|
||||||
func (ega *EntityGroupAdapter) GetDistance(other entity.Entity) float32 {
|
func (ega *EntityGroupAdapter) GetDistance(other Entity) float32 {
|
||||||
return ega.entity.GetDistance(&other.Spawn)
|
return ega.entity.GetDistance(other)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package groups
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eq2emu/internal/entity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewGroupManager creates a new group manager with the given configuration
|
// NewGroupManager creates a new group manager with the given configuration
|
||||||
@ -54,7 +52,7 @@ func (gm *GroupManager) Stop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGroup creates a new group with the given leader and options
|
// NewGroup creates a new group with the given leader and options
|
||||||
func (gm *GroupManager) NewGroup(leader entity.Entity, options *GroupOptions, overrideGroupID int32) (int32, error) {
|
func (gm *GroupManager) NewGroup(leader Entity, options *GroupOptions, overrideGroupID int32) (int32, error) {
|
||||||
if leader == nil {
|
if leader == nil {
|
||||||
return 0, fmt.Errorf("leader cannot be nil")
|
return 0, fmt.Errorf("leader cannot be nil")
|
||||||
}
|
}
|
||||||
@ -138,7 +136,7 @@ func (gm *GroupManager) IsGroupIDValid(groupID int32) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddGroupMember adds a member to an existing group
|
// AddGroupMember adds a member to an existing group
|
||||||
func (gm *GroupManager) AddGroupMember(groupID int32, member entity.Entity, isLeader bool) error {
|
func (gm *GroupManager) AddGroupMember(groupID int32, member Entity, isLeader bool) error {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return fmt.Errorf("group %d not found", groupID)
|
return fmt.Errorf("group %d not found", groupID)
|
||||||
@ -164,7 +162,7 @@ func (gm *GroupManager) AddGroupMemberFromPeer(groupID int32, info *GroupMemberI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveGroupMember removes a member from a group
|
// RemoveGroupMember removes a member from a group
|
||||||
func (gm *GroupManager) RemoveGroupMember(groupID int32, member entity.Entity) error {
|
func (gm *GroupManager) RemoveGroupMember(groupID int32, member Entity) error {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return fmt.Errorf("group %d not found", groupID)
|
return fmt.Errorf("group %d not found", groupID)
|
||||||
@ -214,7 +212,7 @@ func (gm *GroupManager) SendGroupUpdate(groupID int32, excludeClient any, forceR
|
|||||||
// Group invitation handling
|
// Group invitation handling
|
||||||
|
|
||||||
// Invite handles inviting a player to a group
|
// Invite handles inviting a player to a group
|
||||||
func (gm *GroupManager) Invite(leader entity.Entity, member entity.Entity) int8 {
|
func (gm *GroupManager) Invite(leader Entity, member Entity) int8 {
|
||||||
if leader == nil || member == nil {
|
if leader == nil || member == nil {
|
||||||
return GROUP_INVITE_TARGET_NOT_FOUND
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
||||||
}
|
}
|
||||||
@ -226,7 +224,7 @@ func (gm *GroupManager) Invite(leader entity.Entity, member entity.Entity) int8
|
|||||||
|
|
||||||
// Check if member already has an invite
|
// Check if member already has an invite
|
||||||
inviteKey := member.GetName()
|
inviteKey := member.GetName()
|
||||||
if gm.hasPendingInvite(inviteKey) {
|
if gm.hasPendingInvite(inviteKey) != "" {
|
||||||
return GROUP_INVITE_ALREADY_HAS_INVITE
|
return GROUP_INVITE_ALREADY_HAS_INVITE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,12 +246,12 @@ func (gm *GroupManager) Invite(leader entity.Entity, member entity.Entity) int8
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddInvite adds a group invitation
|
// AddInvite adds a group invitation
|
||||||
func (gm *GroupManager) AddInvite(leader entity.Entity, member entity.Entity) bool {
|
func (gm *GroupManager) AddInvite(leader Entity, member Entity) bool {
|
||||||
return gm.addInvite(leader, member)
|
return gm.addInvite(leader, member)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addInvite internal method to add an invitation
|
// addInvite internal method to add an invitation
|
||||||
func (gm *GroupManager) addInvite(leader entity.Entity, member entity.Entity) bool {
|
func (gm *GroupManager) addInvite(leader Entity, member Entity) bool {
|
||||||
if leader == nil || member == nil {
|
if leader == nil || member == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -281,7 +279,7 @@ func (gm *GroupManager) addInvite(leader entity.Entity, member entity.Entity) bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AcceptInvite handles accepting of a group invite
|
// AcceptInvite handles accepting of a group invite
|
||||||
func (gm *GroupManager) AcceptInvite(member entity.Entity, groupOverrideID *int32, autoAddGroup bool) int8 {
|
func (gm *GroupManager) AcceptInvite(member Entity, groupOverrideID *int32, autoAddGroup bool) int8 {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return GROUP_INVITE_TARGET_NOT_FOUND
|
return GROUP_INVITE_TARGET_NOT_FOUND
|
||||||
}
|
}
|
||||||
@ -312,7 +310,7 @@ func (gm *GroupManager) AcceptInvite(member entity.Entity, groupOverrideID *int3
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the leader
|
// Find the leader
|
||||||
var leader entity.Entity
|
var leader Entity
|
||||||
// TODO: Find leader entity by name
|
// TODO: Find leader entity by name
|
||||||
// leader = world.GetPlayerByName(invite.InviterName)
|
// leader = world.GetPlayerByName(invite.InviterName)
|
||||||
|
|
||||||
@ -361,7 +359,7 @@ func (gm *GroupManager) AcceptInvite(member entity.Entity, groupOverrideID *int3
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeclineInvite handles declining of a group invite
|
// DeclineInvite handles declining of a group invite
|
||||||
func (gm *GroupManager) DeclineInvite(member entity.Entity) {
|
func (gm *GroupManager) DeclineInvite(member Entity) {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -369,7 +367,7 @@ func (gm *GroupManager) DeclineInvite(member entity.Entity) {
|
|||||||
inviteKey := member.GetName()
|
inviteKey := member.GetName()
|
||||||
|
|
||||||
gm.invitesMutex.Lock()
|
gm.invitesMutex.Lock()
|
||||||
invite, exists := gm.pendingInvites[inviteKey]
|
_, exists := gm.pendingInvites[inviteKey]
|
||||||
if exists {
|
if exists {
|
||||||
delete(gm.pendingInvites, inviteKey)
|
delete(gm.pendingInvites, inviteKey)
|
||||||
}
|
}
|
||||||
@ -380,7 +378,7 @@ func (gm *GroupManager) DeclineInvite(member entity.Entity) {
|
|||||||
gm.updateStatsForDeclinedInvite()
|
gm.updateStatsForDeclinedInvite()
|
||||||
|
|
||||||
// Fire event
|
// Fire event
|
||||||
var leader entity.Entity
|
var leader Entity
|
||||||
// TODO: Find leader entity by name
|
// TODO: Find leader entity by name
|
||||||
// leader = world.GetPlayerByName(invite.InviterName)
|
// leader = world.GetPlayerByName(invite.InviterName)
|
||||||
gm.fireGroupInviteDeclinedEvent(leader, member)
|
gm.fireGroupInviteDeclinedEvent(leader, member)
|
||||||
@ -388,7 +386,7 @@ func (gm *GroupManager) DeclineInvite(member entity.Entity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClearPendingInvite clears a pending invite for a member
|
// ClearPendingInvite clears a pending invite for a member
|
||||||
func (gm *GroupManager) ClearPendingInvite(member entity.Entity) {
|
func (gm *GroupManager) ClearPendingInvite(member Entity) {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -401,7 +399,7 @@ func (gm *GroupManager) ClearPendingInvite(member entity.Entity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPendingInvite checks if a member has a pending invite and returns the inviter name
|
// HasPendingInvite checks if a member has a pending invite and returns the inviter name
|
||||||
func (gm *GroupManager) HasPendingInvite(member entity.Entity) string {
|
func (gm *GroupManager) HasPendingInvite(member Entity) string {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -436,7 +434,7 @@ func (gm *GroupManager) GetGroupSize(groupID int32) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsInGroup checks if an entity is in a specific group
|
// IsInGroup checks if an entity is in a specific group
|
||||||
func (gm *GroupManager) IsInGroup(groupID int32, member entity.Entity) bool {
|
func (gm *GroupManager) IsInGroup(groupID int32, member Entity) bool {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil || member == nil {
|
if group == nil || member == nil {
|
||||||
return false
|
return false
|
||||||
@ -453,7 +451,7 @@ func (gm *GroupManager) IsInGroup(groupID int32, member entity.Entity) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsPlayerInGroup checks if a player with the given character ID is in a group
|
// IsPlayerInGroup checks if a player with the given character ID is in a group
|
||||||
func (gm *GroupManager) IsPlayerInGroup(groupID int32, charID int32) entity.Entity {
|
func (gm *GroupManager) IsPlayerInGroup(groupID int32, charID int32) Entity {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -490,7 +488,7 @@ func (gm *GroupManager) IsSpawnInGroup(groupID int32, name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupLeader returns the leader of a group
|
// GetGroupLeader returns the leader of a group
|
||||||
func (gm *GroupManager) GetGroupLeader(groupID int32) entity.Entity {
|
func (gm *GroupManager) GetGroupLeader(groupID int32) Entity {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -507,7 +505,7 @@ func (gm *GroupManager) GetGroupLeader(groupID int32) entity.Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MakeLeader changes the leader of a group
|
// MakeLeader changes the leader of a group
|
||||||
func (gm *GroupManager) MakeLeader(groupID int32, newLeader entity.Entity) bool {
|
func (gm *GroupManager) MakeLeader(groupID int32, newLeader Entity) bool {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group == nil {
|
if group == nil {
|
||||||
return false
|
return false
|
||||||
@ -541,7 +539,7 @@ func (gm *GroupManager) GroupMessage(groupID int32, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GroupChatMessage sends a chat message from a member to the group
|
// GroupChatMessage sends a chat message from a member to the group
|
||||||
func (gm *GroupManager) GroupChatMessage(groupID int32, from entity.Entity, language int32, message string, channel int16) {
|
func (gm *GroupManager) GroupChatMessage(groupID int32, from Entity, language int32, message string, channel int16) {
|
||||||
group := gm.GetGroup(groupID)
|
group := gm.GetGroup(groupID)
|
||||||
if group != nil {
|
if group != nil {
|
||||||
group.GroupChatMessage(from, language, message, channel)
|
group.GroupChatMessage(from, language, message, channel)
|
||||||
@ -934,7 +932,7 @@ func (gm *GroupManager) SetNotifier(notifier GroupNotifier) {
|
|||||||
// Event firing methods
|
// Event firing methods
|
||||||
|
|
||||||
// fireGroupCreatedEvent fires a group created event
|
// fireGroupCreatedEvent fires a group created event
|
||||||
func (gm *GroupManager) fireGroupCreatedEvent(group *Group, leader entity.Entity) {
|
func (gm *GroupManager) fireGroupCreatedEvent(group *Group, leader Entity) {
|
||||||
gm.eventHandlersMutex.RLock()
|
gm.eventHandlersMutex.RLock()
|
||||||
defer gm.eventHandlersMutex.RUnlock()
|
defer gm.eventHandlersMutex.RUnlock()
|
||||||
|
|
||||||
@ -954,7 +952,7 @@ func (gm *GroupManager) fireGroupDisbandedEvent(group *Group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fireGroupInviteSentEvent fires a group invite sent event
|
// fireGroupInviteSentEvent fires a group invite sent event
|
||||||
func (gm *GroupManager) fireGroupInviteSentEvent(leader, member entity.Entity) {
|
func (gm *GroupManager) fireGroupInviteSentEvent(leader, member Entity) {
|
||||||
gm.eventHandlersMutex.RLock()
|
gm.eventHandlersMutex.RLock()
|
||||||
defer gm.eventHandlersMutex.RUnlock()
|
defer gm.eventHandlersMutex.RUnlock()
|
||||||
|
|
||||||
@ -964,7 +962,7 @@ func (gm *GroupManager) fireGroupInviteSentEvent(leader, member entity.Entity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fireGroupInviteAcceptedEvent fires a group invite accepted event
|
// fireGroupInviteAcceptedEvent fires a group invite accepted event
|
||||||
func (gm *GroupManager) fireGroupInviteAcceptedEvent(leader, member entity.Entity, groupID int32) {
|
func (gm *GroupManager) fireGroupInviteAcceptedEvent(leader, member Entity, groupID int32) {
|
||||||
gm.eventHandlersMutex.RLock()
|
gm.eventHandlersMutex.RLock()
|
||||||
defer gm.eventHandlersMutex.RUnlock()
|
defer gm.eventHandlersMutex.RUnlock()
|
||||||
|
|
||||||
@ -974,7 +972,7 @@ func (gm *GroupManager) fireGroupInviteAcceptedEvent(leader, member entity.Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fireGroupInviteDeclinedEvent fires a group invite declined event
|
// fireGroupInviteDeclinedEvent fires a group invite declined event
|
||||||
func (gm *GroupManager) fireGroupInviteDeclinedEvent(leader, member entity.Entity) {
|
func (gm *GroupManager) fireGroupInviteDeclinedEvent(leader, member Entity) {
|
||||||
gm.eventHandlersMutex.RLock()
|
gm.eventHandlersMutex.RLock()
|
||||||
defer gm.eventHandlersMutex.RUnlock()
|
defer gm.eventHandlersMutex.RUnlock()
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eq2emu/internal/entity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service provides a high-level interface for group management
|
// Service provides a high-level interface for group management
|
||||||
@ -128,7 +126,7 @@ func (s *Service) GetManager() GroupManagerInterface {
|
|||||||
// High-level group operations
|
// High-level group operations
|
||||||
|
|
||||||
// CreateGroup creates a new group with validation
|
// CreateGroup creates a new group with validation
|
||||||
func (s *Service) CreateGroup(leader entity.Entity, options *GroupOptions) (int32, error) {
|
func (s *Service) CreateGroup(leader Entity, options *GroupOptions) (int32, error) {
|
||||||
if leader == nil {
|
if leader == nil {
|
||||||
return 0, fmt.Errorf("leader cannot be nil")
|
return 0, fmt.Errorf("leader cannot be nil")
|
||||||
}
|
}
|
||||||
@ -150,7 +148,7 @@ func (s *Service) CreateGroup(leader entity.Entity, options *GroupOptions) (int3
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InviteToGroup invites a member to join a group
|
// InviteToGroup invites a member to join a group
|
||||||
func (s *Service) InviteToGroup(leader entity.Entity, member entity.Entity) error {
|
func (s *Service) InviteToGroup(leader Entity, member Entity) error {
|
||||||
if leader == nil || member == nil {
|
if leader == nil || member == nil {
|
||||||
return fmt.Errorf("leader and member cannot be nil")
|
return fmt.Errorf("leader and member cannot be nil")
|
||||||
}
|
}
|
||||||
@ -190,7 +188,7 @@ func (s *Service) InviteToGroup(leader entity.Entity, member entity.Entity) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AcceptGroupInvite accepts a group invitation
|
// AcceptGroupInvite accepts a group invitation
|
||||||
func (s *Service) AcceptGroupInvite(member entity.Entity) error {
|
func (s *Service) AcceptGroupInvite(member Entity) error {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return fmt.Errorf("member cannot be nil")
|
return fmt.Errorf("member cannot be nil")
|
||||||
}
|
}
|
||||||
@ -212,14 +210,14 @@ func (s *Service) AcceptGroupInvite(member entity.Entity) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeclineGroupInvite declines a group invitation
|
// DeclineGroupInvite declines a group invitation
|
||||||
func (s *Service) DeclineGroupInvite(member entity.Entity) {
|
func (s *Service) DeclineGroupInvite(member Entity) {
|
||||||
if member != nil {
|
if member != nil {
|
||||||
s.manager.DeclineInvite(member)
|
s.manager.DeclineInvite(member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeaveGroup removes a member from their current group
|
// LeaveGroup removes a member from their current group
|
||||||
func (s *Service) LeaveGroup(member entity.Entity) error {
|
func (s *Service) LeaveGroup(member Entity) error {
|
||||||
if member == nil {
|
if member == nil {
|
||||||
return fmt.Errorf("member cannot be nil")
|
return fmt.Errorf("member cannot be nil")
|
||||||
}
|
}
|
||||||
@ -241,7 +239,7 @@ func (s *Service) DisbandGroup(groupID int32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferLeadership transfers group leadership
|
// TransferLeadership transfers group leadership
|
||||||
func (s *Service) TransferLeadership(groupID int32, newLeader entity.Entity) error {
|
func (s *Service) TransferLeadership(groupID int32, newLeader Entity) error {
|
||||||
if newLeader == nil {
|
if newLeader == nil {
|
||||||
return fmt.Errorf("new leader cannot be nil")
|
return fmt.Errorf("new leader cannot be nil")
|
||||||
}
|
}
|
||||||
@ -287,7 +285,7 @@ func (s *Service) GetGroupInfo(groupID int32) (*GroupInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMemberGroups returns all groups that contain any of the specified members
|
// GetMemberGroups returns all groups that contain any of the specified members
|
||||||
func (s *Service) GetMemberGroups(members []entity.Entity) []*GroupInfo {
|
func (s *Service) GetMemberGroups(members []Entity) []*GroupInfo {
|
||||||
var groups []*GroupInfo
|
var groups []*GroupInfo
|
||||||
|
|
||||||
allGroups := s.manager.GetAllGroups()
|
allGroups := s.manager.GetAllGroups()
|
||||||
@ -449,7 +447,7 @@ func (s *Service) GetServiceStats() *ServiceStats {
|
|||||||
// Validation methods
|
// Validation methods
|
||||||
|
|
||||||
// validateGroupCreation validates group creation parameters
|
// validateGroupCreation validates group creation parameters
|
||||||
func (s *Service) validateGroupCreation(leader entity.Entity, options *GroupOptions) error {
|
func (s *Service) validateGroupCreation(leader Entity, options *GroupOptions) error {
|
||||||
// Check if leader is already in a group
|
// Check if leader is already in a group
|
||||||
// TODO: Check leader's group status
|
// TODO: Check leader's group status
|
||||||
// if leader.GetGroupMemberInfo() != nil {
|
// if leader.GetGroupMemberInfo() != nil {
|
||||||
@ -465,10 +463,10 @@ func (s *Service) validateGroupCreation(leader entity.Entity, options *GroupOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validateGroupInvitation validates group invitation parameters
|
// validateGroupInvitation validates group invitation parameters
|
||||||
func (s *Service) validateGroupInvitation(leader entity.Entity, member entity.Entity) error {
|
func (s *Service) validateGroupInvitation(leader Entity, member Entity) error {
|
||||||
// Check distance if enabled
|
// Check distance if enabled
|
||||||
if s.config.MaxInviteDistance > 0 {
|
if s.config.MaxInviteDistance > 0 {
|
||||||
distance := leader.GetDistance(&member.Spawn)
|
distance := leader.GetDistance(member)
|
||||||
if distance > s.config.MaxInviteDistance {
|
if distance > s.config.MaxInviteDistance {
|
||||||
return fmt.Errorf("member is too far away (%.1f > %.1f)", distance, s.config.MaxInviteDistance)
|
return fmt.Errorf("member is too far away (%.1f > %.1f)", distance, s.config.MaxInviteDistance)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package groups
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eq2emu/internal/entity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GroupOptions holds group configuration settings
|
// GroupOptions holds group configuration settings
|
||||||
@ -56,7 +54,7 @@ type GroupMemberInfo struct {
|
|||||||
ClientPeerPort int16 `json:"client_peer_port"`
|
ClientPeerPort int16 `json:"client_peer_port"`
|
||||||
|
|
||||||
// Entity reference (local members only)
|
// Entity reference (local members only)
|
||||||
Member entity.Entity `json:"-"`
|
Member Entity `json:"-"`
|
||||||
|
|
||||||
// Client reference (players only) - interface to avoid circular deps
|
// Client reference (players only) - interface to avoid circular deps
|
||||||
Client any `json:"-"`
|
Client any `json:"-"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user