simplify guilds
This commit is contained in:
parent
575f94f3c1
commit
95c0561416
@ -13,6 +13,8 @@ This document outlines how we successfully simplified the EverQuest II housing p
|
||||
- Entity
|
||||
- Factions
|
||||
- Ground Spawn
|
||||
- Groups
|
||||
- Guilds
|
||||
|
||||
## Before: Complex Architecture (8 Files, ~2000+ Lines)
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package guilds
|
||||
|
||||
import "time"
|
||||
|
||||
// Guild rank constants
|
||||
const (
|
||||
RankLeader = 0
|
||||
@ -228,3 +230,27 @@ var DefaultRankNames = map[int8]string{
|
||||
RankInitiate: "Initiate",
|
||||
RankRecruit: "Recruit",
|
||||
}
|
||||
|
||||
// Additional guild-related constants
|
||||
|
||||
// Guild event types (additional)
|
||||
const (
|
||||
EventGuildFormed = 200 // Custom event for guild formation
|
||||
EventGuildDisbanded = 201 // Custom event for guild disbanding
|
||||
)
|
||||
|
||||
// Guild invitation result codes (preserved from C++)
|
||||
const (
|
||||
GUILD_INVITE_SUCCESS = 0
|
||||
GUILD_INVITE_GUILD_NOT_FOUND = 1
|
||||
GUILD_INVITE_NO_PERMISSION = 2
|
||||
GUILD_INVITE_ALREADY_IN_GUILD = 3
|
||||
GUILD_INVITE_ALREADY_HAS_INVITE = 4
|
||||
GUILD_INVITE_TARGET_NOT_FOUND = 5
|
||||
GUILD_INVITE_NO_INVITE = 6
|
||||
GUILD_INVITE_EXPIRED = 7
|
||||
GUILD_INVITE_FAILED = 8
|
||||
)
|
||||
|
||||
// Guild invitation timeout (5 minutes)
|
||||
const GUILD_INVITE_TIMEOUT = 5 * time.Minute
|
||||
|
@ -1,926 +0,0 @@
|
||||
package guilds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
)
|
||||
|
||||
// New creates a new guild instance
|
||||
func New(db *database.Database) *Guild {
|
||||
guild := &Guild{
|
||||
db: db,
|
||||
isNew: true,
|
||||
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),
|
||||
level: 1,
|
||||
expCurrent: 111,
|
||||
expToNextLevel: 2521,
|
||||
recruitingMinLevel: 1,
|
||||
recruitingPlayStyle: RecruitingPlayStyleNone,
|
||||
nextEventID: 1,
|
||||
lastModified: time.Now(),
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return guild
|
||||
}
|
||||
|
||||
// Load loads a guild from the database by ID
|
||||
func Load(db *database.Database, id int32) (*Guild, error) {
|
||||
guild := &Guild{
|
||||
db: db,
|
||||
isNew: false,
|
||||
id: id,
|
||||
}
|
||||
|
||||
if err := guild.Reload(); err != nil {
|
||||
return nil, fmt.Errorf("failed to load guild %d: %w", id, err)
|
||||
}
|
||||
|
||||
return guild, nil
|
||||
}
|
||||
|
||||
// Save saves the guild to the database
|
||||
func (g *Guild) Save() error {
|
||||
ctx := context.Background()
|
||||
|
||||
if g.isNew {
|
||||
return g.create(ctx)
|
||||
}
|
||||
return g.update(ctx)
|
||||
}
|
||||
|
||||
// Delete removes the guild from the database
|
||||
func (g *Guild) Delete() error {
|
||||
ctx := context.Background()
|
||||
return g.delete(ctx)
|
||||
}
|
||||
|
||||
// Reload refreshes the guild data from the database
|
||||
func (g *Guild) Reload() error {
|
||||
ctx := context.Background()
|
||||
return g.load(ctx)
|
||||
}
|
||||
|
||||
// SetID sets the guild ID
|
||||
func (g *Guild) SetID(id int32) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.id = id
|
||||
}
|
||||
|
||||
// SetName sets the guild name
|
||||
func (g *Guild) SetName(name string, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if len(name) > MaxGuildNameLength {
|
||||
name = name[:MaxGuildNameLength]
|
||||
}
|
||||
g.name = name
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetLevel sets the guild level
|
||||
func (g *Guild) SetLevel(level int8, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if level > MaxGuildLevel {
|
||||
level = MaxGuildLevel
|
||||
}
|
||||
if level < 1 {
|
||||
level = 1
|
||||
}
|
||||
|
||||
g.level = level
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetFormedDate sets the guild formation date
|
||||
func (g *Guild) SetFormedDate(formedDate time.Time) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.formedDate = formedDate
|
||||
}
|
||||
|
||||
// SetMOTD sets the guild message of the day
|
||||
func (g *Guild) SetMOTD(motd string, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if len(motd) > MaxMOTDLength {
|
||||
motd = motd[:MaxMOTDLength]
|
||||
}
|
||||
g.motd = motd
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetID returns the guild ID
|
||||
func (g *Guild) GetID() int32 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.id
|
||||
}
|
||||
|
||||
// GetName returns the guild name
|
||||
func (g *Guild) GetName() string {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.name
|
||||
}
|
||||
|
||||
// GetLevel returns the guild level
|
||||
func (g *Guild) GetLevel() int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.level
|
||||
}
|
||||
|
||||
// GetFormedDate returns the guild formation date
|
||||
func (g *Guild) GetFormedDate() time.Time {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.formedDate
|
||||
}
|
||||
|
||||
// GetMOTD returns the guild message of the day
|
||||
func (g *Guild) GetMOTD() string {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.motd
|
||||
}
|
||||
|
||||
// SetEXPCurrent sets the current guild experience
|
||||
func (g *Guild) SetEXPCurrent(exp int64, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.expCurrent = exp
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// AddEXPCurrent adds experience to the guild
|
||||
func (g *Guild) AddEXPCurrent(exp int64, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.expCurrent += exp
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetEXPCurrent returns the current guild experience
|
||||
func (g *Guild) GetEXPCurrent() int64 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.expCurrent
|
||||
}
|
||||
|
||||
// SetEXPToNextLevel sets the experience needed for next level
|
||||
func (g *Guild) SetEXPToNextLevel(exp int64, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.expToNextLevel = exp
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.saveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetEXPToNextLevel returns the experience needed for next level
|
||||
func (g *Guild) GetEXPToNextLevel() int64 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.expToNextLevel
|
||||
}
|
||||
|
||||
// SetRecruitingShortDesc sets the short recruiting description
|
||||
func (g *Guild) SetRecruitingShortDesc(desc string, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.recruitingShortDesc = desc
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecruitingShortDesc returns the short recruiting description
|
||||
func (g *Guild) GetRecruitingShortDesc() string {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.recruitingShortDesc
|
||||
}
|
||||
|
||||
// SetRecruitingFullDesc sets the full recruiting description
|
||||
func (g *Guild) SetRecruitingFullDesc(desc string, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if len(desc) > MaxRecruitingDescLength {
|
||||
desc = desc[:MaxRecruitingDescLength]
|
||||
}
|
||||
g.recruitingFullDesc = desc
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecruitingFullDesc returns the full recruiting description
|
||||
func (g *Guild) GetRecruitingFullDesc() string {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.recruitingFullDesc
|
||||
}
|
||||
|
||||
// SetRecruitingMinLevel sets the minimum level for recruiting
|
||||
func (g *Guild) SetRecruitingMinLevel(level int8, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.recruitingMinLevel = level
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecruitingMinLevel returns the minimum level for recruiting
|
||||
func (g *Guild) GetRecruitingMinLevel() int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.recruitingMinLevel
|
||||
}
|
||||
|
||||
// SetRecruitingPlayStyle sets the recruiting play style
|
||||
func (g *Guild) SetRecruitingPlayStyle(playStyle int8, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.recruitingPlayStyle = playStyle
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecruitingPlayStyle returns the recruiting play style
|
||||
func (g *Guild) GetRecruitingPlayStyle() int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.recruitingPlayStyle
|
||||
}
|
||||
|
||||
// SetRecruitingDescTag sets a recruiting description tag
|
||||
func (g *Guild) SetRecruitingDescTag(index, tag int8, sendPacket bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if index < 0 || index > 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
g.recruitingDescTags[index] = tag
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRecruitingDescTag returns a recruiting description tag
|
||||
func (g *Guild) GetRecruitingDescTag(index int8) int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
if tag, exists := g.recruitingDescTags[index]; exists {
|
||||
return tag
|
||||
}
|
||||
return RecruitingDescTagNone
|
||||
}
|
||||
|
||||
// SetPermission sets a guild permission for a rank
|
||||
func (g *Guild) SetPermission(rank, permission, value int8, sendPacket, saveNeeded bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if rank < RankLeader || rank > RankRecruit {
|
||||
return false
|
||||
}
|
||||
|
||||
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.mu.RLock()
|
||||
defer g.mu.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)
|
||||
}
|
||||
|
||||
// SetEventFilter sets an event filter
|
||||
func (g *Guild) SetEventFilter(eventID, category, value int8, sendPacket, saveNeeded bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.eventFilters[eventID] == nil {
|
||||
g.eventFilters[eventID] = make(map[int8]int8)
|
||||
}
|
||||
|
||||
g.eventFilters[eventID][category] = value
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if saveNeeded {
|
||||
g.eventFiltersSaveNeeded = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetEventFilter returns an event filter
|
||||
func (g *Guild) GetEventFilter(eventID, category int8) int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
if eventFilters, exists := g.eventFilters[eventID]; exists {
|
||||
if value, exists := eventFilters[category]; exists {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return 1 // Default to enabled
|
||||
}
|
||||
|
||||
// GetNumUniqueAccounts returns the number of unique accounts in the guild
|
||||
func (g *Guild) GetNumUniqueAccounts() int32 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
accounts := make(map[int32]bool)
|
||||
for _, member := range g.members {
|
||||
accounts[member.AccountID] = true
|
||||
}
|
||||
|
||||
return int32(len(accounts))
|
||||
}
|
||||
|
||||
// GetNumRecruiters returns the number of recruiters in the guild
|
||||
func (g *Guild) GetNumRecruiters() int32 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
count := int32(0)
|
||||
for _, member := range g.members {
|
||||
if member.MemberFlags&MemberFlagRecruitingForGuild != 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
// GetNextRecruiterID returns the next available recruiter ID
|
||||
func (g *Guild) GetNextRecruiterID() int32 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
maxID := int32(0)
|
||||
for _, member := range g.members {
|
||||
if member.RecruiterID > maxID {
|
||||
maxID = member.RecruiterID
|
||||
}
|
||||
}
|
||||
|
||||
return maxID + 1
|
||||
}
|
||||
|
||||
// GetNextEventID returns the next available event ID
|
||||
func (g *Guild) GetNextEventID() int64 {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
eventID := g.nextEventID
|
||||
g.nextEventID++
|
||||
return eventID
|
||||
}
|
||||
|
||||
// GetGuildMember returns a guild member by character ID
|
||||
func (g *Guild) GetGuildMember(characterID int32) *GuildMember {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
return g.members[characterID]
|
||||
}
|
||||
|
||||
// GetGuildMemberByName returns a guild member by name
|
||||
func (g *Guild) GetGuildMemberByName(playerName string) *GuildMember {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
for _, member := range g.members {
|
||||
if strings.EqualFold(member.Name, playerName) {
|
||||
return member
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGuildRecruiters returns all guild recruiters
|
||||
func (g *Guild) GetGuildRecruiters() []*GuildMember {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
var recruiters []*GuildMember
|
||||
for _, member := range g.members {
|
||||
if member.MemberFlags&MemberFlagRecruitingForGuild != 0 {
|
||||
recruiters = append(recruiters, member)
|
||||
}
|
||||
}
|
||||
|
||||
return recruiters
|
||||
}
|
||||
|
||||
// GetGuildEvent returns a guild event by ID
|
||||
func (g *Guild) GetGuildEvent(eventID int64) *GuildEvent {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
for i := range g.guildEvents {
|
||||
if g.guildEvents[i].EventID == eventID {
|
||||
return &g.guildEvents[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRankName sets a custom rank name
|
||||
func (g *Guild) SetRankName(rank int8, name string, sendPacket bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if rank < RankLeader || rank > RankRecruit {
|
||||
return false
|
||||
}
|
||||
|
||||
g.ranks[rank] = name
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.ranksSaveNeeded = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRankName returns the name for a rank
|
||||
func (g *Guild) GetRankName(rank int8) string {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
if name, exists := g.ranks[rank]; exists {
|
||||
return name
|
||||
}
|
||||
|
||||
// Return default rank name
|
||||
if defaultName, exists := DefaultRankNames[rank]; exists {
|
||||
return defaultName
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// SetRecruitingFlag sets a recruiting flag
|
||||
func (g *Guild) SetRecruitingFlag(flag, value int8, sendPacket bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if flag < RecruitingFlagTraining || flag > RecruitingFlagTradeskillers {
|
||||
return false
|
||||
}
|
||||
|
||||
g.recruitingFlags[flag] = value
|
||||
g.lastModified = time.Now()
|
||||
|
||||
if sendPacket {
|
||||
g.recruitingSaveNeeded = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRecruitingFlag returns a recruiting flag
|
||||
func (g *Guild) GetRecruitingFlag(flag int8) int8 {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
if value, exists := g.recruitingFlags[flag]; exists {
|
||||
return value
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// AddNewGuildMember adds a new member to the guild
|
||||
func (g *Guild) AddNewGuildMember(characterID int32, invitedBy string, joinDate time.Time, rank int8) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
// Check if member already exists
|
||||
if _, exists := g.members[characterID]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
member := &GuildMember{
|
||||
CharacterID: characterID,
|
||||
Rank: rank,
|
||||
JoinDate: joinDate,
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
}
|
||||
|
||||
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.mu.Lock()
|
||||
defer g.mu.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()
|
||||
}
|
||||
}
|
||||
|
||||
// PromoteGuildMember promotes a guild member
|
||||
func (g *Guild) PromoteGuildMember(characterID int32, promoterName string, sendPacket bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
member, exists := g.members[characterID]
|
||||
if !exists || member.Rank <= RankLeader {
|
||||
return false
|
||||
}
|
||||
|
||||
oldRank := member.Rank
|
||||
member.Rank--
|
||||
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.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
member, exists := g.members[characterID]
|
||||
if !exists || member.Rank >= RankRecruit {
|
||||
return false
|
||||
}
|
||||
|
||||
oldRank := member.Rank
|
||||
member.Rank++
|
||||
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
|
||||
}
|
||||
|
||||
// AddPointsToGuildMember adds points to a specific guild member
|
||||
func (g *Guild) AddPointsToGuildMember(characterID int32, points float64, modifiedBy, comment string, sendPacket bool) bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
member, exists := g.members[characterID]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
member.Points += points
|
||||
|
||||
// Add to point history
|
||||
if len(member.PointHistory) >= MaxPointHistory {
|
||||
// Remove oldest entry
|
||||
member.PointHistory = member.PointHistory[1:]
|
||||
}
|
||||
|
||||
member.PointHistory = append(member.PointHistory, PointHistory{
|
||||
Date: time.Now(),
|
||||
ModifiedBy: modifiedBy,
|
||||
Comment: comment,
|
||||
Points: points,
|
||||
SaveNeeded: true,
|
||||
})
|
||||
|
||||
g.pointsHistorySaveNeeded = true
|
||||
g.lastModified = time.Now()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AddNewGuildEvent adds a new event to the guild
|
||||
func (g *Guild) AddNewGuildEvent(eventType int32, description string, date time.Time, sendPacket bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.addNewGuildEventNoLock(eventType, description, date, sendPacket)
|
||||
}
|
||||
|
||||
// addNewGuildEventNoLock is the internal implementation without locking
|
||||
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()
|
||||
}
|
||||
|
||||
// GetGuildInfo returns basic guild information
|
||||
func (g *Guild) GetGuildInfo() GuildInfo {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
return GuildInfo{
|
||||
ID: g.id,
|
||||
Name: g.name,
|
||||
Level: g.level,
|
||||
FormedDate: g.formedDate,
|
||||
MOTD: g.motd,
|
||||
MemberCount: len(g.members),
|
||||
RecruiterCount: int(g.getNumRecruitersNoLock()),
|
||||
RecruitingShortDesc: g.recruitingShortDesc,
|
||||
RecruitingFullDesc: g.recruitingFullDesc,
|
||||
RecruitingMinLevel: g.recruitingMinLevel,
|
||||
RecruitingPlayStyle: g.recruitingPlayStyle,
|
||||
IsRecruiting: g.getNumRecruitersNoLock() > 0,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllMembers returns all guild members
|
||||
func (g *Guild) GetAllMembers() []*GuildMember {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
members := make([]*GuildMember, 0, len(g.members))
|
||||
for _, member := range g.members {
|
||||
members = append(members, member)
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
// Save flag methods
|
||||
func (g *Guild) SetSaveNeeded(val bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.saveNeeded = val
|
||||
}
|
||||
|
||||
func (g *Guild) GetSaveNeeded() bool {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
return g.saveNeeded
|
||||
}
|
||||
|
||||
// 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) getNumRecruitersNoLock() int32 {
|
||||
count := int32(0)
|
||||
for _, member := range g.members {
|
||||
if member.MemberFlags&MemberFlagRecruitingForGuild != 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Database operations
|
||||
|
||||
func (g *Guild) create(ctx context.Context) error {
|
||||
// Use MySQL-compatible approach for both databases
|
||||
result, err := g.db.Exec(`INSERT INTO guilds (name, motd, level, xp, xp_needed, formed_on) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
g.name, g.motd, g.level, g.expCurrent, g.expToNextLevel, g.formedDate.Unix())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.id = int32(id)
|
||||
g.isNew = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Guild) update(ctx context.Context) error {
|
||||
// Use MySQL-compatible approach for both databases
|
||||
_, err := g.db.Exec(`UPDATE guilds SET name = ?, motd = ?, level = ?, xp = ?, xp_needed = ? WHERE id = ?`,
|
||||
g.name, g.motd, g.level, g.expCurrent, g.expToNextLevel, g.id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *Guild) delete(ctx context.Context) error {
|
||||
// Use MySQL-compatible approach for both databases
|
||||
_, err := g.db.Exec(`DELETE FROM guilds WHERE id = ?`, g.id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (g *Guild) load(ctx context.Context) error {
|
||||
// Use MySQL-compatible approach for both databases
|
||||
row := g.db.QueryRow(`SELECT name, motd, level, xp, xp_needed, formed_on FROM guilds WHERE id = ?`, g.id)
|
||||
var formedUnix int64
|
||||
err := row.Scan(&g.name, &g.motd, &g.level, &g.expCurrent, &g.expToNextLevel, &formedUnix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("guild %d not found: %w", g.id, err)
|
||||
}
|
||||
|
||||
g.formedDate = time.Unix(formedUnix, 0)
|
||||
return g.loadMembers(ctx)
|
||||
}
|
||||
|
||||
func (g *Guild) loadMembers(ctx context.Context) error {
|
||||
g.members = make(map[int32]*GuildMember)
|
||||
|
||||
// Use MySQL-compatible approach for both databases
|
||||
rows, err := g.db.Query(`SELECT char_id, name, rank, points, adventure_class, adventure_level,
|
||||
tradeskill_class, tradeskill_level, join_date, last_login_date FROM guild_members WHERE guild_id = ?`, g.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
member := &GuildMember{}
|
||||
var joinUnix, lastLoginUnix int64
|
||||
err := rows.Scan(&member.CharacterID, &member.Name, &member.Rank, &member.Points,
|
||||
&member.AdventureClass, &member.AdventureLevel, &member.TradeskillClass, &member.TradeskillLevel,
|
||||
&joinUnix, &lastLoginUnix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
member.JoinDate = time.Unix(joinUnix, 0)
|
||||
member.LastLoginDate = time.Unix(lastLoginUnix, 0)
|
||||
g.members[member.CharacterID] = member
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
1305
internal/guilds/guilds.go
Normal file
1305
internal/guilds/guilds.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,345 +0,0 @@
|
||||
package guilds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GuildDatabase defines database operations for guilds
|
||||
type GuildDatabase interface {
|
||||
// LoadGuilds retrieves all guilds from database
|
||||
LoadGuilds(ctx context.Context) ([]GuildData, error)
|
||||
|
||||
// LoadGuild retrieves a specific guild from database
|
||||
LoadGuild(ctx context.Context, guildID int32) (*GuildData, error)
|
||||
|
||||
// LoadGuildMembers retrieves all members for a guild
|
||||
LoadGuildMembers(ctx context.Context, guildID int32) ([]GuildMemberData, error)
|
||||
|
||||
// LoadGuildEvents retrieves events for a guild
|
||||
LoadGuildEvents(ctx context.Context, guildID int32) ([]GuildEventData, error)
|
||||
|
||||
// LoadGuildRanks retrieves custom rank names for a guild
|
||||
LoadGuildRanks(ctx context.Context, guildID int32) ([]GuildRankData, error)
|
||||
|
||||
// LoadGuildPermissions retrieves permissions for a guild
|
||||
LoadGuildPermissions(ctx context.Context, guildID int32) ([]GuildPermissionData, error)
|
||||
|
||||
// LoadGuildEventFilters retrieves event filters for a guild
|
||||
LoadGuildEventFilters(ctx context.Context, guildID int32) ([]GuildEventFilterData, error)
|
||||
|
||||
// LoadGuildRecruiting retrieves recruiting settings for a guild
|
||||
LoadGuildRecruiting(ctx context.Context, guildID int32) ([]GuildRecruitingData, error)
|
||||
|
||||
// LoadPointHistory retrieves point history for a member
|
||||
LoadPointHistory(ctx context.Context, characterID int32) ([]PointHistoryData, error)
|
||||
|
||||
// SaveGuild saves guild basic data
|
||||
SaveGuild(ctx context.Context, guild *Guild) error
|
||||
|
||||
// SaveGuildMembers saves all guild members
|
||||
SaveGuildMembers(ctx context.Context, guildID int32, members []*GuildMember) error
|
||||
|
||||
// SaveGuildEvents saves guild events
|
||||
SaveGuildEvents(ctx context.Context, guildID int32, events []GuildEvent) error
|
||||
|
||||
// SaveGuildRanks saves guild rank names
|
||||
SaveGuildRanks(ctx context.Context, guildID int32, ranks map[int8]string) error
|
||||
|
||||
// SaveGuildPermissions saves guild permissions
|
||||
SaveGuildPermissions(ctx context.Context, guildID int32, permissions map[int8]map[int8]int8) error
|
||||
|
||||
// SaveGuildEventFilters saves guild event filters
|
||||
SaveGuildEventFilters(ctx context.Context, guildID int32, filters map[int8]map[int8]int8) error
|
||||
|
||||
// SaveGuildRecruiting saves guild recruiting settings
|
||||
SaveGuildRecruiting(ctx context.Context, guildID int32, flags, descTags map[int8]int8) error
|
||||
|
||||
// SavePointHistory saves point history for a member
|
||||
SavePointHistory(ctx context.Context, characterID int32, history []PointHistory) error
|
||||
|
||||
// GetGuildIDByCharacterID returns guild ID for a character
|
||||
GetGuildIDByCharacterID(ctx context.Context, characterID int32) (int32, error)
|
||||
|
||||
// CreateGuild creates a new guild
|
||||
CreateGuild(ctx context.Context, guildData GuildData) (int32, error)
|
||||
|
||||
// DeleteGuild removes a guild and all related data
|
||||
DeleteGuild(ctx context.Context, guildID int32) error
|
||||
|
||||
// GetNextGuildID returns the next available guild ID
|
||||
GetNextGuildID(ctx context.Context) (int32, error)
|
||||
|
||||
// GetNextEventID returns the next available event ID for a guild
|
||||
GetNextEventID(ctx context.Context, guildID int32) (int64, error)
|
||||
}
|
||||
|
||||
// ClientManager handles client communication for guilds
|
||||
type ClientManager interface {
|
||||
// SendGuildUpdate sends guild information update to client
|
||||
SendGuildUpdate(characterID int32, guild *Guild) error
|
||||
|
||||
// SendGuildMemberList sends guild member list to client
|
||||
SendGuildMemberList(characterID int32, members []MemberInfo) error
|
||||
|
||||
// SendGuildMember sends single member info to client
|
||||
SendGuildMember(characterID int32, member *GuildMember) error
|
||||
|
||||
// SendGuildMOTD sends message of the day to client
|
||||
SendGuildMOTD(characterID int32, motd string) error
|
||||
|
||||
// SendGuildEvent sends guild event to client
|
||||
SendGuildEvent(characterID int32, event *GuildEvent) error
|
||||
|
||||
// SendGuildEventList sends guild event list to client
|
||||
SendGuildEventList(characterID int32, events []GuildEventInfo) error
|
||||
|
||||
// SendGuildChatMessage sends guild chat message to client
|
||||
SendGuildChatMessage(characterID int32, senderName, message string, language int8) error
|
||||
|
||||
// SendOfficerChatMessage sends officer chat message to client
|
||||
SendOfficerChatMessage(characterID int32, senderName, message string, language int8) error
|
||||
|
||||
// SendGuildInvite sends guild invitation to client
|
||||
SendGuildInvite(characterID int32, invite GuildInvite) error
|
||||
|
||||
// SendGuildRecruitingInfo sends recruiting information to client
|
||||
SendGuildRecruitingInfo(characterID int32, info RecruitingInfo) error
|
||||
|
||||
// SendGuildPermissions sends guild permissions to client
|
||||
SendGuildPermissions(characterID int32, permissions map[int8]map[int8]int8) error
|
||||
|
||||
// IsClientOnline checks if a character is currently online
|
||||
IsClientOnline(characterID int32) bool
|
||||
|
||||
// GetClientLanguage returns the language setting for a client
|
||||
GetClientLanguage(characterID int32) int8
|
||||
}
|
||||
|
||||
// PlayerManager provides player information for guilds
|
||||
type PlayerManager interface {
|
||||
// GetPlayerInfo retrieves basic player information
|
||||
GetPlayerInfo(characterID int32) (PlayerInfo, error)
|
||||
|
||||
// IsPlayerOnline checks if a player is currently online
|
||||
IsPlayerOnline(characterID int32) bool
|
||||
|
||||
// GetPlayerZone returns the current zone for a player
|
||||
GetPlayerZone(characterID int32) string
|
||||
|
||||
// GetPlayerLevel returns player's current level
|
||||
GetPlayerLevel(characterID int32) (int8, int8) // adventure, tradeskill
|
||||
|
||||
// GetPlayerClass returns player's current class
|
||||
GetPlayerClass(characterID int32) (int8, int8) // adventure, tradeskill
|
||||
|
||||
// GetPlayerName returns player's character name
|
||||
GetPlayerName(characterID int32) string
|
||||
|
||||
// ValidatePlayerExists checks if a player exists
|
||||
ValidatePlayerExists(characterName string) (int32, error)
|
||||
|
||||
// GetAccountID returns the account ID for a character
|
||||
GetAccountID(characterID int32) int32
|
||||
}
|
||||
|
||||
// GuildEventHandler handles guild-related events
|
||||
type GuildEventHandler interface {
|
||||
// OnGuildCreated called when a guild is created
|
||||
OnGuildCreated(guild *Guild)
|
||||
|
||||
// OnGuildDeleted called when a guild is deleted
|
||||
OnGuildDeleted(guildID int32, guildName string)
|
||||
|
||||
// OnMemberJoined called when a member joins a guild
|
||||
OnMemberJoined(guild *Guild, member *GuildMember, inviterName string)
|
||||
|
||||
// OnMemberLeft called when a member leaves a guild
|
||||
OnMemberLeft(guild *Guild, member *GuildMember, reason string)
|
||||
|
||||
// OnMemberPromoted called when a member is promoted
|
||||
OnMemberPromoted(guild *Guild, member *GuildMember, oldRank, newRank int8, promoterName string)
|
||||
|
||||
// OnMemberDemoted called when a member is demoted
|
||||
OnMemberDemoted(guild *Guild, member *GuildMember, oldRank, newRank int8, demoterName string)
|
||||
|
||||
// OnPointsAwarded called when points are awarded to members
|
||||
OnPointsAwarded(guild *Guild, members []int32, points float64, comment, awardedBy string)
|
||||
|
||||
// OnGuildEvent called when a guild event occurs
|
||||
OnGuildEvent(guild *Guild, event *GuildEvent)
|
||||
|
||||
// OnGuildLevelUp called when a guild levels up
|
||||
OnGuildLevelUp(guild *Guild, oldLevel, newLevel int8)
|
||||
|
||||
// OnGuildChatMessage called when a guild chat message is sent
|
||||
OnGuildChatMessage(guild *Guild, senderID int32, senderName, message string, language int8)
|
||||
|
||||
// OnOfficerChatMessage called when an officer chat message is sent
|
||||
OnOfficerChatMessage(guild *Guild, senderID int32, senderName, message string, language int8)
|
||||
}
|
||||
|
||||
// LogHandler provides logging functionality
|
||||
type LogHandler interface {
|
||||
// LogDebug logs debug messages
|
||||
LogDebug(category, message string, args ...any)
|
||||
|
||||
// LogInfo logs informational messages
|
||||
LogInfo(category, message string, args ...any)
|
||||
|
||||
// LogError logs error messages
|
||||
LogError(category, message string, args ...any)
|
||||
|
||||
// LogWarning logs warning messages
|
||||
LogWarning(category, message string, args ...any)
|
||||
}
|
||||
|
||||
// PlayerInfo contains basic player information
|
||||
type PlayerInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
CharacterName string `json:"character_name"`
|
||||
AccountID int32 `json:"account_id"`
|
||||
AdventureLevel int8 `json:"adventure_level"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class"`
|
||||
Zone string `json:"zone"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
}
|
||||
|
||||
// GuildAware interface for entities that can participate in guilds
|
||||
type GuildAware interface {
|
||||
GetCharacterID() int32
|
||||
GetGuildID() int32
|
||||
GetGuildRank() int8
|
||||
IsInGuild() bool
|
||||
HasGuildPermission(permission int8) bool
|
||||
}
|
||||
|
||||
// EntityGuildAdapter adapts entities to work with guild system
|
||||
type EntityGuildAdapter struct {
|
||||
entity interface {
|
||||
GetID() int32
|
||||
// Add other entity methods as needed
|
||||
}
|
||||
guildManager *GuildManager
|
||||
}
|
||||
|
||||
// GetCharacterID returns the character ID from the adapted entity
|
||||
func (a *EntityGuildAdapter) GetCharacterID() int32 {
|
||||
return a.entity.GetID()
|
||||
}
|
||||
|
||||
// GetGuildID returns the guild ID for the character
|
||||
func (a *EntityGuildAdapter) GetGuildID() int32 {
|
||||
// TODO: Implement guild lookup through guild manager
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetGuildRank returns the guild rank for the character
|
||||
func (a *EntityGuildAdapter) GetGuildRank() int8 {
|
||||
// TODO: Implement rank lookup through guild manager
|
||||
return RankRecruit
|
||||
}
|
||||
|
||||
// IsInGuild checks if the character is in a guild
|
||||
func (a *EntityGuildAdapter) IsInGuild() bool {
|
||||
return a.GetGuildID() > 0
|
||||
}
|
||||
|
||||
// HasGuildPermission checks if the character has a specific guild permission
|
||||
func (a *EntityGuildAdapter) HasGuildPermission(permission int8) bool {
|
||||
// TODO: Implement permission checking through guild manager
|
||||
return false
|
||||
}
|
||||
|
||||
// InviteManager handles guild invitations
|
||||
type InviteManager interface {
|
||||
// SendInvite sends a guild invitation
|
||||
SendInvite(guildID, characterID, inviterID int32, rank int8) error
|
||||
|
||||
// AcceptInvite accepts a guild invitation
|
||||
AcceptInvite(characterID, guildID int32) error
|
||||
|
||||
// DeclineInvite declines a guild invitation
|
||||
DeclineInvite(characterID, guildID int32) error
|
||||
|
||||
// GetPendingInvites returns pending invites for a character
|
||||
GetPendingInvites(characterID int32) ([]GuildInvite, error)
|
||||
|
||||
// ClearExpiredInvites removes expired invitations
|
||||
ClearExpiredInvites() error
|
||||
}
|
||||
|
||||
// PermissionChecker provides permission validation
|
||||
type PermissionChecker interface {
|
||||
// CanInvite checks if a member can invite players
|
||||
CanInvite(guild *Guild, memberRank int8) bool
|
||||
|
||||
// CanRemoveMember checks if a member can remove other members
|
||||
CanRemoveMember(guild *Guild, memberRank, targetRank int8) bool
|
||||
|
||||
// CanPromote checks if a member can promote other members
|
||||
CanPromote(guild *Guild, memberRank, targetRank int8) bool
|
||||
|
||||
// CanDemote checks if a member can demote other members
|
||||
CanDemote(guild *Guild, memberRank, targetRank int8) bool
|
||||
|
||||
// CanEditPermissions checks if a member can edit guild permissions
|
||||
CanEditPermissions(guild *Guild, memberRank int8) bool
|
||||
|
||||
// CanUseBankSlot checks if a member can access a specific bank slot
|
||||
CanUseBankSlot(guild *Guild, memberRank int8, bankSlot int, action string) bool
|
||||
|
||||
// CanSpeakInOfficerChat checks if a member can speak in officer chat
|
||||
CanSpeakInOfficerChat(guild *Guild, memberRank int8) bool
|
||||
|
||||
// CanAssignPoints checks if a member can assign guild points
|
||||
CanAssignPoints(guild *Guild, memberRank int8) bool
|
||||
}
|
||||
|
||||
// NotificationManager handles guild notifications
|
||||
type NotificationManager interface {
|
||||
// NotifyMemberLogin notifies guild of member login
|
||||
NotifyMemberLogin(guild *Guild, member *GuildMember)
|
||||
|
||||
// NotifyMemberLogout notifies guild of member logout
|
||||
NotifyMemberLogout(guild *Guild, member *GuildMember)
|
||||
|
||||
// NotifyGuildMessage sends a message to all guild members
|
||||
NotifyGuildMessage(guild *Guild, eventType int8, message string, args ...any)
|
||||
|
||||
// NotifyOfficers sends a message to officers only
|
||||
NotifyOfficers(guild *Guild, message string, args ...any)
|
||||
|
||||
// NotifyGuildUpdate notifies guild members of guild changes
|
||||
NotifyGuildUpdate(guild *Guild)
|
||||
}
|
||||
|
||||
// BankManager handles guild bank operations
|
||||
type BankManager interface {
|
||||
// GetBankContents returns contents of a guild bank
|
||||
GetBankContents(guildID int32, bankSlot int) ([]BankItem, error)
|
||||
|
||||
// DepositItem deposits an item into guild bank
|
||||
DepositItem(guildID int32, bankSlot int, item BankItem, depositorID int32) error
|
||||
|
||||
// WithdrawItem withdraws an item from guild bank
|
||||
WithdrawItem(guildID int32, bankSlot int, itemSlot int, withdrawerID int32) error
|
||||
|
||||
// LogBankEvent logs a bank event
|
||||
LogBankEvent(guildID int32, bankSlot int, eventType int32, description string) error
|
||||
|
||||
// GetBankEventHistory returns bank event history
|
||||
GetBankEventHistory(guildID int32, bankSlot int) ([]GuildBankEvent, error)
|
||||
}
|
||||
|
||||
// BankItem represents an item in the guild bank
|
||||
type BankItem struct {
|
||||
Slot int `json:"slot"`
|
||||
ItemID int32 `json:"item_id"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
DepositorID int32 `json:"depositor_id"`
|
||||
DepositDate time.Time `json:"deposit_date"`
|
||||
}
|
@ -1,514 +0,0 @@
|
||||
package guilds
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
)
|
||||
|
||||
// MasterList provides a bespoke, performance-optimized guild collection with specialized indices
|
||||
type MasterList struct {
|
||||
// Core storage
|
||||
guilds map[int32]*Guild
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Specialized indices for O(1) lookups
|
||||
byName map[string]*Guild // name -> guild (case-insensitive)
|
||||
byLevel map[int8][]*Guild // level -> guilds
|
||||
recruiting []*Guild // guilds with recruiters
|
||||
|
||||
// Cached metadata with invalidation
|
||||
levels []int8
|
||||
recruitingMap map[int32]bool // guildID -> isRecruiting
|
||||
metaStale bool
|
||||
}
|
||||
|
||||
// NewMasterList creates a new bespoke master list optimized for guild operations
|
||||
func NewMasterList() *MasterList {
|
||||
return &MasterList{
|
||||
guilds: make(map[int32]*Guild),
|
||||
byName: make(map[string]*Guild),
|
||||
byLevel: make(map[int8][]*Guild),
|
||||
recruiting: make([]*Guild, 0),
|
||||
recruitingMap: make(map[int32]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a guild to the master list with O(1) core operations
|
||||
func (m *MasterList) Add(guild *Guild) bool {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
guildID := guild.GetID()
|
||||
|
||||
// Check existence
|
||||
if _, exists := m.guilds[guildID]; exists {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add to core storage
|
||||
m.guilds[guildID] = guild
|
||||
|
||||
// Update specialized indices
|
||||
name := strings.ToLower(guild.GetName())
|
||||
m.byName[name] = guild
|
||||
|
||||
level := guild.GetLevel()
|
||||
m.byLevel[level] = append(m.byLevel[level], guild)
|
||||
|
||||
// Update recruiting index
|
||||
isRecruiting := guild.GetNumRecruiters() > 0
|
||||
m.recruitingMap[guildID] = isRecruiting
|
||||
if isRecruiting {
|
||||
m.recruiting = append(m.recruiting, guild)
|
||||
}
|
||||
|
||||
// Invalidate metadata cache
|
||||
m.metaStale = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Get retrieves a guild by ID with O(1) performance
|
||||
func (m *MasterList) Get(guildID int32) *Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return m.guilds[guildID]
|
||||
}
|
||||
|
||||
// GetByName retrieves a guild by name with O(1) performance
|
||||
func (m *MasterList) GetByName(name string) *Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return m.byName[strings.ToLower(name)]
|
||||
}
|
||||
|
||||
// GetByLevel returns all guilds of a specific level with O(1) performance
|
||||
func (m *MasterList) GetByLevel(level int8) []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
guilds := m.byLevel[level]
|
||||
if guilds == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make([]*Guild, len(guilds))
|
||||
copy(result, guilds)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRecruiting returns all guilds that are recruiting with O(1) performance
|
||||
func (m *MasterList) GetRecruiting() []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
result := make([]*Guild, len(m.recruiting))
|
||||
copy(result, m.recruiting)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetByLevelRange returns guilds within a level range using optimized range queries
|
||||
func (m *MasterList) GetByLevelRange(minLevel, maxLevel int8) []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
var result []*Guild
|
||||
for level := minLevel; level <= maxLevel; level++ {
|
||||
if guilds := m.byLevel[level]; guilds != nil {
|
||||
result = append(result, guilds...)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRecruitingByLevel returns recruiting guilds of a specific level using set intersection
|
||||
func (m *MasterList) GetRecruitingByLevel(level int8) []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
levelGuilds := m.byLevel[level]
|
||||
if levelGuilds == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []*Guild
|
||||
for _, guild := range levelGuilds {
|
||||
if m.recruitingMap[guild.GetID()] {
|
||||
result = append(result, guild)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Remove removes a guild from the master list
|
||||
func (m *MasterList) Remove(guildID int32) bool {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
guild := m.guilds[guildID]
|
||||
if guild == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove from core storage
|
||||
delete(m.guilds, guildID)
|
||||
|
||||
// Remove from name index
|
||||
name := strings.ToLower(guild.GetName())
|
||||
delete(m.byName, name)
|
||||
|
||||
// Remove from level index
|
||||
level := guild.GetLevel()
|
||||
if levelGuilds := m.byLevel[level]; levelGuilds != nil {
|
||||
for i, g := range levelGuilds {
|
||||
if g.GetID() == guildID {
|
||||
m.byLevel[level] = append(levelGuilds[:i], levelGuilds[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Clean up empty level arrays
|
||||
if len(m.byLevel[level]) == 0 {
|
||||
delete(m.byLevel, level)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from recruiting index
|
||||
delete(m.recruitingMap, guildID)
|
||||
for i, g := range m.recruiting {
|
||||
if g.GetID() == guildID {
|
||||
m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate metadata cache
|
||||
m.metaStale = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Update updates a guild's indices when its properties change
|
||||
func (m *MasterList) Update(guild *Guild) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
guildID := guild.GetID()
|
||||
oldGuild := m.guilds[guildID]
|
||||
if oldGuild == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Update core storage
|
||||
m.guilds[guildID] = guild
|
||||
|
||||
// Update name index if changed
|
||||
oldName := strings.ToLower(oldGuild.GetName())
|
||||
newName := strings.ToLower(guild.GetName())
|
||||
if oldName != newName {
|
||||
delete(m.byName, oldName)
|
||||
m.byName[newName] = guild
|
||||
}
|
||||
|
||||
// Update level index if changed
|
||||
oldLevel := oldGuild.GetLevel()
|
||||
newLevel := guild.GetLevel()
|
||||
if oldLevel != newLevel {
|
||||
// Remove from old level
|
||||
if levelGuilds := m.byLevel[oldLevel]; levelGuilds != nil {
|
||||
for i, g := range levelGuilds {
|
||||
if g.GetID() == guildID {
|
||||
m.byLevel[oldLevel] = append(levelGuilds[:i], levelGuilds[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(m.byLevel[oldLevel]) == 0 {
|
||||
delete(m.byLevel, oldLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Add to new level
|
||||
m.byLevel[newLevel] = append(m.byLevel[newLevel], guild)
|
||||
}
|
||||
|
||||
// Update recruiting index if changed
|
||||
oldRecruiting := m.recruitingMap[guildID]
|
||||
newRecruiting := guild.GetNumRecruiters() > 0
|
||||
if oldRecruiting != newRecruiting {
|
||||
m.recruitingMap[guildID] = newRecruiting
|
||||
|
||||
if oldRecruiting && !newRecruiting {
|
||||
// Remove from recruiting list
|
||||
for i, g := range m.recruiting {
|
||||
if g.GetID() == guildID {
|
||||
m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if !oldRecruiting && newRecruiting {
|
||||
// Add to recruiting list
|
||||
m.recruiting = append(m.recruiting, guild)
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate metadata cache
|
||||
m.metaStale = true
|
||||
}
|
||||
|
||||
// GetAll returns all guilds
|
||||
func (m *MasterList) GetAll() []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
result := make([]*Guild, 0, len(m.guilds))
|
||||
for _, guild := range m.guilds {
|
||||
result = append(result, guild)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Count returns the total number of guilds
|
||||
func (m *MasterList) Count() int {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return len(m.guilds)
|
||||
}
|
||||
|
||||
// GetLevels returns all guild levels with lazy caching
|
||||
func (m *MasterList) GetLevels() []int8 {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if !m.metaStale && m.levels != nil {
|
||||
return m.levels
|
||||
}
|
||||
|
||||
// Rebuild level cache
|
||||
levelSet := make(map[int8]bool)
|
||||
for level := range m.byLevel {
|
||||
levelSet[level] = true
|
||||
}
|
||||
|
||||
m.levels = make([]int8, 0, len(levelSet))
|
||||
for level := range levelSet {
|
||||
m.levels = append(m.levels, level)
|
||||
}
|
||||
|
||||
sort.Slice(m.levels, func(i, j int) bool {
|
||||
return m.levels[i] < m.levels[j]
|
||||
})
|
||||
|
||||
m.metaStale = false
|
||||
return m.levels
|
||||
}
|
||||
|
||||
// GetStatistics returns detailed statistics about the guild system
|
||||
func (m *MasterList) GetStatistics() GuildStatistics {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
stats := GuildStatistics{
|
||||
TotalGuilds: len(m.guilds),
|
||||
}
|
||||
|
||||
totalMembers := 0
|
||||
activeGuilds := 0
|
||||
totalRecruiters := len(m.recruiting)
|
||||
uniqueAccounts := make(map[int32]bool)
|
||||
highestLevel := int8(1)
|
||||
|
||||
for _, guild := range m.guilds {
|
||||
members := guild.GetAllMembers()
|
||||
memberCount := len(members)
|
||||
|
||||
totalMembers += memberCount
|
||||
|
||||
if memberCount > 0 {
|
||||
activeGuilds++
|
||||
}
|
||||
|
||||
// Track unique accounts
|
||||
for _, member := range members {
|
||||
uniqueAccounts[member.AccountID] = true
|
||||
}
|
||||
|
||||
// Find highest level
|
||||
if guild.GetLevel() > highestLevel {
|
||||
highestLevel = guild.GetLevel()
|
||||
}
|
||||
}
|
||||
|
||||
stats.TotalMembers = totalMembers
|
||||
stats.ActiveGuilds = activeGuilds
|
||||
stats.TotalRecruiters = totalRecruiters
|
||||
stats.UniqueAccounts = len(uniqueAccounts)
|
||||
stats.HighestGuildLevel = highestLevel
|
||||
|
||||
if len(m.guilds) > 0 {
|
||||
stats.AverageGuildSize = float64(totalMembers) / float64(len(m.guilds))
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// Search performs efficient guild searches using specialized indices
|
||||
func (m *MasterList) Search(criteria GuildSearchCriteria) []*Guild {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
// Start with all guilds or use specialized indices for optimization
|
||||
var candidates []*Guild
|
||||
|
||||
// Use level range optimization if specified
|
||||
if criteria.MinLevel > 0 || criteria.MaxLevel > 0 {
|
||||
minLevel := criteria.MinLevel
|
||||
maxLevel := criteria.MaxLevel
|
||||
|
||||
if minLevel == 0 {
|
||||
minLevel = 1
|
||||
}
|
||||
if maxLevel == 0 {
|
||||
maxLevel = 100 // reasonable max
|
||||
}
|
||||
|
||||
for level := minLevel; level <= maxLevel; level++ {
|
||||
if guilds := m.byLevel[level]; guilds != nil {
|
||||
candidates = append(candidates, guilds...)
|
||||
}
|
||||
}
|
||||
} else if criteria.RecruitingOnly {
|
||||
// Use recruiting index for optimization
|
||||
candidates = make([]*Guild, len(m.recruiting))
|
||||
copy(candidates, m.recruiting)
|
||||
} else {
|
||||
// Use all guilds
|
||||
candidates = make([]*Guild, 0, len(m.guilds))
|
||||
for _, guild := range m.guilds {
|
||||
candidates = append(candidates, guild)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply remaining filters
|
||||
var results []*Guild
|
||||
for _, guild := range candidates {
|
||||
if m.matchesCriteria(guild, criteria) {
|
||||
results = append(results, guild)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].GetName() < results[j].GetName()
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// LoadFromDatabase loads all guilds from database into the master list
|
||||
func (m *MasterList) LoadFromDatabase(db *database.Database) error {
|
||||
// Clear existing data
|
||||
m.mutex.Lock()
|
||||
m.guilds = make(map[int32]*Guild)
|
||||
m.byName = make(map[string]*Guild)
|
||||
m.byLevel = make(map[int8][]*Guild)
|
||||
m.recruiting = make([]*Guild, 0)
|
||||
m.recruitingMap = make(map[int32]bool)
|
||||
m.metaStale = true
|
||||
m.mutex.Unlock()
|
||||
|
||||
// Load guild IDs first using MySQL-compatible approach
|
||||
var guildIDs []int32
|
||||
|
||||
rows, err := db.Query(`SELECT id FROM guilds ORDER BY id`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id int32
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
guildIDs = append(guildIDs, id)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load each guild and add to master list
|
||||
for _, guildID := range guildIDs {
|
||||
guild, err := Load(db, guildID)
|
||||
if err != nil {
|
||||
continue // Skip failed loads
|
||||
}
|
||||
|
||||
m.Add(guild)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper method for criteria matching
|
||||
func (m *MasterList) matchesCriteria(guild *Guild, criteria GuildSearchCriteria) bool {
|
||||
// Name pattern matching
|
||||
if criteria.NamePattern != "" {
|
||||
if !strings.Contains(strings.ToLower(guild.GetName()), strings.ToLower(criteria.NamePattern)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Member count range
|
||||
memberCount := len(guild.GetAllMembers())
|
||||
if criteria.MinMembers > 0 && memberCount < criteria.MinMembers {
|
||||
return false
|
||||
}
|
||||
if criteria.MaxMembers > 0 && memberCount > criteria.MaxMembers {
|
||||
return false
|
||||
}
|
||||
|
||||
// Play style
|
||||
if criteria.PlayStyle > 0 && guild.GetRecruitingPlayStyle() != criteria.PlayStyle {
|
||||
return false
|
||||
}
|
||||
|
||||
// Required flags
|
||||
for _, flag := range criteria.RequiredFlags {
|
||||
if guild.GetRecruitingFlag(flag) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Required description tags
|
||||
for _, tag := range criteria.RequiredDescTags {
|
||||
found := false
|
||||
for i := int8(0); i < 4; i++ {
|
||||
if guild.GetRecruitingDescTag(i) == tag {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Excluded description tags
|
||||
for _, tag := range criteria.ExcludedDescTags {
|
||||
for i := int8(0); i < 4; i++ {
|
||||
if guild.GetRecruitingDescTag(i) == tag {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -1,490 +0,0 @@
|
||||
package guilds
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewGuildMember creates a new guild member instance
|
||||
func NewGuildMember(characterID int32, name string, rank int8) *GuildMember {
|
||||
return &GuildMember{
|
||||
CharacterID: characterID,
|
||||
Name: name,
|
||||
Rank: rank,
|
||||
JoinDate: time.Now(),
|
||||
LastLoginDate: time.Now(),
|
||||
PointHistory: make([]PointHistory, 0),
|
||||
RecruitingShowAdventureClass: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCharacterID returns the character ID
|
||||
func (gm *GuildMember) GetCharacterID() int32 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.CharacterID
|
||||
}
|
||||
|
||||
// GetName returns the member name
|
||||
func (gm *GuildMember) GetName() string {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Name
|
||||
}
|
||||
|
||||
// SetName sets the member name
|
||||
func (gm *GuildMember) SetName(name string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
if len(name) > MaxMemberNameLength {
|
||||
name = name[:MaxMemberNameLength]
|
||||
}
|
||||
gm.Name = name
|
||||
}
|
||||
|
||||
// GetRank returns the member rank
|
||||
func (gm *GuildMember) GetRank() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Rank
|
||||
}
|
||||
|
||||
// SetRank sets the member rank
|
||||
func (gm *GuildMember) SetRank(rank int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
if rank >= RankLeader && rank <= RankRecruit {
|
||||
gm.Rank = rank
|
||||
}
|
||||
}
|
||||
|
||||
// GetPoints returns the member's guild points
|
||||
func (gm *GuildMember) GetPoints() float64 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Points
|
||||
}
|
||||
|
||||
// SetPoints sets the member's guild points
|
||||
func (gm *GuildMember) SetPoints(points float64) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.Points = points
|
||||
}
|
||||
|
||||
// AddPoints adds points to the member
|
||||
func (gm *GuildMember) AddPoints(points float64) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.Points += points
|
||||
}
|
||||
|
||||
// GetAdventureLevel returns the adventure level
|
||||
func (gm *GuildMember) GetAdventureLevel() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.AdventureLevel
|
||||
}
|
||||
|
||||
// SetAdventureLevel sets the adventure level
|
||||
func (gm *GuildMember) SetAdventureLevel(level int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.AdventureLevel = level
|
||||
}
|
||||
|
||||
// GetAdventureClass returns the adventure class
|
||||
func (gm *GuildMember) GetAdventureClass() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.AdventureClass
|
||||
}
|
||||
|
||||
// SetAdventureClass sets the adventure class
|
||||
func (gm *GuildMember) SetAdventureClass(class int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.AdventureClass = class
|
||||
}
|
||||
|
||||
// GetTradeskillLevel returns the tradeskill level
|
||||
func (gm *GuildMember) GetTradeskillLevel() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.TradeskillLevel
|
||||
}
|
||||
|
||||
// SetTradeskillLevel sets the tradeskill level
|
||||
func (gm *GuildMember) SetTradeskillLevel(level int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.TradeskillLevel = level
|
||||
}
|
||||
|
||||
// GetTradeskillClass returns the tradeskill class
|
||||
func (gm *GuildMember) GetTradeskillClass() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.TradeskillClass
|
||||
}
|
||||
|
||||
// SetTradeskillClass sets the tradeskill class
|
||||
func (gm *GuildMember) SetTradeskillClass(class int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.TradeskillClass = class
|
||||
}
|
||||
|
||||
// GetZone returns the member's current zone
|
||||
func (gm *GuildMember) GetZone() string {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Zone
|
||||
}
|
||||
|
||||
// SetZone sets the member's current zone
|
||||
func (gm *GuildMember) SetZone(zone string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.Zone = zone
|
||||
}
|
||||
|
||||
// GetJoinDate returns when the member joined the guild
|
||||
func (gm *GuildMember) GetJoinDate() time.Time {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.JoinDate
|
||||
}
|
||||
|
||||
// SetJoinDate sets when the member joined the guild
|
||||
func (gm *GuildMember) SetJoinDate(date time.Time) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.JoinDate = date
|
||||
}
|
||||
|
||||
// GetLastLoginDate returns the member's last login date
|
||||
func (gm *GuildMember) GetLastLoginDate() time.Time {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.LastLoginDate
|
||||
}
|
||||
|
||||
// SetLastLoginDate sets the member's last login date
|
||||
func (gm *GuildMember) SetLastLoginDate(date time.Time) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.LastLoginDate = date
|
||||
}
|
||||
|
||||
// GetNote returns the member's personal note
|
||||
func (gm *GuildMember) GetNote() string {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Note
|
||||
}
|
||||
|
||||
// SetNote sets the member's personal note
|
||||
func (gm *GuildMember) SetNote(note string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.Note = note
|
||||
}
|
||||
|
||||
// GetOfficerNote returns the member's officer note
|
||||
func (gm *GuildMember) GetOfficerNote() string {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.OfficerNote
|
||||
}
|
||||
|
||||
// SetOfficerNote sets the member's officer note
|
||||
func (gm *GuildMember) SetOfficerNote(note string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.OfficerNote = note
|
||||
}
|
||||
|
||||
// GetMemberFlags returns the member flags
|
||||
func (gm *GuildMember) GetMemberFlags() int8 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.MemberFlags
|
||||
}
|
||||
|
||||
// SetMemberFlags sets the member flags
|
||||
func (gm *GuildMember) SetMemberFlags(flags int8) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.MemberFlags = flags
|
||||
}
|
||||
|
||||
// HasMemberFlag checks if the member has a specific flag
|
||||
func (gm *GuildMember) HasMemberFlag(flag int8) bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.MemberFlags&flag != 0
|
||||
}
|
||||
|
||||
// SetMemberFlag sets or unsets a specific member flag
|
||||
func (gm *GuildMember) SetMemberFlag(flag int8, value bool) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
if value {
|
||||
gm.MemberFlags |= flag
|
||||
} else {
|
||||
gm.MemberFlags &^= flag
|
||||
}
|
||||
}
|
||||
|
||||
// IsRecruiter checks if the member is a recruiter
|
||||
func (gm *GuildMember) IsRecruiter() bool {
|
||||
return gm.HasMemberFlag(MemberFlagRecruitingForGuild)
|
||||
}
|
||||
|
||||
// SetRecruiter sets or unsets the recruiter flag
|
||||
func (gm *GuildMember) SetRecruiter(isRecruiter bool) {
|
||||
gm.SetMemberFlag(MemberFlagRecruitingForGuild, isRecruiter)
|
||||
}
|
||||
|
||||
// GetRecruiterID returns the recruiter ID
|
||||
func (gm *GuildMember) GetRecruiterID() int32 {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.RecruiterID
|
||||
}
|
||||
|
||||
// SetRecruiterID sets the recruiter ID
|
||||
func (gm *GuildMember) SetRecruiterID(id int32) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.RecruiterID = id
|
||||
}
|
||||
|
||||
// GetRecruiterDescription returns the recruiter description
|
||||
func (gm *GuildMember) GetRecruiterDescription() string {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.RecruiterDescription
|
||||
}
|
||||
|
||||
// SetRecruiterDescription sets the recruiter description
|
||||
func (gm *GuildMember) SetRecruiterDescription(description string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
gm.RecruiterDescription = description
|
||||
}
|
||||
|
||||
// GetRecruiterPictureData returns the recruiter picture data
|
||||
func (gm *GuildMember) GetRecruiterPictureData() []byte {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
// Return a copy to prevent external modification
|
||||
if gm.RecruiterPictureData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := make([]byte, len(gm.RecruiterPictureData))
|
||||
copy(data, gm.RecruiterPictureData)
|
||||
return data
|
||||
}
|
||||
|
||||
// SetRecruiterPictureData sets the recruiter picture data
|
||||
func (gm *GuildMember) SetRecruiterPictureData(data []byte) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
if data == nil {
|
||||
gm.RecruiterPictureData = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Make a copy to prevent external modification
|
||||
gm.RecruiterPictureData = make([]byte, len(data))
|
||||
copy(gm.RecruiterPictureData, data)
|
||||
}
|
||||
|
||||
// GetRecruitingShowAdventureClass returns whether to show adventure class
|
||||
func (gm *GuildMember) GetRecruitingShowAdventureClass() bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.RecruitingShowAdventureClass != 0
|
||||
}
|
||||
|
||||
// SetRecruitingShowAdventureClass sets whether to show adventure class
|
||||
func (gm *GuildMember) SetRecruitingShowAdventureClass(show bool) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
if show {
|
||||
gm.RecruitingShowAdventureClass = 1
|
||||
} else {
|
||||
gm.RecruitingShowAdventureClass = 0
|
||||
}
|
||||
}
|
||||
|
||||
// GetPointHistory returns a copy of the point history
|
||||
func (gm *GuildMember) GetPointHistory() []PointHistory {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
history := make([]PointHistory, len(gm.PointHistory))
|
||||
copy(history, gm.PointHistory)
|
||||
return history
|
||||
}
|
||||
|
||||
// AddPointHistory adds a point history entry
|
||||
func (gm *GuildMember) AddPointHistory(date time.Time, modifiedBy string, points float64, comment string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
// Limit history size
|
||||
if len(gm.PointHistory) >= MaxPointHistory {
|
||||
// Remove oldest entry
|
||||
gm.PointHistory = gm.PointHistory[1:]
|
||||
}
|
||||
|
||||
history := PointHistory{
|
||||
Date: date,
|
||||
ModifiedBy: modifiedBy,
|
||||
Points: points,
|
||||
Comment: comment,
|
||||
SaveNeeded: true,
|
||||
}
|
||||
|
||||
gm.PointHistory = append(gm.PointHistory, history)
|
||||
}
|
||||
|
||||
// GetMemberInfo returns formatted member information
|
||||
func (gm *GuildMember) GetMemberInfo(rankName string, isOnline bool) MemberInfo {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
return MemberInfo{
|
||||
CharacterID: gm.CharacterID,
|
||||
Name: gm.Name,
|
||||
Rank: gm.Rank,
|
||||
RankName: rankName,
|
||||
Points: gm.Points,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
AdventureLevel: gm.AdventureLevel,
|
||||
TradeskillClass: gm.TradeskillClass,
|
||||
TradeskillLevel: gm.TradeskillLevel,
|
||||
Zone: gm.Zone,
|
||||
JoinDate: gm.JoinDate,
|
||||
LastLoginDate: gm.LastLoginDate,
|
||||
IsOnline: isOnline,
|
||||
IsRecruiter: gm.MemberFlags&MemberFlagRecruitingForGuild != 0,
|
||||
Note: gm.Note,
|
||||
OfficerNote: gm.OfficerNote,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecruiterInfo returns formatted recruiter information
|
||||
func (gm *GuildMember) GetRecruiterInfo(isOnline bool) RecruiterInfo {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
return RecruiterInfo{
|
||||
CharacterID: gm.CharacterID,
|
||||
Name: gm.Name,
|
||||
Description: gm.RecruiterDescription,
|
||||
PictureData: gm.GetRecruiterPictureData(), // This will make a copy
|
||||
ShowAdventureClass: gm.RecruitingShowAdventureClass != 0,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
IsOnline: isOnline,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdatePlayerInfo updates member info from player data
|
||||
func (gm *GuildMember) UpdatePlayerInfo(playerInfo PlayerInfo) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
gm.AdventureLevel = playerInfo.AdventureLevel
|
||||
gm.AdventureClass = playerInfo.AdventureClass
|
||||
gm.TradeskillLevel = playerInfo.TradeskillLevel
|
||||
gm.TradeskillClass = playerInfo.TradeskillClass
|
||||
gm.Zone = playerInfo.Zone
|
||||
|
||||
if playerInfo.IsOnline {
|
||||
gm.LastLoginDate = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateRank checks if the rank is valid
|
||||
func (gm *GuildMember) ValidateRank() bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.Rank >= RankLeader && gm.Rank <= RankRecruit
|
||||
}
|
||||
|
||||
// CanPromote checks if this member can promote another member
|
||||
func (gm *GuildMember) CanPromote(targetRank int8) bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
// Can only promote members with lower rank (higher rank number)
|
||||
// Cannot promote to same or higher rank than self
|
||||
return gm.Rank < targetRank && targetRank > RankLeader
|
||||
}
|
||||
|
||||
// CanDemote checks if this member can demote another member
|
||||
func (gm *GuildMember) CanDemote(targetRank int8) bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
// Can only demote members with equal or lower rank (same or higher rank number)
|
||||
// Cannot demote to recruit (already lowest)
|
||||
return gm.Rank <= targetRank && targetRank < RankRecruit
|
||||
}
|
||||
|
||||
// CanKick checks if this member can kick another member
|
||||
func (gm *GuildMember) CanKick(targetRank int8) bool {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
// Can only kick members with lower rank (higher rank number)
|
||||
return gm.Rank < targetRank
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the guild member
|
||||
func (gm *GuildMember) Copy() *GuildMember {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
newMember := &GuildMember{
|
||||
CharacterID: gm.CharacterID,
|
||||
AccountID: gm.AccountID,
|
||||
RecruiterID: gm.RecruiterID,
|
||||
Name: gm.Name,
|
||||
GuildStatus: gm.GuildStatus,
|
||||
Points: gm.Points,
|
||||
AdventureClass: gm.AdventureClass,
|
||||
AdventureLevel: gm.AdventureLevel,
|
||||
TradeskillClass: gm.TradeskillClass,
|
||||
TradeskillLevel: gm.TradeskillLevel,
|
||||
Rank: gm.Rank,
|
||||
MemberFlags: gm.MemberFlags,
|
||||
Zone: gm.Zone,
|
||||
JoinDate: gm.JoinDate,
|
||||
LastLoginDate: gm.LastLoginDate,
|
||||
Note: gm.Note,
|
||||
OfficerNote: gm.OfficerNote,
|
||||
RecruiterDescription: gm.RecruiterDescription,
|
||||
RecruitingShowAdventureClass: gm.RecruitingShowAdventureClass,
|
||||
PointHistory: make([]PointHistory, len(gm.PointHistory)),
|
||||
}
|
||||
|
||||
// Deep copy point history
|
||||
copy(newMember.PointHistory, gm.PointHistory)
|
||||
|
||||
// Deep copy picture data
|
||||
if gm.RecruiterPictureData != nil {
|
||||
newMember.RecruiterPictureData = make([]byte, len(gm.RecruiterPictureData))
|
||||
copy(newMember.RecruiterPictureData, gm.RecruiterPictureData)
|
||||
}
|
||||
|
||||
return newMember
|
||||
}
|
@ -1,330 +0,0 @@
|
||||
package guilds
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
)
|
||||
|
||||
// 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
|
||||
type GuildMember struct {
|
||||
mu sync.RWMutex
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
// 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:"-"`
|
||||
}
|
||||
|
||||
// Guild represents a guild with all its properties and members
|
||||
type Guild struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// Database integration
|
||||
db *database.Database
|
||||
isNew bool
|
||||
|
||||
id int32
|
||||
name string
|
||||
level int8
|
||||
formedDate time.Time
|
||||
motd string
|
||||
expCurrent int64
|
||||
expToNextLevel int64
|
||||
recruitingShortDesc string
|
||||
recruitingFullDesc string
|
||||
recruitingMinLevel int8
|
||||
recruitingPlayStyle int8
|
||||
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
|
||||
|
||||
// Save flags
|
||||
saveNeeded bool
|
||||
memberSaveNeeded bool
|
||||
eventsSaveNeeded bool
|
||||
ranksSaveNeeded bool
|
||||
eventFiltersSaveNeeded bool
|
||||
pointsHistorySaveNeeded bool
|
||||
recruitingSaveNeeded bool
|
||||
|
||||
// Tracking
|
||||
nextEventID int64
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
// GuildData represents guild data for database operations
|
||||
type GuildData struct {
|
||||
ID int32 `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Level int8 `json:"level" db:"level"`
|
||||
FormedDate time.Time `json:"formed_date" db:"formed_date"`
|
||||
MOTD string `json:"motd" db:"motd"`
|
||||
EXPCurrent int64 `json:"exp_current" db:"exp_current"`
|
||||
EXPToNextLevel int64 `json:"exp_to_next_level" db:"exp_to_next_level"`
|
||||
RecruitingShortDesc string `json:"recruiting_short_desc" db:"recruiting_short_desc"`
|
||||
RecruitingFullDesc string `json:"recruiting_full_desc" db:"recruiting_full_desc"`
|
||||
RecruitingMinLevel int8 `json:"recruiting_min_level" db:"recruiting_min_level"`
|
||||
RecruitingPlayStyle int8 `json:"recruiting_play_style" db:"recruiting_play_style"`
|
||||
}
|
||||
|
||||
// GuildMemberData represents guild member data for database operations
|
||||
type GuildMemberData struct {
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
GuildID int32 `json:"guild_id" db:"guild_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"`
|
||||
}
|
||||
|
||||
// GuildEventData represents guild event data for database operations
|
||||
type GuildEventData struct {
|
||||
EventID int64 `json:"event_id" db:"event_id"`
|
||||
GuildID int32 `json:"guild_id" db:"guild_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"`
|
||||
}
|
||||
|
||||
// GuildRankData represents guild rank data for database operations
|
||||
type GuildRankData struct {
|
||||
GuildID int32 `json:"guild_id" db:"guild_id"`
|
||||
Rank int8 `json:"rank" db:"rank"`
|
||||
Name string `json:"name" db:"name"`
|
||||
}
|
||||
|
||||
// GuildPermissionData represents guild permission data for database operations
|
||||
type GuildPermissionData struct {
|
||||
GuildID int32 `json:"guild_id" db:"guild_id"`
|
||||
Rank int8 `json:"rank" db:"rank"`
|
||||
Permission int8 `json:"permission" db:"permission"`
|
||||
Value int8 `json:"value" db:"value"`
|
||||
}
|
||||
|
||||
// GuildEventFilterData represents guild event filter data for database operations
|
||||
type GuildEventFilterData struct {
|
||||
GuildID int32 `json:"guild_id" db:"guild_id"`
|
||||
EventID int8 `json:"event_id" db:"event_id"`
|
||||
Category int8 `json:"category" db:"category"`
|
||||
Value int8 `json:"value" db:"value"`
|
||||
}
|
||||
|
||||
// GuildRecruitingData represents guild recruiting data for database operations
|
||||
type GuildRecruitingData struct {
|
||||
GuildID int32 `json:"guild_id" db:"guild_id"`
|
||||
Flag int8 `json:"flag" db:"flag"`
|
||||
Value int8 `json:"value" db:"value"`
|
||||
}
|
||||
|
||||
// PointHistoryData represents point history data for database operations
|
||||
type PointHistoryData struct {
|
||||
CharacterID int32 `json:"character_id" db:"character_id"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// GuildList manages all guilds in the system
|
||||
type GuildList struct {
|
||||
mu sync.RWMutex
|
||||
guilds map[int32]*Guild
|
||||
}
|
||||
|
||||
// GuildManager provides high-level guild management
|
||||
type GuildManager struct {
|
||||
guildList *GuildList
|
||||
database GuildDatabase
|
||||
clientManager ClientManager
|
||||
playerManager PlayerManager
|
||||
eventHandler GuildEventHandler
|
||||
logger LogHandler
|
||||
}
|
||||
|
||||
// GuildStatistics provides guild system usage statistics
|
||||
type GuildStatistics struct {
|
||||
TotalGuilds int `json:"total_guilds"`
|
||||
TotalMembers int `json:"total_members"`
|
||||
ActiveGuilds int `json:"active_guilds"`
|
||||
AverageGuildSize float64 `json:"average_guild_size"`
|
||||
TotalEvents int `json:"total_events"`
|
||||
TotalRecruiters int `json:"total_recruiters"`
|
||||
UniqueAccounts int `json:"unique_accounts"`
|
||||
HighestGuildLevel int8 `json:"highest_guild_level"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// MemberInfo provides guild member information
|
||||
type MemberInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
Name string `json:"name"`
|
||||
Rank int8 `json:"rank"`
|
||||
RankName string `json:"rank_name"`
|
||||
Points float64 `json:"points"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
AdventureLevel int8 `json:"adventure_level"`
|
||||
TradeskillClass int8 `json:"tradeskill_class"`
|
||||
TradeskillLevel int8 `json:"tradeskill_level"`
|
||||
Zone string `json:"zone"`
|
||||
JoinDate time.Time `json:"join_date"`
|
||||
LastLoginDate time.Time `json:"last_login_date"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
IsRecruiter bool `json:"is_recruiter"`
|
||||
Note string `json:"note"`
|
||||
OfficerNote string `json:"officer_note"`
|
||||
}
|
||||
|
||||
// GuildRoster represents the complete guild roster
|
||||
type GuildRoster struct {
|
||||
GuildInfo GuildInfo `json:"guild_info"`
|
||||
Members []MemberInfo `json:"members"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// GuildEventInfo provides formatted guild event information
|
||||
type GuildEventInfo struct {
|
||||
EventID int64 `json:"event_id"`
|
||||
Date time.Time `json:"date"`
|
||||
Type int32 `json:"type"`
|
||||
TypeName string `json:"type_name"`
|
||||
Description string `json:"description"`
|
||||
Locked bool `json:"locked"`
|
||||
}
|
||||
|
||||
// GuildSearchCriteria represents guild search parameters
|
||||
type GuildSearchCriteria struct {
|
||||
NamePattern string `json:"name_pattern"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
MaxLevel int8 `json:"max_level"`
|
||||
MinMembers int `json:"min_members"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
RecruitingOnly bool `json:"recruiting_only"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
RequiredFlags []int8 `json:"required_flags"`
|
||||
RequiredDescTags []int8 `json:"required_desc_tags"`
|
||||
ExcludedDescTags []int8 `json:"excluded_desc_tags"`
|
||||
}
|
||||
|
||||
// RecruitingInfo provides detailed recruiting information
|
||||
type RecruitingInfo struct {
|
||||
GuildID int32 `json:"guild_id"`
|
||||
GuildName string `json:"guild_name"`
|
||||
ShortDesc string `json:"short_desc"`
|
||||
FullDesc string `json:"full_desc"`
|
||||
MinLevel int8 `json:"min_level"`
|
||||
PlayStyle int8 `json:"play_style"`
|
||||
Flags map[int8]int8 `json:"flags"`
|
||||
DescTags map[int8]int8 `json:"desc_tags"`
|
||||
Recruiters []RecruiterInfo `json:"recruiters"`
|
||||
}
|
||||
|
||||
// RecruiterInfo provides recruiter information
|
||||
type RecruiterInfo struct {
|
||||
CharacterID int32 `json:"character_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
PictureData []byte `json:"picture_data"`
|
||||
ShowAdventureClass bool `json:"show_adventure_class"`
|
||||
AdventureClass int8 `json:"adventure_class"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
}
|
@ -147,6 +147,21 @@ const (
|
||||
OP_DefaultGroupOptionsMsg
|
||||
OP_DefaultGroupOptionsRequestMsg
|
||||
|
||||
// Guild system opcodes
|
||||
OP_GuildUpdateMsg
|
||||
OP_GuildEventListMsg
|
||||
OP_GuildEventDetailsMsg
|
||||
OP_GuildEventAddMsg
|
||||
OP_GuildMembershipResponseMsg
|
||||
OP_JoinGuildNotifyMsg
|
||||
OP_LeaveGuildNotifyMsg
|
||||
OP_GuildInviteMsg
|
||||
OP_GuildDeclineMsg
|
||||
OP_GuildRecruitingMsg
|
||||
OP_GuildRecruitingDetailsMsg
|
||||
OP_ModifyGuildMsg
|
||||
OP_RequestGuildInfoMsg
|
||||
|
||||
// Add more opcodes as needed...
|
||||
_maxInternalOpcode // Sentinel value
|
||||
)
|
||||
@ -253,6 +268,21 @@ var OpcodeNames = map[InternalOpcode]string{
|
||||
OP_GroupOptionsMsg: "OP_GroupOptionsMsg",
|
||||
OP_DefaultGroupOptionsMsg: "OP_DefaultGroupOptionsMsg",
|
||||
OP_DefaultGroupOptionsRequestMsg: "OP_DefaultGroupOptionsRequestMsg",
|
||||
|
||||
// Guild system opcodes
|
||||
OP_GuildUpdateMsg: "OP_GuildUpdateMsg",
|
||||
OP_GuildEventListMsg: "OP_GuildEventListMsg",
|
||||
OP_GuildEventDetailsMsg: "OP_GuildEventDetailsMsg",
|
||||
OP_GuildEventAddMsg: "OP_GuildEventAddMsg",
|
||||
OP_GuildMembershipResponseMsg: "OP_GuildMembershipResponseMsg",
|
||||
OP_JoinGuildNotifyMsg: "OP_JoinGuildNotifyMsg",
|
||||
OP_LeaveGuildNotifyMsg: "OP_LeaveGuildNotifyMsg",
|
||||
OP_GuildInviteMsg: "OP_GuildInviteMsg",
|
||||
OP_GuildDeclineMsg: "OP_GuildDeclineMsg",
|
||||
OP_GuildRecruitingMsg: "OP_GuildRecruitingMsg",
|
||||
OP_GuildRecruitingDetailsMsg: "OP_GuildRecruitingDetailsMsg",
|
||||
OP_ModifyGuildMsg: "OP_ModifyGuildMsg",
|
||||
OP_RequestGuildInfoMsg: "OP_RequestGuildInfoMsg",
|
||||
}
|
||||
|
||||
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
|
||||
|
Loading…
x
Reference in New Issue
Block a user