321 lines
9.2 KiB
Markdown
321 lines
9.2 KiB
Markdown
# Languages Package
|
|
|
|
The languages package provides comprehensive multilingual character communication system for EverQuest II server emulation. It manages language learning, chat processing, and player language knowledge with thread-safe operations and efficient lookups.
|
|
|
|
## Features
|
|
|
|
- **Master Language Registry**: Global list of all available languages with ID-based and name-based lookups
|
|
- **Player Language Management**: Individual player language collections with learning/forgetting mechanics
|
|
- **Chat Processing**: Multilingual message processing with scrambling for unknown languages
|
|
- **Database Integration**: Language persistence with player-specific language knowledge
|
|
- **Thread Safety**: All operations use proper Go concurrency patterns with mutexes
|
|
- **Performance**: Optimized hash table lookups with benchmark results ~15ns per operation
|
|
- **EQ2 Compatibility**: Supports all standard EverQuest II racial languages
|
|
|
|
## Core Components
|
|
|
|
### Language Types
|
|
|
|
```go
|
|
// Core language representation
|
|
type Language struct {
|
|
id int32 // Unique language identifier
|
|
name string // Language name
|
|
saveNeeded bool // Whether this language needs database saving
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// Master language registry (system-wide)
|
|
type MasterLanguagesList struct {
|
|
languages map[int32]*Language // Languages indexed by ID
|
|
nameIndex map[string]*Language // Languages indexed by name
|
|
mutex sync.RWMutex // Thread safety
|
|
}
|
|
|
|
// Player-specific language collection
|
|
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
|
|
}
|
|
```
|
|
|
|
### Management System
|
|
|
|
```go
|
|
// High-level language system manager
|
|
type Manager struct {
|
|
masterLanguagesList *MasterLanguagesList
|
|
database Database
|
|
logger Logger
|
|
// Statistics tracking
|
|
languageLookups int64
|
|
playersWithLanguages int64
|
|
languageUsageCount map[int32]int64
|
|
}
|
|
```
|
|
|
|
### Integration Interfaces
|
|
|
|
```go
|
|
// Entity interface for basic entity operations
|
|
type Entity interface {
|
|
GetID() int32
|
|
GetName() string
|
|
IsPlayer() bool
|
|
IsNPC() bool
|
|
IsBot() bool
|
|
}
|
|
|
|
// Player interface extending Entity for player-specific operations
|
|
type Player interface {
|
|
Entity
|
|
GetCharacterID() int32
|
|
SendMessage(message string)
|
|
}
|
|
|
|
// LanguageAware interface for entities with 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
|
|
}
|
|
```
|
|
|
|
## Standard EQ2 Languages
|
|
|
|
The package includes all standard EverQuest II racial languages:
|
|
|
|
```go
|
|
const (
|
|
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
|
|
)
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Basic Setup
|
|
|
|
```go
|
|
// Create language system
|
|
database := NewMockDatabase() // or your database implementation
|
|
logger := NewMockLogger() // or your logger implementation
|
|
manager := NewManager(database, logger)
|
|
|
|
// Add standard EQ2 languages
|
|
languages := map[int32]string{
|
|
LanguageIDCommon: "Common",
|
|
LanguageIDElvish: "Elvish",
|
|
LanguageIDDwarven: "Dwarven",
|
|
// ... add other languages
|
|
}
|
|
|
|
for id, name := range languages {
|
|
lang := NewLanguage()
|
|
lang.SetID(id)
|
|
lang.SetName(name)
|
|
manager.AddLanguage(lang)
|
|
}
|
|
```
|
|
|
|
### Player Integration
|
|
|
|
```go
|
|
// Player struct implementing LanguageAware
|
|
type Player struct {
|
|
id int32
|
|
name string
|
|
languageAdapter *PlayerLanguageAdapter
|
|
}
|
|
|
|
func NewPlayer(id int32, name string, languageManager *Manager, logger Logger) *Player {
|
|
return &Player{
|
|
id: id,
|
|
name: name,
|
|
languageAdapter: NewPlayerLanguageAdapter(languageManager, logger),
|
|
}
|
|
}
|
|
|
|
// Implement LanguageAware interface by delegating to adapter
|
|
func (p *Player) GetKnownLanguages() *PlayerLanguagesList {
|
|
return p.languageAdapter.GetKnownLanguages()
|
|
}
|
|
|
|
func (p *Player) KnowsLanguage(languageID int32) bool {
|
|
return p.languageAdapter.KnowsLanguage(languageID)
|
|
}
|
|
|
|
func (p *Player) LearnLanguage(languageID int32) error {
|
|
return p.languageAdapter.LearnLanguage(languageID)
|
|
}
|
|
|
|
// ... implement other LanguageAware methods
|
|
```
|
|
|
|
### Chat Processing
|
|
|
|
```go
|
|
// Create chat processor
|
|
processor := NewChatLanguageProcessor(manager, logger)
|
|
|
|
// Process message from speaker
|
|
message := "Mae govannen!" // Elvish greeting
|
|
processed, err := processor.ProcessMessage(speaker, message, LanguageIDElvish)
|
|
if err != nil {
|
|
// Handle error (speaker doesn't know language, etc.)
|
|
}
|
|
|
|
// Filter message for listener based on their language knowledge
|
|
filtered := processor.FilterMessage(listener, processed, LanguageIDElvish)
|
|
// If listener doesn't know Elvish, message would be scrambled
|
|
|
|
// Language scrambling for unknown languages
|
|
scrambled := processor.GetLanguageSkramble(message, 0.0) // 0% comprehension
|
|
// Returns scrambled version: "Nhl nbchuulu!"
|
|
```
|
|
|
|
### Database Operations
|
|
|
|
```go
|
|
// Player learning languages with database persistence
|
|
player := NewPlayer(123, "TestPlayer", manager, logger)
|
|
|
|
// Learn a language
|
|
err := player.LearnLanguage(LanguageIDElvish)
|
|
if err != nil {
|
|
log.Printf("Failed to learn language: %v", err)
|
|
}
|
|
|
|
// Save to database
|
|
err = player.languageAdapter.SavePlayerLanguages(database, player.GetCharacterID())
|
|
if err != nil {
|
|
log.Printf("Failed to save languages: %v", err)
|
|
}
|
|
|
|
// Load from database (e.g., on player login)
|
|
newPlayer := NewPlayer(123, "TestPlayer", manager, logger)
|
|
err = newPlayer.languageAdapter.LoadPlayerLanguages(database, player.GetCharacterID())
|
|
if err != nil {
|
|
log.Printf("Failed to load languages: %v", err)
|
|
}
|
|
```
|
|
|
|
### Statistics and Management
|
|
|
|
```go
|
|
// Get system statistics
|
|
stats := manager.GetStatistics()
|
|
fmt.Printf("Total Languages: %d\\n", stats.TotalLanguages)
|
|
fmt.Printf("Players with Languages: %d\\n", stats.PlayersWithLanguages)
|
|
fmt.Printf("Language Lookups: %d\\n", stats.LanguageLookups)
|
|
|
|
// Process management commands
|
|
result, err := manager.ProcessCommand("stats", []string{})
|
|
if err != nil {
|
|
log.Printf("Command failed: %v", err)
|
|
} else {
|
|
fmt.Println(result)
|
|
}
|
|
|
|
// Validate all languages
|
|
issues := manager.ValidateAllLanguages()
|
|
if len(issues) > 0 {
|
|
log.Printf("Language validation issues: %v", issues)
|
|
}
|
|
```
|
|
|
|
## Database Interface
|
|
|
|
Implement the `Database` interface to provide persistence:
|
|
|
|
```go
|
|
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
|
|
}
|
|
```
|
|
|
|
## Performance
|
|
|
|
Benchmark results on AMD Ryzen 7 8845HS:
|
|
|
|
- **Language Lookup**: ~15ns per operation
|
|
- **Player Language Adapter**: ~8ns per operation
|
|
- **Integrated Operations**: ~16ns per operation
|
|
|
|
The system uses hash tables for O(1) lookups and is optimized for high-frequency operations.
|
|
|
|
## Thread Safety
|
|
|
|
All operations are thread-safe using:
|
|
- `sync.RWMutex` for read-heavy operations (language lookups)
|
|
- `sync.Mutex` for write operations and statistics
|
|
- Atomic operations where appropriate
|
|
|
|
## System Limits
|
|
|
|
```go
|
|
const (
|
|
MaxLanguagesPerPlayer = 100 // Reasonable limit per player
|
|
MaxTotalLanguages = 1000 // System-wide language limit
|
|
MaxLanguageNameLength = 50 // Maximum language name length
|
|
)
|
|
```
|
|
|
|
## Command Interface
|
|
|
|
The manager supports administrative commands:
|
|
|
|
- `stats` - Show language system statistics
|
|
- `list` - List all languages
|
|
- `info <id|name>` - Show language information
|
|
- `validate` - Validate all languages
|
|
- `reload` - Reload from database
|
|
- `add <id> <name>` - Add new language
|
|
- `remove <id>` - Remove language
|
|
- `search <term>` - Search languages by name
|
|
|
|
## Testing
|
|
|
|
Comprehensive test suite includes:
|
|
|
|
- Unit tests for all core components
|
|
- Integration tests with mock implementations
|
|
- Performance benchmarks
|
|
- Entity integration examples
|
|
- Thread safety validation
|
|
|
|
Run tests:
|
|
```bash
|
|
go test ./internal/languages -v
|
|
go test ./internal/languages -bench=.
|
|
```
|
|
|
|
## Integration Notes
|
|
|
|
1. **Entity Integration**: Players should embed `PlayerLanguageAdapter` and implement `LanguageAware`
|
|
2. **Database**: Implement the `Database` interface for persistence
|
|
3. **Logging**: Implement the `Logger` interface for system logging
|
|
4. **Chat System**: Use `ChatLanguageProcessor` for multilingual message handling
|
|
5. **Race Integration**: Initialize players with their racial languages at character creation
|
|
|
|
The languages package follows the same architectural patterns as other EQ2Go systems and integrates seamlessly with the entity, player, and chat systems. |