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:
|
||||
* - forge_pmc_fnc_getEnemyFactionOptions reads this config for the startup UI.
|
||||
* - forge_pmc_fnc_resolveEnemyFactionParam maps mission param values back to
|
||||
* faction classnames during fallback/default setup.
|
||||
* - forge_pmc_fnc_getEnemyFactionOptions scans loaded CfgFactionClasses and
|
||||
* CfgVehicles at runtime, then uses this config to filter and polish the UI.
|
||||
* - 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
|
||||
* applied the selected option.
|
||||
*
|
||||
* Keep this list aligned with the server modpack. Classnames that are not
|
||||
* present in CfgFactionClasses may still appear in the UI, but downstream unit
|
||||
* pool generation may fall back or fail to find units.
|
||||
*
|
||||
* BLUFOR/WEST factions are intentionally omitted because generated missions use
|
||||
* these options as opposing forces.
|
||||
* This config is intentionally not the full faction list. Any loaded mod
|
||||
* faction on an allowed side can appear automatically if it has spawnable
|
||||
* infantry or a CfgFactionUnitMap override.
|
||||
*/
|
||||
class CfgEnemyFactions {
|
||||
/*
|
||||
* Option format:
|
||||
* - class name: stable config entry name, normally matching the faction.
|
||||
* - value: legacy mission param numeric value.
|
||||
* - faction: CfgFactionClasses classname used for spawning.
|
||||
* - display: user-facing label shown in the setup UI.
|
||||
* Arma side IDs allowed as generated enemy factions:
|
||||
* - 0: EAST / OPFOR
|
||||
* - 2: RESISTANCE / Independent
|
||||
*
|
||||
* WEST / BLUFOR is intentionally excluded because generated missions use
|
||||
* these options as opposing forces.
|
||||
*/
|
||||
class Options {
|
||||
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"; };
|
||||
class OPF_R_F { value = 3; faction = "OPF_R_F"; display = "Spetnaz"; };
|
||||
class OPF_SFIA_lxWS { value = 4; faction = "OPF_SFIA_lxWS"; display = "SFIA"; };
|
||||
class OPF_TURA_lxWS { value = 5; faction = "OPF_TURA_lxWS"; display = "Tura"; };
|
||||
class IND_F { value = 6; faction = "IND_F"; display = "AAF"; };
|
||||
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"; };
|
||||
class IND_SFIA_lxWS { value = 11; faction = "IND_SFIA_lxWS"; display = "SFIA"; };
|
||||
class IND_TURA_lxWS { value = 12; faction = "IND_TURA_lxWS"; display = "Tura"; };
|
||||
sides[] = {0, 2};
|
||||
|
||||
/*
|
||||
* Factions that should never be offered even if present in the active
|
||||
* modset. Keep this for factions that are unsuitable for PMC contracts.
|
||||
*/
|
||||
denylist[] = {
|
||||
"IND_UN_lxWS"
|
||||
};
|
||||
|
||||
/*
|
||||
* Optional display/order/value metadata for known factions.
|
||||
*
|
||||
* - 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:
|
||||
* - Enemy unit pools are primarily built from CfgGroups/CfgVehicles through
|
||||
* forge_pmc_fnc_getEnemyFactionUnitPool.
|
||||
* - This config is a template for deterministic per-faction unit pools if the
|
||||
* automatic faction lookup is not specific enough for a server/modpack.
|
||||
* - forge_pmc_fnc_getEnemyFactionOptions treats a mapped faction as selectable
|
||||
* when at least one mapped vehicle exists.
|
||||
* - forge_pmc_fnc_getEnemyFactionUnitPool checks this map first.
|
||||
* - 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
|
||||
* generator spawn path before falling back to config traversal.
|
||||
* Most mod factions do not need an entry here. Add a class only when a faction
|
||||
* needs a curated or corrected spawn pool.
|
||||
*/
|
||||
class CfgFactionUnitMap {
|
||||
/*
|
||||
* 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 {
|
||||
/*
|
||||
|
||||
@ -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 enemyFaction {
|
||||
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[] = {
|
||||
"CSAT",
|
||||
"CSAT (Pacific)",
|
||||
"Viper",
|
||||
"Spetnaz",
|
||||
"SFIA (OPFOR)",
|
||||
"Tura (OPFOR)",
|
||||
@ -14,10 +21,9 @@ class Params {
|
||||
"LDF",
|
||||
"Syndikat",
|
||||
"Looters",
|
||||
"SFIA (Independent)",
|
||||
"Tura (Independent)"
|
||||
};
|
||||
default = 7;
|
||||
default = 6;
|
||||
};
|
||||
|
||||
class maxConcurrentMissions {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
#include "CfgFunctions.h"
|
||||
@ -24,6 +24,7 @@ corpseManagerMode = 0;
|
||||
|
||||
#include "CfgParams.hpp"
|
||||
#include "CfgEnemyFactions.hpp"
|
||||
#include "CfgFactionUnitMap.hpp"
|
||||
#include "CfgMissions.hpp"
|
||||
#include "CfgFunctions.hpp"
|
||||
#include "ui\baseControls.hpp"
|
||||
|
||||
@ -4,7 +4,7 @@ Helper functions provide reusable lookups and conversions for the PMC simulator
|
||||
|
||||
## Registered Functions
|
||||
- `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_getEnemyFactionSide` resolves a faction classname to an Arma side.
|
||||
- `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.
|
||||
|
||||
## 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.
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
/*
|
||||
* Author: IDSolutions, Blackbox AI, MrPākehā
|
||||
* Returns candidate enemy faction classnames available to the mission.
|
||||
* This is a runtime helper only; description.ext mission params still use
|
||||
* the static CfgEnemyFactions list.
|
||||
* This is a runtime helper only. The setup UI uses
|
||||
* forge_pmc_fnc_getEnemyFactionOptions so it can filter to spawnable factions.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Exclude BLUFOR/WEST factions <BOOL> (Default: true)
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Author: IDSolutions, Blackbox AI, MrPākehā
|
||||
* Reads enemy faction options from missionConfigFile >> CfgEnemyFactions
|
||||
* >> Options for setup UI hydration and mission param fallback resolution.
|
||||
* Author: IDSolutions, Blackbox AI, MrPakeha
|
||||
* Builds setup UI faction options from the active modset. The helper scans
|
||||
* loaded faction classes and only returns allowed OPFOR/Independent factions
|
||||
* that can provide infantry through CfgVehicles or CfgFactionUnitMap.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
@ -12,40 +13,122 @@
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
private _optionsConfig = missionConfigFile >> "CfgEnemyFactions" >> "Options";
|
||||
private _options = [];
|
||||
|
||||
if (isClass _optionsConfig) then {
|
||||
{
|
||||
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 _config = missionConfigFile >> "CfgEnemyFactions";
|
||||
private _allowedSides = getArray (_config >> "sides");
|
||||
if (_allowedSides isEqualTo []) then {
|
||||
_allowedSides = [0, 2];
|
||||
};
|
||||
|
||||
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 {
|
||||
_options = [
|
||||
["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 _cfgFaction = configFile >> "CfgFactionClasses" >> _enemyFaction;
|
||||
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");
|
||||
|
||||
if (_sideNumber > 0 || { _sideText isEqualTo "0" }) then {
|
||||
if (_hasSideNumber || { _sideText in ["0", "1", "2"] }) then {
|
||||
if (!_hasSideNumber) then {
|
||||
_sideNumber = parseNumber _sideText;
|
||||
};
|
||||
|
||||
switch (_sideNumber) do {
|
||||
case 0: { _side = east; };
|
||||
case 2: { _side = resistance; };
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
* Arguments:
|
||||
* 0: Faction classname <STRING> (Default: ENEMY_FACTION_STR or "IND_G_F")
|
||||
* 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:
|
||||
* Unit definitions with vehicle, rank, and position keys <ARRAY>
|
||||
@ -15,7 +17,8 @@
|
||||
|
||||
params [
|
||||
["_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 {
|
||||
@ -25,33 +28,56 @@ if (_faction isEqualTo "") then {
|
||||
private _pool = [];
|
||||
private _sideNumber = [_fallbackSide] call BIS_fnc_sideID;
|
||||
|
||||
{
|
||||
if (getNumber (_x >> "scope") < 2) then { continue; };
|
||||
if (getText (_x >> "faction") isNotEqualTo _faction) then { continue; };
|
||||
if (getNumber (_x >> "side") isNotEqualTo _sideNumber) then { continue; };
|
||||
if !(configName _x isKindOf "CAManBase") then { continue; };
|
||||
// Check CfgFactionUnitMap first for explicit faction unit definitions
|
||||
private _factionMapConfig = missionConfigFile >> "CfgFactionUnitMap" >> _faction;
|
||||
if (isClass _factionMapConfig) then {
|
||||
{
|
||||
private _vehicle = getText (_x >> "vehicle");
|
||||
if (_vehicle isEqualTo "" || { !(isClass (configFile >> "CfgVehicles" >> _vehicle)) }) 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"));
|
||||
_pool pushBack createHashMapFromArray [
|
||||
["vehicle", _vehicle],
|
||||
["rank", getText (_x >> "rank")],
|
||||
["position", getArray (_x >> "position")]
|
||||
];
|
||||
} forEach ("true" configClasses (_factionMapConfig >> "Units"));
|
||||
};
|
||||
|
||||
// Fall back to config traversal if no explicit mapping exists.
|
||||
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 {
|
||||
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"] };
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* 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:
|
||||
* 0: Listbox/combo control or display <CONTROL|DISPLAY>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* enemy faction classname.
|
||||
*
|
||||
* 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")
|
||||
*
|
||||
* Return Value:
|
||||
@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
params [
|
||||
["_value", 7, [0, ""]],
|
||||
["_value", 6, [0, ""]],
|
||||
["_fallback", "IND_G_F", [""]]
|
||||
];
|
||||
|
||||
|
||||
@ -131,7 +131,7 @@ AttackMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
|
||||
@ -131,7 +131,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -152,7 +152,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _buildingPositions = [];
|
||||
if (!isNull _building) then {
|
||||
if !(isNull _building) then {
|
||||
for "_i" from 0 to 100 do {
|
||||
private _buildingPos = _building buildingPos _i;
|
||||
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
||||
@ -214,7 +214,7 @@ CaptureHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
selectRandom _buildingPositions
|
||||
};
|
||||
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
||||
if (!isNull _escort) then {
|
||||
if !(isNull _escort) then {
|
||||
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
||||
_escortUnits pushBack _escort;
|
||||
};
|
||||
|
||||
@ -131,7 +131,7 @@ DefendMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
|
||||
@ -132,7 +132,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -297,7 +297,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
if (_nearBuildings isNotEqualTo []) then {
|
||||
// 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;
|
||||
};
|
||||
} forEach _nearBuildings;
|
||||
@ -313,14 +313,14 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
};
|
||||
|
||||
if (!isNull _building) then {
|
||||
if !(isNull _building) then {
|
||||
for "_i" from 1 to _buildingSpawnAttempts do {
|
||||
private _posIndex = floor random 1000;
|
||||
private _candidate = _building buildingPos _posIndex;
|
||||
// buildingPos returns [0,0,0] for invalid positions
|
||||
if (_candidate isEqualTo [0, 0, 0]) then { continue; };
|
||||
// ensure candidate is still inside the building footprint
|
||||
if (!(_candidate isEqualType [])) then { continue; };
|
||||
if !((_candidate isEqualType [])) then { continue; };
|
||||
if ((_candidate vectorDistance _position) <= 60) exitWith {
|
||||
_buildingPos = _candidate;
|
||||
};
|
||||
@ -328,7 +328,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _protectedPos = [0,0,0];
|
||||
if (!(_buildingPos isEqualTo [])) then {
|
||||
if !((_buildingPos isEqualTo [])) then {
|
||||
_protectedPos = _buildingPos;
|
||||
} else {
|
||||
// Outdoor fallback: keep previous behavior
|
||||
@ -337,7 +337,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
|
||||
private _protectedObject = createVehicle [_protectedClass, _protectedPos, [], 0, "NONE"];
|
||||
private _protectedObjects = [];
|
||||
if (!isNull _protectedObject) then {
|
||||
if !(isNull _protectedObject) then {
|
||||
_protectedObjects pushBack _protectedObject;
|
||||
};
|
||||
|
||||
@ -356,7 +356,7 @@ DefuseMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
private _devicePos = _protectedPos vectorAdd _deviceOffset;
|
||||
|
||||
private _deviceObject = createVehicle [_deviceClass, _devicePos, [], 0, "NONE"];
|
||||
if (!isNull _deviceObject) then {
|
||||
if !(isNull _deviceObject) then {
|
||||
_devices pushBack _deviceObject;
|
||||
};
|
||||
};
|
||||
|
||||
@ -131,7 +131,7 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -193,22 +193,22 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
if (_cargoPool isEqualTo []) exitWith { [] };
|
||||
|
||||
private _cargoSpawnObj = objNull;
|
||||
if (!isNil "cargoSpawn") then { _cargoSpawnObj = cargoSpawn; };
|
||||
if !(isNil "cargoSpawn") then { _cargoSpawnObj = cargoSpawn; };
|
||||
if (isNull _cargoSpawnObj) then { _cargoSpawnObj = missionNamespace getVariable ["cargoSpawn", 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]; };
|
||||
|
||||
for "_i" from 1 to _cargoCount do {
|
||||
private _cargoClass = selectRandom _cargoPool;
|
||||
|
||||
private _spawnPos = [0, 0, 0];
|
||||
if (!isNull _cargoSpawnObj) then {
|
||||
if !(isNull _cargoSpawnObj) then {
|
||||
private _basePos = getPosATL _cargoSpawnObj;
|
||||
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
||||
} else {
|
||||
if (!isNull _extZoneObj) then {
|
||||
if !(isNull _extZoneObj) then {
|
||||
private _basePos = getPosATL _extZoneObj;
|
||||
_spawnPos = _basePos vectorAdd [(random 12 - 6), (random 12 - 6), 0];
|
||||
} else {
|
||||
@ -217,7 +217,7 @@ DeliveryMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _cargoObject = createVehicle [_cargoClass, _spawnPos, [], 0, "NONE"];
|
||||
if (!isNull _cargoObject) then {
|
||||
if !(isNull _cargoObject) then {
|
||||
_cargoObjects pushBack _cargoObject;
|
||||
};
|
||||
};
|
||||
|
||||
@ -132,7 +132,7 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -349,13 +349,13 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
_position
|
||||
]] call forge_server_common_fnc_log;
|
||||
|
||||
if (!isNull _defendGroup) then {
|
||||
if !(isNull _defendGroup) then {
|
||||
deleteGroup _defendGroup;
|
||||
};
|
||||
};
|
||||
|
||||
private _spawnedGroups = [_group];
|
||||
if (!isNull _defendGroup && { units _defendGroup isNotEqualTo [] }) then {
|
||||
if !(isNull _defendGroup && { units _defendGroup isNotEqualTo [] }) then {
|
||||
_spawnedGroups pushBack _defendGroup;
|
||||
};
|
||||
|
||||
@ -442,7 +442,7 @@ DestroyMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
private _position = _missionRecord getOrDefault ["position", []];
|
||||
private _groups = _missionRecord getOrDefault ["groups", []];
|
||||
{
|
||||
if (!isNull _x) then {
|
||||
if !(isNull _x) then {
|
||||
{ deleteVehicle _x; } forEach (units _x);
|
||||
deleteGroup _x;
|
||||
};
|
||||
|
||||
@ -132,7 +132,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -155,7 +155,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _buildingPositions = [];
|
||||
if (!isNull _building) then {
|
||||
if !(isNull _building) then {
|
||||
// buildingPos returns positions for building interiors; we random-pick from these.
|
||||
for "_i" from 0 to 100 do {
|
||||
private _bp = _building buildingPos _i;
|
||||
@ -282,7 +282,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
private _hostageClass = selectRandom _hostageClasses;
|
||||
|
||||
private _hostagePos = [0,0,0];
|
||||
if (!_useBuildingPositions) then {
|
||||
if !(_useBuildingPositions) then {
|
||||
private _bp = selectRandom _buildingPositions;
|
||||
_hostagePos = _bp;
|
||||
} else {
|
||||
@ -290,7 +290,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _hostage = _hostageGroup createUnit [_hostageClass, _hostagePos, [], 0, "NONE"];
|
||||
if (!isNull _hostage) then {
|
||||
if !(isNull _hostage) then {
|
||||
_hostage setCaptive true;
|
||||
_hostages pushBack _hostage;
|
||||
};
|
||||
@ -369,7 +369,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
_unitOffset set [1, (_unitOffset # 1) + (random 10 - 5)];
|
||||
|
||||
private _shooter = _group createUnit [_unitClass, _shootBasePos vectorAdd _unitOffset, [], 0, "NONE"];
|
||||
if (!isNull _shooter) then {
|
||||
if !(isNull _shooter) then {
|
||||
_shooter setRank (_x getOrDefault ["rank", "PRIVATE"]);
|
||||
_shooters pushBack _shooter;
|
||||
};
|
||||
@ -483,7 +483,7 @@ HostageMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
// Put marker on ground.
|
||||
private _ground = +_mPos;
|
||||
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 set [2, 0];
|
||||
|
||||
@ -131,7 +131,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
} forEach _blkListMarkers;
|
||||
|
||||
if (!_inBlkList) then {
|
||||
if !(_inBlkList) then {
|
||||
_taskPos = _candidate;
|
||||
};
|
||||
};
|
||||
@ -152,7 +152,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
};
|
||||
|
||||
private _buildingPositions = [];
|
||||
if (!isNull _building) then {
|
||||
if !(isNull _building) then {
|
||||
for "_i" from 0 to 100 do {
|
||||
private _buildingPos = _building buildingPos _i;
|
||||
if (_buildingPos isEqualTo [0, 0, 0]) exitWith {};
|
||||
@ -214,7 +214,7 @@ KillHvtMissionGeneratorBaseClass = compileFinal createHashMapFromArray [
|
||||
selectRandom _buildingPositions
|
||||
};
|
||||
private _escort = _group createUnit [_escortClass, _escortPos, [], 0, "NONE"];
|
||||
if (!isNull _escort) then {
|
||||
if !(isNull _escort) then {
|
||||
_escort setRank (_escortDef getOrDefault ["rank", "PRIVATE"]);
|
||||
_escortUnits pushBack _escort;
|
||||
};
|
||||
|
||||
@ -55,7 +55,7 @@ if !(missionNamespace getVariable ["forge_pmc_defuseAceHandlerRegistered", false
|
||||
} forEach _this;
|
||||
|
||||
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]];
|
||||
};
|
||||
}] call CBA_fnc_addEventHandler;
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
if (!isServer) exitWith { 1 };
|
||||
if !(isServer) exitWith { 1 };
|
||||
|
||||
private _table = missionNamespace getVariable [
|
||||
"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`.
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@ -58,10 +58,22 @@ switch (_event) do {
|
||||
];
|
||||
} 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 [
|
||||
["factions", _factions],
|
||||
["settings", createHashMapFromArray [
|
||||
["enemyFaction", "IND_G_F"],
|
||||
["enemyFaction", _defaultFaction],
|
||||
["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")],
|
||||
["missionInterval", getNumber (_missionConfig >> "missionInterval")],
|
||||
["moneyMin", (getArray (_attackConfig >> "Rewards" >> "money")) param [0, 25000]],
|
||||
|
||||
@ -12,15 +12,15 @@
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
if (!hasInterface) exitWith { false };
|
||||
if !(hasInterface) exitWith { false };
|
||||
if (missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) exitWith { false };
|
||||
|
||||
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
||||
private _isCeoSlot =
|
||||
(!isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||
!(isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
||||
|
||||
if (!_isCeoSlot) exitWith { false };
|
||||
if !(_isCeoSlot) exitWith { false };
|
||||
|
||||
private _display = createDialog ["RscPmcMissionSetup", true];
|
||||
if (isNull _display) exitWith { false };
|
||||
@ -46,7 +46,7 @@ _control ctrlWebBrowserAction ["LoadFile", "ui\_site\index.html"];
|
||||
};
|
||||
|
||||
private _display = uiNamespace getVariable ["RscPmcMissionSetup", displayNull];
|
||||
if (!isNull _display) then {
|
||||
if !(isNull _display) then {
|
||||
closeDialog 1;
|
||||
};
|
||||
};
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
* Public: No
|
||||
*/
|
||||
|
||||
if (!isServer) exitWith {};
|
||||
if !(isServer) exitWith {};
|
||||
|
||||
params [
|
||||
["_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
|
||||
// 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 _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 (_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 {
|
||||
_enemyFactionParam = _enemyFaction;
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
private _ceoUnit = missionNamespace getVariable ["ceo", objNull];
|
||||
private _isCeoSlot =
|
||||
(!isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||
!(isNull _ceoUnit && { player isEqualTo _ceoUnit }) ||
|
||||
{ toLowerANSI (vehicleVarName player) isEqualTo "ceo" };
|
||||
|
||||
if (_isCeoSlot && { !(missionNamespace getVariable ["forge_pmc_missionSettingsApplied", false]) }) then {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user