Jump to content
LSValmont

[SOLVED] Seamless replacement of AGENTS for normal UNITS via script.

Recommended Posts

Hello Dear Community!

 

So I am using agents for my Civilian needs on a coop mission but the mission also requires that some of those civilian agents rebel and attack players. Since agents are very basic and cannot attack I must find a way to smoothly replace said agent for a REGULAR UNIT when the WITNESS´s rebel state triggers.

 

Here is how I do stuff so far:

Spoiler

 


// run on the server from serverInit.sqf

Functions:

// Spawn a civilian "Witness" at a pre defined marker
spawnWitness = {
params ["_marker"];

_markerDir = markerDir _marker;
_w = createAgent [_wModel, getMarkerPos _marker, [], 0, "NONE"];
_w setVariable ["BIS_fnc_animalBehaviour_disable", true];
witnessArray pushBack _w;
_w setVariable ["hasInfo", 1, true];
doStop _w;
(group _w) setFormDir _markerDir;
_w setFormDir _markerDirection;
_w setDir _markerDirection;
_w setBehaviour "CARELESS";
group _w deleteGroupWhenEmpty true;
_w enableDynamicSimulation true;
_w enableSimulationGlobal false;
_w setSkill 0;
_w disableAI "ALL";
	_wPos = getPos _w;
	waitUntil {
	sleep (2 + random 2);
	private _targPlayers = (allPlayers - entities "HeadlessClient_F") select {(alive _x) && ((_x distanceSqr _wPos) < 30^2)};
	((count _targPlayers > 0) || !(alive _w))
	};
	if !(alive _w) exitWith {};
	
	if (alive _w && wSpawnCanPatrol && random 100<wSpawnPatChance) then {
		_w enableSimulationGlobal true;
		_w enableAI "ALL";
		_w setSpeedMode "LIMITED";
		_w forceWalk true;
		[group _w, getPos _w, 10] call bis_fnc_taskPatrol;
	};
    
	if (alive _w && wSpawnCanRebel && _w in wRebelArray && Support < wSpawnSupport) then {
		[_w] call witnessRebels;
		if (wSpawnVest && random 100<wSpawnVestChance) then {
			[_w] spawn witnessVest;
		};
	};
};


witnessRebels = {
// This function should contain code to replace the agent for an exact replica but as a Unit so it can fire its weapon at the players!
params ["_witness"];

	_witness enableSimulationGlobal true;
	_witness enableDynamicSimulation true;
	_witness enableAi "ALL";
	_witness setSkill 0.2;
	
	_witness setBehaviour "AWARE";
	_witness setCombatMode "RED";
	
	_wJoinEast = creategroup east; 	
	_witness joinSilent _wJoinEast; 
	[_wJoinEast, (getPos _witness)] call BIS_fnc_taskAttack;
	group _witness deleteGroupWhenEmpty true;
 
	_sideArmsPool =
	[
	"hgun_Pistol_heavy_01_F",
	"hgun_ACPC2_F",
	"hgun_P07_F",
	"hgun_Pistol_01_F",
	"hgun_Rook40_F"
	];
	
	_rndSideArm = selectRandom _sideArmsPool;
	_rndSideArm = [_witness, _rndSideArm, 4] call BIS_fnc_addWeapon;
	_witness addItemToUniform "FirstAidKit";
	_witness addRating -3000;
};

 

 

 

 

PS: As you can probably tell by the script I am going for a "as optimized" as posible script since there will be lots of Witnesses and Enemy Units on the map as well as objects so I am tight on frames.

  • Like 2

Share this post


Link to post
Share on other sites
40 minutes ago, LSValmont said:

"as optimized" as posible script

 

k then. You are missing "private" keywords on your local variables.

40 minutes ago, LSValmont said:

(allPlayers - entities "HeadlessClient_F") select {(alive _x) && ((_x distanceSqr _wPos) < 30^2)};

the distance check can be done with inAreaArray. also the 30^2 is evaluated everytime, for every element in the array. I'd recommend you just calculate it manually and only write down the end result.

