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, } }