diff --git a/.gitignore b/.gitignore index 2739162..3113b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ teeworlds_client teeworlds +go-teeworlds-protocol diff --git a/examples/client_verbose/client_verbose.go b/examples/client_verbose/client_verbose.go index 1376319..893b0a9 100644 --- a/examples/client_verbose/client_verbose.go +++ b/examples/client_verbose/client_verbose.go @@ -28,14 +28,20 @@ func main() { // read incoming traffic // you can also alter packet here before it will be processed by the internal state machine - client.OnPacket(func(packet *protocol7.Packet) { + // + // return false to drop the packet + client.OnPacket(func(packet *protocol7.Packet) bool { fmt.Printf("got packet with %d messages\n", len(packet.Messages)) + return true }) // inspect outgoing traffic // you can also alter packet here before it will be sent to the server - client.OnSend(func(packet *protocol7.Packet) { + // + // return false to drop the packet + client.OnSend(func(packet *protocol7.Packet) bool { fmt.Printf("sending packet with %d messages\n", len(packet.Messages)) + return true }) client.OnChat(func(msg *messages7.SvChat, defaultAction teeworlds7.DefaultAction) { diff --git a/messages7/cl_say.go b/messages7/cl_say.go new file mode 100644 index 0000000..42f7e2b --- /dev/null +++ b/messages7/cl_say.go @@ -0,0 +1,57 @@ +package messages7 + +import ( + "slices" + + "github.com/teeworlds-go/go-teeworlds-protocol/chunk7" + "github.com/teeworlds-go/go-teeworlds-protocol/network7" + "github.com/teeworlds-go/go-teeworlds-protocol/packer" +) + +type ClSay struct { + ChunkHeader *chunk7.ChunkHeader + + Mode network7.ChatMode + TargetId int + Message string +} + +func (msg *ClSay) MsgId() int { + return network7.MsgGameClSay +} + +func (msg *ClSay) MsgType() network7.MsgType { + return network7.TypeNet +} + +func (msg *ClSay) System() bool { + return false +} + +func (msg *ClSay) Vital() bool { + return true +} + +func (msg *ClSay) Pack() []byte { + return slices.Concat( + packer.PackInt(int(msg.Mode)), + packer.PackInt(msg.TargetId), + packer.PackStr(msg.Message), + ) +} + +func (msg *ClSay) Unpack(u *packer.Unpacker) error { + msg.Mode = network7.ChatMode(u.GetInt()) + msg.TargetId = u.GetInt() + msg.Message = u.GetString() + + return nil +} + +func (msg *ClSay) Header() *chunk7.ChunkHeader { + return msg.ChunkHeader +} + +func (msg *ClSay) SetHeader(header *chunk7.ChunkHeader) { + msg.ChunkHeader = header +} diff --git a/protocol7/packet.go b/protocol7/packet.go index f8459c7..d83ce84 100644 --- a/protocol7/packet.go +++ b/protocol7/packet.go @@ -285,6 +285,7 @@ func (packet *Packet) Unpack(data []byte) (err error) { return nil } +// TODO: return error if maxium packet size is exceeded func (packet *Packet) Pack(connection *Session) []byte { payload := []byte{} control := false @@ -297,6 +298,7 @@ func (packet *Packet) Pack(connection *Session) []byte { } packet.Header.NumChunks = len(packet.Messages) + packet.Header.Ack = connection.Ack if control { packet.Header.Flags = PacketFlags{ diff --git a/protocol7/packet_test.go b/protocol7/packet_test.go index aa92e0a..15ae55e 100644 --- a/protocol7/packet_test.go +++ b/protocol7/packet_test.go @@ -140,6 +140,7 @@ func TestRepackUnknownMessages(t *testing.T) { err := packet.Unpack(dump) require.NoError(t, err) + conn.Ack = packet.Header.Ack repack := packet.Pack(&conn) require.Equal(t, dump, repack) } diff --git a/protocol7/session.go b/protocol7/session.go index 38a7b01..a9d48f5 100644 --- a/protocol7/session.go +++ b/protocol7/session.go @@ -17,6 +17,7 @@ type Session struct { PeerAck int } +// TODO: should this be removed? All of this could be set in Packet.Pack() func (connection *Session) BuildResponse() *Packet { return &Packet{ Header: PacketHeader{ @@ -26,7 +27,7 @@ func (connection *Session) BuildResponse() *Packet { Resend: false, Control: false, }, - Ack: connection.Ack, + Ack: 0, // will be set in Packet.Pack() NumChunks: 0, // will be set in Packet.Pack() Token: connection.ServerToken, }, diff --git a/teeworlds7/callbacks.go b/teeworlds7/callbacks.go index cb971ae..6cc45cc 100644 --- a/teeworlds7/callbacks.go +++ b/teeworlds7/callbacks.go @@ -15,8 +15,8 @@ type DefaultAction func() // // key is the network7.MessageId // UserMsgCallbacks map[int]UserMsgCallback type UserMsgCallbacks struct { - PacketIn func(*protocol7.Packet) - PacketOut func(*protocol7.Packet) + PacketIn func(*protocol7.Packet) bool + PacketOut func(*protocol7.Packet) bool MsgUnknown func(*messages7.Unknown, DefaultAction) InternalError func(error) diff --git a/teeworlds7/packet.go b/teeworlds7/packet.go index 9c3e76d..fbaae84 100644 --- a/teeworlds7/packet.go +++ b/teeworlds7/packet.go @@ -39,7 +39,9 @@ func (client *Client) processMessage(msg messages7.NetMessage, response *protoco func (client *Client) processPacket(packet *protocol7.Packet) error { if client.Callbacks.PacketIn != nil { - client.Callbacks.PacketIn(packet) + if client.Callbacks.PacketIn(packet) == false { + return nil + } } response := client.Session.BuildResponse() diff --git a/teeworlds7/user_actions.go b/teeworlds7/user_actions.go index 46effc4..cdc4c24 100644 --- a/teeworlds7/user_actions.go +++ b/teeworlds7/user_actions.go @@ -1,6 +1,8 @@ package teeworlds7 import ( + "fmt" + "github.com/teeworlds-go/go-teeworlds-protocol/messages7" "github.com/teeworlds-go/go-teeworlds-protocol/network7" "github.com/teeworlds-go/go-teeworlds-protocol/protocol7" @@ -10,14 +12,77 @@ import ( // low level access for experts // ---------------------------- -func (client *Client) SendPacket(packet *protocol7.Packet) { +// flish()boo { +// +// ifhf lenesusue +// packte:=}{} +// SendPkacte8) +// +// } +// +// if now > lastsnend d+9191 { +// if !flush() { +// keepalive +// } +// }A - // TODO: append queued messages to packet messages here +func (client *Client) SendPacket(packet *protocol7.Packet) error { + if packet.Header.Flags.Resend == false && len(packet.Messages) == 0 && len(client.QueuedMessages) == 0 { + return fmt.Errorf("Failed to send packet: payload is empty.") + } + + gotNet := false + numCtrlMsgs := 0 + + for _, msg := range packet.Messages { + if msg.MsgType() == network7.TypeControl { + numCtrlMsgs++ + } else if msg.MsgType() == network7.TypeNet { + gotNet = true + } else { + return fmt.Errorf("Failed to send packet: only game, system and control messages are supported.") + } + } + + if gotNet && numCtrlMsgs > 0 { + return fmt.Errorf("Failed to send packet: can not mix control messages with others.") + } + + if numCtrlMsgs > 1 { + // TODO: should this automatically split it up into multiple packets? + return fmt.Errorf("Failed to send packet: can only send one control message at a time.") + } + + // If the user queued a game message and then sends a control message + // before the queue got processed we send two packets in the correct order + // For example in this case: + // + // client.SendChat("bye") // queue game chunk + // client.Disconnect() // SendPacket(ctrl) -> first flush out the game chunk packet then send the control packet + // + if numCtrlMsgs > 0 && len(client.QueuedMessages) > 0 { + // TODO: we could apply compression here + // flushPacket.Header.Flags.Compression = true + + flushPacket := client.Session.BuildResponse() + client.SendPacket(flushPacket) + } + + for _, queuedChunk := range client.QueuedMessages { + // TODO: check if we exceed packet size and only put in as many chunks as we can + // also use a more performant queue implementation then if we unshift it partially + // popping of one element from the queue should not reallocate the entire queued messages slice + packet.Messages = append(packet.Messages, queuedChunk) + } + client.QueuedMessages = nil if client.Callbacks.PacketOut != nil { - client.Callbacks.PacketOut(packet) + if client.Callbacks.PacketOut(packet) == false { + return nil + } } client.Conn.Write(packet.Pack(&client.Session)) + return nil } // WARNING! this is does not send chat messages @@ -25,7 +90,18 @@ func (client *Client) SendPacket(packet *protocol7.Packet) { // // if you want to send a chat message use SendChat() func (client *Client) SendMessage(msg messages7.NetMessage) { - // TODO: set vital header and stuff + if msg.MsgType() == network7.TypeControl { + packet := client.Session.BuildResponse() + packet.Header.Flags.Control = true + packet.Messages = append(packet.Messages, msg) + client.SendPacket(packet) + return + } + if msg.MsgType() == network7.TypeConnless { + // TODO: connless + panic("connless messages are not supported yet") + } + client.QueuedMessages = append(client.QueuedMessages, msg) } @@ -37,7 +113,7 @@ func (client *Client) SendMessage(msg messages7.NetMessage) { // see also SendChatTeam() func (client *Client) SendChat(msg string) { client.SendMessage( - &messages7.SvChat{ + &messages7.ClSay{ Mode: network7.ChatAll, Message: msg, TargetId: -1, @@ -49,7 +125,7 @@ func (client *Client) SendChat(msg string) { // see also SendChat() func (client *Client) SendChatTeam(msg string) { client.SendMessage( - &messages7.SvChat{ + &messages7.ClSay{ Mode: network7.ChatTeam, Message: msg, TargetId: -1, @@ -61,7 +137,7 @@ func (client *Client) SendChatTeam(msg string) { // see also SendChatTeam() func (client *Client) SendWhisper(targetId int, msg string) { client.SendMessage( - &messages7.SvChat{ + &messages7.ClSay{ Mode: network7.ChatWhisper, Message: msg, TargetId: targetId, diff --git a/teeworlds7/user_hooks.go b/teeworlds7/user_hooks.go index 8176eb6..0437fed 100644 --- a/teeworlds7/user_hooks.go +++ b/teeworlds7/user_hooks.go @@ -16,13 +16,17 @@ func (client *Client) OnError(callback func(err error)) { // inspect outgoing traffic // and alter it before it gets sent to the server -func (client *Client) OnSend(callback func(packet *protocol7.Packet)) { +// +// return false to drop the packet +func (client *Client) OnSend(callback func(packet *protocol7.Packet) bool) { client.Callbacks.PacketOut = callback } // read incoming traffic // and alter it before it hits the internal state machine -func (client *Client) OnPacket(callback func(packet *protocol7.Packet)) { +// +// return false to drop the packet +func (client *Client) OnPacket(callback func(packet *protocol7.Packet) bool) { client.Callbacks.PacketIn = callback }