499 lines
15 KiB
Markdown
499 lines
15 KiB
Markdown
# Alternate Advancement System
|
|
|
|
The alternate advancement system (`internal/alt_advancement`) provides comprehensive character progression beyond normal leveling for the EQ2Go server emulator. This system is converted from the original C++ EQ2EMu AltAdvancement implementation with modern Go concurrency patterns and clean architecture principles.
|
|
|
|
## Overview
|
|
|
|
The alternate advancement (AA) system manages character progression through specialized skill trees including:
|
|
|
|
- **Class Trees**: Class-specific advancement paths
|
|
- **Subclass Trees**: Subclass-specific specializations
|
|
- **Heroic Trees**: Heroic advancement from Destiny of Velious
|
|
- **Shadows Trees**: Shadow-based abilities from Shadows of Luclin
|
|
- **Tradeskill Trees**: Tradeskill-focused advancement
|
|
- **Prestige Trees**: Prestigious advancement paths
|
|
- **Dragon Trees**: Dragon-themed advancement
|
|
- **Far Seas Trees**: Far Seas trading company advancement
|
|
|
|
## Architecture
|
|
|
|
### Core Components
|
|
|
|
**MasterAAList** - Global repository of all AA definitions with fast lookup capabilities
|
|
**MasterAANodeList** - Tree node configurations mapping classes to AA trees
|
|
**AAManager** - Central management system for all AA operations
|
|
**AAPlayerState** - Individual player AA progression and template management
|
|
**DatabaseImpl** - Database operations for persistent AA data
|
|
|
|
### Key Files
|
|
|
|
- `types.go` - Core data structures and type definitions
|
|
- `constants.go` - All AA system constants, tab definitions, and limits
|
|
- `master_list.go` - MasterAAList and MasterAANodeList implementations
|
|
- `manager.go` - Central AAManager with player state management
|
|
- `database.go` - Database operations and persistence
|
|
- `interfaces.go` - System integration interfaces and adapters
|
|
- `README.md` - This documentation
|
|
|
|
## System Initialization
|
|
|
|
```go
|
|
// Create AA manager with configuration
|
|
config := alt_advancement.DefaultAAManagerConfig()
|
|
config.EnableCaching = true
|
|
config.DatabaseEnabled = true
|
|
|
|
aaManager := alt_advancement.NewAAManager(config)
|
|
|
|
// Set up database integration
|
|
database := alt_advancement.NewDatabaseImpl(db, masterAAList, masterNodeList, logger)
|
|
aaManager.SetDatabase(database)
|
|
|
|
// Start the system
|
|
err := aaManager.Start()
|
|
if err != nil {
|
|
log.Fatalf("Failed to start AA system: %v", err)
|
|
}
|
|
```
|
|
|
|
## AA Data Management
|
|
|
|
```go
|
|
// Load all AA data from database
|
|
err := aaManager.LoadAAData()
|
|
if err != nil {
|
|
log.Printf("Failed to load AA data: %v", err)
|
|
}
|
|
|
|
// Get specific AA by node ID
|
|
aaData, err := aaManager.GetAA(nodeID)
|
|
if err != nil {
|
|
log.Printf("AA not found: %v", err)
|
|
}
|
|
|
|
// Get AAs for a specific class
|
|
classAAs, err := aaManager.GetAAsByClass(classID)
|
|
fmt.Printf("Found %d AAs for class %d\n", len(classAAs), classID)
|
|
|
|
// Get AAs for a specific tab/group
|
|
tabAAs, err := aaManager.GetAAsByGroup(alt_advancement.AA_CLASS)
|
|
fmt.Printf("Class tab has %d AAs\n", len(tabAAs))
|
|
```
|
|
|
|
## Player AA Management
|
|
|
|
```go
|
|
// Load player's AA data
|
|
characterID := int32(12345)
|
|
playerState, err := aaManager.LoadPlayerAA(characterID)
|
|
if err != nil {
|
|
log.Printf("Failed to load player AA: %v", err)
|
|
}
|
|
|
|
// Get player's AA point totals
|
|
totalPoints, spentPoints, availablePoints, err := aaManager.GetAAPoints(characterID)
|
|
fmt.Printf("Player has %d total, %d spent, %d available AA points\n",
|
|
totalPoints, spentPoints, availablePoints)
|
|
|
|
// Award AA points to player
|
|
err = aaManager.AwardAAPoints(characterID, 10, "Level up bonus")
|
|
if err != nil {
|
|
log.Printf("Failed to award AA points: %v", err)
|
|
}
|
|
```
|
|
|
|
## AA Purchasing System
|
|
|
|
```go
|
|
// Purchase an AA for a player
|
|
nodeID := int32(1001)
|
|
targetRank := int8(1)
|
|
|
|
err := aaManager.PurchaseAA(characterID, nodeID, targetRank)
|
|
if err != nil {
|
|
log.Printf("AA purchase failed: %v", err)
|
|
} else {
|
|
fmt.Println("AA purchased successfully!")
|
|
}
|
|
|
|
// Refund an AA
|
|
err = aaManager.RefundAA(characterID, nodeID)
|
|
if err != nil {
|
|
log.Printf("AA refund failed: %v", err)
|
|
}
|
|
|
|
// Check available AAs for a tab
|
|
availableAAs, err := aaManager.GetAvailableAAs(characterID, alt_advancement.AA_CLASS)
|
|
fmt.Printf("Player can purchase %d AAs in class tab\n", len(availableAAs))
|
|
```
|
|
|
|
## AA Templates System
|
|
|
|
```go
|
|
// Change active AA template
|
|
templateID := int8(alt_advancement.AA_TEMPLATE_PERSONAL_1)
|
|
err := aaManager.ChangeAATemplate(characterID, templateID)
|
|
if err != nil {
|
|
log.Printf("Template change failed: %v", err)
|
|
}
|
|
|
|
// Save custom AA template
|
|
err = aaManager.SaveAATemplate(characterID, templateID, "My Build")
|
|
if err != nil {
|
|
log.Printf("Template save failed: %v", err)
|
|
}
|
|
|
|
// Get all templates for player
|
|
templates, err := aaManager.GetAATemplates(characterID)
|
|
if err != nil {
|
|
log.Printf("Failed to get templates: %v", err)
|
|
} else {
|
|
for id, template := range templates {
|
|
fmt.Printf("Template %d: %s (%d entries)\n",
|
|
id, template.Name, len(template.Entries))
|
|
}
|
|
}
|
|
```
|
|
|
|
## AA Data Structures
|
|
|
|
### AltAdvanceData - Individual AA Definition
|
|
|
|
```go
|
|
type AltAdvanceData struct {
|
|
SpellID int32 // Associated spell ID
|
|
NodeID int32 // Unique node identifier
|
|
Name string // Display name
|
|
Description string // AA description
|
|
Group int8 // Tab/group (AA_CLASS, AA_SUBCLASS, etc.)
|
|
Col int8 // Column position in tree
|
|
Row int8 // Row position in tree
|
|
Icon int16 // Display icon ID
|
|
RankCost int8 // Cost per rank
|
|
MaxRank int8 // Maximum achievable rank
|
|
MinLevel int8 // Minimum character level
|
|
RankPrereqID int32 // Prerequisite AA node ID
|
|
RankPrereq int8 // Required rank in prerequisite
|
|
ClassReq int8 // Required class (0 = all classes)
|
|
// ... additional fields
|
|
}
|
|
```
|
|
|
|
### AAPlayerState - Player AA Progression
|
|
|
|
```go
|
|
type AAPlayerState struct {
|
|
CharacterID int32 // Character identifier
|
|
TotalPoints int32 // Total AA points earned
|
|
SpentPoints int32 // Total AA points spent
|
|
AvailablePoints int32 // Available AA points for spending
|
|
BankedPoints int32 // Banked AA points
|
|
ActiveTemplate int8 // Currently active template
|
|
Templates map[int8]*AATemplate // All templates
|
|
Tabs map[int8]*AATab // Tab states
|
|
AAProgress map[int32]*PlayerAAData // AA progression by node ID
|
|
// ... synchronization and metadata
|
|
}
|
|
```
|
|
|
|
## AA Tab System
|
|
|
|
The system supports 10 different AA tabs:
|
|
|
|
```go
|
|
// AA tab constants
|
|
const (
|
|
AA_CLASS = 0 // Class-specific abilities
|
|
AA_SUBCLASS = 1 // Subclass specializations
|
|
AA_SHADOW = 2 // Shadow abilities
|
|
AA_HEROIC = 3 // Heroic advancement
|
|
AA_TRADESKILL = 4 // Tradeskill abilities
|
|
AA_PRESTIGE = 5 // Prestige advancement
|
|
AA_TRADESKILL_PRESTIGE = 6 // Tradeskill prestige
|
|
AA_DRAGON = 7 // Dragon abilities
|
|
AA_DRAGONCLASS = 8 // Dragon class abilities
|
|
AA_FARSEAS = 9 // Far Seas abilities
|
|
)
|
|
|
|
// Get maximum AA points for each tab
|
|
maxClassAA := alt_advancement.GetMaxAAForTab(alt_advancement.AA_CLASS) // 100
|
|
maxHeroicAA := alt_advancement.GetMaxAAForTab(alt_advancement.AA_HEROIC) // 50
|
|
```
|
|
|
|
## Database Integration
|
|
|
|
```go
|
|
// Custom database implementation
|
|
type MyAADatabase struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func (db *MyAADatabase) LoadAltAdvancements() error {
|
|
// Load AA definitions from database
|
|
return nil
|
|
}
|
|
|
|
func (db *MyAADatabase) LoadPlayerAA(characterID int32) (*AAPlayerState, error) {
|
|
// Load player AA data from database
|
|
return nil, nil
|
|
}
|
|
|
|
func (db *MyAADatabase) SavePlayerAA(playerState *AAPlayerState) error {
|
|
// Save player AA data to database
|
|
return nil
|
|
}
|
|
|
|
// Set database implementation
|
|
aaManager.SetDatabase(&MyAADatabase{db: myDB})
|
|
```
|
|
|
|
## Event Handling
|
|
|
|
```go
|
|
// Custom event handler
|
|
type MyAAEventHandler struct{}
|
|
|
|
func (h *MyAAEventHandler) OnAAPurchased(characterID int32, nodeID int32, newRank int8, pointsSpent int32) error {
|
|
fmt.Printf("Player %d purchased AA %d rank %d for %d points\n",
|
|
characterID, nodeID, newRank, pointsSpent)
|
|
return nil
|
|
}
|
|
|
|
func (h *MyAAEventHandler) OnAATemplateChanged(characterID int32, oldTemplate, newTemplate int8) error {
|
|
fmt.Printf("Player %d changed template from %d to %d\n",
|
|
characterID, oldTemplate, newTemplate)
|
|
return nil
|
|
}
|
|
|
|
func (h *MyAAEventHandler) OnPlayerAAPointsChanged(characterID int32, oldPoints, newPoints int32) error {
|
|
fmt.Printf("Player %d AA points changed from %d to %d\n",
|
|
characterID, oldPoints, newPoints)
|
|
return nil
|
|
}
|
|
|
|
// Register event handler
|
|
aaManager.SetEventHandler(&MyAAEventHandler{})
|
|
```
|
|
|
|
## Validation System
|
|
|
|
```go
|
|
// Custom validator
|
|
type MyAAValidator struct{}
|
|
|
|
func (v *MyAAValidator) ValidateAAPurchase(playerState *AAPlayerState, nodeID int32, targetRank int8) error {
|
|
// Check if player has enough points
|
|
if playerState.AvailablePoints < int32(targetRank) {
|
|
return fmt.Errorf("insufficient AA points")
|
|
}
|
|
|
|
// Check prerequisites
|
|
// ... validation logic
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *MyAAValidator) ValidatePlayerLevel(playerState *AAPlayerState, aaData *AltAdvanceData) error {
|
|
// Check minimum level requirement
|
|
// ... validation logic
|
|
return nil
|
|
}
|
|
|
|
// Set validator
|
|
aaManager.SetValidator(&MyAAValidator{})
|
|
```
|
|
|
|
## Statistics and Monitoring
|
|
|
|
```go
|
|
// Get system statistics
|
|
stats := aaManager.GetSystemStats()
|
|
fmt.Printf("Total AAs loaded: %d\n", stats.TotalAAsLoaded)
|
|
fmt.Printf("Active players: %d\n", stats.ActivePlayers)
|
|
fmt.Printf("Total purchases: %d\n", stats.TotalAAPurchases)
|
|
fmt.Printf("Average points spent: %.1f\n", stats.AveragePointsSpent)
|
|
|
|
// Get player-specific statistics
|
|
playerStats := aaManager.GetPlayerStats(characterID)
|
|
fmt.Printf("Player stats: %+v\n", playerStats)
|
|
|
|
// Get database statistics (if database supports it)
|
|
if db, ok := aaManager.database.(*DatabaseImpl); ok {
|
|
dbStats, err := db.GetAAStatistics()
|
|
if err == nil {
|
|
fmt.Printf("Database stats: %+v\n", dbStats)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Configuration Options
|
|
|
|
```go
|
|
// Configure the AA system
|
|
config := alt_advancement.AAManagerConfig{
|
|
EnableAASystem: true,
|
|
EnableCaching: true,
|
|
EnableValidation: true,
|
|
EnableLogging: false,
|
|
AAPointsPerLevel: 2,
|
|
MaxBankedPoints: 30,
|
|
EnableAABanking: true,
|
|
CacheSize: 10000,
|
|
UpdateInterval: 1 * time.Second,
|
|
BatchSize: 100,
|
|
DatabaseEnabled: true,
|
|
AutoSave: true,
|
|
SaveInterval: 5 * time.Minute,
|
|
}
|
|
|
|
aaManager.SetConfig(config)
|
|
```
|
|
|
|
## Caching System
|
|
|
|
```go
|
|
// Enable caching for better performance
|
|
cache := alt_advancement.NewSimpleAACache(1000)
|
|
aaManager.SetCache(cache)
|
|
|
|
// Get cache statistics
|
|
cacheStats := cache.GetStats()
|
|
fmt.Printf("Cache hits: %d, misses: %d\n",
|
|
cacheStats["hits"], cacheStats["misses"])
|
|
```
|
|
|
|
## Packet Handling Integration
|
|
|
|
```go
|
|
// Custom packet handler
|
|
type MyAAPacketHandler struct{}
|
|
|
|
func (ph *MyAAPacketHandler) GetAAListPacket(client any) ([]byte, error) {
|
|
// Build AA list packet for client
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (ph *MyAAPacketHandler) SendAAUpdate(client any, playerState *AAPlayerState) error {
|
|
// Send AA update to client
|
|
return nil
|
|
}
|
|
|
|
func (ph *MyAAPacketHandler) HandleAAPurchase(client any, nodeID int32, rank int8) error {
|
|
// Handle AA purchase from client
|
|
return nil
|
|
}
|
|
|
|
// Set packet handler
|
|
aaManager.SetPacketHandler(&MyAAPacketHandler{})
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
### Custom AA Trees
|
|
|
|
```go
|
|
// Create custom AA data
|
|
customAA := &alt_advancement.AltAdvanceData{
|
|
SpellID: 2001,
|
|
NodeID: 2001,
|
|
Name: "Custom Ability",
|
|
Description: "A custom AA ability",
|
|
Group: alt_advancement.AA_CLASS,
|
|
Col: 1,
|
|
Row: 1,
|
|
Icon: 100,
|
|
RankCost: 1,
|
|
MaxRank: 5,
|
|
MinLevel: 20,
|
|
ClassReq: 1, // Fighter only
|
|
}
|
|
|
|
// Add to master list
|
|
err := masterAAList.AddAltAdvancement(customAA)
|
|
if err != nil {
|
|
log.Printf("Failed to add custom AA: %v", err)
|
|
}
|
|
```
|
|
|
|
### Bulk Operations
|
|
|
|
```go
|
|
// Award AA points to multiple players
|
|
playerIDs := []int32{1001, 1002, 1003}
|
|
for _, playerID := range playerIDs {
|
|
err := aaManager.AwardAAPoints(playerID, 5, "Server event bonus")
|
|
if err != nil {
|
|
log.Printf("Failed to award points to player %d: %v", playerID, err)
|
|
}
|
|
}
|
|
|
|
// Batch save player states
|
|
for _, playerID := range playerIDs {
|
|
err := aaManager.SavePlayerAA(playerID)
|
|
if err != nil {
|
|
log.Printf("Failed to save player %d AA data: %v", playerID, err)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Thread Safety
|
|
|
|
All AA operations are thread-safe using appropriate synchronization:
|
|
|
|
- **RWMutex** for read-heavy operations (AA lookups, player state access)
|
|
- **Atomic operations** for simple counters and flags
|
|
- **Proper lock ordering** to prevent deadlocks
|
|
- **Background goroutines** for periodic processing and auto-save
|
|
- **Channel-based communication** for event handling
|
|
|
|
## Performance Considerations
|
|
|
|
- **Efficient data structures** with hash maps for O(1) lookups
|
|
- **Caching system** to reduce database queries
|
|
- **Batch processing** for bulk operations
|
|
- **Background processing** to avoid blocking gameplay
|
|
- **Statistics collection** with minimal overhead
|
|
- **Memory-efficient storage** with proper cleanup
|
|
|
|
## Integration with Other Systems
|
|
|
|
The AA system integrates with:
|
|
|
|
- **Player System** - Player-specific AA progression and point management
|
|
- **Spell System** - AA abilities are linked to spells
|
|
- **Class System** - Class-specific AA trees and requirements
|
|
- **Level System** - Level-based AA point awards and prerequisites
|
|
- **Database System** - Persistent storage of AA data and player progress
|
|
- **Client System** - AA UI updates and purchase handling
|
|
- **Achievement System** - AA milestones and progression tracking
|
|
|
|
## Migration from C++
|
|
|
|
This Go implementation maintains compatibility with the original C++ EQ2EMu AA system while providing:
|
|
|
|
- **Modern concurrency** with goroutines and channels
|
|
- **Better error handling** with Go's error interface
|
|
- **Cleaner architecture** with interface-based design
|
|
- **Improved maintainability** with package organization
|
|
- **Enhanced testing** capabilities
|
|
- **Type safety** with Go's type system
|
|
- **Memory management** with Go's garbage collector
|
|
|
|
## TODO Items
|
|
|
|
The conversion includes areas for future implementation:
|
|
|
|
- **Complete packet handling** for all client communication
|
|
- **Advanced validation** for complex AA prerequisites
|
|
- **Lua scripting integration** for custom AA behaviors
|
|
- **Web administration interface** for AA management
|
|
- **Performance optimizations** for large-scale deployments
|
|
- **Advanced caching strategies** with TTL and eviction policies
|
|
- **Metrics and monitoring** integration with external systems
|
|
- **AA import/export** functionality for configuration management
|
|
|
|
## Usage Examples
|
|
|
|
See the code examples throughout this documentation for detailed usage patterns. The system is designed to be used alongside the existing EQ2Go server infrastructure with proper initialization and configuration.
|
|
|
|
The AA system provides a solid foundation for character progression mechanics while maintaining the flexibility to extend and customize behavior through the comprehensive interface system. |