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 }