Add garrison zeus modules (#4555)

* Add garrison and un-garrison modules

* Remove unnecessary text from garrison header

* Add french translations to new strings

* Add changes requested by review

* Change pushback to pushBack

* Move garrison funcs to ai, finish headers

* Remove diag log debug

* Fix typos and header issues

* Add missing newlines

* Fix strings, Fix typos and headers

* Enable debug and disable compile cache, Add trace and comments

* Rebase before review

* Fix default case running instead of case 3

* Fix edge case related to players being in garrison group

The player would make the enableAttack checks in ungarrison and garrisonMove fail, this is now fixed.

* Fix some arrays in garrsionMove and garrison

* Relax distance checks in garrisonMove, change AI behaviour while pathing to aware

* Add debug view

* Remove unused var, fix unit pos using the wrong format

* Make debug more visually pleasing

* Change garrison debug target to a waypoint icon

* Change disableAI event to AISection, comment out doFollow in doMove EH

* Fix locality issue
This commit is contained in:
Josuan Albin 2017-10-10 18:06:37 +02:00 committed by PabstMirror
parent ed1e95106d
commit bb03f55c5c
22 changed files with 979 additions and 4 deletions

View File

@ -0,0 +1,18 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preInit));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_postInit));
};
};

3
addons/ai/XEH_PREP.hpp Normal file
View File

@ -0,0 +1,3 @@
PREP(garrison);
PREP(unGarrison);
PREP(garrisonMove);

View File

@ -0,0 +1,84 @@
#include "script_component.hpp"
[QGVAR(AISection), {
params [["_units", [], [[]]], ["_sections", [], [[]]], ["_bool", true, [true]]];
{
private _section = _x;
{
if (_bool) then {
_x enableAI _section;
} else {
_x disableAI _section;
};
LOG(format [ARR_4("XEH_postInit: %1 disableAI %2 | ID %3", _x, _section, clientOwner)]);
} foreach (_units select {local _x});
} foreach _sections
}] call CBA_fnc_addEventHandler;
[QGVAR(unGarrison), FUNC(unGarrison)] call CBA_fnc_addEventHandler;
[QGVAR(doMove), {
params ["_unitsArray"];
{
_x params ["_unit", "_pos"];
//_unit doFollow leader _unit;
_unit setDestination [_pos, "LEADER PLANNED", true];
_unit doMove _pos;
LOG(format [ARR_4("XEH_postInit: %1 doMove %2 | ID %3", _unit, _pos, clientOwner)]);
} foreach _unitsArray
}] call CBA_fnc_addEventHandler;
[QGVAR(setBehaviour), {
params ["_groupsArray", "_behaviour"];
{
_x params ["_group"];
_group setBehaviour _behaviour;
LOG(format [ARR_4("XEH_postInit: %1 setBehaviour %2 | ID %3", _group, _behaviour, clientOwner)]);
} foreach _groupsArray
}] call CBA_fnc_addEventHandler;
[QGVAR(enableAttack), {
params ["_unitsArray", "_mode"];
{
_x params ["_unit"];
_unit enableAttack _mode;
LOG(format [ARR_4("XEH_postInit: %1 enableAttack %2 | ID %3", _unit, _mode, clientOwner)]);
} foreach _unitsArray
}] call CBA_fnc_addEventHandler;
#ifdef DEBUG_MODE_FULL
addMissionEventHandler ["Draw3D", {
private _unitMoveList = missionNameSpace getVariable [QGVAR(garrison_unitMoveList), []];
{
_x params ["_unit", "_pos"];
switch true do {
case (surfaceIsWater (getPos _unit) && {surfaceIsWater _pos}) : {
for "_i" from 0 to 3 do {
drawLine3D [_unit modelToWorldVisualWorld [0,0,1], (AGLtoASL _pos), [1,0,0,1]];
};
drawIcon3D ["\a3\ui_f\data\map\groupicons\waypoint.paa", [1,0,0,1], (AGLtoASL _pos), 0.75, 0.75, 0.75];
};
case (!surfaceIsWater (getPos _unit) && {!surfaceIsWater _pos}) : {
for "_i" from 0 to 3 do {
drawLine3D [_unit modelToWorldVisual [0,0,1], _pos, [1,0,0,1]];
};
drawIcon3D ["\a3\ui_f\data\map\groupicons\waypoint.paa", [1,0,0,1], _pos, 0.75, 0.75, 0.75];
};
case (!surfaceIsWater (getPos _unit) && {surfaceIsWater _pos}) : {
for "_i" from 0 to 3 do {
drawLine3D [_unit modelToWorldVisual [0,0,1], (AGLToASL _pos), [1,0,0,1]];
};
drawIcon3D ["\a3\ui_f\data\map\groupicons\waypoint.paa", [1,0,0,1], (AGLtoASL _pos), 0.75, 0.75, 0.75];
};
case (surfaceIsWater (getPos _unit) && {!surfaceIsWater _pos}) : {
for "_i" from 0 to 3 do {
drawLine3D [_unit modelToWorldVisualWorld [0,0,1], _pos, [1,0,0,1]];
};
drawIcon3D ["\a3\ui_f\data\map\groupicons\waypoint.paa", [1,0,0,1], _pos, 0.75, 0.75, 0.75];
};
};
} foreach _unitMoveList;
}];
#endif

