diff --git a/addons/modules/$PBOPREFIX$ b/addons/modules/$PBOPREFIX$
new file mode 100644
index 00000000..8898fad7
--- /dev/null
+++ b/addons/modules/$PBOPREFIX$
@@ -0,0 +1 @@
+x\tmf\addons\modules
\ No newline at end of file
diff --git a/addons/modules/CfgEventHandlers.hpp b/addons/modules/CfgEventHandlers.hpp
new file mode 100644
index 00000000..182c3de4
--- /dev/null
+++ b/addons/modules/CfgEventHandlers.hpp
@@ -0,0 +1,16 @@
+
+XEH_PRESTART;
+XEH_PREINIT;
+
+class Extended_InitPost_EventHandlers {
+ class GVAR(ambientVehicles) {
+ class ADDON {
+ init = QUOTE( \
+ params ['_logic']; \
+ if (local _logic) then { \
+ [ARR_2('preInit', [_logic])] call FUNC(ambientVehicleInit); \
+ }; \
+ );
+ };
+ };
+};
diff --git a/addons/modules/CfgModules.hpp b/addons/modules/CfgModules.hpp
new file mode 100644
index 00000000..6da5fc95
--- /dev/null
+++ b/addons/modules/CfgModules.hpp
@@ -0,0 +1,22 @@
+class CfgVehicles {
+ class Logic;
+ class Module_F: Logic {
+ class AttributesBase {
+ class Default;
+ class Edit;
+ class Combo;
+ class Checkbox;
+ class CheckboxNumber;
+ class ModuleDescription;
+ class Units;
+ };
+ class ArgumentsBaseUnits {
+ class Units;
+ };
+ class ModuleDescription {
+ class AnyBrain;
+ };
+ class EventHandlers;
+ };
+ #include "modules\ambientVehicles.hpp"
+};
diff --git a/addons/modules/XEH_PREP.sqf b/addons/modules/XEH_PREP.sqf
new file mode 100644
index 00000000..65c2e525
--- /dev/null
+++ b/addons/modules/XEH_PREP.sqf
@@ -0,0 +1,2 @@
+PREP(createAmbientVehicles);
+PREP(ambientVehicleInit);
diff --git a/addons/modules/XEH_preInit.sqf b/addons/modules/XEH_preInit.sqf
new file mode 100644
index 00000000..43f1adee
--- /dev/null
+++ b/addons/modules/XEH_preInit.sqf
@@ -0,0 +1,3 @@
+#include "script_component.hpp"
+
+#include "XEH_PREP.sqf"
diff --git a/addons/modules/XEH_preStart.sqf b/addons/modules/XEH_preStart.sqf
new file mode 100644
index 00000000..43f1adee
--- /dev/null
+++ b/addons/modules/XEH_preStart.sqf
@@ -0,0 +1,3 @@
+#include "script_component.hpp"
+
+#include "XEH_PREP.sqf"
diff --git a/addons/modules/config.cpp b/addons/modules/config.cpp
new file mode 100644
index 00000000..4eb22c81
--- /dev/null
+++ b/addons/modules/config.cpp
@@ -0,0 +1,24 @@
+#include "script_component.hpp"
+
+class CfgPatches
+{
+ class ADDON
+ {
+ name = "TMF: Modules";
+ author = "TMF Team";
+ url = "http://www.teamonetactical.com";
+ units[] = {
+ QGVAR(ambientVehicles)
+ };
+ weapons[] = {};
+ requiredVersion = REQUIRED_VERSION;
+ requiredAddons[] = {
+ "tmf_common",
+ "tmf_ai"
+ };
+ VERSION_CONFIG;
+ };
+};
+
+#include "CfgEventHandlers.hpp"
+#include "CfgModules.hpp"
diff --git a/addons/modules/functions/fnc_ambientVehicleInit.sqf b/addons/modules/functions/fnc_ambientVehicleInit.sqf
new file mode 100644
index 00000000..d54e3274
--- /dev/null
+++ b/addons/modules/functions/fnc_ambientVehicleInit.sqf
@@ -0,0 +1,180 @@
+#include "../script_component.hpp"
+/* ----------------------------------------------------------------------------
+Internal Function: TMF_modules_fnc_ambientVehicleInit
+
+Description:
+ Initializes the Ambient Vehicles module,
+ wrapper for the createAmbientVehicles function.
+
+Parameters:
+ Standard module parameters
+
+Returns:
+ Nothing
+
+Author:
+ Freddo
+---------------------------------------------------------------------------- */
+
+params ["_mode", "_input"];
+TRACE_2("Initializing Ambient Vehicles module",_mode,_input);
+_input params ["_logic"];
+
+private _code = _logic getVariable [QGVAR(code), ""];
+private _lockedRate = _logic getVariable [QGVAR(lockedRate), 0];
+private _spacing = _logic getVariable [QGVAR(spacing), 2];
+private _vehicleNumber = _logic getVariable [QGVAR(vehicleNumber), 5];
+private _emptyCargo = _logic getVariable [QGVAR(emptyCargo), false];
+
+switch _mode do {
+ // Default object init
+ case "init": {
+ _input params ["", "_isActivated", "_isCuratorPlaced"];
+
+ if !(_isActivated) exitWith {
+ // Clean up vehicles far from players when deactivated on repeatable triggers
+ private _count = 0;
+ {
+ private _obj = _x;
+ // Assumes that triggers are set at least 500m away
+ if (alive _obj && (allPlayers findIf {_x distance2D _obj < 500}) == -1) then {
+ deleteVehicle _obj;
+ TRACE_2("Ambient vehicles despawning vehicle",_logic,_obj);
+ INC(_count);
+ };
+ } forEach (_logic getVariable [QGVAR(spawnedVehicles), []]);
+ TRACE_2("Ambient vehicles finished despawning vehicles",_logic,_count);
+ _logic setVariable [QGVAR(vehicleNumber), _count, true];
+ true
+ };
+
+ if (isNil {_logic getVariable QGVAR(data)}) then {
+ TRACE_1("Ambient Vehicles post init run before pre init, running preinit now",_logic);
+ ["preInit", _logic] call FUNC(ambientVehicleInit);
+ };
+
+ private _moduleData = _logic getVariable QGVAR(data);
+ ASSERT_DEFINED("_moduleData","Ambient Vehicles module failed init, postInit ran before preInit!");
+
+ _moduleData params [
+ ["_vehicleTypes", []],
+ ["_area", []]
+ ];
+
+ if (_area isEqualTo [] || {_area isEqualTo ([] call BIS_fnc_getArea)}) exitWith {
+ ERROR_MSG("No area module synchronized to Ambient Vehicles module: %1",_logic);
+ };
+
+ if (_vehicleTypes isEqualTo []) exitWith {
+ ERROR_MSG("No vehicles synchronized to Ambient Vehicles module: %1",_logic);
+ };
+
+ private _vehicles = [_area, _vehicleTypes, _vehicleNumber, _spacing] call FUNC(createAmbientVehicles);
+
+ {
+ if (_emptyCargo) then {
+ clearWeaponCargoGlobal _x;
+ clearMagazineCargoGlobal _x;
+ clearItemCargoGlobal _x;
+ clearBackpackCargoGlobal _x;
+ };
+
+ if (random 1 < _lockedRate) then {
+ _x setVehicleLock "LOCKED";
+ ["init",_x] call bis_fnc_carAlarm;
+ };
+
+ _x call _code;
+
+ } forEach _vehicles;
+
+ TRACE_2("Ambient Vehicles module spawned vehicles",_logic,_vehicles);
+
+ _logic setVariable [QGVAR(spawnedVehicles), _vehicles, true];
+ };
+
+ case "preInit": {
+ private ["_syncedObjects", "_area"];
+
+ private _moduleData = _logic getVariable QGVAR(data);
+ if (!is3DEN && !isNil "_moduleData") exitWith {
+ TRACE_2("Tried to run preInit on Ambient Vehicles module, but preInit has already been run",_logic,_moduleData);
+ };
+
+ if is3DEN then {
+ private _connections = (get3DENConnections _logic);;
+ FILTER(_connections,(_x select 0) isEqualTo "Sync");
+ _syncedObjects = _connections apply {_x # 1};
+ _area = (_syncedObjects select {_x isKindOf QEGVAR(ai,area)}) param [0, objNull];
+
+ ((_area get3DENAttribute "size2") # 0) params [
+ "_a",
+ "_b"
+ ];
+
+ _area = [
+ getPos _area,
+ [
+ _a,
+ _b,
+ direction _area,
+ (_area get3DENAttribute "IsRectangle") # 0
+ ]
+ ] call BIS_fnc_getArea;
+ } else {
+ _syncedObjects = synchronizedObjects _logic;
+ _area = (_syncedObjects select {_x isKindOf QEGVAR(ai,area)}) param [0, objNull];
+ _area = [getPos _area,_area getVariable "objectarea"] call BIS_fnc_getArea;
+ };
+
+ _syncedObjects = _syncedObjects select {
+ (_x call BIS_fnc_objectType) params ["_category", "_type"];
+ _category in ["Vehicle", "VehicleAutonomous", "Object"] &&
+ !(_type in [
+ "Ship", "Submarine",
+ "Animal", "Camera", "Effect", "Fire", "Marker", "Parachute",
+ "Seagull", "Sound", "Target", "Trigger", "UnknownObject", "VASI"
+ ])
+ };
+ private _vehicleTypes = _syncedObjects apply {typeOf _x};
+
+ // Clean up objects later
+ [{
+ {
+ deleteVehicle _x;
+ } forEach _this;
+ }, _syncedObjects] call CBA_fnc_execNextFrame;
+
+ TRACE_2("Ambient Vehicle PreInit run with result",_vehicleTypes,_area);
+ _logic setVariable [QGVAR(data), [_vehicleTypes, _area]];
+ };
+
+ // When some attributes were changed (including position and rotation)
+ case "attributesChanged3DEN";
+ // When connection to object changes (i.e., new one is added or existing one removed)
+ case "connectionChanged3DEN": {
+ ["preInit", [_logic]] call FUNC(ambientVehicleInit);
+
+ (_logic getVariable QGVAR(data)) params [
+ ["_vehicleTypes", []],
+ ["_area", objNull]
+ ];
+
+ {
+ deleteVehicle _x;
+ } forEach (_logic getVariable [QGVAR(spawnedVehicles), []]);
+
+ private _vehicles = [_area, _vehicleTypes, _vehicleNumber, _spacing] call FUNC(createAmbientVehicles);
+ {_x call _code;} forEach _vehicles;
+
+ _logic setVariable [QGVAR(spawnedVehicles), _vehicles, true];
+ };
+
+ // When removed from the world (i.e., by deletion or undoing creation)
+ case "unregisteredFromWorld3DEN": {
+ {
+ deleteVehicle _x;
+ } forEach (_logic getVariable [QGVAR(spawnedVehicles), []]);
+ };
+};
+true
diff --git a/addons/modules/functions/fnc_createAmbientVehicles.sqf b/addons/modules/functions/fnc_createAmbientVehicles.sqf
new file mode 100644
index 00000000..9e6a1db9
--- /dev/null
+++ b/addons/modules/functions/fnc_createAmbientVehicles.sqf
@@ -0,0 +1,239 @@
+#include "../script_component.hpp"
+/* ----------------------------------------------------------------------------
+Internal Function: TMF_modules_fnc_createAmbientVehicles
+
+Description:
+ Spawns empty vehicles close to roads in an area,
+ script taken from Quarry mission.
+
+Parameters:
+ _area - Area to cover [Area array, trigger or marker]
+ _vehicles - Vehicle types to spawn [Array of class names]
+ _vehicleCount - Number of vehicles to spawn [Number, default 1]
+ _spacing - Number of road segments between spawned vehicles [Number, Default 2]
+
+Returns:
+ List of spawned vehicles [Array of objects]
+
+Author:
+ Snippers(?), Freddo
+---------------------------------------------------------------------------- */
+params [
+ "_area",
+ "_vehicles",
+ ["_vehicleCount", 1, [1]],
+ ["_spacing", 2, [-1]]
+];
+
+_area = _area call BIS_fnc_getArea;
+
+TRACE_4("Creating ambient vehicles",_area,_vehicles,_vehicleCount,_code);
+
+private _createdVehicles = [];
+
+#ifdef DEBUG_MODE_FULL
+ // Initialize or cleanup debug marks
+ if (isNil QGVAR(vehicleDebugMarkers)) then {
+ GVAR(vehicleDebugMarkers) = 0;
+ } else {
+ {
+ deleteMarker _x;
+ } forEach (allMapMarkers select {(_x find QGVAR(roadDebugMark_)) == 0});
+ };
+
+ private _fnc_createDebugMarker = {
+ params [["_road",objNull]];
+
+ if (isNull _road) exitWith {};
+
+ private _childSegments = roadsConnectedTo _road;
+ private _txt = format [QGVAR(roadDebugMark_%1),GVAR(vehicleDebugMarkers)];
+ private _debugMkr = createMarker [_txt, getPos _road];
+ _debugMkr setMarkerShape "ICON";
+ _debugMkr setMarkerType "hd_dot";
+ _debugMkr setMarkerText format ["%1",count _childSegments];
+ switch (count _childSegments) do {
+ case 0: { _debugMkr setMarkerColor "ColorBlue";};
+ case 1: {_debugMkr setMarkerColor "ColorRed";};
+ case 2: {_debugMkr setMarkerColor "ColorGreen";};
+ case 3: {_debugMkr setMarkerColor "ColorYellow";};
+ case 4: {_debugMkr setMarkerColor "ColorOrange";};
+ case 5: {_debugMkr setMarkerColor "ColorPink";};
+ case 6: {_debugMkr setMarkerColor "ColorWhite";};
+ case 7: {_debugMkr setMarkerColor "ColorBrown";};
+ default {};
+ };
+
+ INC(GVAR(vehicleDebugMarkers));
+ };
+#endif
+
+private _fnc_findRoadConnections = {
+ params ["_road", ["_depth", 2, [-1]]];
+
+ #ifdef DEBUG_MODE_FULL
+ [_road] call _fnc_createDebugMarker;
+ #endif
+
+ private _return = [_road];
+ if (_depth > 0) then {
+ {
+ // Filter out isPedestrian roads
+ if !((getRoadInfo _x) # 2) then {
+ _return = _return + ([_x, _depth - 1] call _fnc_findRoadConnections);
+ };
+ } forEach (roadsConnectedTo _road);
+ };
+
+ _return
+};
+
+private _fnc_isNearIntersection = {
+ //Primary logic intersection either has 1 or 2+ connections.
+ params ["_road"];
+
+ private _roadConnectedTo = roadsConnectedTo _road;
+ private _return = false;
+
+ if ((count _roadConnectedTo != 2)) then {
+ _return = true;
+ } else {
+ {
+ if (count (roadsConnectedTo _x) != 2) exitWith { _return = true};
+ } forEach (_roadConnectedTo);
+ };
+
+ _return
+};
+
+// Code start here
+_area params ["_center", "_sizeX", "_sizeY", "_dir", "_isRect", "_height"];
+
+private _size = if _isRect then {
+ sqrt ((_sizeX ^ 2) + (_sizeY ^ 2))
+} else {
+ _sizeX max _sizeY
+};
+private _roadPosArray = (_center nearRoads _size) inAreaArray _area;
+_roadPosArray = _roadPosArray select {count (roadsConnectedTo _x) == 2};
+
+_roadPosArray = _roadPosArray call BIS_fnc_arrayShuffle;
+
+for "_i" from 0 to _vehicleCount do {
+ if (_i > (count _roadPosArray) - 2) exitWith {
+ WARNING_2("Not enough positions to spawn desired number of vehicles. Spawned %1 out of %2",_i,_vehicleCount);
+ };
+
+ private _road = _roadPosArray # 0;
+ private _roadConnectedTo = roadsConnectedTo _road;
+
+ if ([_road] call _fnc_isNearIntersection) then {
+ // Don't spawn vehicles near intersections, reset.
+ _roadPosArray deleteAt 0;
+ DEC(_i);
+ } else {
+ private _connectedRoad = _roadConnectedTo # 0;
+ private _roadPos = getPos _road;
+ private _newPos = _roadPos;
+ private _direction = _road getDir _connectedRoad;
+ private _vehicle = selectRandom _vehicles;
+
+ // Randomize facing direction
+ if (random 2 > 1) then {
+ ADD(_direction,180);
+ if (_direction > 360) then {
+ SUB(_direction,360);
+ };
+ };
+
+ //test either side of the road for more space.
+ private _direction2 = _direction + 90;
+ private _tempPos = _newPos getPos [0.5, _direction2];
+
+ private _k2 = 0;
+ for "_k" from 1 to 15 do {
+ _tempPos = _tempPos getPos [0.5, _direction2];
+
+ if ((_tempPos findEmptyPosition [0, 0, _vehicle]) isEqualTo []) exitWith {TRACE_1("Colliding at",_k,_tempPos)};
+ _newPos = _tempPos;
+
+
+ #ifdef DEBUG_MODE_FULL
+ private _txt = format [QGVAR(roadDebugMark_%1), GVAR(vehicleDebugMarkers)];
+ private _debugMkr = createMarker [_txt,_tempPos];
+ _debugMkr setMarkerShape "ICON";
+ _debugMkr setMarkerType "hd_dot";
+ _debugMkr setMarkerColor "ColorBlue";
+ INC(GVAR(vehicleDebugMarkers));
+ #endif
+
+ INC(_k2);
+ };
+
+ private _jPos = _newPos;
+ private _newPos = _roadPos;
+ _direction2 = _direction - 90;
+
+ private _j2 = 0;
+ for "_j" from 1 to 15 do {
+ _tempPos = _tempPos getPos [0.5, _direction2];
+
+ if ((_tempPos findEmptyPosition [0, 0, _vehicle]) isEqualTo []) exitWith {TRACE_1("Colliding at",_j,_tempPos)};
+ _newPos = _tempPos;
+
+ #ifdef DEBUG_MODE_FULL
+ private _txt = format [QGVAR(roadDebugMark_%1), GVAR(vehicleDebugMarkers)];
+ private _debugMkr = createMarker [_txt,_tempPos];
+ _debugMkr setMarkerShape "ICON";
+ _debugMkr setMarkerType "hd_dot";
+ _debugMkr setMarkerColor "ColorGreen";
+ INC(GVAR(vehicleDebugMarkers));
+ #endif
+
+ INC(_j2);
+ };
+
+ if (_j2 + _k2 <= 2) then {
+ _direction2 = _direction + 90;
+ _newPos = _newPos getPos [0.5, _direction2];
+ } else {
+ if (_k2 > _j2) then {
+ _direction2 = _direction + 90;
+ _newPos = _newPos getPos [0.5 * (_k2 - 4), _direction2];
+ } else {
+ _direction2 = _direction + 90;
+ _newPos = _newPos getPos [0.5 * (_j2 - 4), _direction2];
+ };
+ };
+
+ TRACE_3("Creating vehicle",_vehicle,_newPos,_direction);
+ #ifdef DEBUG_MODE_FULL
+ private _txt = format [QGVAR(roadDebugMark_%1), GVAR(vehicleDebugMarkers)];
+ private _debugMkr = createMarker [_txt, _newPos];
+ _debugMkr setMarkerShape "ICON";
+ _debugMkr setMarkerType "hd_arrow";
+ _debugMkr setMarkerColor "colorCivilian";
+ _debugMkr setMarkerDir _direction;
+ INC(GVAR(vehicleDebugMarkers));
+ #endif
+
+ private _veh = createVehicle [_vehicle, _newPos, [], 0, "NONE"];
+ _veh setDir _direction;
+ _createdVehicles pushBack _veh;
+ [QGVAR(ambientVehicleCreated), [_veh]] call CBA_fnc_localEvent;
+
+ // Handle vehicles getting launched
+ _veh allowDamage false;
+ [{
+ if (speed _this > 1) exitWith {
+ ERROR_1("%1 was launched, deleting",_this);
+ deleteVehicle _this;
+ };
+ _this allowDamage true;
+ }, _veh, 1] call CBA_fnc_waitAndExecute;
+
+ _roadPosArray = _roadPosArray - ([_road, _spacing] call _fnc_findRoadConnections);
+ };
+};
+
+_createdVehicles
diff --git a/addons/modules/modules/ambientVehicles.hpp b/addons/modules/modules/ambientVehicles.hpp
new file mode 100644
index 00000000..b2e54b6b
--- /dev/null
+++ b/addons/modules/modules/ambientVehicles.hpp
@@ -0,0 +1,91 @@
+class GVAR(ambientVehicles): Module_F
+{
+ scope = 2;
+ scopeCurator = 0;
+ displayName = "Ambient Vehicles";
+ icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\car_ca.paa";
+ category = "Teamwork";
+
+ function = QFUNC(ambientVehicleInit);
+ functionPriority = 10;
+ isGlobal = false;
+ isTriggerActivated = true;
+ isDisposable = false;
+ is3DEN = true;
+
+ class Attributes: AttributesBase {
+ class GVAR(vehicleNumber): Default {
+ displayName = "Vehicle number";
+ tooltip = "How many vehicles will be created.";
+ property = QGVAR(DOUBLES(ambientVehicles,vehicleNumber));
+ control = "EditShort";
+ typeName = "NUMBER";
+ expression = "_this setVariable ['%s', _value call BIS_fnc_parseNumberSafe, true];";
+ defaultValue = """10""";
+ };
+ class GVAR(spacing): Default {
+ displayName = "Vehicle spacing";
+ tooltip = "How many road segments will between each vehicle.";
+ property = QGVAR(DOUBLES(ambientVehicles,spacing));
+ control = "EditShort";
+ typeName = "NUMBER";
+ expression = "_this setVariable ['%s', _value call BIS_fnc_parseNumberSafe, true];";
+ defaultValue = """3""";
+ };
+ class GVAR(emptyCargo): Checkbox {
+ property = QGVAR(DOUBLES(ambientVehicles,emptyCargo));
+ displayName = "Empty vehicle cargo";
+ tooltip = "Whether vehicles should spawn with empty cargo.";
+ typeName = "BOOL";
+ defaultValue = false;
+ };
+ class GVAR(lockedRate): Default {
+ property = QGVAR(DOUBLES(ambientVehicles,lockedRate));
+ displayName = "Vehicles locked";
+ tooltip = "Percentage of spawned vehicles that will be locked.";
+ control = "Slider";
+ typeName = "NUMBER";
+ expression = "_this setVariable ['%s', _value call BIS_fnc_parseNumberSafe, true];";
+ defaultValue = 0;
+ };
+ class GVAR(code): Default {
+ property = QGVAR(DOUBLES(ambientVehicles,code));
+ displayName = "Vehicle init";
+ tooltip = "Code executed on every vehicle created";
+ defaultValue = "'params [""_vehicle""];'";
+ expression = "_this setVariable ['%s',compile _value,true];";
+ control = "EditCodeMulti5";
+ };
+
+ class ModuleDescription: ModuleDescription{};
+ };
+
+ class ModuleDescription: ModuleDescription
+ {
+ description = "Populates roads with parked vehicles and objects.
To use sync vehicles/objects and a TMF Area module.
When used with a repeatable trigger, vehicles will be despawned if they are more than 500 meters from any player when the trigger is deactivated.";
+ sync[] = {"AnyVehicle", QEGVAR(ai,area)};
+
+ class EGVAR(ai,area)
+ {
+ description[] = {
+ "Area in which vehicles will be spawned"
+ };
+ position = true; // Position is taken into effect
+ direction = true; // Direction is taken into effect
+ optional = false; // Synced entity is optional
+ duplicate = false; // Multiple entities of this type can be synced
+ synced[] = {}; // Pre-define entities like "AnyBrain" can be used. See the list below
+ };
+ class AnyVehicle
+ {
+ description[] = {
+ "Vehicles that will be spawned in the area"
+ };
+ position = false; // Position is taken into effect
+ direction = false; // Direction is taken into effect
+ optional = false; // Synced entity is optional
+ duplicate = true; // Multiple entities of this type can be synced
+ synced[] = {}; // Pre-define entities like "AnyBrain" can be used. See the list below
+ };
+ };
+};
diff --git a/addons/modules/script_component.hpp b/addons/modules/script_component.hpp
new file mode 100644
index 00000000..571750b5
--- /dev/null
+++ b/addons/modules/script_component.hpp
@@ -0,0 +1,6 @@
+#define COMPONENT modules
+
+//#define DEBUG_MODE_FULL
+
+#include "\x\tmf\addons\main\script_mod.hpp"
+#include "\x\tmf\addons\main\script_macros.hpp"