Files
ngzz-mc/internal/entity/breeding.go

132 lines
3.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 {
BreedCooldown int // 繁殖冷却剩余 tick
GrowthTicks int // 幼崽成长剩余(0 = 成年)
ParentA ID // 近亲繁殖记录(后期)
ParentB ID
}
// 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
}
ad, ok1 := a.Data.(*Animal)
bd, ok2 := b.Data.(*Animal)
if !ok1 {
ad = &Animal{}
a.Data = ad
}
if !ok2 {
bd = &Animal{}
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{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.GrowthTicks > 0 {
d.GrowthTicks--
if d.GrowthTicks == 0 {
e.Health = 20 // 成年
}
}
}
// 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]
}