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"