eq2go/internal/player/database.go

169 lines
3.9 KiB
Go

package player
import (
"database/sql"
"fmt"
"sync"
"eq2emu/internal/database"
)
// PlayerDatabase manages player data persistence using MySQL
type PlayerDatabase struct {
db *database.Database
mutex sync.RWMutex
}
// NewPlayerDatabase creates a new player database instance
func NewPlayerDatabase(db *database.Database) *PlayerDatabase {
return &PlayerDatabase{
db: db,
}
}
// 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)
query := `SELECT name, level, race, class, zone_id, x, y, z, heading
FROM characters WHERE id = ?`
row := pdb.db.QueryRow(query, characterID)
var name string
var level int16
var race, class int8
var zoneID int32
var x, y, z, heading float32
err := row.Scan(&name, &level, &race, &class, &zoneID, &x, &y, &z, &heading)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("player not found: %d", characterID)
}
return nil, fmt.Errorf("failed to load player: %w", err)
}
player.SetName(name)
player.SetLevel(level)
player.SetRace(race)
player.SetClass(class)
player.SetZone(zoneID)
player.SetX(x)
player.SetY(y, false)
player.SetZ(z)
player.SetHeadingFromFloat(heading)
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
return pdb.updatePlayer(player)
}
// 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`
result, err := pdb.db.Exec(query,
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: %w", err)
}
// Get the inserted character ID
characterID, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("failed to get inserted character ID: %w", err)
}
player.SetCharacterID(int32(characterID))
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=?
WHERE id=?`
_, err := pdb.db.Exec(query,
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: %w", err)
}
return nil
}
// DeletePlayer soft-deletes a player (marks as deleted)
func (pdb *PlayerDatabase) DeletePlayer(characterID int32) error {
pdb.mutex.Lock()
defer pdb.mutex.Unlock()
query := `UPDATE characters SET deleted_date = NOW() WHERE id = ?`
_, err := pdb.db.Exec(query, characterID)
if err != nil {
return fmt.Errorf("failed to delete player %d: %w", characterID, err)
}
return nil
}
// PlayerExists checks if a player exists in the database
func (pdb *PlayerDatabase) PlayerExists(characterID int32) (bool, error) {
pdb.mutex.RLock()
defer pdb.mutex.RUnlock()
var count int
query := `SELECT COUNT(*) FROM characters WHERE id = ? AND (deleted_date IS NULL OR deleted_date = 0)`
err := pdb.db.QueryRow(query, characterID).Scan(&count)
if err != nil {
return false, fmt.Errorf("failed to check player existence: %w", err)
}
return count > 0, nil
}