Loading...
  OR  Zero-K Name:    Password:   

Is there a way to send LuaUI commands interactively?

15 posts, 756 views
Post comment
Filter:    Player:  
sort

20 months ago
When you develop a widget, is there a more efficient way to test commands or snippets of code other than making it part of a widget and issuing "/luaui reload" after each change?

Possibly related,
quote:
/LuaUI (unsynced) Allows one to reload or disable LuaUI, or alternatively to send a chat message to LuaUI

what does it mean, "chat message to LuaUI"?
+0 / -0
20 months ago
A complete reload is only needed if you add completely new widget files or change the file names. Ideally, put the widget files in the right folder before starting a battle. Then you can change their content during a running battle. Just update it by disabling and enabling that widget in the widget list.
+1 / -0


20 months ago
quote:
what does it mean, "chat message to LuaUI"?

You can make a widget that listens to "/luaui makemeasammich".
+0 / -0

20 months ago
quote:
You can make a widget that listens to "/luaui makemeasammich".


That what I want but can't figure what callin to use. Tried
function widget:GotChatMsg(msg, player)
    Echo( "msg: " .. msg .. ", player: " .. player)
end
with no success (no output produced).

widget:RecvLuaMsg(msg, playerID) gives a (binary?) stream unrelated to my console messages.

What callin should I use?
+0 / -0


20 months ago
quote:
widget:RecvLuaMsg(msg, playerID) gives a (binary?) stream unrelated to my console messages.

A few other things send text messages to communicate between unsync and sync. Typically it's pipe-separated values after a prefix. It very definitely is not binary and doesn't even look like that.

If you grep for RevLuaMsg, most implementations filter out everything they're not interested in.
+0 / -0

20 months ago
function widget:TextCommand(command)
  if command == "make_me_a_sandwich" then
    become_sandwich("blt")
  end
end

or, alternatively,
local function become_sandwich(type_of_sandwich)
  -- ...
end

function widget:Initialize()
  -- ...
  widgetHandler:AddAction("make_me_a_sandwich", become_sandwich, "blt", "t") -- "t" is for "text", keep it that way
end
+2 / -0
Thanks, PLrankAdminSprung, this works! Where did I need to look to find widget:TextCommand calllin? It isn't in https://springrts.com/wiki/Lua:Callins.


EErankAdminAnarchid, regarding
quote:
Typically it's pipe-separated values after a prefix. It very definitely is not binary and doesn't even look like that.


Okay, binary is probably wrong term, but when I run
function widget:RecvLuaMsg(msg, playerID)
    Echo( "msg: " .. msg .. ", player: " .. playerID)
end

in the debug console and in the infolog I get things like
msg:  %0�
                                       ��
                                         �,player:0

Tried chardet -> iconv with no success.
+0 / -0

20 months ago
(Best thread currently active on the forum.)
+0 / -0
quote:
msg:  %0�
                                       ��
                                         �,player:0

Ok that does look binary, i'll get the ashes for head-sprinkling

I wonder which one is sending that though, i had no idea this existed.
+0 / -0
Progress I made so far:

In PLrankAdminSprung's examples a widget waits for a specific text command to run a specific code. I wanted a solution to run an arbitrary text command. So I found the loadstring() function which makes an executable command out of string (and this is when I found that Spring uses Lua 5.1, because it is load() in newer versions, and there are some other changes in Lua, and I need to switch from 4th to 2nd edition of PiL).

To filter the text commands I start them with 'sudo' (can be any string), like "/luaui sudo Spring.GetOrderToUnit(..)" and the widget filters it (see code below).

The problem with loadstring() is that the resulting command runs in global environment and does not have the access to all the local variables and functions of the widget. So, for example, if I want to use in my command UnitID of a selected unit, I have two options:

1. To make the widget print the UnitID and then to manually insert it into the command. The code then will be

function widget:TextCommand(str)
    if string.find(str, "^sudo ") then -- if a string starts with "sudo "
        local code = string.sub( str, 5 ) -- get the rest of the string after "sudo "
        Echo( "Executing: " .. code )
        cmd = loadstring( code ) -- prepare a command but do not execute it yet
        local ok,msg = pcall( cmd ) -- run the command with "protected call" so in a case of error the widget will not be removed
        if not ok then Echo( "Error: " .. msg ) end -- in case of an error, print the error message
     end
end


2. To prepare an environment for the cmd which will include the local stuff. This is the trickiest part, I tried to automate it with loops of debug.getlocal and debug.getupvalue, traversing along the stack levels, but couldn't find where the widget's locals are residing. So I came with a dirty solution to manually add the locals of interest to a special table and then to make the new environment out of it, adding the index of globals. The code is then:
function widget:TextCommand(str)

    if string.find(str, "^sudo ") then -- if a string starts with "sudo "
        local code = string.sub( str, 5 ) -- get the rest of the string after "sudo "
        Echo( "Executing: " .. code )
        cmd = loadstring( code ) -- prepare a command but do not execute it yet
        local env = getfenv(2) -- get the environment of the caller function (a widget handler?)
        local locals = { z = z, selectedUnits = selectedUnits } -- put here all the local variables you want access to
        setfenv(cmd, setmetatable(locals, { __index = env._G })) -- set environment for the command, including both the locals and the global index
        local ok,msg = pcall( cmd ) -- run the command with "protected call" so in a case of error the widget will not be removed
        if not ok then Echo( "Error: " .. msg ) end -- in case of an error, print the error message
     end
