]> git.lizzy.rs Git - hydra-dragonfire.git/commitdiff
Implement sending of packets
authorElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 29 May 2022 21:09:53 +0000 (23:09 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 29 May 2022 21:09:53 +0000 (23:09 +0200)
17 files changed:
builtin/client.lua [new file with mode: 0644]
builtin/vector.lua
client.go
example/chat-client.lua
example/dump-traffic.lua
fromlua/generate.lua [new file with mode: 0755]
fromlua/generated.go [new file with mode: 0644]
fromlua/static.go [new file with mode: 0644]
hydra.go
parse_spec.lua
spec/casemap
spec/client/pkt
spec/server/enum [new file with mode: 0644]
spec/server/flag [new file with mode: 0644]
spec/server/pkt [new file with mode: 0644]
spec/server/struct [new file with mode: 0644]
tolua/generate.lua

diff --git a/builtin/client.lua b/builtin/client.lua
new file mode 100644 (file)
index 0000000..b4ed556
--- /dev/null
@@ -0,0 +1,11 @@
+--[[ builtin/client.lua ]]--
+function package.loaded.client()
+       local address, name, password = unpack(arg)
+       local client = hydra.client(address)
+
+       client:enable("auth")
+       client.auth:username(name)
+       client.auth:password(password or "")
+
+       return client
+end
index 95e1ebd3c8948bb17eada814426ea370a31043b6..4991c1ef02d92d606878d957d2b7fb7b36416fd5 100644 (file)
@@ -34,6 +34,14 @@ function mt_vec2:__tostring()
        return "(" .. self.x .. ", " .. self.y .. ")"
 end
 
+mt_vec2.__index = {
+       validate = function(self)
+               assert(type(self.x) == "number")
+               assert(type(self.y) == "number")
+               return self
+       end
+}
+
 function vec2(a, b)
        local o = {}
 
@@ -46,7 +54,7 @@ function vec2(a, b)
        end
 
        setmetatable(o, mt_vec2)
-       return o
+       return o:validate()
 end
 
 -- vec3
@@ -57,7 +65,7 @@ local mt_vec3 = arith_mt(function(op)
                        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
        ]]
 end)
 
@@ -69,6 +77,15 @@ function mt_vec3:__tostring()
        return "(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")"
 end
 
+mt_vec3.__index = {
+       validate = function(self)
+               assert(type(self.x) == "number")
+               assert(type(self.y) == "number")
+               assert(type(self.z) == "number")
+               return self
+       end
+}
+
 function vec3(a, b, c)
        local o = {}
 
@@ -83,7 +100,7 @@ function vec3(a, b, c)
        end
 
        setmetatable(o, mt_vec3)
-       return o
+       return o:validate()
 end
 
 -- box
@@ -108,6 +125,15 @@ mt_box.__index = {
                        return a.min <= b.min and a.max >= b.max
                end
        end,
+       validate = function(self)
+               if type(self.min) == "number" then
+                       assert(type(self.max) == "number")
+               else
+                       assert(not self.min.z == not self.max.z)
+                       self.min:validate()
+                       self.max:validate()
+               end
+       end,
 }
 
 function box(a, b)
@@ -122,5 +148,5 @@ function box(a, b)
        end
 
        setmetatable(o, mt_box)
-       return o
+       return o:validate()
 end
