.1.1. The Problem
Given a graph
This “definition” remains vague enough to encompass the most known variants of the VRP. Indeed, not only does the VRP
exist in different flavors (capacitated, multi-depots, with time-windows, with pick-up and delivery, …) but several
slightly different definitions exist in the literature.
In this manual, we will use the definition given by Gilbert Laporte in [Laporte1992]. In this article, a VRP
is designed in such a way that
The last point is important. Indeed, without side constraints and if the graph obeys the triangle inequality
(i.e.
The most common side constraints include:
And the list goes on.
For our basic version of the VRP, all vehicles must be used. This version of the VRP
is better known as the mTSP[2]. Some problems can be coined as mTSP and we refer again the reader to [Bektas2006]
to find some examples.
Below you’ll find a picture of a solution of a VRP with 32 cities and 5 vehicles (A-n32-k5) in the
sub-section Visualization with ePix.
.1.5. To hold and check a (C)VRP solution: the CVRPSolution class
To represent a (C)VRP solution, we have defined the CVRPSolution class. Two constructors are available:
Two methods verify the feasibility of the solution:
The CVRPSolution class provides iterators to run through the solution. For instance, the
ComputeObjectiveValue() method – that computes the objective value of the solution – is written as follows:
Because this method is constant and doesn’t change the solution, it uses constant iterators. The CVRPSolution
class also provides the following non constant iterators:
Audio
-- TUNNEL CLIENT API-- play audio source (once)--- url: valid audio HTML url (ex: .ogg/.wav/direct ogg-stream url)--- volume: 0-1--- x,y,z: position (omit for unspatialized)--- max_dist (omit for unspatialized)
vRP.playAudioSource(url, volume, x, y, z, max_dist)
-- set named audio source (looping)--- name: source name--- url: valid audio HTML url (ex: .ogg/.wav/direct ogg-stream url)--- volume: 0-1--- x,y,z: position (omit for unspatialized)--- max_dist (omit for unspatialized)
vRP.setAudioSource(name, url, volume, x, y, z, max_dist)
-- remove named audio source
vRP.removeAudioSource(name)
Database
SQL queries are managed by DB drivers, you can use the default vRP driver vrp_mysql or use a custom one (vrp_mysql has crappy code, see alternatives).
DB drivers will register themselves (as resources) with a specific name to use in cfg/base.lua. Since there is no guarantee about when the driver will be registered, all queries will be cached until that moment.
-- API (PROXY)-- register a DB driver--- name: unique name for the driver--- on_init(cfg): called when the driver is initialized (connection), should return true on success---- cfg: db config--- on_prepare(name, query): should prepare the query (@param notation)--- on_query(name, params, mode): should execute the prepared query---- params: map of parameters---- mode:----- "query": should return rows (list of map of parameter => value), affected----- "execute": should return affected----- "scalar": should return a scalar
vRP.registerDBDriver(name, on_init, on_prepare, on_query)
-- 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 field => 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)
Group/permission
Group and permissions are a way to limit features to specific players.
Each group have a set of permissions defined in cfg/groups.lua.
Permissions can be used with most of the vRP modules, giving the ability to create specific garages, item transformers, etc.
Api
-- PROXY API-- return group title
vRP.getGroupTitle(group)
-- add a group to a connected user
vRP.addUserGroup(user_id,group)
-- remove a group from a connected user
vRP.removeUserGroup(user_id,group)
-- check if the user has a specific group
vRP.hasGroup(user_id,group)
-- register a special permission function-- name: name of the permission -> "!name.[...]"-- callback(user_id, parts)--- parts: parts (strings) of the permissions, ex "!name.param1.param2" -> ["name", "param1", "param2"]--- should return true or false/nil
vRP.registerPermissionFunction(name, callback)
-- check if the user has a specific permission
vRP.hasPermission(user_id, perm)
-- check if the user has a specific list of permissions (all of them)
vRP.hasPermissions(user_id, perms)
-- get user group by group type-- return group name or an empty string
vRP.getUserGroupByType(user_id,gtype)
-- return list of connected users by group
vRP.getUsersByGroup(group)
-- return list of connected users by permission
vRP.getUsersByPermission(perm)
Inventory
The inventory is autosaved and, as the wallet, gets empty upon death.
Items
Items are simple identifiers associated with a quantity in an inventory. But they can also be parametrics.
Parametrics items are identified like other items in the inventory but also have arguments as: weapon|pistol
instead of just an ID. Parametric items don’t contain any data, they are generic item definitions that will be specialized by the arguments.
-- PROXY API-- define an inventory item (call this at server start) (parametric or plain text data)-- idname: unique item name-- name: display name or genfunction-- description: item description (html) or genfunction-- choices: menudata choices (see gui api) only as genfunction or nil-- weight: weight or genfunction---- genfunction are functions returning a correct value as: function(args) return value end-- where args is a list of {base_idname,arg,arg,arg,...}
vRP.defInventoryItem(idname,name,description,choices,weight)
-- return name, description, weight
vRP.getItemDefinition(idname)
vRP.getItemName(idname)
vRP.getItemDescription(idname)
vRP.getItemChoices(idname)
vRP.getItemWeight(idname)
-- add item to a connected user inventory
vRP.giveInventoryItem(user_id,idname,amount,notify)
-- try to get item from a connected user inventory-- return true if the item has been found and the quantity removed
vRP.tryGetInventoryItem(user_id,idname,amount,notify)
-- get item amount from a connected user inventory
vRP.getInventoryItemAmount(user_id,idname)
-- get connected user inventory-- return map of full idname => amount or nil
vRP.getInventory(user_id)
-- clear connected user inventory
vRP.clearInventory(user_id)
-- compute weight of a list of items (in inventory/chest format)
vRP.computeItemsWeight(items)
-- return user inventory total weight
vRP.getInventoryWeight(user_id)
-- return user inventory max weight
vRP.getInventoryMaxWeight(user_id)
-- open a chest by name-- cb_close(): called when the chest is closed
vRP.openChest(source, name, max_weight, cb_close)
-- TUNNEL SERVER API-- TUNNEL CLIENT API
Once defined, items can be used by any resources (ex: they can be added to shops).
local Proxy =module("vrp", "lib/Proxy")
local Tunnel =require("vrp", "lib/Tunnel")
vRP = Proxy.getInterface("vRP")
vRPclient = Tunnel.getInterface("vRP","vrp_waterbottle")
-- create Water bottle itemlocal wb_choices = {} -- (see gui API for menudata choices structure)
wb_choices["Drink"] = {function(player,choice) -- add drink actionlocal user_id = vRP.getUserId(player) -- get user_idif user_id thenif vRP.tryGetInventoryItem(user_id,"water_bottle",1) then-- try to remove one bottle
vRP.varyThirst(user_id,-35) -- decrease thirst
vRPclient.notify(player,"~b~ Drinking.") -- notify
vRP.closeMenu(player) -- the water bottle is consumed by the action, close the menuendendend,"Do it."}
-- add item definition
vRP.defInventoryItem("water_bottle","Water bottle","Drink this my friend.",function() return wb_choices end,0.5)
-- (at any time later) give 2 water bottles to a connected user
vRP.giveInventoryItem(user_id,"water_bottle",2)
Item transformer
The item transformer is a very generic way to create harvest and processing areas.
you can use the action of the item transformer when entering the area
the item transformer has a number of work units, regenerated at a specific rate
the item transformer takes reagents (money, items or none) to produce products (money or items) and it consumes a work unit
This way, processing and harvesting are limited by the work units.
-- add an item transformer-- name: transformer id name-- itemtr: item transformer definition table--- name--- permissions (optional)--- max_units--- units_per_minute--- x,y,z,radius,height (area properties)--- r,g,b (color)--- recipes, map of action =>---- description---- in_money---- out_money---- reagents: items as idname => amount---- products: items as idname => amount---- aptitudes: list as "group.aptitude" => exp amount generated--- onstart(player,recipe): optional callback--- onstep(player,recipe): optional callback--- onstop(player,recipe): optional callback
vRP.setItemTransformer(name,itemtr)
-- remove an item transformer
vRP.removeItemTransformer(name)
local itemtr = {
name="Water bottles tree", -- menu name
r=0,g=125,b=255, -- color
max_units=10,
units_per_minute=5,
x=1858,y=3687.5,z=34.26, -- pos
radius=5, height=1.5, -- area
recipes = {
["Harvest"] = { -- action name
description="Harvest some water bottles.", -- action description
in_money=0, -- money taken per unit
out_money=0, -- money earned per unit
reagents={}, -- items taken per unit
products={ -- items given per unit
["water_bottle"] =1
}
}
}
}
vRP.setItemTransformer("my_unique_transformer",itemtr)
Items
Items are simple identifiers associated with a quantity in an inventory. But they can also be parametrics.
Parametrics items are identified like other items in the inventory but also have arguments as: weapon|pistol instead of just an ID. Parametric items don’t contain any data, they are generic item definitions that will be specialized by the arguments.
-- PROXY API-- define an inventory item (call this at server start) (parametric or plain text data)-- idname: unique item name-- name: display name or genfunction-- description: item description (html) or genfunction-- choices: menudata choices (see gui api) only as genfunction or nil-- weight: weight or genfunction---- genfunction are functions returning a correct value as: function(args) return value end-- where args is a list of {base_idname,arg,arg,arg,...}
vRP.defInventoryItem(idname,name,description,choices,weight)
-- return name, description, weight
vRP.getItemDefinition(idname)
vRP.getItemName(idname)
vRP.getItemDescription(idname)
vRP.getItemChoices(idname)
vRP.getItemWeight(idname)
-- add item to a connected user inventory
vRP.giveInventoryItem(user_id,idname,amount,notify)
-- try to get item from a connected user inventory-- return true if the item has been found and the quantity removed
vRP.tryGetInventoryItem(user_id,idname,amount,notify)
-- get item amount from a connected user inventory
vRP.getInventoryItemAmount(user_id,idname)
-- get connected user inventory-- return map of full idname => amount or nil
vRP.getInventory(user_id)
-- clear connected user inventory
vRP.clearInventory(user_id)
-- compute weight of a list of items (in inventory/chest format)
vRP.computeItemsWeight(items)
-- return user inventory total weight
vRP.getInventoryWeight(user_id)
-- return user inventory max weight
vRP.getInventoryMaxWeight(user_id)
-- open a chest by name-- cb_close(): called when the chest is closed
vRP.openChest(source, name, max_weight, cb_close)
-- TUNNEL SERVER API-- TUNNEL CLIENT API
Once defined, items can be used by any resources (ex: they can be added to shops).
local Proxy =module("vrp", "lib/Proxy")
local Tunnel =require("vrp", "lib/Tunnel")
vRP = Proxy.getInterface("vRP")
vRPclient = Tunnel.getInterface("vRP","vrp_waterbottle")
-- create Water bottle itemlocal wb_choices = {} -- (see gui API for menudata choices structure)
wb_choices["Drink"] = {function(player,choice) -- add drink actionlocal user_id = vRP.getUserId(player) -- get user_idif user_id thenif vRP.tryGetInventoryItem(user_id,"water_bottle",1) then-- try to remove one bottle
vRP.varyThirst(user_id,-35) -- decrease thirst
vRPclient.notify(player,"~b~ Drinking.") -- notify
vRP.closeMenu(player) -- the water bottle is consumed by the action, close the menuendendend,"Do it."}
-- add item definition
vRP.defInventoryItem("water_bottle","Water bottle","Drink this my friend.",function() return wb_choices end,0.5)
-- (at any time later) give 2 water bottles to a connected user
vRP.giveInventoryItem(user_id,"water_bottle",2)
Player state
-- PROXY API-- TUNNEL SERVER API-- TUNNEL CLIENT API-- get player weapons data-- return table with weapons data, use print(json.encode(result)) to understand the structure
vRP.getWeapons()
-- give weapons-- weapons: same structure as returned by getWeapons()-- (optional) clear_before: if true, will remove all the weapons before adding the new ones
vRP.giveWeapons(weapons,clear_before)
-- get player apparence customization data-- return table with customization data, use print(json.encode(result)) to understand the structure-- .model or .modelhash define the player model, the indexes define each component as [drawable_id,texture_id,palette_id] array-- props are referenced using the prefix "p" for the key (p0,p1,p2,p...), -1 = no prop
vRP.getCustomization()
-- set player apparence-- customization_data: same structure as returned by getCustomization()
vRP.setCustomization(customization_data)
-- set player armour (0-100)
vRP.setArmour(amount)
Proxy
The proxy lib is used to call other resources functions through a proxy event.
The notation is Interface.function(…).
Survival
Running, walking, being hurt/injured, and just living add hunger and thirst. When the hunger and the thirst are at their maximum level (100%), next hunger/thirst overflow will damage the character by the same amount (ex: when thirsty, don’t run, take a car).
The survival module implement also a coma system. If the health of the player is below the coma threshold, the player is in coma for a specific duration before dying. The health (thus coma) is recorded in the player state.
If a player disconnect and reconnect while in coma, he will fall in coma again and die in a few seconds.
Tunnel
The idea behind tunnels is to easily access any declared server function from any client resource, and to access any declared client function from any server resource.
-- build the client-side interface
clientdef = {} -- you can add function to clientdef later in other client scripts
Tunnel.bindInterface("myrsc",clientdef)
functionclientdef.teleport(x,y,z)
SetEntityCoords(GetPlayerPed(-1), x, y, z, 1,0,0,0)
end-- sometimes, you would want to return the tunnel call with asynchronous data-- ex:functionclientdef.setModel(hash)
local r =async()
Citizen.CreateThread(function()
-- do the asynchronous model loading
Citizen.Wait(1000)
r(true) -- return trueend)
return r:wait() -- wait for the async returned valueend-- get the server-side access
serveraccess = Tunnel.getInterface("myrsc")
-- call test on server and print the returned value (in an async context)local r = serveraccess.test("my client message")
print(r) -- true
Now if we want to use the same teleport function in another resource:
This way resources can easily use other resources client/server API.
The notation is Interface.function(dest, …).