aboutsummaryrefslogtreecommitdiffstats
path: root/client/quake/commands.go
diff options
context:
space:
mode:
Diffstat (limited to 'client/quake/commands.go')
-rw-r--r--client/quake/commands.go237
1 files changed, 237 insertions, 0 deletions
diff --git a/client/quake/commands.go b/client/quake/commands.go
new file mode 100644
index 0000000..a416753
--- /dev/null
+++ b/client/quake/commands.go
@@ -0,0 +1,237 @@
+package quake
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/osm/quake/common/args"
+ "github.com/osm/quake/common/infostring"
+ "github.com/osm/quake/common/sequencer"
+ "github.com/osm/quake/packet/command"
+ "github.com/osm/quake/packet/command/connect"
+ "github.com/osm/quake/packet/command/download"
+ "github.com/osm/quake/packet/command/ip"
+ "github.com/osm/quake/packet/command/modellist"
+ "github.com/osm/quake/packet/command/s2cchallenge"
+ "github.com/osm/quake/packet/command/s2cconnection"
+ "github.com/osm/quake/packet/command/serverdata"
+ "github.com/osm/quake/packet/command/soundlist"
+ "github.com/osm/quake/packet/command/stringcmd"
+ "github.com/osm/quake/packet/command/stufftext"
+ "github.com/osm/quake/protocol"
+ "github.com/osm/quake/protocol/fte"
+ "github.com/osm/quake/protocol/mvd"
+)
+
+func (c *Client) handleServerCommand(cmd command.Command) []command.Command {
+ switch cmd := cmd.(type) {
+ case *s2cchallenge.Command:
+ return c.handleChallenge(cmd)
+ case *s2cconnection.Command:
+ c.seq.SetState(sequencer.Connecting)
+ return c.handleNew()
+ case *stufftext.Command:
+ return c.handleStufftext(cmd)
+ case *soundlist.Command:
+ return c.handleSoundList(cmd)
+ case *modellist.Command:
+ return c.handleModelList(cmd)
+ case *serverdata.Command:
+ c.serverCount = cmd.ServerCount
+ }
+
+ return nil
+}
+
+func (c *Client) handleChallenge(cmd *s2cchallenge.Command) []command.Command {
+ userInfo := infostring.New(
+ infostring.WithKeyValue("name", c.name),
+ infostring.WithKeyValue("team", c.team),
+ infostring.WithKeyValue("topcolor", fmt.Sprintf("%d", c.topColor)),
+ infostring.WithKeyValue("bottomcolor", fmt.Sprintf("%d", c.bottomColor)),
+ )
+
+ if c.addrPort != "" {
+ userInfo.Info = append(userInfo.Info, infostring.Info{
+ Key: "prx",
+ Value: c.addrPort,
+ })
+ }
+
+ if c.clientVersion != "" {
+ userInfo.Info = append(userInfo.Info, infostring.Info{
+ Key: "*client",
+ Value: c.clientVersion,
+ })
+ }
+
+ if c.isSpectator {
+ userInfo.Info = append(userInfo.Info, infostring.Info{
+ Key: "spectator",
+ Value: "1",
+ })
+ }
+
+ if c.zQuakeEnabled {
+ userInfo.Info = append(userInfo.Info, infostring.Info{
+ Key: "*z_ext",
+ Value: fmt.Sprintf("%d", c.zQuakeExtensions),
+ })
+ }
+
+ var extensions []*protocol.Extension
+ for _, ext := range cmd.Extensions {
+ if c.fteEnabled && ext.Version == fte.ProtocolVersion {
+ extensions = append(extensions, ext)
+ } else if c.fte2Enabled && ext.Version == fte.ProtocolVersion2 {
+ extensions = append(extensions, ext)
+ } else if c.mvdEnabled && ext.Version == mvd.ProtocolVersion {
+ extensions = append(extensions, ext)
+ }
+ }
+
+ return []command.Command{
+ &connect.Command{
+ Command: "connect",
+ Version: fmt.Sprintf("%v", c.ctx.GetProtocolVersion()),
+ QPort: c.qPort,
+ ChallengeID: cmd.ChallengeID,
+ UserInfo: userInfo,
+ Extensions: extensions,
+ },
+ }
+}
+
+func (c *Client) handleNew() []command.Command {
+ return []command.Command{&stringcmd.Command{String: "new"}}
+}
+
+func (c *Client) handleStufftext(cmd *stufftext.Command) []command.Command {
+ var cmds []command.Command
+
+ for _, a := range args.Parse(cmd.String) {
+ switch a.Cmd {
+ case "reconnect":
+ c.seq.SetState(sequencer.Connecting)
+ cmds = append(cmds, c.handleNew()...)
+ case "cmd":
+ cmds = append(cmds, c.handleStufftextCmds(a.Args)...)
+ case "packet":
+ cmds = append(cmds, &ip.Command{String: a.Args[1][1 : len(a.Args[1])-1]})
+ case "fullserverinfo":
+ cmds = append(cmds, c.handleFullServerInfo(a.Args[0])...)
+ case "skins":
+ c.readDeadline = time.Duration(c.ping)
+ c.seq.SetState(sequencer.Connected)
+ cmds = append(cmds, &stringcmd.Command{
+ String: fmt.Sprintf("begin %d", c.serverCount),
+ })
+ }
+ }
+
+ return cmds
+}
+
+func (c *Client) handleStufftextCmds(args []string) []command.Command {
+ switch args[0] {
+ case "ack", "prespawn", "spawn":
+ return []command.Command{&stringcmd.Command{String: strings.Join(args, " ")}}
+ case "pext":
+ return c.handleProtocolExtensions()
+ case "new":
+ return c.handleNew()
+ }
+
+ return nil
+}
+
+func (c *Client) handleProtocolExtensions() []command.Command {
+ var fteVersion uint32
+ var fteExtensions uint32
+ var fte2Version uint32
+ var fte2Extensions uint32
+ var mvdVersion uint32
+ var mvdExtensions uint32
+
+ if c.fteEnabled {
+ fteVersion = fte.ProtocolVersion
+ fteExtensions = c.fteExtensions
+ }
+ if c.fte2Enabled {
+ fte2Version = fte.ProtocolVersion2
+ fte2Extensions = c.fte2Extensions
+ }
+ if c.mvdEnabled {
+ mvdVersion = mvd.ProtocolVersion
+ mvdExtensions = c.mvdExtensions
+ }
+
+ return []command.Command{
+ &stringcmd.Command{String: fmt.Sprintf("pext 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
+ fteVersion,
+ fteExtensions,
+ fte2Version,
+ fte2Extensions,
+ mvdVersion,
+ mvdExtensions,
+ )},
+ }
+}
+
+func (c *Client) handleFullServerInfo(args string) []command.Command {
+ inf := infostring.Parse(args)
+ c.mapName = inf.Get("map")
+
+ return []command.Command{
+ &stringcmd.Command{String: fmt.Sprintf("soundlist %d 0", c.serverCount)},
+ }
+}
+
+func (c *Client) handleSoundList(cmd *soundlist.Command) []command.Command {
+ nextCmd := "soundlist"
+ if cmd.Index == 0 {
+ nextCmd = "modellist"
+ }
+
+ return []command.Command{
+ &stringcmd.Command{
+ String: fmt.Sprintf("%s %d %d", nextCmd, c.serverCount, cmd.Index),
+ },
+ }
+}
+
+func (c *Client) handleModelList(cmd *modellist.Command) []command.Command {
+ if cmd.Index == 0 {
+ return c.handlePrespawn()
+ }
+
+ return []command.Command{
+ &stringcmd.Command{
+ String: fmt.Sprintf("modellist %d %d", c.serverCount, cmd.Index),
+ },
+ }
+}
+
+func (c *Client) handlePrespawn() []command.Command {
+ hash, ok := protocol.MapChecksum[c.mapName]
+ if !ok {
+ c.logger.Printf("missing map checksum for %s, requesting download", c.mapName)
+
+ // Send download command for the missing map
+ return []command.Command{
+ &download.Command{
+ FTEProtocolExtension: c.fteExtensions,
+ Size16: -1, // -1 signals the server to start sending the file
+ Name: c.mapName, // map filename
+ },
+ }
+ }
+
+ // If hash exists, continue normally
+ return []command.Command{
+ &stringcmd.Command{String: fmt.Sprintf("setinfo pmodel %d", protocol.PlayerModel)},
+ &stringcmd.Command{String: fmt.Sprintf("setinfo emodel %d", protocol.EyeModel)},
+ &stringcmd.Command{String: fmt.Sprintf("prespawn %v 0 %d", c.serverCount, hash)},
+ }
+}