index 69a4c2902795fb05b00f74e78678045419c7a030..341f564a257bc6e3e1c1b910f0b1c40c45ca8242 100644 (file)
--- a/client.go
+++ b/client.go
@@ -3,6 +3,7 @@ package main
 import (
        "errors"
        "github.com/anon55555/mt"
+       "github.com/dragonfireclient/hydra-dragonfire/fromlua"
        "github.com/dragonfireclient/hydra-dragonfire/tolua"
        "github.com/yuin/gopher-lua"
        "net"
@@ -46,6 +47,7 @@ var clientFuncs = map[string]lua.LGFunction{
        "subscribe":   l_client_subscribe,
        "unsubscribe": l_client_unsubscribe,
        "wildcard":    l_client_wildcard,
+       "send":        l_client_send,
 }
 
 func getClient(l *lua.LState) *Client {
@@ -254,20 +256,24 @@ func l_client_wildcard(l *lua.LState) int {
        return 0
 }
 
-/*
-
 func l_client_send(l *lua.LState) int {
        client := getClient(l)
-       pkt := fromlua.Pkt(l.CheckTable(2))
+       cmd := fromlua.Cmd(l)
+       doAck := l.ToBool(4)
 
        client.mu.Lock()
        defer client.mu.Unlock()
 
        if client.state == csConnected {
-               client.conn.Send(pkt)
+               ack, err := client.conn.SendCmd(cmd)
+               if err != nil {
+                       panic(err)
+               }
+
+               if doAck && !cmd.DefaultPktInfo().Unrel {
+                       <-ack
+               }
        }
 
        return 0
 }
-
-*/
index 7acb9a6a02d3e3ec4a7abd56cf026d68cb18df97..b1bd505651b470676ae05828774910120dd6d6ec 100755 (executable)
@@ -1,21 +1,18 @@
 #!/usr/bin/env hydra-dragonfire
 local escapes = require("escapes")
-local address, name, password = unpack(arg)
-local client = hydra.client(address)
-
-client:enable("auth")
-client.auth:username(name)
-client.auth:password(password or "")
+local client = require("client")()
 
 client:subscribe("chat_msg")
 client:connect()
 
 while not hydra.canceled() do
-       local pkt, interrupt = client:poll()
+       local pkt, interrupt = client:poll(1)
 
        if pkt then
                print(escapes.strip_all(pkt.text))
-       elseif not interrupt then
+       elseif interrupt then
+               client:send("chat_msg", {msg = "test"})
+       else
                print("disconnected")
                break
        end
index 1fd43163049e6473c3b09d759008713c4b3af278..648aa4d091df5ea71622678779c98a22f4af5c46 100755 (executable)
@@ -1,10 +1,5 @@
 #!/usr/bin/env hydra-dragonfire
-local address, name, password = unpack(arg)
-local client = hydra.client(address)
-
-client:enable("auth")
-client.auth:username(name)
-client.auth:password(password or "")
+local client = require("client")()
 
 client:wildcard(true)
 client:connect()
@@ -21,7 +16,7 @@ while not hydra.canceled() do
                end
        elseif not interrupt then
                print("disconnected")
-               break   
+               break
        end
 end
 
diff --git a/fromlua/generate.lua b/fromlua/generate.lua
new file mode 100755 (executable)
index 0000000..75899b7
--- /dev/null
@@ -0,0 +1,198 @@
+#!/usr/bin/env lua
+dofile("../parse_spec.lua")
+
+local readers = {
+       SliceByte = true,
+       Byte = true,
+       String = true,
+       SliceField = true,
+       Field = true,
+       Bool = true,
+       PointedThing = true,
+}
+
+local static_uses = {
+       "[3]int16",
+       "AOID"
+}
+
+local function generate(name)
+       local fnname, index, child, childfn, childtype
+       local type = name
+
+       local open = name:find("%[")
+       local clos = name:find("%]")
+
+       if open == 1 then
+               index = name:sub(open + 1, clos - 1)
+               child = name:sub(clos + 1)
+               childfn, childtype = generate(child)
+               fnname = (index == "" and "Slice" or "Vec" .. index) .. childfn
+
+               type = "[" .. index .. "]" .. childtype
+       else
+               fnname = camel_case(name)
+
+               local c = name:sub(1, 1)
+               if c == c:upper() then
+                        type = "mt." .. name
+               end
+       end
+
+       if not readers[fnname] then
+               local fun = "func read" .. fnname .. "(l *lua.LState, val lua.LValue, ptr *" .. type  .. ") {\n"
+
+               if child then
+                       fun = fun .. "\tif val.Type() != lua.LTTable {\n\t\tpanic(\"invalid value for "
+                               .. name .. ": must be a table\")\n\t}\n"
+
+                       if index == "" then
+                               fun = fun ..
+[[
+       tbl := val.(*lua.LTable)
+       n := tbl.MaxN()
+       *ptr = make(]] .. type .. [[, n)
+       for i := range *ptr {
+               read]] .. childfn .. [[(l, l.RawGetInt(tbl, i+1), &(*ptr)[i])
+       }
+]]
+                       else
+                               local n = tonumber(index)
+                               for i, v in ipairs({"x", "y", "z"}) do
+                                       if i > n then
+                                               break
+                                       end
+
+                                       fun = fun
+                                               .. "\tread" .. childfn
+                                               .. "(l, l.GetField(val, \"" .. v .. "\"), &(*ptr)[" .. (i - 1) .. "])\n"
+                               end
+                       end
+               else
+                       fun = fun .. "\tif val.Type() != lua.LTNumber {\n\t\tpanic(\"invalid value for "
+                               .. name .. ": must be a number\")\n\t}\n"
+                               .. "\t*ptr = " .. type .. "(val.(lua.LNumber))\n"
+               end
+
+               fun = fun .. "}\n\n"
+
+               readers[fnname] = fun
+       end
+
+       return fnname, type
+end
+
+for _, use in ipairs(static_uses) do
+       generate(use)
+end
+
+local function signature(name, prefix, type)
+       local camel = camel_case(name)
+       return "func read" .. camel .. "(l *lua.LState, val lua.LValue, ptr *" .. prefix .. camel  .. ") {\n"
+end
+
+for name, fields in spairs(parse_spec("server/enum")) do
+       local camel = camel_case(name)
+       local fun = signature(name, "mt.")
+
+       local impl = ""
+       for _, var in ipairs(fields) do
+               local equals = "*ptr = mt." .. apply_prefix(fields, var) .. "\n"
+
+               if var == "no" then
+                       fun = fun .. "\tif val.Type() == lua.LTNil {\n\t\t" .. equals .. "\t\treturn\n\t}\n"
+               else
+                       impl = impl .. "\tcase \"" .. var .. "\":\n\t\t" .. equals
+               end
+       end
+
+       fun = fun
+               .. "\tif val.Type() != lua.LTString {\n\t\tpanic(\"invalid value for "
+               .. camel .. ": must be a string\")\n\t}\n"
+               .. "\tstr := string(val.(lua.LString))\n"
+               .. "\tswitch str {\n" .. impl
+               .. "\tdefault:\n\t\tpanic(\"invalid value for " .. name .. ": \" + str)\n\t}\n}\n\n"
+
+       readers[camel] = fun
+end
+
+for name, fields in spairs(parse_spec("server/flag")) do
+       local camel = camel_case(name)
+       local fun = signature(name, "mt.")
+               .. "\tif val.Type() != lua.LTTable {\n\t\tpanic(\"invalid value for "
+               .. camel .. ": must be a table\")\n\t}\n"
+
+       for _, var in ipairs(fields) do
+               fun = fun .. "\tif l.GetField(val, \"" .. var .. "\") == lua.LTrue {\n"
+                       .. "\t\t*ptr = *ptr | mt." .. apply_prefix(fields, var) .. "\n\t}\n"
+       end
+
+       fun = fun .. "}\n\n"
+       readers[camel] = fun
+end
+
+local function fields_fromlua(fields, indent)
+       local impl = ""
+
+       for name, type in spairs(fields) do
+               impl = impl .. indent .. "read" .. generate(type) .. "(l, l.GetField(val, \"" .. name .. "\"), &ptr."
+                       .. camel_case(name) .. ")\n"
+       end
+
+       return impl
+end
+
+for name, fields in spairs(parse_spec("server/struct", true)) do
+       local camel = camel_case(name)
+       readers[camel] = signature(name, "mt.")
+               .. "\tif val.Type() != lua.LTTable {\n"
+               .. "\t\tpanic(\"invalid value for " .. camel .. ": must be a table\")\n\t}\n"
+               .. fields_fromlua(fields, "\t")
+               .. "}\n\n"
+end
+
+local pkt_impl = ""
+
+for name, fields in spairs(parse_spec("server/pkt", true)) do
+       pkt_impl = pkt_impl
+               .. "\tcase \"" .. name .. "\"" .. "" .. ":\n"
+               .. "\t\tptr := &mt.ToSrv" .. camel_case(name) .. "{}\n"
+
+       if next(fields) then
+               pkt_impl = pkt_impl
+                       .. "\t\tval := l.CheckTable(3)\n"
+                       .. fields_fromlua(fields, "\t\t")
+       end
+
+       pkt_impl = pkt_impl
+               .. "\t\treturn ptr\n"
+end
+
+local funcs = ""
+for _, fn in spairs(readers) do
+       if type(fn) == "string" then
+               funcs = funcs .. fn
+       end
+end
+
+local f = io.open("generated.go", "w")
+f:write([[
+// generated by generate.lua, DO NOT EDIT
+package fromlua
+
+import (
+       "github.com/anon55555/mt"
+       "github.com/yuin/gopher-lua"
+)
+
+]] .. funcs .. [[
+func Cmd(l *lua.LState) mt.Cmd {
+       str := l.CheckString(2)
+       switch str {
+]] .. pkt_impl .. [[
+       }
+
+       panic("invalid packet type: " + str)
+}
+]])
+f:close()
diff --git a/fromlua/generated.go b/fromlua/generated.go
new file mode 100644 (file)
index 0000000..c93c229
--- /dev/null
@@ -0,0 +1,320 @@
+// generated by generate.lua, DO NOT EDIT
+package fromlua
+
+import (
+       "github.com/anon55555/mt"
+       "github.com/yuin/gopher-lua"
+)
+
+func readAOID(l *lua.LState, val lua.LValue, ptr *mt.AOID) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for AOID: must be a number")
+       }
+       *ptr = mt.AOID(val.(lua.LNumber))
+}
+
+func readCompressionModes(l *lua.LState, val lua.LValue, ptr *mt.CompressionModes) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for CompressionModes: must be a number")
+       }
+       *ptr = mt.CompressionModes(val.(lua.LNumber))
+}
+
+func readInt16(l *lua.LState, val lua.LValue, ptr *int16) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for int16: must be a number")
+       }
+       *ptr = int16(val.(lua.LNumber))
+}
+
+func readInt32(l *lua.LState, val lua.LValue, ptr *int32) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for int32: must be a number")
+       }
+       *ptr = int32(val.(lua.LNumber))
+}
+
+func readInteraction(l *lua.LState, val lua.LValue, ptr *mt.Interaction) {
+       if val.Type() != lua.LTString {
+               panic("invalid value for Interaction: must be a string")
+       }
+       str := string(val.(lua.LString))
+       switch str {
+       case "dig":
+               *ptr = mt.Dig
+       case "stop_digging":
+               *ptr = mt.StopDigging
+       case "dug":
+               *ptr = mt.Dug
+       case "place":
+               *ptr = mt.Place
+       case "use":
+               *ptr = mt.Use
+       case "activate":
+               *ptr = mt.Activate
+       default:
+               panic("invalid value for interaction: " + str)
+       }
+}
+
+func readKeys(l *lua.LState, val lua.LValue, ptr *mt.Keys) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for Keys: must be a table")
+       }
+       if l.GetField(val, "forward") == lua.LTrue {
+               *ptr = *ptr | mt.ForwardKey
+       }
+       if l.GetField(val, "backward") == lua.LTrue {
+               *ptr = *ptr | mt.BackwardKey
+       }
+       if l.GetField(val, "left") == lua.LTrue {
+               *ptr = *ptr | mt.LeftKey
+       }
+       if l.GetField(val, "right") == lua.LTrue {
+               *ptr = *ptr | mt.RightKey
+       }
+       if l.GetField(val, "jump") == lua.LTrue {
+               *ptr = *ptr | mt.JumpKey
+       }
+       if l.GetField(val, "special") == lua.LTrue {
+               *ptr = *ptr | mt.SpecialKey
+       }
+       if l.GetField(val, "sneak") == lua.LTrue {
+               *ptr = *ptr | mt.SneakKey
+       }
+       if l.GetField(val, "dig") == lua.LTrue {
+               *ptr = *ptr | mt.DigKey
+       }
+       if l.GetField(val, "place") == lua.LTrue {
+               *ptr = *ptr | mt.PlaceKey
+       }
+       if l.GetField(val, "zoom") == lua.LTrue {
+               *ptr = *ptr | mt.ZoomKey
+       }
+}
+
+func readPlayerPos(l *lua.LState, val lua.LValue, ptr *mt.PlayerPos) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for PlayerPos: must be a table")
+       }
+       readUint8(l, l.GetField(val, "fov80"), &ptr.FOV80)
+       readKeys(l, l.GetField(val, "keys"), &ptr.Keys)
+       readInt32(l, l.GetField(val, "pitch100"), &ptr.Pitch100)
+       readVec3Int32(l, l.GetField(val, "pos100"), &ptr.Pos100)
+       readVec3Int32(l, l.GetField(val, "vel100"), &ptr.Vel100)
+       readUint8(l, l.GetField(val, "wanted_range"), &ptr.WantedRange)
+       readInt32(l, l.GetField(val, "yaw100"), &ptr.Yaw100)
+}
+
+func readSliceSoundID(l *lua.LState, val lua.LValue, ptr *[]mt.SoundID) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for []SoundID: must be a table")
+       }
+       tbl := val.(*lua.LTable)
+       n := tbl.MaxN()
+       *ptr = make([]mt.SoundID, n)
+       for i := range *ptr {
+               readSoundID(l, l.RawGetInt(tbl, i+1), &(*ptr)[i])
+       }
+}
+
+func readSliceString(l *lua.LState, val lua.LValue, ptr *[]string) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for []string: must be a table")
+       }
+       tbl := val.(*lua.LTable)
+       n := tbl.MaxN()
+       *ptr = make([]string, n)
+       for i := range *ptr {
+               readString(l, l.RawGetInt(tbl, i+1), &(*ptr)[i])
+       }
+}
+
+func readSliceVec3Int16(l *lua.LState, val lua.LValue, ptr *[][3]int16) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for [][3]int16: must be a table")
+       }
+       tbl := val.(*lua.LTable)
+       n := tbl.MaxN()
+       *ptr = make([][3]int16, n)
+       for i := range *ptr {
+               readVec3Int16(l, l.RawGetInt(tbl, i+1), &(*ptr)[i])
+       }
+}
+
+func readSoundID(l *lua.LState, val lua.LValue, ptr *mt.SoundID) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for SoundID: must be a number")
+       }
+       *ptr = mt.SoundID(val.(lua.LNumber))
+}
+
+func readUint16(l *lua.LState, val lua.LValue, ptr *uint16) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for uint16: must be a number")
+       }
+       *ptr = uint16(val.(lua.LNumber))
+}
+
+func readUint8(l *lua.LState, val lua.LValue, ptr *uint8) {
+       if val.Type() != lua.LTNumber {
+               panic("invalid value for uint8: must be a number")
+       }
+       *ptr = uint8(val.(lua.LNumber))
+}
+
+func readVec3Int16(l *lua.LState, val lua.LValue, ptr *[3]int16) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for [3]int16: must be a table")
+       }
+       readInt16(l, l.GetField(val, "x"), &(*ptr)[0])
+       readInt16(l, l.GetField(val, "y"), &(*ptr)[1])
+       readInt16(l, l.GetField(val, "z"), &(*ptr)[2])
+}
+
+func readVec3Int32(l *lua.LState, val lua.LValue, ptr *[3]int32) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for [3]int32: must be a table")
+       }
+       readInt32(l, l.GetField(val, "x"), &(*ptr)[0])
+       readInt32(l, l.GetField(val, "y"), &(*ptr)[1])
+       readInt32(l, l.GetField(val, "z"), &(*ptr)[2])
+}
+
+func Cmd(l *lua.LState) mt.Cmd {
+       str := l.CheckString(2)
+       switch str {
+       case "chat_msg":
+               ptr := &mt.ToSrvChatMsg{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "msg"), &ptr.Msg)
+               return ptr
+       case "clt_ready":
+               ptr := &mt.ToSrvCltReady{}
+               val := l.CheckTable(3)
+               readUint16(l, l.GetField(val, "formspec"), &ptr.Formspec)
+               readUint8(l, l.GetField(val, "major"), &ptr.Major)
+               readUint8(l, l.GetField(val, "minor"), &ptr.Minor)
+               readUint8(l, l.GetField(val, "patch"), &ptr.Patch)
+               readString(l, l.GetField(val, "version"), &ptr.Version)
+               return ptr
+       case "deleted_blks":
+               ptr := &mt.ToSrvDeletedBlks{}
+               val := l.CheckTable(3)
+               readSliceVec3Int16(l, l.GetField(val, "blks"), &ptr.Blks)
+               return ptr
+       case "fall_dmg":
+               ptr := &mt.ToSrvFallDmg{}
+               val := l.CheckTable(3)
+               readUint16(l, l.GetField(val, "amount"), &ptr.Amount)
+               return ptr
+       case "first_srp":
+               ptr := &mt.ToSrvFirstSRP{}
+               val := l.CheckTable(3)
+               readBool(l, l.GetField(val, "empty_passwd"), &ptr.EmptyPasswd)
+               readSliceByte(l, l.GetField(val, "salt"), &ptr.Salt)
+               readSliceByte(l, l.GetField(val, "verifier"), &ptr.Verifier)
+               return ptr
+       case "got_blks":
+               ptr := &mt.ToSrvGotBlks{}
+               val := l.CheckTable(3)
+               readSliceVec3Int16(l, l.GetField(val, "blks"), &ptr.Blks)
+               return ptr
+       case "init":
+               ptr := &mt.ToSrvInit{}
+               val := l.CheckTable(3)
+               readUint16(l, l.GetField(val, "max_proto_ver"), &ptr.MaxProtoVer)
+               readUint16(l, l.GetField(val, "min_proto_ver"), &ptr.MinProtoVer)
+               readString(l, l.GetField(val, "player_name"), &ptr.PlayerName)
+               readBool(l, l.GetField(val, "send_full_item_meta"), &ptr.SendFullItemMeta)
+               readUint8(l, l.GetField(val, "serialize_ver"), &ptr.SerializeVer)
+               readCompressionModes(l, l.GetField(val, "supported_compression"), &ptr.SupportedCompression)
+               return ptr
+       case "init2":
+               ptr := &mt.ToSrvInit2{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "lang"), &ptr.Lang)
+               return ptr
+       case "interact":
+               ptr := &mt.ToSrvInteract{}
+               val := l.CheckTable(3)
+               readInteraction(l, l.GetField(val, "action"), &ptr.Action)
+               readUint16(l, l.GetField(val, "item_slot"), &ptr.ItemSlot)
+               readPointedThing(l, l.GetField(val, "pointed"), &ptr.Pointed)
+               readPlayerPos(l, l.GetField(val, "pos"), &ptr.Pos)
+               return ptr
+       case "inv_action":
+               ptr := &mt.ToSrvInvAction{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "action"), &ptr.Action)
+               return ptr
+       case "inv_fields":
+               ptr := &mt.ToSrvInvFields{}
+               val := l.CheckTable(3)
+               readSliceField(l, l.GetField(val, "fields"), &ptr.Fields)
+               readString(l, l.GetField(val, "formname"), &ptr.Formname)
+               return ptr
+       case "join_mod_chan":
+               ptr := &mt.ToSrvJoinModChan{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "channel"), &ptr.Channel)
+               return ptr
+       case "leave_mod_chan":
+               ptr := &mt.ToSrvLeaveModChan{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "channel"), &ptr.Channel)
+               return ptr
+       case "msg_mod_chan":
+               ptr := &mt.ToSrvMsgModChan{}
+               val := l.CheckTable(3)
+               readString(l, l.GetField(val, "channel"), &ptr.Channel)
+               readString(l, l.GetField(val, "msg"), &ptr.Msg)
+               return ptr
+       case "nil":
+               ptr := &mt.ToSrvNil{}
+               return ptr
+       case "node_meta_fields":
+               ptr := &mt.ToSrvNodeMetaFields{}
+               val := l.CheckTable(3)
+               readSliceField(l, l.GetField(val, "fields"), &ptr.Fields)
+               readString(l, l.GetField(val, "formname"), &ptr.Formname)
+               readVec3Int16(l, l.GetField(val, "pos"), &ptr.Pos)
+               return ptr
+       case "player_pos":
+               ptr := &mt.ToSrvPlayerPos{}
+               val := l.CheckTable(3)
+               readPlayerPos(l, l.GetField(val, "pos"), &ptr.Pos)
+               return ptr
+       case "removed_sounds":
+               ptr := &mt.ToSrvRemovedSounds{}
+               val := l.CheckTable(3)
+               readSliceSoundID(l, l.GetField(val, "ids"), &ptr.IDs)
+               return ptr
+       case "req_media":
+               ptr := &mt.ToSrvReqMedia{}
+               val := l.CheckTable(3)
+               readSliceString(l, l.GetField(val, "filenames"), &ptr.Filenames)
+               return ptr
+       case "respawn":
+               ptr := &mt.ToSrvRespawn{}
+               return ptr
+       case "select_item":
+               ptr := &mt.ToSrvSelectItem{}
+               val := l.CheckTable(3)
+               readUint16(l, l.GetField(val, "slot"), &ptr.Slot)
+               return ptr
+       case "srp_bytes_a":
+               ptr := &mt.ToSrvSRPBytesA{}
+               val := l.CheckTable(3)
+               readSliceByte(l, l.GetField(val, "a"), &ptr.A)
+               readBool(l, l.GetField(val, "no_sha1"), &ptr.NoSHA1)
+               return ptr
+       case "srp_bytes_m":
+               ptr := &mt.ToSrvSRPBytesM{}
+               val := l.CheckTable(3)
+               readSliceByte(l, l.GetField(val, "m"), &ptr.M)
+               return ptr
+       }
+
+       panic("invalid packet type: " + str)
+}
diff --git a/fromlua/static.go b/fromlua/static.go
new file mode 100644 (file)
index 0000000..b989db8
--- /dev/null
@@ -0,0 +1,59 @@
+package fromlua
+
+import (
+       "github.com/anon55555/mt"
+       "github.com/yuin/gopher-lua"
+)
+
+//go:generate ./generate.lua
+
+func readBool(l *lua.LState, val lua.LValue, ptr *bool) {
+       if val.Type() != lua.LTBool {
+               panic("invalid value for bool: must be a boolean")
+       }
+       *ptr = bool(val.(lua.LBool))
+}
+
+func readString(l *lua.LState, val lua.LValue, ptr *string) {
+       if val.Type() != lua.LTString {
+               panic("invalid value for string: must be a string")
+       }
+       *ptr = string(val.(lua.LString))
+}
+
+func readSliceByte(l *lua.LState, val lua.LValue, ptr *[]byte) {
+       if val.Type() != lua.LTString {
+               panic("invalid value for []byte: must be a string")
+       }
+       *ptr = []byte(val.(lua.LString))
+}
+
+func readSliceField(l *lua.LState, val lua.LValue, ptr *[]mt.Field) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for []Field: must be a table")
+       }
+       val.(*lua.LTable).ForEach(func(k, v lua.LValue) {
+               if k.Type() != lua.LTString || v.Type() != lua.LTString {
+                       panic("invalid value for Field: key and value must be strings")
+               }
+               *ptr = append(*ptr, mt.Field{Name: string(k.(lua.LString)), Value: string(v.(lua.LString))})
+       })
+}
+
+func readPointedThing(l *lua.LState, val lua.LValue, ptr *mt.PointedThing) {
+       if val.Type() != lua.LTTable {
+               panic("invalid value for PointedThing: must be a table")
+       }
+       id := l.GetField(val, "id")
+
+       if id == lua.LNil {
+               pt := &mt.PointedAO{}
+               readAOID(l, id, &(*pt).ID)
+               *ptr = pt
+       } else {
+               pt := &mt.PointedNode{}
+               readVec3Int16(l, l.GetField(val, "under"), &(*pt).Under)
+               readVec3Int16(l, l.GetField(val, "above"), &(*pt).Above)
+               *ptr = pt
+       }
+}
index 5deb1d4a2e4c10118316cc4821e9f26ce15f74bf..2f976e20b4118d0e84ac0c5f64e93ef72311a4cd 100644 (file)
--- a/hydra.go
+++ b/hydra.go
@@ -22,10 +22,14 @@ var builtinVector string
 //go:embed builtin/escapes.lua
 var builtinEscapes string
 