your 30^2 is WAY more expensive, than what you save by using distanceSqr instead of distance.

 

40 minutes ago, LSValmont said:

(count _targPlayers > 0)

Why do you grab all of them if you only want to check whether there is one? use findIf and have it abort at the first one thats found.

 

40 minutes ago, LSValmont said:

if !(alive _w) exitWith {};
if (alive _w && wSpawnCanPatrol && random 100<wSpawnPatChance) then {

The second alive check makes no sense here.

 

40 minutes ago, LSValmont said:

&& Support < wSpawnSupport

all global variables should have proper tags. "Support" seems to have none, and is also a very generic name.

 

40 minutes ago, LSValmont said:

// This function should contain code to replace the agent for an exact replica but as a Unit so it can fire its weapon at the players!

createUnit real AI unit (offscreen, maybe outside of map) copy loadout using get/setUnitLoadout, or spawn a unit with correct loadout right away.

then delete the agent, and at same time setPos the new unit onto agents position.

Then also do setDir to the dir previously retrieved from agent.

 

40 minutes ago, LSValmont said:

_wJoinEast = creategroup east;

creating seperate groups for every unit might get you into the max group limit quick. Though if you move multiple into one group, they'll also act like a AI group.

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, Dedmen said:

k then.

 

Always on point @Dedmen! Thank you!

 

I believe I adressed all your points correctly with this updated script:

Spoiler

 


// Spawn a civilian "Witness" at a pre defined marker
spawnWitness = {
params ["_marker"];

private _markerDir = markerDir _marker;
private _w = createAgent [_wModel, getMarkerPos _marker, [], 0, "NONE"];
_w setVariable ["BIS_fnc_animalBehaviour_disable", true];
witnessArray pushBack _w;
_w setVariable ["hasInfo", 1, true];
doStop _w;
(group _w) setFormDir _markerDir;
_w setFormDir _markerDirection;
_w setDir _markerDirection;
_w setBehaviour "CARELESS";
group _w deleteGroupWhenEmpty true;
_w enableDynamicSimulation true;
_w enableSimulationGlobal false;
_w setSkill 0;
_w disableAI "ALL";

	private  _wPos = getPos _w;
	waitUntil {
	sleep (2 + random 2);
	private _playerIsNear = ((allPlayers - entities "HeadlessClient_F") findIf { (alive _x) && (_x  distanceSqr _wPos < 30^2) }) != -1;
	((_playerIsNear) || !(alive _w))
	};
	if !(alive _w) exitWith {};
	
	if (wSpawnCanPatrol && random 100<wSpawnPatChance) then {
		_w enableSimulationGlobal true;
		_w enableAI "ALL";
		_w setSpeedMode "LIMITED";
		_w forceWalk true;
		[group _w, getPos _w, 10] call bis_fnc_taskPatrol;
	};
	if (alive _w && wSpawnCanRebel && _w in wRebelArray && xcomSupport < wSpawnSupport) then {
		[_w,_wModel] call witnessRebels;
		if (wSpawnVest && random 100<wSpawnVestChance) then {
			[_w] spawn witnessVest;
		};
	};
};

witnessRebels = {
params ["_witness","_wModel"];

	private _witnessPos = getPos _witness;
	private _witnessDir = getDir _witnessDir;
	private _wJoinEast = creategroup east;
	deleteVehicle _witness;
	_newWitness = _wJoinEast createUnit [_wModel,[0,0,0], , 0, "CAN_COLLIDE"];
	_newWitness setPos _witnessPos;
	_newWitness setDir _witnessDir;
	(group _newWitness) setFormDir _witnessDir;

	_newWitness enableSimulationGlobal true;
	_newWitness enableDynamicSimulation true;
	_newWitness enableAi "ALL";
	_newWitness disableAi "FSM";
	_newWitness disableAi "COVER";
	_newWitness disableAi "MINEDETECTION";
	_newWitness disableAi "NVG";
	_newWitness disableAi "LIGHTS";
	_newWitness disableAi "RADIOPROTOCOL";
	_newWitness setSkill 0.2;
	_newWitness setBehaviour "AWARE";
	_newWitness setCombatMode "RED";
	deleteGroup (group _newWitness);
	_newWitness addRating -3000;
	[(group _newWitness), (getPos _newWitness)] call BIS_fnc_taskAttack;
	group _newWitness deleteGroupWhenEmpty true;
 
	private _sideArmsPool =
	[
	"hgun_Pistol_heavy_01_F",
	"hgun_ACPC2_F",
	"hgun_P07_F",
	"hgun_Pistol_01_F",
	"hgun_Rook40_F"
	];
	
	private _rndSideArm = selectRandom _sideArmsPool;
	_rndSideArm = [_newWitness, _rndSideArm, 4] call BIS_fnc_addWeapon;
	_newWitness addItemToUniform "FirstAidKit";
};

 

 

 

 

Of course if you have anything else please do share as I am always improving my scripts 😃

  • Like 1

Share this post


Link to post
Share on other sites

If it's a given that there will always be a certain amount of units that will engage the player I'd just spawn them as regular soldier units in the first place.

 

Cheers

  • Like 3

Share this post


Link to post
Share on other sites
13 minutes ago, Grumpy Old Man said:

If it's a given that there will always be a certain amount of units that will engage the player I'd just spawn them as regular soldier units in the first place.

 

Cheers

 

Well, mission hosters could modify the % of Rebel Civilians but on my current config only around 10% out of the 40 Civilians have a chance to become rebels and engage the players and that is only if their "SUPPORT" stat is low.

 

Quick question to all:

 

How could I get the player that triggered this:

private _playerIsNear = ((allPlayers - entities "HeadlessClient_F") findIf { (alive _x) && (_x distanceSqr _wPos < 30^2) }) != -1;

Without messing up my "optimization".

Share this post


Link to post
Share on other sites

I don't see getUnitLoadout/setUnitLoadout in your script.  Using this will guarantee new unit has same, uniform, gear, weapons, mags, everything.  You also wouldn't need your code to randomly assign a pistol to new unit.

 

 

Share this post


Link to post
Share on other sites
19 minutes ago, LSValmont said:

How could I get the player that triggered this

private _allPlayers = (allPlayers - entities "HeadlessClient_F"); //Save the array, if we regenerate it below it might contain different units
private _nearPlayerIndex = _allPlayers findIf { (alive _x) && (_x distanceSqr _wPos < 30^2) });
private _playerIsNear = _nearPlayerIndex != -1
private _nearPlayer = _allPlayers select _nearPlayerIndex; //Will error on -1, so only do if actually found, or use param [_nearPlayerIndex, objNull]

 

