Jump to content
Neonz27

I can not get my kill-feed script to execute properly. No errors displayed.

Recommended Posts

While I am no beginner at programming in general, I am relatively new to SQF scripting for Arma 3. After 1000 hours I decided to finally toy around with the scripting side of things and I've already completely broken my first script. I apologize for any mistakes I may have made in advance, I am really new to this and I need some help solving this problem. At the moment the script is executing, however, it is not displaying the kills in the chat as it should be. I am realizing more and more that the YouTube video I learned this from is very inaccurate and riddled with errors.

 

----------initClient.sqf----------

// Executes all client scripts remotely and globally (with restrictions to client only if set in the script itself).
[] remoteExec ["killFeedClient", 0];

----------initServer.sqf----------

// Executes all client scripts remotely and server-sided.
[] remoteExec ["killFeedServer", 2];

----------killFeedClient.sqf----------

// Creates "killFeedClient" function to be executed in "initClient.sqf."
killFeedClient = {
  {
    // Adding an event handler for the "Killed" action to every unit.
    _x addEventHandler ["Killed",
    {
      // Initializes all of the variables used in the formatting of the kill-feed statement.
      _unit = (_this select 0);
      _killedBy = (_this select 1);

      // Organizing and grouping all of the variables into a single array.
      deathInfo = [_unitName, _killedBy];

      // Creating the "killFeedUpdate" public variable event handler.
      publicVariableServer "killFeedUpdate";
    }];
  } forEach allUnits;
};

----------killFeedServer.sqf----------

// Creates "killFeedServer" function to be executed in "initServer.sqf."
killFeedServer = {
  // Only runs this script if the machine executing it is the server.
  if (isServer) then {
    // Listens for updates from the "killFeedUpdate" public variable event handler.
    "killFeedUpdate" addPublicVariableEventHandler {
      // Initalizes "_deathInfo" private array and sets it's value from the previously mentioned event handler.
      private "_deathInfo";
      _deathInfo = (_this select 1);
      // If you are confused by why we used 1 and not 0 refer to "addPublicVariableEventHandler" documentation on the wiki.

      // Extracts each variable from the "_deathInfo" private array.
      _unit = (_deathInfo select 0);
      _killedBy = (_deathInfo select 1);

      // Grabs some more data by passing the previously mentioned variables through a variety of functions.
      _unitName = name _unit;
      _killedByName = name _killedBy;
      _distance = _unit distance _killedBy;

      // Formats the kill-feed statement to be displayed in the game chat.
      _killFeedStatement = format ["%1 was killed by %2. (%3m)", _unitName, _killedByName, _distance];

      // Displays the kill-feed statement in the game chat as if it were being called in by the killer.
      _killedBy globalChat _killFeedStatement;
    };
  };
};

 

Share this post


Link to post
Share on other sites

The syntax for your remote execution is incorrect. Also you can use addMPeventHandler  (not addEventHandler )to have the "killed" handler trigger everywhere. 

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

And don't forget the allUnits (or allPlayers, vehicles..) represents the units, (players/vehicles) at the time you run the code. So, at start for example. Then there is no update to take into account the spawned units,/vehicles. In this case you need to loop the code in order to treat such new entities.

Share this post


Link to post
Share on other sites
2 hours ago, Mr H. said:

The syntax for your remote execution is incorrect.

