diff --git a/addons/task/XEH_PREP.hpp b/addons/task/XEH_PREP.hpp index 6707b13..751cb01 100644 --- a/addons/task/XEH_PREP.hpp +++ b/addons/task/XEH_PREP.hpp @@ -1,5 +1,7 @@ PREP(attack); PREP(attackModule); +PREP(defend); +PREP(defendModule); PREP(defuse); PREP(defuseModule); PREP(delivery); @@ -22,4 +24,5 @@ PREP(makeObject); PREP(makeShooter); PREP(makeTarget); PREP(protectedModule); -PREP(shootersModule); \ No newline at end of file +PREP(shootersModule); +PREP(spawnEnemyWave); \ No newline at end of file diff --git a/addons/task/functions/fnc_defend.sqf b/addons/task/functions/fnc_defend.sqf new file mode 100644 index 0000000..f36ffa0 --- /dev/null +++ b/addons/task/functions/fnc_defend.sqf @@ -0,0 +1,105 @@ +#include "..\script_component.hpp" + +/* + * Author: IDSolutions + * Registers a defend task where players must hold a zone marked by a marker + * + * Arguments: + * 0: ID of the task + * 1: Defense zone marker name + * 2: Time to defend in seconds + * 3: Amount of funds the company receives if the task is successful (default: 0) + * 4: Amount of rating the company and player lose if the task is failed (default: 0) + * 5: Amount of rating the company and player receive if the task is successful (default: 0) + * 6: Should the mission end (MissionSuccess) if the task is successful (default: false) + * 7: Should the mission end (MissionFailed) if the task is failed (default: false) + * 8: Enemy wave count (default: 3) + * 9: Time between waves in seconds (default: 300) + * 10: Minimum BLUFOR units required in zone (default: 1) + * + * Return Value: + * None + * + * Example: + * ["defend_zone_1", "defend_marker", 900, 500000, -100, 400, false, false, 3, 300, 1] spawn forge_client_task_fnc_defend; + * + * Public: Yes + */ + +params [["_taskID", "", [""]], ["_defenseZone", "", [""]], ["_defendTime", 600, [0]], ["_companyFunds", 0, [0]], ["_ratingFail", 0, [0]], ["_ratingSuccess", 0, [0]], ["_endSuccess", false, [false]], ["_endFail", false, [false]], ["_waveCount", 3, [0]], ["_waveCooldown", 300, [0]], ["_minBlufor", 1, [0]]]; + +if (_defenseZone == "" || !(markerShape _defenseZone in ["RECTANGLE", "ELLIPSE"])) exitWith { diag_log format ["ERROR: Invalid defense zone marker: %1", _defenseZone]; }; + +private _result = 0; +private _startTime = time; +private _nextWaveTime = _startTime; +private _currentWave = 0; +private _zoneEmptyCounter = 0; +private _warningIssued = false; + +waitUntil { + sleep 1; + private _bluforInZone = count (allUnits select { + _x isKindOf "CAManBase" && + {side _x == west} && + {alive _x} + } inAreaArray _defenseZone); + + private _timeElapsed = time - _startTime; + + if (_bluforInZone < _minBlufor) then { + _zoneEmptyCounter = _zoneEmptyCounter + 1; + + if (_zoneEmptyCounter == 15 && !_warningIssued) then { + ["Warning", ["Defense Zone Empty!", "Return to the defense zone immediately!"]] remoteExec ["BIS_fnc_showNotification", 0]; + _warningIssued = true; + }; + } else { + _zoneEmptyCounter = 0; + _warningIssued = false; + }; + + if (_currentWave < _waveCount && time >= _nextWaveTime) then { + [_defenseZone, _taskID, _currentWave] call FUNC(spawnEnemyWave); + + _currentWave = _currentWave + 1; + _nextWaveTime = time + _waveCooldown; + + ["IncomingQRF", ["Enemy forces approaching!", format ["Wave %1 of %2", _currentWave, _waveCount]]] remoteExec ["BIS_fnc_showNotification", 0]; + }; + + if (_zoneEmptyCounter >= 30) then { _result = 1; }; + + (_result == 1) or ((_bluforInZone >= _minBlufor) && (_timeElapsed >= _defendTime) && (_currentWave >= _waveCount)); +}; + +if (_result == 1) then { + [_taskID, "FAILED"] call BFUNC(taskSetState); + + if (_endFail) then { + ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; + }; + + // ["deduct", _ratingFail] remoteExecCall ["forge_server_rating_fnc_handleRating", 2]; + [_ratingFail] call EFUNC(org,addReputation); + [format ["Task failed: %1 reputation", _ratingFail], "warning", 5, "right"] call EFUNC(misc,notify); + +} else { + [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); + + if (_endSuccess) then { + ["MissionSuccess", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; + }; + + // ["advance", _ratingSuccess] remoteExecCall ["forge_server_rating_fnc_handleRating", 2]; + [_ratingSuccess] call EFUNC(org,addReputation); + [format ["Task succeeded: %1 reputation", _ratingSuccess], "success", 5, "right"] call EFUNC(misc,notify); + + sleep 1; + + { [_x, _ratingSuccess] remoteExec ["addRating", -2] } forEach allPlayers; + + // ["advance", _companyFunds] remoteExecCall ["forge_server_money_fnc_handleFunds", 2]; + [_companyFunds] call EFUNC(org,addFunds); + [format ["Task succeeded: %1 funds", _companyFunds], "success", 5, "right"] call EFUNC(misc,notify); +}; \ No newline at end of file diff --git a/addons/task/functions/fnc_defendModule.sqf b/addons/task/functions/fnc_defendModule.sqf new file mode 100644 index 0000000..00b0a76 --- /dev/null +++ b/addons/task/functions/fnc_defendModule.sqf @@ -0,0 +1,60 @@ +#include "..\script_component.hpp" + +/* + * Author: IDSolutions + * Creates a defend task module + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * call forge_client_task_fnc_defendModule; + * + * Public: No + */ + +// Module category +private _category = "Forge Tasks"; +private _subCategory = "Defense Tasks"; + +// Create the module +private _module = createDialog "RscDisplayAttributes"; +_module setVariable ["category", _category]; +_module setVariable ["subcategory", _subCategory]; +_module setVariable ["description", "Configure a defend task"]; + +// Add fields for task configuration +[_module, "Task ID", "taskID", "", true] call BIS_fnc_addAttribute; +[_module, "Defense Zone Marker", "defenseZone", "", true] call BIS_fnc_addAttribute; +[_module, "Defense Time (seconds)", "defendTime", "600", true] call BIS_fnc_addAttribute; +[_module, "Min BLUFOR in Zone", "minBlufor", "1", true] call BIS_fnc_addAttribute; +[_module, "Company Funds Reward", "companyFunds", "500000", true] call BIS_fnc_addAttribute; +[_module, "Rating Loss on Fail", "ratingFail", "-100", true] call BIS_fnc_addAttribute; +[_module, "Rating Gain on Success", "ratingSuccess", "400", true] call BIS_fnc_addAttribute; +[_module, "End Mission on Success", "endSuccess", "false", false] call BIS_fnc_addAttribute; +[_module, "End Mission on Fail", "endFail", "false", false] call BIS_fnc_addAttribute; +[_module, "Enemy Wave Count", "waveCount", "3", false] call BIS_fnc_addAttribute; +[_module, "Time Between Waves (seconds)", "waveCooldown", "300", false] call BIS_fnc_addAttribute; + +// Add confirm button handler +_module setVariable ["onConfirm", { + params ["_module"]; + private _taskID = _module getVariable ["taskID", ""]; + private _defenseZone = _module getVariable ["defenseZone", ""]; + private _defendTime = parseNumber (_module getVariable ["defendTime", "600"]); + private _companyFunds = parseNumber (_module getVariable ["companyFunds", "500000"]); + private _ratingFail = parseNumber (_module getVariable ["ratingFail", "-100"]); + private _ratingSuccess = parseNumber (_module getVariable ["ratingSuccess", "400"]); + private _endSuccess = _module getVariable ["endSuccess", "false"] == "true"; + private _endFail = _module getVariable ["endFail", "false"] == "true"; + private _waveCount = parseNumber (_module getVariable ["waveCount", "3"]); + private _waveCooldown = parseNumber (_module getVariable ["waveCooldown", "300"]); + private _minBlufor = parseNumber (_module getVariable ["minBlufor", "1"]); + + // Create the task + private _params = [_taskID, _defenseZone, _defendTime, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _waveCount, _waveCooldown, _minBlufor]; + ["defend", _params] remoteExec ["forge_client_task_fnc_handler", 2, false]; +}]; diff --git a/addons/task/functions/fnc_handler.sqf b/addons/task/functions/fnc_handler.sqf index 7667da1..e791528 100644 --- a/addons/task/functions/fnc_handler.sqf +++ b/addons/task/functions/fnc_handler.sqf @@ -6,7 +6,7 @@ * * Arguments: * 0: Type of task - * 1: Params for task + * 1: Arguments for task * 2: Minimum rating for task (default: nil) * * Return Value: @@ -24,6 +24,7 @@ private _thread = 0; GVAR(acceptTask) = false; +// TODO: Use player organization rating instead of global company rating if (isNil "companyRating") then { companyRating = 0; }; private _companyRating = companyRating; @@ -48,6 +49,10 @@ switch (_taskType) do { private _thread = _args spawn FUNC(delivery); waitUntil { sleep 2; scriptDone _thread }; }; + case "defend": { + private _thread = _args spawn FUNC(defend); + waitUntil { sleep 2; scriptDone _thread }; + }; case "hostage": { private _thread = _args spawn FUNC(hostage); waitUntil { sleep 2; scriptDone _thread }; diff --git a/addons/task/functions/fnc_spawnEnemyWave.sqf b/addons/task/functions/fnc_spawnEnemyWave.sqf new file mode 100644 index 0000000..3f21567 --- /dev/null +++ b/addons/task/functions/fnc_spawnEnemyWave.sqf @@ -0,0 +1,83 @@ +#include "..\script_component.hpp" + +/* + * Author: IDSolutions + * Spawns an enemy wave for a defense task + * + * Arguments: + * 0: Defense zone marker name + * 1: Task ID + * 2: Wave number (0-based) + * + * Return Value: + * None + * + * Example: + * ["defend_marker", "defend_1", 0] call forge_client_task_fnc_spawnEnemyWave; + * + * Public: No + */ + +params [["_defenseZone", "", [""]], ["_taskID", "", [""]], ["_waveNumber", 0, [0]]]; + +if (_defenseZone == "") exitWith { diag_log "ERROR: No defense zone provided for enemy wave spawn"; }; + +// TODO: Add unit types to mission config +private _basicTypes = ["O_Soldier_F", "O_Soldier_AR_F", "O_Soldier_GL_F", "O_medic_F"]; +private _specialTypes = ["O_Soldier_LAT_F", "O_soldier_M_F", "O_Soldier_TL_F", "O_Soldier_SL_F"]; +private _eliteTypes = ["O_Soldier_HAT_F", "O_Soldier_AA_F", "O_engineer_F", "O_Sharpshooter_F"]; + +private _unitCount = 6 + (_waveNumber * 2); // TODO: Make this configurable in mission config +private _specialChance = 0.2 + (_waveNumber * 0.1); // TODO: Make this configurable in mission config +private _eliteChance = (_waveNumber * 0.05); // TODO: Make this configurable in mission config + +private _center = getMarkerPos _defenseZone; +private _radius = (getMarkerSize _defenseZone select 0) max (getMarkerSize _defenseZone select 1); +private _spawnRadius = _radius + 150; +private _spawnPositions = []; + +for "_i" from 0 to 3 do { + private _angle = _i * 90; + private _variance = 45; + private _spawnAngle = _angle + (random (_variance * 2) - _variance); + private _spawnDist = _spawnRadius + (random 50 - 25); + + private _spawnX = (_center select 0) + (_spawnDist * cos _spawnAngle); + private _spawnY = (_center select 1) + (_spawnDist * sin _spawnAngle); + private _spawnPos = [_spawnX, _spawnY, 0]; + + private _safePos = _spawnPos findEmptyPosition [0, 50, "O_Soldier_F"]; + if (count _safePos > 0) then { + _spawnPositions pushBack _safePos; + }; +}; + +private _groups = []; +{ + private _groupSize = ceil(_unitCount / (count _spawnPositions)); + private _group = createGroup east; + _groups pushBack _group; + + for "_i" from 1 to _groupSize do { + private _unitType = _basicTypes select (floor random count _basicTypes); + private _roll = random 1; + + if (_roll < _eliteChance) then { + _unitType = _eliteTypes select (floor random count _eliteTypes); + } else { + if (_roll < _specialChance) then { + _unitType = _specialTypes select (floor random count _specialTypes); + }; + }; + + private _unit = _group createUnit [_unitType, _x, [], 0, "NONE"]; + _unit setVariable ["assignedTask", _taskID, true]; + _unit setBehaviour "AWARE"; + _unit setSpeedMode "NORMAL"; + _unit enableDynamicSimulation true; + }; + + [_group, _center, _radius * 0.75] call CBA_fnc_taskDefend; +} forEach _spawnPositions; + +diag_log format ["Spawned defense wave %1 for task %2 with %3 units", _waveNumber + 1, _taskID, _unitCount]; \ No newline at end of file