Jump to content
Sign in to follow this  
mrflay

Stealth Hack [SP] (script)

Recommended Posts

This is my attempt to make some kind of stealth play possible in arma.

The script simply 'hides' the shooter from its target by making the shooter captive for five seconds. This appears to temporarily disrupt the AI hive-mind and forces them to use only sight/hearing to detect enemies.

Usage:

1. Copy-paste the following script (see spoiler) into a empty text file in the mission directory. Name the file "flay_stealth.sqf".

2. In your mission, add a Game Logic and put "0=[] execVM 'flay_stealth.sqf' in the init field.

3. preview mission.

Note:

If it doesn't appear to work. Try using a trigger instead of the game logic. Make the trigger activate when mission is started (e.g. by detecting the player).

Script:

/* flay_stealth.sqf
*
* This script is a temporary workaround for the issue(s) that currently make stealth play
* impossible in arma.
*
* The script adds a HandleDamage event handler to every unit in the mission. The event
* handler attempts to 'hide' the shooter temporarily from the enemy AI using setCaptive.
*
* Usage: 
* 
*     0=[] execVM "flay_stealth.sqf"
*
* author: mrflay
* version: 1.0a
* license: public domain
*/

FLAY_stealth_IsStealthKillCallback = {

private ["_target", "_selection", "_damage","_unit","_projectile","_stealthKilled"];

_target = _this select 0;
_selection = _this select 1;
_damage = _this select 2;
_unit = _this select 3;
_projectile = _this select 4;

// handle function being called multiple times per shot (ie. once for every selection)
_stealthKilled = _target getVariable ["flay.stealth.killed", false];
if (_stealthKilled) exitWith { true };

// determine weather the shot was stealthy and fatal
if (_damage < 1) exitWith { false };

// TODO: add weapon / ammo check

_target setVariable ["flay.stealth.killed", true];	
};

FLAY_stealth_InLineOfSight = {

// quite messy because i'm not sure how to do a proper line of sight test in sqf.

private ["_unit", "_target", "_range", "_fov", "_inView", "_inSight", "_inRange", "_detected"];

_unit = [_this,0] call BIS_fnc_param; 
_target = [_this,1] call BIS_fnc_param; 
_range = [_this,2,100,[0]] call BIS_fnc_param; 
_fov = [_this,3,130,[0]] call BIS_fnc_param; 
_debug = [_this,4,false,[false]] call BIS_fnc_param;

_viewDirX = (eyeDirection _unit) select 0;
_viewDirY = (eyeDirection _unit) select 1;
_viewDir = _viewDirX atan2 _viewDirY;

_detected = false;
_inRange = (_unit distance _target) < _range;
if (_inRange) then {
	_inView = [position _unit, _viewDir, _fov, position _target] call BIS_fnc_inAngleSector; 
	_occluded = terrainIntersect [ASLtoATL (eyePos _unit), ASLtoATL (eyePos _target)];
	_objects = lineIntersectsWith [eyePos _unit, eyePos _target, _unit, _target];
	_blocked = count _objects;

	// the weapon blocks the line of sight, so ignore it...
	{ if (_x isKindOf "WeaponHolder") then {_blocked = _blocked - 1};
	} forEach _objects;

	_inSight = _blocked == 0;
	_detected = _inView && _inSight && not _occluded;
	if (_debug) then {
		player globalchat format ["unit:%4 target:%5 view:%1 sight:%2 occ:%3", _inView, _inSight, _occluded, _unit, _target];
	};
};
_detected;
};

FLAY_stealth_OnDeadBodyDetected = {
private ["_deadbody","_unit","_killer","_groupx","_mode","_behaviour","_waypoint"];

   _deadbody = _this select 0;
   _unit = _this select 1; // unit who detected the dead body
   _killer = _this select 2; // unit who killed the dead body

   player globalchat format ["body %1 detected by %2 killer is %3", _deadbody, _unit, _killer];

   _groupx = group _unit;
   _groupx reveal [_killer, 1];
_unit reveal [_killer, 1];
   _mode = combatMode _unit;
   _behaviour = behaviour _unit;

   //if (_mode in ["RED"]) exitWith {};
  	if (_behaviour in ["COMBAT","STEALTH"]) exitWith {};

   _waypoint = _groupx addWaypoint [position _deadbody, 0];
   _waypoint setWaypointType "SAD";
   _waypoint setWaypointBehaviour "COMBAT";
   _waypoint setWaypointCombatMode "RED";
   _groupx setCurrentWaypoint _waypoint;

   _killer addRating 1000; // TODO: add proper rating    
_killer setCaptive false;
};

