Posts for calcwatch2

Experienced Forum User, Published Author, Player (12)
Joined: 9/14/2022
Posts: 5
YoshiRulz wrote:
Is there a reason you're not just doing everything in C#?
I'd love to do everything in C#. I thought Lua scripts were the only official way to write tools that ran while the game was running. Is there a C# way to do this?
Language: lua

-- Set up GUI forms, etc. -- ... while true do -- Load from game memory, update forms, log text to console, draw on emulator screen etc. -- ... emu.frameadvance() end
And then would this be a DLL plugin of some kind? I'd prefer something that's simple for others to set up. It's easy enough to tell people "Download this Lua script and run it from the console."
Experienced Forum User, Published Author, Player (12)
Joined: 9/14/2022
Posts: 5
I recently went down a rabbit hole trying to optimize a Lua script, and hit a snag. I'm pretty sure it's in the realm of "internal stuff that you shouldn't rely on", but I'm curious if anyone knows what's going wrong. Update: Just a few minutes after posting this, the issue with my code became apparent. It works now! I'll fix my code below. The script I'm working on does lots of graphics processing, and I found it could go much faster by calling out to C# from Lua, as needed. This isn't a documented feature, but I got it working, and it satisfies my use case. For example, I can call C#'s String.Format(String, Object, Object) method like this:
Language: lua

luanet.load_assembly("System") local String = luanet.import_type("System.String") console.log( String.Format("{0} {1}!", "Hello", "World") )
But what if you want to do something more complicated, like processing a range of bytes from the game's RAM? In Lua, you just call memory.read_bytes_as_array(), which internally calls the C# method MemoryApi.ReadByteRange() and converts the byte array into a Lua table of doubles. Ideally, I'd call that C# method directly so I can use the byte array in other C# methods, but I don't have a reference to the ApiContainer I'd need in order to call it. Instead, I call memory.read_bytes_as_array() in Lua, convert the Lua table back into a C# byte array, and pass that along to the other C# method. It's inefficient, but not too bad. But another idea would be to have my C# code call the Lua function memory.read_bytes_as_array() directly, and handle the conversion back to a byte array there. This is usually a bad idea. I've personally worked on a code base where people wrote scripts that called C++ that called scripts that called C++, and so on. It gets ugly fast. So it's totally reasonable to disallow this in BizHawk. Still, I feel like I almost got it working, in the script below: It works now:
Language: lua

luanet.load_assembly("System") local ArrayList = luanet.import_type("System.Collections.ArrayList") local Type = luanet.import_type("System.Type") -- Takes a Lua array and returns a C# Object[] local function toCSharpArray(lua_array) local arrayList = ArrayList() for _, v in ipairs(lua_array) do arrayList:Add(v) end return arrayList:ToArray() end -- Does nothing. Just here to test Lua function calls local function testFunction() end -- This calls a Lua function from C# local function callLuaFunctionInCSharp(lua_function, lua_arg_array) local t = Type.GetTypeFromHandle(Type.GetTypeHandle(lua_function)) console.log("C# Object for function is of type: ", t) console.log("lua_function.ToString() returns: " .. t:GetMethod("ToString"):Invoke(lua_function, toCSharpArray({}))) console.log("lua_function.GetHashCode() returns: " .. t:GetMethod("GetHashCode"):Invoke(lua_function, toCSharpArray({}))) -- The bug was here. The Object[] passed into "Invoke" is an array of arguments passed into Call(). -- Call() takes only one argument, an Object[] of the Lua function arguments. -- So we actually want to send Invoke an Object[][] containing just one Object[]: the array of Lua function arguments. -- -- Incorrect: -- local arg_array = toCSharpArray(lua_arg_array) -- Correct: local arg_array = toCSharpArray({toCSharpArray(lua_arg_array)}) console.log("arg_array is of type:", arg_array) local method = t:GetMethod("Call") console.log("Method is of type:", method) console.log("Invoking Call method...") method:Invoke(lua_function, arg_array) end callLuaFunctionInCSharp(testFunction, {})
When you pass a Lua function into a C# method call, it gets converted along the way into a NLua.LuaFunction. I'm able to call LuaFunction.ToString() and LuaFunction.GetHashCode() without any trouble. My issue was that I was passing into Invoke() an array of the Lua args, instead of an array of size 1 containing an array of the Lua args. I've fixed the code. But when I try to call LuaFunction.Call(), which should execute the Lua function, it throws an exception. Here's the script's output on the latest debug build, when it had the bug:
C# Object for function is of type: 	NLua.LuaFunction
lua_function.ToString() returns: function
lua_function.GetHashCode() returns: 125
arg_array is of type:	System.Object[]
Method is of type:	System.Object[] Call(System.Object[])
Invoking Call method...
NLua.Exceptions.LuaException: unprotected error in call to Lua API (0)
   at NLua.Lua.PanicCallback(IntPtr luaState)
   at lua_error(lua_State* )
   at NLua.ObjectTranslator.throwError(IntPtr luaState, Object e)
   at NLua.Lua.SetPendingException(Exception e)
   at NLua.LuaMethodWrapper.call(IntPtr luaState)
   at NLua.MetaFunctions.runFunctionDelegate(IntPtr luaState)
   at lua_resume(lua_State* , Int32 )
   at NLua.Lua.Resume(Int32 narg)
   at BizHawk.Client.EmuHawk.Win32LuaLibraries.ResumeScript(LuaFile lf)
   at BizHawk.Client.EmuHawk.LuaConsole.<>c__DisplayClass61_1.<ResumeScripts>b__1()
   at BizHawk.Client.Common.EnvironmentSandbox.Sandbox(Action callback) in /builds/TASVideos/BizHawk/src/BizHawk.Client.Common/lua/EnvironmentSandbox.cs:line 11
   at BizHawk.Client.Common.LuaSandbox.Sandbox(Action callback, Action exceptionCallback) in /builds/TASVideos/BizHawk/src/BizHawk.Client.Common/lua/LuaSandbox.cs:line 57
