From afded7da3b5d074c5227a1f52847c449da998f17 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 29 Aug 2025 17:02:35 -0500 Subject: [PATCH] simplify languages --- SIMPLIFICATION.md | 1 + internal/languages/constants.go | 39 - internal/languages/interfaces.go | 397 --------- internal/languages/languages.go | 1409 ++++++++++++++++++++++++++++++ internal/languages/manager.go | 525 ----------- internal/languages/types.go | 460 ---------- 6 files changed, 1410 insertions(+), 1421 deletions(-) delete mode 100644 internal/languages/constants.go delete mode 100644 internal/languages/interfaces.go create mode 100644 internal/languages/languages.go delete mode 100644 internal/languages/manager.go delete mode 100644 internal/languages/types.go diff --git a/SIMPLIFICATION.md b/SIMPLIFICATION.md index d7f6d34..eb28a9f 100644 --- a/SIMPLIFICATION.md +++ b/SIMPLIFICATION.md @@ -18,6 +18,7 @@ This document outlines how we successfully simplified the EverQuest II housing p - Heroic Ops - Items - Items/Loot +- Languages ## Before: Complex Architecture (8 Files, ~2000+ Lines) diff --git a/internal/languages/constants.go b/internal/languages/constants.go deleted file mode 100644 index 4feb720..0000000 --- a/internal/languages/constants.go +++ /dev/null @@ -1,39 +0,0 @@ -package languages - -// Language system constants -const ( - // Maximum language name length - MaxLanguageNameLength = 50 - - // Special language IDs (common in EQ2) - LanguageIDCommon = 0 // Common tongue (default) - LanguageIDElvish = 1 // Elvish - LanguageIDDwarven = 2 // Dwarven - LanguageIDHalfling = 3 // Halfling - LanguageIDGnomish = 4 // Gnomish - LanguageIDIksar = 5 // Iksar - LanguageIDTrollish = 6 // Trollish - LanguageIDOgrish = 7 // Ogrish - LanguageIDFae = 8 // Fae - LanguageIDArasai = 9 // Arasai - LanguageIDSarnak = 10 // Sarnak - LanguageIDFroglok = 11 // Froglok -) - -// Language validation constants -const ( - MinLanguageID = 0 - MaxLanguageID = 999999 // Reasonable upper bound -) - -// Database operation constants -const ( - SaveStatusUnchanged = false - SaveStatusNeeded = true -) - -// System limits -const ( - MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse - MaxTotalLanguages = 1000 // System-wide language limit -) diff --git a/internal/languages/interfaces.go b/internal/languages/interfaces.go deleted file mode 100644 index ff989c0..0000000 --- a/internal/languages/interfaces.go +++ /dev/null @@ -1,397 +0,0 @@ -package languages - -import "fmt" - -// Database interface for language persistence -type Database interface { - LoadAllLanguages() ([]*Language, error) - SaveLanguage(language *Language) error - DeleteLanguage(languageID int32) error - LoadPlayerLanguages(playerID int32) ([]*Language, error) - SavePlayerLanguage(playerID int32, languageID int32) error - DeletePlayerLanguage(playerID int32, languageID int32) error -} - -// Logger interface for language logging -type Logger interface { - LogInfo(message string, args ...any) - LogError(message string, args ...any) - LogDebug(message string, args ...any) - LogWarning(message string, args ...any) -} - -// Entity interface for language-related entity operations -// This interface should be implemented by Player, NPC, and Bot types -type Entity interface { - GetID() int32 - GetName() string - IsPlayer() bool - IsNPC() bool - IsBot() bool -} - -// Player interface for language-related player operations -// This interface should be implemented by Player types -type Player interface { - Entity - GetCharacterID() int32 - SendMessage(message string) -} - -// Client interface for language-related client operations -type Client interface { - GetVersion() int16 - SendLanguageUpdate(languageData []byte) error -} - -// LanguageProvider interface for systems that provide language functionality -type LanguageProvider interface { - GetMasterLanguagesList() *MasterLanguagesList - GetLanguage(languageID int32) *Language - GetLanguageByName(name string) *Language - CreatePlayerLanguagesList() *PlayerLanguagesList -} - -// LanguageAware interface for entities that can understand languages -// This interface should be implemented by players who have language capabilities -type LanguageAware interface { - GetKnownLanguages() *PlayerLanguagesList - KnowsLanguage(languageID int32) bool - GetPrimaryLanguage() int32 - SetPrimaryLanguage(languageID int32) - CanUnderstand(languageID int32) bool - LearnLanguage(languageID int32) error - ForgetLanguage(languageID int32) error -} - -// LanguageHandler interface for handling language events -type LanguageHandler interface { - OnLanguageLearned(player Player, languageID int32) error - OnLanguageForgotten(player Player, languageID int32) error - OnLanguageUsed(player Player, languageID int32, message string) error -} - -// ChatProcessor interface for processing multilingual chat -type ChatProcessor interface { - ProcessMessage(speaker Player, message string, languageID int32) (string, error) - FilterMessage(listener Player, message string, languageID int32) string - GetLanguageSkramble(message string, comprehension float32) string -} - -// PlayerLanguageAdapter provides language functionality for players -// This adapter can be embedded in player structs to provide language capabilities -type PlayerLanguageAdapter struct { - languages *PlayerLanguagesList - primaryLang int32 - manager *Manager - logger Logger -} - -// NewPlayerLanguageAdapter creates a new player language adapter -func NewPlayerLanguageAdapter(manager *Manager, logger Logger) *PlayerLanguageAdapter { - adapter := &PlayerLanguageAdapter{ - languages: manager.CreatePlayerLanguagesList(), - primaryLang: LanguageIDCommon, // Default to common - manager: manager, - logger: logger, - } - - // Ensure common language is always known - if commonLang := manager.GetLanguage(LanguageIDCommon); commonLang != nil { - adapter.languages.Add(commonLang.Copy()) - } - - return adapter -} - -// GetKnownLanguages returns the player's known languages -func (pla *PlayerLanguageAdapter) GetKnownLanguages() *PlayerLanguagesList { - return pla.languages -} - -// KnowsLanguage checks if the player knows a specific language -func (pla *PlayerLanguageAdapter) KnowsLanguage(languageID int32) bool { - return pla.languages.HasLanguage(languageID) -} - -// GetPrimaryLanguage returns the player's primary language -func (pla *PlayerLanguageAdapter) GetPrimaryLanguage() int32 { - return pla.primaryLang -} - -// SetPrimaryLanguage sets the player's primary language -func (pla *PlayerLanguageAdapter) SetPrimaryLanguage(languageID int32) { - // Only allow setting to a known language - if pla.languages.HasLanguage(languageID) { - pla.primaryLang = languageID - - if pla.logger != nil { - lang := pla.manager.GetLanguage(languageID) - langName := "Unknown" - if lang != nil { - langName = lang.GetName() - } - pla.logger.LogDebug("Player set primary language to %s (%d)", langName, languageID) - } - } -} - -// CanUnderstand checks if the player can understand a language -func (pla *PlayerLanguageAdapter) CanUnderstand(languageID int32) bool { - // Common language is always understood - if languageID == LanguageIDCommon { - return true - } - - // Check if player knows the language - return pla.languages.HasLanguage(languageID) -} - -// LearnLanguage teaches the player a new language -func (pla *PlayerLanguageAdapter) LearnLanguage(languageID int32) error { - // Get the language from master list - language := pla.manager.GetLanguage(languageID) - if language == nil { - return fmt.Errorf("language with ID %d does not exist", languageID) - } - - // Check if already known - if pla.languages.HasLanguage(languageID) { - return fmt.Errorf("player already knows language %s", language.GetName()) - } - - // Create a copy for the player - playerLang := language.Copy() - playerLang.SetSaveNeeded(true) - - // Add to player's languages - if err := pla.languages.Add(playerLang); err != nil { - return fmt.Errorf("failed to add language to player: %w", err) - } - - // Record usage statistics - pla.manager.RecordLanguageUsage(languageID) - - if pla.logger != nil { - pla.logger.LogInfo("Player learned language %s (%d)", language.GetName(), languageID) - } - - return nil -} - -// ForgetLanguage makes the player forget a language -func (pla *PlayerLanguageAdapter) ForgetLanguage(languageID int32) error { - // Cannot forget common language - if languageID == LanguageIDCommon { - return fmt.Errorf("cannot forget common language") - } - - // Check if player knows the language - if !pla.languages.HasLanguage(languageID) { - return fmt.Errorf("player does not know language %d", languageID) - } - - // Get language name for logging - language := pla.manager.GetLanguage(languageID) - langName := "Unknown" - if language != nil { - langName = language.GetName() - } - - // Remove from player's languages - if !pla.languages.RemoveLanguage(languageID) { - return fmt.Errorf("failed to remove language from player") - } - - // Reset primary language if this was it - if pla.primaryLang == languageID { - pla.primaryLang = LanguageIDCommon - } - - if pla.logger != nil { - pla.logger.LogInfo("Player forgot language %s (%d)", langName, languageID) - } - - return nil -} - -// LoadPlayerLanguages loads the player's languages from database -func (pla *PlayerLanguageAdapter) LoadPlayerLanguages(database Database, playerID int32) error { - if database == nil { - return fmt.Errorf("database is nil") - } - - languages, err := database.LoadPlayerLanguages(playerID) - if err != nil { - return fmt.Errorf("failed to load player languages: %w", err) - } - - // Clear current languages - pla.languages.Clear() - - // Add loaded languages - for _, lang := range languages { - if err := pla.languages.Add(lang); err != nil && pla.logger != nil { - pla.logger.LogWarning("Failed to add loaded language %d: %v", lang.GetID(), err) - } - } - - // Ensure player knows common language - if !pla.languages.HasLanguage(LanguageIDCommon) { - commonLang := pla.manager.GetLanguage(LanguageIDCommon) - if commonLang != nil { - playerCommon := commonLang.Copy() - pla.languages.Add(playerCommon) - } - } - - if pla.logger != nil { - pla.logger.LogDebug("Loaded %d languages for player", len(languages)) - } - - return nil -} - -// SavePlayerLanguages saves the player's languages to database -func (pla *PlayerLanguageAdapter) SavePlayerLanguages(database Database, playerID int32) error { - if database == nil { - return fmt.Errorf("database is nil") - } - - languages := pla.languages.GetAllLanguages() - - // Save each language that needs saving - for _, lang := range languages { - if lang.GetSaveNeeded() { - if err := database.SavePlayerLanguage(playerID, lang.GetID()); err != nil { - return fmt.Errorf("failed to save player language %d: %w", lang.GetID(), err) - } - lang.SetSaveNeeded(false) - } - } - - if pla.logger != nil { - pla.logger.LogDebug("Saved languages for player") - } - - return nil -} - -// ChatLanguageProcessor handles multilingual chat processing -type ChatLanguageProcessor struct { - manager *Manager - logger Logger -} - -// NewChatLanguageProcessor creates a new chat language processor -func NewChatLanguageProcessor(manager *Manager, logger Logger) *ChatLanguageProcessor { - return &ChatLanguageProcessor{ - manager: manager, - logger: logger, - } -} - -// ProcessMessage processes a chat message in a specific language -func (clp *ChatLanguageProcessor) ProcessMessage(speaker Player, message string, languageID int32) (string, error) { - if speaker == nil { - return "", fmt.Errorf("speaker cannot be nil") - } - - // Validate language exists - language := clp.manager.GetLanguage(languageID) - if language == nil { - return "", fmt.Errorf("language %d does not exist", languageID) - } - - // Record language usage (we can't check if speaker knows the language without extending the interface) - clp.manager.RecordLanguageUsage(languageID) - - return message, nil -} - -// FilterMessage filters a message for a listener based on language comprehension -func (clp *ChatLanguageProcessor) FilterMessage(listener Player, message string, languageID int32) string { - if listener == nil { - return message - } - - // Common language is always understood - if languageID == LanguageIDCommon { - return message - } - - // For now, we'll always return the message since we can't check language knowledge - // This would need integration with a PlayerLanguageAdapter or similar - return message -} - -// GetLanguageSkramble scrambles a message based on comprehension level -func (clp *ChatLanguageProcessor) GetLanguageSkramble(message string, comprehension float32) string { - if comprehension >= 1.0 { - return message - } - - if comprehension <= 0.0 { - // Complete scramble - replace with gibberish - runes := []rune(message) - scrambled := make([]rune, len(runes)) - - for i, r := range runes { - if r == ' ' { - scrambled[i] = ' ' - } else if r >= 'a' && r <= 'z' { - scrambled[i] = 'a' + rune((int(r-'a')+7)%26) - } else if r >= 'A' && r <= 'Z' { - scrambled[i] = 'A' + rune((int(r-'A')+7)%26) - } else { - scrambled[i] = r - } - } - - return string(scrambled) - } - - // Partial comprehension - scramble some words - // This is a simplified implementation - return message -} - -// LanguageEventAdapter handles language-related events -type LanguageEventAdapter struct { - handler LanguageHandler - logger Logger -} - -// NewLanguageEventAdapter creates a new language event adapter -func NewLanguageEventAdapter(handler LanguageHandler, logger Logger) *LanguageEventAdapter { - return &LanguageEventAdapter{ - handler: handler, - logger: logger, - } -} - -// ProcessLanguageEvent processes a language-related event -func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player Player, languageID int32, data any) { - if lea.handler == nil { - return - } - - switch eventType { - case "language_learned": - if err := lea.handler.OnLanguageLearned(player, languageID); err != nil && lea.logger != nil { - lea.logger.LogError("Language learned handler failed: %v", err) - } - - case "language_forgotten": - if err := lea.handler.OnLanguageForgotten(player, languageID); err != nil && lea.logger != nil { - lea.logger.LogError("Language forgotten handler failed: %v", err) - } - - case "language_used": - if message, ok := data.(string); ok { - if err := lea.handler.OnLanguageUsed(player, languageID, message); err != nil && lea.logger != nil { - lea.logger.LogError("Language used handler failed: %v", err) - } - } - } -} diff --git a/internal/languages/languages.go b/internal/languages/languages.go new file mode 100644 index 0000000..1500439 --- /dev/null +++ b/internal/languages/languages.go @@ -0,0 +1,1409 @@ +package languages + +import ( + "fmt" + "sync" +) + +// Simplified Language System +// Consolidates all functionality from 4 files into unified architecture +// Preserves 100% C++ EQ2 language functionality while eliminating Active Record patterns + +// Language system constants +const ( + // Maximum language name length + MaxLanguageNameLength = 50 + + // Special language IDs (common in EQ2) + LanguageIDCommon = 0 // Common tongue (default) + LanguageIDElvish = 1 // Elvish + LanguageIDDwarven = 2 // Dwarven + LanguageIDHalfling = 3 // Halfling + LanguageIDGnomish = 4 // Gnomish + LanguageIDIksar = 5 // Iksar + LanguageIDTrollish = 6 // Trollish + LanguageIDOgrish = 7 // Ogrish + LanguageIDFae = 8 // Fae + LanguageIDArasai = 9 // Arasai + LanguageIDSarnak = 10 // Sarnak + LanguageIDFroglok = 11 // Froglok +) + +// Language validation constants +const ( + MinLanguageID = 0 + MaxLanguageID = 999999 // Reasonable upper bound +) + +// Database operation constants +const ( + SaveStatusUnchanged = false + SaveStatusNeeded = true +) + +// System limits +const ( + MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse + MaxTotalLanguages = 1000 // System-wide language limit +) + +// Database interface for language persistence +type Database interface { + LoadAllLanguages() ([]*Language, error) + SaveLanguage(language *Language) error + DeleteLanguage(languageID int32) error + LoadPlayerLanguages(playerID int32) ([]*Language, error) + SavePlayerLanguage(playerID int32, languageID int32) error + DeletePlayerLanguage(playerID int32, languageID int32) error +} + +// Logger interface for language logging +type Logger interface { + LogInfo(message string, args ...any) + LogError(message string, args ...any) + LogDebug(message string, args ...any) + LogWarning(message string, args ...any) +} + +// Entity interface for language-related entity operations +type Entity interface { + GetID() int32 + GetName() string + IsPlayer() bool + IsNPC() bool + IsBot() bool +} + +// Player interface for language-related player operations +type Player interface { + Entity + GetCharacterID() int32 + SendMessage(message string) +} + +// Client interface for language-related client operations +type Client interface { + GetVersion() int16 + SendLanguageUpdate(languageData []byte) error +} + +// LanguageProvider interface for systems that provide language functionality +type LanguageProvider interface { + GetMasterLanguagesList() *MasterLanguagesList + GetLanguage(languageID int32) *Language + GetLanguageByName(name string) *Language + CreatePlayerLanguagesList() *PlayerLanguagesList +} + +// LanguageAware interface for entities that can understand languages +type LanguageAware interface { + GetKnownLanguages() *PlayerLanguagesList + KnowsLanguage(languageID int32) bool + GetPrimaryLanguage() int32 + SetPrimaryLanguage(languageID int32) + CanUnderstand(languageID int32) bool + LearnLanguage(languageID int32) error + ForgetLanguage(languageID int32) error +} + +// LanguageHandler interface for handling language events +type LanguageHandler interface { + OnLanguageLearned(player Player, languageID int32) error + OnLanguageForgotten(player Player, languageID int32) error + OnLanguageUsed(player Player, languageID int32, message string) error +} + +// ChatProcessor interface for processing multilingual chat +type ChatProcessor interface { + ProcessMessage(speaker Player, message string, languageID int32) (string, error) + FilterMessage(listener Player, message string, languageID int32) string + GetLanguageSkramble(message string, comprehension float32) string +} + +// Language represents a single language that can be learned by players +type Language struct { + id int32 // Unique language identifier + name string // Language name + saveNeeded bool // Whether this language needs to be saved to database + mutex sync.RWMutex // Thread safety +} + +// NewLanguage creates a new language with default values +func NewLanguage() *Language { + return &Language{ + id: 0, + name: "", + saveNeeded: false, + } +} + +// NewLanguageFromExisting creates a copy of an existing language +func NewLanguageFromExisting(source *Language) *Language { + if source == nil { + return NewLanguage() + } + + source.mutex.RLock() + defer source.mutex.RUnlock() + + return &Language{ + id: source.id, + name: source.name, + saveNeeded: source.saveNeeded, + } +} + +// GetID returns the language's unique identifier +func (l *Language) GetID() int32 { + l.mutex.RLock() + defer l.mutex.RUnlock() + + return l.id +} + +// SetID updates the language's unique identifier +func (l *Language) SetID(id int32) { + l.mutex.Lock() + defer l.mutex.Unlock() + + l.id = id +} + +// GetName returns the language name +func (l *Language) GetName() string { + l.mutex.RLock() + defer l.mutex.RUnlock() + + return l.name +} + +// SetName updates the language name +func (l *Language) SetName(name string) { + l.mutex.Lock() + defer l.mutex.Unlock() + + // Truncate if too long + if len(name) > MaxLanguageNameLength { + name = name[:MaxLanguageNameLength] + } + + l.name = name +} + +// GetSaveNeeded returns whether this language needs database saving +func (l *Language) GetSaveNeeded() bool { + l.mutex.RLock() + defer l.mutex.RUnlock() + + return l.saveNeeded +} + +// SetSaveNeeded updates the save status +func (l *Language) SetSaveNeeded(needed bool) { + l.mutex.Lock() + defer l.mutex.Unlock() + + l.saveNeeded = needed +} + +// IsValid validates the language data +func (l *Language) IsValid() bool { + l.mutex.RLock() + defer l.mutex.RUnlock() + + if l.id < MinLanguageID || l.id > MaxLanguageID { + return false + } + + if len(l.name) == 0 || len(l.name) > MaxLanguageNameLength { + return false + } + + return true +} + +// String returns a string representation of the language +func (l *Language) String() string { + l.mutex.RLock() + defer l.mutex.RUnlock() + + return fmt.Sprintf("Language{ID: %d, Name: %s, SaveNeeded: %v}", l.id, l.name, l.saveNeeded) +} + +// Copy creates a deep copy of the language +func (l *Language) Copy() *Language { + return NewLanguageFromExisting(l) +} + +// MasterLanguagesList manages the global list of all available languages +type MasterLanguagesList struct { + languages map[int32]*Language // Languages indexed by ID for fast lookup + nameIndex map[string]*Language // Languages indexed by name for name lookups + mutex sync.RWMutex // Thread safety +} + +// NewMasterLanguagesList creates a new master languages list +func NewMasterLanguagesList() *MasterLanguagesList { + return &MasterLanguagesList{ + languages: make(map[int32]*Language), + nameIndex: make(map[string]*Language), + } +} + +// Clear removes all languages from the list +func (mll *MasterLanguagesList) Clear() { + mll.mutex.Lock() + defer mll.mutex.Unlock() + + mll.languages = make(map[int32]*Language) + mll.nameIndex = make(map[string]*Language) +} + +// Size returns the number of languages in the list +func (mll *MasterLanguagesList) Size() int32 { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + return int32(len(mll.languages)) +} + +// AddLanguage adds a new language to the master list +func (mll *MasterLanguagesList) AddLanguage(language *Language) error { + if language == nil { + return fmt.Errorf("language cannot be nil") + } + + if !language.IsValid() { + return fmt.Errorf("language is not valid: %s", language.String()) + } + + mll.mutex.Lock() + defer mll.mutex.Unlock() + + // Check for duplicate ID + if _, exists := mll.languages[language.GetID()]; exists { + return fmt.Errorf("language with ID %d already exists", language.GetID()) + } + + // Check for duplicate name + name := language.GetName() + if _, exists := mll.nameIndex[name]; exists { + return fmt.Errorf("language with name '%s' already exists", name) + } + + // Add to both indexes + mll.languages[language.GetID()] = language + mll.nameIndex[name] = language + + return nil +} + +// GetLanguage retrieves a language by ID +func (mll *MasterLanguagesList) GetLanguage(id int32) *Language { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + return mll.languages[id] +} + +// GetLanguageByName retrieves a language by name +func (mll *MasterLanguagesList) GetLanguageByName(name string) *Language { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + return mll.nameIndex[name] +} + +// GetAllLanguages returns a copy of all languages +func (mll *MasterLanguagesList) GetAllLanguages() []*Language { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + result := make([]*Language, 0, len(mll.languages)) + for _, lang := range mll.languages { + result = append(result, lang) + } + + return result +} + +// HasLanguage checks if a language exists by ID +func (mll *MasterLanguagesList) HasLanguage(id int32) bool { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + _, exists := mll.languages[id] + return exists +} + +// HasLanguageByName checks if a language exists by name +func (mll *MasterLanguagesList) HasLanguageByName(name string) bool { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + _, exists := mll.nameIndex[name] + return exists +} + +// RemoveLanguage removes a language by ID +func (mll *MasterLanguagesList) RemoveLanguage(id int32) bool { + mll.mutex.Lock() + defer mll.mutex.Unlock() + + language, exists := mll.languages[id] + if !exists { + return false + } + + // Remove from both indexes + delete(mll.languages, id) + delete(mll.nameIndex, language.GetName()) + + return true +} + +// UpdateLanguage updates an existing language +func (mll *MasterLanguagesList) UpdateLanguage(language *Language) error { + if language == nil { + return fmt.Errorf("language cannot be nil") + } + + if !language.IsValid() { + return fmt.Errorf("language is not valid: %s", language.String()) + } + + mll.mutex.Lock() + defer mll.mutex.Unlock() + + id := language.GetID() + oldLanguage, exists := mll.languages[id] + if !exists { + return fmt.Errorf("language with ID %d does not exist", id) + } + + // Remove old name index if name changed + oldName := oldLanguage.GetName() + newName := language.GetName() + if oldName != newName { + delete(mll.nameIndex, oldName) + + // Check for name conflicts + if _, exists := mll.nameIndex[newName]; exists { + return fmt.Errorf("language with name '%s' already exists", newName) + } + + mll.nameIndex[newName] = language + } + + // Update language + mll.languages[id] = language + + return nil +} + +// GetLanguageNames returns all language names +func (mll *MasterLanguagesList) GetLanguageNames() []string { + mll.mutex.RLock() + defer mll.mutex.RUnlock() + + names := make([]string, 0, len(mll.nameIndex)) + for name := range mll.nameIndex { + names = append(names, name) + } + + return names +} + +// PlayerLanguagesList manages languages known by a specific player +type PlayerLanguagesList struct { + languages map[int32]*Language // Player's languages indexed by ID + nameIndex map[string]*Language // Player's languages indexed by name + mutex sync.RWMutex // Thread safety +} + +// NewPlayerLanguagesList creates a new player languages list +func NewPlayerLanguagesList() *PlayerLanguagesList { + return &PlayerLanguagesList{ + languages: make(map[int32]*Language), + nameIndex: make(map[string]*Language), + } +} + +// Clear removes all languages from the player's list +func (pll *PlayerLanguagesList) Clear() { + pll.mutex.Lock() + defer pll.mutex.Unlock() + + pll.languages = make(map[int32]*Language) + pll.nameIndex = make(map[string]*Language) +} + +// Add adds a language to the player's known languages +func (pll *PlayerLanguagesList) Add(language *Language) error { + if language == nil { + return fmt.Errorf("language cannot be nil") + } + + pll.mutex.Lock() + defer pll.mutex.Unlock() + + id := language.GetID() + name := language.GetName() + + // Check if already known + if _, exists := pll.languages[id]; exists { + return fmt.Errorf("player already knows language with ID %d", id) + } + + // Check player language limit + if len(pll.languages) >= MaxLanguagesPerPlayer { + return fmt.Errorf("player has reached maximum language limit (%d)", MaxLanguagesPerPlayer) + } + + // Add to both indexes + pll.languages[id] = language + pll.nameIndex[name] = language + + return nil +} + +// GetLanguage retrieves a language the player knows by ID +func (pll *PlayerLanguagesList) GetLanguage(id int32) *Language { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + return pll.languages[id] +} + +// GetLanguageByName retrieves a language the player knows by name +func (pll *PlayerLanguagesList) GetLanguageByName(name string) *Language { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + return pll.nameIndex[name] +} + +// GetAllLanguages returns a copy of all languages the player knows +func (pll *PlayerLanguagesList) GetAllLanguages() []*Language { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + result := make([]*Language, 0, len(pll.languages)) + for _, lang := range pll.languages { + result = append(result, lang) + } + + return result +} + +// HasLanguage checks if the player knows a language by ID +func (pll *PlayerLanguagesList) HasLanguage(id int32) bool { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + _, exists := pll.languages[id] + return exists +} + +// HasLanguageByName checks if the player knows a language by name +func (pll *PlayerLanguagesList) HasLanguageByName(name string) bool { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + _, exists := pll.nameIndex[name] + return exists +} + +// RemoveLanguage removes a language from the player's known languages +func (pll *PlayerLanguagesList) RemoveLanguage(id int32) bool { + pll.mutex.Lock() + defer pll.mutex.Unlock() + + language, exists := pll.languages[id] + if !exists { + return false + } + + // Remove from both indexes + delete(pll.languages, id) + delete(pll.nameIndex, language.GetName()) + + return true +} + +// Size returns the number of languages the player knows +func (pll *PlayerLanguagesList) Size() int32 { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + return int32(len(pll.languages)) +} + +// GetLanguageIDs returns all language IDs the player knows +func (pll *PlayerLanguagesList) GetLanguageIDs() []int32 { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + ids := make([]int32, 0, len(pll.languages)) + for id := range pll.languages { + ids = append(ids, id) + } + + return ids +} + +// GetLanguageNames returns all language names the player knows +func (pll *PlayerLanguagesList) GetLanguageNames() []string { + pll.mutex.RLock() + defer pll.mutex.RUnlock() + + names := make([]string, 0, len(pll.nameIndex)) + for name := range pll.nameIndex { + names = append(names, name) + } + + return names +} + +// LanguageStatistics contains language system statistics +type LanguageStatistics struct { + TotalLanguages int `json:"total_languages"` + PlayersWithLanguages int `json:"players_with_languages"` + LanguageUsageCount map[int32]int64 `json:"language_usage_count"` + LanguageLookups int64 `json:"language_lookups"` + LanguagesByName map[string]int32 `json:"languages_by_name"` +} + +// LanguageManager provides high-level management of the language system +type LanguageManager struct { + masterLanguagesList *MasterLanguagesList + database Database + logger Logger + languageLookups int64 + playersWithLanguages int64 + languageUsageCount map[int32]int64 // Language ID -> usage count + mutex sync.RWMutex +} + +// NewLanguageManager creates a new language manager +func NewLanguageManager(database Database, logger Logger) *LanguageManager { + return &LanguageManager{ + masterLanguagesList: NewMasterLanguagesList(), + database: database, + logger: logger, + languageUsageCount: make(map[int32]int64), + } +} + +// Initialize loads languages from database +func (lm *LanguageManager) Initialize() error { + if lm.logger != nil { + lm.logger.LogInfo("Initializing language manager...") + } + + if lm.database == nil { + if lm.logger != nil { + lm.logger.LogWarning("No database provided, starting with empty language list") + } + return nil + } + + // Load languages from database + languages, err := lm.database.LoadAllLanguages() + if err != nil { + return fmt.Errorf("failed to load languages from database: %w", err) + } + + for _, language := range languages { + if err := lm.masterLanguagesList.AddLanguage(language); err != nil { + if lm.logger != nil { + lm.logger.LogError("Failed to add language %d (%s): %v", language.GetID(), language.GetName(), err) + } + } + } + + if lm.logger != nil { + lm.logger.LogInfo("Loaded %d languages from database", len(languages)) + } + + return nil +} + +// GetMasterLanguagesList returns the master language list +func (lm *LanguageManager) GetMasterLanguagesList() *MasterLanguagesList { + return lm.masterLanguagesList +} + +// CreatePlayerLanguagesList creates a new player language list +func (lm *LanguageManager) CreatePlayerLanguagesList() *PlayerLanguagesList { + lm.mutex.Lock() + lm.playersWithLanguages++ + lm.mutex.Unlock() + + return NewPlayerLanguagesList() +} + +// GetLanguage returns a language by ID +func (lm *LanguageManager) GetLanguage(id int32) *Language { + lm.mutex.Lock() + lm.languageLookups++ + lm.mutex.Unlock() + + return lm.masterLanguagesList.GetLanguage(id) +} + +// GetLanguageByName returns a language by name +func (lm *LanguageManager) GetLanguageByName(name string) *Language { + lm.mutex.Lock() + lm.languageLookups++ + lm.mutex.Unlock() + + return lm.masterLanguagesList.GetLanguageByName(name) +} + +// AddLanguage adds a new language to the system +func (lm *LanguageManager) AddLanguage(language *Language) error { + if language == nil { + return fmt.Errorf("language cannot be nil") + } + + // Add to master list + if err := lm.masterLanguagesList.AddLanguage(language); err != nil { + return fmt.Errorf("failed to add language to master list: %w", err) + } + + // Save to database if available + if lm.database != nil { + if err := lm.database.SaveLanguage(language); err != nil { + // Remove from master list if database save failed + lm.masterLanguagesList.RemoveLanguage(language.GetID()) + return fmt.Errorf("failed to save language to database: %w", err) + } + } + + if lm.logger != nil { + lm.logger.LogInfo("Added language %d: %s", language.GetID(), language.GetName()) + } + + return nil +} + +// UpdateLanguage updates an existing language +func (lm *LanguageManager) UpdateLanguage(language *Language) error { + if language == nil { + return fmt.Errorf("language cannot be nil") + } + + // Update in master list + if err := lm.masterLanguagesList.UpdateLanguage(language); err != nil { + return fmt.Errorf("failed to update language in master list: %w", err) + } + + // Save to database if available + if lm.database != nil { + if err := lm.database.SaveLanguage(language); err != nil { + return fmt.Errorf("failed to save language to database: %w", err) + } + } + + if lm.logger != nil { + lm.logger.LogInfo("Updated language %d: %s", language.GetID(), language.GetName()) + } + + return nil +} + +// RemoveLanguage removes a language from the system +func (lm *LanguageManager) RemoveLanguage(id int32) error { + // Check if language exists + if !lm.masterLanguagesList.HasLanguage(id) { + return fmt.Errorf("language with ID %d does not exist", id) + } + + // Remove from database first if available + if lm.database != nil { + if err := lm.database.DeleteLanguage(id); err != nil { + return fmt.Errorf("failed to delete language from database: %w", err) + } + } + + // Remove from master list + if !lm.masterLanguagesList.RemoveLanguage(id) { + return fmt.Errorf("failed to remove language from master list") + } + + if lm.logger != nil { + lm.logger.LogInfo("Removed language %d", id) + } + + return nil +} + +// RecordLanguageUsage records language usage for statistics +func (lm *LanguageManager) RecordLanguageUsage(languageID int32) { + lm.mutex.Lock() + defer lm.mutex.Unlock() + + lm.languageUsageCount[languageID]++ +} + +// GetStatistics returns language system statistics +func (lm *LanguageManager) GetStatistics() *LanguageStatistics { + lm.mutex.RLock() + defer lm.mutex.RUnlock() + + // Create language name mapping + languagesByName := make(map[string]int32) + allLanguages := lm.masterLanguagesList.GetAllLanguages() + for _, lang := range allLanguages { + languagesByName[lang.GetName()] = lang.GetID() + } + + // Copy usage count + usageCount := make(map[int32]int64) + for id, count := range lm.languageUsageCount { + usageCount[id] = count + } + + return &LanguageStatistics{ + TotalLanguages: len(allLanguages), + PlayersWithLanguages: int(lm.playersWithLanguages), + LanguageUsageCount: usageCount, + LanguageLookups: lm.languageLookups, + LanguagesByName: languagesByName, + } +} + +// ResetStatistics resets all statistics +func (lm *LanguageManager) ResetStatistics() { + lm.mutex.Lock() + defer lm.mutex.Unlock() + + lm.languageLookups = 0 + lm.playersWithLanguages = 0 + lm.languageUsageCount = make(map[int32]int64) +} + +// ValidateAllLanguages validates all languages in the system +func (lm *LanguageManager) ValidateAllLanguages() []string { + allLanguages := lm.masterLanguagesList.GetAllLanguages() + var issues []string + + for _, lang := range allLanguages { + if !lang.IsValid() { + issues = append(issues, fmt.Sprintf("Language %d (%s) is invalid", lang.GetID(), lang.GetName())) + } + } + + return issues +} + +// ReloadFromDatabase reloads all languages from database +func (lm *LanguageManager) ReloadFromDatabase() error { + if lm.database == nil { + return fmt.Errorf("no database available") + } + + // Clear current languages + lm.masterLanguagesList.Clear() + + // Reload from database + return lm.Initialize() +} + +// GetLanguageCount returns the total number of languages +func (lm *LanguageManager) GetLanguageCount() int32 { + return lm.masterLanguagesList.Size() +} + +// ProcessCommand handles language-related commands +func (lm *LanguageManager) ProcessCommand(command string, args []string) (string, error) { + switch command { + case "stats": + return lm.handleStatsCommand(args) + case "validate": + return lm.handleValidateCommand(args) + case "list": + return lm.handleListCommand(args) + case "info": + return lm.handleInfoCommand(args) + case "reload": + return lm.handleReloadCommand(args) + case "add": + return lm.handleAddCommand(args) + case "remove": + return lm.handleRemoveCommand(args) + case "search": + return lm.handleSearchCommand(args) + default: + return "", fmt.Errorf("unknown language command: %s", command) + } +} + +// handleStatsCommand shows language system statistics +func (lm *LanguageManager) handleStatsCommand(args []string) (string, error) { + stats := lm.GetStatistics() + + result := "Language System Statistics:\n" + result += fmt.Sprintf("Total Languages: %d\n", stats.TotalLanguages) + result += fmt.Sprintf("Players with Languages: %d\n", stats.PlayersWithLanguages) + result += fmt.Sprintf("Language Lookups: %d\n", stats.LanguageLookups) + + if len(stats.LanguageUsageCount) > 0 { + result += "\nMost Used Languages:\n" + // Show top 5 most used languages + count := 0 + for langID, usage := range stats.LanguageUsageCount { + if count >= 5 { + break + } + lang := lm.GetLanguage(langID) + name := "Unknown" + if lang != nil { + name = lang.GetName() + } + result += fmt.Sprintf(" %s (ID: %d): %d uses\n", name, langID, usage) + count++ + } + } + + return result, nil +} + +// handleValidateCommand validates all languages +func (lm *LanguageManager) handleValidateCommand(args []string) (string, error) { + issues := lm.ValidateAllLanguages() + + if len(issues) == 0 { + return "All languages are valid.", nil + } + + result := fmt.Sprintf("Found %d issues with languages:\n", len(issues)) + for i, issue := range issues { + if i >= 10 { // Limit output + result += "... (and more)\n" + break + } + result += fmt.Sprintf("%d. %s\n", i+1, issue) + } + + return result, nil +} + +// handleListCommand lists languages +func (lm *LanguageManager) handleListCommand(args []string) (string, error) { + languages := lm.masterLanguagesList.GetAllLanguages() + + if len(languages) == 0 { + return "No languages loaded.", nil + } + + result := fmt.Sprintf("Languages (%d):\n", len(languages)) + count := 0 + for _, language := range languages { + if count >= 20 { // Limit output + result += "... (and more)\n" + break + } + result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName()) + count++ + } + + return result, nil +} + +// handleInfoCommand shows information about a specific language +func (lm *LanguageManager) handleInfoCommand(args []string) (string, error) { + if len(args) == 0 { + return "", fmt.Errorf("language ID or name required") + } + + var language *Language + + // Try to parse as ID first + var languageID int32 + if _, err := fmt.Sscanf(args[0], "%d", &languageID); err == nil { + language = lm.GetLanguage(languageID) + } else { + // Try as name + language = lm.GetLanguageByName(args[0]) + } + + if language == nil { + return fmt.Sprintf("Language '%s' not found.", args[0]), nil + } + + result := "Language Information:\n" + result += fmt.Sprintf("ID: %d\n", language.GetID()) + result += fmt.Sprintf("Name: %s\n", language.GetName()) + result += fmt.Sprintf("Save Needed: %v\n", language.GetSaveNeeded()) + + // Show usage statistics if available + lm.mutex.RLock() + if usage, exists := lm.languageUsageCount[language.GetID()]; exists { + result += fmt.Sprintf("Usage Count: %d\n", usage) + } else { + result += "Usage Count: 0\n" + } + lm.mutex.RUnlock() + + return result, nil +} + +// handleReloadCommand reloads languages from database +func (lm *LanguageManager) handleReloadCommand(args []string) (string, error) { + if err := lm.ReloadFromDatabase(); err != nil { + return "", fmt.Errorf("failed to reload languages: %w", err) + } + + count := lm.GetLanguageCount() + return fmt.Sprintf("Successfully reloaded %d languages from database.", count), nil +} + +// handleAddCommand adds a new language +func (lm *LanguageManager) handleAddCommand(args []string) (string, error) { + if len(args) < 2 { + return "", fmt.Errorf("usage: add ") + } + + var id int32 + if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil { + return "", fmt.Errorf("invalid language ID: %s", args[0]) + } + + name := args[1] + + language := NewLanguage() + language.SetID(id) + language.SetName(name) + language.SetSaveNeeded(true) + + if err := lm.AddLanguage(language); err != nil { + return "", fmt.Errorf("failed to add language: %w", err) + } + + return fmt.Sprintf("Successfully added language %d: %s", id, name), nil +} + +// handleRemoveCommand removes a language +func (lm *LanguageManager) handleRemoveCommand(args []string) (string, error) { + if len(args) == 0 { + return "", fmt.Errorf("language ID required") + } + + var id int32 + if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil { + return "", fmt.Errorf("invalid language ID: %s", args[0]) + } + + if err := lm.RemoveLanguage(id); err != nil { + return "", fmt.Errorf("failed to remove language: %w", err) + } + + return fmt.Sprintf("Successfully removed language %d", id), nil +} + +// handleSearchCommand searches for languages by name +func (lm *LanguageManager) handleSearchCommand(args []string) (string, error) { + if len(args) == 0 { + return "", fmt.Errorf("search term required") + } + + searchTerm := args[0] + languages := lm.masterLanguagesList.GetAllLanguages() + var results []*Language + + // Search by name (case-insensitive partial match) + for _, language := range languages { + if lm.contains(language.GetName(), searchTerm) { + results = append(results, language) + } + } + + if len(results) == 0 { + return fmt.Sprintf("No languages found matching '%s'.", searchTerm), nil + } + + result := fmt.Sprintf("Found %d languages matching '%s':\n", len(results), searchTerm) + for i, language := range results { + if i >= 20 { // Limit output + result += "... (and more)\n" + break + } + result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName()) + } + + return result, nil +} + +// contains checks if a string contains a substring (case-insensitive) +func (lm *LanguageManager) contains(str, substr string) bool { + if len(substr) == 0 { + return true + } + if len(str) < len(substr) { + return false + } + + // Convert to lowercase for case-insensitive comparison + strLower := make([]byte, len(str)) + substrLower := make([]byte, len(substr)) + + for i := 0; i < len(str); i++ { + if str[i] >= 'A' && str[i] <= 'Z' { + strLower[i] = str[i] + 32 + } else { + strLower[i] = str[i] + } + } + + for i := 0; i < len(substr); i++ { + if substr[i] >= 'A' && substr[i] <= 'Z' { + substrLower[i] = substr[i] + 32 + } else { + substrLower[i] = substr[i] + } + } + + for i := 0; i <= len(strLower)-len(substrLower); i++ { + match := true + for j := 0; j < len(substrLower); j++ { + if strLower[i+j] != substrLower[j] { + match = false + break + } + } + if match { + return true + } + } + + return false +} + +// Shutdown gracefully shuts down the manager +func (lm *LanguageManager) Shutdown() { + if lm.logger != nil { + lm.logger.LogInfo("Shutting down language manager...") + } + + // Clear languages + lm.masterLanguagesList.Clear() +} + +// PlayerLanguageAdapter provides language functionality for players +type PlayerLanguageAdapter struct { + languages *PlayerLanguagesList + primaryLang int32 + manager *LanguageManager + logger Logger +} + +// NewPlayerLanguageAdapter creates a new player language adapter +func NewPlayerLanguageAdapter(manager *LanguageManager, logger Logger) *PlayerLanguageAdapter { + adapter := &PlayerLanguageAdapter{ + languages: manager.CreatePlayerLanguagesList(), + primaryLang: LanguageIDCommon, // Default to common + manager: manager, + logger: logger, + } + + // Ensure common language is always known + if commonLang := manager.GetLanguage(LanguageIDCommon); commonLang != nil { + adapter.languages.Add(commonLang.Copy()) + } + + return adapter +} + +// GetKnownLanguages returns the player's known languages +func (pla *PlayerLanguageAdapter) GetKnownLanguages() *PlayerLanguagesList { + return pla.languages +} + +// KnowsLanguage checks if the player knows a specific language +func (pla *PlayerLanguageAdapter) KnowsLanguage(languageID int32) bool { + return pla.languages.HasLanguage(languageID) +} + +// GetPrimaryLanguage returns the player's primary language +func (pla *PlayerLanguageAdapter) GetPrimaryLanguage() int32 { + return pla.primaryLang +} + +// SetPrimaryLanguage sets the player's primary language +func (pla *PlayerLanguageAdapter) SetPrimaryLanguage(languageID int32) { + // Only allow setting to a known language + if pla.languages.HasLanguage(languageID) { + pla.primaryLang = languageID + + if pla.logger != nil { + lang := pla.manager.GetLanguage(languageID) + langName := "Unknown" + if lang != nil { + langName = lang.GetName() + } + pla.logger.LogDebug("Player set primary language to %s (%d)", langName, languageID) + } + } +} + +// CanUnderstand checks if the player can understand a language +func (pla *PlayerLanguageAdapter) CanUnderstand(languageID int32) bool { + // Common language is always understood + if languageID == LanguageIDCommon { + return true + } + + // Check if player knows the language + return pla.languages.HasLanguage(languageID) +} + +// LearnLanguage teaches the player a new language +func (pla *PlayerLanguageAdapter) LearnLanguage(languageID int32) error { + // Get the language from master list + language := pla.manager.GetLanguage(languageID) + if language == nil { + return fmt.Errorf("language with ID %d does not exist", languageID) + } + + // Check if already known + if pla.languages.HasLanguage(languageID) { + return fmt.Errorf("player already knows language %s", language.GetName()) + } + + // Create a copy for the player + playerLang := language.Copy() + playerLang.SetSaveNeeded(true) + + // Add to player's languages + if err := pla.languages.Add(playerLang); err != nil { + return fmt.Errorf("failed to add language to player: %w", err) + } + + // Record usage statistics + pla.manager.RecordLanguageUsage(languageID) + + if pla.logger != nil { + pla.logger.LogInfo("Player learned language %s (%d)", language.GetName(), languageID) + } + + return nil +} + +// ForgetLanguage makes the player forget a language +func (pla *PlayerLanguageAdapter) ForgetLanguage(languageID int32) error { + // Cannot forget common language + if languageID == LanguageIDCommon { + return fmt.Errorf("cannot forget common language") + } + + // Check if player knows the language + if !pla.languages.HasLanguage(languageID) { + return fmt.Errorf("player does not know language %d", languageID) + } + + // Get language name for logging + language := pla.manager.GetLanguage(languageID) + langName := "Unknown" + if language != nil { + langName = language.GetName() + } + + // Remove from player's languages + if !pla.languages.RemoveLanguage(languageID) { + return fmt.Errorf("failed to remove language from player") + } + + // Reset primary language if this was it + if pla.primaryLang == languageID { + pla.primaryLang = LanguageIDCommon + } + + if pla.logger != nil { + pla.logger.LogInfo("Player forgot language %s (%d)", langName, languageID) + } + + return nil +} + +// LoadPlayerLanguages loads the player's languages from database +func (pla *PlayerLanguageAdapter) LoadPlayerLanguages(database Database, playerID int32) error { + if database == nil { + return fmt.Errorf("database is nil") + } + + languages, err := database.LoadPlayerLanguages(playerID) + if err != nil { + return fmt.Errorf("failed to load player languages: %w", err) + } + + // Clear current languages + pla.languages.Clear() + + // Add loaded languages + for _, lang := range languages { + if err := pla.languages.Add(lang); err != nil && pla.logger != nil { + pla.logger.LogWarning("Failed to add loaded language %d: %v", lang.GetID(), err) + } + } + + // Ensure player knows common language + if !pla.languages.HasLanguage(LanguageIDCommon) { + commonLang := pla.manager.GetLanguage(LanguageIDCommon) + if commonLang != nil { + playerCommon := commonLang.Copy() + pla.languages.Add(playerCommon) + } + } + + if pla.logger != nil { + pla.logger.LogDebug("Loaded %d languages for player", len(languages)) + } + + return nil +} + +// SavePlayerLanguages saves the player's languages to database +func (pla *PlayerLanguageAdapter) SavePlayerLanguages(database Database, playerID int32) error { + if database == nil { + return fmt.Errorf("database is nil") + } + + languages := pla.languages.GetAllLanguages() + + // Save each language that needs saving + for _, lang := range languages { + if lang.GetSaveNeeded() { + if err := database.SavePlayerLanguage(playerID, lang.GetID()); err != nil { + return fmt.Errorf("failed to save player language %d: %w", lang.GetID(), err) + } + lang.SetSaveNeeded(false) + } + } + + if pla.logger != nil { + pla.logger.LogDebug("Saved languages for player") + } + + return nil +} + +// ChatLanguageProcessor handles multilingual chat processing +type ChatLanguageProcessor struct { + manager *LanguageManager + logger Logger +} + +// NewChatLanguageProcessor creates a new chat language processor +func NewChatLanguageProcessor(manager *LanguageManager, logger Logger) *ChatLanguageProcessor { + return &ChatLanguageProcessor{ + manager: manager, + logger: logger, + } +} + +// ProcessMessage processes a chat message in a specific language +func (clp *ChatLanguageProcessor) ProcessMessage(speaker Player, message string, languageID int32) (string, error) { + if speaker == nil { + return "", fmt.Errorf("speaker cannot be nil") + } + + // Validate language exists + language := clp.manager.GetLanguage(languageID) + if language == nil { + return "", fmt.Errorf("language %d does not exist", languageID) + } + + // Record language usage + clp.manager.RecordLanguageUsage(languageID) + + return message, nil +} + +// FilterMessage filters a message for a listener based on language comprehension +func (clp *ChatLanguageProcessor) FilterMessage(listener Player, message string, languageID int32) string { + if listener == nil { + return message + } + + // Common language is always understood + if languageID == LanguageIDCommon { + return message + } + + // For now, we'll always return the message since we can't check language knowledge + // This would need integration with a PlayerLanguageAdapter or similar + return message +} + +// GetLanguageSkramble scrambles a message based on comprehension level +func (clp *ChatLanguageProcessor) GetLanguageSkramble(message string, comprehension float32) string { + if comprehension >= 1.0 { + return message + } + + if comprehension <= 0.0 { + // Complete scramble - replace with gibberish + runes := []rune(message) + scrambled := make([]rune, len(runes)) + + for i, r := range runes { + if r == ' ' { + scrambled[i] = ' ' + } else if r >= 'a' && r <= 'z' { + scrambled[i] = 'a' + rune((int(r-'a')+7)%26) + } else if r >= 'A' && r <= 'Z' { + scrambled[i] = 'A' + rune((int(r-'A')+7)%26) + } else { + scrambled[i] = r + } + } + + return string(scrambled) + } + + // Partial comprehension - scramble some words + // This is a simplified implementation + return message +} + +// LanguageEventAdapter handles language-related events +type LanguageEventAdapter struct { + handler LanguageHandler + logger Logger +} + +// NewLanguageEventAdapter creates a new language event adapter +func NewLanguageEventAdapter(handler LanguageHandler, logger Logger) *LanguageEventAdapter { + return &LanguageEventAdapter{ + handler: handler, + logger: logger, + } +} + +// ProcessLanguageEvent processes a language-related event +func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player Player, languageID int32, data any) { + if lea.handler == nil { + return + } + + switch eventType { + case "language_learned": + if err := lea.handler.OnLanguageLearned(player, languageID); err != nil && lea.logger != nil { + lea.logger.LogError("Language learned handler failed: %v", err) + } + + case "language_forgotten": + if err := lea.handler.OnLanguageForgotten(player, languageID); err != nil && lea.logger != nil { + lea.logger.LogError("Language forgotten handler failed: %v", err) + } + + case "language_used": + if message, ok := data.(string); ok { + if err := lea.handler.OnLanguageUsed(player, languageID, message); err != nil && lea.logger != nil { + lea.logger.LogError("Language used handler failed: %v", err) + } + } + } +} \ No newline at end of file diff --git a/internal/languages/manager.go b/internal/languages/manager.go deleted file mode 100644 index 4277056..0000000 --- a/internal/languages/manager.go +++ /dev/null @@ -1,525 +0,0 @@ -package languages - -import ( - "fmt" - "sync" -) - -// Manager provides high-level management of the language system -type Manager struct { - masterLanguagesList *MasterLanguagesList - database Database - logger Logger - mutex sync.RWMutex - - // Statistics - languageLookups int64 - playersWithLanguages int64 - languageUsageCount map[int32]int64 // Language ID -> usage count -} - -// NewManager creates a new language manager -func NewManager(database Database, logger Logger) *Manager { - return &Manager{ - masterLanguagesList: NewMasterLanguagesList(), - database: database, - logger: logger, - languageUsageCount: make(map[int32]int64), - } -} - -// Initialize loads languages from database -func (m *Manager) Initialize() error { - if m.logger != nil { - m.logger.LogInfo("Initializing language manager...") - } - - if m.database == nil { - if m.logger != nil { - m.logger.LogWarning("No database provided, starting with empty language list") - } - return nil - } - - // Load languages from database - languages, err := m.database.LoadAllLanguages() - if err != nil { - return fmt.Errorf("failed to load languages from database: %w", err) - } - - for _, language := range languages { - if err := m.masterLanguagesList.AddLanguage(language); err != nil { - if m.logger != nil { - m.logger.LogError("Failed to add language %d (%s): %v", language.GetID(), language.GetName(), err) - } - } - } - - if m.logger != nil { - m.logger.LogInfo("Loaded %d languages from database", len(languages)) - } - - return nil -} - -// GetMasterLanguagesList returns the master language list -func (m *Manager) GetMasterLanguagesList() *MasterLanguagesList { - return m.masterLanguagesList -} - -// CreatePlayerLanguagesList creates a new player language list -func (m *Manager) CreatePlayerLanguagesList() *PlayerLanguagesList { - m.mutex.Lock() - m.playersWithLanguages++ - m.mutex.Unlock() - - return NewPlayerLanguagesList() -} - -// GetLanguage returns a language by ID -func (m *Manager) GetLanguage(id int32) *Language { - m.mutex.Lock() - m.languageLookups++ - m.mutex.Unlock() - - return m.masterLanguagesList.GetLanguage(id) -} - -// GetLanguageByName returns a language by name -func (m *Manager) GetLanguageByName(name string) *Language { - m.mutex.Lock() - m.languageLookups++ - m.mutex.Unlock() - - return m.masterLanguagesList.GetLanguageByName(name) -} - -// AddLanguage adds a new language to the system -func (m *Manager) AddLanguage(language *Language) error { - if language == nil { - return fmt.Errorf("language cannot be nil") - } - - // Add to master list - if err := m.masterLanguagesList.AddLanguage(language); err != nil { - return fmt.Errorf("failed to add language to master list: %w", err) - } - - // Save to database if available - if m.database != nil { - if err := m.database.SaveLanguage(language); err != nil { - // Remove from master list if database save failed - m.masterLanguagesList.RemoveLanguage(language.GetID()) - return fmt.Errorf("failed to save language to database: %w", err) - } - } - - if m.logger != nil { - m.logger.LogInfo("Added language %d: %s", language.GetID(), language.GetName()) - } - - return nil -} - -// UpdateLanguage updates an existing language -func (m *Manager) UpdateLanguage(language *Language) error { - if language == nil { - return fmt.Errorf("language cannot be nil") - } - - // Update in master list - if err := m.masterLanguagesList.UpdateLanguage(language); err != nil { - return fmt.Errorf("failed to update language in master list: %w", err) - } - - // Save to database if available - if m.database != nil { - if err := m.database.SaveLanguage(language); err != nil { - return fmt.Errorf("failed to save language to database: %w", err) - } - } - - if m.logger != nil { - m.logger.LogInfo("Updated language %d: %s", language.GetID(), language.GetName()) - } - - return nil -} - -// RemoveLanguage removes a language from the system -func (m *Manager) RemoveLanguage(id int32) error { - // Check if language exists - if !m.masterLanguagesList.HasLanguage(id) { - return fmt.Errorf("language with ID %d does not exist", id) - } - - // Remove from database first if available - if m.database != nil { - if err := m.database.DeleteLanguage(id); err != nil { - return fmt.Errorf("failed to delete language from database: %w", err) - } - } - - // Remove from master list - if !m.masterLanguagesList.RemoveLanguage(id) { - return fmt.Errorf("failed to remove language from master list") - } - - if m.logger != nil { - m.logger.LogInfo("Removed language %d", id) - } - - return nil -} - -// RecordLanguageUsage records language usage for statistics -func (m *Manager) RecordLanguageUsage(languageID int32) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.languageUsageCount[languageID]++ -} - -// GetStatistics returns language system statistics -func (m *Manager) GetStatistics() *LanguageStatistics { - m.mutex.RLock() - defer m.mutex.RUnlock() - - // Create language name mapping - languagesByName := make(map[string]int32) - allLanguages := m.masterLanguagesList.GetAllLanguages() - for _, lang := range allLanguages { - languagesByName[lang.GetName()] = lang.GetID() - } - - // Copy usage count - usageCount := make(map[int32]int64) - for id, count := range m.languageUsageCount { - usageCount[id] = count - } - - return &LanguageStatistics{ - TotalLanguages: len(allLanguages), - PlayersWithLanguages: int(m.playersWithLanguages), - LanguageUsageCount: usageCount, - LanguageLookups: m.languageLookups, - LanguagesByName: languagesByName, - } -} - -// ResetStatistics resets all statistics -func (m *Manager) ResetStatistics() { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.languageLookups = 0 - m.playersWithLanguages = 0 - m.languageUsageCount = make(map[int32]int64) -} - -// ValidateAllLanguages validates all languages in the system -func (m *Manager) ValidateAllLanguages() []string { - allLanguages := m.masterLanguagesList.GetAllLanguages() - var issues []string - - for _, lang := range allLanguages { - if !lang.IsValid() { - issues = append(issues, fmt.Sprintf("Language %d (%s) is invalid", lang.GetID(), lang.GetName())) - } - } - - return issues -} - -// ReloadFromDatabase reloads all languages from database -func (m *Manager) ReloadFromDatabase() error { - if m.database == nil { - return fmt.Errorf("no database available") - } - - // Clear current languages - m.masterLanguagesList.Clear() - - // Reload from database - return m.Initialize() -} - -// GetLanguageCount returns the total number of languages -func (m *Manager) GetLanguageCount() int32 { - return m.masterLanguagesList.Size() -} - -// ProcessCommand handles language-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) - case "reload": - return m.handleReloadCommand(args) - case "add": - return m.handleAddCommand(args) - case "remove": - return m.handleRemoveCommand(args) - case "search": - return m.handleSearchCommand(args) - default: - return "", fmt.Errorf("unknown language command: %s", command) - } -} - -// handleStatsCommand shows language system statistics -func (m *Manager) handleStatsCommand(args []string) (string, error) { - stats := m.GetStatistics() - - result := "Language System Statistics:\n" - result += fmt.Sprintf("Total Languages: %d\n", stats.TotalLanguages) - result += fmt.Sprintf("Players with Languages: %d\n", stats.PlayersWithLanguages) - result += fmt.Sprintf("Language Lookups: %d\n", stats.LanguageLookups) - - if len(stats.LanguageUsageCount) > 0 { - result += "\nMost Used Languages:\n" - // Show top 5 most used languages - count := 0 - for langID, usage := range stats.LanguageUsageCount { - if count >= 5 { - break - } - lang := m.GetLanguage(langID) - name := "Unknown" - if lang != nil { - name = lang.GetName() - } - result += fmt.Sprintf(" %s (ID: %d): %d uses\n", name, langID, usage) - count++ - } - } - - return result, nil -} - -// handleValidateCommand validates all languages -func (m *Manager) handleValidateCommand(args []string) (string, error) { - issues := m.ValidateAllLanguages() - - if len(issues) == 0 { - return "All languages are valid.", nil - } - - result := fmt.Sprintf("Found %d issues with languages:\n", len(issues)) - for i, issue := range issues { - if i >= 10 { // Limit output - result += "... (and more)\n" - break - } - result += fmt.Sprintf("%d. %s\n", i+1, issue) - } - - return result, nil -} - -// handleListCommand lists languages -func (m *Manager) handleListCommand(args []string) (string, error) { - languages := m.masterLanguagesList.GetAllLanguages() - - if len(languages) == 0 { - return "No languages loaded.", nil - } - - result := fmt.Sprintf("Languages (%d):\n", len(languages)) - count := 0 - for _, language := range languages { - if count >= 20 { // Limit output - result += "... (and more)\n" - break - } - result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName()) - count++ - } - - return result, nil -} - -// handleInfoCommand shows information about a specific language -func (m *Manager) handleInfoCommand(args []string) (string, error) { - if len(args) == 0 { - return "", fmt.Errorf("language ID or name required") - } - - var language *Language - - // Try to parse as ID first - var languageID int32 - if _, err := fmt.Sscanf(args[0], "%d", &languageID); err == nil { - language = m.GetLanguage(languageID) - } else { - // Try as name - language = m.GetLanguageByName(args[0]) - } - - if language == nil { - return fmt.Sprintf("Language '%s' not found.", args[0]), nil - } - - result := "Language Information:\n" - result += fmt.Sprintf("ID: %d\n", language.GetID()) - result += fmt.Sprintf("Name: %s\n", language.GetName()) - result += fmt.Sprintf("Save Needed: %v\n", language.GetSaveNeeded()) - - // Show usage statistics if available - m.mutex.RLock() - if usage, exists := m.languageUsageCount[language.GetID()]; exists { - result += fmt.Sprintf("Usage Count: %d\n", usage) - } else { - result += "Usage Count: 0\n" - } - m.mutex.RUnlock() - - return result, nil -} - -// handleReloadCommand reloads languages from database -func (m *Manager) handleReloadCommand(args []string) (string, error) { - if err := m.ReloadFromDatabase(); err != nil { - return "", fmt.Errorf("failed to reload languages: %w", err) - } - - count := m.GetLanguageCount() - return fmt.Sprintf("Successfully reloaded %d languages from database.", count), nil -} - -// handleAddCommand adds a new language -func (m *Manager) handleAddCommand(args []string) (string, error) { - if len(args) < 2 { - return "", fmt.Errorf("usage: add ") - } - - var id int32 - if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil { - return "", fmt.Errorf("invalid language ID: %s", args[0]) - } - - name := args[1] - - language := NewLanguage() - language.SetID(id) - language.SetName(name) - language.SetSaveNeeded(true) - - if err := m.AddLanguage(language); err != nil { - return "", fmt.Errorf("failed to add language: %w", err) - } - - return fmt.Sprintf("Successfully added language %d: %s", id, name), nil -} - -// handleRemoveCommand removes a language -func (m *Manager) handleRemoveCommand(args []string) (string, error) { - if len(args) == 0 { - return "", fmt.Errorf("language ID required") - } - - var id int32 - if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil { - return "", fmt.Errorf("invalid language ID: %s", args[0]) - } - - if err := m.RemoveLanguage(id); err != nil { - return "", fmt.Errorf("failed to remove language: %w", err) - } - - return fmt.Sprintf("Successfully removed language %d", id), nil -} - -// handleSearchCommand searches for languages by name -func (m *Manager) handleSearchCommand(args []string) (string, error) { - if len(args) == 0 { - return "", fmt.Errorf("search term required") - } - - searchTerm := args[0] - languages := m.masterLanguagesList.GetAllLanguages() - var results []*Language - - // Search by name (case-insensitive partial match) - for _, language := range languages { - if contains(language.GetName(), searchTerm) { - results = append(results, language) - } - } - - if len(results) == 0 { - return fmt.Sprintf("No languages found matching '%s'.", searchTerm), nil - } - - result := fmt.Sprintf("Found %d languages matching '%s':\n", len(results), searchTerm) - for i, language := range results { - if i >= 20 { // Limit output - result += "... (and more)\n" - break - } - result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName()) - } - - return result, nil -} - -// Shutdown gracefully shuts down the manager -func (m *Manager) Shutdown() { - if m.logger != nil { - m.logger.LogInfo("Shutting down language manager...") - } - - // Clear languages - m.masterLanguagesList.Clear() -} - -// contains checks if a string contains a substring (case-insensitive) -func contains(str, substr string) bool { - if len(substr) == 0 { - return true - } - if len(str) < len(substr) { - return false - } - - // Convert to lowercase for case-insensitive comparison - strLower := make([]byte, len(str)) - substrLower := make([]byte, len(substr)) - - for i := 0; i < len(str); i++ { - if str[i] >= 'A' && str[i] <= 'Z' { - strLower[i] = str[i] + 32 - } else { - strLower[i] = str[i] - } - } - - for i := 0; i < len(substr); i++ { - if substr[i] >= 'A' && substr[i] <= 'Z' { - substrLower[i] = substr[i] + 32 - } else { - substrLower[i] = substr[i] - } - } - - for i := 0; i <= len(strLower)-len(substrLower); i++ { - match := true - for j := 0; j < len(substrLower); j++ { - if strLower[i+j] != substrLower[j] { - match = false - break - } - } - if match { - return true - } - } - - return false -} diff --git a/internal/languages/types.go b/internal/languages/types.go deleted file mode 100644 index 72796a9..0000000 --- a/internal/languages/types.go +++ /dev/null @@ -1,460 +0,0 @@ -package languages - -import ( - "fmt" - "sync" -) - -// Language represents a single language that can be learned by players -type Language struct { - id int32 // Unique language identifier - name string // Language name - saveNeeded bool // Whether this language needs to be saved to database - mutex sync.RWMutex // Thread safety -} - -// NewLanguage creates a new language with default values -func NewLanguage() *Language { - return &Language{ - id: 0, - name: "", - saveNeeded: false, - } -} - -// NewLanguageFromExisting creates a copy of an existing language -func NewLanguageFromExisting(source *Language) *Language { - if source == nil { - return NewLanguage() - } - - source.mutex.RLock() - defer source.mutex.RUnlock() - - return &Language{ - id: source.id, - name: source.name, - saveNeeded: source.saveNeeded, - } -} - -// GetID returns the language's unique identifier -func (l *Language) GetID() int32 { - l.mutex.RLock() - defer l.mutex.RUnlock() - - return l.id -} - -// SetID updates the language's unique identifier -func (l *Language) SetID(id int32) { - l.mutex.Lock() - defer l.mutex.Unlock() - - l.id = id -} - -// GetName returns the language name -func (l *Language) GetName() string { - l.mutex.RLock() - defer l.mutex.RUnlock() - - return l.name -} - -// SetName updates the language name -func (l *Language) SetName(name string) { - l.mutex.Lock() - defer l.mutex.Unlock() - - // Truncate if too long - if len(name) > MaxLanguageNameLength { - name = name[:MaxLanguageNameLength] - } - - l.name = name -} - -// GetSaveNeeded returns whether this language needs database saving -func (l *Language) GetSaveNeeded() bool { - l.mutex.RLock() - defer l.mutex.RUnlock() - - return l.saveNeeded -} - -// SetSaveNeeded updates the save status -func (l *Language) SetSaveNeeded(needed bool) { - l.mutex.Lock() - defer l.mutex.Unlock() - - l.saveNeeded = needed -} - -// IsValid validates the language data -func (l *Language) IsValid() bool { - l.mutex.RLock() - defer l.mutex.RUnlock() - - if l.id < MinLanguageID || l.id > MaxLanguageID { - return false - } - - if len(l.name) == 0 || len(l.name) > MaxLanguageNameLength { - return false - } - - return true -} - -// String returns a string representation of the language -func (l *Language) String() string { - l.mutex.RLock() - defer l.mutex.RUnlock() - - return fmt.Sprintf("Language{ID: %d, Name: %s, SaveNeeded: %v}", l.id, l.name, l.saveNeeded) -} - -// Copy creates a deep copy of the language -func (l *Language) Copy() *Language { - return NewLanguageFromExisting(l) -} - -// MasterLanguagesList manages the global list of all available languages -type MasterLanguagesList struct { - languages map[int32]*Language // Languages indexed by ID for fast lookup - nameIndex map[string]*Language // Languages indexed by name for name lookups - mutex sync.RWMutex // Thread safety -} - -// NewMasterLanguagesList creates a new master languages list -func NewMasterLanguagesList() *MasterLanguagesList { - return &MasterLanguagesList{ - languages: make(map[int32]*Language), - nameIndex: make(map[string]*Language), - } -} - -// Clear removes all languages from the list -func (mll *MasterLanguagesList) Clear() { - mll.mutex.Lock() - defer mll.mutex.Unlock() - - mll.languages = make(map[int32]*Language) - mll.nameIndex = make(map[string]*Language) -} - -// Size returns the number of languages in the list -func (mll *MasterLanguagesList) Size() int32 { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - return int32(len(mll.languages)) -} - -// AddLanguage adds a new language to the master list -func (mll *MasterLanguagesList) AddLanguage(language *Language) error { - if language == nil { - return fmt.Errorf("language cannot be nil") - } - - if !language.IsValid() { - return fmt.Errorf("language is not valid: %s", language.String()) - } - - mll.mutex.Lock() - defer mll.mutex.Unlock() - - // Check for duplicate ID - if _, exists := mll.languages[language.GetID()]; exists { - return fmt.Errorf("language with ID %d already exists", language.GetID()) - } - - // Check for duplicate name - name := language.GetName() - if _, exists := mll.nameIndex[name]; exists { - return fmt.Errorf("language with name '%s' already exists", name) - } - - // Add to both indexes - mll.languages[language.GetID()] = language - mll.nameIndex[name] = language - - return nil -} - -// GetLanguage retrieves a language by ID -func (mll *MasterLanguagesList) GetLanguage(id int32) *Language { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - return mll.languages[id] -} - -// GetLanguageByName retrieves a language by name -func (mll *MasterLanguagesList) GetLanguageByName(name string) *Language { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - return mll.nameIndex[name] -} - -// GetAllLanguages returns a copy of all languages -func (mll *MasterLanguagesList) GetAllLanguages() []*Language { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - result := make([]*Language, 0, len(mll.languages)) - for _, lang := range mll.languages { - result = append(result, lang) - } - - return result -} - -// HasLanguage checks if a language exists by ID -func (mll *MasterLanguagesList) HasLanguage(id int32) bool { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - _, exists := mll.languages[id] - return exists -} - -// HasLanguageByName checks if a language exists by name -func (mll *MasterLanguagesList) HasLanguageByName(name string) bool { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - _, exists := mll.nameIndex[name] - return exists -} - -// RemoveLanguage removes a language by ID -func (mll *MasterLanguagesList) RemoveLanguage(id int32) bool { - mll.mutex.Lock() - defer mll.mutex.Unlock() - - language, exists := mll.languages[id] - if !exists { - return false - } - - // Remove from both indexes - delete(mll.languages, id) - delete(mll.nameIndex, language.GetName()) - - return true -} - -// UpdateLanguage updates an existing language -func (mll *MasterLanguagesList) UpdateLanguage(language *Language) error { - if language == nil { - return fmt.Errorf("language cannot be nil") - } - - if !language.IsValid() { - return fmt.Errorf("language is not valid: %s", language.String()) - } - - mll.mutex.Lock() - defer mll.mutex.Unlock() - - id := language.GetID() - oldLanguage, exists := mll.languages[id] - if !exists { - return fmt.Errorf("language with ID %d does not exist", id) - } - - // Remove old name index if name changed - oldName := oldLanguage.GetName() - newName := language.GetName() - if oldName != newName { - delete(mll.nameIndex, oldName) - - // Check for name conflicts - if _, exists := mll.nameIndex[newName]; exists { - return fmt.Errorf("language with name '%s' already exists", newName) - } - - mll.nameIndex[newName] = language - } - - // Update language - mll.languages[id] = language - - return nil -} - -// GetLanguageNames returns all language names -func (mll *MasterLanguagesList) GetLanguageNames() []string { - mll.mutex.RLock() - defer mll.mutex.RUnlock() - - names := make([]string, 0, len(mll.nameIndex)) - for name := range mll.nameIndex { - names = append(names, name) - } - - return names -} - -// PlayerLanguagesList manages languages known by a specific player -type PlayerLanguagesList struct { - languages map[int32]*Language // Player's languages indexed by ID - nameIndex map[string]*Language // Player's languages indexed by name - mutex sync.RWMutex // Thread safety -} - -// NewPlayerLanguagesList creates a new player languages list -func NewPlayerLanguagesList() *PlayerLanguagesList { - return &PlayerLanguagesList{ - languages: make(map[int32]*Language), - nameIndex: make(map[string]*Language), - } -} - -// Clear removes all languages from the player's list -func (pll *PlayerLanguagesList) Clear() { - pll.mutex.Lock() - defer pll.mutex.Unlock() - - pll.languages = make(map[int32]*Language) - pll.nameIndex = make(map[string]*Language) -} - -// Add adds a language to the player's known languages -func (pll *PlayerLanguagesList) Add(language *Language) error { - if language == nil { - return fmt.Errorf("language cannot be nil") - } - - pll.mutex.Lock() - defer pll.mutex.Unlock() - - id := language.GetID() - name := language.GetName() - - // Check if already known - if _, exists := pll.languages[id]; exists { - return fmt.Errorf("player already knows language with ID %d", id) - } - - // Check player language limit - if len(pll.languages) >= MaxLanguagesPerPlayer { - return fmt.Errorf("player has reached maximum language limit (%d)", MaxLanguagesPerPlayer) - } - - // Add to both indexes - pll.languages[id] = language - pll.nameIndex[name] = language - - return nil -} - -// GetLanguage retrieves a language the player knows by ID -func (pll *PlayerLanguagesList) GetLanguage(id int32) *Language { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - return pll.languages[id] -} - -// GetLanguageByName retrieves a language the player knows by name -func (pll *PlayerLanguagesList) GetLanguageByName(name string) *Language { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - return pll.nameIndex[name] -} - -// GetAllLanguages returns a copy of all languages the player knows -func (pll *PlayerLanguagesList) GetAllLanguages() []*Language { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - result := make([]*Language, 0, len(pll.languages)) - for _, lang := range pll.languages { - result = append(result, lang) - } - - return result -} - -// HasLanguage checks if the player knows a language by ID -func (pll *PlayerLanguagesList) HasLanguage(id int32) bool { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - _, exists := pll.languages[id] - return exists -} - -// HasLanguageByName checks if the player knows a language by name -func (pll *PlayerLanguagesList) HasLanguageByName(name string) bool { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - _, exists := pll.nameIndex[name] - return exists -} - -// RemoveLanguage removes a language from the player's known languages -func (pll *PlayerLanguagesList) RemoveLanguage(id int32) bool { - pll.mutex.Lock() - defer pll.mutex.Unlock() - - language, exists := pll.languages[id] - if !exists { - return false - } - - // Remove from both indexes - delete(pll.languages, id) - delete(pll.nameIndex, language.GetName()) - - return true -} - -// Size returns the number of languages the player knows -func (pll *PlayerLanguagesList) Size() int32 { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - return int32(len(pll.languages)) -} - -// GetLanguageIDs returns all language IDs the player knows -func (pll *PlayerLanguagesList) GetLanguageIDs() []int32 { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - ids := make([]int32, 0, len(pll.languages)) - for id := range pll.languages { - ids = append(ids, id) - } - - return ids -} - -// GetLanguageNames returns all language names the player knows -func (pll *PlayerLanguagesList) GetLanguageNames() []string { - pll.mutex.RLock() - defer pll.mutex.RUnlock() - - names := make([]string, 0, len(pll.nameIndex)) - for name := range pll.nameIndex { - names = append(names, name) - } - - return names -} - -// LanguageStatistics contains language system statistics -type LanguageStatistics struct { - TotalLanguages int `json:"total_languages"` - PlayersWithLanguages int `json:"players_with_languages"` - LanguageUsageCount map[int32]int64 `json:"language_usage_count"` - LanguageLookups int64 `json:"language_lookups"` - LanguagesByName map[string]int32 `json:"languages_by_name"` -}