FLAY_stealth_DeadBodyHandler = {
private ["_x","_detected","_deadbody","_killer", "_units","_xlos","_deadbodySide","_onDeadBodyDetected"];
_detected = false;
_deadbody = _this select 0;
_killer = _this select 3;
_deadbodySide = _deadbody getVariable ["flay.stealth.side", civilian]; // dead bodies become civs after a while
_onDeadBodyDetected = missionNamespace getVariable ["flay.stealth.onDeadBodyDetected", FLAY_stealth_OnDeadBodyDetected];
_posASL = getPosASL _deadbody;
// TODO: likelihood of detection should depend of weather, timeofday, and how long the body has been dead.
while { not _detected } do {
	_units = (position _deadbody) nearEntities ["Man", 50]; 
	{
   		if (alive _x && (side _x == _deadbodySide)) then {
			// TODO: base fov on distance.
   			_xlos = [_x, _deadbody, 50, 130] call FLAY_stealth_InLineOfSight;
   			if (_xlos) then {
   				[_deadbody, _x, _killer] call _onDeadBodyDetected;
   				_detected = true;
   			};
   		};
   	} forEach _units;
   	sleep 1;		
};
};

FLAY_stealth_Detected = {
private ["_detected", "_unit"];
_unit = _this select 0;
_detected = 0;
{
	if (side _x != side _unit and side _x != civilian) then {
		_detected = _detected max (_x knowsAbout _unit);
	};
} forEach allUnits;
_detected;
};

FLAY_stealth_DamageEventHandler = {

private ["_target", "_damage","_unit","_projectile", "_isStealthKillCallback","_isStealthKill","_states","_anim"];

// assume it was a stealth kill, revert later if it turns out it wasn't.
(_this select 3) setCaptive true;

_target = _this select 0;
_damage = _this select 2;
_unit = _this select 3;

_newRating = -1000;
_oldRating = rating _unit;
if (_oldRating < 0) then {
	_newRating = 0;
};

_unit addRating _newRating;

// if it wasn't a stealth kill, then revert to default damage
_isStealthKillCallback = missionNamespace getVariable ["flay.stealth.isStealthKillCallback", FLAY_stealth_IsStealthKillCallback];
_isStealthKill = _this call _isStealthKillCallback;
if (not _isStealthKill) exitWith {
	//_unit addRating (_oldRating - _newRating);
	//_unit setCaptive false;
	if (not isNull _unit) then {
		FLAY_stealth_kills set [count FLAY_stealth_kills, _this];
	};
	_damage;
};

// FIXME: this part is run more than needed (ie. once for every selection with damage > 1)

 // dead bodies becomes civilian, remember target's side for handling detection later
_target setVariable ["flay.stealth.side", side _target];

// how can _unit even be null?!!!
if (not isNull _unit) then {
	FLAY_stealth_kills set [count FLAY_stealth_kills, _this];
};
1; // fatal damage
};


[] spawn {

private ["_event","_unit","_handler"];

waitUntil { BIS_fnc_init };

// add custom damage event handler to all units
{ _handler = _x addEventHandler ["HandleDamage", "_this call FLAY_stealth_DamageEventHandler"];
  _x setVariable ["flay.stealth.handler", _handler];
} forEach allUnits;

FLAY_stealth_kills = [];

while { true } do {
	waitUntil { count FLAY_stealth_kills > 0 };
	_event = [FLAY_stealth_kills] call bis_fnc_arrayshift;
	_target = _event select 0;
	_unit = _event select 3;
	if (not alive _target) then {
		_event spawn FLAY_stealth_DeadBodyHandler;
	} else {
		_target reveal [_unit, 1];
	};
	sleep 5; // arbitrary delay to prevent detection when setting captive to false
	_unit setCaptive false;
};
};

Also, please vote for these issues :)

http://feedback.arma3.com/view.php?id=10143

http://feedback.arma3.com/view.php?id=13493

http://feedback.arma3.com/view.php?id=13494

Thanks!

Edited by mrflay
removed workaround for units stuck in cutscene animation

Share this post


Link to post
Share on other sites

Nice one, i tried it with two units, where one was cycling a builing and the other just standing and facing one directon. With your script on, i could kill the satnding dude, without the other going into combat mode and engaging me, while he certainly did without your mod. It also seems to work with UPSMON.

In one of the test however, the soldier on patrol was going crazy after finding the body of his buddy and walked away from the crimescene, never to return again. I waited some time, but couldn't even find him with the splendid cam :). Think i scared him away...

Anyway, any thoughts on this issue? Otherwise, good job, gonna try build a stealth mission with this.

