471 lines
11 KiB
Go
471 lines
11 KiB
Go
package sign
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// Manager provides high-level management of the sign system
|
|
type Manager struct {
|
|
signs map[int32]*Sign // Signs by ID
|
|
signsByZone map[int32][]*Sign // Signs by zone ID
|
|
signsByWidget map[int32]*Sign // Signs by widget ID
|
|
database Database
|
|
logger Logger
|
|
mutex sync.RWMutex
|
|
|
|
// Statistics
|
|
totalSigns int64
|
|
signsByType map[int8]int64 // Sign type -> count
|
|
signInteractions int64
|
|
zoneTransports int64
|
|
transporterUses int64
|
|
}
|
|
|
|
// NewManager creates a new sign manager
|
|
func NewManager(database Database, logger Logger) *Manager {
|
|
return &Manager{
|
|
signs: make(map[int32]*Sign),
|
|
signsByZone: make(map[int32][]*Sign),
|
|
signsByWidget: make(map[int32]*Sign),
|
|
database: database,
|
|
logger: logger,
|
|
signsByType: make(map[int8]int64),
|
|
}
|
|
}
|
|
|
|
// Initialize loads signs from database
|
|
func (m *Manager) Initialize() error {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Initializing sign manager...")
|
|
}
|
|
|
|
// TODO: Load all signs from database when database system is integrated
|
|
// This would typically iterate through all zones and load their signs
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadZoneSigns loads signs for a specific zone
|
|
func (m *Manager) LoadZoneSigns(zoneID int32) error {
|
|
if m.database == nil {
|
|
return fmt.Errorf("database is nil")
|
|
}
|
|
|
|
signs, err := m.database.LoadSigns(zoneID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load signs for zone %d: %w", zoneID, err)
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
for _, sign := range signs {
|
|
m.addSignUnsafe(sign)
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Loaded %d signs for zone %d", len(signs), zoneID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddSign adds a sign to the manager
|
|
func (m *Manager) AddSign(sign *Sign) error {
|
|
if sign == nil {
|
|
return fmt.Errorf("sign is nil")
|
|
}
|
|
|
|
// Validate the sign
|
|
if issues := sign.Validate(); len(issues) > 0 {
|
|
return fmt.Errorf("sign validation failed: %v", issues)
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.addSignUnsafe(sign)
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Added sign %d (widget %d) of type %d",
|
|
sign.Spawn.GetDatabaseID(), sign.GetWidgetID(), sign.GetSignType())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addSignUnsafe adds a sign without locking (internal use)
|
|
func (m *Manager) addSignUnsafe(sign *Sign) {
|
|
signID := sign.Spawn.GetDatabaseID()
|
|
widgetID := sign.GetWidgetID()
|
|
|
|
// Add to main collection
|
|
m.signs[signID] = sign
|
|
|
|
// Add to widget collection
|
|
if widgetID > 0 {
|
|
m.signsByWidget[widgetID] = sign
|
|
}
|
|
|
|
// Add to zone collection
|
|
if sign.Spawn != nil {
|
|
zoneID := sign.Spawn.GetZone()
|
|
m.signsByZone[zoneID] = append(m.signsByZone[zoneID], sign)
|
|
}
|
|
|
|
// Update statistics
|
|
m.totalSigns++
|
|
m.signsByType[sign.GetSignType()]++
|
|
}
|
|
|
|
// RemoveSign removes a sign from the manager
|
|
func (m *Manager) RemoveSign(signID int32) bool {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
sign, exists := m.signs[signID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from main collection
|
|
delete(m.signs, signID)
|
|
|
|
// Remove from widget collection
|
|
if sign.GetWidgetID() > 0 {
|
|
delete(m.signsByWidget, sign.GetWidgetID())
|
|
}
|
|
|
|
// Remove from zone collection
|
|
if sign.Spawn != nil {
|
|
zoneID := sign.Spawn.GetZone()
|
|
if zoneSigns, exists := m.signsByZone[zoneID]; exists {
|
|
for i, zoneSign := range zoneSigns {
|
|
if zoneSign == sign {
|
|
m.signsByZone[zoneID] = append(zoneSigns[:i], zoneSigns[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update statistics
|
|
m.totalSigns--
|
|
m.signsByType[sign.GetSignType()]--
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Removed sign %d (widget %d)", signID, sign.GetWidgetID())
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GetSign returns a sign by ID
|
|
func (m *Manager) GetSign(signID int32) *Sign {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.signs[signID]
|
|
}
|
|
|
|
// GetSignByWidget returns a sign by widget ID
|
|
func (m *Manager) GetSignByWidget(widgetID int32) *Sign {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.signsByWidget[widgetID]
|
|
}
|
|
|
|
// GetZoneSigns returns all signs in a zone
|
|
func (m *Manager) GetZoneSigns(zoneID int32) []*Sign {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
signs := m.signsByZone[zoneID]
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]*Sign, len(signs))
|
|
copy(result, signs)
|
|
|
|
return result
|
|
}
|
|
|
|
// GetSignsByType returns all signs of a specific type
|
|
func (m *Manager) GetSignsByType(signType int8) []*Sign {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
var result []*Sign
|
|
for _, sign := range m.signs {
|
|
if sign.GetSignType() == signType {
|
|
result = append(result, sign)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// SaveSign saves a sign to database
|
|
func (m *Manager) SaveSign(sign *Sign) error {
|
|
if m.database == nil {
|
|
return fmt.Errorf("database is nil")
|
|
}
|
|
|
|
if sign == nil {
|
|
return fmt.Errorf("sign is nil")
|
|
}
|
|
|
|
err := m.database.SaveSign(sign)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save sign: %w", err)
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogDebug("Saved sign %d to database", sign.Spawn.GetDatabaseID())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HandleSignUse processes sign usage and records statistics
|
|
func (m *Manager) HandleSignUse(sign *Sign, client Client, command string) error {
|
|
if sign == nil {
|
|
return fmt.Errorf("sign is nil")
|
|
}
|
|
|
|
m.mutex.Lock()
|
|
m.signInteractions++
|
|
m.mutex.Unlock()
|
|
|
|
err := sign.HandleUse(client, command)
|
|
|
|
// Record specific interaction types for statistics
|
|
if err == nil {
|
|
m.mutex.Lock()
|
|
if sign.IsZoneSign() {
|
|
m.zoneTransports++
|
|
}
|
|
if sign.Spawn != nil && sign.Spawn.GetTransporterID() > 0 {
|
|
m.transporterUses++
|
|
}
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
if m.logger != nil {
|
|
if err != nil {
|
|
m.logger.LogError("Sign %d use failed: %v", sign.Spawn.GetDatabaseID(), err)
|
|
} else {
|
|
m.logger.LogDebug("Sign %d used successfully by character %d",
|
|
sign.Spawn.GetDatabaseID(), client.GetCharacterID())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// GetStatistics returns sign system statistics
|
|
func (m *Manager) GetStatistics() map[string]interface{} {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
stats := make(map[string]interface{})
|
|
stats["total_signs"] = m.totalSigns
|
|
stats["sign_interactions"] = m.signInteractions
|
|
stats["zone_transports"] = m.zoneTransports
|
|
stats["transporter_uses"] = m.transporterUses
|
|
|
|
// Copy sign type statistics
|
|
typeStats := make(map[int8]int64)
|
|
for signType, count := range m.signsByType {
|
|
typeStats[signType] = count
|
|
}
|
|
stats["signs_by_type"] = typeStats
|
|
|
|
// Zone statistics
|
|
zoneStats := make(map[int32]int)
|
|
for zoneID, signs := range m.signsByZone {
|
|
zoneStats[zoneID] = len(signs)
|
|
}
|
|
stats["signs_by_zone"] = zoneStats
|
|
|
|
return stats
|
|
}
|
|
|
|
// ResetStatistics resets all statistics
|
|
func (m *Manager) ResetStatistics() {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.signInteractions = 0
|
|
m.zoneTransports = 0
|
|
m.transporterUses = 0
|
|
}
|
|
|
|
// ValidateAllSigns validates all signs in the system
|
|
func (m *Manager) ValidateAllSigns() map[int32][]string {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
issues := make(map[int32][]string)
|
|
|
|
for signID, sign := range m.signs {
|
|
if signIssues := sign.Validate(); len(signIssues) > 0 {
|
|
issues[signID] = signIssues
|
|
}
|
|
}
|
|
|
|
return issues
|
|
}
|
|
|
|
// GetSignCount returns the total number of signs
|
|
func (m *Manager) GetSignCount() int64 {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.totalSigns
|
|
}
|
|
|
|
// GetSignTypeCount returns the number of signs of a specific type
|
|
func (m *Manager) GetSignTypeCount(signType int8) int64 {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
return m.signsByType[signType]
|
|
}
|
|
|
|
// ProcessCommand handles sign-related commands
|
|
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
|
|
switch command {
|
|
case "stats":
|
|
return m.handleStatsCommand(args)
|
|
case "validate":
|
|
return m.handleValidateCommand(args)
|
|
case "list":
|
|
return m.handleListCommand(args)
|
|
case "info":
|
|
return m.handleInfoCommand(args)
|
|
default:
|
|
return "", fmt.Errorf("unknown sign command: %s", command)
|
|
}
|
|
}
|
|
|
|
// handleStatsCommand shows sign system statistics
|
|
func (m *Manager) handleStatsCommand(args []string) (string, error) {
|
|
stats := m.GetStatistics()
|
|
|
|
result := "Sign System Statistics:\n"
|
|
result += fmt.Sprintf("Total Signs: %d\n", stats["total_signs"])
|
|
result += fmt.Sprintf("Sign Interactions: %d\n", stats["sign_interactions"])
|
|
result += fmt.Sprintf("Zone Transports: %d\n", stats["zone_transports"])
|
|
result += fmt.Sprintf("Transporter Uses: %d\n", stats["transporter_uses"])
|
|
|
|
typeStats := stats["signs_by_type"].(map[int8]int64)
|
|
result += fmt.Sprintf("Generic Signs: %d\n", typeStats[SignTypeGeneric])
|
|
result += fmt.Sprintf("Zone Signs: %d\n", typeStats[SignTypeZone])
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleValidateCommand validates all signs
|
|
func (m *Manager) handleValidateCommand(args []string) (string, error) {
|
|
issues := m.ValidateAllSigns()
|
|
|
|
if len(issues) == 0 {
|
|
return "All signs are valid.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found issues with %d signs:\n", len(issues))
|
|
count := 0
|
|
for signID, signIssues := range issues {
|
|
if count >= 10 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf("Sign %d:\n", signID)
|
|
for _, issue := range signIssues {
|
|
result += fmt.Sprintf(" - %s\n", issue)
|
|
}
|
|
count++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleListCommand lists signs
|
|
func (m *Manager) handleListCommand(args []string) (string, error) {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
if len(m.signs) == 0 {
|
|
return "No signs loaded.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Signs (%d):\n", len(m.signs))
|
|
count := 0
|
|
for signID, sign := range m.signs {
|
|
if count >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
|
|
typeName := "Generic"
|
|
if sign.GetSignType() == SignTypeZone {
|
|
typeName = "Zone"
|
|
}
|
|
|
|
result += fmt.Sprintf(" %d: %s (%s, Widget: %d)\n",
|
|
signID, sign.GetSignTitle(), typeName, sign.GetWidgetID())
|
|
count++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleInfoCommand shows information about a specific sign
|
|
func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("sign ID required")
|
|
}
|
|
|
|
// Parse sign ID
|
|
var signID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &signID); err != nil {
|
|
return "", fmt.Errorf("invalid sign ID: %s", args[0])
|
|
}
|
|
|
|
sign := m.GetSign(signID)
|
|
if sign == nil {
|
|
return fmt.Sprintf("Sign %d not found.", signID), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Sign Information:\n")
|
|
result += fmt.Sprintf("ID: %d\n", signID)
|
|
result += fmt.Sprintf("Widget ID: %d\n", sign.GetWidgetID())
|
|
result += fmt.Sprintf("Type: %d\n", sign.GetSignType())
|
|
result += fmt.Sprintf("Title: %s\n", sign.GetSignTitle())
|
|
result += fmt.Sprintf("Description: %s\n", sign.GetSignDescription())
|
|
result += fmt.Sprintf("Language: %d\n", sign.GetLanguage())
|
|
|
|
if sign.IsZoneSign() {
|
|
result += fmt.Sprintf("Zone ID: %d\n", sign.GetSignZoneID())
|
|
result += fmt.Sprintf("Zone Coords: (%.2f, %.2f, %.2f)\n",
|
|
sign.GetSignZoneX(), sign.GetSignZoneY(), sign.GetSignZoneZ())
|
|
result += fmt.Sprintf("Zone Heading: %.2f\n", sign.GetSignZoneHeading())
|
|
result += fmt.Sprintf("Distance: %.2f\n", sign.GetSignDistance())
|
|
}
|
|
|
|
result += fmt.Sprintf("Include Location: %t\n", sign.GetIncludeLocation())
|
|
result += fmt.Sprintf("Include Heading: %t\n", sign.GetIncludeHeading())
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the manager
|
|
func (m *Manager) Shutdown() {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Shutting down sign manager...")
|
|
}
|
|
|
|
// Nothing to clean up currently, but placeholder for future cleanup
|
|
} |