49 minutes ago, LSValmont said:

_newWitness = _wJoinEast createUnit [_wModel,[0,0,0], , 0, "CAN_COLLIDE"];

You have a syntax error there, after coordinates you have a double comma without a value inbetween.

also.. "private" 😉

 

49 minutes ago, LSValmont said:

deleteVehicle _witness;

_newWitness = _wJoinEast createUnit

I would recommend to only delete old witness after the new witness is ready to be setPos'ed to correct location, or you'll end up with maybe half a second of no unit standing there.

 

49 minutes ago, LSValmont said:

deleteGroup (group _newWitness);

You are deleting the group, while the unit is still in the group? According to wiki that does nothing if group is not empty, and empty group will be auto deleted anyway.

 

49 minutes ago, LSValmont said:

_rndSideArm = [_newWitness, _rndSideArm, 4] call BIS_fnc_addWeapon;

It does not remove weapons, so might need to make sure that unit doesn't already have a sidearm.

Also you're storing the result in a variable that you never use.

 

 

Also you didn't respect my comments on distanceSqr and inAreaArray ._.

 

Just now, johnnyboy said:

I don't see getUnitLoadout/setUnitLoadout in your script.

not needed, its spawning from the same CfgVehicles config as the agent, will end up with same loadout

 

 

 

  • Like 1

