Jump to content

tactical AI terrain awareness


Recommended Posts

Just how much terrain awareness AI has? Could there be any automated way in which AI would consider the terrain details in and around an AO and instead of doing random, more or less tactically meaningful circular patrols to maybe focus (watch, patrol) on the high grounds, the approach routes with cover (forest, buildings, ditches etc)  ? Do they know where the roads are ?

Link to comment
Share on other sites

  • 2 weeks later...

given the huge potential and the level of enthusiasm on the subject  i have taken a suicidal, bold first step into the world of Arma 3 coding with an amazing work designed to find one of the points on the highest elevation contour line around a given coordinate (the player in this case, but why not the center of an AO...) and to spawn a wonderful tower at that amazing bellevue location:


 

Spoiler

 

init = getposatl player;
scancircle = 500;
accuracy = scancircle*2; 
flagx = 0; flagy = 0;  flagz = 0; maxflagz = 0;  
_coordinate = []; _tgtcoord = [];   i=0;    
while {i<accuracy} do {  
dummy = createVehicle ["TargetP_Inf2_F", init, [], scancircle, "NONE"] ;    
_coordinate = getposasl dummy;  
diag_log format ["coordinate: %1",_coordinate]; 

flagx = _coordinate select 0;  
flagy = _coordinate select 1;  
flagz = _coordinate select 2; 
if (flagz > maxflagz) then {maxflagz = flagz; tgtflagx = flagx; tgtflagy = flagy;
diag_log format ["newmax elevation coordinate is : %1 , %2 , %3",tgtflagx, tgtflagy, maxflagz];

};

deletevehicle dummy;
i=i+1;

};     
_tgtcoord = [tgtflagx,tgtflagy,maxflagz];
diag_log format ["FINAL max elevation coordinate is : %1",_tgtcoord];

gg = [tgtflagx,tgtflagy,0];
createVehicle ["Land_TTowerBig_2_F", gg, [], 0, "CAN_COLLIDE"];

 

 

my extremely limited coding "abilities"  (as demonstrated above; "don't make me regret this") assure me that a much better code to do this task would be a piece of cake for most of you  :) .... my desire would be to write a function designed to find such a "high ground" (or more around an AO) in order to be able to send some AI to patrol there ... maybe a single scan of each map could generate a database of coordinates for that map to be used in a much better approach to coding this task ... :huh:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

*ver1.1 also finds the minimum height coordinate and spawns a hospital there :

 

Spoiler

init = getposatl player;  
scanradius = 300;  
accuracy = scanradius*2;   
flagx = 0; flagy = 0;  flagz = 0; maxflagz = 0; minflagz = 4000;   
_coordinate = []; _tgtcoord = []; _tgtmincoord = [];   i=0;      
while {i<accuracy} do {    
dummy = createVehicle ["Land_PowerPoleWooden_small_F", init, [], scanradius, "NONE"] ;      
_coordinate = getposasl dummy;      
flagx = _coordinate select 0;    
flagy = _coordinate select 1;    
flagz = _coordinate select 2;   
if (flagz > maxflagz) then {maxflagz = flagz; tgtflagx = flagx; tgtflagy = flagy;} else
{if (flagz >0 && flagz < minflagz) then {minflagz = flagz; tgtminx = flagx; tgtminy = flagy; };
deletevehicle dummy;  
i=i+1;   
};      
};
_tgtcoord = [tgtflagx,tgtflagy,maxflagz];
_tgtmincoord = [tgtminx,tgtminy,minflagz];  
diag_log format ["FINAL max elevation coordinate is : %1",_tgtcoord];
diag_log format ["FINAL min elevation coordinate is : %1",_tgtmincoord];
gg = [tgtflagx,tgtflagy,0];  
ggmin = [tgtminx,tgtminy,0];
createVehicle ["Land_TTowerBig_2_F", gg, [], 0, "CAN_COLLIDE"];
createVehicle ["Land_Hospital_main_F", ggmin, [], 0, "CAN_COLLIDE"];

 

 

