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)) }
entries = append(entries, entry)
entry.BonusTable = bonusTable == 1 return nil
entries = append(entries, entry) },
})
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,21 +133,23 @@ 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{
gs.GetID(), Args: []any{
gs.GetName(), gs.GetID(),
gs.GetX(), gs.GetName(),
gs.GetY(), gs.GetX(),
gs.GetZ(), gs.GetY(),
int16(gs.GetHeading()), gs.GetZ(),
300, // Default 5 minutes respawn timer int16(gs.GetHeading()),
gs.GetCollectionSkill(), 300, // Default 5 minutes respawn timer
gs.GetNumberHarvests(), gs.GetCollectionSkill(),
gs.GetAttemptsPerHarvest(), gs.GetNumberHarvests(),
gs.GetGroundSpawnEntryID(), gs.GetAttemptsPerHarvest(),
randomizeHeading, gs.GetGroundSpawnEntryID(),
zoneID, randomizeHeading,
) 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,44 +179,45 @@ 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{
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
err := da.db.Query(query, func(row *database.Row) error { config := GroundSpawnConfig{
id := int32(row.Int(0)) GroundSpawnID: groundspawnEntryID,
name := row.Text(1) CollectionSkill: collectionSkill,
x := float32(row.Float(2)) NumberHarvests: numberHarvests,
y := float32(row.Float(3)) AttemptsPerHarvest: attemptsPerHarvest,
z := float32(row.Float(4)) RandomizeHeading: randomizeHeading,
heading := float32(row.Float(5)) Location: SpawnLocation{
collectionSkill := row.Text(6) X: x,
numberHarvests := int8(row.Int(7)) Y: y,
attemptsPerHarvest := int8(row.Int(8)) Z: z,
groundspawnEntryID := int32(row.Int(9)) Heading: heading,
randomizeHeading := int32(row.Int(10)) GridID: 1, // TODO: Load from database
},
Name: name,
Description: fmt.Sprintf("A %s node", collectionSkill),
}
config := GroundSpawnConfig{ gs := NewGroundSpawn(config)
GroundSpawnID: groundspawnEntryID, gs.SetID(id)
CollectionSkill: collectionSkill,
NumberHarvests: numberHarvests,
AttemptsPerHarvest: attemptsPerHarvest,
RandomizeHeading: randomizeHeading == 1,
Location: SpawnLocation{
X: x,
Y: y,
Z: z,
Heading: heading,
GridID: 1, // TODO: Load from database
},
Name: name,
Description: fmt.Sprintf("A %s node", collectionSkill),
}
gs := NewGroundSpawn(config) groundSpawns = append(groundSpawns, gs)
gs.SetID(id) return nil
},
groundSpawns = append(groundSpawns, gs) })
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
}