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(initClass);
PREP(initRepository);
PREP(openUI);

View File

@ -6,10 +6,10 @@
[QGVAR(initPhone), []] call CFUNC(localEvent);
}] call CFUNC(waitUntilAndExecute);
if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
if (isNil QGVAR(PhoneRepository)) then { [] call FUNC(initRepository); };
[QGVAR(initPhone), {
GVAR(PhoneClass) call ["init", []];
GVAR(PhoneRepository) call ["init", []];
["forge_server_phone_requestInitPhone", [getPlayerUID player, createHashMap]] call CFUNC(serverEvent);
["forge_server_phone_requestRefreshContacts", [getPlayerUID player, player]] call CFUNC(serverEvent);
@ -18,7 +18,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
[QGVAR(responseSyncPhone), {
params [["_data", createHashMap, [createHashMap]]];
GVAR(PhoneClass) call ["sync", [_data]];
GVAR(PhoneRepository) call ["sync", [_data]];
}] call CFUNC(addEventHandler);
// Contact Management Response Events
@ -26,10 +26,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Contact Added", "Contact added successfully", 3000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", "Contact added successfully", 3000]];
[QGVAR(refreshUI), []] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Contact Error", "Failed to add contact", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Contact Error", "Failed to add contact", 4000]];
};
}] call CFUNC(addEventHandler);
@ -37,10 +37,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_phoneNumber", "", [""]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Contact Added", format ["Contact with phone %1 added successfully", _phoneNumber], 3000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", format ["Contact with phone %1 added successfully", _phoneNumber], 3000]];
[QGVAR(refreshUI), []] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["warning", "Contact Not Found", format ["Player with phone %1 not found", _phoneNumber], 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["warning", "Contact Not Found", format ["Player with phone %1 not found", _phoneNumber], 4000]];
};
}] call CFUNC(addEventHandler);
@ -48,10 +48,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_email", "", [""]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Contact Added", format ["Contact with email %1 added successfully", _email], 3000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Added", format ["Contact with email %1 added successfully", _email], 3000]];
[QGVAR(refreshUI), []] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["warning", "Contact Not Found", format ["Player with email %1 not found", _email], 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["warning", "Contact Not Found", format ["Player with email %1 not found", _email], 4000]];
};
}] call CFUNC(addEventHandler);
@ -59,10 +59,10 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]], ["_contactUid", "", [""]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Contact Removed", "Contact removed successfully", 3000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Contact Removed", "Contact removed successfully", 3000]];
[QGVAR(refreshUI), []] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Contact Error", "Failed to remove contact", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Contact Error", "Failed to remove contact", 4000]];
};
}] call CFUNC(addEventHandler);
@ -103,7 +103,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
};
} forEach _contacts;
[QEGVAR(notifications,recieveNotification), ["info", "New Message", format ["From %1", _senderName], 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["info", "New Message", format ["From %1", _senderName], 4000]];
diag_log format ["[FORGE:Client:Phone] Message received from %1: %2", _fromUid, _message];
@ -114,9 +114,9 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Message Sent", "Message sent successfully", 2000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Message Sent", "Message sent successfully", 2000]];
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Message Failed", "Failed to send message", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Message Failed", "Failed to send message", 4000]];
};
}] call CFUNC(addEventHandler);
@ -157,7 +157,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
diag_log format ["[FORGE:Client:Phone] Message %1 deleted", _messageId];
[QGVAR(updateMessageDeleted), [_messageId]] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Message Delete Failed", "Failed to delete message", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Message Delete Failed", "Failed to delete message", 4000]];
};
}] call CFUNC(addEventHandler);
@ -184,7 +184,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
};
} forEach _contacts;
[QEGVAR(notifications,recieveNotification), ["info", "New Email", format ["From %1: %2", _senderName, _subject], 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["info", "New Email", format ["From %1: %2", _senderName, _subject], 4000]];
diag_log format ["[FORGE:Client:Phone] Email received from %1: %2", _fromUid, _subject];
@ -195,9 +195,9 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
params [["_success", false, [false]]];
if (_success) then {
[QEGVAR(notifications,recieveNotification), ["success", "Email Sent", "Email sent successfully", 2000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["success", "Email Sent", "Email sent successfully", 2000]];
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Email Failed", "Failed to send email", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Email Failed", "Failed to send email", 4000]];
};
}] call CFUNC(addEventHandler);
@ -233,7 +233,7 @@ if (isNil QGVAR(PhoneClass)) then { [] call FUNC(initClass); };
diag_log format ["[FORGE:Client:Phone] Email %1 deleted", _emailId];
[QGVAR(updateEmailDeleted), [_emailId]] call CFUNC(localEvent);
} else {
[QEGVAR(notifications,recieveNotification), ["danger", "Email Delete Failed", "Failed to delete email", 4000]] call CFUNC(localEvent);
EGVAR(notifications,NotificationService) call ["create", ["danger", "Email Delete Failed", "Failed to delete email", 4000]];
};
}] call CFUNC(addEventHandler);

