// Package mpsync 多人同步:登录发送区块数据、方块变更广播(多人同步.md §2、§4–§5)。 // // 服务器权威:方块破坏/放置由服务器执行后广播(多人同步.md §5)。 package mpsync import ( "sync" "mc/internal/block" "mc/internal/netproto" "mc/internal/netserver" "mc/internal/save" "mc/internal/world" ) // Hub 同步中心:玩家会话与广播。 type Hub struct { mu sync.Mutex clients map[*netserver.Conn]struct{} // 已登录连接 world *world.World viewDist int } // NewHub 创建同步中心。 func NewHub(w *world.World, viewDist int) *Hub { return &Hub{clients: make(map[*netserver.Conn]struct{}), world: w, viewDist: viewDist} } // AddPlayer 玩家登录:加入广播集并发送周围区块(多人同步.md §4 区块流)。 func (h *Hub) AddPlayer(c *netserver.Conn, spawnX, spawnZ float64) { h.mu.Lock() h.clients[c] = struct{}{} h.mu.Unlock() c.Send(netproto.Frame{MsgID: netproto.MsgLoginResponse, Payload: []byte{0}}) // 发送视距内已加载区块(按距离排序,多人同步.md §4) chunks := h.world.ActiveChunks() for _, ch := range chunks { if absi32(ch.CX-int32(spawnX)/16) > int32(h.viewDist) || absi32(ch.CZ-int32(spawnZ)/16) > int32(h.viewDist) { continue } data := save.EncodeChunk(ch) c.Send(netproto.Frame{MsgID: netproto.MsgChunkData, Payload: data}) } } // RemovePlayer 玩家断开:移出广播集。 func (h *Hub) RemovePlayer(c *netserver.Conn) { h.mu.Lock() delete(h.clients, c) h.mu.Unlock() } // BroadcastBlockChange 广播方块变更(多人同步.md §5:只发视距内,MVP 全量广播)。 func (h *Hub) BroadcastBlockChange(x, y, z int32, id uint16, meta uint8) { msg := netproto.BlockChange{X: x, Y: y, Z: z, BlockID: id, Meta: meta} payload := msg.Encode() h.mu.Lock() clients := make([]*netserver.Conn, 0, len(h.clients)) for c := range h.clients { clients = append(clients, c) } h.mu.Unlock() for _, c := range clients { c.Send(netproto.Frame{MsgID: netproto.MsgBlockChange, Payload: payload}) } } // SetBlockWorld 服务器权威写方块:修改世界并广播(多人同步.md §2 权威链路)。 func (h *Hub) SetBlockWorld(x, y, z int32, s block.State) { h.world.SetBlock(x, y, z, s) h.BroadcastBlockChange(x, y, z, s.ID(), s.Meta()) } // BroadcastEntityMove 广播玩家移动给其他客户端(多人同步.md §3:20Hz 增量)。 // from 为发送者(不回传给自己)。 func (h *Hub) BroadcastEntityMove(from *netserver.Conn, entityID uint32, m netproto.PlayerMove) { msg := netproto.EntityMove{EntityID: entityID, X: m.X, Y: m.Y, Z: m.Z, Yaw: m.Yaw} payload := msg.Encode() h.mu.Lock() clients := make([]*netserver.Conn, 0, len(h.clients)) for c := range h.clients { if c != from { clients = append(clients, c) } } h.mu.Unlock() for _, c := range clients { c.Send(netproto.Frame{MsgID: netproto.MsgEntityMove, Payload: payload}) } } // absi32 绝对值。 func absi32(v int32) int32 { if v < 0 { return -v } return v }