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 package ground_spawn
import ( import (
"context"
"fmt" "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 { type DatabaseAdapter struct {
db *database.DB pool *sqlitex.Pool
} }
// NewDatabaseAdapter creates a new database adapter using the database wrapper // NewDatabaseAdapter creates a new database adapter using the sqlite pool
func NewDatabaseAdapter(db *database.DB) *DatabaseAdapter { func NewDatabaseAdapter(pool *sqlitex.Pool) *DatabaseAdapter {
return &DatabaseAdapter{ return &DatabaseAdapter{
db: db, pool: pool,
} }
} }
// LoadGroundSpawnEntries loads harvest entries for a ground spawn // LoadGroundSpawnEntries loads harvest entries for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*GroundSpawnEntry, error) { 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 := ` query := `
SELECT min_skill_level, min_adventure_level, bonus_table, harvest_1, harvest_3, SELECT min_skill_level, min_adventure_level, bonus_table, harvest_1, harvest_3,
harvest_5, harvest_imbue, harvest_rare, harvest_10, harvest_coin harvest_5, harvest_imbue, harvest_rare, harvest_10, harvest_coin
@ -29,27 +38,25 @@ func (da *DatabaseAdapter) LoadGroundSpawnEntries(groundspawnID int32) ([]*Groun
` `
var entries []*GroundSpawnEntry var entries []*GroundSpawnEntry
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := da.db.Query(query, func(row *database.Row) error { Args: []any{groundspawnID},
entry := &GroundSpawnEntry{} ResultFunc: func(stmt *sqlite.Stmt) error {
var bonusTable int32 entry := &GroundSpawnEntry{
MinSkillLevel: int16(stmt.ColumnInt64(0)),
entry.MinSkillLevel = int16(row.Int(0)) MinAdventureLevel: int16(stmt.ColumnInt64(1)),
entry.MinAdventureLevel = int16(row.Int(1)) BonusTable: stmt.ColumnInt64(2) == 1,
bonusTable = int32(row.Int(2)) Harvest1: float32(stmt.ColumnFloat(3)),
entry.Harvest1 = float32(row.Float(3)) Harvest3: float32(stmt.ColumnFloat(4)),
entry.Harvest3 = float32(row.Float(4)) Harvest5: float32(stmt.ColumnFloat(5)),
entry.Harvest5 = float32(row.Float(5)) HarvestImbue: float32(stmt.ColumnFloat(6)),
entry.HarvestImbue = float32(row.Float(6)) HarvestRare: float32(stmt.ColumnFloat(7)),
entry.HarvestRare = float32(row.Float(7)) Harvest10: float32(stmt.ColumnFloat(8)),
entry.Harvest10 = float32(row.Float(8)) HarvestCoin: float32(stmt.ColumnFloat(9)),
entry.HarvestCoin = float32(row.Float(9)) }
entry.BonusTable = bonusTable == 1
entries = append(entries, entry) entries = append(entries, entry)
return nil return nil
}, groundspawnID) },
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query groundspawn entries: %w", err) 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 // LoadGroundSpawnItems loads harvest items for a ground spawn
func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundSpawnEntryItem, error) { 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 := ` query := `
SELECT item_id, is_rare, grid_id, quantity SELECT item_id, is_rare, grid_id, quantity
FROM groundspawn_items FROM groundspawn_items
@ -68,19 +82,19 @@ func (da *DatabaseAdapter) LoadGroundSpawnItems(groundspawnID int32) ([]*GroundS
` `
var items []*GroundSpawnEntryItem var items []*GroundSpawnEntryItem
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := da.db.Query(query, func(row *database.Row) error { Args: []any{groundspawnID},
item := &GroundSpawnEntryItem{} ResultFunc: func(stmt *sqlite.Stmt) error {
item := &GroundSpawnEntryItem{
item.ItemID = int32(row.Int(0)) ItemID: int32(stmt.ColumnInt64(0)),
item.IsRare = int8(row.Int(1)) IsRare: int8(stmt.ColumnInt64(1)),
item.GridID = int32(row.Int(2)) GridID: int32(stmt.ColumnInt64(2)),
item.Quantity = int16(row.Int(3)) Quantity: int16(stmt.ColumnInt64(3)),
}
items = append(items, item) items = append(items, item)
return nil return nil
}, groundspawnID) },
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query groundspawn items: %w", err) 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") 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 := ` query := `
INSERT OR REPLACE INTO groundspawns ( INSERT OR REPLACE INTO groundspawns (
id, name, x, y, z, heading, respawn_timer, collection_skill, 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 // TODO: Get actual zone ID from spawn
zoneID := int32(1) zoneID := int32(1)
err := da.db.Exec(query, err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
Args: []any{
gs.GetID(), gs.GetID(),
gs.GetName(), gs.GetName(),
gs.GetX(), gs.GetX(),
@ -126,7 +148,8 @@ func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
gs.GetGroundSpawnEntryID(), gs.GetGroundSpawnEntryID(),
randomizeHeading, randomizeHeading,
zoneID, zoneID,
) },
})
if err != nil { if err != nil {
return fmt.Errorf("failed to save ground spawn %d: %w", gs.GetID(), err) 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 // LoadAllGroundSpawns loads all ground spawns from the database
func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) { 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 := ` query := `
SELECT id, name, x, y, z, heading, collection_skill, number_harvests, SELECT id, name, x, y, z, heading, collection_skill, number_harvests,
attempts_per_harvest, groundspawn_entry_id, randomize_heading attempts_per_harvest, groundspawn_entry_id, randomize_heading
@ -149,26 +179,27 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
zoneID := int32(1) zoneID := int32(1)
var groundSpawns []*GroundSpawn var groundSpawns []*GroundSpawn
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := da.db.Query(query, func(row *database.Row) error { Args: []any{zoneID},
id := int32(row.Int(0)) ResultFunc: func(stmt *sqlite.Stmt) error {
name := row.Text(1) id := int32(stmt.ColumnInt64(0))
x := float32(row.Float(2)) name := stmt.ColumnText(1)
y := float32(row.Float(3)) x := float32(stmt.ColumnFloat(2))
z := float32(row.Float(4)) y := float32(stmt.ColumnFloat(3))
heading := float32(row.Float(5)) z := float32(stmt.ColumnFloat(4))
collectionSkill := row.Text(6) heading := float32(stmt.ColumnFloat(5))
numberHarvests := int8(row.Int(7)) collectionSkill := stmt.ColumnText(6)
attemptsPerHarvest := int8(row.Int(8)) numberHarvests := int8(stmt.ColumnInt64(7))
groundspawnEntryID := int32(row.Int(9)) attemptsPerHarvest := int8(stmt.ColumnInt64(8))
randomizeHeading := int32(row.Int(10)) groundspawnEntryID := int32(stmt.ColumnInt64(9))
randomizeHeading := stmt.ColumnInt64(10) == 1
config := GroundSpawnConfig{ config := GroundSpawnConfig{
GroundSpawnID: groundspawnEntryID, GroundSpawnID: groundspawnEntryID,
CollectionSkill: collectionSkill, CollectionSkill: collectionSkill,
NumberHarvests: numberHarvests, NumberHarvests: numberHarvests,
AttemptsPerHarvest: attemptsPerHarvest, AttemptsPerHarvest: attemptsPerHarvest,
RandomizeHeading: randomizeHeading == 1, RandomizeHeading: randomizeHeading,
Location: SpawnLocation{ Location: SpawnLocation{
X: x, X: x,
Y: y, Y: y,
@ -184,9 +215,9 @@ func (da *DatabaseAdapter) LoadAllGroundSpawns() ([]*GroundSpawn, error) {
gs.SetID(id) gs.SetID(id)
groundSpawns = append(groundSpawns, gs) groundSpawns = append(groundSpawns, gs)
return nil return nil
}, zoneID) },
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query groundspawns: %w", err) 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 // DeleteGroundSpawn deletes a ground spawn from the database
func (da *DatabaseAdapter) DeleteGroundSpawn(id int32) error { 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 = ?` 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 { if err != nil {
return fmt.Errorf("failed to delete ground spawn %d: %w", id, err) 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 // LoadPlayerHarvestStatistics loads harvest statistics for a player
func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[string]int64, error) { 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 := ` query := `
SELECT skill_name, harvest_count SELECT skill_name, harvest_count
FROM player_harvest_stats FROM player_harvest_stats
@ -216,15 +263,15 @@ func (da *DatabaseAdapter) LoadPlayerHarvestStatistics(playerID int32) (map[stri
` `
stats := make(map[string]int64) stats := make(map[string]int64)
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
err := da.db.Query(query, func(row *database.Row) error { Args: []any{playerID},
skillName := row.Text(0) ResultFunc: func(stmt *sqlite.Stmt) error {
harvestCount := row.Int64(1) skillName := stmt.ColumnText(0)
harvestCount := stmt.ColumnInt64(1)
stats[skillName] = harvestCount stats[skillName] = harvestCount
return nil return nil
}, playerID) },
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query player harvest stats: %w", err) 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 // SavePlayerHarvestStatistic saves a player's harvest statistic
func (da *DatabaseAdapter) SavePlayerHarvestStatistic(playerID int32, skillName string, count int64) error { 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 := ` query := `
INSERT OR REPLACE INTO player_harvest_stats (player_id, skill_name, harvest_count, updated_at) INSERT OR REPLACE INTO player_harvest_stats (player_id, skill_name, harvest_count, updated_at)
VALUES (?, ?, ?, datetime('now')) 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 { if err != nil {
return fmt.Errorf("failed to save player harvest stat: %w", err) return fmt.Errorf("failed to save player harvest stat: %w", err)
} }
return nil 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
}