Jump to content
Raining6

Towards the "AI can't see through the grass" holy grail

Recommended Posts

How it is right now: AI is able to spot the player through grass and, once spotted, is able to maintain lock despite the player being prone and changing directions hundreds of meters away. Playing without grass is not an option (very ugly), same with short grass mods (ugly ). Being unable to disengage and use grass for cover is also boring, as is being relentlessly pinged by the omniscient AI after the first shots have been fired, and our Dynamic Camo mod/Real Engine mod cover is blown. Long grass looks great and allows for deeper gameplay - when this grass isn't ignored by the AI that is 🙂

 

I'm thinking about proceeding in two stages:

1) AI loses sight of player in prone stance far away in long grass. This part is kind of done - see below. Hacky and imperfect of course, but I much prefer it over the Vanilla, Dynamic Camo mod, Real Engine, etc. Plus it is straight forward to improve - add range circles, dynamic camo, detect buildings etc.

Vanilla at the start, hiding script at 0:54

Code:

Spoiler

 


private _uniform = uniform player; 
p_uni = toLowerANSI (STR getText (configFile >> "CfgWeapons" >> _uniform >> "displayName"));   
  
[] spawn {   
while{true} do {     
if ("Grass" in STR surfaceType position player) then {     
   
if (stance player == "PRONE") then {    
if ("ghillie" in p_uni and speed player < 2.5) then { player setCaptive 1; player setUnitTrait["camouflageCoef",0.01]} else {player setUnitTrait["camouflageCoef",0.01]}; };   
    
if (stance player == "CROUCH") then {     
if ("ghillie" in p_uni) then {player setCaptive 0; player setUnitTrait["camouflageCoef",0.1]; player setUnitTrait["audibleCoef",0.5]} else {  player setUnitTrait["camouflageCoef",0.5]}; player setUnitTrait["audibleCoef",0.6] };    
    
if (stance player == "STAND") then {   
if ("ghillie" in p_uni) then { player setCaptive 0; player setUnitTrait["camouflageCoef",0.7]} else { player setUnitTrait["camouflageCoef",0.9]}; };      
     
    
}    
    
else {   
if (stance player == "PRONE") then { player setCaptive 0;  
player setUnitTrait["camouflageCoef",0.3]};    
    
if (stance player == "CROUCH") then {     
player setUnitTrait["camouflageCoef",0.6]; player setUnitTrait["audibleCoef",0.5]};    
   
    
if (stance player == "STAND") then {     
player setUnitTrait["camouflageCoef",1.0]};     
     
};     
     
sleep 2;     
}; }

 

 

 

2) The harder part. The goal is for AI not to see even a standing upright player - when the view is obstructed by a patch of grass. My idea in pseudo code:

Spoiler

 


IF (enemy within D meters range from player):

find a line that connects enemy's and player's heads;

for every M meters on the line, find which of these points is ATL_min. //ATL means Above Terrain Level



IF ATL_min < 0.5meters AND this point position has Surface type grass AND no structures at the position:

place an 0.5 meter high invisible wall at that point to stop AI "seeing".

 

 

 

Here is a sketch 

Untitled.jpg

 

This I need help with. Any thoughts, tips, criticisms are most welcome! As are partial or full implementations 🙂

 

EDIT: A very happy ending! The following script by Pierremgi solves this decade-old problem! Its only side effect is that this may affect AI mods, e.g., the VComAI becomes more static with this script. 

How to use: pause the game, paste the code below into the debug window, press "Local Exec". The game will remember the script and you will only need to pause and press the "Local Exec" before every mission.

 

Pierremgi's script:

Spoiler

addMissionEventHandler ["EachFrame", {     
  if ( diag_frameNo mod 30 isEqualTo 0) then {
    {
      if ([objNull,"view"] checkVisibility [eyePos _x, eyepos player] <0.7) then {
        _x disableAI "FSM"; _x disableAI "checkVisible";
      } else {
        _x enableAI "FSM"; _x enableAI "checkVisible"
      };
    } forEach (allUnits select {simulationEnabled _x && {side _x getFriend playerSide <0.6}});
  };
}];

 

 

Finally, combining the hide in grass scripts with VComAI leads to AI laying more suppressive fire onto a patch of grass where you were seen last. 

 

EDIT2: The ending is happier still 🙂 I have finished the "invis wall" approach to this problem and, unlike the solution above, it has no side effects on the AI. This means that, e.g., Vcom AI mod's AI still aggressively flank and hunt the player down, and so on for all other types of AI behaviour -both vanilla and mods. The AI can no longer see through the grass, but behave exactly the same as before otherwise. 

The Code:

Spoiler

[] spawn{while {true} do
	
	{_pspeed = speed player;
	
	 if ( _pspeed >14) then {_list = position player nearObjects ["GalleryLabel_01_F", 7]; {deleteVehicle _x} forEach _list};
	 
	 if ( _pspeed >10) then {_list = position player nearObjects ["GalleryLabel_01_F", 5]; {deleteVehicle _x} forEach _list};
	 
	 if ( _pspeed >5) then {_list = position player nearObjects ["GalleryLabel_01_F", 2]; {deleteVehicle _x} forEach _list};
	 	 
	 sleep 0.2;
	}

};