View File

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

View File

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

View File

@ -42,11 +42,13 @@
#ifdef DISABLE_COMPILE_CACHE
#define LINKFUNC(x) {call FUNC(x)}
#define PREP(fncName) FUNC(fncName) = compile preprocessFileLineNumbers QPATHTOF(functions\DOUBLES(fnc,fncName).sqf)
#define PREP_SUBDIR(dir,fncName) FUNC(fncName) = compile preprocessFileLineNumbers QPATHTOF(functions\dir\DOUBLES(fnc,fncName).sqf)
#define PREP_RECOMPILE_START if (isNil "forge_server_fnc_recompile") then {forge_server_recompiles = []; forge_server_fnc_recompile = {{call _x} forEach forge_server_recompiles;}}; private _recomp = {
#define PREP_RECOMPILE_END }; call _recomp; forge_server_recompiles pushBack _recomp;
#else
#define LINKFUNC(x) FUNC(x)
#define PREP(fncName) [QPATHTOF(functions\DOUBLES(fnc,fncName).sqf), QFUNC(fncName)] call CBA_fnc_compileFunction
#define PREP_SUBDIR(dir,fncName) [QPATHTOF(functions\dir\DOUBLES(fnc,fncName).sqf), QFUNC(fncName)] call CBA_fnc_compileFunction
#define PREP_RECOMPILE_START ; /* disabled */
#define PREP_RECOMPILE_END ; /* disabled */
#endif

View File

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

View File

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

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
`prototypes/`. It is not wired into runtime.
- `prototypes/taskObjectPrototypes.sqf`
- `forge_server_task_fnc_initPrototypes`
- `prototypes/README.md`
The prototype sketches `TaskInstanceBaseClass`, `HostageTaskBaseClass`, and

View File

@ -1,23 +1,11 @@
PREP(attack);
PREP(attackModule);
PREP(cargoModule);
PREP(defend);
PREP(defendModule);
PREP(defuse);
PREP(defuseModule);
PREP(delivery);
PREP(deliveryModule);
PREP(destroy);
PREP(destroyModule);
PREP(explosivesModule);
PREP(handler);
PREP(handleTaskRewards);
PREP(heartBeat);
PREP(hostage);
PREP(hostageModule);
PREP(hostagesModule);
PREP(hvt);
PREP(hvtModule);
PREP(makeCargo);
PREP(makeHostage);
PREP(makeHVT);
@ -27,7 +15,32 @@ PREP(makeShooter);
PREP(makeTarget);
PREP(missionManager);
PREP(initTaskStore);
PREP(protectedModule);
PREP(shootersModule);
PREP(spawnEnemyWave);
PREP(startTask);
PREP_SUBDIR(generators,attackMissionGenerator);
PREP_SUBDIR(helpers,handleTaskRewards);
PREP_SUBDIR(helpers,heartBeat);
PREP_SUBDIR(helpers,parseRewards);
PREP_SUBDIR(helpers,spawnEnemyWave);
PREP_SUBDIR(helpers,startTask);
PREP_SUBDIR(modules,attackModule);
PREP_SUBDIR(modules,cargoModule);
PREP_SUBDIR(modules,defendModule);
PREP_SUBDIR(modules,defuseModule);
PREP_SUBDIR(modules,deliveryModule);
PREP_SUBDIR(modules,destroyModule);
PREP_SUBDIR(modules,explosivesModule);
PREP_SUBDIR(modules,hostageModule);
PREP_SUBDIR(modules,hostagesModule);
PREP_SUBDIR(modules,hvtModule);
PREP_SUBDIR(modules,protectedModule);
PREP_SUBDIR(modules,shootersModule);
PREP_SUBDIR(prototypes,initPrototypes);
PREP_SUBDIR(prototypes,TaskInstanceBaseClass);
PREP_SUBDIR(prototypes,EntityControllerBaseClass);
PREP_SUBDIR(prototypes,AttackTaskBaseClass);
PREP_SUBDIR(prototypes,HostageTaskBaseClass);
PREP_SUBDIR(prototypes,HostageEntityController);
PREP_SUBDIR(prototypes,DefuseTaskBaseClass);

