Files
ngzz-mc/internal/entity/breeding.go
2026-09-19 14:21:08 +08:00

225 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 动物繁殖与村民交易:动物体系.md §3、村民系统.md §7 的核心玩法逻辑。
package entity
import (
"encoding/json"
"fmt"
"os"
)
// 动物繁殖参数(动物体系.md §3
const (
breedCooldown = 60 * 20 // 繁殖冷却 60 秒tick
babyGrowTime = 20 * 60 * 20 // 幼崽成长 20 分钟tick
perChunkAnimal = 10 // 每区块同种动物上限
)
// Animal 动物附加数据(动物体系.md §2
type Animal struct {
Species int // 0 牛 / 1 猪 / 2 羊 / 3 鸡(声音/外观/掉落/繁殖共用)
BreedCooldown int // 繁殖冷却剩余 tick
GrowthTicks int // 幼崽成长剩余0 = 成年)
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{Species: sa}
a.Data = ad
}
if !ok2 {
bd = &Animal{Species: sb}
b.Data = bd
}
if ad.BreedCooldown > 0 || bd.BreedCooldown > 0 || ad.GrowthTicks > 0 || bd.GrowthTicks > 0 {
return nil
}
// 每区块数量控制(防无限繁殖卡服,动物体系.md §7 踩坑)
if m.countNear(a, perChunkAnimal) >= perChunkAnimal {
return nil
}
ad.BreedCooldown = breedCooldown
bd.BreedCooldown = breedCooldown
baby := m.Spawn(KindAnimal, a.Pos[0], a.Pos[1], a.Pos[2])
baby.Data = &Animal{Species: sa, GrowthTicks: babyGrowTime, ParentA: a.ID, ParentB: b.ID}
baby.Health = 10 // 幼崽血量
return baby
}
// TickAnimal 动物成长/冷却计时(调用方每 tick 对动物实体调用)。
func TickAnimal(e *Entity) {
if e.Kind != KindAnimal {
return
}
d, ok := e.Data.(*Animal)
if !ok {
return
}
if d.BreedCooldown > 0 {
d.BreedCooldown--
}
if d.LoveTicks > 0 {
d.LoveTicks--
}
if d.GrowthTicks > 0 {
d.GrowthTicks--
if d.GrowthTicks == 0 {
e.Health = 20 // 成年
}
}
}
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 剪羊毛:未剪过的羊掉 13 白羊毛。
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
for _, e := range m.List() {
if e.Kind == KindAnimal {
dx, dz := e.Pos[0]-a.Pos[0], e.Pos[2]-a.Pos[2]
if dx*dx+dz*dz < 16*16 {
n++
}
}
}
return n
}
// ---- 村民交易(村民系统.md §7数据驱动交易表----
// TradeOffer 一条交易(收购 → 出售)。
type TradeOffer struct {
Buy string `json:"buy"` // 收购物品
BuyN int `json:"buy_n"` // 收购数量
Sell string `json:"sell"` // 出售物品
SellN int `json:"sell_n"` // 出售数量
Uses int `json:"uses"` // 已交易次数(涨价基础)
}
// TradeTable 交易表(按职业)。
type TradeTable struct {
byProfession map[uint8][]TradeOffer
}
// LoadTrades 解析交易表 JSON。
func LoadTrades(path string) (*TradeTable, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("entity.LoadTrades: %w", err)
}
var doc struct {
Professions map[string][]TradeOffer `json:"professions"`
}
if err := json.Unmarshal(b, &doc); err != nil {
return nil, fmt.Errorf("entity.LoadTrades: %w", err)
}
t := &TradeTable{byProfession: make(map[uint8][]TradeOffer)}
names := map[string]uint8{"farmer": 0, "builder": 1, "blacksmith": 2, "trader": 3}
for name, offers := range doc.Professions {
if id, ok := names[name]; ok {
t.byProfession[id] = offers
}
}
return t, nil
}
// OffersFor 返回某职业的交易列表。
func (t *TradeTable) OffersFor(profession uint8) []TradeOffer {
return t.byProfession[profession]
}