236 lines
6.0 KiB
Go

package appearances
import (
"fmt"
"eq2emu/internal/common"
"eq2emu/internal/database"
)
// MasterList manages a collection of appearances using the generic MasterList base
type MasterList struct {
*common.MasterList[int32, *Appearance]
}
// NewMasterList creates a new appearance master list
func NewMasterList() *MasterList {
return &MasterList{
MasterList: common.NewMasterList[int32, *Appearance](),
}
}
// AddAppearance adds an appearance to the master list
func (ml *MasterList) AddAppearance(appearance *Appearance) bool {
return ml.Add(appearance)
}
// GetAppearance retrieves an appearance by ID
func (ml *MasterList) GetAppearance(id int32) *Appearance {
return ml.Get(id)
}
// GetAppearanceSafe retrieves an appearance by ID with existence check
func (ml *MasterList) GetAppearanceSafe(id int32) (*Appearance, bool) {
return ml.GetSafe(id)
}
// HasAppearance checks if an appearance exists by ID
func (ml *MasterList) HasAppearance(id int32) bool {
return ml.Exists(id)
}
// RemoveAppearance removes an appearance by ID
func (ml *MasterList) RemoveAppearance(id int32) bool {
return ml.Remove(id)
}
// GetAllAppearances returns all appearances as a map
func (ml *MasterList) GetAllAppearances() map[int32]*Appearance {
return ml.GetAll()
}
// GetAllAppearancesList returns all appearances as a slice
func (ml *MasterList) GetAllAppearancesList() []*Appearance {
return ml.GetAllSlice()
}
// GetAppearanceCount returns the number of appearances
func (ml *MasterList) GetAppearanceCount() int {
return ml.Size()
}
// ClearAppearances removes all appearances from the list
func (ml *MasterList) ClearAppearances() {
ml.Clear()
}
// FindAppearancesByName finds appearances containing the given name substring
func (ml *MasterList) FindAppearancesByName(nameSubstring string) []*Appearance {
return ml.Filter(func(appearance *Appearance) bool {
return contains(appearance.GetName(), nameSubstring)
})
}
// FindAppearancesByMinClient finds appearances with specific minimum client version
func (ml *MasterList) FindAppearancesByMinClient(minClient int16) []*Appearance {
return ml.Filter(func(appearance *Appearance) bool {
return appearance.GetMinClientVersion() == minClient
})
}
// GetCompatibleAppearances returns appearances compatible with the given client version
func (ml *MasterList) GetCompatibleAppearances(clientVersion int16) []*Appearance {
return ml.Filter(func(appearance *Appearance) bool {
return appearance.IsCompatibleWithClient(clientVersion)
})
}
// GetAppearancesByIDRange returns appearances within the given ID range (inclusive)
func (ml *MasterList) GetAppearancesByIDRange(minID, maxID int32) []*Appearance {
return ml.Filter(func(appearance *Appearance) bool {
id := appearance.GetID()
return id >= minID && id <= maxID
})
}
// ValidateAppearances checks all appearances for consistency
func (ml *MasterList) ValidateAppearances() []string {
var issues []string
ml.ForEach(func(id int32, appearance *Appearance) {
if appearance == nil {
issues = append(issues, fmt.Sprintf("Appearance ID %d is nil", id))
return
}
if appearance.GetID() != id {
issues = append(issues, fmt.Sprintf("Appearance ID mismatch: map key %d != appearance ID %d", id, appearance.GetID()))
}
if len(appearance.GetName()) == 0 {
issues = append(issues, fmt.Sprintf("Appearance ID %d has empty name", id))
}
if appearance.GetMinClientVersion() < 0 {
issues = append(issues, fmt.Sprintf("Appearance ID %d has negative min client version: %d", id, appearance.GetMinClientVersion()))
}
})
return issues
}
// IsValid returns true if all appearances are valid
func (ml *MasterList) IsValid() bool {
issues := ml.ValidateAppearances()
return len(issues) == 0
}
// GetStatistics returns statistics about the appearance collection
func (ml *MasterList) GetStatistics() map[string]any {
stats := make(map[string]any)
stats["total_appearances"] = ml.Size()
if ml.IsEmpty() {
return stats
}
// Count by minimum client version
versionCounts := make(map[int16]int)
var minID, maxID int32
first := true
ml.ForEach(func(id int32, appearance *Appearance) {
versionCounts[appearance.GetMinClientVersion()]++
if first {
minID = id
maxID = id
first = false
} else {
if id < minID {
minID = id
}
if id > maxID {
maxID = id
}
}
})
stats["appearances_by_min_client"] = versionCounts
stats["min_id"] = minID
stats["max_id"] = maxID
stats["id_range"] = maxID - minID
return stats
}
// LoadAllAppearances loads all appearances from the database into the master list
func (ml *MasterList) LoadAllAppearances(db *database.Database) error {
if db == nil {
return fmt.Errorf("database connection is nil")
}
// Clear existing appearances
ml.Clear()
query := `SELECT appearance_id, name, min_client_version FROM appearances ORDER BY appearance_id`
rows, err := db.Query(query)
if err != nil {
return fmt.Errorf("failed to query appearances: %w", err)
}
defer rows.Close()
count := 0
for rows.Next() {
appearance := &Appearance{
db: db,
isNew: false,
}
err := rows.Scan(&appearance.ID, &appearance.Name, &appearance.MinClient)
if err != nil {
return fmt.Errorf("failed to scan appearance: %w", err)
}
if !ml.AddAppearance(appearance) {
return fmt.Errorf("failed to add appearance %d to master list", appearance.ID)
}
count++
}
if err := rows.Err(); err != nil {
return fmt.Errorf("error iterating appearance rows: %w", err)
}
return nil
}
// LoadAllAppearancesFromDatabase is a convenience function that creates a master list and loads all appearances
func LoadAllAppearancesFromDatabase(db *database.Database) (*MasterList, error) {
masterList := NewMasterList()
err := masterList.LoadAllAppearances(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
}