250 lines
5.2 KiB
Go
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,
|
|
}
|
|
}
|