You scrolled until here and might even have read as far, so without further ado, here’s the github link ![]()
https://github.com/xSilvermanx/serpent
The last update I did in public about this was in January. So take the time and look into the Youtube-video to see how Serpent acts ingame.
So looking at the github, where the hell do I start?
Let’s go through it and check what of the description above can be found in the scripts.
The startup
When loading the resource the most important parts happen in the files Def/def_sv.lua, Startup/startup_sv.lua and of course Main/main_sv.lua.
As explained in The main idea for a solution the peds and vehicles get created serverside and every bit of information is saved in serpent itself. def_sv.lua creates the tables that will be filled with every serpent-ped (a ped that is controlled by serpent; this is opposed to your normal ped that will be spawned and despawned by GTA and which has nothing to do with this resource).
The file startup_sv.lua starts the main-loop inside main_sv.lua and creates event handlers for when players connect. This makes it possible for serpent to store the player information as described in Player-Position above.
The serverside main thread
Main/main_sv.lua lines 173 to 215 is the central piece of code for serpent. This is a loop triggered every 500 ms. Every single serpent-ped is controlled from here, by asking the very same question:
Is any player close enough to the ped so that it has to be spawned in the game?
If yes, spawn the ped and move the responsibility over to a client. (more on that later)
If no, execute the task the the ped has been assigned.
Let’s go through the code using an example. Let’s create a ped utilizing serpent:
local ModelName = 'a_f_m_beach_01'
local ModelHash = GetHashKey(ModelName)
local SID = exports.serpent:ssv_nat_CreatePed(26, ModelHash, -180.0, 6190.0, 31.2, 66.0)
Yes, this is serverside code resembling the FiveM-native CreatePed. The executed code is found in Main/main_sv.lua lines 3 to 154. Note how we can define the position, the model hash and everything else and have it saved in the serpent ´ssv_PedList` (ssv is short for serpent serverside).
Now the server knows the ped and utilizing the SID (short for Serpent ID) you can assign commands to that ped from the server.
Let’s assume that two players are on the server, both in Los Santos. Let (x = 0, y = 0, z = 69) and (x = 30, y = -50, z = 100) be the positions for those players.
The main thread will calculate the distance from the spawned ped to both players and will determine whether a player is in spawning range. That isn’t the case as the spawnrange has been set to 175 meters.
Now when a player moves closer the script would be able to recognize that, thus eventually spawning the ped (more on that later).
The clientside main tasks
Every spawned client has one main task: Continually inform the server about your position. In serpent this is solved in the main loop in Main/main_cl.lua line 7 (The rest of the loop will be explained further down). So every single player will be broadcasting his position to the server every 500 ms.
That’s it. The rest is handled by the server.
Executing commands for serverhandled peds - how do peds act on the server?
For the enduser this is trivially easy. Take the SID we have fetched above and do this:
exports.serpent:ssv_nat_TaskFollowNavMeshToCoord(SID, -224.0, 6149.0, 31.2, ,1.0, -1, 2.0, true, 'Curr', false)
This is serverside code and emulates TaskFollowNavMeshToCoord.
How is this done?
Assuming that our ped is not near a player the Main Server Loop will trigger the Main Task Handler at Main/main_sv.lua line 207, main task handler is located same file at lines 217 - 236.
The task of the MainTaskHandler is to understand which task the ped should be doing currently and triggering the correct serpent event. For that it reads the data saved for the serpent-ped and sends the necessary data further to the next event.
In this case we are triggering a so called Current event TaskFollowNavMeshToCoord, so the main task handler will trigger the event ssv:nat:TaskFollowNavMeshToCoord.
Handlers for the serverside tasks
Our next stop is the file Natives/Handler/TaskFollowNavMeshToCoord.lua. The first function (lines 1 - 66) define the export that we have already used above. This function only saves data into the ped-table and can be ignored for now.
The important part starts at line 68:
The task FollowNavMeshToCoord knows two states (out of three possible): Init or Initialize and Continue (the third state would be Ignore).
This does what it says on the tin: Init will create much of the data that can be reused later, so Continue will in general be shorter.
The event starting line 68 will check for the current state and trigger the appropriate function, then change the state appropriately. It also includes a check for successful termination of the task, located at lines 85 - 93 (checking for the distance needed to be travelled and finishing the event).
As we have only just entered the command, the script will execute Init.
Initialising a task
FollowNavMeshToCoord needs pathfinding. I have implemented a working version of A-Star and pathnodes for Paleto Bay. It’s already been stated in the first post so I’ll not comment on this even though a bit has changed since the main assumption is still the same.
First we are checking whether PathfindingData is already existing. If not we create the pathfinding data for the current task (lines 110 - 162, the main function is line 145). Note line 152, which will send the created data to a client if the ped is currently spawned.
After the pathfinding data has been created the function will read out the next target position for the ped. This will in general be the next node in the pathfinding data, so a set of coordinates x, y and z (lines 179 - 215). It also checks whether the node has been reached and the next node should be targeted (lines 164 - 177).
Lastly it checks whether the ped is spawned or not and executes either the server-version or the client-version of the native.
As our ped is not spawned it executes ssv:nat:res:TaskFollowNavMeshToCoord:Init as in line 226.
Moving the ped on the server
The file is Natives/Server/TaskFollowNavMeshToCoord_sv.lua. This is classic linear algebra. I’m fetching the current position and the end position. From that I calculate the vector between them and normalize that vector (so that I got a vector pointing to my goal with a length of 1 m). Now I’ll multiply the vector with the speed of my ped (i.e. 1.0 m/s).
The resulting vector is the distance the ped moves in 1 second. So in order to get the new position of the ped I’ll add half of that vector to the current position (reason for half the value is because the main loop works on 500 ms or half a second).
I’m already finished here and got my ped moving whereever I want as long as pathnodes exist for that place.
Continuing the task
After the ped has moved one step (the distance in half a second) the main loop continues and will at some point get to my ped again. The task handler gets triggered just as described above, this time however the task is set to Continue. This doesn’t change too much; however the script can fetch saved values instead of recreating them thus hopefully speeding up quite a bit.
The real task will come however when
Players get closer to the ped
At this point the Main Server Loop will not trigger the Main Task Handler but rather the function ssv:SpawnPed (Main/main_sv.lua line 204, function lines 238 - 250). This saves necessary data and shoves all of it to the player who got close to the ped.
Now it’s time for the Main Client Loop to take over (Main/main_cl.lua lines 1 to 32). The client loop will check for every ped in the clients’ responsibility whether it is still inside the despawning range (200 meters). For now this is only our ped we spawned but later on there might be multiple peds that the client controls.
The functionality of this loop is the same as the main server loop. Check whether the player is still close. If yes, trigger the task handler, if no, trigger the despawning function.
Let’s assume the player is close, triggering the clients’ main task handler.
A word on distance
You might have noticed that the spawning range 175.0 is different from the despawing range 200.0 . This is to avoid cases of player’s edging in and out of spawning range with every tick, meaning the serpent-ped spawns and despawns every 500 ms. Having different distances eliminates this issue.
Triggering the clientside task handler
This function (Main/main_cl.lua lines 34 to 54) does the same as the serverside task handler. Note that the client task handler still triggers the serverside event handler as described above.
This is because the event itself checks whether the ped is spawned.
Tasks triggering clientside
Check Natives/Handler/TaskFollowNavMeshToCoord.lua lines 219 - 224. This is what is triggered for the Init-version of the native but is the same for Continue. The script checks for the entity ownership and triggers the clientside function for the entity owner.
Note that entity ownership (i.e. FiveM / GTA V-assigned ownership) and serpent-ownership might be two different clients!
Tasks executing clientside
The file is Natives/Client/TaskFollowNavMeshToCoord_cl.lua. We’ll disregard the boolean persistFollowing for now so assume that one is set to false. We’ll thus check lines 3 to 33. Basically the script will check for a safe end coordinate for the ped (lines 6 to 15, so it doesn’t stop in the middle of the street) and then trigger the GTA V-native just normally (line 33).
That’s it, task is executing on the client.
Players moving away
Now let’s assume that the player who has first spawned the ped (and thus has serpent ownership) moves out of the despawning range. The players’ main client loop notices this and triggers a despawning function (Main/main_cl.lua lines 129 - 138). Unlike the name suggests this does NOT despawn the ped. Instead it merely deletes the information on the client and sends control back to the server (line 135).
Now the server takes over (Main/main_sv.lua lines 263 - 304). It resets a few informations and then checks if any other player is still near said ped. If yes it’ll transfer ownership over to that player. If no it’ll despawn the ped and resume ownership itself, thus making the main server loop responsible again.
That was quite technical here. I hope to have explained the code in a way that it’s understandable what is happening behind the scenes.
If you want to test out the script, you are welcome to use the following server side code:
local ModelName = 'a_f_m_beach_01'
local ModelHash = GetHashKey(ModelName)
local SID = exports.serpent:ssv_nat_CreatePed(26, ModelHash, -180.0, 6190.0, 31.2, 66.0)
Goals = {
{x=-224.0, y=6149.0, z=31.2, h=66.0},
{x=-180.0, y=6190.0, z=31.2, h=246.0}
}
local goalcoordinate = 1
RegisterCommand("movetocoord", function(source, args)
local x = Goals[goalcoordinate].x
local y = Goals[goalcoordinate].y
local z = Goals[goalcoordinate].z
local h = Goals[goalcoordinate].h
print('Triggering export function')
exports.serpent:ssv_nat_TaskFollowNavMeshToCoord(SID, x, y, z, 1.0, -1, 2.0, true, 'Curr', false)
if goalcoordinate == 1 then
goalcoordinate = 2
elseif goalcoordinate == 2 then
goalcoordinate = 1
end
end)
Load in serpent, load in the above code as a separate resource and use the chatcommand /movetocoord. It might bug a bit but a ped should be moving between two coords on the main street of Paleto Bay.
Have fun testing it out and finding bugs ![]()
In order to see what the server is doing you can enable server prints in Startup/startup_sv.lua. Uncomment lines 35 to 47 and you are good to go.
Beware: I was able to bluescreen my PC several times when running a local server and filling my console with printed data…
Again, any questions feel free to post here in this topic or hit me up ![]()
Thanks for sticking by to this point, yes it’s a VERY long one. Hope it’s worth the read (and I hope that you don’t want to admit me to psychiatry due to obvious insanity, I mean who would seriously commit to such a project?).