handled_enemies = []; 
dtangos =[];  //downed tangos 
[] spawn 
	{ while {true} do 
		{      
       
		  _allEnemies = allUnits select {side _x getFriend playerSide <0.6}; 
		  _enemies = []; //enemies near player
		  {if ((player distance _x)  < 300) then {_enemies append [_x]} } forEach _allEnemies;    
		 //hint STR handled_enemies;   
		 //systemChat STR _enemies; 
		 dtangos = handled_enemies - _enemies; 
		 handled_enemies = handled_enemies - dtangos; //remove dead dudes from the calculation list 
   
   
    
			{  if (!(_x in handled_enemies) ) then 
				{   
					handled_enemies append [_x];   
		
				   _x spawn
				    {	         
					  if ( !alive _this) then {terminate _thisScript}; // terminate a dead dude's thread   
			 
					  
					  while{alive _this } do 
						{             
						  //build a list of sect coords sects           
						  _a = eyePos player;             
						  _b = eyePos _this;        
						  _dist = player distance _this;        
						               
						  _wH = -0.7;    //invis wall height, make less negative to increase height
						  _losH = 0.3;  //LOS  height threshold AGL, higher = easier to make walls, but less likely to hit hills apexes. Lower = reverse.
						  _n = 30;   //into how many sections to split LOS         
						  _dcf = 30;        
						  _c_set =[];              
    
						  _createWall = objNull;
						  
						  _sol_found = false; //try to find solution closer to hill's apex, if cannot- go for less ambitious sol
								
   
							   
						  _sect_fn = {            
						  params ["_a","_b","_n","_dcf"];            
						  _x_sect =  _a#0+_dcf*(_b#0-_a#0)/_n;             
						  _y_sect =  _a#1+_dcf*(_b#1-_a#1)/_n;            
						  _z_sect =  _a#2+_dcf*(_b#2-_a#2)/_n;            
						  [_x_sect, _y_sect, _z_sect];            
						  };            
							  
						  for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do
						    {              
							  _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos          
							              
							  //_sectsASL set [_i, _c_set];           
							  //_sectsAGL set [_i, ASLToAGL _c_set];           
							  _thASL = getTerrainHeightASL [_c_set#0, _c_set#1];           
							   
							   _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level
							   
							   if ( (abs _thresh) < _losH && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then //try to pick the lowest LOS above ground, e.g. closer to hill apex
							    { 
									//systemChat "Short grass!";
									_vec2e = _this getDir player;            
									_tgtAGL = _c_set;
									_tgtAGL set [2, _wH];
								   _createWall = createVehicle["GalleryLabel_01_F",  _tgtAGL, [], 0, "CAN_COLLIDE"];      
								    player disableCollisionWith _createWall;  
								   _createWall setDir _vec2e;   
								   _createWall setVectorUp surfaceNormal position _createWall;   
								     
								   _createWall setObjectScale 12;
								   
								   _createWall setObjectTexture [0,""]; //comment out to make the walls visible 								   
								   _sol_found = true;
								   break;
								};
									
						    };           
							
						if (_sol_found==false) then //if the above fails, create a wall at a higher point
						{
							for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do
							{              
							  _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos          
										  
							  //_sectsASL set [_i, _c_set];           
							  //_sectsAGL set [_i, ASLToAGL _c_set];           
							  _thASL = getTerrainHeightASL [_c_set#0, _c_set#1];           
							   
							   _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level
							   
							   if ( (abs _thresh) < (_losH+0.2) && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then
								{ 
									//systemChat "Long grass!";
									_vec2e = _this getDir player;            
									_tgtAGL = _c_set;
									_tgtAGL set [2, _wH];
								   _createWall = createVehicle["GalleryLabel_01_F",  _tgtAGL, [], 0, "CAN_COLLIDE"];      
									player disableCollisionWith _createWall; 
								   _createWall setDir _vec2e;   
								   _createWall setVectorUp surfaceNormal position _createWall;   
								     
								   _createWall setObjectScale 12;
								   
								   _createWall setObjectTexture [0,""]; //comment out to make the walls visible 								   
								   _sol_found = true;
								   break;
								};
									
							}; 
						};
							
							
								 
							sleep 1;
							if (!isNull _createWall) then { deleteVehicle _createWall; _createWall = objNull;}
							
						};
	
				    };   

				};      
		 
			} forEach _enemies;
		 sleep 2;	
		};

	}; 

 

 

EDIT 3: The mod is now also on Steam Workshop: https://steamcommunity.com/sharedfiles/filedetails/?id=2946868556 , its thread is here https://forums.bohemia.net/forums/topic/241678-acstg-ai-cannot-see-through-grass/

  • Like 2

Share this post


Link to post
Share on other sites

Slow progress with the algo, but progress nonetheless. 

There is a line connecting two heads ( the player and the AI), and I want to find coords of N sections of this line. A bit of geometry (from https://math.stackexchange.com/questions/563566/how-do-i-find-the-middle1-2-1-3-1-4-etc-of-a-line ) :

geom.jpg

 

In Python (for now)

a = [3,5]
b = [7,9]

def sect(a, b, n, d_cf):
    x_sect =  a[0]+d_cf*(b[0]-a[0])/n  # x1---x1+d---x1+2d--------x1+nd 
    y_sect =  a[1]+d_cf*(b[1]-a[1])/n  # x_sect = x1 + d_cf*(d) = x1 +d_cf*(x2-x1)/n
   #z_sect = a[2]+d_cf*(b[2]-a[2]/n)
#     print([x_sect, y_sect])
    return [x_sect, y_sect]

def sect_iter(a,b,n, d_cf):
    sects=[]
    for d_cf in range(n-1):
        sects.append(sect(a,b,n,d_cf+1))
    return sects
 
print(sect_iter(a,b,6,6)) #split the line between points a and b into 6 parts, find coords for each
>>[[3.6666666666666665, 5.666666666666667], [4.333333333333333, 6.333333333333333], [5.0, 7.0], [5.666666666666666, 7.666666666666666], [6.333333333333334, 8.333333333333334]]

This could be a costly computation in Arma, so I'll need to play around with the number of sections that the line is split and thus with the accuracy of grass detection. I'm excluding the start and the end line coords, but will simply take them from eyePos player and eyePos AI in arma so as not to waste calculation cycles.

 

 

Share this post


Link to post
Share on other sites

It's difficult to check that for every units and even for player because player can have multiple enemies insight.

 

Anyway, there are already powerful commands for what you intend to do.

Check this code, bob is an AI, enemy of player :

 

addMissionEventHandler ["EachFrame", {    if (lineIntersectsObjs [eyePos bob, eyePos player,bob,player,false,6] isNotEqualTo []) then {bob forgetTarget player} }];

  • Thanks 1

Share this post


Link to post
Share on other sites

Many thanks for the code, "Bob forgetTarget player" seems much better than spawning invisible walls! 🙂 

 

LineIntersectsObjs I am not sure how to go about making it useful. Arma's grass does not appear to be an object, see screenshots below:

Faint green line from players head intersects a Ficus plant - Ficus is listed in objects. (The precision with which Ficus is detected is very impressive - if the green line misses a branch even by 1cm, no Ficus is detected. This is why it is hard to hide behind them of course. The 2nd bush on the left is detected throughout its area and is much better for hiding.)

line-Intersect-Ficus.jpg

 

But, if the same green line intersects grass, no object is detected. Grass doesn't exist. An it's not a precision thing where the line misses the blades of grass, wiggling the line still returns nothing.

line-Intersect-nothing.jpg


Script (I've named my AI Bill in the Eden editor):

Spoiler

 


Sto = [];  
Fn = {  
  {  
 Sto set [_foreachindex,lineIntersectsObjs [(eyePos player),(eyePos Bill),objNull,objNull,false,_x]];  
  } forEach [1,2,4,8,16,32];  
  hintSilent format ["  
  ONLY_WATER: %1,  
  NEAREST_CONTACT: %2,  
  ONLY_STATIC: %3,  
  ONLY_DYNAMIC: %4,  
  FIRST_CONTACT: %5,  
  ALL_OBJECTS: %6",  
  Sto select 0,Sto select 1,Sto select 2,Sto select 3,Sto select 4,Sto select 5];
drawLine3D [ ASLToAGL eyePos player, ASLToAGL eyePos Bill, [0,1,0,1]];  
};  
["sample_id","onEachFrame","Fn"] call BIS_fnc_addStackedEventHandler;  
//Example display objects' array in the middle of the screen sorted by 6 flags 

 

 

 

As for checking for every unit - the initial goal is to solve the player being spotted as this annoys me the most. I know that I cannot see the AI, but I know that the AI can see me. Whether the AI see each other through the grass is much less of a pain for me personally, although in a perfect world it would be nice to lead a group of AI who obey the same laws of (simulated) optics as their human commander...

 

We'll see how computationally expensive the whole thing is. I could also cull the algo to work for only some of the AI if they are too numerous. But we need a proof of concept first.

Share this post


Link to post
Share on other sites

i am going to follow this thread because it is a great idea, and i hate that the player can't get away from a Ai when he is seen.  
So if you need a tester i'm ready. 🙂

  • Like 3

Share this post


Link to post
Share on other sites

Some more progress and, dare i say it, the proof of concept seems to be working. The vids below are pure Line of sight (LOS), no hacks with camo like in part 1 of the OP. This means that player can now lie behind the grass and not just in it (but not in a cheating way, if LOS is above the grass level, the AI will shoot - like in the beginning of the first vid below). The player can now also be hidden while standing up on a grassy hill, behind a grassy hill and so on. The LOS grass level can be set lower (AI will shoot sooner and peeking is hard) or higher (you can peek above the grass). 

 

 

 

 

 

The code (the AI should be called Bill once again):

Spoiler

draw_Fn = {    
  
drawLine3D [ ASLToAGL eyePos player, ASLToAGL eyePos Bill, [1,0,0,1]];    
};    
["sample_id","onEachFrame","draw_Fn"] call BIS_fnc_addStackedEventHandler; 
 
 [] spawn { 
  
while{true} do {     
     //build a list of sect coords sects   
_a = eyePos Bill;   
_b = eyePos player;    
_n = 6;    
_dcf = 6;    
sectsASL=[]; //find sect coords in ASL, AGL   
sectsAGL=[];     
terhASL=[];    //find height of terrain at each of sect coords    
hdiffs =[];     //find diffs between LOS sects heights and terrain heights -scalar   
tgtarr =[];        //find the target coords of where the LOS is closest to the ground [x,y,z]   
sect_fn = {    
    params ["_a","_b","_n","_dcf"];    
    x_sect =  _a#0+_dcf*(_b#0-_a#0)/_n;     
    y_sect =  _a#1+_dcf*(_b#1-_a#1)/_n;    
    z_sect =  _a#2+_dcf*(_b#2-_a#2)/_n;    
    [x_sect, y_sect, z_sect];    
};    
    
for [{ _i = 0 }, { _i <= _n }, { _i = _i + 1 }] do {    
    c_set = [_a, _b, _n, _i] call sect_fn;    
    sectsASL set [_i, c_set];   
    sectsAGL set [_i, ASLToAGL c_set];   
    thASL = getTerrainHeightASL [c_set#0, c_set#1];   
    terhASL set [_i, thASL ];   
    hdiffs set [_i, (c_set#2 - thASL)];   
};   
hdiffs_copy = + hdiffs; //deep copy  
hdiffs_copy sort true; //sort small first  
  
hdiffs_copy resize 3; // array with only 3 smallest heights   
minHarr = hdiffs_copy;  
for [{ _i = 0 }, { _i < count minHarr }, { _i = _i + 1 }] do {   
    for [{ _j = 0 }, { _j < count hdiffs }, { _j = _j + 1 }] do {  
      
    if (minHarr#_i == hdiffs#_j) then { tgtarr set [_i, sectsAGL#_j]}; };  
};   
  
for [{ _i = 0 }, { _i < count tgtarr }, { _i = _i + 1 }] do {    
    surf_tgt = surfaceType (tgtarr#_i); //find the surface type at the target coords   
    if ((minHarr#_i) < 0.6 && "Grass" in STR surf_tgt) then {hint "Grass in AI's LOS!"; Bill forgetTarget player} else {hint "They may see you!"; hint STR (minHarr#_i) };  
      
      
    }; 
sleep 0.3; 

};};

addMissionEventHandler ["Draw3D", {  
 drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Radar\radar_ca.paa", [1,0,0,1], (tgtarr#0), 1, 1, 0, "", 1, 0.05, "TahomaB"];}];
addMissionEventHandler ["Draw3D", {  
 drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Radar\radar_ca.paa", [1,0,0,1], (tgtarr#1), 1, 1, 0, "", 1, 0.05, "TahomaB"];}];

 

 

  • Like 3

Share this post


Link to post
Share on other sites

This is coming along, once you get this working 100% Bi should adopt your work into the base game with an official update!

Couple questions

Would the same thing work on modded terrains?

Is this only effecting the ai trying to see through grass or would other foliage like bushes and trees effect their los?

 

Tanoa would be a good map for a variety of foliage.

  • Like 1

Share this post


Link to post
Share on other sites

I forgot this one!  Working with grass, particles...

Try that:

 

addMissionEventHandler ["EachFrame", {    
  if ( [objNull,"view"] checkVisibility [eyePos bill,eyepos player] <0.7) then {
    bill forgetTarget player
  };
  hint str  ([objNull,"view"] checkVisibility [eyePos bill,eyepos player]);
}];

 

So, my final code, for multiple enemies could be, in initPlayerLocal.sqf:

 

addMissionEventHandler ["EachFrame", {    
  if ( diag_frameNo mod 30 isEqualTo 0) then {
    {
      if ([objNull,"view"] checkVisibility [eyePos _x, eyepos player] <0.7) then {
        _x forgetTarget player
      }
    } forEach (allUnits select {simulationEnabled _x && {side _x getFriend playerSide <0.6}});
  };
}];

 

Have fun!

Share this post


Link to post
Share on other sites

Even if they lose direct eye contact they should still guess fire from time to time I would think

  • Like 2

Share this post


Link to post
Share on other sites
23 hours ago, Gunter Severloh said:

Couple questions

Would the same thing work on modded terrains?

Is this only effecting the ai trying to see through grass or would other foliage like bushes and trees effect their los?

I'm pretty sure this would work on any map which has correct ASL, AGL coords and grass textures with string "grass" being part of the texture name. 

I'm also pretty sure foliage like solid bushes (not the Ficus like see thorough bushes like in my pics some posts above ) work with vanilla AI, they stop shooting once I'm  behind them. And my mod does not check for bushes, only elevation and grass textures.

16 hours ago, pierremgi said:

I forgot this one!  Working with grass, particles...

Try that:

snip

Have fun!

Wow! Your version is 10 times shorter and 100 times faster 🙂 And works pretty much just as well! Unfortunately, both of our algos fail once we have a squad of AI. 2 separate AI soldiers work great, but a squad of 2 AI soldiers becomes dumb as a doorknob. They usually fire only when the squad leader has a good LOS or his checkVisibility is above threshold.

 

 

Replacing forgetTarget player with getHideFrom (so that the AI squaddies cold share knowledge)  is not an option as the AI starts to shoot through the grass regardless of LOS and visibility.

 

Spawning an invisible wall (as explained in my OP) remains to be an option, I think it would only work with my algo though as checkVisibility does not return any coords on where the wall should be spawned. Or is there another way?

Share this post


Link to post
Share on other sites
23 hours ago, Gunter Severloh said:

This is coming along, once you get this working 100% Bi should adopt your work into the base game with an official update!

I really hope they will implement proper grass cover mechanics in their future Armas. From what i've seen on youtube, Arma Reforger AI still happily shoots through the grass. Although I've not seen any proper tests, maybe I got the wrong impression.

Share this post


Link to post
Share on other sites

Yep! forgetTarget applies to the group. I missed that. It's always messy in Arma. AI brains!!!! So code is fine for sentinels or single units.

Share this post


Link to post
Share on other sites

This code should increase realism, and each unit is concerned separately from group:

 

addMissionEventHandler ["EachFrame", {     
  if ( diag_frameNo mod 30 isEqualTo 0) then {
    {
      if ([objNull,"view"] checkVisibility [eyePos _x, eyepos player] <0.7) then {
        _x disableAI "FSM"; _x disableAI "checkVisible";
      } else {
        _x enableAI "FSM"; _x enableAI "checkVisible"
      };
    } forEach (allUnits select {simulationEnabled _x && {side _x getFriend playerSide <0.6}});
  };
}];

 

 

  • Like 3

Share this post


Link to post
Share on other sites

Looks intriguing, will try it tomorrow! 

 

Here is my usual 10 times longer and 100 times slower algo 🙂 - with the (not yet invisible and permeable) wall spawning. (I should say the large computational cost should not matter too much as the code will not need to run every frame. Even once a second per soldier would do the job ok I think.) 

The next stage is to delete each wall on the following loop cycle and also find a way to spawn the walls closer to the player. At this moment if the grassy ATL-min spot is next to the player, the walls are spawned not between the enemy and the player, but to the players' side. 

 

Code:

Spoiler

 


draw_Fn = {     
   
drawLine3D [ ASLToAGL eyePos player, ASLToAGL eyePos Bill, [1,0,0,1]];     
};     
["sample_id","onEachFrame","draw_Fn"] call BIS_fnc_addStackedEventHandler;  
  
 [] spawn {  
   
while{true} do {      
     //build a list of sect coords sects    
_a = eyePos player;      
_b = eyePos Bill; 
_dist = player distance Bill; 
_ratio = 7; //how close wall to player        
_n = 6;     
_dcf = 6; 
c_set =[];       
sectsASL=[]; //find sect coords in ASL, AGL    
sectsAGL=[];      
terhASL=[];    //find height of terrain at each of sect coords     
hdiffs =[];     //find diffs between LOS sects heights and terrain heights -scalar    
tgtarr =[];        //find the target coords of where the LOS is closest to the ground [x,y,z] 
publicVariable "tgtarr";  //make public for drawIcon3D below  
 
mult_fn = {  
    params ["_dist","_tgtn"];  
    mult = _tgtn / _dist;  
    mult;  
};  
      
sect_fn = {     
    params ["_a","_b","_n","_dcf"];     
    x_sect =  _a#0+_dcf*(_b#0-_a#0)/_n;      
    y_sect =  _a#1+_dcf*(_b#1-_a#1)/_n;     
    z_sect =  _a#2+_dcf*(_b#2-_a#2)/_n;     
    [x_sect, y_sect, z_sect];     
};     
     
for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do {       
    if (count c_set == 0) then { c_set = [_a, _b, _n, [_dist,_ratio] call mult_fn] call sect_fn; //try to create wall near to player   
    } else {    
   
    c_set = [_a, _b, _n, _i+1] call sect_fn; //i+1 as dcf+1 to ignore player eyepos ;_n-1 to ignore Bill eyepos   
     };      
    sectsASL set [_i, c_set];    
    sectsAGL set [_i, ASLToAGL c_set];    
    thASL = getTerrainHeightASL [c_set#0, c_set#1];    
    terhASL set [_i, thASL ];    
    hdiffs set [_i, (c_set#2 - thASL)];    
};    
hdiffs_copy = + hdiffs; //deep copy   
hdiffs_copy sort true; //sort small first   
   
hdiffs_copy resize 3; // array with only 2 smallest heights    
minHarr = hdiffs_copy;   
for [{ _i = 0 }, { _i < count minHarr }, { _i = _i + 1 }] do {    
    for [{ _j = 0 }, { _j < count hdiffs }, { _j = _j + 1 }] do {   
       
    if (minHarr#_i == hdiffs#_j) then { tgtarr set [_i, sectsAGL#_j]}; };   
};    
   
for [{ _i = 0 }, { _i < count tgtarr }, { _i = _i + 1 }] do {     
    surf_tgt = surfaceType (tgtarr#_i); //find the surface type at the target coords    
    if ((minHarr#_i) < 0.6 && "Grass" in STR surf_tgt) then { 
    hint "Grass in AI's LOS!";  
    tgtAGL = ASLToAGL (tgtarr#_i);     
    vec2e = Bill getDir player;     
    tgtAGL = ASLToAGL (tgtarr#_i);     
      
   _wall ="Land_CncBarrier_F" createVehicle tgtAGL;     
   _wall setDir vec2e;    
    addMissionEventHandler ["Draw3D", {      
 drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Radar\radar_ca.paa", [1,0,0,1], (tgtarr#_i), 1, 1, 0, "Split", 1, 0.05, "TahomaB"];}]; 
     
    } else {hint "They may see you!"; hint STR (minHarr#0) };   
       
       
    };  
sleep 1;  
 
};};

 

 

 

 

 

 

Share this post


Link to post
Share on other sites
On 2/14/2023 at 3:41 PM, pierremgi said:

This code should increase realism, and each unit is concerned separately from group:

 


addMissionEventHandler ["EachFrame", {     
  if ( diag_frameNo mod 30 isEqualTo 0) then {
    {
      if ([objNull,"view"] checkVisibility [eyePos _x, eyepos player] <0.7) then {
        _x disableAI "FSM"; _x disableAI "checkVisible";
      } else {
        _x enableAI "FSM"; _x enableAI "checkVisible"
      };
    } forEach (allUnits select {simulationEnabled _x && {side _x getFriend playerSide <0.6}});
  };
}];

 

 

You're a genius! 10 lines of code (some of which I do not understand even after googling - the disable FSM stuff) , but it works! Eureka!

 

 

This may need further testing, but my quick tests are very promising - all of the AI react normally, they lose sight when player LOS is covered by grass... Even single player campaign missions seem to work fine with your magic "disableAI FSM". I presume there must be some sideeffects, but so far so good. Looks like my algo with wall spawning is not needed and I can play some Arma missions instead - happy days 🙂

 

PS I've seen so many threads over so many years talking about this problem, hard to believe it is now finally addressed. Many, many thanks pierremgi!

 

 

 

  • Like 6

Share this post


Link to post
Share on other sites
23 hours ago, Raining6 said:

You're a genius!

True statement!  Pierre is a genius!

23 hours ago, Raining6 said:

_x disableAI "FSM"; _x disableAI "checkVisible";

While this may work for purpose of getting AI to not shoot directly at player in grass, won't disableAI "FSM"  also disturb their ability to move properly?

 

btw, I love this thread.  And your cool debug window in lower left showing the various relevant values is slick too.

  • Like 5

Share this post


Link to post
Share on other sites
19 hours ago, johnnyboy said:

While this may work for purpose of getting AI to not shoot directly at player in grass, won't disableAI "FSM"  also disturb their ability to move properly?

Also curious as to what the side effects of this might be and how to check for them. The AI shoots and moves with FSM disabled, but does it do so less optimally? - it is hard to say. 

 

I'm half heartedly continuing on with the wall spawn algo (just for the fun of it as it's nowhere near as fast or concise as Pierre's). Once, I complete it, we could compare how AI behaves with FSM and  without. 

Code:

Spoiler

draw_Fn = {       
     
drawLine3D [ ASLToAGL eyePos player, ASLToAGL eyePos Bill, [1,0,0,1]];       
};       
["sample_id","onEachFrame","draw_Fn"] call BIS_fnc_addStackedEventHandler;    
    
 [] spawn {    
     
while{true} do {        
     //build a list of sect coords sects      
_a = eyePos player;        
_b = eyePos Bill;   
_dist = player distance Bill;   
_ratio = 7; //how close wall to player          
_n = 6;       
_dcf = 6;   
c_set =[];         
sectsASL=[]; //find sect coords in ASL, AGL      
sectsAGL=[];        
terhASL=[];    //find height of terrain at each of sect coords       
hdiffs =[];     //find diffs between LOS sects heights and terrain heights -scalar      
tgtarr =[];        //find the target coords of where the LOS is closest to the ground [x,y,z]   
publicVariable "tgtarr";  //make public for drawIcon3D below    
tgtharr =[];
   
mult_fn = {    // find the wall location cose to the player (ie where the distance is less then 1 delta)
    params ["_dist","_tgtn"];    
    mult = _tgtn / _dist;    
    mult;    
};    
        
sect_fn = {       
    params ["_a","_b","_n","_dcf"];       
    x_sect =  _a#0+_dcf*(_b#0-_a#0)/_n;        
    y_sect =  _a#1+_dcf*(_b#1-_a#1)/_n;       
    z_sect =  _a#2+_dcf*(_b#2-_a#2)/_n;       
    [x_sect, y_sect, z_sect];       
};       
       
for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do {         
    if (count c_set == 0) then { c_set = [_a, _b, _n, [_dist,_ratio] call mult_fn] call sect_fn; //try to create wall near to player     
    } else {      
     
    c_set = [_a, _b, _n, _i+1] call sect_fn; //i+1 as dcf+1 to ignore AI eyepos ;_n-1 to ignore player eyepos  or the other way round - I forgot :)   
     };        
    sectsASL set [_i, c_set];      
    sectsAGL set [_i, ASLToAGL c_set];      
    thASL = getTerrainHeightASL [c_set#0, c_set#1];      
    terhASL set [_i, thASL ];      
    hdiffs set [_i, (c_set#2 - thASL)];      
};      
hdiffs_copy = + hdiffs; //deep copy     
hdiffs_copy sort true; //sort small first     
     
hdiffs_copy resize 3; // array with only 2 smallest heights      
minHarr = hdiffs_copy;     
for [{ _i = 0 }, { _i < count minHarr }, { _i = _i + 1 }] do {      
    for [{ _j = 0 }, { _j < count hdiffs }, { _j = _j + 1 }] do {     
         
    if (minHarr#_i == hdiffs#_j) then { tgtarr set [_i, sectsASL#_j]}; tgtharr set [_i, terhASL#_j]};};     
     
     
for [{ _i = 0 }, { _i < count tgtarr }, { _i = _i + 1 }] do {       
    surf_tgt = surfaceType (tgtarr#_i); //find the surface type at the target coords      
    if ((minHarr#_i) < 0.6 && "Grass" in STR surf_tgt) then {   
    hint "Grass in AI's LOS!";    
    tgtAGL = ASLToAGL ([tgtarr#_i#0, tgtarr#_i#1, tgtharr#_i]); 
    tgtAGL set [2,-0.1];      
    vec2e = Bill getDir player;       
      
  
if (!isNull createWall) then {Hint "Wall"; deleteVehicle createWall; createWall = objNull; publicVariable "createWall";};    
           
    createWall = createVehicle["Land_CncBarrier_F",  tgtAGL, [], 0, "CAN_COLLIDE"] ;    
    publicVariable "createWall";       
    createWall setDir vec2e;  
      
           
    addMissionEventHandler ["Draw3D", {        
 drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Radar\radar_ca.paa", [1,0,0,1], (tgtarr#_i), 1, 1, 0, "Split", 1, 0.05, "TahomaB"];}];   
       
    } else {hint "They may see you!"; hint STR (minHarr#0) };     
         
         
    };    
sleep 1;    
   
};};

 

 

 

 

19 hours ago, johnnyboy said:

btw, I love this thread.  And your cool debug window in lower left showing the various relevant values is slick too.

I love the result of this thread 🙂 

The cool debug window is not mine, I had trouble remembering where i got it from, but have now found the author : thanks Greenfist, your script makes working with AI so much more productive!

 

  • Like 2

Share this post


Link to post
Share on other sites

So funny I was literally thinking of greenfists old debug while just casually glancing over your pics. His debug ai perception kit is still one of my all time favorites and made me see there’s a future in this ai yet

  • Like 3

Share this post


Link to post
Share on other sites

If anyone could be so kind to bugtest my new version, I'd be most grateful. The LOS wall spawning algo appears to be working ok when I use createVehicle["Land_CncBarrier_F",  _tgtAGL, [], 0, "NO_COLLISION"] , but drops FPS from 60 to mid 40s after a few minutes of crawling around the same 3 AI as in the vid. Moreover, if I change the line above to createVehicle["Land_CncBarrier_F",  _tgtAGL, [], 0, "NONE"] the field quickly turns into a graveyard of concrete slabs (maybe its a sign regarding my coding aspirations). Am I not deleting the wall objects in the right manner? 

 

The kinda works ok vid :

 

Graveyard:

 

My suspicion is that even in the "kinda ok" version the number of slabs is also large, they are simply put onto the same spot and overlay each other.

The section I'm not sure about:

    _walls_dict = createHashMap;   
   for [{ _i = 0 }, { _i < count _tgtarr }, { _i = _i + 1 }] do {          
   ... SNIP...          
    
  createWall = createVehicle["Land_CncBarrier_F",  _tgtAGL, [], 0, "CAN_COLLIDE"];  //from this line onwards  
  publicVariable "createWall";         
  createWall setDir _vec2e; 
  _walls_dict set ["wall"+ str _i, createWall]; 
   
		};      
               
   };       
  sleep 0.3;  
  {if (!isNull _y) then { deleteVehicle _y; _y = objNull; } } forEach _walls_dict;  

 

The full Code

Spoiler

 
  
[] spawn { while {true} do {     
      
 _enemies = player call BIS_fnc_enemyTargets;    
 {    
     
   _x spawn {        
        
  while{true} do {            
    //build a list of sect coords sects          
  _a = eyePos player;            
  _b = eyePos _this;       
  _dist = player distance _this;       
  _ratio = 7; //how close a wall should be sapwned next to the player              
  _n = 6;   //inot how many parts the line is split        
  _dcf = 6;  //delta coeff (from 2nd post)     
  _c_set =[];             
  _sectsASL=[]; //find sect coords in ASL, AGL          
  _sectsAGL=[];            
  _terhASL=[];    //find height of terrain at each of sect coords           
  _hdiffs =[];     //find diffs between LOS sects heights and terrain heights -scalar          
  _tgtarr =[];        //find the target coords of where the LOS is closest to the ground [x,y,z]       
        
    
         
  mult_fn = {        
   params ["_dist","_ratio"];        
   mult = _ratio / _dist;        
   mult;        
  };        
        
  sect_fn = {           
   params ["_a","_b","_n","_dcf"];           
   x_sect =  _a#0+_dcf*(_b#0-_a#0)/_n;            
   y_sect =  _a#1+_dcf*(_b#1-_a#1)/_n;           
   z_sect =  _a#2+_dcf*(_b#2-_a#2)/_n;           
   [x_sect, y_sect, z_sect];           
  };           
          
  for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do {             
   if (count _c_set == 0) then { _c_set = [_a, _b, _n, [_dist,_ratio] call mult_fn] call sect_fn; //try to create wall near to player         
   } else {          
        
   _c_set = [_a, _b, _n, _i+1] call sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos         
    };            
   _sectsASL set [_i, _c_set];          
   _sectsAGL set [_i, ASLToAGL _c_set];          
   _thASL = getTerrainHeightASL [_c_set#0, _c_set#1];          
            
   _hdiffs set [_i, (_c_set#2 - _thASL)];          
  };          
  _minHarr = + _hdiffs; //deep copy         
  _minHarr sort true; //sort small first         
        
  _minHarr resize 2; // array with only 2 smallest heights          
       
  for [{ _i = 0 }, { _i < count _minHarr }, { _i = _i + 1 }] do {          
   for [{ _j = 0 }, { _j < count _hdiffs }, { _j = _j + 1 }] do {         
         
   if (_minHarr#_i == _hdiffs#_j) then { _tgtarr set [_i, _sectsAGL#_j]}; };};         
        
    _walls_dict = createHashMap;    
   for [{ _i = 0 }, { _i < count _tgtarr }, { _i = _i + 1 }] do {           
    _surf_tgt = surfaceType (_tgtarr#_i); //find the surface type at the target coords          
    if ((_minHarr#_i) < 0.6 && "Grass" in STR _surf_tgt) then {       
       
  _tgtAGL = _tgtarr#_i;  
  _tgtAGL set [2,-0.1];     
     
  _vec2e = _this getDir player;           
     //THE SECTION I"M NOT SURE ABOUT STARTS BELOW
  createWall = createVehicle["Land_CncBarrier_F",  _tgtAGL, [], 0, "CAN_COLLIDE"];     
  publicVariable "createWall";          
  createWall setDir _vec2e;  
  _walls_dict set ["wall"+ str _i, createWall];  
 
     
  };       
          
         
   };        
  sleep 0.3;   
  {if (!isNull _y) then { deleteVehicle _y; _y = objNull; publicVariable "createWall"} } forEach _walls_dict;     
   deleteVehicle createWall; createWall = objNull; publicVariable "createWall";      
  };};    

    //END OF THE SECTION IN QUESTION
     
 } forEach _enemies;     
    
    
 sleep 0.05;   
    
    
 };    
}   
 

 

 

PS All sorted now, see next page posts.

 

Share this post


Link to post
Share on other sites

Well grass is a very bad cover 😄 and not so good concealment once you've been spotted.

In your first test video, the "knowsAbout" value always is around 2.2. I'm fairly certain if you'd have peeled / rolled away to your rear or left for 20-30m and just wait for a few minutes the value would have gone down. I understand what you desire, but tbh if a player spots an enemy standing up and then going prone he'll start shooting at that position as well. Difference being that the AI always will shoot at the exact same spot it has last seen an enemy, while players may suppress or have some kind of dispersion. Not sure how realistic it is that the AI immediately disengages just because you dropped down behind some short grass, but the solutions may be desirable for some mission makers.

 

Share this post


Link to post
Share on other sites
On 2/22/2023 at 11:13 PM, lukio said:

In your first test video, the "knowsAbout" value always is around 2.2. I'm fairly certain if you'd have peeled / rolled away to your rear or left for 20-30m and just wait for a few minutes the value would have gone down. I understand what you desire, but tbh if a player spots an enemy standing up and then going prone he'll start shooting at that position as well. Difference being that the AI always will shoot at the exact same spot it has last seen an enemy, while players may suppress or have some kind of dispersion.

I've made another quick test with vanilla grass spotting mechanics. Rolling away is not an option and the AI shoots where the player IS, not "the exact same spot it has last seen an enemy" - @2:09. Also, in the "standing on a hill" situation, once the player's cover is blown, the AI sees the player coming through the grass even after the player disengages or hides behind a solid obstacle. 

 

 

On 2/22/2023 at 11:13 PM, lukio said:

while players may suppress or have some kind of dispersion. Not sure how realistic it is that the AI immediately disengages just because you dropped down behind some short grass,

The grass is not short, it completely blocks the line of sight. But good point about suppression and the AI disengaging too soon. Pierre's algo + VcomAI mod combo does what you suggest - suppressive fire once player hides behind grass - if he is 200m or closer. With dispersion too, see @1:03 in the vid below. However, the "disable FSM" part of the algo somewhat breaks the Vcom mod as its AI stops flanking and moving.

 

 

 

Here is the same Vcom AI without the disable FSM - notice how it moves instead of being static. (The idea was to make concrete slabs transparent to players eyes collisions and bullets, so its not like one can be actually safe behind grass. The blocks would not block AI's bullets, but would only block AI's "vision". The mod is still unfinished as I got stuck on the bug covered in my previous post and then got distracted with other stuff 🙂)

 

Share this post


Link to post
Share on other sites
2 hours ago, Raining6 said:

I've made another quick test with vanilla grass spotting mechanics. Rolling away is not an option and the AI shoots where the player IS, not "the exact same spot it has last seen an enemy" - @2:09. Also, in the "standing on a hill" situation, once the player's cover is blown, the AI sees the player coming through the grass even after the player disengages or hides behind a solid obstacle.  


Ok maybe it was a mod or something but I can regularly disengage with a full fireteam, pull or peel back then move about 50m while the AI will continue engaging our prior position and then attack from a newer position or flank them. They also don't immediately spot us when we've done that, peeling left / right and then re-engaging is something we do regularly and with success.

I don't get the "The grass is not short, it completely blocks the line of sight" statement - it doesn't block line of sight? Like there is tall grass (we call it elephant grass - on Altis) that blocks line of sight and provides concealment, the short grass does not. Probably why most units turn off grass all together, because it's just decorative. Your videos (and mod/script work) definitely are interesting for those that want to use the grass as concealment - like in that old CoD mission where you crawl around.

Share this post


Link to post
Share on other sites

Depending on how grass, bushes and small trees are made of. Unsung maps, for example, have plants extremely "opaque" for AIs.

Share this post


Link to post
Share on other sites
7 hours ago, lukio said:

I don't get the "The grass is not short, it completely blocks the line of sight" statement - it doesn't block line of sight? Like there is tall grass (we call it elephant grass - on Altis) that blocks line of sight and provides concealment, the short grass does not.

Stratis grass in the videos above completely blocks the line of sight in first person view, not with the (cheating 🙂) third person camera. I specifically switched to gunsight view seral times to show this - the player sees nothing but grass through the sight. 

Quote

Probably why most units turn off grass all together, because it's just decorative. 

No grass = ugly and boring, see the OP 😉

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

×