Jump to content
Lucky44

A better explanation of BIS_fnc_MP and remoteExec, please?

Recommended Posts

I have a decent understanding of locality. And I've read the Biki pages on BIS_fnc_MP and remoteExec. I understand that remoteExec is the newer, preferred approach, but that the other still works too.

 

But I have not been able to wrap my head around the syntax/usage of either of these. So I'm asking for someone who really does understand them to expand on what the Biki has and explain the usage -not just syntax, but how to use it in different situations. A variety of examples (and why it's done the way it's done in each example, and the effects of the variations) would be awesome.

 

Anyone able to do this? It'd be a real benefit to all of us who make multiplayer missions!

Share this post


Link to post
Share on other sites

Hey there,

 

Basicly remoteExec and BIS_fnc_MP do the same job. They remotely execute a given function or code at a single or many targets. And that is the problem. With bis_fnc_mp every god damn script kiddies could execute the hell he wanted. player setDamage 1 -> allPlayers and there you go. Also because it is scripted funtion, and not engine based, it is by far slower than remoteexec.

 

Remoteexec is faster, gives better control for security purpose (remoteexec whitelist stuff) and things like sending to -2 so everybody but the server is great in MP gamemodes. 

 

The synatx is easy, just take you mp call and rearrange the pices. There is even some automatic tools for that aleady (somewhere on the altisliferpg forum)

 

So yeah, not much at the first look, but it is very useful and I suggest using that!

Share this post


Link to post
Share on other sites

Don't worry about using BIS_fnc_MP anymore.  BIS_fnc_MP used to be a purely scripted way of executing code on remote machines, but as mentioned above, it lacked security and was slower than a purely engine based solution.

 

So, BIS created remoteExec.  remoteExec allows the mission maker to directly specify what clients can and cannot do in the description.ext, so you can make sure that nobody is running malicious code on your server or client from their computer.  

 

So what happened to BIS_fnc_MP now that we have remoteExec?  Well, BIS_fnc_MP was basically just re-written so that it just calls remoteExec.  There is no point in using it all when writing new code, they just re-wrote it to continue working but use remoteExec because it ensures that all old missions and stuff that used BIS_fnc_MP will still work.

 

To use remoteExec, it is really easy.  

 

[_parameter1,_parameter2,_parameter3] remoteExec [myFunctionName,_targets,_jip];

 

_parameter1, 2, 3, are just the parameters that you want to pass to myFunction.  Note that these parameters are passed as they exist on the local machine that is calling remoteExec, not how they will look on the target machine.  So passing "player" as a parameter in remoteExec will retrieve the player on the local machine, THEN send it over to the target, and myFunction will execute code with the parameter referencing the player back on the machine that called remoteExec.  If, however, myFunction contains something like the "player" command, it will retrieve the player within the function code on the target machine, and therefore player will reference the player on the target machine.

 

myFunction is the function you want to execute on the target machine, and pass those parameters to.  You can define functions in description.ext and just call them by name here.  It works with BIS functions too.  This is the easiest way to do this.  note that myFunctionName needs to be a string.  For example, "BIS_fnc_holdActionAdd" with the quotes like that.  

 

_targets defines where you want to execute the function.  This is a very powerful parameter.  You can put in all sorts of stuff here to target only the machine you want to run the code on.  Use the number 0 to run the function everywhere.  Use 2 to run the function on the server.  Use ([0,-2] select (isMultiplayer && isDedicated)) to run the code on every machine with a player. You can also put an object in here, and the function will execute where the object is local, or specify a specific machine by using its ownerID.

 

_jip is a little complex. I don't really have time to explain it now and don't really understand it myself.

  • Like 1

Share this post


Link to post
Share on other sites

I personally don't bother too much with the security stuff of remoteExec, I've only encountered 3 clowns that did anything malicious to a server, most of those back before battleye security was strengthened further with the launcher.  I've got more pressing problems to worry about in my mission rather than setting up a complex list of what clients can't/can do. Regardless it can be useful if you feel your mission needs it, everything else you need to know is explained above.

