Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task framework #2275

Draft
wants to merge 56 commits into
base: unstable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2034352
First itteration
HakonRydland Feb 28, 2022
55e500a
Renamed component to 'Tasks'
HakonRydland Mar 1, 2022
2ff8e5c
added task saving and load from save
HakonRydland Mar 1, 2022
d8349a4
added support for chained tasks
HakonRydland Mar 3, 2022
3b2bffe
typos
HakonRydland Mar 5, 2022
8983712
fixed: passing task hm to all function calls in runtask
HakonRydland Mar 6, 2022
c302ca6
- improved saving efficiency
HakonRydland Mar 6, 2022
7c704e3
fixed syntax errors whit requested changes
HakonRydland Mar 19, 2022
108a4c4
corrected weigthed array value weight order
HakonRydland Mar 19, 2022
cd9d285
streamlined weigth based randomisation
HakonRydland Mar 19, 2022
16c11f4
ported legacy task as official
HakonRydland Mar 28, 2022
fcd7869
Merge branch 'unstable' into TaskFramework
HakonRydland Mar 28, 2022
248fe44
added punishment for failing mission... whops
HakonRydland Mar 28, 2022
e621d7e
added support for addon dependancies for missions
HakonRydland Mar 28, 2022
ddf4559
Merge branch 'unstable' into TaskFramework
HakonRydland Apr 2, 2022
e18c5f7
unified timelimit to one line
HakonRydland Apr 2, 2022
f972ec9
ported specOp task to new framework
HakonRydland Apr 2, 2022
6e8733f
added missing calls in new task params getter
HakonRydland Apr 2, 2022
0553f56
- corrected typo in filename
HakonRydland Apr 3, 2022
33ccc03
Merge branch 'unstable' into TaskFramework
HakonRydland Jul 3, 2022
f51333b
fixed required addons check
HakonRydland Jul 3, 2022
86698ef
corrected max task of cat default when no setting has been mae for it…
HakonRydland Jul 3, 2022
299e1ac
changed naming of genTaskID -> genTaskUID
HakonRydland Jul 3, 2022
31aff5b
fixed bad arguments for requiredAddons check
HakonRydland Jul 3, 2022
507d862
corrected inverted if statement
HakonRydland Jul 3, 2022
8b87cd3
added missing includes
HakonRydland Jul 3, 2022
5a25685
fixed wrong oop
HakonRydland Jul 3, 2022
9272299
fixed bad arguments for requiredAddons check
HakonRydland Jul 3, 2022
df06727
corrected inverted if statement
HakonRydland Jul 3, 2022
ae1519c
Ported CON_Outpost to new Framework
msyulia Jul 3, 2022
e0d02c7
Fixed array concatenation of non-array type
msyulia Jul 3, 2022
3976fd9
Code style and formatting improvements
msyulia Jul 3, 2022
cc99c1a
Merge pull request #3 from Jul1aK0wal1k/tasks/CON_Outpost
HakonRydland Jul 3, 2022
dc7d8e5
corrected typo in macro
HakonRydland Jul 4, 2022
7d5fcd2
moved stage counter increment to after stage condition is meet
HakonRydland Jul 4, 2022
1bbfd62
Merge remote-tracking branch 'antistasi/unstable' into TaskFramework
HakonRydland Sep 18, 2022
08c8db0
fixed param getter bug for two legacy missions
HakonRydland Sep 25, 2022
9d6f5b3
update naming convention to agreed upon standard (per meeting)
HakonRydland Sep 25, 2022
7fe7e88
Merge branch 'unstable' into TaskFramework
HakonRydland Oct 17, 2022
8497324
Merge branch 'unstable' into TaskFramework
HakonRydland Nov 27, 2022
7797675
Fix missing task marker for CON_Outpost plus incorrect conversion of …
jaj22 Nov 28, 2022
57b8027
ported LOG_Supplies with some changes
killerswin2 Jan 13, 2023
f6de614
Merge pull request #4 from jaj22/fix-task-destination-bugs
HakonRydland Jan 21, 2023
65d0fbe
Merge branch 'unstable' into TaskFramework
HakonRydland Jan 22, 2023
43933a0
Merge branch 'TaskFramework' of https://github.com/HakonRydland/A3-An…
HakonRydland Jan 22, 2023
6110aa6
ported log_ammo
killerswin2 Mar 17, 2023
ee53bf2
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_LOG_Ammo.sqf
HakonRydland Mar 18, 2023
3f6cba3
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_LOG_Ammo.sqf
HakonRydland Mar 18, 2023
f0ee2f4
Update A3A/addons/Tasks/Params/LegacyParams/fn_LOG_Ammo_p.sqf
HakonRydland Mar 18, 2023
1d2e24c
Update A3A/addons/Tasks/Params/LegacyParams/fn_LOG_Ammo_p.sqf
HakonRydland Mar 18, 2023
18b5550
Update A3A/addons/Tasks/Params/LegacyParams/fn_LOG_Ammo_p.sqf
HakonRydland Mar 18, 2023
9e27cf7
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_LOG_Ammo.sqf
HakonRydland Mar 18, 2023
2d40806
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_LOG_Supplies.sqf
HakonRydland Mar 18, 2023
820de60
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_LOG_Supplies.sqf
HakonRydland Mar 18, 2023
b61761c
Update A3A/addons/Tasks/Tasks/LegacyTasks/fn_AS_Official.sqf
HakonRydland Mar 20, 2023
a4417c6
- updated the task runner to correctly give out rewards and punishments
HakonRydland Mar 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions A3A/addons/Tasks/$PBOPREFIX$
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x\A3A\addons\Tasks
45 changes: 45 additions & 0 deletions A3A/addons/Tasks/CfgFunctions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class CfgFunctions {
class ADDON {
class Core {
file = QPATHTOFOLDER(Core);
class genTaskUID {};
class getSettings { postInit = 1; };
class requestTask {};
class runTask {};
class updateTaskState {};
};

class Helpers { // task helper functions | Common functionality used by tasks or the params getters
file = QPATHTOFOLDER(Helpers);
class minutesFromNow {};
class nearFriendlyMarkers {};
class nearHostileMarkers {};
};
class Params { // params getter functions for the tasks | returns false if failed, otherwise params array
file = QPATHTOFOLDER(Params);
class testTask_p {};
};
class Tasks { // task | Passed task HM to store task instructions into
file = QPATHTOFOLDER(Tasks);
class testTask {};
};

//Legacy missions
class LegacyParams {
file = QPATHTOFOLDER(Params\LegacyParams);
class AS_Official_p {};
class AS_specOP_p {};
class CON_Outpost_p {};
class LOG_Supplies_p {};
class LOG_Ammo_p {};
};
class LegacyTasks {
file = QPATHTOFOLDER(Tasks\LegacyTasks);
class AS_Official {};
class AS_specOP {};
class CON_Outpost {};
class LOG_Supplies {};
class LOG_Ammo {};
};
};
};
6 changes: 6 additions & 0 deletions A3A/addons/Tasks/Core/fn_genTaskUID.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()
if (!isServer) exitWith {};

