227 lines
5.6 KiB
Go
227 lines
5.6 KiB
Go
package player
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"zombiezen.com/go/sqlite"
|
|
"zombiezen.com/go/sqlite/sqlitex"
|
|
)
|
|
|
|
// PlayerDatabase manages player data persistence using SQLite
|
|
type PlayerDatabase struct {
|
|
conn *sqlite.Conn
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewPlayerDatabase creates a new player database instance
|
|
func NewPlayerDatabase(conn *sqlite.Conn) *PlayerDatabase {
|
|
return &PlayerDatabase{
|
|
conn: conn,
|
|
}
|
|
}
|
|
|
|
// LoadPlayer loads a player from the database
|
|
func (pdb *PlayerDatabase) LoadPlayer(characterID int32) (*Player, error) {
|
|
pdb.mutex.RLock()
|
|
defer pdb.mutex.RUnlock()
|
|
|
|
player := NewPlayer()
|
|
player.SetCharacterID(characterID)
|
|
found := false
|
|
|
|
query := `SELECT name, level, race, class, zone_id, x, y, z, heading
|
|
FROM characters WHERE id = ?`
|
|
|
|
err := sqlitex.Execute(pdb.conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{characterID},
|
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
|
player.SetName(stmt.ColumnText(0))
|
|
player.SetLevel(int16(stmt.ColumnInt(1)))
|
|
player.SetRace(int8(stmt.ColumnInt(2)))
|
|
player.SetClass(int8(stmt.ColumnInt(3)))
|
|
player.SetZone(int32(stmt.ColumnInt(4)))
|
|
player.SetX(float32(stmt.ColumnFloat(5)))
|
|
player.SetY(float32(stmt.ColumnFloat(6)), false)
|
|
player.SetZ(float32(stmt.ColumnFloat(7)))
|
|
player.SetHeadingFromFloat(float32(stmt.ColumnFloat(8)))
|
|
found = true
|
|
return nil
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load player %d: %w", characterID, err)
|
|
}
|
|
|
|
if !found {
|
|
return nil, fmt.Errorf("player %d not found", characterID)
|
|
}
|
|
|
|
return player, nil
|
|
}
|
|
|
|
// SavePlayer saves a player to the database
|
|
func (pdb *PlayerDatabase) SavePlayer(player *Player) error {
|
|
if player == nil {
|
|
return fmt.Errorf("cannot save nil player")
|
|
}
|
|
|
|
pdb.mutex.Lock()
|
|
defer pdb.mutex.Unlock()
|
|
|
|
characterID := player.GetCharacterID()
|
|
if characterID == 0 {
|
|
// Insert new player
|
|
return pdb.insertPlayer(player)
|
|
}
|
|
|
|
// Try to update existing player first
|
|
err := pdb.updatePlayer(player)
|
|
if err == nil {
|
|
// Check if any rows were affected
|
|
changes := pdb.conn.Changes()
|
|
if changes == 0 {
|
|
// No rows updated, record doesn't exist - insert it
|
|
return pdb.insertPlayerWithID(player)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// insertPlayer inserts a new player record
|
|
func (pdb *PlayerDatabase) insertPlayer(player *Player) error {
|
|
query := `INSERT INTO characters
|
|
(name, level, race, class, zone_id, x, y, z, heading, created_date)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`
|
|
|
|
err := sqlitex.Execute(pdb.conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{
|
|
player.GetName(),
|
|
player.GetLevel(),
|
|
player.GetRace(),
|
|
player.GetClass(),
|
|
player.GetZone(),
|
|
player.GetX(),
|
|
player.GetY(),
|
|
player.GetZ(),
|
|
player.GetHeading(),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert player %s: %w", player.GetName(), err)
|
|
}
|
|
|
|
// Get the new character ID
|
|
characterID := pdb.conn.LastInsertRowID()
|
|
player.SetCharacterID(int32(characterID))
|
|
|
|
return nil
|
|
}
|
|
|
|
// insertPlayerWithID inserts a player with a specific ID
|
|
func (pdb *PlayerDatabase) insertPlayerWithID(player *Player) error {
|
|
query := `INSERT INTO characters
|
|
(id, name, level, race, class, zone_id, x, y, z, heading, created_date)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`
|
|
|
|
err := sqlitex.Execute(pdb.conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{
|
|
player.GetCharacterID(),
|
|
player.GetName(),
|
|
player.GetLevel(),
|
|
player.GetRace(),
|
|
player.GetClass(),
|
|
player.GetZone(),
|
|
player.GetX(),
|
|
player.GetY(),
|
|
player.GetZ(),
|
|
player.GetHeading(),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert player %s with ID %d: %w", player.GetName(), player.GetCharacterID(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updatePlayer updates an existing player record
|
|
func (pdb *PlayerDatabase) updatePlayer(player *Player) error {
|
|
query := `UPDATE characters
|
|
SET name = ?, level = ?, race = ?, class = ?, zone_id = ?,
|
|
x = ?, y = ?, z = ?, heading = ?, last_save = datetime('now')
|
|
WHERE id = ?`
|
|
|
|
err := sqlitex.Execute(pdb.conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{
|
|
player.GetName(),
|
|
player.GetLevel(),
|
|
player.GetRace(),
|
|
player.GetClass(),
|
|
player.GetZone(),
|
|
player.GetX(),
|
|
player.GetY(),
|
|
player.GetZ(),
|
|
player.GetHeading(),
|
|
player.GetCharacterID(),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update player %d: %w", player.GetCharacterID(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeletePlayer deletes a player from the database
|
|
func (pdb *PlayerDatabase) DeletePlayer(characterID int32) error {
|
|
pdb.mutex.Lock()
|
|
defer pdb.mutex.Unlock()
|
|
|
|
query := `DELETE FROM characters WHERE id = ?`
|
|
|
|
err := sqlitex.Execute(pdb.conn, query, &sqlitex.ExecOptions{
|
|
Args: []any{characterID},
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete player %d: %w", characterID, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateSchema creates the database schema for player data
|
|
func (pdb *PlayerDatabase) CreateSchema() error {
|
|
pdb.mutex.Lock()
|
|
defer pdb.mutex.Unlock()
|
|
|
|
schema := `
|
|
CREATE TABLE IF NOT EXISTS characters (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
level INTEGER DEFAULT 1,
|
|
race INTEGER DEFAULT 1,
|
|
class INTEGER DEFAULT 1,
|
|
zone_id INTEGER DEFAULT 1,
|
|
x REAL DEFAULT 0,
|
|
y REAL DEFAULT 0,
|
|
z REAL DEFAULT 0,
|
|
heading REAL DEFAULT 0,
|
|
hp INTEGER DEFAULT 100,
|
|
power INTEGER DEFAULT 100,
|
|
created_date TEXT,
|
|
last_save TEXT,
|
|
account_id INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_characters_name ON characters(name);
|
|
CREATE INDEX IF NOT EXISTS idx_characters_account ON characters(account_id);
|
|
`
|
|
|
|
return sqlitex.ExecuteScript(pdb.conn, schema, &sqlitex.ExecOptions{})
|
|
} |