524 lines
14 KiB
Go
524 lines
14 KiB
Go
package collections
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"eq2emu/internal/database"
|
|
"eq2emu/internal/packets"
|
|
)
|
|
|
|
// Manager provides centralized management for the collections system
|
|
// following the SIMPLIFICATION.md methodology while maintaining C++ API compatibility
|
|
type Manager struct {
|
|
// Core components
|
|
masterList *MasterList
|
|
db *database.Database
|
|
|
|
// Thread safety
|
|
mutex sync.RWMutex
|
|
|
|
// Performance monitoring
|
|
stats struct {
|
|
// Collection operations
|
|
CollectionsLoaded int64
|
|
CollectionsSaved int64
|
|
CollectionItemsUpdated int64
|
|
|
|
// Player operations
|
|
PlayersLoaded int64
|
|
PlayersUpdated int64
|
|
ItemChecks int64
|
|
|
|
// Packet operations
|
|
PacketsSent int64
|
|
PacketErrors int64
|
|
PacketUpdates int64
|
|
|
|
// Cache performance
|
|
CacheHits int64
|
|
CacheMisses int64
|
|
}
|
|
|
|
// Configuration
|
|
config struct {
|
|
EnableCaching bool
|
|
EnableStatistics bool
|
|
EnableValidation bool
|
|
MaxPlayersToTrack int
|
|
AutoSaveInterval time.Duration
|
|
PacketBuilderTimeout time.Duration
|
|
}
|
|
}
|
|
|
|
// NewManager creates a new collections manager with default configuration
|
|
func NewManager(db *database.Database) *Manager {
|
|
m := &Manager{
|
|
masterList: NewMasterList(),
|
|
db: db,
|
|
}
|
|
|
|
// Set default configuration
|
|
m.config.EnableCaching = true
|
|
m.config.EnableStatistics = true
|
|
m.config.EnableValidation = true
|
|
m.config.MaxPlayersToTrack = 1000
|
|
m.config.AutoSaveInterval = 5 * time.Minute
|
|
m.config.PacketBuilderTimeout = 30 * time.Second
|
|
|
|
return m
|
|
}
|
|
|
|
// Initialize loads all collections from database and prepares the manager
|
|
func (m *Manager) Initialize() error {
|
|
if m.db == nil {
|
|
return fmt.Errorf("database connection is required")
|
|
}
|
|
|
|
err := m.masterList.LoadAllCollections(m.db)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load collections: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.CollectionsLoaded = int64(m.masterList.Size())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// === C++ API Compatibility Methods ===
|
|
|
|
// AddCollection adds a collection to the master list (C++ API compatibility)
|
|
func (m *Manager) AddCollection(collection *Collection) bool {
|
|
if collection == nil {
|
|
return false
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.CollectionsLoaded++
|
|
}
|
|
|
|
return m.masterList.AddCollection(collection)
|
|
}
|
|
|
|
// GetCollection retrieves a collection by ID (C++ API compatibility)
|
|
func (m *Manager) GetCollection(id int32) *Collection {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
if m.config.EnableStatistics {
|
|
if collection := m.masterList.GetCollection(id); collection != nil {
|
|
m.stats.CacheHits++
|
|
return collection
|
|
}
|
|
m.stats.CacheMisses++
|
|
return nil
|
|
}
|
|
|
|
return m.masterList.GetCollection(id)
|
|
}
|
|
|
|
// ClearCollections removes all collections (C++ API compatibility)
|
|
func (m *Manager) ClearCollections() {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.masterList.ClearCollections()
|
|
}
|
|
|
|
// Size returns the number of collections (C++ API compatibility)
|
|
func (m *Manager) Size() int32 {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return int32(m.masterList.Size())
|
|
}
|
|
|
|
// NeedsItem checks if any collection needs the specified item (C++ API compatibility)
|
|
func (m *Manager) NeedsItem(itemID int32) bool {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.ItemChecks++
|
|
}
|
|
|
|
return m.masterList.NeedsItem(itemID)
|
|
}
|
|
|
|
// GetMasterList returns the master collection list (C++ API compatibility)
|
|
func (m *Manager) GetMasterList() *MasterList {
|
|
return m.masterList
|
|
}
|
|
|
|
// === Enhanced Go Methods ===
|
|
|
|
// GetCollectionsSafe retrieves all collections with thread safety
|
|
func (m *Manager) GetCollectionsSafe() map[int32]*Collection {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.masterList.GetAllCollections()
|
|
}
|
|
|
|
// UpdateCollection updates an existing collection with validation
|
|
func (m *Manager) UpdateCollection(collection *Collection) error {
|
|
if collection == nil {
|
|
return fmt.Errorf("collection cannot be nil")
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
if m.config.EnableValidation {
|
|
if collection.GetID() <= 0 {
|
|
return fmt.Errorf("invalid collection ID: %d", collection.GetID())
|
|
}
|
|
if len(collection.GetName()) == 0 {
|
|
return fmt.Errorf("collection name cannot be empty")
|
|
}
|
|
}
|
|
|
|
err := m.masterList.UpdateCollection(collection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.CollectionsLoaded++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// === Packet Building Methods ===
|
|
|
|
// SendCollectionUpdate builds and returns collection update packet data (C++ API compatibility)
|
|
func (m *Manager) SendCollectionUpdate(characterID int32, clientVersion uint32, playerCollections map[int32]*Collection) ([]byte, error) {
|
|
packet, exists := packets.GetPacket("WS_CollectionUpdate")
|
|
if !exists {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to get WS_CollectionUpdate packet structure: packet not found")
|
|
}
|
|
|
|
// Build collection array
|
|
collectionArray := make([]map[string]interface{}, 0, len(playerCollections))
|
|
for _, collection := range playerCollections {
|
|
// Build collection items array
|
|
itemsArray := make([]map[string]interface{}, 0, len(collection.CollectionItems))
|
|
for _, item := range collection.CollectionItems {
|
|
itemsArray = append(itemsArray, map[string]interface{}{
|
|
"item_flag": item.Found,
|
|
})
|
|
}
|
|
|
|
collectionData := map[string]interface{}{
|
|
"collection_name": collection.GetName(),
|
|
"collection_category": collection.GetCategory(),
|
|
"completed": collection.Completed,
|
|
"collection_id": collection.GetID(),
|
|
"level": collection.GetLevel(),
|
|
"ready_to_turn_in": collection.GetIsReadyToTurnIn(),
|
|
"num_items": len(collection.CollectionItems),
|
|
"items_array": itemsArray,
|
|
}
|
|
collectionArray = append(collectionArray, collectionData)
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"num_collections": len(playerCollections),
|
|
"collections_array": collectionArray,
|
|
"new_collection_flag": 1,
|
|
}
|
|
|
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
|
packetData, err := builder.Build(data)
|
|
if err != nil {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to build collection update packet: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketsSent++
|
|
m.stats.PacketUpdates++
|
|
}
|
|
|
|
return packetData, nil
|
|
}
|
|
|
|
// SendCollectionFilter builds and returns collection filter packet data (C++ API compatibility)
|
|
func (m *Manager) SendCollectionFilter(characterID int32, clientVersion uint32, itemID int32, collectionsForItem []*Collection) ([]byte, error) {
|
|
packet, exists := packets.GetPacket("WS_CollectionFilter")
|
|
if !exists {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to get WS_CollectionFilter packet structure: packet not found")
|
|
}
|
|
|
|
// Build filter array
|
|
filterArray := make([]map[string]interface{}, 0, len(collectionsForItem))
|
|
for _, collection := range collectionsForItem {
|
|
collectionItem := collection.GetCollectionItemByItemID(itemID)
|
|
if collectionItem != nil {
|
|
filterData := map[string]interface{}{
|
|
"collection_id": collection.GetID(),
|
|
"collection_item_num": collectionItem.Index,
|
|
}
|
|
filterArray = append(filterArray, filterData)
|
|
}
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"num_filters": len(filterArray),
|
|
"filters_array": filterArray,
|
|
"unknown3": m.Size(), // Player collection count
|
|
}
|
|
|
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
|
packetData, err := builder.Build(data)
|
|
if err != nil {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to build collection filter packet: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketsSent++
|
|
}
|
|
|
|
return packetData, nil
|
|
}
|
|
|
|
// SendCollectionItem builds and returns collection item packet data (C++ API compatibility)
|
|
func (m *Manager) SendCollectionItem(characterID int32, clientVersion uint32, collectionID int32, itemID int32) ([]byte, error) {
|
|
packet, exists := packets.GetPacket("WS_CollectionItem")
|
|
if !exists {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to get WS_CollectionItem packet structure: packet not found")
|
|
}
|
|
|
|
collection := m.GetCollection(collectionID)
|
|
if collection == nil {
|
|
return nil, fmt.Errorf("collection %d not found", collectionID)
|
|
}
|
|
|
|
collectionItem := collection.GetCollectionItemByItemID(itemID)
|
|
if collectionItem == nil {
|
|
return nil, fmt.Errorf("collection item %d not found in collection %d", itemID, collectionID)
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"collection_id": collectionID,
|
|
"collection_item_num": collectionItem.Index,
|
|
"name": collection.GetName(),
|
|
"description": collection.GetCategory(),
|
|
"level": collection.GetLevel(),
|
|
"max_coin": collection.RewardCoin,
|
|
"min_coin": collection.RewardCoin,
|
|
}
|
|
|
|
builder := packets.NewPacketBuilder(packet, clientVersion, 0)
|
|
packetData, err := builder.Build(data)
|
|
if err != nil {
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketErrors++
|
|
}
|
|
return nil, fmt.Errorf("failed to build collection item packet: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.PacketsSent++
|
|
}
|
|
|
|
return packetData, nil
|
|
}
|
|
|
|
// === Player Collection Management ===
|
|
|
|
// CreatePlayerList creates a new player collection list
|
|
func (m *Manager) CreatePlayerList(characterID int32) *PlayerList {
|
|
return NewPlayerList(characterID, m.db)
|
|
}
|
|
|
|
// LoadPlayerCollections loads all collections for a player
|
|
func (m *Manager) LoadPlayerCollections(characterID int32) (*PlayerList, error) {
|
|
playerList := NewPlayerList(characterID, m.db)
|
|
err := playerList.LoadPlayerCollections(m.masterList)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load player collections: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.PlayersLoaded++
|
|
}
|
|
|
|
return playerList, nil
|
|
}
|
|
|
|
// SavePlayerCollections saves all player collection progress
|
|
func (m *Manager) SavePlayerCollections(playerList *PlayerList) error {
|
|
if playerList == nil {
|
|
return fmt.Errorf("player list cannot be nil")
|
|
}
|
|
|
|
err := playerList.SaveAllCollections()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save player collections: %v", err)
|
|
}
|
|
|
|
if m.config.EnableStatistics {
|
|
m.stats.CollectionsSaved++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// === Statistics and Monitoring ===
|
|
|
|
// GetStatistics returns comprehensive manager statistics
|
|
func (m *Manager) GetStatistics() map[string]interface{} {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
stats := make(map[string]interface{})
|
|
|
|
// Manager stats
|
|
stats["collections_loaded"] = m.stats.CollectionsLoaded
|
|
stats["collections_saved"] = m.stats.CollectionsSaved
|
|
stats["collection_items_updated"] = m.stats.CollectionItemsUpdated
|
|
|
|
// Player stats
|
|
stats["players_loaded"] = m.stats.PlayersLoaded
|
|
stats["players_updated"] = m.stats.PlayersUpdated
|
|
stats["item_checks"] = m.stats.ItemChecks
|
|
|
|
// Packet stats
|
|
stats["packets_sent"] = m.stats.PacketsSent
|
|
stats["packet_errors"] = m.stats.PacketErrors
|
|
stats["packet_updates"] = m.stats.PacketUpdates
|
|
|
|
// Cache stats
|
|
stats["cache_hits"] = m.stats.CacheHits
|
|
stats["cache_misses"] = m.stats.CacheMisses
|
|
|
|
if m.stats.CacheHits+m.stats.CacheMisses > 0 {
|
|
hitRate := float64(m.stats.CacheHits) / float64(m.stats.CacheHits+m.stats.CacheMisses) * 100
|
|
stats["cache_hit_rate"] = fmt.Sprintf("%.2f%%", hitRate)
|
|
}
|
|
|
|
// Master list stats
|
|
masterStats := m.masterList.GetStatistics()
|
|
for key, value := range masterStats {
|
|
stats["master_"+key] = value
|
|
}
|
|
|
|
// Configuration
|
|
stats["config_caching_enabled"] = m.config.EnableCaching
|
|
stats["config_statistics_enabled"] = m.config.EnableStatistics
|
|
stats["config_validation_enabled"] = m.config.EnableValidation
|
|
|
|
return stats
|
|
}
|
|
|
|
// ResetStatistics clears all performance counters
|
|
func (m *Manager) ResetStatistics() {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.stats = struct {
|
|
CollectionsLoaded int64
|
|
CollectionsSaved int64
|
|
CollectionItemsUpdated int64
|
|
PlayersLoaded int64
|
|
PlayersUpdated int64
|
|
ItemChecks int64
|
|
PacketsSent int64
|
|
PacketErrors int64
|
|
PacketUpdates int64
|
|
CacheHits int64
|
|
CacheMisses int64
|
|
}{}
|
|
}
|
|
|
|
// === Configuration Management ===
|
|
|
|
// SetConfiguration updates manager configuration
|
|
func (m *Manager) SetConfiguration(config map[string]interface{}) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
if val, ok := config["enable_caching"].(bool); ok {
|
|
m.config.EnableCaching = val
|
|
}
|
|
if val, ok := config["enable_statistics"].(bool); ok {
|
|
m.config.EnableStatistics = val
|
|
}
|
|
if val, ok := config["enable_validation"].(bool); ok {
|
|
m.config.EnableValidation = val
|
|
}
|
|
if val, ok := config["max_players_to_track"].(int); ok {
|
|
m.config.MaxPlayersToTrack = val
|
|
}
|
|
if val, ok := config["auto_save_interval"].(time.Duration); ok {
|
|
m.config.AutoSaveInterval = val
|
|
}
|
|
}
|
|
|
|
// GetConfiguration returns current manager configuration
|
|
func (m *Manager) GetConfiguration() map[string]interface{} {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"enable_caching": m.config.EnableCaching,
|
|
"enable_statistics": m.config.EnableStatistics,
|
|
"enable_validation": m.config.EnableValidation,
|
|
"max_players_to_track": m.config.MaxPlayersToTrack,
|
|
"auto_save_interval": m.config.AutoSaveInterval,
|
|
"packet_builder_timeout": m.config.PacketBuilderTimeout,
|
|
}
|
|
}
|
|
|
|
// === Validation and Health Checks ===
|
|
|
|
// ValidateCollections performs comprehensive validation
|
|
func (m *Manager) ValidateCollections() []string {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.masterList.ValidateCollections()
|
|
}
|
|
|
|
// IsHealthy returns true if the manager is in a healthy state
|
|
func (m *Manager) IsHealthy() bool {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
if m.db == nil {
|
|
return false
|
|
}
|
|
|
|
if m.masterList == nil {
|
|
return false
|
|
}
|
|
|
|
if m.config.EnableValidation {
|
|
issues := m.masterList.ValidateCollections()
|
|
return len(issues) == 0
|
|
}
|
|
|
|
return true
|
|
} |