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
|
- Heroic Ops
|
||||||
- Items
|
- Items
|
||||||
- Items/Loot
|
- Items/Loot
|
||||||
|
- Languages
|
||||||
|
|
||||||
## Before: Complex Architecture (8 Files, ~2000+ Lines)
|
## 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