NPC/Bots implementation in VCMP 0.4 Servers

Started by habi, Apr 01, 2022, 05:37 PM

Previous topic - Next topic



An attempt to implement Non Playable Characters (NPC) for vc-mp has been made. The working of this was inspired by SA-MP.

ConnectNPCEx("Oliver", Vector (235.399, -1293.33, 11.071),  -0.272573, 1);

ConnectNPCEx("Meera", Vector(233.236, -1291.67, 11.0706), -1.47619, 63);


Application Download links ( v1.7 Beta 3)( 2023-10-08) (YYYY-MM-DD)
Windows:    Windows-installer     

Linux x64    Linux x86         

Windows+Linux: binaries

A discord has been setup at my friends request to discuss about NPC question, answers: here

Tutorials :  1. Creating an idle NPC    2. Recording player actions  3. Recording vehicle drivings

11 minute long video tutorial of setting up (w/o installer) and recording/replay: here

NPC Full Wiki - documentation ( Click image to open )

Functions and callbacks plain documentation
NPC Callbacks
OnNPCScriptLoad( params )
OnNPCConnect([integer] myplayerid)
OnNPCDisconnect([integer] reason)
OnNPCEnterVehicle([integer] vehicleid, [integer] seatid )
OnClientMessage([integer] r, [integer] g, [integer] b, [string] message )
OnPlayerDeath([integer] playerid )
OnPlayerText([integer] playerid, [string] message )
OnPlayerStreamIn([integer] playerid )
OnPlayerStreamOut([integer] playerid )
OnVehicleStreamIn([integer] vehicleid )
OnVehicleStreamOut([integer[ vehicleid )
OnPlayerUpdate( [integer]playerid, [integer]updatetype )
OnSniperRifleFired( [integer]playerid, [integer]weaponid, [float]x, [float]y, [float]z, [float]dx, [float]dy, [float]dz )
OnConsoleInput( [string]text )
OnServerData( [blob]data )
NPC Functions
[true/null]StartRecordingPlayback([integer] playbacktype, [string] recordname )
[null]SendChat([string] text)
[null]SendCommand([string] command) //without '/'
[null]SetMyPos( [Vector] position )
[null]SetMyFacingAngle( [float] angle ) //in radians
[float]GetDistanceFromMeToPoint( [Vector] point )
[bool]IsVehicleStreamedIn( [integer] vehicleid )
[bool]IsPlayerStreamedIn( [integer] playerid )

[integer] SetTimer( [string] FunctionName, [integer] interval, [integer] repeat) //repeat=1 means loop
[null]KillTimer( [integer] timerid )
[integer] GetPlayerState( [integer] playerid )
[Vector] GetPlayerPos( [integer] playerid )
[integer] GetPlayerVehicleID( [integer] playerid )
[integer] GetPlayerArmedWeapon( [integer] playerid )
[integer] GetPlayerHealth( [integer] playerid )
[integer] GetPlayerArmour( [integer] playerid )
[integer] GetPlayerKeys( [integer] playerid )
[float(in radians)] GetPlayerFacingAngle( [integer] playerid )
[bool]IsPlayerInRangeOfPoint( [integer] playerid, [float] radius, [Vector] point )
[string/null] GetPlayerName( [integer] playerid )
[bool] IsPlayerConnected( [integer] playerid )

[null]SendOnFootSyncData( [integer] keys, [float]posx, [float]posy, [float]posz, [float]angle, [integer]health, [integer]armour, [integer]weapon, [integer]ammo, [float]speedx, [float]speedy, [float]speedz, [float]aimposx, [float]aimposy, [float]aimposz, [float|integer]aimdirx, [float|integer]aimdiry, [float|integer]aimdirz, [bool]isCrouching, [bool]isReloading )
[true]SendOnFootSyncDataEx( [integer] keys, [Vector]pos,  [float]angle, [integer]health, [integer]armour, [integer]weapon, [integer]ammo, [Vector]speed, [Vector]aimpos, [Vector]aimdir, [bool]isCrouching, [bool]isReloading )
[null]FireSniperRifle( [integer]rifleid, [float]aimposx, [float]aimposy, [float]aimposz, [float|integer]dx, [float|integer]dy, [float|integer]dz)
[true]FireSniperRifleEx( [integer]rifleid, [Vector]aimpos, [Vector]naimdir )
[true]SendInCarSyncData( [integer]keys, [integer]health, [integer]armour, [integer]weapon, [integer]ammo, [float|integer]carhealth, [integer]damage, [float|integer]posx, [float|integer]posy, [float|integer]posz, [float|integer]rotx, [float|integer]roty, [float|integer]rotz, [float|integer]rotw, [float|integer]speedx, [float|integer]speedy, [float|integer]speedz, [float|integer]turretx, [float|integer]turrety )
[true]SendInCarSyncDataEx( [integer]keys, [integer]health, [integer]armour, [integer]weapon, [integer]ammo, [float|integer]carhealth, [integer]damage, [Vector]pos, [Quaternion]rotation, [Vector]speed, [float|integer]turretx, [float|integer]turrety )
[bool]EnterVehicle( [integer]vehicleid, [integer]seatid )
[null]SendShotInfo( [integer]bodypartId, [integer]animationId )
[null/true]SetLocalValue( [integer]field, [integer|float]value)
[integer/float/null]GetLocalValue( [integer]field )
[null]SendDeathInfo( [integer]weaponId, [integer]killerId, [integer]bodypartId )
[null/true/false]FaceNPCToPlayer( [integer]playerid )
[float/null]WhereIsPlayer( [integer]playerid )
[integer]GetPlayerSlotWeapon( [integer]playerid, [integer]slotid )
[integer]GetPlayerSlotAmmo( [integer]playerid, [integer]slotid )
[integer]GetSlotFromWeaponId( [integer]weaponid )
[integer|null]SetTimerEx( [string]functionname, [integer]interval, [integer]n, ...)
[null]SetPSLimit([integer] n)
[integer]GetPlayerSkin( [integer]playerid )
[integer]GetPlayerTeam( [integer]playerid )
[integer]GetClosestPlayer( [bool]checkteam=false )



[true]WriteInt([blob]data, [integer]int)
[true]WriteByte([blob]data, [integer]int)
[true]WriteFloat([blob]data, [float]fval)
[true]WriteString([blob]data, [string]str)
1. Vector
    Constructor( float x, float y, float z )
    Metamethods: _add, _sub, _mul(float), _div(float), _tostring
2. Quaternion
    Constructor( float x, float y, float z, float w )
    Metamethods: _add, _sub, _mul(float), _div(float), _tostring

MAX_PLAYERS                            <-    100
MAX_PLAYER_NAME                        <-    24
MAX_VEHICLES                        <-    1000
INVALID_PLAYER_ID                    <-    0xFF
INVALID_VEHICLE_ID                    <-    0

PLAYER_STATE_NONE                    <-    0
PLAYER_STATE_ONFOOT                    <-    1
PLAYER_STATE_AIM                    <-    2
PLAYER_STATE_DRIVER                    <-    3
PLAYER_STATE_PASSENGER                <-    4
PLAYER_STATE_EXIT_VEHICLE            <-    7
PLAYER_STATE_WASTED                    <-    8
PLAYER_STATE_SPAWNED                <-    9

KEY_ONFOOT_AIM        1   
KEY_ONFOOT_PUNCH        64   
KEY_ONFOOT_CROUCH        288   
KEY_ONFOOT_FIRE        576   
KEY_ONFOOT_SPRINT        1024   
KEY_ONFOOT_JUMP        2176   
KEY_ONFOOT_RIGHT        4096   
KEY_ONFOOT_LEFT        8192   
KEY_ONFOOT_BACKWARD        16384   
KEY_ONFOOT_FORWARD        32768   
KEY_ONFOOT_LOOKBHND        65536   
KEY_INCAR_LEANUP        16   
KEY_INCAR_FORWARD        1024   
KEY_INCAR_HORN        288   
KEY_INCAR_BACKWARD        2176   
KEY_INCAR_RIGHT        4096   
KEY_INCAR_LEFT        8192   
KEY_INCAR_SUB_MISSION        65536   


fields and types( of 'SetLocalValue' and 'GetLocalValue' )
I_KEYS    integer
F_POSX    float
F_POSY    float
F_POSZ    float
F_ANGLE    float
I_HEALTH    integer
I_ARMOUR    integer
I_CURWEP    integer
I_CURWEP_AMMO    integer
V_AIMPOS    Vector
V_AIMDIR    Vector
V_POS        Vector
Q_CAR_ROTATION    Quaternion
F_CAR_HEALTH    float
I_CAR_DAMAGE    integer
V_CAR_SPEED        Vector
F_CAR_TURRETX    float
F_CAR_TURRETY    float
Squirrel Add-On Plugin npc04relxx functions
ConnectNPC( [string] name, [string] script="", [bool]enableconsoleinput=false, [string] host="", [string] plugins="")
ConnectNPCEx( [string] name, [float|integer]x, [float|integer]y, [float|integer]z, [float|integer]angle, [integer] skinId=default, [integer] weaponId=default, [integer] classId=0, [string]script="", [bool]enableconsoleinput=false, [string]host="", [string]plugins="", [string]... )
[bool]IsPlayerNPC( [integer] playerid )
[bool]StartRecordingPlayerData( [integer] playerid, [integer] recordtype, [string]recordname )//recordname without .rec extn
[bool]StopRecordingPlayerData( [integer] playerid )

[bool]SetMaxPlayersOut( [integer] maxplayers )

[function a]F([string]funcname or [userdata] returned by another F)
    function a, Variable Parameters( null, integer, float, string, bool, array, userdata made by another F, Vector,
    and Quaternion).
    returns userdata

[function b]RFC([integer]npcid, [string]funcname|[userdata])
    function b, Variable Parameters( null, integer, float, string, bool, array, userdata returned by F, Vector,
    and Quaternion).
    returns bool

Squirrel Add-On Plugin npc04relxx functions
ConnectNPC( [string] name, [string] script, [string] host="")
[bool]IsPlayerNPC( [integer] playerid )
[bool]StartRecordingPlayerData( [integer] playerid, [integer] recordtype, [string]recordname )//recordname without .rec extn
[bool]StopRecordingPlayerData( [integer] playerid )

See also on wiki: v1.7 beta 3 release notes

Source: Click here ( github ), License: GPL v3


Lemme love ya


Now imagine the Vice City coming to life, with NPCs simulating the traffic. I would love to see that!
(if you guys do that, please tell me the server ip)

habi, man, I don't know what will happen to vc:mp in the future, but I'm glad you shown people a way to play with NPCs !
Everybody screaming for them. Well, here they are! Everything looks pretty easy to use.

Thanks for this!


A long wait came to reality, thanks @habi + a video tutorial about implementing and creating an npc would be appreciated



This looks amazing!
(Though I'm not sure if I'll go through the pain of working with the squirrel plugin again)


Quote from: Spiller on Apr 06, 2022, 04:52 AMThough I'm not sure if I'll go through the pain of working with the squirrel plugin again
Actually, this works with the squirrel plugin. This is supposed to work with java plugin also, if you are using it. i.e. if your server language is java, you still can npc without switching to squirrel. In that case a separate plugin is to be made, a plugin that attaches to the java host, and creating two functions "ConnectNPC" and "IsPlayerNPC". (The codes are available. Check the first post. )
The intelligence of NPC lies in the scripts which ofcourse have to be scripted in squirrel.

If you are recording actions of player (tutorial 2),  you can run a blank squirrel server with above npc04relxx module.  Then copy the. rec file to its place. Then make NPC Script which may use the. rec  file.  Then from java call the npc program using system command.


The older plugin "actor" is very similar to this? Why rewrite it?


Yes, earlier plugin called "actor" is very similar to this. Here are the reasons why i rewrite it.

First, "actor" plugin used "server hooks" somewhere. So a possible bug in the plugin will affect the server.
But in the new model,  any possible bug cannot affect server because server calls an external application to run. A bug will result in the failing of the external application and not the server.

Secondly, in the earlier plugin if you want to check say "a specific actor (say John) entered a vehicle,
1. you have to first find squirrel onPlayerVehicleEnter function in main.nut
2. Check if the player is an actor
3. Check if the name of the player == "John"
4. Script the codes
But in the new model, the code is separate. It is not coded in main.nut.
It is a separate nut file which determines all events of John like onNPCEnterVehicle, onNPCSpawn, onClientMessage, etc. And you connect John with the script file. ( It is like saying "Hey John, take this script file and connect to my server " )


Working with npcs and sniper rifles,

'habi' in the image is npc and it is shooting me with a weapon 29 (laser sniper)

This function in npc's script
FireSniperRifle([integer]wep(28/29), [float]aimPosX, [float]aimPosY, [float]aimPosZ, [float](alpha), [float]Angle ) where alpha is an angle in radians. 0. 0 means complete horizonal, postive means above, negative means below. can be obtained from aimdir.y. Angle - npc's facing angle if faced to target.
which fires a sniper shot from npc to any target.

Need to perfect some codes and will release here.

Version 1.3
npcclient.exe here

Changelog below[spoiler]Version 1.3 Changes (15.11.2022)
Extended Timer implemented SetTimerEx (
Fixed npc running unwantedly just after sending some packet.
Added boolean parameter for crouching in SendOnFootSyncData[/spoiler]


Version 1.5 released ( 05-Dec-2022 )

Lot of bugs fixed
-Version 1.4 (was available to download from npc-wiki) has been compiled on older version of linux and hence had problems running on Ubuntu 18.04

New changes

-NPCs can be connected without a script. ( See ConnectNPCEx on wiki ). This new functions parameters include position, skin, weapon and class to spawn. This is useful in creating models in shops like ammunation, toolstore or similary places.
ConnectNPCEx(name, x, y, z, angle, skinId, weaponId, classId, script="", host="", ...)
ConnectNPCEx( "Mercedes", 100.0, 200.0, 10.0, 0.0, 116, 0, 0,"mercedes_script.nut","","parameter1", "parameter2", "parameter3");
//Mercedes will be created at the specified position with tag 'Mercedes'. The 'parameter1', 'parameter2', 'parameter3' will be passed to npcscript. Note that 'mercedes_script.nut' must lie in [Server folder]/npcscripts folder.

-Parameters can be passed to the npcscripts ( like argv of C ) ( from server main.nut). This is passed as an array to OnNPCScriptLoad( params ).

-GetTickCount on npcscripts

- old scripts are not compatible with this version due to the addition of argument 'params' in onNPCScriptLoad. Other than that, it works fine.

-Playback recordings created prior to v1.4 are incompatible. A command-line tool to convert old .rec files to new format .rec files is provided along with setup. See npcclient/recupdate folder.

-Introduction of 'SendDeathInfo' function which will let the npc's be killed by a player.
//  [Server folder]/npcscripts/mynpc.nut
function ICantTakeItAnyMore
SendDeathInfo( 70, 255, 0 ); //70 means weapon Id. 255 the killer ID (for suicide). 0 the bodypart.

Download links v1.5

Windows       Windows x86 and x64
Linux             Linux x86
Linux             Linux x64

A small mission-like server here:

If you find any bugs, do report it.


Amazing work, bringing life to the world!


Damn,  only if I would have checked this few hours ago.
Now I need to wait until tomorrow to join that server with demo mission,  haha.

Great job,  man!
All the effort you put into this is enormous,  and the new features sound great!


Quote from: Sebastian on Dec 06, 2022, 09:35 PMDamn,  only if I would have checked this few hours ago.
Now I need to wait until tomorrow to join that server with demo mission.
I'm glad to hear it