Link to comment
Share on other sites

waypoints coordinates generator for patrolling high ground 1000m around an area:


 

Spoiler

 

init = getposatl player;   
j=30;    
flagx = 0; flagy = 0;  flagz = 0; maxflagz = 0;     
_coordinate = []; _tgtcoord = [];  
while {j<1001} do { i=0; accuracy = j/10;
while {i<accuracy} do {     
dummy = createVehicle ["Land_PowerPoleWooden_small_F", init, [], j, "NONE"] ;       
_coordinate = getposasl dummy;       
flagx = _coordinate select 0;     
flagy = _coordinate select 1;     
flagz = _coordinate select 2;    
if (flagz > maxflagz) then {maxflagz = flagz; tgtflagx = flagx; tgtflagy = flagy;};
deletevehicle dummy;   
i=i+1;    
};       
_tgtcoord = [tgtflagx,tgtflagy,maxflagz];
diag_log format ["high elevation waypoint coordinates  : %1",_tgtcoord];
gg = [tgtflagx,tgtflagy,0];   
createVehicle ["Land_TTowerBig_2_F", gg, [], 0, "CAN_COLLIDE"];
j=j+30;
};


 

waypoints coordinates generator for patrolling low ground (valley) 1000m around an area:

 

Spoiler

init = getposatl player;   
j=30;    
flagx = 0; flagy = 0;  flagz = 0; minflagz = 9000;     
_coordinate = []; _tgtmincoord = [];  
while {j<1001} do { i=0; accuracy = j/10;
while {i<accuracy} do {     
dummy = createVehicle ["Land_PowerPoleWooden_small_F", init, [], j, "NONE"] ;       
_coordinate = getposasl dummy;       
flagx = _coordinate select 0;     
flagy = _coordinate select 1;     
flagz = _coordinate select 2;    
if (flagz >0 && flagz < minflagz) then {minflagz = flagz; tgtminx = flagx; tgtminy = flagy; };
deletevehicle dummy;   
i=i+1;    
};       
_tgtmincoord = [tgtminx,tgtminy,minflagz];
diag_log format ["low elevation waypoint coordinates: %1",_tgtmincoord];
ggmin = [tgtminx,tgtminy,0];   
createVehicle ["Land_TTowerBig_2_F", ggmin, [], 0, "CAN_COLLIDE"];
j=j+30;
};

 

 

Link to comment
Share on other sites

Let me just say: great job so far!

 

Since I personally think programmers should encourage and help each other, especially if someone's starting out, here's some tips:

  • Avoid spawning objects when not it's not needed. It is one of the most expensive (read performance hitting) things a script can do.
  • Always uses local variables instead of global ones when you can (_variable is local, variable is global). It's more secure and will prevent weird things from happening.

Don't interpret these tips as something you should be ashamed of, I'm really quite impressed you managed to write something like this seeing you just started out.

 

If it can help you, this code finds the heighest point in the given radius with 10m accuracy (altough it actually searches a square of that size not a circle) and is around 83x faster (42ms vs 3500ms execution time) when executed with a radius to search of 1000m:

Spoiler
_pos = getPos player;
_radius = 1000;
 
_highestPos = [0,0,0];
_highestHeight = 0;
 
_posX = 0;
_posY = 0;
 
_startPos = (_pos getPos [245, 0]) getPos [245, -90];
_currentPos = _startPos;
 
while{_posY < _radius}do{
    while{_posX < _radius}do{
        if(getTerrainHeightASL _currentPos > _highestHeight)then{
            _highestHeight = getTerrainHeightASL _currentPos;
            _highestPos = _currentPos;
        };
        _currentPos = _currentPos getPos [10,90];
        _posX = _posX + 10;
    };
    _posY = _posY + 10;
    _posX = 0;
    player setPos _startPos;
    _currentPos = _startPos getPos [_posY,-180];
};
 
player setPos _highestPos;

 

