564 lines
14 KiB
Go

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
}