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) } } } }