eq2go/internal/tradeskills/database.go

430 lines
11 KiB
Go

package tradeskills
import (
"database/sql"
"fmt"
"log"
)
// DatabaseService handles database operations for the tradeskills system.
type DatabaseService interface {
// LoadTradeskillEvents loads all tradeskill events from the database
LoadTradeskillEvents(masterList *MasterTradeskillEventsList) error
// CreateTradeskillTables creates the required database tables
CreateTradeskillTables() error
// SaveTradeskillEvent saves a tradeskill event to the database
SaveTradeskillEvent(event *TradeskillEvent) error
// DeleteTradeskillEvent removes a tradeskill event from the database
DeleteTradeskillEvent(name string, technique uint32) error
}
// SQLiteTradeskillDatabase implements DatabaseService for SQLite.
type SQLiteTradeskillDatabase struct {
db *sql.DB
}
// NewSQLiteTradeskillDatabase creates a new SQLite database service.
func NewSQLiteTradeskillDatabase(db *sql.DB) *SQLiteTradeskillDatabase {
return &SQLiteTradeskillDatabase{
db: db,
}
}
// CreateTradeskillTables creates the required database tables for tradeskills.
func (db *SQLiteTradeskillDatabase) CreateTradeskillTables() error {
createTableSQL := `
CREATE TABLE IF NOT EXISTS tradeskillevents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
icon INTEGER NOT NULL,
technique INTEGER NOT NULL,
success_progress INTEGER NOT NULL DEFAULT 0,
success_durability INTEGER NOT NULL DEFAULT 0,
success_hp INTEGER NOT NULL DEFAULT 0,
success_power INTEGER NOT NULL DEFAULT 0,
success_spell_id INTEGER NOT NULL DEFAULT 0,
success_item_id INTEGER NOT NULL DEFAULT 0,
fail_progress INTEGER NOT NULL DEFAULT 0,
fail_durability INTEGER NOT NULL DEFAULT 0,
fail_hp INTEGER NOT NULL DEFAULT 0,
fail_power INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(name, technique)
);
CREATE INDEX IF NOT EXISTS idx_tradeskillevents_technique ON tradeskillevents(technique);
CREATE INDEX IF NOT EXISTS idx_tradeskillevents_name ON tradeskillevents(name);
`
_, err := db.db.Exec(createTableSQL)
if err != nil {
return fmt.Errorf("failed to create tradeskillevents table: %w", err)
}
log.Printf("Created tradeskillevents table")
return nil
}
// LoadTradeskillEvents loads all tradeskill events from the database into the master list.
func (db *SQLiteTradeskillDatabase) LoadTradeskillEvents(masterList *MasterTradeskillEventsList) error {
if masterList == nil {
return fmt.Errorf("masterList cannot be nil")
}
query := `
SELECT name, icon, technique, success_progress, success_durability,
success_hp, success_power, success_spell_id, success_item_id,
fail_progress, fail_durability, fail_hp, fail_power
FROM tradeskillevents
ORDER BY technique, name
`
rows, err := db.db.Query(query)
if err != nil {
return fmt.Errorf("failed to query tradeskillevents: %w", err)
}
defer rows.Close()
eventsLoaded := 0
for rows.Next() {
event := &TradeskillEvent{}
err := rows.Scan(
&event.Name,
&event.Icon,
&event.Technique,
&event.SuccessProgress,
&event.SuccessDurability,
&event.SuccessHP,
&event.SuccessPower,
&event.SuccessSpellID,
&event.SuccessItemID,
&event.FailProgress,
&event.FailDurability,
&event.FailHP,
&event.FailPower,
)
if err != nil {
log.Printf("Warning: Failed to scan tradeskill event row: %v", err)
continue
}
// Validate the event
if event.Name == "" {
log.Printf("Warning: Skipping tradeskill event with empty name")
continue
}
if !IsValidTechnique(event.Technique) {
log.Printf("Warning: Skipping tradeskill event '%s' with invalid technique %d",
event.Name, event.Technique)
continue
}
masterList.AddEvent(event)
eventsLoaded++
log.Printf("Loaded tradeskill event: %s (technique: %d)", event.Name, event.Technique)
}
if err = rows.Err(); err != nil {
return fmt.Errorf("error iterating tradeskill events: %w", err)
}
log.Printf("Loaded %d tradeskill events", eventsLoaded)
return nil
}
// SaveTradeskillEvent saves a tradeskill event to the database.
func (db *SQLiteTradeskillDatabase) SaveTradeskillEvent(event *TradeskillEvent) error {
if event == nil {
return fmt.Errorf("event cannot be nil")
}
if event.Name == "" {
return fmt.Errorf("event name cannot be empty")
}
if !IsValidTechnique(event.Technique) {
return fmt.Errorf("invalid technique: %d", event.Technique)
}
insertSQL := `
INSERT OR REPLACE INTO tradeskillevents (
name, icon, technique, success_progress, success_durability,
success_hp, success_power, success_spell_id, success_item_id,
fail_progress, fail_durability, fail_hp, fail_power, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`
_, err := db.db.Exec(insertSQL,
event.Name,
event.Icon,
event.Technique,
event.SuccessProgress,
event.SuccessDurability,
event.SuccessHP,
event.SuccessPower,
event.SuccessSpellID,
event.SuccessItemID,
event.FailProgress,
event.FailDurability,
event.FailHP,
event.FailPower,
)
if err != nil {
return fmt.Errorf("failed to save tradeskill event '%s': %w", event.Name, err)
}
log.Printf("Saved tradeskill event: %s", event.Name)
return nil
}
// DeleteTradeskillEvent removes a tradeskill event from the database.
func (db *SQLiteTradeskillDatabase) DeleteTradeskillEvent(name string, technique uint32) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
deleteSQL := `DELETE FROM tradeskillevents WHERE name = ? AND technique = ?`
result, err := db.db.Exec(deleteSQL, name, technique)
if err != nil {
return fmt.Errorf("failed to delete tradeskill event '%s': %w", name, err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected for event '%s': %w", name, err)
}
if rowsAffected == 0 {
return fmt.Errorf("tradeskill event '%s' with technique %d not found", name, technique)
}
log.Printf("Deleted tradeskill event: %s (technique: %d)", name, technique)
return nil
}
// GetTradeskillEventsByTechnique retrieves all events for a specific technique from the database.
func (db *SQLiteTradeskillDatabase) GetTradeskillEventsByTechnique(technique uint32) ([]*TradeskillEvent, error) {
if !IsValidTechnique(technique) {
return nil, fmt.Errorf("invalid technique: %d", technique)
}
query := `
SELECT name, icon, technique, success_progress, success_durability,
success_hp, success_power, success_spell_id, success_item_id,
fail_progress, fail_durability, fail_hp, fail_power
FROM tradeskillevents
WHERE technique = ?
ORDER BY name
`
rows, err := db.db.Query(query, technique)
if err != nil {
return nil, fmt.Errorf("failed to query events for technique %d: %w", technique, err)
}
defer rows.Close()
var events []*TradeskillEvent
for rows.Next() {
event := &TradeskillEvent{}
err := rows.Scan(
&event.Name,
&event.Icon,
&event.Technique,
&event.SuccessProgress,
&event.SuccessDurability,
&event.SuccessHP,
&event.SuccessPower,
&event.SuccessSpellID,
&event.SuccessItemID,
&event.FailProgress,
&event.FailDurability,
&event.FailHP,
&event.FailPower,
)
if err != nil {
log.Printf("Warning: Failed to scan event row: %v", err)
continue
}
events = append(events, event)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating events for technique %d: %w", technique, err)
}
return events, nil
}
// GetTradeskillEventByName retrieves a specific event by name and technique.
func (db *SQLiteTradeskillDatabase) GetTradeskillEventByName(name string, technique uint32) (*TradeskillEvent, error) {
if name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
if !IsValidTechnique(technique) {
return nil, fmt.Errorf("invalid technique: %d", technique)
}
query := `
SELECT name, icon, technique, success_progress, success_durability,
success_hp, success_power, success_spell_id, success_item_id,
fail_progress, fail_durability, fail_hp, fail_power
FROM tradeskillevents
WHERE name = ? AND technique = ?
`
row := db.db.QueryRow(query, name, technique)
event := &TradeskillEvent{}
err := row.Scan(
&event.Name,
&event.Icon,
&event.Technique,
&event.SuccessProgress,
&event.SuccessDurability,
&event.SuccessHP,
&event.SuccessPower,
&event.SuccessSpellID,
&event.SuccessItemID,
&event.FailProgress,
&event.FailDurability,
&event.FailHP,
&event.FailPower,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("tradeskill event '%s' with technique %d not found", name, technique)
}
return nil, fmt.Errorf("failed to get tradeskill event '%s': %w", name, err)
}
return event, nil
}
// CountTradeskillEvents returns the total number of events in the database.
func (db *SQLiteTradeskillDatabase) CountTradeskillEvents() (int32, error) {
query := `SELECT COUNT(*) FROM tradeskillevents`
var count int32
err := db.db.QueryRow(query).Scan(&count)
if err != nil {
return 0, fmt.Errorf("failed to count tradeskill events: %w", err)
}
return count, nil
}
// GetTechniqueCounts returns the number of events per technique.
func (db *SQLiteTradeskillDatabase) GetTechniqueCounts() (map[uint32]int32, error) {
query := `
SELECT technique, COUNT(*) as count
FROM tradeskillevents
GROUP BY technique
ORDER BY technique
`
rows, err := db.db.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to query technique counts: %w", err)
}
defer rows.Close()
counts := make(map[uint32]int32)
for rows.Next() {
var technique uint32
var count int32
err := rows.Scan(&technique, &count)
if err != nil {
log.Printf("Warning: Failed to scan technique count row: %v", err)
continue
}
counts[technique] = count
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating technique counts: %w", err)
}
return counts, nil
}
// InsertDefaultTradeskillEvents adds some default tradeskill events to the database.
func (db *SQLiteTradeskillDatabase) InsertDefaultTradeskillEvents() error {
defaultEvents := []*TradeskillEvent{
{
Name: "Blazing Heat",
Icon: 1234,
Technique: TechniqueSkillAlchemy,
SuccessProgress: 25,
SuccessDurability: 0,
SuccessHP: 0,
SuccessPower: -10,
SuccessSpellID: 0,
SuccessItemID: 0,
FailProgress: -10,
FailDurability: -25,
FailHP: 0,
FailPower: 0,
},
{
Name: "Precise Cut",
Icon: 5678,
Technique: TechniqueSkillFletching,
SuccessProgress: 30,
SuccessDurability: 5,
SuccessHP: 0,
SuccessPower: -5,
SuccessSpellID: 0,
SuccessItemID: 0,
FailProgress: 0,
FailDurability: -30,
FailHP: 0,
FailPower: 0,
},
{
Name: "Perfect Stitch",
Icon: 9012,
Technique: TechniqueSkillTailoring,
SuccessProgress: 20,
SuccessDurability: 10,
SuccessHP: 0,
SuccessPower: -8,
SuccessSpellID: 0,
SuccessItemID: 0,
FailProgress: -5,
FailDurability: -15,
FailHP: 0,
FailPower: 0,
},
}
for _, event := range defaultEvents {
err := db.SaveTradeskillEvent(event)
if err != nil {
log.Printf("Warning: Failed to insert default event '%s': %v", event.Name, err)
}
}
log.Printf("Inserted %d default tradeskill events", len(defaultEvents))
return nil
}