lil refactor
This commit is contained in:
parent
0a88246a9a
commit
a6c11c71a8
50
README.md
50
README.md
|
@ -1,13 +1,55 @@
|
|||
# go-teeworlds-protocol
|
||||
|
||||
## Early and active development! Still undergoing major refactors!
|
||||
## WARNING! NOT READY TO BE USED YET!
|
||||
## Apis might change. Packages and repository might be renamed!
|
||||
|
||||
A client side network protocol implementation of the game teeworlds.
|
||||
|
||||
## run client
|
||||
## low level api for power users
|
||||
|
||||
The packages **chunk7, messages7, network7, packer, protocol7** Implement the low level 0.7 teeworlds protocol. Use them if you want to build something advanced such as a custom proxy.
|
||||
|
||||
## high level api for ease of use
|
||||
|
||||
The package **teeworlds7** implements a high level client library. Designed for ease of use.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/teeworlds7"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := teeworlds7.Client{Name: "nameless tee"}
|
||||
|
||||
// Register your callback for incoming chat messages
|
||||
// For a full list of all callbacks see: https://github.com/teeworlds-go/go-teeworlds-protocol/tree/master/teeworlds7/user_hooks.go
|
||||
client.OnChat(func(msg *messages7.SvChat, defaultAction teeworlds7.DefaultAction) {
|
||||
// the default action prints the chat message to the console
|
||||
// if this is not called and you don't print it your self the chat will not be visible
|
||||
defaultAction()
|
||||
|
||||
if msg.Message == "!ping" {
|
||||
// Send reply in chat using the SendChat() action
|
||||
// For a full list of all actions see: https://github.com/teeworlds-go/go-teeworlds-protocol/tree/master/teeworlds7/user_actions.go
|
||||
client.SendChat("pong")
|
||||
}
|
||||
})
|
||||
|
||||
client.Connect("127.0.0.1", 8303)
|
||||
}
|
||||
```
|
||||
go build
|
||||
./teeworlds 127.0.0.1 8303
|
||||
```
|
||||
|
||||
|
||||
Example usages:
|
||||
- [client_verbose](./examples/client_verbose/) a verbose client show casing the easy to use high level api
|
||||
|
||||
## tests
|
||||
|
||||
|
|
2
examples/client_verbose/.gitignore
vendored
Normal file
2
examples/client_verbose/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
client_verbose
|
||||
client_verbose.exe
|
7
examples/client_verbose/README.md
Normal file
7
examples/client_verbose/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# client_verbose
|
||||
|
||||
```
|
||||
go build client_verbose.go
|
||||
./client_verbose
|
||||
```
|
||||
|
69
examples/client_verbose/client_verbose.go
Normal file
69
examples/client_verbose/client_verbose.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
// TODO: split this up into multiple examples
|
||||
// the verbose mode should not have a render loop
|
||||
// the verbose mode should not implement disconnect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/teeworlds7"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := teeworlds7.Client{
|
||||
Name: "nameless tee",
|
||||
}
|
||||
|
||||
client.OnAccept(func(msg *messages7.CtrlAccept, defaultAction teeworlds7.DefaultAction) {
|
||||
// respond with the next message to establish a connection
|
||||
defaultAction()
|
||||
|
||||
fmt.Println("got accept message")
|
||||
})
|
||||
|
||||
// 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) {
|
||||
fmt.Printf("got packet with %d messages\n", len(packet.Messages))
|
||||
})
|
||||
|
||||
// inspect outgoing traffic
|
||||
// you can also alter packet here before it will be sent to the server
|
||||
client.OnSend(func(packet *protocol7.Packet) {
|
||||
fmt.Printf("sending packet with %d messages\n", len(packet.Messages))
|
||||
})
|
||||
|
||||
client.OnChat(func(msg *messages7.SvChat, defaultAction teeworlds7.DefaultAction) {
|
||||
// the default action prints the chat message to the console
|
||||
// if this is not called and you don't print it your self the chat will not be visible
|
||||
defaultAction()
|
||||
|
||||
// additional custom chat print
|
||||
fmt.Printf("%d %s\n", msg.ClientId, msg.Message)
|
||||
})
|
||||
|
||||
// this is matching the default behavior
|
||||
client.OnDisconnect(func(msg *messages7.CtrlClose, defaultAction teeworlds7.DefaultAction) {
|
||||
fmt.Printf("disconnected (%s)\n", msg.Reason)
|
||||
os.Exit(0)
|
||||
})
|
||||
|
||||
// if you do not implement OnError it will throw on error
|
||||
client.OnError(func(err error) {
|
||||
fmt.Print(err)
|
||||
})
|
||||
|
||||
go func() {
|
||||
client.Connect("127.0.0.1", 8303)
|
||||
}()
|
||||
|
||||
for {
|
||||
// render loop
|
||||
time.Sleep(100_000_000)
|
||||
}
|
||||
}
|
45
messages7/sv_broadcast.go
Normal file
45
messages7/sv_broadcast.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package messages7
|
||||
|
||||
import (
|
||||
"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 SvBroadcast struct {
|
||||
ChunkHeader *chunk7.ChunkHeader
|
||||
|
||||
Message string
|
||||
}
|
||||
|
||||
func (msg SvBroadcast) MsgId() int {
|
||||
return network7.MsgGameSvBroadcast
|
||||
}
|
||||
|
||||
func (msg SvBroadcast) MsgType() network7.MsgType {
|
||||
return network7.TypeNet
|
||||
}
|
||||
|
||||
func (msg SvBroadcast) System() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (msg SvBroadcast) Vital() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (msg SvBroadcast) Pack() []byte {
|
||||
return packer.PackStr(msg.Message)
|
||||
}
|
||||
|
||||
func (msg *SvBroadcast) Unpack(u *packer.Unpacker) {
|
||||
msg.Message = u.GetString()
|
||||
}
|
||||
|
||||
func (msg *SvBroadcast) Header() *chunk7.ChunkHeader {
|
||||
return msg.ChunkHeader
|
||||
}
|
||||
|
||||
func (msg *SvBroadcast) SetHeader(header *chunk7.ChunkHeader) {
|
||||
msg.ChunkHeader = header
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
type SvChat struct {
|
||||
ChunkHeader *chunk7.ChunkHeader
|
||||
|
||||
Mode int
|
||||
Mode network7.ChatMode
|
||||
ClientId int
|
||||
TargetId int
|
||||
Message string
|
||||
|
@ -35,7 +35,7 @@ func (msg SvChat) Vital() bool {
|
|||
|
||||
func (msg SvChat) Pack() []byte {
|
||||
return slices.Concat(
|
||||
packer.PackInt(msg.Mode),
|
||||
packer.PackInt(int(msg.Mode)),
|
||||
packer.PackInt(msg.ClientId),
|
||||
packer.PackInt(msg.TargetId),
|
||||
packer.PackStr(msg.Message),
|
||||
|
@ -43,7 +43,7 @@ func (msg SvChat) Pack() []byte {
|
|||
}
|
||||
|
||||
func (msg *SvChat) Unpack(u *packer.Unpacker) {
|
||||
msg.Mode = u.GetInt()
|
||||
msg.Mode = network7.ChatMode(u.GetInt())
|
||||
msg.ClientId = u.GetInt()
|
||||
msg.TargetId = u.GetInt()
|
||||
msg.Message = u.GetString()
|
||||
|
|
55
messages7/sv_team.go
Normal file
55
messages7/sv_team.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
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 SvTeam struct {
|
||||
ChunkHeader *chunk7.ChunkHeader
|
||||
|
||||
ClientId int
|
||||
Silent bool
|
||||
CooldownTick int
|
||||
}
|
||||
|
||||
func (msg SvTeam) MsgId() int {
|
||||
return network7.MsgGameSvTeam
|
||||
}
|
||||
|
||||
func (msg SvTeam) MsgType() network7.MsgType {
|
||||
return network7.TypeNet
|
||||
}
|
||||
|
||||
func (msg SvTeam) System() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (msg SvTeam) Vital() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (msg SvTeam) Pack() []byte {
|
||||
return slices.Concat(
|
||||
packer.PackInt(msg.ClientId),
|
||||
packer.PackBool(msg.Silent),
|
||||
packer.PackInt(msg.CooldownTick),
|
||||
)
|
||||
}
|
||||
|
||||
func (msg *SvTeam) Unpack(u *packer.Unpacker) {
|
||||
msg.ClientId = u.GetInt()
|
||||
msg.Silent = u.GetInt() != 0
|
||||
msg.CooldownTick = u.GetInt()
|
||||
}
|
||||
|
||||
func (msg *SvTeam) Header() *chunk7.ChunkHeader {
|
||||
return msg.ChunkHeader
|
||||
}
|
||||
|
||||
func (msg *SvTeam) SetHeader(header *chunk7.ChunkHeader) {
|
||||
msg.ChunkHeader = header
|
||||
}
|
|
@ -5,6 +5,14 @@ const (
|
|||
NetVersion = "0.7 802f1be60a05665f"
|
||||
ClientVersion = 0x0705
|
||||
|
||||
ChatAll ChatMode = 1
|
||||
ChatTeam ChatMode = 2
|
||||
ChatWhisper ChatMode = 3
|
||||
|
||||
TeamSpectators GameTeam = -1
|
||||
TeamRed GameTeam = 0
|
||||
TeamBlue GameTeam = 1
|
||||
|
||||
MsgCtrlKeepAlive = 0x00
|
||||
MsgCtrlConnect = 0x01
|
||||
MsgCtrlAccept = 0x02
|
||||
|
@ -43,10 +51,44 @@ const (
|
|||
MsgSysMaplistEntryRem = 30
|
||||
|
||||
MsgGameSvMotd = 1
|
||||
MsgGameSvBroadcast = 2
|
||||
MsgGameSvChat = 3
|
||||
MsgGameSvTeam = 4
|
||||
MsgGameSvKillMsg = 5
|
||||
MsgGameSvTuneParams = 6
|
||||
MsgGameSvExtraProjectile = 7
|
||||
MsgGameReadyToEnter = 8
|
||||
MsgGameWeaponPickup = 9
|
||||
MsgGameEmoticon = 10
|
||||
MsgGameSvVoteClearoptions = 11
|
||||
MsgGameSvVoteOptionlistadd = 12
|
||||
MsgGameSvVotePptionadd = 13
|
||||
MsgGameSvVoteOptionremove = 14
|
||||
MsgGameSvVoteSet = 15
|
||||
MsgGameSvVoteStatus = 16
|
||||
MsgGameSvServerSettings = 17
|
||||
MsgGameSvClientInfo = 18
|
||||
MsgGameSvGameInfo = 19
|
||||
MsgGameSvClientDrop = 20
|
||||
MsgGameSvGameMsg = 21
|
||||
MsgGameDeClientEnter = 22
|
||||
MsgGameDeClientLeave = 23
|
||||
MsgGameClSay = 24
|
||||
MsgGameClSetTeam = 25
|
||||
MsgGameClSetSpectatorMode = 26
|
||||
MsgGameClStartInfo = 27
|
||||
MsgGameClKill = 28
|
||||
MsgGameClReadyChange = 29
|
||||
MsgGameClEmoticon = 30
|
||||
MsgGameClVote = 31
|
||||
MsgGameClCallVote = 32
|
||||
MsgGameSvSkinChange = 33
|
||||
MsgGameClSkinChange = 34
|
||||
MsgGameSvRaceFinish = 35
|
||||
MsgGameSvCheckpoint = 36
|
||||
MsgGameSvCommandInfo = 37
|
||||
MsgGameSvCommandInfoRemove = 38
|
||||
MsgGameClCommand = 39
|
||||
|
||||
TypeControl MsgType = 1
|
||||
TypeNet MsgType = 2
|
||||
|
@ -61,5 +103,7 @@ const (
|
|||
NumWeapons Weapon = 6
|
||||
)
|
||||
|
||||
type ChatMode int
|
||||
type GameTeam int
|
||||
type MsgType int
|
||||
type Weapon int
|
||||
|
|
|
@ -1,246 +0,0 @@
|
|||
package protocol7
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/network7"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
Info messages7.SvClientInfo
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
ClientToken [4]byte
|
||||
ServerToken [4]byte
|
||||
|
||||
// The amount of vital chunks received
|
||||
Ack int
|
||||
|
||||
// The amount of vital chunks sent
|
||||
Sequence int
|
||||
|
||||
// The amount of vital chunks acknowledged by the peer
|
||||
PeerAck int
|
||||
|
||||
Players []Player
|
||||
}
|
||||
|
||||
func (connection *Connection) BuildResponse() *Packet {
|
||||
return &Packet{
|
||||
Header: PacketHeader{
|
||||
Flags: PacketFlags{
|
||||
Connless: false,
|
||||
Compression: false,
|
||||
Resend: false,
|
||||
Control: false,
|
||||
},
|
||||
Ack: connection.Ack,
|
||||
NumChunks: 0, // will be set in Packet.Pack()
|
||||
Token: connection.ServerToken,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (connection *Connection) CtrlToken() *Packet {
|
||||
response := connection.BuildResponse()
|
||||
response.Header.Flags.Control = true
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.CtrlToken{
|
||||
Token: connection.ClientToken,
|
||||
},
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func (client *Connection) MsgStartInfo() *messages7.ClStartInfo {
|
||||
return &messages7.ClStartInfo{
|
||||
Name: "gopher",
|
||||
Clan: "",
|
||||
Country: 0,
|
||||
Body: "greensward",
|
||||
Marking: "duodonny",
|
||||
Decoration: "",
|
||||
Hands: "standard",
|
||||
Feet: "standard",
|
||||
Eyes: "standard",
|
||||
CustomColorBody: false,
|
||||
CustomColorMarking: false,
|
||||
CustomColorDecoration: false,
|
||||
CustomColorHands: false,
|
||||
CustomColorFeet: false,
|
||||
CustomColorEyes: false,
|
||||
ColorBody: 0,
|
||||
ColorMarking: 0,
|
||||
ColorDecoration: 0,
|
||||
ColorHands: 0,
|
||||
ColorFeet: 0,
|
||||
ColorEyes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func byteSliceToString(s []byte) string {
|
||||
n := bytes.IndexByte(s, 0)
|
||||
if n >= 0 {
|
||||
s = s[:n]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (connection *Connection) printUnknownMessage(msg messages7.NetMessage, msgType string) {
|
||||
fmt.Printf("%s message id=%d\n", msgType, msg.MsgId())
|
||||
if msg.Header() == nil {
|
||||
fmt.Println(" header: nil")
|
||||
} else {
|
||||
fmt.Printf(" header: %x\n", msg.Header().Pack())
|
||||
}
|
||||
fmt.Printf(" payload: %x\n", msg.Pack())
|
||||
if msg.Header() != nil {
|
||||
fmt.Printf(" full msg: %x%x\n", msg.Header().Pack(), msg.Pack())
|
||||
}
|
||||
}
|
||||
|
||||
func (connection *Connection) OnSystemMsg(msg messages7.NetMessage, response *Packet) bool {
|
||||
// TODO: is this shadow nasty?
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.MapChange:
|
||||
fmt.Println("got map change")
|
||||
response.Messages = append(response.Messages, &messages7.Ready{})
|
||||
case *messages7.MapData:
|
||||
fmt.Printf("got map chunk %x\n", msg.Data)
|
||||
case *messages7.ServerInfo:
|
||||
fmt.Printf("connected to server with name '%s'\n", msg.Name)
|
||||
case *messages7.ConReady:
|
||||
fmt.Println("got ready")
|
||||
response.Messages = append(response.Messages, connection.MsgStartInfo())
|
||||
case *messages7.Snap:
|
||||
// fmt.Printf("got snap tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.SnapSingle:
|
||||
// fmt.Printf("got snap single tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.SnapEmpty:
|
||||
// fmt.Printf("got snap empty tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.InputTiming:
|
||||
// fmt.Printf("timing time left=%d\n", msg.TimeLeft)
|
||||
case *messages7.RconAuthOn:
|
||||
fmt.Println("you are now authenticated in rcon")
|
||||
case *messages7.RconAuthOff:
|
||||
fmt.Println("you are no longer authenticated in rcon")
|
||||
case *messages7.RconLine:
|
||||
fmt.Printf("[rcon] %s\n", msg.Line)
|
||||
case *messages7.RconCmdAdd:
|
||||
// fmt.Printf("got rcon cmd=%s %s %s\n", msg.Name, msg.Params, msg.Help)
|
||||
case *messages7.RconCmdRem:
|
||||
// fmt.Printf("removed cmd=%s\n", msg.Name)
|
||||
case *messages7.Unknown:
|
||||
// TODO: msg id of unknown messages should not be -1
|
||||
fmt.Println("TODO: why is the msg id -1???")
|
||||
connection.printUnknownMessage(msg, "unknown system")
|
||||
default:
|
||||
connection.printUnknownMessage(msg, "unhandled system")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (client *Connection) OnChatMessage(msg *messages7.SvChat) {
|
||||
if msg.ClientId < 0 || msg.ClientId > network7.MaxClients {
|
||||
fmt.Printf("[chat] *** %s\n", msg.Message)
|
||||
return
|
||||
}
|
||||
name := client.Players[msg.ClientId].Info.Name
|
||||
fmt.Printf("[chat] <%s> %s\n", name, msg.Message)
|
||||
}
|
||||
|
||||
func (connection *Connection) OnGameMsg(msg messages7.NetMessage, response *Packet) bool {
|
||||
// TODO: is this shadow nasty?
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.ReadyToEnter:
|
||||
fmt.Println("got ready to enter")
|
||||
response.Messages = append(response.Messages, &messages7.EnterGame{})
|
||||
case *messages7.SvMotd:
|
||||
if msg.Message != "" {
|
||||
fmt.Printf("[motd] %s\n", msg.Message)
|
||||
}
|
||||
case *messages7.SvChat:
|
||||
connection.OnChatMessage(msg)
|
||||
case *messages7.SvClientInfo:
|
||||
connection.Players[msg.ClientId].Info = *msg
|
||||
fmt.Printf("got client info id=%d name=%s\n", msg.ClientId, msg.Name)
|
||||
case *messages7.Unknown:
|
||||
connection.printUnknownMessage(msg, "unknown game")
|
||||
default:
|
||||
connection.printUnknownMessage(msg, "unhandled game")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (connection *Connection) OnMessage(msg messages7.NetMessage, response *Packet) bool {
|
||||
if msg.Header() == nil {
|
||||
// this is probably an unknown message
|
||||
fmt.Printf("warning ignoring msgId=%d because header is nil\n", msg.MsgId())
|
||||
return false
|
||||
}
|
||||
if msg.Header().Flags.Vital {
|
||||
connection.Ack++
|
||||
}
|
||||
|
||||
if msg.System() {
|
||||
return connection.OnSystemMsg(msg, response)
|
||||
}
|
||||
return connection.OnGameMsg(msg, response)
|
||||
}
|
||||
|
||||
// Takes a full teeworlds packet as argument
|
||||
// And returns the response packet from the clients perspective
|
||||
func (connection *Connection) OnPacket(packet *Packet) *Packet {
|
||||
response := connection.BuildResponse()
|
||||
|
||||
if packet.Header.Flags.Control {
|
||||
msg := packet.Messages[0]
|
||||
fmt.Printf("got ctrl msg %d\n", msg.MsgId())
|
||||
// TODO: is this shadow nasty?
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.CtrlToken:
|
||||
fmt.Printf("got server token %x\n", msg.Token)
|
||||
connection.ServerToken = msg.Token
|
||||
response.Header.Token = msg.Token
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.CtrlConnect{
|
||||
Token: connection.ClientToken,
|
||||
},
|
||||
)
|
||||
case *messages7.CtrlAccept:
|
||||
fmt.Println("got accept")
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.Info{
|
||||
Version: network7.NetVersion,
|
||||
Password: "",
|
||||
ClientVersion: network7.ClientVersion,
|
||||
},
|
||||
)
|
||||
case *messages7.CtrlClose:
|
||||
fmt.Printf("disconnected (%s)\n", msg.Reason)
|
||||
os.Exit(0)
|
||||
default:
|
||||
fmt.Printf("unknown control message: %d\n", msg.MsgId())
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
for _, msg := range packet.Messages {
|
||||
connection.OnMessage(msg, response)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
|
@ -39,7 +39,7 @@ type Packet struct {
|
|||
Messages []messages7.NetMessage
|
||||
}
|
||||
|
||||
func PackChunk(msg messages7.NetMessage, connection *Connection) []byte {
|
||||
func PackChunk(msg messages7.NetMessage, connection *Session) []byte {
|
||||
if _, ok := msg.(*messages7.Unknown); ok {
|
||||
return msg.Pack()
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ func (packet *Packet) Unpack(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (packet *Packet) Pack(connection *Connection) []byte {
|
||||
func (packet *Packet) Pack(connection *Session) []byte {
|
||||
payload := []byte{}
|
||||
control := false
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ func TestRepackUnknownMessages(t *testing.T) {
|
|||
0x00,
|
||||
}
|
||||
|
||||
conn := Connection{}
|
||||
conn := Session{}
|
||||
|
||||
packet := Packet{}
|
||||
packet.Unpack(dump)
|
||||
|
@ -162,7 +162,7 @@ func TestPackUpdateChunkHeaders(t *testing.T) {
|
|||
|
||||
// When packing the chunk header will be set automatically
|
||||
// Based on the current context
|
||||
conn := &Connection{Sequence: 1}
|
||||
conn := &Session{Sequence: 1}
|
||||
packet.Pack(conn)
|
||||
|
||||
{
|
||||
|
|
72
protocol7/session.go
Normal file
72
protocol7/session.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package protocol7
|
||||
|
||||
import "github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
|
||||
// teeworlds connection state
|
||||
type Session struct {
|
||||
ClientToken [4]byte
|
||||
ServerToken [4]byte
|
||||
|
||||
// The amount of vital chunks received
|
||||
Ack int
|
||||
|
||||
// The amount of vital chunks sent
|
||||
Sequence int
|
||||
|
||||
// The amount of vital chunks acknowledged by the peer
|
||||
PeerAck int
|
||||
}
|
||||
|
||||
func (connection *Session) BuildResponse() *Packet {
|
||||
return &Packet{
|
||||
Header: PacketHeader{
|
||||
Flags: PacketFlags{
|
||||
Connless: false,
|
||||
Compression: false,
|
||||
Resend: false,
|
||||
Control: false,
|
||||
},
|
||||
Ack: connection.Ack,
|
||||
NumChunks: 0, // will be set in Packet.Pack()
|
||||
Token: connection.ServerToken,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (connection *Session) CtrlToken() *Packet {
|
||||
response := connection.BuildResponse()
|
||||
response.Header.Flags.Control = true
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.CtrlToken{
|
||||
Token: connection.ClientToken,
|
||||
},
|
||||
)
|
||||
return response
|
||||
}
|
||||
|
||||
func (client *Session) MsgStartInfo() *messages7.ClStartInfo {
|
||||
return &messages7.ClStartInfo{
|
||||
Name: "gopher",
|
||||
Clan: "",
|
||||
Country: 0,
|
||||
Body: "greensward",
|
||||
Marking: "duodonny",
|
||||
Decoration: "",
|
||||
Hands: "standard",
|
||||
Feet: "standard",
|
||||
Eyes: "standard",
|
||||
CustomColorBody: false,
|
||||
CustomColorMarking: false,
|
||||
CustomColorDecoration: false,
|
||||
CustomColorHands: false,
|
||||
CustomColorFeet: false,
|
||||
CustomColorEyes: false,
|
||||
ColorBody: 0,
|
||||
ColorMarking: 0,
|
||||
ColorDecoration: 0,
|
||||
ColorHands: 0,
|
||||
ColorFeet: 0,
|
||||
ColorEyes: 0,
|
||||
}
|
||||
}
|
128
teeworlds.go
128
teeworlds.go
|
@ -1,128 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/network7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPacksize = 1400
|
||||
)
|
||||
|
||||
func getConnection(serverIp string, serverPort int) (net.Conn, error) {
|
||||
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", serverIp, serverPort))
|
||||
if err != nil {
|
||||
fmt.Printf("Some error %v", err)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func readNetwork(ch chan<- []byte, conn net.Conn) {
|
||||
buf := make([]byte, maxPacksize)
|
||||
|
||||
for {
|
||||
len, err := bufio.NewReader(conn).Read(buf)
|
||||
packet := make([]byte, len)
|
||||
copy(packet[:], buf[:])
|
||||
if err == nil {
|
||||
ch <- packet
|
||||
} else {
|
||||
fmt.Printf("Some error %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
ch := make(chan []byte, maxPacksize)
|
||||
|
||||
serverIp := "127.0.0.1"
|
||||
serverPort := 8303
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
if os.Args[1][0] == '-' {
|
||||
fmt.Println("usage: ./teeworlds [serverIp] [serverPort]")
|
||||
os.Exit(1)
|
||||
}
|
||||
serverIp = os.Args[1]
|
||||
}
|
||||
if len(os.Args) > 2 {
|
||||
var err error
|
||||
serverPort, err = strconv.Atoi(os.Args[2])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := getConnection(serverIp, serverPort)
|
||||
if err != nil {
|
||||
fmt.Printf("error connecting %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
client := &protocol7.Connection{
|
||||
ClientToken: [4]byte{0x01, 0x02, 0x03, 0x04},
|
||||
ServerToken: [4]byte{0xff, 0xff, 0xff, 0xff},
|
||||
Ack: 0,
|
||||
Players: make([]protocol7.Player, network7.MaxClients),
|
||||
}
|
||||
|
||||
go readNetwork(ch, conn)
|
||||
|
||||
tokenPacket := client.CtrlToken()
|
||||
conn.Write(tokenPacket.Pack(client))
|
||||
|
||||
for {
|
||||
time.Sleep(10_000_000)
|
||||
select {
|
||||
case msg := <-ch:
|
||||
packet := &protocol7.Packet{}
|
||||
err := packet.Unpack(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
response := client.OnPacket(packet)
|
||||
if response != nil {
|
||||
// example of inspecting incoming trafic
|
||||
for _, msg := range packet.Messages {
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.SvChat:
|
||||
// inspect incoming traffic
|
||||
fmt.Printf("got incoming chat msg: %s\n", msg.Message)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// example of modifying outgoing traffic
|
||||
for i, msg := range response.Messages {
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.SvChat:
|
||||
// inspect outgoing traffic
|
||||
fmt.Printf("got outgoing chat msg: %s\n", msg.Message)
|
||||
|
||||
// change outgoing traffic
|
||||
msg.Message += " (edited by go)"
|
||||
packet.Messages[i] = msg
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if len(response.Messages) > 0 || response.Header.Flags.Resend {
|
||||
conn.Write(response.Pack(client))
|
||||
}
|
||||
}
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
99
teeworlds7/callbacks.go
Normal file
99
teeworlds7/callbacks.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
// Processes the incoming packet
|
||||
// It might print to the console
|
||||
// It might send a response packet
|
||||
type DefaultAction func()
|
||||
|
||||
// TODO: this should be a map but the type checker broke me
|
||||
//
|
||||
// // key is the network7.MessageId
|
||||
// UserMsgCallbacks map[int]UserMsgCallback
|
||||
type UserMsgCallbacks struct {
|
||||
PacketIn func(*protocol7.Packet)
|
||||
PacketOut func(*protocol7.Packet)
|
||||
MsgUnknown func(*messages7.Unknown, DefaultAction)
|
||||
InternalError func(error)
|
||||
|
||||
CtrlKeepAlive func(*messages7.CtrlKeepAlive, DefaultAction)
|
||||
CtrlConnect func(*messages7.CtrlConnect, DefaultAction)
|
||||
CtrlAccept func(*messages7.CtrlAccept, DefaultAction)
|
||||
CtrlToken func(*messages7.CtrlToken, DefaultAction)
|
||||
CtrlClose func(*messages7.CtrlClose, DefaultAction)
|
||||
|
||||
SysInfo func(*messages7.Info, DefaultAction)
|
||||
SysMapChange func(*messages7.MapChange, DefaultAction)
|
||||
SysMapData func(*messages7.MapData, DefaultAction)
|
||||
SysServerInfo func(*messages7.ServerInfo, DefaultAction)
|
||||
SysConReady func(*messages7.ConReady, DefaultAction)
|
||||
SysSnap func(*messages7.Snap, DefaultAction)
|
||||
SysSnapEmpty func(*messages7.SnapEmpty, DefaultAction)
|
||||
SysSnapSingle func(*messages7.SnapSingle, DefaultAction)
|
||||
SysSnapSmall func(*messages7.SnapSmall, DefaultAction)
|
||||
SysInputTiming func(*messages7.InputTiming, DefaultAction)
|
||||
SysRconAuthOn func(*messages7.RconAuthOn, DefaultAction)
|
||||
SysRconAuthOff func(*messages7.RconAuthOff, DefaultAction)
|
||||
SysRconLine func(*messages7.RconLine, DefaultAction)
|
||||
SysRconCmdAdd func(*messages7.RconCmdAdd, DefaultAction)
|
||||
SysRconCmdRem func(*messages7.RconCmdRem, DefaultAction)
|
||||
SysAuthChallenge func(*messages7.AuthChallenge, DefaultAction)
|
||||
SysAuthResult func(*messages7.AuthResult, DefaultAction)
|
||||
SysReady func(*messages7.Ready, DefaultAction)
|
||||
SysEnterGame func(*messages7.EnterGame, DefaultAction)
|
||||
SysInput func(*messages7.Input, DefaultAction)
|
||||
SysRconCmd func(*messages7.RconCmd, DefaultAction)
|
||||
SysRconAuth func(*messages7.RconAuth, DefaultAction)
|
||||
SysRequestMapData func(*messages7.RequestMapData, DefaultAction)
|
||||
SysAuthStart func(*messages7.AuthStart, DefaultAction)
|
||||
SysAuthResponse func(*messages7.AuthResponse, DefaultAction)
|
||||
SysPing func(*messages7.Ping, DefaultAction)
|
||||
SysPingReply func(*messages7.PingReply, DefaultAction)
|
||||
SysError func(*messages7.Error, DefaultAction)
|
||||
SysMaplistEntryAdd func(*messages7.MaplistEntryAdd, DefaultAction)
|
||||
SysMaplistEntryRem func(*messages7.MaplistEntryRem, DefaultAction)
|
||||
|
||||
GameSvMotd func(*messages7.SvMotd, DefaultAction)
|
||||
GameSvBroadcast func(*messages7.SvBroadcast, DefaultAction)
|
||||
GameSvChat func(*messages7.SvChat, DefaultAction)
|
||||
GameSvTeam func(*messages7.SvTeam, DefaultAction)
|
||||
// GameSvKillMsg func(*messages7.SvKillMsg, DefaultAction)
|
||||
// GameSvTuneParams func(*messages7.SvTuneParams, DefaultAction)
|
||||
// GameSvExtraProjectile func(*messages7.SvExtraProjectile, DefaultAction)
|
||||
GameReadyToEnter func(*messages7.ReadyToEnter, DefaultAction)
|
||||
// GameWeaponPickup func(*messages7.WeaponPickup, DefaultAction)
|
||||
// GameEmoticon func(*messages7.Emoticon, DefaultAction)
|
||||
// GameSvVoteClearoptions func(*messages7.SvVoteClearoptions, DefaultAction)
|
||||
// GameSvVoteOptionlistadd func(*messages7.SvVoteOptionlistadd, DefaultAction)
|
||||
// GameSvVotePptionadd func(*messages7.SvVotePptionadd, DefaultAction)
|
||||
// GameSvVoteOptionremove func(*messages7.SvVoteOptionremove, DefaultAction)
|
||||
// GameSvVoteSet func(*messages7.SvVoteSet, DefaultAction)
|
||||
// GameSvVoteStatus func(*messages7.SvVoteStatus, DefaultAction)
|
||||
// GameSvServerSettings func(*messages7.SvServerSettings, DefaultAction)
|
||||
GameSvClientInfo func(*messages7.SvClientInfo, DefaultAction)
|
||||
// GameSvGameInfo func(*messages7.SvGameInfo, DefaultAction)
|
||||
// GameSvClientDrop func(*messages7.SvClientDrop, DefaultAction)
|
||||
// GameSvGameMsg func(*messages7.SvGameMsg, DefaultAction)
|
||||
// GameDeClientEnter func(*messages7.DeClientEnter, DefaultAction)
|
||||
// GameDeClientLeave func(*messages7.DeClientLeave, DefaultAction)
|
||||
// GameClSay func(*messages7.ClSay, DefaultAction)
|
||||
// GameClSetTeam func(*messages7.ClSetTeam, DefaultAction)
|
||||
// GameClSetSpectatorMode func(*messages7.ClSetSpectatorMode, DefaultAction)
|
||||
GameClStartInfo func(*messages7.ClStartInfo, DefaultAction)
|
||||
// GameClKill func(*messages7.ClKill, DefaultAction)
|
||||
// GameClReadyChange func(*messages7.ClReadyChange, DefaultAction)
|
||||
// GameClEmoticon func(*messages7.ClEmoticon, DefaultAction)
|
||||
// GameClVote func(*messages7.ClVote, DefaultAction)
|
||||
// GameClCallVote func(*messages7.ClCallVote, DefaultAction)
|
||||
// GameSvSkinChange func(*messages7.SvSkinChange, DefaultAction)
|
||||
// GameClSkinChange func(*messages7.ClSkinChange, DefaultAction)
|
||||
// GameSvRaceFinish func(*messages7.SvRaceFinish, DefaultAction)
|
||||
// GameSvCheckpoint func(*messages7.SvCheckpoint, DefaultAction)
|
||||
// GameSvCommandInfo func(*messages7.SvCommandInfo, DefaultAction)
|
||||
// GameSvCommandInfoRemove func(*messages7.SvCommandInfoRemove, DefaultAction)
|
||||
// GameClCommand func(*messages7.ClCommand, DefaultAction)
|
||||
}
|
47
teeworlds7/client.go
Normal file
47
teeworlds7/client.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
Info messages7.SvClientInfo
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
Players []Player
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Name string
|
||||
Clan string
|
||||
Country int
|
||||
|
||||
// chunks to be sent on next packet send
|
||||
// use client.SendMessage() to put your chunks here
|
||||
QueuedMessages []messages7.NetMessage
|
||||
|
||||
// hooks from the user
|
||||
Callbacks UserMsgCallbacks
|
||||
|
||||
// udp connection
|
||||
Conn net.Conn
|
||||
|
||||
// teeworlds session
|
||||
Session protocol7.Session
|
||||
|
||||
// teeworlds game state
|
||||
Game Game
|
||||
}
|
||||
|
||||
func (client *Client) throwError(err error) {
|
||||
if client.Callbacks.InternalError != nil {
|
||||
client.Callbacks.InternalError(err)
|
||||
return
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
83
teeworlds7/game.go
Normal file
83
teeworlds7/game.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func (client *Client) processGame(netMsg messages7.NetMessage, response *protocol7.Packet) bool {
|
||||
switch msg := netMsg.(type) {
|
||||
case *messages7.SvMotd:
|
||||
defaultAction := func() {
|
||||
if msg.Message != "" {
|
||||
fmt.Printf("[motd] %s\n", msg.Message)
|
||||
}
|
||||
}
|
||||
if client.Callbacks.GameSvMotd == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.GameSvMotd(msg, defaultAction)
|
||||
}
|
||||
case *messages7.SvBroadcast:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("[broadcast] %s\n", msg.Message)
|
||||
}
|
||||
if client.Callbacks.GameSvBroadcast == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.GameSvBroadcast(msg, defaultAction)
|
||||
}
|
||||
case *messages7.SvChat:
|
||||
defaultAction := func() {
|
||||
if msg.ClientId < 0 || msg.ClientId > network7.MaxClients {
|
||||
fmt.Printf("[chat] *** %s\n", msg.Message)
|
||||
return
|
||||
}
|
||||
name := client.Game.Players[msg.ClientId].Info.Name
|
||||
fmt.Printf("[chat] <%s> %s\n", name, msg.Message)
|
||||
}
|
||||
if client.Callbacks.GameSvChat == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.GameSvChat(msg, defaultAction)
|
||||
}
|
||||
case *messages7.SvClientInfo:
|
||||
defaultAction := func() {
|
||||
client.Game.Players[msg.ClientId].Info = *msg
|
||||
fmt.Printf("got client info id=%d name=%s\n", msg.ClientId, msg.Name)
|
||||
}
|
||||
if client.Callbacks.GameSvClientInfo == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.GameSvClientInfo(msg, defaultAction)
|
||||
}
|
||||
case *messages7.ReadyToEnter:
|
||||
defaultAction := func() {
|
||||
fmt.Println("got ready to enter")
|
||||
response.Messages = append(response.Messages, &messages7.EnterGame{})
|
||||
}
|
||||
if client.Callbacks.GameReadyToEnter == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.GameReadyToEnter(msg, defaultAction)
|
||||
}
|
||||
case *messages7.Unknown:
|
||||
defaultAction := func() {
|
||||
// TODO: msg id of unknown messages should not be -1
|
||||
fmt.Println("TODO: why is the msg id -1???")
|
||||
printUnknownMessage(msg, "unknown game")
|
||||
}
|
||||
if client.Callbacks.MsgUnknown == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.MsgUnknown(msg, defaultAction)
|
||||
}
|
||||
default:
|
||||
printUnknownMessage(netMsg, "unprocessed game")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
84
teeworlds7/networking.go
Normal file
84
teeworlds7/networking.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/network7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPacksize = 1400
|
||||
)
|
||||
|
||||
func getConnection(serverIp string, serverPort int) (net.Conn, error) {
|
||||
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", serverIp, serverPort))
|
||||
if err != nil {
|
||||
fmt.Printf("Some error %v", err)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func readNetwork(ch chan<- []byte, conn net.Conn) {
|
||||
buf := make([]byte, maxPacksize)
|
||||
|
||||
for {
|
||||
len, err := bufio.NewReader(conn).Read(buf)
|
||||
packet := make([]byte, len)
|
||||
copy(packet[:], buf[:])
|
||||
if err == nil {
|
||||
ch <- packet
|
||||
} else {
|
||||
fmt.Printf("Some error %v\n", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func (client *Client) Connect(serverIp string, serverPort int) {
|
||||
ch := make(chan []byte, maxPacksize)
|
||||
|
||||
conn, err := getConnection(serverIp, serverPort)
|
||||
if err != nil {
|
||||
fmt.Printf("error connecting %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
client.Session = protocol7.Session{
|
||||
ClientToken: [4]byte{0x01, 0x02, 0x03, 0x04},
|
||||
ServerToken: [4]byte{0xff, 0xff, 0xff, 0xff},
|
||||
Ack: 0,
|
||||
}
|
||||
client.Game.Players = make([]Player, network7.MaxClients)
|
||||
client.Conn = conn
|
||||
|
||||
go readNetwork(ch, conn)
|
||||
|
||||
client.SendPacket(client.Session.CtrlToken())
|
||||
|
||||
// TODO: do we really need a non blocking network read?
|
||||
// if not remove the channel, the sleep and the select statement
|
||||
// if yes also offer an OnTick callback to the user, and also do keepalives and resends
|
||||
for {
|
||||
time.Sleep(10_000_000)
|
||||
select {
|
||||
case msg := <-ch:
|
||||
packet := &protocol7.Packet{}
|
||||
err := packet.Unpack(msg)
|
||||
if err != nil {
|
||||
client.throwError(err)
|
||||
}
|
||||
err = client.processPacket(packet)
|
||||
if err != nil {
|
||||
client.throwError(err)
|
||||
}
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
143
teeworlds7/packet.go
Normal file
143
teeworlds7/packet.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
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"
|
||||
)
|
||||
|
||||
func printUnknownMessage(msg messages7.NetMessage, msgType string) {
|
||||
fmt.Printf("%s message id=%d\n", msgType, msg.MsgId())
|
||||
if msg.Header() == nil {
|
||||
fmt.Println(" header: nil")
|
||||
} else {
|
||||
fmt.Printf(" header: %x\n", msg.Header().Pack())
|
||||
}
|
||||
fmt.Printf(" payload: %x\n", msg.Pack())
|
||||
if msg.Header() != nil {
|
||||
fmt.Printf(" full msg: %x%x\n", msg.Header().Pack(), msg.Pack())
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) processMessage(msg messages7.NetMessage, response *protocol7.Packet) bool {
|
||||
if msg.Header() == nil {
|
||||
// this is probably an unknown message
|
||||
fmt.Printf("warning ignoring msgId=%d because header is nil\n", msg.MsgId())
|
||||
return false
|
||||
}
|
||||
if msg.Header().Flags.Vital {
|
||||
client.Session.Ack++
|
||||
}
|
||||
|
||||
if msg.System() {
|
||||
return client.processSystem(msg, response)
|
||||
}
|
||||
return client.processGame(msg, response)
|
||||
}
|
||||
|
||||
func (client *Client) processPacket(packet *protocol7.Packet) error {
|
||||
if client.Callbacks.PacketIn != nil {
|
||||
client.Callbacks.PacketIn(packet)
|
||||
}
|
||||
|
||||
response := client.Session.BuildResponse()
|
||||
|
||||
if packet.Header.Flags.Control {
|
||||
if len(packet.Messages) != 1 {
|
||||
return fmt.Errorf("got control packet with %d messages.\n", len(packet.Messages))
|
||||
}
|
||||
|
||||
msg := packet.Messages[0]
|
||||
// TODO: is this shadow nasty?
|
||||
switch msg := msg.(type) {
|
||||
case *messages7.CtrlKeepAlive:
|
||||
defaultAction := func() {
|
||||
fmt.Println("got keep alive")
|
||||
}
|
||||
if client.Callbacks.CtrlKeepAlive == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.CtrlKeepAlive(msg, defaultAction)
|
||||
}
|
||||
case *messages7.CtrlConnect:
|
||||
defaultAction := func() {
|
||||
fmt.Println("we got connect as a client. this should never happen lol.")
|
||||
fmt.Println("who is tryint to connect to us? We are not a server!")
|
||||
}
|
||||
if client.Callbacks.CtrlConnect == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.CtrlConnect(msg, defaultAction)
|
||||
}
|
||||
case *messages7.CtrlAccept:
|
||||
defaultAction := func() {
|
||||
fmt.Println("got accept")
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.Info{
|
||||
Version: network7.NetVersion,
|
||||
Password: "",
|
||||
ClientVersion: network7.ClientVersion,
|
||||
},
|
||||
)
|
||||
client.SendPacket(response)
|
||||
}
|
||||
if client.Callbacks.CtrlAccept == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.CtrlAccept(msg, defaultAction)
|
||||
}
|
||||
case *messages7.CtrlClose:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("disconnected (%s)\n", msg.Reason)
|
||||
}
|
||||
if client.Callbacks.CtrlClose == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.CtrlClose(msg, defaultAction)
|
||||
}
|
||||
case *messages7.CtrlToken:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("got server token %x\n", msg.Token)
|
||||
client.Session.ServerToken = msg.Token
|
||||
response.Header.Token = msg.Token
|
||||
response.Messages = append(
|
||||
response.Messages,
|
||||
&messages7.CtrlConnect{
|
||||
Token: client.Session.ClientToken,
|
||||
},
|
||||
)
|
||||
client.SendPacket(response)
|
||||
}
|
||||
if client.Callbacks.CtrlToken == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.CtrlToken(msg, defaultAction)
|
||||
}
|
||||
case *messages7.Unknown:
|
||||
defaultAction := func() {
|
||||
printUnknownMessage(msg, "unknown control")
|
||||
}
|
||||
if client.Callbacks.MsgUnknown == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.MsgUnknown(msg, defaultAction)
|
||||
}
|
||||
return fmt.Errorf("unknown control message: %d\n", msg.MsgId())
|
||||
default:
|
||||
return fmt.Errorf("unprocessed control message: %d\n", msg.MsgId())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, msg := range packet.Messages {
|
||||
client.processMessage(msg, response)
|
||||
}
|
||||
|
||||
if len(response.Messages) > 0 || response.Header.Flags.Resend {
|
||||
client.SendPacket(response)
|
||||
}
|
||||
return nil
|
||||
}
|
152
teeworlds7/system.go
Normal file
152
teeworlds7/system.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
func (client *Client) processSystem(netMsg messages7.NetMessage, response *protocol7.Packet) bool {
|
||||
switch msg := netMsg.(type) {
|
||||
case *messages7.MapChange:
|
||||
defaultAction := func() {
|
||||
fmt.Println("got map change")
|
||||
response.Messages = append(response.Messages, &messages7.Ready{})
|
||||
}
|
||||
if client.Callbacks.SysMapChange == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysMapChange(msg, defaultAction)
|
||||
}
|
||||
case *messages7.MapData:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("got map chunk %x\n", msg.Data)
|
||||
}
|
||||
if client.Callbacks.SysMapData == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysMapData(msg, defaultAction)
|
||||
}
|
||||
case *messages7.ServerInfo:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("connected to server with name '%s'\n", msg.Name)
|
||||
}
|
||||
if client.Callbacks.SysServerInfo == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysServerInfo(msg, defaultAction)
|
||||
}
|
||||
case *messages7.ConReady:
|
||||
defaultAction := func() {
|
||||
fmt.Println("connected, sending info")
|
||||
info := &messages7.ClStartInfo{
|
||||
Name: client.Name,
|
||||
Clan: client.Clan,
|
||||
Country: client.Country,
|
||||
Body: "greensward",
|
||||
Marking: "duodonny",
|
||||
Decoration: "",
|
||||
Hands: "standard",
|
||||
Feet: "standard",
|
||||
Eyes: "standard",
|
||||
CustomColorBody: false,
|
||||
CustomColorMarking: false,
|
||||
CustomColorDecoration: false,
|
||||
CustomColorHands: false,
|
||||
CustomColorFeet: false,
|
||||
CustomColorEyes: false,
|
||||
ColorBody: 0,
|
||||
ColorMarking: 0,
|
||||
ColorDecoration: 0,
|
||||
ColorHands: 0,
|
||||
ColorFeet: 0,
|
||||
ColorEyes: 0,
|
||||
}
|
||||
response.Messages = append(response.Messages, info)
|
||||
}
|
||||
if client.Callbacks.SysConReady == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysConReady(msg, defaultAction)
|
||||
}
|
||||
case *messages7.Snap:
|
||||
// fmt.Printf("got snap tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.SnapSingle:
|
||||
// fmt.Printf("got snap single tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.SnapEmpty:
|
||||
// fmt.Printf("got snap empty tick=%d\n", msg.GameTick)
|
||||
response.Messages = append(response.Messages, &messages7.CtrlKeepAlive{})
|
||||
case *messages7.InputTiming:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("timing time left=%d\n", msg.TimeLeft)
|
||||
}
|
||||
if client.Callbacks.SysInputTiming == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysInputTiming(msg, defaultAction)
|
||||
}
|
||||
case *messages7.RconAuthOn:
|
||||
defaultAction := func() {
|
||||
fmt.Println("you are now authenticated in rcon")
|
||||
}
|
||||
if client.Callbacks.SysRconAuthOn == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysRconAuthOn(msg, defaultAction)
|
||||
}
|
||||
case *messages7.RconAuthOff:
|
||||
defaultAction := func() {
|
||||
fmt.Println("you are no longer authenticated in rcon")
|
||||
}
|
||||
if client.Callbacks.SysRconAuthOff == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysRconAuthOff(msg, defaultAction)
|
||||
}
|
||||
case *messages7.RconLine:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("[rcon] %s\n", msg.Line)
|
||||
}
|
||||
if client.Callbacks.SysRconLine == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysRconLine(msg, defaultAction)
|
||||
}
|
||||
case *messages7.RconCmdAdd:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("got rcon cmd=%s %s %s\n", msg.Name, msg.Params, msg.Help)
|
||||
}
|
||||
if client.Callbacks.SysRconCmdAdd == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysRconCmdAdd(msg, defaultAction)
|
||||
}
|
||||
case *messages7.RconCmdRem:
|
||||
defaultAction := func() {
|
||||
fmt.Printf("removed cmd=%s\n", msg.Name)
|
||||
}
|
||||
if client.Callbacks.SysRconCmdRem == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.SysRconCmdRem(msg, defaultAction)
|
||||
}
|
||||
case *messages7.Unknown:
|
||||
defaultAction := func() {
|
||||
// TODO: msg id of unknown messages should not be -1
|
||||
fmt.Println("TODO: why is the msg id -1???")
|
||||
printUnknownMessage(msg, "unknown system")
|
||||
}
|
||||
if client.Callbacks.MsgUnknown == nil {
|
||||
defaultAction()
|
||||
} else {
|
||||
client.Callbacks.MsgUnknown(msg, defaultAction)
|
||||
}
|
||||
default:
|
||||
printUnknownMessage(netMsg, "unprocessed system")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
70
teeworlds7/user_actions.go
Normal file
70
teeworlds7/user_actions.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/network7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
// ----------------------------
|
||||
// low level access for experts
|
||||
// ----------------------------
|
||||
|
||||
func (client *Client) SendPacket(packet *protocol7.Packet) {
|
||||
|
||||
// TODO: append queued messages to packet messages here
|
||||
|
||||
if client.Callbacks.PacketOut != nil {
|
||||
client.Callbacks.PacketOut(packet)
|
||||
}
|
||||
client.Conn.Write(packet.Pack(&client.Session))
|
||||
}
|
||||
|
||||
// WARNING! this is does not send chat messages
|
||||
// this sends a network chunk and is for expert users
|
||||
//
|
||||
// if you want to send a chat message use SendChat()
|
||||
func (client *Client) SendMessage(msg messages7.NetMessage) {
|
||||
// TODO: set vital header and stuff
|
||||
client.QueuedMessages = append(client.QueuedMessages, msg)
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// high level actions
|
||||
// ----------------------------
|
||||
|
||||
// see also SendWhisper()
|
||||
// see also SendChatTeam()
|
||||
func (client *Client) SendChat(msg string) {
|
||||
client.SendMessage(
|
||||
&messages7.SvChat{
|
||||
Mode: network7.ChatAll,
|
||||
Message: msg,
|
||||
TargetId: -1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// see also SendWhisper()
|
||||
// see also SendChat()
|
||||
func (client *Client) SendChatTeam(msg string) {
|
||||
client.SendMessage(
|
||||
&messages7.SvChat{
|
||||
Mode: network7.ChatTeam,
|
||||
Message: msg,
|
||||
TargetId: -1,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// see also SendChat()
|
||||
// see also SendChatTeam()
|
||||
func (client *Client) SendWhisper(targetId int, msg string) {
|
||||
client.SendMessage(
|
||||
&messages7.SvChat{
|
||||
Mode: network7.ChatWhisper,
|
||||
Message: msg,
|
||||
TargetId: targetId,
|
||||
},
|
||||
)
|
||||
}
|
84
teeworlds7/user_hooks.go
Normal file
84
teeworlds7/user_hooks.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package teeworlds7
|
||||
|
||||
import (
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/messages7"
|
||||
"github.com/teeworlds-go/go-teeworlds-protocol/protocol7"
|
||||
)
|
||||
|
||||
// --------------------------------
|
||||
// special cases
|
||||
// --------------------------------
|
||||
|
||||
// if not implemented by the user the application might throw and exit
|
||||
func (client *Client) OnError(callback func(err error)) {
|
||||
client.Callbacks.InternalError = callback
|
||||
}
|
||||
|
||||
// inspect outgoing traffic
|
||||
// and alter it before it gets sent to the server
|
||||
func (client *Client) OnSend(callback func(packet *protocol7.Packet)) {
|
||||
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)) {
|
||||
client.Callbacks.PacketIn = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnUnknown(callback func(msg *messages7.Unknown, defaultAction DefaultAction)) {
|
||||
client.Callbacks.MsgUnknown = callback
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
// control messages
|
||||
// --------------------------------
|
||||
|
||||
func (client *Client) OnKeepAlive(callback func(msg *messages7.CtrlKeepAlive, defaultAction DefaultAction)) {
|
||||
client.Callbacks.CtrlKeepAlive = callback
|
||||
}
|
||||
|
||||
// This is just misleading. It should never be called. This message is only received by the server.
|
||||
// func (client *Client) OnCtrlConnect(callback func(msg *messages7.CtrlConnect, defaultAction DefaultAction)) {
|
||||
// client.Callbacks.CtrlConnect = callback
|
||||
// }
|
||||
|
||||
func (client *Client) OnAccept(callback func(msg *messages7.CtrlAccept, defaultAction DefaultAction)) {
|
||||
client.Callbacks.CtrlAccept = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnDisconnect(callback func(msg *messages7.CtrlClose, defaultAction DefaultAction)) {
|
||||
client.Callbacks.CtrlClose = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnToken(callback func(msg *messages7.CtrlToken, defaultAction DefaultAction)) {
|
||||
client.Callbacks.CtrlToken = callback
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
// game messages
|
||||
// --------------------------------
|
||||
|
||||
func (client *Client) OnMotd(callback func(msg *messages7.SvMotd, defaultAction DefaultAction)) {
|
||||
client.Callbacks.GameSvMotd = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnBroadcast(callback func(msg *messages7.SvBroadcast, defaultAction DefaultAction)) {
|
||||
client.Callbacks.GameSvBroadcast = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnChat(callback func(msg *messages7.SvChat, defaultAction DefaultAction)) {
|
||||
client.Callbacks.GameSvChat = callback
|
||||
}
|
||||
|
||||
func (client *Client) OnTeam(callback func(msg *messages7.SvTeam, defaultAction DefaultAction)) {
|
||||
client.Callbacks.GameSvTeam = callback
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
// system messages
|
||||
// --------------------------------
|
||||
|
||||
func (client *Client) OnMapChange(callback func(msg *messages7.MapChange, defaultAction DefaultAction)) {
|
||||
client.Callbacks.SysMapChange = callback
|
||||
}
|
Loading…
Reference in a new issue