aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--auth.go254
-rw-r--r--builtin/vector.lua126
-rw-r--r--callbacks.go85
-rw-r--r--client.go172
-rw-r--r--convert.go882
-rw-r--r--go.mod10
-rw-r--r--go.sum8
-rw-r--r--hydra.go74
-rwxr-xr-xmkconvert.lua201
-rw-r--r--poll.go89
-rw-r--r--spec/casemap23
-rw-r--r--spec/client/enum70
-rw-r--r--spec/client/flag19
-rw-r--r--spec/client/pkt276
-rw-r--r--spec/client/struct24
-rw-r--r--types.go81
17 files changed, 2395 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..83f0206
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+hydra
diff --git a/auth.go b/auth.go
new file mode 100644
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
index 0000000..2fd926a
--- /dev/null
+++ b/builtin/vector.lua
@@ -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
index 0000000..e16aec7
--- /dev/null
+++ b/callbacks.go
@@ -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
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
index 0000000..c5e4daa
--- /dev/null
+++ b/convert.go
@@ -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
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
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
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
index 0000000..d49d46d
--- /dev/null
+++ b/mkconvert.lua
@@ -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
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
index 0000000..c72df40
--- /dev/null
+++ b/spec/casemap
@@ -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
index 0000000..cd5f166
--- /dev/null
+++ b/spec/client/enum
@@ -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
index 0000000..e13f4da
--- /dev/null
+++ b/spec/client/flag
@@ -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
index 0000000..bc04127
--- /dev/null
+++ b/spec/client/pkt
@@ -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
index 0000000..1fa149c
--- /dev/null
+++ b/spec/client/struct
@@ -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
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)
+ }
+}