feat(game): 玩法接线——实体AI/夜间刷僵尸/受伤重生、掉落物拾取、熔炉烧炼产出、服务器与客户端 Mod 加载、客户端单机存档
This commit is contained in:
@@ -29,6 +29,7 @@ import (
|
||||
"mc/internal/physics"
|
||||
"mc/internal/recipe"
|
||||
"mc/internal/render"
|
||||
"mc/internal/save"
|
||||
"mc/internal/ui"
|
||||
"mc/internal/world"
|
||||
"mc/internal/worldgen"
|
||||
@@ -56,6 +57,7 @@ type clientApp struct {
|
||||
pendingMesh []*worldMeshJob // tick 构建 → 渲染线程上传
|
||||
|
||||
winW, winH int
|
||||
tickCnt uint64
|
||||
lastTime time.Time
|
||||
}
|
||||
|
||||
@@ -110,6 +112,15 @@ func (a *clientApp) Tick(dt float64) {
|
||||
a.in.apply(a.sess)
|
||||
a.sess.Tick(dt)
|
||||
|
||||
// 定时存档:30s(存档与持久化.md §2)
|
||||
a.tickCnt++
|
||||
if a.tickCnt%(20*30) == 0 {
|
||||
if err := save.SaveWorld(a.sess.World, "worlds", "NewWorld", 123456789, int64(a.tickCnt)); err != nil {
|
||||
// 日志由调用方输出(避免每次 tick 刷屏)
|
||||
_ = err
|
||||
}
|
||||
}
|
||||
|
||||
// 脏区块网格重建(CPU 侧,预算内)
|
||||
start := time.Now()
|
||||
for _, c := range a.sess.World.ActiveChunks() {
|
||||
@@ -253,6 +264,15 @@ func main() {
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
// 单机存档(存档与持久化.md §3:diff-only 载入,无存档则新建)
|
||||
if _, err := os.Stat("worlds/NewWorld/level.dat"); err == nil {
|
||||
if _, err := save.LoadWorld(w, "worlds", "NewWorld"); err != nil {
|
||||
log.Warnf("存档载入失败: %v", err)
|
||||
} else {
|
||||
log.Infof("已载入世界 NewWorld")
|
||||
}
|
||||
}
|
||||
|
||||
// 4) 游戏会话(玩家先高空下落,区块加载后落地)
|
||||
p := physics.NewPlayer(8.5, 100, 8.5)
|
||||
cam := camera.New(p.Eye(), 0, 0, cfg.Client.FOV)
|
||||
|
||||
@@ -11,10 +11,12 @@ import (
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"mc/internal/assets"
|
||||
"mc/internal/block"
|
||||
"mc/internal/config"
|
||||
"mc/internal/engine"
|
||||
"mc/internal/logx"
|
||||
"mc/internal/mods"
|
||||
"mc/internal/netproto"
|
||||
"mc/internal/netserver"
|
||||
"mc/internal/save"
|
||||
@@ -22,13 +24,15 @@ import (
|
||||
"mc/internal/worldgen"
|
||||
)
|
||||
|
||||
// serverApp 服务器应用:世界 + 网络 + 定时存档。
|
||||
// serverApp 服务器应用:世界 + 网络 + 定时存档 + Mod。
|
||||
type serverApp struct {
|
||||
cfg *config.ServerConfig
|
||||
log *logx.Logger
|
||||
reg *block.Registry
|
||||
world *world.World
|
||||
net *netserver.Server
|
||||
modReg *mods.Registry
|
||||
luaMods []*mods.LuaMod
|
||||
seed int64
|
||||
tickCnt uint64
|
||||
}
|
||||
@@ -66,6 +70,12 @@ func (s *serverApp) Tick(dt float64) {
|
||||
s.tickCnt++
|
||||
// 区块调度(以出生点为中心;后续按玩家位置)
|
||||
s.world.Update(8, 64, 8, 8*time.Millisecond)
|
||||
// Mod 事件:每秒派发 on_tick(Mod开发指南.md §4.3)
|
||||
if s.tickCnt%20 == 0 {
|
||||
for _, m := range s.luaMods {
|
||||
_ = m.DispatchEvent("tick")
|
||||
}
|
||||
}
|
||||
// 定时存档:30s(存档与持久化.md §2)
|
||||
if s.tickCnt%(20*30) == 0 {
|
||||
if err := save.SaveWorld(s.world, "worlds", s.cfg.World.Name, s.seed, int64(s.tickCnt)); err != nil {
|
||||
@@ -119,6 +129,30 @@ func main() {
|
||||
|
||||
app := &serverApp{cfg: cfg, log: log, reg: reg, world: w, seed: seed}
|
||||
|
||||
// Mod 加载(设计.md §3.3:扫描 mods/,type=mod 且有入口脚本的执行;热更新.md 由注册表支持)
|
||||
am := assets.New()
|
||||
if err := am.Load("assets", "mods", "resourcepacks"); err != nil {
|
||||
log.Warnf("内容包扫描失败: %v", err)
|
||||
}
|
||||
app.modReg = mods.NewRegistry()
|
||||
for _, p := range am.Packs() {
|
||||
if p.Type != assets.PackMod || p.Manifest == nil || p.Manifest.Entry == "" {
|
||||
continue
|
||||
}
|
||||
script, err := os.ReadFile(p.Root + "/" + p.Manifest.Entry)
|
||||
if err != nil {
|
||||
log.Warnf("Mod %q 入口脚本读取失败: %v", p.ID, err)
|
||||
continue
|
||||
}
|
||||
m := mods.NewLuaMod(p.ID, app.modReg)
|
||||
if err := m.Load(string(script)); err != nil {
|
||||
log.Warnf("Mod %q 加载失败: %v", p.ID, err)
|
||||
continue
|
||||
}
|
||||
app.luaMods = append(app.luaMods, m)
|
||||
log.Infof("已加载 Mod %q(注册 %d 条)", p.ID, len(app.modReg.Entries()))
|
||||
}
|
||||
|
||||
// 网络监听(多人同步.md §7:心跳由连接层自动处理)
|
||||
app.net = netserver.New("0.0.0.0:"+itoa(cfg.Server.Port), log, app)
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
|
||||
Reference in New Issue
Block a user