]> git.lizzy.rs Git - hydra-dragonfire.git/commitdiff
Allow multiple clients to share a map
authorElias Fleckenstein <eliasfleckenstein@web.de>
Wed, 1 Jun 2022 16:09:48 +0000 (18:09 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Wed, 1 Jun 2022 16:09:48 +0000 (18:09 +0200)
17 files changed:
auth.go [deleted file]
client.go
comp_auth.go [new file with mode: 0644]
comp_map.go [new file with mode: 0644]
comp_pkts.go [new file with mode: 0644]
doc/auth.md [deleted file]
doc/client.md
doc/comp_auth.md [new file with mode: 0644]
doc/comp_map.md [new file with mode: 0644]
doc/comp_pkts.md [new file with mode: 0644]
doc/hydra.md
doc/map.md
doc/pkts.md [deleted file]
example/print-node.lua
hydra.go
map.go
pkts.go [deleted file]

diff --git a/auth.go b/auth.go
deleted file mode 100644 (file)
index 93af0ee..0000000
--- a/auth.go
+++ /dev/null
@@ -1,245 +0,0 @@
-package main
-
-import (
-       "github.com/HimbeerserverDE/srp"
-       "github.com/anon55555/mt"
-       "github.com/dragonfireclient/hydra-dragonfire/convert"
-       "github.com/yuin/gopher-lua"
-       "strings"
-       "time"
-)
-
-type authState uint8
-
-const (
-       asInit authState = iota
-       asRequested
-       asVerified
-       asActive
-       asError
-)
-
-type Auth struct {
-       client            *Client
-       username          string
-       password          string
-       language          string
-       version           string
-       state             authState
-       err               string
-       srpBytesA, bytesA []byte
-       userdata          *lua.LUserData
-}
-
-var authFuncs = map[string]lua.LGFunction{
-       "username": l_auth_username,
-       "password": l_auth_password,
-       "language": l_auth_language,
-       "version":  l_auth_version,
-       "state":    l_auth_state,
-}
-
-func getAuth(l *lua.LState) *Auth {
-       return l.CheckUserData(1).Value.(*Auth)
-}
-
-func (auth *Auth) create(client *Client, l *lua.LState) {
-       if client.state != csNew {
-               panic("can't add auth component after connect")
-       }
-
-       auth.client = client
-       auth.language = "en_US"
-       auth.version = "hydra-dragonfire"
-       auth.state = asInit
-       auth.userdata = l.NewUserData()
-       auth.userdata.Value = auth
-       l.SetMetatable(auth.userdata, l.GetTypeMetatable("hydra.auth"))
-}
-
-func (auth *Auth) push() lua.LValue {
-       return auth.userdata
-}
-
-func (auth *Auth) connect() {
-       if auth.username == "" {
-               panic("missing username")
-       }
-
-       go func() {
-               for auth.client.state == csConnected && auth.state == asInit {
-                       auth.client.conn.SendCmd(&mt.ToSrvInit{
-                               SerializeVer: serializeVer,
-                               MinProtoVer:  protoVer,
-                               MaxProtoVer:  protoVer,
-                               PlayerName:   auth.username,
-                       })
-                       time.Sleep(500 * time.Millisecond)
-               }
-       }()
-}
-
-func (auth *Auth) fail(err string) {
-       auth.err = err
-       auth.state = asError
-       auth.client.closeConn()
-}
-
-func (auth *Auth) checkState(state authState, pkt *mt.Pkt) bool {
-       if auth.state == state {
-               return true
-       }
-
-       auth.fail("received " + string(convert.PushPktType(pkt)) + " in invalid state")
-       return false
-}
-
-func (auth *Auth) process(pkt *mt.Pkt) {
-       if auth.state == asError {
-               return
-       }
-
-       switch cmd := pkt.Cmd.(type) {
-       case *mt.ToCltHello:
-               if !auth.checkState(asInit, pkt) {
-                       return
-               }
-
-               if cmd.SerializeVer != 28 {
-                       auth.fail("unsupported serialize version")
-                       return
-               }
-
-               if cmd.AuthMethods == mt.FirstSRP {
-                       salt, verifier, err := srp.NewClient([]byte(strings.ToLower(auth.username)), []byte(auth.password))
-                       if err != nil {
-                               auth.fail(err.Error())
-                               return
-                       }
-
-                       auth.client.conn.SendCmd(&mt.ToSrvFirstSRP{
-                               Salt:        salt,
-                               Verifier:    verifier,
-                               EmptyPasswd: auth.password == "",
-                       })
-                       auth.state = asVerified
-               } else if cmd.AuthMethods == mt.SRP {
-                       var err error
-                       auth.srpBytesA, auth.bytesA, err = srp.InitiateHandshake()
-                       if err != nil {
-                               auth.fail(err.Error())
-                               return
-                       }
-
-                       auth.client.conn.SendCmd(&mt.ToSrvSRPBytesA{
-                               A:      auth.srpBytesA,
-                               NoSHA1: true,
-                       })
-                       auth.state = asRequested
-               } else {
-                       auth.fail("invalid auth methods")
-                       return
-               }
-
-       case *mt.ToCltSRPBytesSaltB:
-               if !auth.checkState(asRequested, pkt) {
-                       return
-               }
-
-               srpBytesK, err := srp.CompleteHandshake(auth.srpBytesA, auth.bytesA, []byte(strings.ToLower(auth.username)), []byte(auth.password), cmd.Salt, cmd.B)
-               if err != nil {
-                       auth.fail(err.Error())
-                       return
-               }
-
-               M := srp.ClientProof([]byte(auth.username), cmd.Salt, auth.srpBytesA, cmd.B, srpBytesK)
-               auth.srpBytesA = []byte{}
-               auth.bytesA = []byte{}
-
-               if M == nil {
-                       auth.fail("srp safety check fail")
-                       return
-               }
-
-               auth.client.conn.SendCmd(&mt.ToSrvSRPBytesM{
-                       M: M,
-               })
-               auth.state = asVerified
-
-       case *mt.ToCltAcceptAuth:
-               auth.client.conn.SendCmd(&mt.ToSrvInit2{Lang: auth.language})
-
-       case *mt.ToCltTimeOfDay:
-               if auth.state == asActive {
-                       return
-               }
-
-               if !auth.checkState(asVerified, pkt) {
-                       return
-               }
-
-               auth.client.conn.SendCmd(&mt.ToSrvCltReady{
-                       Major:    5,
-                       Minor:    6,
-                       Patch:    0,
-                       Reserved: 0,
-                       Formspec: 4,
-                       Version:  auth.version,
-               })
-               auth.state = asActive
-       }
-}
-
-func (auth *Auth) accessProperty(l *lua.LState, key string, ptr *string) int {
-       if str, ok := l.Get(2).(lua.LString); ok {
-               if auth.client.state != csNew {
-                       panic("can't change " + key + " after connecting")
-               }
-               *ptr = string(str)
-               return 0
-       } else {
-               l.Push(lua.LString(*ptr))
-               return 1
-       }
-}
-
-func l_auth_username(l *lua.LState) int {
-       auth := getAuth(l)
-       return auth.accessProperty(l, "username", &auth.username)
-}
-
-func l_auth_password(l *lua.LState) int {
-       auth := getAuth(l)
-       return auth.accessProperty(l, "password", &auth.password)
-}
-
-func l_auth_language(l *lua.LState) int {
-       auth := getAuth(l)
-       return auth.accessProperty(l, "language", &auth.language)
-}
-
-func l_auth_version(l *lua.LState) int {
-       auth := getAuth(l)
-       return auth.accessProperty(l, "version", &auth.version)
-}
-
-func l_auth_state(l *lua.LState) int {
-       auth := getAuth(l)
-
-       switch auth.state {
-       case asInit:
-               l.Push(lua.LString("init"))
-       case asRequested:
-               l.Push(lua.LString("requested"))
-       case asVerified:
-               l.Push(lua.LString("verified"))
-       case asActive:
-               l.Push(lua.LString("active"))
-       case asError:
-               l.Push(lua.LString("error"))
-               l.Push(lua.LString(auth.err))
-               return 2
-       }
-
-       return 1
-}
index 4595d131383ac8b2fc746934713be2d7a63b5be7..2e475887629b76eb339c3bd6fcbd3e7e9f5d00e6 100644 (file)
--- a/client.go
+++ b/client.go
@@ -166,8 +166,8 @@ func l_client_connect(l *lua.LState) int {
 
                        if err == nil {
                                client.mu.Lock()
-                               for _, component := range client.components {
-                                       component.process(&pkt)
+                               for _, comp := range client.components {
+                                       comp.process(&pkt)
                                }
                                client.mu.Unlock()
                        } else if errors.Is(err, net.ErrClosed) {
@@ -180,8 +180,8 @@ func l_client_connect(l *lua.LState) int {
        }()
 
        client.mu.Lock()
-       for _, component := range client.components {
-               component.connect()
+       for _, comp := range client.components {
+               comp.connect()
        }
        client.mu.Unlock()
 
@@ -207,22 +207,22 @@ func l_client_enable(l *lua.LState) int {
        defer client.mu.Unlock()
 
        for i := 2; i <= n; i++ {
-               compname := l.CheckString(i)
+               name := l.CheckString(i)
 
-               if component, exists := client.components[compname]; !exists {
-                       switch compname {
+               if comp, exists := client.components[name]; !exists {
+                       switch name {
                        case "auth":
-                               component = &Auth{}
+                               comp = &CompAuth{}
                        case "map":
-                               component = &Map{}
+                               comp = &CompMap{}
                        case "pkts":
-                               component = &Pkts{}
+                               comp = &CompPkts{}
                        default:
-                               panic("invalid component: " + compname)
+                               panic("invalid component: " + name)
                        }
 
-                       client.components[compname] = component
-                       component.create(client, l)
+                       client.components[name] = comp
+                       comp.create(client, l)
                }
        }
 
diff --git a/comp_auth.go b/comp_auth.go
new file mode 100644 (file)
index 0000000..5d3f838
--- /dev/null
@@ -0,0 +1,245 @@
+package main
+
+import (
+       "github.com/HimbeerserverDE/srp"
+       "github.com/anon55555/mt"
+       "github.com/dragonfireclient/hydra-dragonfire/convert"
+       "github.com/yuin/gopher-lua"
+       "strings"
+       "time"
+)
+
+type authState uint8
+
+const (
+       asInit authState = iota
+       asRequested
+       asVerified
+       asActive
+       asError
+)
+
+type CompAuth struct {
+       client            *Client
+       username          string
+       password          string
+       language          string
+       version           string
+       state             authState
+       err               string
+       srpBytesA, bytesA []byte
+       userdata          *lua.LUserData
+}
+
+var compAuthFuncs = map[string]lua.LGFunction{
+       "username": l_comp_auth_username,
+       "password": l_comp_auth_password,
+       "language": l_comp_auth_language,
+       "version":  l_comp_auth_version,
+       "state":    l_comp_auth_state,
+}
+
+func getCompAuth(l *lua.LState) *CompAuth {
+       return l.CheckUserData(1).Value.(*CompAuth)
+}
+
+func (comp *CompAuth) create(client *Client, l *lua.LState) {
+       if client.state != csNew {
+               panic("can't add auth component after connect")
+       }
+
+       comp.client = client
+       comp.language = "en_US"
+       comp.version = "hydra-dragonfire"
+       comp.state = asInit
+       comp.userdata = l.NewUserData()
+       comp.userdata.Value = comp
+       l.SetMetatable(comp.userdata, l.GetTypeMetatable("hydra.comp.auth"))
+}
+
+func (comp *CompAuth) push() lua.LValue {
+       return comp.userdata
+}
+
+func (comp *CompAuth) connect() {
+       if comp.username == "" {
+               panic("missing username")
+       }
+
+       go func() {
+               for comp.client.state == csConnected && comp.state == asInit {
+                       comp.client.conn.SendCmd(&mt.ToSrvInit{
+                               SerializeVer: serializeVer,
+                               MinProtoVer:  protoVer,
+                               MaxProtoVer:  protoVer,
+                               PlayerName:   comp.username,
+                       })
+                       time.Sleep(500 * time.Millisecond)
+               }
+       }()
+}
+
+func (comp *CompAuth) fail(err string) {
+       comp.err = err
+       comp.state = asError
+       comp.client.closeConn()
+}
+
+func (comp *CompAuth) checkState(state authState, pkt *mt.Pkt) bool {
+       if comp.state == state {
+               return true
+       }
+
+       comp.fail("received " + string(convert.PushPktType(pkt)) + " in invalid state")
+       return false
+}
+
+func (comp *CompAuth) process(pkt *mt.Pkt) {
+       if comp.state == asError {
+               return
+       }
+
+       switch cmd := pkt.Cmd.(type) {
+       case *mt.ToCltHello:
+               if !comp.checkState(asInit, pkt) {
+                       return
+               }
+
+               if cmd.SerializeVer != 28 {
+                       comp.fail("unsupported serialize version")
+                       return
+               }
+
+               if cmd.AuthMethods == mt.FirstSRP {
+                       salt, verifier, err := srp.NewClient([]byte(strings.ToLower(comp.username)), []byte(comp.password))
+                       if err != nil {
+                               comp.fail(err.Error())
+                               return
+                       }
+
+                       comp.client.conn.SendCmd(&mt.ToSrvFirstSRP{
+                               Salt:        salt,
+                               Verifier:    verifier,
+                               EmptyPasswd: comp.password == "",
+                       })
+                       comp.state = asVerified
+               } else if cmd.AuthMethods == mt.SRP {
+                       var err error
+                       comp.srpBytesA, comp.bytesA, err = srp.InitiateHandshake()
+                       if err != nil {
+                               comp.fail(err.Error())
+                               return
+                       }
+
+                       comp.client.conn.SendCmd(&mt.ToSrvSRPBytesA{
+                               A:      comp.srpBytesA,
+                               NoSHA1: true,
+                       })
+                       comp.state = asRequested
+               } else {
+                       comp.fail("invalid auth methods")
+                       return
+               }
+
+       case *mt.ToCltSRPBytesSaltB:
+               if !comp.checkState(asRequested, pkt) {
+                       return
+               }
+
+               srpBytesK, err := srp.CompleteHandshake(comp.srpBytesA, comp.bytesA, []byte(strings.ToLower(comp.username)), []byte(comp.password), cmd.Salt, cmd.B)
+               if err != nil {
+                       comp.fail(err.Error())
+                       return
+               }
+
+               M := srp.ClientProof([]byte(comp.username), cmd.Salt, comp.srpBytesA, cmd.B, srpBytesK)
+               comp.srpBytesA = []byte{}
+               comp.bytesA = []byte{}
+
+               if M == nil {
+                       comp.fail("srp safety check fail")
+                       return
+               }
+
+               comp.client.conn.SendCmd(&mt.ToSrvSRPBytesM{
+                       M: M,
+               })
+               comp.state = asVerified
+
+       case *mt.ToCltAcceptAuth:
+               comp.client.conn.SendCmd(&mt.ToSrvInit2{Lang: comp.language})
+
+       case *mt.ToCltTimeOfDay:
+               if comp.state == asActive {
+                       return
+               }
+
+               if !comp.checkState(asVerified, pkt) {
+                       return
+               }
+
+               comp.client.conn.SendCmd(&mt.ToSrvCltReady{
+                       Major:    5,
+                       Minor:    6,
+                       Patch:    0,
+                       Reserved: 0,
+                       Formspec: 4,
+                       Version:  comp.version,
+               })
+               comp.state = asActive
+       }
+}
+
+func (comp *CompAuth) accessProperty(l *lua.LState, key string, ptr *string) int {
+       if str, ok := l.Get(2).(lua.LString); ok {
+               if comp.client.state != csNew {
+                       panic("can't change " + key + " after connecting")
+               }
+               *ptr = string(str)
+               return 0
+       } else {
+               l.Push(lua.LString(*ptr))
+               return 1
+       }
+}
+
+func l_comp_auth_username(l *lua.LState) int {
+       comp := getCompAuth(l)
+       return comp.accessProperty(l, "username", &comp.username)
+}
+
+func l_comp_auth_password(l *lua.LState) int {
+       comp := getCompAuth(l)
+       return comp.accessProperty(l, "password", &comp.password)
+}
+
+func l_comp_auth_language(l *lua.LState) int {
+       comp := getCompAuth(l)
+       return comp.accessProperty(l, "language", &comp.language)
+}
+
+func l_comp_auth_version(l *lua.LState) int {
+       comp := getCompAuth(l)
+       return comp.accessProperty(l, "version", &comp.version)
+}
+
+func l_comp_auth_state(l *lua.LState) int {
+       comp := getCompAuth(l)
+
+       switch comp.state {
+       case asInit:
+               l.Push(lua.LString("init"))
+       case asRequested:
+               l.Push(lua.LString("requested"))
+       case asVerified:
+               l.Push(lua.LString("verified"))
+       case asActive:
+               l.Push(lua.LString("active"))
+       case asError:
+               l.Push(lua.LString("error"))
+               l.Push(lua.LString(comp.err))
+               return 2
+       }
+
+       return 1
+}
diff --git a/comp_map.go b/comp_map.go
new file mode 100644 (file)
index 0000000..c45155e
--- /dev/null
@@ -0,0 +1,52 @@
+package main
+
+import (
+       "github.com/anon55555/mt"
+       "github.com/yuin/gopher-lua"
+)
+
+type CompMap struct {
+       client   *Client
+       mapdata *Map
+       userdata *lua.LUserData
+}
+
+var compMapFuncs = map[string]lua.LGFunction{
+       "get": l_comp_map_get,
+       "set": l_comp_map_set,
+}
+
+func getCompMap(l *lua.LState) *CompMap {
+       return l.CheckUserData(1).Value.(*CompMap)
+}
+
+func (comp *CompMap) create(client *Client, l *lua.LState) {
+       comp.client = client
+       comp.mapdata = newMap(l)
+       comp.userdata = l.NewUserData()
+       comp.userdata.Value = comp
+       l.SetMetatable(comp.userdata, l.GetTypeMetatable("hydra.comp.map"))
+}
+
+func (comp *CompMap) push() lua.LValue {
+       return comp.userdata
+}
+
+func (comp *CompMap) connect() {
+}
+
+func (comp *CompMap) process(pkt *mt.Pkt) {
+       comp.mapdata.process(comp.client, pkt)
+}
+
+func l_comp_map_set(l *lua.LState) int {
+       comp := getCompMap(l)
+       comp.mapdata = getMap(l, 2)
+       return 0
+}
+
+func l_comp_map_get(l *lua.LState) int {
+       comp := getCompMap(l)
+       l.Push(comp.mapdata.userdata)
+       return 1
+}
diff --git a/comp_pkts.go b/comp_pkts.go
new file mode 100644 (file)
index 0000000..37e2284
--- /dev/null
@@ -0,0 +1,99 @@
+package main
+
+import (
+       "github.com/anon55555/mt"
+       "github.com/dragonfireclient/hydra-dragonfire/convert"
+       "github.com/yuin/gopher-lua"
+       "sync"
+)
+
+type CompPkts struct {
+       client     *Client
+       mu         sync.Mutex
+       wildcard   bool
+       subscribed map[string]struct{}
+       userdata   *lua.LUserData
+}
+
+var compPktsFuncs = map[string]lua.LGFunction{
+       "subscribe":   l_comp_pkts_subscribe,
+       "unsubscribe": l_comp_pkts_unsubscribe,
+       "wildcard":    l_comp_pkts_wildcard,
+}
+
+type EventPkt struct {
+       pktType string
+       pktData *mt.Pkt
+}
+
+func (evt EventPkt) handle(l *lua.LState, val lua.LValue) {
+       l.SetField(val, "type", lua.LString("pkt"))
+       l.SetField(val, "pkt_type", lua.LString(evt.pktType))
+       l.SetField(val, "pkt_data", convert.PushPkt(l, evt.pktData))
+}
+
+func getCompPkts(l *lua.LState) *CompPkts {
+       return l.CheckUserData(1).Value.(*CompPkts)
+}
+
+func (comp *CompPkts) create(client *Client, l *lua.LState) {
+       comp.client = client
+       comp.wildcard = false
+       comp.subscribed = map[string]struct{}{}
+       comp.userdata = l.NewUserData()
+       comp.userdata.Value = comp
+       l.SetMetatable(comp.userdata, l.GetTypeMetatable("hydra.comp.pkts"))
+}
+
+func (comp *CompPkts) push() lua.LValue {
+       return comp.userdata
+}
+
+func (comp *CompPkts) connect() {
+}
+
+func (comp *CompPkts) process(pkt *mt.Pkt) {
+       pktType := string(convert.PushPktType(pkt))
+
+       comp.mu.Lock()
+       _, subscribed := comp.subscribed[pktType]
+       comp.mu.Unlock()
+
+       if subscribed || comp.wildcard {
+               comp.client.queue <- EventPkt{pktType: pktType, pktData: pkt}
+       }
+}
+
+func l_comp_pkts_subscribe(l *lua.LState) int {
+       comp := getCompPkts(l)
+       n := l.GetTop()
+
+       comp.mu.Lock()
+       defer comp.mu.Unlock()
+
+       for i := 2; i <= n; i++ {
+               comp.subscribed[l.CheckString(i)] = struct{}{}
+       }
+
+       return 0
+}
+
+func l_comp_pkts_unsubscribe(l *lua.LState) int {
+       comp := getCompPkts(l)
+       n := l.GetTop()
+
+       comp.mu.Lock()
+       defer comp.mu.Unlock()
+
+       for i := 2; i <= n; i++ {
+               delete(comp.subscribed, l.CheckString(i))
+       }
+
+       return 0
+}
+
+func l_comp_pkts_wildcard(l *lua.LState) int {
+       comp := getCompPkts(l)
+       comp.wildcard = l.ToBool(2)
+       return 0
+}
diff --git a/doc/auth.md b/doc/auth.md
deleted file mode 100644 (file)
index f496632..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-# Authentication Component
-Source code: [auth.go](../auth.go)
-
-Handles packets necessary to complete authentication and responds with according packets. Sends the `init` packet unpon connection open.
-Invalid packets related to auth received from server or detected incompabilities may result in the client being closed. In this case, an error state is set that can be read using the `self:state()` method.
-
-**Important: ** the auth component does not automatically disconnect if authentication fails due to an invalid password or already being logged in; it is up to the API user to handle these cases by subscribing to the `kick` and `legacy_kick` packets.
-
-Authentication handles the `hello`, `srp_bytes_salt_b`, `accept_auth` and `time_of_day` packets (the last one is only handled when received the first time and sets the state to active).
-
-Authentication may send `init`, `first_srp`, `srp_bytes_a`, `srp_bytes_m`, `init2` and `ready` packets.
-
-## Functions
-
-`self:username([username])`: Sets or gets the username (string). Setting may not occur after having connected the client. A username must be set before connecting.
-`self:password([password])`: Sets or gets the password (string). Setting may not occur after having connected the client. By default, an empty password is used.
-`self:language([language])`: Sets or gets the language sent to server. Setting may not occur after having connected the client. By default, "en_US" is used.
-`self:version([version])`: Sets or gets the version string sent to server. Setting may not occur after having connected the client. By default, "hydra-dragonfire" is used.
-`self:state()`: Returns `state, error`. State is one of "init", "requested", "verified", "active", "error". If state is "error", error is a string containing a description of the problem that occured. Otherwise, error is nil.
index fe9fb7d9878f7ab7ab88336e57d9e144cc4dc78f..0886ecf7ab0012bf2d0fc72e2ee1bd5b99c9995c 100644 (file)
@@ -18,6 +18,6 @@ After being disconnect, a client cannot be reconnected.
 
 Enabled components can be accessed by using `self.<component name>`.
 
-- `self.pkt`: Allows you to handle selected packets yourself. Most scripts use this. See [pkts.md](pkts.md).
-- `self.auth`: Handles authentication. Recommended for the vast majority of scripts. See [auth.md](auth.md).
-- `self.map`: Stores MapBlocks received from server. See [map.md](map.md).
+- `self.pkt`: Allows you to handle selected packets yourself. Most scripts use this. See [comp_pkts.md](comp_pkts.md).
+- `self.auth`: Handles authentication. Recommended for the vast majority of scripts. See [comp_auth.md](comp_auth.md).
+- `self.map`: Stores MapBlocks received from server. See [comp_map.md](comp_map.md).
diff --git a/doc/comp_auth.md b/doc/comp_auth.md
new file mode 100644 (file)
index 0000000..94e4824
--- /dev/null
@@ -0,0 +1,18 @@
+# Authentication Component
+Source code: [comp_auth.go](../comp_auth.go)
+
+Handles packets necessary to complete authentication and responds with according packets. Sends the `init` packet unpon connection open.
+Invalid packets related to auth received from server or detected incompabilities may result in the client being closed. In this case, an error state is set that can be read using the `self:state()` method.
+
+**Important: ** the auth component does not automatically disconnect if authentication fails due to an invalid password or already being logged in; it is up to the API user to handle these cases by subscribing to the `kick` and `legacy_kick` packets.
+
+Handles the `hello`, `srp_bytes_salt_b`, `accept_auth` and `time_of_day` packets (the last one is only handled when received the first time and sets the state to active).
+May send `init`, `first_srp`, `srp_bytes_a`, `srp_bytes_m`, `init2` and `ready` packets.
+
+## Functions
+
+`self:username([username])`: Sets or gets the username (string). Setting may not occur after having connected the client. A username must be set before connecting.
+`self:password([password])`: Sets or gets the password (string). Setting may not occur after having connected the client. By default, an empty password is used.
+`self:language([language])`: Sets or gets the language sent to server. Setting may not occur after having connected the client. By default, "en_US" is used.
+`self:version([version])`: Sets or gets the version string sent to server. Setting may not occur after having connected the client. By default, "hydra-dragonfire" is used.
+`self:state()`: Returns `state, error`. State is one of "init", "requested", "verified", "active", "error". If state is "error", error is a string containing a description of the problem that occured. Otherwise, error is nil.
diff --git a/doc/comp_map.md b/doc/comp_map.md
new file mode 100644 (file)
index 0000000..1c72a34
--- /dev/null
@@ -0,0 +1,16 @@
+# Map Component
+Source code: [comp_map.go](../comp_map.go)
+
+The Map component stores a reference to a `hydra.map` (See [map.md](map.md)).
+
+Initially, an empty map is created. You can replace this by a map reference obtained from `hydra.map` however: this way, multiple clients can share a map and explore different areas of it.
+
+Handles the `blk_data` and `node_metas_changed` packets.
+May send `got_blks` packets.
+
+## Functions
+
+`self:set(mapref)`: Data will be stored in `mapref` in the future.
+
+`self:get()`: Returns the current `mapref`.
+
diff --git a/doc/comp_pkts.md b/doc/comp_pkts.md
new file mode 100644 (file)
index 0000000..a5be670
--- /dev/null
@@ -0,0 +1,18 @@
+# Packets Component
+Source code: [pkts.go](../pkts.go)
+
+The packets component allows you to handle packets yourself. It fires events in the form of `{ type = "pkt", client = ..., pkt_type = "...", pkt_data = { ... } }``` when subscribed packets are received.
+For available packets, see [client_pkts.md](client_pkts.md). By default, no packets are packets subscribed.
+
+## Wildcard mode
+
+If wildcard is enabled, events for all packets are fired, even ones that are not subscribed. It is not recommended to use this without a reason since converting packets to Lua costs performance and creates and overhead due to poll returning more often. `wildcard` is unnecessary if only certain packets are handled anyway, but it is useful for traffic inspection and debugging.
+
+## Functions
+
+- `self:subscribe(pkt1, [pkt2, ...])`: Subscribes to all packet types passed as arguments (strings).
+
+- `self:unsubscribe(pkt1, [pkt2, ...])`: Unsubscribes from all packet passed as arguments (strings).
+
+- `self:wildcard(wildcard)`: Sets wildcard mode to `wildcard` (boolean).
+
index fd916e07fbd481c6ca3067cfbc687b0a5785105c..90083bc569fbb3e414ee60bef68bb7b106ebfa8f 100644 (file)
@@ -12,6 +12,7 @@ The `hydra` table contains functions necessary to handle connections.
 ## Functions
 
 - `hydra.client(address)`: Returns a new client. Address must be a string. For client functions, see [client.md](client.md).
+- `hydra.map()`: Return a new map. For map functions, see [map.md](map.md).
 - `hydra.dtime()`: Utility function that turns the elapsed time in seconds (floating point) since it was last called (or since program start).
 - `hydra.poll(clients, [timeout])`: Polls events from all clients in `clients` (table). For behavior and return value, see [poll.md](poll.md).
 - `hydra.close(clients)`: Closes all clients in `clients` (table) that are currently connected. See `client:close()` in [client.md](client.md) for more info.
index 26fd51a7e57ea2c25104fd8e30951c464d95ef03..9e857c55dc2679ff50b991f9d58663dce324a214 100644 (file)
@@ -1,13 +1,10 @@
-# Map Component
+# Map interface
 Source code: [map.go](../map.go)
 
-Map handles the `blk_data` and `node_metas_changed` packets.
-Map may send `got_blks`, `deleted_blks` packets.
+A map stores mapblocks received from server. To be of any use, a map needs to be added to one or more clients. See [comp_map.md](comp_map.md) and [client.md](client.md).
 
 ## Functions
 
-`self:clear()`: Forget all blocks.
-
 `self:block(blkpos)`: Return the `map_blk` at `blkpos` as found in the `blk_data` packet (See [client_pkts.md](client_pkts.md)). `nil` if block is not present.
 
 `self:node(pos)`: Return a node in the form `{ param0 = 126, param1 = 0, param2 = 0, meta = { ... } }`. The meta field is a `node_meta` as found in the `blk_data` packet. `nil` if node is not present.
diff --git a/doc/pkts.md b/doc/pkts.md
deleted file mode 100644 (file)
index fba5083..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# Packet Handler Component
-Source code: [pkts.go](../pkts.go)
-
-The packet handler component allows you to handle packets yourself. It fires events in the form of `{ type = "pkt", client = ..., pkt_type = "...", pkt_data = { ... } }``` when subscribed packets are received.
-For available packets, see [client_pkts.md](client_pkts.md). By default, not packets are packets subscribed.
-
-## Wildcard mode
-
-If wildcard is enabled, events for all packets are fired, even ones that are not subscribed. It is not recommended to use this without a reason since converting packets to Lua costs performance and creates and overhead due to poll returning more often. `wildcard` is unnecessary if only certain packets are handled anyway, but it is useful for traffic inspection and debugging.
-
-## Functions
-
-- `self:subscribe(pkt1, [pkt2, ...])`: Subscribes to all packet types passed as arguments (strings).
-
-- `self:unsubscribe(pkt1, [pkt2, ...])`: Unsubscribes from all packet passed as arguments (strings).
-
-- `self:wildcard(wildcard)`: Sets wildcard mode to `wildcard` (boolean).
-
index d72f4744476d665a6de0620b957039c7040a3a4f..30d2506d05587712300468d7547d87190db0fde1 100755 (executable)
@@ -16,7 +16,7 @@ while true do
        elseif evt.type == "pkt" then
                pos = (evt.pkt_data.pos / hydra.BS + vec3(0, -1, 0)):round()
        elseif evt.type == "timeout" and pos then
-               local node = client.map:node(pos)
+               local node = client.map:get():node(pos)
                print(pos, node and node.param0)
        end
 end
index 7f7b2dac28e563bb8ced73b410711b868971673f..6da1822cc295ef5548288846c61a2b222efce393 100644 (file)
--- a/hydra.go
+++ b/hydra.go
@@ -40,6 +40,7 @@ var builtinFiles = []string{
 
 var hydraFuncs = map[string]lua.LGFunction{
        "client": l_client,
+       "map": l_map,
        "dtime":  l_dtime,
        "poll":   l_poll,
        "close":  l_close,
@@ -86,10 +87,12 @@ func main() {
        l.SetField(hydra, "proto_ver", lua.LNumber(protoVer))
        l.SetGlobal("hydra", hydra)
 
-       l.SetField(l.NewTypeMetatable("hydra.auth"), "__index", l.SetFuncs(l.NewTable(), authFuncs))
        l.SetField(l.NewTypeMetatable("hydra.client"), "__index", l.NewFunction(l_client_index))
        l.SetField(l.NewTypeMetatable("hydra.map"), "__index", l.SetFuncs(l.NewTable(), mapFuncs))
-       l.SetField(l.NewTypeMetatable("hydra.pkts"), "__index", l.SetFuncs(l.NewTable(), pktsFuncs))
+
+       l.SetField(l.NewTypeMetatable("hydra.comp.auth"), "__index", l.SetFuncs(l.NewTable(), compAuthFuncs))
+       l.SetField(l.NewTypeMetatable("hydra.comp.map"), "__index", l.SetFuncs(l.NewTable(), compMapFuncs))
+       l.SetField(l.NewTypeMetatable("hydra.comp.pkts"), "__index", l.SetFuncs(l.NewTable(), compPktsFuncs))
 
        for _, str := range builtinFiles {
                if err := l.DoString(str); err != nil {
diff --git a/map.go b/map.go
index 08b97ec1bef9eb736078292c6cc8a329b9c2d59c..1ffae233dc6940a63cf58c1ca343261bc845c464 100644 (file)
--- a/map.go
+++ b/map.go
@@ -8,74 +8,54 @@ import (
 )
 
 type Map struct {
-       client   *Client
        mu       sync.Mutex
        blocks   map[[3]int16]*mt.MapBlk
        userdata *lua.LUserData
 }
 
 var mapFuncs = map[string]lua.LGFunction{
-       "clear": l_map_clear,
        "block": l_map_block,
        "node":  l_map_node,
 }
 
-func getMap(l *lua.LState) *Map {
-       return l.CheckUserData(1).Value.(*Map)
+func getMap(l *lua.LState, idx int) *Map {
+       return l.CheckUserData(idx).Value.(*Map)
 }
 
-func (mtmap *Map) create(client *Client, l *lua.LState) {
-       mtmap.client = client
-       mtmap.blocks = map[[3]int16]*mt.MapBlk{}
-       mtmap.userdata = l.NewUserData()
-       mtmap.userdata.Value = mtmap
-       l.SetMetatable(mtmap.userdata, l.GetTypeMetatable("hydra.map"))
+func newMap(l *lua.LState) *Map {
+       mp := &Map{}
+       mp.blocks = map[[3]int16]*mt.MapBlk{}
+       mp.userdata = l.NewUserData()
+       mp.userdata.Value = mp
+       l.SetMetatable(mp.userdata, l.GetTypeMetatable("hydra.map"))
+       return mp
 }
 
-func (mtmap *Map) push() lua.LValue {
-       return mtmap.userdata
-}
-
-func (mtmap *Map) connect() {
-}
-
-func (mtmap *Map) process(pkt *mt.Pkt) {
+func (mp *Map) process(client *Client, pkt *mt.Pkt) {
        switch cmd := pkt.Cmd.(type) {
        case *mt.ToCltBlkData:
-               mtmap.mu.Lock()
-               mtmap.blocks[cmd.Blkpos] = &cmd.Blk
-               mtmap.client.conn.SendCmd(&mt.ToSrvGotBlks{Blks: [][3]int16{cmd.Blkpos}})
-               mtmap.mu.Unlock()
+               mp.mu.Lock()
+               mp.blocks[cmd.Blkpos] = &cmd.Blk
+               mp.mu.Unlock()
+               client.conn.SendCmd(&mt.ToSrvGotBlks{Blks: [][3]int16{cmd.Blkpos}})
        }
 }
 
-func l_map_clear(l *lua.LState) int {
-       mtmap := getMap(l)
-
-       mtmap.mu.Lock()
-       defer mtmap.mu.Unlock()
-
-       var cmd mt.ToSrvDeletedBlks
-       for pos := range mtmap.blocks {
-               cmd.Blks = append(cmd.Blks, pos)
-       }
-
-       mtmap.blocks = map[[3]int16]*mt.MapBlk{}
-
-       mtmap.client.conn.SendCmd(&cmd)
-
-       return 0
+func l_map(l *lua.LState) int {
+       mp := newMap(l)
+       l.Push(mp.userdata)
+       return 1
 }
 
 func l_map_block(l *lua.LState) int {
-       mtmap := getMap(l)
+       mp := getMap(l, 1)
        var blkpos [3]int16
        convert.ReadVec3Int16(l, l.Get(2), &blkpos)
 
-       mtmap.mu.Lock()
-       defer mtmap.mu.Unlock()
+       mp.mu.Lock()
+       defer mp.mu.Unlock()
 
-       block, ok := mtmap.blocks[blkpos]
+       block, ok := mp.blocks[blkpos]
        if ok {
                l.Push(convert.PushMapBlk(l, *block))
        } else {
@@ -86,16 +66,16 @@ func l_map_block(l *lua.LState) int {
 }
 
 func l_map_node(l *lua.LState) int {
-       mtmap := getMap(l)
+       mp := getMap(l, 1)
 
        var pos [3]int16
        convert.ReadVec3Int16(l, l.Get(2), &pos)
        blkpos, i := mt.Pos2Blkpos(pos)
 
-       mtmap.mu.Lock()
-       defer mtmap.mu.Unlock()
+       mp.mu.Lock()
+       defer mp.mu.Unlock()
 
-       block, block_exists := mtmap.blocks[blkpos]
+       block, block_exists := mp.blocks[blkpos]
        if block_exists {
                meta, meta_exists := block.NodeMetas[i]
                if !meta_exists {
diff --git a/pkts.go b/pkts.go
deleted file mode 100644 (file)
index e34f3b8..0000000
--- a/pkts.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package main
-
-import (
-       "github.com/anon55555/mt"
-       "github.com/dragonfireclient/hydra-dragonfire/convert"
-       "github.com/yuin/gopher-lua"
-       "sync"
-)
-
-type Pkts struct {
-       client     *Client
-       mu         sync.Mutex
-       wildcard   bool
-       subscribed map[string]struct{}
-       userdata   *lua.LUserData
-}
-
-var pktsFuncs = map[string]lua.LGFunction{
-       "subscribe":   l_pkts_subscribe,
-       "unsubscribe": l_pkts_unsubscribe,
-       "wildcard":    l_pkts_wildcard,
-}
-
-type EventPkt struct {
-       pktType string
-       pktData *mt.Pkt
-}
-
-func (evt EventPkt) handle(l *lua.LState, val lua.LValue) {
-       l.SetField(val, "type", lua.LString("pkt"))
-       l.SetField(val, "pkt_type", lua.LString(evt.pktType))
-       l.SetField(val, "pkt_data", convert.PushPkt(l, evt.pktData))
-}
-
-func getPkts(l *lua.LState) *Pkts {
-       return l.CheckUserData(1).Value.(*Pkts)
-}
-
-func (pkts *Pkts) create(client *Client, l *lua.LState) {
-       pkts.client = client
-       pkts.wildcard = false
-       pkts.subscribed = map[string]struct{}{}
-       pkts.userdata = l.NewUserData()
-       pkts.userdata.Value = pkts
-       l.SetMetatable(pkts.userdata, l.GetTypeMetatable("hydra.pkts"))
-}
-
-func (pkts *Pkts) push() lua.LValue {
-       return pkts.userdata
-}
-
-func (pkts *Pkts) connect() {
-}
-
-func (pkts *Pkts) process(pkt *mt.Pkt) {
-       pktType := string(convert.PushPktType(pkt))
-
-       pkts.mu.Lock()
-       _, subscribed := pkts.subscribed[pktType]
-       pkts.mu.Unlock()
-
-       if subscribed || pkts.wildcard {
-               pkts.client.queue <- EventPkt{pktType: pktType, pktData: pkt}
-       }
-}
-
-func l_pkts_subscribe(l *lua.LState) int {
-       pkts := getPkts(l)
-       n := l.GetTop()
-
-       pkts.mu.Lock()
-       defer pkts.mu.Unlock()
-
-       for i := 2; i <= n; i++ {
-               pkts.subscribed[l.CheckString(i)] = struct{}{}
-       }
-
-       return 0
-}
-
-func l_pkts_unsubscribe(l *lua.LState) int {
-       pkts := getPkts(l)
-       n := l.GetTop()
-
-       pkts.mu.Lock()
-       defer pkts.mu.Unlock()
-
-       for i := 2; i <= n; i++ {
-               delete(pkts.subscribed, l.CheckString(i))
-       }
-
-       return 0
-}
-
-func l_pkts_wildcard(l *lua.LState) int {
-       pkts := getPkts(l)
-       pkts.wildcard = l.ToBool(2)
-       return 0
-}