406 lines
11 KiB
Go
406 lines
11 KiB
Go
package ground_spawn
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"zombiezen.com/go/sqlite"
|
|
"zombiezen.com/go/sqlite/sqlitex"
|
|
)
|
|
|
|
// DatabaseAdapter implements the Database interface using zombiezen.com/go/sqlite
|
|
type DatabaseAdapter struct {
|
|
pool *sqlitex.Pool
|
|
}
|
|
|
|
// NewDatabaseAdapter creates a new database adapter using the sqlite pool
|
|
func NewDatabaseAdapter(pool *sqlitex.Pool) *DatabaseAdapter {
|
|
return &DatabaseAdapter{
|
|
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
|
|
FROM groundspawn_entries
|
|
WHERE groundspawn_id = ?
|
|
ORDER BY min_skill_level ASC
|
|
`
|
|
|
|
var entries []*GroundSpawnEntry
|
|
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
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query groundspawn entries: %w", err)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// 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
|
|
WHERE groundspawn_id = ?
|
|
ORDER BY item_id ASC
|
|
`
|
|
|
|
var items []*GroundSpawnEntryItem
|
|
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
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query groundspawn items: %w", err)
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// SaveGroundSpawn saves a ground spawn to the database
|
|
func (da *DatabaseAdapter) SaveGroundSpawn(gs *GroundSpawn) error {
|
|
if gs == 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 := `
|
|
INSERT OR REPLACE INTO groundspawns (
|
|
id, name, x, y, z, heading, respawn_timer, collection_skill,
|
|
number_harvests, attempts_per_harvest, groundspawn_entry_id,
|
|
randomize_heading, zone_id, created_at, updated_at
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
|
`
|
|
|
|
randomizeHeading := 0
|
|
if gs.GetRandomizeHeading() {
|
|
randomizeHeading = 1
|
|
}
|
|
|
|
// TODO: Get actual zone ID from spawn
|
|
zoneID := int32(1)
|
|
|
|
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{
|
|
gs.GetID(),
|
|
gs.GetName(),
|
|
gs.GetX(),
|
|
gs.GetY(),
|
|
gs.GetZ(),
|
|
int16(gs.GetHeading()),
|
|
300, // Default 5 minutes respawn timer
|
|
gs.GetCollectionSkill(),
|
|
gs.GetNumberHarvests(),
|
|
gs.GetAttemptsPerHarvest(),
|
|
gs.GetGroundSpawnEntryID(),
|
|
randomizeHeading,
|
|
zoneID,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save ground spawn %d: %w", gs.GetID(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
FROM groundspawns
|
|
WHERE zone_id = ?
|
|
ORDER BY id ASC
|
|
`
|
|
|
|
// TODO: Support multiple zones
|
|
zoneID := int32(1)
|
|
|
|
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
|
|
|
|
config := GroundSpawnConfig{
|
|
GroundSpawnID: groundspawnEntryID,
|
|
CollectionSkill: collectionSkill,
|
|
NumberHarvests: numberHarvests,
|
|
AttemptsPerHarvest: attemptsPerHarvest,
|
|
RandomizeHeading: randomizeHeading,
|
|
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)
|
|
gs.SetID(id)
|
|
|
|
groundSpawns = append(groundSpawns, gs)
|
|
return nil
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query groundspawns: %w", err)
|
|
}
|
|
|
|
return groundSpawns, nil
|
|
}
|
|
|
|
// 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 = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{id},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete ground spawn %d: %w", id, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
WHERE player_id = ?
|
|
`
|
|
|
|
stats := make(map[string]int64)
|
|
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
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query player harvest stats: %w", err)
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// 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 = 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
|
|
} |