package repository import ( "context" "fmt" "a301_game_server/internal/db" ) // CharacterData holds persisted character state. type CharacterData struct { ID int64 AccountID int64 Name string Level int32 Exp int64 HP int32 MaxHP int32 MP int32 MaxMP int32 Str int32 Dex int32 IntStat int32 ZoneID int32 PosX float32 PosY float32 PosZ float32 Rotation float32 } // CharacterRepo handles character persistence. type CharacterRepo struct { pool *db.Pool } // NewCharacterRepo creates a new character repository. func NewCharacterRepo(pool *db.Pool) *CharacterRepo { return &CharacterRepo{pool: pool} } // Create inserts a new character. func (r *CharacterRepo) Create(ctx context.Context, accountID int64, name string) (*CharacterData, error) { c := &CharacterData{ AccountID: accountID, Name: name, Level: 1, HP: 100, MaxHP: 100, MP: 50, MaxMP: 50, Str: 10, Dex: 10, IntStat: 10, ZoneID: 1, } err := r.pool.QueryRow(ctx, `INSERT INTO characters (account_id, name, level, hp, max_hp, mp, max_mp, str, dex, int_stat, zone_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`, c.AccountID, c.Name, c.Level, c.HP, c.MaxHP, c.MP, c.MaxMP, c.Str, c.Dex, c.IntStat, c.ZoneID, ).Scan(&c.ID) if err != nil { return nil, fmt.Errorf("create character: %w", err) } return c, nil } // GetByAccountID returns all characters for an account. func (r *CharacterRepo) GetByAccountID(ctx context.Context, accountID int64) ([]*CharacterData, error) { rows, err := r.pool.Query(ctx, `SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat, zone_id, pos_x, pos_y, pos_z, rotation FROM characters WHERE account_id = $1`, accountID, ) if err != nil { return nil, fmt.Errorf("query characters: %w", err) } defer rows.Close() var chars []*CharacterData for rows.Next() { c := &CharacterData{} if err := rows.Scan( &c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp, &c.HP, &c.MaxHP, &c.MP, &c.MaxMP, &c.Str, &c.Dex, &c.IntStat, &c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation, ); err != nil { return nil, fmt.Errorf("scan character: %w", err) } chars = append(chars, c) } return chars, nil } // GetByID loads a single character. func (r *CharacterRepo) GetByID(ctx context.Context, id int64) (*CharacterData, error) { c := &CharacterData{} err := r.pool.QueryRow(ctx, `SELECT id, account_id, name, level, exp, hp, max_hp, mp, max_mp, str, dex, int_stat, zone_id, pos_x, pos_y, pos_z, rotation FROM characters WHERE id = $1`, id, ).Scan( &c.ID, &c.AccountID, &c.Name, &c.Level, &c.Exp, &c.HP, &c.MaxHP, &c.MP, &c.MaxMP, &c.Str, &c.Dex, &c.IntStat, &c.ZoneID, &c.PosX, &c.PosY, &c.PosZ, &c.Rotation, ) if err != nil { return nil, fmt.Errorf("get character %d: %w", id, err) } return c, nil } // Save persists the current character state. func (r *CharacterRepo) Save(ctx context.Context, c *CharacterData) error { _, err := r.pool.Exec(ctx, `UPDATE characters SET level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7, str = $8, dex = $9, int_stat = $10, zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15, updated_at = NOW() WHERE id = $1`, c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP, c.Str, c.Dex, c.IntStat, c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation, ) if err != nil { return fmt.Errorf("save character %d: %w", c.ID, err) } return nil } // SaveBatch saves multiple characters in a single transaction. func (r *CharacterRepo) SaveBatch(ctx context.Context, chars []*CharacterData) error { if len(chars) == 0 { return nil } tx, err := r.pool.Begin(ctx) if err != nil { return fmt.Errorf("begin tx: %w", err) } defer tx.Rollback(ctx) for _, c := range chars { _, err := tx.Exec(ctx, `UPDATE characters SET level = $2, exp = $3, hp = $4, max_hp = $5, mp = $6, max_mp = $7, str = $8, dex = $9, int_stat = $10, zone_id = $11, pos_x = $12, pos_y = $13, pos_z = $14, rotation = $15, updated_at = NOW() WHERE id = $1`, c.ID, c.Level, c.Exp, c.HP, c.MaxHP, c.MP, c.MaxMP, c.Str, c.Dex, c.IntStat, c.ZoneID, c.PosX, c.PosY, c.PosZ, c.Rotation, ) if err != nil { return fmt.Errorf("save character %d in batch: %w", c.ID, err) } } return tx.Commit(ctx) }