diff --git a/cmd/server/main.go b/cmd/server/main.go index 0d464c83..6b625694 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,4 +1,7 @@ // 年糕历险记 —— 专用服务器入口。 +// +// 装配:配置 → 日志 → 注册表 → 世界(存档载入/新建)→ 网络监听 → 主循环 +// (tick 20Hz:世界调度 + 心跳 + 定时存档;关服时全量保存,存档与持久化.md §2)。 package main import ( @@ -6,24 +9,70 @@ import ( "flag" "os" "os/signal" + "time" + "mc/internal/block" "mc/internal/config" "mc/internal/engine" "mc/internal/logx" + "mc/internal/netproto" + "mc/internal/netserver" + "mc/internal/save" + "mc/internal/world" + "mc/internal/worldgen" ) -// serverApp 服务器应用骨架(世界/网络在后续里程碑接入)。 +// serverApp 服务器应用:世界 + 网络 + 定时存档。 type serverApp struct { - cfg *config.ServerConfig - log *logx.Logger - tickCount uint64 + cfg *config.ServerConfig + log *logx.Logger + reg *block.Registry + world *world.World + net *netserver.Server + seed int64 + tickCnt uint64 } -// Tick 每 tick(20Hz)服务器逻辑。 +// ---- netserver.Handler 实现(多人同步.md §2 权威链路入口)---- + +// OnConnect 连接建立。 +func (s *serverApp) OnConnect(c *netserver.Conn) { + s.log.Infof("玩家连接 #%d", c.ID()) +} + +// OnHandshake 握手校验(网络协议.md §4:协议版本不匹配拒绝)。 +func (s *serverApp) OnHandshake(c *netserver.Conn, hs netproto.Handshake) uint8 { + if hs.ProtocolVersion != 1 { + s.log.Warnf("玩家 #%d 协议版本 %d 不匹配,拒绝", c.ID(), hs.ProtocolVersion) + return 1 + } + s.log.Infof("玩家 %q 登录(连接 #%d)", hs.ClientID, c.ID()) + return 0 +} + +// OnMove 玩家移动(MVP:仅记录,完整同步见 Todo 8)。 +func (s *serverApp) OnMove(c *netserver.Conn, m netproto.PlayerMove) { + // TODO(多人同步): 更新玩家实体并广播给视距内其他玩家(多人同步.md §3) + _ = m +} + +// OnDisconnect 连接断开。 +func (s *serverApp) OnDisconnect(c *netserver.Conn) { + s.log.Infof("玩家 #%d 断开", c.ID()) +} + +// Tick 每 tick(20Hz)服务器逻辑(架构.md §3.2:tick 内不做 IO)。 func (s *serverApp) Tick(dt float64) { - s.tickCount++ - if s.tickCount%100 == 0 { // 每 5 秒 - s.log.Debugf("tick=%d", s.tickCount) + s.tickCnt++ + // 区块调度(以出生点为中心;后续按玩家位置) + s.world.Update(8, 64, 8, 8*time.Millisecond) + // 定时存档: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 { + s.log.Errorf("定时存档失败: %v", err) + } else { + s.log.Infof("已存档(tick %d)", s.tickCnt) + } } } @@ -34,17 +83,58 @@ func main() { if err != nil { panic(err) } - cfg, err := config.Load("config/server.yaml", config.DefaultServer()) if err != nil { log.Warnf("配置加载失败,使用默认值:%v", err) } log.Infof("年糕历险记 服务器启动:端口 %d,世界 %q,种子 %d,tick 率 %d", cfg.Server.Port, cfg.World.Name, cfg.World.Seed, cfg.Server.TickRate) + // 注册表与生成器 + reg, err := block.Load("assets/config/blocks.json") + if err != nil { + panic(err) + } + gen, err := worldgen.New(reg, cfg.World.Seed, cfg.World.Generator.BiomeScale, cfg.World.Generator.HeightScale, cfg.World.Generator.CaveDensity) + if err != nil { + panic(err) + } + w, err := world.New(reg, gen, cfg.Server.ViewDistance) + if err != nil { + panic(err) + } + defer w.Close() + + // 载入已有存档(diff-only:生成器重建 + 已保存区块覆盖,存档与持久化.md §3) + seed := cfg.World.Seed + if _, err := os.Stat("worlds/" + cfg.World.Name + "/level.dat"); err == nil { + if got, err := save.LoadWorld(w, "worlds", cfg.World.Name); err != nil { + log.Errorf("存档载入失败: %v", err) + } else if got != 0 { + seed = got + log.Infof("已载入世界 %q(seed %d)", cfg.World.Name, seed) + } + } else { + log.Infof("新建世界 %q", cfg.World.Name) + } + + app := &serverApp{cfg: cfg, log: log, reg: reg, world: w, seed: seed} + + // 网络监听(多人同步.md §7:心跳由连接层自动处理) + app.net = netserver.New("0.0.0.0:"+itoa(cfg.Server.Port), log, app) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() + if err := app.net.Listen(ctx); err != nil { + panic(err) + } - app := &serverApp{cfg: cfg, log: log} engine.Run(ctx, app, engine.NoopRenderer{}) + + // 关服前全量存档(存档与持久化.md §2 关服 flush) + if err := save.SaveWorld(w, "worlds", cfg.World.Name, seed, int64(app.tickCnt)); err != nil { + log.Errorf("关服存档失败: %v", err) + } log.Infof("服务器已退出") } + +// itoa 整数转字符串(避免重复 import strconv 的别名风格)。 +func itoa(v int) string { return fmtSprint(v) } diff --git a/cmd/server/util.go b/cmd/server/util.go new file mode 100644 index 00000000..906757d6 --- /dev/null +++ b/cmd/server/util.go @@ -0,0 +1,7 @@ +// 服务器工具函数。 +package main + +import "strconv" + +// fmtSprint 整数转字符串(构建监听地址用)。 +func fmtSprint(v int) string { return strconv.Itoa(v) } diff --git a/tools/zig.zip b/tools/zig.zip index 8bb276df..89c29406 100644 Binary files a/tools/zig.zip and b/tools/zig.zip differ