Jump to content
Tankbuster

AI convoy. Best practices?

Recommended Posts

I'm not doing this in the editor. It's all done by dynamic scripting.

You're right, there is an effectivecommander once there is at least one crew in any vehicle. The problem is that the gunners of ifrits and marids are the effective commanders of their vehicles and need to be in the group that gets the waypoint, otherwise nothing happens.

  • Like 1

Share this post


Link to post
Share on other sites

@pierremgi The groups are created with the createGroup command. There are two groups, one for drivers and the other for non drivers. There are reasons for this. It isn't as simple as the Editor, nothing with SQF really is usually lol. Creating the convoy and waypoints, along with limitSpeed, setConvoySeparation, etc isn't the issue. The problem is AI in general. We understand that AI is trash in A3, especially for vehicles but the results we were getting wasn't acceptable, not as a convoy. However, that said. We (mostly @Tankbuster) have made serious progress with this.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

I'll be posting some code later, just tidying it up and preparing some documentation because it's just part of a larger mission and it's important to see it in context.

Share this post


Link to post
Share on other sites
9 hours ago, pierremgi said:

How do you create your group?

You have the basics correct and we're very grateful for your guidance. The key things to get right are the form up of the convoy at start, limitspeed, setconvoyseparation, setbehaviour and grouping them in order. Once you've done that, you're in the hands of the AI Gods. :) I'll still be interested to see yours and other comments on what I've done though.

It can be a little tricky translating your 3den stuff into SQF.
At the moment, we've got a 3-7 vehicle convoy spawning at a dynamic location and convoying to a dynamically chosen destination with about 80% reliability. I think much of what we're doing from here on in is trying to smooth out the wrinkles in the driving AI and we've had some success in 'unsticking' vehicles using setvelocitymodelspace.  Hopefully I'll be able to post some code later today.

  • Like 1

Share this post


Link to post
Share on other sites

Here's some code;

It references some variables defined elsewhere. For example, all the target towns have a logic which holds their radius, name and a few other bits.

 

 

Spoiler

//by tankbuster
 #include "..\includes.sqf"
_myscript = "do_template";
__tky_starts;
missionactive = true; publicVariable "missionactive";
missionsuccess = false; publicVariable "missionsuccess";
sleep 5;
if (testmode) then
	{// remove any existing debug markerS
		{
		if ((str _x) find "debug" > 0) then
			{
			deleteMarker _x;
			};
		}forEach allmapmarkers;
	};
if (not (isNil "convoyvecs")) then
	{// check for an existing convoy and delete if reqd. allows for mission rerunning if stuck vecs etc
		{
			_vectodelete = _x;
			{_vectodelete deleteVehicleCrew _x } forEach (crew _vectodelete);
			deleteVehicle _vectodelete;
		} forEach convoyvecs;
	};
private _smcleanup = [];
private ["_potentialstarttowns","_mystart","_townrad","_drivers","_potentialstarts","_mystart2","_tc","_actualstart","_roadarray","_selectlast","_potends","_vectype","_myvec","_actualdest","_condrvgrp","_conmangrp","_distanddir","_mytarget","_sometown", "_lastpathfinderpos"];
convoyvecs = [];
_potentialstarttowns = (cpt_position nearEntities ["Logic", 10000]) select {((_x getVariable ["targetstatus", -1]) > -1) and {(_x distance2d cpt_position) > 2500} and ((_x getvariable "targetlandmassid") isEqualTo cpt_island)};
_mystart = selectRandom _potentialstarttowns;
_townrad = _mystart getVariable "targetradius";
_drivers = [];
_potentialstarts = (_mystart nearRoads (100 + _townrad)) select {((_x distance2D _mystart) > _townrad)};// an array of roadpieces outside the chosen town
if (_potentialstarts isEqualTo []) then
	{// if no potentialstarts found, try with a different town
		_potentialstarttowns = _potentialstarttowns - [_mystart];
		_mystart2 = selectRandom _potentialstarttowns;
		_townrad = _mystart2 getvariable "targetradius";
		_potentialstarts = _mystart2 nearRoads (250 + _townrad) select {((_x distance2D _mystart2) > _townrad)};
	};
_tc = 3 + (floor random 4);
_actualstart = selectRandom _potentialstarts;
[_actualstart, "start"] execVM "server\debug\debug_makemarker.sqf";
_roadarray = [getpos _actualstart, (_tc * 2)] call tky_fnc_findroads;
_selectlast = (count _roadarray) -1;

