修复了一些bug
This commit is contained in:
@@ -16,26 +16,44 @@ const (
|
||||
|
||||
// Animal 动物附加数据(动物体系.md §2)。
|
||||
type Animal struct {
|
||||
Species int // 0 牛 / 1 猪 / 2 羊 / 3 鸡(声音/外观/掉落/繁殖共用)
|
||||
BreedCooldown int // 繁殖冷却剩余 tick
|
||||
GrowthTicks int // 幼崽成长剩余(0 = 成年)
|
||||
ParentA ID // 近亲繁殖记录(后期)
|
||||
LoveTicks int // 喂食后进入繁殖模式剩余 tick
|
||||
Sheared bool
|
||||
ParentA ID // 近亲繁殖记录(后期)
|
||||
ParentB ID
|
||||
}
|
||||
|
||||
// AnimalSpecies 读取物种字段;无 Animal 数据时回退 ID%4(动物体系.md §2)。
|
||||
func AnimalSpecies(e *Entity) int {
|
||||
if e == nil || e.Kind != KindAnimal {
|
||||
return 0
|
||||
}
|
||||
if d, ok := e.Data.(*Animal); ok {
|
||||
return d.Species
|
||||
}
|
||||
return int(e.ID % 4)
|
||||
}
|
||||
|
||||
// Breed 繁殖两只同种成年动物 → 幼崽(动物体系.md §3:冷却与上限控制)。
|
||||
// 返回新幼崽;冷却未到/上限达到返回 nil。
|
||||
func (m *Manager) Breed(a, b *Entity, w WorldView) *Entity {
|
||||
if a.Kind != KindAnimal || b.Kind != KindAnimal || a.Dead || b.Dead {
|
||||
return nil
|
||||
}
|
||||
sa, sb := AnimalSpecies(a), AnimalSpecies(b)
|
||||
if sa != sb {
|
||||
return nil
|
||||
}
|
||||
ad, ok1 := a.Data.(*Animal)
|
||||
bd, ok2 := b.Data.(*Animal)
|
||||
if !ok1 {
|
||||
ad = &Animal{}
|
||||
ad = &Animal{Species: sa}
|
||||
a.Data = ad
|
||||
}
|
||||
if !ok2 {
|
||||
bd = &Animal{}
|
||||
bd = &Animal{Species: sb}
|
||||
b.Data = bd
|
||||
}
|
||||
if ad.BreedCooldown > 0 || bd.BreedCooldown > 0 || ad.GrowthTicks > 0 || bd.GrowthTicks > 0 {
|
||||
@@ -48,7 +66,7 @@ func (m *Manager) Breed(a, b *Entity, w WorldView) *Entity {
|
||||
ad.BreedCooldown = breedCooldown
|
||||
bd.BreedCooldown = breedCooldown
|
||||
baby := m.Spawn(KindAnimal, a.Pos[0], a.Pos[1], a.Pos[2])
|
||||
baby.Data = &Animal{GrowthTicks: babyGrowTime, ParentA: a.ID, ParentB: b.ID}
|
||||
baby.Data = &Animal{Species: sa, GrowthTicks: babyGrowTime, ParentA: a.ID, ParentB: b.ID}
|
||||
baby.Health = 10 // 幼崽血量
|
||||
return baby
|
||||
}
|
||||
@@ -65,6 +83,9 @@ func TickAnimal(e *Entity) {
|
||||
if d.BreedCooldown > 0 {
|
||||
d.BreedCooldown--
|
||||
}
|
||||
if d.LoveTicks > 0 {
|
||||
d.LoveTicks--
|
||||
}
|
||||
if d.GrowthTicks > 0 {
|
||||
d.GrowthTicks--
|
||||
if d.GrowthTicks == 0 {
|
||||
@@ -73,6 +94,78 @@ func TickAnimal(e *Entity) {
|
||||
}
|
||||
}
|
||||
|
||||
const loveDuration = 30 * 20 // 喂食后 30 秒可配对
|
||||
|
||||
// Feed 喂食进入 love;幼崽/冷却中返回 false。
|
||||
func Feed(e *Entity) bool {
|
||||
if e == nil || e.Kind != KindAnimal || e.Dead {
|
||||
return false
|
||||
}
|
||||
d, ok := e.Data.(*Animal)
|
||||
if !ok {
|
||||
d = &Animal{Species: AnimalSpecies(e)}
|
||||
e.Data = d
|
||||
}
|
||||
if d.GrowthTicks > 0 || d.BreedCooldown > 0 {
|
||||
return false
|
||||
}
|
||||
d.LoveTicks = loveDuration
|
||||
return true
|
||||
}
|
||||
|
||||
// LovePartner 附近同种 love 中的另一只成年动物。
|
||||
func (m *Manager) LovePartner(e *Entity) *Entity {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
sp := AnimalSpecies(e)
|
||||
for _, o := range m.List() {
|
||||
if o == e || o.Kind != KindAnimal || o.Dead {
|
||||
continue
|
||||
}
|
||||
if AnimalSpecies(o) != sp {
|
||||
continue
|
||||
}
|
||||
d, ok := o.Data.(*Animal)
|
||||
if !ok || d.LoveTicks <= 0 || d.GrowthTicks > 0 {
|
||||
continue
|
||||
}
|
||||
dx, dz := o.Pos[0]-e.Pos[0], o.Pos[2]-e.Pos[2]
|
||||
if dx*dx+dz*dz < 8*8 {
|
||||
return o
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShearSheep 剪羊毛:未剪过的羊掉 1–3 白羊毛。
|
||||
func ShearSheep(e *Entity, nWool int) bool {
|
||||
if e == nil || e.Kind != KindAnimal || AnimalSpecies(e) != 2 {
|
||||
return false
|
||||
}
|
||||
d, ok := e.Data.(*Animal)
|
||||
if !ok {
|
||||
d = &Animal{Species: 2}
|
||||
e.Data = d
|
||||
}
|
||||
if d.Sheared {
|
||||
return false
|
||||
}
|
||||
d.Sheared = true
|
||||
if nWool < 1 {
|
||||
nWool = 1
|
||||
}
|
||||
if nWool > 3 {
|
||||
nWool = 3
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MilkCow 空桶挤牛奶(牛)。
|
||||
func MilkCow(e *Entity) bool {
|
||||
return e != nil && e.Kind == KindAnimal && !e.Dead && AnimalSpecies(e) == 0
|
||||
}
|
||||
|
||||
// countNear 统计同种动物数量(以 a 所在区块计,简化按距离 16)。
|
||||
func (m *Manager) countNear(a *Entity, _ int) int {
|
||||
n := 0
|
||||
|
||||
@@ -10,15 +10,15 @@ func TestBreed(t *testing.T) {
|
||||
m := NewManager()
|
||||
w := flatWorld{}
|
||||
a := m.Spawn(KindAnimal, 0.5, 63, 0.5)
|
||||
a.Data = &Animal{}
|
||||
a.Data = &Animal{Species: 1}
|
||||
b := m.Spawn(KindAnimal, 1.5, 63, 0.5)
|
||||
b.Data = &Animal{}
|
||||
b.Data = &Animal{Species: 1}
|
||||
|
||||
baby := m.Breed(a, b, w)
|
||||
if baby == nil {
|
||||
t.Fatal("应繁殖出幼崽")
|
||||
}
|
||||
if d, ok := baby.Data.(*Animal); !ok || d.GrowthTicks == 0 {
|
||||
if d, ok := baby.Data.(*Animal); !ok || d.GrowthTicks == 0 || d.Species != 1 {
|
||||
t.Fatalf("幼崽数据异常: %+v", baby.Data)
|
||||
}
|
||||
// 冷却中:拒绝
|
||||
@@ -40,9 +40,9 @@ func TestBreedCap(t *testing.T) {
|
||||
m := NewManager()
|
||||
w := flatWorld{}
|
||||
a := m.Spawn(KindAnimal, 0.5, 63, 0.5)
|
||||
a.Data = &Animal{}
|
||||
a.Data = &Animal{Species: 0}
|
||||
b := m.Spawn(KindAnimal, 1.5, 63, 0.5)
|
||||
b.Data = &Animal{}
|
||||
b.Data = &Animal{Species: 0}
|
||||
for i := 0; i < 12; i++ {
|
||||
m.Spawn(KindAnimal, 2.5, 63, 2.5+float64(i))
|
||||
}
|
||||
@@ -51,7 +51,64 @@ func TestBreedCap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnimalSpeciesFallback(t *testing.T) {
|
||||
e := &Entity{ID: 6, Kind: KindAnimal}
|
||||
if AnimalSpecies(e) != 2 {
|
||||
t.Fatalf("无 Data 应回退 ID%%4=2,实际 %d", AnimalSpecies(e))
|
||||
}
|
||||
e.Data = &Animal{Species: 3}
|
||||
if AnimalSpecies(e) != 3 {
|
||||
t.Fatalf("应读 Species 字段,实际 %d", AnimalSpecies(e))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreedCrossSpecies(t *testing.T) {
|
||||
m := NewManager()
|
||||
w := flatWorld{}
|
||||
a := m.Spawn(KindAnimal, 0.5, 63, 0.5)
|
||||
a.Data = &Animal{Species: 0}
|
||||
b := m.Spawn(KindAnimal, 1.5, 63, 0.5)
|
||||
b.Data = &Animal{Species: 2}
|
||||
if m.Breed(a, b, w) != nil {
|
||||
t.Fatal("异种不应繁殖")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadTrades 交易表解析与职业查询(村民系统.md §7)。
|
||||
func TestFeedAndLove(t *testing.T) {
|
||||
e := &Entity{Kind: KindAnimal, Data: &Animal{Species: 0}}
|
||||
if !Feed(e) {
|
||||
t.Fatal("成年牛应能喂食")
|
||||
}
|
||||
if e.Data.(*Animal).LoveTicks <= 0 {
|
||||
t.Fatal("喂食应进入 love")
|
||||
}
|
||||
baby := &Entity{Kind: KindAnimal, Data: &Animal{Species: 0, GrowthTicks: 10}}
|
||||
if Feed(baby) {
|
||||
t.Fatal("幼崽不应进入繁殖")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShearAndMilk(t *testing.T) {
|
||||
sheep := &Entity{Kind: KindAnimal, Data: &Animal{Species: 2}}
|
||||
if !ShearSheep(sheep, 2) {
|
||||
t.Fatal("羊应能剪毛")
|
||||
}
|
||||
if !sheep.Data.(*Animal).Sheared {
|
||||
t.Fatal("剪后应标记 Sheared")
|
||||
}
|
||||
if ShearSheep(sheep, 1) {
|
||||
t.Fatal("已剪不应再剪")
|
||||
}
|
||||
cow := &Entity{Kind: KindAnimal, Data: &Animal{Species: 0}}
|
||||
if !MilkCow(cow) {
|
||||
t.Fatal("牛应能挤奶")
|
||||
}
|
||||
if MilkCow(sheep) {
|
||||
t.Fatal("羊不能挤奶")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTrades(t *testing.T) {
|
||||
tbl, err := LoadTrades(filepath.Join("..", "..", "assets", "config", "villager_trades.json"))
|
||||
if err != nil {
|
||||
|
||||
@@ -59,6 +59,10 @@ type Entity struct {
|
||||
|
||||
// Data 类型附加数据(掉落物 = *Drop,村民 = *Villager)
|
||||
Data any
|
||||
|
||||
// AmbientT / WalkDist 音效时钟(音频.md §6.2;由 Session 推进)。
|
||||
AmbientT int
|
||||
WalkDist float64
|
||||
}
|
||||
|
||||
// 僵尸 AI 状态。
|
||||
@@ -140,6 +144,7 @@ func (m *Manager) Tick(dt float64, w WorldView, playerPos [3]float64) {
|
||||
e.tickZombie(dt, w, playerPos)
|
||||
case KindAnimal:
|
||||
e.tickAnimal(dt, w)
|
||||
TickAnimal(e)
|
||||
case KindVillager:
|
||||
e.tickVillager(dt, w)
|
||||
}
|
||||
@@ -217,7 +222,7 @@ func (e *Entity) tickZombie(dt float64, w WorldView, playerPos [3]float64) {
|
||||
speed := 3.0
|
||||
e.Vel[0] = dx / dist * speed
|
||||
e.Vel[2] = dz / dist * speed
|
||||
e.Yaw = math.Atan2(-dx, -dz)
|
||||
e.Yaw = faceVelYaw(e.Vel[0], e.Vel[2])
|
||||
}
|
||||
if dist > 24 {
|
||||
e.state = zIdle
|
||||
@@ -256,6 +261,9 @@ func (e *Entity) tickAnimal(dt float64, w WorldView) {
|
||||
}
|
||||
e.Vel[0] = e.wanderDir[0] * 1.2
|
||||
e.Vel[2] = e.wanderDir[1] * 1.2
|
||||
if e.Vel[0] != 0 || e.Vel[2] != 0 {
|
||||
e.Yaw = faceVelYaw(e.Vel[0], e.Vel[2])
|
||||
}
|
||||
_ = w
|
||||
}
|
||||
|
||||
@@ -274,4 +282,12 @@ func (e *Entity) tickVillager(dt float64, w WorldView) {
|
||||
}
|
||||
e.Vel[0] = e.wanderDir[0]
|
||||
e.Vel[2] = e.wanderDir[1]
|
||||
if e.Vel[0] != 0 || e.Vel[2] != 0 {
|
||||
e.Yaw = faceVelYaw(e.Vel[0], e.Vel[2])
|
||||
}
|
||||
}
|
||||
|
||||
// faceVelYaw 盒模型 −Z 指向速度(渲染.md §5.3:atan2(vx,vz)+π)。
|
||||
func faceVelYaw(vx, vz float64) float64 {
|
||||
return math.Atan2(vx, vz) + math.Pi
|
||||
}
|
||||
|
||||
@@ -205,3 +205,11 @@ func TestEntityFreeze(t *testing.T) {
|
||||
t.Fatalf("未加载区块实体应冻结,实际 x=%v", v.Pos[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFaceVelYaw(t *testing.T) {
|
||||
// 速度 +Z → yaw=π,模型 −Z 指向世界 +Z(渲染.md §5.3)
|
||||
y := faceVelYaw(0, 1)
|
||||
if y < 3.14 || y > 3.15 {
|
||||
t.Fatalf("朝 +Z 时期望 π,实际 %v", y)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user