2026-08-16 00:01:20 +08:00
|
|
|
|
// 动物繁殖与村民交易:动物体系.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 {
|
2026-09-19 14:21:08 +08:00
|
|
|
|
Species int // 0 牛 / 1 猪 / 2 羊 / 3 鸡(声音/外观/掉落/繁殖共用)
|
2026-08-16 00:01:20 +08:00
|
|
|
|
BreedCooldown int // 繁殖冷却剩余 tick
|
|
|
|
|
|
GrowthTicks int // 幼崽成长剩余(0 = 成年)
|
2026-09-19 14:21:08 +08:00
|
|
|
|
LoveTicks int // 喂食后进入繁殖模式剩余 tick
|
|
|
|
|
|
Sheared bool
|
|
|
|
|
|
ParentA ID // 近亲繁殖记录(后期)
|
2026-08-16 00:01:20 +08:00
|
|
|
|
ParentB ID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 14:21:08 +08:00
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-16 00:01:20 +08:00
|
|
|
|
// 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
|
|
|
|
|
|
}
|
2026-09-19 14:21:08 +08:00
|
|
|
|
sa, sb := AnimalSpecies(a), AnimalSpecies(b)
|
|
|
|
|
|
if sa != sb {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2026-08-16 00:01:20 +08:00
|
|
|
|
ad, ok1 := a.Data.(*Animal)
|
|
|
|
|
|
bd, ok2 := b.Data.(*Animal)
|
|
|
|
|
|
if !ok1 {
|
2026-09-19 14:21:08 +08:00
|
|
|
|
ad = &Animal{Species: sa}
|
2026-08-16 00:01:20 +08:00
|
|
|
|
a.Data = ad
|
|
|
|
|
|
}
|
|
|
|
|
|
if !ok2 {
|
2026-09-19 14:21:08 +08:00
|
|
|
|
bd = &Animal{Species: sb}
|
2026-08-16 00:01:20 +08:00
|
|
|
|
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])
|
2026-09-19 14:21:08 +08:00
|
|
|
|
baby.Data = &Animal{Species: sa, GrowthTicks: babyGrowTime, ParentA: a.ID, ParentB: b.ID}
|
2026-08-16 00:01:20 +08:00
|
|
|
|
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--
|
|
|
|
|
|
}
|
2026-09-19 14:21:08 +08:00
|
|
|
|
if d.LoveTicks > 0 {
|
|
|
|
|
|
d.LoveTicks--
|
|
|
|
|
|
}
|
2026-08-16 00:01:20 +08:00
|
|
|
|
if d.GrowthTicks > 0 {
|
|
|
|
|
|
d.GrowthTicks--
|
|
|
|
|
|
if d.GrowthTicks == 0 {
|
|
|
|
|
|
e.Health = 20 // 成年
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 14:21:08 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-16 00:01:20 +08:00
|
|
|
|
// 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]
|
|
|
|
|
|
}
|