138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
package ai
|
|
|
|
import (
|
|
"a301_game_server/internal/combat"
|
|
"a301_game_server/internal/entity"
|
|
"a301_game_server/pkg/mathutil"
|
|
pb "a301_game_server/proto/gen/pb"
|
|
)
|
|
|
|
// MobDef defines a mob template loaded from data.
|
|
type MobDef struct {
|
|
ID uint32
|
|
Name string
|
|
Level int32
|
|
HP int32
|
|
MP int32
|
|
Str int32
|
|
Dex int32
|
|
Int int32
|
|
MoveSpeed float32 // units per second
|
|
AggroRange float32 // distance to start chasing
|
|
AttackRange float32
|
|
AttackSkill uint32 // skill ID used for auto-attack
|
|
LeashRange float32 // max distance from spawn before returning
|
|
ExpReward int64
|
|
LootTable []LootEntry
|
|
}
|
|
|
|
// LootEntry defines a possible drop.
|
|
type LootEntry struct {
|
|
ItemID uint32
|
|
Quantity int32
|
|
Chance float32 // 0.0 - 1.0
|
|
}
|
|
|
|
// Mob is a server-controlled enemy entity.
|
|
type Mob struct {
|
|
id uint64
|
|
def *MobDef
|
|
position mathutil.Vec3
|
|
rotation float32
|
|
hp int32
|
|
maxHP int32
|
|
mp int32
|
|
maxMP int32
|
|
spawnPos mathutil.Vec3
|
|
alive bool
|
|
|
|
// AI state
|
|
state AIState
|
|
targetID uint64 // entity being chased/attacked
|
|
}
|
|
|
|
// NewMob creates a mob from a definition at the given position.
|
|
func NewMob(id uint64, def *MobDef, spawnPos mathutil.Vec3) *Mob {
|
|
return &Mob{
|
|
id: id,
|
|
def: def,
|
|
position: spawnPos,
|
|
spawnPos: spawnPos,
|
|
hp: def.HP,
|
|
maxHP: def.HP,
|
|
mp: def.MP,
|
|
maxMP: def.MP,
|
|
alive: true,
|
|
state: StateIdle,
|
|
}
|
|
}
|
|
|
|
// Entity interface
|
|
func (m *Mob) EntityID() uint64 { return m.id }
|
|
func (m *Mob) EntityType() entity.Type { return entity.TypeMob }
|
|
|
|
func (m *Mob) Position() mathutil.Vec3 { return m.position }
|
|
func (m *Mob) SetPosition(p mathutil.Vec3) { m.position = p }
|
|
func (m *Mob) Rotation() float32 { return m.rotation }
|
|
func (m *Mob) SetRotation(r float32) { m.rotation = r }
|
|
|
|
// Combatant interface
|
|
func (m *Mob) HP() int32 { return m.hp }
|
|
func (m *Mob) MaxHP() int32 { return m.maxHP }
|
|
func (m *Mob) SetHP(hp int32) {
|
|
if hp < 0 {
|
|
hp = 0
|
|
}
|
|
if hp > m.maxHP {
|
|
hp = m.maxHP
|
|
}
|
|
m.hp = hp
|
|
m.alive = hp > 0
|
|
}
|
|
func (m *Mob) MP() int32 { return m.mp }
|
|
func (m *Mob) SetMP(mp int32) {
|
|
if mp < 0 {
|
|
mp = 0
|
|
}
|
|
m.mp = mp
|
|
}
|
|
func (m *Mob) IsAlive() bool { return m.alive }
|
|
func (m *Mob) Stats() combat.CombatStats {
|
|
return combat.CombatStats{
|
|
Str: m.def.Str,
|
|
Dex: m.def.Dex,
|
|
Int: m.def.Int,
|
|
Level: m.def.Level,
|
|
}
|
|
}
|
|
|
|
func (m *Mob) Def() *MobDef { return m.def }
|
|
func (m *Mob) SpawnPos() mathutil.Vec3 { return m.spawnPos }
|
|
func (m *Mob) State() AIState { return m.state }
|
|
func (m *Mob) SetState(s AIState) { m.state = s }
|
|
func (m *Mob) TargetID() uint64 { return m.targetID }
|
|
func (m *Mob) SetTargetID(id uint64) { m.targetID = id }
|
|
|
|
func (m *Mob) ToProto() *pb.EntityState {
|
|
return &pb.EntityState{
|
|
EntityId: m.id,
|
|
Name: m.def.Name,
|
|
Position: &pb.Vector3{X: m.position.X, Y: m.position.Y, Z: m.position.Z},
|
|
Rotation: m.rotation,
|
|
Hp: m.hp,
|
|
MaxHp: m.maxHP,
|
|
Level: m.def.Level,
|
|
EntityType: pb.EntityType_ENTITY_TYPE_MOB,
|
|
}
|
|
}
|
|
|
|
// Reset restores the mob to full health at its spawn position.
|
|
func (m *Mob) Reset() {
|
|
m.hp = m.maxHP
|
|
m.mp = m.maxMP
|
|
m.position = m.spawnPos
|
|
m.alive = true
|
|
m.state = StateIdle
|
|
m.targetID = 0
|
|
}
|