diff --git a/addons/ai/CfgEventHandlers.hpp b/addons/ai/CfgEventHandlers.hpp new file mode 100644 index 0000000000..becf395052 --- /dev/null +++ b/addons/ai/CfgEventHandlers.hpp @@ -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)); + }; +}; diff --git a/addons/ai/XEH_PREP.hpp b/addons/ai/XEH_PREP.hpp new file mode 100644 index 0000000000..d3a61e3012 --- /dev/null +++ b/addons/ai/XEH_PREP.hpp @@ -0,0 +1,3 @@ +PREP(garrison); +PREP(unGarrison); +PREP(garrisonMove); diff --git a/addons/ai/XEH_postInit.sqf b/addons/ai/XEH_postInit.sqf new file mode 100644 index 0000000000..cf0ab8e49d --- /dev/null +++ b/addons/ai/XEH_postInit.sqf @@ -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 \ No newline at end of file diff --git a/addons/ai/XEH_preInit.sqf b/addons/ai/XEH_preInit.sqf new file mode 100644 index 0000000000..b47cf6628d --- /dev/null +++ b/addons/ai/XEH_preInit.sqf @@ -0,0 +1,9 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +ADDON = true; diff --git a/addons/ai/XEH_preStart.sqf b/addons/ai/XEH_preStart.sqf new file mode 100644 index 0000000000..022888575e --- /dev/null +++ b/addons/ai/XEH_preStart.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +#include "XEH_PREP.hpp" diff --git a/addons/ai/config.cpp b/addons/ai/config.cpp index a269bdcdbc..bc3b2df4ac 100644 --- a/addons/ai/config.cpp +++ b/addons/ai/config.cpp @@ -15,3 +15,4 @@ class CfgPatches { }; #include "CfgWeapons.hpp" +#include "CfgEventHandlers.hpp" diff --git a/addons/ai/functions/fnc_garrison.sqf b/addons/ai/functions/fnc_garrison.sqf new file mode 100644 index 0000000000..c303d2b90c --- /dev/null +++ b/addons/ai/functions/fnc_garrison.sqf @@ -0,0 +1,278 @@ +/* + * Author: alganthe + * Garrison function used to garrison AI inside buildings. + * + * Arguments: + * 0: The building(s) nearest this position are used + * 1: Limit the building search to those type of building + * 2: Units that will be garrisoned + * 3: Radius to fill building(s) (default: 50) + * 4: 0: even filling, 1: building by building, 2: random filling (default: 0) + * 5: True to fill building(s) from top to bottom (default: false) (note: only works with filling mode 0 and 1) + * 6: Teleport units (default: false) + + * Return Value: + * Units not garrisoned + * + * 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 diff --git a/addons/ai/functions/fnc_garrisonMove.sqf b/addons/ai/functions/fnc_garrisonMove.sqf new file mode 100644 index 0000000000..fc9f09b6ce --- /dev/null +++ b/addons/ai/functions/fnc_garrisonMove.sqf @@ -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 + * 0: Unit needing to be placed + * 1: Position the unit need to be placed at + * + * 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; +}; diff --git a/addons/ai/functions/fnc_unGarrison.sqf b/addons/ai/functions/fnc_unGarrison.sqf new file mode 100644 index 0000000000..6174a840bd --- /dev/null +++ b/addons/ai/functions/fnc_unGarrison.sqf @@ -0,0 +1,57 @@ +/* + * Author: alganthe + * Used to un-garrison units. + * + * Arguments: + * 0: Units to un-garrison + * + * 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; diff --git a/addons/ai/functions/script_component.hpp b/addons/ai/functions/script_component.hpp new file mode 100644 index 0000000000..91e8ec0809 --- /dev/null +++ b/addons/ai/functions/script_component.hpp @@ -0,0 +1 @@ +#include "\z\ace\addons\ai\script_component.hpp" diff --git a/addons/ai/script_component.hpp b/addons/ai/script_component.hpp index 9a0afcf434..3a05db6896 100644 --- a/addons/ai/script_component.hpp +++ b/addons/ai/script_component.hpp @@ -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 diff --git a/addons/ai/stringtable.xml b/addons/ai/stringtable.xml new file mode 100644 index 0000000000..3637cd3975 --- /dev/null +++ b/addons/ai/stringtable.xml @@ -0,0 +1,21 @@ + + + + + Invalid position provided. + Position invalide fourni + + + No units provided. + Aucune unité fourni + + + There aren't enough positions to place all units. + Il n'y a pas assez de positions pour placer toutes les unités + + + No building found. + Aucun bâtiment trouvé + + + \ No newline at end of file diff --git a/addons/zeus/CfgVehicles.hpp b/addons/zeus/CfgVehicles.hpp index 748e8ebf67..9c61c2f5dd 100644 --- a/addons/zeus/CfgVehicles.hpp +++ b/addons/zeus/CfgVehicles.hpp @@ -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); diff --git a/addons/zeus/XEH_PREP.hpp b/addons/zeus/XEH_PREP.hpp index 627be9846d..8c86dc1590 100644 --- a/addons/zeus/XEH_PREP.hpp +++ b/addons/zeus/XEH_PREP.hpp @@ -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); diff --git a/addons/zeus/config.cpp b/addons/zeus/config.cpp index d5ec434c34..392c012251 100644 --- a/addons/zeus/config.cpp +++ b/addons/zeus/config.cpp @@ -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); diff --git a/addons/zeus/functions/fnc_moduleGarrison.sqf b/addons/zeus/functions/fnc_moduleGarrison.sqf new file mode 100644 index 0000000000..9b8e2df0a2 --- /dev/null +++ b/addons/zeus/functions/fnc_moduleGarrison.sqf @@ -0,0 +1,62 @@ +/* + * Author: alganthe + * Module calling the garrison function. + * + * Arguments: + * 0: Module logic + * 1: Position of the module + * 2: Radius of the task + * 3: Filling mode of the garrison function + * 4: Enable or not top down filling + * + * 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; diff --git a/addons/zeus/functions/fnc_moduleUnGarrison.sqf b/addons/zeus/functions/fnc_moduleUnGarrison.sqf new file mode 100644 index 0000000000..e0c632cd3f --- /dev/null +++ b/addons/zeus/functions/fnc_moduleUnGarrison.sqf @@ -0,0 +1,51 @@ +/* + * Author: alganthe + * Un-garrison a garrisoned group. + * + * Arguments: + * 0: Module logic + * + * 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; diff --git a/addons/zeus/functions/fnc_ui_garrison.sqf b/addons/zeus/functions/fnc_ui_garrison.sqf new file mode 100644 index 0000000000..b408d8595c --- /dev/null +++ b/addons/zeus/functions/fnc_ui_garrison.sqf @@ -0,0 +1,104 @@ +/* + * Author: alganthe + * Initalises the "Garrison" zeus module display. + * + * Arguments: + * 0: Garrison controls group + * + * 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]; diff --git a/addons/zeus/stringtable.xml b/addons/zeus/stringtable.xml index 57f3baa5d7..06afe89aaa 100644 --- a/addons/zeus/stringtable.xml +++ b/addons/zeus/stringtable.xml @@ -1116,5 +1116,45 @@ Aggiungi equipaggiamento 装備を追加 + + Garrison group + Garnir zone + + + Fill from top to bottom + Remplir de haut en bas + + + Fill buildings from the highest position first + Remplir les bâtiments par la position la plus haute d'abord + + + Building filling mode + Mode de remplissage de bâtiment + + + Even filling + Remplissage égal + + + Building by building + Bâtiment par bâtiment + + + Random filling + Remplir au hasard + + + Teleport + Téléporter + + + Unit must not be a player + L'unité ne doit pas être un joueur + + + Un-garrison group + Dégarnir zone + - + \ No newline at end of file diff --git a/addons/zeus/ui/Icon_Module_Zeus_Garrison_ca.paa b/addons/zeus/ui/Icon_Module_Zeus_Garrison_ca.paa new file mode 100644 index 0000000000..8410763fd0 Binary files /dev/null and b/addons/zeus/ui/Icon_Module_Zeus_Garrison_ca.paa differ diff --git a/addons/zeus/ui/Icon_Module_Zeus_UnGarrison_ca.paa b/addons/zeus/ui/Icon_Module_Zeus_UnGarrison_ca.paa new file mode 100644 index 0000000000..387b1b2884 Binary files /dev/null and b/addons/zeus/ui/Icon_Module_Zeus_UnGarrison_ca.paa differ diff --git a/addons/zeus/ui/RscAttributes.hpp b/addons/zeus/ui/RscAttributes.hpp index 4ee2a099c6..2bb2c97d85 100644 --- a/addons/zeus/ui/RscAttributes.hpp +++ b/addons/zeus/ui/RscAttributes.hpp @@ -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));