Share this post


Link to post
Share on other sites
10 hours ago, Dedmen said:

private _allPlayers = (allPlayers - entities "HeadlessClient_F"); //Save the array, if we regenerate it below it might contain different units
private _nearPlayerIndex = _allPlayers findIf { (alive _x) && (_x distanceSqr _wPos < 30^2) });
private _playerIsNear = _nearPlayerIndex != -1
private _nearPlayer = _allPlayers select _nearPlayerIndex; //Will error on -1, so only do if actually found, or use param [_nearPlayerIndex, objNull]

Wow! Incredibly useful! Implemented on all my scripts from now on!

 

In order to avoid the error on -1 should I do? Something like:

if ! (_nearPlayerIndex isEqualTo -1) then {private _nearPlayer = _allPlayers select _nearPlayerIndex;} else {private _nearPlayer = objNull;};

 

I don't get the param approach, I am terrible with params, sorry.

 

Quote

You have a syntax error there, after coordinates you have a double comma without a value inbetween.

also.. "private" 😉

Fixed and done!

Quote

I would recommend to only delete old witness after the new witness is ready to be setPos'ed to correct location, or you'll end up with maybe half a second of no unit standing there.

Implemented!

Quote

You are deleting the group, while the unit is still in the group? According to wiki that does nothing if group is not empty, and empty group will be auto deleted anyway.

That is redundant indeed, good eye!

Quote

It does not remove weapons, so might need to make sure that unit doesn't already have a sidearm.

Also you're storing the result in a variable that you never use.

Right again!

Quote

Also you didn't respect my comments on distanceSqr and inAreaArray ._.

Only because I couldn't figure how to do that, if you could give me an example I would be most grateful!

PS: Is this an example of what you are talking about:

private _nearbyPlayers = ((allPlayers - entities "HeadlessClient_F") inAreaArray [(getPos _agent),100,100,0,FALSE,-1]) select {((alive _x))};
if (!(_nearbyPlayers isEqualTo [])) then {
		private _nearestPlayer = _nearbyPlayers select 0;
};        

Also, is this inAreaArray method as good as the first one on this post using findIf?

Quote

not needed, its spawning from the same CfgVehicles config as the agent, will end up with same loadout

Exactly... good Point Johny I almost did it but then I decided to just pass the class from the first FNC to the Rebel FNC so no longer needed to copy loadout. In fact I will increase the range at which the change happens from 30^2 to 90^2 so players do not spot changes in the faces etc.

 

Share this post


Link to post
Share on other sites

I've been working on a similar project and will note that under poorer performance conditions, the unit/agent swapping will become more and more visually noticeable. Personally, I just use a generally low number of units and the rest are agents. In some cases I have it so the civilian/agent goes to their home and transitions to a unit there.

  • Like 2

Share this post


Link to post
Share on other sites
6 hours ago, phronk said:

I've been working on a similar project and will note that under poorer performance conditions, the unit/agent swapping will become more and more visually noticeable. Personally, I just use a generally low number of units and the rest are agents. In some cases I have it so the civilian/agent goes to their home and transitions to a unit there.

 

Good input Phronk! May I ask at which distance from the player do you do the swap? And you use a CALL or a SPAWN? Because using a CALL even with my 2 + 2 sleep I still get a nice swap unless the player is driving super fast towards the Civilian's location and only on that condition is the swap visible. (I am getting more than 50 FPS thou). Highs 70s most of the time. (MALDEN).

 

PS: Since bis_fnc_taskPatrol doesn't work on Agents I am using this for making them patrol a little bit and then return to their original position. It works quite good but I wonder if it could be improved:

Spoiler

 


	if (wSpawnCanPatrol && random 100<wSpawnPatChance) then {
		_w enableSimulationGlobal true;
		_w enableAI "ALL";
		_w disableAI "NVG";
		_w disableAI "MINEDETECTION";
		_w disableAI "COVER";
		_w disableAI "FSM";
		_w disableAI "RADIOPROTOCOL";
		_w setSpeedMode "LIMITED";
		_w forceWalk true;
		[_w, 50] spawn agentPatrol;
	};

