-
Notifications
You must be signed in to change notification settings - Fork 2
Home
libLassebq is an experimental modding SDK for YYC games, featuring a GMLua backend to execute Lua code inside object events.
Russische sprache версия находится вот здесь: https://github.com/nkrapivin/libLassebq/wiki/Russian-Home
For those who don't speak Russian, this is the English version. :)
- Katana ZERO, v1.0.5, Windows Steam, Askiisoft.
- Katana ZERO, v1.0.5, Windows GOG, Askiisoft.
- The Swords of Ditto, 1.17.05-205 STM, Windows Steam, OneBitBeyond & Devolver Digital.
Extract the libLassebq package to your game folder, then open the game's data.win
in UndertaleModTool and run the llbqInjector.csx
script.
Then run the game and observe the magic.
GMLua is a backend that runs inside libLassebq and provides Lua scripts access to GameMaker's instances, object events, scripts, constants, and asset indexes.
Using GMLua's API may be a bit tricky, but once you get it, it becomes really powerful.
Your libLassebq package should come with a test.lua
file with some very basic explanation of the functions. But if you still don't get how to use it, here's the information about GMLua functions:
GMLua_with(inst_or_object:GMLInstance or integer, thing:function)
inst_or_object - can be an instance id, an object index, all
keyword, or a GMLInstance.
thing - function to execute in the GMLInstance's context
The function has to take two arguments, _pSelf
and _pOther
, just like an object event.
Since with()
is a loop in GM, when an object index or all
is passed, it will execute this function in a loop.
If you wish to break out of it, simply return any value, not just return
, but return 0
for example.
Please please please do not call instance_destroy or any other functions that affect the instance order for god's sake.
GMLua_getvar(inst:lightuserdata, varid:GMLVar, [arrayIndex:integer])
inst - _pSelf
/_pOther
/_pGlobal
or any other GMLInstance. (for example one returned by GMLua_inst
)
varid - a GMLVar_
constant.
arrayIndex - only used if GMLVar_
is a builtin variable, accesses a builtin array variable.
Returns the value of the variable.
GMLua_setvar(inst:lightuserdata, varid:integer, Rvalue, [arrayIndex:integer])
inst - _pSelf
/_pOther
/_pGlobal
or any other GMLInstance. (for example one returned by GMLua_inst
)
varid - a GMLVar_
constant.
Rvalue - can be any type, the value you want to assign to the variable.
arrayIndex - only used if GMLVar_
is a builtin variable, accesses a builtin array variable.
Sets the variable that has an id varid
to the value Rvalue
.
Returns nothing.
GMLua_inst(instid:integer or number)
instid - can be an integer (if you already know the instance id), or a number, if you got one from a GM function (they all treat instance ids as numbers).
This function is very useful if you want to loop through all instances and want to get variable values from them e.g. by using GMLua_getvar
.
Returns GMLInstance which id
variable is equal to instid
.
GMLua_ignoreArgc(boolean:doIgnore)
doIgnore - ignore argument count? (true) or don't? (false)
This function was designed as a hack for some built-ins such as gif_add_surface
or game_get_speed
that accept extra arguments,
but the argument count doesn't reflect that, or their argument count is wrong, so libLassebq throws an error, because there are more (or less) arguments than expected, but in the GM documentation and the GM IDE all is fine.
Please don't use this function for anything else, it is only for cursed built-in runtime functions.
This function is global, meaning it will affect all functions and all Lua scripts.
Returns nothing.
GMLInstances have two metamethods, __index
and __newindex
. Also __tostring
is defined but it's useless.
With these, you can access instance variables as if you were accessing an array, e.g.:
local myX = _pSelf.x
local myY = _pSelf.y
Or global/builtin variables:
local myFPS = _pGlobal.fps
local myDir = _pGlobal.program_directory
local myGlistenerX = _pGlobal.listener_x
Or assign values to instance variables:
_pSelf.x = 222.0
_pSelf.y = 137 + _pSelf.x
_pGlobal.version_string = _pGlobal.version_string .. "/libLassebq!"
Also since libLassebq v2.0, alarms and other built-in array variables are properly supported.
They all have the GML_
prefix, so if you want to call draw_set_halign
, you'd call GML_draw_set_halign
:
GML_window_set_caption("whatever")
GML_room_goto_next()
Same as runtime functions, except the prefix is GMLScript_
, so for room_transition
you'd call GMLScript_room_transition
.
Keep in mind that in Lua you can assign methods to variables, so you don't have to retype the function name every time, you can just do:
Grt = GMLScript_room_transition
Grt(room_of_the_pug, 1) -- will transition to `room_of_the_pug` with mode `1`.
You can define two hooks on a GML script, Before
and After
.
When at least one of them is defined, the script is being detoured with a special Lua routine that works like this:
If Before
is defined, it will be executed, if Before
returned something, the routine will return that value without executing the original script and After
.
Then if Before
didn't return anything, the original script is executed.
Then if After
hook is defined, it will be executed and the original script's return value will be provided as the fourth argument.
If After
returned something, the result will be replaced with After's return value.
Then the Lua routine returns.
That means you can prevent the original script from executing in the first place by returning something from Before
.
And you can also hijack the return value by returning from After
.
Also, _args
is the arguments that were provided to the script, it's just a regular Lua array.
Lua example:
local scr_test = { }
local obj_some = { }
function obj_some.Create(_pSelf, _pOther)
-- do some stuff.
end
function scr_test.Before(_pSelf, _pOther, _args)
for i,v in ipairs(_args) do
print(i,v)
end
end
function scr_test.After(_pSelf, _pOther, _args, _ret)
return _ret -- don't have to do this actually.
end
return { obj_some=obj_some, scr_test=scr_test }
Also, if you want to get the current index of your script, use the global variable GMLua_Detours_Index
.
That variable is an integer asset index of your script and IT IS ONLY VALID INSIDE Before
and After
HOOKS, IT IS nil
AT ALL OTHER TIMES.
You can get the script's name like so:
local myname = GML_script_get_name(GMLua_Detours_Index)
print("i am script: ", myname)
Oh, and keep in mind that if you call your script via GMLScript_
while being inside Before
or After
, this will cause an infinite loop. (because recursion!)
They are exposed to you as global variables, constants are all numbers, asset indexes are all integers.
They do not have any prefix or suffix, use them as you would use them in GM.
The only constants that are not exposed is self
(-1.0), and local
(-7.0), because they conflict with Lua keywords.
It is advised that you don't use them anyway, use the instance id as self
, and don't use local
, you have Lua variables why would you use local
?
GMLua will load the file GMLuaConfig.txt
and loop through every line of the file.
So yeah, the config file is just a bunch of lines for now.
Valid lines:
-
addCollisionEvents
- Enable generation of collision event names, will slow down the loading times drastically. -
ignoreLuaErrors
- will ignore Lua errors, this is very evil and should not be used unless you really have to. (only Lua errors are ignored, if an error happens in the GameMaker engine itself, the game WILL close itself, as this cannot be handled by libLassebq!) -
noConsole
- won't allocate the console window when starting up. Useful if you want to stay silent hehe. -
waitForDebugger
- will wait for the Visual Studio's debugger after loading the config in the config loader function. Only I use this when I need to, just ignore it :p -
enableBeforeEvents
- enable support for execution of Lua code before events, name the functionBefore[Event name here]
. -
addScripts
- hook GML scripts in the first place or not? if your computer has problems with handling Detours (weird AV?), consider removing it. -
iWannaMakeMyOwnObjectsKthx
- enables parsing of theGMLuaNewObjects.txt
file used to define new objects (see below).
Invalid or empty lines are simply ignored.
Sometimes to componentize your mod, you may want to add your own very new objects into the game.
To do that, add the appropriate config line which enables that, and in GMLuaNewObjects.txt
type names of the objects you want to add.
All of them will be created with default properties, depth = 0, visible = true, solid = false, persistent = false, etc.
They will not be placed into any room, unless you desire so (GML_instance_create_depth/layer
), you can also change various properties of your new objects via GML_object_set_*
functions. (except for the name!)
If your name conflicts with an existing asset, or an object, it will not be added and a message will be printed into the console.
Example file:
obj_mod_controller
obj_mod_playertrack
obj_mod_hud
Empty lines or the ones that only contain spaces are not read at all.
To add your scripts to GMLua, you edit the loadlist.txt
file, this file is just a list of paths to .lua scripts.
Compiled lua scripts are also supported just fine. Specify them exactly as you would specify a text lua script.
Keep in mind that the script is first executed to determine which object events and scripts it's hooking, then libLassebq will patch these events/scripts and continue to the next script in the list.
That means, you cannot patch new object events or hook new GML scripts after the game has started, for that, you need to run the game again.
Kinda sucks if you LOVE live-coding, but that's the best I can do.
To do anything meaningful, you'd want to replace or append your code to an object event.
By default, libLassebq will provide an empty persistent object named obj_libLassebq
added to the first room of the game, you are safe to replace any events in it, Create and Game Start will work too. (DOES NOT WORK ON DITTO BECAUSE IT'S A WEIRD GAME! USE obj_controller!)
Although you can use any object in the game.
To replace an event, you declare a table named however you want, for example:
local what = { }
Then you declare a function which's name matches the name of the event you want to replace, for example:
function what.Step(_pSelf, _pOther)
--[[
_pSelf is the pointer to your current instance (here, it's a pointer to an instance of obj_libLassebq).
_pOther is only used in Collision events, points to an instance that collided with _pSelf,
at any other times it points to any random instance (or even the same as _pSelf) and should NOT be used.
]]--
end
And, VERY IMPORTANT, you MUST return a table AT THE END of the script (NOT IN A FUNCTION), where key
is the object/script's name, and value
is the table with events (or Before
/After
hooks), the final script would look like this:
local what = { }
function what.Step(_pSelf, _pOther)
--[[
_pSelf is the pointer to your current instance (here, it's a pointer to an instance of obj_libLassebq).
_pOther is only used in Collision events, points to an instance that collided with _pSelf,
at any other times it points to any random instance (or even the same as _pSelf) and should NOT be used.
]]--
end
return { obj_libLassebq=what } -- t["obj_libLassebq"] = what
This will define a new Step event in obj_libLassebq
.
If an event already exists (already used by the game), your Lua function will be executed after it. However GML scripts can define Before
and After
hooks, more on that above.
The order of execution is defined by the order of the scripts in loadlist.txt
.
Here's a list of valid event names: https://github.com/nkrapivin/libLassebq/blob/master/libLassebq/Data.cpp#L10
Collision events are named Collision_ObjectNameHere
, e.g. Collision_obj_libLassebq
.
libLassebq will overwrite the game version in the main menu, to notify that this game is modified.
And it will also add a huge moving rectangle with bloody red text on it to the speedrun results room, notifying that this speedrun should not be trusted, as the game has been modified.
Also, any replacements to obj_speedrun_results_screen
are ignored, that means any Lua functions referencing that object will not be executed.
This is intentional and will not be removed.
This game doesn't like when a new object is added, as such you cannot use obj_libLassebq
in your mods,
please use any other persistent object.