Files
ngzz-mc/cmd/server/main.go

141 lines
4.4 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.
// 年糕历险记 —— 专用服务器入口。
//
// 装配:配置 → 日志 → 注册表 → 世界(存档载入/新建)→ 网络监听 → 主循环
// tick 20Hz世界调度 + 心跳 + 定时存档;关服时全量保存,存档与持久化.md §2
package main
import (
"context"
"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 服务器应用:世界 + 网络 + 定时存档。
type serverApp struct {
cfg *config.ServerConfig
log *logx.Logger
reg *block.Registry
world *world.World
net *netserver.Server
seed int64
tickCnt uint64
}
// ---- 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 每 tick20Hz服务器逻辑架构.md §3.2tick 内不做 IO
func (s *serverApp) Tick(dt float64) {
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)
}
}
}
func main() {
flag.Parse()
log, err := logx.New("logs/server.log", logx.LevelInfo)
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种子 %dtick 率 %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("已载入世界 %qseed %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)
}
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) }