GVAR(taskUID) = GVAR(taskUID) + 1;
str GVAR(taskUID);
8 changes: 8 additions & 0 deletions A3A/addons/Tasks/Core/fn_getSaveData.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "..\script_component.sqf"
FIX_LINE_NUMBERS()
private _saveData = [];
{
private _cfg = _x get "cfg";
_saveData pushBack [configName _cfg, _x get "params" ,_x getOrDefault ["__CurrentStage",1], getNumber (_cfg/"version")];
} forEach GVAR(activeTasks);
_saveData;
22 changes: 22 additions & 0 deletions A3A/addons/Tasks/Core/fn_getSettings.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()
if (isNil QGVAR(taskUID)) then { GVAR(taskUID) = 0 };
if (isNil QGVAR(chainStates)) then { GVAR(chainStates) = createHashMap };
if (isNil QGVAR(settings)) then { GVAR(settings) = createHashMap };

//no cba fallback
if !("taskLingerTime" in GVAR(settings)) then { GVAR(settings) set ["taskLingerTime", 180] };
if !("maxTestMissions" in GVAR(settings)) then { GVAR(settings) set ["maxTestMissions", 3] };

//CBA settings
if (isClass (configfile >> "CBA_Extended_EventHandlers")) then {
killerswin2 marked this conversation as resolved.
Show resolved Hide resolved

[QGVAR(taskLingerTime), "SLIDER", ["Task linger time", "Time in minutes a task remains in the task list after completion"], ["Antistasi","Tasks"], [1, 15, 3, 0], true, {
GVAR(settings) set ["taskLingerTime",round _this * 60];
}] call CBA_fnc_addSetting;

[QGVAR(maxTestMissions), "SLIDER", ["Max test missions", "Maximum amount of test missions that can be active at the same time"], ["Antistasi","Tasks"], [0, 10, 3, 0], true, {
GVAR(settings) set ["maxTasksOfCat_Test",round _this];
}] call CBA_fnc_addSetting;

};
94 changes: 94 additions & 0 deletions A3A/addons/Tasks/Core/fn_requestTask.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()

