Shared State
Lua uses a shared state across all scripts. Use local
variables and local
functions (not necessary for local tables) so as to not cause conflicts with other scripts.
Example
-- explicit local variables
local foo = {}
-- implicitly local due to local foo
function foo.baz()
return "baz"
end
-- explicitly local because not part of a table
local function bar()
return "bar"
end
Hooking gotchas
As of commit 7145dbda6cb7cb5b8dd12d9dee14f51850a76ec6, these duplicate functions are displayed next to the method.
If you are hooking a function that looks like a get_
or set_
function, double check the disassembly in the Object Explorer. More often than not, these functions will be extremely simple and prone to compiler optimizations causing multiple unrelated function calls to go through the hook. This means garbage data you don't want will flow through the function, and you can potentially crash the game if you are modifying the control flow of the wrong functions.
If you really want to hook these functions, you will need to verify that the object type being passed through the arguments is the one you want. Leave the control flow state and arguments pristine until you are 100% sure what is being passed through is what you want.
Hooking performance considerations
Using sdk.hook
is very useful. However, this comes with a few things to take note of.
When you are hooking something like an update
function that runs every frame, for every entity in the game, this can cause performance problems if you are calling a lot of functions within the hook.
A way to work around this, if it's possible in your script, is to stagger the function calls across multiple ticks. Also cache the methods and fields. A real world example can be found here.
You can also create a plugin that will run the heavy code natively, and then call it from Lua.
Method calls & field access performance considerations
Another very useful pair of functions is object:call
and object:get/set_field
. Whenever these functions are called, they perform a hashmap lookup. These can be slightly more efficent if you cache off the method and field definitions.
Example implementation
local method1 = sdk.find_type_definition("Foo"):get_method("Bar")
local field1 = sdk.find_type_definition("Foo"):get_field("Baz")
re.on_frame(function()
local some_object = sdk.get_managed_singleton("Qux")
local bar_result = method1:call(some_object, 1, 2, 3)
local baz_result = field1:get_data(some_object)
end)
Plugins
If there's something you find you can't do without native code, Lua can require
native DLLs. Native plugins are also an option.
Plugins can also be used to run heavy code that would otherwise be very slow in Lua. Parts of your script can still run in Lua, but you can expose APIs from your plugin that would run the heavy parts natively.
Modules
Lua can require modules. Modules are a way to organize your code.
Module1.lua
This goes in a subfolder inside the autorun folder.
local test = {}
function test.foo()
print("foo")
end
return test
Main.lua
This goes in the autorun folder.
local module1 = require("subfolder/Module1")
-- Now you can call module1.foo()
module1.foo()