Don't let that discourage you though. Nobody learns programming on a day and optimization will come naturally over time.

 

I'm interested to see what you'll do next, keep up the good work!

Link to comment
Share on other sites

@Jochem thank you !

i didn't know about getterrainheight ... :)   spawning the objects was part for visual fun in the editor, part cause i only knew about getpos for player / object, and yes it froze the pc for some seconds :lol:;  using local variables was not an issue in the editor but i know it must be done if the code is to be used anywhere else. I am not a programmer ...  so i am not ashamed ... it is just fun for me trying to understand how coding works in Arma ... and maybe, one day, help to see AI do more than circular random patrols as if unaware of any tactical value of the terrain topology and features around them.

 

Thank you again for the kind words, the advice and the 83x :lol: faster code !

tiny question: is there any function similar to getTerrainheight to scan if a given area has vegetation, trees, rocks, water, constructions or roads on it ?

Link to comment
Share on other sites

I don't think there is a function for that: you can use nearObjects and scan for the general class names of what you're looking for. This will do everything but roads, for which you will need to use nearRoads. 

Link to comment
Share on other sites

@Jochem i'm getting better (20 ms) ... :) thank you again !

Spoiler

_radius=1000;
_r=10;
_highestpos=[0,0,0];
_highestz = 0;
_startpos = getpos player;
while {_r<_radius} do {
_ang=0;
while {_ang<360} do {
_currentpos = player getpos [_r,_ang];
_currentz = getTerrainHeightASL _currentpos;
if (_currentz > _highestz) then {
_highestz = _currentz;  
_highestpos = _currentpos;};
_ang = _ang + (360/(4+0.032*_r));
};
_r = _r + 10;
};
player setPos _highestPos;

 

Link to comment
Share on other sites

For the terrain objects like trees and rocks: https://community.bistudio.com/wiki/nearestTerrainObjects

 

Amazing work with the new version, I'm impressed you managed to cut off half the execution time. When I'm home I might try one of your suggestions about indexing the map at the start of the mission. I'm not very optimistic about it but we'll see, 20ms is a hard time to beat.

Link to comment
Share on other sites

Truth is i have inadvertently cheated at the long ranges accuracy since it is searching in arcs ...

Spoiler

if you replace _ang = _ang + (360/(4+0.032*_r)); with _ang = _ang + (360/(0.201*_r));  you look at a truer 10m accuracy with (10,000 measurements at 1000m similar to the squares method) with 80ms execution time

 I'll try an (archimede) spiral instead of multiple circles to see if it gets faster.

About indexing the map i thought it was a good idea at the time only because i didn't know about getTerrainHeight , but since it exists there should already be a map coord database where it searches the heights... or it works in some other way ...

thx !

 

Link to comment
Share on other sites

did some further testing also with a random

Spoiler

_counter=0;
_radius=1000;
_npix=_radius^2/100;
_i=1;
_highestpos=[0,0,0];
_highestz = 0;
_startpos = getpos player;
while {_i<_npix} do {
_counter = counter +1;
_currentpos = player getpos [10+random _radius , random 360];
_currentz = getTerrainHeightASL _currentpos;
if (_currentz > _highestz) then {
_highestz = _currentz;   
_highestpos = _currentpos;
};
_i = _i + 1;
};
player setPos _highestPos;

 a spiral method

Spoiler

_counter=0;
_acc=10010;
_radius=1000
_r=10;  
_highestpos=[0,0,0];  
_highestz = 0;  
_startpos = getpos player;  
while {_r<_acc} do {  
_counter = counter +1;
_currentpos = player getpos [_r/(_acc/_radius),36*_r];  
_currentz = getTerrainHeightASL _currentpos;  
if (_currentz > _highestz) then {  
_highestz = _currentz;    
_highestpos = _currentpos;};  
 
_r = _r + 1;  
};  
player setPos _highestPos;

and circles at 10m accuracy

Spoiler