params [
["_taskTypes",[],[[]]] //task types or task save
];

#define TASKS_CFG (configFile/"A3A"/"Tasks")

if (!isServer) exitWith { Error("Server function called on client"); false };
if (isNil QGVAR(activeTasks)) then { GVAR(activeTasks) = [] };
if (isNil QGVAR(settings)) then { call FUNC(getSettings)};

//Task loading from save, format: taskName, params, stage, task version
if (_taskTypes isEqualTypeParams ["",[],0,0]) exitWith {
private _cfg = TASKS_CFG/(_taskTypes#0);
private _version = getNumber (_cfg/"version");

if (_version isNotEqualTo (_taskTypes#3)) exitWith { false; };

private _taskHM = createHashMapFromArray [
["cfg", _cfg]
, ["params", _taskTypes#1]
];

[_taskHM] spawn FUNC(runTask);
true
}; //loading task from save

// gather tasks pool
private _allTasks = ("true" configClasses TASKS_CFG) apply {configName _x};
private _tasksOfCat = _allTasks select {
getText (TASKS_CFG/_x/"category") in _taskTypes;
};
if (_taskTypes isEqualTo []) then { _taskTypes = _allTasks };
{
_taskTypes deleteAt (_taskTypes find (getText (TASKS_CFG/_x/"category")));
_taskTypes pushBackUnique _x;
} forEach _tasksOfCat;

//filter task that can be run
private _fnc_requirementMeet = { getArray (_this/"requiredAddons") findIf { !(isClass (configFile/"CfgPatches"/_x)) } == -1 };
private _taskParams = createHashMap;
_taskTypes = _taskTypes select {
private _cfg = TASKS_CFG / _x;

//Common criterias

if !(_cfg call _fnc_requirementMeet) then {
Debug_2("Task %1 is missing one or more required addons: %2", _x, getArray (_cfg/_requiredAddons)); continueWith false;
};

private _cat = getText (_cfg/"category");
if !(
{ (_x get "category") isEqualTo _cat } count GVAR(activeTasks)
< ( GVAR(settings) getOrDefault ["maxTasksOfCat_" + _cat, 1, true] )
) then { Debug_1("Max tasks of type %1 active", _x); continueWith false; };

//chained tasks
if (isClass (_cfg/"chain")) then {
private _chain = getText (_cfg/"chain"/"name");
if (_chain isEqualTo "") then { Error_1("Misconfigured task %1, lacking name of chain", _x); continueWith false; };
private _requiredStage = getNumber (_cfg/"chain"/"stage");
if (_requiredStage < 1) then { Error_1("Misconfigured task %1, chain stage requirement: >=1", _x); continueWith false; };

private _stage = GVAR(chainStates) getOrDefault [_chain, 1];
if (_stage isNotEqualTo _requiredStage) then {
Debug_4("%1 chain not at the required stage for task %2 | Current: %3 | Required: %4", _chain, _x, _stage, _requiredStage);
continueWith false;
};
};

//parameters getters of the mission type should handle mission type specific criterias
private _paramsGetter = missionNamespace getVariable getText (_cfg / "params");
if (isNil "_paramsGetter") then { Debug_1("No params getter for task %1", _x); continueWith false; };
private _taskParam = call _paramsGetter;
_taskParams set [_x, _taskParam];

if (_taskParam isEqualType false) then { Debug_1("No valid params for task %1", _x); continueWith false; };

true;
};

private _selectedTask = _taskTypes selectRandomWeighted (_taskTypes apply {getNumber (TASKS_CFG/_x/"weight")});
if (isNil "_selectedTask") exitWith { Info_1("Unable to start a task of types: ", if ((_this#0) isEqualTo []) then {"All", _this#0}); false };

// determine task parameters
private _taskHM = createHashMap;
_taskHM set ["cfg", TASKS_CFG/_selectedTask];
_taskHM set ["params", _taskParams get _selectedTask];

// start task
[_taskHM] spawn FUNC(runTask);
true;
103 changes: 103 additions & 0 deletions A3A/addons/Tasks/Core/fn_runTask.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()
private _emptyHM = createHashMap;
params [["_taskHM", _emptyHM, [_emptyHM]]];

//validation
if (_taskHM isEqualTo _emptyHM) exitWith { Error_1("Invalid parameters passed: %1", _this) };
if (!canSuspend) exitWith { Error_1("Tasks needs to run in scheduled environment") };

_taskHM set ["taskID", call FUNC(genTaskUID)];

//load task
private _taskData = missionNameSpace getVariable getText ((_taskHM get "cfg") / "func");
if (isNil "_taskData") exitWith { Error_1("Function does not exist: %1", _taskData) };
_taskHM call _taskData;

//run task
_taskHM call FUNC(updateTaskState);
if !("constructor" in _taskHM) exitWith { Error_1("Task %1 lacks a constructor", configName (_taskHM get "cfg")) };
GVAR(activeTasks) pushBackUnique _taskHM;
_taskHM call (_taskHM get "constructor");

private _stage = _taskHM getOrDefault ["__CurrentStage", 1];
private _stages = (_taskHM get "stages") select [_stage-1, count (_taskHM get "stages")];
if (_stage > 1) then { _taskHM call ((_stages#0) getOrDefault ["Init",{}]) }; //if loading any state other than first stage, run innit code to set prepare for to run the stage

_taskHM set ["rewards",[]];
{
//update the map task info
_taskHM call FUNC(updateTaskState);
if !("action" in _x) exitWith { Error_1("Stage %1 is missing the action",_x) };
if !("condition" in _x) exitWith { Error_1("Stage %1 is missing the condition",_x) };

//start stage
private _endTime = time + (_x getOrDefault ["timeout", 1e12]); //stage time limit
_taskHM call (_x get "action"); //run stage action
_taskHM call FUNC(updateTaskState); //update task map task info

//condition for moving to next stage
waitUntil {
sleep 1;
(if ("timeout" in _x) then {time >= _endTime} else {false}) //check timelimit for stage
|| _taskHM call (_x get "condition") //check stage specific conditions
|| (_taskHM getOrDefault ["cancellationToken", false]) //allow cancelation at the global level
|| (_x getOrDefault ["cancellationToken", false]) // allow cancelation at the stage level
};
_taskHM set ["__CurrentStage", (_taskHM get "__CurrentStage") +1]; //stage finished, increment stage count (for saving/loading)

//if global canceled bail as fast as possible
if (_taskHM getOrDefault ["cancellationToken", false]) exitWith {
_taskHM set ["successful", false];
_taskHM set ["state", "CANCELED"];
};

//reward is always run even for failures, if stage is canceled no reward is triggered for that stage, but delayed rewarded previous stages still run reward code
//reward code handles both reward and punishment
if ("reward" in _x) then {
if (_x getOrDefault ["rewardInstant",false]) then (
_x getOrDefault ["reward",{}] //returns code block
) else {
(_taskHM get "rewards") pushBack (_x get "reward");
};
};

//stage state info
private _successful = (if ("timeout" in _x) then {time < _endTime} else {true}) && !(_x getOrDefault ["cancellationToken", false]);
_x set ["successful", _successful];
_x set ["state", (switch true do {
case _successful: {"SUCCEEDED"};
case (_x getOrDefault ["cancellationToken", false]): {"CANCELED"};
Default {"FAILED"};
})];

//if stage failed/canceled and was required for the task, fail and bail
if (!_successful && (_x getOrDefault ["required", true])) exitWith {
_taskHM set ["successful", false];
_taskHM set ["state", "FAILED"];
};
} forEach _stages;

//update task state info and if part of chain update record
if (_taskHM getOrDefault ["successful", true]) then {// on completion assume task successful unless otherwise state (global cancel or failed/canceled required stage)
_taskHM set ["state", "SUCCEEDED"]; //update tasks state (failed or canceled updated above in stage loop)

if (isClass ((_taskHM get "cfg")/"chain")) then { //if part of chain
private _chain = (_taskHM get "cfg")/"chain";
private _name = _chain/"name";

if (getNumber (_chain/"blockProgress") < 1) then { //if task is part of chain only increment chain progress if not side task in chain
GVAR(chainStates) set [_name, (GVAR(chainStates) getOrDefault [_name, 1]) +1];
};

if (getNumber (_chain/"lastStage") > 0) then { //reset chain when last chain completed
GVAR(chainStates) deleteAt _name;
};
};
};

{_taskHM call _x} forEach (_taskHM get "rewards"); //dish out none instat rewards for all completed stages of the task
_taskHM call (_taskHM getOrDefault ["destructor", {}]); //run task destructor to clean up, should be none halting

_taskHM call FUNC(updateTaskState); //update the task map info
GVAR(activeTasks) deleteAt ( GVAR(activeTasks) findIf {(_x get "taskID") isEqualTo (_taskHM get "taskID")} ); // cleare from active task record
66 changes: 66 additions & 0 deletions A3A/addons/Tasks/Core/fn_updateTaskState.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()
params ["_taskHM"];

//get task state from hm
private _taskID = _taskHM get "taskID";
if (isNil "_taskID") then { _taskID = call FUNC(genTaskUID); _taskHM set ["taskID", _taskID]; };
if (_taskID isEqualType []) then {_taskID = _taskID#0};

private _description = [
_taskHM getOrDefault ["description", ""],
_taskHM get "title",
_taskHM getOrDefault ["marker", ""]
];
if (isNil {_description#1}) exitWith { Error_1("No title set for task %1", configName (_taskHM get "cfg")) };

private _destination = _taskHM getOrDefault ["destination",objNull];
private _state = _taskHM getOrDefault ["state","CREATED"];
private _priority = _taskHM getOrDefault ["priority", -1];
private _showNotification = _taskHM getOrDefault ["showNotification",true];
private _type = _taskHM getOrDefault ["type",""];
private _visibleIn3D = _taskHM getOrDefault ["visibleIn3D",false];

//update task state
if !([_taskID] call BIS_fnc_taskExists) then {
Debug_1("Task created | ID: %1", _taskHM get "taskID");
[
true, _taskHM get "taskID", _description, _destination, _state, _priority, _showNotification, _type, _visibleIn3D
] call BIS_fnc_taskCreate;
};

if (_taskID call BIS_fnc_taskState isNotEqualTo _state) then {
[_taskID, _state, _showNotification] call BIS_fnc_taskSetState;
};

if (_taskID call BIS_fnc_taskDescription isNotEqualTo _description) then {
[_taskID, _description] call BIS_fnc_taskSetDescription;
};

private _destinationCheck = switch (true) do {
case (_destination isEqualType objNull): { getPos _destination };
case (_destination isEqualType ""): { getMarkerPos _destination };
default { _destination };
};
if (_taskId call BIS_fnc_taskDestination isNotEqualTo _destinationCheck) then {
[_taskID, _destination] call BIS_fnc_taskSetDestination;
};

if (_taskId call BIS_fnc_taskType isNotEqualTo _type) then {
[_taskID, _type] call BIS_fnc_taskSetType;
};

if (_taskID call BIS_fnc_taskAlwaysVisible isNotEqualTo _visibleIn3D) then {
[_taskID, _visibleIn3D] call BIS_fnc_taskSetAlwaysVisible;
};

//update child tasks
{ _x call FUNC(updateTaskState) } forEach (_taskHM getOrDefault ["Children", []]);

//task removal
if (_state in ["SUCCEEDED","FAILED","CANCELED"]) then {
_taskID spawn {
sleep (GVAR(settings) get "taskLingerTime");
[_this, true, true] call BIS_fnc_deleteTask;
};
};
40 changes: 40 additions & 0 deletions A3A/addons/Tasks/Helpers/fn_minutesFromNow.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Author: Håkon
Description:
returns the ingame time X minutes from now

Arguments:
0. <Int> minutes

Return Value:
<string> formated time string "H:M"

Scope: Any
Environment: Any
Public: Yes
Dependencies:

Example:
private _time = [20] call FUNC(minutesFromNow); //returns formated time 20 minutes from the call
License: MIT License
*/
#include "..\script_component.hpp"
FIX_LINE_NUMBERS()

params [["_minutesDiff", 0, [0]]];
if (_minutesDiff < 0) then {
Error_1("Function does not support negative time: %1", _minutesDiff);
_minutesDiff = 0;
};

// get current time
private _dayTime = daytime;
private _hours = floor _dayTime;
private _minutes = floor ((_dayTime - _hours) * 60);

// calculate time from now
_minutes = _minutes + _minutesDiff;
_hours = _hours + floor (_minutes/60);
_minutes = floor (_minutes % 60);

str _hours + ":" + str _minutes;
Loading