Hello all! I have decided to make a small guide that covers special situations in the C# environment of FiveM. You’ll pick those up as you read through. Feel free to comment your own info.
1. Native / API Callbacks
Native or their API wrapping methods shouldn’t be executed in the constructor of a BaseScript derived class (this mostly applies to the client, although the exact behavior here is unknown).
A workaround for this limitation is to execute Function or API calls and any method that uses Function or API calls on the first tick:
public class TestScript : BaseScript
{
#region Private Fields
private bool firstTick = false;
#endregion
#region Initialization
public TestScript()
{
Tick += OnTick;
}
#endregion
#region Private Methods
private async Task OnTick()
{
if(!firstTick)
{
firstTick = true;
// API calls.
}
}
#endregion
}
2. NUI Callbacks (Client)
API callbacks are tricky. Since the communication between NUI and the client are done in JavaScript, the returned data is already parsed and is pushed into ExpandoObject properties.
// ... Inside BaseScript
# NUI Implementation
private void RegisterNUICallback(string msg, Func<IDictionary<string, object>, CallbackDelegate, CallbackDelegate> callback)
{
API.RegisterNuiCallbackType(msg); // Remember API calls must be executed on the first tick at the earliest!
EventHandlers[$"__cfx_nui:{msg}"] += new Action<ExpandoObject, CallbackDelegate>((body, resultCallback) =>
{
callback.Invoke(body, resultCallback);
});
}
#endregion
I would understand if you’ll need a second to let it sink in. What we do here is listen to NUI events with a special event prefix: __cfx_nui:{eventName}
. This is how FiveM framework does it so we have to obey. Now what is special are the passed arguments. They will ALWAYS be of type ExpandoObject and CallbackDelegate. The ExpandoObject is our data (google about the type and the DLR for more info). The ExpandoObject is of type IDictionary<string, object>, essentially keeping the data by <VariableName, VariableData>.
Essentially, our RegisterNUICallback command accepts a callback we call when the event is raised. So we just make life easier and pass it to the callback as IDictionary.
Note: I have yet to discover what does the second passed parameter (of type CallbackDelegate) is for. Please share if you can!
3. API RegisterCommand (Server)
This is yet another tricky command. RegisterCommand listens to commands typed in the remote console. The API function requests a string for the command name, a weird type of ‘InputArgument’ and a bool named restricted. The restricted is usually false, and the input argument is going to act as a delegate for us to execute when the command is passed. The question here is which arguments are passed with this callback. Appearantly these are <int, List. string> where int is source, List is really a list of parameter strings and the last string being the entire command.
// ... fyi: This function works in the constructor of the server BaseScript.
API.RegisterCommand("say", new Action<int, List<object>, string>((source, arguments, raw) =>
{
Debug.WriteLine($"{source}\n{raw}");
foreach (var arg in arguments)
Debug.WriteLine($" {arg}");
}), false);
// Console command: say hello world!
// Debug:
// 0,
// say hello world!
// hello
// world!