1305 lines
35 KiB
Go
1305 lines
35 KiB
Go
package guilds
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"eq2emu/internal/database"
|
|
"eq2emu/internal/packets"
|
|
)
|
|
|
|
// Entity represents a game entity that can be in guilds (simplified interface)
|
|
type Entity interface {
|
|
GetID() int32
|
|
GetName() string
|
|
GetLevel() int8
|
|
GetClass() int8
|
|
IsPlayer() bool
|
|
IsOnline() bool
|
|
}
|
|
|
|
// Client represents a connected client (simplified interface)
|
|
type Client interface {
|
|
GetVersion() uint32
|
|
SendPacket(opcode packets.InternalOpcode, data []byte) error
|
|
GetCharacterID() int32
|
|
}
|
|
|
|
// Logger interface for logging operations
|
|
type Logger interface {
|
|
Debug(msg string, args ...any)
|
|
Info(msg string, args ...any)
|
|
Error(msg string, args ...any)
|
|
}
|
|
|
|
// PointHistory represents a point modification entry in a member's history
|
|
type PointHistory struct {
|
|
Date time.Time `json:"date" db:"date"`
|
|
ModifiedBy string `json:"modified_by" db:"modified_by"`
|
|
Comment string `json:"comment" db:"comment"`
|
|
Points float64 `json:"points" db:"points"`
|
|
SaveNeeded bool `json:"-" db:"-"`
|
|
}
|
|
|
|
// GuildMember represents a member of a guild (consolidated from multiple files)
|
|
type GuildMember struct {
|
|
CharacterID int32 `json:"character_id" db:"character_id"`
|
|
AccountID int32 `json:"account_id" db:"account_id"`
|
|
RecruiterID int32 `json:"recruiter_id" db:"recruiter_id"`
|
|
Name string `json:"name" db:"name"`
|
|
GuildStatus int32 `json:"guild_status" db:"guild_status"`
|
|
Points float64 `json:"points" db:"points"`
|
|
AdventureClass int8 `json:"adventure_class" db:"adventure_class"`
|
|
AdventureLevel int8 `json:"adventure_level" db:"adventure_level"`
|
|
TradeskillClass int8 `json:"tradeskill_class" db:"tradeskill_class"`
|
|
TradeskillLevel int8 `json:"tradeskill_level" db:"tradeskill_level"`
|
|
Rank int8 `json:"rank" db:"rank"`
|
|
MemberFlags int8 `json:"member_flags" db:"member_flags"`
|
|
Zone string `json:"zone" db:"zone"`
|
|
JoinDate time.Time `json:"join_date" db:"join_date"`
|
|
LastLoginDate time.Time `json:"last_login_date" db:"last_login_date"`
|
|
Note string `json:"note" db:"note"`
|
|
OfficerNote string `json:"officer_note" db:"officer_note"`
|
|
RecruiterDescription string `json:"recruiter_description" db:"recruiter_description"`
|
|
RecruiterPictureData []byte `json:"recruiter_picture_data" db:"recruiter_picture_data"`
|
|
RecruitingShowAdventureClass int8 `json:"recruiting_show_adventure_class" db:"recruiting_show_adventure_class"`
|
|
PointHistory []PointHistory `json:"point_history" db:"-"`
|
|
|
|
// Runtime data
|
|
Entity Entity `json:"-" db:"-"`
|
|
Client Client `json:"-" db:"-"`
|
|
LastUpdate time.Time `json:"-" db:"-"`
|
|
SaveNeeded bool `json:"-" db:"-"`
|
|
}
|
|
|
|
// GuildEvent represents an event in the guild's history
|
|
type GuildEvent struct {
|
|
EventID int64 `json:"event_id" db:"event_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
Type int32 `json:"type" db:"type"`
|
|
Description string `json:"description" db:"description"`
|
|
Locked int8 `json:"locked" db:"locked"`
|
|
SaveNeeded bool `json:"-" db:"-"`
|
|
}
|
|
|
|
// GuildBankEvent represents an event in a guild bank's history
|
|
type GuildBankEvent struct {
|
|
EventID int64 `json:"event_id" db:"event_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
Type int32 `json:"type" db:"type"`
|
|
Description string `json:"description" db:"description"`
|
|
}
|
|
|
|
// Bank represents a guild bank with its event history
|
|
type Bank struct {
|
|
Name string `json:"name" db:"name"`
|
|
Events []GuildBankEvent `json:"events" db:"-"`
|
|
}
|
|
|
|
// GuildInvite represents a pending guild invitation
|
|
type GuildInvite struct {
|
|
GuildID int32 `json:"guild_id"`
|
|
GuildName string `json:"guild_name"`
|
|
CharacterID int32 `json:"character_id"`
|
|
CharacterName string `json:"character_name"`
|
|
InviterID int32 `json:"inviter_id"`
|
|
InviterName string `json:"inviter_name"`
|
|
Rank int8 `json:"rank"`
|
|
InviteDate time.Time `json:"invite_date"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
// Guild represents a guild with all its properties and members (consolidated from multiple files)
|
|
type Guild struct {
|
|
// Core guild properties
|
|
id int32
|
|
name string
|
|
level int8
|
|
formedDate time.Time
|
|
motd string
|
|
expCurrent int64
|
|
expToNextLevel int64
|
|
recruitingShortDesc string
|
|
recruitingFullDesc string
|
|
recruitingMinLevel int8
|
|
recruitingPlayStyle int8
|
|
|
|
// Guild data structures
|
|
members map[int32]*GuildMember
|
|
guildEvents []GuildEvent
|
|
permissions map[int8]map[int8]int8 // rank -> permission -> value
|
|
eventFilters map[int8]map[int8]int8 // event_id -> category -> value
|
|
recruitingFlags map[int8]int8
|
|
recruitingDescTags map[int8]int8
|
|
ranks map[int8]string // rank -> name
|
|
banks [4]Bank
|
|
|
|
// Guild hall information
|
|
guildHallLocation string
|
|
guildHallZoneName string
|
|
guildHallFilename string
|
|
|
|
// Tracking
|
|
nextEventID int64
|
|
lastModified time.Time
|
|
|
|
// Save flags
|
|
saveNeeded bool
|
|
memberSaveNeeded bool
|
|
eventsSaveNeeded bool
|
|
ranksSaveNeeded bool
|
|
eventFiltersSaveNeeded bool
|
|
pointsHistorySaveNeeded bool
|
|
recruitingSaveNeeded bool
|
|
|
|
// Thread safety
|
|
membersMutex sync.RWMutex
|
|
eventsMutex sync.RWMutex
|
|
ranksMutex sync.RWMutex
|
|
permissionsMutex sync.RWMutex
|
|
filtersMutex sync.RWMutex
|
|
recruitingMutex sync.RWMutex
|
|
coreMutex sync.RWMutex
|
|
}
|
|
|
|
// GuildManager manages all guilds in the system (consolidated from multiple files)
|
|
type GuildManager struct {
|
|
// Guild storage
|
|
guilds map[int32]*Guild
|
|
guildsMutex sync.RWMutex
|
|
|
|
// Guild ID generation
|
|
nextGuildID int32
|
|
nextGuildIDMutex sync.Mutex
|
|
|
|
// Pending invitations
|
|
pendingInvites map[int32]*GuildInvite // character_id -> invite
|
|
invitesMutex sync.RWMutex
|
|
|
|
// Statistics (simplified)
|
|
stats GuildManagerStats
|
|
statsMutex sync.RWMutex
|
|
|
|
// Dependencies
|
|
database *database.Database
|
|
logger Logger
|
|
}
|
|
|
|
// GuildManagerStats holds essential guild management statistics
|
|
type GuildManagerStats struct {
|
|
TotalGuilds int64 `json:"total_guilds"`
|
|
ActiveGuilds int64 `json:"active_guilds"`
|
|
TotalMembers int64 `json:"total_members"`
|
|
TotalEvents int64 `json:"total_events"`
|
|
TotalRecruiters int64 `json:"total_recruiters"`
|
|
UniqueAccounts int64 `json:"unique_accounts"`
|
|
TotalInvites int64 `json:"total_invites"`
|
|
AcceptedInvites int64 `json:"accepted_invites"`
|
|
DeclinedInvites int64 `json:"declined_invites"`
|
|
ExpiredInvites int64 `json:"expired_invites"`
|
|
LastStatsUpdate time.Time `json:"last_stats_update"`
|
|
}
|
|
|
|
// GuildInfo provides basic guild information
|
|
type GuildInfo struct {
|
|
ID int32 `json:"id"`
|
|
Name string `json:"name"`
|
|
Level int8 `json:"level"`
|
|
FormedDate time.Time `json:"formed_date"`
|
|
MOTD string `json:"motd"`
|
|
MemberCount int `json:"member_count"`
|
|
OnlineMemberCount int `json:"online_member_count"`
|
|
RecruiterCount int `json:"recruiter_count"`
|
|
RecruitingShortDesc string `json:"recruiting_short_desc"`
|
|
RecruitingFullDesc string `json:"recruiting_full_desc"`
|
|
RecruitingMinLevel int8 `json:"recruiting_min_level"`
|
|
RecruitingPlayStyle int8 `json:"recruiting_play_style"`
|
|
IsRecruiting bool `json:"is_recruiting"`
|
|
UniqueAccounts int `json:"unique_accounts"`
|
|
ExpCurrent int64 `json:"exp_current"`
|
|
ExpToNextLevel int64 `json:"exp_to_next_level"`
|
|
}
|
|
|
|
// NewGuildManager creates a new guild manager
|
|
func NewGuildManager(db *database.Database, logger Logger) *GuildManager {
|
|
if logger == nil {
|
|
logger = &nullLogger{}
|
|
}
|
|
|
|
return &GuildManager{
|
|
guilds: make(map[int32]*Guild),
|
|
nextGuildID: 1,
|
|
pendingInvites: make(map[int32]*GuildInvite),
|
|
database: db,
|
|
logger: logger,
|
|
stats: GuildManagerStats{LastStatsUpdate: time.Now()},
|
|
}
|
|
}
|
|
|
|
// Guild creation and management (preserving C++ API)
|
|
|
|
// CreateGuild creates a new guild with the specified leader and name
|
|
func (gm *GuildManager) CreateGuild(leaderID int32, guildName string) (*Guild, error) {
|
|
if len(guildName) == 0 || len(guildName) > MaxGuildNameLength {
|
|
return nil, fmt.Errorf("invalid guild name length")
|
|
}
|
|
|
|
// Check if guild name already exists
|
|
if gm.GuildNameExists(guildName) {
|
|
return nil, fmt.Errorf("guild name already exists")
|
|
}
|
|
|
|
// Generate guild ID
|
|
gm.nextGuildIDMutex.Lock()
|
|
guildID := gm.nextGuildID
|
|
gm.nextGuildID++
|
|
gm.nextGuildIDMutex.Unlock()
|
|
|
|
// Create the guild
|
|
guild := &Guild{
|
|
id: guildID,
|
|
name: guildName,
|
|
level: 1,
|
|
formedDate: time.Now(),
|
|
motd: "",
|
|
expCurrent: 111,
|
|
expToNextLevel: 2521,
|
|
recruitingMinLevel: 1,
|
|
members: make(map[int32]*GuildMember),
|
|
guildEvents: make([]GuildEvent, 0),
|
|
permissions: make(map[int8]map[int8]int8),
|
|
eventFilters: make(map[int8]map[int8]int8),
|
|
recruitingFlags: make(map[int8]int8),
|
|
recruitingDescTags: make(map[int8]int8),
|
|
ranks: make(map[int8]string),
|
|
nextEventID: 1,
|
|
lastModified: time.Now(),
|
|
saveNeeded: true,
|
|
}
|
|
|
|
// Initialize default recruiting flags
|
|
guild.recruitingFlags[RecruitingFlagTraining] = 0
|
|
guild.recruitingFlags[RecruitingFlagFighters] = 0
|
|
guild.recruitingFlags[RecruitingFlagPriests] = 0
|
|
guild.recruitingFlags[RecruitingFlagScouts] = 0
|
|
guild.recruitingFlags[RecruitingFlagMages] = 0
|
|
guild.recruitingFlags[RecruitingFlagTradeskillers] = 0
|
|
|
|
// Initialize default description tags
|
|
guild.recruitingDescTags[0] = RecruitingDescTagNone
|
|
guild.recruitingDescTags[1] = RecruitingDescTagNone
|
|
guild.recruitingDescTags[2] = RecruitingDescTagNone
|
|
guild.recruitingDescTags[3] = RecruitingDescTagNone
|
|
|
|
// Initialize default bank names
|
|
guild.banks[0].Name = "Bank 1"
|
|
guild.banks[1].Name = "Bank 2"
|
|
guild.banks[2].Name = "Bank 3"
|
|
guild.banks[3].Name = "Bank 4"
|
|
|
|
// Initialize default rank names
|
|
for rank, name := range DefaultRankNames {
|
|
guild.ranks[rank] = name
|
|
}
|
|
|
|
// Add leader as first member
|
|
leader := &GuildMember{
|
|
CharacterID: leaderID,
|
|
Rank: RankLeader,
|
|
JoinDate: time.Now(),
|
|
LastLoginDate: time.Now(),
|
|
PointHistory: make([]PointHistory, 0),
|
|
SaveNeeded: true,
|
|
}
|
|
|
|
guild.members[leaderID] = leader
|
|
guild.memberSaveNeeded = true
|
|
|
|
// Add guild to manager
|
|
gm.guildsMutex.Lock()
|
|
gm.guilds[guildID] = guild
|
|
gm.guildsMutex.Unlock()
|
|
|
|
// Add formation event
|
|
guild.AddNewGuildEvent(EventGuildFormed, fmt.Sprintf("%s formed the guild", leader.Name), time.Now(), true)
|
|
|
|
// Update statistics
|
|
gm.statsMutex.Lock()
|
|
gm.stats.TotalGuilds++
|
|
gm.stats.ActiveGuilds++
|
|
gm.stats.TotalMembers++
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
gm.statsMutex.Unlock()
|
|
|
|
gm.logger.Info("Created new guild", "guild_id", guildID, "name", guildName, "leader_id", leaderID)
|
|
|
|
return guild, nil
|
|
}
|
|
|
|
// DeleteGuild removes a guild from the manager
|
|
func (gm *GuildManager) DeleteGuild(guildID int32, reason string) error {
|
|
gm.guildsMutex.Lock()
|
|
guild, exists := gm.guilds[guildID]
|
|
if !exists {
|
|
gm.guildsMutex.Unlock()
|
|
return fmt.Errorf("guild not found: %d", guildID)
|
|
}
|
|
delete(gm.guilds, guildID)
|
|
gm.guildsMutex.Unlock()
|
|
|
|
// Add disbanding event
|
|
guild.AddNewGuildEvent(EventGuildDisbanded, reason, time.Now(), true)
|
|
|
|
// Clear all members
|
|
guild.membersMutex.Lock()
|
|
memberCount := len(guild.members)
|
|
guild.members = make(map[int32]*GuildMember)
|
|
guild.membersMutex.Unlock()
|
|
|
|
// Update statistics
|
|
gm.statsMutex.Lock()
|
|
gm.stats.ActiveGuilds--
|
|
gm.stats.TotalMembers -= int64(memberCount)
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
gm.statsMutex.Unlock()
|
|
|
|
gm.logger.Info("Deleted guild", "guild_id", guildID, "reason", reason)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetGuild retrieves a guild by ID
|
|
func (gm *GuildManager) GetGuild(guildID int32) *Guild {
|
|
gm.guildsMutex.RLock()
|
|
defer gm.guildsMutex.RUnlock()
|
|
|
|
return gm.guilds[guildID]
|
|
}
|
|
|
|
// GetGuildByName retrieves a guild by name
|
|
func (gm *GuildManager) GetGuildByName(name string) *Guild {
|
|
gm.guildsMutex.RLock()
|
|
defer gm.guildsMutex.RUnlock()
|
|
|
|
for _, guild := range gm.guilds {
|
|
if guild.GetName() == name {
|
|
return guild
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GuildNameExists checks if a guild name already exists
|
|
func (gm *GuildManager) GuildNameExists(name string) bool {
|
|
return gm.GetGuildByName(name) != nil
|
|
}
|
|
|
|
// GetPlayerGuild returns the guild for a player character
|
|
func (gm *GuildManager) GetPlayerGuild(characterID int32) *Guild {
|
|
gm.guildsMutex.RLock()
|
|
defer gm.guildsMutex.RUnlock()
|
|
|
|
for _, guild := range gm.guilds {
|
|
if guild.HasMember(characterID) {
|
|
return guild
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Guild invitation system (preserving C++ API)
|
|
|
|
// InvitePlayerToGuild invites a player to join a guild
|
|
func (gm *GuildManager) InvitePlayerToGuild(guildID, inviterID, characterID int32, rank int8) int8 {
|
|
guild := gm.GetGuild(guildID)
|
|
if guild == nil {
|
|
return GUILD_INVITE_GUILD_NOT_FOUND
|
|
}
|
|
|
|
// Check if inviter has permission
|
|
inviter := guild.GetMember(inviterID)
|
|
if inviter == nil {
|
|
return GUILD_INVITE_NO_PERMISSION
|
|
}
|
|
|
|
if !guild.HasPermission(inviter.Rank, PermissionInvite) {
|
|
return GUILD_INVITE_NO_PERMISSION
|
|
}
|
|
|
|
// Check if player is already in a guild
|
|
if gm.GetPlayerGuild(characterID) != nil {
|
|
return GUILD_INVITE_ALREADY_IN_GUILD
|
|
}
|
|
|
|
// Check if player already has a pending invite
|
|
if gm.HasPendingInvite(characterID) {
|
|
return GUILD_INVITE_ALREADY_HAS_INVITE
|
|
}
|
|
|
|
// Create the invitation
|
|
invite := &GuildInvite{
|
|
GuildID: guildID,
|
|
GuildName: guild.GetName(),
|
|
CharacterID: characterID,
|
|
InviterID: inviterID,
|
|
InviterName: inviter.Name,
|
|
Rank: rank,
|
|
InviteDate: time.Now(),
|
|
ExpiresAt: time.Now().Add(GUILD_INVITE_TIMEOUT),
|
|
}
|
|
|
|
gm.invitesMutex.Lock()
|
|
gm.pendingInvites[characterID] = invite
|
|
gm.invitesMutex.Unlock()
|
|
|
|
gm.statsMutex.Lock()
|
|
gm.stats.TotalInvites++
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
gm.statsMutex.Unlock()
|
|
|
|
gm.logger.Info("Guild invitation sent", "guild_id", guildID, "inviter_id", inviterID, "character_id", characterID)
|
|
|
|
return GUILD_INVITE_SUCCESS
|
|
}
|
|
|
|
// AcceptGuildInvite handles accepting a guild invitation
|
|
func (gm *GuildManager) AcceptGuildInvite(characterID int32) int8 {
|
|
gm.invitesMutex.Lock()
|
|
invite, exists := gm.pendingInvites[characterID]
|
|
if !exists {
|
|
gm.invitesMutex.Unlock()
|
|
return GUILD_INVITE_NO_INVITE
|
|
}
|
|
|
|
// Check if invite has expired
|
|
if invite.IsExpired() {
|
|
delete(gm.pendingInvites, characterID)
|
|
gm.invitesMutex.Unlock()
|
|
gm.statsMutex.Lock()
|
|
gm.stats.ExpiredInvites++
|
|
gm.statsMutex.Unlock()
|
|
return GUILD_INVITE_EXPIRED
|
|
}
|
|
|
|
// Remove the invite
|
|
delete(gm.pendingInvites, characterID)
|
|
gm.invitesMutex.Unlock()
|
|
|
|
// Get the guild
|
|
guild := gm.GetGuild(invite.GuildID)
|
|
if guild == nil {
|
|
return GUILD_INVITE_GUILD_NOT_FOUND
|
|
}
|
|
|
|
// Add the member to the guild
|
|
success := guild.AddNewGuildMember(characterID, invite.InviterName, time.Now(), invite.Rank)
|
|
if !success {
|
|
return GUILD_INVITE_FAILED
|
|
}
|
|
|
|
gm.statsMutex.Lock()
|
|
gm.stats.AcceptedInvites++
|
|
gm.stats.TotalMembers++
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
gm.statsMutex.Unlock()
|
|
|
|
gm.logger.Info("Guild invitation accepted", "guild_id", invite.GuildID, "character_id", characterID)
|
|
|
|
return GUILD_INVITE_SUCCESS
|
|
}
|
|
|
|
// DeclineGuildInvite handles declining a guild invitation
|
|
func (gm *GuildManager) DeclineGuildInvite(characterID int32) {
|
|
gm.invitesMutex.Lock()
|
|
_, exists := gm.pendingInvites[characterID]
|
|
if exists {
|
|
delete(gm.pendingInvites, characterID)
|
|
}
|
|
gm.invitesMutex.Unlock()
|
|
|
|
if exists {
|
|
gm.statsMutex.Lock()
|
|
gm.stats.DeclinedInvites++
|
|
gm.stats.LastStatsUpdate = time.Now()
|
|
gm.statsMutex.Unlock()
|
|
|
|
gm.logger.Info("Guild invitation declined", "character_id", characterID)
|
|
}
|
|
}
|
|
|
|
// HasPendingInvite checks if a character has a pending guild invitation
|
|
func (gm *GuildManager) HasPendingInvite(characterID int32) bool {
|
|
gm.invitesMutex.RLock()
|
|
invite, exists := gm.pendingInvites[characterID]
|
|
gm.invitesMutex.RUnlock()
|
|
|
|
if !exists || invite.IsExpired() {
|
|
if exists {
|
|
// Clean up expired invite
|
|
gm.invitesMutex.Lock()
|
|
delete(gm.pendingInvites, characterID)
|
|
gm.invitesMutex.Unlock()
|
|
}
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetPendingInvite returns the pending invite for a character
|
|
func (gm *GuildManager) GetPendingInvite(characterID int32) *GuildInvite {
|
|
gm.invitesMutex.RLock()
|
|
defer gm.invitesMutex.RUnlock()
|
|
|
|
invite, exists := gm.pendingInvites[characterID]
|
|
if !exists || invite.IsExpired() {
|
|
return nil
|
|
}
|
|
|
|
return invite
|
|
}
|
|
|
|
// Statistics and utilities
|
|
|
|
// GetStats returns the guild manager statistics
|
|
func (gm *GuildManager) GetStats() GuildManagerStats {
|
|
gm.statsMutex.RLock()
|
|
defer gm.statsMutex.RUnlock()
|
|
|
|
return gm.stats
|
|
}
|
|
|
|
// GetGuildCount returns the current number of active guilds
|
|
func (gm *GuildManager) GetGuildCount() int32 {
|
|
gm.guildsMutex.RLock()
|
|
defer gm.guildsMutex.RUnlock()
|
|
|
|
return int32(len(gm.guilds))
|
|
}
|
|
|
|
// GetAllGuilds returns a copy of all guilds
|
|
func (gm *GuildManager) GetAllGuilds() []*Guild {
|
|
gm.guildsMutex.RLock()
|
|
defer gm.guildsMutex.RUnlock()
|
|
|
|
guilds := make([]*Guild, 0, len(gm.guilds))
|
|
for _, guild := range gm.guilds {
|
|
guilds = append(guilds, guild)
|
|
}
|
|
|
|
return guilds
|
|
}
|
|
|
|
// Guild methods (preserving C++ API)
|
|
|
|
// GetID returns the guild ID
|
|
func (g *Guild) GetID() int32 {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.id
|
|
}
|
|
|
|
// GetName returns the guild name
|
|
func (g *Guild) GetName() string {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.name
|
|
}
|
|
|
|
// GetLevel returns the guild level
|
|
func (g *Guild) GetLevel() int8 {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.level
|
|
}
|
|
|
|
// GetMOTD returns the guild message of the day
|
|
func (g *Guild) GetMOTD() string {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.motd
|
|
}
|
|
|
|
// GetFormedDate returns the guild formation date
|
|
func (g *Guild) GetFormedDate() time.Time {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.formedDate
|
|
}
|
|
|
|
// SetName sets the guild name
|
|
func (g *Guild) SetName(name string, sendPacket bool) {
|
|
if len(name) > MaxGuildNameLength {
|
|
name = name[:MaxGuildNameLength]
|
|
}
|
|
|
|
g.coreMutex.Lock()
|
|
g.name = name
|
|
g.lastModified = time.Now()
|
|
g.saveNeeded = true
|
|
g.coreMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
}
|
|
|
|
// SetLevel sets the guild level
|
|
func (g *Guild) SetLevel(level int8, sendPacket bool) {
|
|
if level > MaxGuildLevel {
|
|
level = MaxGuildLevel
|
|
}
|
|
if level < 1 {
|
|
level = 1
|
|
}
|
|
|
|
g.coreMutex.Lock()
|
|
g.level = level
|
|
g.lastModified = time.Now()
|
|
g.saveNeeded = true
|
|
g.coreMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
}
|
|
|
|
// SetMOTD sets the guild message of the day
|
|
func (g *Guild) SetMOTD(motd string, sendPacket bool) {
|
|
if len(motd) > MaxMOTDLength {
|
|
motd = motd[:MaxMOTDLength]
|
|
}
|
|
|
|
g.coreMutex.Lock()
|
|
g.motd = motd
|
|
g.lastModified = time.Now()
|
|
g.saveNeeded = true
|
|
g.coreMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
}
|
|
|
|
// AddEXPCurrent adds experience to the guild
|
|
func (g *Guild) AddEXPCurrent(exp int64, sendPacket bool) {
|
|
g.coreMutex.Lock()
|
|
g.expCurrent += exp
|
|
g.lastModified = time.Now()
|
|
g.saveNeeded = true
|
|
g.coreMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
}
|
|
|
|
// GetEXPCurrent returns the current guild experience
|
|
func (g *Guild) GetEXPCurrent() int64 {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.expCurrent
|
|
}
|
|
|
|
// GetEXPToNextLevel returns the experience needed for next level
|
|
func (g *Guild) GetEXPToNextLevel() int64 {
|
|
g.coreMutex.RLock()
|
|
defer g.coreMutex.RUnlock()
|
|
return g.expToNextLevel
|
|
}
|
|
|
|
// Member management (preserving C++ API)
|
|
|
|
// AddNewGuildMember adds a new member to the guild
|
|
func (g *Guild) AddNewGuildMember(characterID int32, invitedBy string, joinDate time.Time, rank int8) bool {
|
|
g.membersMutex.Lock()
|
|
defer g.membersMutex.Unlock()
|
|
|
|
// Check if member already exists
|
|
if _, exists := g.members[characterID]; exists {
|
|
return false
|
|
}
|
|
|
|
member := &GuildMember{
|
|
CharacterID: characterID,
|
|
Rank: rank,
|
|
JoinDate: joinDate,
|
|
LastLoginDate: time.Now(),
|
|
PointHistory: make([]PointHistory, 0),
|
|
SaveNeeded: true,
|
|
}
|
|
|
|
g.members[characterID] = member
|
|
g.memberSaveNeeded = true
|
|
g.lastModified = time.Now()
|
|
|
|
// Add guild event
|
|
g.addNewGuildEventNoLock(EventMemberJoins, fmt.Sprintf("%s has joined the guild", member.Name), time.Now(), true)
|
|
|
|
return true
|
|
}
|
|
|
|
// RemoveGuildMember removes a member from the guild
|
|
func (g *Guild) RemoveGuildMember(characterID int32, sendPacket bool) {
|
|
g.membersMutex.Lock()
|
|
defer g.membersMutex.Unlock()
|
|
|
|
if member, exists := g.members[characterID]; exists {
|
|
// Add guild event
|
|
g.addNewGuildEventNoLock(EventMemberLeaves, fmt.Sprintf("%s has left the guild", member.Name), time.Now(), sendPacket)
|
|
|
|
delete(g.members, characterID)
|
|
g.memberSaveNeeded = true
|
|
g.lastModified = time.Now()
|
|
}
|
|
}
|
|
|
|
// GetMember returns a guild member by character ID
|
|
func (g *Guild) GetMember(characterID int32) *GuildMember {
|
|
g.membersMutex.RLock()
|
|
defer g.membersMutex.RUnlock()
|
|
|
|
return g.members[characterID]
|
|
}
|
|
|
|
// GetMemberByName returns a guild member by name
|
|
func (g *Guild) GetMemberByName(playerName string) *GuildMember {
|
|
g.membersMutex.RLock()
|
|
defer g.membersMutex.RUnlock()
|
|
|
|
for _, member := range g.members {
|
|
if member.Name == playerName {
|
|
return member
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasMember checks if a character is a member of the guild
|
|
func (g *Guild) HasMember(characterID int32) bool {
|
|
g.membersMutex.RLock()
|
|
defer g.membersMutex.RUnlock()
|
|
|
|
_, exists := g.members[characterID]
|
|
return exists
|
|
}
|
|
|
|
// GetMemberCount returns the number of members in the guild
|
|
func (g *Guild) GetMemberCount() int {
|
|
g.membersMutex.RLock()
|
|
defer g.membersMutex.RUnlock()
|
|
|
|
return len(g.members)
|
|
}
|
|
|
|
// GetAllMembers returns all guild members
|
|
func (g *Guild) GetAllMembers() []*GuildMember {
|
|
g.membersMutex.RLock()
|
|
defer g.membersMutex.RUnlock()
|
|
|
|
members := make([]*GuildMember, 0, len(g.members))
|
|
for _, member := range g.members {
|
|
members = append(members, member)
|
|
}
|
|
|
|
return members
|
|
}
|
|
|
|
// PromoteGuildMember promotes a guild member
|
|
func (g *Guild) PromoteGuildMember(characterID int32, promoterName string, sendPacket bool) bool {
|
|
g.membersMutex.Lock()
|
|
defer g.membersMutex.Unlock()
|
|
|
|
member, exists := g.members[characterID]
|
|
if !exists || member.Rank <= RankLeader {
|
|
return false
|
|
}
|
|
|
|
oldRank := member.Rank
|
|
member.Rank--
|
|
member.SaveNeeded = true
|
|
g.memberSaveNeeded = true
|
|
g.lastModified = time.Now()
|
|
|
|
// Add guild event
|
|
g.addNewGuildEventNoLock(EventMemberPromoted,
|
|
fmt.Sprintf("%s has been promoted from %s to %s by %s",
|
|
member.Name, g.getRankNameNoLock(oldRank), g.getRankNameNoLock(member.Rank), promoterName),
|
|
time.Now(), sendPacket)
|
|
|
|
return true
|
|
}
|
|
|
|
// DemoteGuildMember demotes a guild member
|
|
func (g *Guild) DemoteGuildMember(characterID int32, demoterName string, sendPacket bool) bool {
|
|
g.membersMutex.Lock()
|
|
defer g.membersMutex.Unlock()
|
|
|
|
member, exists := g.members[characterID]
|
|
if !exists || member.Rank >= RankRecruit {
|
|
return false
|
|
}
|
|
|
|
oldRank := member.Rank
|
|
member.Rank++
|
|
member.SaveNeeded = true
|
|
g.memberSaveNeeded = true
|
|
g.lastModified = time.Now()
|
|
|
|
// Add guild event
|
|
g.addNewGuildEventNoLock(EventMemberDemoted,
|
|
fmt.Sprintf("%s has been demoted from %s to %s by %s",
|
|
member.Name, g.getRankNameNoLock(oldRank), g.getRankNameNoLock(member.Rank), demoterName),
|
|
time.Now(), sendPacket)
|
|
|
|
return true
|
|
}
|
|
|
|
// Permission system (preserving C++ API)
|
|
|
|
// SetPermission sets a guild permission for a rank
|
|
func (g *Guild) SetPermission(rank, permission, value int8, sendPacket, saveNeeded bool) bool {
|
|
if rank < RankLeader || rank > RankRecruit {
|
|
return false
|
|
}
|
|
|
|
g.permissionsMutex.Lock()
|
|
defer g.permissionsMutex.Unlock()
|
|
|
|
if g.permissions[rank] == nil {
|
|
g.permissions[rank] = make(map[int8]int8)
|
|
}
|
|
|
|
g.permissions[rank][permission] = value
|
|
g.lastModified = time.Now()
|
|
|
|
if saveNeeded {
|
|
g.ranksSaveNeeded = true
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetPermission returns a guild permission for a rank
|
|
func (g *Guild) GetPermission(rank, permission int8) int8 {
|
|
g.permissionsMutex.RLock()
|
|
defer g.permissionsMutex.RUnlock()
|
|
|
|
if rankPerms, exists := g.permissions[rank]; exists {
|
|
if value, exists := rankPerms[permission]; exists {
|
|
return value
|
|
}
|
|
}
|
|
|
|
// Return default permission based on rank
|
|
return g.getDefaultPermission(rank, permission)
|
|
}
|
|
|
|
// HasPermission checks if a rank has a specific permission
|
|
func (g *Guild) HasPermission(rank, permission int8) bool {
|
|
return g.GetPermission(rank, permission) > 0
|
|
}
|
|
|
|
// Rank management (preserving C++ API)
|
|
|
|
// SetRankName sets a custom rank name
|
|
func (g *Guild) SetRankName(rank int8, name string, sendPacket bool) bool {
|
|
if rank < RankLeader || rank > RankRecruit {
|
|
return false
|
|
}
|
|
|
|
g.ranksMutex.Lock()
|
|
g.ranks[rank] = name
|
|
g.lastModified = time.Now()
|
|
g.ranksSaveNeeded = true
|
|
g.ranksMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetRankName returns the name for a rank
|
|
func (g *Guild) GetRankName(rank int8) string {
|
|
g.ranksMutex.RLock()
|
|
defer g.ranksMutex.RUnlock()
|
|
|
|
return g.getRankNameNoLock(rank)
|
|
}
|
|
|
|
// Event system (preserving C++ API)
|
|
|
|
// AddNewGuildEvent adds a new event to the guild
|
|
func (g *Guild) AddNewGuildEvent(eventType int32, description string, date time.Time, sendPacket bool) {
|
|
g.eventsMutex.Lock()
|
|
defer g.eventsMutex.Unlock()
|
|
g.addNewGuildEventNoLock(eventType, description, date, sendPacket)
|
|
}
|
|
|
|
// GetGuildEvent returns a guild event by ID
|
|
func (g *Guild) GetGuildEvent(eventID int64) *GuildEvent {
|
|
g.eventsMutex.RLock()
|
|
defer g.eventsMutex.RUnlock()
|
|
|
|
for i := range g.guildEvents {
|
|
if g.guildEvents[i].EventID == eventID {
|
|
return &g.guildEvents[i]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetNextEventID returns the next available event ID
|
|
func (g *Guild) GetNextEventID() int64 {
|
|
g.eventsMutex.Lock()
|
|
defer g.eventsMutex.Unlock()
|
|
|
|
eventID := g.nextEventID
|
|
g.nextEventID++
|
|
return eventID
|
|
}
|
|
|
|
// Recruiting system (preserving C++ API)
|
|
|
|
// SetRecruitingFlag sets a recruiting flag
|
|
func (g *Guild) SetRecruitingFlag(flag, value int8, sendPacket bool) bool {
|
|
if flag < RecruitingFlagTraining || flag > RecruitingFlagTradeskillers {
|
|
return false
|
|
}
|
|
|
|
g.recruitingMutex.Lock()
|
|
g.recruitingFlags[flag] = value
|
|
g.lastModified = time.Now()
|
|
g.recruitingSaveNeeded = true
|
|
g.recruitingMutex.Unlock()
|
|
|
|
if sendPacket {
|
|
g.SendGuildUpdate()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetRecruitingFlag returns a recruiting flag
|
|
func (g *Guild) GetRecruitingFlag(flag int8) int8 {
|
|
g.recruitingMutex.RLock()
|
|
defer g.recruitingMutex.RUnlock()
|
|
|
|
if value, exists := g.recruitingFlags[flag]; exists {
|
|
return value
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// GetGuildInfo returns basic guild information
|
|
func (g *Guild) GetGuildInfo() GuildInfo {
|
|
g.coreMutex.RLock()
|
|
g.membersMutex.RLock()
|
|
g.recruitingMutex.RLock()
|
|
|
|
defer g.coreMutex.RUnlock()
|
|
defer g.membersMutex.RUnlock()
|
|
defer g.recruitingMutex.RUnlock()
|
|
|
|
return GuildInfo{
|
|
ID: g.id,
|
|
Name: g.name,
|
|
Level: g.level,
|
|
FormedDate: g.formedDate,
|
|
MOTD: g.motd,
|
|
MemberCount: len(g.members),
|
|
OnlineMemberCount: g.getOnlineMemberCountNoLock(),
|
|
RecruiterCount: g.getRecruiterCountNoLock(),
|
|
RecruitingShortDesc: g.recruitingShortDesc,
|
|
RecruitingFullDesc: g.recruitingFullDesc,
|
|
RecruitingMinLevel: g.recruitingMinLevel,
|
|
RecruitingPlayStyle: g.recruitingPlayStyle,
|
|
IsRecruiting: g.getRecruiterCountNoLock() > 0,
|
|
UniqueAccounts: g.getUniqueAccountCountNoLock(),
|
|
ExpCurrent: g.expCurrent,
|
|
ExpToNextLevel: g.expToNextLevel,
|
|
}
|
|
}
|
|
|
|
// Packet sending methods
|
|
|
|
// SendGuildUpdate sends a guild update packet to all members
|
|
func (g *Guild) SendGuildUpdate() {
|
|
members := g.GetAllMembers()
|
|
|
|
for _, member := range members {
|
|
if member.Client != nil {
|
|
g.sendGuildUpdateToClient(member.Client)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sendGuildUpdateToClient sends a guild update packet to a specific client
|
|
func (g *Guild) sendGuildUpdateToClient(client Client) {
|
|
clientVersion := client.GetVersion()
|
|
packetData, err := g.buildGuildUpdatePacket(clientVersion)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
client.SendPacket(packets.OP_GuildUpdateMsg, packetData)
|
|
}
|
|
|
|
// Helper methods (internal, no lock versions)
|
|
|
|
func (g *Guild) getDefaultPermission(rank, permission int8) int8 {
|
|
// Leaders have all permissions by default
|
|
if rank == RankLeader {
|
|
return 1
|
|
}
|
|
|
|
// Default permissions based on rank and permission type
|
|
switch permission {
|
|
case PermissionSeeGuildChat, PermissionSpeakInGuildChat:
|
|
return 1 // All members can see and speak in guild chat
|
|
case PermissionReceivePoints:
|
|
return 1 // All members can receive points
|
|
case PermissionSeeOfficerChat, PermissionSpeakInOfficerChat:
|
|
if rank <= RankOfficer {
|
|
return 1
|
|
}
|
|
case PermissionInvite:
|
|
if rank <= RankSeniorMember {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
return 0 // Default to no permission
|
|
}
|
|
|
|
func (g *Guild) getRankNameNoLock(rank int8) string {
|
|
if name, exists := g.ranks[rank]; exists {
|
|
return name
|
|
}
|
|
if defaultName, exists := DefaultRankNames[rank]; exists {
|
|
return defaultName
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
func (g *Guild) addNewGuildEventNoLock(eventType int32, description string, date time.Time, sendPacket bool) {
|
|
event := GuildEvent{
|
|
EventID: g.nextEventID,
|
|
Date: date,
|
|
Type: eventType,
|
|
Description: description,
|
|
Locked: 0,
|
|
SaveNeeded: true,
|
|
}
|
|
|
|
g.nextEventID++
|
|
|
|
// Add to front of events list (newest first)
|
|
g.guildEvents = append([]GuildEvent{event}, g.guildEvents...)
|
|
|
|
// Limit event history
|
|
if len(g.guildEvents) > MaxEvents {
|
|
g.guildEvents = g.guildEvents[:MaxEvents]
|
|
}
|
|
|
|
g.eventsSaveNeeded = true
|
|
g.lastModified = time.Now()
|
|
}
|
|
|
|
func (g *Guild) getOnlineMemberCountNoLock() int {
|
|
count := 0
|
|
for _, member := range g.members {
|
|
if member.Entity != nil && member.Entity.IsOnline() {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (g *Guild) getRecruiterCountNoLock() int {
|
|
count := 0
|
|
for _, member := range g.members {
|
|
if member.MemberFlags&MemberFlagRecruitingForGuild != 0 {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (g *Guild) getUniqueAccountCountNoLock() int {
|
|
accounts := make(map[int32]bool)
|
|
for _, member := range g.members {
|
|
accounts[member.AccountID] = true
|
|
}
|
|
return len(accounts)
|
|
}
|
|
|
|
// Helper methods for GuildInvite
|
|
|
|
// IsExpired checks if the guild invite has expired
|
|
func (gi *GuildInvite) IsExpired() bool {
|
|
return time.Now().After(gi.ExpiresAt)
|
|
}
|
|
|
|
// TimeRemaining returns the remaining time for the invite
|
|
func (gi *GuildInvite) TimeRemaining() time.Duration {
|
|
return time.Until(gi.ExpiresAt)
|
|
}
|
|
|
|
// buildGuildUpdatePacket builds a guild update packet using the packet system
|
|
func (g *Guild) buildGuildUpdatePacket(clientVersion uint32) ([]byte, error) {
|
|
g.coreMutex.RLock()
|
|
g.membersMutex.RLock()
|
|
g.ranksMutex.RLock()
|
|
g.permissionsMutex.RLock()
|
|
g.recruitingMutex.RLock()
|
|
|
|
defer g.coreMutex.RUnlock()
|
|
defer g.membersMutex.RUnlock()
|
|
defer g.ranksMutex.RUnlock()
|
|
defer g.permissionsMutex.RUnlock()
|
|
defer g.recruitingMutex.RUnlock()
|
|
|
|
packet := make(map[string]any)
|
|
|
|
// Basic guild information
|
|
packet["guild_name"] = g.name
|
|
packet["guild_motd"] = g.motd
|
|
packet["guild_id"] = g.id
|
|
packet["guild_level"] = uint8(g.level)
|
|
packet["formed_date"] = uint32(g.formedDate.Unix())
|
|
packet["unique_accounts"] = uint16(g.getUniqueAccountCountNoLock())
|
|
packet["num_members"] = uint16(len(g.members))
|
|
packet["exp_current"] = uint64(g.expCurrent)
|
|
packet["exp_to_next_level"] = uint64(g.expToNextLevel)
|
|
|
|
// Version-specific fields
|
|
if clientVersion >= 1008 {
|
|
packet["guild_hall_location"] = g.guildHallLocation
|
|
packet["guild_hall_zonename"] = g.guildHallZoneName
|
|
packet["guild_hall_filename"] = g.guildHallFilename
|
|
}
|
|
|
|
// Event filters (simplified - would need proper implementation based on C++)
|
|
packet["event_filter_retain1"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_retain2"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_retain3"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_retain4"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_broadcast1"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_broadcast2"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_broadcast3"] = uint32(0xFFFFFFFF)
|
|
packet["event_filter_broadcast4"] = uint32(0xFFFFFFFF)
|
|
|
|
// Recruiting information
|
|
if clientVersion >= 562 {
|
|
packet["recruiting_looking_for"] = uint8(g.getRecruitingLookingForNoLock())
|
|
packet["recruiting_desc_tag1"] = uint8(g.recruitingDescTags[0])
|
|
packet["recruiting_desc_tag2"] = uint8(g.recruitingDescTags[1])
|
|
packet["recruiting_desc_tag3"] = uint8(g.recruitingDescTags[2])
|
|
packet["recruiting_desc_tag4"] = uint8(g.recruitingDescTags[3])
|
|
packet["recruiting_playstyle"] = uint8(g.recruitingPlayStyle)
|
|
packet["recruiting_min_level"] = uint8(g.recruitingMinLevel)
|
|
}
|
|
|
|
packet["recuiting_short_description"] = g.recruitingShortDesc // Note: typo preserved from C++
|
|
packet["recruiting_full_description"] = g.recruitingFullDesc
|
|
|
|
// Rank names and permissions
|
|
for rank := int8(RankLeader); rank <= int8(RankRecruit); rank++ {
|
|
rankNameField := fmt.Sprintf("rank%d_name", rank)
|
|
perm1Field := fmt.Sprintf("rank%d_permissions1", rank)
|
|
perm2Field := fmt.Sprintf("rank%d_permissions2", rank)
|
|
|
|
packet[rankNameField] = g.getRankNameNoLock(rank)
|
|
|
|
// Pack permissions into two uint32 values (simplified)
|
|
perm1, perm2 := g.packPermissionsNoLock(rank)
|
|
packet[perm1Field] = perm1
|
|
packet[perm2Field] = perm2
|
|
}
|
|
|
|
// Bank names (for version >= 562)
|
|
if clientVersion >= 562 {
|
|
packet["bank1_name"] = g.banks[0].Name
|
|
packet["bank2_name"] = g.banks[1].Name
|
|
packet["bank3_name"] = g.banks[2].Name
|
|
packet["bank4_name"] = g.banks[3].Name
|
|
}
|
|
|
|
// Build the packet
|
|
return packets.BuildPacket("GuildUpdate", packet, clientVersion, 0)
|
|
}
|
|
|
|
// packPermissionsNoLock packs guild permissions for a rank into two uint32 values
|
|
func (g *Guild) packPermissionsNoLock(rank int8) (uint32, uint32) {
|
|
var perm1, perm2 uint32
|
|
|
|
// Pack the first 32 permissions into perm1
|
|
for i := int8(0); i < 32; i++ {
|
|
if g.getPermissionNoLock(rank, i) > 0 {
|
|
perm1 |= 1 << i
|
|
}
|
|
}
|
|
|
|
// Pack the next 32 permissions into perm2
|
|
for i := int8(32); i < 64; i++ {
|
|
if g.getPermissionNoLock(rank, i) > 0 {
|
|
perm2 |= 1 << (i - 32)
|
|
}
|
|
}
|
|
|
|
return perm1, perm2
|
|
}
|
|
|
|
// getPermissionNoLock returns a guild permission without locking (for internal use)
|
|
func (g *Guild) getPermissionNoLock(rank, permission int8) int8 {
|
|
if rankPerms, exists := g.permissions[rank]; exists {
|
|
if value, exists := rankPerms[permission]; exists {
|
|
return value
|
|
}
|
|
}
|
|
|
|
// Return default permission based on rank
|
|
return g.getDefaultPermission(rank, permission)
|
|
}
|
|
|
|
// getRecruitingLookingForNoLock returns the recruiting flags as a packed byte
|
|
func (g *Guild) getRecruitingLookingForNoLock() uint8 {
|
|
var flags uint8
|
|
|
|
if g.recruitingFlags[RecruitingFlagTraining] > 0 {
|
|
flags |= 1 << 0
|
|
}
|
|
if g.recruitingFlags[RecruitingFlagFighters] > 0 {
|
|
flags |= 1 << 1
|
|
}
|
|
if g.recruitingFlags[RecruitingFlagPriests] > 0 {
|
|
flags |= 1 << 2
|
|
}
|
|
if g.recruitingFlags[RecruitingFlagScouts] > 0 {
|
|
flags |= 1 << 3
|
|
}
|
|
if g.recruitingFlags[RecruitingFlagMages] > 0 {
|
|
flags |= 1 << 4
|
|
}
|
|
if g.recruitingFlags[RecruitingFlagTradeskillers] > 0 {
|
|
flags |= 1 << 5
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
// nullLogger is a no-op logger implementation
|
|
type nullLogger struct{}
|
|
|
|
func (nl *nullLogger) Debug(msg string, args ...any) {}
|
|
func (nl *nullLogger) Info(msg string, args ...any) {}
|
|
func (nl *nullLogger) Error(msg string, args ...any) {} |