+//go:embed builtin/client.lua
+var builtinClient string
+
 var builtinFiles = []string{
        builtinLuaX,
        builtinVector,
        builtinEscapes,
+       builtinClient,
 }
 
 var hydraFuncs = map[string]lua.LGFunction{
index 449360cef79efedd6d7a1f177ce1d3f064a71dc7..588f4d715976ed7105fa48c481bb747ecad9fc45 100644 (file)
@@ -9,7 +9,7 @@ local function snext(t, state)
                        end
                end
                table.sort(t.__sorted)
-               
+
                key = t.__sorted[1]
        else
                for i, v in ipairs(t.__sorted) do
@@ -41,7 +41,7 @@ local function parse_pair(pair, value_first)
        if idx then
                local first, second = pair:sub(1, idx - 1), pair:sub(idx + 1)
 
-               if value_first and first:sub(1, 1) ~= "[" then
+               if value_first and first:sub(1, 1) ~= "{" then
                        return second, first
                else
                        return first, second
@@ -103,3 +103,6 @@ function camel_case(snake)
        return camel
 end
 
+function apply_prefix(fields, str)
+       return (fields.prefix or "") .. camel_case(str) .. (fields.postfix or "")
+end
index c72df400f435b43623177862de846324f3bad5d4..ab4c10a74faf8a2fe7360c08df31cb057e4010da 100644 (file)
@@ -18,6 +18,11 @@ hud_field HUDField
 first_srp FirstSRP
 csm_restriction_flags CSMRestrictionFlags
 srp_bytes_salt_b SRPBytesSaltB
+srp_bytes_a SRPBytesA
+srp_bytes_m SRPBytesM
 no_csms NoCSMs
 join_ok JoinOK
 leave_ok LeaveOK
+no_sha1 NoSHA1
+fov80 FOV80
+ids IDs
index e7c609bd9b3ba30c901d7c9b62bfb2d18bbb90a2..2a150e38ac89c78ddbf0abf6830075b03ddf6833 100644 (file)
@@ -6,7 +6,6 @@ hello
        string username
 accept_auth
        vec3 player_pos
-       # int64
        number map_seed
        number send_interval
        auth_methods sudo_auth_methods
@@ -14,9 +13,9 @@ accept_sudo_mode
 deny_sudo_mode
 kick
        kick_reason reason
-       [custom] val.Reason == mt.Custom || val.Reason == mt.Shutdown || val.Reason == mt.Crash
+       {custom} val.Reason == mt.Custom || val.Reason == mt.Shutdown || val.Reason == mt.Crash
        string custom
-       [reconnect] val.Reason == mt.Shutdown || val.Reason == mt.Crash
+       {reconnect} val.Reason == mt.Shutdown || val.Reason == mt.Crash
        boolean reconnect
 blk_data
        vec3 blkpos
@@ -46,7 +45,6 @@ chat_msg
        chat_msg_type type
        string sender
        string text
-       # int64
        number timestamp
 ao_rm_add
        # TODO
@@ -156,18 +154,18 @@ rm_hud
 change_hud
        number 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
+       {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
@@ -196,15 +194,15 @@ sky_params
        color sun_fog_tint
        color moon_fog_tint
        string fog_tint_type
-       [textures] val.Type == "skybox"
+       {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"
+       {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
diff --git a/spec/server/enum b/spec/server/enum
new file mode 100644 (file)
index 0000000..6f49e6d
--- /dev/null
@@ -0,0 +1,7 @@
+interaction
+       dig
+       stop_digging
+       dug
+       place
+       use
+       activate
diff --git a/spec/server/flag b/spec/server/flag
new file mode 100644 (file)
index 0000000..4f8c164
--- /dev/null
@@ -0,0 +1,12 @@
+keys
+       postfix Key
+       forward
+       backward
+       left
+       right
+       jump
+       special
+       sneak
+       dig
+       place
+       zoom
diff --git a/spec/server/pkt b/spec/server/pkt
new file mode 100644 (file)
index 0000000..5578d55
--- /dev/null
@@ -0,0 +1,63 @@
+nil
+init
+       uint8 serialize_ver
+       CompressionModes supported_compression
+       uint16 min_proto_ver
+       uint16 max_proto_ver
+       string player_name
+       bool send_full_item_meta
+init2
+       string lang
+join_mod_chan
+       string channel
+leave_mod_chan
+       string channel
+msg_mod_chan
+       string channel
+       string msg
+player_pos
+       PlayerPos pos
+got_blks
+       [][3]int16 blks
+deleted_blks
+       [][3]int16 blks
+inv_action
+       string action
+chat_msg
+       string msg
+fall_dmg
+       uint16 amount
+select_item
+       uint16 slot
+respawn
+interact
+       Interaction action
+       uint16 item_slot
+       PointedThing pointed
+       PlayerPos pos
+removed_sounds
+       []SoundID ids
+node_meta_fields
+       [3]int16 pos
+       string formname
+       []Field fields
+inv_fields
+       string formname
+       []Field fields
+req_media
+       []string filenames
+clt_ready
+       uint8 major
+       uint8 minor
+       uint8 patch
+       string version
+       uint16 formspec
+first_srp
+       []byte salt
+       []byte verifier
+       bool empty_passwd
+srp_bytes_a
+       []byte a
+       bool no_sha1
+srp_bytes_m
+       []byte m
diff --git a/spec/server/struct b/spec/server/struct
new file mode 100644 (file)
index 0000000..e971dbc
--- /dev/null
@@ -0,0 +1,8 @@
+player_pos
+       [3]int32 pos100
+       [3]int32 vel100
+       int32 pitch100
+       int32 yaw100
+       Keys keys
+       uint8 fov80
+       uint8 wanted_range
index 7c2d5a712fb3baa7375cb143e821d9b3f76ab797..31177138b0ee5ccafd5d48b69e235cb76c37c6b4 100755 (executable)
@@ -8,9 +8,8 @@ for name, fields in spairs(parse_spec("client/enum")) do
        funcs = funcs .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\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 "return lua.LNil" or "return lua.LString(\"" .. var .. "\")") .. "\n"
+               funcs = funcs .. "\tcase mt." .. apply_prefix(fields, var) .. ":\n\t\t" ..
+                       (var == "no" and "return lua.LNil" or "return lua.LString(\"" .. var .. "\")") .. "\n"
        end
 
        funcs = funcs .. "\t}\n\tpanic(\"impossible\")\n\treturn lua.LNil\n}\n\n"
@@ -21,15 +20,14 @@ for name, fields in spairs(parse_spec("client/flag")) do
        funcs = funcs .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\n\ttbl := l.NewTable()\n"
 
        for _, var in ipairs(fields) do
-               funcs = funcs .. "\tif val&mt."
-                       .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "")
+               funcs = funcs .. "\tif val&mt." .. apply_prefix(fields, var)
                        .. " != 0 {\n\t\tl.SetField(tbl, \"" .. var  .. "\", lua.LTrue)\n\t}\n"
        end
 
        funcs = funcs .. "\treturn tbl\n}\n\n"
 end
 
-local to_lua = {
+local tolua = {
        string = "lua.LString(string(VAL))",
        fixed_string = "lua.LString(string(VAL[:]))",
        boolean = "lua.LBool(VAL)",
@@ -41,24 +39,24 @@ local to_lua = {
        box3 = "Box3(l, [2][3]lua.LNumber{{lua.LNumber(VAL[0][0]), lua.LNumber(VAL[0][1]), lua.LNumber(VAL[0][2])}, {lua.LNumber(VAL[1][0]), lua.LNumber(VAL[1][1]), lua.LNumber(VAL[1][2])}})",
 }
 
-local function fields_to_lua(fields, indent)
+local function fields_tolua(fields, indent)
        local impl = ""
-       
+
        for name, type in spairs(fields) do
-               if name:sub(1, 1) ~= "[" then
+               if name:sub(1, 1) ~= "{" then
                        local camel = "val." .. camel_case(name)
 
                        local idt = indent
-                       local condition = fields["[" .. name .. "]"]
+                       local condition = fields["{" .. name .. "}"]
 
                        if condition then
-                               impl = impl .. indent .. "if " .. condition .. " {\n" 
+                               impl = impl .. indent .. "if " .. condition .. " {\n"
                                idt = idt .. "\t"
                        end
 
                        impl = impl .. idt .. "l.SetField(tbl, \"" .. name .. "\", "
-                       if to_lua[type] then
-                               impl = impl .. to_lua[type]:gsub("VAL", camel)
+                       if tolua[type] then
+                               impl = impl .. tolua[type]:gsub("VAL", camel)
                        else
                                impl = impl .. camel_case(type) .. "(l, " .. camel .. ")"
                        end
@@ -77,21 +75,21 @@ for name, fields in spairs(parse_spec("client/struct", true)) do
        local camel = camel_case(name)
        funcs = funcs
                .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\n\ttbl := l.NewTable()\n"
-               .. fields_to_lua(fields, "\t")
+               .. fields_tolua(fields, "\t")
                .. "\treturn tbl\n}\n\n"
 end
 
-local to_string_impl = ""
-local to_lua_impl = ""
+local pkt_type_impl = ""
+local pkt_impl = ""
 
 for name, fields in spairs(parse_spec("client/pkt", true)) do
        local case = "\tcase *mt.ToClt" .. camel_case(name) .. ":\n"
 
-       to_string_impl = to_string_impl
+       pkt_type_impl = pkt_type_impl
                .. case .. "\t\treturn lua.LString(\"" .. name .. "\")\n"
 
        if next(fields) then
-               to_lua_impl = to_lua_impl .. case .. fields_to_lua(fields, "\t\t")
+               pkt_impl = pkt_impl .. case .. fields_tolua(fields, "\t\t")
        end
 end
 
@@ -108,7 +106,7 @@ import (
 ]] .. funcs .. [[
 func PktType(pkt *mt.Pkt) lua.LString {
        switch pkt.Cmd.(type) {
-]] .. to_string_impl .. [[
+]] .. pkt_type_impl .. [[
        }
        panic("impossible")
        return ""
@@ -121,7 +119,7 @@ func Pkt(l *lua.LState, pkt *mt.Pkt) lua.LValue {
        tbl := l.NewTable()
        l.SetField(tbl, "_type", PktType(pkt))
        switch val := pkt.Cmd.(type) {
-]] .. to_lua_impl .. [[
+]] .. pkt_impl .. [[
        }
        return tbl
 }