100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
// 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
|
||
}
|