603 lines
17 KiB
Go
603 lines
17 KiB
Go
package alt_advancement
|
|
|
|
import (
|
|
"database/sql"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AADatabase interface for database operations
|
|
type AADatabase interface {
|
|
// Core data loading
|
|
LoadAltAdvancements() error
|
|
LoadTreeNodes() error
|
|
|
|
// Player data operations
|
|
LoadPlayerAA(characterID int32) (*AAPlayerState, error)
|
|
SavePlayerAA(playerState *AAPlayerState) error
|
|
DeletePlayerAA(characterID int32) error
|
|
|
|
// Template operations
|
|
LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error)
|
|
|
|
// Statistics
|
|
GetAAStatistics() (map[string]interface{}, error)
|
|
}
|
|
|
|
// AAPacketHandler interface for handling AA-related packets
|
|
type AAPacketHandler interface {
|
|
// List packets
|
|
GetAAListPacket(client interface{}) ([]byte, error)
|
|
SendAAUpdate(client interface{}, playerState *AAPlayerState) error
|
|
|
|
// Purchase packets
|
|
HandleAAPurchase(client interface{}, nodeID int32, rank int8) error
|
|
SendAAPurchaseResponse(client interface{}, success bool, nodeID int32, newRank int8) error
|
|
|
|
// Template packets
|
|
SendAATemplateList(client interface{}, templates map[int8]*AATemplate) error
|
|
HandleAATemplateChange(client interface{}, templateID int8) error
|
|
|
|
// Display packets
|
|
DisplayAA(client interface{}, templateID int8, changeMode int8) error
|
|
SendAATabUpdate(client interface{}, tabID int8, tab *AATab) error
|
|
}
|
|
|
|
// AAEventHandler interface for handling AA events
|
|
type AAEventHandler interface {
|
|
// Purchase events
|
|
OnAAPurchased(characterID int32, nodeID int32, newRank int8, pointsSpent int32) error
|
|
OnAARefunded(characterID int32, nodeID int32, oldRank int8, pointsRefunded int32) error
|
|
|
|
// Template events
|
|
OnAATemplateChanged(characterID int32, oldTemplate, newTemplate int8) error
|
|
OnAATemplateCreated(characterID int32, templateID int8, name string) error
|
|
|
|
// System events
|
|
OnAASystemLoaded(totalAAs int32, totalNodes int32) error
|
|
OnAADataReloaded() error
|
|
|
|
// Player events
|
|
OnPlayerAALoaded(characterID int32, playerState *AAPlayerState) error
|
|
OnPlayerAAPointsChanged(characterID int32, oldPoints, newPoints int32) error
|
|
}
|
|
|
|
// AAValidator interface for validating AA operations
|
|
type AAValidator interface {
|
|
// Purchase validation
|
|
ValidateAAPurchase(playerState *AAPlayerState, nodeID int32, targetRank int8) error
|
|
ValidateAAPrerequisites(playerState *AAPlayerState, aaData *AltAdvanceData) error
|
|
ValidateAAPoints(playerState *AAPlayerState, pointsRequired int32) error
|
|
|
|
// Player validation
|
|
ValidatePlayerLevel(playerState *AAPlayerState, aaData *AltAdvanceData) error
|
|
ValidatePlayerClass(playerState *AAPlayerState, aaData *AltAdvanceData) error
|
|
ValidateExpansionRequirements(playerState *AAPlayerState, aaData *AltAdvanceData) error
|
|
|
|
// Template validation
|
|
ValidateTemplateChange(playerState *AAPlayerState, templateID int8) error
|
|
ValidateTemplateEntries(entries []*AAEntry) error
|
|
|
|
// System validation
|
|
ValidateAAData(aaData *AltAdvanceData) error
|
|
ValidateTreeNodeData(nodeData *TreeNodeData) error
|
|
}
|
|
|
|
// AANotifier interface for sending notifications
|
|
type AANotifier interface {
|
|
// Purchase notifications
|
|
NotifyAAPurchaseSuccess(characterID int32, aaName string, newRank int8) error
|
|
NotifyAAPurchaseFailure(characterID int32, reason string) error
|
|
NotifyAARefund(characterID int32, aaName string, pointsRefunded int32) error
|
|
|
|
// Progress notifications
|
|
NotifyAAProgressUpdate(characterID int32, tabID int8, pointsSpent int32) error
|
|
NotifyAAPointsAwarded(characterID int32, pointsAwarded int32, reason string) error
|
|
|
|
// System notifications
|
|
NotifyAASystemUpdate(message string) error
|
|
NotifyAASystemMaintenance(maintenanceStart time.Time, duration time.Duration) error
|
|
|
|
// Achievement notifications
|
|
NotifyAAMilestone(characterID int32, milestone string, totalPoints int32) error
|
|
NotifyAATreeCompleted(characterID int32, tabID int8, tabName string) error
|
|
}
|
|
|
|
// AAStatistics interface for tracking AA statistics
|
|
type AAStatistics interface {
|
|
// Purchase statistics
|
|
RecordAAPurchase(characterID int32, nodeID int32, pointsSpent int32)
|
|
RecordAARefund(characterID int32, nodeID int32, pointsRefunded int32)
|
|
|
|
// Usage statistics
|
|
RecordAAUsage(characterID int32, nodeID int32, usageType string)
|
|
RecordPlayerLogin(characterID int32, totalAAPoints int32)
|
|
RecordPlayerLogout(characterID int32, sessionDuration time.Duration)
|
|
|
|
// Performance statistics
|
|
RecordDatabaseQuery(queryType string, duration time.Duration)
|
|
RecordPacketSent(packetType string, size int32)
|
|
RecordCacheHit(cacheType string)
|
|
RecordCacheMiss(cacheType string)
|
|
|
|
// Aggregated statistics
|
|
GetAAPurchaseStats() map[int32]int64
|
|
GetPopularAAs() map[int32]int64
|
|
GetPlayerProgressStats() map[string]interface{}
|
|
GetSystemPerformanceStats() map[string]interface{}
|
|
}
|
|
|
|
// AACache interface for caching AA data
|
|
type AACache interface {
|
|
// AA data caching
|
|
GetAA(nodeID int32) (*AltAdvanceData, bool)
|
|
SetAA(nodeID int32, aaData *AltAdvanceData)
|
|
InvalidateAA(nodeID int32)
|
|
|
|
// Player state caching
|
|
GetPlayerState(characterID int32) (*AAPlayerState, bool)
|
|
SetPlayerState(characterID int32, playerState *AAPlayerState)
|
|
InvalidatePlayerState(characterID int32)
|
|
|
|
// Tree node caching
|
|
GetTreeNode(treeID int32) (*TreeNodeData, bool)
|
|
SetTreeNode(treeID int32, nodeData *TreeNodeData)
|
|
InvalidateTreeNode(treeID int32)
|
|
|
|
// Cache management
|
|
Clear()
|
|
GetStats() map[string]interface{}
|
|
SetMaxSize(maxSize int32)
|
|
}
|
|
|
|
// Client interface for client operations (to avoid circular dependencies)
|
|
type Client interface {
|
|
GetCharacterID() int32
|
|
GetPlayer() Player
|
|
SendPacket(data []byte) error
|
|
GetClientVersion() int16
|
|
}
|
|
|
|
// Player interface for player operations (to avoid circular dependencies)
|
|
type Player interface {
|
|
GetCharacterID() int32
|
|
GetLevel() int8
|
|
GetClass() int8
|
|
GetRace() int8
|
|
GetName() string
|
|
GetAdventureClass() int8
|
|
HasExpansion(expansionFlag int8) bool
|
|
}
|
|
|
|
// Transaction interface for database transactions
|
|
type Transaction interface {
|
|
Exec(query string, args ...interface{}) (sql.Result, error)
|
|
Query(query string, args ...interface{}) (*sql.Rows, error)
|
|
QueryRow(query string, args ...interface{}) *sql.Row
|
|
Commit() error
|
|
Rollback() error
|
|
}
|
|
|
|
// DatabaseImpl provides a concrete implementation of AADatabase
|
|
type DatabaseImpl struct {
|
|
db *sql.DB
|
|
masterAAList *MasterAAList
|
|
masterNodeList *MasterAANodeList
|
|
logger *log.Logger
|
|
}
|
|
|
|
// NewDatabaseImpl creates a new database implementation
|
|
func NewDatabaseImpl(db *sql.DB, masterAAList *MasterAAList, masterNodeList *MasterAANodeList, logger *log.Logger) *DatabaseImpl {
|
|
return &DatabaseImpl{
|
|
db: db,
|
|
masterAAList: masterAAList,
|
|
masterNodeList: masterNodeList,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// AAManagerInterface defines the main interface for AA management
|
|
type AAManagerInterface interface {
|
|
// System lifecycle
|
|
Start() error
|
|
Stop() error
|
|
IsRunning() bool
|
|
|
|
// Data loading
|
|
LoadAAData() error
|
|
ReloadAAData() error
|
|
|
|
// Player operations
|
|
LoadPlayerAA(characterID int32) (*AAPlayerState, error)
|
|
SavePlayerAA(characterID int32) error
|
|
GetPlayerAAState(characterID int32) (*AAPlayerState, error)
|
|
|
|
// AA operations
|
|
PurchaseAA(characterID int32, nodeID int32, targetRank int8) error
|
|
RefundAA(characterID int32, nodeID int32) error
|
|
GetAvailableAAs(characterID int32, tabID int8) ([]*AltAdvanceData, error)
|
|
|
|
// Template operations
|
|
ChangeAATemplate(characterID int32, templateID int8) error
|
|
SaveAATemplate(characterID int32, templateID int8, name string) error
|
|
GetAATemplates(characterID int32) (map[int8]*AATemplate, error)
|
|
|
|
// Point operations
|
|
AwardAAPoints(characterID int32, points int32, reason string) error
|
|
GetAAPoints(characterID int32) (int32, int32, int32, error) // total, spent, available
|
|
|
|
// Query operations
|
|
GetAA(nodeID int32) (*AltAdvanceData, error)
|
|
GetAABySpellID(spellID int32) (*AltAdvanceData, error)
|
|
GetAAsByGroup(group int8) ([]*AltAdvanceData, error)
|
|
GetAAsByClass(classID int8) ([]*AltAdvanceData, error)
|
|
|
|
// Statistics
|
|
GetSystemStats() *AAManagerStats
|
|
GetPlayerStats(characterID int32) map[string]interface{}
|
|
|
|
// Configuration
|
|
SetConfig(config AAManagerConfig) error
|
|
GetConfig() AAManagerConfig
|
|
|
|
// Integration
|
|
SetDatabase(db AADatabase)
|
|
SetPacketHandler(handler AAPacketHandler)
|
|
SetEventHandler(handler AAEventHandler)
|
|
SetValidator(validator AAValidator)
|
|
SetNotifier(notifier AANotifier)
|
|
SetStatistics(stats AAStatistics)
|
|
SetCache(cache AACache)
|
|
}
|
|
|
|
// AAAware interface for entities that can interact with the AA system
|
|
type AAAware interface {
|
|
// AA point management
|
|
GetAAPoints() (total, spent, available int32)
|
|
SetAAPoints(total, spent, available int32)
|
|
AwardAAPoints(points int32, reason string) error
|
|
SpendAAPoints(points int32) error
|
|
|
|
// AA progression
|
|
GetAAState() *AAPlayerState
|
|
SetAAState(state *AAPlayerState)
|
|
GetAARank(nodeID int32) int8
|
|
SetAARank(nodeID int32, rank int8) error
|
|
|
|
// Template management
|
|
GetActiveAATemplate() int8
|
|
SetActiveAATemplate(templateID int8) error
|
|
GetAATemplate(templateID int8) *AATemplate
|
|
SaveAATemplate(templateID int8, name string) error
|
|
}
|
|
|
|
// AAAdapter adapts AA functionality for other systems
|
|
type AAAdapter struct {
|
|
manager AAManagerInterface
|
|
characterID int32
|
|
}
|
|
|
|
// NewAAAdapter creates a new AA adapter
|
|
func NewAAAdapter(manager AAManagerInterface, characterID int32) *AAAdapter {
|
|
return &AAAdapter{
|
|
manager: manager,
|
|
characterID: characterID,
|
|
}
|
|
}
|
|
|
|
// GetManager returns the wrapped AA manager
|
|
func (aa *AAAdapter) GetManager() AAManagerInterface {
|
|
return aa.manager
|
|
}
|
|
|
|
// GetCharacterID returns the character ID
|
|
func (aa *AAAdapter) GetCharacterID() int32 {
|
|
return aa.characterID
|
|
}
|
|
|
|
// GetAAPoints returns the character's AA points
|
|
func (aa *AAAdapter) GetAAPoints() (total, spent, available int32, err error) {
|
|
return aa.manager.GetAAPoints(aa.characterID)
|
|
}
|
|
|
|
// PurchaseAA purchases an AA for the character
|
|
func (aa *AAAdapter) PurchaseAA(nodeID int32, targetRank int8) error {
|
|
return aa.manager.PurchaseAA(aa.characterID, nodeID, targetRank)
|
|
}
|
|
|
|
// RefundAA refunds an AA for the character
|
|
func (aa *AAAdapter) RefundAA(nodeID int32) error {
|
|
return aa.manager.RefundAA(aa.characterID, nodeID)
|
|
}
|
|
|
|
// GetAvailableAAs returns available AAs for a tab
|
|
func (aa *AAAdapter) GetAvailableAAs(tabID int8) ([]*AltAdvanceData, error) {
|
|
return aa.manager.GetAvailableAAs(aa.characterID, tabID)
|
|
}
|
|
|
|
// ChangeTemplate changes the active AA template
|
|
func (aa *AAAdapter) ChangeTemplate(templateID int8) error {
|
|
return aa.manager.ChangeAATemplate(aa.characterID, templateID)
|
|
}
|
|
|
|
// GetTemplates returns all AA temples for the character
|
|
func (aa *AAAdapter) GetTemplates() (map[int8]*AATemplate, error) {
|
|
return aa.manager.GetAATemplates(aa.characterID)
|
|
}
|
|
|
|
// GetPlayerStats returns AA statistics for the character
|
|
func (aa *AAAdapter) GetPlayerStats() map[string]interface{} {
|
|
return aa.manager.GetPlayerStats(aa.characterID)
|
|
}
|
|
|
|
// AwardPoints awards AA points to the character
|
|
func (aa *AAAdapter) AwardPoints(points int32, reason string) error {
|
|
return aa.manager.AwardAAPoints(aa.characterID, points, reason)
|
|
}
|
|
|
|
// GetAAState returns the character's complete AA state
|
|
func (aa *AAAdapter) GetAAState() (*AAPlayerState, error) {
|
|
return aa.manager.GetPlayerAAState(aa.characterID)
|
|
}
|
|
|
|
// SaveAAState saves the character's AA state
|
|
func (aa *AAAdapter) SaveAAState() error {
|
|
return aa.manager.SavePlayerAA(aa.characterID)
|
|
}
|
|
|
|
// PlayerAAAdapter adapts player functionality for AA systems
|
|
type PlayerAAAdapter struct {
|
|
player Player
|
|
}
|
|
|
|
// NewPlayerAAAdapter creates a new player AA adapter
|
|
func NewPlayerAAAdapter(player Player) *PlayerAAAdapter {
|
|
return &PlayerAAAdapter{player: player}
|
|
}
|
|
|
|
// GetPlayer returns the wrapped player
|
|
func (paa *PlayerAAAdapter) GetPlayer() Player {
|
|
return paa.player
|
|
}
|
|
|
|
// GetCharacterID returns the player's character ID
|
|
func (paa *PlayerAAAdapter) GetCharacterID() int32 {
|
|
return paa.player.GetCharacterID()
|
|
}
|
|
|
|
// GetLevel returns the player's level
|
|
func (paa *PlayerAAAdapter) GetLevel() int8 {
|
|
return paa.player.GetLevel()
|
|
}
|
|
|
|
// GetClass returns the player's class
|
|
func (paa *PlayerAAAdapter) GetClass() int8 {
|
|
return paa.player.GetClass()
|
|
}
|
|
|
|
// GetAdventureClass returns the player's adventure class
|
|
func (paa *PlayerAAAdapter) GetAdventureClass() int8 {
|
|
return paa.player.GetAdventureClass()
|
|
}
|
|
|
|
// GetRace returns the player's race
|
|
func (paa *PlayerAAAdapter) GetRace() int8 {
|
|
return paa.player.GetRace()
|
|
}
|
|
|
|
// GetName returns the player's name
|
|
func (paa *PlayerAAAdapter) GetName() string {
|
|
return paa.player.GetName()
|
|
}
|
|
|
|
// HasExpansion checks if the player has a specific expansion
|
|
func (paa *PlayerAAAdapter) HasExpansion(expansionFlag int8) bool {
|
|
return paa.player.HasExpansion(expansionFlag)
|
|
}
|
|
|
|
// ClientAAAdapter adapts client functionality for AA systems
|
|
type ClientAAAdapter struct {
|
|
client Client
|
|
}
|
|
|
|
// NewClientAAAdapter creates a new client AA adapter
|
|
func NewClientAAAdapter(client Client) *ClientAAAdapter {
|
|
return &ClientAAAdapter{client: client}
|
|
}
|
|
|
|
// GetClient returns the wrapped client
|
|
func (caa *ClientAAAdapter) GetClient() Client {
|
|
return caa.client
|
|
}
|
|
|
|
// GetCharacterID returns the client's character ID
|
|
func (caa *ClientAAAdapter) GetCharacterID() int32 {
|
|
return caa.client.GetCharacterID()
|
|
}
|
|
|
|
// GetPlayer returns the client's player
|
|
func (caa *ClientAAAdapter) GetPlayer() Player {
|
|
return caa.client.GetPlayer()
|
|
}
|
|
|
|
// SendPacket sends a packet to the client
|
|
func (caa *ClientAAAdapter) SendPacket(data []byte) error {
|
|
return caa.client.SendPacket(data)
|
|
}
|
|
|
|
// GetClientVersion returns the client version
|
|
func (caa *ClientAAAdapter) GetClientVersion() int16 {
|
|
return caa.client.GetClientVersion()
|
|
}
|
|
|
|
// Simple cache implementation for testing
|
|
type SimpleAACache struct {
|
|
aaData map[int32]*AltAdvanceData
|
|
playerStates map[int32]*AAPlayerState
|
|
treeNodes map[int32]*TreeNodeData
|
|
mutex sync.RWMutex
|
|
maxSize int32
|
|
hits int64
|
|
misses int64
|
|
}
|
|
|
|
// NewSimpleAACache creates a new simple cache
|
|
func NewSimpleAACache(maxSize int32) *SimpleAACache {
|
|
return &SimpleAACache{
|
|
aaData: make(map[int32]*AltAdvanceData),
|
|
playerStates: make(map[int32]*AAPlayerState),
|
|
treeNodes: make(map[int32]*TreeNodeData),
|
|
maxSize: maxSize,
|
|
}
|
|
}
|
|
|
|
// GetAA retrieves AA data from cache
|
|
func (c *SimpleAACache) GetAA(nodeID int32) (*AltAdvanceData, bool) {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
if data, exists := c.aaData[nodeID]; exists {
|
|
c.hits++
|
|
return data.Copy(), true
|
|
}
|
|
|
|
c.misses++
|
|
return nil, false
|
|
}
|
|
|
|
// SetAA stores AA data in cache
|
|
func (c *SimpleAACache) SetAA(nodeID int32, aaData *AltAdvanceData) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if int32(len(c.aaData)) >= c.maxSize {
|
|
// Simple eviction: remove a random entry
|
|
for k := range c.aaData {
|
|
delete(c.aaData, k)
|
|
break
|
|
}
|
|
}
|
|
|
|
c.aaData[nodeID] = aaData.Copy()
|
|
}
|
|
|
|
// InvalidateAA removes AA data from cache
|
|
func (c *SimpleAACache) InvalidateAA(nodeID int32) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
delete(c.aaData, nodeID)
|
|
}
|
|
|
|
// GetPlayerState retrieves player state from cache
|
|
func (c *SimpleAACache) GetPlayerState(characterID int32) (*AAPlayerState, bool) {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
if state, exists := c.playerStates[characterID]; exists {
|
|
c.hits++
|
|
return state, true
|
|
}
|
|
|
|
c.misses++
|
|
return nil, false
|
|
}
|
|
|
|
// SetPlayerState stores player state in cache
|
|
func (c *SimpleAACache) SetPlayerState(characterID int32, playerState *AAPlayerState) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if int32(len(c.playerStates)) >= c.maxSize {
|
|
// Simple eviction: remove a random entry
|
|
for k := range c.playerStates {
|
|
delete(c.playerStates, k)
|
|
break
|
|
}
|
|
}
|
|
|
|
c.playerStates[characterID] = playerState
|
|
}
|
|
|
|
// InvalidatePlayerState removes player state from cache
|
|
func (c *SimpleAACache) InvalidatePlayerState(characterID int32) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
delete(c.playerStates, characterID)
|
|
}
|
|
|
|
// GetTreeNode retrieves tree node from cache
|
|
func (c *SimpleAACache) GetTreeNode(treeID int32) (*TreeNodeData, bool) {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
if node, exists := c.treeNodes[treeID]; exists {
|
|
c.hits++
|
|
nodeCopy := *node
|
|
return &nodeCopy, true
|
|
}
|
|
|
|
c.misses++
|
|
return nil, false
|
|
}
|
|
|
|
// SetTreeNode stores tree node in cache
|
|
func (c *SimpleAACache) SetTreeNode(treeID int32, nodeData *TreeNodeData) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if int32(len(c.treeNodes)) >= c.maxSize {
|
|
// Simple eviction: remove a random entry
|
|
for k := range c.treeNodes {
|
|
delete(c.treeNodes, k)
|
|
break
|
|
}
|
|
}
|
|
|
|
nodeCopy := *nodeData
|
|
c.treeNodes[treeID] = &nodeCopy
|
|
}
|
|
|
|
// InvalidateTreeNode removes tree node from cache
|
|
func (c *SimpleAACache) InvalidateTreeNode(treeID int32) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
delete(c.treeNodes, treeID)
|
|
}
|
|
|
|
// Clear removes all cached data
|
|
func (c *SimpleAACache) Clear() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
c.aaData = make(map[int32]*AltAdvanceData)
|
|
c.playerStates = make(map[int32]*AAPlayerState)
|
|
c.treeNodes = make(map[int32]*TreeNodeData)
|
|
}
|
|
|
|
// GetStats returns cache statistics
|
|
func (c *SimpleAACache) GetStats() map[string]interface{} {
|
|
c.mutex.RLock()
|
|
defer c.mutex.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"hits": c.hits,
|
|
"misses": c.misses,
|
|
"aa_data_count": len(c.aaData),
|
|
"player_count": len(c.playerStates),
|
|
"tree_node_count": len(c.treeNodes),
|
|
"max_size": c.maxSize,
|
|
}
|
|
}
|
|
|
|
// SetMaxSize sets the maximum cache size
|
|
func (c *SimpleAACache) SetMaxSize(maxSize int32) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
c.maxSize = maxSize
|
|
}
|