293 lines
7.6 KiB
Go
293 lines
7.6 KiB
Go
package chat
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"eq2emu/internal/common"
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// MasterList manages a collection of channels using the generic MasterList base
|
|
type MasterList struct {
|
|
*common.MasterList[int32, *Channel]
|
|
}
|
|
|
|
// NewMasterList creates a new channel master list
|
|
func NewMasterList() *MasterList {
|
|
return &MasterList{
|
|
MasterList: common.NewMasterList[int32, *Channel](),
|
|
}
|
|
}
|
|
|
|
// AddChannel adds a channel to the master list
|
|
func (ml *MasterList) AddChannel(channel *Channel) bool {
|
|
return ml.Add(channel)
|
|
}
|
|
|
|
// GetChannel retrieves a channel by ID
|
|
func (ml *MasterList) GetChannel(id int32) *Channel {
|
|
return ml.Get(id)
|
|
}
|
|
|
|
// GetChannelSafe retrieves a channel by ID with existence check
|
|
func (ml *MasterList) GetChannelSafe(id int32) (*Channel, bool) {
|
|
return ml.GetSafe(id)
|
|
}
|
|
|
|
// HasChannel checks if a channel exists by ID
|
|
func (ml *MasterList) HasChannel(id int32) bool {
|
|
return ml.Exists(id)
|
|
}
|
|
|
|
// RemoveChannel removes a channel by ID
|
|
func (ml *MasterList) RemoveChannel(id int32) bool {
|
|
return ml.Remove(id)
|
|
}
|
|
|
|
// GetAllChannels returns all channels as a map
|
|
func (ml *MasterList) GetAllChannels() map[int32]*Channel {
|
|
return ml.GetAll()
|
|
}
|
|
|
|
// GetAllChannelsList returns all channels as a slice
|
|
func (ml *MasterList) GetAllChannelsList() []*Channel {
|
|
return ml.GetAllSlice()
|
|
}
|
|
|
|
// GetChannelCount returns the number of channels
|
|
func (ml *MasterList) GetChannelCount() int {
|
|
return ml.Size()
|
|
}
|
|
|
|
// ClearChannels removes all channels from the list
|
|
func (ml *MasterList) ClearChannels() {
|
|
ml.Clear()
|
|
}
|
|
|
|
// FindChannelsByName finds channels containing the given name substring
|
|
func (ml *MasterList) FindChannelsByName(nameSubstring string) []*Channel {
|
|
return ml.Filter(func(channel *Channel) bool {
|
|
return contains(channel.GetName(), nameSubstring)
|
|
})
|
|
}
|
|
|
|
// FindChannelsByType finds channels of a specific type
|
|
func (ml *MasterList) FindChannelsByType(channelType int) []*Channel {
|
|
return ml.Filter(func(channel *Channel) bool {
|
|
return channel.GetType() == channelType
|
|
})
|
|
}
|
|
|
|
// GetWorldChannels returns all world channels
|
|
func (ml *MasterList) GetWorldChannels() []*Channel {
|
|
return ml.FindChannelsByType(ChannelTypeWorld)
|
|
}
|
|
|
|
// GetCustomChannels returns all custom channels
|
|
func (ml *MasterList) GetCustomChannels() []*Channel {
|
|
return ml.FindChannelsByType(ChannelTypeCustom)
|
|
}
|
|
|
|
// GetActiveChannels returns channels that have members
|
|
func (ml *MasterList) GetActiveChannels() []*Channel {
|
|
return ml.Filter(func(channel *Channel) bool {
|
|
return !channel.IsEmpty()
|
|
})
|
|
}
|
|
|
|
// GetEmptyChannels returns channels that have no members
|
|
func (ml *MasterList) GetEmptyChannels() []*Channel {
|
|
return ml.Filter(func(channel *Channel) bool {
|
|
return channel.IsEmpty()
|
|
})
|
|
}
|
|
|
|
// GetChannelByName retrieves a channel by name (case-insensitive)
|
|
func (ml *MasterList) GetChannelByName(name string) *Channel {
|
|
name = strings.ToLower(name)
|
|
var foundChannel *Channel
|
|
ml.ForEach(func(id int32, channel *Channel) {
|
|
if strings.ToLower(channel.GetName()) == name {
|
|
foundChannel = channel
|
|
}
|
|
})
|
|
return foundChannel
|
|
}
|
|
|
|
// HasChannelByName checks if a channel exists by name (case-insensitive)
|
|
func (ml *MasterList) HasChannelByName(name string) bool {
|
|
return ml.GetChannelByName(name) != nil
|
|
}
|
|
|
|
// GetCompatibleChannels returns channels compatible with player restrictions
|
|
func (ml *MasterList) GetCompatibleChannels(level, race, class int32) []*Channel {
|
|
return ml.Filter(func(channel *Channel) bool {
|
|
return channel.CanJoinChannelByLevel(level) &&
|
|
channel.CanJoinChannelByRace(race) &&
|
|
channel.CanJoinChannelByClass(class)
|
|
})
|
|
}
|
|
|
|
// ValidateChannels checks all channels for consistency
|
|
func (ml *MasterList) ValidateChannels() []string {
|
|
var issues []string
|
|
|
|
ml.ForEach(func(id int32, channel *Channel) {
|
|
if channel == nil {
|
|
issues = append(issues, fmt.Sprintf("Channel ID %d is nil", id))
|
|
return
|
|
}
|
|
|
|
if channel.GetID() != id {
|
|
issues = append(issues, fmt.Sprintf("Channel ID mismatch: map key %d != channel ID %d", id, channel.GetID()))
|
|
}
|
|
|
|
if len(channel.GetName()) == 0 {
|
|
issues = append(issues, fmt.Sprintf("Channel ID %d has empty name", id))
|
|
}
|
|
|
|
if channel.GetType() < ChannelTypeNone || channel.GetType() > ChannelTypeCustom {
|
|
issues = append(issues, fmt.Sprintf("Channel ID %d has invalid type: %d", id, channel.GetType()))
|
|
}
|
|
|
|
if len(channel.GetName()) > MaxChannelNameLength {
|
|
issues = append(issues, fmt.Sprintf("Channel ID %d name too long: %d > %d", id, len(channel.GetName()), MaxChannelNameLength))
|
|
}
|
|
|
|
if len(channel.Password) > MaxChannelPasswordLength {
|
|
issues = append(issues, fmt.Sprintf("Channel ID %d password too long: %d > %d", id, len(channel.Password), MaxChannelPasswordLength))
|
|
}
|
|
})
|
|
|
|
return issues
|
|
}
|
|
|
|
// IsValid returns true if all channels are valid
|
|
func (ml *MasterList) IsValid() bool {
|
|
issues := ml.ValidateChannels()
|
|
return len(issues) == 0
|
|
}
|
|
|
|
// GetStatistics returns statistics about the channel collection
|
|
func (ml *MasterList) GetStatistics() map[string]any {
|
|
stats := make(map[string]any)
|
|
stats["total_channels"] = ml.Size()
|
|
|
|
if ml.IsEmpty() {
|
|
return stats
|
|
}
|
|
|
|
// Count by channel type
|
|
typeCounts := make(map[int]int)
|
|
var totalMembers int
|
|
var activeChannels int
|
|
var minID, maxID int32
|
|
first := true
|
|
|
|
ml.ForEach(func(id int32, channel *Channel) {
|
|
typeCounts[channel.GetType()]++
|
|
totalMembers += channel.GetNumClients()
|
|
|
|
if !channel.IsEmpty() {
|
|
activeChannels++
|
|
}
|
|
|
|
if first {
|
|
minID = id
|
|
maxID = id
|
|
first = false
|
|
} else {
|
|
if id < minID {
|
|
minID = id
|
|
}
|
|
if id > maxID {
|
|
maxID = id
|
|
}
|
|
}
|
|
})
|
|
|
|
stats["channels_by_type"] = typeCounts
|
|
stats["world_channels"] = typeCounts[ChannelTypeWorld]
|
|
stats["custom_channels"] = typeCounts[ChannelTypeCustom]
|
|
stats["total_members"] = totalMembers
|
|
stats["active_channels"] = activeChannels
|
|
stats["min_id"] = minID
|
|
stats["max_id"] = maxID
|
|
stats["id_range"] = maxID - minID
|
|
|
|
return stats
|
|
}
|
|
|
|
// LoadAllChannels loads all channels from the database into the master list
|
|
func (ml *MasterList) LoadAllChannels(db *database.Database) error {
|
|
if db == nil {
|
|
return fmt.Errorf("database connection is nil")
|
|
}
|
|
|
|
// Clear existing channels
|
|
ml.Clear()
|
|
|
|
query := `SELECT id, name, password, type, level_restriction, race_restriction, class_restriction, discord_enabled, created_at, updated_at FROM channels ORDER BY id`
|
|
rows, err := db.Query(query)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query channels: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
count := 0
|
|
for rows.Next() {
|
|
channel := &Channel{
|
|
db: db,
|
|
isNew: false,
|
|
members: make([]int32, 0),
|
|
}
|
|
|
|
err := rows.Scan(&channel.ID, &channel.Name, &channel.Password, &channel.ChannelType,
|
|
&channel.LevelRestriction, &channel.RaceRestriction, &channel.ClassRestriction,
|
|
&channel.DiscordEnabled, &channel.Created, &channel.Updated)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to scan channel: %w", err)
|
|
}
|
|
|
|
if !ml.AddChannel(channel) {
|
|
return fmt.Errorf("failed to add channel %d to master list", channel.ID)
|
|
}
|
|
|
|
count++
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return fmt.Errorf("error iterating channel rows: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadAllChannelsFromDatabase is a convenience function that creates a master list and loads all channels
|
|
func LoadAllChannelsFromDatabase(db *database.Database) (*MasterList, error) {
|
|
masterList := NewMasterList()
|
|
err := masterList.LoadAllChannels(db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return masterList, nil
|
|
}
|
|
|
|
// contains checks if a string contains a substring (case-sensitive)
|
|
func contains(str, substr string) bool {
|
|
if len(substr) == 0 {
|
|
return true
|
|
}
|
|
if len(str) < len(substr) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i <= len(str)-len(substr); i++ {
|
|
if str[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
} |