Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Headless - Improve group transfer and add API #9874

Merged
merged 8 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions addons/headless/XEH_PREP.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ACEX_PREP(blacklist);
ACEX_PREP(endMissionNoPlayers);
ACEX_PREP(handleConnectHC);
ACEX_PREP(handleDisconnect);
Expand Down
15 changes: 15 additions & 0 deletions addons/headless/XEH_postInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
};
// Add disconnect EH
addMissionEventHandler ["HandleDisconnect", {call FUNC(handleDisconnect)}];

[QGVAR(transferGroupsRebalance), {
params ["_groups", "_owner", "_rebalance"];

if (_groups isNotEqualTo [] && {_owner > 1}) then {
{
_x setGroupOwner _owner;
} forEach _groups;
};

// Rebalance units
if (_rebalance in [REBALANCE, FORCED_REBALANCE]) then {
(_rebalance == FORCED_REBALANCE) call FUNC(rebalance);
};
}] call CBA_fnc_addEventHandler;
} else {
// Register HC (this part happens on HC only)
[QXGVAR(headlessClientJoined), [player]] call CBA_fnc_globalEvent; // Global event for API purposes
Expand Down
51 changes: 51 additions & 0 deletions addons/headless/functions/fnc_blacklist.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "..\script_component.hpp"
/*
* Author: johnb43
* Modifies which units are blacklisted from being transferred to HCs.
*
* Arguments:
* 0: Units <OBJECT, GROUP, ARRAY>
* 1: Add (true) or remove (false) from blacklist <BOOL> (default: true)
* 2: Owner to transfer units to <NUMBER> (default: -1)
* 3: Rebalance <NUMBER> (default: 0)
*
* Return Value:
* None
*
* Example:
* [cursorObject, true] call ace_headless_fnc_blacklist
*
* Public: Yes
*/

params [["_units", objNull, [objNull, grpNull, []]], ["_blacklist", true, [false]], ["_owner", -1, [false]], ["_rebalance", NO_REBALANCE, [0]]];

if !(_units isEqualType []) then {
_units = [_units];
};

// Make sure passed arguments are objects or groups
_units = _units select {_x isEqualType objNull || {_x isEqualType grpNull}};
_units = _units select {!isNull _x};

if (_units isEqualTo []) exitWith {};

private _transfer = _blacklist && {_owner > 1};
private _groups = [];

{
_x setVariable [QXGVAR(blacklist), _blacklist, true];

if (_transfer) then {
if (_x isEqualType objNull) then {
_groups pushBack group _x;
} else {
_groups pushBack _x;
};
};
} forEach _units;

// Try to move AI to new owner; Also takes care of rebalancing groups
if (_transfer || {_rebalance in [REBALANCE, FORCED_REBALANCE]}) then {
[QGVAR(transferGroupsRebalance), [_groups arrayIntersect _groups, _owner, _rebalance]] call CBA_fnc_serverEvent;
};
184 changes: 127 additions & 57 deletions addons/headless/functions/fnc_transferGroups.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

params ["_force"];

// Filter out any invalid entries
GVAR(headlessClients) = GVAR(headlessClients) select {!isNull _x};

GVAR(headlessClients) params [
["_HC1", objNull, [objNull]],
["_HC2", objNull, [objNull]],
Expand All @@ -36,105 +39,172 @@ private _idHC2 = -1;
private _idHC3 = -1;
private _currentHC = 0;

if (!local _HC1) then {
// objNull is never local
if (!local _HC1 && !isNull _HC1) then {
_idHC1 = owner _HC1;
_currentHC = 1;
};

if (!local _HC2) then {
if (!local _HC2 && !isNull _HC2) then {
_idHC2 = owner _HC2;

if (_currentHC == 0) then {
_currentHC = 2;
};
};

if (!local _HC3) then {
if (!local _HC3 && !isNull _HC3) then {
_idHC3 = owner _HC3;

if (_currentHC == 0) then {
_currentHC = 3;
};
};

if (_currentHC == 0) exitWith {
TRACE_1("No Valid HC to transfer to",_currentHC);

if (XGVAR(log)) then {
INFO("No Valid HC to transfer to");
};
};

// Prepare statistics
private _numTransferredHC1 = 0;
private _numTransferredHC2 = 0;
private _numTransferredHC3 = 0;

private _units = [];
private _transfer = false;
private _previousOwner = -1;

// Transfer AI groups
{
// No transfer if empty group
private _transfer = ((units _x) isNotEqualTo []) && {!(_x getVariable [QXGVAR(blacklist), false])};
if (_transfer) then {
// No transfer if waypoints with synchronized triggers exist for the group
private _allWaypointsWithTriggers = (waypoints _x) select {(synchronizedTriggers _x) isNotEqualTo []};
if (_allWaypointsWithTriggers isNotEqualTo []) exitWith {
_units = units _x;

// No transfer if empty group or if group is blacklisted
if (_units isEqualTo [] || {_x getVariable [QXGVAR(blacklist), false]}) then {
continue;
};

// No transfer if waypoints with synchronized triggers exist for the group
if (((waypoints _x) select {(synchronizedTriggers _x) isNotEqualTo []}) isNotEqualTo []) then {
continue;
};

{
// No transfer if already transferred
if (!_force && {(owner _x) in [_idHC1, _idHC2, _idHC3]}) exitWith {
_transfer = false;
};

{
// No transfer if already transferred
if (!_force && {(owner _x) in [_idHC1, _idHC2, _idHC3]}) exitWith {
_transfer = false;
};
// No transfer if any unit in group is blacklisted
if (_x getVariable [QXGVAR(blacklist), false]) exitWith {
_transfer = false;
};

// No transfer if player or UAV in this group
if (isPlayer _x || {unitIsUAV _x}) exitWith {
_transfer = false;
};
// No transfer if player or UAV in this group
if (isPlayer _x || {unitIsUAV _x}) exitWith {
_transfer = false;
};

// No transfer if any unit in group is blacklisted
if (_x getVariable [QXGVAR(blacklist), false]) exitWith {
_transfer = false;
};
private _vehicle = objectParent _x;

private _vehicle = objectParent _x;
// No transfer if the vehicle the unit is in or if the crew in that vehicle is blacklisted
if ((_vehicle getVariable [QXGVAR(blacklist), false]) || {unitIsUAV _vehicle}) exitWith {
_transfer = false;
};

// No transfer if the vehicle the unit is in or if the crew in that vehicle is blacklisted
if ((_vehicle getVariable [QXGVAR(blacklist), false]) || {unitIsUAV _vehicle}) exitWith {
_transfer = false;
};
// Save gear if unit about to be transferred with current loadout (naked unit work-around)
if (XGVAR(transferLoadout) == 1) then {
_x setVariable [QGVAR(loadout), _x call CBA_fnc_getLoadout, true];
};
} forEach _units;

// Save gear if unit about to be transferred with current loadout (naked unit work-around)
if (XGVAR(transferLoadout) == 1) then {
_x setVariable [QGVAR(loadout), _x call CBA_fnc_getLoadout, true];
};
} forEach (units _x);
if (!_transfer) then {
continue;
};

// Round robin between HCs if load balance enabled, else pass all to one HC
if (_transfer) then {
switch (_currentHC) do {
case 1: {
private _transferred = _x setGroupOwner _idHC1;
if (_loadBalance) then {
_currentHC = [3, 2] select (!local _HC2);
};
if (_transferred) then {
_numTransferredHC1 = _numTransferredHC1 + 1;
_previousOwner = groupOwner _x;

switch (_currentHC) do {
case 1: {
if (_loadBalance) then {
// Find the next valid HC
// If none are valid, _currentHC will remain the same
if (_idHC2 != -1) then {
_currentHC = 2;
} else {
if (_idHC3 != -1) then {
_currentHC = 3;
};
};
};
case 2: {
private _transferred = _x setGroupOwner _idHC2;
if (_loadBalance) then {
_currentHC = [1, 3] select (!local _HC3);
};
if (_transferred) then {
_numTransferredHC2 = _numTransferredHC2 + 1;
};

// Don't transfer if it's already local to HC1
if (_previousOwner == _idHC1) exitWith {};

[QGVAR(groupTransferPre), [_x, _HC1, _previousOwner, _idHC1], [_previousOwner, _idHC1]] call CBA_fnc_targetEvent; // API

private _transferred = _x setGroupOwner _idHC1;

[QGVAR(groupTransferPost), [_x, _HC1, _previousOwner, _idHC1, _transferred], [_previousOwner, _idHC1]] call CBA_fnc_targetEvent; // API

if (_transferred) then {
_numTransferredHC1 = _numTransferredHC1 + 1;
};
case 3: {
private _transferred = _x setGroupOwner _idHC3;
if (_loadBalance) then {
_currentHC = [2, 1] select (!local _HC1);
};
case 2: {
if (_loadBalance) then {
// Find the next valid HC
// If none are valid, _currentHC will remain the same
if (_idHC3 != -1) then {
_currentHC = 3;
} else {
if (_idHC1 != -1) then {
_currentHC = 1;
};
};
if (_transferred) then {
_numTransferredHC3 = _numTransferredHC3 + 1;
};

// Don't transfer if it's already local to HC2
if (_previousOwner == _idHC2) exitWith {};

[QGVAR(groupTransferPre), [_x, _HC2, _previousOwner, _idHC2], [_previousOwner, _idHC2]] call CBA_fnc_targetEvent; // API

private _transferred = _x setGroupOwner _idHC2;

[QGVAR(groupTransferPost), [_x, _HC2, _previousOwner, _idHC2, _transferred], [_previousOwner, _idHC2]] call CBA_fnc_targetEvent; // API

if (_transferred) then {
_numTransferredHC2 = _numTransferredHC2 + 1;
};
};
case 3: {
if (_loadBalance) then {
// Find the next valid HC
// If none are valid, _currentHC will remain the same
if (_idHC1 != -1) then {
_currentHC = 1;
} else {
if (_idHC2 != -1) then {
_currentHC = 2;
};
};
};
default {
TRACE_1("No Valid HC to transfer to",_currentHC);

// Don't transfer if it's already local to HC3
if (_previousOwner == _idHC3) exitWith {};

[QGVAR(groupTransferPre), [_x, _HC3, _previousOwner, _idHC3], [_previousOwner, _idHC3]] call CBA_fnc_targetEvent; // API

private _transferred = _x setGroupOwner _idHC2;

[QGVAR(groupTransferPost), [_x, _HC3, _previousOwner, _idHC3, _transferred], [_previousOwner, _idHC3]] call CBA_fnc_targetEvent; // API

if (_transferred) then {
_numTransferredHC3 = _numTransferredHC3 + 1;
};
};
};
Expand Down
4 changes: 4 additions & 0 deletions addons/headless/script_component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@
#include "\z\ace\addons\main\script_macros.hpp"

#define DELAY_DEFAULT 15

#define NO_REBALANCE 0
#define REBALANCE 1
#define FORCED_REBALANCE 2
10 changes: 8 additions & 2 deletions docs/wiki/framework/events-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,15 @@ MenuType: 0 = Interaction, 1 = Self Interaction

| Event Key | Parameters | Locality | Type | Description |
|---------- |------------|----------|------|-------------|
|---------- |------------|----------|------|-------------|
| `ace_interaction_doorOpeningStarted` | [_house, _door, _animations] | Local | Listen | Called when local unit starts interacting with doors
| `ace_interaction_doorOpeningStopped` | [_house, _door, _animations] | Local | Listen | Called when local unit stopps interacting with doors
| `ace_interaction_doorOpeningStopped` | [_house, _door, _animations] | Local | Listen | Called when local unit stops interacting with doors

### 2.17 Headless (`ace_headless`)

| Event Key | Parameters | Locality | Type | Description |
|---------- |------------|----------|------|-------------|
| `ace_headless_groupTransferPre` | [_group, _HC (OBJECT), _previousOwner, _idHC] | Target | Listen | Called just before a group is transferred from any machine to a HC. Called where group currently is local and on the HC, where group is going to be local.
| `ace_headless_groupTransferPost` | [_group, _HC (OBJECT), _previousOwner, _idHC, _transferredSuccessfully] | Target | Listen | Called just after a group is transferred from a machine to a HC. Called where group was local and on the HC, where group is now local. `_transferredSuccessfully` is passed so mods can actually check if the locality was properly transferred, as ownership transfer is not guaranteed.

## 3. Usage
Also Reference [CBA Events System](https://github.com/CBATeam/CBA_A3/wiki/Custom-Events-System){:target="_blank"} documentation.
Expand Down
23 changes: 21 additions & 2 deletions docs/wiki/framework/headless-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,29 @@ As of ACEX v3.2.0 _(before merge into ACE3)_ this feature can also be enabled wi

## 2. Scripting

### 2.1 Disable Transferring for a Group
### 2.1 Manipulating HC Transfers of Groups via function

To prevent a group from transferring to a Headless Client use the following line on a group leader (or every unit in a group in case group leader may not spawn):
`ace_headless_fnc_blacklist`

| Arguments | Type | Optional (default value)
---| --------- | ---- | ------------------------
0 | Units | Object, Group or Array of both | Required
1 | Add (true) or remove (false) from blacklist | Bool | Optional (default: `true`)
2 | Owner to transfer units to | Number | Optional (default: `-1`)
3 | Rebalance (0 = no rebalance, 1 = rebalance, 2 = force rebalance) | Number | (default: `0`)
**R** | None | None | Return value

`Force rebalance` means that all units, including the ones that are on the HCs, are rebalanced amongst the HCs, whereas `rebalance` means that newly spawned units are going to be evenly distributed amongst HCs. Therefore, `rebalance` does not guarantee that the HCs will have an equal amount of groups, whereas `force rebalance` does.

### 2.2 Disable Transferring for a Group via variable

To prevent a group from transferring to a Headless Client use the following line on a unit within a group:

```sqf
this setVariable ["acex_headless_blacklist", true];
```

This variable can also be set on vehicles, disabling transferal of any groups having units in said vehicles.

## 3. Limitations

Expand All @@ -48,3 +63,7 @@ Some Arma 3 features are incompatible, this is up to BI to add support. Disable
Additionally, groups will not be transferred due to lack of support if they:

- Have waypoints with synchronized triggers (waypoint would not change status based on trigger condition) (added in ACEX v3.2.0 - _before merge into ACE3_)

Groups will not be transferred to avoid issues:
- If a player is within the group.
- If they contain UAVs.