View File

@ -0,0 +1,9 @@
#include "script_component.hpp"
ADDON = false;
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;
ADDON = true;

View File

@ -0,0 +1,3 @@
#include "script_component.hpp"
#include "XEH_PREP.hpp"

View File

@ -15,3 +15,4 @@ class CfgPatches {
};
#include "CfgWeapons.hpp"
#include "CfgEventHandlers.hpp"

View File

@ -0,0 +1,278 @@
/*
* Author: alganthe
* Garrison function used to garrison AI inside buildings.
*
* Arguments:
* 0: The building(s) nearest this position are used <POSITION>
* 1: Limit the building search to those type of building <ARRAY>
* 2: Units that will be garrisoned <ARRAY>
* 3: Radius to fill building(s) <SCALAR> (default: 50)
* 4: 0: even filling, 1: building by building, 2: random filling <SCALAR> (default: 0)
* 5: True to fill building(s) from top to bottom <BOOL> (default: false) (note: only works with filling mode 0 and 1)
* 6: Teleport units <BOOL> (default: false)
* Return Value:
* Units not garrisoned <ARRAY>
*
* Example:
* [position, nil, [unit1, unit2, unit3, unitN], 200, 1, false, false] call ace_ai_fnc_garrison
*
* Public: Yes
*/
#include "script_component.hpp"
params [["_startingPos",[0,0,0], [[]], 3], ["_buildingTypes", ["Building"], [[]]], ["_unitsArray", [], [[]]], ["_fillingRadius", 50, [0]], ["_fillingType", 0, [0]], ["_topDownFilling", false, [true]], ["_teleport", false, [true]]];
TRACE_6("fnc_garrison: Start",_startingPos,_buldingTypes,count _unitsArray,_fillingRadius,_fillingTYpe,_topDownFilling);
_unitsArray = _unitsArray select {alive _x && {!isPlayer _x}};
if (_startingPos isEqualTo [0,0,0]) exitWith {
TRACE_1("fnc_garrison: StartingPos error",_startingPos);
[LSTRING(GarrisonInvalidPosition)] call EFUNC(common,displayTextStructured);
};
if (count _unitsArray == 0 || {isNull (_unitsArray select 0)}) exitWith {
TRACE_1("fnc_garrison: Units error",_unitsArray);
[LSTRING(GarrisonNoUnits)] call EFUNC(common,displayTextStructured);
};
private _buildings = nearestObjects [_startingPos, _buildingTypes, ([_fillingRadius, 50] select (_fillingRadius < 50))];
if (_fillingRadius >= 50) then {
_buildings = [_buildings] call CBA_fnc_shuffle;
};
if (count _buildings == 0) exitWith {
TRACE_1("fnc_garrison: Building error",_buildings);
[LSTRING(GarrisonNoBuilding)] call EFUNC(common,displayTextStructured);
};
private _buildingsIndex = [];
if (_topDownFilling) then {
{
private _buildingPos = _x buildingPos -1;
// Those reverse are necessary, as dumb as it is there's no better way to sort those subarrays in sqf
{
reverse _x;
} foreach _buildingPos;
_buildingPos sort false;
{
reverse _x;
} foreach _buildingPos;
_buildingsIndex pushBack _buildingPos;
} foreach _buildings;
} else {
{
_buildingsIndex pushBack (_x buildingPos -1);
} foreach _buildings;
};
// Remove buildings without positions
{
_buildingsIndex deleteAt (_buildingsIndex find _x);
} foreach (_buildingsIndex select {count _x == 0});
//Remove positions units are already pathing to
_buildingsIndex = _buildingsIndex apply {
private _testedBuilding = _x;
_testedBuilding select {
private _testedPos = _x;
(({(_x select 1) isEqualTo _testedPos} count (missionNameSpace getVariable [QGVAR(garrison_unitMoveList), []])) == 0)
}
};
// Warn the user that there's not enough positions to place all units
private _count = 0;
{_count = _count + count _x} foreach _buildingsIndex;
if ( (count _unitsArray) - _count > 0) then {
TRACE_4("fnc_garrison: Not enough spots to place all units",_unitsArray,count _unitsArray,_count,((count _unitsArray) - _count > 0));
[LSTRING(GarrisonNotEnoughPos)] call EFUNC(common,displayTextStructured);
};
private _placedUnits = [];
private _unitMoveList = [];
// Force all units to un-garrison
[QGVAR(unGarrison), [_unitsArray], _unitsArray] call CBA_fnc_targetEvent;
private _fnc_comparePos = {
params ["_nearestUnits", "_pos"];
({
if (surfaceIsWater getPos _x) then {
floor ((getPosASL _x) select 2) == floor ((AGLtoASL _pos) select 2)
} else {
floor ((getPosATL _x) select 2) == floor (_pos select 2)
};
} count _nearestUnits) > 0
};
// Do the placement
switch (_fillingType) do {
// Even filling
case 0: {
while {count _unitsArray > 0} do {
if (count _buildingsIndex == 0) exitWith {};
private _building = _buildingsIndex select 0;
if (_building isEqualTo []) then {
LOG(format [ARR_2("fnc_garrison: Empty building array | removing building from buildingsIndex | %1 buildings remaining",count _buildingsIndex)]);
_buildingsIndex deleteAt 0;
} else {
private _pos = _building select 0;
private _nearestUnits = (_pos nearEntities ["CAManBase", 2]);
LOG(format [ARR_3("fnc_garrison: Unit detection | %1 units nearby | %2 units within height",count _nearestUnits, {floor ((getPos _x) select 2) == floor (_pos select 2)} count _nearestUnits)]);
if (count _nearestUnits > 0 && {[_nearestUnits, _pos] call _fnc_comparePos}) then {
LOG(format [ARR_2("fnc_garrison: Unit present | removing position | %1 positions remaining for this building",count (_buildingsIndex select (_buildingsIndex find _building)) - 1)]);
_buildingsIndex set [0, _building - [_pos]];
} else {
private _unit = _unitsArray select 0;
private _posSurface = surfaceIsWater _pos;
if (_teleport) then {
doStop _unit;
if (_posSurface) then {
_unit setPosASL (AGLtoASL _pos);
} else {
_unit setPosATL _pos;
};
} else {
_unitMoveList pushBack [_unit,[_pos, AGLToASL _pos] select (_posSurface)];
};
_placedUnits pushBack _unit;
_unitsArray deleteAt (_unitsArray find _unit);
_building deleteAt 0;
_buildingsIndex deleteAt 0;
_buildingsIndex pushBackUnique _building;
_unit setVariable [QGVAR(garrisonned), true, true];
};
};
};
};
// Building by building
case 1: {
while {count _unitsArray > 0} do {
if (count _buildingsIndex == 0) exitWith {};
private _building = _buildingsIndex select 0;
if (_building isEqualTo []) then {
LOG(format [ARR_2("fnc_garrison: empty building array | removing building from buildingsIndex | %1 buildings remaining",count _buildingsIndex)]);
_buildingsIndex deleteAt 0;
} else {
private _pos = _building select 0;
private _nearestUnits = (_pos nearEntities ["CAManBase", 2]);
LOG(format [ARR_3("fnc_garrison: Unit detection | %1 units nearby | %2 units within height",count _nearestUnits, {floor ((getPos _x) select 2) == floor (_pos select 2)} count _nearestUnits)]);
if (count _nearestUnits > 0 && {[_nearestUnits, _pos] call _fnc_comparePos}) then {
LOG(format [ARR_2("fnc_garrison: Unit present | removing position | %1 positions remaining for this building",count (_buildingsIndex select (_buildingsIndex find _building)) - 1)]);
_buildingsIndex set [0, _building - [_pos]];
} else {
private _unit = _unitsArray select 0;
private _posSurface = surfaceIsWater _pos;
if (_teleport) then {
doStop _unit;
if (_posSurface) then {
_unit setPosASL (AGLtoASL _pos);
} else {
_unit setPosATL _pos;
};
} else {
_unitMoveList pushBack [_unit,[_pos, AGLToASL _pos] select (_posSurface)];
};
_placedUnits pushBack _unit;
_unitsArray deleteAt (_unitsArray find _unit);
_buildingsIndex set [0, _building - [_pos]];
_unit setVariable [QGVAR(garrisonned), true, true];
};
};
};
};
// Random
case 2: {
while {count _unitsArray > 0} do {
if (count _buildingsIndex == 0) exitWith {};
private _building = selectRandom _buildingsIndex;
if (_building isEqualTo []) then {
LOG(format [ARR_2("fnc_garrison: empty building array | removing building from buildingsIndex | %1 buildings remaining",count _buildingsIndex)]);
_buildingsIndex deleteAt (_buildingsIndex find _building);
} else {
private _pos = selectRandom _building;
private _nearestUnits = (_pos nearEntities ["CAManBase", 2]);
LOG(format [ARR_3("fnc_garrison: Unit detection | %1 units nearby | %2 units within height",count _nearestUnits, {floor ((getPos _x) select 2) == floor (_pos select 2)} count _nearestUnits)]);
if (count _nearestUnits > 0 && {[_nearestUnits, _pos] call _fnc_comparePos}) then {
LOG(format [ARR_2("fnc_garrison: Unit present | removing position | %1 positions remaining for this building",count (_buildingsIndex select (_buildingsIndex find _building)) - 1)]);
_buildingsIndex set [(_buildingsIndex find _building), _building - [_pos]];
} else {
private _unit = _unitsArray select 0;
private _posSurface = surfaceIsWater _pos;
if (_teleport) then {
doStop _unit;
if (_posSurface) then {
_unit setPosASL (AGLtoASL _pos);
} else {
_unit setPosATL _pos;
};
} else {
_unitMoveList pushBack [_unit,[_pos, AGLToASL _pos] select (_posSurface)];
};
_placedUnits pushBack _unit;
_unitsArray deleteAt (_unitsArray find _unit);
_buildingsIndex set [(_buildingsIndex find _building), _building - [_pos]];
_unit setVariable [QGVAR(garrisonned), true, true];
};
};
};
};
};
TRACE_1(format [ARR_2("fnc_garrison: while loop ended | %1 units ready to be treated by PFH",count _unitMoveList)], _teleport);
// Update the unit list and remove duplicate positions and units
private _garrison_unitMoveList = missionNameSpace getVariable [QGVAR(garrison_unitMoveList), []];
_garrison_unitMoveList = _garrison_unitMoveList select {
_x params ["_testedUnit", "_testedPos"];
({(_x select 0) isEqualTo _testedUnit} count _unitMoveList == 0)
};
_garrison_unitMoveList append _unitMoveList;
missionNameSpace setVariable [QGVAR(garrison_unitMoveList), _garrison_unitMoveList, true];
if (_teleport) then {
[QGVAR(AISection), [_placedUnits, ["PATH"], false], _placedUnits] call CBA_fnc_targetEvent;
} else {
[_unitMoveList] call FUNC(garrisonMove);
};
TRACE_1(format [ARR_3("fnc_garrison: End | %1 units left | %2 buildings left", count _unitsArray, count _buildingsIndex)], _unitsArray);
_unitsArray