View File

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

View File

@ -60,10 +60,23 @@ waitUntil {
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
if (_timeLimit isNotEqualTo 0) then {
private _catalogEntry = GVAR(TaskStore) call ["getTaskCatalogEntry", [_taskID]];
["INFO", format [
"Attack task %1 initial state before acceptance wait. Accepted=%2, RequesterUid='%3', Source='%4', TimeLimit=%5s",
_taskID,
_catalogEntry getOrDefault ["accepted", false],
_catalogEntry getOrDefault ["requesterUid", ""],
_catalogEntry getOrDefault ["source", ""],
_timeLimit
]] call EFUNC(common,log);
["INFO", format ["Attack task %1 waiting for acceptance before starting %2s time limit.", _taskID, _timeLimit]] call EFUNC(common,log);
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
};
["INFO", format ["Attack task %1 accepted. Starting %2s time limit.", _taskID, _timeLimit]] call EFUNC(common,log);
};
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
@ -77,7 +90,16 @@ waitUntil {
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_targetsKilled < _limitSuccess && _timeExpired) then { _result = 1; };
if (_targetsKilled < _limitSuccess && _timeExpired) then {
["WARNING", format [
"Attack task %1 failed by timeout. TargetsKilled=%2, Required=%3, TimeLimit=%4s",
_taskID,
_targetsKilled,
_limitSuccess,
_timeLimit
]] call EFUNC(common,log);
_result = 1;
};
(_result == 1) or (_targetsKilled >= _limitSuccess)
} else {
@ -99,6 +121,13 @@ if (_result == 1) then {
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
["INFO", format [
"Attack task %1 succeeded. TargetsRequired=%2, TargetsKilled=%3",
_taskID,
_limitSuccess,
{ !alive _x } count _targets
]] call EFUNC(common,log);
{ deleteVehicle _x } forEach _targets;
private _rewards = createHashMap;

View File

@ -2,7 +2,7 @@
/*
* Author: IDSolutions
* Manages attack-only dynamic mission generation.
* Manages dynamic mission generators.
*
* Arguments:
* None
@ -16,335 +16,120 @@
* Public: No
*/
if !(isServer) exitWith { false };
if !(isNil QGVAR(MissionManagerPFH)) exitWith { false };
if (isNil QGVAR(AttackMissionGeneratorBaseClass)) then { call FUNC(attackMissionGenerator); };
#pragma hemtt ignore_variables ["_self"]
GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [
["#type", "MissionManagerBaseClass"],
["#create", compileFinal {
private _missionConfig = missionConfigFile >> "CfgMissions";
_self set ["missionConfig", _missionConfig];
_self set ["locationsConfig", (_missionConfig >> "Locations")];
_self set ["aiGroupsConfig", (_missionConfig >> "AIGroups")];
_self set ["attackConfig", (_missionConfig >> "MissionTypes" >> "Attack")];
_self set ["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")];
_self set ["missionInterval", getNumber (_missionConfig >> "missionInterval")];
_self set ["recentLocationRegistry", createHashMap];
_self set ["recentLocationRegistry", []];
_self set ["activeMissionRegistry", createHashMap];
_self set ["generators", [createHashMapObject [GVAR(AttackMissionGeneratorBaseClass)]]];
}],
["getMissionInterval", compileFinal {
private _interval = _self getOrDefault ["missionInterval", 300];
if (_interval <= 0) then { _interval = 300; };
_interval
}],
["getMaxConcurrentMissions", compileFinal {
private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1];
if (_maxConcurrent <= 0) then { _maxConcurrent = 1; };
private _attackLocationCount = _self call ["getAttackLocationCount", []];
if (_attackLocationCount > 0) then {
_maxConcurrent = _maxConcurrent min _attackLocationCount;
};
_maxConcurrent
}],
["getAttackLocationCount", compileFinal {
private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
if (isNull _locationsConfig) exitWith { 0 };
private _count = 0;
{
if ("attack" in getArray (_x >> "suitable")) then {
_count = _count + 1;
};
} forEach ("true" configClasses _locationsConfig);
_count
}],
["getLocationReuseCooldown", compileFinal {
private _missionConfig = _self getOrDefault ["missionConfig", configNull];
private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
if (_cooldown <= 0) then { _cooldown = 900; };
_cooldown
["getGenerators", compileFinal {
_self getOrDefault ["generators", []]
}],
["getActiveMissionIds", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
keys _activeMissionRegistry
}],
["getActiveLocationKeys", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
private _locationKeys = [];
["getActiveGeneratedMissionIds", compileFinal {
private _activeCatalog = GVAR(TaskStore) call ["getActiveTaskCatalog", []];
if !(_activeCatalog isEqualType []) exitWith { [] };
private _taskIds = [];
{
private _locationKey = _y getOrDefault ["locationKey", ""];
if (_locationKey isNotEqualTo "") then {
_locationKeys pushBackUnique _locationKey;
if !(_x isEqualType createHashMap) then { continue; };
if ((_x getOrDefault ["source", ""]) isNotEqualTo "mission_manager") then { continue; };
private _taskID = _x getOrDefault ["taskId", _x getOrDefault ["taskID", ""]];
if (_taskID isNotEqualTo "") then {
_taskIds pushBackUnique _taskID;
};
} forEach _activeMissionRegistry;
_locationKeys
} forEach _activeCatalog;
_taskIds
}],
["buildAttackSpawnPosition", compileFinal {
params [["_locationConfig", configNull, [configNull]]];
if (isNull _locationConfig) exitWith { [0, 0, 0] };
private _center = getArray (_locationConfig >> "position");
private _radius = getNumber (_locationConfig >> "radius");
if (_radius <= 0) exitWith { _center };
private _spawnPosition = +_center;
private _attempts = 0;
while { _attempts < 8 } do {
private _angle = random 360;
private _distance = (_radius * 0.2) + random (_radius * 0.65);
private _candidate = [
(_center # 0) + ((sin _angle) * _distance),
(_center # 1) + ((cos _angle) * _distance),
_center param [2, 0]
];
if !(surfaceIsWater _candidate) exitWith {
_spawnPosition = _candidate;
};
_attempts = _attempts + 1;
};
_spawnPosition
["getMaxConcurrentMissions", compileFinal {
private _maxConcurrent = 1;
{
_maxConcurrent = _maxConcurrent max (_x call ["getMaxConcurrentMissions", []]);
} forEach (_self call ["getGenerators", []]);
_maxConcurrent
}],
["selectAttackLocation", compileFinal {
private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
private _locations = [];
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
private _activeLocationKeys = _self call ["getActiveLocationKeys", []];
private _reuseCooldown = _self call ["getLocationReuseCooldown", []];
private _now = serverTime;
["getMissionInterval", compileFinal {
private _interval = 300;
private _generators = _self call ["getGenerators", []];
if (_generators isEqualTo []) exitWith { _interval };
_interval = (_generators select 0) call ["getMissionInterval", []];
{
private _locationKey = configName _x;
private _lastUsed = _recentLocationRegistry getOrDefault [_locationKey, -1];
private _isCoolingDown = (_lastUsed >= 0) && { (_now - _lastUsed) < _reuseCooldown };
if (
"attack" in getArray (_x >> "suitable")
&& { !(_locationKey in _activeLocationKeys) }
&& { !_isCoolingDown }
) then {
_locations pushBack _x;
};
} forEach ("true" configClasses _locationsConfig);
if (_locations isEqualTo []) then {
{
if ("attack" in getArray (_x >> "suitable") && { !(configName _x in _activeLocationKeys) }) then {
_locations pushBack _x;
};
} forEach ("true" configClasses _locationsConfig);
};
if (_locations isEqualTo []) exitWith { createHashMap };
private _location = selectRandom _locations;
createHashMapFromArray [
["config", _location],
["key", configName _location],
["position", _self call ["buildAttackSpawnPosition", [_location]]]
]
_interval = _interval min (_x call ["getMissionInterval", []]);
} forEach _generators;
_interval
}],
["spawnAttackGroup", compileFinal {
params [["_position", [0, 0, 0], [[]]]];
private _aiGroupsConfig = _self getOrDefault ["aiGroupsConfig", configNull];
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _groups = [];
["cleanupCompletedMissions", compileFinal {
{
if ("attack" in getArray (_x >> "suitable")) then {
_groups pushBack _x;
};
} forEach ("true" configClasses _aiGroupsConfig);
if (_groups isEqualTo []) exitWith { grpNull };
private _groupConfig = selectRandom _groups;
private _side = getText (_groupConfig >> "side");
private _group = createGroup (call compile _side);
private _minUnits = getNumber (_attackConfig >> "minUnits");
private _maxUnits = getNumber (_attackConfig >> "maxUnits");
if (_minUnits <= 0) then { _minUnits = 4; };
if (_maxUnits < _minUnits) then { _maxUnits = _minUnits; };
private _targetUnitCount = floor random [ _minUnits, ceil ((_minUnits + _maxUnits) / 2), _maxUnits + 1 ];
private _unitPool = [];
{
if ((getText (_x >> "side")) isNotEqualTo _side) then { continue; };
private _taskID = _x;
private _status = GVAR(TaskStore) call ["getTaskStatus", [_taskID]];
private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_taskID]];
if !(_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then { continue; };
{
_unitPool pushBack createHashMapFromArray [
["vehicle", getText (_x >> "vehicle")],
["rank", getText (_x >> "rank")],
["position", getArray (_x >> "position")]
];
} forEach ("true" configClasses (_x >> "Units"));
} forEach _groups;
if (_x call ["completeMission", [_self, _taskID]]) exitWith {};
} forEach (_self call ["getGenerators", []]);
if (_unitPool isEqualTo []) exitWith {
deleteGroup _group;
grpNull
};
GVAR(TaskStore) call ["clearTaskStatus", [_taskID]];
} forEach (_self call ["getActiveMissionIds", []]);
private _leaderPool = _unitPool select {
toUpperANSI (_x getOrDefault ["rank", "PRIVATE"]) in ["SERGEANT", "LIEUTENANT", "CAPTAIN", "MAJOR", "COLONEL"]
};
if (_leaderPool isEqualTo []) then { _leaderPool = +_unitPool; };
private _spawnDefs = [selectRandom _leaderPool];
for "_i" from 1 to (_targetUnitCount - 1) do {
_spawnDefs pushBack (selectRandom _unitPool);
};
{
private _unitClass = _x getOrDefault ["vehicle", ""];
if (_unitClass isEqualTo "") then { continue; };
private _unitOffset = +(_x getOrDefault ["position", [0, 0, 0]]);
if (count _unitOffset < 3) then { _unitOffset resize 3; };
_unitOffset set [0, (_unitOffset # 0) + (random 6 - 3)];
_unitOffset set [1, (_unitOffset # 1) + (random 6 - 3)];
private _unit = _group createUnit [_unitClass, _position vectorAdd _unitOffset, [], 0, "NONE"];
_unit setRank (_x getOrDefault ["rank", "PRIVATE"]);
} forEach _spawnDefs;
_group
}],
["rollRewards", compileFinal {
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _equipmentRewards = [];
private _supplyRewards = [];
private _weaponRewards = [];
private _vehicleRewards = [];
private _specialRewards = [];
{
private _category = _x;
{
_x params ["_item", "_chance"];
if (random 1 < _chance) then {
switch (_category) do {
case "equipment": { _equipmentRewards pushBack _item; };
case "supplies": { _supplyRewards pushBack _item; };
case "weapons": { _weaponRewards pushBack _item; };
case "vehicles": { _vehicleRewards pushBack _item; };
case "special": { _specialRewards pushBack _item; };
};
};
} forEach (getArray (_attackConfig >> "Rewards" >> _category));
} forEach ["equipment", "supplies", "weapons", "vehicles", "special"];
createHashMapFromArray [
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
}],
["startAttackMission", compileFinal {
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _locationData = _self call ["selectAttackLocation"];
if (_locationData isEqualTo createHashMap) exitWith { "" };
private _locationKey = _locationData getOrDefault ["key", ""];
private _position = _locationData getOrDefault ["position", [0, 0, 0]];
private _group = _self call ["spawnAttackGroup", [_position]];
if (isNull _group) exitWith { "" };
private _units = units _group;
if (_units isEqualTo []) exitWith {
deleteGroup _group;
""
};
private _taskID = format ["task_attack_%1", round (diag_tickTime * 1000)];
private _rewardRange = getArray (_attackConfig >> "Rewards" >> "money");
private _reputationRange = getArray (_attackConfig >> "Rewards" >> "reputation");
private _penaltyRange = getArray (_attackConfig >> "penalty");
private _timeRange = getArray (_attackConfig >> "timeLimit");
private _rewards = _self call ["rollRewards"];
private _success = [
"attack",
_taskID,
_position,
format ["Attack: %1", _locationKey],
format ["Eliminate hostile forces operating near %1.", _locationKey],
createHashMapFromArray [["targets", _units]],
createHashMapFromArray [
["limitFail", 0],
["limitSuccess", count _units],
["funds", _rewardRange call BFUNC(randomNum)],
["ratingFail", _penaltyRange call BFUNC(randomNum)],
["ratingSuccess", _reputationRange call BFUNC(randomNum)],
["endSuccess", false],
["endFail", false],
["timeLimit", _timeRange call BFUNC(randomNum)],
["equipment", _rewards get "equipment"],
["supplies", _rewards get "supplies"],
["weapons", _rewards get "weapons"],
["vehicles", _rewards get "vehicles"],
["special", _rewards get "special"]
],
0,
"",
"mission_manager"
] call FUNC(startTask);
if !(_success) exitWith { "" };
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
_activeMissionRegistry set [_taskID, createHashMapFromArray [
["locationKey", _locationKey],
["startedAt", serverTime]
]];
_self set ["activeMissionRegistry", _activeMissionRegistry];
_taskID
true
}],
["completeMission", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap];
private _locationKey = _missionRecord getOrDefault ["locationKey", ""];
{
if (_x call ["completeMission", [_self, _taskID]]) exitWith { true };
} forEach (_self call ["getGenerators", []]);
_activeMissionRegistry deleteAt _taskID;
_self set ["activeMissionRegistry", _activeMissionRegistry];
if (_locationKey isNotEqualTo "") then {
private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
_recentLocationRegistry set [_locationKey, serverTime];
_self set ["recentLocationRegistry", _recentLocationRegistry];
false
}],
["startAvailableMissions", compileFinal {
private _activeGeneratedMissionIds = _self call ["getActiveGeneratedMissionIds", []];
private _maxConcurrentMissions = _self call ["getMaxConcurrentMissions", []];
if (count _activeGeneratedMissionIds >= _maxConcurrentMissions) exitWith {
["INFO", format [
"Mission manager skipped generation because cap was reached. ActiveGenerated=%1, Cap=%2, TaskIDs=%3",
count _activeGeneratedMissionIds,
_maxConcurrentMissions,
_activeGeneratedMissionIds
]] call EFUNC(common,log);
""
};
true
private _startedTaskID = "";
{
private _taskID = _x call ["startMission", [_self]];
if (_taskID isNotEqualTo "") exitWith {
_startedTaskID = _taskID;
};
} forEach (_self call ["getGenerators", []]);
_startedTaskID
}]
];
GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];
GVAR(MissionManagerPFH) = [{
GVAR(MissionManager) call ["cleanupCompletedMissions", []];
[{
{
private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_x]];
if (_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then {
GVAR(MissionManager) call ["completeMission", [_x]];
GVAR(TaskStore) call ["clearTaskStatus", [_x]];
};
} forEach (GVAR(MissionManager) call ["getActiveMissionIds", []]);
private _taskID = GVAR(MissionManager) call ["startAvailableMissions", []];
if (_taskID isEqualTo "") exitWith {};
if (count (GVAR(MissionManager) call ["getActiveMissionIds", []]) >= (GVAR(MissionManager) call ["getMaxConcurrentMissions", []])) exitWith {};
private _taskID = GVAR(MissionManager) call ["startAttackMission", []];
if (_taskID isEqualTo "") exitWith {
["WARNING", "Mission manager failed to start an attack mission."] call EFUNC(common,log);
};
["INFO", format ["Mission manager started attack mission %1.", _taskID]] call EFUNC(common,log);
["INFO", format ["Mission manager started mission %1.", _taskID]] call EFUNC(common,log);
}, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler);
true

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
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"attack",
_taskID,
@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]]
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -42,6 +42,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit
_logic getVariable ["MinBlufor", 1]
]] call EFUNC(common,log);
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"defend",
_taskID,
@ -59,7 +65,12 @@ if (_defenseZone isEqualTo "" || { markerShape _defenseZone isEqualTo "" }) exit
["defendTime", _logic getVariable ["DefendTime", 600]],
["waveCount", _logic getVariable ["WaveCount", 3]],
["waveCooldown", _logic getVariable ["WaveCooldown", 300]],
["minBlufor", _logic getVariable ["MinBlufor", 1]]
["minBlufor", _logic getVariable ["MinBlufor", 1]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -53,6 +53,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then {
getPosATL _logic
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"defuse",
_taskID,
@ -71,7 +77,12 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["iedTimer", _logic getVariable ["TimeLimit", 300]]
["iedTimer", _logic getVariable ["TimeLimit", 300]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -46,6 +46,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then {
getPosATL _logic
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"delivery",
_taskID,
@ -64,7 +70,12 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then {
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["deliveryZone", _logic getVariable ["DeliveryZone", ""]]
["deliveryZone", _logic getVariable ["DeliveryZone", ""]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -36,6 +36,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
getPosATL _logic
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"destroy",
_taskID,
@ -53,7 +59,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]]
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -61,6 +61,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then {
getPosATL _logic
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"hostage",
_taskID,
@ -83,7 +89,12 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then {
["extractionZone", _logic getVariable ["ExtZone", ""]],
["cbrn", _logic getVariable ["CBRN", false]],
["execution", _logic getVariable ["Execution", false]],
["cbrnZone", _logic getVariable ["CBRNZone", ""]]
["cbrnZone", _logic getVariable ["CBRNZone", ""]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -42,6 +42,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
getPosATL _logic
};
private _equipmentRewards = [_logic getVariable ["EquipmentRewards", "[]"], _taskID, "equipment"] call FUNC(parseRewards);
private _supplyRewards = [_logic getVariable ["SupplyRewards", "[]"], _taskID, "supplies"] call FUNC(parseRewards);
private _weaponRewards = [_logic getVariable ["WeaponRewards", "[]"], _taskID, "weapons"] call FUNC(parseRewards);
private _vehicleRewards = [_logic getVariable ["VehicleRewards", "[]"], _taskID, "vehicles"] call FUNC(parseRewards);
private _specialRewards = [_logic getVariable ["SpecialRewards", "[]"], _taskID, "special"] call FUNC(parseRewards);
[
"hvt",
_taskID,
@ -61,7 +67,12 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["extractionZone", _logic getVariable ["ExtZone", ""]],
["captureHvt", _logic getVariable ["CaptureHVT", true]]
["captureHvt", _logic getVariable ["CaptureHVT", true]],
["equipment", _equipmentRewards],
["supplies", _supplyRewards],
["weapons", _weaponRewards],
["vehicles", _vehicleRewards],
["special", _specialRewards]
]
] call FUNC(startTask);

View File

@ -1,7 +1,8 @@
# Task Object Prototypes
This folder contains review-only `createHashMapObject` prototypes for task
instances. They are not part of runtime initialization.
instances. Their source now lives under `functions/prototypes/`, and they are
loaded for review with `[] call forge_server_task_fnc_initPrototypes;`.
Current prototypes:
- `TaskInstanceBaseClass`
@ -11,14 +12,15 @@ Current prototypes:
- `HostageEntityController`
- `DefuseTaskBaseClass`
Source:
Review entry points:
- [taskObjectPrototypes.sqf](./taskObjectPrototypes.sqf)
- [TaskInstanceBaseClass.sqf](./TaskInstanceBaseClass.sqf)
- [EntityControllerBaseClass.sqf](./EntityControllerBaseClass.sqf)
- [AttackTaskBaseClass.sqf](./AttackTaskBaseClass.sqf)
- [HostageTaskBaseClass.sqf](./HostageTaskBaseClass.sqf)
- [HostageEntityController.sqf](./HostageEntityController.sqf)
- [DefuseTaskBaseClass.sqf](./DefuseTaskBaseClass.sqf)
- [fnc_initPrototypes.sqf](../functions/prototypes/fnc_initPrototypes.sqf)
- [fnc_TaskInstanceBaseClass.sqf](../functions/prototypes/fnc_TaskInstanceBaseClass.sqf)
- [fnc_EntityControllerBaseClass.sqf](../functions/prototypes/fnc_EntityControllerBaseClass.sqf)
- [fnc_AttackTaskBaseClass.sqf](../functions/prototypes/fnc_AttackTaskBaseClass.sqf)
- [fnc_HostageTaskBaseClass.sqf](../functions/prototypes/fnc_HostageTaskBaseClass.sqf)
- [fnc_HostageEntityController.sqf](../functions/prototypes/fnc_HostageEntityController.sqf)
- [fnc_DefuseTaskBaseClass.sqf](../functions/prototypes/fnc_DefuseTaskBaseClass.sqf)
Purpose:
- show what per-task instance objects could look like

View File

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

View File

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

View File

@ -63,6 +63,30 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
["getEntity", compileFinal {
_self getOrDefault ["entity", objNull]
}],
["getRegistryKey", compileFinal {
private _entity = _self getOrDefault ["entity", objNull];
if (isNull _entity) exitWith { "" };
format ["hostage_controller_%1", netId _entity]
}],
["registerInstance", compileFinal {
private _registryKey = _self call ["getRegistryKey", []];
if (_registryKey isEqualTo "") exitWith { false };
private _registry = missionNamespace getVariable [QGVAR(PrototypeControllerInstances), createHashMap];
_registry set [_registryKey, _self];
missionNamespace setVariable [_registryKey, _self];
true
}],
["unregisterInstance", compileFinal {
private _registryKey = _self call ["getRegistryKey", []];
if (_registryKey isEqualTo "") exitWith { false };
private _registry = missionNamespace getVariable [QGVAR(PrototypeControllerInstances), createHashMap];
_registry deleteAt _registryKey;
missionNamespace setVariable [_registryKey, nil];
true
}],
["markActive", compileFinal {
_self set ["status", "active"];
_self set ["startedAt", serverTime];
@ -79,7 +103,7 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
true
}],
["cleanup", compileFinal {
false
_self call ["unregisterInstance", []]
}],
["runLoop", compileFinal {
false

View File

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

View File

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

View File

@ -86,6 +86,27 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["getRewardData", compileFinal {
_self getOrDefault ["rewardData", createHashMap]
}],
["getRegistryKey", compileFinal {
_self getOrDefault ["taskID", ""]
}],
["registerInstance", compileFinal {
private _registryKey = _self call ["getRegistryKey", []];
if (_registryKey isEqualTo "") exitWith { false };
private _registry = missionNamespace getVariable [QGVAR(PrototypeTaskInstances), createHashMap];
_registry set [_registryKey, _self];
missionNamespace setVariable [_registryKey, _self];
true
}],
["unregisterInstance", compileFinal {
private _registryKey = _self call ["getRegistryKey", []];
if (_registryKey isEqualTo "") exitWith { false };
private _registry = missionNamespace getVariable [QGVAR(PrototypeTaskInstances), createHashMap];
_registry deleteAt _registryKey;
missionNamespace setVariable [_registryKey, nil];
true
}],
["markActive", compileFinal {
_self set ["status", "active"];
_self set ["startedAt", serverTime];
@ -109,7 +130,7 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
true
}],
["cleanup", compileFinal {
false
_self call ["unregisterInstance", []]
}],
["tick", compileFinal {
createHashMap

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
#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"; \
};