_counter =0;
_radius=1000;
_r=10;
_highestpos=[0,0,0];
_highestz = 0;
_startpos = getpos player;
while {_r<_radius} do {
_ang=0;
while {_ang<360} do {
_counter = counter +1;
_currentpos = player getpos [_r,_ang];
_currentz = getTerrainHeightASL _currentpos;
if (_currentz > _highestz) then {
_highestz = _currentz;   
_highestpos = _currentpos;};
_ang = _ang + (360/(0.201*_r));
};
_r = _r + 10;
};
player setPos _highestPos;

and the results at 1000m, 10m accuracy were:

square :65ms

circles: 88ms

random: 76ms

spiral: 94ms

 

Link to comment
Share on other sites

Tried indexing the map, like I expected didn't work nearly as fast as the other solutions (for a 1000m radius).

 

Also did some optimizing of my first code, runs around 38% faster (29ms vs 41ms)

Spoiler

_pos = getPosWorld player;
_radius = 1000;

_highestPos = [0,0,0];
_highestHeight = 0;

_posX = 0;
_posY = 0;

_startPos = (_pos getPos [245, 0]) getPos [245, -90];
_currentPos = _startPos;

for "_posY" from 10 to _radius step 10 do{
	for "_posX" from 10 to _radius step 10 do{
		if(getTerrainHeightASL _currentPos > _highestHeight)then{
			_highestHeight = getTerrainHeightASL _currentPos;
			_highestPos = _currentPos;
		};
		_currentPos = _currentPos getPos [10, 90];
	};
	_currentPos = _startPos getPos [_posY, -180];
};

 

 

Edited by Jochem
Code fix
Link to comment
Share on other sites

@ JOCHEM, nice, but why start it at(-245,+245) from the player position ?

_startPos = [(_pos select 0) - 245,(_pos select 1) + 245,0];

?

 

need a bit of help with this one as i am trying to get the parachute removed and the backpack on back only after the landing .... :) :

Spoiler

_z=500;
[player, (player call removebackpackback)] call setbackpackonchest;
player addbackpack "B_Parachute";
player setpos [getpos player select 0,getpos player select 1,_z];
sleep 5;
waitUntil {getposatl player select 2 < 1};
player call removebackpackback;
[player, (player call removebackpackchest)] call setbackpackonback;

 

the sleep 5 and waituntil do not work as intended ...

Link to comment
Share on other sites

Nah, I broke it apperently, fixed the code in the post above.

 

245 = ((_radius/2) - 5)

It get's the uppder right corner of the square.

I was a bit lazy and harcoded it in, but you can replace it with ((_radius/2) - 5).

 

For your script, make it like this:

[]spawn{
  	_z=500;
	[player, (player call removebackpackback)] call setbackpackonchest;
	player addbackpack "B_Parachute";
	player setpos [getpos player select 0,getpos player select 1,_z];
	sleep 5;
	waitUntil {getposatl player select 2 < 1};
	player call removebackpackback;
	[player, (player call removebackpackchest)] call setbackpackonback;
}

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

 

You dicovered scheduled and unscheduled execution, welcome to the complications of ArmA scripting.

Link to comment
Share on other sites

off-topic, just for fun self paradrop with chestpack swap to back after landing:

Spoiler

[]spawn{
 _z=500;
 [player, (player call removebackpackback)] call setbackpackonchest;
 player addbackpack "B_Parachute";
 player setpos [getpos player select 0,getpos player select 1,_z];
  waitUntil {istouchingground player};
 player call removebackpackback;
 [player, (player call removebackpackchest)] call setbackpackonback;
};

paradrop eject with auto chestpack swap to back after landing (this would be nice as an action menu ...)

Spoiler

[]spawn{
[player, (player call removebackpackback)] call setbackpackonchest;
player addbackpack "B_Parachute";
player action ["eject",vehicle player];
waitUntil {istouchingground player};
player call removebackpackback;
[player, (player call removebackpackchest)] call setbackpackonback;
};

 and safety parachute, for accidental ejection,  detects ejection(keyboard), player falling and gives parachute, with auto open