_potends = alllogics select {((_x getVariable "targetlandmassid") isEqualTo cpt_island) and ((_x getvariable ["targetstatus", 2]) != 2 ) and ((_x distance2D _actualstart) > 1500) };
// ^^^ get all opfor held, logics on the same island more than 2 k away. if logic has no targetstatus, its set to 2, which is same as player held. cunning, eh?
_actualdest = selectRandom _potends;
[_actualdest, "dest"] execvm "server\debug\debug_makemarker.sqf";
_pathgrp = createGroup [east, true];
_pathfinder = "O_Quadbike_01_F" createVehicle getpos (_roadarray # ((count _roadarray ) /2));// put the pf somwhere in the middle of the roadarray
createVehicleCrew _pathfinder;
driver _pathfinder setskill 1;
// ATV pathfinder is used to see which way the convoy will set off from the start point
// so we will create him and see which way he goes
sleep 10;
_pathfinder forceFollowRoad true;
[driver _pathfinder] join _pathgrp;
_pathfinder doMove (getpos _actualdest);
_starttime = diag_tickTime;
waitUntil {(diag_tickTime > (_starttime + 120)) or ( ( (count (_pathfinder nearRoads 3) > 0)) and (not( ( (_pathfinder nearRoads 3) # 0 ) in _roadarray)) )};
if (diag_tickTime > (_starttime + 120)) exitWith
	{// pathfinder has failed, delete him and restart
	diag_log format ["*** ck2 restarting because pathfinder failed"];
	_pathfinder deleteVehicleCrew (driver _pathfinder);
	deleteVehicle _pathfinder;
	execVM "server\SecondaryMissions\do_convoykill2.sqf";
	};
sleep 2;
_lastpathfinderpos = getpos _pathfinder;
_pathfinder deleteVehicleCrew (driver _pathfinder);
deleteVehicle _pathfinder;
// we know which way the pathfinder chose to go to the destination, delete him and remember his last position
sleep 2;
	{// choose the vehicle type
	if ((_foreachindex %2) isEqualTo 0) then
		{// use every other rp to try to space out the convoy
		if ((_foreachindex isEqualTo 0) or (_foreachindex isEqualTo _selectlast)) then
			{// first and last convoy vecs are escorts
			_vectype = selectRandom opforconvoyescorttypes;
			}
			else
			{
			_ctrucks = selectRandom opforconvoytrucktypes;// choose zamaks or tempests
			_vectype = selectRandom _ctrucks;
			};
		_myvec = createVehicle [_vectype, [(getpos _x) select 0, (getpos _x) select 1, 0.5 + ((getpos _x) select 2)] ,[],0, "NONE"];// create the vec slightly above terrain
		_myvec enableSimulationGlobal false;
		convoyvecs pushBack _myvec;
		};
	} foreach _roadarray;
	_condrvgrp = createGroup [east, true];
	_conmangrp = createGroup [east, true];
	{ // make the vecs face to pathfinder (or prev vec), put in crew and make an array of drivers
	createVehicleCrew _x;
	_vehCrew = [_x, _conmangrp, false, false, "O_Soldier_F"] call BIS_fnc_spawnCrew;
	_drivers pushback (driver _x);
	if (_x isKindOf "APC_Wheeled_02_base_F" or _x isKindOf "MRAP_02_base_F") then
		{// marids and ifrits are not commanded by their driver but by their gunner
		_drivers pushback (gunner _x);
		};
	_x setConvoySeparation 20;
	_x allowCrewInImmobile true;
	_x setUnloadInCombat [false, false];
	_x addEventHandler ["HandleDamage", {if (isNull (_this # 3)) then { 0; } else { _this # 2; }; }];// prevent any collision damage
	//_x forceFollowRoad true;// not using this right now, not sure it really makes any difference
	}forEach convoyvecs;

_sorteddrivers = [_drivers,[], {_x distance2D _lastpathfinderpos }, "ASCEND"] call BIS_fnc_sortBy;
// sort the drivers array into closest to the pathfinder pos
	{
	[_x] joinSilent _condrvgrp;
	if (_foreachindex isEqualTo 0) then
		{// driver nearest pathfinder (ie, front of the convoy) is leader)
			_condrvgrp selectLeader _x;
		(vehicle _x) setdir ((vehicle _x) getdir _lastpathfinderpos);// make him face the pathfinder pos
		}
		else
		{// remaning vehicles face the vehicle in front
		(vehicle _x) setdir (_x getdir (_sorteddrivers # (_foreachindex -1)));
		};
	if (_x isEqualTo (driver (vehicle _x)) ) then
		{//for ifrits and marids, gunners are in the driver group, cant disable their combat
		_x disableAI "AUTOCOMBAT";
		};
	_x assignAsCommander (vehicle _x);
	_x setskill 1;
	} forEach _sorteddrivers;
	{// unflip any vehicles and enable their sim
	_x setVectorUp (surfaceNormal getpos _x);
	_x enableSimulationGlobal true;
	} forEach convoyvecs;
_condrvgrp setFormation "COLUMN";

(vehicle leader _condrvgrp) limitSpeed 40;
_convdestwp = _condrvgrp addWaypoint [getpos _actualdest, 10];
_convdestwp setWaypointBehaviour "SAFE";
_convdestwp setWaypointType "MOVE";
diag_log format ["&&&convoy leader is %1,", name (leader _condrvgrp)];
[leader _condrvgrp, _lastpathfinderpos] spawn
	{
	params ["_vec", "_lastpathfinderpos"];
	while {(_vec distance2D _lastpathfinderpos) < 25} do
		{
			// watch for conv lead getting stuck/confused at start
			_1pos = getpos _vec;
			sleep 30;
			if ((_vec distance2D _1pos) < 10) then
				{// suspect leader stuck at start, bump him to a nearby rp with no cars near it
					diag_log format ["*** suspect leader stuck at start!"];
					_randrp = (_vec nearRoads 40) select {(_x nearEntities ["Car", 6]) isEqualTo []};
					_sortedrandrp = [_randrp, {_x distance2D _vec}, "ASCEND"] call BIS_fnc_sortBy;
					_vec setVehiclePosition [(_sortedrandrp # 0), [],0,"NONE"];
					diag_log format ["*** leader moved %1m to %2", _1pos distance2D _vec, getpos _vec];
				};
		};
			diag_log format ["*** leader moved away from start, so stopped checking for him getting stuck there"];
	};

{
	[_x] spawn
		{// spawned code to try to unstick stuck vehicles
		params ["_q"];
		while {(alive (driver _q)) and missionactive} do
			{
			private ["_pushdir"];
			private _vq = vehicle _q;
			private _pvq = getpos _vq;
			private _psq = getPosASL _vq;
			sleep 5;
			if ((abs(speed _vq) < 2) and { (alive _q) and (canMove _vq) and ((fuel _vq) > 0) and ((_vq distance2D _pvq) < 8) }) then
				{// vehicle is stuck
				if ((lineintersectssurfaces [_vq modeltoworldworld [0,0,0.2], _vq modeltoworldworld [0,8,0.2], _vq]) isEqualTo []) then
					{//push it forwards a little
					_pushdir = 10;
					}
					else
					{// if there's something in front, push backwards, not forwards
					_pushdir = -10;
					};
				_vq setVelocityModelSpace [0,_pushdir,0];
				diag_log format ["*** pushing %1 a little", name driver _q];
				};
			};
		};

} foreach _sorteddrivers;

while {true} do
	{
	sleep 4;
	diag_log "---------------------------------------------------------------------------------------";
		{
		diag_log format ["&&&%1,role %5, speed %2,groupleader %3,CMDR %4, effectiveCMDR %6", name _x, floor (speed _x), name leader _x, name (commander (_x)), assignedVehicleRole _x , name (effectiveCommander (vehicle _x))];
		} foreach (units _condrvgrp);
	};
if (true) exitWith {};
// nothing beyond here is actually executed, will be used when mission is more developed
_distanddir = [_mytarget] call tky_fnc_distanddirfromtown;
smmissionstring = format ["Do some shit at %1 and blah blah etc", _sometown getVariable "targetname"];
["<img color='#ffffff' image= 'pics\authlogo2.paa'/>",smmissionstring] remoteexec ["haz_fnc_createnotification", 0, false];
publicVariable "smmissionstring";

failtext = "Dudes. You suck texts";

while {missionactive} do
	{
	sleep 3;
	if (false) then
		{
		missionsuccess = false;
		missionactive = false;
		failtext = "You suck. Mission failed because of reasons"; publicVariable "failtext";
		};

	if (false) then
		{
		missionsuccess = true;
		missionactive = false;
		["<img color='#ffffff' image= 'pics\authlogo2.paa'/>","Dudes. You rock! Mission successful. Yey."] remoteexec ["haz_fnc_createnotification", 0, false];
		};
	};
publicVariable "failtext";
publicVariable "missionsuccess";
publicVariable "missionactive";
[_smcleanup, 60] execVM "server\Functions\fn_smcleanup.sqf";

__tky_ends

 

 
Spoiler

//fn_findroads
 #include "..\includes.sqf"
 // find a string of roads near to the supplied position, the string of roads will be as long as the supplied parameter.
 // this will rarely fail as it looks further away from the supplied position if it doesn't find anything suitable,
 // so giving it a good start position is useful.
 // will not return jungle tracks or bridges, even though they are roads,
params ["_pos", "_pcsreqd"];
_radius = 2;
_masterroadarray = [];
_nrs = (_pos nearRoads _radius) select {isOnRoad _x};
while {_nrs isEqualTo []} do
	{
		_radius = _radius * 2;
		_nrs = (_pos nearRoads _radius) select {(isOnRoad _x) and (count (roadsConnectedTo _x) > 0) and (((getposATL _x) #2) < 0.5)};
		diag_log format ["&&& %1 roads at radius %2", count _nrs, _radius];
	};
_roadarray = [];
_loopcounter = 0;
while {((count _roadarray) < _pcsreqd) and (_loopcounter < 20)} do
	{
	_road0 = selectRandom _nrs;
	_keepgoing = true;
	_loopcounter = _loopcounter + 1;
	_roadarray pushback _road0;
	while {_keepgoing} do
		{
			_rc = roadsConnectedTo _road0;
			if ((count _rc) > 0) then
				{// if there is a connected rp
				if (not (_rc # 0 in _roadarray)) then
					{// and its not already in the ra, add it
					_roadarray pushBack _rc # 0;
					_road0 = _rc # 0;// make the peice we just added the next peice to work off
					}
					else
					{
					if (((count _rc) > 1) and ((count _roadarray) > 1)) then
						{// if there is another connected rp and this isnt the first rp (so we don't go backwards)
						if (not (_rc # 1 in _roadarray)) then
							{// and its not alredy in the ra, add it
							_roadarray pushBack _rc # 1;// make the piece we just added the next piece to work off
							_road0 = _rc # 1;
							};
						};
					};
				}
				else
				{
				_keepgoing = false;
				};
			if ((count _roadarray) > _pcsreqd) then
				{
				_keepgoing = false;
				_masterroadarray pushBack _roadarray;
				};
		};
	};
reverse _roadarray;
{
[_x, str _foreachindex] execVM "server\debug\debug_makemarker.sqf";
} foreach _roadarray;
_roadarray

 

 

Other than the driving AI failing, this is pretty reliable. I need to add some safeties to the findroads function as it occasionally returns an empty array.

Share this post


Link to post
Share on other sites

Well... impressive complexity.

You could use locations (see all family commands like createLocation...)

 

I'd rather use one unique bis_fnc_spawnGroup with all optional features, then vehicles of it for speed and separation, the group itself for waypoint behavior (column, safe for roads). nothing more.

 

For stuck vehicle, i'm using EH epeContactStart. Not always working and non-completed code.

 

 

  • Thanks 1

Share this post


Link to post
Share on other sites

Complexity isn't my goal when I'm coding, I prefer to keep it simple.:) But what you see there is a number of apparently wasteful loops that are there because the order we do this stuff is crucial. I'm having to do in dynamic scripts what you do when you handle place vehicles on a road with an accuracy of a metre or so.

 

About locations; on Altis, they are (or were, last time I looked) notoriously unreliable. They have weird x and y sizes, are not in the middle of the towns and many are missing. Also, you can't setvariable on locations. So I decided to hand create a logic on all my towns, villages and airports and any other features that don't have a location. It takes a couple of hours for me to write the datafile that is parsed into the logics the mission uses.

 

When I say 'stuck' vehicles, I don't just mean those that are in contact with buildings. My script also gives a nudge to vehicles that have stopped for no known reason. Sometimes a vehicle in the convoy will just stop and these nudges are often enough to get them underway again. Also, they are good for where the road network is broken, particularly some of the bridges. These nudges can often push the vehicle over the 'break' in the road network back on the 'good' parts. My main complaint about the epe eventhandlers is that they sometimes fire multiple times and I never really worked out a tecnhnique for getting around that.

 

I will look at the spawngroup function to see if it can bring anything to the party, thanks for the suggestion. I'll gladly take more. :)

Share this post


Link to post
Share on other sites

Another progress report: Dynamic start finding, convoy formup and dynamic destination finding have all been tweaked and are working reliably enough that I don't feel the need to babysit the convoys.

 

One thing that crops up and is really breaking stuff is sometimes, one or more of the convoy vehicles break off and drive back to the convoy start point. They are still in the group and haven't lost their leader, so it's odd that they do this. I'm going to have top sense this behaviour in script and perhaps take them out of the group and put them back in, don't know if that will help. Any thoughts anyone?

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

×