Syntax is ok, but.. as the functions are defined in code killfeedserver = { and killFeedClient = { you cannot guarantee they have been initialised before called. All dependant on how your scripts are initialised, of which we cannot tell for the client due to the use of non A3 event script( initClient.sqf ).

 

killfeedserver.sqf has to be executed before initServer.sqf runs so that killfeedserver( the global var representing the function ) is initialised before initServer remoteExecs the function. Why remoteExec a function on the server, from the server, when you can just call it?

 

Is there some initialisation, maybe CfgFunctions, of these scripts that you have not shown?

 

 

6 hours ago, Neonz27 said:

// Organizing and grouping all of the variables into a single array.
deathInfo = [_unitName, _killedBy];
// Creating the "killFeedUpdate" public variable event handler.
publicVariableServer "killFeedUpdate";

 

You put kill info into a variable called deathInfo, yet then PV a variable called killFeedUpdate. Which from what you have shown contains nothing.

Yet...

6 hours ago, Neonz27 said:

"killFeedUpdate" addPublicVariableEventHandler {
	// Initalizes "_deathInfo" private array and sets it's value from the previously mentioned event handler.
	private "_deathInfo";
	_deathInfo = (_this select 1);

 

...then expect the info to be available from the PVed value?

Also globalChat only has Local Effect so the message is only ever going to be seen by the server( if hosted ).

 

 

As @Mr H. says you could do this via an MP Killed EH which will need to be added to every joining client.

Or maybe just a MEH of EntityKilled on the server...

Spoiler


//initServer.sqf

addMissionEventHandler[ "EntityKilled", {
	params[ "_killed", "_killer", "_instigator" ];

	if ( isPlayer _killed ) then {
		if ( isNull _instigator ) then {
			_instigator = UAVControl vehicle _killer select 0;  // UAV/UGV player operated road kill
		};
		if ( isNull _instigator ) then {
			_instigator = _killer;  // player driven vehicle road kill
		};

		// Formats the kill-feed statement to be displayed in the global chat.
		_killFeedStatement = format[ "%1 was killed by %2. (%3m)", name _killed, name _instigator, _killed distance _instigator ];

		// Shows _killFeedStatement on all clients globalChat via remoteExec
		[ _instigator, _killFeedStatement ] remoteExec[ "globalChat", 0 ];
	};
}];

 

...then there is no need to add a new event for each connecting client. Client has to know nothing about operation. Only bandwidth consideration is remoteExec on globalChat message, of which could be extracted out into a client side function. Then all you need to broadcast is _killed and _instigator and let the client side function handle formatting of the globalChat message.

  • Like 2
  • Thanks 2

Share this post


Link to post
Share on other sites
 
 
 
 
Quote
15 hours ago, Larrow said:

//initServer.sqf addMissionEventHandler[ "EntityKilled", { params[ "_killed", "_killer", "_instigator" ]; if ( isPlayer _killed ) then { if ( isNull _instigator ) then { _instigator = UAVControl vehicle _killer select 0; // UAV/UGV player operated road kill }; if ( isNull _instigator ) then { _instigator = _killer; // player driven vehicle road kill }; // Formats the kill-feed statement to be displayed in the global chat. _killFeedStatement = format[ "%1 was killed by %2. (%3m)", name _killed, name _instigator, _killed distance _instigator ]; // Shows _killFeedStatement on all clients globalChat via remoteExec [ _instigator, _killFeedStatement ] remoteExec[ "globalChat", 0 ]; }; }];

 

Doesn't this imply that it only displays kills against players? I need to use something for both editor placed and spawned AI units. It will be used for a multiplayer game, however, I do not believe there will be any JIP players. I'm just using this to track kills and spawns for the AI in a small op with friends.

Share this post


Link to post
Share on other sites

I have no clue why that was formatted so badly. 😛 Sorry.

Share this post


Link to post
Share on other sites

I got it working with the following code. Thanks for your assistance guys! Especially to Larrow!

 

----------initServer.sqf----------

// Only runs this script if the machine executing it is the server.
if (isServer) then {
  // Adding a mission event-handler for the "EntityKilled" action.
  addMissionEventHandler ["EntityKilled", {
    // Parses input argument into array of private variables.
    params ["_killed", "_killer"];

    // Initalizes more private variables so they can have their values defined afterwards.
    private ["_killedName", "_killerName", "_distance", "_killFeedStatement"];

    // Grabs some more data by passing the previously mentioned parameters through a variety of functions.
    _killedName = name _killed;
    _killerName = name _killer;
    _distance = _killed distance _killer;

    // Formats the kill-feed statement to be displayed in the game chat.
    _killFeedStatement = format ["%1 was killed by %2. (%3m)", _killedName, _killerName, _distance];

    // Displays the kill-feed statement in the game chat as if it were being called in by the killer.
    [_killer, _killFeedStatement] remoteExec ["globalChat", 0];
  }];
};

 

  • Like 1

Share this post


Link to post
Share on other sites

There is MPKilled MP EH that is added globally and executes globally, no need remote executing EntityKilled

Share this post


Link to post
Share on other sites

So, if I'm right, you have choice between:

- a MEH (usually run on each PC through init.sqf, or initPlayerLocal.sqf for players things), mission oriented, then you don't need to check for units update (i.e. your code applies on each entity kill). The inner code will run locally, but on every PC running the code;

- a MPEH (AG EG) but applied to unit(s), so that means you need to run a loop for adding this MPEH on "spawned in game" units.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Does this mean I could hypothetically loop over the working script I sent earlier to add the event handler to each new unit spawned by my other scripts? It already works for units placed in the editor as well as those placed by Zeus though.

On 9/1/2019 at 10:31 AM, pierremgi said:

So, if I'm right, you have choice between:

- a MEH (usually run on each PC through init.sqf, or initPlayerLocal.sqf for players things), mission oriented, then you don't need to check for units update (i.e. your code applies on each entity kill). The inner code will run locally, but on every PC running the code;

- a MPEH (AG EG) but applied to unit(s), so that means you need to run a loop for adding this MPEH on "spawned in game" units.

Share this post


Link to post
Share on other sites

If you want to apply an EH or MPEH on all existing units, no matter how they spawn, you can:

[] spawn {
  while {true} do {
    {

      _x setVariable ["yourVarNameHere",true];

      _x addMPEventHandler ["MPKilled", {hint "bla bla"}];
    } forEach (allUnits select {isNil {_x getVariable "yourVarNameHere"}});
  sleep 2;
};

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

So I incorporated what pierremgi said and it worked fine with vanilla Arma 3, however, when I added ACE and RHS it broke the script. It still displays the kill in the chat but it says that the unit being killed committed suicide (regardless of how they were killed) and the range displayed is set to zero meters for some reason. What the actual hell is going on, and how can I fix it?

// Only runs this script if the machine executing it is the server.
if (isServer) then {
  // Whenever an entity is spawned, run the following code regardless of how it was spawned.
  [] spawn {
    while {true} do {
      {
        // Adds a multiplayer event-handler for each unidentified unit in the game.
        _x addMPEventHandler ["MPKilled", {
          // Parses input argument into array of private variables.
          params ["_killed", "_killer"];

          // Initalizes more private variables so they can have their values defined afterwards.
          private ["_killedName", "_killerName", "_distance", "_killFeedStatement"];

          // Grabs some more data by passing the previously mentioned parameters through a variety of functions.
          _killedName = name _killed;
          _killerName = name _killer;
          _distance = _killed distance _killer;

          // Formats the kill-feed statement to be displayed in the game chat.
          _killFeedStatement = format ["%1 was killed by %2. (%3m)", _killedName, _killerName, _distance];

          // Displays the kill-feed statement in the game chat as if it were being called in by the killer.
          [_killer, _killFeedStatement] remoteExec ["globalChat", 0];
        }];

        // Sets the "unitIdentified" variable to true, letting the loop know this unit has already been modified.
        _x setVariable ["unitIdentified", true];
      } forEach (allUnits select {isNil {_x getVariable "unitIdentified"}});

      // Sleeping 2 seconds so we don't overload the game with too many loop cycles.
      sleep 2;
    };
  };
};

 

Share this post


Link to post
Share on other sites

ACE 3 rewrites the handlers, you have to use  CBA events instead of arma event handlers.

Scratch that
_unit getVariable ["ace_medical_lastDamageSource", objNull]; // will return the killer, if killer is victim it means the victim bled out.
See here: https://github.com/acemod/ACE3/issues/3790

Share this post


Link to post
Share on other sites

Also if you're using cba you can get rid of that ugly while loop and use

["CAManBase", "killed", {
    params ["_unit", ["_killer", objNull]];
    
}] call CBA_fnc_addClassEventHandler;

in your init.sqf
The event will only fire where the unit is local but you can remote exec the message.
something like:
 

["CAManBase", "killed", {
    params ["_unit", ["_killer", objNull]];
    [[_unit,_killer],{params ["_unit","_killer"]; systemChat format ["%1 was killed by %2",name _unit,name _killer];}] remoteExec ["Call",0];
}] call CBA_fnc_addClassEventHandler;

Or more elaborate and using what I said above:
 

["CAManBase", "killed", {
    params ["_unit", ["_killer", objNull]];
	//if !(isPlayer _unit) exitWith {}; uncomment if you only want this to fire for players
    _killer = _unit getVariable ["ace_medical_lastDamageSource", objNull];

    [[_unit,_killer],{
        params ["_unit","_killer"]; 
        
        _message = format ["%1 was killed by %2 with %3",name _unit,name _killer, (getText (configfile>>"cfgWeapons">>(currentWeapon _killer)>>"displayName"))];
        if (_killer isEqualTo _unit) then {_message = format ["%1 bled out and died, poor bastard",_unit]};
		systemChat _message;
        }] remoteExec ["Call",0];
}] call CBA_fnc_addClassEventHandler;

 

  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks, again, to Mr.H for the assistance and wonderful explanation to go along with it. I now have two completely working versions. One of which works for vanilla Arma 3 and the other of which works with the CBA/ACE mods.

 

----------CBA Version----------

// Only runs the following code if the machines executing it is the server.
if (!isServer) exitWith {};
if (isServer) then {
  // Add a "killed" CBA event-handler to all units in the game.
  ["CAManBase", "killed", {
    // Telling the previously mentioned event-handler which parameters we need access to.
    params ["_unit", ["_killer", objNull]];

    // Only use the following line of code if you are doing strictly PVP, this will remove PVE capabilities.
    // To enable, simply remove the /'s signifying a comment.
    // if !(isPlayer _unit) exitWith {};

    // This sets the "_killer" variable to the last unit that damaged the target unit.
    // If the killer is the target itself, then the target died due to bleeding out.
    _killer = _unit getVariable ["ace_medical_lastDamageSource", objNull];

    // Remotely executing a system chat message that will display our kill-feed statement.
    [[_unit, _killer], {
      // Grabbing our previous variables to format the kill-feed statement.
      params ["_unit", "_killer"];

      // Initializing the "killFeedStatement" variable.
      _killFeedStatement = "";

      // Checking whether the target unit was killed by another unit or bled out.
      if (_killer isEqualTo _unit) then {
        // Initializes a variable to contain the unit's name.
        _unitName = name _unit;

        // Formatting the kill-feed statement that displays when a unit has bled out.
        _killFeedStatement = format ["%1 has died of blood loss.", _unitName];

        // Displays the kill-feed statement as a system chat log.
        systemChat _killFeedStatement;
      } else {
        // Initializing variables to be used in the formatting of the kill-feed statement.
        _unitName = name _unit;
        _killerName = name _killer;
        _distance = _unit distance _killer;

        // Formatting the regular kill-feed statement.
        _killFeedStatement = format ["%1 was killed by %2. [%3m]", _unitName, _killerName, _distance];

        // Displays the kill-feed statement as a system chat log.
        systemChat _killFeedStatement;
      };
    }] remoteExec ["Call", 0];
  }] call CBA_fnc_addClassEventHandler;
};

----------Vanilla Version----------

// Only runs this script if the machine executing it is the server.
if (!isServer) exitWith {};
if (isServer) then {
  // Whenever an entity is spawned, run the following code regardless of how it was spawned.
  [] spawn {
    while {true} do {
      {
        // Adds a multiplayer event-handler for each unidentified unit in the game.
        _x addMPEventHandler ["MPKilled", {
          // Parses input argument into array of private variables.
          params ["_killed", "_killer"];

          // Initalizes more private variables so they can have their values defined afterwards.
          private ["_killedName", "_killerName", "_distance", "_killFeedStatement"];

          // Grabs some more data by passing the previously mentioned parameters through a variety of functions.
          _killedName = name _killed;
          _killerName = name _killer;
          _distance = _killed distance _killer;

          // Formats the kill-feed statement differently depending on whether another unit killed the target unit or the target unit bled out.
          if (_killed isEqualTo _killer) then {
            // Formats the kill-feed statement to be displayed in the game chat when the target unit has bled out.
            _killFeedStatement = format ["%1 has died of blood loss.", _killedName];
          } else {
            // Formats the regular kill-feed statement to be displayed in the game chat.
            _killFeedStatement = format ["%1 was killed by %2. [%3m]", _killedName, _killerName, _distance];
          };

          // Displays the kill-feed statement in the game chat as if it were being called in by the killer.
          [_killer, _killFeedStatement] remoteExec ["globalChat", 0];
        }];

        // Sets the "unitIdentified" variable to true, letting the loop know this unit has already been modified.
        _x setVariable ["unitIdentified", true];
      } forEach (allUnits select {isNil {_x getVariable "unitIdentified"}});

      // Sleeping 2 seconds so we don't overload the game with too many loop cycles.
      sleep 2;
    };
  };
};

 

Share this post


Link to post
Share on other sites

On another note, does anyone know if there is a way to close this forum thread?

Share this post


Link to post
Share on other sites
41 minutes ago, Neonz27 said:

On another note, does anyone know if there is a way to close this forum thread?

Yep. Just ask a moderator. Easiest way is to just report your own post and explain what you need.

Share this post


Link to post
Share on other sites

@Neonz27 you're not there yet: the cba killed event will only fire where unit is local. In the version you have posted the event is only set on the server. That will work with editor placed AIs but not for players or units in groups of players or vehicles driven by players. I suggest you run it from init on all machines. Just to be on the safe side you can add if !(local  _killer) exitWith {}; see https://github.com/CBATeam/CBA_A3/wiki/Adding-Event-Handlers-to-Classes-of-Objects

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

×