Spoiler

[]spawn{  
 waitUntil {inputAction "eject">0};
_i=getposasl player select 2 ;  
sleep 1;  
_f=getposasl player select 2 ;  
if ((_i-_f) > 2) then {  
[player, (player call removebackpackback)] call setbackpackonchest;   
player addbackpack "B_Parachute";  
waituntil {getposasl player select 2 < 160};
player action ["OpenParachute", player];  
waitUntil {istouchingground player};   
player call removebackpackback;   
[player, (player call removebackpackchest)] call setbackpackonback;   
};  
};

 

Link to comment
Share on other sites

If you start/ stop the para auto ejection script from an ASL action menu (auto para on/off) you only use resources when needed, give each player para eject action menu only when ASL sets it on and gain alot of time from not taking a parachute from the arsenal and not having to swap backpacks at the arsenal and after landing. Plus ASL could decide a paradrop action on the fly if needed. Would be like gearing up while in the air vehicle..... If you use the safety para script you wouldn't even have to add an action menu for each player, just the on/off for the ASL...i can only test the scripts on local unfortunately.

Link to comment
Share on other sites

I really like those ideas, specially the give ASL the control over when to paradrop, in Gauntlet that could be easily implemented with the control being given to the pilot of the aircraft and when the action was performed everyone except pilot and co-pilot would be ejected from the helicopter/plane. That could even give the cool effect of the sequencial paradrop by forcing the eject with a given interval (e.g. every 0.5s) and forcing the parachute to open after a certain time (I can already imagine the cinematics of that!).

 

Excellent ideas @D34TH:)

 

Link to comment
Share on other sites

5 minutes ago, Ryko said:

Nice scripting, but I don't want to add magical solutions to problems which are due to a player simply not wanting to take the time to prepare.

 

The part of the automatic parachute I agree with you, but having the control of when to eject given to the pilot (for example) would be awesome for a correct dropzone 

Link to comment
Share on other sites

@Jochem

I could use some help when/if you find the time :) as i got steam coming out of my ears on this one ...
 

Spoiler

[]spawn{
 _excess = 10;
_radius=50;
_counter =0;
while {_counter<_excess} do {
waituntil {inputaction "pushtotalk">0};
_counter=(_counter +1);
hint format ["radio counter %1", _counter ];
sleep 1;
};
playsound "electricity_loop";
 

_pos = getPosWorld player;
_highestPos = [0,0,0];
_highestHeight = 0;
_posX = 0;
_posY = 0;
_startPos = (_pos getPos [_radius/2-5, 0]) getPos [_radius/2-5, -90];
_currentPos = _startPos;
for "_posY" from 10 to _radius step 10 do{
    for "_posX" from 10 to _radius step 10 do{
        if(getTerrainHeightASL _currentPos > _highestHeight)then{
            _highestHeight = getTerrainHeightASL _currentPos;
            _highestPos = _currentPos;
        };
        _currentPos = _currentPos getPos [10, 90];
    };
    _currentPos = _startPos getPos [_posY, -180];
};

 

_grp = createGroup east;
_snipercover =[];
for "_i" from 1 to 10 step 1 do{
_snipercover = createVehicle ["Land_Grave_dirt_F",_highestpos, [], 15, "none"];
};
_sniper = _grp createUnit ["O_T_sniper_F", getposatl _snipercover, [], 0, "can collide"];

_sniper setunitpos "auto";
_sniper setcombatmode "red";
_sniper setbehaviour "stealth";
_sniper dowatch getpos player;
_sniper dotarget player;
_sniper setskill 0.9;
}

 

 

 

counts number of "radio" transmissions and/or number of seconds as trigger (*atm i could only do  it to work for the vanilla "TAB" key PushToTalk) and once _excess (10 sec and/or transmissions) is reached gives a sound warning and spawns a sniper and some "cover" for him at a high ground, at a distance of _radius (50m but could go up to 1000m) from player.

Spoiler

 

The plan was:

