So, I know diving into the scripting world isn't an easy thing to do. It might seem a bit overwhelming at first, and when you think you grasped it, you let yourself go at scripting in a multiplayer environment and drown into locality issues. Even if you are a more experienced scripter, you might have to reinvent the wheel everytime you have a go at it. So here is a hopefully helpful script for all the mission makers around, and the ones that would like to try it. The first part is more of a tuto/explanation about how locality actually works. More experienced scripters might want to skip directly to the second part, that contains the scripts and the comments that come with.
First Part:
Let's try and do this with an example: you want to create an adversarial mission, in which you want to give players the opportunity to disarm and/or handcuff one another.
So, after searching the whole internet and your pockets, you find those things:
Code: Select all
{_unit action ["dropWeapon",_unit, _x] } forEach (weapons _unit);
{_unit action ["DropMagazine", _unit, _x] } forEach (magazines _unit);
Now, you want to make sure to know how those commands work. So you go to the wiki and go check the page for the command action.
I'll go over the details, as the syntax we have above is already functional. What is of interest for us today, are those two symbols on the top-left corner of the page.
The red one, that says EG, means Effects: Global. This means that if that command is executed, say, to make a unit drop its weapons, everybody on the server will see it drop its weapons. That's good. We'll only have to execute that command once, and everybody will see the effects.
The blue one, that says AL, means Arguments: Local. This means that the arguments to the command (here the unit) must be local to the computer the action will be performed on. In short, if you execute that on your computer while the unit is actually another player, nothing will happen.
Not good.
Why? Because, to disarm somebody, say player2, we will use the action menu, via the addAction command. Now, if you look at those two symbols here, it says that both arguments and effects are local to the computer that executes that command. Now, addAction is atricky one, yet is one that we use a lot when adding possibilities to the game.
The action must have been created on your computer for you to see it, and when you use it, it will launch a script on your computer only. Starting to see the problem?
When you use that action in game, the resulting script will execute on your computer only, but we need the resulting actions to be performed by your soon-to-be-disarmed comrade's computer.
Let's create the action on the player2 first:
Code: Select all
_id = player2 addAction addAction ["Search/Disarm", "disarm.sqf", [], 6, false, true, "", "lifeState _target == ""UNCONSCIOUS"""];
Once again, if we just paste the initial code in that script, nothing will happen. So we need to find a way to inform player2's computer that he has to run that code itself.
This is where it usually gets tricky. Using CBA Extended Eventhandlers would be the way to go, but we can't do that here, because of the no-addon policy. What would happen if player2 was not running CBA? Nothing. You could also use BIS's built-in Multiplayer Framework. That is, if you can figure out how that works. And even if you do, that's probably the worst piece of code in the whole Armaverse*.
That's where the following script comes in handy: it allows you to easily run code from one computer to all the others, or to one specific computer.
To do that, we are going to create an event, on all machines, at the beginning of the mission. The place of choice to do that would be the init.sqf, as this script is run by all machines when the mission initializes.
But first, we are going to initialize the script:
Code: Select all
nul = call compile preprocessfile "BM_XEH.sqf";
Code: Select all
["Disarmed", {
_unit = _this;
{_unit action ["dropWeapon",_unit, _x] } forEach (weapons _unit);
{_unit action ["DropMagazine", _unit, _x] } forEach (magazines _unit);
}
] call BM_addEventHandler;
All we need to do now is to execute that event on the right computer.
So, back to our disarm.sqf, executed by you when you use the action, and let's paste this inside:
Code: Select all
_target = _this select 0; //the unit the action is attached to, here player2's unit.
[_target, "Disarmed", _target] call BM_localRemoteEvent;
"Disarmed" indicates which event you want to run.
The second _target is the argument that will be passed to the event's code (here it also contains the unit as this is the only argument needed).
Now, let's say whe have a time bomb situation, you have an action on the bomb object to defuse it, but want everybody to know about it when it's done? Same here.
You create an event, say:
Code: Select all
["Defused", {
_defuser = _this;
hint format ["The bomb was defused by %1", _defuser];
}
] call BM_addEventHandler;
Code: Select all
_bomb = _this select 0;
_defuser = _this select 1;
// whatever code you need to defuse the bomb
// hint to everybody:
["Defused", _defuser] call BM_globalEvent;
Second Part: The actual script
Code: Select all
// BM - Extended EventHandlers
// Credits: Black Mamba [FA], inspired by CBA and based on Muzzleflash's work. Thanks a lot.
// ====================================================================================
// INIT
if (!isnil "BM_EventsLogic") exitWith {};
BM_EventsLogic = "Logic" createVehicleLocal [0,0,0];
// ====================================================================================
// Adds an eventHandler to the local machine(must usually be run globally, i.e in the init.sqf for example, at least it needs to be on the computers you want to raise the event on)
// _id = ["My_Event", {code to be executed when the event is raised}] call BM_addEventHandler;
// returns an index associated with the eventHandler (can be used to remove that eventHandler later)
BM_addEventHandler = {
_event = _this select 0;
_code = _this select 1;
_eventHandlers = BM_EventsLogic getVariable _event;
if (isnil "_eventHandlers") then {
_eventHandlers = [];
BM_EventsLogic setVariable [_event, _eventHandlers];
};
_id = count _eventHandlers;
_eventHandlers set [_id, _code];
_id
};
// ====================================================================================
// Removes an eventHandler with the given index on the local machine
// bool = ["My_Event", index] call BM_removeEventHandler;
// Returns true if the event was removed, false if it was not dound.
BM_removeEventHandler = {
_event = _this select 0;
_id = _this select 1;
_eventHandlers = BM_EventsLogic getVariable [_event, []];
_wasRemoved = _id >= 0 && _id < count _eventHandlers;
if (_wasRemoved) then {
if (!isNil {_eventHandlers select _id}) then {
_eventHandlers set [_id, nil];
} else {
_wasRemoved = false;
};
};
_wasRemoved
};
// ====================================================================================
// Executes an Event's code on the local machine
// ["My_Event", [arguments]] call BM_localEvent;
BM_localEvent = {
_event = _this select 0;
_args = if (count _this > 1) then {_this select 1} else {[]};
_eventHandlers = BM_EventsLogic getVariable [_event, []];
{
if (!isNil "_x") then {
_args call _x;
};
} forEach _eventHandlers;
};
// ====================================================================================
// Executes an event on all remote machines (except the local one)
// ["My_Event", [arguments]] call BM_allRemoteEvent;
BM_allRemoteEvent = {
BM_RE = _this;
publicVariable "BM_RE";
};
// ====================================================================================
// Executes the event only on the machine where object is local
// [object, "My_Event", [arguments]] call BM_localRemoteEvent;
BM_localRemoteEvent = {
_object = _this select 0;
if (local _object) then {
_event = _this select 1;
_args = _this select 2;
[_event, _args] call BM_localEvent;
} else {
BM_lRE = _this;
publicVariableServer "BM_lRE";
};
};
// ====================================================================================
// Executes the event on all machines (including the local one)
// ["My_Event", [arguments]] call BM_globalEvent;
BM_globalEvent = {
_this call BM_localEvent;
_this call BM_allRemoteEvent;
};
// ====================================================================================
// Sets up the system
"BM_RE" addPublicVariableEventHandler {(_this select 1) call BM_localEvent};
if (isServer) then {
"BM_lRE" addPublicVariableEventHandler {
_holder = _this select 1;
_object = _holder select 0;
_event = _holder select 1;
_args = if (count _holder > 2) then {_holder select 2} else {[]};
if (local _object) then {
[_event, _args] call BM_localEvent;
} else {
_owner = owner _object;
BM_RE = [_event, _args];
_owner publicVariableClient "BM_RE";
};
};
};
I'll expand on that post if I find sme time and ideas to do it. Feel free to ask any questions regarding how this works here, or by pm.