Share this post


Link to post
Share on other sites
why would you not want the other to be alerted when they here a gunshot

That is not the issue. The script does not change the AI's ability to see or hear gunshots. It prevents the AI from immediately becoming aware when a unit it knows about is killed.

In one of the test however, the soldier on patrol was going crazy after finding the body of his buddy and walked away from the crimescene, never to return again. I waited some time, but couldn't even find him with the splendid cam :). Think i scared him away...

Anyway, any thoughts on this issue? Otherwise, good job, gonna try build a stealth mission with this.

When a body is detected, the script triggers the unit to do a Search & Destroy from the location of the body. Currently, this search is never terminated. I'll see if I can improve it somehow.

Btw, you may find the following script helpful when building your mission. It visualizes AI awareness.

/* knowsabout.sqf
* 
* This script visualizes what the AI knowsabout other units. Add a gamelogic and put the following in the init field.
*
* Usage: [(<unit>),(<reverse>)] execVM "knowsabout.sqf"
*
*      <unit>: (optional) When player is not pointing at any unit, show what all other AI units know about this unit (default is player).
*   <reverse>: (optional) If true show what selected unit knowsabout all other units, otherwise display what all other units know about selected unit.
*/

#define DECIMAL(x,n) (str(round((x)*(10^(n)))/(10^(n))))

// display what all other unit knowsabout this unit, when player is not pointing at another unit.
FLAY_debug_KnowsAboutUnit = [_this,0,player,[objNull]] call bis_fnc_param;

// if true display what selected unit knowsabout all other units, otherwise display what all other unit knowsabout unit.
FLAY_debug_KnowsAbout = [_this,1,false,[true]] call bis_fnc_param; 

// register key handler, when display is ready (shift key toggles FLAY_debug_KnowsAbout on/off).
[] spawn { 
waitUntil { !isNull findDisplay 46 };
(findDisplay 46) displayAddEventHandler ["KeyUp", "if ((_this select 1) in [0x2A,0x36]) then { FLAY_debug_KnowsAbout = !FLAY_debug_KnowsAbout; };"];
};

onEachFrame {

private ["_unit", "_x", "_rgba"];

_unit = FLAY_debug_KnowsAboutUnit;

if (cursorTarget isKindOf "Man") then {
	{
		// display what the unit the player is pointing at knowsabout all other units.
		if (FLAY_debug_KnowsAbout) then {
			_rgba = [0,0,1,1];
			if ((cursorTarget knowsAbout _x) > 0) then
			{
				drawLine3D [ASLtoATL(eyePos _x), ASLtoATL (eyePos cursorTarget), _rgba];      
			};				
			if (_x != cursorTarget && (cursorTarget knowsAbout _x) > 0) then
			{ 
				drawIcon3D ["\a3\ui_f\data\gui\cfg\Hints\icon_text\group_1_ca.paa", _rgba, ASLtoATL (eyepos _x), 1, 1, 45, DECIMAL(cursorTarget knowsAbout _x, 1), 0, 0.025, "puristaLight"];
			};				
		} else {
			// display what all other AI knowsabout the unit the player is pointing at.
			_rgba = [0,1,0,1];
			if (side _x != side cursorTarget) then 
			{
				_rgba = [1,0,0,1];
			};
			if ((_x knowsAbout cursorTarget) > 0) then
			{
				drawLine3D [ASLtoATL(eyePos _x), ASLtoATL (eyePos cursorTarget), _rgba];      
			};				
			if (_x != cursorTarget && (_x knowsAbout cursorTarget) > 0) then
			{ 
				drawIcon3D ["\a3\ui_f\data\gui\cfg\Hints\icon_text\group_1_ca.paa", _rgba, ASLtoATL (eyepos _x), 1, 1, 45, DECIMAL(_x knowsAbout cursorTarget, 1), 0, 0.025, "puristaLight"];
			};				
		};
	} forEach allUnits;  	
} else {
	// if player is not pointing at a unit, display what all AI units knowsabout the specified unit (by default player)
	{
		if (_x != _unit && (_x knowsAbout _unit) > 0) then
		{
			_rgba = [0,1,0,1];
			if (side _x != side _unit) then 
			{
				_rgba = [1,0,0,1];
			};				
			drawIcon3D ["\a3\ui_f\data\gui\cfg\Hints\icon_text\group_1_ca.paa", _rgba, ASLtoATL (eyepos _x), 1, 1, 45, DECIMAL(_x knowsAbout _unit, 1), 0, 0.025, "puristaLight"];
		};
	} forEach allUnits;
};
};

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
Sign in to follow this  

×