Refactor phone and task systems around repositories

- Rename phone class to repository and update client event handlers
- Split task helpers/modules/prototypes and add mission generator flow
- Add reward parsing, mission manager startup, and attack logging
This commit is contained in:
Jacob Schmidt 2026-05-02 01:29:25 -05:00
parent 1b000af2e6
commit 0cd6509337
41 changed files with 953 additions and 524 deletions

View File

@ -1,3 +1,3 @@
PREP(handleUIEvents); PREP(handleUIEvents);
PREP(initClass); PREP(initRepository);
PREP(openUI); PREP(openUI);

View File

@ -6,10 +6,10 @@
[QGVAR(initPhone), []] call CFUNC(localEvent); [QGVAR(initPhone), []] call CFUNC(localEvent);
}] call CFUNC(waitUntilAndExecute); }] call CFUNC(waitUntilAndExecute);
if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); }; if (isNil QGVAR(PhoneRepository)) then { [] call FUNC(initRepository); };
[QGVAR(initPhone), { [QGVAR(initPhone), {
GVAR(PhoneClass) call ["init", []]; GVAR(PhoneRepository) call ["init", []];
["forge_server_phone_requestInitPhone", [getPlayerUID player, createHashMap]] call CFUNC(serverEvent); ["forge_server_phone_requestInitPhone", [getPlayerUID player, createHashMap]] call CFUNC(serverEvent);
["forge_server_phone_requestRefreshContacts", [getPlayerUID player, player]] 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), { [QGVAR(responseSyncPhone), {
params [["_data", createHashMap, [createHashMap]]]; params [["_data", createHashMap, [createHashMap]]];
GVAR(PhoneClass) call ["sync", [_data]]; GVAR(PhoneRepository) call ["sync", [_data]];
}] call CFUNC(addEventHandler); }] call CFUNC(addEventHandler);
// Contact Management Response Events // Contact Management Response Events
@ -26,10 +26,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]]]; params [["_success", false, [false]]];
if (_success) then { 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); [QGVAR(refreshUI), []] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);
@ -37,10 +37,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_phoneNumber", "", [""]]]; params [["_success", false, [false]], ["_phoneNumber", "", [""]]];
if (_success) then { 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); [QGVAR(refreshUI), []] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);
@ -48,10 +48,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_email", "", [""]]]; params [["_success", false, [false]], ["_email", "", [""]]];
if (_success) then { 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); [QGVAR(refreshUI), []] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);
@ -59,10 +59,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_contactUid", "", [""]]]; params [["_success", false, [false]], ["_contactUid", "", [""]]];
if (_success) then { 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); [QGVAR(refreshUI), []] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);
@ -103,7 +103,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
}; };
} forEach _contacts; } 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]; 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]]]; params [["_success", false, [false]]];
if (_success) then { 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 { } 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); }] 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]; diag_log format ["[FORGE:Client:Phone] Message %1 deleted", _messageId];
[QGVAR(updateMessageDeleted), [_messageId]] call CFUNC(localEvent); [QGVAR(updateMessageDeleted), [_messageId]] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);
@ -184,7 +184,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
}; };
} forEach _contacts; } 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]; 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]]]; params [["_success", false, [false]]];
if (_success) then { 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 { } 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); }] 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]; diag_log format ["[FORGE:Client:Phone] Email %1 deleted", _emailId];
[QGVAR(updateEmailDeleted), [_emailId]] call CFUNC(localEvent); [QGVAR(updateEmailDeleted), [_emailId]] call CFUNC(localEvent);
} else { } 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); }] call CFUNC(addEventHandler);

View File

