Files
a301_game_server/internal/network/packet.go
2026-02-26 17:52:48 +09:00

122 lines
4.0 KiB
Go

package network
import (
"encoding/binary"
"errors"
"fmt"
"google.golang.org/protobuf/proto"
pb "a301_game_server/proto/gen/pb"
)
// Message type IDs — the wire protocol uses 2-byte type prefixes.
const (
// Auth
MsgLoginRequest uint16 = 0x0001
MsgLoginResponse uint16 = 0x0002
MsgEnterWorldRequest uint16 = 0x0003
MsgEnterWorldResponse uint16 = 0x0004
// Movement
MsgMoveRequest uint16 = 0x0010
MsgStateUpdate uint16 = 0x0011
MsgSpawnEntity uint16 = 0x0012
MsgDespawnEntity uint16 = 0x0013
// Zone Transfer
MsgZoneTransferNotify uint16 = 0x0014
// System
MsgPing uint16 = 0x0020
MsgPong uint16 = 0x0021
// Combat
MsgUseSkillRequest uint16 = 0x0040
MsgUseSkillResponse uint16 = 0x0041
MsgCombatEvent uint16 = 0x0042
MsgBuffApplied uint16 = 0x0043
MsgBuffRemoved uint16 = 0x0044
MsgRespawnRequest uint16 = 0x0045
MsgRespawnResponse uint16 = 0x0046
// Admin / Debug
MsgAOIToggleRequest uint16 = 0x0030
MsgAOIToggleResponse uint16 = 0x0031
MsgMetricsRequest uint16 = 0x0032
MsgServerMetrics uint16 = 0x0033
)
var (
ErrUnknownMessageType = errors.New("unknown message type")
ErrMessageTooShort = errors.New("message too short")
)
// Packet is a decoded network message.
type Packet struct {
Type uint16
Payload proto.Message
}
// messageFactory maps type IDs to protobuf message constructors.
var messageFactory = map[uint16]func() proto.Message{
MsgLoginRequest: func() proto.Message { return &pb.LoginRequest{} },
MsgLoginResponse: func() proto.Message { return &pb.LoginResponse{} },
MsgEnterWorldRequest: func() proto.Message { return &pb.EnterWorldRequest{} },
MsgEnterWorldResponse: func() proto.Message { return &pb.EnterWorldResponse{} },
MsgMoveRequest: func() proto.Message { return &pb.MoveRequest{} },
MsgStateUpdate: func() proto.Message { return &pb.StateUpdate{} },
MsgSpawnEntity: func() proto.Message { return &pb.SpawnEntity{} },
MsgDespawnEntity: func() proto.Message { return &pb.DespawnEntity{} },
MsgPing: func() proto.Message { return &pb.Ping{} },
MsgPong: func() proto.Message { return &pb.Pong{} },
MsgZoneTransferNotify: func() proto.Message { return &pb.ZoneTransferNotify{} },
MsgUseSkillRequest: func() proto.Message { return &pb.UseSkillRequest{} },
MsgUseSkillResponse: func() proto.Message { return &pb.UseSkillResponse{} },
MsgCombatEvent: func() proto.Message { return &pb.CombatEvent{} },
MsgBuffApplied: func() proto.Message { return &pb.BuffApplied{} },
MsgBuffRemoved: func() proto.Message { return &pb.BuffRemoved{} },
MsgRespawnRequest: func() proto.Message { return &pb.RespawnRequest{} },
MsgRespawnResponse: func() proto.Message { return &pb.RespawnResponse{} },
MsgAOIToggleRequest: func() proto.Message { return &pb.AOIToggleRequest{} },
MsgAOIToggleResponse: func() proto.Message { return &pb.AOIToggleResponse{} },
MsgMetricsRequest: func() proto.Message { return &pb.MetricsRequest{} },
MsgServerMetrics: func() proto.Message { return &pb.ServerMetrics{} },
}
// Encode serializes a packet into a wire-format byte slice: [2-byte type][protobuf payload].
func Encode(msgType uint16, msg proto.Message) ([]byte, error) {
payload, err := proto.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("marshal message 0x%04X: %w", msgType, err)
}
buf := make([]byte, 2+len(payload))
binary.BigEndian.PutUint16(buf[:2], msgType)
copy(buf[2:], payload)
return buf, nil
}
// Decode parses a wire-format byte slice into a Packet.
func Decode(data []byte) (*Packet, error) {
if len(data) < 2 {
return nil, ErrMessageTooShort
}
msgType := binary.BigEndian.Uint16(data[:2])
factory, ok := messageFactory[msgType]
if !ok {
return nil, fmt.Errorf("%w: 0x%04X", ErrUnknownMessageType, msgType)
}
msg := factory()
if len(data) > 2 {
if err := proto.Unmarshal(data[2:], msg); err != nil {
return nil, fmt.Errorf("unmarshal message 0x%04X: %w", msgType, err)
}
}
return &Packet{Type: msgType, Payload: msg}, nil
}