修复了一些bug

This commit is contained in:
NianGao Dev
2026-09-19 14:21:08 +08:00
parent db81873409
commit 6c23eb4c62
73 changed files with 7354 additions and 2205 deletions

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -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)
}
}