@ -162,18 +162,18 @@ switch (_event) do {
}; };
}; };
case "phone::get::notes": { case "phone::get::notes": {
private _notes = GVAR(PhoneClass) call ["getAllNotes", []]; private _notes = GVAR(PhoneRepository) call ["getAllNotes", []];
_control ctrlWebBrowserAction ["ExecJS", format ["loadNotes(%1)", (toJSON _notes)]]; _control ctrlWebBrowserAction ["ExecJS", format ["loadNotes(%1)", (toJSON _notes)]];
}; };
case "phone::save::note": { case "phone::save::note": {
private _success = GVAR(PhoneClass) call ["addNote", [_data]]; private _success = GVAR(PhoneRepository) call ["addNote", [_data]];
_success _success
}; };
case "phone::delete::note": { case "phone::delete::note": {
private _noteId = _data get "id"; private _noteId = _data get "id";
private _success = GVAR(PhoneClass) call ["deleteNote", [_noteId]]; private _success = GVAR(PhoneRepository) call ["deleteNote", [_noteId]];
_success _success
}; };
case "phone::get::events": { case "phone::get::events": {

View File

@ -4,29 +4,22 @@
/* /*
* Author: IDSolutions * Author: IDSolutions
* Initialize unified phone class * Initialize phone repository
* *
* Arguments: * Arguments:
* N/A * N/A
* *
* Return Value: * Return Value:
* N/A * Phone repository object
* *
* Examples: * Examples:
* [] call forge_client_phone_fnc_initClass * [] call forge_client_phone_fnc_initRepository
* *
* Public: Yes * Public: Yes
*/ */
// TODO: Perform comprehensive review and edit of phone class implementation GVAR(PhoneRepository) = createHashMapObject [[
// Then integrate this class to replace current phone handling logic ["#type", "IPhoneRepository"],
// 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"],
["#create", { ["#create", {
_self set ["uid", getPlayerUID player]; _self set ["uid", getPlayerUID player];
_self set ["notes", createHashMap]; _self set ["notes", createHashMap];
@ -35,7 +28,6 @@ GVAR(PhoneClass) = createHashMapObject [[
_self set ["isLoaded", false]; _self set ["isLoaded", false];
_self set ["lastSave", time]; _self set ["lastSave", time];
// Initialize default settings
private _settings = createHashMap; private _settings = createHashMap;
_settings set ["theme", "light"]; _settings set ["theme", "light"];
_settings set ["notifications", true]; _settings set ["notifications", true];
@ -44,8 +36,6 @@ GVAR(PhoneClass) = createHashMapObject [[
_self set ["settings", _settings]; _self set ["settings", _settings];
}], }],
["init", { ["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 _savedNotes = profileNamespace getVariable ["FORGE_Phone_Notes", createHashMap];
private _savedEvents = profileNamespace getVariable ["FORGE_Phone_Events", []]; private _savedEvents = profileNamespace getVariable ["FORGE_Phone_Events", []];
private _savedSettings = profileNamespace getVariable ["FORGE_Phone_Settings", createHashMap]; private _savedSettings = profileNamespace getVariable ["FORGE_Phone_Settings", createHashMap];
@ -53,7 +43,6 @@ GVAR(PhoneClass) = createHashMapObject [[
_self set ["notes", _savedNotes]; _self set ["notes", _savedNotes];
_self set ["events", _savedEvents]; _self set ["events", _savedEvents];
// Merge saved settings with defaults
private _defaultSettings = _self get "settings"; private _defaultSettings = _self get "settings";
{ {
_defaultSettings set [_x, _y]; _defaultSettings set [_x, _y];
@ -62,32 +51,28 @@ GVAR(PhoneClass) = createHashMapObject [[
_self set ["settings", _defaultSettings]; _self set ["settings", _defaultSettings];
_self set ["isLoaded", true]; _self set ["isLoaded", true];
systemChat format ["Phone loaded for %1", (name player)]; systemChat format ["Phone loaded for %1", name player];
diag_log "[FORGE:Client:Phone] Phone Class Initialized!"; diag_log "[FORGE:Client:Phone] Phone Repository Initialized!";
}], }],
["_padString", { ["_padString", {
params [["_number", 0, [0]], ["_length", 0, [0]]]; params [["_number", 0, [0]], ["_length", 0, [0]]];
private _str = str _number; private _str = str _number;
while { (_str select [(_length - 1), 1]) == "" } do { _str = "0" + _str }; while { (_str select [(_length - 1), 1]) == "" } do { _str = "0" + _str };
_str _str
}], }],
["save", { ["save", {
params [["_sync", false, [false]]]; params [["_sync", false, [false]]];
// Save local-only phone app state to profile.
profileNamespace setVariable ["FORGE_Phone_Notes", _self get "notes"]; profileNamespace setVariable ["FORGE_Phone_Notes", _self get "notes"];
profileNamespace setVariable ["FORGE_Phone_Events", _self get "events"]; profileNamespace setVariable ["FORGE_Phone_Events", _self get "events"];
profileNamespace setVariable ["FORGE_Phone_Settings", _self get "settings"]; profileNamespace setVariable ["FORGE_Phone_Settings", _self get "settings"];
if (_sync) then { saveProfileNamespace; }; if (_sync) then { saveProfileNamespace; };
_self set ["lastSave", time]; _self set ["lastSave", time];
}], }],
["sync", { ["sync", {
params [["_data", createHashMap, [createHashMap]]]; params [["_data", createHashMap, [createHashMap]]];
if (_data isEqualTo createHashMap) exitWith { diag_log "[FORGE:Client:Phone] Empty data received for sync, skipping."; }; if (_data isEqualTo createHashMap) exitWith { diag_log "[FORGE:Client:Phone] Empty data received for sync, skipping."; };
}], }],
["get", { ["get", {
@ -98,12 +83,10 @@ GVAR(PhoneClass) = createHashMapObject [[
}], }],
["addNote", { ["addNote", {
params [["_data", createHashMap, [createHashMap]]]; params [["_data", createHashMap, [createHashMap]]];
if (_data isEqualTo createHashMap) exitWith { false }; if (_data isEqualTo createHashMap) exitWith { false };
private _noteId = _data get "id"; private _noteId = _data get "id";
private _notes = _self get "notes"; private _notes = _self get "notes";
_notes set [_noteId, _data]; _notes set [_noteId, _data];
_self call ["save", [true]]; _self call ["save", [true]];
@ -128,11 +111,10 @@ GVAR(PhoneClass) = createHashMapObject [[
}], }],
["deleteNote", { ["deleteNote", {
params [["_noteId", "", [""]]]; params [["_noteId", "", [""]]];
if (_noteId == "") exitWith { false }; if (_noteId == "") exitWith { false };
private _notes = _self get "notes"; private _notes = _self get "notes";
if (!(_noteId in _notes)) exitWith { false }; if !(_noteId in _notes) exitWith { false };
_notes deleteAt _noteId; _notes deleteAt _noteId;
_self set ["notes", _notes]; _self set ["notes", _notes];
@ -159,7 +141,6 @@ GVAR(PhoneClass) = createHashMapObject [[
}], }],
["setSetting", { ["setSetting", {
params [["_key", "", [""]], ["_value", nil]]; params [["_key", "", [""]], ["_value", nil]];
if (_key == "") exitWith { false }; if (_key == "") exitWith { false };
private _settings = _self get "settings"; private _settings = _self get "settings";
@ -180,23 +161,18 @@ GVAR(PhoneClass) = createHashMapObject [[
}], }],
["addEvent", { ["addEvent", {
params [["_eventData", createHashMap, [createHashMap]]]; params [["_eventData", createHashMap, [createHashMap]]];
if (_eventData isEqualTo createHashMap) exitWith { false }; if (_eventData isEqualTo createHashMap) exitWith { false };
private _eventId = _eventData get "id"; private _eventId = _eventData get "id";
if (isNil "_eventId" || _eventId == "") exitWith { false }; if (isNil "_eventId" || _eventId == "") exitWith { false };
private _events = _self get "events"; private _events = _self get "events";
private _existingIndex = _events findIf { (_x get "id") isEqualTo _eventId };
// Check if event already exists
private _existingIndex = _events findIf {(_x get "id") isEqualTo _eventId};
if (_existingIndex >= 0) then { if (_existingIndex >= 0) then {
// Update existing event
_events set [_existingIndex, _eventData]; _events set [_existingIndex, _eventData];
diag_log format ["[FORGE:Client:Phone] Updated event [ID: %1]", _eventId]; diag_log format ["[FORGE:Client:Phone] Updated event [ID: %1]", _eventId];
} else { } else {
// Add new event
_events pushBack _eventData; _events pushBack _eventData;
diag_log format ["[FORGE:Client:Phone] Added event [ID: %1]", _eventId]; diag_log format ["[FORGE:Client:Phone] Added event [ID: %1]", _eventId];
}; };
@ -212,8 +188,7 @@ GVAR(PhoneClass) = createHashMapObject [[
if (isNil "_eventId" || _eventId == "") exitWith { false }; if (isNil "_eventId" || _eventId == "") exitWith { false };
private _events = _self get "events"; 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 }; if (_existingIndex < 0) exitWith { false };
_events set [_existingIndex, _eventData]; _events set [_existingIndex, _eventData];
@ -225,12 +200,10 @@ GVAR(PhoneClass) = createHashMapObject [[
}], }],
["deleteEvent", { ["deleteEvent", {
params [["_eventId", "", [""]]]; params [["_eventId", "", [""]]];
if (_eventId == "") exitWith { false }; if (_eventId == "") exitWith { false };
private _events = _self get "events"; 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 }; if (_existingIndex < 0) exitWith { false };
_events deleteAt _existingIndex; _events deleteAt _existingIndex;
@ -244,32 +217,23 @@ GVAR(PhoneClass) = createHashMapObject [[
params [["_eventId", "", [""]], ["_default", nil]]; params [["_eventId", "", [""]], ["_default", nil]];
private _events = _self get "events"; private _events = _self get "events";
private _event = _events select {(_x get "id") isEqualTo _eventId}; private _event = _events select { (_x get "id") isEqualTo _eventId };
if (_event isNotEqualTo []) then { _event select 0 } else { _default };
if (_event isNotEqualTo []) then {
_event select 0
} else {
_default
};
}], }],
["getAllEvents", { ["getAllEvents", {
private _events = _self get "events"; _self get "events";
_events
}], }],
["getEventsByDate", { ["getEventsByDate", {
params [["_date", "", [""]]]; params [["_date", "", [""]]];
private _events = _self get "events"; private _events = _self get "events";
private _dateEvents = _events select { _events select {
private _eventStartTime = _x get "startTime"; private _eventStartTime = _x get "startTime";
if (isNil "_eventStartTime") then { false } else { if (isNil "_eventStartTime") then { false } else {
// Extract date from ISO string (YYYY-MM-DD)
private _eventDate = (_eventStartTime splitString "T") select 0; private _eventDate = (_eventStartTime splitString "T") select 0;
_eventDate isEqualTo _date _eventDate isEqualTo _date
}; };
}; }
_dateEvents
}], }],
["clearAllEvents", { ["clearAllEvents", {
_self set ["events", []]; _self set ["events", []];
@ -289,5 +253,4 @@ GVAR(PhoneClass) = createHashMapObject [[
}] }]
]]; ]];
SETVAR(player,FORGE_PhoneClass,GVAR(PhoneClass)); GVAR(PhoneRepository)
GVAR(PhoneClass)

View File

@ -42,11 +42,13 @@
#ifdef DISABLE_COMPILE_CACHE #ifdef DISABLE_COMPILE_CACHE
#define LINKFUNC(x) {call FUNC(x)} #define LINKFUNC(x) {call FUNC(x)}
#define PREP(fncName) FUNC(fncName) = compile preprocessFileLineNumbers QPATHTOF(functions\DOUBLES(fnc,fncName).sqf) #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_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; #define PREP_RECOMPILE_END }; call _recomp; forge_server_recompiles pushBack _recomp;
#else #else
#define LINKFUNC(x) FUNC(x) #define LINKFUNC(x) FUNC(x)
#define PREP(fncName) [QPATHTOF(functions\DOUBLES(fnc,fncName).sqf), QFUNC(fncName)] call CBA_fnc_compileFunction #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_START ; /* disabled */
#define PREP_RECOMPILE_END ; /* disabled */ #define PREP_RECOMPILE_END ; /* disabled */
#endif #endif

View File

@ -161,6 +161,7 @@ class CfgMissions {
class Attack { class Attack {
minUnits = 4; minUnits = 4;
maxUnits = 8; maxUnits = 8;
patrolRadius = 200;
class Rewards { class Rewards {
money[] = {25000, 60000}; money[] = {25000, 60000};
reputation[] = {6, 14}; reputation[] = {6, 14};

View File

@ -55,6 +55,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Attack)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Attack_RatingFail"; property = "FORGE_Module_Attack_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -332,6 +333,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Defend)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Defend_RatingFail"; property = "FORGE_Module_Defend_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -436,6 +438,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Defuse)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Defuse_RatingFail"; property = "FORGE_Module_Defuse_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -547,6 +550,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Destroy)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Destroy_RatingFail"; property = "FORGE_Module_Destroy_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -665,6 +669,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Hostage)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Hostage_RatingFail"; property = "FORGE_Module_Hostage_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -811,6 +816,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_Delivery)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_Delivery_RatingFail"; property = "FORGE_Module_Delivery_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";
@ -964,6 +970,7 @@ class CfgVehicles {
typeName = "NUMBER"; typeName = "NUMBER";
defaultValue = 0; defaultValue = 0;
}; };
REWARD_ARRAY_ATTRIBUTES(FORGE_Module_HVT)
class RatingFail: Edit { class RatingFail: Edit {
property = "FORGE_Module_HVT_RatingFail"; property = "FORGE_Module_HVT_RatingFail";
displayName = "Rating Loss"; displayName = "Rating Loss";

View File

@ -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 A review-only prototype for object-based task instances lives under
`prototypes/`. It is not wired into runtime. `prototypes/`. It is not wired into runtime.
- `prototypes/taskObjectPrototypes.sqf` - `forge_server_task_fnc_initPrototypes`
- `prototypes/README.md` - `prototypes/README.md`
The prototype sketches `TaskInstanceBaseClass`, `HostageTaskBaseClass`, and The prototype sketches `TaskInstanceBaseClass`, `HostageTaskBaseClass`, and

View File

@ -1,23 +1,11 @@
PREP(attack); PREP(attack);
PREP(attackModule);
PREP(cargoModule);
PREP(defend); PREP(defend);
PREP(defendModule);
PREP(defuse); PREP(defuse);
PREP(defuseModule);
PREP(delivery); PREP(delivery);
PREP(deliveryModule);
PREP(destroy); PREP(destroy);
PREP(destroyModule);
PREP(explosivesModule);
PREP(handler); PREP(handler);
PREP(handleTaskRewards);
PREP(heartBeat);
PREP(hostage); PREP(hostage);
PREP(hostageModule);
PREP(hostagesModule);
PREP(hvt); PREP(hvt);
PREP(hvtModule);
PREP(makeCargo); PREP(makeCargo);
PREP(makeHostage); PREP(makeHostage);
PREP(makeHVT); PREP(makeHVT);
@ -27,7 +15,32 @@ PREP(makeShooter);
PREP(makeTarget); PREP(makeTarget);
PREP(missionManager); PREP(missionManager);
PREP(initTaskStore); PREP(initTaskStore);
PREP(protectedModule);
PREP(shootersModule); PREP_SUBDIR(generators,attackMissionGenerator);
PREP(spawnEnemyWave);
PREP(startTask); 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);

View File

@ -26,3 +26,4 @@
GVAR(TaskStore) call ["incrementDefuseCount", [_taskID]]; GVAR(TaskStore) call ["incrementDefuseCount", [_taskID]];
}] call CFUNC(addEventHandler); }] call CFUNC(addEventHandler);
[] call FUNC(missionManager);

View File

@ -60,10 +60,23 @@ waitUntil {
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]]; _targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
if (_timeLimit isNotEqualTo 0) then { 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 { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] 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 }; private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
@ -77,7 +90,16 @@ waitUntil {
if (_timeLimit isNotEqualTo 0) then { if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit); 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) (_result == 1) or (_targetsKilled >= _limitSuccess)
} else { } else {
@ -99,6 +121,13 @@ if (_result == 1) then {
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else { } 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; { deleteVehicle _x } forEach _targets;
private _rewards = createHashMap; private _rewards = createHashMap;

View File

@ -2,7 +2,7 @@
/* /*
* Author: IDSolutions * Author: IDSolutions
* Manages attack-only dynamic mission generation. * Manages dynamic mission generators.
* *
* Arguments: * Arguments:
* None * None
@ -16,335 +16,120 @@
* Public: No * 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"] #pragma hemtt ignore_variables ["_self"]
GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [ GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [
["#type", "MissionManagerBaseClass"], ["#type", "MissionManagerBaseClass"],
["#create", compileFinal { ["#create", compileFinal {
private _missionConfig = missionConfigFile >> "CfgMissions"; _self set ["recentLocationRegistry", []];
_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 ["activeMissionRegistry", createHashMap]; _self set ["activeMissionRegistry", createHashMap];
_self set ["generators", [createHashMapObject [GVAR(AttackMissionGeneratorBaseClass)]]];
}], }],
["getMissionInterval", compileFinal { ["getGenerators", compileFinal {
private _interval = _self getOrDefault ["missionInterval", 300]; _self getOrDefault ["generators", []]
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
}], }],
["getActiveMissionIds", compileFinal { ["getActiveMissionIds", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
keys _activeMissionRegistry keys _activeMissionRegistry
}], }],
["getActiveLocationKeys", compileFinal { ["getActiveGeneratedMissionIds", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; private _activeCatalog = GVAR(TaskStore) call ["getActiveTaskCatalog", []];
private _locationKeys = []; if !(_activeCatalog isEqualType []) exitWith { [] };
private _taskIds = [];
{ {
private _locationKey = _y getOrDefault ["locationKey", ""]; if !(_x isEqualType createHashMap) then { continue; };
if (_locationKey isNotEqualTo "") then { if ((_x getOrDefault ["source", ""]) isNotEqualTo "mission_manager") then { continue; };
_locationKeys pushBackUnique _locationKey;
private _taskID = _x getOrDefault ["taskId", _x getOrDefault ["taskID", ""]];
if (_taskID isNotEqualTo "") then {
_taskIds pushBackUnique _taskID;
}; };
} forEach _activeMissionRegistry; } forEach _activeCatalog;
_locationKeys
_taskIds
}], }],
["buildAttackSpawnPosition", compileFinal { ["getMaxConcurrentMissions", compileFinal {
params [["_locationConfig", configNull, [configNull]]]; private _maxConcurrent = 1;
{
if (isNull _locationConfig) exitWith { [0, 0, 0] }; _maxConcurrent = _maxConcurrent max (_x call ["getMaxConcurrentMissions", []]);
} forEach (_self call ["getGenerators", []]);
private _center = getArray (_locationConfig >> "position"); _maxConcurrent
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
}], }],
["selectAttackLocation", compileFinal { ["getMissionInterval", compileFinal {
private _locationsConfig = _self getOrDefault ["locationsConfig", configNull]; private _interval = 300;
private _locations = []; private _generators = _self call ["getGenerators", []];
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap]; if (_generators isEqualTo []) exitWith { _interval };
private _activeLocationKeys = _self call ["getActiveLocationKeys", []];
private _reuseCooldown = _self call ["getLocationReuseCooldown", []];
private _now = serverTime;
_interval = (_generators select 0) call ["getMissionInterval", []];
{ {
private _locationKey = configName _x; _interval = _interval min (_x call ["getMissionInterval", []]);
private _lastUsed = _recentLocationRegistry getOrDefault [_locationKey, -1]; } forEach _generators;
private _isCoolingDown = (_lastUsed >= 0) && { (_now - _lastUsed) < _reuseCooldown }; _interval
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]]]
]
}], }],
["spawnAttackGroup", compileFinal { ["cleanupCompletedMissions", 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 { private _taskID = _x;
_groups pushBack _x; private _status = GVAR(TaskStore) call ["getTaskStatus", [_taskID]];
}; private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_taskID]];
} forEach ("true" configClasses _aiGroupsConfig); if !(_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then { continue; };
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; };
{ {
_unitPool pushBack createHashMapFromArray [ if (_x call ["completeMission", [_self, _taskID]]) exitWith {};
["vehicle", getText (_x >> "vehicle")], } forEach (_self call ["getGenerators", []]);
["rank", getText (_x >> "rank")],
["position", getArray (_x >> "position")]
];
} forEach ("true" configClasses (_x >> "Units"));
} forEach _groups;
if (_unitPool isEqualTo []) exitWith { GVAR(TaskStore) call ["clearTaskStatus", [_taskID]];
deleteGroup _group; } forEach (_self call ["getActiveMissionIds", []]);
grpNull
};
private _leaderPool = _unitPool select { true
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
}], }],
["completeMission", compileFinal { ["completeMission", compileFinal {
params [["_taskID", "", [""]]]; params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false }; if (_taskID isEqualTo "") exitWith { false };
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap]; {
private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap]; if (_x call ["completeMission", [_self, _taskID]]) exitWith { true };
private _locationKey = _missionRecord getOrDefault ["locationKey", ""]; } forEach (_self call ["getGenerators", []]);
_activeMissionRegistry deleteAt _taskID; false
_self set ["activeMissionRegistry", _activeMissionRegistry]; }],
["startAvailableMissions", compileFinal {
if (_locationKey isNotEqualTo "") then { private _activeGeneratedMissionIds = _self call ["getActiveGeneratedMissionIds", []];
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap]; private _maxConcurrentMissions = _self call ["getMaxConcurrentMissions", []];
_recentLocationRegistry set [_locationKey, serverTime]; if (count _activeGeneratedMissionIds >= _maxConcurrentMissions) exitWith {
_self set ["recentLocationRegistry", _recentLocationRegistry]; ["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(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];
GVAR(MissionManagerPFH) = [{
GVAR(MissionManager) call ["cleanupCompletedMissions", []];
[{ private _taskID = GVAR(MissionManager) call ["startAvailableMissions", []];
{ if (_taskID isEqualTo "") exitWith {};
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", []]);
if (count (GVAR(MissionManager) call ["getActiveMissionIds", []]) >= (GVAR(MissionManager) call ["getMaxConcurrentMissions", []])) exitWith {}; ["INFO", format ["Mission manager started mission %1.", _taskID]] call EFUNC(common,log);
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);
}, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler); }, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler);
true

View File

@ -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
}]
];

View File

@ -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 <STRING>
* 1: Task label <STRING>
* 2: Reward key <STRING>
*
* Return Value:
* Parsed reward array <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);
[]

View File

@ -36,6 +36,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
getPosATL _logic 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", "attack",
_taskID, _taskID,
@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]], ["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", 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); ] call FUNC(startTask);

View File

@ -42,6 +42,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit
_logic getVariable ["MinBlufor", 1] _logic getVariable ["MinBlufor", 1]
]] call EFUNC(common,log); ]] 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", "defend",
_taskID, _taskID,
@ -59,7 +65,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit
["defendTime", _logic getVariable ["DefendTime", 600]], ["defendTime", _logic getVariable ["DefendTime", 600]],
["waveCount", _logic getVariable ["WaveCount", 3]], ["waveCount", _logic getVariable ["WaveCount", 3]],
["waveCooldown", _logic getVariable ["WaveCooldown", 300]], ["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); ] call FUNC(startTask);

View File

@ -53,6 +53,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then {
getPosATL _logic 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", "defuse",
_taskID, _taskID,
@ -71,7 +77,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]], ["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", 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); ] call FUNC(startTask);

View File

@ -46,6 +46,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then {
getPosATL _logic 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", "delivery",
_taskID, _taskID,
@ -64,7 +70,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then {
["endSuccess", _logic getVariable ["EndSuccess", false]], ["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]], ["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]], ["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); ] call FUNC(startTask);

View File

@ -36,6 +36,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
getPosATL _logic 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", "destroy",
_taskID, _taskID,
@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]], ["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]], ["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", 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); ] call FUNC(startTask);

View File

@ -61,6 +61,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then {
getPosATL _logic 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", "hostage",
_taskID, _taskID,
@ -83,7 +89,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then {
["extractionZone", _logic getVariable ["ExtZone", ""]], ["extractionZone", _logic getVariable ["ExtZone", ""]],
["cbrn", _logic getVariable ["CBRN", false]], ["cbrn", _logic getVariable ["CBRN", false]],
["execution", _logic getVariable ["Execution", 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); ] call FUNC(startTask);

View File

@ -42,6 +42,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
getPosATL _logic 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", "hvt",
_taskID, _taskID,
@ -61,7 +67,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["endFail", _logic getVariable ["EndFail", false]], ["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]], ["timeLimit", _logic getVariable ["TimeLimit", 0]],
["extractionZone", _logic getVariable ["ExtZone", ""]], ["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); ] call FUNC(startTask);

View File

@ -1,7 +1,8 @@
# Task Object Prototypes # Task Object Prototypes
This folder contains review-only `createHashMapObject` prototypes for task 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: Current prototypes:
- `TaskInstanceBaseClass` - `TaskInstanceBaseClass`
@ -11,14 +12,15 @@ Current prototypes:
- `HostageEntityController` - `HostageEntityController`
- `DefuseTaskBaseClass` - `DefuseTaskBaseClass`
Source: Review entry points:
- [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf) - [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf)
- [TaskInstanceBaseClass.sqf](./TaskInstanceBaseClass.sqf) - [fnc_initPrototypes.sqf](../functions/prototypes/fnc_initPrototypes.sqf)
- [EntityControllerBaseClass.sqf](./EntityControllerBaseClass.sqf) - [fnc_TaskInstanceBaseClass.sqf](../functions/prototypes/fnc_TaskInstanceBaseClass.sqf)
- [AttackTaskBaseClass.sqf](./AttackTaskBaseClass.sqf) - [fnc_EntityControllerBaseClass.sqf](../functions/prototypes/fnc_EntityControllerBaseClass.sqf)
- [HostageTaskBaseClass.sqf](./HostageTaskBaseClass.sqf) - [fnc_AttackTaskBaseClass.sqf](../functions/prototypes/fnc_AttackTaskBaseClass.sqf)
- [HostageEntityController.sqf](./HostageEntityController.sqf) - [fnc_HostageTaskBaseClass.sqf](../functions/prototypes/fnc_HostageTaskBaseClass.sqf)
- [DefuseTaskBaseClass.sqf](./DefuseTaskBaseClass.sqf) - [fnc_HostageEntityController.sqf](../functions/prototypes/fnc_HostageEntityController.sqf)
- [fnc_DefuseTaskBaseClass.sqf](../functions/prototypes/fnc_DefuseTaskBaseClass.sqf)
Purpose: Purpose:
- show what per-task instance objects could look like - show what per-task instance objects could look like

View File

@ -60,15 +60,12 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [
_self set ["requiredKills", _requiredKills]; _self set ["requiredKills", _requiredKills];
_self set ["maxTargetLosses", _maxTargetLosses]; _self set ["maxTargetLosses", _maxTargetLosses];
_self set ["timeLimit", _taskParams getOrDefault ["timeLimit", 0]]; _self set ["timeLimit", _taskParams getOrDefault ["timeLimit", 0]];
_self set ["useTaskStore", _taskParams getOrDefault ["useTaskStore", false]];
// Review-only registry entry to demonstrate where #delete is useful. _self call ["registerInstance", []];
missionNamespace setVariable [_taskID, _self];
}], }],
["#delete", compileFinal { ["#delete", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; _self call ["unregisterInstance", []];
if (_taskID isNotEqualTo "") then {
missionNamespace setVariable [_taskID, nil];
};
}], }],
["countKilledTargets", compileFinal { ["countKilledTargets", compileFinal {
private _targets = _self getOrDefault ["targets", []]; private _targets = _self getOrDefault ["targets", []];
@ -105,14 +102,22 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
private _useTaskStore = _self getOrDefault ["useTaskStore", false];
waitUntil { if (_useTaskStore) then {
sleep 1; waitUntil {
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; sleep 1;
count _targets > 0 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 { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
@ -122,7 +127,9 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { 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", []]; private _snapshot = _self call ["tick", []];
@ -140,32 +147,37 @@ GVAR(AttackTaskBaseClass) = createHashMapFromArray [
if ((_self call ["getStatus", []]) isEqualTo "failed") then { if ((_self call ["getStatus", []]) isEqualTo "failed") then {
{ deleteVehicle _x } forEach _targets; { deleteVehicle _x } forEach _targets;
[_taskID, "FAILED"] call BFUNC(taskSetState); if (_useTaskStore) then {
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; [_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 ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]];
GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]];
GVAR(TaskStore) call ["clearTask", [_taskID]]; GVAR(TaskStore) call ["clearTask", [_taskID]];
};
if (_endFail) then { ["MissionFail", false] remoteExec ["BIS_fnc_endMission", playerSide]; }; if (_endFail) then { ["MissionFail", false] remoteExec ["BIS_fnc_endMission", playerSide]; };
} else { } else {
{ deleteVehicle _x } forEach _targets; { deleteVehicle _x } forEach _targets;
[_taskID, _rewardData] call FUNC(handleTaskRewards); if (_useTaskStore) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, _rewardData] call FUNC(handleTaskRewards);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; [_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 ["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 ["applyRatingOutcome", [_taskID, _ratingSuccess]];
GVAR(TaskStore) call ["clearTask", [_taskID]]; GVAR(TaskStore) call ["clearTask", [_taskID]];
};
if (_endSuccess) then { ["MissionSuccess", true] remoteExec ["BIS_fnc_endMission", playerSide]; }; if (_endSuccess) then { ["MissionSuccess", true] remoteExec ["BIS_fnc_endMission", playerSide]; };
}; };
_self call ["cleanup", []];
true true
}] }]
]; ];

View File

@ -62,15 +62,13 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [
_self set ["requiredDefusals", _requiredDefusals]; _self set ["requiredDefusals", _requiredDefusals];
_self set ["maxProtectedLosses", _maxProtectedLosses]; _self set ["maxProtectedLosses", _maxProtectedLosses];
_self set ["iedTimer", _taskParams getOrDefault ["iedTimer", 300]]; _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. _self call ["registerInstance", []];
missionNamespace setVariable [_taskID, _self];
}], }],
["#delete", compileFinal { ["#delete", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; _self call ["unregisterInstance", []];
if (_taskID isNotEqualTo "") then {
missionNamespace setVariable [_taskID, nil];
};
}], }],
["countProtectedDestroyed", compileFinal { ["countProtectedDestroyed", compileFinal {
private _protected = _self getOrDefault ["protected", []]; private _protected = _self getOrDefault ["protected", []];
@ -79,9 +77,17 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [
["getDefuseCount", compileFinal { ["getDefuseCount", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
if (_taskID isEqualTo "") exitWith { 0 }; if (_taskID isEqualTo "") exitWith { 0 };
if !(_self getOrDefault ["useTaskStore", false]) exitWith {
_self getOrDefault ["localDefuseCount", 0]
};
GVAR(TaskStore) call ["getDefuseCount", [_taskID]] GVAR(TaskStore) call ["getDefuseCount", [_taskID]]
}], }],
["incrementLocalDefuseCount", compileFinal {
private _next = (_self getOrDefault ["localDefuseCount", 0]) + 1;
_self set ["localDefuseCount", _next];
_next
}],
["tick", compileFinal { ["tick", compileFinal {
private _defusedCount = _self call ["getDefuseCount", []]; private _defusedCount = _self call ["getDefuseCount", []];
private _protectedDestroyed = _self call ["countProtectedDestroyed", []]; private _protectedDestroyed = _self call ["countProtectedDestroyed", []];
@ -114,6 +120,7 @@ GVAR(DefuseTaskBaseClass) = createHashMapFromArray [
sleep 1; sleep 1;
}; };
_self call ["cleanup", []];
true true
}] }]
]; ];

View File

@ -63,6 +63,30 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
["getEntity", compileFinal { ["getEntity", compileFinal {
_self getOrDefault ["entity", objNull] _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 { ["markActive", compileFinal {
_self set ["status", "active"]; _self set ["status", "active"];
_self set ["startedAt", serverTime]; _self set ["startedAt", serverTime];
@ -79,7 +103,7 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
true true
}], }],
["cleanup", compileFinal { ["cleanup", compileFinal {
false _self call ["unregisterInstance", []]
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
false false

View File

@ -47,18 +47,10 @@ GVAR(HostageEntityController) = createHashMapFromArray [
_self set ["loopAnimation", _controllerParams getOrDefault ["loopAnimation", "acts_executionvictim_loop"]]; _self set ["loopAnimation", _controllerParams getOrDefault ["loopAnimation", "acts_executionvictim_loop"]];
_self set ["rescueAnimation", _controllerParams getOrDefault ["rescueAnimation", "acts_executionvictim_unbow"]]; _self set ["rescueAnimation", _controllerParams getOrDefault ["rescueAnimation", "acts_executionvictim_unbow"]];
private _netID = if (isNull _entity) then { "" } else { netId _entity }; _self call ["registerInstance", []];
if (_netID isNotEqualTo "") then {
private _controllerKey = format ["hostage_controller_%1", _netID];
missionNamespace setVariable [_controllerKey, _self];
};
}], }],
["#delete", compileFinal { ["#delete", compileFinal {
private _entity = _self getOrDefault ["entity", objNull]; _self call ["unregisterInstance", []];
if (!isNull _entity) then {
private _controllerKey = format ["hostage_controller_%1", netId _entity];
missionNamespace setVariable [_controllerKey, nil];
};
}], }],
["applyInitialState", compileFinal { ["applyInitialState", compileFinal {
private _entity = _self getOrDefault ["entity", objNull]; private _entity = _self getOrDefault ["entity", objNull];
@ -95,6 +87,7 @@ GVAR(HostageEntityController) = createHashMapFromArray [
private _entity = _self getOrDefault ["entity", objNull]; private _entity = _self getOrDefault ["entity", objNull];
if (isNull _entity) exitWith { if (isNull _entity) exitWith {
_self call ["markAborted", []]; _self call ["markAborted", []];
_self call ["cleanup", []];
false false
}; };
@ -102,6 +95,7 @@ GVAR(HostageEntityController) = createHashMapFromArray [
if !(_self call ["applyInitialState", []]) exitWith { if !(_self call ["applyInitialState", []]) exitWith {
_self call ["markAborted", []]; _self call ["markAborted", []];
_self call ["cleanup", []];
false false
}; };
@ -117,11 +111,13 @@ GVAR(HostageEntityController) = createHashMapFromArray [
if (isNull _entity || { !alive _entity }) exitWith { if (isNull _entity || { !alive _entity }) exitWith {
_self call ["markAborted", []]; _self call ["markAborted", []];
_self call ["cleanup", []];
false false
}; };
_self call ["transitionToRescued", [_rescuer]]; _self call ["transitionToRescued", [_rescuer]];
_self call ["markFinished", []]; _self call ["markFinished", []];
_self call ["cleanup", []];
true true
}] }]
]; ];

View File

@ -67,20 +67,17 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
_self set ["execution", _taskParams getOrDefault ["execution", false]]; _self set ["execution", _taskParams getOrDefault ["execution", false]];
_self set ["cbrn", _taskParams getOrDefault ["cbrn", false]]; _self set ["cbrn", _taskParams getOrDefault ["cbrn", false]];
_self set ["cbrnZone", _taskParams getOrDefault ["cbrnZone", ""]]; _self set ["cbrnZone", _taskParams getOrDefault ["cbrnZone", ""]];
_self set ["useTaskStore", _taskParams getOrDefault ["useTaskStore", false]];
_self set ["requiredRescues", _requiredRescues]; _self set ["requiredRescues", _requiredRescues];
_self set ["maxHostageLosses", _maxHostageLosses]; _self set ["maxHostageLosses", _maxHostageLosses];
_self set ["hostageControllers", []]; _self set ["hostageControllers", []];
_self call ["createHostageControllers", []]; _self call ["createHostageControllers", []];
// Review-only registry entry to demonstrate where #delete is useful. _self call ["registerInstance", []];
missionNamespace setVariable [_taskID, _self];
}], }],
["#delete", compileFinal { ["#delete", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; _self call ["unregisterInstance", []];
if (_taskID isNotEqualTo "") then {
missionNamespace setVariable [_taskID, nil];
};
}], }],
["createHostageControllers", compileFinal { ["createHostageControllers", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
@ -119,7 +116,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
}], }],
["refreshEntitiesFromStore", compileFinal { ["refreshEntitiesFromStore", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; 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 _hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]];
private _shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]]; private _shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]];
@ -130,7 +127,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
}], }],
["trackParticipants", compileFinal { ["trackParticipants", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; 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 _hostages = _self getOrDefault ["hostages", []];
private _shooters = _self getOrDefault ["shooters", []]; private _shooters = _self getOrDefault ["shooters", []];
@ -140,17 +137,29 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
true true
}], }],
["waitForRequiredEntities", compileFinal { ["waitForRequiredEntities", compileFinal {
waitUntil { if (_self getOrDefault ["useTaskStore", false]) then {
sleep 1; waitUntil {
_self call ["refreshEntitiesFromStore", []]; sleep 1;
count (_self getOrDefault ["hostages", []]) > 0 _self call ["refreshEntitiesFromStore", []];
}; count (_self getOrDefault ["hostages", []]) > 0
};
waitUntil { waitUntil {
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
count (_self getOrDefault ["shooters", []]) > 0 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", []]; private _hostages = _self getOrDefault ["hostages", []];
@ -169,7 +178,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
private _timeLimit = _self getOrDefault ["timeLimit", 0]; private _timeLimit = _self getOrDefault ["timeLimit", 0];
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
if (_timeLimit <= 0 || { _taskID isEqualTo "" }) exitWith { true }; if (_timeLimit <= 0 || { _taskID isEqualTo "" } || { !(_self getOrDefault ["useTaskStore", false]) }) exitWith { true };
waitUntil { waitUntil {
sleep 1; sleep 1;
@ -238,6 +247,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
private _cbrn = _self getOrDefault ["cbrn", false]; private _cbrn = _self getOrDefault ["cbrn", false];
private _hostage = _self getOrDefault ["execution", false]; private _hostage = _self getOrDefault ["execution", false];
private _cbrnZone = _self getOrDefault ["cbrnZone", ""]; private _cbrnZone = _self getOrDefault ["cbrnZone", ""];
private _useTaskStore = _self getOrDefault ["useTaskStore", false];
if (_cbrn && { _cbrnZone isNotEqualTo "" }) then { if (_cbrn && { _cbrnZone isNotEqualTo "" }) then {
"SmokeShellYellow" createVehicle getMarkerPos _cbrnZone; "SmokeShellYellow" createVehicle getMarkerPos _cbrnZone;
@ -272,13 +282,15 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
{ deleteVehicle _x } forEach _hostages; { deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters; { 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 ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]];
GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]]; GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]];
GVAR(TaskStore) call ["clearTask", [_taskID]]; GVAR(TaskStore) call ["clearTask", [_taskID]];
};
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
true true
@ -291,18 +303,21 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
private _useTaskStore = _self getOrDefault ["useTaskStore", false];
{ deleteVehicle _x } forEach _hostages; { deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters; { deleteVehicle _x } forEach _shooters;
[_taskID, _rewardData] call FUNC(handleTaskRewards); if (_useTaskStore) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_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 ["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 ["applyRatingOutcome", [_taskID, _ratingSuccess]];
GVAR(TaskStore) call ["clearTask", [_taskID]]; GVAR(TaskStore) call ["clearTask", [_taskID]];
};
if (_endSuccess) then { ["MissionSuccess", true] remoteExecCall ["BIS_fnc_endMission", playerSide]; }; if (_endSuccess) then { ["MissionSuccess", true] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
true true
@ -334,6 +349,7 @@ GVAR(HostageTaskBaseClass) = createHashMapFromArray [
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };
_self call ["cleanup", []];
true true
}] }]
]; ];

View File

@ -86,6 +86,27 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["getRewardData", compileFinal { ["getRewardData", compileFinal {
_self getOrDefault ["rewardData", createHashMap] _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 { ["markActive", compileFinal {
_self set ["status", "active"]; _self set ["status", "active"];
_self set ["startedAt", serverTime]; _self set ["startedAt", serverTime];
@ -109,7 +130,7 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
true true
}], }],
["cleanup", compileFinal { ["cleanup", compileFinal {
false _self call ["unregisterInstance", []]
}], }],
["tick", compileFinal { ["tick", compileFinal {
createHashMap createHashMap

View File

@ -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)]
]

View File

@ -0,0 +1 @@
#include "..\script_component.hpp"

View File

@ -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)]
]

View File

@ -7,3 +7,35 @@
// #define ENABLE_PERFORMANCE_COUNTERS // #define ENABLE_PERFORMANCE_COUNTERS
#include "\forge\forge_server\addons\main\script_macros.hpp" #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"; \
};