diff --git a/arma/client/addons/phone/XEH_PREP.hpp b/arma/client/addons/phone/XEH_PREP.hpp index 15fff64..97e1950 100644 --- a/arma/client/addons/phone/XEH_PREP.hpp +++ b/arma/client/addons/phone/XEH_PREP.hpp @@ -1,3 +1,3 @@ PREP(handleUIEvents); -PREP(initClass); +PREP(initRepository); PREP(openUI); diff --git a/arma/client/addons/phone/XEH_postInitClient.sqf b/arma/client/addons/phone/XEH_postInitClient.sqf index f3292be..532ae08 100644 --- a/arma/client/addons/phone/XEH_postInitClient.sqf +++ b/arma/client/addons/phone/XEH_postInitClient.sqf @@ -6,10 +6,10 @@ [QGVAR(initPhone), []] call CFUNC(localEvent); }] call CFUNC(waitUntilAndExecute); -if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; +if (isNil QGVAR(PhoneRepository)) then { [] call FUNC(initRepository); }; [QGVAR(initPhone), { - GVAR(PhoneClass) call ["init", []]; + GVAR(PhoneRepository) call ["init", []]; ["forge_server_phone_requestInitPhone", [getPlayerUID player, createHashMap]] call CFUNC(serverEvent); ["forge_server_phone_requestRefreshContacts", [getPlayerUID player, player]] call CFUNC(serverEvent); @@ -18,7 +18,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; [QGVAR(responseSyncPhone), { params [["_data", createHashMap, [createHashMap]]]; - GVAR(PhoneClass) call ["sync", [_data]]; + GVAR(PhoneRepository) call ["sync", [_data]]; }] call CFUNC(addEventHandler); // Contact Management Response Events @@ -26,10 +26,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Contact Added", "Contact added successfully", 3000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", "Contact added successfully", 3000]]; [QGVAR(refreshUI), []] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Contact Error", "Failed to add contact", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Contact Error", "Failed to add contact", 4000]]; }; }] call CFUNC(addEventHandler); @@ -37,10 +37,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]], ["_phoneNumber", "", [""]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Contact Added", format ["Contact with phone %1 added successfully", _phoneNumber], 3000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", format ["Contact with phone %1 added successfully", _phoneNumber], 3000]]; [QGVAR(refreshUI), []] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["warning", "Contact Not Found", format ["Player with phone %1 not found", _phoneNumber], 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["warning", "Contact Not Found", format ["Player with phone %1 not found", _phoneNumber], 4000]]; }; }] call CFUNC(addEventHandler); @@ -48,10 +48,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]], ["_email", "", [""]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Contact Added", format ["Contact with email %1 added successfully", _email], 3000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", format ["Contact with email %1 added successfully", _email], 3000]]; [QGVAR(refreshUI), []] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["warning", "Contact Not Found", format ["Player with email %1 not found", _email], 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["warning", "Contact Not Found", format ["Player with email %1 not found", _email], 4000]]; }; }] call CFUNC(addEventHandler); @@ -59,10 +59,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]], ["_contactUid", "", [""]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Contact Removed", "Contact removed successfully", 3000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Removed", "Contact removed successfully", 3000]]; [QGVAR(refreshUI), []] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Contact Error", "Failed to remove contact", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Contact Error", "Failed to remove contact", 4000]]; }; }] call CFUNC(addEventHandler); @@ -103,7 +103,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; }; } forEach _contacts; - [QEGVAR(notifications,recieveNotification), ["info", "New Message", format ["From %1", _senderName], 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["info", "New Message", format ["From %1", _senderName], 4000]]; diag_log format ["[FORGE:Client:Phone] Message received from %1: %2", _fromUid, _message]; @@ -114,9 +114,9 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Message Sent", "Message sent successfully", 2000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Message Sent", "Message sent successfully", 2000]]; } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Message Failed", "Failed to send message", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Message Failed", "Failed to send message", 4000]]; }; }] call CFUNC(addEventHandler); @@ -157,7 +157,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; diag_log format ["[FORGE:Client:Phone] Message %1 deleted", _messageId]; [QGVAR(updateMessageDeleted), [_messageId]] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Message Delete Failed", "Failed to delete message", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Message Delete Failed", "Failed to delete message", 4000]]; }; }] call CFUNC(addEventHandler); @@ -184,7 +184,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; }; } forEach _contacts; - [QEGVAR(notifications,recieveNotification), ["info", "New Email", format ["From %1: %2", _senderName, _subject], 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["info", "New Email", format ["From %1: %2", _senderName, _subject], 4000]]; diag_log format ["[FORGE:Client:Phone] Email received from %1: %2", _fromUid, _subject]; @@ -195,9 +195,9 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; params [["_success", false, [false]]]; if (_success) then { - [QEGVAR(notifications,recieveNotification), ["success", "Email Sent", "Email sent successfully", 2000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["success", "Email Sent", "Email sent successfully", 2000]]; } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Email Failed", "Failed to send email", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Email Failed", "Failed to send email", 4000]]; }; }] call CFUNC(addEventHandler); @@ -233,7 +233,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; diag_log format ["[FORGE:Client:Phone] Email %1 deleted", _emailId]; [QGVAR(updateEmailDeleted), [_emailId]] call CFUNC(localEvent); } else { - [QEGVAR(notifications,recieveNotification), ["danger", "Email Delete Failed", "Failed to delete email", 4000]] call CFUNC(localEvent); + EGVAR(notifications,NotificationService) call ["create", ["danger", "Email Delete Failed", "Failed to delete email", 4000]]; }; }] call CFUNC(addEventHandler); diff --git a/arma/client/addons/phone/functions/fnc_handleUIEvents.sqf b/arma/client/addons/phone/functions/fnc_handleUIEvents.sqf index 3b66a67..c715b27 100644 --- a/arma/client/addons/phone/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/phone/functions/fnc_handleUIEvents.sqf @@ -162,18 +162,18 @@ switch (_event) do { }; }; case "phone::get::notes": { - private _notes = GVAR(PhoneClass) call ["getAllNotes", []]; + private _notes = GVAR(PhoneRepository) call ["getAllNotes", []]; _control ctrlWebBrowserAction ["ExecJS", format ["loadNotes(%1)", (toJSON _notes)]]; }; case "phone::save::note": { - private _success = GVAR(PhoneClass) call ["addNote", [_data]]; + private _success = GVAR(PhoneRepository) call ["addNote", [_data]]; _success }; case "phone::delete::note": { private _noteId = _data get "id"; - private _success = GVAR(PhoneClass) call ["deleteNote", [_noteId]]; + private _success = GVAR(PhoneRepository) call ["deleteNote", [_noteId]]; _success }; case "phone::get::events": { diff --git a/arma/client/addons/phone/functions/fnc_initClass.sqf b/arma/client/addons/phone/functions/fnc_initRepository.sqf similarity index 80% rename from arma/client/addons/phone/functions/fnc_initClass.sqf rename to arma/client/addons/phone/functions/fnc_initRepository.sqf index 71bae1c..d563b93 100644 --- a/arma/client/addons/phone/functions/fnc_initClass.sqf +++ b/arma/client/addons/phone/functions/fnc_initRepository.sqf @@ -4,29 +4,22 @@ /* * Author: IDSolutions - * Initialize unified phone class + * Initialize phone repository * * Arguments: * N/A * * Return Value: - * N/A + * Phone repository object * * Examples: - * [] call forge_client_phone_fnc_initClass + * [] call forge_client_phone_fnc_initRepository * * Public: Yes */ -// TODO: Perform comprehensive review and edit of phone class implementation -// Then integrate this class to replace current phone handling logic -// Key areas to address: -// - Verify all phone data structures and methods -// - Ensure proper data persistence -// - Implement robust error handling -// - Replace direct UI manipulation with class-based approach -GVAR(PhoneClass) = createHashMapObject [[ - ["#type", "IPhoneClass"], +GVAR(PhoneRepository) = createHashMapObject [[ + ["#type", "IPhoneRepository"], ["#create", { _self set ["uid", getPlayerUID player]; _self set ["notes", createHashMap]; @@ -35,7 +28,6 @@ GVAR(PhoneClass) = createHashMapObject [[ _self set ["isLoaded", false]; _self set ["lastSave", time]; - // Initialize default settings private _settings = createHashMap; _settings set ["theme", "light"]; _settings set ["notifications", true]; @@ -44,8 +36,6 @@ GVAR(PhoneClass) = createHashMapObject [[ _self set ["settings", _settings]; }], ["init", { - // Contacts/messages/emails are server-owned. Keep only local utility-app - // state in profileNamespace until those apps are migrated. private _savedNotes = profileNamespace getVariable ["FORGE_Phone_Notes", createHashMap]; private _savedEvents = profileNamespace getVariable ["FORGE_Phone_Events", []]; private _savedSettings = profileNamespace getVariable ["FORGE_Phone_Settings", createHashMap]; @@ -53,7 +43,6 @@ GVAR(PhoneClass) = createHashMapObject [[ _self set ["notes", _savedNotes]; _self set ["events", _savedEvents]; - // Merge saved settings with defaults private _defaultSettings = _self get "settings"; { _defaultSettings set [_x, _y]; @@ -62,32 +51,28 @@ GVAR(PhoneClass) = createHashMapObject [[ _self set ["settings", _defaultSettings]; _self set ["isLoaded", true]; - systemChat format ["Phone loaded for %1", (name player)]; - diag_log "[FORGE:Client:Phone] Phone Class Initialized!"; + systemChat format ["Phone loaded for %1", name player]; + diag_log "[FORGE:Client:Phone] Phone Repository Initialized!"; }], ["_padString", { params [["_number", 0, [0]], ["_length", 0, [0]]]; private _str = str _number; - while { (_str select [(_length - 1), 1]) == "" } do { _str = "0" + _str }; _str }], ["save", { params [["_sync", false, [false]]]; - // Save local-only phone app state to profile. profileNamespace setVariable ["FORGE_Phone_Notes", _self get "notes"]; profileNamespace setVariable ["FORGE_Phone_Events", _self get "events"]; profileNamespace setVariable ["FORGE_Phone_Settings", _self get "settings"]; if (_sync) then { saveProfileNamespace; }; - _self set ["lastSave", time]; }], ["sync", { params [["_data", createHashMap, [createHashMap]]]; - if (_data isEqualTo createHashMap) exitWith { diag_log "[FORGE:Client:Phone] Empty data received for sync, skipping."; }; }], ["get", { @@ -98,12 +83,10 @@ GVAR(PhoneClass) = createHashMapObject [[ }], ["addNote", { params [["_data", createHashMap, [createHashMap]]]; - if (_data isEqualTo createHashMap) exitWith { false }; private _noteId = _data get "id"; private _notes = _self get "notes"; - _notes set [_noteId, _data]; _self call ["save", [true]]; @@ -128,11 +111,10 @@ GVAR(PhoneClass) = createHashMapObject [[ }], ["deleteNote", { params [["_noteId", "", [""]]]; - if (_noteId == "") exitWith { false }; private _notes = _self get "notes"; - if (!(_noteId in _notes)) exitWith { false }; + if !(_noteId in _notes) exitWith { false }; _notes deleteAt _noteId; _self set ["notes", _notes]; @@ -159,7 +141,6 @@ GVAR(PhoneClass) = createHashMapObject [[ }], ["setSetting", { params [["_key", "", [""]], ["_value", nil]]; - if (_key == "") exitWith { false }; private _settings = _self get "settings"; @@ -180,23 +161,18 @@ GVAR(PhoneClass) = createHashMapObject [[ }], ["addEvent", { params [["_eventData", createHashMap, [createHashMap]]]; - if (_eventData isEqualTo createHashMap) exitWith { false }; private _eventId = _eventData get "id"; if (isNil "_eventId" || _eventId == "") exitWith { false }; private _events = _self get "events"; - - // Check if event already exists - private _existingIndex = _events findIf {(_x get "id") isEqualTo _eventId}; + private _existingIndex = _events findIf { (_x get "id") isEqualTo _eventId }; if (_existingIndex >= 0) then { - // Update existing event _events set [_existingIndex, _eventData]; diag_log format ["[FORGE:Client:Phone] Updated event [ID: %1]", _eventId]; } else { - // Add new event _events pushBack _eventData; diag_log format ["[FORGE:Client:Phone] Added event [ID: %1]", _eventId]; }; @@ -212,8 +188,7 @@ GVAR(PhoneClass) = createHashMapObject [[ if (isNil "_eventId" || _eventId == "") exitWith { false }; private _events = _self get "events"; - private _existingIndex = _events findIf {(_x get "id") isEqualTo _eventId}; - + private _existingIndex = _events findIf { (_x get "id") isEqualTo _eventId }; if (_existingIndex < 0) exitWith { false }; _events set [_existingIndex, _eventData]; @@ -225,12 +200,10 @@ GVAR(PhoneClass) = createHashMapObject [[ }], ["deleteEvent", { params [["_eventId", "", [""]]]; - if (_eventId == "") exitWith { false }; private _events = _self get "events"; - private _existingIndex = _events findIf {(_x get "id") isEqualTo _eventId}; - + private _existingIndex = _events findIf { (_x get "id") isEqualTo _eventId }; if (_existingIndex < 0) exitWith { false }; _events deleteAt _existingIndex; @@ -244,32 +217,23 @@ GVAR(PhoneClass) = createHashMapObject [[ params [["_eventId", "", [""]], ["_default", nil]]; private _events = _self get "events"; - private _event = _events select {(_x get "id") isEqualTo _eventId}; - - if (_event isNotEqualTo []) then { - _event select 0 - } else { - _default - }; + private _event = _events select { (_x get "id") isEqualTo _eventId }; + if (_event isNotEqualTo []) then { _event select 0 } else { _default }; }], ["getAllEvents", { - private _events = _self get "events"; - _events + _self get "events"; }], ["getEventsByDate", { params [["_date", "", [""]]]; private _events = _self get "events"; - private _dateEvents = _events select { + _events select { private _eventStartTime = _x get "startTime"; if (isNil "_eventStartTime") then { false } else { - // Extract date from ISO string (YYYY-MM-DD) private _eventDate = (_eventStartTime splitString "T") select 0; _eventDate isEqualTo _date }; - }; - - _dateEvents + } }], ["clearAllEvents", { _self set ["events", []]; @@ -289,5 +253,4 @@ GVAR(PhoneClass) = createHashMapObject [[ }] ]]; -SETVAR(player,FORGE_PhoneClass,GVAR(PhoneClass)); -GVAR(PhoneClass) +GVAR(PhoneRepository) diff --git a/arma/server/addons/main/script_macros.hpp b/arma/server/addons/main/script_macros.hpp index a3ab43b..44efa49 100644 --- a/arma/server/addons/main/script_macros.hpp +++ b/arma/server/addons/main/script_macros.hpp @@ -42,11 +42,13 @@ #ifdef DISABLE_COMPILE_CACHE #define LINKFUNC(x) {call FUNC(x)} #define PREP(fncName) FUNC(fncName) = compile preprocessFileLineNumbers QPATHTOF(functions\DOUBLES(fnc,fncName).sqf) + #define PREP_SUBDIR(dir,fncName) FUNC(fncName) = compile preprocessFileLineNumbers QPATHTOF(functions\dir\DOUBLES(fnc,fncName).sqf) #define PREP_RECOMPILE_START if (isNil "forge_server_fnc_recompile") then {forge_server_recompiles = []; forge_server_fnc_recompile = {{call _x} forEach forge_server_recompiles;}}; private _recomp = { #define PREP_RECOMPILE_END }; call _recomp; forge_server_recompiles pushBack _recomp; #else #define LINKFUNC(x) FUNC(x) #define PREP(fncName) [QPATHTOF(functions\DOUBLES(fnc,fncName).sqf), QFUNC(fncName)] call CBA_fnc_compileFunction + #define PREP_SUBDIR(dir,fncName) [QPATHTOF(functions\dir\DOUBLES(fnc,fncName).sqf), QFUNC(fncName)] call CBA_fnc_compileFunction #define PREP_RECOMPILE_START ; /* disabled */ #define PREP_RECOMPILE_END ; /* disabled */ #endif diff --git a/arma/server/addons/task/CfgMissions.hpp b/arma/server/addons/task/CfgMissions.hpp index 33f7c12..38576f5 100644 --- a/arma/server/addons/task/CfgMissions.hpp +++ b/arma/server/addons/task/CfgMissions.hpp @@ -161,6 +161,7 @@ class CfgMissions { class Attack { minUnits = 4; maxUnits = 8; + patrolRadius = 200; class Rewards { money[] = {25000, 60000}; reputation[] = {6, 14}; diff --git a/arma/server/addons/task/CfgVehicles.hpp b/arma/server/addons/task/CfgVehicles.hpp index 534b2ed..5f774a6 100644 --- a/arma/server/addons/task/CfgVehicles.hpp +++ b/arma/server/addons/task/CfgVehicles.hpp @@ -55,6 +55,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Attack) class RatingFail: Edit { property = "FORGE_Module_Attack_RatingFail"; displayName = "Rating Loss"; @@ -332,6 +333,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Defend) class RatingFail: Edit { property = "FORGE_Module_Defend_RatingFail"; displayName = "Rating Loss"; @@ -436,6 +438,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Defuse) class RatingFail: Edit { property = "FORGE_Module_Defuse_RatingFail"; displayName = "Rating Loss"; @@ -547,6 +550,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Destroy) class RatingFail: Edit { property = "FORGE_Module_Destroy_RatingFail"; displayName = "Rating Loss"; @@ -665,6 +669,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Hostage) class RatingFail: Edit { property = "FORGE_Module_Hostage_RatingFail"; displayName = "Rating Loss"; @@ -811,6 +816,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Delivery) class RatingFail: Edit { property = "FORGE_Module_Delivery_RatingFail"; displayName = "Rating Loss"; @@ -964,6 +970,7 @@ class CfgVehicles { typeName = "NUMBER"; defaultValue = 0; }; + REWARD_ARRAY_ATTRIBUTES(FORGE_Module_HVT) class RatingFail: Edit { property = "FORGE_Module_HVT_RatingFail"; displayName = "Rating Loss"; diff --git a/arma/server/addons/task/README.md b/arma/server/addons/task/README.md index be57885..14af7e7 100644 --- a/arma/server/addons/task/README.md +++ b/arma/server/addons/task/README.md @@ -45,7 +45,7 @@ system intentionally starts clean after each server or mission restart. A review-only prototype for object-based task instances lives under `prototypes/`. It is not wired into runtime. -- `prototypes/taskObjectPrototypes.sqf` +- `forge_server_task_fnc_initPrototypes` - `prototypes/README.md` The prototype sketches `TaskInstanceBaseClass`, `HostageTaskBaseClass`, and diff --git a/arma/server/addons/task/XEH_PREP.hpp b/arma/server/addons/task/XEH_PREP.hpp index db12aaa..aad944b 100644 --- a/arma/server/addons/task/XEH_PREP.hpp +++ b/arma/server/addons/task/XEH_PREP.hpp @@ -1,23 +1,11 @@ PREP(attack); -PREP(attackModule); -PREP(cargoModule); PREP(defend); -PREP(defendModule); PREP(defuse); -PREP(defuseModule); PREP(delivery); -PREP(deliveryModule); PREP(destroy); -PREP(destroyModule); -PREP(explosivesModule); PREP(handler); -PREP(handleTaskRewards); -PREP(heartBeat); PREP(hostage); -PREP(hostageModule); -PREP(hostagesModule); PREP(hvt); -PREP(hvtModule); PREP(makeCargo); PREP(makeHostage); PREP(makeHVT); @@ -27,7 +15,32 @@ PREP(makeShooter); PREP(makeTarget); PREP(missionManager); PREP(initTaskStore); -PREP(protectedModule); -PREP(shootersModule); -PREP(spawnEnemyWave); -PREP(startTask); + +PREP_SUBDIR(generators,attackMissionGenerator); + +PREP_SUBDIR(helpers,handleTaskRewards); +PREP_SUBDIR(helpers,heartBeat); +PREP_SUBDIR(helpers,parseRewards); +PREP_SUBDIR(helpers,spawnEnemyWave); +PREP_SUBDIR(helpers,startTask); + +PREP_SUBDIR(modules,attackModule); +PREP_SUBDIR(modules,cargoModule); +PREP_SUBDIR(modules,defendModule); +PREP_SUBDIR(modules,defuseModule); +PREP_SUBDIR(modules,deliveryModule); +PREP_SUBDIR(modules,destroyModule); +PREP_SUBDIR(modules,explosivesModule); +PREP_SUBDIR(modules,hostageModule); +PREP_SUBDIR(modules,hostagesModule); +PREP_SUBDIR(modules,hvtModule); +PREP_SUBDIR(modules,protectedModule); +PREP_SUBDIR(modules,shootersModule); + +PREP_SUBDIR(prototypes,initPrototypes); +PREP_SUBDIR(prototypes,TaskInstanceBaseClass); +PREP_SUBDIR(prototypes,EntityControllerBaseClass); +PREP_SUBDIR(prototypes,AttackTaskBaseClass); +PREP_SUBDIR(prototypes,HostageTaskBaseClass); +PREP_SUBDIR(prototypes,HostageEntityController); +PREP_SUBDIR(prototypes,DefuseTaskBaseClass); diff --git a/arma/server/addons/task/XEH_postInit.sqf b/arma/server/addons/task/XEH_postInit.sqf index 3d29ab1..464334d 100644 --- a/arma/server/addons/task/XEH_postInit.sqf +++ b/arma/server/addons/task/XEH_postInit.sqf @@ -26,3 +26,4 @@ GVAR(TaskStore) call ["incrementDefuseCount", [_taskID]]; }] call CFUNC(addEventHandler); +[] call FUNC(missionManager); diff --git a/arma/server/addons/task/functions/fnc_attack.sqf b/arma/server/addons/task/functions/fnc_attack.sqf index c3909ee..a51955e 100644 --- a/arma/server/addons/task/functions/fnc_attack.sqf +++ b/arma/server/addons/task/functions/fnc_attack.sqf @@ -60,10 +60,23 @@ waitUntil { _targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]]; if (_timeLimit isNotEqualTo 0) then { + private _catalogEntry = GVAR(TaskStore) call ["getTaskCatalogEntry", [_taskID]]; + ["INFO", format [ + "Attack task %1 initial state before acceptance wait. Accepted=%2, RequesterUid='%3', Source='%4', TimeLimit=%5s", + _taskID, + _catalogEntry getOrDefault ["accepted", false], + _catalogEntry getOrDefault ["requesterUid", ""], + _catalogEntry getOrDefault ["source", ""], + _timeLimit + ]] call EFUNC(common,log); + + ["INFO", format ["Attack task %1 waiting for acceptance before starting %2s time limit.", _taskID, _timeLimit]] call EFUNC(common,log); waitUntil { sleep 1; GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }; + + ["INFO", format ["Attack task %1 accepted. Starting %2s time limit.", _taskID, _timeLimit]] call EFUNC(common,log); }; private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil }; @@ -77,7 +90,16 @@ waitUntil { if (_timeLimit isNotEqualTo 0) then { private _timeExpired = (floor time - _startTime >= _timeLimit); - if (_targetsKilled < _limitSuccess && _timeExpired) then { _result = 1; }; + if (_targetsKilled < _limitSuccess && _timeExpired) then { + ["WARNING", format [ + "Attack task %1 failed by timeout. TargetsKilled=%2, Required=%3, TimeLimit=%4s", + _taskID, + _targetsKilled, + _limitSuccess, + _timeLimit + ]] call EFUNC(common,log); + _result = 1; + }; (_result == 1) or (_targetsKilled >= _limitSuccess) } else { @@ -99,6 +121,13 @@ if (_result == 1) then { if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; } else { + ["INFO", format [ + "Attack task %1 succeeded. TargetsRequired=%2, TargetsKilled=%3", + _taskID, + _limitSuccess, + { !alive _x } count _targets + ]] call EFUNC(common,log); + { deleteVehicle _x } forEach _targets; private _rewards = createHashMap; diff --git a/arma/server/addons/task/functions/fnc_missionManager.sqf b/arma/server/addons/task/functions/fnc_missionManager.sqf index bde5f6f..1ee7ae6 100644 --- a/arma/server/addons/task/functions/fnc_missionManager.sqf +++ b/arma/server/addons/task/functions/fnc_missionManager.sqf @@ -2,7 +2,7 @@ /* * Author: IDSolutions - * Manages attack-only dynamic mission generation. + * Manages dynamic mission generators. * * Arguments: * None @@ -16,335 +16,120 @@ * Public: No */ +if !(isServer) exitWith { false }; +if !(isNil QGVAR(MissionManagerPFH)) exitWith { false }; +if (isNil QGVAR(AttackMissionGeneratorBaseClass)) then { call FUNC(attackMissionGenerator); }; + #pragma hemtt ignore_variables ["_self"] GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [ ["#type", "MissionManagerBaseClass"], ["#create", compileFinal { - private _missionConfig = missionConfigFile >> "CfgMissions"; - _self set ["missionConfig", _missionConfig]; - _self set ["locationsConfig", (_missionConfig >> "Locations")]; - _self set ["aiGroupsConfig", (_missionConfig >> "AIGroups")]; - _self set ["attackConfig", (_missionConfig >> "MissionTypes" >> "Attack")]; - _self set ["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")]; - _self set ["missionInterval", getNumber (_missionConfig >> "missionInterval")]; - _self set ["recentLocationRegistry", createHashMap]; + _self set ["recentLocationRegistry", []]; _self set ["activeMissionRegistry", createHashMap]; + _self set ["generators", [createHashMapObject [GVAR(AttackMissionGeneratorBaseClass)]]]; }], - ["getMissionInterval", compileFinal { - private _interval = _self getOrDefault ["missionInterval", 300]; - if (_interval <= 0) then { _interval = 300; }; - _interval - }], - ["getMaxConcurrentMissions", compileFinal { - private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1]; - if (_maxConcurrent <= 0) then { _maxConcurrent = 1; }; - private _attackLocationCount = _self call ["getAttackLocationCount", []]; - if (_attackLocationCount > 0) then { - _maxConcurrent = _maxConcurrent min _attackLocationCount; - }; - _maxConcurrent - }], - ["getAttackLocationCount", compileFinal { - private _locationsConfig = _self getOrDefault ["locationsConfig", configNull]; - if (isNull _locationsConfig) exitWith { 0 }; - - private _count = 0; - { - if ("attack" in getArray (_x >> "suitable")) then { - _count = _count + 1; - }; - } forEach ("true" configClasses _locationsConfig); - - _count - }], - ["getLocationReuseCooldown", compileFinal { - private _missionConfig = _self getOrDefault ["missionConfig", configNull]; - private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown"); - if (_cooldown <= 0) then { _cooldown = 900; }; - _cooldown + ["getGenerators", compileFinal { + _self getOrDefault ["generators", []] }], ["getActiveMissionIds", compileFinal { private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; keys _activeMissionRegistry }], - ["getActiveLocationKeys", compileFinal { - private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; - private _locationKeys = []; + ["getActiveGeneratedMissionIds", compileFinal { + private _activeCatalog = GVAR(TaskStore) call ["getActiveTaskCatalog", []]; + if !(_activeCatalog isEqualType []) exitWith { [] }; + + private _taskIds = []; { - private _locationKey = _y getOrDefault ["locationKey", ""]; - if (_locationKey isNotEqualTo "") then { - _locationKeys pushBackUnique _locationKey; + if !(_x isEqualType createHashMap) then { continue; }; + if ((_x getOrDefault ["source", ""]) isNotEqualTo "mission_manager") then { continue; }; + + private _taskID = _x getOrDefault ["taskId", _x getOrDefault ["taskID", ""]]; + if (_taskID isNotEqualTo "") then { + _taskIds pushBackUnique _taskID; }; - } forEach _activeMissionRegistry; - _locationKeys + } forEach _activeCatalog; + + _taskIds }], - ["buildAttackSpawnPosition", compileFinal { - params [["_locationConfig", configNull, [configNull]]]; - - if (isNull _locationConfig) exitWith { [0, 0, 0] }; - - private _center = getArray (_locationConfig >> "position"); - private _radius = getNumber (_locationConfig >> "radius"); - if (_radius <= 0) exitWith { _center }; - - private _spawnPosition = +_center; - private _attempts = 0; - while { _attempts < 8 } do { - private _angle = random 360; - private _distance = (_radius * 0.2) + random (_radius * 0.65); - private _candidate = [ - (_center # 0) + ((sin _angle) * _distance), - (_center # 1) + ((cos _angle) * _distance), - _center param [2, 0] - ]; - - if !(surfaceIsWater _candidate) exitWith { - _spawnPosition = _candidate; - }; - - _attempts = _attempts + 1; - }; - - _spawnPosition + ["getMaxConcurrentMissions", compileFinal { + private _maxConcurrent = 1; + { + _maxConcurrent = _maxConcurrent max (_x call ["getMaxConcurrentMissions", []]); + } forEach (_self call ["getGenerators", []]); + _maxConcurrent }], - ["selectAttackLocation", compileFinal { - private _locationsConfig = _self getOrDefault ["locationsConfig", configNull]; - private _locations = []; - private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap]; - private _activeLocationKeys = _self call ["getActiveLocationKeys", []]; - private _reuseCooldown = _self call ["getLocationReuseCooldown", []]; - private _now = serverTime; + ["getMissionInterval", compileFinal { + private _interval = 300; + private _generators = _self call ["getGenerators", []]; + if (_generators isEqualTo []) exitWith { _interval }; + _interval = (_generators select 0) call ["getMissionInterval", []]; { - private _locationKey = configName _x; - private _lastUsed = _recentLocationRegistry getOrDefault [_locationKey, -1]; - private _isCoolingDown = (_lastUsed >= 0) && { (_now - _lastUsed) < _reuseCooldown }; - - if ( - "attack" in getArray (_x >> "suitable") - && { !(_locationKey in _activeLocationKeys) } - && { !_isCoolingDown } - ) then { - _locations pushBack _x; - }; - } forEach ("true" configClasses _locationsConfig); - - if (_locations isEqualTo []) then { - { - if ("attack" in getArray (_x >> "suitable") && { !(configName _x in _activeLocationKeys) }) then { - _locations pushBack _x; - }; - } forEach ("true" configClasses _locationsConfig); - }; - - if (_locations isEqualTo []) exitWith { createHashMap }; - - private _location = selectRandom _locations; - createHashMapFromArray [ - ["config", _location], - ["key", configName _location], - ["position", _self call ["buildAttackSpawnPosition", [_location]]] - ] + _interval = _interval min (_x call ["getMissionInterval", []]); + } forEach _generators; + _interval }], - ["spawnAttackGroup", compileFinal { - params [["_position", [0, 0, 0], [[]]]]; - - private _aiGroupsConfig = _self getOrDefault ["aiGroupsConfig", configNull]; - private _attackConfig = _self getOrDefault ["attackConfig", configNull]; - private _groups = []; + ["cleanupCompletedMissions", compileFinal { { - if ("attack" in getArray (_x >> "suitable")) then { - _groups pushBack _x; - }; - } forEach ("true" configClasses _aiGroupsConfig); - - if (_groups isEqualTo []) exitWith { grpNull }; - - private _groupConfig = selectRandom _groups; - private _side = getText (_groupConfig >> "side"); - private _group = createGroup (call compile _side); - private _minUnits = getNumber (_attackConfig >> "minUnits"); - private _maxUnits = getNumber (_attackConfig >> "maxUnits"); - if (_minUnits <= 0) then { _minUnits = 4; }; - if (_maxUnits < _minUnits) then { _maxUnits = _minUnits; }; - private _targetUnitCount = floor random [ _minUnits, ceil ((_minUnits + _maxUnits) / 2), _maxUnits + 1 ]; - - private _unitPool = []; - { - if ((getText (_x >> "side")) isNotEqualTo _side) then { continue; }; + private _taskID = _x; + private _status = GVAR(TaskStore) call ["getTaskStatus", [_taskID]]; + private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_taskID]]; + if !(_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then { continue; }; { - _unitPool pushBack createHashMapFromArray [ - ["vehicle", getText (_x >> "vehicle")], - ["rank", getText (_x >> "rank")], - ["position", getArray (_x >> "position")] - ]; - } forEach ("true" configClasses (_x >> "Units")); - } forEach _groups; + if (_x call ["completeMission", [_self, _taskID]]) exitWith {}; + } forEach (_self call ["getGenerators", []]); - if (_unitPool isEqualTo []) exitWith { - deleteGroup _group; - grpNull - }; + GVAR(TaskStore) call ["clearTaskStatus", [_taskID]]; + } forEach (_self call ["getActiveMissionIds", []]); - private _leaderPool = _unitPool select { - toUpperANSI (_x getOrDefault ["rank", "PRIVATE"]) in ["SERGEANT", "LIEUTENANT", "CAPTAIN", "MAJOR", "COLONEL"] - }; - if (_leaderPool isEqualTo []) then { _leaderPool = +_unitPool; }; - - private _spawnDefs = [selectRandom _leaderPool]; - for "_i" from 1 to (_targetUnitCount - 1) do { - _spawnDefs pushBack (selectRandom _unitPool); - }; - - { - private _unitClass = _x getOrDefault ["vehicle", ""]; - if (_unitClass isEqualTo "") then { continue; }; - - private _unitOffset = +(_x getOrDefault ["position", [0, 0, 0]]); - if (count _unitOffset < 3) then { _unitOffset resize 3; }; - _unitOffset set [0, (_unitOffset # 0) + (random 6 - 3)]; - _unitOffset set [1, (_unitOffset # 1) + (random 6 - 3)]; - - private _unit = _group createUnit [_unitClass, _position vectorAdd _unitOffset, [], 0, "NONE"]; - _unit setRank (_x getOrDefault ["rank", "PRIVATE"]); - } forEach _spawnDefs; - - _group - }], - ["rollRewards", compileFinal { - private _attackConfig = _self getOrDefault ["attackConfig", configNull]; - private _equipmentRewards = []; - private _supplyRewards = []; - private _weaponRewards = []; - private _vehicleRewards = []; - private _specialRewards = []; - - { - private _category = _x; - { - _x params ["_item", "_chance"]; - if (random 1 < _chance) then { - switch (_category) do { - case "equipment": { _equipmentRewards pushBack _item; }; - case "supplies": { _supplyRewards pushBack _item; }; - case "weapons": { _weaponRewards pushBack _item; }; - case "vehicles": { _vehicleRewards pushBack _item; }; - case "special": { _specialRewards pushBack _item; }; - }; - }; - } forEach (getArray (_attackConfig >> "Rewards" >> _category)); - } forEach ["equipment", "supplies", "weapons", "vehicles", "special"]; - - createHashMapFromArray [ - ["equipment", _equipmentRewards], - ["supplies", _supplyRewards], - ["weapons", _weaponRewards], - ["vehicles", _vehicleRewards], - ["special", _specialRewards] - ] - }], - ["startAttackMission", compileFinal { - private _attackConfig = _self getOrDefault ["attackConfig", configNull]; - private _locationData = _self call ["selectAttackLocation"]; - if (_locationData isEqualTo createHashMap) exitWith { "" }; - - private _locationKey = _locationData getOrDefault ["key", ""]; - private _position = _locationData getOrDefault ["position", [0, 0, 0]]; - private _group = _self call ["spawnAttackGroup", [_position]]; - if (isNull _group) exitWith { "" }; - - private _units = units _group; - if (_units isEqualTo []) exitWith { - deleteGroup _group; - "" - }; - - private _taskID = format ["task_attack_%1", round (diag_tickTime * 1000)]; - private _rewardRange = getArray (_attackConfig >> "Rewards" >> "money"); - private _reputationRange = getArray (_attackConfig >> "Rewards" >> "reputation"); - private _penaltyRange = getArray (_attackConfig >> "penalty"); - private _timeRange = getArray (_attackConfig >> "timeLimit"); - private _rewards = _self call ["rollRewards"]; - - private _success = [ - "attack", - _taskID, - _position, - format ["Attack: %1", _locationKey], - format ["Eliminate hostile forces operating near %1.", _locationKey], - createHashMapFromArray [["targets", _units]], - createHashMapFromArray [ - ["limitFail", 0], - ["limitSuccess", count _units], - ["funds", _rewardRange call BFUNC(randomNum)], - ["ratingFail", _penaltyRange call BFUNC(randomNum)], - ["ratingSuccess", _reputationRange call BFUNC(randomNum)], - ["endSuccess", false], - ["endFail", false], - ["timeLimit", _timeRange call BFUNC(randomNum)], - ["equipment", _rewards get "equipment"], - ["supplies", _rewards get "supplies"], - ["weapons", _rewards get "weapons"], - ["vehicles", _rewards get "vehicles"], - ["special", _rewards get "special"] - ], - 0, - "", - "mission_manager" - ] call FUNC(startTask); - - if !(_success) exitWith { "" }; - - private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; - _activeMissionRegistry set [_taskID, createHashMapFromArray [ - ["locationKey", _locationKey], - ["startedAt", serverTime] - ]]; - _self set ["activeMissionRegistry", _activeMissionRegistry]; - - _taskID + true }], ["completeMission", compileFinal { params [["_taskID", "", [""]]]; if (_taskID isEqualTo "") exitWith { false }; - private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; - private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap]; - private _locationKey = _missionRecord getOrDefault ["locationKey", ""]; + { + if (_x call ["completeMission", [_self, _taskID]]) exitWith { true }; + } forEach (_self call ["getGenerators", []]); - _activeMissionRegistry deleteAt _taskID; - _self set ["activeMissionRegistry", _activeMissionRegistry]; - - if (_locationKey isNotEqualTo "") then { - private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap]; - _recentLocationRegistry set [_locationKey, serverTime]; - _self set ["recentLocationRegistry", _recentLocationRegistry]; + false + }], + ["startAvailableMissions", compileFinal { + private _activeGeneratedMissionIds = _self call ["getActiveGeneratedMissionIds", []]; + private _maxConcurrentMissions = _self call ["getMaxConcurrentMissions", []]; + if (count _activeGeneratedMissionIds >= _maxConcurrentMissions) exitWith { + ["INFO", format [ + "Mission manager skipped generation because cap was reached. ActiveGenerated=%1, Cap=%2, TaskIDs=%3", + count _activeGeneratedMissionIds, + _maxConcurrentMissions, + _activeGeneratedMissionIds + ]] call EFUNC(common,log); + "" }; - true + private _startedTaskID = ""; + { + private _taskID = _x call ["startMission", [_self]]; + if (_taskID isNotEqualTo "") exitWith { + _startedTaskID = _taskID; + }; + } forEach (_self call ["getGenerators", []]); + + _startedTaskID }] ]; GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)]; +GVAR(MissionManagerPFH) = [{ + GVAR(MissionManager) call ["cleanupCompletedMissions", []]; -[{ - { - private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]]; - private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_x]]; - if (_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then { - GVAR(MissionManager) call ["completeMission", [_x]]; - GVAR(TaskStore) call ["clearTaskStatus", [_x]]; - }; - } forEach (GVAR(MissionManager) call ["getActiveMissionIds", []]); + private _taskID = GVAR(MissionManager) call ["startAvailableMissions", []]; + if (_taskID isEqualTo "") exitWith {}; - if (count (GVAR(MissionManager) call ["getActiveMissionIds", []]) >= (GVAR(MissionManager) call ["getMaxConcurrentMissions", []])) exitWith {}; - - private _taskID = GVAR(MissionManager) call ["startAttackMission", []]; - if (_taskID isEqualTo "") exitWith { - ["WARNING", "Mission manager failed to start an attack mission."] call EFUNC(common,log); - }; - - ["INFO", format ["Mission manager started attack mission %1.", _taskID]] call EFUNC(common,log); + ["INFO", format ["Mission manager started mission %1.", _taskID]] call EFUNC(common,log); }, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler); + +true diff --git a/arma/server/addons/task/functions/generators/fnc_attackMissionGenerator.sqf b/arma/server/addons/task/functions/generators/fnc_attackMissionGenerator.sqf new file mode 100644 index 0000000..40f9978 --- /dev/null +++ b/arma/server/addons/task/functions/generators/fnc_attackMissionGenerator.sqf @@ -0,0 +1,405 @@ +#include "..\script_component.hpp" + +/* + * Author: IDSolutions + * Attack mission generator used by the dynamic mission manager. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call forge_server_task_fnc_attackMissionGenerator + * + * Public: No + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(AttackMissionGeneratorBaseClass) = compileFinal createHashMapFromArray [ + ["#type", "AttackMissionGeneratorBaseClass"], + ["#create", compileFinal { + private _missionConfig = missionConfigFile >> "CfgMissions"; + _self set ["missionConfig", _missionConfig]; + _self set ["aiGroupsConfig", (_missionConfig >> "AIGroups")]; + _self set ["attackConfig", (_missionConfig >> "MissionTypes" >> "Attack")]; + _self set ["generatorType", "attack"]; + }], + ["getGeneratorType", compileFinal { + _self getOrDefault ["generatorType", "attack"] + }], + ["getMissionInterval", compileFinal { + private _missionConfig = _self getOrDefault ["missionConfig", configNull]; + private _interval = getNumber (_missionConfig >> "missionInterval"); + if (_interval <= 0) then { _interval = 300; }; + _interval + }], + ["getMaxConcurrentMissions", compileFinal { + private _missionConfig = _self getOrDefault ["missionConfig", configNull]; + private _maxConcurrent = getNumber (_missionConfig >> "maxConcurrentMissions"); + if (_maxConcurrent <= 0) then { _maxConcurrent = 1; }; + _maxConcurrent + }], + ["getLocationReuseCooldown", compileFinal { + private _missionConfig = _self getOrDefault ["missionConfig", configNull]; + private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown"); + if (_cooldown <= 0) then { _cooldown = 900; }; + _cooldown + }], + ["pruneRecentLocations", compileFinal { + params [["_manager", createHashMapObject [createHashMapFromArray []], [createHashMap]]]; + + private _recentLocationRegistry = _manager getOrDefault ["recentLocationRegistry", []]; + private _reuseCooldown = _self call ["getLocationReuseCooldown", []]; + private _now = serverTime; + + _recentLocationRegistry = _recentLocationRegistry select { + private _usedAt = _x param [1, -1, [0]]; + (_usedAt >= 0) && { (_now - _usedAt) < _reuseCooldown } + }; + + _manager set ["recentLocationRegistry", _recentLocationRegistry]; + _recentLocationRegistry + }], + ["getActiveMissionPositions", compileFinal { + params [["_manager", createHashMapObject [createHashMapFromArray []], [createHashMap]]]; + + private _activeMissionRegistry = _manager getOrDefault ["activeMissionRegistry", createHashMap]; + private _positions = []; + { + if ((_y getOrDefault ["generatorType", ""]) isNotEqualTo "attack") then { continue; }; + + private _position = _y getOrDefault ["position", []]; + if (_position isEqualType [] && { count _position >= 2 }) then { + _positions pushBack _position; + }; + } forEach _activeMissionRegistry; + _positions + }], + ["selectLocation", compileFinal { + params [["_manager", createHashMapObject [createHashMapFromArray []], [createHashMap]]]; + + private _worldSize = worldSize; + private _center = [_worldSize / 2, _worldSize / 2, 0]; + private _safeDist = 800; + private _playerPos = _center; + private _minEdgeDist = _safeDist + 200; + + private _recentLocationRegistry = _self call ["pruneRecentLocations", [_manager]]; + private _activeMissionPositions = _self call ["getActiveMissionPositions", [_manager]]; + + private _blkListMarkers = allMapMarkers select { markerShape _x in ["RECTANGLE", "ELLIPSE"] }; + _blkListMarkers = _blkListMarkers select { + ( + (toLowerANSI _x find "blklist") == 0 + || { (toLowerANSI (markerText _x) find "blklist") == 0 } + ) + && { getMarkerPos _x distance2D [0, 0] > 0 } + }; + + private _taskPos = []; + private _attempt = 0; + private _maxAttempts = 50; + + while { _attempt < _maxAttempts && { _taskPos isEqualTo [] } } do { + _attempt = _attempt + 1; + private _candidate = [_center, _worldSize / 2 - _minEdgeDist, _worldSize / 2 - _minEdgeDist, 3, 0, 0.3, 0] call BIS_fnc_findSafePos; + + if (_candidate isEqualTo [0, 0, 0]) then { continue; }; + if (_candidate distance2D _playerPos < _safeDist) then { continue; }; + + private _isTooClose = false; + { + private _prevPos = _x param [0, [], [[]]]; + if (_prevPos isEqualType [] && { count _prevPos >= 2 } && { _candidate distance2D _prevPos < 500 }) exitWith { + _isTooClose = true; + }; + } forEach _recentLocationRegistry; + + if (_isTooClose) then { continue; }; + + { + if (_candidate distance2D _x < 500) exitWith { + _isTooClose = true; + }; + } forEach _activeMissionPositions; + + if (_isTooClose) then { continue; }; + + private _inBlkList = false; + { + if (_candidate inArea _x) exitWith { + _inBlkList = true; + }; + } forEach _blkListMarkers; + + if (!_inBlkList) then { + _taskPos = _candidate; + }; + }; + + if (_taskPos isEqualTo []) exitWith { + ["WARNING", "Attack mission generator: selectLocation failed to find a valid dynamic position."] call EFUNC(common,log); + createHashMap + }; + + createHashMapFromArray [ + ["position", _taskPos], + ["grid", mapGridPosition _taskPos] + ] + }], + ["spawnAttackGroup", compileFinal { + params [["_position", [0, 0, 0], [[]]]]; + + private _aiGroupsConfig = _self getOrDefault ["aiGroupsConfig", configNull]; + private _attackConfig = _self getOrDefault ["attackConfig", configNull]; + private _groups = []; + { + if ("attack" in getArray (_x >> "suitable")) then { + _groups pushBack _x; + }; + } forEach ("true" configClasses _aiGroupsConfig); + + if (_groups isEqualTo []) exitWith { + ["WARNING", "Attack mission generator: no AI group configs are suitable for attack missions."] call EFUNC(common,log); + grpNull + }; + + private _groupConfig = selectRandom _groups; + private _side = getText (_groupConfig >> "side"); + private _group = createGroup (call compile _side); + private _minUnits = getNumber (_attackConfig >> "minUnits"); + private _maxUnits = getNumber (_attackConfig >> "maxUnits"); + private _patrolRadius = getNumber (_attackConfig >> "patrolRadius"); + + if (_minUnits <= 0) then { _minUnits = 4; }; + if (_maxUnits < _minUnits) then { _maxUnits = _minUnits; }; + if (_patrolRadius <= 0) then { _patrolRadius = 200; }; + + private _targetUnitCount = floor random [_minUnits, ceil ((_minUnits + _maxUnits) / 2), _maxUnits + 1]; + private _unitPool = []; + { + if ((getText (_x >> "side")) isNotEqualTo _side) then { continue; }; + + { + _unitPool pushBack createHashMapFromArray [ + ["vehicle", getText (_x >> "vehicle")], + ["rank", getText (_x >> "rank")], + ["position", getArray (_x >> "position")] + ]; + } forEach ("true" configClasses (_x >> "Units")); + } forEach _groups; + + if (_unitPool isEqualTo []) exitWith { + ["WARNING", format ["Attack mission generator: selected AI group side '%1' produced an empty unit pool.", _side]] call EFUNC(common,log); + deleteGroup _group; + grpNull + }; + + private _leaderPool = _unitPool select { + toUpperANSI (_x getOrDefault ["rank", "PRIVATE"]) in ["SERGEANT", "LIEUTENANT", "CAPTAIN", "MAJOR", "COLONEL"] + }; + if (_leaderPool isEqualTo []) then { _leaderPool = +_unitPool; }; + + private _spawnDefs = [selectRandom _leaderPool]; + for "_i" from 1 to (_targetUnitCount - 1) do { + _spawnDefs pushBack (selectRandom _unitPool); + }; + + { + private _unitClass = _x getOrDefault ["vehicle", ""]; + if (_unitClass isEqualTo "") then { continue; }; + + private _unitOffset = +(_x getOrDefault ["position", [0, 0, 0]]); + if (count _unitOffset < 3) then { _unitOffset resize 3; }; + _unitOffset set [0, (_unitOffset # 0) + (random 6 - 3)]; + _unitOffset set [1, (_unitOffset # 1) + (random 6 - 3)]; + + private _unit = _group createUnit [_unitClass, _position vectorAdd _unitOffset, [], 0, "NONE"]; + _unit setRank (_x getOrDefault ["rank", "PRIVATE"]); + } forEach _spawnDefs; + + [_group, _position, _patrolRadius] call BFUNC(taskPatrol); + + ["INFO", format [ + "Attack mission generator: spawned attack group. Side=%1, Units=%2, PatrolRadius=%3, Position=%4", + _side, + count (units _group), + _patrolRadius, + _position + ]] call EFUNC(common,log); + _group + }], + ["rollRewards", compileFinal { + private _attackConfig = _self getOrDefault ["attackConfig", configNull]; + private _equipmentRewards = []; + private _supplyRewards = []; + private _weaponRewards = []; + private _vehicleRewards = []; + private _specialRewards = []; + + { + private _category = _x; + { + _x params ["_item", "_chance"]; + if (random 1 < _chance) then { + switch (_category) do { + case "equipment": { _equipmentRewards pushBack _item; }; + case "supplies": { _supplyRewards pushBack _item; }; + case "weapons": { _weaponRewards pushBack _item; }; + case "vehicles": { _vehicleRewards pushBack _item; }; + case "special": { _specialRewards pushBack _item; }; + }; + }; + } forEach (getArray (_attackConfig >> "Rewards" >> _category)); + } forEach ["equipment", "supplies", "weapons", "vehicles", "special"]; + + createHashMapFromArray [ + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] + ] + }], + ["startMission", compileFinal { + params [["_manager", createHashMapObject [createHashMapFromArray []], [createHashMap]]]; + + private _attackConfig = _self getOrDefault ["attackConfig", configNull]; + private _locationData = _self call ["selectLocation", [_manager]]; + if (_locationData isEqualTo createHashMap) exitWith { "" }; + + private _position = _locationData getOrDefault ["position", [0, 0, 0]]; + private _grid = _locationData getOrDefault ["grid", mapGridPosition _position]; + + ["INFO", format [ + "Attack mission generator: selected location. Grid=%1, Position=%2", + _grid, + _position + ]] call EFUNC(common,log); + + private _group = _self call ["spawnAttackGroup", [_position]]; + if (isNull _group) exitWith { + ["WARNING", format [ + "Attack mission generator: spawnAttackGroup failed for Grid=%1, Position=%2", + _grid, + _position + ]] call EFUNC(common,log); + "" + }; + + private _units = units _group; + if (_units isEqualTo []) exitWith { + ["WARNING", format [ + "Attack mission generator: spawned group has no units. Grid=%1, Group=%2", + _grid, + _group + ]] call EFUNC(common,log); + deleteGroup _group; + "" + }; + + private _taskID = format ["task_attack_%1", round (diag_tickTime * 1000)]; + private _rewardRange = getArray (_attackConfig >> "Rewards" >> "money"); + private _reputationRange = getArray (_attackConfig >> "Rewards" >> "reputation"); + private _penaltyRange = getArray (_attackConfig >> "penalty"); + private _timeRange = getArray (_attackConfig >> "timeLimit"); + private _rewards = _self call ["rollRewards"]; + private _fundsReward = _rewardRange call BFUNC(randomNum); + private _reputationReward = _reputationRange call BFUNC(randomNum); + private _reputationPenalty = _penaltyRange call BFUNC(randomNum); + private _timeLimit = _timeRange call BFUNC(randomNum); + + ["INFO", format [ + "Attack mission generator: creating task. TaskID=%1, Grid=%2, Units=%3", + _taskID, + _grid, + count _units + ]] call EFUNC(common,log); + + private _success = [ + "attack", + _taskID, + _position, + format ["Attack: Grid %1", _grid], + format ["Eliminate hostile forces operating near grid %1.", _grid], + createHashMapFromArray [["targets", _units]], + createHashMapFromArray [ + ["limitFail", 0], + ["limitSuccess", count _units], + ["funds", _fundsReward], + ["ratingFail", _reputationPenalty], + ["ratingSuccess", _reputationReward], + ["endSuccess", false], + ["endFail", false], + ["timeLimit", _timeLimit], + ["equipment", _rewards get "equipment"], + ["supplies", _rewards get "supplies"], + ["weapons", _rewards get "weapons"], + ["vehicles", _rewards get "vehicles"], + ["special", _rewards get "special"] + ], + 0, + "", + "mission_manager" + ] call FUNC(startTask); + + if !(_success) exitWith { + ["WARNING", format [ + "Attack mission generator: startTask failed. TaskID=%1, Grid=%2, Units=%3", + _taskID, + _grid, + count _units + ]] call EFUNC(common,log); + "" + }; + + ["INFO", format [ + "Attack mission generator: task registered. TaskID=%1, Source=mission_manager, TimeLimit=%2s, LimitSuccess=%3", + _taskID, + _timeLimit, + count _units + ]] call EFUNC(common,log); + + private _activeMissionRegistry = _manager getOrDefault ["activeMissionRegistry", createHashMap]; + _activeMissionRegistry set [_taskID, createHashMapFromArray [ + ["generatorType", _self call ["getGeneratorType", []]], + ["position", _position], + ["startedAt", serverTime] + ]]; + _manager set ["activeMissionRegistry", _activeMissionRegistry]; + + ["INFO", format [ + "Attack mission generator: mission started successfully. TaskID=%1, Grid=%2", + _taskID, + _grid + ]] call EFUNC(common,log); + + _taskID + }], + ["completeMission", compileFinal { + params [ + ["_manager", createHashMapObject [createHashMapFromArray []], [createHashMap]], + ["_taskID", "", [""]] + ]; + + if (_taskID isEqualTo "") exitWith { false }; + + private _activeMissionRegistry = _manager getOrDefault ["activeMissionRegistry", createHashMap]; + private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap]; + if ((_missionRecord getOrDefault ["generatorType", ""]) isNotEqualTo (_self call ["getGeneratorType", []])) exitWith { false }; + + private _position = _missionRecord getOrDefault ["position", []]; + _activeMissionRegistry deleteAt _taskID; + _manager set ["activeMissionRegistry", _activeMissionRegistry]; + + if (_position isEqualType [] && { count _position >= 2 }) then { + private _recentLocationRegistry = _manager getOrDefault ["recentLocationRegistry", []]; + _recentLocationRegistry pushBack [_position, serverTime]; + _manager set ["recentLocationRegistry", _recentLocationRegistry]; + }; + + true + }] +]; diff --git a/arma/server/addons/task/functions/fnc_handleTaskRewards.sqf b/arma/server/addons/task/functions/helpers/fnc_handleTaskRewards.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_handleTaskRewards.sqf rename to arma/server/addons/task/functions/helpers/fnc_handleTaskRewards.sqf diff --git a/arma/server/addons/task/functions/fnc_heartBeat.sqf b/arma/server/addons/task/functions/helpers/fnc_heartBeat.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_heartBeat.sqf rename to arma/server/addons/task/functions/helpers/fnc_heartBeat.sqf diff --git a/arma/server/addons/task/functions/helpers/fnc_parseRewards.sqf b/arma/server/addons/task/functions/helpers/fnc_parseRewards.sqf new file mode 100644 index 0000000..7650cec --- /dev/null +++ b/arma/server/addons/task/functions/helpers/fnc_parseRewards.sqf @@ -0,0 +1,40 @@ +#include "..\script_component.hpp" + +/* + * Author: OpenAI + * Parses an Eden module reward-array string into a SQF array. + * + * Arguments: + * 0: Raw value + * 1: Task label + * 2: Reward key + * + * Return Value: + * Parsed reward array + * + * Example: + * [_logic getVariable ["EquipmentRewards", "[]"], "attack_01", "equipment"] call forge_server_task_fnc_parseRewards; + * + * Public: No + */ + +params [ + ["_rawValue", "[]", [""]], + ["_taskLabel", "", [""]], + ["_rewardKey", "", [""]] +]; + +private _trimmed = trim _rawValue; +if (_trimmed isEqualTo "") exitWith { [] }; + +private _parsed = parseSimpleArray _trimmed; +if (_parsed isEqualType []) exitWith { _parsed }; + +["WARNING", format [ + "Task module '%1' reward input '%2' is invalid: %3. Expected SQF array syntax like [""ItemGPS"",""FirstAidKit""].", + _taskLabel, + _rewardKey, + _rawValue +]] call EFUNC(common,log); + +[] diff --git a/arma/server/addons/task/functions/fnc_spawnEnemyWave.sqf b/arma/server/addons/task/functions/helpers/fnc_spawnEnemyWave.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_spawnEnemyWave.sqf rename to arma/server/addons/task/functions/helpers/fnc_spawnEnemyWave.sqf diff --git a/arma/server/addons/task/functions/fnc_startTask.sqf b/arma/server/addons/task/functions/helpers/fnc_startTask.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_startTask.sqf rename to arma/server/addons/task/functions/helpers/fnc_startTask.sqf diff --git a/arma/server/addons/task/functions/fnc_attackModule.sqf b/arma/server/addons/task/functions/modules/fnc_attackModule.sqf similarity index 67% rename from arma/server/addons/task/functions/fnc_attackModule.sqf rename to arma/server/addons/task/functions/modules/fnc_attackModule.sqf index 17d413e..bd2a0e1 100644 --- a/arma/server/addons/task/functions/fnc_attackModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_attackModule.sqf @@ -36,6 +36,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "attack", _taskID, @@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["endSuccess", _logic getVariable ["EndSuccess", false]], ["endFail", _logic getVariable ["EndFail", false]], - ["timeLimit", _logic getVariable ["TimeLimit", 0]] + ["timeLimit", _logic getVariable ["TimeLimit", 0]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_cargoModule.sqf b/arma/server/addons/task/functions/modules/fnc_cargoModule.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_cargoModule.sqf rename to arma/server/addons/task/functions/modules/fnc_cargoModule.sqf diff --git a/arma/server/addons/task/functions/fnc_defendModule.sqf b/arma/server/addons/task/functions/modules/fnc_defendModule.sqf similarity index 72% rename from arma/server/addons/task/functions/fnc_defendModule.sqf rename to arma/server/addons/task/functions/modules/fnc_defendModule.sqf index 37f9cd0..5b12280 100644 --- a/arma/server/addons/task/functions/fnc_defendModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_defendModule.sqf @@ -42,6 +42,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit _logic getVariable ["MinBlufor", 1] ]] call EFUNC(common,log); +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "defend", _taskID, @@ -59,7 +65,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit ["defendTime", _logic getVariable ["DefendTime", 600]], ["waveCount", _logic getVariable ["WaveCount", 3]], ["waveCooldown", _logic getVariable ["WaveCooldown", 300]], - ["minBlufor", _logic getVariable ["MinBlufor", 1]] + ["minBlufor", _logic getVariable ["MinBlufor", 1]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_defuseModule.sqf b/arma/server/addons/task/functions/modules/fnc_defuseModule.sqf similarity index 74% rename from arma/server/addons/task/functions/fnc_defuseModule.sqf rename to arma/server/addons/task/functions/modules/fnc_defuseModule.sqf index 36bf58c..25359bb 100644 --- a/arma/server/addons/task/functions/fnc_defuseModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_defuseModule.sqf @@ -53,6 +53,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "defuse", _taskID, @@ -71,7 +77,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then { ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["endSuccess", _logic getVariable ["EndSuccess", false]], ["endFail", _logic getVariable ["EndFail", false]], - ["iedTimer", _logic getVariable ["TimeLimit", 300]] + ["iedTimer", _logic getVariable ["TimeLimit", 300]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_deliveryModule.sqf b/arma/server/addons/task/functions/modules/fnc_deliveryModule.sqf similarity index 74% rename from arma/server/addons/task/functions/fnc_deliveryModule.sqf rename to arma/server/addons/task/functions/modules/fnc_deliveryModule.sqf index 757a3f4..1a7fb0a 100644 --- a/arma/server/addons/task/functions/fnc_deliveryModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_deliveryModule.sqf @@ -46,6 +46,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "delivery", _taskID, @@ -64,7 +70,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then { ["endSuccess", _logic getVariable ["EndSuccess", false]], ["endFail", _logic getVariable ["EndFail", false]], ["timeLimit", _logic getVariable ["TimeLimit", 0]], - ["deliveryZone", _logic getVariable ["DeliveryZone", ""]] + ["deliveryZone", _logic getVariable ["DeliveryZone", ""]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_destroyModule.sqf b/arma/server/addons/task/functions/modules/fnc_destroyModule.sqf similarity index 67% rename from arma/server/addons/task/functions/fnc_destroyModule.sqf rename to arma/server/addons/task/functions/modules/fnc_destroyModule.sqf index 499d55d..82b6add 100644 --- a/arma/server/addons/task/functions/fnc_destroyModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_destroyModule.sqf @@ -36,6 +36,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "destroy", _taskID, @@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["endSuccess", _logic getVariable ["EndSuccess", false]], ["endFail", _logic getVariable ["EndFail", false]], - ["timeLimit", _logic getVariable ["TimeLimit", 0]] + ["timeLimit", _logic getVariable ["TimeLimit", 0]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_explosivesModule.sqf b/arma/server/addons/task/functions/modules/fnc_explosivesModule.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_explosivesModule.sqf rename to arma/server/addons/task/functions/modules/fnc_explosivesModule.sqf diff --git a/arma/server/addons/task/functions/fnc_hostageModule.sqf b/arma/server/addons/task/functions/modules/fnc_hostageModule.sqf similarity index 77% rename from arma/server/addons/task/functions/fnc_hostageModule.sqf rename to arma/server/addons/task/functions/modules/fnc_hostageModule.sqf index 9e9040b..03e3170 100644 --- a/arma/server/addons/task/functions/fnc_hostageModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_hostageModule.sqf @@ -61,6 +61,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "hostage", _taskID, @@ -83,7 +89,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then { ["extractionZone", _logic getVariable ["ExtZone", ""]], ["cbrn", _logic getVariable ["CBRN", false]], ["execution", _logic getVariable ["Execution", false]], - ["cbrnZone", _logic getVariable ["CBRNZone", ""]] + ["cbrnZone", _logic getVariable ["CBRNZone", ""]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_hostagesModule.sqf b/arma/server/addons/task/functions/modules/fnc_hostagesModule.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_hostagesModule.sqf rename to arma/server/addons/task/functions/modules/fnc_hostagesModule.sqf diff --git a/arma/server/addons/task/functions/fnc_hvtModule.sqf b/arma/server/addons/task/functions/modules/fnc_hvtModule.sqf similarity index 69% rename from arma/server/addons/task/functions/fnc_hvtModule.sqf rename to arma/server/addons/task/functions/modules/fnc_hvtModule.sqf index 58f242a..2a378fb 100644 --- a/arma/server/addons/task/functions/fnc_hvtModule.sqf +++ b/arma/server/addons/task/functions/modules/fnc_hvtModule.sqf @@ -42,6 +42,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { getPosATL _logic }; +private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards); +private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards); +private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards); +private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards); +private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards); + [ "hvt", _taskID, @@ -61,7 +67,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then { ["endFail", _logic getVariable ["EndFail", false]], ["timeLimit", _logic getVariable ["TimeLimit", 0]], ["extractionZone", _logic getVariable ["ExtZone", ""]], - ["captureHvt", _logic getVariable ["CaptureHVT", true]] + ["captureHvt", _logic getVariable ["CaptureHVT", true]], + ["equipment", _equipmentRewards], + ["supplies", _supplyRewards], + ["weapons", _weaponRewards], + ["vehicles", _vehicleRewards], + ["special", _specialRewards] ] ] call FUNC(startTask); diff --git a/arma/server/addons/task/functions/fnc_protectedModule.sqf b/arma/server/addons/task/functions/modules/fnc_protectedModule.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_protectedModule.sqf rename to arma/server/addons/task/functions/modules/fnc_protectedModule.sqf diff --git a/arma/server/addons/task/functions/fnc_shootersModule.sqf b/arma/server/addons/task/functions/modules/fnc_shootersModule.sqf similarity index 100% rename from arma/server/addons/task/functions/fnc_shootersModule.sqf rename to arma/server/addons/task/functions/modules/fnc_shootersModule.sqf diff --git a/arma/server/addons/task/prototypes/README.md b/arma/server/addons/task/functions/prototypes/README.md similarity index 62% rename from arma/server/addons/task/prototypes/README.md rename to arma/server/addons/task/functions/prototypes/README.md index 98a2d0a..40ae95a 100644 --- a/arma/server/addons/task/prototypes/README.md +++ b/arma/server/addons/task/functions/prototypes/README.md @@ -1,7 +1,8 @@ # Task Object Prototypes This folder contains review-only `createHashMapObject` prototypes for task -instances. They are not part of runtime initialization. +instances. Their source now lives under `functions/prototypes/`, and they are +loaded for review with `[] call forge_server_task_fnc_initPrototypes;`. Current prototypes: - `TaskInstanceBaseClass` @@ -11,14 +12,15 @@ Current prototypes: - `HostageEntityController` - `DefuseTaskBaseClass` -Source: +Review entry points: - [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf) -- [TaskInstanceBaseClass.sqf](./TaskInstanceBaseClass.sqf) -- [EntityControllerBaseClass.sqf](./EntityControllerBaseClass.sqf) -- [AttackTaskBaseClass.sqf](./AttackTaskBaseClass.sqf) -- [HostageTaskBaseClass.sqf](./HostageTaskBaseClass.sqf) -- [HostageEntityController.sqf](./HostageEntityController.sqf) -- [DefuseTaskBaseClass.sqf](./DefuseTaskBaseClass.sqf) +- [fnc_initPrototypes.sqf](../functions/prototypes/fnc_initPrototypes.sqf) +- [fnc_TaskInstanceBaseClass.sqf](../functions/prototypes/fnc_TaskInstanceBaseClass.sqf) +- [fnc_EntityControllerBaseClass.sqf](../functions/prototypes/fnc_EntityControllerBaseClass.sqf) +- [fnc_AttackTaskBaseClass.sqf](../functions/prototypes/fnc_AttackTaskBaseClass.sqf) +- [fnc_HostageTaskBaseClass.sqf](../functions/prototypes/fnc_HostageTaskBaseClass.sqf) +- [fnc_HostageEntityController.sqf](../functions/prototypes/fnc_HostageEntityController.sqf) +- [fnc_DefuseTaskBaseClass.sqf](../functions/prototypes/fnc_DefuseTaskBaseClass.sqf) Purpose: - show what per-task instance objects could look like diff --git a/arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf b/arma/server/addons/task/functions/prototypes/fnc_AttackTaskBaseClass.sqf similarity index 72% rename from arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf rename to arma/server/addons/task/functions/prototypes/fnc_AttackTaskBaseClass.sqf index 23e7649..6592f7f 100644 --- a/arma/server/addons/task/prototypes/AttackTaskBaseClass.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_AttackTaskBaseClass.sqf @@ -60,15 +60,12 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [ _self set ["requiredKills", _requiredKills]; _self set ["maxTargetLosses", _maxTargetLosses]; _self set ["timeLimit", _taskParams getOrDefault ["timeLimit", 0]]; + _self set ["useTaskStore", _taskParams getOrDefault ["useTaskStore", false]]; - // Review-only registry entry to demonstrate where #delete is useful. - missionNamespace setVariable [_taskID, _self]; + _self call ["registerInstance", []]; }], ["#delete", compileFinal { - private _taskID = _self getOrDefault ["taskID", ""]; - if (_taskID isNotEqualTo "") then { - missionNamespace setVariable [_taskID, nil]; - }; + _self call ["unregisterInstance", []]; }], ["countKilledTargets", compileFinal { private _targets = _self getOrDefault ["targets", []]; @@ -105,14 +102,22 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [ private _funds = _rewardData getOrDefault ["funds", 0]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; + private _useTaskStore = _self getOrDefault ["useTaskStore", false]; - waitUntil { - sleep 1; - GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; - count _targets > 0 + if (_useTaskStore) then { + waitUntil { + sleep 1; + GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; + count _targets > 0 + }; + } else { + waitUntil { + sleep 1; + count _targets > 0 + }; }; - if (_timeLimit isNotEqualTo 0) then { + if (_timeLimit isNotEqualTo 0 && { _useTaskStore }) then { waitUntil { sleep 1; GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] @@ -122,7 +127,9 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [ _self call ["markActive", []]; while { (_self call ["getStatus", []]) isEqualTo "active" } do { - GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; + if (_useTaskStore) then { + GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; + }; private _snapshot = _self call ["tick", []]; @@ -140,32 +147,37 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [ if ((_self call ["getStatus", []]) isEqualTo "failed") then { { deleteVehicle _x } forEach _targets; - [_taskID, "FAILED"] call BFUNC(taskSetState); - GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; + if (_useTaskStore) then { + [_taskID, "FAILED"] call BFUNC(taskSetState); + GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; - sleep 1; + sleep 1; - GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; - GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; - GVAR(TaskStore) call ["clearTask", [_taskID]]; + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + }; if (_endFail) then { ["MissionFail", false] remoteExec ["BIS_fnc_endMission", playerSide]; }; } else { { deleteVehicle _x } forEach _targets; - [_taskID, _rewardData] call FUNC(handleTaskRewards); - [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); - GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; + if (_useTaskStore) then { + [_taskID, _rewardData] call FUNC(handleTaskRewards); + [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); + GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; - sleep 1; + sleep 1; - GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; - GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; - GVAR(TaskStore) call ["clearTask", [_taskID]]; + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + }; if (_endSuccess) then { ["MissionSuccess", true] remoteExec ["BIS_fnc_endMission", playerSide]; }; }; + _self call ["cleanup", []]; true }] ]; diff --git a/arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf b/arma/server/addons/task/functions/prototypes/fnc_DefuseTaskBaseClass.sqf similarity index 86% rename from arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf rename to arma/server/addons/task/functions/prototypes/fnc_DefuseTaskBaseClass.sqf index ea89bf3..569cade 100644 --- a/arma/server/addons/task/prototypes/DefuseTaskBaseClass.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_DefuseTaskBaseClass.sqf @@ -62,15 +62,13 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [ _self set ["requiredDefusals", _requiredDefusals]; _self set ["maxProtectedLosses", _maxProtectedLosses]; _self set ["iedTimer", _taskParams getOrDefault ["iedTimer", 300]]; + _self set ["useTaskStore", _taskParams getOrDefault ["useTaskStore", false]]; + _self set ["localDefuseCount", _taskParams getOrDefault ["localDefuseCount", 0]]; - // Review-only registry entry to demonstrate where #delete is useful. - missionNamespace setVariable [_taskID, _self]; + _self call ["registerInstance", []]; }], ["#delete", compileFinal { - private _taskID = _self getOrDefault ["taskID", ""]; - if (_taskID isNotEqualTo "") then { - missionNamespace setVariable [_taskID, nil]; - }; + _self call ["unregisterInstance", []]; }], ["countProtectedDestroyed", compileFinal { private _protected = _self getOrDefault ["protected", []]; @@ -79,9 +77,17 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [ ["getDefuseCount", compileFinal { private _taskID = _self getOrDefault ["taskID", ""]; if (_taskID isEqualTo "") exitWith { 0 }; + if !(_self getOrDefault ["useTaskStore", false]) exitWith { + _self getOrDefault ["localDefuseCount", 0] + }; GVAR(TaskStore) call ["getDefuseCount", [_taskID]] }], + ["incrementLocalDefuseCount", compileFinal { + private _next = (_self getOrDefault ["localDefuseCount", 0]) + 1; + _self set ["localDefuseCount", _next]; + _next + }], ["tick", compileFinal { private _defusedCount = _self call ["getDefuseCount", []]; private _protectedDestroyed = _self call ["countProtectedDestroyed", []]; @@ -114,6 +120,7 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [ sleep 1; }; + _self call ["cleanup", []]; true }] ]; diff --git a/arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf b/arma/server/addons/task/functions/prototypes/fnc_EntityControllerBaseClass.sqf similarity index 71% rename from arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf rename to arma/server/addons/task/functions/prototypes/fnc_EntityControllerBaseClass.sqf index ddb3efd..6616bc1 100644 --- a/arma/server/addons/task/prototypes/EntityControllerBaseClass.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_EntityControllerBaseClass.sqf @@ -63,6 +63,30 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [ ["getEntity", compileFinal { _self getOrDefault ["entity", objNull] }], + ["getRegistryKey", compileFinal { + private _entity = _self getOrDefault ["entity", objNull]; + if (isNull _entity) exitWith { "" }; + + format ["hostage_controller_%1", netId _entity] + }], + ["registerInstance", compileFinal { + private _registryKey = _self call ["getRegistryKey", []]; + if (_registryKey isEqualTo "") exitWith { false }; + + private _registry = missionNamespace getVariable [QGVAR(PrototypeControllerInstances), createHashMap]; + _registry set [_registryKey, _self]; + missionNamespace setVariable [_registryKey, _self]; + true + }], + ["unregisterInstance", compileFinal { + private _registryKey = _self call ["getRegistryKey", []]; + if (_registryKey isEqualTo "") exitWith { false }; + + private _registry = missionNamespace getVariable [QGVAR(PrototypeControllerInstances), createHashMap]; + _registry deleteAt _registryKey; + missionNamespace setVariable [_registryKey, nil]; + true + }], ["markActive", compileFinal { _self set ["status", "active"]; _self set ["startedAt", serverTime]; @@ -79,7 +103,7 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [ true }], ["cleanup", compileFinal { - false + _self call ["unregisterInstance", []] }], ["runLoop", compileFinal { false diff --git a/arma/server/addons/task/prototypes/HostageEntityController.sqf b/arma/server/addons/task/functions/prototypes/fnc_HostageEntityController.sqf similarity index 88% rename from arma/server/addons/task/prototypes/HostageEntityController.sqf rename to arma/server/addons/task/functions/prototypes/fnc_HostageEntityController.sqf index a538e43..de61f2a 100644 --- a/arma/server/addons/task/prototypes/HostageEntityController.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_HostageEntityController.sqf @@ -47,18 +47,10 @@ GVAR(HostageEntityController) = createHashMapFromArray [ _self set ["loopAnimation", _controllerParams getOrDefault ["loopAnimation", "acts_executionvictim_loop"]]; _self set ["rescueAnimation", _controllerParams getOrDefault ["rescueAnimation", "acts_executionvictim_unbow"]]; - private _netID = if (isNull _entity) then { "" } else { netId _entity }; - if (_netID isNotEqualTo "") then { - private _controllerKey = format ["hostage_controller_%1", _netID]; - missionNamespace setVariable [_controllerKey, _self]; - }; + _self call ["registerInstance", []]; }], ["#delete", compileFinal { - private _entity = _self getOrDefault ["entity", objNull]; - if (!isNull _entity) then { - private _controllerKey = format ["hostage_controller_%1", netId _entity]; - missionNamespace setVariable [_controllerKey, nil]; - }; + _self call ["unregisterInstance", []]; }], ["applyInitialState", compileFinal { private _entity = _self getOrDefault ["entity", objNull]; @@ -95,6 +87,7 @@ GVAR(HostageEntityController) = createHashMapFromArray [ private _entity = _self getOrDefault ["entity", objNull]; if (isNull _entity) exitWith { _self call ["markAborted", []]; + _self call ["cleanup", []]; false }; @@ -102,6 +95,7 @@ GVAR(HostageEntityController) = createHashMapFromArray [ if !(_self call ["applyInitialState", []]) exitWith { _self call ["markAborted", []]; + _self call ["cleanup", []]; false }; @@ -117,11 +111,13 @@ GVAR(HostageEntityController) = createHashMapFromArray [ if (isNull _entity || { !alive _entity }) exitWith { _self call ["markAborted", []]; + _self call ["cleanup", []]; false }; _self call ["transitionToRescued", [_rescuer]]; _self call ["markFinished", []]; + _self call ["cleanup", []]; true }] ]; diff --git a/arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf b/arma/server/addons/task/functions/prototypes/fnc_HostageTaskBaseClass.sqf similarity index 83% rename from arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf rename to arma/server/addons/task/functions/prototypes/fnc_HostageTaskBaseClass.sqf index 2d9aa4a..0685cfe 100644 --- a/arma/server/addons/task/prototypes/HostageTaskBaseClass.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_HostageTaskBaseClass.sqf @@ -67,20 +67,17 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ _self set ["execution", _taskParams getOrDefault ["execution", false]]; _self set ["cbrn", _taskParams getOrDefault ["cbrn", false]]; _self set ["cbrnZone", _taskParams getOrDefault ["cbrnZone", ""]]; + _self set ["useTaskStore", _taskParams getOrDefault ["useTaskStore", false]]; _self set ["requiredRescues", _requiredRescues]; _self set ["maxHostageLosses", _maxHostageLosses]; _self set ["hostageControllers", []]; _self call ["createHostageControllers", []]; - // Review-only registry entry to demonstrate where #delete is useful. - missionNamespace setVariable [_taskID, _self]; + _self call ["registerInstance", []]; }], ["#delete", compileFinal { - private _taskID = _self getOrDefault ["taskID", ""]; - if (_taskID isNotEqualTo "") then { - missionNamespace setVariable [_taskID, nil]; - }; + _self call ["unregisterInstance", []]; }], ["createHostageControllers", compileFinal { private _taskID = _self getOrDefault ["taskID", ""]; @@ -119,7 +116,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ }], ["refreshEntitiesFromStore", compileFinal { private _taskID = _self getOrDefault ["taskID", ""]; - if (_taskID isEqualTo "") exitWith { false }; + if (_taskID isEqualTo "" || { !(_self getOrDefault ["useTaskStore", false]) }) exitWith { false }; private _hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]]; private _shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]]; @@ -130,7 +127,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ }], ["trackParticipants", compileFinal { private _taskID = _self getOrDefault ["taskID", ""]; - if (_taskID isEqualTo "") exitWith { false }; + if (_taskID isEqualTo "" || { !(_self getOrDefault ["useTaskStore", false]) }) exitWith { false }; private _hostages = _self getOrDefault ["hostages", []]; private _shooters = _self getOrDefault ["shooters", []]; @@ -140,17 +137,29 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ true }], ["waitForRequiredEntities", compileFinal { - waitUntil { - sleep 1; - _self call ["refreshEntitiesFromStore", []]; - count (_self getOrDefault ["hostages", []]) > 0 - }; + if (_self getOrDefault ["useTaskStore", false]) then { + waitUntil { + sleep 1; + _self call ["refreshEntitiesFromStore", []]; + count (_self getOrDefault ["hostages", []]) > 0 + }; - waitUntil { - sleep 1; - _self call ["refreshEntitiesFromStore", []]; - _self call ["trackParticipants", []]; - count (_self getOrDefault ["shooters", []]) > 0 + waitUntil { + sleep 1; + _self call ["refreshEntitiesFromStore", []]; + _self call ["trackParticipants", []]; + count (_self getOrDefault ["shooters", []]) > 0 + }; + } else { + waitUntil { + sleep 1; + count (_self getOrDefault ["hostages", []]) > 0 + }; + + waitUntil { + sleep 1; + count (_self getOrDefault ["shooters", []]) > 0 + }; }; private _hostages = _self getOrDefault ["hostages", []]; @@ -169,7 +178,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ private _timeLimit = _self getOrDefault ["timeLimit", 0]; private _taskID = _self getOrDefault ["taskID", ""]; - if (_timeLimit <= 0 || { _taskID isEqualTo "" }) exitWith { true }; + if (_timeLimit <= 0 || { _taskID isEqualTo "" } || { !(_self getOrDefault ["useTaskStore", false]) }) exitWith { true }; waitUntil { sleep 1; @@ -238,6 +247,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ private _cbrn = _self getOrDefault ["cbrn", false]; private _hostage = _self getOrDefault ["execution", false]; private _cbrnZone = _self getOrDefault ["cbrnZone", ""]; + private _useTaskStore = _self getOrDefault ["useTaskStore", false]; if (_cbrn && { _cbrnZone isNotEqualTo "" }) then { "SmokeShellYellow" createVehicle getMarkerPos _cbrnZone; @@ -272,13 +282,15 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ { deleteVehicle _x } forEach _hostages; { deleteVehicle _x } forEach _shooters; - [_taskID, "FAILED"] call BFUNC(taskSetState); + if (_useTaskStore) then { + [_taskID, "FAILED"] call BFUNC(taskSetState); - sleep 1; + sleep 1; - GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; - GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; - GVAR(TaskStore) call ["clearTask", [_taskID]]; + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + }; if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; true @@ -291,18 +303,21 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _funds = _rewardData getOrDefault ["funds", 0]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; + private _useTaskStore = _self getOrDefault ["useTaskStore", false]; { deleteVehicle _x } forEach _hostages; { deleteVehicle _x } forEach _shooters; - [_taskID, _rewardData] call FUNC(handleTaskRewards); - [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); + if (_useTaskStore) then { + [_taskID, _rewardData] call FUNC(handleTaskRewards); + [_taskID, "SUCCEEDED"] call BFUNC(taskSetState); - sleep 1; + sleep 1; - GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; - GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; - GVAR(TaskStore) call ["clearTask", [_taskID]]; + GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_funds] call EFUNC(common,formatNumber)]]]; + GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]]; + GVAR(TaskStore) call ["clearTask", [_taskID]]; + }; if (_endSuccess) then { ["MissionSuccess", true] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; true @@ -334,6 +349,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [ _self call ["handleSuccessOutcome", []]; }; + _self call ["cleanup", []]; true }] ]; diff --git a/arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf b/arma/server/addons/task/functions/prototypes/fnc_TaskInstanceBaseClass.sqf similarity index 81% rename from arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf rename to arma/server/addons/task/functions/prototypes/fnc_TaskInstanceBaseClass.sqf index a0b084d..87782fa 100644 --- a/arma/server/addons/task/prototypes/TaskInstanceBaseClass.sqf +++ b/arma/server/addons/task/functions/prototypes/fnc_TaskInstanceBaseClass.sqf @@ -86,6 +86,27 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [ ["getRewardData", compileFinal { _self getOrDefault ["rewardData", createHashMap] }], + ["getRegistryKey", compileFinal { + _self getOrDefault ["taskID", ""] + }], + ["registerInstance", compileFinal { + private _registryKey = _self call ["getRegistryKey", []]; + if (_registryKey isEqualTo "") exitWith { false }; + + private _registry = missionNamespace getVariable [QGVAR(PrototypeTaskInstances), createHashMap]; + _registry set [_registryKey, _self]; + missionNamespace setVariable [_registryKey, _self]; + true + }], + ["unregisterInstance", compileFinal { + private _registryKey = _self call ["getRegistryKey", []]; + if (_registryKey isEqualTo "") exitWith { false }; + + private _registry = missionNamespace getVariable [QGVAR(PrototypeTaskInstances), createHashMap]; + _registry deleteAt _registryKey; + missionNamespace setVariable [_registryKey, nil]; + true + }], ["markActive", compileFinal { _self set ["status", "active"]; _self set ["startedAt", serverTime]; @@ -109,7 +130,7 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [ true }], ["cleanup", compileFinal { - false + _self call ["unregisterInstance", []] }], ["tick", compileFinal { createHashMap diff --git a/arma/server/addons/task/functions/prototypes/fnc_initPrototypes.sqf b/arma/server/addons/task/functions/prototypes/fnc_initPrototypes.sqf new file mode 100644 index 0000000..a40f107 --- /dev/null +++ b/arma/server/addons/task/functions/prototypes/fnc_initPrototypes.sqf @@ -0,0 +1,42 @@ +#include "..\script_component.hpp" + +/* + * Review-only prototype initializer for object-based task instances. + * + * Usage in debug/testing: + * private _prototypes = [] call FUNC(initPrototypes); + * + * private _task = createHashMapObject [ + * _prototypes get "HostageTaskBaseClass", + * [ + * "task_hostage_review", + * createHashMapFromArray [ + * ["hostages", [hostage1, hostage2]], + * ["shooters", [shooter1, shooter2]] + * ], + * createHashMapFromArray [ + * ["extractionZone", "hostage_extract"], + * ["limitSuccess", 2], + * ["limitFail", 1], + * ["execution", true], + * ["timeLimit", 900] + * ] + * ] + * ]; + */ + +[] call FUNC(TaskInstanceBaseClass); +[] call FUNC(EntityControllerBaseClass); +[] call FUNC(AttackTaskBaseClass); +[] call FUNC(HostageTaskBaseClass); +[] call FUNC(HostageEntityController); +[] call FUNC(DefuseTaskBaseClass); + +createHashMapFromArray [ + ["TaskInstanceBaseClass", GVAR(TaskInstanceBaseClass)], + ["EntityControllerBaseClass", GVAR(EntityControllerBaseClass)], + ["AttackTaskBaseClass", GVAR(AttackTaskBaseClass)], + ["HostageTaskBaseClass", GVAR(HostageTaskBaseClass)], + ["HostageEntityController", GVAR(HostageEntityController)], + ["DefuseTaskBaseClass", GVAR(DefuseTaskBaseClass)] +] diff --git a/arma/server/addons/task/functions/script_component.hpp b/arma/server/addons/task/functions/script_component.hpp new file mode 100644 index 0000000..fcf9da9 --- /dev/null +++ b/arma/server/addons/task/functions/script_component.hpp @@ -0,0 +1 @@ +#include "..\script_component.hpp" diff --git a/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf b/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf deleted file mode 100644 index bb834ba..0000000 --- a/arma/server/addons/task/prototypes/taskObjectPrototypes.sqf +++ /dev/null @@ -1,47 +0,0 @@ -#include "..\script_component.hpp" - -/* - * Review-only prototype loader for object-based task instances. - * - * This file is intentionally not referenced by XEH_PREP or runtime init. - * It exists so the current procedural task flows can be compared against - * a createHashMapObject-based design before any live refactor is attempted. - * - * Usage in debug/testing: - * private _prototypes = call compile preprocessFileLineNumbers - * "\forge\forge_server\addons\task\prototypes\taskObjectPrototypes.sqf"; - * - * private _task = createHashMapObject [ - * _prototypes get "HostageTaskBaseClass", - * [ - * "task_hostage_review", - * createHashMapFromArray [ - * ["hostages", [hostage1, hostage2]], - * ["shooters", [shooter1, shooter2]] - * ], - * createHashMapFromArray [ - * ["extractionZone", "hostage_extract"], - * ["limitSuccess", 2], - * ["limitFail", 1], - * ["execution", true], - * ["timeLimit", 900] - * ] - * ] - * ]; - */ - -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\TaskInstanceBaseClass.sqf"; -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\EntityControllerBaseClass.sqf"; -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\AttackTaskBaseClass.sqf"; -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\HostageTaskBaseClass.sqf"; -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\HostageEntityController.sqf"; -call compile preprocessFileLineNumbers "\forge\forge_server\addons\task\prototypes\DefuseTaskBaseClass.sqf"; - -createHashMapFromArray [ - ["TaskInstanceBaseClass", GVAR(TaskInstanceBaseClass)], - ["EntityControllerBaseClass", GVAR(EntityControllerBaseClass)], - ["AttackTaskBaseClass", GVAR(AttackTaskBaseClass)], - ["HostageTaskBaseClass", GVAR(HostageTaskBaseClass)], - ["HostageEntityController", GVAR(HostageEntityController)], - ["DefuseTaskBaseClass", GVAR(DefuseTaskBaseClass)] -] diff --git a/arma/server/addons/task/script_component.hpp b/arma/server/addons/task/script_component.hpp index c90c053..e56f4f2 100644 --- a/arma/server/addons/task/script_component.hpp +++ b/arma/server/addons/task/script_component.hpp @@ -7,3 +7,35 @@ // #define ENABLE_PERFORMANCE_COUNTERS #include "\forge\forge_server\addons\main\script_macros.hpp" + +#define REWARD_ARRAY_ATTRIBUTES(PREFIX) \ + class EquipmentRewards: Edit { \ + property = QUOTE(DOUBLES(PREFIX,EquipmentRewards)); \ + displayName = "Equipment Rewards"; \ + tooltip = "SQF array string for equipment rewards, e.g. [""ItemGPS"",""ItemCompass""]"; \ + typeName = "STRING"; \ + }; \ + class SupplyRewards: Edit { \ + property = QUOTE(DOUBLES(PREFIX,SupplyRewards)); \ + displayName = "Supply Rewards"; \ + tooltip = "SQF array string for supply rewards, e.g. [""FirstAidKit"",""Medikit""]"; \ + typeName = "STRING"; \ + }; \ + class WeaponRewards: Edit { \ + property = QUOTE(DOUBLES(PREFIX,WeaponRewards)); \ + displayName = "Weapon Rewards"; \ + tooltip = "SQF array string for weapon rewards, e.g. [""arifle_MX_F""]"; \ + typeName = "STRING"; \ + }; \ + class VehicleRewards: Edit { \ + property = QUOTE(DOUBLES(PREFIX,VehicleRewards)); \ + displayName = "Vehicle Rewards"; \ + tooltip = "SQF array string for vehicle rewards, e.g. [""B_MRAP_01_F""]"; \ + typeName = "STRING"; \ + }; \ + class SpecialRewards: Edit { \ + property = QUOTE(DOUBLES(PREFIX,SpecialRewards)); \ + displayName = "Special Rewards"; \ + tooltip = "SQF array string for special rewards, e.g. [""B_UAV_01_F""]"; \ + typeName = "STRING"; \ + };