fixes for heroic ops

This commit is contained in:
Sky Johnson 2025-08-03 20:08:00 -05:00
parent 966501670f
commit 1c26bb600d
5 changed files with 1192 additions and 359 deletions

View File

@ -6,7 +6,7 @@ const (
MaxAbilities = 6 MaxAbilities = 6
// Special ability icon values // 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 AbilityIconNone = 0 // No ability required
// Default wheel timer (in seconds) // Default wheel timer (in seconds)

View File

@ -5,119 +5,121 @@ import (
"fmt" "fmt"
"time" "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 { type DatabaseHeroicOPManager struct {
db *database.DB pool *sqlitex.Pool
} }
// NewDatabaseHeroicOPManager creates a new database heroic OP manager // NewDatabaseHeroicOPManager creates a new database heroic OP manager
func NewDatabaseHeroicOPManager(db *database.DB) *DatabaseHeroicOPManager { func NewDatabaseHeroicOPManager(pool *sqlitex.Pool) *DatabaseHeroicOPManager {
return &DatabaseHeroicOPManager{ return &DatabaseHeroicOPManager{
db: db, pool: pool,
} }
} }
// LoadStarters retrieves all starters from database // LoadStarters retrieves all starters from database
func (dhom *DatabaseHeroicOPManager) LoadStarters(ctx context.Context) ([]HeroicOPData, error) { 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, 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, 0 as chain_order, 0 as shift_icon, 0 as spell_id, 0.0 as chance,
ability1, ability2, ability3, ability4, ability5, ability6, ability1, ability2, ability3, ability4, ability5, ability6,
name, description FROM heroic_ops WHERE ho_type = ?` 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 { if err != nil {
return nil, fmt.Errorf("failed to query heroic op starters: %w", err) 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 return starters, nil
} }
// LoadStarter retrieves a specific starter from database // LoadStarter retrieves a specific starter from database
func (dhom *DatabaseHeroicOPManager) LoadStarter(ctx context.Context, starterID int32) (*HeroicOPData, error) { 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, 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, 0 as chain_order, 0 as shift_icon, 0 as spell_id, 0.0 as chance,
ability1, ability2, ability3, ability4, ability5, ability6, ability1, ability2, ability3, ability4, ability5, ability6,
name, description FROM heroic_ops WHERE id = ? AND ho_type = ?` name, description FROM heroic_ops WHERE id = ? AND ho_type = ?`
var starter HeroicOPData var starter HeroicOPData
var name, description *string var found bool
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := dhom.db.QueryRowContext(ctx, query, starterID, HOTypeStarter).Scan( Args: []any{starterID, HOTypeStarter},
&starter.ID, ResultFunc: func(stmt *sqlite.Stmt) error {
&starter.HOType, found = true
&starter.StarterClass, starter.ID = int32(stmt.ColumnInt64(0))
&starter.StarterIcon, starter.HOType = stmt.ColumnText(1)
&starter.StarterLinkID, starter.StarterClass = int8(stmt.ColumnInt64(2))
&starter.ChainOrder, starter.StarterIcon = int16(stmt.ColumnInt64(3))
&starter.ShiftIcon, starter.StarterLinkID = int32(stmt.ColumnInt64(4))
&starter.SpellID, starter.ChainOrder = int8(stmt.ColumnInt64(5))
&starter.Chance, starter.ShiftIcon = int16(stmt.ColumnInt64(6))
&starter.Ability1, starter.SpellID = int32(stmt.ColumnInt64(7))
&starter.Ability2, starter.Chance = float32(stmt.ColumnFloat(8))
&starter.Ability3, starter.Ability1 = int16(stmt.ColumnInt64(9))
&starter.Ability4, starter.Ability2 = int16(stmt.ColumnInt64(10))
&starter.Ability5, starter.Ability3 = int16(stmt.ColumnInt64(11))
&starter.Ability6, starter.Ability4 = int16(stmt.ColumnInt64(12))
&name, starter.Ability5 = int16(stmt.ColumnInt64(13))
&description, 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 { if err != nil {
return nil, fmt.Errorf("failed to load heroic op starter %d: %w", starterID, err) return nil, fmt.Errorf("failed to load heroic op starter %d: %w", starterID, err)
} }
// Handle nullable fields if !found {
if name != nil { return nil, fmt.Errorf("heroic op starter %d not found", starterID)
starter.Name = *name
}
if description != nil {
starter.Description = *description
} }
return &starter, nil return &starter, nil
@ -125,161 +127,153 @@ func (dhom *DatabaseHeroicOPManager) LoadStarter(ctx context.Context, starterID
// LoadWheels retrieves all wheels from database // LoadWheels retrieves all wheels from database
func (dhom *DatabaseHeroicOPManager) LoadWheels(ctx context.Context) ([]HeroicOPData, error) { 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, query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
chain_order, shift_icon, spell_id, chance, chain_order, shift_icon, spell_id, chance,
ability1, ability2, ability3, ability4, ability5, ability6, ability1, ability2, ability3, ability4, ability5, ability6,
name, description FROM heroic_ops WHERE ho_type = ?` 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 { if err != nil {
return nil, fmt.Errorf("failed to query heroic op wheels: %w", err) 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 return wheels, nil
} }
// LoadWheelsForStarter retrieves wheels for a specific starter // LoadWheelsForStarter retrieves wheels for a specific starter
func (dhom *DatabaseHeroicOPManager) LoadWheelsForStarter(ctx context.Context, starterID int32) ([]HeroicOPData, error) { 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, query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
chain_order, shift_icon, spell_id, chance, chain_order, shift_icon, spell_id, chance,
ability1, ability2, ability3, ability4, ability5, ability6, ability1, ability2, ability3, ability4, ability5, ability6,
name, description FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?` 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 { if err != nil {
return nil, fmt.Errorf("failed to query wheels for starter %d: %w", starterID, err) 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 return wheels, nil
} }
// LoadWheel retrieves a specific wheel from database // LoadWheel retrieves a specific wheel from database
func (dhom *DatabaseHeroicOPManager) LoadWheel(ctx context.Context, wheelID int32) (*HeroicOPData, error) { 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, query := `SELECT id, ho_type, 0 as starter_class, 0 as starter_icon, starter_link_id,
chain_order, shift_icon, spell_id, chance, chain_order, shift_icon, spell_id, chance,
ability1, ability2, ability3, ability4, ability5, ability6, ability1, ability2, ability3, ability4, ability5, ability6,
name, description FROM heroic_ops WHERE id = ? AND ho_type = ?` name, description FROM heroic_ops WHERE id = ? AND ho_type = ?`
var wheel HeroicOPData var wheel HeroicOPData
var name, description *string var found bool
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := dhom.db.QueryRowContext(ctx, query, wheelID, HOTypeWheel).Scan( Args: []any{wheelID, HOTypeWheel},
&wheel.ID, ResultFunc: func(stmt *sqlite.Stmt) error {
&wheel.HOType, found = true
&wheel.StarterClass, wheel.ID = int32(stmt.ColumnInt64(0))
&wheel.StarterIcon, wheel.HOType = stmt.ColumnText(1)
&wheel.StarterLinkID, wheel.StarterClass = int8(stmt.ColumnInt64(2))
&wheel.ChainOrder, wheel.StarterIcon = int16(stmt.ColumnInt64(3))
&wheel.ShiftIcon, wheel.StarterLinkID = int32(stmt.ColumnInt64(4))
&wheel.SpellID, wheel.ChainOrder = int8(stmt.ColumnInt64(5))
&wheel.Chance, wheel.ShiftIcon = int16(stmt.ColumnInt64(6))
&wheel.Ability1, wheel.SpellID = int32(stmt.ColumnInt64(7))
&wheel.Ability2, wheel.Chance = float32(stmt.ColumnFloat(8))
&wheel.Ability3, wheel.Ability1 = int16(stmt.ColumnInt64(9))
&wheel.Ability4, wheel.Ability2 = int16(stmt.ColumnInt64(10))
&wheel.Ability5, wheel.Ability3 = int16(stmt.ColumnInt64(11))
&wheel.Ability6, wheel.Ability4 = int16(stmt.ColumnInt64(12))
&name, wheel.Ability5 = int16(stmt.ColumnInt64(13))
&description, 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 { if err != nil {
return nil, fmt.Errorf("failed to load heroic op wheel %d: %w", wheelID, err) return nil, fmt.Errorf("failed to load heroic op wheel %d: %w", wheelID, err)
} }
// Handle nullable fields if !found {
if name != nil { return nil, fmt.Errorf("heroic op wheel %d not found", wheelID)
wheel.Name = *name
}
if description != nil {
wheel.Description = *description
} }
return &wheel, nil return &wheel, nil
@ -287,13 +281,20 @@ func (dhom *DatabaseHeroicOPManager) LoadWheel(ctx context.Context, wheelID int3
// SaveStarter saves a heroic op starter // SaveStarter saves a heroic op starter
func (dhom *DatabaseHeroicOPManager) SaveStarter(ctx context.Context, starter *HeroicOPStarter) error { 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 query := `INSERT OR REPLACE INTO heroic_ops
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order, (id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4, shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
ability5, ability6, name, description) ability5, ability6, name, description)
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := dhom.db.ExecContext(ctx, query, err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
starter.ID, starter.ID,
HOTypeStarter, HOTypeStarter,
starter.StartClass, starter.StartClass,
@ -306,7 +307,8 @@ func (dhom *DatabaseHeroicOPManager) SaveStarter(ctx context.Context, starter *H
starter.Abilities[5], starter.Abilities[5],
starter.Name, starter.Name,
starter.Description, starter.Description,
) },
})
if err != nil { if err != nil {
return fmt.Errorf("failed to save heroic op starter %d: %w", starter.ID, err) 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 // SaveWheel saves a heroic op wheel
func (dhom *DatabaseHeroicOPManager) SaveWheel(ctx context.Context, wheel *HeroicOPWheel) error { 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 query := `INSERT OR REPLACE INTO heroic_ops
(id, ho_type, starter_class, starter_icon, starter_link_id, chain_order, (id, ho_type, starter_class, starter_icon, starter_link_id, chain_order,
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4, shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
ability5, ability6, name, description) ability5, ability6, name, description)
VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := dhom.db.ExecContext(ctx, query, err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
wheel.ID, wheel.ID,
HOTypeWheel, HOTypeWheel,
wheel.StarterLinkID, wheel.StarterLinkID,
@ -338,7 +347,8 @@ func (dhom *DatabaseHeroicOPManager) SaveWheel(ctx context.Context, wheel *Heroi
wheel.Abilities[5], wheel.Abilities[5],
wheel.Name, wheel.Name,
wheel.Description, wheel.Description,
) },
})
if err != nil { if err != nil {
return fmt.Errorf("failed to save heroic op wheel %d: %w", wheel.ID, err) 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 // DeleteStarter removes a starter from database
func (dhom *DatabaseHeroicOPManager) DeleteStarter(ctx context.Context, starterID int32) error { 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 // 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 { if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err) return fmt.Errorf("failed to begin transaction: %w", err)
} }
defer tx.Rollback() defer sqlitex.Execute(conn, "ROLLBACK", nil)
// Delete associated wheels first // Delete associated wheels first
_, err = tx.ExecContext(ctx, "DELETE FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?", err = sqlitex.Execute(conn, "DELETE FROM heroic_ops WHERE starter_link_id = ? AND ho_type = ?", &sqlitex.ExecOptions{
starterID, HOTypeWheel) Args: []any{starterID, HOTypeWheel},
})
if err != nil { if err != nil {
return fmt.Errorf("failed to delete wheels for starter %d: %w", starterID, err) return fmt.Errorf("failed to delete wheels for starter %d: %w", starterID, err)
} }
// Delete the starter // Delete the starter
_, err = tx.ExecContext(ctx, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?", err = sqlitex.Execute(conn, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?", &sqlitex.ExecOptions{
starterID, HOTypeStarter) Args: []any{starterID, HOTypeStarter},
})
if err != nil { if err != nil {
return fmt.Errorf("failed to delete starter %d: %w", starterID, err) return fmt.Errorf("failed to delete starter %d: %w", starterID, err)
} }
if err := tx.Commit(); err != nil { return sqlitex.Execute(conn, "COMMIT", nil)
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
} }
// DeleteWheel removes a wheel from database // DeleteWheel removes a wheel from database
func (dhom *DatabaseHeroicOPManager) DeleteWheel(ctx context.Context, wheelID int32) error { func (dhom *DatabaseHeroicOPManager) DeleteWheel(ctx context.Context, wheelID int32) error {
_, err := dhom.db.ExecContext(ctx, "DELETE FROM heroic_ops WHERE id = ? AND ho_type = ?", conn, err := dhom.pool.Take(ctx)
wheelID, HOTypeWheel) 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 { if err != nil {
return fmt.Errorf("failed to delete wheel %d: %w", wheelID, err) 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 // SaveHOInstance saves a heroic opportunity instance
func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *HeroicOP) error { 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 query := `INSERT OR REPLACE INTO heroic_op_instances
(id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time, (id, encounter_id, starter_id, wheel_id, state, start_time, wheel_start_time,
time_remaining, total_time, complete, countered_1, countered_2, countered_3, 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() startTimeUnix := ho.StartTime.Unix()
wheelStartTimeUnix := ho.WheelStartTime.Unix() wheelStartTimeUnix := ho.WheelStartTime.Unix()
_, err := dhom.db.ExecContext(ctx, query, err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
ho.ID, ho.ID,
ho.EncounterID, ho.EncounterID,
ho.StarterID, ho.StarterID,
@ -421,7 +449,8 @@ func (dhom *DatabaseHeroicOPManager) SaveHOInstance(ctx context.Context, ho *Her
ho.CompletedBy, ho.CompletedBy,
ho.SpellName, ho.SpellName,
ho.SpellDescription, ho.SpellDescription,
) },
})
if err != nil { if err != nil {
return fmt.Errorf("failed to save HO instance %d: %w", ho.ID, err) 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 // LoadHOInstance retrieves a heroic opportunity instance
func (dhom *DatabaseHeroicOPManager) LoadHOInstance(ctx context.Context, instanceID int64) (*HeroicOP, error) { 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, 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, time_remaining, total_time, complete, countered_1, countered_2, countered_3,
countered_4, countered_5, countered_6, shift_used, starter_progress, 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 = ?` FROM heroic_op_instances WHERE id = ?`
var ho HeroicOP var ho HeroicOP
var startTimeUnix, wheelStartTimeUnix int64 var found bool
var spellName, spellDescription *string err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{instanceID},
err := dhom.db.QueryRowContext(ctx, query, instanceID).Scan( ResultFunc: func(stmt *sqlite.Stmt) error {
&ho.ID, found = true
&ho.EncounterID, ho.ID = stmt.ColumnInt64(0)
&ho.StarterID, ho.EncounterID = int32(stmt.ColumnInt64(1))
&ho.WheelID, ho.StarterID = int32(stmt.ColumnInt64(2))
&ho.State, ho.WheelID = int32(stmt.ColumnInt64(3))
&startTimeUnix, ho.State = int8(stmt.ColumnInt64(4))
&wheelStartTimeUnix, startTimeUnix := stmt.ColumnInt64(5)
&ho.TimeRemaining, wheelStartTimeUnix := stmt.ColumnInt64(6)
&ho.TotalTime, ho.TimeRemaining = int32(stmt.ColumnInt64(7))
&ho.Complete, ho.TotalTime = int32(stmt.ColumnInt64(8))
&ho.Countered[0], ho.Complete = int8(stmt.ColumnInt64(9))
&ho.Countered[1], ho.Countered[0] = int8(stmt.ColumnInt64(10))
&ho.Countered[2], ho.Countered[1] = int8(stmt.ColumnInt64(11))
&ho.Countered[3], ho.Countered[2] = int8(stmt.ColumnInt64(12))
&ho.Countered[4], ho.Countered[3] = int8(stmt.ColumnInt64(13))
&ho.Countered[5], ho.Countered[4] = int8(stmt.ColumnInt64(14))
&ho.ShiftUsed, ho.Countered[5] = int8(stmt.ColumnInt64(15))
&ho.StarterProgress, ho.ShiftUsed = int8(stmt.ColumnInt64(16))
&ho.CompletedBy, ho.StarterProgress = int8(stmt.ColumnInt64(17))
&spellName, ho.CompletedBy = int32(stmt.ColumnInt64(18))
&spellDescription, if stmt.ColumnType(19) != sqlite.TypeNull {
) ho.SpellName = stmt.ColumnText(19)
if err != nil { }
return nil, fmt.Errorf("failed to load HO instance %d: %w", instanceID, err) if stmt.ColumnType(20) != sqlite.TypeNull {
ho.SpellDescription = stmt.ColumnText(20)
} }
// Convert timestamps // Convert timestamps
ho.StartTime = time.Unix(startTimeUnix, 0) ho.StartTime = time.Unix(startTimeUnix, 0)
ho.WheelStartTime = time.Unix(wheelStartTimeUnix, 0) ho.WheelStartTime = time.Unix(wheelStartTimeUnix, 0)
// Handle nullable fields return nil
if spellName != nil { },
ho.SpellName = *spellName })
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 // Initialize maps
@ -489,7 +528,15 @@ func (dhom *DatabaseHeroicOPManager) LoadHOInstance(ctx context.Context, instanc
// DeleteHOInstance removes a heroic opportunity instance // DeleteHOInstance removes a heroic opportunity instance
func (dhom *DatabaseHeroicOPManager) DeleteHOInstance(ctx context.Context, instanceID int64) error { 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 { if err != nil {
return fmt.Errorf("failed to delete HO instance %d: %w", instanceID, err) 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 // SaveHOEvent saves a heroic opportunity event
func (dhom *DatabaseHeroicOPManager) SaveHOEvent(ctx context.Context, event *HeroicOPEvent) error { 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 query := `INSERT INTO heroic_op_events
(id, instance_id, event_type, character_id, ability_icon, timestamp, data) (id, instance_id, event_type, character_id, ability_icon, timestamp, data)
VALUES (?, ?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?, ?, ?)`
timestampUnix := event.Timestamp.Unix() timestampUnix := event.Timestamp.Unix()
_, err := dhom.db.ExecContext(ctx, query, err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
event.ID, event.ID,
event.InstanceID, event.InstanceID,
event.EventType, event.EventType,
@ -513,7 +567,8 @@ func (dhom *DatabaseHeroicOPManager) SaveHOEvent(ctx context.Context, event *Her
event.AbilityIcon, event.AbilityIcon,
timestampUnix, timestampUnix,
event.Data, event.Data,
) },
})
if err != nil { if err != nil {
return fmt.Errorf("failed to save HO event %d: %w", event.ID, err) 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 // LoadHOEvents retrieves events for a heroic opportunity instance
func (dhom *DatabaseHeroicOPManager) LoadHOEvents(ctx context.Context, instanceID int64) ([]HeroicOPEvent, error) { 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 query := `SELECT id, instance_id, event_type, character_id, ability_icon, timestamp, data
FROM heroic_op_events WHERE instance_id = ? ORDER BY timestamp ASC` 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 { if err != nil {
return nil, fmt.Errorf("failed to query HO events for instance %d: %w", instanceID, err) 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,
&timestampUnix,
&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 return events, nil
} }
// GetHOStatistics retrieves statistics for a character // GetHOStatistics retrieves statistics for a character
func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, characterID int32) (*HeroicOPStatistics, error) { 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 // This is a simplified implementation - in practice you'd want more complex statistics
stats := &HeroicOPStatistics{ stats := &HeroicOPStatistics{
ParticipationStats: make(map[int32]int64), ParticipationStats: make(map[int32]int64),
@ -576,7 +629,13 @@ func (dhom *DatabaseHeroicOPManager) GetHOStatistics(ctx context.Context, charac
// Count total HOs started by this character // Count total HOs started by this character
query := `SELECT COUNT(*) FROM heroic_op_events query := `SELECT COUNT(*) FROM heroic_op_events
WHERE character_id = ? AND event_type = ?` 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 { if err != nil {
return nil, fmt.Errorf("failed to get HO started count: %w", err) 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 // Count total HOs completed by this character
query = `SELECT COUNT(*) FROM heroic_op_events query = `SELECT COUNT(*) FROM heroic_op_events
WHERE character_id = ? AND event_type = ?` 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 { if err != nil {
return nil, fmt.Errorf("failed to get HO completed count: %w", err) 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 // GetNextStarterID returns the next available starter ID
func (dhom *DatabaseHeroicOPManager) GetNextStarterID(ctx context.Context) (int32, error) { 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 = ?" query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_ops WHERE ho_type = ?"
var nextID int32 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 { if err != nil {
return 0, fmt.Errorf("failed to get next starter ID: %w", err) 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 // GetNextWheelID returns the next available wheel ID
func (dhom *DatabaseHeroicOPManager) GetNextWheelID(ctx context.Context) (int32, error) { 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 = ?" query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_ops WHERE ho_type = ?"
var nextID int32 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 { if err != nil {
return 0, fmt.Errorf("failed to get next wheel ID: %w", err) 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 // GetNextInstanceID returns the next available instance ID
func (dhom *DatabaseHeroicOPManager) GetNextInstanceID(ctx context.Context) (int64, error) { 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" query := "SELECT COALESCE(MAX(id), 0) + 1 FROM heroic_op_instances"
var nextID int64 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 { if err != nil {
return 0, fmt.Errorf("failed to get next instance ID: %w", err) 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 // EnsureHOTables creates the heroic opportunity tables if they don't exist
func (dhom *DatabaseHeroicOPManager) EnsureHOTables(ctx context.Context) error { 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{ queries := []string{
`CREATE TABLE IF NOT EXISTS heroic_ops ( `CREATE TABLE IF NOT EXISTS heroic_ops (
id INTEGER NOT NULL, id INTEGER NOT NULL,
@ -700,7 +806,7 @@ func (dhom *DatabaseHeroicOPManager) EnsureHOTables(ctx context.Context) error {
} }
for i, query := range queries { for i, query := range queries {
_, err := dhom.db.ExecContext(ctx, query) err := sqlitex.Execute(conn, query, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to create HO table %d: %w", i+1, err) 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 { for i, query := range indexes {
_, err := dhom.db.ExecContext(ctx, query) err := sqlitex.Execute(conn, query, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to create HO index %d: %w", i+1, err) return fmt.Errorf("failed to create HO index %d: %w", i+1, err)
} }

View 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)
}
}
}

View File

@ -14,6 +14,9 @@ func NewHeroicOPManager(masterList *MasterHeroicOPList, database HeroicOPDatabas
encounterHOs: make(map[int32][]*HeroicOP), encounterHOs: make(map[int32][]*HeroicOP),
masterList: masterList, masterList: masterList,
database: database, database: database,
clientManager: clientManager,
encounterManager: encounterManager,
playerManager: playerManager,
nextInstanceID: 1, nextInstanceID: 1,
defaultWheelTimer: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds defaultWheelTimer: DefaultWheelTimerSeconds * 1000, // Convert to milliseconds
maxConcurrentHOs: MaxConcurrentHOs, maxConcurrentHOs: MaxConcurrentHOs,
@ -415,7 +418,7 @@ func (hom *HeroicOPManager) completeHO(ctx context.Context, ho *HeroicOP, wheel
// Cast completion spell // Cast completion spell
if wheel.SpellID > 0 { 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 // TODO: Cast spell on participants through spell manager
// hom.spellManager.CastSpell(completedBy, wheel.SpellID, participants) // hom.spellManager.CastSpell(completedBy, wheel.SpellID, participants)
} }
@ -547,9 +550,9 @@ func (hom *HeroicOPManager) sendShiftUpdate(ho *HeroicOP, oldWheelID, newWheelID
participants := ho.GetParticipants() participants := ho.GetParticipants()
packetBuilder := NewHeroicOPPacketBuilder(0) packetBuilder := NewHeroicOPPacketBuilder(0)
for _, characterID := range participants { for range participants {
if packet, err := packetBuilder.BuildHOShiftPacket(ho, oldWheelID, newWheelID); err == nil { 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 _ = packet // Placeholder
} }
} }

View File

@ -104,6 +104,9 @@ type HeroicOPManager struct {
database HeroicOPDatabase database HeroicOPDatabase
eventHandler HeroicOPEventHandler eventHandler HeroicOPEventHandler
logger LogHandler logger LogHandler
clientManager ClientManager
encounterManager EncounterManager
playerManager PlayerManager
nextInstanceID int64 nextInstanceID int64
// Configuration // Configuration
defaultWheelTimer int32 // milliseconds defaultWheelTimer int32 // milliseconds