]> git.lizzy.rs Git - hydra-dragonfire.git/commitdiff
Initial commit
authorElias Fleckenstein <eliasfleckenstein@web.de>
Sat, 28 May 2022 13:00:35 +0000 (15:00 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Sat, 28 May 2022 13:00:35 +0000 (15:00 +0200)
17 files changed:
.gitignore [new file with mode: 0644]
auth.go [new file with mode: 0644]
builtin/vector.lua [new file with mode: 0644]
callbacks.go [new file with mode: 0644]
client.go [new file with mode: 0644]
convert.go [new file with mode: 0644]
go.mod [new file with mode: 0644]
go.sum [new file with mode: 0644]
hydra.go [new file with mode: 0644]
mkconvert.lua [new file with mode: 0755]
poll.go [new file with mode: 0644]
spec/casemap [new file with mode: 0644]
spec/client/enum [new file with mode: 0644]
spec/client/flag [new file with mode: 0644]
spec/client/pkt [new file with mode: 0644]
spec/client/struct [new file with mode: 0644]
types.go [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..83f0206
--- /dev/null
@@ -0,0 +1 @@
+hydra
diff --git a/auth.go b/auth.go
new file mode 100644 (file)
index 0000000..3e9cb9b
--- /dev/null
+++ b/auth.go
@@ -0,0 +1,254 @@
+package main
+
+import (
+       "github.com/HimbeerserverDE/srp"
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+       "strings"
+       "time"
+)
+
+type authState uint8
+
+const (
+       asInit authState = iota
+       asRequested
+       asVerified
+       asActive
+       asError
+)
+
+type Auth struct {
+       client            *Client
+       username          string
+       password          string
+       language          string
+       state             authState
+       err               string
+       srpBytesA, bytesA []byte
+}
+
+func getAuth(l *lua.State) *Auth {
+       return lua.CheckUserData(l, 1, "hydra.auth").(*Auth)
+}
+
+func (auth *Auth) create(client *Client) {
+       auth.client = client
+       auth.language = "en_US"
+       auth.state = asInit
+}
+
+func (auth *Auth) push(l *lua.State) {
+       l.PushUserData(auth)
+
+       if lua.NewMetaTable(l, "hydra.auth") {
+               lua.NewLibrary(l, []lua.RegistryFunction{
+                       {Name: "username", Function: l_auth_username},
+                       {Name: "password", Function: l_auth_password},
+                       {Name: "language", Function: l_auth_language},
+                       {Name: "state", Function: l_auth_state},
+               })
+               l.SetField(-2, "__index")
+       }
+       l.SetMetaTable(-2)
+}
+
+func (auth *Auth) canConnect() (bool, string) {
+       if auth.username == "" {
+               return false, "missing username"
+       }
+
+       return true, ""
+}
+
+func (auth *Auth) connect() {
+       go func() {
+               for auth.state == asInit && auth.client.state == csConnected {
+                       auth.client.conn.SendCmd(&mt.ToSrvInit{
+                               SerializeVer: 28,
+                               MinProtoVer:  39,
+                               MaxProtoVer:  39,
+                               PlayerName:   auth.username,
+                       })
+                       time.Sleep(500 * time.Millisecond)
+               }
+       }()
+}
+
+func (auth *Auth) setError(err string) {
+       auth.state = asError
+       auth.err = err
+       auth.client.conn.Close()
+}
+
+func (auth *Auth) checkState(state authState, pkt *mt.Pkt) bool {
+       if auth.state == state {
+               return true
+       }
+
+       auth.setError("received " + pktToString(pkt) + " in invalid state")
+       return false
+}
+
+func (auth *Auth) handle(pkt *mt.Pkt, l *lua.State, idx int) {
+       if pkt == nil {
+               return
+       }
+
+       switch cmd := pkt.Cmd.(type) {
+       case *mt.ToCltHello:
+               if !auth.checkState(asInit, pkt) {
+                       return
+               }
+
+               if cmd.SerializeVer != 28 {
+                       auth.setError("unsupported serialize_ver")
+                       return
+               }
+
+               if cmd.AuthMethods == mt.FirstSRP {
+                       salt, verifier, err := srp.NewClient([]byte(strings.ToLower(auth.username)), []byte(auth.password))
+                       if err != nil {
+                               auth.setError(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.setError(err.Error())
+                               return
+                       }
+
+                       auth.client.conn.SendCmd(&mt.ToSrvSRPBytesA{
+                               A:      auth.srpBytesA,
+                               NoSHA1: true,
+                       })
+                       auth.state = asRequested
+               } else {
+                       auth.setError("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.setError(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.setError("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:  "hydra-dragonfire",
+                       Version: "astolfo",
+               })
+               auth.state = asActive
+       }
+}
+
+func l_auth_username(l *lua.State) int {
+       auth := getAuth(l)
+
+       if l.IsString(2) {
+               if auth.client.state > csNew {
+                       panic("can't change username after connecting")
+               }
+               auth.username = lua.CheckString(l, 2)
+               return 0
+       } else {
+               l.PushString(auth.username)
+               return 1
+       }
+}
+
+func l_auth_password(l *lua.State) int {
+       auth := getAuth(l)
+
+       if l.IsString(2) {
+               if auth.client.state > csNew {
+                       panic("can't change password after connecting")
+               }
+               auth.password = lua.CheckString(l, 2)
+               return 0
+       } else {
+               l.PushString(auth.password)
+               return 1
+       }
+}
+
+func l_auth_language(l *lua.State) int {
+       auth := getAuth(l)
+
+       if l.IsString(2) {
+               if auth.client.state > csNew {
+                       panic("can't change language after connecting")
+               }
+               auth.language = lua.CheckString(l, 2)
+               return 0
+       } else {
+               l.PushString(auth.language)
+               return 1
+       }
+}
+
+func l_auth_state(l *lua.State) int {
+       auth := getAuth(l)
+
+       switch auth.state {
+       case asInit:
+               l.PushString("init")
+       case asRequested:
+               l.PushString("requested")
+       case asVerified:
+               l.PushString("verified")
+       case asActive:
+               l.PushString("active")
+       case asError:
+               l.PushString("error")
+               l.PushString(auth.err)
+               return 2
+       }
+
+       return 1
+}
diff --git a/builtin/vector.lua b/builtin/vector.lua
new file mode 100644 (file)
index 0000000..2fd926a
--- /dev/null
@@ -0,0 +1,126 @@
+--[[ builtin/vector.lua ]]--
+
+local function wrap(op, body_wrapper, ...)
+       return load("return function(a, b) " .. body_wrapper(op, ...) .. "end")()
+end
+
+local function arith_mt(...)
+       return {
+               __add = wrap("+", ...),
+               __sub = wrap("-", ...),
+               __mul = wrap("*", ...),
+               __div = wrap("/", ...),
+               __mod = wrap("%", ...),
+       }
+end
+
+-- vec2
+
+local mt_vec2 = arith_mt(function(op)
+       return [[
+               if type(b) == "number" then
+                       return vec2(a.x ]] .. op.. [[ b, a.y ]] .. op .. [[ b)
+               else
+                       return vec2(a.x ]] .. op.. [[ b.x, a.y ]] .. op.. [[ b.y)
+               end
+       ]]
+end)
+
+function mt_vec2:__neg()
+       return vec2(-self.x, -self.y)
+end
+
+function mt_vec2:__tostring()
+       return "(" .. self.x .. ", " .. self.y .. ")"
+end
+
+function vec2(a, b)
+       local o = {}
+
+       if type(a) == "number" then
+               o.x = a
+               o.y = b or a
+       else
+               o.x = a.x
+               o.y = a.y
+       end
+
+       setmetatable(o, mt_vec2)
+       return o
+end
+
+-- vec3
+
+local mt_vec3 = arith_mt(function(op)
+       return [[
+               if type(b) == "number" then
+                       return vec3(a.x ]] .. op.. [[ b, a.y ]] .. op .. [[ b, a.z ]] .. op .. [[ b)
+               else
+                       return vec3(a.x ]] .. op.. [[ b.x, a.y ]] .. op.. [[ b.y, a.z ]] .. op.. [[ b.z)
+               end     
+       ]]
+end)
+
+function mt_vec3:__neg()
+       return vec3(-self.x, -self.y, -self.z)
+end
+
+function mt_vec3:__tostring()
+       return "(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")"
+end
+
+function vec3(a, b, c)
+       local o = {}
+
+       if type(a) == "number" then
+               o.x = a
+               o.y = b or a
+               o.z = c or a
+       else
+               o.x = a.x
+               o.y = a.y
+               o.z = a.z
+       end
+
+       setmetatable(o, mt_vec3)
+       return o
+end
+
+-- box
+
+local mt_box = arith_mt(function(op)
+       return "return box(a.min " .. op .. " b, a.max " .. op .. " b)"
+end)
+
+function mt_box:__neg()
+       return box(-self.min, -self.max)
+end
+
+function mt_box:__tostring()
+       return "[" .. self.min .. "; " .. self.max .. "]"
+end
+
+mt_box.__index = {
+       contains = function(a, b)
+               if type(b) == "number" or b.x then
+                       return a.min <= b and a.max >= b
+               else
+                       return a.min <= b.min and a.max >= b.max
+               end
+       end,
+}
+
+function box(a, b)
+       local o = {}
+
+       if type(a) == "number" or a.x then
+               o.min = a
+               o.max = b
+       else
+               o.min = a.min
+               o.max = a.max
+       end
+
+       setmetatable(o, mt_box)
+       return o
+end
diff --git a/callbacks.go b/callbacks.go
new file mode 100644 (file)
index 0000000..e16aec7
--- /dev/null
@@ -0,0 +1,85 @@
+package main
+
+import (
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+)
+
+type Callbacks struct {
+       wildcard   bool
+       subscribed map[string]struct{}
+}
+
+func getCallbacks(l *lua.State) *Callbacks {
+       return lua.CheckUserData(l, 1, "hydra.callbacks").(*Callbacks)
+}
+
+func (handler *Callbacks) create(client *Client) {
+       handler.subscribed = map[string]struct{}{}
+}
+
+func (handler *Callbacks) push(l *lua.State) {
+       l.PushUserData(handler)
+
+       if lua.NewMetaTable(l, "hydra.callbacks") {
+               lua.NewLibrary(l, []lua.RegistryFunction{
+                       {Name: "wildcard", Function: l_callbacks_wildcard},
+                       {Name: "subscribe", Function: l_callbacks_subscribe},
+                       {Name: "unsubscribe", Function: l_callbacks_unsubscribe},
+               })
+               l.SetField(-2, "__index")
+       }
+       l.SetMetaTable(-2)
+}
+
+func (handler *Callbacks) canConnect() (bool, string) {
+       return true, ""
+}
+
+func (handler *Callbacks) connect() {
+}
+
+func (handler *Callbacks) handle(pkt *mt.Pkt, l *lua.State, idx int) {
+       if !handler.wildcard && pkt != nil {
+               if _, exists := handler.subscribed[pktToString(pkt)]; !exists {
+                       return
+               }
+       }
+
+       if !l.IsFunction(2) {
+               return
+       }
+
+       l.PushValue(2)      // callback
+       l.RawGetInt(1, idx) // arg 1: client
+       pktToLua(l, pkt)    // arg 2: pkt
+       l.Call(2, 0)
+}
+
+func l_callbacks_wildcard(l *lua.State) int {
+       handler := getCallbacks(l)
+       handler.wildcard = l.ToBoolean(2)
+       return 0
+}
+
+func l_callbacks_subscribe(l *lua.State) int {
+       handler := getCallbacks(l)
+
+       n := l.Top()
+       for i := 2; i <= n; i++ {
+               handler.subscribed[lua.CheckString(l, i)] = struct{}{}
+       }
+
+       return 0
+}
+
+func l_callbacks_unsubscribe(l *lua.State) int {
+       handler := getCallbacks(l)
+
+       n := l.Top()
+       for i := 2; i <= n; i++ {
+               delete(handler.subscribed, lua.CheckString(l, i))
+       }
+
+       return 0
+}
diff --git a/client.go b/client.go
new file mode 100644 (file)
index 0000000..a57e032
--- /dev/null
+++ b/client.go
@@ -0,0 +1,172 @@
+package main
+
+import (
+       "errors"
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+       "net"
+)
+
+type clientState uint8
+
+const (
+       csNew clientState = iota
+       csConnected
+       csDisconnected
+)
+
+type Handler interface {
+       create(client *Client)
+       push(l *lua.State)
+       canConnect() (bool, string)
+       connect()
+       handle(pkt *mt.Pkt, l *lua.State, idx int)
+}
+
+type Client struct {
+       address  string
+       state    clientState
+       handlers map[string]Handler
+       conn     mt.Peer
+       queue    chan *mt.Pkt
+}
+
+func getClient(l *lua.State) *Client {
+       return lua.CheckUserData(l, 1, "hydra.client").(*Client)
+}
+
+func l_client(l *lua.State) int {
+       client := &Client{
+               address:  lua.CheckString(l, 1),
+               state:    csNew,
+               handlers: map[string]Handler{},
+       }
+
+       l.PushUserData(client)
+
+       if lua.NewMetaTable(l, "hydra.client") {
+               lua.NewLibrary(l, []lua.RegistryFunction{
+                       {Name: "address", Function: l_client_address},
+                       {Name: "state", Function: l_client_state},
+                       {Name: "handler", Function: l_client_handler},
+                       {Name: "connect", Function: l_client_connect},
+                       {Name: "disconnect", Function: l_client_disconnect},
+               })
+               l.SetField(-2, "__index")
+       }
+       l.SetMetaTable(-2)
+
+       return 1
+}
+
+func l_client_address(l *lua.State) int {
+       client := getClient(l)
+       l.PushString(client.address)
+       return 1
+}
+
+func l_client_state(l *lua.State) int {
+       client := getClient(l)
+       switch client.state {
+       case csNew:
+               l.PushString("new")
+       case csConnected:
+               l.PushString("connected")
+       case csDisconnected:
+               l.PushString("disconnected")
+       }
+       return 1
+}
+
+func l_client_handler(l *lua.State) int {
+       client := getClient(l)
+       name := lua.CheckString(l, 2)
+
+       handler, exists := client.handlers[name]
+       if !exists {
+               switch name {
+               case "callbacks":
+                       handler = &Callbacks{}
+
+               case "auth":
+                       handler = &Auth{}
+
+               default:
+                       return 0
+               }
+
+               client.handlers[name] = handler
+               handler.create(client)
+       }
+
+       handler.push(l)
+       return 1
+}
+
+func l_client_connect(l *lua.State) int {
+       client := getClient(l)
+
+       if client.state != csNew {
+               l.PushBoolean(false)
+               l.PushString("invalid state")
+               return 2
+       }
+
+       for _, handler := range client.handlers {
+               ok, err := handler.canConnect()
+
+               if !ok {
+                       l.PushBoolean(false)
+                       l.PushString(err)
+                       return 2
+               }
+       }
+
+       addr, err := net.ResolveUDPAddr("udp", client.address)
+       if err != nil {
+               l.PushBoolean(false)
+               l.PushString(err.Error())
+               return 2
+       }
+
+       conn, err := net.DialUDP("udp", nil, addr)
+       if err != nil {
+               l.PushBoolean(false)
+               l.PushString(err.Error())
+               return 2
+       }
+
+       client.state = csConnected
+       client.conn = mt.Connect(conn)
+       client.queue = make(chan *mt.Pkt, 1024)
+
+       for _, handler := range client.handlers {
+               handler.connect()
+       }
+
+       go func() {
+               for {
+                       pkt, err := client.conn.Recv()
+
+                       if err == nil {
+                               client.queue <- &pkt
+                       } else if errors.Is(err, net.ErrClosed) {
+                               close(client.queue)
+                               return
+                       }
+               }
+       }()
+
+       l.PushBoolean(true)
+       return 1
+}
+
+func l_client_disconnect(l *lua.State) int {
+       client := getClient(l)
+
+       if client.state == csConnected {
+               client.conn.Close()
+       }
+
+       return 0
+}
diff --git a/convert.go b/convert.go
new file mode 100644 (file)
index 0000000..c5e4daa
--- /dev/null
@@ -0,0 +1,882 @@
+// generated by mkconvert.lua, DO NOT EDIT
+package main
+
+import (
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+)
+
+func luaPushHotbarParam(l *lua.State, val mt.HotbarParam) {
+       switch val {
+       case mt.HotbarSize:
+               l.PushString("size")
+       case mt.HotbarImg:
+               l.PushString("img")
+       case mt.HotbarSelImg:
+               l.PushString("sel_img")
+       }
+}
+
+func luaPushChatMsgType(l *lua.State, val mt.ChatMsgType) {
+       switch val {
+       case mt.RawMsg:
+               l.PushString("raw")
+       case mt.NormalMsg:
+               l.PushString("normal")
+       case mt.AnnounceMsg:
+               l.PushString("announce")
+       case mt.SysMsg:
+               l.PushString("sys")
+       }
+}
+
+func luaPushHUDType(l *lua.State, val mt.HUDType) {
+       switch val {
+       case mt.ImgHUD:
+               l.PushString("img")
+       case mt.TextHUD:
+               l.PushString("text")
+       case mt.StatbarHUD:
+               l.PushString("statbar")
+       case mt.InvHUD:
+               l.PushString("inv")
+       case mt.WaypointHUD:
+               l.PushString("waypoint")
+       case mt.ImgWaypointHUD:
+               l.PushString("img_waypoint")
+       }
+}
+
+func luaPushPlayerListUpdateType(l *lua.State, val mt.PlayerListUpdateType) {
+       switch val {
+       case mt.InitPlayers:
+               l.PushString("init")
+       case mt.AddPlayers:
+               l.PushString("add")
+       case mt.RemovePlayers:
+               l.PushString("remove")
+       }
+}
+
+func luaPushHUDField(l *lua.State, val mt.HUDField) {
+       switch val {
+       case mt.HUDPos:
+               l.PushString("pos")
+       case mt.HUDName:
+               l.PushString("name")
+       case mt.HUDScale:
+               l.PushString("scale")
+       case mt.HUDText:
+               l.PushString("text")
+       case mt.HUDNumber:
+               l.PushString("number")
+       case mt.HUDItem:
+               l.PushString("item")
+       case mt.HUDDir:
+               l.PushString("dir")
+       case mt.HUDAlign:
+               l.PushString("align")
+       case mt.HUDOffset:
+               l.PushString("offset")
+       case mt.HUDWorldPos:
+               l.PushString("world_pos")
+       case mt.HUDSize:
+               l.PushString("size")
+       case mt.HUDZIndex:
+               l.PushString("z_index")
+       case mt.HUDText2:
+               l.PushString("text_2")
+       }
+}
+
+func luaPushModChanSig(l *lua.State, val mt.ModChanSig) {
+       switch val {
+       case mt.JoinOK:
+               l.PushString("join_ok")
+       case mt.JoinFail:
+               l.PushString("join_fail")
+       case mt.LeaveOK:
+               l.PushString("leave_ok")
+       case mt.LeaveFail:
+               l.PushString("leave_fail")
+       case mt.NotRegistered:
+               l.PushString("not_registered")
+       case mt.SetState:
+               l.PushString("set_state")
+       }
+}
+
+func luaPushKickReason(l *lua.State, val mt.KickReason) {
+       switch val {
+       case mt.WrongPasswd:
+               l.PushString("wrong_passwd")
+       case mt.UnexpectedData:
+               l.PushString("unexpected_data")
+       case mt.SrvIsSingleplayer:
+               l.PushString("srv_is_singleplayer")
+       case mt.UnsupportedVer:
+               l.PushString("unsupported_ver")
+       case mt.BadNameChars:
+               l.PushString("bad_name_chars")
+       case mt.BadName:
+               l.PushString("bad_name")
+       case mt.TooManyClts:
+               l.PushString("too_many_clts")
+       case mt.EmptyPasswd:
+               l.PushString("empty_passwd")
+       case mt.AlreadyConnected:
+               l.PushString("already_connected")
+       case mt.SrvErr:
+               l.PushString("srv_err")
+       case mt.Custom:
+               l.PushString("custom")
+       case mt.Shutdown:
+               l.PushString("shutdown")
+       case mt.Crash:
+               l.PushString("crash")
+       }
+}
+
+func luaPushSoundSrcType(l *lua.State, val mt.SoundSrcType) {
+       switch val {
+       case mt.NoSrc:
+               l.PushNil()
+       case mt.PosSrc:
+               l.PushString("pos")
+       case mt.AOSrc:
+               l.PushString("ao")
+       }
+}
+
+func luaPushAnimType(l *lua.State, val mt.AnimType) {
+       switch val {
+       case mt.NoAnim:
+               l.PushNil()
+       case mt.VerticalFrameAnim:
+               l.PushString("vertical_frame")
+       case mt.SpriteSheetAnim:
+               l.PushString("sprite_sheet")
+       }
+}
+
+func luaPushAuthMethods(l *lua.State, val mt.AuthMethods) {
+       l.NewTable()
+       if val&mt.LegacyPasswd != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "legacy_passwd")
+       }
+       if val&mt.SRP != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "srp")
+       }
+       if val&mt.FirstSRP != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "first_srp")
+       }
+}
+
+func luaPushHUDFlags(l *lua.State, val mt.HUDFlags) {
+       l.NewTable()
+       if val&mt.ShowHotbar != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "hotbar")
+       }
+       if val&mt.ShowHealthBar != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "health_bar")
+       }
+       if val&mt.ShowCrosshair != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "crosshair")
+       }
+       if val&mt.ShowWieldedItem != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "wielded_item")
+       }
+       if val&mt.ShowBreathBar != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "breath_bar")
+       }
+       if val&mt.ShowMinimap != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "minimap")
+       }
+       if val&mt.ShowRadarMinimap != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "radar_minimap")
+       }
+}
+
+func luaPushCSMRestrictionFlags(l *lua.State, val mt.CSMRestrictionFlags) {
+       l.NewTable()
+       if val&mt.NoCSMs != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "no_csms")
+       }
+       if val&mt.NoChatMsgs != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "no_chat_msgs")
+       }
+       if val&mt.NoNodeDefs != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "no_node_defs")
+       }
+       if val&mt.LimitMapRange != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "limit_map_range")
+       }
+       if val&mt.NoPlayerList != 0 {
+               l.PushBoolean(true)
+               l.SetField(-2, "no_player_list")
+       }
+}
+
+func luaPushTileAnim(l *lua.State, val mt.TileAnim) {
+       l.NewTable()
+       luaPushAnimType(l, val.Type)
+       l.SetField(-2, "type")
+       luaPushVec2(l, [2]float64{float64(val.NFrames[0]), float64(val.NFrames[1])})
+       l.SetField(-2, "n_frames")
+       l.PushNumber(float64(val.Duration))
+       l.SetField(-2, "duration")
+       luaPushVec2(l, [2]float64{float64(val.AspectRatio[0]), float64(val.AspectRatio[1])})
+       l.SetField(-2, "aspect_ratio")
+}
+
+func luaPushNode(l *lua.State, val mt.Node) {
+       l.NewTable()
+       l.PushInteger(int(val.Param0))
+       l.SetField(-2, "param0")
+       l.PushInteger(int(val.Param1))
+       l.SetField(-2, "param1")
+       l.PushInteger(int(val.Param2))
+       l.SetField(-2, "param2")
+}
+
+func luaPushHUD(l *lua.State, val mt.HUD) {
+       l.NewTable()
+       luaPushHUDType(l, val.Type)
+       l.SetField(-2, "type")
+       l.PushInteger(int(val.ZIndex))
+       l.SetField(-2, "z_index")
+       luaPushVec2(l, [2]float64{float64(val.Scale[0]), float64(val.Scale[1])})
+       l.SetField(-2, "scale")
+       l.PushString(string(val.Name))
+       l.SetField(-2, "name")
+       luaPushVec3(l, [3]float64{float64(val.WorldPos[0]), float64(val.WorldPos[1]), float64(val.WorldPos[2])})
+       l.SetField(-2, "world_pos")
+       l.PushString(string(val.Text))
+       l.SetField(-2, "text")
+       l.PushString(string(val.Text2))
+       l.SetField(-2, "text_2")
+       luaPushVec2(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])})
+       l.SetField(-2, "size")
+       luaPushVec2(l, [2]float64{float64(val.Align[0]), float64(val.Align[1])})
+       l.SetField(-2, "align")
+       luaPushVec2(l, [2]float64{float64(val.Pos[0]), float64(val.Pos[1])})
+       l.SetField(-2, "pos")
+       l.PushInteger(int(val.Dir))
+       l.SetField(-2, "dir")
+       luaPushVec2(l, [2]float64{float64(val.Offset[0]), float64(val.Offset[1])})
+       l.SetField(-2, "offset")
+       l.PushInteger(int(val.Number))
+       l.SetField(-2, "number")
+       l.PushInteger(int(val.Item))
+       l.SetField(-2, "item")
+}
+
+func pktToString(pkt *mt.Pkt) string {
+       switch pkt.Cmd.(type) {
+       case *mt.ToCltPlaySound:
+               return "play_sound"
+       case *mt.ToCltLegacyKick:
+               return "legacy_kick"
+       case *mt.ToCltFOV:
+               return "fov"
+       case *mt.ToCltNodeMetasChanged:
+               return "node_metas_changed"
+       case *mt.ToCltHello:
+               return "hello"
+       case *mt.ToCltAcceptSudoMode:
+               return "accept_sudo_mode"
+       case *mt.ToCltPrivs:
+               return "privs"
+       case *mt.ToCltDetachedInv:
+               return "detached_inv"
+       case *mt.ToCltSpawnParticle:
+               return "spawn_particle"
+       case *mt.ToCltAcceptAuth:
+               return "accept_auth"
+       case *mt.ToCltOverrideDayNightRatio:
+               return "override_day_night_ratio"
+       case *mt.ToCltMinimapModes:
+               return "minimap_modes"
+       case *mt.ToCltAddHUD:
+               return "add_hud"
+       case *mt.ToCltHP:
+               return "hp"
+       case *mt.ToCltChangeHUD:
+               return "change_hud"
+       case *mt.ToCltFormspecPrepend:
+               return "formspec_prepend"
+       case *mt.ToCltSRPBytesSaltB:
+               return "srp_bytes_salt_b"
+       case *mt.ToCltSetHotbarParam:
+               return "set_hotbar_param"
+       case *mt.ToCltMovePlayer:
+               return "move_player"
+       case *mt.ToCltAddParticleSpawner:
+               return "add_particle_spawner"
+       case *mt.ToCltKick:
+               return "kick"
+       case *mt.ToCltDelParticleSpawner:
+               return "del_particle_spawner"
+       case *mt.ToCltModChanSig:
+               return "mod_chan_sig"
+       case *mt.ToCltMoonParams:
+               return "moon_params"
+       case *mt.ToCltModChanMsg:
+               return "mod_chan_msg"
+       case *mt.ToCltSunParams:
+               return "sun_params"
+       case *mt.ToCltInv:
+               return "inv"
+       case *mt.ToCltRemoveNode:
+               return "remove_node"
+       case *mt.ToCltNodeDefs:
+               return "node_defs"
+       case *mt.ToCltMediaPush:
+               return "media_push"
+       case *mt.ToCltLocalPlayerAnim:
+               return "local_player_anim"
+       case *mt.ToCltFadeSound:
+               return "fade_sound"
+       case *mt.ToCltItemDefs:
+               return "item_defs"
+       case *mt.ToCltUpdatePlayerList:
+               return "update_player_list"
+       case *mt.ToCltEyeOffset:
+               return "eye_offset"
+       case *mt.ToCltMedia:
+               return "media"
+       case *mt.ToCltDisco:
+               return "disco"
+       case *mt.ToCltBlkData:
+               return "blk_data"
+       case *mt.ToCltSkyParams:
+               return "sky_params"
+       case *mt.ToCltBreath:
+               return "breath"
+       case *mt.ToCltChatMsg:
+               return "chat_msg"
+       case *mt.ToCltHUDFlags:
+               return "hud_flags"
+       case *mt.ToCltAOMsgs:
+               return "ao_msgs"
+       case *mt.ToCltRmHUD:
+               return "rm_hud"
+       case *mt.ToCltStarParams:
+               return "star_params"
+       case *mt.ToCltDeathScreen:
+               return "death_screen"
+       case *mt.ToCltAORmAdd:
+               return "ao_rm_add"
+       case *mt.ToCltAddPlayerVel:
+               return "add_player_vel"
+       case *mt.ToCltMovement:
+               return "movement"
+       case *mt.ToCltCloudParams:
+               return "cloud_params"
+       case *mt.ToCltDenySudoMode:
+               return "deny_sudo_mode"
+       case *mt.ToCltCSMRestrictionFlags:
+               return "csm_restriction_flags"
+       case *mt.ToCltAddNode:
+               return "add_node"
+       case *mt.ToCltStopSound:
+               return "stop_sound"
+       case *mt.ToCltInvFormspec:
+               return "inv_formspec"
+       case *mt.ToCltAnnounceMedia:
+               return "announce_media"
+       case *mt.ToCltShowFormspec:
+               return "show_formspec"
+       case *mt.ToCltTimeOfDay:
+               return "time_of_day"
+       }
+       panic("impossible")
+       return ""
+}
+
+func pktToLua(l *lua.State, pkt *mt.Pkt) {
+       if pkt == nil {
+               l.PushNil()
+               return
+       }
+       l.NewTable()
+       l.PushString(pktToString(pkt))
+       l.SetField(-2, "_type")
+       switch val := pkt.Cmd.(type) {
+       case *mt.ToCltPlaySound:
+               l.PushNumber(float64(val.Gain))
+               l.SetField(-2, "gain")
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+               l.PushNumber(float64(val.Pitch))
+               l.SetField(-2, "pitch")
+               luaPushSoundSrcType(l, val.SrcType)
+               l.SetField(-2, "src_type")
+               l.PushInteger(int(val.SrcAOID))
+               l.SetField(-2, "src_aoid")
+               l.PushNumber(float64(val.Fade))
+               l.SetField(-2, "fade")
+               l.PushBoolean(bool(val.Ephemeral))
+               l.SetField(-2, "ephemeral")
+               l.PushBoolean(bool(val.Loop))
+               l.SetField(-2, "loop")
+               l.PushString(string(val.Name))
+               l.SetField(-2, "name")
+               luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])})
+               l.SetField(-2, "pos")
+       case *mt.ToCltLegacyKick:
+               l.PushString(string(val.Reason))
+               l.SetField(-2, "reason")
+       case *mt.ToCltFOV:
+               l.PushNumber(float64(val.TransitionTime))
+               l.SetField(-2, "transition_time")
+               l.PushBoolean(bool(val.Multiplier))
+               l.SetField(-2, "multiplier")
+               l.PushNumber(float64(val.FOV))
+               l.SetField(-2, "fov")
+       case *mt.ToCltHello:
+               luaPushAuthMethods(l, val.AuthMethods)
+               l.SetField(-2, "auth_methods")
+               l.PushString(string(val.Username))
+               l.SetField(-2, "username")
+               l.PushInteger(int(val.Compression))
+               l.SetField(-2, "compression")
+               l.PushInteger(int(val.ProtoVer))
+               l.SetField(-2, "proto_ver")
+               l.PushInteger(int(val.SerializeVer))
+               l.SetField(-2, "serialize_ver")
+       case *mt.ToCltPrivs:
+               luaPushStringSet(l, val.Privs)
+               l.SetField(-2, "privs")
+       case *mt.ToCltDetachedInv:
+               l.PushString(string(val.Inv))
+               l.SetField(-2, "inv")
+               l.PushBoolean(bool(val.Keep))
+               l.SetField(-2, "keep")
+               l.PushInteger(int(val.Len))
+               l.SetField(-2, "len")
+               l.PushString(string(val.Name))
+               l.SetField(-2, "name")
+       case *mt.ToCltSpawnParticle:
+               l.PushBoolean(bool(val.Collide))
+               l.SetField(-2, "collide")
+               l.PushString(string(val.Texture))
+               l.SetField(-2, "texture")
+               luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])})
+               l.SetField(-2, "pos")
+               l.PushInteger(int(val.NodeTile))
+               l.SetField(-2, "node_tile")
+               l.PushBoolean(bool(val.Vertical))
+               l.SetField(-2, "vertical")
+               l.PushInteger(int(val.Glow))
+               l.SetField(-2, "glow")
+               l.PushInteger(int(val.NodeParam2))
+               l.SetField(-2, "node_param2")
+               l.PushInteger(int(val.NodeParam0))
+               l.SetField(-2, "node_param0")
+               l.PushBoolean(bool(val.AOCollision))
+               l.SetField(-2, "ao_collision")
+               l.PushNumber(float64(val.Size))
+               l.SetField(-2, "size")
+               l.PushNumber(float64(val.ExpirationTime))
+               l.SetField(-2, "expiration_time")
+               l.PushBoolean(bool(val.CollisionRm))
+               l.SetField(-2, "collision_rm")
+               luaPushTileAnim(l, val.AnimParams)
+               l.SetField(-2, "anim_params")
+               luaPushVec3(l, [3]float64{float64(val.Acc[0]), float64(val.Acc[1]), float64(val.Acc[2])})
+               l.SetField(-2, "acc")
+               luaPushVec3(l, [3]float64{float64(val.Vel[0]), float64(val.Vel[1]), float64(val.Vel[2])})
+               l.SetField(-2, "vel")
+       case *mt.ToCltAcceptAuth:
+               l.PushNumber(float64(val.MapSeed))
+               l.SetField(-2, "map_seed")
+               luaPushAuthMethods(l, val.SudoAuthMethods)
+               l.SetField(-2, "sudo_auth_methods")
+               l.PushNumber(float64(val.SendInterval))
+               l.SetField(-2, "send_interval")
+               luaPushVec3(l, [3]float64{float64(val.PlayerPos[0]), float64(val.PlayerPos[1]), float64(val.PlayerPos[2])})
+               l.SetField(-2, "player_pos")
+       case *mt.ToCltOverrideDayNightRatio:
+               l.PushInteger(int(val.Ratio))
+               l.SetField(-2, "ratio")
+               l.PushBoolean(bool(val.Override))
+               l.SetField(-2, "override")
+       case *mt.ToCltAddHUD:
+               luaPushHUD(l, val.HUD)
+               l.SetField(-2, "hud")
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+       case *mt.ToCltHP:
+               l.PushInteger(int(val.HP))
+               l.SetField(-2, "hp")
+       case *mt.ToCltChangeHUD:
+               if val.Field == mt.HUDWorldPos {
+                       luaPushVec3(l, [3]float64{float64(val.WorldPos[0]), float64(val.WorldPos[1]), float64(val.WorldPos[2])})
+                       l.SetField(-2, "world_pos")
+               }
+               if val.Field == mt.HUDText2 {
+                       l.PushString(string(val.Text2))
+                       l.SetField(-2, "text_2")
+               }
+               if val.Field == mt.HUDItem {
+                       l.PushInteger(int(val.Item))
+                       l.SetField(-2, "item")
+               }
+               if val.Field == mt.HUDZIndex {
+                       l.PushInteger(int(val.ZIndex))
+                       l.SetField(-2, "z_index")
+               }
+               if val.Field == mt.HUDPos {
+                       luaPushVec2(l, [2]float64{float64(val.Pos[0]), float64(val.Pos[1])})
+                       l.SetField(-2, "pos")
+               }
+               if val.Field == mt.HUDSize {
+                       luaPushVec2(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])})
+                       l.SetField(-2, "size")
+               }
+               if val.Field == mt.HUDName {
+                       l.PushString(string(val.Name))
+                       l.SetField(-2, "name")
+               }
+               if val.Field == mt.HUDDir {
+                       l.PushInteger(int(val.Dir))
+                       l.SetField(-2, "dir")
+               }
+               if val.Field == mt.HUDAlign {
+                       luaPushVec2(l, [2]float64{float64(val.Align[0]), float64(val.Align[1])})
+                       l.SetField(-2, "align")
+               }
+               if val.Field == mt.HUDNumber {
+                       l.PushInteger(int(val.Number))
+                       l.SetField(-2, "number")
+               }
+               if val.Field == mt.HUDText {
+                       l.PushString(string(val.Text))
+                       l.SetField(-2, "text")
+               }
+               if val.Field == mt.HUDOffset {
+                       luaPushVec2(l, [2]float64{float64(val.Offset[0]), float64(val.Offset[1])})
+                       l.SetField(-2, "offset")
+               }
+               luaPushHUDField(l, val.Field)
+               l.SetField(-2, "field")
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+       case *mt.ToCltFormspecPrepend:
+               l.PushString(string(val.Prepend))
+               l.SetField(-2, "prepend")
+       case *mt.ToCltSRPBytesSaltB:
+               l.PushString(string(val.B))
+               l.SetField(-2, "b")
+               l.PushString(string(val.Salt))
+               l.SetField(-2, "salt")
+       case *mt.ToCltSetHotbarParam:
+               luaPushHotbarParam(l, val.Param)
+               l.SetField(-2, "param")
+               l.PushInteger(int(val.Size))
+               l.SetField(-2, "size")
+               l.PushString(string(val.Img))
+               l.SetField(-2, "img")
+       case *mt.ToCltMovePlayer:
+               l.PushNumber(float64(val.Yaw))
+               l.SetField(-2, "yaw")
+               l.PushNumber(float64(val.Pitch))
+               l.SetField(-2, "pitch")
+               luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])})
+               l.SetField(-2, "pos")
+       case *mt.ToCltAddParticleSpawner:
+               luaPushBox3(l, [2][3]float64{{float64(val.Acc[0][0]), float64(val.Acc[0][1]), float64(val.Acc[0][2])}, {float64(val.Acc[1][0]), float64(val.Acc[1][1]), float64(val.Acc[1][2])}})
+               l.SetField(-2, "acc")
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+               l.PushString(string(val.Texture))
+               l.SetField(-2, "texture")
+               l.PushBoolean(bool(val.Vertical))
+               l.SetField(-2, "vertical")
+               luaPushBox1(l, [2]float64{float64(val.ExpirationTime[0]), float64(val.ExpirationTime[1])})
+               l.SetField(-2, "expiration_time")
+               luaPushTileAnim(l, val.AnimParams)
+               l.SetField(-2, "anim_params")
+               l.PushBoolean(bool(val.AOCollision))
+               l.SetField(-2, "ao_collision")
+               luaPushBox3(l, [2][3]float64{{float64(val.Pos[0][0]), float64(val.Pos[0][1]), float64(val.Pos[0][2])}, {float64(val.Pos[1][0]), float64(val.Pos[1][1]), float64(val.Pos[1][2])}})
+               l.SetField(-2, "pos")
+               l.PushInteger(int(val.Glow))
+               l.SetField(-2, "glow")
+               l.PushInteger(int(val.NodeParam0))
+               l.SetField(-2, "node_param0")
+               luaPushBox3(l, [2][3]float64{{float64(val.Vel[0][0]), float64(val.Vel[0][1]), float64(val.Vel[0][2])}, {float64(val.Vel[1][0]), float64(val.Vel[1][1]), float64(val.Vel[1][2])}})
+               l.SetField(-2, "vel")
+               l.PushBoolean(bool(val.Collide))
+               l.SetField(-2, "collide")
+               luaPushBox1(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])})
+               l.SetField(-2, "size")
+               l.PushInteger(int(val.NodeParam2))
+               l.SetField(-2, "node_param2")
+               l.PushNumber(float64(val.Duration))
+               l.SetField(-2, "duration")
+               l.PushInteger(int(val.NodeTile))
+               l.SetField(-2, "node_tile")
+               l.PushInteger(int(val.Amount))
+               l.SetField(-2, "amount")
+               l.PushBoolean(bool(val.CollisionRm))
+               l.SetField(-2, "collision_rm")
+       case *mt.ToCltKick:
+               luaPushKickReason(l, val.Reason)
+               l.SetField(-2, "reason")
+               if dr := val.Reason; dr == mt.Custom || dr == mt.Shutdown || dr == mt.Crash {
+                       l.PushString(string(val.Custom))
+                       l.SetField(-2, "custom")
+               }
+               if dr := val.Reason; dr == mt.Shutdown || dr == mt.Crash {
+                       l.PushBoolean(bool(val.Reconnect))
+                       l.SetField(-2, "reconnect")
+               }
+       case *mt.ToCltDelParticleSpawner:
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+       case *mt.ToCltModChanSig:
+               l.PushString(string(val.Channel))
+               l.SetField(-2, "channel")
+               luaPushModChanSig(l, val.Signal)
+               l.SetField(-2, "signal")
+       case *mt.ToCltMoonParams:
+               l.PushNumber(float64(val.Size))
+               l.SetField(-2, "size")
+               l.PushString(string(val.ToneMap))
+               l.SetField(-2, "tone_map")
+               l.PushString(string(val.Texture))
+               l.SetField(-2, "texture")
+               l.PushBoolean(bool(val.Visible))
+               l.SetField(-2, "visible")
+       case *mt.ToCltModChanMsg:
+               l.PushString(string(val.Channel))
+               l.SetField(-2, "channel")
+               l.PushString(string(val.Sender))
+               l.SetField(-2, "sender")
+               l.PushString(string(val.Msg))
+               l.SetField(-2, "msg")
+       case *mt.ToCltSunParams:
+               l.PushString(string(val.Texture))
+               l.SetField(-2, "texture")
+               l.PushBoolean(bool(val.Visible))
+               l.SetField(-2, "visible")
+               l.PushNumber(float64(val.Size))
+               l.SetField(-2, "size")
+               l.PushString(string(val.ToneMap))
+               l.SetField(-2, "tone_map")
+               l.PushBoolean(bool(val.Rising))
+               l.SetField(-2, "rising")
+               l.PushString(string(val.Rise))
+               l.SetField(-2, "rise")
+       case *mt.ToCltInv:
+               l.PushString(string(val.Inv))
+               l.SetField(-2, "inv")
+       case *mt.ToCltRemoveNode:
+               luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])})
+               l.SetField(-2, "pos")
+       case *mt.ToCltMediaPush:
+               l.PushString(string(val.SHA1[:]))
+               l.SetField(-2, "sha1")
+               l.PushString(string(val.Data))
+               l.SetField(-2, "data")
+               l.PushBoolean(bool(val.ShouldCache))
+               l.SetField(-2, "should_cache")
+               l.PushString(string(val.Filename))
+               l.SetField(-2, "filename")
+       case *mt.ToCltLocalPlayerAnim:
+               luaPushBox1(l, [2]float64{float64(val.Walk[0]), float64(val.Walk[1])})
+               l.SetField(-2, "walk")
+               luaPushBox1(l, [2]float64{float64(val.Idle[0]), float64(val.Idle[1])})
+               l.SetField(-2, "idle")
+               l.PushNumber(float64(val.Speed))
+               l.SetField(-2, "speed")
+               luaPushBox1(l, [2]float64{float64(val.Dig[0]), float64(val.Dig[1])})
+               l.SetField(-2, "dig")
+               luaPushBox1(l, [2]float64{float64(val.WalkDig[0]), float64(val.WalkDig[1])})
+               l.SetField(-2, "walk_dig")
+       case *mt.ToCltFadeSound:
+               l.PushNumber(float64(val.Step))
+               l.SetField(-2, "step")
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+               l.PushNumber(float64(val.Gain))
+               l.SetField(-2, "gain")
+       case *mt.ToCltUpdatePlayerList:
+               luaPushPlayerListUpdateType(l, val.Type)
+               l.SetField(-2, "type")
+               luaPushStringList(l, val.Players)
+               l.SetField(-2, "players")
+       case *mt.ToCltEyeOffset:
+               luaPushVec3(l, [3]float64{float64(val.First[0]), float64(val.First[1]), float64(val.First[2])})
+               l.SetField(-2, "first")
+               luaPushVec3(l, [3]float64{float64(val.Third[0]), float64(val.Third[1]), float64(val.Third[2])})
+               l.SetField(-2, "third")
+       case *mt.ToCltBlkData:
+               luaPushVec3(l, [3]float64{float64(val.Blkpos[0]), float64(val.Blkpos[1]), float64(val.Blkpos[2])})
+               l.SetField(-2, "blkpos")
+       case *mt.ToCltSkyParams:
+               luaPushColor(l, val.SunFogTint)
+               l.SetField(-2, "sun_fog_tint")
+               l.PushString(string(val.FogTintType))
+               l.SetField(-2, "fog_tint_type")
+               if val.Type == "regular" {
+                       luaPushColor(l, val.DawnHorizon)
+                       l.SetField(-2, "dawn_horizon")
+               }
+               if val.Type == "regular" {
+                       luaPushColor(l, val.DaySky)
+                       l.SetField(-2, "day_sky")
+               }
+               l.PushBoolean(bool(val.Clouds))
+               l.SetField(-2, "clouds")
+               l.PushString(string(val.Type))
+               l.SetField(-2, "type")
+               luaPushColor(l, val.BgColor)
+               l.SetField(-2, "bg_color")
+               if val.Type == "regular" {
+                       luaPushColor(l, val.DawnSky)
+                       l.SetField(-2, "dawn_sky")
+               }
+               if val.Type == "regular" {
+                       luaPushColor(l, val.NightHorizon)
+                       l.SetField(-2, "night_horizon")
+               }
+               if val.Type == "regular" {
+                       luaPushColor(l, val.NightSky)
+                       l.SetField(-2, "night_sky")
+               }
+               if val.Type == "regular" {
+                       luaPushColor(l, val.DayHorizon)
+                       l.SetField(-2, "day_horizon")
+               }
+               if val.Type == "skybox" {
+                       luaPushTextureList(l, val.Textures)
+                       l.SetField(-2, "textures")
+               }
+               luaPushColor(l, val.MoonFogTint)
+               l.SetField(-2, "moon_fog_tint")
+               if val.Type == "regular" {
+                       luaPushColor(l, val.Indoor)
+                       l.SetField(-2, "indoor")
+               }
+       case *mt.ToCltBreath:
+               l.PushInteger(int(val.Breath))
+               l.SetField(-2, "breath")
+       case *mt.ToCltChatMsg:
+               luaPushChatMsgType(l, val.Type)
+               l.SetField(-2, "type")
+               l.PushString(string(val.Sender))
+               l.SetField(-2, "sender")
+               l.PushNumber(float64(val.Timestamp))
+               l.SetField(-2, "timestamp")
+               l.PushString(string(val.Text))
+               l.SetField(-2, "text")
+       case *mt.ToCltHUDFlags:
+               luaPushHUDFlags(l, val.Flags)
+               l.SetField(-2, "flags")
+               luaPushHUDFlags(l, val.Mask)
+               l.SetField(-2, "mask")
+       case *mt.ToCltRmHUD:
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+       case *mt.ToCltStarParams:
+               l.PushInteger(int(val.Count))
+               l.SetField(-2, "count")
+               l.PushNumber(float64(val.Size))
+               l.SetField(-2, "size")
+               luaPushColor(l, val.Color)
+               l.SetField(-2, "color")
+               l.PushBoolean(bool(val.Visible))
+               l.SetField(-2, "visible")
+       case *mt.ToCltDeathScreen:
+               l.PushBoolean(bool(val.PointCam))
+               l.SetField(-2, "point_cam")
+               luaPushVec3(l, [3]float64{float64(val.PointAt[0]), float64(val.PointAt[1]), float64(val.PointAt[2])})
+               l.SetField(-2, "point_at")
+       case *mt.ToCltAddPlayerVel:
+               luaPushVec3(l, [3]float64{float64(val.Vel[0]), float64(val.Vel[1]), float64(val.Vel[2])})
+               l.SetField(-2, "vel")
+       case *mt.ToCltMovement:
+               l.PushNumber(float64(val.JumpSpeed))
+               l.SetField(-2, "jump_speed")
+               l.PushNumber(float64(val.FastAccel))
+               l.SetField(-2, "fast_accel")
+               l.PushNumber(float64(val.FastSpeed))
+               l.SetField(-2, "fast_speed")
+               l.PushNumber(float64(val.Sink))
+               l.SetField(-2, "sink")
+               l.PushNumber(float64(val.AirAccel))
+               l.SetField(-2, "air_accel")
+               l.PushNumber(float64(val.Gravity))
+               l.SetField(-2, "gravity")
+               l.PushNumber(float64(val.CrouchSpeed))
+               l.SetField(-2, "crouch_speed")
+               l.PushNumber(float64(val.Smoothing))
+               l.SetField(-2, "smoothing")
+               l.PushNumber(float64(val.WalkSpeed))
+               l.SetField(-2, "walk_speed")
+               l.PushNumber(float64(val.Fluidity))
+               l.SetField(-2, "fluidity")
+               l.PushNumber(float64(val.ClimbSpeed))
+               l.SetField(-2, "climb_speed")
+               l.PushNumber(float64(val.DefaultAccel))
+               l.SetField(-2, "default_accel")
+       case *mt.ToCltCloudParams:
+               luaPushColor(l, val.AmbientColor)
+               l.SetField(-2, "ambient_color")
+               l.PushNumber(float64(val.Density))
+               l.SetField(-2, "density")
+               luaPushColor(l, val.DiffuseColor)
+               l.SetField(-2, "diffuse_color")
+               l.PushNumber(float64(val.Height))
+               l.SetField(-2, "height")
+               luaPushVec2(l, [2]float64{float64(val.Speed[0]), float64(val.Speed[1])})
+               l.SetField(-2, "speed")
+               l.PushNumber(float64(val.Thickness))
+               l.SetField(-2, "thickness")
+       case *mt.ToCltCSMRestrictionFlags:
+               luaPushCSMRestrictionFlags(l, val.Flags)
+               l.SetField(-2, "flags")
+               l.PushInteger(int(val.MapRange))
+               l.SetField(-2, "map_range")
+       case *mt.ToCltAddNode:
+               luaPushNode(l, val.Node)
+               l.SetField(-2, "node")
+               l.PushBoolean(bool(val.KeepMeta))
+               l.SetField(-2, "keep_meta")
+               luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])})
+               l.SetField(-2, "pos")
+       case *mt.ToCltStopSound:
+               l.PushInteger(int(val.ID))
+               l.SetField(-2, "id")
+       case *mt.ToCltInvFormspec:
+               l.PushString(string(val.Formspec))
+               l.SetField(-2, "formspec")
+       case *mt.ToCltShowFormspec:
+               l.PushString(string(val.Formspec))
+               l.SetField(-2, "formspec")
+               l.PushString(string(val.Formname))
+               l.SetField(-2, "formname")
+       case *mt.ToCltTimeOfDay:
+               l.PushNumber(float64(val.Speed))
+               l.SetField(-2, "speed")
+               l.PushInteger(int(val.Time))
+               l.SetField(-2, "time")
+       }
+}
diff --git a/go.mod b/go.mod
new file mode 100644 (file)
index 0000000..9a3d9b0
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module github.com/dragonfireclient/hydra
+
+go 1.17
+
+require (
+       github.com/HimbeerserverDE/srp v0.0.0 // indirect
+       github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807 // indirect
+       github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f // indirect
+       github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644 (file)
index 0000000..42ec0ba
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,8 @@
+github.com/HimbeerserverDE/srp v0.0.0 h1:Iy2GIF7DJphXXO9NjncLEBO6VsZd8Yhrlxl/qTr09eE=
+github.com/HimbeerserverDE/srp v0.0.0/go.mod h1:pxNH8S2nh4n2DWE0ToX5GnnDr/uEAuaAhJsCpkDLIWw=
+github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807 h1:b10jUZ94GuJk5GBl0iElM5aGIPPHi7FTRvqOKA7Ku+s=
+github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807/go.mod h1:1cxA/QL5xgRGP7Crq6tXSOY4eo//me8GHGMyypHynM8=
+github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f h1:tZU8VPYLyRrG3Lj9zBZvTVF5tUGciC/2aUIgTcU4WaM=
+github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f/go.mod h1:jH4ER+ahjl7H6TczzK+q4V9sXY++U2Geh6/vt3r4Xvs=
+github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
+github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
diff --git a/hydra.go b/hydra.go
new file mode 100644 (file)
index 0000000..77d7e05
--- /dev/null
+++ b/hydra.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+       _ "embed"
+       "github.com/Shopify/go-lua"
+       "os"
+       "os/signal"
+       "syscall"
+       "time"
+)
+
+var lastTime = time.Now()
+var canceled = false
+
+//go:embed builtin/vector.lua
+var vectorLibrary string
+
+func l_dtime(l *lua.State) int {
+       l.PushNumber(time.Since(lastTime).Seconds())
+       lastTime = time.Now()
+       return 1
+}
+
+func l_canceled(l *lua.State) int {
+       l.PushBoolean(canceled)
+       return 1
+}
+
+func signalChannel() chan os.Signal {
+       sig := make(chan os.Signal, 1)
+       signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
+       return sig
+}
+
+func main() {
+       if len(os.Args) < 2 {
+               panic("missing filename")
+       }
+
+       go func() {
+               <-signalChannel()
+               canceled = true
+       }()
+
+       l := lua.NewState()
+       lua.OpenLibraries(l)
+
+       lua.NewLibrary(l, []lua.RegistryFunction{
+               {Name: "client", Function: l_client},
+               {Name: "dtime", Function: l_dtime},
+               {Name: "canceled", Function: l_canceled},
+               {Name: "poll", Function: l_poll},
+       })
+
+       l.PushNumber(10.0)
+       l.SetField(-2, "BS")
+
+       l.SetGlobal("hydra")
+
+       l.NewTable()
+       for i, arg := range os.Args {
+               l.PushString(arg)
+               l.RawSetInt(-2, i - 1)
+       }
+       l.SetGlobal("arg")
+
+       if err := lua.DoString(l, vectorLibrary); err != nil {
+               panic(err)
+       }
+
+       if err := lua.DoFile(l, os.Args[1]); err != nil {
+               panic(err)
+       }
+}
diff --git a/mkconvert.lua b/mkconvert.lua
new file mode 100755 (executable)
index 0000000..d49d46d
--- /dev/null
@@ -0,0 +1,201 @@
+#!/usr/bin/env lua
+local function parse_pair(pair, value_first)
+       if pair:sub(1, 1) == "#" then
+               return
+       end
+
+       local idx = pair:find(" ")
+
+       if idx then
+               local first, second = pair:sub(1, idx - 1), pair:sub(idx + 1)
+
+               if value_first and first:sub(1, 1) ~= "[" then
+                       return second, first
+               else
+                       return first, second
+               end
+       else
+               return pair
+       end
+end
+
+local function parse_spec(name, value_first)
+       local f = io.open("spec/" .. name, "r")
+       local spec = {}
+       local top
+
+       for l in f:lines() do
+               if l:sub(1, 1) == "\t" then
+                       local key, val = parse_pair(l:sub(2), value_first)
+
+                       if val then
+                               top[key] = val
+                       elseif key then
+                               table.insert(top, key)
+                       end
+               else
+                       local key, val = parse_pair(l, value_first)
+
+                       if val then
+                               spec[key] = val
+                       elseif key then
+                               top = {}
+                               spec[key] = top
+                       end
+               end
+       end
+
+       f:close()
+       return spec
+end
+
+local casemap = parse_spec("casemap")
+
+local function camel_case(snake)
+       if casemap[snake] then
+               return casemap[snake]
+       end
+
+       local camel = ""
+
+       while #snake > 0 do
+               local idx = snake:find("_") or #snake + 1
+
+               camel = camel
+                       .. snake:sub(1, 1):upper()
+                       .. snake:sub(2, idx - 1)
+
+               snake = snake:sub(idx + 1)
+       end
+
+       return camel
+end
+
+local funcs = ""
+
+for name, fields in pairs(parse_spec("client/enum")) do
+       local camel = camel_case(name)
+       funcs = funcs .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tswitch val {\n"
+
+       for _, var in ipairs(fields) do
+               funcs = funcs .. "\tcase mt."
+                       .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "")
+                       .. ":\n\t\t" .. (var == "no" and "l.PushNil()" or "l.PushString(\"" .. var .. "\")") .. "\n"
+       end
+
+       funcs = funcs .. "\t}\n}\n\n"
+end
+
+for name, fields in pairs(parse_spec("client/flag")) do
+       local camel = camel_case(name)
+       funcs = funcs .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tl.NewTable()\n"
+
+       for _, var in ipairs(fields)    do
+               funcs = funcs .. "\tif val&mt."
+                       .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "")
+                       .. " != 0 {\n\t\tl.PushBoolean(true)\n\t\tl.SetField(-2, \"" .. var  .. "\")\n\t}\n"
+       end
+
+       funcs = funcs .. "}\n\n"
+end
+
+local push_type = {
+       string = "l.PushString(string(VAL))",
+       fixed_string = "l.PushString(string(VAL[:]))",
+       boolean = "l.PushBoolean(bool(VAL))",
+       integer = "l.PushInteger(int(VAL))",
+       number = "l.PushNumber(float64(VAL))",
+       vec2 = "luaPushVec2(l, [2]float64{float64(VAL[0]), float64(VAL[1])})",
+       vec3 = "luaPushVec3(l, [3]float64{float64(VAL[0]), float64(VAL[1]), float64(VAL[2])})",
+       box1 = "luaPushBox1(l, [2]float64{float64(VAL[0]), float64(VAL[1])})",
+       box2 = "luaPushBox2(l, [2][2]float64{{float64(VAL[0][0]), float64(VAL[0][1])}, {float64(VAL[1][0]), float64(VAL[1][1])}})",
+       box3 = "luaPushBox3(l, [2][3]float64{{float64(VAL[0][0]), float64(VAL[0][1]), float64(VAL[0][2])}, {float64(VAL[1][0]), float64(VAL[1][1]), float64(VAL[1][2])}})",
+}
+
+local function push_fields(fields, indent)
+       local impl = ""
+       
+       for name, type in pairs(fields) do
+               if name:sub(1, 1) ~= "[" then
+                       local camel = "val." .. camel_case(name)
+
+                       local idt = indent
+                       local condition = fields["[" .. name .. "]"]
+
+                       if condition then
+                               impl = impl .. indent .. "if " .. condition .. " {\n" 
+                               idt = idt .. "\t"
+                       end
+
+                       if push_type[type] then
+                               impl = impl .. idt .. push_type[type]:gsub("VAL", camel) .. "\n"
+                       else
+                               impl = impl .. idt .. "luaPush" .. camel_case(type) .. "(l, " .. camel .. ")\n"
+                       end
+
+                       impl = impl .. idt .. "l.SetField(-2, \"" .. name .. "\")\n"
+
+                       if condition then
+                               impl = impl .. indent .. "}\n"
+                       end
+               end
+       end
+
+       return impl
+end
+
+for name, fields in pairs(parse_spec("client/struct", true)) do
+       local camel = camel_case(name)
+       funcs = funcs
+               .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tl.NewTable()\n"
+               .. push_fields(fields, "\t")
+               .. "}\n\n"
+end
+
+local to_string_impl = ""
+local to_lua_impl = ""
+
+for name, fields in pairs(parse_spec("client/pkt", true)) do
+       local case = "\tcase *mt.ToClt" .. camel_case(name) .. ":\n"
+
+       to_string_impl = to_string_impl
+               .. case .. "\t\treturn \"" .. name .. "\"\n"
+
+       if next(fields) then
+               to_lua_impl = to_lua_impl .. case .. push_fields(fields, "\t\t")
+       end
+end
+
+local f = io.open("convert.go", "w")
+f:write([[
+// generated by mkconvert.lua, DO NOT EDIT
+package main
+
+import (
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+)
+
+]] .. funcs .. [[
+func pktToString(pkt *mt.Pkt) string {
+       switch pkt.Cmd.(type) {
+]] .. to_string_impl .. [[
+       }
+       panic("impossible")
+       return ""
+}
+
+func pktToLua(l *lua.State, pkt *mt.Pkt) {
+       if pkt == nil {
+               l.PushNil()
+               return
+       }
+       l.NewTable()
+       l.PushString(pktToString(pkt))
+       l.SetField(-2, "_type")
+       switch val := pkt.Cmd.(type) {
+]] .. to_lua_impl .. [[
+       }
+}
+]])
+f:close()
diff --git a/poll.go b/poll.go
new file mode 100644 (file)
index 0000000..bfbe298
--- /dev/null
+++ b/poll.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+       "reflect"
+       "time"
+)
+
+func l_poll(l *lua.State) int {
+       clients := make([]*Client, 0)
+
+       lua.CheckType(l, 1, lua.TypeTable)
+       i := 1
+       for {
+               l.RawGetInt(1, i)
+               if l.IsNil(-1) {
+                       l.Pop(1)
+                       break
+               }
+
+               clients = append(clients, l.ToUserData(-1).(*Client))
+               i++
+       }
+
+       var timeout time.Duration
+       hasTimeout := false
+       if l.IsNumber(3) {
+               timeout = time.Duration(lua.CheckNumber(l, 3) * float64(time.Second))
+               hasTimeout = true
+       }
+
+       for {
+               cases := make([]reflect.SelectCase, 0, len(clients)+2)
+
+               for _, client := range clients {
+                       if client.state != csConnected {
+                               continue
+                       }
+
+                       cases = append(cases, reflect.SelectCase{
+                               Dir:  reflect.SelectRecv,
+                               Chan: reflect.ValueOf(client.queue),
+                       })
+               }
+
+               offset := len(cases)
+
+               if offset < 1 {
+                       l.PushBoolean(false)
+                       return 1
+               }
+
+               cases = append(cases, reflect.SelectCase{
+                       Dir:  reflect.SelectRecv,
+                       Chan: reflect.ValueOf(signalChannel()),
+               })
+
+               if hasTimeout {
+                       cases = append(cases, reflect.SelectCase{
+                               Dir:  reflect.SelectRecv,
+                               Chan: reflect.ValueOf(time.After(timeout)),
+                       })
+               }
+
+               idx, value, ok := reflect.Select(cases)
+
+               if idx >= offset {
+                       l.PushBoolean(true)
+                       return 1
+               }
+
+               client := clients[idx]
+
+               var pkt *mt.Pkt = nil
+               if ok {
+                       pkt = value.Interface().(*mt.Pkt)
+               } else {
+                       client.state = csDisconnected
+               }
+
+               for _, handler := range client.handlers {
+                       handler.handle(pkt, l, idx+1)
+               }
+       }
+
+       panic("impossible")
+       return 0
+}
diff --git a/spec/casemap b/spec/casemap
new file mode 100644 (file)
index 0000000..c72df40
--- /dev/null
@@ -0,0 +1,23 @@
+id ID
+ao AO
+hud HUD
+hp HP
+fov FOV
+srp SRP
+sha1 SHA1
+ao_rm_add AORmAdd
+ao_msgs AOMsgs
+src_aoid SrcAOID
+ao_collision AOCollision
+add_hud AddHUD
+rm_hud RmHUD
+change_hud ChangeHUD
+hud_flags HUDFlags
+hud_type HUDType
+hud_field HUDField
+first_srp FirstSRP
+csm_restriction_flags CSMRestrictionFlags
+srp_bytes_salt_b SRPBytesSaltB
+no_csms NoCSMs
+join_ok JoinOK
+leave_ok LeaveOK
diff --git a/spec/client/enum b/spec/client/enum
new file mode 100644 (file)
index 0000000..cd5f166
--- /dev/null
@@ -0,0 +1,70 @@
+kick_reason
+       wrong_passwd
+       unexpected_data
+       srv_is_singleplayer
+       unsupported_ver
+       bad_name_chars
+       bad_name
+       too_many_clts
+       empty_passwd
+       already_connected
+       srv_err
+       custom
+       shutdown
+       crash
+chat_msg_type
+       postfix Msg
+       raw
+       normal
+       announce
+       sys
+sound_src_type
+       postfix Src
+       no
+       pos
+       ao
+anim_type
+       postfix Anim
+       no
+       vertical_frame
+       sprite_sheet
+hud_type
+       postfix HUD
+       img
+       text
+       statbar
+       inv
+       waypoint
+       img_waypoint
+hud_field
+       prefix HUD
+       pos
+       name
+       scale
+       text
+       number
+       item
+       dir
+       align
+       offset
+       world_pos
+       size
+       z_index
+       text_2
+hotbar_param
+       prefix Hotbar
+       size
+       img
+       sel_img
+mod_chan_sig
+       join_ok
+       join_fail
+       leave_ok
+       leave_fail
+       not_registered
+       set_state
+player_list_update_type
+       postfix Players
+       init
+       add
+       remove
diff --git a/spec/client/flag b/spec/client/flag
new file mode 100644 (file)
index 0000000..e13f4da
--- /dev/null
@@ -0,0 +1,19 @@
+auth_methods
+       legacy_passwd
+       srp
+       first_srp
+csm_restriction_flags
+       no_csms
+       no_chat_msgs
+       no_node_defs
+       limit_map_range
+       no_player_list
+hud_flags
+       prefix Show
+       hotbar
+       health_bar
+       crosshair
+       wielded_item
+       breath_bar
+       minimap
+       radar_minimap
diff --git a/spec/client/pkt b/spec/client/pkt
new file mode 100644 (file)
index 0000000..bc04127
--- /dev/null
@@ -0,0 +1,276 @@
+hello
+       integer serialize_ver
+       integer compression
+       integer proto_ver
+       auth_methods auth_methods
+       string username
+accept_auth
+       vec3 player_pos
+       # int64
+       number map_seed
+       number send_interval
+       auth_methods sudo_auth_methods
+accept_sudo_mode
+deny_sudo_mode
+kick
+       kick_reason reason
+       [custom] dr := val.Reason; dr == mt.Custom || dr == mt.Shutdown || dr == mt.Crash
+       string custom
+       [reconnect] dr := val.Reason; dr == mt.Shutdown || dr == mt.Crash
+       boolean reconnect
+blk_data
+       vec3 blkpos
+       # TODO
+add_node
+       vec3 pos
+       node node
+       boolean keep_meta
+remove_node
+       vec3 pos
+inv
+       string inv
+time_of_day
+       integer time
+       number speed
+csm_restriction_flags
+       csm_restriction_flags flags
+       integer map_range
+add_player_vel
+       vec3 vel
+media_push
+       fixed_string sha1
+       string filename
+       boolean should_cache
+       string data
+chat_msg
+       chat_msg_type type
+       string sender
+       string text
+       # int64
+       number timestamp
+ao_rm_add
+       # TODO
+ao_msgs
+       # TODO
+hp
+       integer hp
+move_player
+       vec3 pos
+       number pitch
+       number yaw
+legacy_kick
+       string reason
+fov
+       number fov
+       boolean multiplier
+       number transition_time
+death_screen
+       boolean point_cam
+       vec3 point_at
+media
+       # TODO
+node_defs
+       # TODO
+announce_media
+       # TODO
+item_defs
+       # TODO
+play_sound
+       integer id
+       string name
+       number gain
+       sound_src_type src_type
+       vec3 pos
+       integer src_aoid
+       boolean loop
+       number fade
+       number pitch
+       boolean ephemeral
+stop_sound
+       integer id
+privs
+       string_set privs
+inv_formspec
+       string formspec
+detached_inv
+       string name
+       boolean keep
+       integer len
+       string inv
+show_formspec
+       string formspec
+       string formname
+movement
+       number default_accel
+       number air_accel
+       number fast_accel
+       number walk_speed
+       number crouch_speed
+       number fast_speed
+       number climb_speed
+       number jump_speed
+       number fluidity
+       number smoothing
+       number sink
+       number gravity
+spawn_particle
+       vec3 pos
+       vec3 vel
+       vec3 acc
+       number expiration_time
+       number size
+       boolean collide
+       string texture
+       boolean vertical
+       boolean collision_rm
+       tile_anim anim_params
+       integer glow
+       boolean ao_collision
+       integer node_param0
+       integer node_param2
+       integer node_tile
+add_particle_spawner
+       integer amount
+       number duration
+       box3 pos
+       box3 vel
+       box3 acc
+       box1 expiration_time
+       box1 size
+       boolean collide
+       string texture
+       integer id
+       boolean vertical
+       boolean collision_rm
+       tile_anim anim_params
+       integer glow
+       boolean ao_collision
+       integer node_param0
+       integer node_param2
+       integer node_tile
+add_hud
+       integer id
+       hud hud
+rm_hud
+       integer id
+change_hud
+       integer id
+       hud_field field
+       [pos] val.Field == mt.HUDPos
+       [name] val.Field == mt.HUDName
+       [text] val.Field == mt.HUDText
+       [number] val.Field == mt.HUDNumber
+       [item] val.Field == mt.HUDItem
+       [dir] val.Field == mt.HUDDir
+       [align] val.Field == mt.HUDAlign
+       [offset] val.Field == mt.HUDOffset
+       [world_pos] val.Field == mt.HUDWorldPos
+       [size] val.Field == mt.HUDSize
+       [z_index] val.Field == mt.HUDZIndex
+       [text_2] val.Field == mt.HUDText2
+       vec2 pos
+       string name
+       string text
+       integer number
+       integer item
+       integer dir
+       vec2 align
+       vec2 offset
+       vec3 world_pos
+       vec2 size
+       integer z_index
+       string text_2
+hud_flags
+       hud_flags flags
+       hud_flags mask
+set_hotbar_param
+       hotbar_param param
+       integer size
+       string img
+breath
+       integer breath
+sky_params
+       color bg_color
+       string type
+       boolean clouds
+       color sun_fog_tint
+       color moon_fog_tint
+       string fog_tint_type
+       [textures] val.Type == "skybox"
+       texture_list textures
+       [day_sky] val.Type == "regular"
+       [day_horizon] val.Type == "regular"
+       [dawn_sky] val.Type == "regular"
+       [dawn_horizon] val.Type == "regular"
+       [night_sky] val.Type == "regular"
+       [night_horizon] val.Type == "regular"
+       [indoor] val.Type == "regular"
+       color day_sky
+       color day_horizon
+       color dawn_sky
+       color dawn_horizon
+       color night_sky
+       color night_horizon
+       color indoor
+override_day_night_ratio
+       boolean override
+       integer ratio
+local_player_anim
+       box1 idle
+       box1 walk
+       box1 dig
+       box1 walk_dig
+       number speed
+eye_offset
+       vec3 first
+       vec3 third
+del_particle_spawner
+       integer id
+cloud_params
+       number density
+       color diffuse_color
+       color ambient_color
+       number height
+       number thickness
+       vec2 speed
+fade_sound
+       integer id
+       number step
+       number gain
+update_player_list
+       player_list_update_type type
+       string_list players
+mod_chan_msg
+       string channel
+       string sender
+       string msg
+mod_chan_sig
+       mod_chan_sig signal
+       string channel
+node_metas_changed
+       # TODO
+sun_params
+       boolean visible
+       string texture
+       string tone_map
+       string rise
+       boolean rising
+       number size
+moon_params
+       boolean visible
+       string texture
+       string tone_map
+       number size
+star_params
+       boolean visible
+       integer count
+       color color
+       number size
+srp_bytes_salt_b
+       string salt
+       string b
+formspec_prepend
+       string prepend
+minimap_modes
+       # TODO
+disco
diff --git a/spec/client/struct b/spec/client/struct
new file mode 100644 (file)
index 0000000..1fa149c
--- /dev/null
@@ -0,0 +1,24 @@
+node
+       integer param0
+       integer param1
+       integer param2
+tile_anim
+       anim_type type
+       vec2 aspect_ratio
+       vec2 n_frames
+       number duration
+hud
+       hud_type type
+       vec2 pos
+       string name
+       vec2 scale
+       string text
+       integer number
+       integer item
+       integer dir
+       vec2 align
+       vec2 offset
+       vec3 world_pos
+       vec2 size
+       integer z_index
+       string text_2
diff --git a/types.go b/types.go
new file mode 100644 (file)
index 0000000..6410457
--- /dev/null
+++ b/types.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+       "github.com/Shopify/go-lua"
+       "github.com/anon55555/mt"
+       "image/color"
+)
+
+func luaPushVec2(l *lua.State, val [2]float64) {
+       l.Global("vec2")
+       l.PushNumber(val[0])
+       l.PushNumber(val[1])
+       l.Call(2, 1)
+}
+
+func luaPushVec3(l *lua.State, val [3]float64) {
+       l.Global("vec3")
+       l.PushNumber(val[0])
+       l.PushNumber(val[1])
+       l.PushNumber(val[2])
+       l.Call(3, 1)
+}
+
+func luaPushBox1(l *lua.State, val [2]float64) {
+       l.Global("box")
+       l.PushNumber(val[0])
+       l.PushNumber(val[1])
+       l.Call(2, 1)
+}
+
+func luaPushBox2(l *lua.State, val [2][2]float64) {
+       l.Global("box")
+       luaPushVec2(l, val[0])
+       luaPushVec2(l, val[1])
+       l.Call(2, 1)
+}
+
+func luaPushBox3(l *lua.State, val [2][3]float64) {
+       l.Global("box")
+       luaPushVec3(l, val[0])
+       luaPushVec3(l, val[1])
+       l.Call(2, 1)
+}
+
+func luaPushColor(l *lua.State, val color.NRGBA) {
+       l.NewTable()
+       l.PushInteger(int(val.R))
+       l.SetField(-2, "r")
+       l.PushInteger(int(val.G))
+       l.SetField(-2, "g")
+       l.PushInteger(int(val.B))
+       l.SetField(-2, "b")
+       l.PushInteger(int(val.A))
+       l.SetField(-2, "a")
+}
+
+func luaPushStringSet(l *lua.State, val []string) {
+       l.NewTable()
+       for _, str := range val {
+               l.PushBoolean(true)
+               l.SetField(-2, str)
+       }
+}
+
+func luaPushStringList(l *lua.State, val []string) {
+       l.NewTable()
+       for i, str := range val {
+               l.PushString(str)
+               l.RawSetInt(-2, i+1)
+       }
+}
+
+// i hate go for making me do this instead of just using luaPushStringList
+// but i dont want to make an unsafe cast either
+func luaPushTextureList(l *lua.State, val []mt.Texture) {
+       l.NewTable()
+       for i, str := range val {
+               l.PushString(string(str))
+               l.RawSetInt(-2, i+1)
+       }
+}