fix ground_spawn database file

This commit is contained in:
Sky Johnson 2025-08-03 20:45:54 -05:00
parent 1c26bb600d
commit aa362bde07

View File

@ -1,25 +1,34 @@
package ground_spawn
import (
"context"
"fmt"
"eq2emu/internal/database"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
)
// DatabaseAdapter implements the Database interface using the internal database wrapper
// DatabaseAdapter implements the Database interface using zombiezen.com/go/sqlite
type DatabaseAdapter struct {
db *database.DB
pool *sqlitex.Pool
}
// NewDatabaseAdapter creates a new database adapter using the database wrapper
func NewDatabaseAdapter(db *database.DB) *DatabaseAdapter {
// NewDatabaseAdapter creates a new database adapter using the sqlite pool
func NewDatabaseAdapter(pool *sqlitex.Pool) *DatabaseAdapter {
return &DatabaseAdapter{
db: db,
pool: pool,
}
}
// LoadGroundSpawnEntries loads harvest entries for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*GroundSpawnEntry, error) {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
SELECT min_skill_level, min_adventure_level, bonus_table, harvest_1, harvest_3,
harvest_5, harvest_imbue, harvest_rare, harvest_10, harvest_coin
@ -29,27 +38,25 @@ func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*Groun
`
var entries []*GroundSpawnEntry
err := da.db.Query(query, func(row *database.Row) error {
entry := &GroundSpawnEntry{}
var bonusTable int32
entry.MinSkillLevel = int16(row.Int(0))
entry.MinAdventureLevel = int16(row.Int(1))
bonusTable = int32(row.Int(2))
entry.Harvest1 = float32(row.Float(3))
entry.Harvest3 = float32(row.Float(4))
entry.Harvest5 = float32(row.Float(5))
entry.HarvestImbue = float32(row.Float(6))
entry.HarvestRare = float32(row.Float(7))
entry.Harvest10 = float32(row.Float(8))
entry.HarvestCoin = float32(row.Float(9))
entry.BonusTable = bonusTable == 1
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{groundspawnID},
ResultFunc: func(stmt *sqlite.Stmt) error {
entry := &GroundSpawnEntry{
MinSkillLevel: int16(stmt.ColumnInt64(0)),
MinAdventureLevel: int16(stmt.ColumnInt64(1)),
BonusTable: stmt.ColumnInt64(2) == 1,
Harvest1: float32(stmt.ColumnFloat(3)),
Harvest3: float32(stmt.ColumnFloat(4)),
Harvest5: float32(stmt.ColumnFloat(5)),
HarvestImbue: float32(stmt.ColumnFloat(6)),
HarvestRare: float32(stmt.ColumnFloat(7)),
Harvest10: float32(stmt.ColumnFloat(8)),
HarvestCoin: float32(stmt.ColumnFloat(9)),
}
entries = append(entries, entry)
return nil
}, groundspawnID)
},
})
if err != nil {
return nil, fmt.Errorf("failed to query groundspawn entries: %w", err)
@ -60,6 +67,13 @@ func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*Groun
// LoadGroundSpawnItems loads harvest items for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundSpawnEntryItem, error) {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
SELECT item_id, is_rare, grid_id, quantity
FROM groundspawn_items
@ -68,19 +82,19 @@ func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundS
`
var items []*GroundSpawnEntryItem
err := da.db.Query(query, func(row *database.Row) error {
item := &GroundSpawnEntryItem{}
item.ItemID = int32(row.Int(0))
item.IsRare = int8(row.Int(1))
item.GridID = int32(row.Int(2))
item.Quantity = int16(row.Int(3))
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{groundspawnID},
ResultFunc: func(stmt *sqlite.Stmt) error {
item := &GroundSpawnEntryItem{
ItemID: int32(stmt.ColumnInt64(0)),
IsRare: int8(stmt.ColumnInt64(1)),
GridID: int32(stmt.ColumnInt64(2)),
Quantity: int16(stmt.ColumnInt64(3)),
}
items = append(items, item)
return nil
}, groundspawnID)
},
})
if err != nil {
return nil, fmt.Errorf("failed to query groundspawn items: %w", err)
@ -95,6 +109,13 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
return fmt.Errorf("ground spawn cannot be nil")
}
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
INSERT OR REPLACE INTO groundspawns (
id, name, x, y, z, heading, respawn_timer, collection_skill,
@ -112,7 +133,8 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
// TODO: Get actual zone ID from spawn
zoneID := int32(1)
err := da.db.Exec(query,
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
gs.GetID(),
gs.GetName(),
gs.GetX(),
@ -126,7 +148,8 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
gs.GetGroundSpawnEntryID(),
randomizeHeading,
zoneID,
)
},
})
if err != nil {
return fmt.Errorf("failed to save ground spawn %d: %w", gs.GetID(), err)
@ -137,6 +160,13 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
// LoadAllGroundSpawns loads all ground spawns from the database
func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
SELECT id, name, x, y, z, heading, collection_skill, number_harvests,
attempts_per_harvest, groundspawn_entry_id, randomize_heading
@ -149,26 +179,27 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
zoneID := int32(1)
var groundSpawns []*GroundSpawn
err := da.db.Query(query, func(row *database.Row) error {
id := int32(row.Int(0))
name := row.Text(1)
x := float32(row.Float(2))
y := float32(row.Float(3))
z := float32(row.Float(4))
heading := float32(row.Float(5))
collectionSkill := row.Text(6)
numberHarvests := int8(row.Int(7))
attemptsPerHarvest := int8(row.Int(8))
groundspawnEntryID := int32(row.Int(9))
randomizeHeading := int32(row.Int(10))
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{zoneID},
ResultFunc: func(stmt *sqlite.Stmt) error {
id := int32(stmt.ColumnInt64(0))
name := stmt.ColumnText(1)
x := float32(stmt.ColumnFloat(2))
y := float32(stmt.ColumnFloat(3))
z := float32(stmt.ColumnFloat(4))
heading := float32(stmt.ColumnFloat(5))
collectionSkill := stmt.ColumnText(6)
numberHarvests := int8(stmt.ColumnInt64(7))
attemptsPerHarvest := int8(stmt.ColumnInt64(8))
groundspawnEntryID := int32(stmt.ColumnInt64(9))
randomizeHeading := stmt.ColumnInt64(10) == 1
config := GroundSpawnConfig{
GroundSpawnID: groundspawnEntryID,
CollectionSkill: collectionSkill,
NumberHarvests: numberHarvests,
AttemptsPerHarvest: attemptsPerHarvest,
RandomizeHeading: randomizeHeading == 1,
RandomizeHeading: randomizeHeading,
Location: SpawnLocation{
X: x,
Y: y,
@ -184,9 +215,9 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
gs.SetID(id)
groundSpawns = append(groundSpawns, gs)
return nil
}, zoneID)
},
})
if err != nil {
return nil, fmt.Errorf("failed to query groundspawns: %w", err)
@ -197,9 +228,18 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
// DeleteGroundSpawn deletes a ground spawn from the database
func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `DELETE FROM groundspawns WHERE id = ?`
err := da.db.Exec(query, id)
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{id},
})
if err != nil {
return fmt.Errorf("failed to delete ground spawn %d: %w", id, err)
}
@ -209,6 +249,13 @@ func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error {
// LoadPlayerHarvestStatistics loads harvest statistics for a player
func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[string]int64, error) {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
SELECT skill_name, harvest_count
FROM player_harvest_stats
@ -216,15 +263,15 @@ func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[stri
`
stats := make(map[string]int64)
err := da.db.Query(query, func(row *database.Row) error {
skillName := row.Text(0)
harvestCount := row.Int64(1)
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{playerID},
ResultFunc: func(stmt *sqlite.Stmt) error {
skillName := stmt.ColumnText(0)
harvestCount := stmt.ColumnInt64(1)
stats[skillName] = harvestCount
return nil
}, playerID)
},
})
if err != nil {
return nil, fmt.Errorf("failed to query player harvest stats: %w", err)
@ -235,15 +282,125 @@ func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[stri
// SavePlayerHarvestStatistic saves a player's harvest statistic
func (da *DatabaseAdapter) SavePlayerHarvestStatistic(playerID int32, skillName string, count int64) error {
ctx := context.Background()
conn, err := da.pool.Take(ctx)
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
query := `
INSERT OR REPLACE INTO player_harvest_stats (player_id, skill_name, harvest_count, updated_at)
VALUES (?, ?, ?, datetime('now'))
`
err := da.db.Exec(query, playerID, skillName, count)
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{playerID, skillName, count},
})
if err != nil {
return fmt.Errorf("failed to save player harvest stat: %w", err)
}
return nil
}
// EnsureGroundSpawnTables creates the necessary database tables if they don't exist
func (da *DatabaseAdapter) EnsureGroundSpawnTables(ctx context.Context) error {
conn, err := da.pool.Take(ctx)
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
defer da.pool.Put(conn)
// Create groundspawns table
createGroundSpawnsTable := `
CREATE TABLE IF NOT EXISTS groundspawns (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
x REAL NOT NULL,
y REAL NOT NULL,
z REAL NOT NULL,
heading REAL NOT NULL,
respawn_timer INTEGER NOT NULL DEFAULT 300,
collection_skill TEXT NOT NULL,
number_harvests INTEGER NOT NULL DEFAULT 1,
attempts_per_harvest INTEGER NOT NULL DEFAULT 1,
groundspawn_entry_id INTEGER NOT NULL,
randomize_heading INTEGER NOT NULL DEFAULT 0,
zone_id INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`
// Create groundspawn_entries table
createGroundSpawnEntriesTable := `
CREATE TABLE IF NOT EXISTS groundspawn_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
groundspawn_id INTEGER NOT NULL,
min_skill_level INTEGER NOT NULL DEFAULT 0,
min_adventure_level INTEGER NOT NULL DEFAULT 0,
bonus_table INTEGER NOT NULL DEFAULT 0,
harvest_1 REAL NOT NULL DEFAULT 0.0,
harvest_3 REAL NOT NULL DEFAULT 0.0,
harvest_5 REAL NOT NULL DEFAULT 0.0,
harvest_imbue REAL NOT NULL DEFAULT 0.0,
harvest_rare REAL NOT NULL DEFAULT 0.0,
harvest_10 REAL NOT NULL DEFAULT 0.0,
harvest_coin REAL NOT NULL DEFAULT 0.0
)
`
// Create groundspawn_items table
createGroundSpawnItemsTable := `
CREATE TABLE IF NOT EXISTS groundspawn_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
groundspawn_id INTEGER NOT NULL,
item_id INTEGER NOT NULL,
is_rare INTEGER NOT NULL DEFAULT 0,
grid_id INTEGER NOT NULL DEFAULT 0,
quantity INTEGER NOT NULL DEFAULT 1
)
`
// Create player_harvest_stats table
createPlayerHarvestStatsTable := `
CREATE TABLE IF NOT EXISTS player_harvest_stats (
player_id INTEGER NOT NULL,
skill_name TEXT NOT NULL,
harvest_count INTEGER NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (player_id, skill_name)
)
`
// Execute table creation statements
tables := []string{
createGroundSpawnsTable,
createGroundSpawnEntriesTable,
createGroundSpawnItemsTable,
createPlayerHarvestStatsTable,
}
for _, tableSQL := range tables {
if err := sqlitex.Execute(conn, tableSQL, nil); err != nil {
return fmt.Errorf("failed to create table: %w", err)
}
}
// Create indexes
indexes := []string{
`CREATE INDEX IF NOT EXISTS idx_groundspawn_entries_groundspawn_id ON groundspawn_entries(groundspawn_id)`,
`CREATE INDEX IF NOT EXISTS idx_groundspawn_items_groundspawn_id ON groundspawn_items(groundspawn_id)`,
`CREATE INDEX IF NOT EXISTS idx_groundspawns_zone_id ON groundspawns(zone_id)`,
`CREATE INDEX IF NOT EXISTS idx_player_harvest_stats_player_id ON player_harvest_stats(player_id)`,
}
for _, indexSQL := range indexes {
if err := sqlitex.Execute(conn, indexSQL, nil); err != nil {
return fmt.Errorf("failed to create index: %w", err)
}
}
return nil
}