simplify languages
This commit is contained in:
parent
6b3270684f
commit
afded7da3b
@ -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)
|
||||
|
||||
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1409
internal/languages/languages.go
Normal file
1409
internal/languages/languages.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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 <id> <name>")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -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"`
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user