Skip to content

Latest commit

 

History

History
484 lines (346 loc) · 12.9 KB

README.adoc

File metadata and controls

484 lines (346 loc) · 12.9 KB

Developer documentation

Concepts

OOP

vRP 2 Object-Oriented Programming uses the Luaoop library.

ℹ️
By using OOP, instead of loading resources communicating with vRP through Proxy, extensions can directly be loaded into the vRP resource context. Each extension keeps its data and is able to access other extensions data and methods, making extending vRP easier, more powerful (you can access stuff even if not exposed) and with less overhead.

Extension

An extension is a class extending vRP. Two versions of an extension (same name) can be loaded server-side and client-side.

local MyExt = class("MyExt", vRP.Extension)

Loaded extensions are accessibles through the vRP instance:

vRP.EXT.MyExt:test()
💡
You can see how an extension is made by looking at the code of vRP modules or https://github.com/ImagicTheCat/vRP-basic-mission.

User

Extensions can extend User properties/methods with a User class (constructor is called).

⚠️
To not conflict with other extensions, make sure the added properties and methods have a very specific name or prefix.
MyExt.User = class("User")

Event

Extensions can listen to global events by defining methods in the event table.

MyExt.event = {}

function MyExt.event:playerSpawn(user, first_spawn)
end
ℹ️
Events marked with (sync) in the documentation may be called using vRP:triggerEventSync which will wait for the listeners to complete, meaning that listeners must return (mostly in a short time frame) in order to let the execution continue normally.

Proxy and Tunnel

Extensions can listen to proxy/tunnel calls by defining methods in the proxy or tunnel table.

MyExt.proxy = {}
function MyExt.proxy:getInfo()
end

-- client-side
MyExt.tunnel = {}
function MyExt.tunnel:test()
end

The proxy interface generated will be accessible from other resources like this:

local my_ext = Proxy.getInterface("vRP.EXT.MyExt")
local info = my_ext.getInfo()
Extensions don’t need/should not use proxy between them.

The tunnel is accessible (from the client-side or server-side extension) like this:

-- server-side
function MyExt.event:playerSpawn(user, first_spawn)
  self.remote._test(user.source)
end

-- client-side
function MyExt.event:playerDeath()
  self.remote._test()
end

DB driver

A DB driver is a class handling MySQL queries. When the vRP MySQL API is used, the DB driver methods are called to process the queries. If the DB driver is not loaded yet, queries will be cached until loaded.

Data

vRP provides basic methods to store custom binary data in the database. A string key is associated to a binary value (ex: a Lua string). The key is a VARCHAR(100) and the value a BLOB (probably 65535 bytes).

global

GData

per server

SData

per user

UData

per character

CData

🔥
vRP core tables, as core files, should not be modified for the same reasons. New tables and queries should be created if the data system is not powerful enough.

Multi-server and character

vRP 2 has multi-server and multi-character support. Each server has a string identifier.

⚠️
Players can use another character after spawned, so extensions should properly handle character load/unload events and check if the character is ready.

Proxy and Tunnel

The proxy library is used to call other resources functions through a proxy event.

The idea behind tunnel is to easily access any declared server function from any client resource, and to access any declared client function from any server resource.

💡
Good practice is to get the interface once and set it as a global, if you want to get multiple times the same interface from the same resource, you need to specify a unique identifier (the name of the resource + a unique id for each one).
ℹ️
Tunnel and Proxy are blocking calls in the current coroutine until the values are returned, to bypass this behaviour, especially for the Tunnel to optimize speed (ping latency of each call), use as prefix for the function name (Proxy/Tunnel interfaces should not have functions starting with ). This will discard the returned values, but if you still need them, you can make normal calls in a new Citizen thread with Citizen.CreateThreadNow or async to have non-blocking code.
⚠️
Also remember that Citizen event handlers (used by Proxy and Tunnel) may not work while loading the resource, to use the Proxy at loading time, you will need to delay it with Citizen.CreateThread or a SetTimeout.

Asynchronous

vRP 2 extensively uses asynchronous tasks in a transparent way using coroutines, some functions may not return immediately (or never).

Loading script

To use vRP 2, a script must be loaded in the vRP resource context.

-- include `@vrp/lib/utils.lua` in `__resource.lua` of the resource

local Proxy = module("vrp", "lib/Proxy")

local vRP = Proxy.getInterface("vRP")

vRP.loadScript("my_resource", "server_vrp") -- load server_vrp.lua

The content of server_vrp.lua is now executed in the vRP context and can now use the API. The same thing should be done client-side.

General API

utils

lib/utils.lua defines some useful globals.

-- side detection
SERVER -- boolean
CLIENT -- boolean

-- load a lua resource file as module
-- rsc: resource name
-- path: lua file path without extension
module(rsc, path)

class -- Luaoop class

-- create an async returner or a thread (Citizen.CreateThreadNow)
-- func: if passed, will create a thread, otherwise will return an async returner
async(func)

-- convert Lua string to hexadecimal
tohex(str)