View File

@ -0,0 +1,149 @@
/*
* Author: alganthe
* Internal function used by ace_ai_fnc_garrison to make the units move to the positions it picked.
*
* Arguments:
* 0: Array of arrays <ARRAY>
* 0: Unit needing to be placed <UNIT>
* 1: Position the unit need to be placed at <POSITION>
*
* Return Value:
* Nothing
*
* Example:
* [ [unit1, [10, 10, 10]], [unit2, [30, 30, 30]], [unitN, getPos player] ] call ace_ai_fnc_garrisonMove
*
* Public: No
*/
#include "script_component.hpp"
params [ ["_unitMoveList", nil, [[]]] ];
if (isNil "_unitMoveList") exitWith {};
// Start initial movement
private _unitMoveListUnits = (_unitMoveList apply {_x select 0});
[QGVAR(setBehaviour), [(_unitMoveListUnits select {leader _x == _x}), "AWARE"], _unitMoveListUnits] call CBA_fnc_targetEvent;
[QGVAR(AISection), [_unitMoveListUnits, ["FSM"], false], _unitMoveListUnits] call CBA_fnc_targetEvent;
[QGVAR(doMove), [_unitMoveList], _unitMoveListUnits] call CBA_fnc_targetEvent;
[QGVAR(enableAttack), [_unitMoveListUnits select {leader _x == _x}, false], _unitMoveListUnits] call CBA_fnc_targetEvent;
{
_x setVariable [QGVAR(garrisonMove_failSafe), nil, true];
_x setVariable [QGVAR(garrisonMove_unitPosMemory), nil, true];
} foreach _unitMoveListUnits;
// Avoid duplicate PFHs
if (isNil QGVAR(garrison_moveUnitPFH)) then {
missionNameSpace setVariable [QGVAR(garrison_moveUnitPFH), true, true];
// PFH checking if the units have reached their destination
[{
params ["_args", "_pfhID"];
private _unitMoveList = missionNameSpace getVariable [QGVAR(garrison_unitMoveList), []];
// End PFH if all units are placed / unable to reach position
if (_unitMoveList isEqualTo []) then {
missionNameSpace setVariable [QGVAR(garrison_moveUnitPFH), nil, true];
LOG("garrisonMove PFH: PFH finished it's job | deleting PFH");
_pfhID call CBA_fnc_removePerFrameHandler;
} else {
{
_x params ["_unit", "_pos"];
// Check if unit is alive or even existing
if (!alive _unit) then {
_unitMoveList deleteAt (_unitMoveList find _x);
LOG(format [ARR_2("garrisonMove PFH: unit dead or deleted | %1 units left", count _unitMoveList)]);
} else {
private _unitPos = getPos _unit;
if (surfaceisWater _unitPos) then {
_unitPos = getPosASL _unit;
} else {
_unitPos = getPosATL _unit;
};
if (unitReady _unit) then {
// Check for distance, doMove and AI are moody and may stop for no reason, within 6 meters and ready should be fine
if (_unitPos distance _pos < 3) then {
_unit setVariable [QGVAR(garrisonMove_failSafe), nil, true];
_unit setVariable [QGVAR(garrisonMove_unitPosMemory), nil, true];
_unit setVariable [QGVAR(garrisonned), true, true];
_unitMoveList deleteAt (_unitMoveList find _x);
[QGVAR(AISection), [[_unit], ["PATH"], false], _unit] call CBA_fnc_targetEvent;
[QGVAR(AISection), [[_unit], ["FSM"], true], _unit] call CBA_fnc_targetEvent;
if ({(_x select 0) in units _unit && {!isPlayer (_x select 0)}} count _unitMoveList == 0) then {
[QGVAR(enableAttack), [[_unit], true], _unit] call CBA_fnc_targetEvent;
};
LOG(format [ARR_2("garrisonMove PFH: unit in position | %1 units left", count _unitMoveList)]);
} else {
// Tell the unit to move if an order wasn't given within 30s, avoid doMove spam
(_unit getVariable [QGVAR(garrisonMove_failSafe), [CBA_missionTime, 5]]) params ["_failSafeTimer", "_failSafeRemainingAttemps"];
if (_failSafeTimer <= CBA_missionTime) then {
if (_failSafeRemainingAttemps == 0 ) then {
_unit setVariable [QGVAR(garrisonMove_failSafe), nil, true];
_unit setVariable [QGVAR(garrisonMove_unitPosMemory), nil, true];
[QGVAR(unGarrison), [[_unit]], _unit] call CBA_fnc_targetEvent;
_unitMoveList deleteAt (_unitMoveList find _x);
LOG("garrisonMove PFH unitReady: all moving commands failed | restoring AI capabilities");
} else {
_unit setVariable [QGVAR(garrisonMove_failSafe), [_failSafeTimer + 15, _failSafeRemainingAttemps - 1]];
[QGVAR(doMove), [[[_unit, _pos]]], _unit] call CBA_fnc_targetEvent;
LOG("garrisonMove PFH unitReady: unit not close enough | Sending another doMove command");
};
};
};
} else {
(_unit getVariable [QGVAR(garrisonMove_unitPosMemory), [CBA_missionTime, [0,0,0]]]) params ["_unitPosTimer", "_unitOldPos"];
// AI may sometimes not be able to report unitReady, this is to avoid the PFH running forever
switch true do {
case ((_unitPosTimer + 15) < CBA_missionTime && {(_unitPos distance _pos) < 3}) : {
TRACE_1("case 1",_unit);
_unit setVariable [QGVAR(garrisonMove_failSafe), nil, true];
_unit setVariable [QGVAR(garrisonMove_unitPosMemory), nil, true];
_unit setVariable [QGVAR(garrisonned), true, true];
_unitMoveList deleteAt (_unitMoveList find _x);
[QGVAR(AISection), [[_unit], ["PATH"], false], _unit] call CBA_fnc_targetEvent;
[QGVAR(AISection), [[_unit], ["FSM"], true], _unit] call CBA_fnc_targetEvent;
if ({(_x select 0) in units _unit && {!isPlayer (_x select 0)}} count _unitMoveList == 0) then {
[QGVAR(enableAttack), [[_unit], true], _unit] call CBA_fnc_targetEvent;
};
LOG(format [ARR_2("garrisonMove PFH unitNotReady: unit in position | %1 units left", count _unitMoveList)]);
};
case ((_unitPosTimer + 15) < CBA_missionTime && {_unitOldPos distance _unitPos < 0.5}) : {
TRACE_3("case 2",_unit, ((_unitPosTimer + 15) < CBA_missionTime), (_unitOldPos distance _unitPos < 0.5));
_unit setVariable [QGVAR(garrisonMove_failSafe), nil, true];
_unit setVariable [QGVAR(garrisonMove_unitPosMemory), nil, true];
[QGVAR(unGarrison), [[_unit]], _unit] call CBA_fnc_targetEvent;
_unitMoveList deleteAt (_unitMoveList find _x);
LOG("garrisonMove PFH unitNotReady: all moving commands failed | restoring AI capabilities");
};
case (_unitOldPos distance _unitPos < 0.5) : {};
default {
_unit setVariable [QGVAR(garrisonMove_unitPosMemory), [CBA_missionTime, _unitPos]];
};
};
};
};
} foreach _unitMoveList;
missionNameSpace setVariable [QGVAR(garrison_unitMoveList), _unitMoveList, true];
};
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
};

View File

@ -0,0 +1,57 @@
/*
* Author: alganthe
* Used to un-garrison units.
*
* Arguments:
* 0: Units to un-garrison <ARRAY>
*
* Return Value:
* None
*
* Example:
* [unit1, unit2, unit3] call ace_ai_fnc_unGarrison
*
* Public: Yes
*
*/
#include "script_component.hpp"
params [["_units", [], [[]]]];
_units = _units select {local _x};
{
if (!isPlayer _x && {local _x}) then {
_x enableAI "PATH";
_x enableAI "FSM";
private _leader = leader _x;
TRACE_3("fnc_ungarrison: unit and leader",_x , _leader, (_leader == _x));
_x setVariable [QGVAR(garrisonned), false, true];
if (_leader != _x) then {
doStop _x;
_x doFollow _leader;
} else {
_x doMove ((nearestBuilding (getPos _x)) buildingExit 0);
};
private _fnc_countGarrisonnedUnits = {
params ["_unit", "_bool"];
if (_bool) then {
({(_x getVariable [QGVAR(garrisonned), false]) && {!isPlayer _x}} count units _unit)
} else {
({!(_x getVariable [QGVAR(garrisonned), false]) && {!isPlayer _x}} count units _unit)
};
};
if ([_x, true] call _fnc_countGarrisonnedUnits == ({!isPlayer _x} count (units _x)) - 1 || {[_x, false] call _fnc_countGarrisonnedUnits == {!isPlayer _x} count (units _x)}) then {
LOG("fnc_ungarrison: enableAttack true");
(group _x) enableAttack true;
};
};
} foreach _units;

View File

@ -0,0 +1 @@
#include "\z\ace\addons\ai\script_component.hpp"

View File

@ -2,8 +2,8 @@
#define COMPONENT_BEAUTIFIED AI
#include "\z\ace\addons\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
//#define DEBUG_MODE_FULL
//#define DISABLE_COMPILE_CACHE
// #define ENABLE_PERFORMANCE_COUNTERS
#ifdef DEBUG_ENABLED_AI

21
addons/ai/stringtable.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project name="ACE">
<Package name="ai">
<Key ID="STR_ACE_ai_GarrisonInvalidPosition">
<English>Invalid position provided.</English>
<French>Position invalide fourni</French>
</Key>
<Key ID="STR_ACE_ai_GarrisonNoUnits">
<English>No units provided.</English>
<French>Aucune unité fourni</French>
</Key>
<Key ID="STR_ACE_ai_GarrisonNotEnoughPos">
<English>There aren't enough positions to place all units.</English>
<French>Il n'y a pas assez de positions pour placer toutes les unités</French>
</Key>
<Key ID="STR_ACE_ai_GarrisonNoBuilding">
<English>No building found.</English>
<French>Aucun bâtiment trouvé</French>
</Key>
</Package>
</Project>

View File

@ -221,6 +221,20 @@ class CfgVehicles {
function = QFUNC(moduleUnconscious);
icon = QPATHTOF(UI\Icon_Module_Zeus_Unconscious_ca.paa);
};
class GVAR(moduleGarrison): GVAR(moduleBase) {
curatorCanAttach = 1;
category = QGVAR(AI);
displayName = CSTRING(ModuleGarrison_DisplayName);
curatorInfoType = QGVAR(RscGarrison);
icon = QPATHTOF(UI\Icon_Module_Zeus_Garrison_ca.paa);
};
class GVAR(moduleUnGarrison): GVAR(moduleBase) {
curatorCanAttach = 1;
category = QGVAR(AI);
displayName = CSTRING(ModuleUnGarrison_DisplayName);
function = QFUNC(moduleUnGarrison);
icon = QPATHTOF(UI\Icon_Module_Zeus_UnGarrison_ca.paa);
};
class GVAR(moduleToggleNvg): GVAR(moduleBase) {
curatorCanAttach = 1;
category = QGVAR(AI);

View File

@ -11,6 +11,7 @@ PREP(moduleAddSpareTrack);
PREP(moduleAddSpareWheel);
PREP(moduleAddOrRemoveFRIES);
PREP(moduleCaptive);
PREP(moduleGarrison);
PREP(moduleGlobalSetSkill);
PREP(moduleGroupSide);
PREP(moduleLoadIntoCargo);
@ -27,12 +28,14 @@ PREP(moduleTeleportPlayers);
PREP(moduleToggleFlashlight);
PREP(moduleToggleNvg);
PREP(moduleUnconscious);
PREP(moduleUnGarrison);
PREP(moduleZeusSettings);
PREP(showMessage);
PREP(ui_attributeCargo);
//PREP(ui_attributePosition);
PREP(ui_attributeRadius);
PREP(ui_defendArea);
PREP(ui_garrison);
PREP(ui_editableObjects);
PREP(ui_globalSetSkill);
PREP(ui_groupSide);

View File

@ -12,6 +12,8 @@ class CfgPatches {
QGVAR(modulePatrolArea),
QGVAR(moduleSearchArea),
QGVAR(moduleSearchNearby),
QGVAR(moduleGarrison),
QGVAR(moduleUnGarrison),
QGVAR(moduleTeleportPlayers),
QGVAR(moduleToggleNvg),
QGVAR(moduleToggleFlashlight),
@ -23,7 +25,7 @@ class CfgPatches {
};
weapons[] = {};
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"ace_common"};
requiredAddons[] = {"ace_common", "ace_ai"};
author = ECSTRING(common,ACETeam);
authors[] = {"SilentSpike"};
url = ECSTRING(main,URL);

View File

@ -0,0 +1,62 @@
/*
* Author: alganthe
* Module calling the garrison function.
*
* Arguments:
* 0: Module logic <OBJECT>
* 1: Position of the module <POSITION>
* 2: Radius of the task <NUMBER>
* 3: Filling mode of the garrison function <NUMBER>
* 4: Enable or not top down filling <BOOL>
*
* Return Value:
* None
*
* Example:
* [LOGIC, [2203.64, 2281.47, 1], 20, 2, true] call ace_zeus_fnc_moduleUngarrison
*
* Public: No
*/
#include "script_component.hpp"
params ["_logic", "_pos", "_radius" ,"_mode" , "_topDownMode", "_teleport"];
private _unit = (attachedTo _logic);
private _building = nearestBuilding (getPosASL _unit);
// Handles errors
scopeName "Main";
private _fnc_errorAndClose = {
params ["_msg"];
deleteVehicle _logic;
[_msg] call EFUNC(common,displayTextStructured);
breakOut "Main";
};
switch (false) do {
case !(isNull _unit): {
[LSTRING(NothingSelected)] call _fnc_errorAndClose;
};
case (_unit isKindOf "CAManBase"): {
[LSTRING(OnlyInfantry)] call _fnc_errorAndClose;
};
case (alive _unit): {
[LSTRING(OnlyAlive)] call _fnc_errorAndClose;
};
case (_unit distance _building < 500): {
[LSTRING(BuildingTooFar)] call _fnc_errorAndClose;
};
};
private _units = units _unit;
// Make sure all units are disembarked
{
if (vehicle _x != _x) then {
moveOut _x;
};
} forEach _units;
[_pos, ["Building"], _units, _radius, _mode, _topDownMode, _teleport] call EFUNC(ai,garrison);
deleteVehicle _logic;

View File

@ -0,0 +1,51 @@
/*
* Author: alganthe
* Un-garrison a garrisoned group.
*
* Arguments:
* 0: Module logic <OBJECT>
*
* Return Value:
* None
*
* Example:
* [LOGIC] call ace_zeus_fnc_moduleUngarrison
*
* Public: No
*/
#include "script_component.hpp"
params ["_logic"];
if (!local _logic) exitWith {}; // Module is global
scopeName "Main";
private _fnc_errorAndClose = {
params ["_msg"];
deleteVehicle _logic;
[_msg] call EFUNC(common,displayTextStructured);
breakOut "Main";
};
private _unit = effectiveCommander (attachedTo _logic);
switch (false) do {
case !(isNull _unit): {
[LSTRING(NothingSelected)] call _fnc_errorAndClose;
};
case (_unit isKindOf "CAManBase"): {
[LSTRING(OnlyInfantry)] call _fnc_errorAndClose;
};
case (alive _unit): {
[LSTRING(OnlyAlive)] call _fnc_errorAndClose;
};
case !(isPlayer _unit): {
[LSTRING(OnlyNonPlayer)] call _fnc_errorAndClose;
};
};
private _units = units _unit;
[QEGVAR(ai,unGarrison), [_units], _units] call CBA_fnc_targetEvent;
deleteVehicle _logic;

View File

@ -0,0 +1,104 @@
/*
* Author: alganthe
* Initalises the "Garrison" zeus module display.
*
* Arguments:
* 0: Garrison controls group <CONTROL>
*
* Return Value:
* None
*
* Example:
* onSetFocus = "_this call ace_zeus_fnc_ui_garrison"
*
* Public: No
*/
#include "script_component.hpp"
disableSerialization;
params ["_control"];
//Generic Init:
private _display = ctrlparent _control;
private _ctrlButtonOK = _display displayctrl 1; //IDC_OK
private _logic = GETMVAR(BIS_fnc_initCuratorAttributes_target,objnull);
TRACE_1("logicObject",_logic);
_control ctrlRemoveAllEventHandlers "setFocus";
// Handles errors
private _unit = effectiveCommander (attachedTo _logic);
scopeName "Main";
private _fnc_errorAndClose = {
params ["_msg"];
_display closeDisplay 0;
deleteVehicle _logic;
[_msg] call FUNC(showMessage);
breakOut "Main";
};
switch (false) do {
case !(isNull _unit): {
[LSTRING(NothingSelected)] call _fnc_errorAndClose;
};
case (_unit isKindOf "CAManBase"): {
[LSTRING(OnlyInfantry)] call _fnc_errorAndClose;
};
case (alive _unit): {
[LSTRING(OnlyAlive)] call _fnc_errorAndClose;
};
case !(isPlayer _unit): {
[LSTRING(OnlyNonPlayer)] call _fnc_errorAndClose;
};
};
//Specific on-load stuff:
private _listbox = _display displayCtrl 73063;
{
_listbox lbSetValue [_listbox lbAdd (_x select 0), _x select 1];
} forEach [
[localize LSTRING(ModuleGarrison_FillingModeEven), 0],
[localize LSTRING(ModuleGarrison_FillingModeBuilding), 1],
[localize LSTRING(ModuleGarrison_FillingModeRandom), 2]
];
_listbox lbSetCurSel 0;
//Specific on-load stuff:
(_display displayCtrl 73061) cbSetChecked (_logic getVariable ["TopDownFilling",false]);
(_display displayCtrl 73062) cbSetChecked (_logic getVariable ["Teleport",false]);
private _fnc_onUnload = {
params ["_display"];
private _logic = GETMVAR(BIS_fnc_initCuratorAttributes_target,objnull);
if (isNull _logic) exitWith {};
deleteVehicle _logic;
};
private _fnc_onConfirm = {
params [["_ctrlButtonOK", controlNull, [controlNull]]];
private _display = ctrlparent _ctrlButtonOK;
if (isNull _display) exitWith {};
private _logic = GETMVAR(BIS_fnc_initCuratorAttributes_target,objnull);
if (isNull _logic) exitWith {};
private _lb = _display displayCtrl 73063;
private _radius = GETVAR(_display,GVAR(radius),50);
private _position = GETVAR(_display,GVAR(position),getPos _logic);
private _mode = _lb lbValue (lbCurSel _lb);
private _TopDownFilling = cbChecked (_display displayCtrl 73061);
private _teleport = cbChecked (_display displayCtrl 73062);
[_logic, _position ,_radius, _mode, _TopDownFilling, _teleport] call FUNC(moduleGarrison);
};
_display displayAddEventHandler ["unload", _fnc_onUnload];
_ctrlButtonOK ctrlAddEventHandler ["buttonclick", _fnc_onConfirm];

View File

@ -1116,5 +1116,45 @@
<Italian>Aggiungi equipaggiamento</Italian>
<Japanese>装備を追加</Japanese>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_DisplayName">
<English>Garrison group</English>
<French>Garnir zone</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_TopDownFillingText">
<English>Fill from top to bottom</English>
<French>Remplir de haut en bas</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_TopDownFillingTooltip">
<English>Fill buildings from the highest position first</English>
<French>Remplir les bâtiments par la position la plus haute d'abord</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_FillingModeText">
<English>Building filling mode</English>
<French>Mode de remplissage de bâtiment</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_FillingModeEven">
<English>Even filling</English>
<French>Remplissage égal</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_FillingModeBuilding">
<English>Building by building</English>
<French>Bâtiment par bâtiment</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_FillingModeRandom">
<English>Random filling</English>
<French>Remplir au hasard</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleGarrison_TeleportText">
<English>Teleport</English>
<French>Téléporter</French>
</Key>
<Key ID="STR_ACE_Zeus_OnlyNonPlayer">
<English>Unit must not be a player</English>
<French>L'unité ne doit pas être un joueur</French>
</Key>
<Key ID="STR_ACE_Zeus_ModuleUnGarrison_DisplayName">
<English>Un-garrison group</English>
<French>Dégarnir zone</French>
</Key>
</Package>
</Project>
</Project>

Binary file not shown.

Binary file not shown.

View File

@ -473,6 +473,81 @@ class RscDisplayAttributesVehicleEmpty: RscDisplayAttributes {
};
};
class GVAR(RscGarrison): RscDisplayAttributes {
onLoad = QUOTE([ARR_3('onLoad', _this, QUOTE(QGVAR(RscGarrison)))] call FUNC(zeusAttributes));
onUnload = QUOTE([ARR_3('onUnload', _this, QUOTE(QGVAR(RscGarrison)))] call FUNC(zeusAttributes));
class Controls: Controls {
class Background: Background {};
class Title: Title {};
class Content: Content {
class Controls {
class Garrison: RscControlsGroupNoScrollbars {
onSetFocus = QUOTE(_this call FUNC(ui_garrison));
idc = 73060;
x = 0;
y = 0;
w = W_PART(26);
h = H_PART(8.5);
class controls {
class radius: GVAR(AttributeRadius) {};
class TopDownFillingTitle: Title {
idc = -1;
text = CSTRING(ModuleGarrison_TopDownFillingText);
toolTip = CSTRING(ModuleGarrison_TopDownFillingTooltip);
x = 0;
y = H_PART(1.2);
w = W_PART(10);
h = H_PART(1);
colorBackground[] = {0,0,0,0.5};
};
class TopDownFilling: RscCheckBox {
idc = 73061;
x = W_PART(10.1);
y = H_PART(1.2);
w = W_PART(1);
h = H_PART(1);
};
class TeleportTitle: Title {
idc = -1;
text = CSTRING(ModuleGarrison_TeleportText);
x = 0;
y = H_PART(2.3);
w = W_PART(10);
h = H_PART(1);
colorBackground[] = {0,0,0,0.5};
};
class Teleport: RscCheckBox {
idc = 73062;
x = W_PART(10.1);
y = H_PART(2.3);
w = W_PART(1);
h = H_PART(1);
};
class FillingModeTitle: RscText {
idc = -1;
text = CSTRING(ModuleGarrison_FillingModeText);
x = 0;
y = H_PART(3.5);
w = W_PART(26);
h = H_PART(1);
colorBackground[] = {0,0,0,0.5};
};
class FillingMode: RscListbox {
idc = 73063;
x = 0;
y = H_PART(4.5);
w = W_PART(26);
h = H_PART(4);
};
};
};
};
};
class ButtonOK: ButtonOK {};
class ButtonCancel: ButtonCancel {};
};
};
class GVAR(RscToggleNvg): RscDisplayAttributes {
onLoad = QUOTE([ARR_3('onLoad', _this, QUOTE(QGVAR(RscToggleNvg)))] call FUNC(zeusAttributes));
onUnload = QUOTE([ARR_3('onUnload', _this, QUOTE(QGVAR(RscToggleNvg)))] call FUNC(zeusAttributes));