package alt_advancement import ( "fmt" "time" ) // LoadAltAdvancements loads all AA definitions from the database func (db *DatabaseImpl) LoadAltAdvancements() error { query := ` SELECT nodeid, minlevel, spellcrc, name, description, aa_list_fk, icon_id, icon_backdrop, xcoord, ycoord, pointspertier, maxtier, firstparentid, firstparentrequiredtier, displayedclassification, requiredclassification, classificationpointsrequired, pointsspentintreetounlock, title, titlelevel FROM spell_aa_nodelist ORDER BY aa_list_fk, ycoord, xcoord` rows, err := db.db.Query(query) if err != nil { return fmt.Errorf("failed to query AA data: %v", err) } defer rows.Close() loadedCount := 0 for rows.Next() { data := &AltAdvanceData{ CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := rows.Scan( &data.NodeID, &data.MinLevel, &data.SpellCRC, &data.Name, &data.Description, &data.Group, &data.Icon, &data.Icon2, &data.Col, &data.Row, &data.RankCost, &data.MaxRank, &data.RankPrereqID, &data.RankPrereq, &data.ClassReq, &data.Tier, &data.ReqPoints, &data.ReqTreePoints, &data.LineTitle, &data.TitleLevel, ) if err != nil { return fmt.Errorf("failed to scan AA data: %v", err) } // Set spell ID to node ID if not provided separately data.SpellID = data.NodeID // Validate and add to master list if err := db.masterAAList.AddAltAdvancement(data); err != nil { // Log warning but continue loading if db.logger != nil { db.logger.Printf("Warning: failed to add AA node %d: %v", data.NodeID, err) } continue } loadedCount++ } if err = rows.Err(); err != nil { return fmt.Errorf("error iterating AA rows: %v", err) } // Sort AAs within each group for proper display order db.masterAAList.SortAAsByGroup() if db.logger != nil { db.logger.Printf("Loaded %d Alternate Advancement(s)", loadedCount) } return nil } // LoadTreeNodes loads tree node configurations from the database func (db *DatabaseImpl) LoadTreeNodes() error { query := ` SELECT class_id, tree_node, aa_tree_id FROM spell_aa_class_list ORDER BY class_id, tree_node` rows, err := db.db.Query(query) if err != nil { return fmt.Errorf("failed to query tree node data: %v", err) } defer rows.Close() loadedCount := 0 for rows.Next() { data := &TreeNodeData{} err := rows.Scan( &data.ClassID, &data.TreeID, &data.AATreeID, ) if err != nil { return fmt.Errorf("failed to scan tree node data: %v", err) } // Add to master node list if err := db.masterNodeList.AddTreeNode(data); err != nil { // Log warning but continue loading if db.logger != nil { db.logger.Printf("Warning: failed to add tree node %d: %v", data.TreeID, err) } continue } loadedCount++ } if err = rows.Err(); err != nil { return fmt.Errorf("error iterating tree node rows: %v", err) } if db.logger != nil { db.logger.Printf("Loaded %d AA Tree Nodes", loadedCount) } return nil } // LoadPlayerAA loads AA data for a specific player func (db *DatabaseImpl) LoadPlayerAA(characterID int32) (*AAPlayerState, error) { playerState := NewAAPlayerState(characterID) // Load player's AA entries query := ` SELECT template_id, tab_id, aa_id, order, treeid FROM character_aa WHERE char_id = ? ORDER BY template_id, tab_id, order` rows, err := db.db.Query(query, characterID) if err != nil { return nil, fmt.Errorf("failed to query player AA data: %v", err) } defer rows.Close() // Group entries by template templateEntries := make(map[int8][]*AAEntry) for rows.Next() { entry := &AAEntry{} err := rows.Scan( &entry.TemplateID, &entry.TabID, &entry.AAID, &entry.Order, &entry.TreeID, ) if err != nil { return nil, fmt.Errorf("failed to scan player AA entry: %v", err) } if templateEntries[entry.TemplateID] == nil { templateEntries[entry.TemplateID] = make([]*AAEntry, 0) } templateEntries[entry.TemplateID] = append(templateEntries[entry.TemplateID], entry) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating player AA rows: %v", err) } // Create templates from loaded entries for templateID, entries := range templateEntries { template := NewAATemplate(templateID, GetTemplateName(templateID)) template.Entries = entries playerState.Templates[templateID] = template } // Load player's AA progression data err = db.loadPlayerAAProgress(characterID, playerState) if err != nil { return nil, fmt.Errorf("failed to load player AA progress: %v", err) } // Load player's AA point totals err = db.loadPlayerAAPoints(characterID, playerState) if err != nil { return nil, fmt.Errorf("failed to load player AA points: %v", err) } // Initialize tabs based on loaded data db.initializePlayerTabs(playerState) return playerState, nil } // loadPlayerAAProgress loads detailed AA progression for a player func (db *DatabaseImpl) loadPlayerAAProgress(characterID int32, playerState *AAPlayerState) error { query := ` SELECT node_id, current_rank, points_spent, template_id, tab_id, purchased_at, updated_at FROM character_aa_progress WHERE character_id = ?` rows, err := db.db.Query(query, characterID) if err != nil { return fmt.Errorf("failed to query player AA progress: %v", err) } defer rows.Close() for rows.Next() { progress := &PlayerAAData{ CharacterID: characterID, } var purchasedAt, updatedAt string err := rows.Scan( &progress.NodeID, &progress.CurrentRank, &progress.PointsSpent, &progress.TemplateID, &progress.TabID, &purchasedAt, &updatedAt, ) if err != nil { return fmt.Errorf("failed to scan player AA progress: %v", err) } // Parse timestamps if progress.PurchasedAt, err = time.Parse("2006-01-02 15:04:05", purchasedAt); err != nil { progress.PurchasedAt = time.Now() } if progress.UpdatedAt, err = time.Parse("2006-01-02 15:04:05", updatedAt); err != nil { progress.UpdatedAt = time.Now() } playerState.AAProgress[progress.NodeID] = progress } return rows.Err() } // loadPlayerAAPoints loads AA point totals for a player func (db *DatabaseImpl) loadPlayerAAPoints(characterID int32, playerState *AAPlayerState) error { query := ` SELECT total_points, spent_points, available_points, banked_points, active_template FROM character_aa_points WHERE character_id = ?` row := db.db.QueryRow(query, characterID) err := row.Scan( &playerState.TotalPoints, &playerState.SpentPoints, &playerState.AvailablePoints, &playerState.BankedPoints, &playerState.ActiveTemplate, ) if err != nil { // If no record exists, initialize with defaults if err.Error() == "sql: no rows in result set" { playerState.TotalPoints = 0 playerState.SpentPoints = 0 playerState.AvailablePoints = 0 playerState.BankedPoints = 0 playerState.ActiveTemplate = AA_TEMPLATE_CURRENT return nil } return fmt.Errorf("failed to load player AA points: %v", err) } return nil } // initializePlayerTabs initializes tab states based on loaded data func (db *DatabaseImpl) initializePlayerTabs(playerState *AAPlayerState) { // Initialize all standard tabs for i := int8(0); i < 10; i++ { tab := NewAATab(i, i, GetTabName(i)) tab.MaxAA = GetMaxAAForTab(i) // Calculate points spent in this tab pointsSpent := int32(0) for _, progress := range playerState.AAProgress { if progress.TabID == i { pointsSpent += progress.PointsSpent } } tab.PointsSpent = pointsSpent tab.PointsAvailable = playerState.AvailablePoints playerState.Tabs[i] = tab } } // SavePlayerAA saves a player's AA data to the database func (db *DatabaseImpl) SavePlayerAA(playerState *AAPlayerState) error { if playerState == nil { return fmt.Errorf("player state cannot be nil") } // Start transaction tx, err := db.db.Begin() if err != nil { return fmt.Errorf("failed to begin transaction: %v", err) } defer tx.Rollback() // Save AA point totals err = db.savePlayerAAPoints(tx, playerState) if err != nil { return fmt.Errorf("failed to save player AA points: %v", err) } // Save AA progress err = db.savePlayerAAProgress(tx, playerState) if err != nil { return fmt.Errorf("failed to save player AA progress: %v", err) } // Save template entries err = db.savePlayerAATemplates(tx, playerState) if err != nil { return fmt.Errorf("failed to save player AA templates: %v", err) } // Commit transaction if err = tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %v", err) } // Update last save time playerState.lastUpdate = time.Now() playerState.needsSync = false return nil } // savePlayerAAPoints saves AA point totals to the database func (db *DatabaseImpl) savePlayerAAPoints(tx Transaction, playerState *AAPlayerState) error { query := ` INSERT OR REPLACE INTO character_aa_points (character_id, total_points, spent_points, available_points, banked_points, active_template, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)` _, err := tx.Exec(query, playerState.CharacterID, playerState.TotalPoints, playerState.SpentPoints, playerState.AvailablePoints, playerState.BankedPoints, playerState.ActiveTemplate, time.Now().Format("2006-01-02 15:04:05"), ) return err } // savePlayerAAProgress saves AA progression data to the database func (db *DatabaseImpl) savePlayerAAProgress(tx Transaction, playerState *AAPlayerState) error { // Delete existing progress _, err := tx.Exec("DELETE FROM character_aa_progress WHERE character_id = ?", playerState.CharacterID) if err != nil { return err } // Insert current progress query := ` INSERT INTO character_aa_progress (character_id, node_id, current_rank, points_spent, template_id, tab_id, purchased_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` for _, progress := range playerState.AAProgress { _, err = tx.Exec(query, progress.CharacterID, progress.NodeID, progress.CurrentRank, progress.PointsSpent, progress.TemplateID, progress.TabID, progress.PurchasedAt.Format("2006-01-02 15:04:05"), progress.UpdatedAt.Format("2006-01-02 15:04:05"), ) if err != nil { return err } } return nil } // savePlayerAATemplates saves AA template entries to the database func (db *DatabaseImpl) savePlayerAATemplates(tx Transaction, playerState *AAPlayerState) error { // Delete existing entries for server templates (4-6) _, err := tx.Exec("DELETE FROM character_aa WHERE char_id = ? AND template_id BETWEEN 4 AND 6", playerState.CharacterID) if err != nil { return err } // Insert current template entries for server templates only query := ` INSERT INTO character_aa (char_id, template_id, tab_id, aa_id, order, treeid) VALUES (?, ?, ?, ?, ?, ?)` for _, template := range playerState.Templates { // Only save server templates (4-6) as personal templates (1-3) are class defaults if template.TemplateID >= 4 && template.TemplateID <= 6 { for _, entry := range template.Entries { _, err = tx.Exec(query, playerState.CharacterID, entry.TemplateID, entry.TabID, entry.AAID, entry.Order, entry.TreeID, ) if err != nil { return err } } } } return nil } // LoadPlayerAADefaults loads default AA templates for a class func (db *DatabaseImpl) LoadPlayerAADefaults(classID int8) (map[int8][]*AAEntry, error) { query := ` SELECT template_id, tab_id, aa_id, order, treeid FROM character_aa_defaults WHERE class = ? ORDER BY template_id, tab_id, order` rows, err := db.db.Query(query, classID) if err != nil { return nil, fmt.Errorf("failed to query AA defaults: %v", err) } defer rows.Close() templates := make(map[int8][]*AAEntry) for rows.Next() { entry := &AAEntry{} err := rows.Scan( &entry.TemplateID, &entry.TabID, &entry.AAID, &entry.Order, &entry.TreeID, ) if err != nil { return nil, fmt.Errorf("failed to scan AA default entry: %v", err) } if templates[entry.TemplateID] == nil { templates[entry.TemplateID] = make([]*AAEntry, 0) } templates[entry.TemplateID] = append(templates[entry.TemplateID], entry) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating AA default rows: %v", err) } return templates, nil } // DeletePlayerAA removes all AA data for a player func (db *DatabaseImpl) DeletePlayerAA(characterID int32) error { // Start transaction tx, err := db.db.Begin() if err != nil { return fmt.Errorf("failed to begin transaction: %v", err) } defer tx.Rollback() // Delete from all related tables tables := []string{ "character_aa_points", "character_aa_progress", "character_aa", } for _, table := range tables { query := fmt.Sprintf("DELETE FROM %s WHERE char_id = ? OR character_id = ?", table) _, err = tx.Exec(query, characterID, characterID) if err != nil { return fmt.Errorf("failed to delete from %s: %v", table, err) } } // Commit transaction if err = tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %v", err) } return nil } // GetAAStatistics returns statistics about AA usage func (db *DatabaseImpl) GetAAStatistics() (map[string]interface{}, error) { stats := make(map[string]interface{}) // Get total players with AA data var totalPlayers int64 err := db.db.QueryRow("SELECT COUNT(DISTINCT character_id) FROM character_aa_points").Scan(&totalPlayers) if err != nil { return nil, fmt.Errorf("failed to get total players: %v", err) } stats["total_players_with_aa"] = totalPlayers // Get average points spent var avgPointsSpent float64 err = db.db.QueryRow("SELECT AVG(spent_points) FROM character_aa_points").Scan(&avgPointsSpent) if err != nil { return nil, fmt.Errorf("failed to get average points spent: %v", err) } stats["average_points_spent"] = avgPointsSpent // Get most popular AAs query := ` SELECT node_id, COUNT(*) as usage_count FROM character_aa_progress WHERE current_rank > 0 GROUP BY node_id ORDER BY usage_count DESC LIMIT 10` rows, err := db.db.Query(query) if err != nil { return nil, fmt.Errorf("failed to query popular AAs: %v", err) } defer rows.Close() popularAAs := make(map[int32]int64) for rows.Next() { var nodeID int32 var count int64 err := rows.Scan(&nodeID, &count) if err != nil { return nil, fmt.Errorf("failed to scan popular AA: %v", err) } popularAAs[nodeID] = count } stats["popular_aas"] = popularAAs return stats, nil }