Skip to content

Commit

Permalink
Added vehicle assault pattern
Browse files Browse the repository at this point in the history
Added Vehicle Assault pattern. Allows armored vehicles to hunt down other vehicles or certain infantry

Fixed vehicle in "ATTACK" stance getting stuck on targets
Fixed mortar attacking air targets
Fixed vehicles never calling tactics state (especially if group leader and vehicle commander were not identical)
Fixed vehicles getting "stuck" in combat dismounting
Fixed cases where doVehicleRotate would not correctly trigger

Improved balance of mortar rate of fire
Improved many cases where debug information were not assigned to vehicle commander
  • Loading branch information
nk3nny committed Feb 23, 2025
1 parent 07d2569 commit a3894d3
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 51 deletions.
5 changes: 0 additions & 5 deletions addons/danger/functions/fnc_brainForced.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ if (fleeing _unit) exitWith {
_timeout
};

// units in vehicles
if (!isNull objectParent _unit) exitWith {
_timeout + 1.5
};

// attack speed and stance
if ((currentCommand _unit) isEqualTo "ATTACK") then {
private _attackTarget = getAttackTarget _unit;
Expand Down
56 changes: 36 additions & 20 deletions addons/danger/functions/fnc_brainVehicle.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _causeArray params ["_cause", "_dangerPos", "", "_dangerCausedBy"]; // "_dangerU
_unit setVariable [QEGVAR(main,FSMDangerCauseData), _causeArray, EGVAR(main,debug_functions)];

// is it an attack?
private _attack = _cause in [DANGER_ENEMYDETECTED, DANGER_ENEMYNEAR, DANGER_HIT, DANGER_CANFIRE, DANGER_BULLETCLOSE] && {(side _dangerCausedBy) isNotEqualTo (side _unit)} && {!isNull _dangerCausedBy};
private _attack = _cause in [DANGER_ENEMYDETECTED, DANGER_ENEMYNEAR, DANGER_HIT, DANGER_CANFIRE, DANGER_BULLETCLOSE] && {(side _dangerCausedBy) isNotEqualTo (side _unit)} && {!isNull _dangerCausedBy} && {(behaviour _unit) isEqualTo "COMBAT"};

// update dangerPos if attacking. Check that the position is not too far above, or below ground.
if (_attack) then {
Expand Down Expand Up @@ -83,7 +83,7 @@ if (_artillery) exitWith {

// mortars fire rounds
private _mortarTime = _vehicle getVariable [QEGVAR(main,mortarTime), 0];
if (_attack && {_vehicle isKindOf "StaticMortar"} && {unitReady _vehicle} && {_mortarTime < time}) then {
if (_attack && {_vehicle isKindOf "StaticMortar"} && {unitReady _vehicle} && {_mortarTime < time} && {isTouchingGround _dangerCausedBy}) then {

// delay
_timeout = _timeout + 2;
Expand Down Expand Up @@ -121,7 +121,7 @@ if (_artillery) exitWith {

// execute fire command
_vehicle commandArtilleryFire [_dangerPos getPos [30 + random 80, (_dangerPos getDir _vehicle) - 10 + random 20], _shell, 1 + random 2];
_vehicle setVariable [QEGVAR(main,mortarTime), time + 24 + random 6];
_vehicle setVariable [QEGVAR(main,mortarTime), time + 24 + random 66];
_unit setVariable [QEGVAR(main,currentTask), "Mortar Fire", EGVAR(main,debug_functions)];
if (_repeatRounds) then {
[
Expand Down Expand Up @@ -152,7 +152,7 @@ if (_vehicle isKindOf "StaticWeapon") exitWith {

// get out if enemy near OR out of ammunition
if ((count (magazines _vehicle)) isEqualTo 0 || {(_unit findNearestEnemy _dangerPos) distance _vehicle < (6 + random 15)}) then {
private _vehicleCrew = (crew _vehicle);
private _vehicleCrew = crew _vehicle;
_vehicleCrew orderGetIn false;
[_unit, "Combat", "Eject"] call EFUNC(main,doCallout);
{
Expand All @@ -170,6 +170,12 @@ if (_vehicle isKindOf "StaticWeapon") exitWith {
[_timeout + random 4] + _causeArray
};

// Make leadership assessment as infantry
private _leader = leader _unit;
if (((vehicle _leader) isEqualTo _vehicle) && {_leader call FUNC(isLeader)}) then {
[_leader, _dangerCausedBy] call FUNC(tactics);
};

// update information
if (_cause in [DANGER_ENEMYNEAR, DANGER_SCREAM]) then {[_unit, _dangerCausedBy] call EFUNC(main,doShareInformation);};

Expand Down Expand Up @@ -223,16 +229,17 @@ if (_armored && {!isNull _dangerCausedBy}) exitWith {

// define enemy direction
_group setFormDir (_vehicle getDir _dangerCausedBy);
_cargo doMove _dangerPos;

// delayed unload
_unit setVariable [QEGVAR(main,currentTask), "Dismounting troops", EGVAR(main,debug_functions)];
[
{
params [["_cargo", []], ["_side", east], ["_vehicle", objNull]];
{_x action ["Eject", _vehicle];} forEach _cargo;
[selectRandom _cargo, "Combat", "Dismount"] call EFUNC(main,doCallout);
_cargo allowGetIn false;
if (EGVAR(main,debug_functions)) then {["%1 %2 unloading %3 carried troops", _side, getText (configOf _vehicle >> "displayName"), count _cargo] call EFUNC(main,debugLog);};
_vehicle doMove (getPosASL _vehicle);
},
[_cargo, side _group, _vehicle],
0.1
Expand Down Expand Up @@ -265,18 +272,32 @@ if (_armored && {!isNull _dangerCausedBy}) exitWith {
};

// tank assault
if (_attack && _slow && {(getUnitState _unit) isEqualTo "OK"}) then {
if (_attack && _slow && {(getUnitState _unit) in ["OK", "DELAY"]}) then {

// rotate
private _rotate = [_vehicle, _dangerPos] call EFUNC(main,doVehicleRotate);

// assault
if (!_rotate && {_distance < 750} && {_dangerCausedBy isKindOf "CAManBase"} && {(gunner _vehicle) call EFUNC(main,isAlive)}) then {
[
{_this call EFUNC(main,doVehicleAssault)},
[_unit, _dangerPos, _dangerCausedBy],
_delay - 1.5
] call CBA_fnc_waitAndExecute;
private _rotate = [_unit, _dangerPos] call EFUNC(main,doVehicleRotate);

// assault + vehicle assault
if (!_rotate && {_distance < 750} && {(gunner _vehicle) call EFUNC(main,isAlive)}) then {

// infantry
if ( _dangerCausedBy isKindOf "CAManBase" && { !( terrainIntersectASL [ eyePos _vehicle, eyePos _dangerCausedBy ] ) } ) exitWith {
[
{_this call EFUNC(main,doVehicleAssault)},
[_unit, _dangerPos, _dangerCausedBy],
_delay - 1.5
] call CBA_fnc_waitAndExecute;
};

// everything else -- assault!
if (
isTouchingGround _dangerCausedBy
&& { unitReady _vehicle }
&& { (driver _vehicle) call EFUNC(main,isAlive) }
&& { [_vehicle, "VIEW", vehicle _dangerCausedBy] checkVisibility [eyePos _vehicle, eyePos _dangerCausedBy] < 0.5 }
) then {
[_unit, _dangerPos, _dangerCausedBy, _distance] call EFUNC(main,doVehicleAssaultMove);
};
};
};

Expand Down Expand Up @@ -366,10 +387,5 @@ if (_vehicle isKindOf "Car_F" && {!someAmmo _vehicle}) then {
};
};

// Make leadership assessment as infantry
if (_unit call FUNC(isLeader)) then {
[_unit, _dangerCausedBy] call FUNC(tactics);
};

// end
[_timeout] + _causeArray
1 change: 1 addition & 0 deletions addons/main/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ SUBPREP(UnitAction,doSuppress);
SUBPREP(UnitAction,doUGL);

SUBPREP(VehicleAction,doVehicleAssault);
SUBPREP(VehicleAction,doVehicleAssaultMove);
SUBPREP(VehicleAction,doVehicleJink);
SUBPREP(VehicleAction,doVehicleRotate);
SUBPREP(VehicleAction,doVehicleSuppress);
Expand Down
4 changes: 2 additions & 2 deletions addons/main/functions/VehicleAction/fnc_doVehicleAssault.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ _vehicle doWatch (AGLToASL _pos);
private _suppression = [_unit, _pos] call FUNC(doVehicleSuppress);

// set task
_unit setVariable [QEGVAR(main,currentTarget), _target, EGVAR(main,debug_functions)];
_unit setVariable [QEGVAR(main,currentTask), "Vehicle Assault", EGVAR(main,debug_functions)];
_unit setVariable [QGVAR(currentTarget), _target, GVAR(debug_functions)];
_unit setVariable [QGVAR(currentTask), "Vehicle Assault", GVAR(debug_functions)];

// minor jink if no suppression possible
if (!_suppression) exitWith {[_unit, 35] call FUNC(doVehicleJink)};
Expand Down
97 changes: 97 additions & 0 deletions addons/main/functions/VehicleAction/fnc_doVehicleAssaultMove.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "script_component.hpp"
/*
* Author: nkenny
* Vehicle moves aggressively to superior position
*
* Arguments:
* 0: _unit moving <OBJECT>
* 1: dangerous position <ARRAY>
* 2: dangerous object <OBJECT>
* 3: distance to position and object <NUMBER>
*
* Return Value:
* bool
*
* Example:
* [bob, getPos angryJoe, angryJoe] call lambs_main_fnc_doVehicleAssaultMove;
*
* Public: No
*/
params ["_unit", "_pos", ["_target", objNull], ["_distance", -1]];

// settings
private _vehicle = vehicle _unit;

// distance to position
if (_distance < 0) then {_distance = _vehicle distance _pos};
if (isNull _target) then {_target = _vehicle;};

// cannot move or moving or enemy too close or too far away
if (
!canMove _vehicle
|| { (fuel _vehicle) < 0.1 }
|| { (currentCommand _vehicle) in ["MOVE", "ATTACK"] }
|| {_distance < (precision _vehicle)}
|| {_distance > 200}
) exitWith {
_vehicle doMove (getPosASL _vehicle);
false
};

private _destination = call {

// 25 meters ahead
private _typeOf = typeOf _vehicle;
private _distance = _vehicle distance _pos;
private _movePos = _vehicle getPos [50 min _distance, _vehicle getDir _pos];
_movePos = _movePos findEmptyPosition [0, 15, _typeOf];
if (_movePos isNotEqualTo [] && {[vehicle _target, "VIEW", objNull] checkVisibility [(AGLToASL _movePos) vectorAdd [0, 0, 3], AGLToASL _pos] > 0}) exitWith {
_movePos
};

// random 200 + road adjustment
_movePos = (_vehicle getPos [200 min _distance, (_vehicle getDir _pos) - 45 + random 90]) findEmptyPosition [10, 30, _typeOf];
if (_movePos isNotEqualTo [] && {[vehicle _target, "VIEW", objNull] checkVisibility [(AGLToASL _movePos) vectorAdd [0, 0, 3], AGLToASL _pos] > 0}) exitWith {

// road adjust
private _roads = _movePos nearRoads 20;
if (_roads isNotEqualTo []) then {_movePos = (ASLToAGL (getPosASL (selectRandom _roads)));};

// return
_movePos
};

// On top of
_movePos = _pos findEmptyPosition [5, 35, _typeOf];
if (_movePos isNotEqualTo []) exitWith {
_movePos
};

// none
[]
};

// check it!
if (_destination isEqualTo []) exitWith {
false
};

// set task
_unit setVariable [QGVAR(currentTarget), _destination, GVAR(debug_functions)];
_unit setVariable [QGVAR(currentTask), "Vehicle Assault Move", GVAR(debug_functions)];

// execute
_vehicle doMove _destination;

// debug
if (GVAR(debug_functions)) then {
[
"%1 assault move (%2 moves %3m | visiblity %4)",
side _unit, getText (configOf _vehicle >> "displayName"),
round (_unit distance _destination),
[vehicle _target, "VIEW", objNull] checkVisibility [(AGLToASL _destination) vectorAdd [0, 0, 5], AGLToASL _pos]
] call FUNC(debugLog);
};

// exit
true
54 changes: 30 additions & 24 deletions addons/main/functions/VehicleAction/fnc_doVehicleRotate.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,29 @@ _target = _target call CBA_fnc_getPos;

// cannot move or moving
private _vehicle = vehicle _unit;
if (!canMove _vehicle || {(currentCommand _vehicle) isEqualTo "MOVE"} || {!alive (driver _vehicle)}) exitWith {false};
if (
!canMove _vehicle
|| {(currentCommand _vehicle) isEqualTo "MOVE"}
|| {!((driver _vehicle) call FUNC(isAlive))}
|| {((vehicleMoveInfo _vehicle) select 1) in ["LEFT", "RIGHT"]}
) exitWith {
false
};

// CQB tweak -- target within 35m - look instead
if (_unit distanceSqr _target < 1225) exitWith {
_vehicle doWatch (ATLToASL _target);
false
};

_unit setVariable [QGVAR(currentTarget), _target, GVAR(debug_functions)];
_unit setVariable [QGVAR(currentTask), "Vehicle Rotate", GVAR(debug_functions)];

// within acceptable limits
if (_unit getRelDir _target < _threshold || {_unit getRelDir _target > (360-_threshold)}) exitWith {
if (_vehicle getRelDir _target < _threshold || {_vehicle getRelDir _target > (360-_threshold)}) exitWith {
false
};

_unit setVariable [QGVAR(currentTarget), _target, GVAR(debug_functions)];
_unit setVariable [QGVAR(currentTask), "Vehicle Rotate", GVAR(debug_functions)];

// move
_unit setFormDir (_unit getDir _target);
if (_vehicle isKindOf "Tank") then {
Expand All @@ -60,40 +67,39 @@ if (_vehicle isKindOf "Tank") then {
private _min = 20; // Minimum range

for "_i" from 0 to 5 do {
_pos = (_unit getPos [_min, _unit getDir _target]) findEmptyPosition [0, 2.2, typeOf _unit];
_pos = (_vehicle getPos [_min, _vehicle getDir _target]) findEmptyPosition [0, precision _vehicle, typeOf _vehicle];

// water or exit
if !(_pos isEqualTo [] || {surfaceIsWater _pos}) exitWith {};

// update
_min = _min + 15;
};
if (_pos isEqualTo []) then {_pos = _unit modelToWorldVisual [0, -100, 0]};
_unit doMove _pos;
if (_pos isEqualTo []) then {_pos = _vehicle modelToWorldVisual [0, -100, 0]};
_vehicle doMove _pos;
};


// waitUntil
[
{
params ["_unit", "_target", "_threshold"];
((_unit getRelDir _target) < _threshold || {(_unit getRelDir _target) > (360 - _threshold)})
params ["_vehicle", "_target", "_threshold"];
((_vehicle getRelDir _target) < _threshold || {(_vehicle getRelDir _target) > (360 - _threshold)})
}, {
params ["_unit", "_target"];
// check vehicle
if (canMove _unit && {(crew _unit) isNotEqualTo []}) then {

// refresh ready
(vehicle _unit) sendSimpleCommand "STOPTURNING";
(effectiveCommander _unit) doMove (_unit getPos [10, _unit getDir _target]);

// refresh formation
(group _unit) setFormDir (_unit getDir _target);
};
}, [_unit, _target, _threshold * 2], 4 + random 3,
params ["_vehicle", "_target"];
// refresh ready
_vehicle sendSimpleCommand "STOPTURNING";
_vehicle sendSimpleCommand "STOP";
_vehicle doMove (_vehicle getPos [precision _vehicle, _vehicle getDir _target]);

// refresh formation
(group _vehicle) setFormDir (_vehicle getDir _target);
}, [_vehicle, _target, _threshold * 3], 4 + random 3,
{
params ["_unit"];
(vehicle _unit) sendSimpleCommand "STOPTURNING";
params ["_vehicle", "_target"];
_vehicle doWatch (ATLToASL _target);
_vehicle sendSimpleCommand "STOPTURNING";
_vehicle doMove (getPosASL _vehicle);
}
] call CBA_fnc_waitUntilAndExecute;

Expand Down

0 comments on commit a3894d3

Please sign in to comment.