1 find ASL player, check if near AO
2 trigger excessive radio chatter, +/-give subtle warning to ASL
3 find high position to spawn a sniper
4 spawn sniper and some _snipercover at high position and make him "smart" and join the action
5 after 5 min remove sniper if not dead and the _snipercover obj created

6 reset radio chatter counter and make  the script loop

 

1  i didn't try yet

2 its done but only for "vanilla PushToTalk"

3 its done / could be refined

4 done / still needs refinement to make the sniper smarter

5,6 i tried but did not manage to get it to work

(*the counter hint is in there just for testing/debug)

 

 

 

Link to comment
Share on other sites

I knocked myself out, the code is a bit more complex, but I kept all the code in one file:

Spoiler

cs_spawnSniper = {
	//Pos needs to be in PositionAGL
	params["_pos","_target"];

	//Create sniper
	_grp = createGroup east;
	_sniper = _grp createUnit ["O_T_sniper_F", _pos, [], 10, "FORM"];
	_sniper setUnitPos "down";
	_sniper setCombatMode "red";
	_sniper setBehaviour "stealth";

	//Sniper now knows about the target
	_sniper reveal [_target,4];
	//Sniper will engage target
	_sniper doTarget _target;

	_sniper setSkill 0.9; 
};

cs_getHighestPos = {
	params["_pos","_radius"];

	_highestPos = [0,0,0];
	_highestHeight = 0;

	_posX = 0;
	_posY = 0;

	_startPos = (_pos getPos [(_radius/2)-5, 0]) getPos [(_radius/2)-5, -90];
	_currentPos = _startPos;

	for "_posY" from 10 to _radius step 10 do{
		for "_posX" from 10 to _radius step 10 do{
			if((getTerrainHeightASL _currentPos > _highestHeight) && (west countSide (_currentPos nearEntities 250) == 0))then{
				_highestHeight = getTerrainHeightASL _currentPos;
				_highestPos = _currentPos;
			};
			_currentPos = _currentPos getPos [10, 90];
		};
		_currentPos = _startPos getPos [_posY, -180];
	};
	
	_highestPos;
};

cs_init = {
	//https://community.bistudio.com/wiki/remoteExecCall
	{
		//https://github.com/michail-nikolaev/task-force-arma-3-radio/wiki/API%3A-Events
		["DetectLRTraffic", "OnTangent", {
			params["_unit","_notUsed","_radioType"];
			if(_radioType == 1)then{
				if(player getVariable["LRIsSpeaking",false])then{
					player setVariable["LRIsSpeaking",false,true];
					player setVariable["LRTransmissionTime", ((player getVariable["LRTransmissionTime",0]) + (diag_tickTime - (player getVariable["LRStartedSpeaking",0]))), true];
					player setVariable["LRStartedSpeaking",0,true];
				}else{
					player setVariable["LRIsSpeaking",true,true];
					player setVariable["LRStartedSpeaking",diag_tickTime,true];
				};
			};
		}, player] call TFAR_fnc_addEventHandler;
	} remoteExecCall ["bis_fnc_call", 0, true];

	[{
		{
			if(player getVariable["LRTransmissionTime",0] >= 10)then{
				_posSniper = [getPosATL player,1000]call cs_getHighestPos;
				[_posSniper, player]call cs_spawnSniper;
				player setVariable["LRTransmissionTime",0,true];
			};
		} forEach allPlayers;
	} , 1, []] call CBA_fnc_addPerFrameHandler;
};

 

 

The main thing about this is that it works in both SP/MP (altough for pure MP use some slight adjustments should be made for performance), is JIP compatible and doesn't contain any scheduled code (spawn, execVM).

 

If you want to test it paste the code in a file (i.e. sniper.sqf) in the mission folder and execute these two lines:

Spoiler

call compile preprocessFileLineNumbers "sniper.sqf"; 
[]call cs_init;

 

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Forum Statistics

    11.1k
    Total Topics
    66.4k
    Total Posts
×
×
  • Create New...