345 lines
9.4 KiB
Go
345 lines
9.4 KiB
Go
package alt_advancement
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"eq2emu/internal/database"
|
|
)
|
|
|
|
// AltAdvancement represents an Alternate Advancement node with database operations
|
|
type AltAdvancement struct {
|
|
// Core identification
|
|
SpellID int32 `json:"spell_id" db:"spell_id"`
|
|
NodeID int32 `json:"node_id" db:"node_id"`
|
|
SpellCRC int32 `json:"spell_crc" db:"spell_crc"`
|
|
|
|
// Display information
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description" db:"description"`
|
|
|
|
// Tree organization
|
|
Group int8 `json:"group" db:"group"` // AA tab (AA_CLASS, AA_SUBCLASS, etc.)
|
|
Col int8 `json:"col" db:"col"` // Column position in tree
|
|
Row int8 `json:"row" db:"row"` // Row position in tree
|
|
|
|
// Visual representation
|
|
Icon int16 `json:"icon" db:"icon"` // Primary icon ID
|
|
Icon2 int16 `json:"icon2" db:"icon2"` // Secondary icon ID
|
|
|
|
// Ranking system
|
|
RankCost int8 `json:"rank_cost" db:"rank_cost"` // Cost per rank
|
|
MaxRank int8 `json:"max_rank" db:"max_rank"` // Maximum achievable rank
|
|
|
|
// Prerequisites
|
|
MinLevel int8 `json:"min_level" db:"min_level"` // Minimum character level
|
|
RankPrereqID int32 `json:"rank_prereq_id" db:"rank_prereq_id"` // Prerequisite AA node ID
|
|
RankPrereq int8 `json:"rank_prereq" db:"rank_prereq"` // Required rank in prerequisite
|
|
ClassReq int8 `json:"class_req" db:"class_req"` // Required class
|
|
Tier int8 `json:"tier" db:"tier"` // AA tier
|
|
ReqPoints int8 `json:"req_points" db:"req_points"` // Required points in classification
|
|
ReqTreePoints int16 `json:"req_tree_points" db:"req_tree_points"` // Required points in entire tree
|
|
|
|
// Display classification
|
|
ClassName string `json:"class_name" db:"class_name"` // Class name for display
|
|
SubclassName string `json:"subclass_name" db:"subclass_name"` // Subclass name for display
|
|
LineTitle string `json:"line_title" db:"line_title"` // AA line title
|
|
TitleLevel int8 `json:"title_level" db:"title_level"` // Title level requirement
|
|
|
|
// Metadata
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
|
|
// Database connection
|
|
db *database.Database
|
|
isNew bool
|
|
}
|
|
|
|
// New creates a new alternate advancement with database connection
|
|
func New(db *database.Database) *AltAdvancement {
|
|
return &AltAdvancement{
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
db: db,
|
|
isNew: true,
|
|
}
|
|
}
|
|
|
|
// Load loads an alternate advancement by node ID
|
|
func Load(db *database.Database, nodeID int32) (*AltAdvancement, error) {
|
|
aa := &AltAdvancement{
|
|
db: db,
|
|
isNew: false,
|
|
}
|
|
|
|
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 WHERE nodeid = ?`
|
|
|
|
err := db.QueryRow(query, nodeID).Scan(
|
|
&aa.NodeID,
|
|
&aa.MinLevel,
|
|
&aa.SpellCRC,
|
|
&aa.Name,
|
|
&aa.Description,
|
|
&aa.Group,
|
|
&aa.Icon,
|
|
&aa.Icon2,
|
|
&aa.Col,
|
|
&aa.Row,
|
|
&aa.RankCost,
|
|
&aa.MaxRank,
|
|
&aa.RankPrereqID,
|
|
&aa.RankPrereq,
|
|
&aa.ClassReq,
|
|
&aa.Tier,
|
|
&aa.ReqPoints,
|
|
&aa.ReqTreePoints,
|
|
&aa.LineTitle,
|
|
&aa.TitleLevel,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("alternate advancement not found: %d", nodeID)
|
|
}
|
|
return nil, fmt.Errorf("failed to load alternate advancement: %w", err)
|
|
}
|
|
|
|
// Set spell ID to node ID if not provided separately
|
|
aa.SpellID = aa.NodeID
|
|
|
|
return aa, nil
|
|
}
|
|
|
|
// LoadAll loads all alternate advancements from database
|
|
func LoadAll(db *database.Database) ([]*AltAdvancement, 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.Query(query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query alternate advancements: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var aas []*AltAdvancement
|
|
|
|
for rows.Next() {
|
|
aa := &AltAdvancement{
|
|
db: db,
|
|
isNew: false,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := rows.Scan(
|
|
&aa.NodeID,
|
|
&aa.MinLevel,
|
|
&aa.SpellCRC,
|
|
&aa.Name,
|
|
&aa.Description,
|
|
&aa.Group,
|
|
&aa.Icon,
|
|
&aa.Icon2,
|
|
&aa.Col,
|
|
&aa.Row,
|
|
&aa.RankCost,
|
|
&aa.MaxRank,
|
|
&aa.RankPrereqID,
|
|
&aa.RankPrereq,
|
|
&aa.ClassReq,
|
|
&aa.Tier,
|
|
&aa.ReqPoints,
|
|
&aa.ReqTreePoints,
|
|
&aa.LineTitle,
|
|
&aa.TitleLevel,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan alternate advancement: %w", err)
|
|
}
|
|
|
|
// Set spell ID to node ID if not provided separately
|
|
aa.SpellID = aa.NodeID
|
|
|
|
aas = append(aas, aa)
|
|
}
|
|
|
|
return aas, rows.Err()
|
|
}
|
|
|
|
// Save saves the alternate advancement to the database (insert if new, update if existing)
|
|
func (aa *AltAdvancement) Save() error {
|
|
if aa.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
tx, err := aa.db.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if aa.isNew {
|
|
err = aa.insert(tx)
|
|
} else {
|
|
err = aa.update(tx)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// Delete removes the alternate advancement from the database
|
|
func (aa *AltAdvancement) Delete() error {
|
|
if aa.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
if aa.isNew {
|
|
return fmt.Errorf("cannot delete unsaved alternate advancement")
|
|
}
|
|
|
|
_, err := aa.db.Exec("DELETE FROM spell_aa_nodelist WHERE nodeid = ?", aa.NodeID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete alternate advancement: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reload reloads the alternate advancement from the database
|
|
func (aa *AltAdvancement) Reload() error {
|
|
if aa.db == nil {
|
|
return fmt.Errorf("no database connection")
|
|
}
|
|
|
|
if aa.isNew {
|
|
return fmt.Errorf("cannot reload unsaved alternate advancement")
|
|
}
|
|
|
|
reloaded, err := Load(aa.db, aa.NodeID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy all fields from reloaded AA
|
|
*aa = *reloaded
|
|
return nil
|
|
}
|
|
|
|
// IsNew returns true if this is a new (unsaved) alternate advancement
|
|
func (aa *AltAdvancement) IsNew() bool {
|
|
return aa.isNew
|
|
}
|
|
|
|
// GetID returns the node ID (implements common.Identifiable interface)
|
|
func (aa *AltAdvancement) GetID() int32 {
|
|
return aa.NodeID
|
|
}
|
|
|
|
// IsValid validates the alternate advancement data
|
|
func (aa *AltAdvancement) IsValid() bool {
|
|
return aa.SpellID > 0 &&
|
|
aa.NodeID > 0 &&
|
|
len(aa.Name) > 0 &&
|
|
aa.MaxRank > 0 &&
|
|
aa.RankCost > 0
|
|
}
|
|
|
|
// Clone creates a deep copy of the alternate advancement
|
|
func (aa *AltAdvancement) Clone() *AltAdvancement {
|
|
clone := &AltAdvancement{
|
|
SpellID: aa.SpellID,
|
|
NodeID: aa.NodeID,
|
|
SpellCRC: aa.SpellCRC,
|
|
Name: aa.Name,
|
|
Description: aa.Description,
|
|
Group: aa.Group,
|
|
Col: aa.Col,
|
|
Row: aa.Row,
|
|
Icon: aa.Icon,
|
|
Icon2: aa.Icon2,
|
|
RankCost: aa.RankCost,
|
|
MaxRank: aa.MaxRank,
|
|
MinLevel: aa.MinLevel,
|
|
RankPrereqID: aa.RankPrereqID,
|
|
RankPrereq: aa.RankPrereq,
|
|
ClassReq: aa.ClassReq,
|
|
Tier: aa.Tier,
|
|
ReqPoints: aa.ReqPoints,
|
|
ReqTreePoints: aa.ReqTreePoints,
|
|
ClassName: aa.ClassName,
|
|
SubclassName: aa.SubclassName,
|
|
LineTitle: aa.LineTitle,
|
|
TitleLevel: aa.TitleLevel,
|
|
CreatedAt: aa.CreatedAt,
|
|
UpdatedAt: aa.UpdatedAt,
|
|
db: aa.db,
|
|
isNew: false,
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
// Private helper methods
|
|
|
|
func (aa *AltAdvancement) insert(tx *sql.Tx) error {
|
|
query := `INSERT INTO spell_aa_nodelist
|
|
(nodeid, minlevel, spellcrc, name, description, aa_list_fk,
|
|
icon_id, icon_backdrop, xcoord, ycoord, pointspertier, maxtier,
|
|
firstparentid, firstparentrequiredtier, displayedclassification,
|
|
requiredclassification, classificationpointsrequired,
|
|
pointsspentintreetounlock, title, titlelevel)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
_, err := tx.Exec(query,
|
|
aa.NodeID, aa.MinLevel, aa.SpellCRC, aa.Name, aa.Description, aa.Group,
|
|
aa.Icon, aa.Icon2, aa.Col, aa.Row, aa.RankCost, aa.MaxRank,
|
|
aa.RankPrereqID, aa.RankPrereq, aa.ClassReq, aa.Tier, aa.ReqPoints,
|
|
aa.ReqTreePoints, aa.LineTitle, aa.TitleLevel)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert alternate advancement: %w", err)
|
|
}
|
|
|
|
aa.isNew = false
|
|
return nil
|
|
}
|
|
|
|
func (aa *AltAdvancement) update(tx *sql.Tx) error {
|
|
query := `UPDATE spell_aa_nodelist SET
|
|
minlevel = ?, spellcrc = ?, name = ?, description = ?, aa_list_fk = ?,
|
|
icon_id = ?, icon_backdrop = ?, xcoord = ?, ycoord = ?, pointspertier = ?,
|
|
maxtier = ?, firstparentid = ?, firstparentrequiredtier = ?,
|
|
displayedclassification = ?, requiredclassification = ?,
|
|
classificationpointsrequired = ?, pointsspentintreetounlock = ?,
|
|
title = ?, titlelevel = ?
|
|
WHERE nodeid = ?`
|
|
|
|
_, err := tx.Exec(query,
|
|
aa.MinLevel, aa.SpellCRC, aa.Name, aa.Description, aa.Group,
|
|
aa.Icon, aa.Icon2, aa.Col, aa.Row, aa.RankCost, aa.MaxRank,
|
|
aa.RankPrereqID, aa.RankPrereq, aa.ClassReq, aa.Tier, aa.ReqPoints,
|
|
aa.ReqTreePoints, aa.LineTitle, aa.TitleLevel, aa.NodeID)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update alternate advancement: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|