fixes for heroic ops
This commit is contained in:
parent
966501670f
commit
1c26bb600d
@ -6,7 +6,7 @@ const (
|
||||
MaxAbilities = 6
|
||||
|
||||
// Special ability icon values
|
||||
AbilityIconAny = 0xFFFF // Wildcard - any ability can be used
|
||||
AbilityIconAny = -1 // Wildcard - any ability can be used (using -1 to fit in int16)
|
||||
AbilityIconNone = 0 // No ability required
|
||||
|
||||
// Default wheel timer (in seconds)
|
||||
|
@ -5,119 +5,121 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"eq2emu/internal/database"
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// DatabaseHeroicOPManager implements HeroicOPDatabase interface using the existing database wrapper
|
||||
// DatabaseHeroicOPManager implements HeroicOPDatabase interface using sqlitex.Pool
|
||||
type DatabaseHeroicOPManager struct {
|
||||
db *database.DB
|
||||
pool *sqlitex.Pool
|
||||
}
|
||||
|
||||
// NewDatabaseHeroicOPManager creates a new database heroic OP manager
|
||||
func NewDatabaseHeroicOPManager(db *database.DB) *DatabaseHeroicOPManager {
|
||||
func NewDatabaseHeroicOPManager(pool *sqlitex.Pool) *DatabaseHeroicOPManager {
|
||||
return &DatabaseHeroicOPManager{
|
||||
db: db,
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadStarters retrieves all starters from database
|
||||
func (dhom *DatabaseHeroicOPManager) LoadStarters(ctx context.Context) ([]HeroicOPData, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, ho_type, starter_class, starter_icon, 0 as starter_link_id,
|
||||
0 as chain_order, 0 as shift_icon, 0 as spell_id, 0.0 as chance,
|
||||
ability1, ability2, ability3, ability4, ability5, ability6,
|
||||
name, description FROM heroic_ops WHERE ho_type = ?`
|
||||
|
||||
rows, err := dhom.db.QueryContext(ctx, query, HOTypeStarter)
|
||||
var starters []HeroicOPData
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{HOTypeStarter},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
var starter HeroicOPData
|
||||
starter.ID = int32(stmt.ColumnInt64(0))
|
||||
starter.HOType = stmt.ColumnText(1)
|
||||
starter.StarterClass = int8(stmt.ColumnInt64(2))
|
||||
starter.StarterIcon = int16(stmt.ColumnInt64(3))
|
||||
starter.StarterLinkID = int32(stmt.ColumnInt64(4))
|
||||
starter.ChainOrder = int8(stmt.ColumnInt64(5))
|
||||
starter.ShiftIcon = int16(stmt.ColumnInt64(6))
|
||||
starter.SpellID = int32(stmt.ColumnInt64(7))
|
||||
starter.Chance = float32(stmt.ColumnFloat(8))
|
||||
starter.Ability1 = int16(stmt.ColumnInt64(9))
|
||||
starter.Ability2 = int16(stmt.ColumnInt64(10))
|
||||
starter.Ability3 = int16(stmt.ColumnInt64(11))
|
||||
starter.Ability4 = int16(stmt.ColumnInt64(12))
|
||||
starter.Ability5 = int16(stmt.ColumnInt64(13))
|
||||
starter.Ability6 = int16(stmt.ColumnInt64(14))
|
||||
if stmt.ColumnType(15) != sqlite.TypeNull {
|
||||
starter.Name = stmt.ColumnText(15)
|
||||
}
|
||||
if stmt.ColumnType(16) != sqlite.TypeNull {
|
||||
starter.Description = stmt.ColumnText(16)
|
||||
}
|
||||
starters = append(starters, starter)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query heroic op starters: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var starters []HeroicOPData
|
||||
for rows.Next() {
|
||||
var starter HeroicOPData
|
||||
var name, description *string
|
||||
|
||||
err := rows.Scan(
|
||||
&starter.ID,
|
||||
&starter.HOType,
|
||||
&starter.StarterClass,
|
||||
&starter.StarterIcon,
|
||||
&starter.StarterLinkID,
|
||||
&starter.ChainOrder,
|
||||
&starter.ShiftIcon,
|
||||
&starter.SpellID,
|
||||
&starter.Chance,
|
||||
&starter.Ability1,
|
||||
&starter.Ability2,
|
||||
&starter.Ability3,
|
||||
&starter.Ability4,
|
||||
&starter.Ability5,
|
||||
&starter.Ability6,
|
||||
&name,
|
||||
&description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan heroic op starter row: %w", err)
|
||||
}
|
||||
|
||||
// Handle nullable fields
|
||||
if name != nil {
|
||||
starter.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
starter.Description = *description
|
||||
}
|
||||
|
||||
starters = append(starters, starter)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating heroic op starter rows: %w", err)
|
||||
}
|
||||
|
||||
return starters, nil
|
||||
}
|
||||
|
||||
// LoadStarter retrieves a specific starter from database
|
||||
func (dhom *DatabaseHeroicOPManager) LoadStarter(ctx context.Context, starterID int32) (*HeroicOPData, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, ho_type, starter_class, starter_icon, 0 as starter_link_id,
|
||||
0 as chain_order, 0 as shift_icon, 0 as spell_id, 0.0 as chance,
|
||||
ability1, ability2, ability3, ability4, ability5, ability6,
|
||||
name, description FROM heroic_ops WHERE id = ? AND ho_type = ?`
|
||||
|
||||
var starter HeroicOPData
|
||||
var name, description *string
|
||||
|
||||
err := dhom.db.QueryRowContext(ctx, query, starterID, HOTypeStarter).Scan(
|
||||
&starter.ID,
|
||||
&starter.HOType,
|
||||
&starter.StarterClass,
|
||||
&starter.StarterIcon,
|
||||
&starter.StarterLinkID,
|
||||
&starter.ChainOrder,
|
||||
&starter.ShiftIcon,
|
||||
&starter.SpellID,
|
||||
&starter.Chance,
|
||||
&starter.Ability1,
|
||||
&starter.Ability2,
|
||||
&starter.Ability3,
|
||||
&starter.Ability4,
|
||||
&starter.Ability5,
|
||||
&starter.Ability6,
|
||||
&name,
|
||||
&description,
|
||||
)
|
||||
var found bool
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{starterID, HOTypeStarter},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
starter.ID = int32(stmt.ColumnInt64(0))
|
||||
starter.HOType = stmt.ColumnText(1)
|
||||
starter.StarterClass = int8(stmt.ColumnInt64(2))
|
||||
starter.StarterIcon = int16(stmt.ColumnInt64(3))
|
||||
starter.StarterLinkID = int32(stmt.ColumnInt64(4))
|
||||
starter.ChainOrder = int8(stmt.ColumnInt64(5))
|
||||
starter.ShiftIcon = int16(stmt.ColumnInt64(6))
|
||||
starter.SpellID = int32(stmt.ColumnInt64(7))
|
||||
starter.Chance = float32(stmt.ColumnFloat(8))
|
||||
starter.Ability1 = int16(stmt.ColumnInt64(9))
|
||||
starter.Ability2 = int16(stmt.ColumnInt64(10))
|
||||
starter.Ability3 = int16(stmt.ColumnInt64(11))
|
||||
starter.Ability4 = int16(stmt.ColumnInt64(12))
|
||||
starter.Ability5 = int16(stmt.ColumnInt64(13))
|
||||
starter.Ability6 = int16(stmt.ColumnInt64(14))
|
||||
if stmt.ColumnType(15) != sqlite.TypeNull {
|
||||
starter.Name = stmt.ColumnText(15)
|
||||
}
|
||||
if stmt.ColumnType(16) != sqlite.TypeNull {
|
||||
starter.Description = stmt.ColumnText(16)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load heroic op starter %d: %w", starterID, err)
|
||||
}
|
||||
|
||||
// Handle nullable fields
|
||||
if name != nil {
|
||||
starter.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
starter.Description = *description
|
||||
if !found {
|
||||
return nil, fmt.Errorf("heroic op starter %d not found", starterID)
|
||||
}
|
||||
|
||||
return &starter, nil
|
||||
@ -125,161 +127,153 @@ func (dhom *DatabaseHeroicOPManager) LoadStarter(ctx context.Context, starterID
|
||||
|
||||
// LoadWheels retrieves all wheels from database
|
||||
func (dhom *DatabaseHeroicOPManager) LoadWheels(ctx context.Context) ([]HeroicOPData, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
|
||||
chain_order, shift_icon, spell_id, chance,
|
||||
ability1, ability2, ability3, ability4, ability5, ability6,
|
||||
name, description FROM heroic_ops WHERE ho_type = ?`
|
||||
|
||||
rows, err := dhom.db.QueryContext(ctx, query, HOTypeWheel)
|
||||
var wheels []HeroicOPData
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{HOTypeWheel},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
var wheel HeroicOPData
|
||||
wheel.ID = int32(stmt.ColumnInt64(0))
|
||||
wheel.HOType = stmt.ColumnText(1)
|
||||
wheel.StarterClass = int8(stmt.ColumnInt64(2))
|
||||
wheel.StarterIcon = int16(stmt.ColumnInt64(3))
|
||||
wheel.StarterLinkID = int32(stmt.ColumnInt64(4))
|
||||
wheel.ChainOrder = int8(stmt.ColumnInt64(5))
|
||||
wheel.ShiftIcon = int16(stmt.ColumnInt64(6))
|
||||
wheel.SpellID = int32(stmt.ColumnInt64(7))
|
||||
wheel.Chance = float32(stmt.ColumnFloat(8))
|
||||
wheel.Ability1 = int16(stmt.ColumnInt64(9))
|
||||
wheel.Ability2 = int16(stmt.ColumnInt64(10))
|
||||
wheel.Ability3 = int16(stmt.ColumnInt64(11))
|
||||
wheel.Ability4 = int16(stmt.ColumnInt64(12))
|
||||
wheel.Ability5 = int16(stmt.ColumnInt64(13))
|
||||
wheel.Ability6 = int16(stmt.ColumnInt64(14))
|
||||
if stmt.ColumnType(15) != sqlite.TypeNull {
|
||||
wheel.Name = stmt.ColumnText(15)
|
||||
}
|
||||
if stmt.ColumnType(16) != sqlite.TypeNull {
|
||||
wheel.Description = stmt.ColumnText(16)
|
||||
}
|
||||
wheels = append(wheels, wheel)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query heroic op wheels: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wheels []HeroicOPData
|
||||
for rows.Next() {
|
||||
var wheel HeroicOPData
|
||||
var name, description *string
|
||||
|
||||
err := rows.Scan(
|
||||
&wheel.ID,
|
||||
&wheel.HOType,
|
||||
&wheel.StarterClass,
|
||||
&wheel.StarterIcon,
|
||||
&wheel.StarterLinkID,
|
||||
&wheel.ChainOrder,
|
||||
&wheel.ShiftIcon,
|
||||
&wheel.SpellID,
|
||||
&wheel.Chance,
|
||||
&wheel.Ability1,
|
||||
&wheel.Ability2,
|
||||
&wheel.Ability3,
|
||||
&wheel.Ability4,
|
||||
&wheel.Ability5,
|
||||
&wheel.Ability6,
|
||||
&name,
|
||||
&description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan heroic op wheel row: %w", err)
|
||||
}
|
||||
|
||||
// Handle nullable fields
|
||||
if name != nil {
|
||||
wheel.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
wheel.Description = *description
|
||||
}
|
||||
|
||||
wheels = append(wheels, wheel)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating heroic op wheel rows: %w", err)
|
||||
}
|
||||
|
||||
return wheels, nil
|
||||
}
|
||||
|
||||
// LoadWheelsForStarter retrieves wheels for a specific starter
|
||||
func (dhom *DatabaseHeroicOPManager) LoadWheelsForStarter(ctx context.Context, starterID int32) ([]HeroicOPData, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
|
||||
chain_order, shift_icon, spell_id, chance,
|
||||
ability1, ability2, ability3, ability4, ability5, ability6,
|
||||
name, description FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?`
|
||||
|
||||
rows, err := dhom.db.QueryContext(ctx, query, starterID, HOTypeWheel)
|
||||
var wheels []HeroicOPData
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{starterID, HOTypeWheel},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
var wheel HeroicOPData
|
||||
wheel.ID = int32(stmt.ColumnInt64(0))
|
||||
wheel.HOType = stmt.ColumnText(1)
|
||||
wheel.StarterClass = int8(stmt.ColumnInt64(2))
|
||||
wheel.StarterIcon = int16(stmt.ColumnInt64(3))
|
||||
wheel.StarterLinkID = int32(stmt.ColumnInt64(4))
|
||||
wheel.ChainOrder = int8(stmt.ColumnInt64(5))
|
||||
wheel.ShiftIcon = int16(stmt.ColumnInt64(6))
|
||||
wheel.SpellID = int32(stmt.ColumnInt64(7))
|
||||
wheel.Chance = float32(stmt.ColumnFloat(8))
|
||||
wheel.Ability1 = int16(stmt.ColumnInt64(9))
|
||||
wheel.Ability2 = int16(stmt.ColumnInt64(10))
|
||||
wheel.Ability3 = int16(stmt.ColumnInt64(11))
|
||||
wheel.Ability4 = int16(stmt.ColumnInt64(12))
|
||||
wheel.Ability5 = int16(stmt.ColumnInt64(13))
|
||||
wheel.Ability6 = int16(stmt.ColumnInt64(14))
|
||||
if stmt.ColumnType(15) != sqlite.TypeNull {
|
||||
wheel.Name = stmt.ColumnText(15)
|
||||
}
|
||||
if stmt.ColumnType(16) != sqlite.TypeNull {
|
||||
wheel.Description = stmt.ColumnText(16)
|
||||
}
|
||||
wheels = append(wheels, wheel)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query wheels for starter %d: %w", starterID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wheels []HeroicOPData
|
||||
for rows.Next() {
|
||||
var wheel HeroicOPData
|
||||
var name, description *string
|
||||
|
||||
err := rows.Scan(
|
||||
&wheel.ID,
|
||||
&wheel.HOType,
|
||||
&wheel.StarterClass,
|
||||
&wheel.StarterIcon,
|
||||
&wheel.StarterLinkID,
|
||||
&wheel.ChainOrder,
|
||||
&wheel.ShiftIcon,
|
||||
&wheel.SpellID,
|
||||
&wheel.Chance,
|
||||
&wheel.Ability1,
|
||||
&wheel.Ability2,
|
||||
&wheel.Ability3,
|
||||
&wheel.Ability4,
|
||||
&wheel.Ability5,
|
||||
&wheel.Ability6,
|
||||
&name,
|
||||
&description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan wheel row: %w", err)
|
||||
}
|
||||
|
||||
// Handle nullable fields
|
||||
if name != nil {
|
||||
wheel.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
wheel.Description = *description
|
||||
}
|
||||
|
||||
wheels = append(wheels, wheel)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating wheel rows: %w", err)
|
||||
}
|
||||
|
||||
return wheels, nil
|
||||
}
|
||||
|
||||
// LoadWheel retrieves a specific wheel from database
|
||||
func (dhom *DatabaseHeroicOPManager) LoadWheel(ctx context.Context, wheelID int32) (*HeroicOPData, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
|
||||
chain_order, shift_icon, spell_id, chance,
|
||||
ability1, ability2, ability3, ability4, ability5, ability6,
|
||||
name, description FROM heroic_ops WHERE id = ? AND ho_type = ?`
|
||||
|
||||
var wheel HeroicOPData
|
||||
var name, description *string
|
||||
|
||||
err := dhom.db.QueryRowContext(ctx, query, wheelID, HOTypeWheel).Scan(
|
||||
&wheel.ID,
|
||||
&wheel.HOType,
|
||||
&wheel.StarterClass,
|
||||
&wheel.StarterIcon,
|
||||
&wheel.StarterLinkID,
|
||||
&wheel.ChainOrder,
|
||||
&wheel.ShiftIcon,
|
||||
&wheel.SpellID,
|
||||
&wheel.Chance,
|
||||
&wheel.Ability1,
|
||||
&wheel.Ability2,
|
||||
&wheel.Ability3,
|
||||
&wheel.Ability4,
|
||||
&wheel.Ability5,
|
||||
&wheel.Ability6,
|
||||
&name,
|
||||
&description,
|
||||
)
|
||||
var found bool
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{wheelID, HOTypeWheel},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
wheel.ID = int32(stmt.ColumnInt64(0))
|
||||
wheel.HOType = stmt.ColumnText(1)
|
||||
wheel.StarterClass = int8(stmt.ColumnInt64(2))
|
||||
wheel.StarterIcon = int16(stmt.ColumnInt64(3))
|
||||
wheel.StarterLinkID = int32(stmt.ColumnInt64(4))
|
||||
wheel.ChainOrder = int8(stmt.ColumnInt64(5))
|
||||
wheel.ShiftIcon = int16(stmt.ColumnInt64(6))
|
||||
wheel.SpellID = int32(stmt.ColumnInt64(7))
|
||||
wheel.Chance = float32(stmt.ColumnFloat(8))
|
||||
wheel.Ability1 = int16(stmt.ColumnInt64(9))
|
||||
wheel.Ability2 = int16(stmt.ColumnInt64(10))
|
||||
wheel.Ability3 = int16(stmt.ColumnInt64(11))
|
||||
wheel.Ability4 = int16(stmt.ColumnInt64(12))
|
||||
wheel.Ability5 = int16(stmt.ColumnInt64(13))
|
||||
wheel.Ability6 = int16(stmt.ColumnInt64(14))
|
||||
if stmt.ColumnType(15) != sqlite.TypeNull {
|
||||
wheel.Name = stmt.ColumnText(15)
|
||||
}
|
||||
if stmt.ColumnType(16) != sqlite.TypeNull {
|
||||
wheel.Description = stmt.ColumnText(16)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load heroic op wheel %d: %w", wheelID, err)
|
||||
}
|
||||
|
||||
// Handle nullable fields
|
||||
if name != nil {
|
||||
wheel.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
wheel.Description = *description
|
||||
if !found {
|
||||
return nil, fmt.Errorf("heroic op wheel %d not found", wheelID)
|
||||
}
|
||||
|
||||
return &wheel, nil
|
||||
@ -287,13 +281,20 @@ func (dhom *DatabaseHeroicOPManager) LoadWheel(ctx context.Context, wheelID int3
|
||||
|
||||
// SaveStarter saves a heroic op starter
|
||||
func (dhom *DatabaseHeroicOPManager) SaveStarter(ctx context.Context, starter *HeroicOPStarter) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `INSERT OR REPLACE INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
_, err := dhom.db.ExecContext(ctx, query,
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
starter.ID,
|
||||
HOTypeStarter,
|
||||
starter.StartClass,
|
||||
@ -306,7 +307,8 @@ func (dhom *DatabaseHeroicOPManager) SaveStarter(ctx context.Context, starter *H
|
||||
starter.Abilities[5],
|
||||
starter.Name,
|
||||
starter.Description,
|
||||
)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save heroic op starter %d: %w", starter.ID, err)
|
||||
}
|
||||
@ -316,13 +318,20 @@ func (dhom *DatabaseHeroicOPManager) SaveStarter(ctx context.Context, starter *H
|
||||
|
||||
// SaveWheel saves a heroic op wheel
|
||||
func (dhom *DatabaseHeroicOPManager) SaveWheel(ctx context.Context, wheel *HeroicOPWheel) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `INSERT OR REPLACE INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
_, err := dhom.db.ExecContext(ctx, query,
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
wheel.ID,
|
||||
HOTypeWheel,
|
||||
wheel.StarterLinkID,
|
||||
@ -338,7 +347,8 @@ func (dhom *DatabaseHeroicOPManager) SaveWheel(ctx context.Context, wheel *Heroi
|
||||
wheel.Abilities[5],
|
||||
wheel.Name,
|
||||
wheel.Description,
|
||||
)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save heroic op wheel %d: %w", wheel.ID, err)
|
||||
}
|
||||
@ -348,38 +358,49 @@ func (dhom *DatabaseHeroicOPManager) SaveWheel(ctx context.Context, wheel *Heroi
|
||||
|
||||
// DeleteStarter removes a starter from database
|
||||
func (dhom *DatabaseHeroicOPManager) DeleteStarter(ctx context.Context, starterID int32) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
// Use a transaction to delete starter and associated wheels
|
||||
tx, err := dhom.db.BeginTx(ctx, nil)
|
||||
err = sqlitex.Execute(conn, "BEGIN", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
defer sqlitex.Execute(conn, "ROLLBACK", nil)
|
||||
|
||||
// Delete associated wheels first
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?",
|
||||
starterID, HOTypeWheel)
|
||||
err = sqlitex.Execute(conn, "DELETE FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?", &sqlitex.ExecOptions{
|
||||
Args: []any{starterID, HOTypeWheel},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete wheels for starter %d: %w", starterID, err)
|
||||
}
|
||||
|
||||
// Delete the starter
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?",
|
||||
starterID, HOTypeStarter)
|
||||
err = sqlitex.Execute(conn, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?", &sqlitex.ExecOptions{
|
||||
Args: []any{starterID, HOTypeStarter},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete starter %d: %w", starterID, err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return sqlitex.Execute(conn, "COMMIT", nil)
|
||||
}
|
||||
|
||||
// DeleteWheel removes a wheel from database
|
||||
func (dhom *DatabaseHeroicOPManager) DeleteWheel(ctx context.Context, wheelID int32) error {
|
||||
_, err := dhom.db.ExecContext(ctx, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?",
|
||||
wheelID, HOTypeWheel)
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
err = sqlitex.Execute(conn, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?", &sqlitex.ExecOptions{
|
||||
Args: []any{wheelID, HOTypeWheel},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete wheel %d: %w", wheelID, err)
|
||||
}
|
||||
@ -389,6 +410,12 @@ func (dhom *DatabaseHeroicOPManager) DeleteWheel(ctx context.Context, wheelID in
|
||||
|
||||
// SaveHOInstance saves a heroic opportunity instance
|
||||
func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *HeroicOP) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `INSERT OR REPLACE INTO heroic_op_instances
|
||||
(id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time,
|
||||
time_remaining, total_time, complete, countered_1, countered_2, countered_3,
|
||||
@ -399,7 +426,8 @@ func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *Her
|
||||
startTimeUnix := ho.StartTime.Unix()
|
||||
wheelStartTimeUnix := ho.WheelStartTime.Unix()
|
||||
|
||||
_, err := dhom.db.ExecContext(ctx, query,
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
ho.ID,
|
||||
ho.EncounterID,
|
||||
ho.StarterID,
|
||||
@ -421,7 +449,8 @@ func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *Her
|
||||
ho.CompletedBy,
|
||||
ho.SpellName,
|
||||
ho.SpellDescription,
|
||||
)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save HO instance %d: %w", ho.ID, err)
|
||||
}
|
||||
@ -431,6 +460,12 @@ func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *Her
|
||||
|
||||
// LoadHOInstance retrieves a heroic opportunity instance
|
||||
func (dhom *DatabaseHeroicOPManager) LoadHOInstance(ctx context.Context, instanceID int64) (*HeroicOP, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time,
|
||||
time_remaining, total_time, complete, countered_1, countered_2, countered_3,
|
||||
countered_4, countered_5, countered_6, shift_used, starter_progress,
|
||||
@ -438,46 +473,50 @@ func (dhom *DatabaseHeroicOPManager) LoadHOInstance(ctx context.Context, instanc
|
||||
FROM heroic_op_instances WHERE id = ?`
|
||||
|
||||
var ho HeroicOP
|
||||
var startTimeUnix, wheelStartTimeUnix int64
|
||||
var spellName, spellDescription *string
|
||||
|
||||
err := dhom.db.QueryRowContext(ctx, query, instanceID).Scan(
|
||||
&ho.ID,
|
||||
&ho.EncounterID,
|
||||
&ho.StarterID,
|
||||
&ho.WheelID,
|
||||
&ho.State,
|
||||
&startTimeUnix,
|
||||
&wheelStartTimeUnix,
|
||||
&ho.TimeRemaining,
|
||||
&ho.TotalTime,
|
||||
&ho.Complete,
|
||||
&ho.Countered[0],
|
||||
&ho.Countered[1],
|
||||
&ho.Countered[2],
|
||||
&ho.Countered[3],
|
||||
&ho.Countered[4],
|
||||
&ho.Countered[5],
|
||||
&ho.ShiftUsed,
|
||||
&ho.StarterProgress,
|
||||
&ho.CompletedBy,
|
||||
&spellName,
|
||||
&spellDescription,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load HO instance %d: %w", instanceID, err)
|
||||
var found bool
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{instanceID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
ho.ID = stmt.ColumnInt64(0)
|
||||
ho.EncounterID = int32(stmt.ColumnInt64(1))
|
||||
ho.StarterID = int32(stmt.ColumnInt64(2))
|
||||
ho.WheelID = int32(stmt.ColumnInt64(3))
|
||||
ho.State = int8(stmt.ColumnInt64(4))
|
||||
startTimeUnix := stmt.ColumnInt64(5)
|
||||
wheelStartTimeUnix := stmt.ColumnInt64(6)
|
||||
ho.TimeRemaining = int32(stmt.ColumnInt64(7))
|
||||
ho.TotalTime = int32(stmt.ColumnInt64(8))
|
||||
ho.Complete = int8(stmt.ColumnInt64(9))
|
||||
ho.Countered[0] = int8(stmt.ColumnInt64(10))
|
||||
ho.Countered[1] = int8(stmt.ColumnInt64(11))
|
||||
ho.Countered[2] = int8(stmt.ColumnInt64(12))
|
||||
ho.Countered[3] = int8(stmt.ColumnInt64(13))
|
||||
ho.Countered[4] = int8(stmt.ColumnInt64(14))
|
||||
ho.Countered[5] = int8(stmt.ColumnInt64(15))
|
||||
ho.ShiftUsed = int8(stmt.ColumnInt64(16))
|
||||
ho.StarterProgress = int8(stmt.ColumnInt64(17))
|
||||
ho.CompletedBy = int32(stmt.ColumnInt64(18))
|
||||
if stmt.ColumnType(19) != sqlite.TypeNull {
|
||||
ho.SpellName = stmt.ColumnText(19)
|
||||
}
|
||||
if stmt.ColumnType(20) != sqlite.TypeNull {
|
||||
ho.SpellDescription = stmt.ColumnText(20)
|
||||
}
|
||||
|
||||
// Convert timestamps
|
||||
ho.StartTime = time.Unix(startTimeUnix, 0)
|
||||
ho.WheelStartTime = time.Unix(wheelStartTimeUnix, 0)
|
||||
|
||||
// Handle nullable fields
|
||||
if spellName != nil {
|
||||
ho.SpellName = *spellName
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load HO instance %d: %w", instanceID, err)
|
||||
}
|
||||
if spellDescription != nil {
|
||||
ho.SpellDescription = *spellDescription
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("HO instance %d not found", instanceID)
|
||||
}
|
||||
|
||||
// Initialize maps
|
||||
@ -489,7 +528,15 @@ func (dhom *DatabaseHeroicOPManager) LoadHOInstance(ctx context.Context, instanc
|
||||
|
||||
// DeleteHOInstance removes a heroic opportunity instance
|
||||
func (dhom *DatabaseHeroicOPManager) DeleteHOInstance(ctx context.Context, instanceID int64) error {
|
||||
_, err := dhom.db.ExecContext(ctx, "DELETE FROM heroic_op_instances WHERE id = ?", instanceID)
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
err = sqlitex.Execute(conn, "DELETE FROM heroic_op_instances WHERE id = ?", &sqlitex.ExecOptions{
|
||||
Args: []any{instanceID},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete HO instance %d: %w", instanceID, err)
|
||||
}
|
||||
@ -499,13 +546,20 @@ func (dhom *DatabaseHeroicOPManager) DeleteHOInstance(ctx context.Context, insta
|
||||
|
||||
// SaveHOEvent saves a heroic opportunity event
|
||||
func (dhom *DatabaseHeroicOPManager) SaveHOEvent(ctx context.Context, event *HeroicOPEvent) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
timestampUnix := event.Timestamp.Unix()
|
||||
|
||||
_, err := dhom.db.ExecContext(ctx, query,
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{
|
||||
event.ID,
|
||||
event.InstanceID,
|
||||
event.EventType,
|
||||
@ -513,7 +567,8 @@ func (dhom *DatabaseHeroicOPManager) SaveHOEvent(ctx context.Context, event *Her
|
||||
event.AbilityIcon,
|
||||
timestampUnix,
|
||||
event.Data,
|
||||
)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save HO event %d: %w", event.ID, err)
|
||||
}
|
||||
@ -523,51 +578,49 @@ func (dhom *DatabaseHeroicOPManager) SaveHOEvent(ctx context.Context, event *Her
|
||||
|
||||
// LoadHOEvents retrieves events for a heroic opportunity instance
|
||||
func (dhom *DatabaseHeroicOPManager) LoadHOEvents(ctx context.Context, instanceID int64) ([]HeroicOPEvent, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := `SELECT id, instance_id, event_type, character_id, ability_icon, timestamp, data
|
||||
FROM heroic_op_events WHERE instance_id = ? ORDER BY timestamp ASC`
|
||||
|
||||
rows, err := dhom.db.QueryContext(ctx, query, instanceID)
|
||||
var events []HeroicOPEvent
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{instanceID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
var event HeroicOPEvent
|
||||
event.ID = stmt.ColumnInt64(0)
|
||||
event.InstanceID = stmt.ColumnInt64(1)
|
||||
event.EventType = int(stmt.ColumnInt64(2))
|
||||
event.CharacterID = int32(stmt.ColumnInt64(3))
|
||||
event.AbilityIcon = int16(stmt.ColumnInt64(4))
|
||||
timestampUnix := stmt.ColumnInt64(5)
|
||||
event.Timestamp = time.Unix(timestampUnix, 0)
|
||||
if stmt.ColumnType(6) != sqlite.TypeNull {
|
||||
event.Data = stmt.ColumnText(6)
|
||||
}
|
||||
events = append(events, event)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query HO events for instance %d: %w", instanceID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []HeroicOPEvent
|
||||
for rows.Next() {
|
||||
var event HeroicOPEvent
|
||||
var timestampUnix int64
|
||||
var data *string
|
||||
|
||||
err := rows.Scan(
|
||||
&event.ID,
|
||||
&event.InstanceID,
|
||||
&event.EventType,
|
||||
&event.CharacterID,
|
||||
&event.AbilityIcon,
|
||||
×tampUnix,
|
||||
&data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan HO event row: %w", err)
|
||||
}
|
||||
|
||||
event.Timestamp = time.Unix(timestampUnix, 0)
|
||||
if data != nil {
|
||||
event.Data = *data
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating HO event rows: %w", err)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// GetHOStatistics retrieves statistics for a character
|
||||
func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, characterID int32) (*HeroicOPStatistics, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
// This is a simplified implementation - in practice you'd want more complex statistics
|
||||
stats := &HeroicOPStatistics{
|
||||
ParticipationStats: make(map[int32]int64),
|
||||
@ -576,7 +629,13 @@ func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, charac
|
||||
// Count total HOs started by this character
|
||||
query := `SELECT COUNT(*) FROM heroic_op_events
|
||||
WHERE character_id = ? AND event_type = ?`
|
||||
err := dhom.db.QueryRowContext(ctx, query, characterID, EventHOStarted).Scan(&stats.TotalHOsStarted)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{characterID, EventHOStarted},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
stats.TotalHOsStarted = stmt.ColumnInt64(0)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get HO started count: %w", err)
|
||||
}
|
||||
@ -584,7 +643,13 @@ func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, charac
|
||||
// Count total HOs completed by this character
|
||||
query = `SELECT COUNT(*) FROM heroic_op_events
|
||||
WHERE character_id = ? AND event_type = ?`
|
||||
err = dhom.db.QueryRowContext(ctx, query, characterID, EventHOCompleted).Scan(&stats.TotalHOsCompleted)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{characterID, EventHOCompleted},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
stats.TotalHOsCompleted = stmt.ColumnInt64(0)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get HO completed count: %w", err)
|
||||
}
|
||||
@ -601,10 +666,22 @@ func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, charac
|
||||
|
||||
// GetNextStarterID returns the next available starter ID
|
||||
func (dhom *DatabaseHeroicOPManager) GetNextStarterID(ctx context.Context) (int32, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_ops WHERE ho_type = ?"
|
||||
|
||||
var nextID int32
|
||||
err := dhom.db.QueryRowContext(ctx, query, HOTypeStarter).Scan(&nextID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{HOTypeStarter},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
nextID = int32(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get next starter ID: %w", err)
|
||||
}
|
||||
@ -614,10 +691,22 @@ func (dhom *DatabaseHeroicOPManager) GetNextStarterID(ctx context.Context) (int3
|
||||
|
||||
// GetNextWheelID returns the next available wheel ID
|
||||
func (dhom *DatabaseHeroicOPManager) GetNextWheelID(ctx context.Context) (int32, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_ops WHERE ho_type = ?"
|
||||
|
||||
var nextID int32
|
||||
err := dhom.db.QueryRowContext(ctx, query, HOTypeWheel).Scan(&nextID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: []any{HOTypeWheel},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
nextID = int32(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get next wheel ID: %w", err)
|
||||
}
|
||||
@ -627,10 +716,21 @@ func (dhom *DatabaseHeroicOPManager) GetNextWheelID(ctx context.Context) (int32,
|
||||
|
||||
// GetNextInstanceID returns the next available instance ID
|
||||
func (dhom *DatabaseHeroicOPManager) GetNextInstanceID(ctx context.Context) (int64, error) {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_op_instances"
|
||||
|
||||
var nextID int64
|
||||
err := dhom.db.QueryRowContext(ctx, query).Scan(&nextID)
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
nextID = stmt.ColumnInt64(0)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get next instance ID: %w", err)
|
||||
}
|
||||
@ -640,6 +740,12 @@ func (dhom *DatabaseHeroicOPManager) GetNextInstanceID(ctx context.Context) (int
|
||||
|
||||
// EnsureHOTables creates the heroic opportunity tables if they don't exist
|
||||
func (dhom *DatabaseHeroicOPManager) EnsureHOTables(ctx context.Context) error {
|
||||
conn, err := dhom.pool.Take(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database connection: %w", err)
|
||||
}
|
||||
defer dhom.pool.Put(conn)
|
||||
|
||||
queries := []string{
|
||||
`CREATE TABLE IF NOT EXISTS heroic_ops (
|
||||
id INTEGER NOT NULL,
|
||||
@ -700,7 +806,7 @@ func (dhom *DatabaseHeroicOPManager) EnsureHOTables(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for i, query := range queries {
|
||||
_, err := dhom.db.ExecContext(ctx, query)
|
||||
err := sqlitex.Execute(conn, query, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HO table %d: %w", i+1, err)
|
||||
}
|
||||
@ -720,7 +826,7 @@ func (dhom *DatabaseHeroicOPManager) EnsureHOTables(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for i, query := range indexes {
|
||||
_, err := dhom.db.ExecContext(ctx, query)
|
||||
err := sqlitex.Execute(conn, query, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HO index %d: %w", i+1, err)
|
||||
}
|
||||
|
721
internal/heroic_ops/database_test.go
Normal file
721
internal/heroic_ops/database_test.go
Normal file
@ -0,0 +1,721 @@
|
||||
package heroic_ops
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// createTestPool creates a temporary test database pool
|
||||
func createTestPool(t *testing.T) *sqlitex.Pool {
|
||||
// Create temporary directory for test database
|
||||
tempDir := t.TempDir()
|
||||
dbPath := filepath.Join(tempDir, "test_heroic_ops.db")
|
||||
|
||||
// Create and initialize database pool
|
||||
pool, err := sqlitex.NewPool(dbPath, sqlitex.PoolOptions{
|
||||
Flags: sqlite.OpenReadWrite | sqlite.OpenCreate,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database pool: %v", err)
|
||||
}
|
||||
|
||||
// Create heroic ops tables for testing
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
err = dhom.EnsureHOTables(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create heroic ops tables: %v", err)
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// execSQL is a helper to execute SQL with parameters
|
||||
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...interface{}) {
|
||||
conn, err := pool.Take(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get connection: %v", err)
|
||||
}
|
||||
defer pool.Put(conn)
|
||||
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Args: args,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute SQL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadStarters tests loading starters from database
|
||||
func TestDatabaseHeroicOPManager_LoadStarters(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test starter data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
1, HOTypeStarter, 5, 100, 10, 20, 30, 40, 50, 60, "Test Starter", "A test starter")
|
||||
|
||||
// Test loading starters
|
||||
starters, err := dhom.LoadStarters(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load starters: %v", err)
|
||||
}
|
||||
|
||||
if len(starters) != 1 {
|
||||
t.Errorf("Expected 1 starter, got %d", len(starters))
|
||||
}
|
||||
|
||||
if len(starters) > 0 {
|
||||
starter := starters[0]
|
||||
if starter.ID != 1 {
|
||||
t.Errorf("Expected starter ID 1, got %d", starter.ID)
|
||||
}
|
||||
if starter.StarterClass != 5 {
|
||||
t.Errorf("Expected starter class 5, got %d", starter.StarterClass)
|
||||
}
|
||||
if starter.Name != "Test Starter" {
|
||||
t.Errorf("Expected name 'Test Starter', got '%s'", starter.Name)
|
||||
}
|
||||
if starter.Ability1 != 10 {
|
||||
t.Errorf("Expected ability1 10, got %d", starter.Ability1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadStarter tests loading a specific starter
|
||||
func TestDatabaseHeroicOPManager_LoadStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test starter data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
2, HOTypeStarter, 10, 200, 11, 21, 31, 41, 51, 61, "Specific Starter", "A specific test starter")
|
||||
|
||||
// Test loading specific starter
|
||||
starter, err := dhom.LoadStarter(ctx, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load starter: %v", err)
|
||||
}
|
||||
|
||||
if starter.ID != 2 {
|
||||
t.Errorf("Expected starter ID 2, got %d", starter.ID)
|
||||
}
|
||||
if starter.StarterClass != 10 {
|
||||
t.Errorf("Expected starter class 10, got %d", starter.StarterClass)
|
||||
}
|
||||
if starter.Name != "Specific Starter" {
|
||||
t.Errorf("Expected name 'Specific Starter', got '%s'", starter.Name)
|
||||
}
|
||||
|
||||
// Test loading non-existent starter
|
||||
_, err = dhom.LoadStarter(ctx, 999)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading non-existent starter, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadWheels tests loading wheels from database
|
||||
func TestDatabaseHeroicOPManager_LoadWheels(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test wheel data
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
10, HOTypeWheel, 1, 1, 300, 1001, 0.75, 15, 25, 35, 45, 55, 65, "Test Wheel", "A test wheel")
|
||||
|
||||
// Test loading wheels
|
||||
wheels, err := dhom.LoadWheels(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 1 {
|
||||
t.Errorf("Expected 1 wheel, got %d", len(wheels))
|
||||
}
|
||||
|
||||
if len(wheels) > 0 {
|
||||
wheel := wheels[0]
|
||||
if wheel.ID != 10 {
|
||||
t.Errorf("Expected wheel ID 10, got %d", wheel.ID)
|
||||
}
|
||||
if wheel.StarterLinkID != 1 {
|
||||
t.Errorf("Expected starter link ID 1, got %d", wheel.StarterLinkID)
|
||||
}
|
||||
if wheel.SpellID != 1001 {
|
||||
t.Errorf("Expected spell ID 1001, got %d", wheel.SpellID)
|
||||
}
|
||||
if wheel.Chance != 0.75 {
|
||||
t.Errorf("Expected chance 0.75, got %f", wheel.Chance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_LoadWheelsForStarter tests loading wheels for specific starter
|
||||
func TestDatabaseHeroicOPManager_LoadWheelsForStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starterID := int32(5)
|
||||
|
||||
// Insert multiple wheels for the same starter
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
20, HOTypeWheel, starterID, 1, 400, 1002, 0.5, 16, 26, 36, 46, 56, 66, "Wheel 1", "First wheel")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
21, HOTypeWheel, starterID, 2, 500, 1003, 0.8, 17, 27, 37, 47, 57, 67, "Wheel 2", "Second wheel")
|
||||
|
||||
// Test loading wheels for specific starter
|
||||
wheels, err := dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels for starter: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 2 {
|
||||
t.Errorf("Expected 2 wheels, got %d", len(wheels))
|
||||
}
|
||||
|
||||
// Test loading wheels for non-existent starter
|
||||
wheels, err = dhom.LoadWheelsForStarter(ctx, 999)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels for non-existent starter: %v", err)
|
||||
}
|
||||
|
||||
if len(wheels) != 0 {
|
||||
t.Errorf("Expected 0 wheels for non-existent starter, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_SaveStarter tests saving a heroic op starter
|
||||
func TestDatabaseHeroicOPManager_SaveStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test starter
|
||||
starter := &HeroicOPStarter{
|
||||
ID: 100,
|
||||
StartClass: 15,
|
||||
StarterIcon: 500,
|
||||
Abilities: [6]int16{70, 80, 90, 100, 110, 120},
|
||||
Name: "Saved Starter",
|
||||
Description: "A saved test starter",
|
||||
}
|
||||
|
||||
// Save the starter
|
||||
err := dhom.SaveStarter(ctx, starter)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save starter: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadStarter(ctx, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load saved starter: %v", err)
|
||||
}
|
||||
|
||||
if loaded.StarterClass != 15 {
|
||||
t.Errorf("Expected starter class 15, got %d", loaded.StarterClass)
|
||||
}
|
||||
if loaded.Name != "Saved Starter" {
|
||||
t.Errorf("Expected name 'Saved Starter', got '%s'", loaded.Name)
|
||||
}
|
||||
if loaded.Ability1 != 70 {
|
||||
t.Errorf("Expected ability1 70, got %d", loaded.Ability1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_SaveWheel tests saving a heroic op wheel
|
||||
func TestDatabaseHeroicOPManager_SaveWheel(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test wheel
|
||||
wheel := &HeroicOPWheel{
|
||||
ID: 200,
|
||||
StarterLinkID: 100,
|
||||
Order: 2,
|
||||
ShiftIcon: 600,
|
||||
SpellID: 2001,
|
||||
Chance: 0.9,
|
||||
Abilities: [6]int16{71, 81, 91, 101, 111, 121},
|
||||
Name: "Saved Wheel",
|
||||
Description: "A saved test wheel",
|
||||
}
|
||||
|
||||
// Save the wheel
|
||||
err := dhom.SaveWheel(ctx, wheel)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save wheel: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadWheel(ctx, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load saved wheel: %v", err)
|
||||
}
|
||||
|
||||
if loaded.StarterLinkID != 100 {
|
||||
t.Errorf("Expected starter link ID 100, got %d", loaded.StarterLinkID)
|
||||
}
|
||||
if loaded.SpellID != 2001 {
|
||||
t.Errorf("Expected spell ID 2001, got %d", loaded.SpellID)
|
||||
}
|
||||
if loaded.Chance != 0.9 {
|
||||
t.Errorf("Expected chance 0.9, got %f", loaded.Chance)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_DeleteStarter tests deleting a starter and its wheels
|
||||
func TestDatabaseHeroicOPManager_DeleteStarter(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starterID := int32(300)
|
||||
|
||||
// Insert starter
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
starterID, HOTypeStarter, 20, 700, 72, 82, 92, 102, 112, 122, "Delete Test Starter", "To be deleted")
|
||||
|
||||
// Insert associated wheels
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
400, HOTypeWheel, starterID, 1, 800, 3001, 0.6, 73, 83, 93, 103, 113, 123, "Wheel to Delete", "Will be deleted")
|
||||
|
||||
// Verify they exist
|
||||
_, err := dhom.LoadStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Starter should exist before deletion: %v", err)
|
||||
}
|
||||
|
||||
wheels, err := dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels before deletion: %v", err)
|
||||
}
|
||||
if len(wheels) != 1 {
|
||||
t.Errorf("Expected 1 wheel before deletion, got %d", len(wheels))
|
||||
}
|
||||
|
||||
// Delete the starter
|
||||
err = dhom.DeleteStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete starter: %v", err)
|
||||
}
|
||||
|
||||
// Verify they're gone
|
||||
_, err = dhom.LoadStarter(ctx, starterID)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading deleted starter, got nil")
|
||||
}
|
||||
|
||||
wheels, err = dhom.LoadWheelsForStarter(ctx, starterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load wheels after deletion: %v", err)
|
||||
}
|
||||
if len(wheels) != 0 {
|
||||
t.Errorf("Expected 0 wheels after deletion, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_HeroicOPInstance tests HO instance operations
|
||||
func TestDatabaseHeroicOPManager_HeroicOPInstance(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a test HO instance
|
||||
ho := &HeroicOP{
|
||||
ID: 1000,
|
||||
EncounterID: 500,
|
||||
StarterID: 10,
|
||||
WheelID: 20,
|
||||
State: int8(HOStateWheelPhase),
|
||||
StartTime: time.Now(),
|
||||
WheelStartTime: time.Now().Add(5 * time.Second),
|
||||
TimeRemaining: 8000,
|
||||
TotalTime: 10000,
|
||||
Complete: 0,
|
||||
Countered: [6]int8{1, 1, 0, 0, 0, 0},
|
||||
ShiftUsed: 0,
|
||||
StarterProgress: 3,
|
||||
CompletedBy: 0,
|
||||
SpellName: "Test Spell",
|
||||
SpellDescription: "A test completion spell",
|
||||
}
|
||||
|
||||
// Save the HO instance
|
||||
err := dhom.SaveHOInstance(ctx, ho)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save HO instance: %v", err)
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loaded, err := dhom.LoadHOInstance(ctx, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load HO instance: %v", err)
|
||||
}
|
||||
|
||||
if loaded.EncounterID != 500 {
|
||||
t.Errorf("Expected encounter ID 500, got %d", loaded.EncounterID)
|
||||
}
|
||||
if loaded.State != int8(HOStateWheelPhase) {
|
||||
t.Errorf("Expected state %d, got %d", HOStateWheelPhase, loaded.State)
|
||||
}
|
||||
if loaded.TimeRemaining != 8000 {
|
||||
t.Errorf("Expected time remaining 8000, got %d", loaded.TimeRemaining)
|
||||
}
|
||||
if loaded.SpellName != "Test Spell" {
|
||||
t.Errorf("Expected spell name 'Test Spell', got '%s'", loaded.SpellName)
|
||||
}
|
||||
|
||||
// Delete the instance
|
||||
err = dhom.DeleteHOInstance(ctx, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete HO instance: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's gone
|
||||
_, err = dhom.LoadHOInstance(ctx, 1000)
|
||||
if err == nil {
|
||||
t.Error("Expected error loading deleted HO instance, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_HeroicOPEvents tests HO event operations
|
||||
func TestDatabaseHeroicOPManager_HeroicOPEvents(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
instanceID := int64(2000)
|
||||
|
||||
// Create test events
|
||||
events := []*HeroicOPEvent{
|
||||
{
|
||||
ID: 1,
|
||||
InstanceID: instanceID,
|
||||
EventType: EventHOStarted,
|
||||
CharacterID: 100,
|
||||
AbilityIcon: 50,
|
||||
Timestamp: time.Now(),
|
||||
Data: "started",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
InstanceID: instanceID,
|
||||
EventType: EventHOAbilityUsed,
|
||||
CharacterID: 101,
|
||||
AbilityIcon: 51,
|
||||
Timestamp: time.Now().Add(1 * time.Second),
|
||||
Data: "ability used",
|
||||
},
|
||||
}
|
||||
|
||||
// Save events
|
||||
for _, event := range events {
|
||||
err := dhom.SaveHOEvent(ctx, event)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save HO event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load and verify
|
||||
loadedEvents, err := dhom.LoadHOEvents(ctx, instanceID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load HO events: %v", err)
|
||||
}
|
||||
|
||||
if len(loadedEvents) != 2 {
|
||||
t.Errorf("Expected 2 events, got %d", len(loadedEvents))
|
||||
}
|
||||
|
||||
// Events should be ordered by timestamp
|
||||
if len(loadedEvents) >= 2 {
|
||||
if loadedEvents[0].EventType != EventHOStarted {
|
||||
t.Errorf("Expected first event type %d, got %d", EventHOStarted, loadedEvents[0].EventType)
|
||||
}
|
||||
if loadedEvents[1].EventType != EventHOAbilityUsed {
|
||||
t.Errorf("Expected second event type %d, got %d", EventHOAbilityUsed, loadedEvents[1].EventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_Statistics tests HO statistics retrieval
|
||||
func TestDatabaseHeroicOPManager_Statistics(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
characterID := int32(1001)
|
||||
|
||||
// Insert test events for statistics
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
1, 100, EventHOStarted, characterID, 0, time.Now().Unix(), "")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
2, 100, EventHOCompleted, characterID, 0, time.Now().Add(10*time.Second).Unix(), "")
|
||||
|
||||
execSQL(t, pool, `INSERT INTO heroic_op_events
|
||||
(id, instance_id, event_type, character_id, ability_icon, timestamp, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
3, 101, EventHOStarted, characterID, 0, time.Now().Add(20*time.Second).Unix(), "")
|
||||
|
||||
// Get statistics
|
||||
stats, err := dhom.GetHOStatistics(ctx, characterID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get HO statistics: %v", err)
|
||||
}
|
||||
|
||||
if stats.TotalHOsStarted != 2 {
|
||||
t.Errorf("Expected 2 HOs started, got %d", stats.TotalHOsStarted)
|
||||
}
|
||||
if stats.TotalHOsCompleted != 1 {
|
||||
t.Errorf("Expected 1 HO completed, got %d", stats.TotalHOsCompleted)
|
||||
}
|
||||
if stats.SuccessRate != 50.0 {
|
||||
t.Errorf("Expected success rate 50.0, got %f", stats.SuccessRate)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDatabaseHeroicOPManager_NextIDs tests ID generation
|
||||
func TestDatabaseHeroicOPManager_NextIDs(t *testing.T) {
|
||||
pool := createTestPool(t)
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Test next starter ID (should be 1 for empty database)
|
||||
starterID, err := dhom.GetNextStarterID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next starter ID: %v", err)
|
||||
}
|
||||
if starterID != 1 {
|
||||
t.Errorf("Expected next starter ID 1, got %d", starterID)
|
||||
}
|
||||
|
||||
// Insert a starter and test again
|
||||
execSQL(t, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
5, HOTypeStarter, 1, 100, 1, 2, 3, 4, 5, 6, "Test", "Test")
|
||||
|
||||
starterID, err = dhom.GetNextStarterID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next starter ID after insert: %v", err)
|
||||
}
|
||||
if starterID != 6 {
|
||||
t.Errorf("Expected next starter ID 6, got %d", starterID)
|
||||
}
|
||||
|
||||
// Test next wheel ID
|
||||
wheelID, err := dhom.GetNextWheelID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next wheel ID: %v", err)
|
||||
}
|
||||
if wheelID != 1 {
|
||||
t.Errorf("Expected next wheel ID 1, got %d", wheelID)
|
||||
}
|
||||
|
||||
// Test next instance ID
|
||||
instanceID, err := dhom.GetNextInstanceID(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get next instance ID: %v", err)
|
||||
}
|
||||
if instanceID != 1 {
|
||||
t.Errorf("Expected next instance ID 1, got %d", instanceID)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_LoadStarters benchmarks loading starters
|
||||
func BenchmarkDatabaseHeroicOPManager_LoadStarters(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test data
|
||||
for i := 1; i <= 50; i++ {
|
||||
execSQL(&testing.T{}, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
i, HOTypeStarter, i%10+1, i*10,
|
||||
i*1, i*2, i*3, i*4, i*5, i*6,
|
||||
fmt.Sprintf("Starter %d", i), fmt.Sprintf("Description %d", i))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
starters, err := dhom.LoadStarters(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to load starters: %v", err)
|
||||
}
|
||||
if len(starters) != 50 {
|
||||
b.Errorf("Expected 50 starters, got %d", len(starters))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_LoadWheels benchmarks loading wheels
|
||||
func BenchmarkDatabaseHeroicOPManager_LoadWheels(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
// Insert test wheel data
|
||||
for i := 1; i <= 100; i++ {
|
||||
execSQL(&testing.T{}, pool, `INSERT INTO heroic_ops
|
||||
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
|
||||
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
|
||||
ability5, ability6, name, description)
|
||||
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
i, HOTypeWheel, i%10+1, i%3+1, i*10, i*100+1000, float32(i%100)/100.0,
|
||||
i*1, i*2, i*3, i*4, i*5, i*6,
|
||||
fmt.Sprintf("Wheel %d", i), fmt.Sprintf("Wheel Description %d", i))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
wheels, err := dhom.LoadWheels(ctx)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to load wheels: %v", err)
|
||||
}
|
||||
if len(wheels) != 100 {
|
||||
b.Errorf("Expected 100 wheels, got %d", len(wheels))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_SaveStarter benchmarks saving starters
|
||||
func BenchmarkDatabaseHeroicOPManager_SaveStarter(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
starter := &HeroicOPStarter{
|
||||
ID: 1000,
|
||||
StartClass: 5,
|
||||
StarterIcon: 100,
|
||||
Abilities: [6]int16{10, 20, 30, 40, 50, 60},
|
||||
Name: "Benchmark Starter",
|
||||
Description: "A benchmark test starter",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := dhom.SaveStarter(ctx, starter)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to save starter: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDatabaseHeroicOPManager_SaveHOInstance benchmarks saving HO instances
|
||||
func BenchmarkDatabaseHeroicOPManager_SaveHOInstance(b *testing.B) {
|
||||
pool := createTestPool(&testing.T{})
|
||||
defer pool.Close()
|
||||
|
||||
dhom := NewDatabaseHeroicOPManager(pool)
|
||||
ctx := context.Background()
|
||||
|
||||
ho := &HeroicOP{
|
||||
ID: 5000,
|
||||
EncounterID: 1000,
|
||||
StarterID: 50,
|
||||
WheelID: 100,
|
||||
State: int8(HOStateWheelPhase),
|
||||
StartTime: time.Now(),
|
||||
WheelStartTime: time.Now(),
|
||||
TimeRemaining: 10000,
|
||||
TotalTime: 10000,
|
||||
Complete: 0,
|
||||
Countered: [6]int8{0, 0, 0, 0, 0, 0},
|
||||
ShiftUsed: 0,
|
||||
StarterProgress: 0,
|
||||
CompletedBy: 0,
|
||||
SpellName: "Benchmark Spell",
|
||||
SpellDescription: "A benchmark spell",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := dhom.SaveHOInstance(ctx, ho)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to save HO instance: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,9 @@ func NewHeroicOPManager(masterList *MasterHeroicOPList, database HeroicOPDatabas
|
||||
encounterHOs: make(map[int32][]*HeroicOP),
|
||||
masterList: masterList,
|
||||
database: database,
|
||||
clientManager: clientManager,
|
||||
encounterManager: encounterManager,
|
||||
playerManager: playerManager,
|
||||
nextInstanceID: 1,
|
||||
defaultWheelTimer: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
|
||||
maxConcurrentHOs: MaxConcurrentHOs,
|
||||
@ -415,7 +418,7 @@ func (hom *HeroicOPManager) completeHO(ctx context.Context, ho *HeroicOP, wheel
|
||||
|
||||
// Cast completion spell
|
||||
if wheel.SpellID > 0 {
|
||||
participants := ho.GetParticipants()
|
||||
_ = ho.GetParticipants() // participants will be used when spell manager is integrated
|
||||
// TODO: Cast spell on participants through spell manager
|
||||
// hom.spellManager.CastSpell(completedBy, wheel.SpellID, participants)
|
||||
}
|
||||
@ -547,9 +550,9 @@ func (hom *HeroicOPManager) sendShiftUpdate(ho *HeroicOP, oldWheelID, newWheelID
|
||||
participants := ho.GetParticipants()
|
||||
packetBuilder := NewHeroicOPPacketBuilder(0)
|
||||
|
||||
for _, characterID := range participants {
|
||||
for range participants {
|
||||
if packet, err := packetBuilder.BuildHOShiftPacket(ho, oldWheelID, newWheelID); err == nil {
|
||||
// TODO: Send packet through client manager
|
||||
// TODO: Send packet through client manager using characterID
|
||||
_ = packet // Placeholder
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,9 @@ type HeroicOPManager struct {
|
||||
database HeroicOPDatabase
|
||||
eventHandler HeroicOPEventHandler
|
||||
logger LogHandler
|
||||
clientManager ClientManager
|
||||
encounterManager EncounterManager
|
||||
playerManager PlayerManager
|
||||
nextInstanceID int64
|
||||
// Configuration
|
||||
defaultWheelTimer int32 // milliseconds
|
||||
|
Loading…
x
Reference in New Issue
Block a user