-- basic deep clone function (doesn't handle circular references)
clone(t)

parseInt(v)

parseDouble(v)

-- will remove chars not allowed/disabled by strchars
-- allow_policy: if true, will allow all strchars, if false, will allow everything except the strchars
sanitizeString(str, strchars, allow_policy)

splitString(str, sep)

Proxy and Tunnel

Proxy

-- add event handler to call interface functions
-- name: interface name
-- itable: table containing functions
Proxy.addInterface(name, itable)

-- get a proxy interface
-- name: interface name
-- identifier: (optional) unique string to identify this proxy interface access; if nil, will be the name of the resource
Proxy.getInterface(name, identifier)

Tunnel

-- set the base delay between Triggers for a destination
-- dest: player source
-- delay: milliseconds (0 for instant trigger)
Tunnel.setDestDelay(dest, delay)

-- bind an interface (listen to net requests)
-- name: interface name
-- interface: table containing functions
Tunnel.bindInterface(name,interface)

-- get a tunnel interface to send requests
-- name: interface name
-- identifier: (optional) unique string to identify this tunnel interface access; if nil, will be the name of the resource
Tunnel.getInterface(name,identifier)

Interface

  • interface defined function names should not start with an underscore (_)

  • the tunnel server-side call requires the player source as first parameter

  • the tunnel server-side called function can use the global source (correct until a TriggerEvent/yield/etc) as the remote player source

  • using an underscore to call a remote function interface ignores (no wait) the returned values

-- PROXY any side, TUNNEL client-side

-- call and wait for returned values
-- ...: arguments
-- return values
interface.func(...)

-- call without waiting
-- ...: arguments
interface._func(...)

-- TUNNEL server-side

-- call and wait for returned values
-- ...: arguments
-- return values
interface.func(player, ...) -- or _func to ignore returned values

DBDriver

-- called when the driver is initialized (connection), should return true on success
-- db_cfg: cfg/base.lua .db config
DBDriver:onInit(db_cfg)

-- should prepare the query (@param notation)
DBDriver:onPrepare(name, query)

-- should execute the prepared query
-- params: map of parameters
-- mode:
--- "query": should return rows, affected
--- "execute": should return affected
--- "scalar": should return a scalar
DBDriver:onQuery(name, params, mode)

Extension

self.remote -- tunnel interface to other network side

-- level: (optional) level, 0 by default
Extension:log(msg, level)

Extension:error(msg)

User

User inherits from all extensions sub-class User (if registered before the first user instantiation).

self.source
self.name -- FiveM name (may be steam name)
self.id
self.cid -- character id
self.endpoint -- FiveM endpoint
self.data -- user data
self.cdata -- character data
self.loading_character -- flag
self.use_character_action -- action delay
self.spawns -- spawn count

-- return true if the user character is ready (loaded, not loading)
User:isReady()

User:save()

-- return characters id list
User:getCharacters()

-- return created character id or nil if failed
User:createCharacter()

-- use character
-- return true or false, err_code
-- err_code:
--- 1: delay error, too soon
--- 2: already loading
--- 3: invalid character
User:useCharacter(id)

-- delete character
-- return true or false on failure
User:deleteCharacter(id)

vRP

Shared

self.EXT -- map of name => ext
self.modules -- cfg/modules

vRP.Extension

-- register an extension
-- extension: Extension class
vRP:registerExtension(extension)

-- trigger event (with async call for each listener)
vRP:triggerEvent(name, ...)

-- trigger event and wait for all listeners to complete
vRP:triggerEventSync(name, ...)

-- msg: log message
-- suffix: (optional) category, string
-- level: (optional) level, 0 by default
vRP:log(msg, suffix, level)

-- msg: error message
-- suffix: optional category, string
vRP:error(msg, suffix)
Events
extensionLoad(extension)

called when an extension is loaded, passing the extension instance (can be used to initialize with another extension when loaded before the latter)

Server

self.cfg -- cfg/base config
self.lang -- loaded lang (https://github.com/ImagicTheCat/Luang)
self.users -- map of id => User
self.pending_users -- pending user source update (first spawn), map of ids key => user
self.users_by_source -- map of source => user
self.users_by_cid -- map of character id => user

-- db/SQL API
self.db_drivers
self.db_driver
self.db_initialized

vRP.DBDriver

-- return identification string for a specific source
vRP.getSourceIdKey(source)

vRP.getPlayerEndpoint(player)

vRP.getPlayerName(player)

-- register a DB driver
-- db_driver: DBDriver class
vRP:registerDBDriver(db_driver)

-- prepare a query
--- name: unique name for the query
--- query: SQL string with @params notation
vRP:prepare(name, query)

-- execute a query
--- name: unique name of the query
--- params: map of parameters
--- mode: default is "query"
---- "query": should return rows (list of map of parameter => value), affected
---- "execute": should return affected
---- "scalar": should return a scalar
vRP:query(name, params, mode)

-- shortcut for vRP.query with "execute"
vRP:execute(name, params)

-- shortcut for vRP.query with "scalar"
vRP:scalar(name, params)

vRP:isBanned(user_id)

vRP:setBanned(user_id,banned)

vRP:isWhitelisted(user_id)

vRP:setWhitelisted(user_id,whitelisted)

-- user data
-- value: binary string
vRP:setUData(user_id,key,value)

vRP:getUData(user_id,key)

-- character data
-- value: binary string
vRP:setCData(character_id,key,value)

vRP:getCData(character_id,key)

-- server data
-- value: binary string
vRP:setSData(key,value,id)

vRP:getSData(key,id)

-- global data
-- value: binary string
vRP:setGData(key,value)

vRP:getGData(key)

vRP:ban(user,reason)

vRP:kick(user,reason)

vRP:save()
Events
(sync) characterLoad(user)

called right after the character loading

(sync) characterUnload(user)

called before character unloading

playerJoin(user)

called when a player joins (valid user)

playerRejoin(user)

called when a player re-joins (ex: after a crash)

playerDelay(user, state)

called when the player tunnel delay changes, state is true if delay is enabled

playerSpawn(user, first_spawn)

called when the player spawns

playerDeath(user)

called when the player dies

(sync) playerLeave(user)

called before user removal

save

called when vRP performs a save (can be used to sync the save of external extension data)

Client

self.cfg -- cfg/client config
Events
playerSpawn()

called when the player spawns

playerDeath()

called when the player dies

Modules

See modules/.