end


With the latter, I could run things like
/luaui sudo Spring.Echo( z )
and
/luaui sudo Spring.Echo( Spring.GetUnitMaxRange( selectedUnits[1] ))


and to get in the console

Executing:  Spring.Echo( Spring.GetUnitMaxRange( selectedUnits[1] ))
600.000061
Executing:  Spring.Echo( z )
ZZZ


and if the command fails, I get its error message, but the widget is not getting removed.

TODO/questions:

1. Anyone can explain how to make automatically a table of all the locals of a widget from inside a callin?

2. I'd like to make a dedicated widget serving the interactive commands, so that in a widget I'm working on, I don't need to include the snippet above (may be just 'require'?). Probably need to replace "sudo" with a widget's name so the dedicated widget will know in which widget to run the command.

3. I'm still interested to know where can I find the full list of callins.
+0 / -0
20 months ago
%0 is probably a clue. Some string search somewhere failing and sending gibberish as the msg?
+0 / -0

20 months ago
quote:
1. Anyone can explain how to make automatically a table of all the locals of a widget from inside a callin?

There's the debug.bla table which has some sort of "get local" function, but I don't think that's a good idea. Your sudo widget should just provide sudo, so will have no interesting locals. Any other widget that has interesting locals should put them in some WG subtable because you're not going to put copies of sudo in each widget (well, you can, but that sounds silly given you can use WG).

quote:
2. I'd like to make a dedicated widget serving the interactive commands, so that in a widget I'm working on, I don't need to include the snippet above (may be just 'require'?). Probably need to replace "sudo" with a widget's name so the dedicated widget will know in which widget to run the command.

See above. Have other widgets do
WG.LocalsForSudo["MySuperWidget v1"] = { my_cool_local = blabla }

Then you can use it like
/luaui sudo Spring.Echo(WG.LocalsForSudo["MySuperWidget v1"].my_cool_local)


quote:
3. I'm still interested to know where can I find the full list of callins.

https://github.com/ZeroK-RTS/Zero-K/blob/master/LuaUI/cawidgets.lua
+1 / -0

20 months ago
Okay, a made it and it seems to work. The code is in the spoiler below, here I repeat the comments for readability:

-- RATIONALE
--
-- Serves as a helper for widget developers. Upon installing this, when one is working on some widget and is interested to see the result of a certain command (an one-liner, to be precise), one can simply run the command of interest from the chat console, instead of the usual workflow of adding the command to the widget code and re-enabling it.
--
-- Especially useful when you want to run the same command several times with slight variations or in different game situations, since the up arrow in the chat console gives you the last command(s) so you can make a needed change quickly, or to wait for the right moment and hit enter to execute.
--
-- In the case of an error, prints the error message, but the widget itself is not fails (not being removed).
--
-- See https://zero-k.info/Forum/Thread/35548 for the background discussion (thanks, Sprung!)


-- USAGE
--
-- Prepend the desired command with '/luaui sudo ', e.g. '/luaui sudo Spring.Echo("make me a sandwich")'
--
-- Optionally, put short aliases for frequently used commands or values in WG.sudoWidget to have them available in the console. With the provided exemplary aliases (see below), you can shorten the aforementioned command to '/luaui sudo e("make me a sandwich").
--
-- As this widget (as any other) doesn't have access to the local stuff of other widgets, if you want some of it to be available at the console, put them (in your widget code) in WG.sudoWidget as well. For example, if you have in your widget the following snippet:
--[[
local selectedUnits = {}
function widget:SelectionChanged(sel)
    selectedUnits = {}
    for _, uid in pairs(sel) do
        selectedUnits[ #selectedUnits + 1 ] = uid
    end
    WG.sudoWidget.s = selectedUnits
end
--]]
-- then, upon selecting some unit(s), you can run, e.g. '/luaui sudo e(pos(s[1]))' ('pos' being an alias for Spring.GetUnitPosition as in the example below).


code:
[Spoiler]

Now that I think about it, seems better to have a dedicated console so '/luaui sudo' can be omitted. Guess I need to look into WG.Chili stuff?
+0 / -0


20 months ago
function widget:TextCommand(msg)
	--Spring.Echo(msg)
	if msg:find("sudo") then
		local cmd = msg:gsub("sudo ", "")
		-- do your stuff here using cmd variable
	end
end


Try something like this. This gets rid of the need for /luaui.
+0 / -0
quote:
-- Serves as a helper for widget developers. Upon installing this, when one is working on some widget and is interested to see the result of a certain command (an one-liner, to be precise), one can simply run the command of interest from the chat console, instead of the usual workflow of adding the command to the widget code and re-enabling it.

You may be interested in Chonsole

+1 / -0