Share this post


Link to post
Share on other sites

I personally don't bother too much with the security stuff of remoteExec, I've only encountered 3 clowns that did anything malicious to a server, most of those back before battleye security was strengthened further with the launcher.  I've got more pressing problems to worry about in my mission rather than setting up a complex list of what clients can't/can do. Regardless it can be useful if you feel your mission needs it, everything else you need to know is explained above.

 

 

Depending on how you set up your scripts, it can actually be very easy.

 

Since the server can always remoteExec anything regardless of what is in description.ext, you really only have to whitelist the stuff that clients will be remoteExec'ing to the server or other clients. 

 

I only have four functions whitelisted.  3 are BIS functions required for some some of the init scripts to work in multiplayer and the last one is mine.  Most of my scripting happens on the server and rarely to clients need to actually send anything to the server.

Share this post


Link to post
Share on other sites

Thanks for trying to help, all.

 

But frankly, this is not helping! I would benefit from examples, for one thing. Let me be more clear about what I don't understand and what might help.

 

Say I want to put a trigger on the map to detect BluFor Present. Let's skip the fact that triggers on the map have their own sets of problem for now. And when the trigger conditions become true, I want to run a script called myScript1.sqf. It has no arguments. How would I script that with remoteExec?

 

Another example, I want to addAction to an object to hide it (aka run a script that makes it drop into the ground then deleteVehicle's it. I'm thinking it's better to put the code in the initFramework.sqf rather than in the init box of the object - would there be a difference in how remoteExec would be used there? How would I code it if the script is called hideObject.sqf?

 

Let's just start with those examples. I'm sure I'll have questions after seeing the syntax!

 

Thanks in advance for the help!

Share this post


Link to post
Share on other sites

Thanks for trying to help, all.

 

But frankly, this is not helping! I would benefit from examples, for one thing. Let me be more clear about what I don't understand and what might help.

 

Say I want to put a trigger on the map to detect BluFor Present. Let's skip the fact that triggers on the map have their own sets of problem for now. And when the trigger conditions become true, I want to run a script called myScript1.sqf. It has no arguments. How would I script that with remoteExec?

 

Another example, I want to addAction to an object to hide it (aka run a script that makes it drop into the ground then deleteVehicle's it. I'm thinking it's better to put the code in the initFramework.sqf rather than in the init box of the object - would there be a difference in how remoteExec would be used there? How would I code it if the script is called hideObject.sqf?

 

Let's just start with those examples. I'm sure I'll have questions after seeing the syntax!

 

Thanks in advance for the help!

 

 

Ok, well as for the trigger, it depends what you want to do with the script really.  Do you want the execVM script to fire ONLY on the server when the trigger is activated?  Or do you want it to fire on every client and the server?  It all depends on what you are trying to accomplish, as there are many ways to go about it and the nature of what you are trying to do will guide you to the best solution.

 

In general, unless you are only doing things to the player's UI, or making some hints pop up, or something else that is super client-local and relatively simple in your execVM script, then you probably only want the trigger on the server.  Then from the execVM'ed script you can remoteExec any little local stuff to the clients if you have to, but most of the script probably should run only on the server.  So, in the 3DEN editor, tick the little box in the trigger's property window that says "server only."  This will prevent a copy of the trigger with the same parameters from being created on every client at mission initialization, and therefore prevent your script from firing on every computer.  Then just put execVM "myScript.sqf"; into the on Act box of the trigger.

 

As for how to use remoteExec.  Well, I have an example that I will show you for something I am doing in my mission.  It is a little long and there is a lot of other stuff going on, but just bear with it and see if you can make sense of what is happening.

//start setting up all the objectives.{
  _area = _x;
  //Create readable name from variable name by inserting spaces
  _name = _area call JWL_fnc_spacer;
  //get the flag variable, store it, delete global public variable, change texture.
  _flag = missionNamespace getVariable ("JWL_areaFlag_" + _area);
  missionNamespace setVariable [("JWL_flag_" + _area),nil,true];
  _flag setFlagTexture "a3\data_f_exp\flags\flag_synd_co.paa";
  //create additional markers to determine activation area and to hightlight the border of each area
  ("JWL_areaZone_" + _area) setMarkerColor "ColorGrey";
  ("JWL_areaZone_" + _area) setMarkerAlpha 0.75;
  _posn = (markerPos ("JWL_areaZone_" + _area));
  _size = (markerSize ("JWL_areaZone_" + _area));
  _rotn = (markerDir ("JWL_areaZone_" + _area));
  _actvMarker = createMarker [("JWL_areaTrgr_" + _area),_posn];
  _actvMarker setMarkerShape "ELLIPSE";
  _actvMarker setMarkerDir _rotn;
  _actvMarker setMarkerSize [((_size select 0) + (paramsArray select 0)),((_size select 1) + (paramsArray select 0))];
  _actvMarker setMarkerAlpha 0;
  _brdrMarker = createMarker [("JWL_areaBrdr_" + _area),_posn];
  _brdrMarker setMarkerShape "ELLIPSE";
  _brdrMarker setMarkerBrush "Border";
  _brdrMarker setMarkerColor "ColorGUER";
  _brdrMarker setMarkerSize _size;
  _brdrMarker setMarkerDir _rotn;
  //create respawn array in form of [[pos1,pos2,pos3],[rIndex1,rIndex2,rIndex3]]
  //make sure that if a respawn point is inside a building, that building cant be destroyed
  _rArr = [];
  {
    _logic = missionNamespace getVariable ("JWL_areaRspn_" + _area + _x);
    if (!(isNil "_logic")) then {
      _lPos = getPosWorld _logic;
      _nBlg = nearestBuilding _lPos;
      if ([_logic,_nBlg] call JWL_fnc_inBuilding) then {
        _nBlg allowDamage false;
      };
      deleteVehicle _logic;
      _rArr pushBack _lPos;
      true
    };
  } count ["_1","_2","_3","_4","_5"];
  _rArr = [_rArr,[]];
  //create secure array with all the data about the area, protected from publicVariable
  [("JWL_areaData_" + _area),[_name,true,independent,_flag,_rArr,[]]] call JWL_fnc_createArraySecure;
  //add capture action to all the flags, make sure JIP = true
  _grbg = [
    _flag,
    (format ["Capture %1",_name]),
    "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa",
    "\a3\3DEN\Data\CfgWaypoints\Scripted_ca.paa",
    (format ["((_target distanceSqr _this) < 25) && (((JWL_areaData_%1) select 2) != (side _this)) && ((JWL_areaData_%1) select 1)",_x]),
    "true",
    {},
    {},
    {[((_this select 3) select 0),(side (_this select 1))] remoteExecCall ["JWL_fnc_captureArea",2];},
    {},
    [_area],
    10,
    0,
    false,
    false
  ] remoteExecCall ["BIS_fnc_holdActionAdd",([0,-2] select (isMultiplayer && isDedicated)),true];
} count _this;

First thing to note is that this script runs ONCE on the server.

 

As you can see, it is one big "count" loop.  So it iterates through all elements in the array _this and then does all the code in the brackets.  _this is an array of strings, such as ["LumberYard","DieselPlant","Airport"].  But once it iterates through all of them the script is done and is discarded.

 

Inside the loop, first it saves the current element, eg. "DieselPlant" into the variable _area with _area = _x;  It does this so I can access the current element inside of any loops within the main loop later on, since _x will refer to something else in that case.

 

Next it takes the current element and turns it into something I can use later on in the UI, eg. "LumberYard" -> "Lumber Yard".

 

Then it finds a specially named flagpole, gets a reference to it, deletes the global public variable, and sets the texture.  The reference to the flag is used later and stored elsewhere.

 

Then it sets up a bunch of markers and junk which I use in the mission for some other stuff, but markers created with createMarker are global so changes made to them on the server show up everywhere.  

 

Then it starts up a mini-loop and finds some specially named game logics that I have placed, determines if they are in a building and if so makes the building invulnerable, and then creates an array with each of their positions in it and deletes the logic afterwards.  This is just to permanently store the positions at the start so I can dynamically create respawn points on them in the mission.

 

Now, finally, we get to the remoteExecCall part.

 

this part:

  _grbg = [
    _flag,
    (format ["Capture %1",_name]),
    "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa",
    "\a3\3DEN\Data\CfgWaypoints\Scripted_ca.paa",
    (format ["((_target distanceSqr _this) < 25) && (((JWL_areaData_%1) select 2) != (side _this)) && ((JWL_areaData_%1) select 1)",_x]),
    "true",
    {},
    {},
    {[((_this select 3) select 0),(side (_this select 1))] remoteExecCall ["JWL_fnc_captureArea",2];},
    {},
    [_area],
    10,
    0,
    false,
    false
  ] remoteExecCall ["BIS_fnc_holdActionAdd",([0,-2] select (isMultiplayer && isDedicated)),true];

It looks scary, but all it is doing is executing the function "BIS_fnc_holdActionAdd" on every client, including JIP players.  BIS_fnc_holdActionAdd must be executed locally on the client that you want to have the action, so I have to remoteExecCall the function from the server to get it on to every client.  Lets turn all the crazy parameters into one variable name so it is easier to see what is going on.

_grbg = _holdActionParamsArray remoteExecCall ["BIS_fnc_holdActionAdd",([0,-2] select (isMultiplayer && isDedicated)),true];

Ok, now that all the parameters for "BIS_fnc_holdActionAdd" are referenced by the array _holdActionParamsArray we can see what is happening with remoteExecCall a little better.

 

First off, _grbg is the return value of remoteExecCall.  If JIP is set to true in the command, it will return the JIP ID of the command in the JIP queue.  This is not important to me because I don't need to ever manually remove this from the JIP queue using this ID, so I named this variable _grbg.  However, for some reason I get script errors without catching the return value in a variable when JIP = true, so I have to include it.

 

Alright, look at the array after remoteExecCall.  The array before it are the parameters passed to the function called by remoteExecCall, but the array after it consists of three elements that are the parameters for the remoteExecCall command itself.  

 

"BIS_fnc_holdActionAdd" this is the function that will be executed on the target machine

 

([0,-2] select (isMultiplayer && isDedicated)) is the ID of the target machine.  In this case, it will resolve to 0 in most cases and execute "BIS_fnc_holdActionAdd" on every machine on the network.  However, only if the mission is run in multiplayer in a dedicated server it will resolve to "-2" and execute "BIS_fnc_holdActionAdd" on every machine but the server.  This is a good way to always execute your function on every machine that has a player behind it no matter whether you are playing singleplayer or multiplayer.

 

true simply tells remoteExecCall to add the function to the JIP queue.  This means if a player joins the mission after it has started, they will immediately run "BIS_fnc_holdActionAdd" on their computer.  

 

So you can see that I am using remoteExecCall here to execute "BIS_fnc_holdActionAdd" on every player's computer, without running the entire setup script anywhere but the server.

 

 

Now, lets go back to _holdActionParamsArray.  The array itself is not important, it is just all sorts of parameters for "BIS_fnc_holdActionAdd."  However, one of the parameters is a code block that is executed locally on the machine when the hold action is completed.  So when a player holds spacebar and the action finishes, it runs this code ONLY on that player's computer.  Well, I have another remoteExecCall in there too.

 

This is the specific parameter.

   //.... a bunch of params
    {[((_this select 3) select 0),(side (_this select 1))] remoteExecCall ["JWL_fnc_captureArea",2];},
   //.... a bunch more params

"JWL_fnc_captureArea" is a function that I wrote that I only want to execute on the server.  But the code in this parameter is executed only on the local client that used the hold action.  So here I use remoteExecCall again to run a function on a server from a client, the reverse of what I did before by adding the hold action to every client from the server.

 

The parameters I am passing to "JWL_fnc_captureArea" are in front of remoteExecCall.  

 

The parameters I am passing to remoteExecCall are again behind the command.

 

Once again, the first parameter is the function I want to run on the remote machine, in this case "JWL_fnc_captureArea"

 

The second parameter is again the target machine, in this case, it is 2, which means the server.  So this will execute on the server machine whether the server is a player, a dedicated server, or even your own computer if you are hosting.

 

In this case, JIP is not important, so I left the third parameter blank and it will default to false and not add anything to the JIP queue.  

Share this post


Link to post
Share on other sites

Another example, I want to addAction to an object to hide it (aka run a script that makes it drop into the ground then deleteVehicle's it. I'm thinking it's better to put the code in the initFramework.sqf rather than in the init box of the object - would there be a difference in how remoteExec would be used there? How would I code it if the script is called hideObject.sqf?

In SP where you don't need to bother about locality, you would just add an action like this :

_someObject addAction ["A Useless Action That Does Nothing", {}];

But addAction has local effects, so you have to make it available to everyone in MP; here's how it looks like with remoteExec :

[_someObject, ["A Useless Action That Does Nothing", {}]] remoteExec ["addAction", 0, true];

Or, to make easier to read :

_myAction = ["A Useless Action That Does Nothing", {}];
[_someObject, _myAction] remoteExec ["addAction", 0, true];

The arguments you pass (_someObject and _myAction) can be local variables/arrays; the targets (0) means that the action will be broadcasted to all clients, the JIP parameter (true) makes sure the action is available for join-in-progress players.

Hope that helps a bit. :)

 

The thing about remoteExec, is that its syntax will change depending on the syntax of the original command : sometimes you just need to pass arguments to a function, sometimes you need to pass the target object and some parameters (like addAction).

  • Thanks 1

Share this post


Link to post
Share on other sites

let's say i have a function called message_global and i want everyone to get the same message at the same time.

 

 

okay so if one client call the function and everyone will get a message saying that this player did that blah blah blah we will use remoteExec

Message_Global = {
	private ["_Message","_Player"];
	
	_Message = _this select 0;
	_Player = _this select 1;
	systemChat _Message;

	//Now let's say something back to the client who used the function
	[] remoteExec ["Message_Back",_Player];
};

Message_Back = {
	systemChat format ["%1 Well done.",name player];	
};

_Msg = format ["%1 Used this function",name player];

[_Msg,player] remoteExec ["Message_Back"]; //Now if we want to run this function only to the server we will run it like this [] remoteExec ["Message_Back",2];


//let's say we want to message everyone on blufor

[_Msg,player] remoteExec ["Message_Global",west];


//Server Message
[_Msg,player] remoteExec ["Message_Global",2];

[/* Some variables in here? */] remoteExec ["Function name",/*Who you to run this functio on, if global remove the , and leave it empty*/];

Share this post


Link to post
Share on other sites
Quote

 


params ["_player","_killer"];   
[_player,_killer] remoteExec ["fn_myFunction1",_killer];
[_player,_killer] remoteExec ["fn_myfunction2",_player];

This script is called is from "EntityKilled" MissionEventHandler at my Host server from initServer.sqf.

But I cannot understand, why <fn_myFunction1> and <fn_myFunction2> are executed on every client when player is killed?
Please Help.

 

Nevermind, I am just stupid.

Edited by GameBoss
stupidity

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×