agentPatrol = {
	params ["_agent","_radius"];
	private _agentMoveCount = 5;
	private _origAgentPos = getPos _agent;
	while {alive _agent} do {
		if (simulationEnabled _agent) then {
		uiSleep (60 + (random 60));
			if (random 10>2) then {
			private _nOa = nearestObjects [_agent, ["house"], _radius];
			private _rdmO = selectRandom _nOa; 
			private _aDest = getPos _rdmO;
			_agent setDestination [_aDest, "LEADER PLANNED", true];
			} else {
			private _nearestPlayers = allPlayers select { ( _x distance2d _agent < 50 ) && { alive _x } } apply {[_x distance _agent,_x]};
				if (count _nearestPlayers > 0) then {
				_nearestPlayers sort true;
				_nearestPlayers select 0 params[ "_nearestPlayerDistance", "_nearestPlayer" ];
				_agent setDestination [getPos _nearestPlayer, "LEADER PLANNED", true];
				};
			};
		_agentMoveCount = _agentMoveCount - 1;
		if (_agentMoveCount < 1) then {_agent setDestination [_origAgentPos, "LEADER PLANNED", true]; _agentMoveCount = 5;};
		};
	uiSleep (60 + (random 60));
	};
};

 

 

 

 

Also, I've just tried the mission and with 36 Civilian Agents and 4 Rebel Units active I only lost around 2 FPS...

 

The rebellious civilians engaged the players perfectly!

 

Not only that but agents have the added benefit of not using groups so the mission gains unused group slots perhaps in the hundreds!

 

I am wondering if the dynamic simulation system also works on agents?

 

Since I am checking for simulationEnabled on their patrol script and also I don't deSpawn them when players are far like I do with normal units hoping that the dynamic simul system will take care of them.

 

Will firedNear EH work on Agents?

Share this post


Link to post
Share on other sites

I've done a test with 6 players with 600 active agents moving to random building positions in randomly selected buildings around them, had 40-120 FPS. Tested on a $7 virtual server.

Share this post


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

In order to avoid the error on -1 should I do?

use param.

15 hours ago, LSValmont said:

I don't get the param approach, I am terrible with params, sorry.

well...

https://community.bistudio.com/wiki/param

param is like "select" but with error handling.

If the index you provide is invalid (negative, or past the end of the array) it will instead return the defaultValue.

 

15 hours ago, LSValmont said:

if ! (_nearPlayerIndex isEqualTo -1) then {private _nearPlayer = _allPlayers select _nearPlayerIndex;} else {private _nearPlayer = objNull;};

That code is equivalent to

private _nearPlayer = _allPlayers param [_nearPlayerIndex, objNull];

 

15 hours ago, LSValmont said:

Is this an example of what you are talking about:

Yes, that's exactly what I meant.

If you want you can again replace the select by findIf, but there aren't that many units left now so its not as bad.

 

15 hours ago, LSValmont said:

if (!(_nearbyPlayers isEqualTo [])) then { private _nearestPlayer = _nearbyPlayers select 0; };

That is not _nearESTplayer, that list is not sorted, its just the first player that happened to be in the list.

Also

private _nearPlayer = _nearByPlayers param [0, objNull]

 

The good thing about inAreaArray is that it does all the distance checking and looping over the array in engine, which is SO much faster than doing it in SQF.

But, it checks all units, unlike findIf which aborts early. But considering how much faster inAreaArray is, it will still be way better on average.

 

12 hours ago, phronk said:

that under poorer performance conditions, the unit/agent swapping will become more and more visually noticeable.

You can just force it to be fast by using unscheduled for the swapping part.

by wrapping the
 

deleteVehicle _oldWitness;
_newWitness setPos _targetPosition

in a isNil {}

 

  • Like 1
  • Thanks 1

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

×