Discover enemy factions dynamically from loaded mods
- Scan CfgFactionClasses/CfgVehicles at runtime for selectable enemy factions - Add CfgFactionUnitMap overrides and keep legacy params as fallback
This commit is contained in:
parent
256ce0d5af
commit
8582e6c5e5
@ -1,41 +1,58 @@
|
|||||||
/*
|
/*
|
||||||
* Enemy faction options for the PMC simulator setup flow.
|
* Runtime enemy faction discovery controls for the PMC simulator setup flow.
|
||||||
*
|
*
|
||||||
* Consumers:
|
* Consumers:
|
||||||
* - forge_pmc_fnc_getEnemyFactionOptions reads this config for the startup UI.
|
* - forge_pmc_fnc_getEnemyFactionOptions scans loaded CfgFactionClasses and
|
||||||
* - forge_pmc_fnc_resolveEnemyFactionParam maps mission param values back to
|
* CfgVehicles at runtime, then uses this config to filter and polish the UI.
|
||||||
* faction classnames during fallback/default setup.
|
* - forge_pmc_fnc_resolveEnemyFactionParam uses override values here to keep
|
||||||
|
* legacy mission params compatible when the setup UI is cancelled.
|
||||||
* - Mission generators use ENEMY_FACTION_STR and ENEMY_SIDE after setup has
|
* - Mission generators use ENEMY_FACTION_STR and ENEMY_SIDE after setup has
|
||||||
* applied the selected option.
|
* applied the selected option.
|
||||||
*
|
*
|
||||||
* Keep this list aligned with the server modpack. Classnames that are not
|
* This config is intentionally not the full faction list. Any loaded mod
|
||||||
* present in CfgFactionClasses may still appear in the UI, but downstream unit
|
* faction on an allowed side can appear automatically if it has spawnable
|
||||||
* pool generation may fall back or fail to find units.
|
* infantry or a CfgFactionUnitMap override.
|
||||||
*
|
|
||||||
* BLUFOR/WEST factions are intentionally omitted because generated missions use
|
|
||||||
* these options as opposing forces.
|
|
||||||
*/
|
*/
|
||||||
class CfgEnemyFactions {
|
class CfgEnemyFactions {
|
||||||
/*
|
/*
|
||||||
* Option format:
|
* Arma side IDs allowed as generated enemy factions:
|
||||||
* - class name: stable config entry name, normally matching the faction.
|
* - 0: EAST / OPFOR
|
||||||
* - value: legacy mission param numeric value.
|
* - 2: RESISTANCE / Independent
|
||||||
* - faction: CfgFactionClasses classname used for spawning.
|
*
|
||||||
* - display: user-facing label shown in the setup UI.
|
* WEST / BLUFOR is intentionally excluded because generated missions use
|
||||||
|
* these options as opposing forces.
|
||||||
*/
|
*/
|
||||||
class Options {
|
sides[] = {0, 2};
|
||||||
class OPF_F { value = 0; faction = "OPF_F"; display = "CSAT"; };
|
|
||||||
class OPF_T_F { value = 1; faction = "OPF_T_F"; display = "CSAT (Pacific)"; };
|
/*
|
||||||
class OPF_V_F { value = 2; faction = "OPF_V_F"; display = "Viper"; };
|
* Factions that should never be offered even if present in the active
|
||||||
class OPF_R_F { value = 3; faction = "OPF_R_F"; display = "Spetnaz"; };
|
* modset. Keep this for factions that are unsuitable for PMC contracts.
|
||||||
class OPF_SFIA_lxWS { value = 4; faction = "OPF_SFIA_lxWS"; display = "SFIA"; };
|
*/
|
||||||
class OPF_TURA_lxWS { value = 5; faction = "OPF_TURA_lxWS"; display = "Tura"; };
|
denylist[] = {
|
||||||
class IND_F { value = 6; faction = "IND_F"; display = "AAF"; };
|
"IND_UN_lxWS"
|
||||||
class IND_G_F { value = 7; faction = "IND_G_F"; display = "FIA"; };
|
};
|
||||||
class IND_E_F { value = 8; faction = "IND_E_F"; display = "LDF"; };
|
|
||||||
class IND_C_F { value = 9; faction = "IND_C_F"; display = "Syndikat"; };
|
/*
|
||||||
class IND_L_F { value = 10; faction = "IND_L_F"; display = "Looters"; };
|
* Optional display/order/value metadata for known factions.
|
||||||
class IND_SFIA_lxWS { value = 11; faction = "IND_SFIA_lxWS"; display = "SFIA"; };
|
*
|
||||||
class IND_TURA_lxWS { value = 12; faction = "IND_TURA_lxWS"; display = "Tura"; };
|
* - value keeps legacy Params::enemyFaction values stable.
|
||||||
|
* - order controls setup UI ordering before dynamically discovered factions.
|
||||||
|
* - display overrides raw CfgFactionClasses names when we want cleaner text.
|
||||||
|
*
|
||||||
|
* Factions not listed here are still discovered automatically and sorted
|
||||||
|
* after these known options.
|
||||||
|
*/
|
||||||
|
class Overrides {
|
||||||
|
class OPF_F { value = 0; order = 0; display = "CSAT"; };
|
||||||
|
class OPF_T_F { value = 1; order = 1; display = "CSAT (Pacific)"; };
|
||||||
|
class OPF_R_F { value = 2; order = 2; display = "Spetnaz"; };
|
||||||
|
class OPF_SFIA_lxWS { value = 3; order = 3; display = "SFIA"; };
|
||||||
|
class OPF_TURA_lxWS { value = 4; order = 4; display = "Tura"; };
|
||||||
|
class IND_F { value = 5; order = 5; display = "AAF"; };
|
||||||
|
class IND_G_F { value = 6; order = 6; display = "FIA"; };
|
||||||
|
class IND_E_F { value = 7; order = 7; display = "LDF"; };
|
||||||
|
class IND_C_F { value = 8; order = 8; display = "Syndikat"; };
|
||||||
|
class IND_L_F { value = 9; order = 9; display = "Looters"; };
|
||||||
|
class IND_TURA_lxWS { value = 10; order = 10; display = "Tura"; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Optional faction-to-unit template map.
|
* Optional faction-to-unit override map.
|
||||||
*
|
*
|
||||||
* Current behavior:
|
* Current behavior:
|
||||||
* - Enemy unit pools are primarily built from CfgGroups/CfgVehicles through
|
* - forge_pmc_fnc_getEnemyFactionOptions treats a mapped faction as selectable
|
||||||
* forge_pmc_fnc_getEnemyFactionUnitPool.
|
* when at least one mapped vehicle exists.
|
||||||
* - This config is a template for deterministic per-faction unit pools if the
|
* - forge_pmc_fnc_getEnemyFactionUnitPool checks this map first.
|
||||||
* automatic faction lookup is not specific enough for a server/modpack.
|
* - If a selected faction has a class here, the listed Units are used as the
|
||||||
|
* deterministic spawn pool for generated mission enemies.
|
||||||
|
* - If no class exists here, the helper falls back to CfgVehicles traversal for
|
||||||
|
* units whose faction and side match the selected faction.
|
||||||
*
|
*
|
||||||
* To enable this map, wire it into forge_pmc_fnc_getEnemyFactionUnitPool or the
|
* Most mod factions do not need an entry here. Add a class only when a faction
|
||||||
* generator spawn path before falling back to config traversal.
|
* needs a curated or corrected spawn pool.
|
||||||
*/
|
*/
|
||||||
class CfgFactionUnitMap {
|
class CfgFactionUnitMap {
|
||||||
/*
|
/*
|
||||||
* Mapping key should match the selected faction classname from
|
* Mapping key should match the selected faction classname from
|
||||||
* CfgEnemyFactions.Options[].faction, such as "IND_G_F".
|
* CfgFactionClasses, such as "IND_G_F".
|
||||||
*/
|
*/
|
||||||
class IND_G_F {
|
class IND_G_F {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Mission lobby fallback params.
|
||||||
|
*
|
||||||
|
* The startup setup UI now discovers selectable factions dynamically from the
|
||||||
|
* active modset. Params remain intentionally static because Arma evaluates
|
||||||
|
* them before mission runtime scripts can scan loaded factions. If the setup UI
|
||||||
|
* is cancelled or never opened, these values provide the default fallback.
|
||||||
|
*/
|
||||||
class Params {
|
class Params {
|
||||||
class enemyFaction {
|
class enemyFaction {
|
||||||
title = "Enemy Faction";
|
title = "Enemy Faction";
|
||||||
values[] = {0,1,2,3,4,5,6,7,8,9,10,11,12};
|
values[] = {0,1,2,3,4,5,6,7,8,9,10};
|
||||||
texts[] = {
|
texts[] = {
|
||||||
"CSAT",
|
"CSAT",
|
||||||
"CSAT (Pacific)",
|
"CSAT (Pacific)",
|
||||||
"Viper",
|
|
||||||
"Spetnaz",
|
"Spetnaz",
|
||||||
"SFIA (OPFOR)",
|
"SFIA (OPFOR)",
|
||||||
"Tura (OPFOR)",
|
"Tura (OPFOR)",
|
||||||
@ -14,10 +21,9 @@ class Params {
|
|||||||
"LDF",
|
"LDF",
|
||||||
"Syndikat",
|
"Syndikat",
|
||||||
"Looters",
|
"Looters",
|
||||||
"SFIA (Independent)",
|
|
||||||
"Tura (Independent)"
|
"Tura (Independent)"
|
||||||
};
|
};
|
||||||
default = 7;
|
default = 6;
|
||||||
};
|
};
|
||||||
|
|
||||||
class maxConcurrentMissions {
|
class maxConcurrentMissions {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
#include "CfgFunctions.h"
|
|
||||||
@ -24,6 +24,7 @@ corpseManagerMode = 0;
|
|||||||
|
|
||||||
#include "CfgParams.hpp"
|
#include "CfgParams.hpp"
|
||||||
#include "CfgEnemyFactions.hpp"
|
#include "CfgEnemyFactions.hpp"
|
||||||
|
#include "CfgFactionUnitMap.hpp"
|
||||||
#include "CfgMissions.hpp"
|
#include "CfgMissions.hpp"
|
||||||
#include "CfgFunctions.hpp"
|
#include "CfgFunctions.hpp"
|
||||||
#include "ui\baseControls.hpp"
|
#include "ui\baseControls.hpp"
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Helper functions provide reusable lookups and conversions for the PMC simulator
|
|||||||
|
|
||||||
## Registered Functions
|
## Registered Functions
|
||||||
- `forge_pmc_fnc_getAllEnemyFactions` returns available non-BLUFOR faction classnames.
|
- `forge_pmc_fnc_getAllEnemyFactions` returns available non-BLUFOR faction classnames.
|
||||||
- `forge_pmc_fnc_getEnemyFactionOptions` reads configured faction options from `CfgEnemyFactions`.
|
- `forge_pmc_fnc_getEnemyFactionOptions` scans the active modset for selectable OPFOR/Independent factions and applies `CfgEnemyFactions` filters/labels.
|
||||||
- `forge_pmc_fnc_resolveEnemyFactionParam` converts a mission parameter value into a faction classname.
|
- `forge_pmc_fnc_resolveEnemyFactionParam` converts a mission parameter value into a faction classname.
|
||||||
- `forge_pmc_fnc_getEnemyFactionSide` resolves a faction classname to an Arma side.
|
- `forge_pmc_fnc_getEnemyFactionSide` resolves a faction classname to an Arma side.
|
||||||
- `forge_pmc_fnc_getEnemyFactionUnitPool` builds the unit pool used by generated enemy spawns.
|
- `forge_pmc_fnc_getEnemyFactionUnitPool` builds the unit pool used by generated enemy spawns.
|
||||||
@ -12,4 +12,8 @@ Helper functions provide reusable lookups and conversions for the PMC simulator
|
|||||||
- `forge_pmc_fnc_getEnemyFactionListboxSelection` and `forge_pmc_fnc_populateEnemyFactionListbox` support faction picker UI/listbox flows.
|
- `forge_pmc_fnc_getEnemyFactionListboxSelection` and `forge_pmc_fnc_populateEnemyFactionListbox` support faction picker UI/listbox flows.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
The mission setup UI is populated dynamically from loaded `CfgFactionClasses` and `CfgVehicles`. `CfgEnemyFactions` only controls allowed sides, denylisted factions, and friendly labels/order for known factions.
|
||||||
|
|
||||||
|
`forge_pmc_fnc_getEnemyFactionUnitPool` checks `CfgFactionUnitMap` first. Add a faction class there when the automatic `CfgVehicles` faction lookup is too broad, too sparse, or needs a curated unit pool.
|
||||||
|
|
||||||
These functions are registered under the `forge_pmc` tag, so their public names do not include the folder name. Moving a helper within this folder should not change callers as long as `CfgFunctions.hpp` remains updated.
|
These functions are registered under the `forge_pmc` tag, so their public names do not include the folder name. Moving a helper within this folder should not change callers as long as `CfgFunctions.hpp` remains updated.
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Author: IDSolutions, Blackbox AI, MrPākehā
|
* Author: IDSolutions, Blackbox AI, MrPākehā
|
||||||
* Returns candidate enemy faction classnames available to the mission.
|
* Returns candidate enemy faction classnames available to the mission.
|
||||||
* This is a runtime helper only; description.ext mission params still use
|
* This is a runtime helper only. The setup UI uses
|
||||||
* the static CfgEnemyFactions list.
|
* forge_pmc_fnc_getEnemyFactionOptions so it can filter to spawnable factions.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* 0: Exclude BLUFOR/WEST factions <BOOL> (Default: true)
|
* 0: Exclude BLUFOR/WEST factions <BOOL> (Default: true)
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Author: IDSolutions, Blackbox AI, MrPākehā
|
* Author: IDSolutions, Blackbox AI, MrPakeha
|
||||||
* Reads enemy faction options from missionConfigFile >> CfgEnemyFactions
|
* Builds setup UI faction options from the active modset. The helper scans
|
||||||
* >> Options for setup UI hydration and mission param fallback resolution.
|
* loaded faction classes and only returns allowed OPFOR/Independent factions
|
||||||
|
* that can provide infantry through CfgVehicles or CfgFactionUnitMap.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
@ -12,40 +13,122 @@
|
|||||||
* Public: No
|
* Public: No
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private _optionsConfig = missionConfigFile >> "CfgEnemyFactions" >> "Options";
|
private _config = missionConfigFile >> "CfgEnemyFactions";
|
||||||
private _options = [];
|
private _allowedSides = getArray (_config >> "sides");
|
||||||
|
if (_allowedSides isEqualTo []) then {
|
||||||
if (isClass _optionsConfig) then {
|
_allowedSides = [0, 2];
|
||||||
{
|
|
||||||
private _configName = configName _x;
|
|
||||||
private _value = getNumber (_x >> "value");
|
|
||||||
private _faction = getText (_x >> "faction");
|
|
||||||
private _display = getText (_x >> "display");
|
|
||||||
|
|
||||||
if (_faction isEqualTo "") then {
|
|
||||||
_faction = _configName;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isClass (configFile >> "CfgFactionClasses" >> _faction) && {
|
|
||||||
isClass (configFile >> "CfgFactionClasses" >> _configName)
|
|
||||||
}) then {
|
|
||||||
_faction = _configName;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_display isEqualTo "") then {
|
|
||||||
_display = _faction;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_faction isNotEqualTo "") then {
|
|
||||||
_options pushBack [_faction, _display, _value];
|
|
||||||
};
|
|
||||||
} forEach ("true" configClasses _optionsConfig);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _denylist = getArray (_config >> "denylist");
|
||||||
|
private _overridesConfig = _config >> "Overrides";
|
||||||
|
|
||||||
|
private _spawnableFactions = createHashMap;
|
||||||
|
{
|
||||||
|
if (getNumber (_x >> "scope") < 2) then { continue; };
|
||||||
|
if !(configName _x isKindOf "CAManBase") then { continue; };
|
||||||
|
|
||||||
|
private _faction = getText (_x >> "faction");
|
||||||
|
if (_faction isEqualTo "") then { continue; };
|
||||||
|
|
||||||
|
private _side = getNumber (_x >> "side");
|
||||||
|
if !(_side in _allowedSides) then { continue; };
|
||||||
|
|
||||||
|
_spawnableFactions set [_faction, true];
|
||||||
|
} forEach ("true" configClasses (configFile >> "CfgVehicles"));
|
||||||
|
|
||||||
|
private _mappedFactions = createHashMap;
|
||||||
|
{
|
||||||
|
private _unitsConfig = _x >> "Units";
|
||||||
|
if (!(isClass _unitsConfig)) then { continue; };
|
||||||
|
|
||||||
|
private _hasUnits = false;
|
||||||
|
{
|
||||||
|
private _vehicle = getText (_x >> "vehicle");
|
||||||
|
if (_vehicle isNotEqualTo "" && { isClass (configFile >> "CfgVehicles" >> _vehicle) }) exitWith {
|
||||||
|
_hasUnits = true;
|
||||||
|
};
|
||||||
|
} forEach ("true" configClasses _unitsConfig);
|
||||||
|
|
||||||
|
if (_hasUnits) then {
|
||||||
|
_mappedFactions set [configName _x, true];
|
||||||
|
};
|
||||||
|
} forEach ("true" configClasses (missionConfigFile >> "CfgFactionUnitMap"));
|
||||||
|
|
||||||
|
private _getFactionSideNumber = {
|
||||||
|
params ["_factionConfig"];
|
||||||
|
|
||||||
|
if (isNumber (_factionConfig >> "side")) exitWith {
|
||||||
|
getNumber (_factionConfig >> "side")
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (toUpperANSI getText (_factionConfig >> "side")) do {
|
||||||
|
case "0";
|
||||||
|
case "EAST";
|
||||||
|
case "OPFOR": { 0 };
|
||||||
|
case "2";
|
||||||
|
case "GUER";
|
||||||
|
case "GUERRILA";
|
||||||
|
case "GUERRILLA";
|
||||||
|
case "INDEPENDENT";
|
||||||
|
case "RESISTANCE": { 2 };
|
||||||
|
default { -1 };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _records = [];
|
||||||
|
private _dynamicIndex = 0;
|
||||||
|
{
|
||||||
|
private _faction = configName _x;
|
||||||
|
if (_faction isEqualTo "") then { continue; };
|
||||||
|
if (_faction in _denylist) then { continue; };
|
||||||
|
|
||||||
|
private _side = [_x] call _getFactionSideNumber;
|
||||||
|
if !(_side in _allowedSides) then { continue; };
|
||||||
|
|
||||||
|
if (!(_spawnableFactions getOrDefault [_faction, false]) && {
|
||||||
|
!(_mappedFactions getOrDefault [_faction, false])
|
||||||
|
}) then {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _override = _overridesConfig >> _faction;
|
||||||
|
private _display = getText (_x >> "displayName");
|
||||||
|
private _order = 1000 + _dynamicIndex;
|
||||||
|
private _value = 1000 + _dynamicIndex;
|
||||||
|
|
||||||
|
if (isClass _override) then {
|
||||||
|
private _overrideDisplay = getText (_override >> "display");
|
||||||
|
if (_overrideDisplay isNotEqualTo "") then {
|
||||||
|
_display = _overrideDisplay;
|
||||||
|
};
|
||||||
|
if (isNumber (_override >> "order")) then {
|
||||||
|
_order = getNumber (_override >> "order");
|
||||||
|
};
|
||||||
|
if (isNumber (_override >> "value")) then {
|
||||||
|
_value = getNumber (_override >> "value");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_display isEqualTo "") then {
|
||||||
|
_display = _faction;
|
||||||
|
};
|
||||||
|
|
||||||
|
_records pushBack [_order, _display, _faction, _value];
|
||||||
|
_dynamicIndex = _dynamicIndex + 1;
|
||||||
|
} forEach ("true" configClasses (configFile >> "CfgFactionClasses"));
|
||||||
|
|
||||||
|
_records sort true;
|
||||||
|
|
||||||
|
private _options = [];
|
||||||
|
{
|
||||||
|
_x params ["_order", "_display", "_faction", "_value"];
|
||||||
|
_options pushBack [_faction, _display, _value];
|
||||||
|
} forEach _records;
|
||||||
|
|
||||||
if (_options isEqualTo []) then {
|
if (_options isEqualTo []) then {
|
||||||
_options = [
|
_options = [
|
||||||
["OPF_F", "CSAT", 0],
|
["OPF_F", "CSAT", 0],
|
||||||
["IND_G_F", "FIA", 7]
|
["IND_G_F", "FIA", 6]
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -25,10 +25,15 @@ if (_f isEqualTo "") exitWith { _fallbackSide };
|
|||||||
private _side = _fallbackSide;
|
private _side = _fallbackSide;
|
||||||
private _cfgFaction = configFile >> "CfgFactionClasses" >> _enemyFaction;
|
private _cfgFaction = configFile >> "CfgFactionClasses" >> _enemyFaction;
|
||||||
if (isClass _cfgFaction) then {
|
if (isClass _cfgFaction) then {
|
||||||
private _sideNumber = getNumber (_cfgFaction >> "side");
|
private _hasSideNumber = isNumber (_cfgFaction >> "side");
|
||||||
|
private _sideNumber = if (_hasSideNumber) then { getNumber (_cfgFaction >> "side") } else { -1 };
|
||||||
private _sideText = toUpperANSI getText (_cfgFaction >> "side");
|
private _sideText = toUpperANSI getText (_cfgFaction >> "side");
|
||||||
|
|
||||||
if (_sideNumber > 0 || { _sideText isEqualTo "0" }) then {
|
if (_hasSideNumber || { _sideText in ["0", "1", "2"] }) then {
|
||||||
|
if (!_hasSideNumber) then {
|
||||||
|
_sideNumber = parseNumber _sideText;
|
||||||
|
};
|
||||||
|
|
||||||
switch (_sideNumber) do {
|
switch (_sideNumber) do {
|
||||||
case 0: { _side = east; };
|
case 0: { _side = east; };
|
||||||
case 2: { _side = resistance; };
|
case 2: { _side = resistance; };
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
* Arguments:
|
* Arguments:
|
||||||
* 0: Faction classname <STRING> (Default: ENEMY_FACTION_STR or "IND_G_F")
|
* 0: Faction classname <STRING> (Default: ENEMY_FACTION_STR or "IND_G_F")
|
||||||
* 1: Fallback side <SIDE> (Default: ENEMY_SIDE or east)
|
* 1: Fallback side <SIDE> (Default: ENEMY_SIDE or east)
|
||||||
|
* 2: Allow side-default fallback units when no faction units exist <BOOL>
|
||||||
|
* (Default: true)
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* Unit definitions with vehicle, rank, and position keys <ARRAY>
|
* Unit definitions with vehicle, rank, and position keys <ARRAY>
|
||||||
@ -15,7 +17,8 @@
|
|||||||
|
|
||||||
params [
|
params [
|
||||||
["_faction", missionNamespace getVariable ["ENEMY_FACTION_STR", "IND_G_F"], [""]],
|
["_faction", missionNamespace getVariable ["ENEMY_FACTION_STR", "IND_G_F"], [""]],
|
||||||
["_fallbackSide", missionNamespace getVariable ["ENEMY_SIDE", east], [east]]
|
["_fallbackSide", missionNamespace getVariable ["ENEMY_SIDE", east], [east]],
|
||||||
|
["_allowSideFallback", true, [false]]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_faction isEqualTo "") then {
|
if (_faction isEqualTo "") then {
|
||||||
@ -25,33 +28,56 @@ if (_faction isEqualTo "") then {
|
|||||||
private _pool = [];
|
private _pool = [];
|
||||||
private _sideNumber = [_fallbackSide] call BIS_fnc_sideID;
|
private _sideNumber = [_fallbackSide] call BIS_fnc_sideID;
|
||||||
|
|
||||||
{
|
// Check CfgFactionUnitMap first for explicit faction unit definitions
|
||||||
if (getNumber (_x >> "scope") < 2) then { continue; };
|
private _factionMapConfig = missionConfigFile >> "CfgFactionUnitMap" >> _faction;
|
||||||
if (getText (_x >> "faction") isNotEqualTo _faction) then { continue; };
|
if (isClass _factionMapConfig) then {
|
||||||
if (getNumber (_x >> "side") isNotEqualTo _sideNumber) then { continue; };
|
{
|
||||||
if !(configName _x isKindOf "CAManBase") then { continue; };
|
private _vehicle = getText (_x >> "vehicle");
|
||||||
|
if (_vehicle isEqualTo "" || { !(isClass (configFile >> "CfgVehicles" >> _vehicle)) }) then {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
private _className = configName _x;
|
_pool pushBack createHashMapFromArray [
|
||||||
private _upperClassName = toUpperANSI _className;
|
["vehicle", _vehicle],
|
||||||
private _rank = "PRIVATE";
|
["rank", getText (_x >> "rank")],
|
||||||
|
["position", getArray (_x >> "position")]
|
||||||
if (
|
];
|
||||||
(_upperClassName find "_SL_" >= 0)
|
} forEach ("true" configClasses (_factionMapConfig >> "Units"));
|
||||||
|| { _upperClassName find "_TL_" >= 0 }
|
};
|
||||||
|| { _upperClassName find "OFFICER" >= 0 }
|
|
||||||
|| { _upperClassName find "COMMANDER" >= 0 }
|
|
||||||
) then {
|
|
||||||
_rank = "SERGEANT";
|
|
||||||
};
|
|
||||||
|
|
||||||
_pool pushBack createHashMapFromArray [
|
|
||||||
["vehicle", _className],
|
|
||||||
["rank", _rank],
|
|
||||||
["position", [0, 0, 0]]
|
|
||||||
];
|
|
||||||
} forEach ("true" configClasses (configFile >> "CfgVehicles"));
|
|
||||||
|
|
||||||
|
// Fall back to config traversal if no explicit mapping exists.
|
||||||
if (_pool isEqualTo []) then {
|
if (_pool isEqualTo []) then {
|
||||||
|
private _factionFallback = _faction;
|
||||||
|
|
||||||
|
{
|
||||||
|
if (getNumber (_x >> "scope") < 2) then { continue; };
|
||||||
|
private _unitFaction = getText (_x >> "faction");
|
||||||
|
if ((_unitFaction isNotEqualTo _faction) && (_unitFaction isNotEqualTo _factionFallback)) then { continue; };
|
||||||
|
if (getNumber (_x >> "side") isNotEqualTo _sideNumber) then { continue; };
|
||||||
|
if !(configName _x isKindOf "CAManBase") then { continue; };
|
||||||
|
|
||||||
|
private _className = configName _x;
|
||||||
|
private _upperClassName = toUpperANSI _className;
|
||||||
|
private _rank = "PRIVATE";
|
||||||
|
|
||||||
|
if (
|
||||||
|
(_upperClassName find "_SL_" >= 0)
|
||||||
|
|| { _upperClassName find "_TL_" >= 0 }
|
||||||
|
|| { _upperClassName find "OFFICER" >= 0 }
|
||||||
|
|| { _upperClassName find "COMMANDER" >= 0 }
|
||||||
|
) then {
|
||||||
|
_rank = "SERGEANT";
|
||||||
|
};
|
||||||
|
|
||||||
|
_pool pushBack createHashMapFromArray [
|
||||||
|
["vehicle", _className],
|
||||||
|
["rank", _rank],
|
||||||
|
["position", [0, 0, 0]]
|
||||||
|
];
|
||||||
|
} forEach ("true" configClasses (configFile >> "CfgVehicles"));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_pool isEqualTo [] && { _allowSideFallback }) then {
|
||||||
private _fallbackUnits = switch (_fallbackSide) do {
|
private _fallbackUnits = switch (_fallbackSide) do {
|
||||||
case east: { ["O_Soldier_SL_F", "O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_AR_F", "O_Soldier_GL_F", "O_medic_F"] };
|
case east: { ["O_Soldier_SL_F", "O_Soldier_TL_F", "O_Soldier_F", "O_Soldier_AR_F", "O_Soldier_GL_F", "O_medic_F"] };
|
||||||
case resistance: { ["I_G_Soldier_SL_F", "I_G_Soldier_TL_F", "I_G_Soldier_F", "I_G_Soldier_AR_F", "I_G_medic_F"] };
|
case resistance: { ["I_G_Soldier_SL_F", "I_G_Soldier_TL_F", "I_G_Soldier_F", "I_G_Soldier_AR_F", "I_G_medic_F"] };
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Author: IDSolutions, Blackbox AI, MrPākehā
|
* Author: IDSolutions, Blackbox AI, MrPākehā
|
||||||
* Populates a listbox or combo control with CfgEnemyFactions options.
|
* Populates a listbox or combo control with dynamically discovered enemy
|
||||||
|
* faction options.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* 0: Listbox/combo control or display <CONTROL|DISPLAY>
|
* 0: Listbox/combo control or display <CONTROL|DISPLAY>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* enemy faction classname.
|
* enemy faction classname.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* 0: Param value from Params::enemyFaction <NUMBER|STRING> (Default: 7)
|
* 0: Param value from Params::enemyFaction <NUMBER|STRING> (Default: 6)
|
||||||
* 1: Fallback faction classname <STRING> (Default: "IND_G_F")
|
* 1: Fallback faction classname <STRING> (Default: "IND_G_F")
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
@ -14,7 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
params [
|
params [
|
||||||
["_value", 7, [0, ""]],
|
["_value", 6, [0, ""]],
|
||||||
["_fallback", "IND_G_F", [""]]
|
["_fallback", "IND_G_F", [""]]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -131,7 +131,7 @@ AttackMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -131,7 +131,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -152,7 +152,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _buildingPositions = [];
|
private _buildingPositions = [];
|
||||||
if (!isNull _building) then {
|
if !(isNull _building) then {
|
||||||
for "_i" from 0 to 100 do {
|
for "_i" from 0 to 100 do {
|
||||||
private _buildingPos = _building buildingPos _i;
|
private _buildingPos = _building buildingPos _i;
|
||||||
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
||||||
@ -214,7 +214,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
selectRandom _buildingPositions
|
selectRandom _buildingPositions
|
||||||
};
|
};
|
||||||
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
||||||
if (!isNull _escort) then {
|
if !(isNull _escort) then {
|
||||||
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
||||||
_escortUnits pushBack _escort;
|
_escortUnits pushBack _escort;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -131,7 +131,7 @@ DefendMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -132,7 +132,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -297,7 +297,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
if (_nearBuildings isNotEqualTo []) then {
|
if (_nearBuildings isNotEqualTo []) then {
|
||||||
// prefer the closest building that actually contains the position
|
// prefer the closest building that actually contains the position
|
||||||
{
|
{
|
||||||
if (!isNull _x && { _position inArea _x }) exitWith {
|
if !(isNull _x && { _position inArea _x }) exitWith {
|
||||||
_building = _x;
|
_building = _x;
|
||||||
};
|
};
|
||||||
} forEach _nearBuildings;
|
} forEach _nearBuildings;
|
||||||
@ -313,14 +313,14 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNull _building) then {
|
if !(isNull _building) then {
|
||||||
for "_i" from 1 to _buildingSpawnAttempts do {
|
for "_i" from 1 to _buildingSpawnAttempts do {
|
||||||
private _posIndex = floor random 1000;
|
private _posIndex = floor random 1000;
|
||||||
private _candidate = _building buildingPos _posIndex;
|
private _candidate = _building buildingPos _posIndex;
|
||||||
// buildingPos returns [0,0,0] for invalid positions
|
// buildingPos returns [0,0,0] for invalid positions
|
||||||
if (_candidate isEqualTo [0, 0, 0]) then { continue; };
|
if (_candidate isEqualTo [0, 0, 0]) then { continue; };
|
||||||
// ensure candidate is still inside the building footprint
|
// ensure candidate is still inside the building footprint
|
||||||
if (!(_candidate isEqualType [])) then { continue; };
|
if !((_candidate isEqualType [])) then { continue; };
|
||||||
if ((_candidate vectorDistance _position) <= 60) exitWith {
|
if ((_candidate vectorDistance _position) <= 60) exitWith {
|
||||||
_buildingPos = _candidate;
|
_buildingPos = _candidate;
|
||||||
};
|
};
|
||||||
@ -328,7 +328,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _protectedPos = [0,0,0];
|
private _protectedPos = [0,0,0];
|
||||||
if (!(_buildingPos isEqualTo [])) then {
|
if !((_buildingPos isEqualTo [])) then {
|
||||||
_protectedPos = _buildingPos;
|
_protectedPos = _buildingPos;
|
||||||
} else {
|
} else {
|
||||||
// Outdoor fallback: keep previous behavior
|
// Outdoor fallback: keep previous behavior
|
||||||
@ -337,7 +337,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _protectedObject = createVehicle [_protectedClass, _protectedPos, [], 0, "NONE"];
|
private _protectedObject = createVehicle [_protectedClass, _protectedPos, [], 0, "NONE"];
|
||||||
private _protectedObjects = [];
|
private _protectedObjects = [];
|
||||||
if (!isNull _protectedObject) then {
|
if !(isNull _protectedObject) then {
|
||||||
_protectedObjects pushBack _protectedObject;
|
_protectedObjects pushBack _protectedObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
private _devicePos = _protectedPos vectorAdd _deviceOffset;
|
private _devicePos = _protectedPos vectorAdd _deviceOffset;
|
||||||
|
|
||||||
private _deviceObject = createVehicle [_deviceClass, _devicePos, [], 0, "NONE"];
|
private _deviceObject = createVehicle [_deviceClass, _devicePos, [], 0, "NONE"];
|
||||||
if (!isNull _deviceObject) then {
|
if !(isNull _deviceObject) then {
|
||||||
_devices pushBack _deviceObject;
|
_devices pushBack _deviceObject;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -131,7 +131,7 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -193,22 +193,22 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
if (_cargoPool isEqualTo []) exitWith { [] };
|
if (_cargoPool isEqualTo []) exitWith { [] };
|
||||||
|
|
||||||
private _cargoSpawnObj = objNull;
|
private _cargoSpawnObj = objNull;
|
||||||
if (!isNil "cargoSpawn") then { _cargoSpawnObj = cargoSpawn; };
|
if !(isNil "cargoSpawn") then { _cargoSpawnObj = cargoSpawn; };
|
||||||
if (isNull _cargoSpawnObj) then { _cargoSpawnObj = missionNamespace getVariable ["cargoSpawn", objNull]; };
|
if (isNull _cargoSpawnObj) then { _cargoSpawnObj = missionNamespace getVariable ["cargoSpawn", objNull]; };
|
||||||
|
|
||||||
private _extZoneObj = objNull;
|
private _extZoneObj = objNull;
|
||||||
if (!isNil "ExtZone") then { _extZoneObj = ExtZone; };
|
if !(isNil "ExtZone") then { _extZoneObj = ExtZone; };
|
||||||
if (isNull _extZoneObj) then { _extZoneObj = missionNamespace getVariable ["ExtZone", objNull]; };
|
if (isNull _extZoneObj) then { _extZoneObj = missionNamespace getVariable ["ExtZone", objNull]; };
|
||||||
|
|
||||||
for "_i" from 1 to _cargoCount do {
|
for "_i" from 1 to _cargoCount do {
|
||||||
private _cargoClass = selectRandom _cargoPool;
|
private _cargoClass = selectRandom _cargoPool;
|
||||||
|
|
||||||
private _spawnPos = [0, 0, 0];
|
private _spawnPos = [0, 0, 0];
|
||||||
if (!isNull _cargoSpawnObj) then {
|
if !(isNull _cargoSpawnObj) then {
|
||||||
private _basePos = getPosATL _cargoSpawnObj;
|
private _basePos = getPosATL _cargoSpawnObj;
|
||||||
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
||||||
} else {
|
} else {
|
||||||
if (!isNull _extZoneObj) then {
|
if !(isNull _extZoneObj) then {
|
||||||
private _basePos = getPosATL _extZoneObj;
|
private _basePos = getPosATL _extZoneObj;
|
||||||
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
||||||
} else {
|
} else {
|
||||||
@ -217,7 +217,7 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _cargoObject = createVehicle [_cargoClass, _spawnPos, [], 0, "NONE"];
|
private _cargoObject = createVehicle [_cargoClass, _spawnPos, [], 0, "NONE"];
|
||||||
if (!isNull _cargoObject) then {
|
if !(isNull _cargoObject) then {
|
||||||
_cargoObjects pushBack _cargoObject;
|
_cargoObjects pushBack _cargoObject;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -132,7 +132,7 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -349,13 +349,13 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
_position
|
_position
|
||||||
]] call forge_server_common_fnc_log;
|
]] call forge_server_common_fnc_log;
|
||||||
|
|
||||||
if (!isNull _defendGroup) then {
|
if !(isNull _defendGroup) then {
|
||||||
deleteGroup _defendGroup;
|
deleteGroup _defendGroup;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
private _spawnedGroups = [_group];
|
private _spawnedGroups = [_group];
|
||||||
if (!isNull _defendGroup && { units _defendGroup isNotEqualTo [] }) then {
|
if !(isNull _defendGroup && { units _defendGroup isNotEqualTo [] }) then {
|
||||||
_spawnedGroups pushBack _defendGroup;
|
_spawnedGroups pushBack _defendGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -442,7 +442,7 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
private _position = _missionRecord getOrDefault ["position", []];
|
private _position = _missionRecord getOrDefault ["position", []];
|
||||||
private _groups = _missionRecord getOrDefault ["groups", []];
|
private _groups = _missionRecord getOrDefault ["groups", []];
|
||||||
{
|
{
|
||||||
if (!isNull _x) then {
|
if !(isNull _x) then {
|
||||||
{ deleteVehicle _x; } forEach (units _x);
|
{ deleteVehicle _x; } forEach (units _x);
|
||||||
deleteGroup _x;
|
deleteGroup _x;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -132,7 +132,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -155,7 +155,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _buildingPositions = [];
|
private _buildingPositions = [];
|
||||||
if (!isNull _building) then {
|
if !(isNull _building) then {
|
||||||
// buildingPos returns positions for building interiors; we random-pick from these.
|
// buildingPos returns positions for building interiors; we random-pick from these.
|
||||||
for "_i" from 0 to 100 do {
|
for "_i" from 0 to 100 do {
|
||||||
private _bp = _building buildingPos _i;
|
private _bp = _building buildingPos _i;
|
||||||
@ -282,7 +282,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
private _hostageClass = selectRandom _hostageClasses;
|
private _hostageClass = selectRandom _hostageClasses;
|
||||||
|
|
||||||
private _hostagePos = [0,0,0];
|
private _hostagePos = [0,0,0];
|
||||||
if (!_useBuildingPositions) then {
|
if !(_useBuildingPositions) then {
|
||||||
private _bp = selectRandom _buildingPositions;
|
private _bp = selectRandom _buildingPositions;
|
||||||
_hostagePos = _bp;
|
_hostagePos = _bp;
|
||||||
} else {
|
} else {
|
||||||
@ -290,7 +290,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _hostage = _hostageGroup createUnit [_hostageClass, _hostagePos, [], 0, "NONE"];
|
private _hostage = _hostageGroup createUnit [_hostageClass, _hostagePos, [], 0, "NONE"];
|
||||||
if (!isNull _hostage) then {
|
if !(isNull _hostage) then {
|
||||||
_hostage setCaptive true;
|
_hostage setCaptive true;
|
||||||
_hostages pushBack _hostage;
|
_hostages pushBack _hostage;
|
||||||
};
|
};
|
||||||
@ -369,7 +369,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
_unitOffset set [1, (_unitOffset # 1) + (random 10 - 5)];
|
_unitOffset set [1, (_unitOffset # 1) + (random 10 - 5)];
|
||||||
|
|
||||||
private _shooter = _group createUnit [_unitClass, _shootBasePos vectorAdd _unitOffset, [], 0, "NONE"];
|
private _shooter = _group createUnit [_unitClass, _shootBasePos vectorAdd _unitOffset, [], 0, "NONE"];
|
||||||
if (!isNull _shooter) then {
|
if !(isNull _shooter) then {
|
||||||
_shooter setRank (_x getOrDefault ["rank", "PRIVATE"]);
|
_shooter setRank (_x getOrDefault ["rank", "PRIVATE"]);
|
||||||
_shooters pushBack _shooter;
|
_shooters pushBack _shooter;
|
||||||
};
|
};
|
||||||
@ -483,7 +483,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
// Put marker on ground.
|
// Put marker on ground.
|
||||||
private _ground = +_mPos;
|
private _ground = +_mPos;
|
||||||
private _safe = [_ground, 30, 0] call BIS_fnc_findSafePos;
|
private _safe = [_ground, 30, 0] call BIS_fnc_findSafePos;
|
||||||
if (!(_safe isEqualTo [0, 0, 0])) then {
|
if !((_safe isEqualTo [0, 0, 0])) then {
|
||||||
_ground = _safe;
|
_ground = _safe;
|
||||||
};
|
};
|
||||||
_ground set [2, 0];
|
_ground set [2, 0];
|
||||||
|
|||||||
@ -131,7 +131,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _blkListMarkers;
|
} forEach _blkListMarkers;
|
||||||
|
|
||||||
if (!_inBlkList) then {
|
if !(_inBlkList) then {
|
||||||
_taskPos = _candidate;
|
_taskPos = _candidate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -152,7 +152,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _buildingPositions = [];
|
private _buildingPositions = [];
|
||||||
if (!isNull _building) then {
|
if !(isNull _building) then {
|
||||||
for "_i" from 0 to 100 do {
|
for "_i" from 0 to 100 do {
|
||||||
private _buildingPos = _building buildingPos _i;
|
private _buildingPos = _building buildingPos _i;
|
||||||
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
||||||
@ -214,7 +214,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
|||||||
selectRandom _buildingPositions
|
selectRandom _buildingPositions
|
||||||
};
|
};
|
||||||
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
||||||
if (!isNull _escort) then {
|
if !(isNull _escort) then {
|
||||||
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
||||||
_escortUnits pushBack _escort;
|
_escortUnits pushBack _escort;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -55,7 +55,7 @@ if !(missionNamespace getVariable ["forge_pmc_defuseAceHandlerRegistered", false
|
|||||||
} forEach _this;
|
} forEach _this;
|
||||||
|
|
||||||
if (_taskID isEqualTo "") exitWith {};
|
if (_taskID isEqualTo "") exitWith {};
|
||||||
if (!isNil "forge_server_task_TaskStore") then {
|
if !(isNil "forge_server_task_TaskStore") then {
|
||||||
forge_server_task_TaskStore call ["incrementDefuseCount", [_taskID]];
|
forge_server_task_TaskStore call ["incrementDefuseCount", [_taskID]];
|
||||||
};
|
};
|
||||||
}] call CBA_fnc_addEventHandler;
|
}] call CBA_fnc_addEventHandler;
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
* Public: No
|
* Public: No
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!isServer) exitWith { 1 };
|
if !(isServer) exitWith { 1 };
|
||||||
|
|
||||||
private _table = missionNamespace getVariable [
|
private _table = missionNamespace getVariable [
|
||||||
"forge_pmc_enemyCountMultiplierTable",
|
"forge_pmc_enemyCountMultiplierTable",
|
||||||
|
|||||||
@ -8,7 +8,7 @@ Mission setup functions own startup configuration for `forge_pmc_simulator.Tanoa
|
|||||||
- `forge_pmc_fnc_setupMenu_applySettings` applies UI overrides or mission parameter defaults into `forge_pmc_missionSettings`.
|
- `forge_pmc_fnc_setupMenu_applySettings` applies UI overrides or mission parameter defaults into `forge_pmc_missionSettings`.
|
||||||
|
|
||||||
## Startup Flow
|
## Startup Flow
|
||||||
The client opens the setup UI from `initPlayerLocal.sqf` when `forge_pmc_missionSettingsApplied` is not set.
|
The CEO client opens the setup UI from `initPlayerLocal.sqf` when `forge_pmc_missionSettingsApplied` is not set. Other players do not receive the startup setup UI.
|
||||||
|
|
||||||
Selecting **Start Mission** sends UI values to the server and applies them. Selecting **Cancel** applies the existing Arma mission params/defaults immediately.
|
Selecting **Start Mission** sends UI values to the server and applies them. Selecting **Cancel** applies the existing Arma mission params/defaults immediately.
|
||||||
|
|
||||||
|
|||||||
@ -58,10 +58,22 @@ switch (_event) do {
|
|||||||
];
|
];
|
||||||
} forEach ([] call forge_pmc_fnc_getEnemyFactionOptions);
|
} forEach ([] call forge_pmc_fnc_getEnemyFactionOptions);
|
||||||
|
|
||||||
|
private _defaultFaction = "IND_G_F";
|
||||||
|
private _hasDefaultFaction = false;
|
||||||
|
{
|
||||||
|
if ((_x getOrDefault ["faction", ""]) isEqualTo _defaultFaction) exitWith {
|
||||||
|
_hasDefaultFaction = true;
|
||||||
|
};
|
||||||
|
} forEach _factions;
|
||||||
|
|
||||||
|
if (!_hasDefaultFaction && { _factions isNotEqualTo [] }) then {
|
||||||
|
_defaultFaction = (_factions select 0) getOrDefault ["faction", _defaultFaction];
|
||||||
|
};
|
||||||
|
|
||||||
private _payload = createHashMapFromArray [
|
private _payload = createHashMapFromArray [
|
||||||
["factions", _factions],
|
["factions", _factions],
|
||||||
["settings", createHashMapFromArray [
|
["settings", createHashMapFromArray [
|
||||||
["enemyFaction", "IND_G_F"],
|
["enemyFaction", _defaultFaction],
|
||||||
["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")],
|
["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")],
|
||||||
["missionInterval", getNumber (_missionConfig >> "missionInterval")],
|
["missionInterval", getNumber (_missionConfig >> "missionInterval")],
|
||||||
["moneyMin", (getArray (_attackConfig >> "Rewards" >> "money")) param [0, 25000]],
|
["moneyMin", (getArray (_attackConfig >> "Rewards" >> "money")) param [0, 25000]],
|
||||||
|
|||||||
@ -12,15 +12,15 @@
|
|||||||
* Public: No
|
* Public: No
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!hasInterface) exitWith { false };
|
if !(hasInterface) exitWith { false };
|
||||||
if (missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) exitWith { false };
|
if (missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) exitWith { false };
|
||||||
|
|
||||||
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
||||||
private _isCeoSlot =
|
private _isCeoSlot =
|
||||||
(!isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
!(isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||||
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
||||||
|
|
||||||
if (!_isCeoSlot) exitWith { false };
|
if !(_isCeoSlot) exitWith { false };
|
||||||
|
|
||||||
private _display = createDialog ["RscPmcMissionSetup", true];
|
private _display = createDialog ["RscPmcMissionSetup", true];
|
||||||
if (isNull _display) exitWith { false };
|
if (isNull _display) exitWith { false };
|
||||||
@ -46,7 +46,7 @@ _control ctrlWebBrowserAction ["LoadFile", "ui\_site\index.html"];
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _display = uiNamespace getVariable ["RscPmcMissionSetup", displayNull];
|
private _display = uiNamespace getVariable ["RscPmcMissionSetup", displayNull];
|
||||||
if (!isNull _display) then {
|
if !(isNull _display) then {
|
||||||
closeDialog 1;
|
closeDialog 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
* Public: No
|
* Public: No
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!isServer) exitWith {};
|
if !(isServer) exitWith {};
|
||||||
|
|
||||||
params [
|
params [
|
||||||
["_overrides", createHashMap, [createHashMap]]
|
["_overrides", createHashMap, [createHashMap]]
|
||||||
@ -56,13 +56,27 @@ private _timeMax = ["timeLimitMax", 1800] call _paramOrDefault;
|
|||||||
|
|
||||||
// Enemy faction selection falls back to Params::enemyFaction when the setup UI
|
// Enemy faction selection falls back to Params::enemyFaction when the setup UI
|
||||||
// is closed without pressing Start Mission.
|
// is closed without pressing Start Mission.
|
||||||
private _enemyFactionParam = ["enemyFaction", 7] call BIS_fnc_getParamValue;
|
private _enemyFactionParam = ["enemyFaction", 6] call BIS_fnc_getParamValue;
|
||||||
private _enemyFaction = _overrides getOrDefault ["enemyFaction", ""];
|
private _enemyFaction = _overrides getOrDefault ["enemyFaction", ""];
|
||||||
|
private _fallbackEnemyFaction = "IND_G_F";
|
||||||
|
private _factionOptions = [] call forge_pmc_fnc_getEnemyFactionOptions;
|
||||||
|
private _hasFallbackFaction = false;
|
||||||
|
{
|
||||||
|
_x params ["_optionFaction", "_display", "_value"];
|
||||||
|
if (_optionFaction isEqualTo _fallbackEnemyFaction) exitWith {
|
||||||
|
_hasFallbackFaction = true;
|
||||||
|
};
|
||||||
|
} forEach _factionOptions;
|
||||||
|
|
||||||
|
if (!_hasFallbackFaction && { _factionOptions isNotEqualTo [] }) then {
|
||||||
|
_fallbackEnemyFaction = (_factionOptions select 0) param [0, _fallbackEnemyFaction];
|
||||||
|
};
|
||||||
|
|
||||||
if (_enemyFaction isEqualTo "") then {
|
if (_enemyFaction isEqualTo "") then {
|
||||||
if (_enemyFactionParam isEqualTo -1) then {
|
if (_enemyFactionParam isEqualTo -1) then {
|
||||||
_enemyFactionParam = 7;
|
_enemyFactionParam = 6;
|
||||||
};
|
};
|
||||||
_enemyFaction = [_enemyFactionParam, "IND_G_F"] call forge_pmc_fnc_resolveEnemyFactionParam;
|
_enemyFaction = [_enemyFactionParam, _fallbackEnemyFaction] call forge_pmc_fnc_resolveEnemyFactionParam;
|
||||||
} else {
|
} else {
|
||||||
_enemyFactionParam = _enemyFaction;
|
_enemyFactionParam = _enemyFaction;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
||||||
private _isCeoSlot =
|
private _isCeoSlot =
|
||||||
(!isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
!(isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||||
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
||||||
|
|
||||||
if (_isCeoSlot && { !(missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) }) then {
|
if (_isCeoSlot && { !(missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) }) then {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user