Files
a301_game_server/internal/player/player.go
2026-02-26 17:52:48 +09:00

250 lines
5.2 KiB
Go

package player
import (
"sync"
"a301_game_server/internal/combat"
"a301_game_server/internal/db/repository"
"a301_game_server/internal/entity"
"a301_game_server/internal/network"
"a301_game_server/pkg/mathutil"
pb "a301_game_server/proto/gen/pb"
)
// Player represents an online player in the game world.
type Player struct {
mu sync.RWMutex
id uint64
charID int64 // database character ID
acctID int64 // database account ID
name string
position mathutil.Vec3
rotation float32
velocity mathutil.Vec3
stats Stats
conn *network.Connection
zoneID uint32
session *Session
dirty bool // true if state changed since last DB save
}
// NewPlayer creates a new player.
func NewPlayer(id uint64, name string, conn *network.Connection) *Player {
return &Player{
id: id,
name: name,
conn: conn,
stats: Stats{
HP: 100, MaxHP: 100,
MP: 50, MaxMP: 50,
Str: 10, Dex: 10, Int: 10,
Level: 1,
},
}
}
// NewPlayerFromDB creates a player from persisted character data.
func NewPlayerFromDB(data *repository.CharacterData, conn *network.Connection) *Player {
return &Player{
id: uint64(data.ID),
charID: data.ID,
acctID: data.AccountID,
name: data.Name,
position: mathutil.NewVec3(data.PosX, data.PosY, data.PosZ),
rotation: data.Rotation,
stats: Stats{
HP: data.HP, MaxHP: data.MaxHP,
MP: data.MP, MaxMP: data.MaxMP,
Str: data.Str, Dex: data.Dex, Int: data.IntStat,
Level: data.Level, Exp: data.Exp,
},
conn: conn,
zoneID: uint32(data.ZoneID),
}
}
// ToCharacterData converts current state to a persistable format.
func (p *Player) ToCharacterData() *repository.CharacterData {
p.mu.RLock()
defer p.mu.RUnlock()
return &repository.CharacterData{
ID: p.charID,
AccountID: p.acctID,
Name: p.name,
Level: p.stats.Level,
Exp: p.stats.Exp,
HP: p.stats.HP,
MaxHP: p.stats.MaxHP,
MP: p.stats.MP,
MaxMP: p.stats.MaxMP,
Str: p.stats.Str,
Dex: p.stats.Dex,
IntStat: p.stats.Int,
ZoneID: int32(p.zoneID),
PosX: p.position.X,
PosY: p.position.Y,
PosZ: p.position.Z,
Rotation: p.rotation,
}
}
func (p *Player) EntityID() uint64 { return p.id }
func (p *Player) EntityType() entity.Type { return entity.TypePlayer }
func (p *Player) Position() mathutil.Vec3 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.position
}
func (p *Player) SetPosition(pos mathutil.Vec3) {
p.mu.Lock()
defer p.mu.Unlock()
p.position = pos
p.dirty = true
}
func (p *Player) Rotation() float32 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.rotation
}
func (p *Player) SetRotation(rot float32) {
p.mu.Lock()
defer p.mu.Unlock()
p.rotation = rot
p.dirty = true
}
func (p *Player) Velocity() mathutil.Vec3 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.velocity
}
func (p *Player) SetVelocity(vel mathutil.Vec3) {
p.mu.Lock()
defer p.mu.Unlock()
p.velocity = vel
}
func (p *Player) Name() string { return p.name }
func (p *Player) CharID() int64 { return p.charID }
func (p *Player) AccountID() int64 { return p.acctID }
func (p *Player) Connection() *network.Connection { return p.conn }
func (p *Player) ZoneID() uint32 { return p.zoneID }
func (p *Player) SetZoneID(id uint32) { p.zoneID = id }
func (p *Player) Stats() combat.CombatStats {
p.mu.RLock()
defer p.mu.RUnlock()
return combat.CombatStats{
Str: p.stats.Str,
Dex: p.stats.Dex,
Int: p.stats.Int,
Level: p.stats.Level,
}
}
func (p *Player) RawStats() Stats {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats
}
func (p *Player) SetStats(s Stats) {
p.mu.Lock()
defer p.mu.Unlock()
p.stats = s
p.dirty = true
}
func (p *Player) HP() int32 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats.HP
}
func (p *Player) SetHP(hp int32) {
p.mu.Lock()
defer p.mu.Unlock()
if hp < 0 {
hp = 0
}
if hp > p.stats.MaxHP {
hp = p.stats.MaxHP
}
p.stats.HP = hp
p.dirty = true
}
func (p *Player) MaxHP() int32 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats.MaxHP
}
func (p *Player) MP() int32 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats.MP
}
func (p *Player) SetMP(mp int32) {
p.mu.Lock()
defer p.mu.Unlock()
if mp < 0 {
mp = 0
}
if mp > p.stats.MaxMP {
mp = p.stats.MaxMP
}
p.stats.MP = mp
p.dirty = true
}
func (p *Player) Level() int32 {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats.Level
}
func (p *Player) IsAlive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.stats.HP > 0
}
// IsDirty returns true if state has changed since last save.
func (p *Player) IsDirty() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.dirty
}
// ClearDirty resets the dirty flag after a successful save.
func (p *Player) ClearDirty() {
p.mu.Lock()
defer p.mu.Unlock()
p.dirty = false
}
func (p *Player) ToProto() *pb.EntityState {
p.mu.RLock()
defer p.mu.RUnlock()
return &pb.EntityState{
EntityId: p.id,
Name: p.name,
Position: &pb.Vector3{X: p.position.X, Y: p.position.Y, Z: p.position.Z},
Rotation: p.rotation,
Hp: p.stats.HP,
MaxHp: p.stats.MaxHP,
Level: p.stats.Level,
EntityType: pb.EntityType_ENTITY_TYPE_PLAYER,
}
}