I assume something somewhere in that stack isn't in the right state to be calling a Lua function. But without building BizHawk myself and setting breakpoints, I'm not sure. Does anyone have an idea what the problem is?
Experienced Forum User, Published Author, Player (12)
Joined: 9/14/2022
Posts: 5
tom_mai78101 wrote:
I haven't dabbled too deeply in this. In your code, it seems you are adding new picture boxes to the same form, and thus you are stacking the forms together. What if you add new picture boxes to different forms?
Like this?
Language: lua

local form1 = forms.newform(100, 300) local box1 = forms.pictureBox(form1, 0, 0, 100, 100) -- draws on box1 forms.drawText(box1, 0, 0, '1') local form2 = forms.newform(100, 300) local box2 = forms.pictureBox(form2, 0, 100, 100, 100) -- draws on box1 & box2 forms.drawText(box2, 20, 0, '2')
It still draws on both PictureBoxes.
Experienced Forum User, Published Author, Player (12)
Joined: 9/14/2022
Posts: 5
Hi all, I'm trying to make a simple GUI using multiple PictureBoxes, but I'm confused about how they're supposed to work. I can't tell if I've found a bug in the Lua wrappers or I just don't understand their implementation. Suppose I have code like this:
Language: lua

local main_form = forms.newform(100, 300) local box1 = forms.pictureBox(main_form, 0, 0, 100, 100) -- This draws in box1 as expected forms.drawText(box1, 0, 0, '1') local box2 = forms.pictureBox(main_form, 0, 100, 100, 100) -- I only want to draw in box2 here, but it draws in both box1 and box2 forms.drawText(box2, 20, 0, '2') local box3 = forms.pictureBox(main_form, 0, 200, 100, 100) -- And now it draws in box1, box2 and box3 forms.drawText(box3, 40, 0, '3') -- Even with an invalid handle, it draws in box1, box2 and box3 forms.drawText(0, 60, 0, '4')
It seems that drawText() (as well as drawImage(), drawRectangle(), clear(), etc.) will always draw in all PictureBox instances in existence when it's called. I even tried associating the PictureBoxes with separate parent forms, but it didn't make a difference. Is this intentional? Is there any way to have two PictureBoxes that can be drawn in independently?
Experienced Forum User, Published Author, Player (12)
Joined: 9/14/2022
Posts: 5
juef wrote:
Looks good to me! Can't say this is super entertaining, but still very satisfying. I always wondered how those fights worked.
Thanks! I agree on the entertainment value point. Most of it is waiting for the ant to move offscreen. And I really wish I could skip those cutscenes.
Would using the SNES mouse make navigating the menus a bit faster?
That might help. At maximum sensitivity, the pointer flies across the screen almost instantly. I experimented with the mouse in tastudio, but I gave up when I saw I’d have to specify integer values for x and y for each frame of movement. If I were to go that route, I think I’d have to use Lua to calculate them for me.
scrimpeh wrote:
Hm, without knowing the game, I have to admit it was very hard for me to parse what's going on. Without reading the description, it's not clear which parts require tool-assistance (and, indeed, which parts even require control by the player). I have to admit that I was more interested by the novelty of a game I didn't know than the gameplay at hand. It's also fairly repetitive to boot.
It might be faster to guide the yellow ant manually instead of using auto path-finding, in all the scenarios. However, there is a trade-off since it takes time to transition to and from the surface closeup view.
Considering how simple the game appears otherwise (2k rerecords), it seems to me like it would've been worth it to test it more to give a more conclusive answer. I have to vote Meh, sorry.
Those are all very fair points. I admit it’s not the most exciting thing to TAS, and it’s partly a design flaw of the game. Each scenario has its own features and challenges for casual play, but the fastest strategy for all of them is to run straight to the queen yourself and button mash. So the only things to optimize are routing to the queen, lag reduction, and beating the game’s frame rule that checks on the queen. And the seven identical, unskippable cut scenes really don’t help make it watchable. This was my first attempt at a TAS, and kind of a warm up for doing the full game run that I also posted. Now that I’ve learned more and worked on tooling to analyze it, I see potential time saves. Here’s a video I just uploaded where I render mini maps to monitor everything: Link to video The yellow ant clearly gets lost around 2:35 and backtracks a bit. Better manual routing can fix that and save at least a couple seconds, though with the frame rule, it might not matter in the end.