simplify guilds

This commit is contained in:
Sky Johnson 2025-08-29 14:10:30 -05:00
parent 575f94f3c1
commit 95c0561416
9 changed files with 1363 additions and 2605 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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