1599 lines
43 KiB
Go
1599 lines
43 KiB
Go
package languages
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// Simplified Language System
|
|
// Consolidates all functionality from 4 files into unified architecture
|
|
// Preserves 100% C++ EQ2 language functionality while eliminating Active Record patterns
|
|
|
|
// Language system constants
|
|
const (
|
|
// Maximum language name length
|
|
MaxLanguageNameLength = 50
|
|
|
|
// Special language IDs (common in EQ2)
|
|
LanguageIDCommon = 0 // Common tongue (default)
|
|
LanguageIDElvish = 1 // Elvish
|
|
LanguageIDDwarven = 2 // Dwarven
|
|
LanguageIDHalfling = 3 // Halfling
|
|
LanguageIDGnomish = 4 // Gnomish
|
|
LanguageIDIksar = 5 // Iksar
|
|
LanguageIDTrollish = 6 // Trollish
|
|
LanguageIDOgrish = 7 // Ogrish
|
|
LanguageIDFae = 8 // Fae
|
|
LanguageIDArasai = 9 // Arasai
|
|
LanguageIDSarnak = 10 // Sarnak
|
|
LanguageIDFroglok = 11 // Froglok
|
|
)
|
|
|
|
// Language validation constants
|
|
const (
|
|
MinLanguageID = 0
|
|
MaxLanguageID = 999999 // Reasonable upper bound
|
|
)
|
|
|
|
// Database operation constants
|
|
const (
|
|
SaveStatusUnchanged = false
|
|
SaveStatusNeeded = true
|
|
)
|
|
|
|
// System limits
|
|
const (
|
|
MaxLanguagesPerPlayer = 100 // Reasonable limit to prevent abuse
|
|
MaxTotalLanguages = 1000 // System-wide language limit
|
|
)
|
|
|
|
// Database interface for language persistence
|
|
type Database interface {
|
|
LoadAllLanguages() ([]*Language, error)
|
|
SaveLanguage(language *Language) error
|
|
DeleteLanguage(languageID int32) error
|
|
LoadPlayerLanguages(playerID int32) ([]*Language, error)
|
|
SavePlayerLanguage(playerID int32, languageID int32) error
|
|
DeletePlayerLanguage(playerID int32, languageID int32) error
|
|
}
|
|
|
|
// Logger interface for language logging
|
|
type Logger interface {
|
|
LogInfo(message string, args ...any)
|
|
LogError(message string, args ...any)
|
|
LogDebug(message string, args ...any)
|
|
LogWarning(message string, args ...any)
|
|
}
|
|
|
|
// Entity interface for language-related entity operations
|
|
type Entity interface {
|
|
GetID() int32
|
|
GetName() string
|
|
IsPlayer() bool
|
|
IsNPC() bool
|
|
IsBot() bool
|
|
}
|
|
|
|
// Player interface for language-related player operations
|
|
type Player interface {
|
|
Entity
|
|
GetCharacterID() int32
|
|
SendMessage(message string)
|
|
}
|
|
|
|
// Client interface for language-related client operations
|
|
type Client interface {
|
|
GetVersion() int16
|
|
SendLanguageUpdate(languageData []byte) error
|
|
}
|
|
|
|
// LanguageProvider interface for systems that provide language functionality
|
|
type LanguageProvider interface {
|
|
GetMasterLanguagesList() *MasterLanguagesList
|
|
GetLanguage(languageID int32) *Language
|
|
GetLanguageByName(name string) *Language
|
|
CreatePlayerLanguagesList() *PlayerLanguagesList
|
|
}
|
|
|
|
// LanguageAware interface for entities that can understand languages
|
|
type LanguageAware interface {
|
|
GetKnownLanguages() *PlayerLanguagesList
|
|
KnowsLanguage(languageID int32) bool
|
|
GetPrimaryLanguage() int32
|
|
SetPrimaryLanguage(languageID int32)
|
|
CanUnderstand(languageID int32) bool
|
|
LearnLanguage(languageID int32) error
|
|
ForgetLanguage(languageID int32) error
|
|
}
|
|
|
|
// LanguageHandler interface for handling language events
|
|
type LanguageHandler interface {
|
|
OnLanguageLearned(player Player, languageID int32) error
|
|
OnLanguageForgotten(player Player, languageID int32) error
|
|
OnLanguageUsed(player Player, languageID int32, message string) error
|
|
}
|
|
|
|
// ChatProcessor interface for processing multilingual chat
|
|
type ChatProcessor interface {
|
|
ProcessMessage(speaker Player, message string, languageID int32) (string, error)
|
|
FilterMessage(listener Player, message string, languageID int32) string
|
|
GetLanguageSkramble(message string, comprehension float32) string
|
|
}
|
|
|
|
// Language represents a single language that can be learned by players
|
|
type Language struct {
|
|
id int32 // Unique language identifier
|
|
name string // Language name
|
|
saveNeeded bool // Whether this language needs to be saved to database
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// NewLanguage creates a new language with default values
|
|
func NewLanguage() *Language {
|
|
return &Language{
|
|
id: 0,
|
|
name: "",
|
|
saveNeeded: false,
|
|
}
|
|
}
|
|
|
|
// NewLanguageFromExisting creates a copy of an existing language
|
|
func NewLanguageFromExisting(source *Language) *Language {
|
|
if source == nil {
|
|
return NewLanguage()
|
|
}
|
|
|
|
source.mutex.RLock()
|
|
defer source.mutex.RUnlock()
|
|
|
|
return &Language{
|
|
id: source.id,
|
|
name: source.name,
|
|
saveNeeded: source.saveNeeded,
|
|
}
|
|
}
|
|
|
|
// GetID returns the language's unique identifier
|
|
func (l *Language) GetID() int32 {
|
|
l.mutex.RLock()
|
|
defer l.mutex.RUnlock()
|
|
|
|
return l.id
|
|
}
|
|
|
|
// SetID updates the language's unique identifier
|
|
func (l *Language) SetID(id int32) {
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
|
|
l.id = id
|
|
}
|
|
|
|
// GetName returns the language name
|
|
func (l *Language) GetName() string {
|
|
l.mutex.RLock()
|
|
defer l.mutex.RUnlock()
|
|
|
|
return l.name
|
|
}
|
|
|
|
// SetName updates the language name
|
|
func (l *Language) SetName(name string) {
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
|
|
// Truncate if too long
|
|
if len(name) > MaxLanguageNameLength {
|
|
name = name[:MaxLanguageNameLength]
|
|
}
|
|
|
|
l.name = name
|
|
}
|
|
|
|
// GetSaveNeeded returns whether this language needs database saving
|
|
func (l *Language) GetSaveNeeded() bool {
|
|
l.mutex.RLock()
|
|
defer l.mutex.RUnlock()
|
|
|
|
return l.saveNeeded
|
|
}
|
|
|
|
// SetSaveNeeded updates the save status
|
|
func (l *Language) SetSaveNeeded(needed bool) {
|
|
l.mutex.Lock()
|
|
defer l.mutex.Unlock()
|
|
|
|
l.saveNeeded = needed
|
|
}
|
|
|
|
// IsValid validates the language data
|
|
func (l *Language) IsValid() bool {
|
|
l.mutex.RLock()
|
|
defer l.mutex.RUnlock()
|
|
|
|
if l.id < MinLanguageID || l.id > MaxLanguageID {
|
|
return false
|
|
}
|
|
|
|
if len(l.name) == 0 || len(l.name) > MaxLanguageNameLength {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// String returns a string representation of the language
|
|
func (l *Language) String() string {
|
|
l.mutex.RLock()
|
|
defer l.mutex.RUnlock()
|
|
|
|
return fmt.Sprintf("Language{ID: %d, Name: %s, SaveNeeded: %v}", l.id, l.name, l.saveNeeded)
|
|
}
|
|
|
|
// Copy creates a deep copy of the language
|
|
func (l *Language) Copy() *Language {
|
|
return NewLanguageFromExisting(l)
|
|
}
|
|
|
|
// MasterLanguagesList manages the global list of all available languages
|
|
type MasterLanguagesList struct {
|
|
languages map[int32]*Language // Languages indexed by ID for fast lookup
|
|
nameIndex map[string]*Language // Languages indexed by name for name lookups
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// NewMasterLanguagesList creates a new master languages list
|
|
func NewMasterLanguagesList() *MasterLanguagesList {
|
|
return &MasterLanguagesList{
|
|
languages: make(map[int32]*Language),
|
|
nameIndex: make(map[string]*Language),
|
|
}
|
|
}
|
|
|
|
// Clear removes all languages from the list
|
|
func (mll *MasterLanguagesList) Clear() {
|
|
mll.mutex.Lock()
|
|
defer mll.mutex.Unlock()
|
|
|
|
mll.languages = make(map[int32]*Language)
|
|
mll.nameIndex = make(map[string]*Language)
|
|
}
|
|
|
|
// Size returns the number of languages in the list
|
|
func (mll *MasterLanguagesList) Size() int32 {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
return int32(len(mll.languages))
|
|
}
|
|
|
|
// AddLanguage adds a new language to the master list
|
|
func (mll *MasterLanguagesList) AddLanguage(language *Language) error {
|
|
if language == nil {
|
|
return fmt.Errorf("language cannot be nil")
|
|
}
|
|
|
|
if !language.IsValid() {
|
|
return fmt.Errorf("language is not valid: %s", language.String())
|
|
}
|
|
|
|
mll.mutex.Lock()
|
|
defer mll.mutex.Unlock()
|
|
|
|
// Check for duplicate ID
|
|
if _, exists := mll.languages[language.GetID()]; exists {
|
|
return fmt.Errorf("language with ID %d already exists", language.GetID())
|
|
}
|
|
|
|
// Check for duplicate name
|
|
name := language.GetName()
|
|
if _, exists := mll.nameIndex[name]; exists {
|
|
return fmt.Errorf("language with name '%s' already exists", name)
|
|
}
|
|
|
|
// Add to both indexes
|
|
mll.languages[language.GetID()] = language
|
|
mll.nameIndex[name] = language
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLanguage retrieves a language by ID
|
|
func (mll *MasterLanguagesList) GetLanguage(id int32) *Language {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
return mll.languages[id]
|
|
}
|
|
|
|
// GetLanguageByName retrieves a language by name
|
|
func (mll *MasterLanguagesList) GetLanguageByName(name string) *Language {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
return mll.nameIndex[name]
|
|
}
|
|
|
|
// GetAllLanguages returns a copy of all languages
|
|
func (mll *MasterLanguagesList) GetAllLanguages() []*Language {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
result := make([]*Language, 0, len(mll.languages))
|
|
for _, lang := range mll.languages {
|
|
result = append(result, lang)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// HasLanguage checks if a language exists by ID
|
|
func (mll *MasterLanguagesList) HasLanguage(id int32) bool {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
_, exists := mll.languages[id]
|
|
return exists
|
|
}
|
|
|
|
// HasLanguageByName checks if a language exists by name
|
|
func (mll *MasterLanguagesList) HasLanguageByName(name string) bool {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
_, exists := mll.nameIndex[name]
|
|
return exists
|
|
}
|
|
|
|
// RemoveLanguage removes a language by ID
|
|
func (mll *MasterLanguagesList) RemoveLanguage(id int32) bool {
|
|
mll.mutex.Lock()
|
|
defer mll.mutex.Unlock()
|
|
|
|
language, exists := mll.languages[id]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from both indexes
|
|
delete(mll.languages, id)
|
|
delete(mll.nameIndex, language.GetName())
|
|
|
|
return true
|
|
}
|
|
|
|
// UpdateLanguage updates an existing language
|
|
func (mll *MasterLanguagesList) UpdateLanguage(language *Language) error {
|
|
if language == nil {
|
|
return fmt.Errorf("language cannot be nil")
|
|
}
|
|
|
|
if !language.IsValid() {
|
|
return fmt.Errorf("language is not valid: %s", language.String())
|
|
}
|
|
|
|
mll.mutex.Lock()
|
|
defer mll.mutex.Unlock()
|
|
|
|
id := language.GetID()
|
|
oldLanguage, exists := mll.languages[id]
|
|
if !exists {
|
|
return fmt.Errorf("language with ID %d does not exist", id)
|
|
}
|
|
|
|
// Remove old name index if name changed
|
|
oldName := oldLanguage.GetName()
|
|
newName := language.GetName()
|
|
if oldName != newName {
|
|
delete(mll.nameIndex, oldName)
|
|
|
|
// Check for name conflicts
|
|
if _, exists := mll.nameIndex[newName]; exists {
|
|
return fmt.Errorf("language with name '%s' already exists", newName)
|
|
}
|
|
|
|
mll.nameIndex[newName] = language
|
|
}
|
|
|
|
// Update language
|
|
mll.languages[id] = language
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLanguageNames returns all language names
|
|
func (mll *MasterLanguagesList) GetLanguageNames() []string {
|
|
mll.mutex.RLock()
|
|
defer mll.mutex.RUnlock()
|
|
|
|
names := make([]string, 0, len(mll.nameIndex))
|
|
for name := range mll.nameIndex {
|
|
names = append(names, name)
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
// PlayerLanguagesList manages languages known by a specific player
|
|
type PlayerLanguagesList struct {
|
|
languages map[int32]*Language // Player's languages indexed by ID
|
|
nameIndex map[string]*Language // Player's languages indexed by name
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// NewPlayerLanguagesList creates a new player languages list
|
|
func NewPlayerLanguagesList() *PlayerLanguagesList {
|
|
return &PlayerLanguagesList{
|
|
languages: make(map[int32]*Language),
|
|
nameIndex: make(map[string]*Language),
|
|
}
|
|
}
|
|
|
|
// Clear removes all languages from the player's list
|
|
func (pll *PlayerLanguagesList) Clear() {
|
|
pll.mutex.Lock()
|
|
defer pll.mutex.Unlock()
|
|
|
|
pll.languages = make(map[int32]*Language)
|
|
pll.nameIndex = make(map[string]*Language)
|
|
}
|
|
|
|
// Add adds a language to the player's known languages
|
|
func (pll *PlayerLanguagesList) Add(language *Language) error {
|
|
if language == nil {
|
|
return fmt.Errorf("language cannot be nil")
|
|
}
|
|
|
|
pll.mutex.Lock()
|
|
defer pll.mutex.Unlock()
|
|
|
|
id := language.GetID()
|
|
name := language.GetName()
|
|
|
|
// Check if already known
|
|
if _, exists := pll.languages[id]; exists {
|
|
return fmt.Errorf("player already knows language with ID %d", id)
|
|
}
|
|
|
|
// Check player language limit
|
|
if len(pll.languages) >= MaxLanguagesPerPlayer {
|
|
return fmt.Errorf("player has reached maximum language limit (%d)", MaxLanguagesPerPlayer)
|
|
}
|
|
|
|
// Add to both indexes
|
|
pll.languages[id] = language
|
|
pll.nameIndex[name] = language
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLanguage retrieves a language the player knows by ID
|
|
func (pll *PlayerLanguagesList) GetLanguage(id int32) *Language {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
return pll.languages[id]
|
|
}
|
|
|
|
// GetLanguageByName retrieves a language the player knows by name
|
|
func (pll *PlayerLanguagesList) GetLanguageByName(name string) *Language {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
return pll.nameIndex[name]
|
|
}
|
|
|
|
// GetAllLanguages returns a copy of all languages the player knows
|
|
func (pll *PlayerLanguagesList) GetAllLanguages() []*Language {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
result := make([]*Language, 0, len(pll.languages))
|
|
for _, lang := range pll.languages {
|
|
result = append(result, lang)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// HasLanguage checks if the player knows a language by ID
|
|
func (pll *PlayerLanguagesList) HasLanguage(id int32) bool {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
_, exists := pll.languages[id]
|
|
return exists
|
|
}
|
|
|
|
// HasLanguageByName checks if the player knows a language by name
|
|
func (pll *PlayerLanguagesList) HasLanguageByName(name string) bool {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
_, exists := pll.nameIndex[name]
|
|
return exists
|
|
}
|
|
|
|
// RemoveLanguage removes a language from the player's known languages
|
|
func (pll *PlayerLanguagesList) RemoveLanguage(id int32) bool {
|
|
pll.mutex.Lock()
|
|
defer pll.mutex.Unlock()
|
|
|
|
language, exists := pll.languages[id]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from both indexes
|
|
delete(pll.languages, id)
|
|
delete(pll.nameIndex, language.GetName())
|
|
|
|
return true
|
|
}
|
|
|
|
// Size returns the number of languages the player knows
|
|
func (pll *PlayerLanguagesList) Size() int32 {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
return int32(len(pll.languages))
|
|
}
|
|
|
|
// GetLanguageIDs returns all language IDs the player knows
|
|
func (pll *PlayerLanguagesList) GetLanguageIDs() []int32 {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
ids := make([]int32, 0, len(pll.languages))
|
|
for id := range pll.languages {
|
|
ids = append(ids, id)
|
|
}
|
|
|
|
return ids
|
|
}
|
|
|
|
// GetLanguageNames returns all language names the player knows
|
|
func (pll *PlayerLanguagesList) GetLanguageNames() []string {
|
|
pll.mutex.RLock()
|
|
defer pll.mutex.RUnlock()
|
|
|
|
names := make([]string, 0, len(pll.nameIndex))
|
|
for name := range pll.nameIndex {
|
|
names = append(names, name)
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
// LanguageStatistics contains language system statistics
|
|
type LanguageStatistics struct {
|
|
TotalLanguages int `json:"total_languages"`
|
|
PlayersWithLanguages int `json:"players_with_languages"`
|
|
LanguageUsageCount map[int32]int64 `json:"language_usage_count"`
|
|
LanguageLookups int64 `json:"language_lookups"`
|
|
LanguagesByName map[string]int32 `json:"languages_by_name"`
|
|
}
|
|
|
|
// LanguageManager provides high-level management of the language system
|
|
type LanguageManager struct {
|
|
masterLanguagesList *MasterLanguagesList
|
|
database Database
|
|
logger Logger
|
|
languageLookups int64
|
|
playersWithLanguages int64
|
|
languageUsageCount map[int32]int64 // Language ID -> usage count
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewLanguageManager creates a new language manager
|
|
func NewLanguageManager(database Database, logger Logger) *LanguageManager {
|
|
return &LanguageManager{
|
|
masterLanguagesList: NewMasterLanguagesList(),
|
|
database: database,
|
|
logger: logger,
|
|
languageUsageCount: make(map[int32]int64),
|
|
}
|
|
}
|
|
|
|
// Initialize loads languages from database
|
|
func (lm *LanguageManager) Initialize() error {
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Initializing language manager...")
|
|
}
|
|
|
|
if lm.database == nil {
|
|
if lm.logger != nil {
|
|
lm.logger.LogWarning("No database provided, starting with empty language list")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Load languages from database
|
|
languages, err := lm.database.LoadAllLanguages()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load languages from database: %w", err)
|
|
}
|
|
|
|
for _, language := range languages {
|
|
if err := lm.masterLanguagesList.AddLanguage(language); err != nil {
|
|
if lm.logger != nil {
|
|
lm.logger.LogError("Failed to add language %d (%s): %v", language.GetID(), language.GetName(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Loaded %d languages from database", len(languages))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMasterLanguagesList returns the master language list
|
|
func (lm *LanguageManager) GetMasterLanguagesList() *MasterLanguagesList {
|
|
return lm.masterLanguagesList
|
|
}
|
|
|
|
// CreatePlayerLanguagesList creates a new player language list
|
|
func (lm *LanguageManager) CreatePlayerLanguagesList() *PlayerLanguagesList {
|
|
lm.mutex.Lock()
|
|
lm.playersWithLanguages++
|
|
lm.mutex.Unlock()
|
|
|
|
return NewPlayerLanguagesList()
|
|
}
|
|
|
|
// GetLanguage returns a language by ID
|
|
func (lm *LanguageManager) GetLanguage(id int32) *Language {
|
|
lm.mutex.Lock()
|
|
lm.languageLookups++
|
|
lm.mutex.Unlock()
|
|
|
|
return lm.masterLanguagesList.GetLanguage(id)
|
|
}
|
|
|
|
// GetLanguageByName returns a language by name
|
|
func (lm *LanguageManager) GetLanguageByName(name string) *Language {
|
|
lm.mutex.Lock()
|
|
lm.languageLookups++
|
|
lm.mutex.Unlock()
|
|
|
|
return lm.masterLanguagesList.GetLanguageByName(name)
|
|
}
|
|
|
|
// AddLanguage adds a new language to the system
|
|
func (lm *LanguageManager) AddLanguage(language *Language) error {
|
|
if language == nil {
|
|
return fmt.Errorf("language cannot be nil")
|
|
}
|
|
|
|
// Add to master list
|
|
if err := lm.masterLanguagesList.AddLanguage(language); err != nil {
|
|
return fmt.Errorf("failed to add language to master list: %w", err)
|
|
}
|
|
|
|
// Save to database if available
|
|
if lm.database != nil {
|
|
if err := lm.database.SaveLanguage(language); err != nil {
|
|
// Remove from master list if database save failed
|
|
lm.masterLanguagesList.RemoveLanguage(language.GetID())
|
|
return fmt.Errorf("failed to save language to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Added language %d: %s", language.GetID(), language.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateLanguage updates an existing language
|
|
func (lm *LanguageManager) UpdateLanguage(language *Language) error {
|
|
if language == nil {
|
|
return fmt.Errorf("language cannot be nil")
|
|
}
|
|
|
|
// Update in master list
|
|
if err := lm.masterLanguagesList.UpdateLanguage(language); err != nil {
|
|
return fmt.Errorf("failed to update language in master list: %w", err)
|
|
}
|
|
|
|
// Save to database if available
|
|
if lm.database != nil {
|
|
if err := lm.database.SaveLanguage(language); err != nil {
|
|
return fmt.Errorf("failed to save language to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Updated language %d: %s", language.GetID(), language.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveLanguage removes a language from the system
|
|
func (lm *LanguageManager) RemoveLanguage(id int32) error {
|
|
// Check if language exists
|
|
if !lm.masterLanguagesList.HasLanguage(id) {
|
|
return fmt.Errorf("language with ID %d does not exist", id)
|
|
}
|
|
|
|
// Remove from database first if available
|
|
if lm.database != nil {
|
|
if err := lm.database.DeleteLanguage(id); err != nil {
|
|
return fmt.Errorf("failed to delete language from database: %w", err)
|
|
}
|
|
}
|
|
|
|
// Remove from master list
|
|
if !lm.masterLanguagesList.RemoveLanguage(id) {
|
|
return fmt.Errorf("failed to remove language from master list")
|
|
}
|
|
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Removed language %d", id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RecordLanguageUsage records language usage for statistics
|
|
func (lm *LanguageManager) RecordLanguageUsage(languageID int32) {
|
|
lm.mutex.Lock()
|
|
defer lm.mutex.Unlock()
|
|
|
|
lm.languageUsageCount[languageID]++
|
|
}
|
|
|
|
// GetStatistics returns language system statistics
|
|
func (lm *LanguageManager) GetStatistics() *LanguageStatistics {
|
|
lm.mutex.RLock()
|
|
defer lm.mutex.RUnlock()
|
|
|
|
// Create language name mapping
|
|
languagesByName := make(map[string]int32)
|
|
allLanguages := lm.masterLanguagesList.GetAllLanguages()
|
|
for _, lang := range allLanguages {
|
|
languagesByName[lang.GetName()] = lang.GetID()
|
|
}
|
|
|
|
// Copy usage count
|
|
usageCount := make(map[int32]int64)
|
|
for id, count := range lm.languageUsageCount {
|
|
usageCount[id] = count
|
|
}
|
|
|
|
return &LanguageStatistics{
|
|
TotalLanguages: len(allLanguages),
|
|
PlayersWithLanguages: int(lm.playersWithLanguages),
|
|
LanguageUsageCount: usageCount,
|
|
LanguageLookups: lm.languageLookups,
|
|
LanguagesByName: languagesByName,
|
|
}
|
|
}
|
|
|
|
// ResetStatistics resets all statistics
|
|
func (lm *LanguageManager) ResetStatistics() {
|
|
lm.mutex.Lock()
|
|
defer lm.mutex.Unlock()
|
|
|
|
lm.languageLookups = 0
|
|
lm.playersWithLanguages = 0
|
|
lm.languageUsageCount = make(map[int32]int64)
|
|
}
|
|
|
|
// ValidateAllLanguages validates all languages in the system
|
|
func (lm *LanguageManager) ValidateAllLanguages() []string {
|
|
allLanguages := lm.masterLanguagesList.GetAllLanguages()
|
|
var issues []string
|
|
|
|
for _, lang := range allLanguages {
|
|
if !lang.IsValid() {
|
|
issues = append(issues, fmt.Sprintf("Language %d (%s) is invalid", lang.GetID(), lang.GetName()))
|
|
}
|
|
}
|
|
|
|
return issues
|
|
}
|
|
|
|
// ReloadFromDatabase reloads all languages from database
|
|
func (lm *LanguageManager) ReloadFromDatabase() error {
|
|
if lm.database == nil {
|
|
return fmt.Errorf("no database available")
|
|
}
|
|
|
|
// Clear current languages
|
|
lm.masterLanguagesList.Clear()
|
|
|
|
// Reload from database
|
|
return lm.Initialize()
|
|
}
|
|
|
|
// GetLanguageCount returns the total number of languages
|
|
func (lm *LanguageManager) GetLanguageCount() int32 {
|
|
return lm.masterLanguagesList.Size()
|
|
}
|
|
|
|
// ProcessCommand handles language-related commands
|
|
func (lm *LanguageManager) ProcessCommand(command string, args []string) (string, error) {
|
|
switch command {
|
|
case "stats":
|
|
return lm.handleStatsCommand(args)
|
|
case "validate":
|
|
return lm.handleValidateCommand(args)
|
|
case "list":
|
|
return lm.handleListCommand(args)
|
|
case "info":
|
|
return lm.handleInfoCommand(args)
|
|
case "reload":
|
|
return lm.handleReloadCommand(args)
|
|
case "add":
|
|
return lm.handleAddCommand(args)
|
|
case "remove":
|
|
return lm.handleRemoveCommand(args)
|
|
case "search":
|
|
return lm.handleSearchCommand(args)
|
|
default:
|
|
return "", fmt.Errorf("unknown language command: %s", command)
|
|
}
|
|
}
|
|
|
|
// handleStatsCommand shows language system statistics
|
|
func (lm *LanguageManager) handleStatsCommand(args []string) (string, error) {
|
|
stats := lm.GetStatistics()
|
|
|
|
result := "Language System Statistics:\n"
|
|
result += fmt.Sprintf("Total Languages: %d\n", stats.TotalLanguages)
|
|
result += fmt.Sprintf("Players with Languages: %d\n", stats.PlayersWithLanguages)
|
|
result += fmt.Sprintf("Language Lookups: %d\n", stats.LanguageLookups)
|
|
|
|
if len(stats.LanguageUsageCount) > 0 {
|
|
result += "\nMost Used Languages:\n"
|
|
// Show top 5 most used languages
|
|
count := 0
|
|
for langID, usage := range stats.LanguageUsageCount {
|
|
if count >= 5 {
|
|
break
|
|
}
|
|
lang := lm.GetLanguage(langID)
|
|
name := "Unknown"
|
|
if lang != nil {
|
|
name = lang.GetName()
|
|
}
|
|
result += fmt.Sprintf(" %s (ID: %d): %d uses\n", name, langID, usage)
|
|
count++
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleValidateCommand validates all languages
|
|
func (lm *LanguageManager) handleValidateCommand(args []string) (string, error) {
|
|
issues := lm.ValidateAllLanguages()
|
|
|
|
if len(issues) == 0 {
|
|
return "All languages are valid.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d issues with languages:\n", len(issues))
|
|
for i, issue := range issues {
|
|
if i >= 10 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf("%d. %s\n", i+1, issue)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleListCommand lists languages
|
|
func (lm *LanguageManager) handleListCommand(args []string) (string, error) {
|
|
languages := lm.masterLanguagesList.GetAllLanguages()
|
|
|
|
if len(languages) == 0 {
|
|
return "No languages loaded.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Languages (%d):\n", len(languages))
|
|
count := 0
|
|
for _, language := range languages {
|
|
if count >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName())
|
|
count++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleInfoCommand shows information about a specific language
|
|
func (lm *LanguageManager) handleInfoCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("language ID or name required")
|
|
}
|
|
|
|
var language *Language
|
|
|
|
// Try to parse as ID first
|
|
var languageID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &languageID); err == nil {
|
|
language = lm.GetLanguage(languageID)
|
|
} else {
|
|
// Try as name
|
|
language = lm.GetLanguageByName(args[0])
|
|
}
|
|
|
|
if language == nil {
|
|
return fmt.Sprintf("Language '%s' not found.", args[0]), nil
|
|
}
|
|
|
|
result := "Language Information:\n"
|
|
result += fmt.Sprintf("ID: %d\n", language.GetID())
|
|
result += fmt.Sprintf("Name: %s\n", language.GetName())
|
|
result += fmt.Sprintf("Save Needed: %v\n", language.GetSaveNeeded())
|
|
|
|
// Show usage statistics if available
|
|
lm.mutex.RLock()
|
|
if usage, exists := lm.languageUsageCount[language.GetID()]; exists {
|
|
result += fmt.Sprintf("Usage Count: %d\n", usage)
|
|
} else {
|
|
result += "Usage Count: 0\n"
|
|
}
|
|
lm.mutex.RUnlock()
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleReloadCommand reloads languages from database
|
|
func (lm *LanguageManager) handleReloadCommand(args []string) (string, error) {
|
|
if err := lm.ReloadFromDatabase(); err != nil {
|
|
return "", fmt.Errorf("failed to reload languages: %w", err)
|
|
}
|
|
|
|
count := lm.GetLanguageCount()
|
|
return fmt.Sprintf("Successfully reloaded %d languages from database.", count), nil
|
|
}
|
|
|
|
// handleAddCommand adds a new language
|
|
func (lm *LanguageManager) handleAddCommand(args []string) (string, error) {
|
|
if len(args) < 2 {
|
|
return "", fmt.Errorf("usage: add <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 := lm.AddLanguage(language); err != nil {
|
|
return "", fmt.Errorf("failed to add language: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully added language %d: %s", id, name), nil
|
|
}
|
|
|
|
// handleRemoveCommand removes a language
|
|
func (lm *LanguageManager) handleRemoveCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("language ID required")
|
|
}
|
|
|
|
var id int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &id); err != nil {
|
|
return "", fmt.Errorf("invalid language ID: %s", args[0])
|
|
}
|
|
|
|
if err := lm.RemoveLanguage(id); err != nil {
|
|
return "", fmt.Errorf("failed to remove language: %w", err)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully removed language %d", id), nil
|
|
}
|
|
|
|
// handleSearchCommand searches for languages by name
|
|
func (lm *LanguageManager) handleSearchCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("search term required")
|
|
}
|
|
|
|
searchTerm := args[0]
|
|
languages := lm.masterLanguagesList.GetAllLanguages()
|
|
var results []*Language
|
|
|
|
// Search by name (case-insensitive partial match)
|
|
for _, language := range languages {
|
|
if lm.contains(language.GetName(), searchTerm) {
|
|
results = append(results, language)
|
|
}
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
return fmt.Sprintf("No languages found matching '%s'.", searchTerm), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d languages matching '%s':\n", len(results), searchTerm)
|
|
for i, language := range results {
|
|
if i >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf(" %d: %s\n", language.GetID(), language.GetName())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// contains checks if a string contains a substring (case-insensitive)
|
|
func (lm *LanguageManager) contains(str, substr string) bool {
|
|
if len(substr) == 0 {
|
|
return true
|
|
}
|
|
if len(str) < len(substr) {
|
|
return false
|
|
}
|
|
|
|
// Convert to lowercase for case-insensitive comparison
|
|
strLower := make([]byte, len(str))
|
|
substrLower := make([]byte, len(substr))
|
|
|
|
for i := 0; i < len(str); i++ {
|
|
if str[i] >= 'A' && str[i] <= 'Z' {
|
|
strLower[i] = str[i] + 32
|
|
} else {
|
|
strLower[i] = str[i]
|
|
}
|
|
}
|
|
|
|
for i := 0; i < len(substr); i++ {
|
|
if substr[i] >= 'A' && substr[i] <= 'Z' {
|
|
substrLower[i] = substr[i] + 32
|
|
} else {
|
|
substrLower[i] = substr[i]
|
|
}
|
|
}
|
|
|
|
for i := 0; i <= len(strLower)-len(substrLower); i++ {
|
|
match := true
|
|
for j := 0; j < len(substrLower); j++ {
|
|
if strLower[i+j] != substrLower[j] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the manager
|
|
func (lm *LanguageManager) Shutdown() {
|
|
if lm.logger != nil {
|
|
lm.logger.LogInfo("Shutting down language manager...")
|
|
}
|
|
|
|
// Clear languages
|
|
lm.masterLanguagesList.Clear()
|
|
}
|
|
|
|
// PlayerLanguageAdapter provides language functionality for players
|
|
type PlayerLanguageAdapter struct {
|
|
languages *PlayerLanguagesList
|
|
primaryLang int32
|
|
manager *LanguageManager
|
|
logger Logger
|
|
}
|
|
|
|
// NewPlayerLanguageAdapter creates a new player language adapter
|
|
func NewPlayerLanguageAdapter(manager *LanguageManager, logger Logger) *PlayerLanguageAdapter {
|
|
adapter := &PlayerLanguageAdapter{
|
|
languages: manager.CreatePlayerLanguagesList(),
|
|
primaryLang: LanguageIDCommon, // Default to common
|
|
manager: manager,
|
|
logger: logger,
|
|
}
|
|
|
|
// Ensure common language is always known
|
|
if commonLang := manager.GetLanguage(LanguageIDCommon); commonLang != nil {
|
|
adapter.languages.Add(commonLang.Copy())
|
|
}
|
|
|
|
return adapter
|
|
}
|
|
|
|
// GetKnownLanguages returns the player's known languages
|
|
func (pla *PlayerLanguageAdapter) GetKnownLanguages() *PlayerLanguagesList {
|
|
return pla.languages
|
|
}
|
|
|
|
// KnowsLanguage checks if the player knows a specific language
|
|
func (pla *PlayerLanguageAdapter) KnowsLanguage(languageID int32) bool {
|
|
return pla.languages.HasLanguage(languageID)
|
|
}
|
|
|
|
// GetPrimaryLanguage returns the player's primary language
|
|
func (pla *PlayerLanguageAdapter) GetPrimaryLanguage() int32 {
|
|
return pla.primaryLang
|
|
}
|
|
|
|
// SetPrimaryLanguage sets the player's primary language
|
|
func (pla *PlayerLanguageAdapter) SetPrimaryLanguage(languageID int32) {
|
|
// Only allow setting to a known language
|
|
if pla.languages.HasLanguage(languageID) {
|
|
pla.primaryLang = languageID
|
|
|
|
if pla.logger != nil {
|
|
lang := pla.manager.GetLanguage(languageID)
|
|
langName := "Unknown"
|
|
if lang != nil {
|
|
langName = lang.GetName()
|
|
}
|
|
pla.logger.LogDebug("Player set primary language to %s (%d)", langName, languageID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// CanUnderstand checks if the player can understand a language
|
|
func (pla *PlayerLanguageAdapter) CanUnderstand(languageID int32) bool {
|
|
// Common language is always understood
|
|
if languageID == LanguageIDCommon {
|
|
return true
|
|
}
|
|
|
|
// Check if player knows the language
|
|
return pla.languages.HasLanguage(languageID)
|
|
}
|
|
|
|
// LearnLanguage teaches the player a new language
|
|
func (pla *PlayerLanguageAdapter) LearnLanguage(languageID int32) error {
|
|
// Get the language from master list
|
|
language := pla.manager.GetLanguage(languageID)
|
|
if language == nil {
|
|
return fmt.Errorf("language with ID %d does not exist", languageID)
|
|
}
|
|
|
|
// Check if already known
|
|
if pla.languages.HasLanguage(languageID) {
|
|
return fmt.Errorf("player already knows language %s", language.GetName())
|
|
}
|
|
|
|
// Create a copy for the player
|
|
playerLang := language.Copy()
|
|
playerLang.SetSaveNeeded(true)
|
|
|
|
// Add to player's languages
|
|
if err := pla.languages.Add(playerLang); err != nil {
|
|
return fmt.Errorf("failed to add language to player: %w", err)
|
|
}
|
|
|
|
// Record usage statistics
|
|
pla.manager.RecordLanguageUsage(languageID)
|
|
|
|
if pla.logger != nil {
|
|
pla.logger.LogInfo("Player learned language %s (%d)", language.GetName(), languageID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ForgetLanguage makes the player forget a language
|
|
func (pla *PlayerLanguageAdapter) ForgetLanguage(languageID int32) error {
|
|
// Cannot forget common language
|
|
if languageID == LanguageIDCommon {
|
|
return fmt.Errorf("cannot forget common language")
|
|
}
|
|
|
|
// Check if player knows the language
|
|
if !pla.languages.HasLanguage(languageID) {
|
|
return fmt.Errorf("player does not know language %d", languageID)
|
|
}
|
|
|
|
// Get language name for logging
|
|
language := pla.manager.GetLanguage(languageID)
|
|
langName := "Unknown"
|
|
if language != nil {
|
|
langName = language.GetName()
|
|
}
|
|
|
|
// Remove from player's languages
|
|
if !pla.languages.RemoveLanguage(languageID) {
|
|
return fmt.Errorf("failed to remove language from player")
|
|
}
|
|
|
|
// Reset primary language if this was it
|
|
if pla.primaryLang == languageID {
|
|
pla.primaryLang = LanguageIDCommon
|
|
}
|
|
|
|
if pla.logger != nil {
|
|
pla.logger.LogInfo("Player forgot language %s (%d)", langName, languageID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadPlayerLanguages loads the player's languages from database
|
|
func (pla *PlayerLanguageAdapter) LoadPlayerLanguages(database Database, playerID int32) error {
|
|
if database == nil {
|
|
return fmt.Errorf("database is nil")
|
|
}
|
|
|
|
languages, err := database.LoadPlayerLanguages(playerID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load player languages: %w", err)
|
|
}
|
|
|
|
// Clear current languages
|
|
pla.languages.Clear()
|
|
|
|
// Add loaded languages
|
|
for _, lang := range languages {
|
|
if err := pla.languages.Add(lang); err != nil && pla.logger != nil {
|
|
pla.logger.LogWarning("Failed to add loaded language %d: %v", lang.GetID(), err)
|
|
}
|
|
}
|
|
|
|
// Ensure player knows common language
|
|
if !pla.languages.HasLanguage(LanguageIDCommon) {
|
|
commonLang := pla.manager.GetLanguage(LanguageIDCommon)
|
|
if commonLang != nil {
|
|
playerCommon := commonLang.Copy()
|
|
pla.languages.Add(playerCommon)
|
|
}
|
|
}
|
|
|
|
if pla.logger != nil {
|
|
pla.logger.LogDebug("Loaded %d languages for player", len(languages))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SavePlayerLanguages saves the player's languages to database
|
|
func (pla *PlayerLanguageAdapter) SavePlayerLanguages(database Database, playerID int32) error {
|
|
if database == nil {
|
|
return fmt.Errorf("database is nil")
|
|
}
|
|
|
|
languages := pla.languages.GetAllLanguages()
|
|
|
|
// Save each language that needs saving
|
|
for _, lang := range languages {
|
|
if lang.GetSaveNeeded() {
|
|
if err := database.SavePlayerLanguage(playerID, lang.GetID()); err != nil {
|
|
return fmt.Errorf("failed to save player language %d: %w", lang.GetID(), err)
|
|
}
|
|
lang.SetSaveNeeded(false)
|
|
}
|
|
}
|
|
|
|
if pla.logger != nil {
|
|
pla.logger.LogDebug("Saved languages for player")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChatLanguageProcessor handles multilingual chat processing
|
|
type ChatLanguageProcessor struct {
|
|
manager *LanguageManager
|
|
logger Logger
|
|
}
|
|
|
|
// NewChatLanguageProcessor creates a new chat language processor
|
|
func NewChatLanguageProcessor(manager *LanguageManager, logger Logger) *ChatLanguageProcessor {
|
|
return &ChatLanguageProcessor{
|
|
manager: manager,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// ProcessMessage processes a chat message in a specific language
|
|
func (clp *ChatLanguageProcessor) ProcessMessage(speaker Player, message string, languageID int32) (string, error) {
|
|
if speaker == nil {
|
|
return "", fmt.Errorf("speaker cannot be nil")
|
|
}
|
|
|
|
// Validate language exists
|
|
language := clp.manager.GetLanguage(languageID)
|
|
if language == nil {
|
|
return "", fmt.Errorf("language %d does not exist", languageID)
|
|
}
|
|
|
|
// Record language usage
|
|
clp.manager.RecordLanguageUsage(languageID)
|
|
|
|
return message, nil
|
|
}
|
|
|
|
// FilterMessage filters a message for a listener based on language comprehension
|
|
func (clp *ChatLanguageProcessor) FilterMessage(listener Player, message string, languageID int32) string {
|
|
if listener == nil {
|
|
return message
|
|
}
|
|
|
|
// Common language is always understood
|
|
if languageID == LanguageIDCommon {
|
|
return message
|
|
}
|
|
|
|
// For now, we'll always return the message since we can't check language knowledge
|
|
// This would need integration with a PlayerLanguageAdapter or similar
|
|
return message
|
|
}
|
|
|
|
// GetLanguageSkramble scrambles a message based on comprehension level
|
|
func (clp *ChatLanguageProcessor) GetLanguageSkramble(message string, comprehension float32) string {
|
|
if comprehension >= 1.0 {
|
|
return message
|
|
}
|
|
|
|
if comprehension <= 0.0 {
|
|
// Complete scramble - replace with gibberish
|
|
runes := []rune(message)
|
|
scrambled := make([]rune, len(runes))
|
|
|
|
for i, r := range runes {
|
|
if r == ' ' {
|
|
scrambled[i] = ' '
|
|
} else if r >= 'a' && r <= 'z' {
|
|
scrambled[i] = 'a' + rune((int(r-'a')+7)%26)
|
|
} else if r >= 'A' && r <= 'Z' {
|
|
scrambled[i] = 'A' + rune((int(r-'A')+7)%26)
|
|
} else {
|
|
scrambled[i] = r
|
|
}
|
|
}
|
|
|
|
return string(scrambled)
|
|
}
|
|
|
|
// Partial comprehension - scramble some words
|
|
// This is a simplified implementation
|
|
return message
|
|
}
|
|
|
|
// LanguageEventAdapter handles language-related events
|
|
type LanguageEventAdapter struct {
|
|
handler LanguageHandler
|
|
logger Logger
|
|
}
|
|
|
|
// NewLanguageEventAdapter creates a new language event adapter
|
|
func NewLanguageEventAdapter(handler LanguageHandler, logger Logger) *LanguageEventAdapter {
|
|
return &LanguageEventAdapter{
|
|
handler: handler,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// ProcessLanguageEvent processes a language-related event
|
|
func (lea *LanguageEventAdapter) ProcessLanguageEvent(eventType string, player Player, languageID int32, data any) {
|
|
if lea.handler == nil {
|
|
return
|
|
}
|
|
|
|
switch eventType {
|
|
case "language_learned":
|
|
if err := lea.handler.OnLanguageLearned(player, languageID); err != nil && lea.logger != nil {
|
|
lea.logger.LogError("Language learned handler failed: %v", err)
|
|
}
|
|
|
|
case "language_forgotten":
|
|
if err := lea.handler.OnLanguageForgotten(player, languageID); err != nil && lea.logger != nil {
|
|
lea.logger.LogError("Language forgotten handler failed: %v", err)
|
|
}
|
|
|
|
case "language_used":
|
|
if message, ok := data.(string); ok {
|
|
if err := lea.handler.OnLanguageUsed(player, languageID, message); err != nil && lea.logger != nil {
|
|
lea.logger.LogError("Language used handler failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// LanguagePacketBuilder handles language-related packet building
|
|
type LanguagePacketBuilder struct {
|
|
logger Logger
|
|
}
|
|
|
|
// NewLanguagePacketBuilder creates a new language packet builder
|
|
func NewLanguagePacketBuilder(logger Logger) *LanguagePacketBuilder {
|
|
return &LanguagePacketBuilder{
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// BuildLanguagesPacket builds the WS_Languages packet for a player
|
|
// This packet sends the player's known languages and current language to the client
|
|
func (lpb *LanguagePacketBuilder) BuildLanguagesPacket(languages []*Language, currentLanguageID int32) map[string]any {
|
|
data := make(map[string]any)
|
|
|
|
// Number of languages the player knows
|
|
data["num_languages"] = int8(len(languages))
|
|
|
|
// Array of language IDs
|
|
languageIDs := make([]int8, len(languages))
|
|
for i, lang := range languages {
|
|
languageIDs[i] = int8(lang.GetID())
|
|
}
|
|
data["language_array"] = languageIDs
|
|
|
|
// Unknown field (from packet structure)
|
|
data["unknown"] = int8(0)
|
|
|
|
// Current active language
|
|
data["current_language"] = int8(currentLanguageID)
|
|
|
|
if lpb.logger != nil {
|
|
lpb.logger.LogDebug("Built Languages packet: %d languages, current: %d", len(languages), currentLanguageID)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// BuildHearChatPacket builds a HearChat packet with language support
|
|
// This is used when players speak in different languages
|
|
func (lpb *LanguagePacketBuilder) BuildHearChatPacket(speakerName string, message string, languageID int32, channel int16) map[string]any {
|
|
data := make(map[string]any)
|
|
|
|
// Basic chat data
|
|
data["from"] = speakerName
|
|
data["message"] = message
|
|
data["language"] = int8(languageID)
|
|
data["channel"] = channel
|
|
|
|
if lpb.logger != nil {
|
|
lpb.logger.LogDebug("Built HearChat packet: %s speaking %s in language %d", speakerName, message, languageID)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// BuildPlayFlavorPacket builds a PlayFlavor packet with language support
|
|
// This is used for emotes and flavor text that can be in different languages
|
|
func (lpb *LanguagePacketBuilder) BuildPlayFlavorPacket(targetName string, message string, languageID int32, emoteID int32) map[string]any {
|
|
data := make(map[string]any)
|
|
|
|
data["target"] = targetName
|
|
data["message"] = message
|
|
data["language"] = int8(languageID)
|
|
data["emote"] = emoteID
|
|
|
|
if lpb.logger != nil {
|
|
lpb.logger.LogDebug("Built PlayFlavor packet: %s with message %s in language %d", targetName, message, languageID)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// BuildUpdateSkillBookPacket builds skill book update packet with language information
|
|
// This is used when showing skill information that may include language requirements
|
|
func (lpb *LanguagePacketBuilder) BuildUpdateSkillBookPacket(skills []SkillInfo) map[string]any {
|
|
data := make(map[string]any)
|
|
|
|
// This would be expanded based on the skill book packet structure
|
|
// For now, we'll focus on language-related fields
|
|
for _, skill := range skills {
|
|
// Mark language skills appropriately
|
|
if skill.IsLanguageSkill {
|
|
data["language_unknown"] = int8(0) // 0 = known, 1 = unknown
|
|
}
|
|
}
|
|
|
|
if lpb.logger != nil {
|
|
lpb.logger.LogDebug("Built UpdateSkillBook packet with %d skills", len(skills))
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// SkillInfo represents skill information for packet building
|
|
type SkillInfo struct {
|
|
ID int32
|
|
Name string
|
|
IsLanguageSkill bool
|
|
Value int16
|
|
MaxValue int16
|
|
}
|
|
|
|
// LanguagePacketHandler handles incoming language-related packets from clients
|
|
type LanguagePacketHandler struct {
|
|
manager *LanguageManager
|
|
builder *LanguagePacketBuilder
|
|
logger Logger
|
|
}
|
|
|
|
// NewLanguagePacketHandler creates a new language packet handler
|
|
func NewLanguagePacketHandler(manager *LanguageManager, builder *LanguagePacketBuilder, logger Logger) *LanguagePacketHandler {
|
|
return &LanguagePacketHandler{
|
|
manager: manager,
|
|
builder: builder,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// HandleLanguageChangeRequest handles requests to change the player's active language
|
|
func (lph *LanguagePacketHandler) HandleLanguageChangeRequest(playerID int32, languageID int32) (map[string]any, error) {
|
|
// Validate language exists
|
|
language := lph.manager.GetLanguage(languageID)
|
|
if language == nil {
|
|
return nil, fmt.Errorf("language %d does not exist", languageID)
|
|
}
|
|
|
|
// Record language usage
|
|
lph.manager.RecordLanguageUsage(languageID)
|
|
|
|
if lph.logger != nil {
|
|
lph.logger.LogInfo("Player %d changed to language %d (%s)", playerID, languageID, language.GetName())
|
|
}
|
|
|
|
// Build response packet - this would typically be sent back to confirm the change
|
|
// For now, we'll return the Languages packet with updated current language
|
|
return lph.buildLanguagesResponsePacket(playerID, languageID)
|
|
}
|
|
|
|
// buildLanguagesResponsePacket builds a response packet for language changes
|
|
func (lph *LanguagePacketHandler) buildLanguagesResponsePacket(playerID int32, currentLanguageID int32) (map[string]any, error) {
|
|
// In a real implementation, you would get the player's known languages from their PlayerLanguageAdapter
|
|
// For now, we'll return a basic response with common language
|
|
languages := []*Language{}
|
|
|
|
// Add common language (always known)
|
|
if commonLang := lph.manager.GetLanguage(LanguageIDCommon); commonLang != nil {
|
|
languages = append(languages, commonLang)
|
|
}
|
|
|
|
// Add the requested language if it exists and is different from common
|
|
if currentLanguageID != LanguageIDCommon {
|
|
if requestedLang := lph.manager.GetLanguage(currentLanguageID); requestedLang != nil {
|
|
languages = append(languages, requestedLang)
|
|
}
|
|
}
|
|
|
|
return lph.builder.BuildLanguagesPacket(languages, currentLanguageID), nil
|
|
}
|
|
|
|
// HandleChatMessage handles chat messages with language support
|
|
func (lph *LanguagePacketHandler) HandleChatMessage(speakerName string, message string, languageID int32, channel int16, listeners []Player) []map[string]any {
|
|
var packets []map[string]any
|
|
|
|
// Process message for each listener
|
|
for range listeners {
|
|
// Check if listener can understand the language
|
|
processedMessage := message
|
|
|
|
// In a real implementation, you would check if the listener knows the language
|
|
// and potentially scramble the message if they don't understand it
|
|
// For now, we'll just pass through the message
|
|
|
|
// Build packet for this listener
|
|
packet := lph.builder.BuildHearChatPacket(speakerName, processedMessage, languageID, channel)
|
|
packets = append(packets, packet)
|
|
}
|
|
|
|
// Record language usage
|
|
lph.manager.RecordLanguageUsage(languageID)
|
|
|
|
if lph.logger != nil {
|
|
lph.logger.LogDebug("Processed chat message for %d listeners in language %d", len(listeners), languageID)
|
|
}
|
|
|
|
return packets
|
|
} |