commit
aa5b67aab7
50
README.md
50
README.md
|
@ -1,13 +1,55 @@
|
||||||
# go-teeworlds-protocol
|
# 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.
|
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
|
## 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 {
|
type SvChat struct {
|
||||||
ChunkHeader *chunk7.ChunkHeader
|
ChunkHeader *chunk7.ChunkHeader
|
||||||
|
|
||||||
Mode int
|
Mode network7.ChatMode
|
||||||
ClientId int
|
ClientId int
|
||||||
TargetId int
|
TargetId int
|
||||||
Message string
|
Message string
|
||||||
|
@ -35,7 +35,7 @@ func (msg SvChat) Vital() bool {
|
||||||
|
|
||||||
func (msg SvChat) Pack() []byte {
|
func (msg SvChat) Pack() []byte {
|
||||||
return slices.Concat(
|
return slices.Concat(
|
||||||
packer.PackInt(msg.Mode),
|
packer.PackInt(int(msg.Mode)),
|
||||||
packer.PackInt(msg.ClientId),
|
packer.PackInt(msg.ClientId),
|
||||||
packer.PackInt(msg.TargetId),
|
packer.PackInt(msg.TargetId),
|
||||||
packer.PackStr(msg.Message),
|
packer.PackStr(msg.Message),
|
||||||
|
@ -43,7 +43,7 @@ func (msg SvChat) Pack() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *SvChat) Unpack(u *packer.Unpacker) {
|
func (msg *SvChat) Unpack(u *packer.Unpacker) {
|
||||||
msg.Mode = u.GetInt()
|
msg.Mode = network7.ChatMode(u.GetInt())
|
||||||
msg.ClientId = u.GetInt()
|
msg.ClientId = u.GetInt()
|
||||||
msg.TargetId = u.GetInt()
|
msg.TargetId = u.GetInt()
|
||||||
msg.Message = u.GetString()
|
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"
|
NetVersion = "0.7 802f1be60a05665f"
|
||||||
ClientVersion = 0x0705
|
ClientVersion = 0x0705
|
||||||
|
|
||||||
|
ChatAll ChatMode = 1
|
||||||
|
ChatTeam ChatMode = 2
|
||||||
|
ChatWhisper ChatMode = 3
|
||||||
|
|
||||||
|
TeamSpectators GameTeam = -1
|
||||||
|
TeamRed GameTeam = 0
|
||||||
|
TeamBlue GameTeam = 1
|
||||||
|
|
||||||
MsgCtrlKeepAlive = 0x00
|
MsgCtrlKeepAlive = 0x00
|
||||||
MsgCtrlConnect = 0x01
|
MsgCtrlConnect = 0x01
|
||||||
MsgCtrlAccept = 0x02
|
MsgCtrlAccept = 0x02
|
||||||
|
@ -43,10 +51,44 @@ const (
|
||||||
MsgSysMaplistEntryRem = 30
|
MsgSysMaplistEntryRem = 30
|
||||||
|
|
||||||
MsgGameSvMotd = 1
|
MsgGameSvMotd = 1
|
||||||
|
MsgGameSvBroadcast = 2
|
||||||
MsgGameSvChat = 3
|
MsgGameSvChat = 3
|
||||||
|
MsgGameSvTeam = 4
|
||||||
|
MsgGameSvKillMsg = 5
|
||||||
|
MsgGameSvTuneParams = 6
|
||||||
|
MsgGameSvExtraProjectile = 7
|
||||||
MsgGameReadyToEnter = 8
|
MsgGameReadyToEnter = 8
|
||||||
|
MsgGameWeaponPickup = 9
|
||||||
|
MsgGameEmoticon = 10
|
||||||
|
MsgGameSvVoteClearoptions = 11
|
||||||
|
MsgGameSvVoteOptionlistadd = 12
|
||||||
|
MsgGameSvVotePptionadd = 13
|
||||||
|
MsgGameSvVoteOptionremove = 14
|
||||||
|
MsgGameSvVoteSet = 15
|
||||||
|
MsgGameSvVoteStatus = 16
|
||||||
|
MsgGameSvServerSettings = 17
|
||||||
MsgGameSvClientInfo = 18
|
MsgGameSvClientInfo = 18
|
||||||
|
MsgGameSvGameInfo = 19
|
||||||
|
MsgGameSvClientDrop = 20
|
||||||
|
MsgGameSvGameMsg = 21
|
||||||
|
MsgGameDeClientEnter = 22
|
||||||
|
MsgGameDeClientLeave = 23
|
||||||
|
MsgGameClSay = 24
|
||||||
|
MsgGameClSetTeam = 25
|
||||||
|
MsgGameClSetSpectatorMode = 26
|
||||||
MsgGameClStartInfo = 27
|
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
|
TypeControl MsgType = 1
|
||||||
TypeNet MsgType = 2
|
TypeNet MsgType = 2
|
||||||
|
@ -61,5 +103,7 @@ const (
|
||||||
NumWeapons Weapon = 6
|
NumWeapons Weapon = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ChatMode int
|
||||||
|
type GameTeam int
|
||||||
type MsgType int
|
type MsgType int
|
||||||
type Weapon 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
|
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 {
|
if _, ok := msg.(*messages7.Unknown); ok {
|
||||||
return msg.Pack()
|
return msg.Pack()
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,7 @@ func (packet *Packet) Unpack(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (packet *Packet) Pack(connection *Connection) []byte {
|
func (packet *Packet) Pack(connection *Session) []byte {
|
||||||
payload := []byte{}
|
payload := []byte{}
|
||||||
control := false
|
control := false
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ func TestRepackUnknownMessages(t *testing.T) {
|
||||||
0x00,
|
0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := Connection{}
|
conn := Session{}
|
||||||
|
|
||||||
packet := Packet{}
|
packet := Packet{}
|
||||||
packet.Unpack(dump)
|
packet.Unpack(dump)
|
||||||
|
@ -162,7 +162,7 @@ func TestPackUpdateChunkHeaders(t *testing.T) {
|
||||||
|
|
||||||
// When packing the chunk header will be set automatically
|
// When packing the chunk header will be set automatically
|
||||||
// Based on the current context
|
// Based on the current context
|
||||||
conn := &Connection{Sequence: 1}
|
conn := &Session{Sequence: 1}
|
||||||
packet.Pack(conn)
|
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