Skip to content

Lua API

Pirulax edited this page Apr 2, 2021 · 6 revisions

The Hitchiker's Guide to MTA's Lua API

Table of Contents

  1. How to add a new Lua definition
  2. How to migrate to the "new" argument parser
  3. How to rename functions
  4. How to delete functions

How to add a new Lua definition

When adding new Lua definitions, there are a few things you need to be aware of:

  1. You should always add your function to the client/server function init list (scroll down for the server list).
  2. Since 5 April 2020, in #973, we introduced a new Lua argument parser. We recommend that all new code uses this "new" style of parsing Lua arguments.
  3. Most existing code uses CScriptArgReader, which is an old-but-still-supported method of parsing arguments. The "new" argument parser does not work perfectly for every use case, and in those scenarios we accept the use of CScriptArgReader.

The new way: ArgumentParser

The new parser is quite straight forward to use, it's much cleaner than the old one.
Note: There are 4 false-positive errors which you can ignore.

Full error text
template parameter "Ret" is not used in or cannot be deduced from the template argument list of class template "CLuaOverloadParser<Func, Func2>" 
template parameter "Args" is not used in or cannot be deduced from the template argument list of class template "CLuaOverloadParser<Func, Func2>"
template parameter "Ret" is not used in or cannot be deduced from the template argument list of class template "pad_func_with_func<Func, FuncB>" 
template parameter "Args" is not used in or cannot be deduced from the template argument list of class template "pad_func_with_func<Func, FuncB>" 

To add a new function:

  1. Add a new function on the C++ side:
    1. Go to the appropriate header, for example if you want to add a new function for elements go to CLuaElementDefs.h
    2. Add the function definition. For example, if you want the function to take in an Element, a string and a bool and returns a bool: static bool YourFunction(CElement* element, std::string string, bool bl). It is very important not to forget the static keyword.
    3. Now, go the the source file (Right click -> Toggle header / Code file or Ctrl K, Ctrl O)
    4. Here you will need to define the function:
    bool CLuaElementDefs::YourFunction(CElement* element, std::string string, bool bl)
    {
        /* do something here */
        return true; /* return some value */
    }
    Note how theres no static, only in the header. See CLuaCryptDefs.cpp for some good examples.
  2. Go to the ::LoadFunctions() function in the given LuaDefs class
    • Add a new entry into the functions array like so: {"yourFunctionsLuaName", ArgumentParser<YourFunction>} - where:
      • yourFunctionsLuaName is the name of the function in Lua
      • YourFunction is the function to-be-called on the C++ side
Errors - Quick troubleshoot
  • error C2065: 'CLuaFunctionParser': undeclared identifier: Go to the top of the file, and add #include <lua/CLuaFunctionParser.h>
  • Something weird like the function (in this case YourFunction) is inaccessible: Make sure you've added the static keydowrd in the header
  • Undefined function: Make sure you didn't forget to add the class prefix to the function in the source file. Eg, you wrote: bool YourFunction instead of bool CLuaElementDefs::YourFunction.
  • Anything else? Ask us over at the development Discord.

The old way: CScriptArgReader

Most existing code uses CScriptArgReader, which is an old-but-still-supported method of parsing arguments. The "new" argument parser does not work perfectly for every use case, and in those scenarios we accept the use of CScriptArgReader.

Copy and paste warning: we error for bad types, now

You may see some m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage());. Use luaL_error(luaVM, argStream.GetFullErrorMessage()); instead.

TODO: improve explanation - we use hard errors now, see #821

Copy and paste warning: very old code performs compulsive checking

Note that some very old code that uses CScriptArgReader might compulsively check the validity of pLuaMain, pResource, or other arguments. There is no need to do this.

For example, CLuaFunctionDefs::ExecuteCommandHandler grabs pLuaMain and doesn't use it anywhere — this is unnecessary!

How to migrate to the "new" argument parser

Here you will need to use ArgumentParserWarn instead of ArgumentParser. (The only difference is that the new parser will hard error on usage mistakes, while the other (ArgumentParserWarn) won't.). You need to specify an additional parameter, the return value in case the function threw an error. This value is usually false, but somne function's might return nil (with lua_pushnil), in which case you will need to specify nullptr (We use nullptr to represent nil). Okay, so, let the function you want to refactor be called YourFunction, this is how the ArgumentParser part should look like: ArgumentParserWarn<<ReturnValueOnErrorHere> YourFunction> where ReturnValueOnErrorHere is the value to-be-returned if the function fails.

How to rename functions

Function renames are primarily a change in documentation—we don't delete old functions when performing renames. If the syntax is changing, you often want to keep the original definition around (example 1). If just the name is changing, you can rename everything, but add an additional alias for the old function name (example 2).

  1. If the new function requires a different syntax, don't rename any Lua definitions.

    If the new function is just a rename, rename the Lua function definitions and add an alias with the old function name.

  2. Make sure you add the new function to the init lists, as mentioned above.

  3. Add to the server/client deprecated list.

    • If the deprecation is a simple rename, set the bRemoved field to false. This allows MTA to automatically upgrade the script to use the new function.

    • If the function was removed and cannot be automatically upgraded (due to a change in syntax), set bRemoved to true. This tells MTA to not automatically upgrade the script. You can also include an extra note explaining how to migrate to the new function, as shown here.

    • If the old functions still work as expected, cannot be automatically upgraded, and you'd like to move people to the new functions... hold off on adding a deprecation entry. In this example (read #289 too) we commented out this deprecation entry because it was too annoying.

      This means that servers do not have to immediately adopt new function names, thus allowing nightly servers to serve stable clients.

Example 1:

Jetpack renamed from givePedJetpack and removePedJetpack to setPedWearingJetpack. We did not delete the original functions and we did not add a deprecation entry. (We did actually add the deprecation entry, but we later reverted it.)

TODO: make this section look better

Example 2

We used to have a function setVehicleTurnVelocity(vehicle, velX, velY, velZ). Later, we extended this to support more than just vehicles, so we renamed this to setElementAngularVelocity.

  1. We added entries to clientFunctionInitList and serverFunctionInitList.
  2. We added entries to clientDeprecatedList and serverDeprecatedList. Because this is just a simple alias, the first field (bRemoved) is set to false.

How to delete functions

It most cases you should not delete functions. Here are some examples of when it's okay to actually delete functions:

  1. The function was only introduced in a nightly, and did not make it into a Point Release.

    For example, if 1.5.7 released in January, and you added a new function in February, it's OK to delete that function any time before August, when 1.5.8 releases.

  2. The original functionality is completely unusable.

    For example, engineLoadIFP was introduced in 2010 and then quickly removed due to instability. Then this function was re-introduced with a new API eight years later.

    (This is perhaps a bad example, because this function never actually made it into a Point Release.)

I really want to delete a function

TOOD: add instructions on where to look for things to delete

Also, if the function did not make it into a Point Release, follow the instructions above on adding to the deprecation lists.