529 lines
14 KiB
Go
529 lines
14 KiB
Go
package groups
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Service provides a high-level interface for group management
|
|
type Service struct {
|
|
manager *GroupManager
|
|
config ServiceConfig
|
|
started bool
|
|
startMutex sync.Mutex
|
|
}
|
|
|
|
// ServiceConfig holds configuration for the group service
|
|
type ServiceConfig struct {
|
|
// Group manager configuration
|
|
ManagerConfig GroupManagerConfig `json:"manager_config"`
|
|
|
|
// Service-specific settings
|
|
AutoCreateGroups bool `json:"auto_create_groups"`
|
|
AllowCrossZoneGroups bool `json:"allow_cross_zone_groups"`
|
|
AllowBotMembers bool `json:"allow_bot_members"`
|
|
AllowNPCMembers bool `json:"allow_npc_members"`
|
|
MaxInviteDistance float32 `json:"max_invite_distance"`
|
|
GroupLevelRange int8 `json:"group_level_range"`
|
|
EnableGroupPvP bool `json:"enable_group_pvp"`
|
|
EnableGroupBuffs bool `json:"enable_group_buffs"`
|
|
LogLevel string `json:"log_level"`
|
|
|
|
// Integration settings
|
|
DatabaseEnabled bool `json:"database_enabled"`
|
|
EventsEnabled bool `json:"events_enabled"`
|
|
StatisticsEnabled bool `json:"statistics_enabled"`
|
|
ValidationEnabled bool `json:"validation_enabled"`
|
|
}
|
|
|
|
// DefaultServiceConfig returns default service configuration
|
|
func DefaultServiceConfig() ServiceConfig {
|
|
return ServiceConfig{
|
|
ManagerConfig: GroupManagerConfig{
|
|
MaxGroups: 1000,
|
|
MaxRaidGroups: 4,
|
|
InviteTimeout: 30 * time.Second,
|
|
UpdateInterval: 1 * time.Second,
|
|
BuffUpdateInterval: 5 * time.Second,
|
|
EnableCrossServer: false,
|
|
EnableRaids: true,
|
|
EnableQuestSharing: true,
|
|
EnableAutoInvite: false,
|
|
EnableStatistics: true,
|
|
},
|
|
AutoCreateGroups: true,
|
|
AllowCrossZoneGroups: true,
|
|
AllowBotMembers: true,
|
|
AllowNPCMembers: false,
|
|
MaxInviteDistance: 100.0,
|
|
GroupLevelRange: 10,
|
|
EnableGroupPvP: false,
|
|
EnableGroupBuffs: true,
|
|
LogLevel: "info",
|
|
DatabaseEnabled: true,
|
|
EventsEnabled: true,
|
|
StatisticsEnabled: true,
|
|
ValidationEnabled: true,
|
|
}
|
|
}
|
|
|
|
// NewService creates a new group service
|
|
func NewService(config ServiceConfig) *Service {
|
|
return &Service{
|
|
manager: NewGroupManager(config.ManagerConfig),
|
|
config: config,
|
|
started: false,
|
|
}
|
|
}
|
|
|
|
// Start starts the group service
|
|
func (s *Service) Start() error {
|
|
s.startMutex.Lock()
|
|
defer s.startMutex.Unlock()
|
|
|
|
if s.started {
|
|
return fmt.Errorf("service already started")
|
|
}
|
|
|
|
if err := s.manager.Start(); err != nil {
|
|
return fmt.Errorf("failed to start group manager: %v", err)
|
|
}
|
|
|
|
s.started = true
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the group service
|
|
func (s *Service) Stop() error {
|
|
s.startMutex.Lock()
|
|
defer s.startMutex.Unlock()
|
|
|
|
if !s.started {
|
|
return nil
|
|
}
|
|
|
|
if err := s.manager.Stop(); err != nil {
|
|
return fmt.Errorf("failed to stop group manager: %v", err)
|
|
}
|
|
|
|
s.started = false
|
|
return nil
|
|
}
|
|
|
|
// IsStarted returns true if the service is started
|
|
func (s *Service) IsStarted() bool {
|
|
s.startMutex.Lock()
|
|
defer s.startMutex.Unlock()
|
|
return s.started
|
|
}
|
|
|
|
// GetManager returns the underlying group manager
|
|
func (s *Service) GetManager() GroupManagerInterface {
|
|
return s.manager
|
|
}
|
|
|
|
// High-level group operations
|
|
|
|
// CreateGroup creates a new group with validation
|
|
func (s *Service) CreateGroup(leader Entity, options *GroupOptions) (int32, error) {
|
|
if leader == nil {
|
|
return 0, fmt.Errorf("leader cannot be nil")
|
|
}
|
|
|
|
// Validate leader can create group
|
|
if s.config.ValidationEnabled {
|
|
if err := s.validateGroupCreation(leader, options); err != nil {
|
|
return 0, fmt.Errorf("group creation validation failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Use default options if none provided
|
|
if options == nil {
|
|
defaultOpts := DefaultGroupOptions()
|
|
options = &defaultOpts
|
|
}
|
|
|
|
return s.manager.NewGroup(leader, options, 0)
|
|
}
|
|
|
|
// InviteToGroup invites a member to join a group
|
|
func (s *Service) InviteToGroup(leader Entity, member Entity) error {
|
|
if leader == nil || member == nil {
|
|
return fmt.Errorf("leader and member cannot be nil")
|
|
}
|
|
|
|
// Validate the invitation
|
|
if s.config.ValidationEnabled {
|
|
if err := s.validateGroupInvitation(leader, member); err != nil {
|
|
return fmt.Errorf("invitation validation failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Send the invitation
|
|
result := s.manager.Invite(leader, member)
|
|
|
|
switch result {
|
|
case GROUP_INVITE_SUCCESS:
|
|
return nil
|
|
case GROUP_INVITE_ALREADY_IN_GROUP:
|
|
return fmt.Errorf("member is already in a group")
|
|
case GROUP_INVITE_ALREADY_HAS_INVITE:
|
|
return fmt.Errorf("member already has a pending invite")
|
|
case GROUP_INVITE_GROUP_FULL:
|
|
return fmt.Errorf("group is full")
|
|
case GROUP_INVITE_DECLINED:
|
|
return fmt.Errorf("invitation was declined")
|
|
case GROUP_INVITE_TARGET_NOT_FOUND:
|
|
return fmt.Errorf("target not found")
|
|
case GROUP_INVITE_SELF_INVITE:
|
|
return fmt.Errorf("cannot invite yourself")
|
|
case GROUP_INVITE_PERMISSION_DENIED:
|
|
return fmt.Errorf("permission denied")
|
|
case GROUP_INVITE_TARGET_BUSY:
|
|
return fmt.Errorf("target is busy")
|
|
default:
|
|
return fmt.Errorf("unknown invitation error: %d", result)
|
|
}
|
|
}
|
|
|
|
// AcceptGroupInvite accepts a group invitation
|
|
func (s *Service) AcceptGroupInvite(member Entity) error {
|
|
if member == nil {
|
|
return fmt.Errorf("member cannot be nil")
|
|
}
|
|
|
|
result := s.manager.AcceptInvite(member, nil, true)
|
|
|
|
switch result {
|
|
case GROUP_INVITE_SUCCESS:
|
|
return nil
|
|
case GROUP_INVITE_TARGET_NOT_FOUND:
|
|
return fmt.Errorf("no pending invitation found")
|
|
case GROUP_INVITE_GROUP_FULL:
|
|
return fmt.Errorf("group is full")
|
|
case GROUP_INVITE_PERMISSION_DENIED:
|
|
return fmt.Errorf("permission denied")
|
|
default:
|
|
return fmt.Errorf("unknown acceptance error: %d", result)
|
|
}
|
|
}
|
|
|
|
// DeclineGroupInvite declines a group invitation
|
|
func (s *Service) DeclineGroupInvite(member Entity) {
|
|
if member != nil {
|
|
s.manager.DeclineInvite(member)
|
|
}
|
|
}
|
|
|
|
// LeaveGroup removes a member from their current group
|
|
func (s *Service) LeaveGroup(member Entity) error {
|
|
if member == nil {
|
|
return fmt.Errorf("member cannot be nil")
|
|
}
|
|
|
|
// TODO: Get member's current group ID
|
|
// groupID := member.GetGroupID()
|
|
groupID := int32(0) // Placeholder
|
|
|
|
if groupID == 0 {
|
|
return fmt.Errorf("member is not in a group")
|
|
}
|
|
|
|
return s.manager.RemoveGroupMember(groupID, member)
|
|
}
|
|
|
|
// DisbandGroup disbands a group
|
|
func (s *Service) DisbandGroup(groupID int32) error {
|
|
return s.manager.RemoveGroup(groupID)
|
|
}
|
|
|
|
// TransferLeadership transfers group leadership
|
|
func (s *Service) TransferLeadership(groupID int32, newLeader Entity) error {
|
|
if newLeader == nil {
|
|
return fmt.Errorf("new leader cannot be nil")
|
|
}
|
|
|
|
if !s.manager.IsGroupIDValid(groupID) {
|
|
return fmt.Errorf("invalid group ID")
|
|
}
|
|
|
|
if !s.manager.MakeLeader(groupID, newLeader) {
|
|
return fmt.Errorf("failed to transfer leadership")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Group information methods
|
|
|
|
// GetGroupInfo returns detailed information about a group
|
|
func (s *Service) GetGroupInfo(groupID int32) (*GroupInfo, error) {
|
|
group := s.manager.GetGroup(groupID)
|
|
if group == nil {
|
|
return nil, fmt.Errorf("group not found")
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
options := group.GetGroupOptions()
|
|
raidGroups := group.GetRaidGroups()
|
|
|
|
info := &GroupInfo{
|
|
GroupID: group.GetID(),
|
|
Size: int(group.GetSize()),
|
|
Members: members,
|
|
Options: options,
|
|
RaidGroups: raidGroups,
|
|
IsRaid: group.IsGroupRaid(),
|
|
LeaderName: group.GetLeaderName(),
|
|
CreatedTime: group.GetCreatedTime(),
|
|
LastActivity: group.GetLastActivity(),
|
|
IsDisbanded: group.IsDisbanded(),
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// GetMemberGroups returns all groups that contain any of the specified members
|
|
func (s *Service) GetMemberGroups(members []Entity) []*GroupInfo {
|
|
var groups []*GroupInfo
|
|
|
|
allGroups := s.manager.GetAllGroups()
|
|
for _, group := range allGroups {
|
|
if group.IsDisbanded() {
|
|
continue
|
|
}
|
|
|
|
groupMembers := group.GetMembers()
|
|
for _, member := range members {
|
|
for _, gmi := range groupMembers {
|
|
if gmi.Member == member {
|
|
if info, err := s.GetGroupInfo(group.GetID()); err == nil {
|
|
groups = append(groups, info)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// GetGroupsByZone returns all groups with members in the specified zone
|
|
func (s *Service) GetGroupsByZone(zoneID int32) []*GroupInfo {
|
|
var groups []*GroupInfo
|
|
|
|
allGroups := s.manager.GetAllGroups()
|
|
for _, group := range allGroups {
|
|
if group.IsDisbanded() {
|
|
continue
|
|
}
|
|
|
|
members := group.GetMembers()
|
|
hasZoneMember := false
|
|
|
|
for _, member := range members {
|
|
if member.ZoneID == zoneID {
|
|
hasZoneMember = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if hasZoneMember {
|
|
if info, err := s.GetGroupInfo(group.GetID()); err == nil {
|
|
groups = append(groups, info)
|
|
}
|
|
}
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// Raid operations
|
|
|
|
// FormRaid forms a raid from multiple groups
|
|
func (s *Service) FormRaid(leaderGroupID int32, targetGroupIDs []int32) error {
|
|
if !s.config.ManagerConfig.EnableRaids {
|
|
return fmt.Errorf("raids are disabled")
|
|
}
|
|
|
|
leaderGroup := s.manager.GetGroup(leaderGroupID)
|
|
if leaderGroup == nil {
|
|
return fmt.Errorf("leader group not found")
|
|
}
|
|
|
|
// Validate all target groups exist
|
|
for _, groupID := range targetGroupIDs {
|
|
if !s.manager.IsGroupIDValid(groupID) {
|
|
return fmt.Errorf("invalid target group ID: %d", groupID)
|
|
}
|
|
}
|
|
|
|
// Add all groups to the raid
|
|
allRaidGroups := append([]int32{leaderGroupID}, targetGroupIDs...)
|
|
|
|
for _, groupID := range allRaidGroups {
|
|
s.manager.ReplaceRaidGroups(groupID, allRaidGroups)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DisbandRaid disbands a raid
|
|
func (s *Service) DisbandRaid(groupID int32) error {
|
|
group := s.manager.GetGroup(groupID)
|
|
if group == nil {
|
|
return fmt.Errorf("group not found")
|
|
}
|
|
|
|
raidGroups := group.GetRaidGroups()
|
|
if len(raidGroups) == 0 {
|
|
return fmt.Errorf("group is not in a raid")
|
|
}
|
|
|
|
// Clear raid associations for all groups
|
|
for _, raidGroupID := range raidGroups {
|
|
s.manager.ClearGroupRaid(raidGroupID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Service configuration
|
|
|
|
// UpdateConfig updates the service configuration
|
|
func (s *Service) UpdateConfig(config ServiceConfig) error {
|
|
s.config = config
|
|
return nil
|
|
}
|
|
|
|
// GetConfig returns the current service configuration
|
|
func (s *Service) GetConfig() ServiceConfig {
|
|
return s.config
|
|
}
|
|
|
|
// Integration methods
|
|
|
|
// SetDatabase sets the database interface
|
|
func (s *Service) SetDatabase(db GroupDatabase) {
|
|
s.manager.SetDatabase(db)
|
|
}
|
|
|
|
// SetPacketHandler sets the packet handler interface
|
|
func (s *Service) SetPacketHandler(handler GroupPacketHandler) {
|
|
s.manager.SetPacketHandler(handler)
|
|
}
|
|
|
|
// SetValidator sets the validator interface
|
|
func (s *Service) SetValidator(validator GroupValidator) {
|
|
s.manager.SetValidator(validator)
|
|
}
|
|
|
|
// SetNotifier sets the notifier interface
|
|
func (s *Service) SetNotifier(notifier GroupNotifier) {
|
|
s.manager.SetNotifier(notifier)
|
|
}
|
|
|
|
// AddEventHandler adds an event handler
|
|
func (s *Service) AddEventHandler(handler GroupEventHandler) {
|
|
s.manager.AddEventHandler(handler)
|
|
}
|
|
|
|
// Statistics
|
|
|
|
// GetServiceStats returns service statistics
|
|
func (s *Service) GetServiceStats() *ServiceStats {
|
|
managerStats := s.manager.GetStats()
|
|
|
|
return &ServiceStats{
|
|
ManagerStats: managerStats,
|
|
ServiceStartTime: time.Now(), // TODO: Track actual start time
|
|
IsStarted: s.started,
|
|
Config: s.config,
|
|
}
|
|
}
|
|
|
|
// Validation methods
|
|
|
|
// validateGroupCreation validates group creation parameters
|
|
func (s *Service) validateGroupCreation(leader Entity, options *GroupOptions) error {
|
|
// Check if leader is already in a group
|
|
// TODO: Check leader's group status
|
|
// if leader.GetGroupMemberInfo() != nil {
|
|
// return fmt.Errorf("leader is already in a group")
|
|
// }
|
|
|
|
// Validate options
|
|
if options != nil && !options.IsValid() {
|
|
return fmt.Errorf("invalid group options")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateGroupInvitation validates group invitation parameters
|
|
func (s *Service) validateGroupInvitation(leader Entity, member Entity) error {
|
|
// Check distance if enabled
|
|
if s.config.MaxInviteDistance > 0 {
|
|
distance := leader.GetDistance(member)
|
|
if distance > s.config.MaxInviteDistance {
|
|
return fmt.Errorf("member is too far away (%.1f > %.1f)", distance, s.config.MaxInviteDistance)
|
|
}
|
|
}
|
|
|
|
// Check level range if enabled
|
|
if s.config.GroupLevelRange > 0 {
|
|
leaderLevel := leader.GetLevel()
|
|
memberLevel := member.GetLevel()
|
|
levelDiff := leaderLevel - memberLevel
|
|
if levelDiff < 0 {
|
|
levelDiff = -levelDiff
|
|
}
|
|
|
|
if levelDiff > s.config.GroupLevelRange {
|
|
return fmt.Errorf("level difference too large (%d > %d)", levelDiff, s.config.GroupLevelRange)
|
|
}
|
|
}
|
|
|
|
// Check if member type is allowed
|
|
if member.IsBot() && !s.config.AllowBotMembers {
|
|
return fmt.Errorf("bot members are not allowed")
|
|
}
|
|
|
|
if member.IsNPC() && !s.config.AllowNPCMembers {
|
|
return fmt.Errorf("NPC members are not allowed")
|
|
}
|
|
|
|
// Check zone restrictions
|
|
if !s.config.AllowCrossZoneGroups {
|
|
if leader.GetZone() != member.GetZone() {
|
|
return fmt.Errorf("cross-zone groups are not allowed")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GroupInfo holds detailed information about a group
|
|
type GroupInfo struct {
|
|
GroupID int32 `json:"group_id"`
|
|
Size int `json:"size"`
|
|
Members []*GroupMemberInfo `json:"members"`
|
|
Options GroupOptions `json:"options"`
|
|
RaidGroups []int32 `json:"raid_groups"`
|
|
IsRaid bool `json:"is_raid"`
|
|
LeaderName string `json:"leader_name"`
|
|
CreatedTime time.Time `json:"created_time"`
|
|
LastActivity time.Time `json:"last_activity"`
|
|
IsDisbanded bool `json:"is_disbanded"`
|
|
}
|
|
|
|
// ServiceStats holds statistics about the service
|
|
type ServiceStats struct {
|
|
ManagerStats GroupManagerStats `json:"manager_stats"`
|
|
ServiceStartTime time.Time `json:"service_start_time"`
|
|
IsStarted bool `json:"is_started"`
|
|
Config ServiceConfig `json:"config"`
|
|
}
|