Skip to content

Creating a Lua gamemode or filterscript

IllidanS4 edited this page Sep 8, 2018 · 1 revision

Setting up the environment

YALP doesn't provide any new system to load server scripts in addition to gamemodes and filterscripts. Instead, you can create a Lua instance in any Pawn script, and let it replace it in terms of all functionality. The loader script is very simple:

#include <a_samp>
#include <YALP>

public OnFilterScriptInit()
{
    new Lua:l = lua_newstate(lua);
    if(lua_loadfile(l, "script.lua") || !lua_bind(l))
    {
        lua_stackdump(l);
        lua_close(l);
    }
}

lua_newstate creates a new Lua instance. lua_loadfile loads a script (from the "scriptfiles" directory) and pushes it on the Lua stack as a function. lua_bind consumes this function and "rewires" all callbacks and other operations to the Lua instance. From this point on, the Lua instance completely replaces the filterscript (or gamemode).

In case of an error, lua_stackdump outputs the contents of the Lua stack, and lua_close deletes the instance. If everything succeeds, the Lua instance will be destroyed when the calling Pawn script is unloaded.

Creating a simple Lua script

Since there is no special entry/main function in the script that is called, the script itself is its entry function. In it, you should perform initialization of the script, load all modules, and register your public functions. The core module in YALP is the interop package, which performs the communication between Lua and SA-MP.

local interop = require "interop"
local public = interop.public
local SendClientMessage, SetPlayerHealth
import(interop.native) -- assigns SendClientMessage and SetPlayerHealth from interop.native.SendClientMessage and interop.native.SetPlayerHealth

local COLOR_WHITE = 0xFFFFFFFF

local function tofloat(num) -- when calling native functions with a float parameter, a float value must be ensured
  return 0.0 + num
end

function public.OnPlayerConnect(playerid) -- adding a function to the "public" table automatically registers the callback (based on the name)
  SendClientMessage(playerid, COLOR_WHITE, "Hello from Lua!") -- calling a native function requires almost no modifications
end

local commands = {}

function commands.hp(playerid, params)
  local hp = tonumber(params)
  if not hp then
    return SendClientMessage(playerid, COLOR_WHITE, "Usage: /hp [value]")
  end
  SetPlayerHealth(playerid, tofloat(hp))
  SendClientMessage(playerid, COLOR_WHITE, "Your health was set to "..hp.."!")
  return true
end

function public.OnPlayerCommandText(playerid, cmdtext)
  playerid = interop.asinteger(playerid) -- YALP cannot guess the type of the arguments, so they must be explicitly converted
  cmdtext = interop.asstring(cmdtext)
  
  local ret
  cmdtext:gsub("^/([^ ]+) ?(.*)$", function(cmd, params)
    local handler = commands[string.lower(cmd)]
    if handler then
      ret = handler(playerid, params)
    end
  end)
  return ret
end

print("Lua script initialized!")

Lua configuration

lua_newstate automatically opens standard Lua packages for use within scripts and new YALP modules as well. For safety, io, os, and debug packages aren't loaded by default, but setting the parameters of lua_newstate can allow them. It is also possible to limit the memory usage of the Lua instance this way.

YALP packages use the standard Lua package system. They are not exported to the global table, but can be loaded by using require, like in the example script above. This is even necessary for the interop module, if you want to use it together with lua_bind, since loading it before the call to the native function creates a fake filterscript to interface with the server.

Controlling Lua from Pawn

Most of the Lua C API is exported for use from Pawn. If you don't want to write SA-MP scripts in Lua or want to have greater control over the Lua instance, you can operate with the Lua stack, register custom functions, or run any Lua code from Pawn. However, using the Lua API is discouraged since it is more error-prone, takes longer to write and debug, and isn't faster than Lua code. You should only resolve to it when absolutely necessary.