Implement org credit line debt and bank repayment flow #2

Merged
J.Schmidt92 merged 14 commits from development into master 2026-04-02 16:50:38 -05:00
107 changed files with 6435 additions and 122 deletions
Showing only changes of commit 45a4f7460a - Show all commits

View File

@ -4,7 +4,7 @@
* File: fnc_handleUIEvents.sqf
* Author: IDSolutions
* Date: 2026-01-28
* Last Update: 2026-02-17
* Last Update: 2026-03-28
* Public: No
*
* Description:
@ -35,6 +35,7 @@ switch (_event) do {
case "actor::close::menu": { closeDialog 1; };
case "actor::open::atm": { [true] spawn EFUNC(bank,openUI); };
case "actor::open::bank": { [] spawn EFUNC(bank,openUI); };
case "actor::open::cad": { [] spawn EFUNC(cad,openUI); };
case "actor::open::device": { hint "Device interaction is not yet implemented."; };
case "actor::open::garage": { [] spawn EFUNC(garage,openUI); };
case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); };

View File

@ -100,6 +100,12 @@ const actions = {
//=============================================================================
const baseMenuItems = [
{
id: "cad",
title: "CAD",
description: "Access CAD (Computer Aided Dispatch)",
action: "actor::open::cad",
},
{
id: "phone",
title: "Phone",
@ -133,6 +139,12 @@ const actionDefinitions = {
description: "Access your bank account and manage finances",
action: "actor::open::bank",
},
cad: {
id: "cad",
title: "CAD",
description: "Access the CAD",
action: "actor::open::cad",
},
phone: {
id: "phone",
title: "Phone",

View File

@ -0,0 +1 @@
forge\forge_client\addons\cad

View File

@ -0,0 +1,12 @@
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
clientInit = QUOTE(call COMPILE_SCRIPT(XEH_preInitClient));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
clientInit = QUOTE(call COMPILE_SCRIPT(XEH_postInitClient));
};
};

View File

@ -0,0 +1,214 @@
# Integrated Map Display System (A3API Pattern)
This system integrates the Arma 3 native map control (`RscMapControl`) within an HTML/CSS/JS UI using Arma's proper WebBrowser control (type 106) and A3API communication pattern.
## How It Works
### Layered Architecture
1. **IFrame Control (type 106)** - Loads HTML content using `ctrlWebBrowserAction`
2. **Map Control (RscMapControl)** - Native Arma map positioned behind/within the UI
3. **A3API Communication** - Bidirectional communication between JavaScript and SQF
### Communication Flow
**JavaScript → SQF:**
```javascript
// Send alert (no response expected)
A3API.SendAlert(JSON.stringify({
event: "map::zoomIn",
data: null
}));
// Send confirm (expects response via ExecJS)
A3API.SendConfirm(JSON.stringify({
event: "map::getPosition",
data: null
}));
```
**SQF → JavaScript:**
```sqf
_control ctrlWebBrowserAction ["ExecJS", "updateMapState({center: [1000, 2000], scale: 0.5});"];
```
## File Structure
```
UI/map/
├── _site/
│ ├── index.html # HTML with A3API dynamic loading
│ ├── script.js # JavaScript using A3API
│ └── style.css # Styling
└── MAP_README.md # This file
functions/map/
├── fn_openMap.sqf # Opens the display
├── fn_mapHandleUIEvents.sqf # Handles JS events
├── fn_mapDisplay.sqf # Display initialization
└── fn_mapDisplayUpdate.sqf # Update loop
UI/MapDisplay.h # Dialog definition
```
## Usage
### Opening the Map
```sqf
[] call FORGE_fnc_openMap;
```
### From Init or Action
```sqf
// Add player action
player addAction ["Open Map", {[] call FORGE_fnc_openMap;}];
// In init.sqf
[] call FORGE_fnc_openMap;
```
## Key Differences from Standard HTML/CSS/JS
### 1. Dynamic Resource Loading
Instead of `<link>` and `<script>` tags, files are loaded using A3API:
```html
<script>
Promise.all([
A3API.RequestFile("UI\\map\\_site\\style.css"),
A3API.RequestFile("UI\\map\\_site\\script.js")
]).then(([css, js]) => {
// Apply CSS
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// Execute JavaScript
const script = document.createElement('script');
script.text = js;
document.head.appendChild(script);
});
</script>
```
### 2. Event Communication
Use **A3API.SendAlert()** for one-way messages:
```javascript
A3API.SendAlert(JSON.stringify({event: "map::action", data: value}));
```
Use **A3API.SendConfirm()** for messages expecting a response:
```javascript
A3API.SendConfirm(JSON.stringify({event: "map::getdata", data: null}));
```
### 3. Pointer Events
UI elements need `pointer-events: auto` while the body has `pointer-events: none`:
```css
body {
pointer-events: none; /* Allows clicks through to map */
}
#topBar {
pointer-events: auto; /* UI elements catch clicks */
}
```
## Dialog Definition Pattern
```cpp
class RscMapDisplay {
idd = 9000;
onLoad = "['onLoad', _this] call FORGE_fnc_mapDisplay;";
class Controls {
class Browser: RscText {
type = 106; // IFrame control type
idc = 9001;
x = "safeZoneX";
y = "safeZoneY";
w = "safeZoneW";
h = "safeZoneH";
};
class MapControl: RscMapControl {
idc = 9002;
// Position to fit within HTML UI
};
};
};
```
## Event Handler Pattern
In `fn_openMap.sqf`:
```sqf
private _ctrl = _display displayCtrl 9001;
// Add JSDialog event handler
_ctrl ctrlAddEventHandler ["JSDialog", {
params ["_control", "_isConfirmDialog", "_message"];
[_control, _isConfirmDialog, _message] call FORGE_fnc_mapHandleUIEvents;
}];
// Load HTML file
_ctrl ctrlWebBrowserAction ["LoadFile", "UI\\map\\_site\\index.html"];
```
In `fn_mapHandleUIEvents.sqf`:
```sqf
params ["_control", "_isConfirmDialog", "_message"];
private _eventData = fromJSON _message;
private _event = _eventData get "event";
private _data = _eventData get "data";
switch (_event) do {
case "map::ready": {
// Initialize
};
case "map::zoomIn": {
// Handle zoom
};
};
```
## Benefits of This Pattern
1. **Proper Arma Integration** - Uses native WebBrowser control (type 106)
2. **File System Compatibility** - A3API.RequestFile() works with Arma's file system
3. **Reliable Communication** - JSDialog event handler is more stable than htmlLoad
4. **Modular** - CSS and JS in separate files, dynamically loaded
5. **Consistent** - Matches bank module pattern used in FORGE
## Troubleshooting
**Files not loading:**
- Check paths use double backslashes: `"UI\\map\\_site\\style.css"`
- Verify files exist in the correct directory
- Check .rpt log for file loading errors
**Events not firing:**
- Verify JSDialog event handler is attached
- Check JSON formatting in A3API calls
- Look for JavaScript console errors (use OpenDevConsole)
**Map not showing:**
- Verify MapControl idc matches (9002)
- Check map control positioning in MapDisplay.h
- Ensure map control is rendered after browser control
## Developer Tools
Enable dev console in `fn_openMap.sqf`:
```sqf
_ctrl ctrlWebBrowserAction ["OpenDevConsole"];
```
This opens Chromium dev tools for debugging JavaScript, CSS, and network requests.

View File

@ -0,0 +1,5 @@
PREP(handleUIEvents);
PREP(initRepository);
PREP(initUIBridge);
PREP(initUI);
PREP(openUI);

View File

@ -0,0 +1,24 @@
#include "script_component.hpp"
if (isNil QGVAR(CADRepository)) then { call FUNC(initRepository); };
if (isNil QGVAR(CADUIBridge)) then { call FUNC(initUIBridge); };
[QGVAR(openCAD), {
call FUNC(openUI);
}] call CFUNC(addEventHandler);
[QGVAR(responseTaskCatalog), {
params [["_entries", [], [[]]]];
if !(isNil QGVAR(CADRepository)) then {
GVAR(CADRepository) call ["setTaskCatalog", [_entries]];
};
GVAR(CADUIBridge) call ["refreshTaskCatalog", []];
}] call CFUNC(addEventHandler);
[QGVAR(responseTaskAccept), {
params [["_result", createHashMap, [createHashMap]]];
GVAR(CADUIBridge) call ["handleTaskAcceptResponse", [_result]];
}] call CFUNC(addEventHandler);

View File

@ -0,0 +1,5 @@
#include "script_component.hpp"
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;

View File

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

View File

@ -0,0 +1,21 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
author = AUTHOR;
authors[] = {"IDSolutions"};
url = ECSTRING(main,url);
name = COMPONENT_NAME;
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {
"forge_client_main"
};
units[] = {};
weapons[] = {};
VERSION_CONFIG;
};
};
#include "CfgEventHandlers.hpp"
#include "ui\RscCommon.hpp"
#include "ui\RscMapUI.hpp"

View File

@ -0,0 +1,87 @@
#include "..\script_component.hpp"
/*
* File: fnc_handleUIEvents.sqf
* Author: IDSolutions
* Date: 2026-03-28
* Public: No
*
* Description:
* Handles CAD browser UI events.
*
* Arguments:
* 0: Control [CONTROL]
* 1: Confirm dialog flag [BOOL]
* 2: Browser message [STRING]
*
* Return Value:
* UI event handled [BOOL]
*
* Example:
* [_control, false, _message] call forge_client_cad_fnc_handleUIEvents
*/
params ["_control", "_isConfirmDialog", "_message"];
private _alert = fromJSON _message;
private _event = _alert getOrDefault ["event", ""];
private _data = _alert getOrDefault ["data", nil];
diag_log format ["[FORGE:Client:CAD] Handling UI event: %1", _event];
if (_isConfirmDialog) exitWith { true };
switch (_event) do {
case "cad::ready": {
GVAR(CADUIBridge) call ["handleReady", [_control, _data]];
};
case "cad::tasks::refresh": {
GVAR(CADUIBridge) call ["requestTaskCatalog", []];
};
case "cad::tasks::accept": {
private _taskID = "";
if (_data isEqualType createHashMap) then {
_taskID = _data getOrDefault ["taskID", ""];
};
GVAR(CADUIBridge) call ["requestTaskAccept", [_taskID]];
};
case "map::zoomIn": {
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
if (isNull _mapCtrl) exitWith {};
private _currentZoom = ctrlMapScale _mapCtrl;
private _newZoom = (_currentZoom * 0.5) max 0.001;
private _center = _mapCtrl ctrlMapScreenToWorld [0.5, 0.5];
_mapCtrl ctrlMapAnimAdd [0.3, _newZoom, _center];
ctrlMapAnimCommit _mapCtrl;
};
case "map::zoomOut": {
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
if (isNull _mapCtrl) exitWith {};
private _currentZoom = ctrlMapScale _mapCtrl;
private _newZoom = (_currentZoom * 2) min 1;
private _center = _mapCtrl ctrlMapScreenToWorld [0.5, 0.5];
_mapCtrl ctrlMapAnimAdd [0.3, _newZoom, _center];
ctrlMapAnimCommit _mapCtrl;
};
case "map::search": {
private _query = str _data;
private _bottomBar = uiNamespace getVariable [QGVAR(BottomBarCtrl), controlNull];
if (isNull _bottomBar) exitWith {};
_bottomBar ctrlWebBrowserAction ["ExecJS", format ["updateStatus('Search not yet implemented: %1');", _query]];
};
case "map::close": {
if !(isNil QGVAR(CADUIBridge)) then {
GVAR(CADUIBridge) call ["handleClose", []];
};
closeDialog 1;
};
default {
diag_log format ["[FORGE:Client:CAD] WARNING: Unhandled UI event: %1", _event];
};
};
true

View File

@ -0,0 +1,52 @@
#include "..\script_component.hpp"
/*
* File: fnc_initRepository.sqf
* Author: IDSolutions
* Date: 2026-03-28
* Public: No
*
* Description:
* Initializes the CAD repository for lightweight client lifecycle state.
*
* Arguments:
* None
*
* Return Value:
* CAD repository object [HASHMAP OBJECT]
*
* Example:
* call forge_client_cad_fnc_initRepository
*/
#pragma hemtt ignore_variables ["_self"]
GVAR(CADRepository) = createHashMapObject [[
["#type", "CADRepository"],
["#create", compileFinal {
_self set ["isLoaded", true];
_self set ["isOpen", false];
_self set ["taskCatalog", []];
}],
["pushTaskCatalog", compileFinal {
params [["_bridge", createHashMap, [createHashMap]]];
if (_bridge isEqualTo createHashMap) exitWith { false };
_bridge call ["sendEvent", ["cad::tasks::hydrate", createHashMapFromArray [
["tasks", +(_self getOrDefault ["taskCatalog", []])]
]]]
}],
["setTaskCatalog", compileFinal {
params [["_entries", [], [[]]]];
_self set ["taskCatalog", +_entries];
true
}],
["setOpen", compileFinal {
params [["_isOpen", false, [false]]];
_self set ["isOpen", _isOpen];
true
}]
]];
GVAR(CADRepository)

View File

@ -0,0 +1,90 @@
#include "..\script_component.hpp"
/*
* File: fnc_initUI.sqf
* Author: IDSolutions
* Date: 2026-03-28
* Public: No
*
* Description:
* Initializes the CAD map dialog controls and local map event handling.
*
* Arguments:
* 0: Display [DISPLAY]
*
* Return Value:
* UI initialized [BOOL]
*
* Example:
* [_display] call forge_client_cad_fnc_initUI
*/
params [["_display", displayNull, [displayNull]]];
if (isNull _display) exitWith { false };
private _mapCtrl = _display displayCtrl 1001;
private _topBarCtrl = _display displayCtrl 1002;
private _bottomBarCtrl = _display displayCtrl 1003;
private _sidePanelCtrl = _display displayCtrl 1005;
uiNamespace setVariable [QGVAR(Display), _display];
uiNamespace setVariable [QGVAR(MapCtrl), _mapCtrl];
uiNamespace setVariable [QGVAR(TopBarCtrl), _topBarCtrl];
uiNamespace setVariable [QGVAR(BottomBarCtrl), _bottomBarCtrl];
uiNamespace setVariable [QGVAR(SidePanelCtrl), _sidePanelCtrl];
private _center = if (isNull player) then {
[worldSize / 2, worldSize / 2, 0]
} else {
getPosATL player
};
_mapCtrl ctrlMapAnimAdd [0, 0.2, _center];
ctrlMapAnimCommit _mapCtrl;
_mapCtrl ctrlAddEventHandler ["MouseButtonClick", {
params ["_ctrl", "_button", "_xPos", "_yPos"];
private _worldPos = _ctrl ctrlMapScreenToWorld [_xPos, _yPos];
private _bottomBar = uiNamespace getVariable [QGVAR(BottomBarCtrl), controlNull];
if (isNull _bottomBar) exitWith {};
private _jsCode = format [
"updateStatus('Clicked at: %1, %2');",
round (_worldPos # 0),
round (_worldPos # 1)
];
_bottomBar ctrlWebBrowserAction ["ExecJS", _jsCode];
}];
_mapCtrl ctrlAddEventHandler ["MouseMoving", {
params ["_ctrl", "_xPos", "_yPos"];
private _worldPos = _ctrl ctrlMapScreenToWorld [_xPos, _yPos];
private _topBar = uiNamespace getVariable [QGVAR(TopBarCtrl), controlNull];
if (isNull _topBar) exitWith {};
private _jsCode = format [
"updateCoordinates(%1, %2);",
_worldPos # 0,
_worldPos # 1
];
_topBar ctrlWebBrowserAction ["ExecJS", _jsCode];
}];
[] spawn {
while { !isNull (uiNamespace getVariable [QGVAR(Display), displayNull]) } do {
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
private _topBar = uiNamespace getVariable [QGVAR(TopBarCtrl), controlNull];
if (!isNull _mapCtrl && { !isNull _topBar }) then {
_topBar ctrlWebBrowserAction ["ExecJS", format ["updateScale(%1);", round (ctrlMapScale _mapCtrl)]];
};
sleep 0.5;
};
};
diag_log "[FORGE:Client:CAD] CAD UI initialized.";
true

View File

@ -0,0 +1,84 @@
#include "..\script_component.hpp"
/*
* File: fnc_initUIBridge.sqf
* Author: IDSolutions
* Date: 2026-03-28
* Public: No
*
* Description:
* Initializes the CAD UI bridge for sidepanel browser state and task event routing.
*
* Arguments:
* None
*
* Return Value:
* CAD UI bridge object [HASHMAP OBJECT]
*
* Example:
* call forge_client_cad_fnc_initUIBridge
*/
#pragma hemtt ignore_variables ["_self"]
private _webUIDeclarations = call EFUNC(common,initWebUIBridge);
private _webUIBridgeDeclaration = _webUIDeclarations get "bridgeDeclaration";
GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
["#base", _webUIBridgeDeclaration],
["#type", "CADUIBridgeBaseClass"],
["getActiveBrowserControl", compileFinal {
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
if (isNull _display) exitWith {
_self call ["setActiveBrowserControl", [controlNull]];
controlNull
};
private _control = _display displayCtrl 1005;
_self call ["setActiveBrowserControl", [_control]];
_control
}],
["hasOpenScreen", compileFinal {
private _screen = _self call ["getScreen", []];
private _control = _self call ["getActiveBrowserControl", []];
!(isNull _control) && { _screen call ["isReady", []] }
}],
["handleReady", compileFinal {
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
private _screen = _self call ["getScreen", []];
_screen call ["setControl", [_control]];
_screen call ["markReady", [true]];
_self call ["flushPendingEvents", []];
_self call ["requestTaskCatalog", []];
_self call ["refreshTaskCatalog", []];
true
}],
["requestTaskCatalog", compileFinal {
[SRPC(task,requestTaskCatalog), [getPlayerUID player]] call CFUNC(serverEvent);
true
}],
["requestTaskAccept", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
[SRPC(task,requestAcceptTask), [getPlayerUID player, _taskID]] call CFUNC(serverEvent);
true
}],
["refreshTaskCatalog", compileFinal {
if (isNil QGVAR(CADRepository)) exitWith { false };
GVAR(CADRepository) call ["pushTaskCatalog", [_self]]
}],
["handleTaskAcceptResponse", compileFinal {
params [["_result", createHashMap, [createHashMap]]];
_self call ["sendEvent", ["cad::tasks::accept::response", createHashMapFromArray [
["message", _result getOrDefault ["message", "Task request processed."]],
["success", _result getOrDefault ["success", false]]
]]]
}]
];
GVAR(CADUIBridge) = createHashMapObject [GVAR(CADUIBridgeBaseClass)];
GVAR(CADUIBridge)

View File

@ -0,0 +1,47 @@
#include "..\script_component.hpp"
/*
* File: fnc_openUI.sqf
* Author: IDSolutions
* Date: 2026-03-28
* Public: No
*
* Description:
* Opens the CAD map interface.
*
* Arguments:
* None
*
* Return Value:
* UI opened [BOOL]
*
* Example:
* call forge_client_cad_fnc_openUI
*/
private _display = createDialog ["RscMapUI", true];
if (isNull _display) exitWith {
diag_log "[FORGE:Client:CAD] ERROR: Failed to create CAD dialog.";
false
};
private _topBarCtrl = _display displayCtrl 1002;
private _bottomBarCtrl = _display displayCtrl 1003;
private _sidePanelCtrl = _display displayCtrl 1005;
{
_x ctrlAddEventHandler ["JSDialog", {
params ["_control", "_isConfirmDialog", "_message"];
[_control, _isConfirmDialog, _message] call FUNC(handleUIEvents);
}];
} forEach [_topBarCtrl, _bottomBarCtrl, _sidePanelCtrl];
_topBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\topbar.html)];
_bottomBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\bottombar.html)];
_sidePanelCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\sidepanel.html)];
if !(isNil QGVAR(CADRepository)) then {
GVAR(CADRepository) call ["setOpen", [true]];
};
true

View File

@ -0,0 +1,9 @@
#define COMPONENT cad
#define COMPONENT_BEAUTIFIED CAD
#include "\forge\forge_client\addons\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
// #define ENABLE_PERFORMANCE_COUNTERS
#include "\forge\forge_client\addons\main\script_macros.hpp"

View File

@ -0,0 +1,6 @@
// Control types
#define CT_STATIC 0
#define CT_MAP 100
class RscText;
class RscMapControl;

View File

@ -0,0 +1,90 @@
class RscMapUI {
idd = 1004;
movingEnable = 0;
enableSimulation = 1;
fadein = 0;
fadeout = 0;
duration = 1e+011;
onLoad = "uiNamespace setVariable ['forge_client_cad_Display', _this select 0]; [_this select 0] call forge_client_cad_fnc_initUI;";
onUnLoad = "uiNamespace setVariable ['forge_client_cad_Display', nil]; uiNamespace setVariable ['forge_client_cad_MapCtrl', nil]; uiNamespace setVariable ['forge_client_cad_TopBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_BottomBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_SidePanelCtrl', nil]; if !(isNil 'forge_client_cad_CADRepository') then { forge_client_cad_CADRepository set ['isOpen', false]; };";
class controlsBackground {
class MapControl: RscMapControl {
idc = 1001;
x = "safeZoneX + (safeZoneW * 0.1)"; // 10% margin (80% width centered)
y = "safeZoneY + (safeZoneH * 0.1) + 0.0926"; // 10% margin + 50px top bar
w = "safeZoneW * 0.8"; // 80% width
h = "(safeZoneH * 0.8) - 0.0926 - 0.0556"; // 80% height minus top and bottom bars
// Map specific settings
maxSatelliteAlpha = 0.85;
alphaFadeStartScale = 0.35;
alphaFadeEndScale = 0.4;
colorBackground[] = {0.969, 0.957, 0.949, 1};
colorSea[] = {0.467, 0.631, 0.851, 0.5};
colorForest[] = {0.624, 0.78, 0.388, 0.5};
colorRocks[] = {0, 0, 0, 0};
colorCountlines[] = {0.572, 0.354, 0.318, 0.25};
colorMainCountlines[] = {0.572, 0.354, 0.318, 0.5};
colorCountlinesWater[] = {0.491, 0.577, 0.702, 0.3};
colorMainCountlinesWater[] = {0.491, 0.577, 0.702, 0.6};
colorForestBorder[] = {0, 0, 0, 0};
colorRocksBorder[] = {0, 0, 0, 0};
colorPowerLines[] = {0.1, 0.1, 0.1, 1};
colorRailWay[] = {0.8, 0.2, 0, 1};
colorNames[] = {0.1, 0.1, 0.1, 0.9};
colorInactive[] = {1, 1, 1, 0.5};
colorLevels[] = {0.286, 0.177, 0.094, 0.5};
colorTracks[] = {0.84, 0.76, 0.65, 0.15};
colorRoads[] = {0.7, 0.7, 0.7, 1};
colorMainRoads[] = {0.9, 0.5, 0.3, 1};
colorTracksFill[] = {0.84, 0.76, 0.65, 1};
colorRoadsFill[] = {1, 1, 1, 1};
colorMainRoadsFill[] = {1, 0.6, 0.4, 1};
colorGrid[] = {0.1, 0.1, 0.1, 0.6};
colorGridMap[] = {0.1, 0.1, 0.1, 0.6};
colorText[] = {1, 1, 1, 1};
font = "PuristaMedium";
sizeEx = 0.04;
showCountourInterval = 0;
scaleMin = 0.001;
scaleMax = 1;
scaleDefault = 0.16;
};
};
class controls {
// Top bar browser
class TopBarBrowser: RscText {
type = 106;
idc = 1002;
x = "safeZoneX + (safeZoneW * 0.1)";
y = "safeZoneY + (safeZoneH * 0.1)";
w = "safeZoneW * 0.8";
h = "0.0926"; // 50px
colorBackground[] = {0, 0, 0, 0};
};
// Bottom bar browser
class BottomBarBrowser: RscText {
type = 106;
idc = 1003;
x = "safeZoneX + (safeZoneW * 0.1)";
y = "safeZoneY + (safeZoneH * 0.9) - 0.0556";
w = "safeZoneW * 0.8";
h = "0.0556"; // 30px
colorBackground[] = {0, 0, 0, 0};
};
// Side panel browser (overlays from right side of 80% box)
class SidePanelBrowser: RscText {
type = 106;
idc = 1005;
x = "safeZoneX + (safeZoneW * 0.1) + (safeZoneW * 0.8) - 0.4630"; // Right edge of 80% box minus panel width
y = "safeZoneY + (safeZoneH * 0.1) + 0.0926"; // Below top bar
w = "0.4630"; // ~250px width
h = "(safeZoneH * 0.8) - 0.0926 - 0.0556"; // Full height minus bars
colorBackground[] = {0, 0, 0, 0};
};
};
};

View File

@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"></head><body><span id="statusText">Map Ready</span> <span id="selectionInfo"></span><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const o=document.createElement("style");o.textContent=e,document.head.appendChild(o)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,o)=>e.then(()=>o.endsWith(".css")?this.loadCSS(o):o.endsWith(".js")?this.loadJS(o):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.js"]).catch(e=>console.error("[BOTTOMBAR] Load error:",e))</script></body></html>

View File

@ -0,0 +1 @@
body{-webkit-backdrop-filter:blur(18px);background:linear-gradient(90deg,#0e131bf5,#121720ed 55%,#0d1219f5);border-top:1px solid #ffffff24;justify-content:space-between;align-items:center;min-height:36px;padding:0 20px;display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden;box-shadow:0 -12px 26px #0000003d}span{color:#f5f8ffcc;text-shadow:0 1px 10px #00000047;font-size:12px}#statusText{color:var(--accent);font-weight:600}

View File

@ -0,0 +1 @@
window.CADBottombar=window.CADBottombar||{};

View File

@ -0,0 +1 @@
:root{--bg:#090c12d1;--panel:#141821e6;--panel2:#11151ed1;--stroke:#ffffff1f;--stroke2:#fff3;--text:#f5f8ffeb;--muted:#f5f8ff9e;--muted2:#f5f8ff6b;--accent:#68c4fff2;--danger:#ff6060f2;--shadow:0 20px 60px #0000008c;--radius:14px;--radius2:10px;--font:ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);color:var(--text);background:var(--bg);-webkit-backdrop-filter:blur(16px)}.btn{border-radius:var(--radius2);color:var(--text);cursor:pointer;user-select:none;background:#ffffff08;border:1px solid #ffffff1a;padding:8px 16px;font-size:14px;transition:background .16s,border-color .16s,transform .16s}.btn:hover{background:#ffffff12;border-color:#ffffff29}.btn:active{transform:scale(.98)}.btn-close{color:#ffdcdcf2;background:#ff60601a;border-color:#ff606040;font-weight:700}.btn-close:hover{background:#ff606033;border-color:#ff606059}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-thumb{background:#ffffff1a;border:2px solid #0000001a;border-radius:999px}

View File

@ -0,0 +1 @@
window.mapUIState={layersPanelVisible:!0,sidePanelElement:null},window.mapUI={sendEvent(e,t){A3API.SendAlert(JSON.stringify({event:e,data:t}))},updateCoordinates(e,t){const n=document.getElementById("coordsDisplay");n&&(n.textContent=`X: ${Math.round(e).toString().padStart(4,"0")} Y: ${Math.round(t).toString().padStart(4,"0")}`)},updateScale(e){const t=document.getElementById("scaleDisplay");t&&(t.textContent=`Scale: 1:${Math.round(e)}`)},updateStatus(e){const t=document.getElementById("statusText");t&&(t.textContent=e)}},window.updateCoordinates=window.mapUI.updateCoordinates,window.updateScale=window.mapUI.updateScale,window.updateStatus=window.mapUI.updateStatus,window.ForgeBridge=window.ForgeBridge||{_handlers:{},on(e,t){this._handlers[e]=this._handlers[e]||[],this._handlers[e].push(t)},ready:e=>(window.mapUI.sendEvent("cad::ready",e||{}),!0),receive(e){if(!e||"object"!=typeof e)return;(this._handlers[e.event]||[]).forEach(t=>t(e.data||{}))},send:(e,t)=>(window.mapUI.sendEvent(e,t||{}),!0),close:e=>(window.mapUI.sendEvent("map::close",e||{}),!0)};

View File

@ -0,0 +1 @@
html,body{background:var(--panel);border-left:1px solid var(--stroke);width:100%;height:100%;box-shadow:var(--shadow);-webkit-backdrop-filter:blur(12px);margin:0;padding:0;overflow:hidden}body{opacity:1;visibility:visible}.panel-header{border-bottom:1px solid var(--stroke);background:linear-gradient(#ffffff0d,#0000);justify-content:space-between;align-items:center;padding:14px;display:flex}.panel-header h3{color:var(--accent);text-transform:uppercase;letter-spacing:.8px;font-size:14px;font-weight:650}.panel-content{height:calc(100% - 56px);padding:14px;overflow:auto}.placeholder-message{text-align:center;padding:20px}.placeholder-message p{color:var(--muted);font-size:13px;font-style:italic}.task-toolbar{margin-bottom:10px}.task-toolbar button,.task-accept-btn{color:#f3f6f9;cursor:pointer;background:#1e252be6;border:1px solid #fff3;width:100%;padding:8px 10px}.task-toolbar button:hover,.task-accept-btn:hover{background:#2e3942f2}.task-toolbar button:disabled,.task-accept-btn:disabled{opacity:.55;cursor:default}.task-status-message{color:#cdd6dd;min-height:18px;margin-bottom:10px;font-size:12px}.task-status-message[data-type=success]{color:#79d28a}.task-status-message[data-type=error]{color:#ff8a80}.task-list{flex-direction:column;gap:10px;display:flex}.task-card{background:#0c10149e;border:1px solid #ffffff14;padding:10px}.task-card-header{justify-content:space-between;gap:8px;margin-bottom:8px;display:flex}.task-type{opacity:.7;text-transform:uppercase;font-size:11px}.task-description{margin:0 0 8px;font-size:12px;line-height:1.4}.task-meta{opacity:.8;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:11px;display:flex}

View File

@ -0,0 +1 @@
window.cadTasks={tasks:[],init(){const s=document.getElementById("refreshTasksBtn");s&&s.addEventListener("click",()=>this.refresh()),window.ForgeBridge.on("cad::tasks::hydrate",s=>{this.setTasks(s.tasks||[])}),window.ForgeBridge.on("cad::tasks::accept::response",s=>{this.handleAcceptResponse(!!s.success,s.message||"")}),window.ForgeBridge.ready({loaded:!0})},setTasks(s){this.tasks=Array.isArray(s)?s:[];const t=document.getElementById("taskStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.render()},setStatus(s,t){const e=document.getElementById("taskStatusMessage");e&&(e.textContent=s||"",e.dataset.type=t||"info")},handleAcceptResponse(s,t){this.setStatus(t||(s?"Task accepted.":"Unable to accept task."),s?"success":"error")},refresh(){this.setStatus("Refreshing tasks...","info"),window.mapUI.sendEvent("cad::tasks::refresh",{})},acceptTask(s){this.setStatus("Submitting acceptance...","info"),window.mapUI.sendEvent("cad::tasks::accept",{taskID:s})},render(){const s=document.getElementById("taskList");s&&(this.tasks.length?s.innerHTML=this.tasks.map(s=>{const t=Array.isArray(s.position)?s.position:[0,0,0],e=!!s.accepted,a=e?`Assigned: ${s.orgID||"Unknown"}`:"Available";return`\n <div class="task-card" data-task-id="${s.taskID}">\n <div class="task-card-header">\n <strong>${s.title||s.taskID}</strong>\n <span class="task-type">${s.type||"task"}</span>\n </div>\n <p class="task-description">${s.description||""}</p>\n <div class="task-meta">\n <span>${a}</span>\n <span>X: ${Math.round(t[0]||0)} Y: ${Math.round(t[1]||0)}</span>\n </div>\n <button type="button" class="task-accept-btn" ${e?"disabled":""} onclick="window.cadTasks.acceptTask('${s.taskID}')">${e?"Accepted":"Accept"}</button>\n </div>\n `}).join(""):s.innerHTML='<div class="placeholder-message"><p>No active tasks are available.</p></div>')}},window.cadTasks.init();

View File

@ -0,0 +1 @@
body{-webkit-backdrop-filter:blur(18px);background:linear-gradient(90deg,#10161ff5,#131a24f0 55%,#0f141cf5);border-bottom:1px solid #ffffff24;justify-content:space-between;align-items:center;height:56px;padding:0 20px;display:flex;position:absolute;top:0;left:0;right:0;overflow:hidden;box-shadow:0 14px 28px #00000047}.logo{color:var(--accent);text-transform:uppercase;letter-spacing:.4px;text-shadow:0 1px 12px #00000059;font-size:16px;font-weight:650}.controls{align-items:center;gap:10px;display:flex}.search-input{color:var(--text);background:#ffffff14;border:1px solid #ffffff24;border-radius:999px;outline:none;width:250px;padding:10px 12px;font-size:13px;box-shadow:inset 0 1px #ffffff08}.search-input::placeholder{color:var(--muted2)}.search-input:focus{background:#ffffff1c;border-color:#68c4ff73}.info{color:#f5f8ffd6;font-size:12px;font-family:var(--font);text-shadow:0 1px 10px #00000047;gap:20px;display:flex}

View File

@ -0,0 +1 @@
document.getElementById("btnZoomIn").addEventListener("click",()=>{window.mapUI.sendEvent("map::zoomIn",null)}),document.getElementById("btnZoomOut").addEventListener("click",()=>{window.mapUI.sendEvent("map::zoomOut",null)}),document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("searchBox").addEventListener("keypress",e=>{"Enter"===e.key&&window.mapUI.sendEvent("map::search",e.target.value)});

View File

@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="panel-header"><h3>CAD System</h3></div><div class="panel-content"><div class="task-toolbar"><button id="refreshTasksBtn" type="button">Refresh Tasks</button></div><div id="taskStatusMessage" class="task-status-message"></div><div id="taskList" class="task-list"><div class="placeholder-message"><p>Loading available tasks...</p></div></div></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const d=document.createElement("style");d.textContent=e,document.head.appendChild(d)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,d)=>e.then(()=>d.endsWith(".css")?this.loadCSS(d):d.endsWith(".js")?this.loadJS(d):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js"]).catch(e=>console.error("[SIDEPANEL] Load error:",e))</script></body></html>

View File

@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="logo">FORGE OS</div><div class="controls"><button id="btnZoomIn" class="btn">+</button> <button id="btnZoomOut" class="btn">-</button> <input id="searchBox" placeholder="Search location..." class="search-input"> <button id="btnClose" class="btn btn-close">X</button></div><div class="info"><span id="coordsDisplay">X: 0000 Y: 0000</span> <span id="scaleDisplay">Scale: 1:1000</span></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const o=document.createElement("style");o.textContent=e,document.head.appendChild(o)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,o)=>e.then(()=>o.endsWith(".css")?this.loadCSS(o):o.endsWith(".js")?this.loadJS(o):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.js"]).catch(e=>console.error("[TOPBAR] Load error:",e))</script></body></html>

View File

@ -0,0 +1,49 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<span id="statusText">Map Ready</span>
<span id="selectionInfo"></span>
<script>
window.MapLoader = {
loadCSS(path) {
return A3API.RequestFile(path).then((css) => {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
});
},
loadJS(path) {
return A3API.RequestFile(path).then((js) => {
eval(js);
});
},
loadAll(resources) {
return resources.reduce((promise, resource) => {
return promise.then(() => {
if (resource.endsWith(".css")) {
return this.loadCSS(resource);
}
if (resource.endsWith(".js")) {
return this.loadJS(resource);
}
return Promise.resolve();
});
}, Promise.resolve());
},
};
MapLoader.loadAll([
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.js",
]).catch((err) => console.error("[BOTTOMBAR] Load error:", err));
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
/*
* Bottombar UI Component
* Displays status and selection information.
*/
window.CADBottombar = window.CADBottombar || {};

View File

@ -0,0 +1,69 @@
/**
* Shared JavaScript for Map UI
* Provides common utilities and state management across all UI components
*/
window.mapUIState = {
layersPanelVisible: true,
sidePanelElement: null,
};
window.mapUI = {
sendEvent(event, data) {
A3API.SendAlert(JSON.stringify({ event: event, data: data }));
},
updateCoordinates(x, y) {
const coordDisplay = document.getElementById("coordsDisplay");
if (coordDisplay) {
coordDisplay.textContent = `X: ${Math.round(x)
.toString()
.padStart(4, "0")} Y: ${Math.round(y)
.toString()
.padStart(4, "0")}`;
}
},
updateScale(scale) {
const scaleDisplay = document.getElementById("scaleDisplay");
if (scaleDisplay) {
scaleDisplay.textContent = `Scale: 1:${Math.round(scale)}`;
}
},
updateStatus(text) {
const statusText = document.getElementById("statusText");
if (statusText) {
statusText.textContent = text;
}
},
};
window.updateCoordinates = window.mapUI.updateCoordinates;
window.updateScale = window.mapUI.updateScale;
window.updateStatus = window.mapUI.updateStatus;
window.ForgeBridge = window.ForgeBridge || {
_handlers: {},
on(event, handler) {
this._handlers[event] = this._handlers[event] || [];
this._handlers[event].push(handler);
},
ready(payload) {
window.mapUI.sendEvent("cad::ready", payload || {});
return true;
},
receive(payload) {
if (!payload || typeof payload !== "object") {
return;
}
const handlers = this._handlers[payload.event] || [];
handlers.forEach((handler) => handler(payload.data || {}));
},
send(event, data) {
window.mapUI.sendEvent(event, data || {});
return true;
},
close(data) {
window.mapUI.sendEvent("map::close", data || {});
return true;
},
};

View File

@ -0,0 +1,63 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div class="panel-header">
<h3>CAD System</h3>
</div>
<div class="panel-content">
<div class="task-toolbar">
<button id="refreshTasksBtn" type="button">
Refresh Tasks
</button>
</div>
<div id="taskStatusMessage" class="task-status-message"></div>
<div id="taskList" class="task-list">
<div class="placeholder-message">
<p>Loading available tasks...</p>
</div>
</div>
</div>
<script>
window.MapLoader = {
loadCSS(path) {
return A3API.RequestFile(path).then((css) => {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
});
},
loadJS(path) {
return A3API.RequestFile(path).then((js) => {
eval(js);
});
},
loadAll(resources) {
return resources.reduce((promise, resource) => {
return promise.then(() => {
if (resource.endsWith(".css")) {
return this.loadCSS(resource);
}
if (resource.endsWith(".js")) {
return this.loadJS(resource);
}
return Promise.resolve();
});
}, Promise.resolve());
},
};
MapLoader.loadAll([
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js",
]).catch((err) => console.error("[SIDEPANEL] Load error:", err));
</script>
</body>
</html>

View File

@ -0,0 +1,94 @@
window.cadTasks = {
tasks: [],
init() {
const refreshBtn = document.getElementById("refreshTasksBtn");
if (refreshBtn) {
refreshBtn.addEventListener("click", () => this.refresh());
}
window.ForgeBridge.on("cad::tasks::hydrate", (payload) => {
this.setTasks(payload.tasks || []);
});
window.ForgeBridge.on("cad::tasks::accept::response", (payload) => {
this.handleAcceptResponse(!!payload.success, payload.message || "");
});
window.ForgeBridge.ready({ loaded: true });
},
setTasks(tasks) {
this.tasks = Array.isArray(tasks) ? tasks : [];
const statusEl = document.getElementById("taskStatusMessage");
if (
statusEl &&
(!statusEl.dataset.type || statusEl.dataset.type === "info")
) {
this.setStatus("", "");
}
this.render();
},
setStatus(message, type) {
const statusEl = document.getElementById("taskStatusMessage");
if (!statusEl) {
return;
}
statusEl.textContent = message || "";
statusEl.dataset.type = type || "info";
},
handleAcceptResponse(success, message) {
this.setStatus(
message || (success ? "Task accepted." : "Unable to accept task."),
success ? "success" : "error",
);
},
refresh() {
this.setStatus("Refreshing tasks...", "info");
window.mapUI.sendEvent("cad::tasks::refresh", {});
},
acceptTask(taskID) {
this.setStatus("Submitting acceptance...", "info");
window.mapUI.sendEvent("cad::tasks::accept", { taskID: taskID });
},
render() {
const listEl = document.getElementById("taskList");
if (!listEl) {
return;
}
if (!this.tasks.length) {
listEl.innerHTML =
'<div class="placeholder-message"><p>No active tasks are available.</p></div>';
return;
}
listEl.innerHTML = this.tasks
.map((task) => {
const position = Array.isArray(task.position)
? task.position
: [0, 0, 0];
const accepted = !!task.accepted;
const ownerLabel = accepted
? `Assigned: ${task.orgID || "Unknown"}`
: "Available";
return `
<div class="task-card" data-task-id="${task.taskID}">
<div class="task-card-header">
<strong>${task.title || task.taskID}</strong>
<span class="task-type">${task.type || "task"}</span>
</div>
<p class="task-description">${task.description || ""}</p>
<div class="task-meta">
<span>${ownerLabel}</span>
<span>X: ${Math.round(position[0] || 0)} Y: ${Math.round(position[1] || 0)}</span>
</div>
<button type="button" class="task-accept-btn" ${accepted ? "disabled" : ""} onclick="window.cadTasks.acceptTask('${task.taskID}')">${accepted ? "Accepted" : "Accept"}</button>
</div>
`;
})
.join("");
},
};
window.cadTasks.init();

View File

@ -0,0 +1,33 @@
body {
position: absolute;
bottom: 0;
left: 0;
right: 0;
min-height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background: linear-gradient(
90deg,
rgba(14, 19, 27, 0.96),
rgba(18, 23, 32, 0.93) 55%,
rgba(13, 18, 25, 0.96)
);
border-top: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: 0 -12px 26px rgba(0, 0, 0, 0.24);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
overflow: hidden;
}
span {
color: rgba(245, 248, 255, 0.8);
font-size: 12px;
text-shadow: 0 1px 10px rgba(0, 0, 0, 0.28);
}
#statusText {
color: var(--accent);
font-weight: 600;
}

View File

@ -0,0 +1,78 @@
:root {
--bg: rgba(9, 12, 18, 0.82);
--panel: rgba(20, 24, 33, 0.9);
--panel2: rgba(17, 21, 30, 0.82);
--stroke: rgba(255, 255, 255, 0.12);
--stroke2: rgba(255, 255, 255, 0.2);
--text: rgba(245, 248, 255, 0.92);
--muted: rgba(245, 248, 255, 0.62);
--muted2: rgba(245, 248, 255, 0.42);
--accent: rgba(104, 196, 255, 0.95);
--danger: rgba(255, 96, 96, 0.95);
--shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
--radius: 14px;
--radius2: 10px;
--font:
ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial,
sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
color: var(--text);
background: var(--bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.btn {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.03);
padding: 8px 16px;
border-radius: var(--radius2);
font-size: 14px;
color: var(--text);
cursor: pointer;
transition:
background 0.16s ease,
border-color 0.16s ease,
transform 0.16s ease;
user-select: none;
}
.btn:hover {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.16);
}
.btn:active {
transform: scale(0.98);
}
.btn-close {
background: rgba(255, 96, 96, 0.1);
border-color: rgba(255, 96, 96, 0.25);
color: rgba(255, 220, 220, 0.95);
font-weight: bold;
}
.btn-close:hover {
background: rgba(255, 96, 96, 0.2);
border-color: rgba(255, 96, 96, 0.35);
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 999px;
border: 2px solid rgba(0, 0, 0, 0.1);
}

View File

@ -0,0 +1,136 @@
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: var(--panel);
border-left: 1px solid var(--stroke);
box-shadow: var(--shadow);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
body {
opacity: 1;
visibility: visible;
}
.panel-header {
padding: 14px;
border-bottom: 1px solid var(--stroke);
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.05),
transparent
);
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-header h3 {
color: var(--accent);
font-size: 14px;
font-weight: 650;
text-transform: uppercase;
letter-spacing: 0.8px;
}
.panel-content {
padding: 14px;
height: calc(100% - 56px);
overflow: auto;
}
.placeholder-message {
padding: 20px;
text-align: center;
}
.placeholder-message p {
color: var(--muted);
font-size: 13px;
font-style: italic;
}
.task-toolbar {
margin-bottom: 10px;
}
.task-toolbar button,
.task-accept-btn {
width: 100%;
padding: 8px 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(30, 37, 43, 0.9);
color: #f3f6f9;
cursor: pointer;
}
.task-toolbar button:hover,
.task-accept-btn:hover {
background: rgba(46, 57, 66, 0.95);
}
.task-toolbar button:disabled,
.task-accept-btn:disabled {
opacity: 0.55;
cursor: default;
}
.task-status-message {
min-height: 18px;
margin-bottom: 10px;
font-size: 12px;
color: #cdd6dd;
}
.task-status-message[data-type="success"] {
color: #79d28a;
}
.task-status-message[data-type="error"] {
color: #ff8a80;
}
.task-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.task-card {
padding: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(12, 16, 20, 0.62);
}
.task-card-header {
display: flex;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
}
.task-type {
opacity: 0.7;
text-transform: uppercase;
font-size: 11px;
}
.task-description {
margin: 0 0 8px;
font-size: 12px;
line-height: 1.4;
}
.task-meta {
display: flex;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
font-size: 11px;
opacity: 0.8;
}

View File

@ -0,0 +1,67 @@
body {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
background: linear-gradient(
90deg,
rgba(16, 22, 31, 0.96),
rgba(19, 26, 36, 0.94) 55%,
rgba(15, 20, 28, 0.96)
);
border-bottom: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.28);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
overflow: hidden;
}
.logo {
color: var(--accent);
font-size: 16px;
font-weight: 650;
text-transform: uppercase;
letter-spacing: 0.4px;
text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
}
.controls {
display: flex;
gap: 10px;
align-items: center;
}
.search-input {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.14);
color: var(--text);
padding: 10px 12px;
border-radius: 999px;
width: 250px;
outline: none;
font-size: 13px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.search-input::placeholder {
color: var(--muted2);
}
.search-input:focus {
border-color: rgba(104, 196, 255, 0.45);
background: rgba(255, 255, 255, 0.11);
}
.info {
display: flex;
gap: 20px;
color: rgba(245, 248, 255, 0.84);
font-size: 12px;
font-family: var(--font);
text-shadow: 0 1px 10px rgba(0, 0, 0, 0.28);
}

View File

@ -0,0 +1,63 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div class="logo">FORGE OS</div>
<div class="controls">
<button id="btnZoomIn" class="btn">+</button>
<button id="btnZoomOut" class="btn">-</button>
<input
type="text"
id="searchBox"
placeholder="Search location..."
class="search-input"
/>
<button id="btnClose" class="btn btn-close">X</button>
</div>
<div class="info">
<span id="coordsDisplay">X: 0000 Y: 0000</span>
<span id="scaleDisplay">Scale: 1:1000</span>
</div>
<script>
window.MapLoader = {
loadCSS(path) {
return A3API.RequestFile(path).then((css) => {
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
});
},
loadJS(path) {
return A3API.RequestFile(path).then((js) => {
eval(js);
});
},
loadAll(resources) {
return resources.reduce((promise, resource) => {
return promise.then(() => {
if (resource.endsWith(".css")) {
return this.loadCSS(resource);
}
if (resource.endsWith(".js")) {
return this.loadJS(resource);
}
return Promise.resolve();
});
}, Promise.resolve());
},
};
MapLoader.loadAll([
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.css",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.js",
]).catch((err) => console.error("[TOPBAR] Load error:", err));
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
document.getElementById("btnZoomIn").addEventListener("click", () => {
window.mapUI.sendEvent("map::zoomIn", null);
});
document.getElementById("btnZoomOut").addEventListener("click", () => {
window.mapUI.sendEvent("map::zoomOut", null);
});
document.getElementById("btnClose").addEventListener("click", () => {
window.mapUI.sendEvent("map::close", null);
});
document.getElementById("searchBox").addEventListener("keypress", (event) => {
if (event.key === "Enter") {
window.mapUI.sendEvent("map::search", event.target.value);
}
});

View File

@ -0,0 +1,69 @@
export default {
addonName: "cad",
title: "FORGE CAD",
logLabel: "CAD UI",
outputDir: "_site",
generateIndex: false,
jsBundles: [
{
name: "CAD shared bridge/runtime",
output: "cad-shared.js",
sources: ["src/shared.js"],
},
{
name: "CAD topbar app",
output: "cad-topbar.js",
sources: ["src/topbar.js"],
},
{
name: "CAD sidepanel app",
output: "cad-sidepanel.js",
sources: ["src/sidepanel.js"],
},
{
name: "CAD bottombar app",
output: "cad-bottombar.js",
sources: ["src/bottombar.js"],
},
],
cssBundles: [
{
name: "CAD common styles",
output: "cad-common.css",
sources: ["src/styles/common.css"],
},
{
name: "CAD topbar styles",
output: "cad-topbar.css",
sources: ["src/styles/topbar.css"],
},
{
name: "CAD sidepanel styles",
output: "cad-sidepanel.css",
sources: ["src/styles/sidepanel.css"],
},
{
name: "CAD bottombar styles",
output: "cad-bottombar.css",
sources: ["src/styles/bottombar.css"],
},
],
htmlTemplates: [
{
name: "CAD topbar page",
output: "topbar.html",
source: "src/topbar.html",
},
{
name: "CAD sidepanel page",
output: "sidepanel.html",
source: "src/sidepanel.html",
},
{
name: "CAD bottombar page",
output: "bottombar.html",
source: "src/bottombar.html",
},
],
site: {},
};

View File

@ -1,6 +1,6 @@
[sqf.banned_commands]
options.banned = [
"spawn", # Scheduled should be avoided whenever possible
# "spawn", # Scheduled should be avoided whenever possible
"execVM", # Script files should never be run directly, they should be functions
# "remoteExec", # CBA events should be used for networking
]

View File

@ -150,7 +150,7 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]];
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1", _amount]]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1", [_amount] call EFUNC(common,formatNumber)]]];
true
}],
["hydrateSession", compileFinal {
@ -247,7 +247,7 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]];
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Paid $%1", _amount]]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Paid $%1", [_amount] call EFUNC(common,formatNumber)]]];
true
}],
["resolveOrgState", compileFinal {
@ -304,8 +304,8 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
name _player
};
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Transferred $%1 to %2", _amount, _targetName]]];
GVAR(BankMessenger) call ["sendClientNotification", [_target, "info", "Bank", format ["Received $%1 from %2", _amount, _playerName]]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Transferred $%1 to %2", [_amount] call EFUNC(common,formatNumber), _targetName]]];
GVAR(BankMessenger) call ["sendClientNotification", [_target, "info", "Bank", format ["Received $%1 from %2", [_amount] call EFUNC(common,formatNumber), _playerName]]];
true
}],
["withdraw", compileFinal {
@ -323,7 +323,7 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]];
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Withdrew $%1", _amount]]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Withdrew $%1", [_amount] call EFUNC(common,formatNumber)]]];
true
}],
["depositEarnings", compileFinal {
@ -341,7 +341,7 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]];
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1 from earnings", _amount]]];
GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1 from earnings", [_amount] call EFUNC(common,formatNumber)]]];
true
}]
];

View File

@ -20,8 +20,13 @@
#define PX_TH_SEP ","
#define PX_DC_PL 2
private _value = _this;
if (_value isEqualType []) then {
_value = _value param [0, 0, [0]];
};
private _count = 0;
private _arr = (_this toFixed PX_DC_PL) splitString ".";
private _arr = (_value toFixed PX_DC_PL) splitString ".";
private _str = PX_DC_SEP+(_arr select 1);
_arr = toArray(_arr select 0);

View File

@ -47,7 +47,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
private _totalLiters = GETVAR(_target,liters,0);
private _totalCost = _totalLiters * 5;
private _formattedTotalCost = _totalCost toFixed 2;
private _formattedTotalCost = [_totalCost] call EFUNC(common,formatNumber);
private _formattedTotalLiters = _totalLiters toFixed 2;
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Total Cost: $%2", _formattedTotalLiters, _formattedTotalCost]], _player] call CFUNC(targetEvent);

View File

@ -80,7 +80,7 @@ GVAR(MEconomyStore) = createHashMapObject [[
private _newBalance = 0;
if (_bank < _healCost && _cash < _healCost) exitWith {
[CRPC(notifications,recieveNotification), ["danger", "Insufficient Funds", format ["Insufficient funds for %1. Bank: %2, Cash: %3, Required: %4", (name _unit), _bank, _cash, _healCost]], _unit] call CFUNC(targetEvent);
[CRPC(notifications,recieveNotification), ["danger", "Insufficient Funds", format ["Insufficient funds for %1. Bank: $%2, Cash: $%3, Required: $%4", (name _unit), [_bank] call EFUNC(common,formatNumber), [_cash] call EFUNC(common,formatNumber), [_healCost] call EFUNC(common,formatNumber)]], _unit] call CFUNC(targetEvent);
};
if (_bank >= _healCost) then {

View File

@ -4,7 +4,7 @@
* File: fnc_initVAStore.sqf
* Author: IDSolutions
* Date: 2025-12-17
* Last Update: 2026-02-13
* Last Update: 2026-03-27
* Public: No
*
* Description:
@ -28,7 +28,7 @@ GVAR(VArsenalModel) = compileFinal createHashMapObject [[
private _vArsenal = createHashMap;
_vArsenal set ["backpacks", ["B_AssaultPack_rgr"]];
_vArsenal set ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_IG_Guerrilla_6_1", "V_TacVest_oli"]];
_vArsenal set ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_IG_Guerrilla_6_1", "V_TacVest_oli", "ACE_EarPlugs"]];
_vArsenal set ["magazines", ["16Rnd_9x21_Mag", "30Rnd_65x39_caseless_black_mag", "Chemlight_blue", "Chemlight_green", "Chemlight_red", "Chemlight_yellow", "HandGrenade", "SmokeShell", "SmokeShellBlue", "SmokeShellGreen", "SmokeShellOrange", "SmokeShellPurple", "SmokeShellRed", "SmokeShellYellow"]];
_vArsenal set ["weapons", ["arifle_MX_F", "hgun_P07_F"]];

View File

@ -127,7 +127,7 @@ PREP_RECOMPILE_END;
private _index = GVAR(IndexRegistry) get _uid;
private _key = _index get "orgID";
GVAR(OrgStore) call ["save", [GVAR(Registry), "org:update", _key]];
GVAR(OrgStore) call ["saveById", [_key]];
}] call CFUNC(addEventHandler);
[QGVAR(requestRemoveOrg), {

View File

@ -50,6 +50,42 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
if !(_x in _org) then { _org set [_x, _y]; };
} forEach _defaults;
private _assets = _org getOrDefault ["assets", createHashMap];
if !(_assets isEqualType createHashMap) then {
_assets = createHashMap;
};
private _migratedAssets = createHashMap;
{
private _categoryKey = _x;
private _value = _y;
if (_value isEqualType createHashMap) then {
private _categoryMap = createHashMap;
if (_categoryKey find ":" >= 0) then {
private _legacyAsset = +_value;
private _category = toLowerANSI (_legacyAsset getOrDefault ["type", "items"]);
private _className = _legacyAsset getOrDefault ["classname", ""];
if (_className isNotEqualTo "") then {
_categoryMap = +(_migratedAssets getOrDefault [_category, createHashMap]);
_categoryMap set [_className, _legacyAsset];
_migratedAssets set [_category, _categoryMap];
};
} else {
{
if (_y isEqualType createHashMap) then {
_categoryMap set [_x, +_y];
};
} forEach _value;
_migratedAssets set [toLowerANSI _categoryKey, _categoryMap];
};
};
} forEach _assets;
_org set ["assets", _migratedAssets];
_org
}],
["validate", compileFinal {
@ -121,6 +157,13 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["org:create", ["default", _defaultJson]] call EFUNC(extension,extCall);
};
_defaultOrg = GVAR(OrgModel) call ["migrate", [_defaultOrg]];
private _defaultAssets = _self call ["fetch", ["org:assets:get", "default"]];
if !(_defaultAssets isEqualType createHashMap) then { _defaultAssets = createHashMap; };
_defaultOrg set ["assets", _defaultAssets];
private _defaultFleet = _self call ["fetch", ["org:fleet:get", "default"]];
if !(_defaultFleet isEqualType createHashMap) then { _defaultFleet = createHashMap; };
_defaultOrg set ["fleet", _defaultFleet];
GVAR(Registry) set ["default", _defaultOrg];
}],
["verifyMember", compileFinal {
@ -231,12 +274,30 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
private _assetsList = [];
{
private _assetData = _y;
_assetsList pushBack (createHashMapFromArray [
["name", _assetData getOrDefault ["name", "Unknown Asset"]],
["type", _assetData getOrDefault ["type", "items"]],
["quantity", str (_assetData getOrDefault ["quantity", 0])]
]);
private _category = _x;
{
private _assetData = _y;
private _className = _assetData getOrDefault ["classname", ""];
private _displayName = _className;
{
private _cfg = _x >> _className;
if (isClass _cfg) exitWith {
private _resolvedName = getText (_cfg >> "displayName");
if (_resolvedName isNotEqualTo "") then { _displayName = _resolvedName; };
};
} forEach [
configFile >> "CfgWeapons",
configFile >> "CfgMagazines",
configFile >> "CfgVehicles",
configFile >> "CfgGlasses"
];
_assetsList pushBack (createHashMapFromArray [
["name", _displayName],
["type", _assetData getOrDefault ["type", _category]],
["quantity", str (_assetData getOrDefault ["quantity", 0])]
]);
} forEach _y;
} forEach _assetsRaw;
private _fleetList = [];
@ -291,8 +352,112 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["chargeCheckout", compileFinal {
GVAR(OrgTreasuryService) call ["chargeCheckout", _this]
}],
["saveById", compileFinal {
params [["_orgID", "", [""]]];
if (_orgID isEqualTo "") exitWith { createHashMap };
private _org = GVAR(Registry) getOrDefault [_orgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = _self call ["loadById", [_orgID]];
};
if (_org isEqualTo createHashMap) exitWith { createHashMap };
private _coreOrg = createHashMapFromArray [
["id", _org getOrDefault ["id", _orgID]],
["owner", _org getOrDefault ["owner", ""]],
["name", _org getOrDefault ["name", ""]],
["funds", _org getOrDefault ["funds", 0]],
["reputation", _org getOrDefault ["reputation", 0]],
["credit_lines", _org getOrDefault ["credit_lines", createHashMap]]
];
private _coreJson = _self call ["toJSON", [_coreOrg]];
["org:update", [_orgID, _coreJson]] call EFUNC(extension,extCall);
private _assets = _org getOrDefault ["assets", createHashMap];
private _assetsJson = _self call ["toJSON", [_assets]];
["org:assets:update", [_orgID, _assetsJson]] call EFUNC(extension,extCall);
private _fleet = _org getOrDefault ["fleet", createHashMap];
private _fleetJson = _self call ["toJSON", [_fleet]];
["org:fleet:update", [_orgID, _fleetJson]] call EFUNC(extension,extCall);
_org
}],
["addAssets", compileFinal {
params [["_requesterUid", "", [""]], ["_assets", [], [[]]], ["_commit", false, [false]], ["_orgID", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to update organization assets."],
["patch", createHashMap],
["memberUids", []]
];
if (_assets isEqualTo []) exitWith {
_result set ["success", true];
_result set ["message", ""];
_result
};
private _resolvedOrgID = _orgID;
if (_resolvedOrgID isEqualTo "") then {
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
_resolvedOrgID = _requesterActor getOrDefault ["organization", "default"];
};
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
private _org = GVAR(Registry) getOrDefault [_resolvedOrgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = _self call ["loadById", [_resolvedOrgID]];
};
if (_org isEqualTo createHashMap) exitWith {
_result set ["message", "Organization data is unavailable for asset updates."];
_result
};
private _assetMap = +(_org getOrDefault ["assets", createHashMap]);
{
private _className = _x getOrDefault ["classname", ""];
private _category = toLowerANSI (_x getOrDefault ["category", "items"]);
private _quantity = floor ((_x getOrDefault ["quantity", 0]) max 0);
if (_className isEqualTo "" || { _quantity <= 0 }) then { continue; };
private _categoryMap = +(_assetMap getOrDefault [_category, createHashMap]);
private _assetEntry = +(_categoryMap getOrDefault [_className, createHashMap]);
private _existingQuantity = _assetEntry getOrDefault ["quantity", 0];
_categoryMap set [_className, createHashMapFromArray [
["classname", _className],
["type", _category],
["quantity", (_existingQuantity + _quantity)]
]];
_assetMap set [_category, _categoryMap];
} forEach _assets;
private _patch = _self call ["mset", [
GVAR(Registry),
"org:update",
_resolvedOrgID,
createHashMapFromArray [["assets", _assetMap]],
false
]];
if (_commit) then {
private _assetJson = _self call ["toJSON", [_assetMap]];
["org:assets:update", [_resolvedOrgID, _assetJson]] call EFUNC(extension,extCall);
};
_result set ["success", true];
_result set ["message", ""];
_result set ["patch", _patch];
_result set ["memberUids", GVAR(OrgTreasuryService) call ["resolveOrgMemberUids", [_org, _requesterUid]]];
_result
}],
["addFleetVehicles", compileFinal {
params [["_requesterUid", "", [""]], ["_vehicles", [], [[]]], ["_commit", false, [false]]];
params [["_requesterUid", "", [""]], ["_vehicles", [], [[]]], ["_commit", false, [false]], ["_orgID", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
@ -301,17 +466,23 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["memberUids", []]
];
if (_requesterUid isEqualTo "" || { _vehicles isEqualTo [] }) exitWith {
if (_vehicles isEqualTo []) exitWith {
_result set ["success", true];
_result set ["message", ""];
_result
};
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
private _orgID = _requesterActor getOrDefault ["organization", "default"];
if (_orgID isEqualTo "") then { _orgID = "default"; };
private _resolvedOrgID = _orgID;
if (_resolvedOrgID isEqualTo "") then {
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
_resolvedOrgID = _requesterActor getOrDefault ["organization", "default"];
};
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
private _org = GVAR(Registry) getOrDefault [_orgID, createHashMap];
private _org = GVAR(Registry) getOrDefault [_resolvedOrgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = _self call ["loadById", [_resolvedOrgID]];
};
if (_org isEqualTo createHashMap) exitWith {
_result set ["message", "Organization data is unavailable for fleet updates."];
_result
@ -347,9 +518,17 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
_fleetIndex = _fleetIndex + 1;
} forEach _vehicles;
private _patch = createHashMapFromArray [["fleet", _fleet]];
private _patch = _self call ["mset", [
GVAR(Registry),
"org:update",
_resolvedOrgID,
createHashMapFromArray [["fleet", _fleet]],
false
]];
if (_commit) then {
_patch = _self call ["mset", [GVAR(Registry), "org:update", _orgID, _patch, false]];
private _fleetJson = _self call ["toJSON", [_fleet]];
["org:fleet:update", [_resolvedOrgID, _fleetJson]] call EFUNC(extension,extCall);
};
_result set ["success", true];
@ -373,6 +552,16 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
if (_org isEqualTo createHashMap) exitWith { _org };
_org = GVAR(OrgModel) call ["migrate", [_org]];
private _assets = _self call ["fetch", ["org:assets:get", _orgID]];
if !(_assets isEqualType createHashMap) then {
_assets = createHashMap;
};
_org set ["assets", _assets];
private _fleet = _self call ["fetch", ["org:fleet:get", _orgID]];
if !(_fleet isEqualType createHashMap) then {
_fleet = createHashMap;
};
_org set ["fleet", _fleet];
private _memberRows = _self call ["fetch", ["org:members:get", _orgID]];
if !(_memberRows isEqualType []) then {
@ -438,8 +627,6 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
["funds", 0],
["reputation", 0],
["credit_lines", createHashMap],
["assets", createHashMap],
["fleet", createHashMap],
["members", createHashMap]
];

View File

@ -17,7 +17,9 @@ GVAR(OrgTreasuryServiceBase) = compileFinal createHashMapFromArray [
params [["_org", createHashMap, [createHashMap]], ["_requesterUid", "", [""]]];
private _memberUids = keys (_org getOrDefault ["members", createHashMap]);
if !(_requesterUid in _memberUids) then { _memberUids pushBack _requesterUid; };
if (_requesterUid isNotEqualTo "" && { !(_requesterUid in _memberUids) }) then {
_memberUids pushBack _requesterUid;
};
_memberUids
}],
@ -88,7 +90,7 @@ GVAR(OrgTreasuryServiceBase) = compileFinal createHashMapFromArray [
private _memberUids = _self call ["resolveOrgMemberUids", [_org, _requesterUid]];
_result set ["success", true];
_result set ["message", format ["Credit line of $%1 assigned to %2.", [_amount] call BIS_fnc_numberText, _resolvedMemberName]];
_result set ["message", format ["Credit line of $%1 assigned to %2.", [_amount] call EFUNC(common,formatNumber), _resolvedMemberName]];
_result set ["patch", _patch];
_result set ["memberUids", _memberUids];
_result

View File

@ -20,7 +20,7 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
["formatCurrency", compileFinal {
params [["_amount", 0, [0]]];
format ["$%1", [_amount max 0] call BIS_fnc_numberText]
format ["$%1", [_amount max 0] call EFUNC(common,formatNumber)]
}],
["isVisibleConfig", compileFinal {
params [["_cfg", configNull, [configNull]]];

View File

@ -158,7 +158,7 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
["formatCurrency", compileFinal {
params [["_amount", 0, [0]]];
format ["$%1", [_amount max 0] call BIS_fnc_numberText]
format ["$%1", [_amount max 0] call EFUNC(common,formatNumber)]
}],
["applyPaymentPatch", compileFinal {
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_paymentMethod", "cash", [""]], ["_total", 0, [0]], ["_commit", false, [false]]];

View File

@ -0,0 +1 @@
forge\forge_server\addons\task

View File

@ -0,0 +1,17 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
};
};

View File

@ -0,0 +1,6 @@
class CfgFactionClasses {
class NO_CATEGORY;
class FORGE_Modules: NO_CATEGORY {
displayName = "FORGE";
};
};

View File

@ -0,0 +1,269 @@
// TODO: Move to mission template and provide documentation
class CfgMissions {
// Global settings
maxConcurrentMissions = 3;
missionInterval = 300; // 5 minutes between mission generation
// Mission type weights
class MissionWeights {
attack = 0.2;
defend = 0.2;
hostage = 0.2;
hvt = 0.15;
defuse = 0.15;
delivery = 0.1;
};
// Mission locations
class Locations {
class CityOne {
position[] = {1000, 1000, 0};
type = "city";
radius = 300;
suitable[] = {"attack", "defend", "hostage"};
};
class MilitaryBase {
position[] = {2000, 2000, 0};
type = "military";
radius = 500;
suitable[] = {"hvt", "defend", "attack"};
};
class Industrial {
position[] = {3000, 3000, 0};
type = "industrial";
radius = 200;
suitable[] = {"delivery", "defuse"};
};
};
// AI Groups configuration
class AIGroups {
class Infantry {
side = "EAST";
class Units {
class Unit0 {
vehicle = "O_Soldier_TL_F";
rank = "SERGEANT";
position[] = {0, 0, 0};
};
class Unit1 {
vehicle = "O_Soldier_AR_F";
rank = "CORPORAL";
position[] = {5, -5, 0};
};
class Unit2 {
vehicle = "O_Soldier_LAT_F";
rank = "PRIVATE";
position[] = {-5, -5, 0};
};
};
suitable[] = {"attack", "defend", "hostage"};
};
class Assault {
side = "EAST";
class Units {
class Unit0 {
vehicle = "O_Soldier_SL_F";
rank = "SERGEANT";
position[] = {0, 0, 0};
};
class Unit1 {
vehicle = "O_Soldier_GL_F";
rank = "CORPORAL";
position[] = {4, -3, 0};
};
class Unit2 {
vehicle = "O_Soldier_AR_F";
rank = "CORPORAL";
position[] = {-4, -3, 0};
};
class Unit3 {
vehicle = "O_medic_F";
rank = "PRIVATE";
position[] = {7, -6, 0};
};
};
suitable[] = {"attack", "defend"};
};
class MotorizedPatrol {
side = "EAST";
class Units {
class Unit0 {
vehicle = "O_Soldier_TL_F";
rank = "SERGEANT";
position[] = {0, 0, 0};
};
class Unit1 {
vehicle = "O_Soldier_LAT_F";
rank = "CORPORAL";
position[] = {5, -4, 0};
};
class Unit2 {
vehicle = "O_Soldier_F";
rank = "PRIVATE";
position[] = {-5, -4, 0};
};
class Unit3 {
vehicle = "O_Soldier_A_F";
rank = "PRIVATE";
position[] = {8, -7, 0};
};
};
suitable[] = {"attack", "defend"};
};
class SpecOps {
side = "EAST";
class Units {
class Unit0 {
vehicle = "O_recon_TL_F";
rank = "SERGEANT";
position[] = {0, 0, 0};
};
class Unit1 {
vehicle = "O_recon_M_F";
rank = "CORPORAL";
position[] = {5, -5, 0};
};
};
suitable[] = {"hvt", "hostage"};
};
class ReconRaid {
side = "EAST";
class Units {
class Unit0 {
vehicle = "O_recon_TL_F";
rank = "SERGEANT";
position[] = {0, 0, 0};
};
class Unit1 {
vehicle = "O_recon_M_F";
rank = "CORPORAL";
position[] = {4, -4, 0};
};
class Unit2 {
vehicle = "O_recon_LAT_F";
rank = "CORPORAL";
position[] = {-4, -4, 0};
};
class Unit3 {
vehicle = "O_recon_medic_F";
rank = "PRIVATE";
position[] = {7, -7, 0};
};
};
suitable[] = {"attack", "hvt", "hostage"};
};
};
// TODO: Continue to refine mission types and their specific settings
// Mission type specific settings
class MissionTypes {
class Attack {
minUnits = 4;
maxUnits = 8;
class Rewards {
money[] = {25000, 60000};
reputation[] = {6, 14};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-8, -3};
timeLimit[] = {900, 1800}; // 15-30 minutes
};
class Defend {
minWaves = 3;
maxWaves = 8;
unitsPerWave[] = {4, 8};
waveCooldown = 300;
class Rewards {
money[] = {40000, 90000};
reputation[] = {8, 18};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-12, -4};
timeLimit[] = {1800, 3600}; // 30-60 minutes
};
class Hostage {
class Hostages {
civilian[] = {"C_man_1", "C_man_polo_1_F"};
military[] = {"B_Pilot_F", "B_officer_F"};
};
class Rewards {
money[] = {60000, 140000};
reputation[] = {12, 25};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-16, -6};
timeLimit[] = {600, 900}; // 10-15 minutes
};
class HVT {
class Targets {
officer[] = {"O_officer_F"};
sniper[] = {"O_sniper_F"};
};
escorts = 4;
class Rewards {
money[] = {50000, 120000};
reputation[] = {10, 22};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-14, -5};
timeLimit[] = {900, 1800}; // 15-30 minutes
};
class Defuse {
class Devices {
small[] = {"DemoCharge_Remote_Mag"};
large[] = {"SatchelCharge_Remote_Mag"};
};
maxDevices = 3;
class Rewards {
money[] = {20000, 50000};
reputation[] = {5, 12};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-9, -3};
timeLimit[] = {600, 900}; // 10-15 minutes
};
class Delivery {
class Cargo {
supplies[] = {"Land_CargoBox_V1_F"};
vehicles[] = {"B_MRAP_01_F", "B_Truck_01_transport_F"};
};
class Rewards {
money[] = {10000, 30000};
reputation[] = {3, 8};
equipment[] = {{"ItemGPS", 0.5}, {"ItemCompass", 0.3}};
supplies[] = {{"FirstAidKit", 0.2}, {"Medikit", 0.1}};
weapons[] = {{"arifle_MX_F", 0.3}, {"arifle_Katiba_F", 0.2}};
vehicles[] = {{"B_MRAP_01_F", 0.1}, {"B_APC_Wheeled_01_cannon_F", 0.05}};
special[] = {{"B_UAV_01_F", 0.05}, {"B_Heli_Light_01_F", 0.02}};
};
penalty[] = {-6, -2};
timeLimit[] = {900, 1800}; // 15-30 minutes
};
};
};

View File

@ -0,0 +1,782 @@
class CfgVehicles {
class Logic;
class Module_F: Logic {
class AttributesBase {
class Edit;
class Combo;
};
class ModuleDescription {};
};
class FORGE_Module_Attack: Module_F {
scope = 2;
displayName = "Attack Task";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(attackModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 1;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {
class TaskID: Edit {
property = "FORGE_Module_Attack_TaskID";
displayName = "Task ID";
tooltip = "Unique identifier for this task";
typeName = "STRING";
// defaultValue = """";
};
class LimitFail: Edit {
property = "FORGE_Module_Attack_LimitFail";
displayName = "Fail Limit";
tooltip = "Number of targets that escape to fail the task";
typeName = "NUMBER";
defaultValue = -1;
};
class LimitSuccess: Edit {
property = "FORGE_Module_Attack_LimitSuccess";
displayName = "Success Limit";
tooltip = "Number of targets that need to be eliminated to succeed the task";
typeName = "NUMBER";
defaultValue = -1;
};
class CompanyFunds: Edit {
property = "FORGE_Module_Attack_CompanyFunds";
displayName = "Reward Funds";
tooltip = "Amount of funds awarded on success";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingFail: Edit {
property = "FORGE_Module_Attack_RatingFail";
displayName = "Rating Loss";
tooltip = "Amount of rating lost on failure";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingSuccess: Edit {
property = "FORGE_Module_Attack_RatingSuccess";
displayName = "Rating Gain";
tooltip = "Amount of rating gained on success";
typeName = "NUMBER";
defaultValue = 0;
};
class EndSuccess: Combo {
property = "FORGE_Module_Attack_EndSuccess";
displayName = "End on Success";
tooltip = "End mission when task is completed successfully";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndSuccess { name = "Enable"; value = 1; };
class DisableEndSuccess { name = "Disable"; value = 0; };
};
};
class EndFail: Combo {
property = "FORGE_Module_Attack_EndFail";
displayName = "End on Failure";
tooltip = "End mission when task fails";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndFail { name = "Enable"; value = 1; };
class DisableEndFail { name = "Disable"; value = 0; };
};
};
class TimeLimit: Edit {
property = "FORGE_Module_Attack_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before targets escape (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
};
class ModuleDescription: ModuleDescription {
description = "Creates an attack task with configurable parameters";
sync[] = { "Anything" };
class Anything {
description[] = {
"Attack task module",
"Sync with units/vehicles to mark as targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Explosives: Module_F {
scope = 2;
displayName = "Explosive Entities";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(explosivesModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 0;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {};
class ModuleDescription: ModuleDescription {
description = "Module for explosive entities that need to be defused";
sync[] = { "Anything" };
class Anything {
description[] = {
"Explosive entities module",
"Sync with objects to mark as explosives",
"Those objects will be processed as defusal targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Hostages: Module_F {
scope = 2;
displayName = "Hostage Entities";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(hostagesModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 0;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {};
class ModuleDescription: ModuleDescription {
description = "Module for hostage entities that need to be rescued";
sync[] = { "Anything" };
class Anything {
description[] = {
"Hostage entities module",
"Sync with units to mark as hostages",
"Those objects will be processed as rescue targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Shooters: Module_F {
scope = 2;
displayName = "Shooter Entities";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(shootersModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 0;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {};
class ModuleDescription: ModuleDescription {
description = "Module for shooter entities that need to be eliminated";
sync[] = { "AnyBrain" };
class AnyBrain {
description[] = {
"Shooter entities module",
"Sync with units to mark as shooters",
"Those objects will be processed as elimination targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Protected: Module_F {
scope = 2;
displayName = "Protected Entities";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(protectedModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 0;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {};
class ModuleDescription: ModuleDescription {
description = "Module for protected entities that need to be protected";
sync[] = { "Anything" };
class Anything {
description[] = {
"Protected entities module",
"Sync with objects to mark as protected entities",
"Those objects will be processed as protected targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Defuse: Module_F {
scope = 2;
displayName = "Defuse Task";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(defuseModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 1;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {
class TaskID: Edit {
property = "FORGE_Module_Defuse_TaskID";
displayName = "Task ID";
tooltip = "Unique identifier for this task";
typeName = "STRING";
// defaultValue = """";
};
class LimitFail: Edit {
property = "FORGE_Module_Defuse_LimitFail";
displayName = "Fail Limit";
tooltip = "Number of protected entities destroyed to fail the task";
typeName = "NUMBER";
defaultValue = -1;
};
class LimitSuccess: Edit {
property = "FORGE_Module_Defuse_LimitSuccess";
displayName = "Success Limit";
tooltip = "Number of entities that need to be defused to complete the task";
typeName = "NUMBER";
defaultValue = -1;
};
class CompanyFunds: Edit {
property = "FORGE_Module_Defuse_CompanyFunds";
displayName = "Reward Funds";
tooltip = "Amount of funds awarded on success";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingFail: Edit {
property = "FORGE_Module_Defuse_RatingFail";
displayName = "Rating Loss";
tooltip = "Amount of rating lost on failure";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingSuccess: Edit {
property = "FORGE_Module_Defuse_RatingSuccess";
displayName = "Rating Gain";
tooltip = "Amount of rating gained on success";
typeName = "NUMBER";
defaultValue = 0;
};
class EndSuccess: Combo {
property = "FORGE_Module_Defuse_EndSuccess";
displayName = "End on Success";
tooltip = "End mission when task is completed successfully";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndSuccess { name = "Enable"; value = 1; };
class DisableEnSuccess { name = "Disable"; value = 0; };
};
};
class EndFail: Combo {
property = "FORGE_Module_Defuse_EndFail";
displayName = "End on Failure";
tooltip = "End mission when task fails";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndFail { name = "Enable"; value = 1; };
class DisableEndFail { name = "Disable"; value = 0; };
};
};
class TimeLimit: Edit {
property = "FORGE_Module_Defuse_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before detenation (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
};
class ModuleDescription: ModuleDescription {
description = "Creates a defuse task with configurable parameters";
sync[] = { "Anything" };
class Anything {
description[] = {
"Defuse task module",
"Sync with entities to mark as explosives and protected entities",
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Destroy: Module_F {
scope = 2;
displayName = "Destroy Task";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(destroyModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 1;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {
class TaskID: Edit {
property = "FORGE_Module_Destroy_TaskID";
displayName = "Task ID";
tooltip = "Unique identifier for this task";
typeName = "STRING";
// defaultValue = """";
};
class LimitFail: Edit {
property = "FORGE_Module_Destroy_LimitFail";
displayName = "Fail Limit";
tooltip = "Number of targets that can escape before failing";
typeName = "NUMBER";
defaultValue = -1;
};
class LimitSuccess: Edit {
property = "FORGE_Module_Destroy_LimitSuccess";
displayName = "Success Limit";
tooltip = "Number of targets that need to be destroyed";
typeName = "NUMBER";
defaultValue = -1;
};
class CompanyFunds: Edit {
property = "FORGE_Module_Destroy_CompanyFunds";
displayName = "Reward Funds";
tooltip = "Amount of funds awarded on success";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingFail: Edit {
property = "FORGE_Module_Destroy_RatingFail";
displayName = "Rating Loss";
tooltip = "Amount of rating lost on failure";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingSuccess: Edit {
property = "FORGE_Module_Destroy_RatingSuccess";
displayName = "Rating Gain";
tooltip = "Amount of rating gained on success";
typeName = "NUMBER";
defaultValue = 0;
};
class EndSuccess: Combo {
property = "FORGE_Module_Destroy_EndSuccess";
displayName = "End on Success";
tooltip = "End mission when task is completed successfully";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndSuccess { name = "Enable"; value = 1; };
class DisableEndSuccess { name = "Disable"; value = 0; };
};
};
class EndFail: Combo {
property = "FORGE_Module_Destroy_EndFail";
displayName = "End on Failure";
tooltip = "End mission when task fails";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndFail { name = "Enable"; value = 1; };
class DisableEndFail { name = "Disable"; value = 0; };
};
};
class TimeLimit: Edit {
property = "FORGE_Module_Destroy_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before targets escape (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
};
class ModuleDescription: ModuleDescription {
description = "Creates a destroy task with configurable parameters";
sync[] = { "Anything" };
class Anything {
description[] = {
"Destroy task module",
"Sync with units and/or vehicles to mark as targets"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_Hostage: Module_F {
scope = 2;
displayName = "Hostage Task";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(hostageModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 1;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {
class TaskID: Edit {
property = "FORGE_Module_Hostage_TaskID";
displayName = "Task ID";
tooltip = "Unique identifier for this task";
typeName = "STRING";
// defaultValue = """";
};
class LimitFail: Edit {
property = "FORGE_Module_Hostage_LimitFail";
displayName = "Fail Limit";
tooltip = "Number of hostages KIA before failing";
typeName = "NUMBER";
defaultValue = -1;
};
class LimitSuccess: Edit {
property = "FORGE_Module_Hostage_LimitSuccess";
displayName = "Success Limit";
tooltip = "Number of hostages rescued before succeeding";
typeName = "NUMBER";
defaultValue = -1;
};
class ExtZone: Edit {
property = "FORGE_Module_Hostage_ExtZone";
displayName = "Extraction Zone";
tooltip = "Unique marker name for the extraction zone";
typeName = "STRING";
// defaultValue = """";
};
class CompanyFunds: Edit {
property = "FORGE_Module_Hostage_CompanyFunds";
displayName = "Reward Funds";
tooltip = "Amount of funds awarded on success";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingFail: Edit {
property = "FORGE_Module_Hostage_RatingFail";
displayName = "Rating Loss";
tooltip = "Amount of rating lost on failure";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingSuccess: Edit {
property = "FORGE_Module_Hostage_RatingSuccess";
displayName = "Rating Gain";
tooltip = "Amount of rating gained on success";
typeName = "NUMBER";
defaultValue = 0;
};
class CBRN: Combo {
property = "FORGE_Module_Hostage_CBRN";
displayName = "CBRN Attack";
tooltip = "CBRN Attack instead of execution";
typeName = "BOOL";
defaultValue = 0;
class Values {
class TrueCBRN { name = "True"; value = 1; };
class FalseCBRN { name = "False"; value = 0; };
};
};
class Execution: Combo {
property = "FORGE_Module_Hostage_Execution";
displayName = "Execution";
tooltip = "Execution instead of CBRN Attack";
typeName = "BOOL";
defaultValue = 0;
class Values {
class TrueExecution { name = "True"; value = 1; };
class FalseExecution { name = "False"; value = 0; };
};
};
class EndSuccess: Combo {
property = "FORGE_Module_Hostage_EndSuccess";
displayName = "End on Success";
tooltip = "End mission when task is completed successfully";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndSuccess { name = "Enable"; value = 1; };
class DisableEndSuccess { name = "Disable"; value = 0; };
};
};
class EndFail: Combo {
property = "FORGE_Module_Hostage_EndFail";
displayName = "End on Failure";
tooltip = "End mission when task fails";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndFail { name = "Enable"; value = 1; };
class DisableEndFail { name = "Disable"; value = 0; };
};
};
class TimeLimit: Edit {
property = "FORGE_Module_Hostage_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before HVTs escape (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
class CBRNZone: Edit {
property = "FORGE_Module_Hostage_CBRNZone";
displayName = "CBRN Zone";
tooltip = "Unique marker name for the CBRN zone";
typeName = "STRING";
// defaultValue = """";
};
};
class ModuleDescription: ModuleDescription {
description = "Creates a Hostage task with configurable parameters";
sync[] = { "Anything" };
class Anything {
description[] = {
"Hostage task module",
"Sync with hostage and shooter module to register the entities to the task"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
class FORGE_Module_HVT: Module_F {
scope = 2;
displayName = "HVT Task";
// icon = "\a3\ui_f\data\IGUI\Cfg\simpleTasks\types\default_ca.paa";
category = "FORGE_Modules";
function = QFUNC(hvtModule);
functionPriority = 1;
isGlobal = 1;
isTriggerActivated = 1;
isDisposable = 1;
is3DEN = 0;
canSetArea = 0;
canSetAreaShape = 0;
canSetAreaHeight = 0;
class AttributeValues {};
class Attributes: AttributesBase {
class TaskID: Edit {
property = "FORGE_Module_HVT_TaskID";
displayName = "Task ID";
tooltip = "Unique identifier for this task";
typeName = "STRING";
// defaultValue = """";
};
class LimitFail: Edit {
property = "FORGE_Module_HVT_LimitFail";
displayName = "Fail Limit";
tooltip = "Number of hvts that can escape or KIA before failing";
typeName = "NUMBER";
defaultValue = -1;
};
class LimitSuccess: Edit {
property = "FORGE_Module_HVT_LimitSuccess";
displayName = "Success Limit";
tooltip = "Number of hvts that need to be captured or KIA";
typeName = "NUMBER";
defaultValue = -1;
};
class ExtZone: Edit {
property = "FORGE_Module_HVT_ExtZone";
displayName = "Extraction Zone";
tooltip = "Unique marker name for the extraction zone";
typeName = "STRING";
// defaultValue = """";
};
class CompanyFunds: Edit {
property = "FORGE_Module_HVT_CompanyFunds";
displayName = "Reward Funds";
tooltip = "Amount of funds awarded on success";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingFail: Edit {
property = "FORGE_Module_HVT_RatingFail";
displayName = "Rating Loss";
tooltip = "Amount of rating lost on failure";
typeName = "NUMBER";
defaultValue = 0;
};
class RatingSuccess: Edit {
property = "FORGE_Module_HVT_RatingSuccess";
displayName = "Rating Gain";
tooltip = "Amount of rating gained on success";
typeName = "NUMBER";
defaultValue = 0;
};
class CaptureHVT: Combo {
property = "FORGE_Module_HVT_CaptureHVT";
displayName = "Capture HVT";
tooltip = "Capture HVT instead of eliminating";
typeName = "BOOL";
defaultValue = 1;
class Values {
class TrueCapture { name = "True"; value = 1; };
class FalseCapture { name = "False"; value = 0; };
};
};
class EliminateHVT: Combo {
property = "FORGE_Module_HVT_EliminateHVT";
displayName = "Eliminate HVT";
tooltip = "Eliminate HVT instead of capturing";
typeName = "BOOL";
defaultValue = 0;
class Values {
class TrueEliminate { name = "True"; value = 1; };
class FalseEliminate { name = "False"; value = 0; };
};
};
class EndSuccess: Combo {
property = "FORGE_Module_HVT_EndSuccess";
displayName = "End on Success";
tooltip = "End mission when task is completed successfully";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndSuccess { name = "Enable"; value = 1; };
class DisableEndSuccess { name = "Disable"; value = 0; };
};
};
class EndFail: Combo {
property = "FORGE_Module_HVT_EndFail";
displayName = "End on Failure";
tooltip = "End mission when task fails";
typeName = "BOOL";
defaultValue = 0;
class Values {
class EnableEndFail { name = "Enable"; value = 1; };
class DisableEndFail { name = "Disable"; value = 0; };
};
};
class TimeLimit: Edit {
property = "FORGE_Module_HVT_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before HVTs escape (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
};
class ModuleDescription: ModuleDescription {
description = "Creates a HVT task with configurable parameters";
sync[] = { "Anything" };
class Anything {
description[] = {
"HVT task module",
"Sync with units to mark as HVTs"
};
position = 1;
direction = 1;
optional = 1;
duplicate = 1;
};
};
};
};

View File

@ -0,0 +1,104 @@
# Forge Task Module
## Overview
The task addon is a server-owned mission/task system for Forge. It manages task execution, task-owned state, participant tracking, contribution-based player earnings, and org-owned rewards.
## Responsibilities
- spawn and monitor task flows on the server
- track per-task entities through `TaskStore`
- track task participants and engine-rating contribution
- award player earnings through the bank module
- award org funds, reputation, assets, and fleet rewards
- notify task participants and sync org updates to online members
## Dependencies
- `forge_server_common`
- `forge_server_actor`
- `forge_server_bank`
- `forge_server_org`
- `forge_client_notifications`
## Main Components
### Task Flows
- `fnc_attack.sqf`
- `fnc_defend.sqf`
- `fnc_defuse.sqf`
- `fnc_delivery.sqf`
- `fnc_destroy.sqf`
- `fnc_hostage.sqf`
- `fnc_hvt.sqf`
### TaskStore
`fnc_initTaskStore.sqf` initializes `TaskStore`, which owns:
- task ownership bindings
- participant snapshots
- defuse progress
- per-task entity registries for cargo, hostages, HVTs, IEDs, protected entities, shooters, and targets
### Reward Handling
`fnc_handleTaskRewards.sqf` applies org-owned rewards:
- `funds` -> org funds
- `equipment`, `supplies`, `weapons`, `special` -> org assets
- `vehicles` -> org fleet
Player `earnings` and org `reputation` from task outcomes are distributed separately through `TaskStore.applyRatingOutcome` using Arma engine `rating` deltas.
## Task Ownership
Tasks are bound to an owner org when they are started through `fnc_handler.sqf`.
- if a requester UID is provided, the task is owned by that requester's org
- if no requester UID is available, the task is bound to the `default` org
Org rewards always go to the bound owner org. Player earnings still use per-player contribution.
## Usage
### Start Through The Handler
Use the handler when you want reputation gating and task ownership binding.
```sqf
["attack", ["task_attack_1", 1, 2, 1500000, -75, 375, false, false], 250, getPlayerUID player] call forge_server_task_fnc_handler;
["delivery", ["task_delivery_1", 1, 3, "delivery_zone", 250000, -75, 300, false, false, 900], 0, getPlayerUID player] call forge_server_task_fnc_handler;
```
Arguments:
- `0`: task type
- `1`: task-specific argument array
- `2`: minimum org reputation required to start the task
- `3`: requester UID used for ownership binding
### Start Task Functions Directly
Direct task calls still work, but they do not provide a requester UID. That means task ownership falls back to the `default` org.
Use direct starts only when that behavior is intended, such as:
- mission-authored tasks
- editor-placed tasks
- server-owned/random tasks
If you want the accepting player's org to own the task rewards, use `fnc_handler.sqf` instead.
```sqf
["task_attack_1", 1, 2, 1500000, -75, 375, false, false] spawn forge_server_task_fnc_attack;
["task_hostage_1", 1, 2, "extract_marker", 1500000, -75, 500, [false, true], false, false] spawn forge_server_task_fnc_hostage;
```
## Event Hooks
- `XEH_preInit.sqf`
- compiles functions
- initializes `TaskStore`
- `XEH_postInit.sqf`
- registers the ACE defuse event hook
- starts the attack-only mission manager on the server
## Notes
- the dynamic mission manager in `fnc_missionManager.sqf` is now limited to attack missions only
- it starts server-owned tasks through `fnc_handler.sqf` and binds them to the `default` org
- task lifecycle for the mission manager is tracked through `TaskStore` status entries
- task rewards are org-owned, not player-owned
- participant notifications are sent through the notifications module, not through local server UI
## Authors
- J. Schmidt
- Creedcoder
- IDSolutions

View File

@ -0,0 +1,31 @@
PREP(attack);
PREP(attackModule);
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);
PREP(makeIED);
PREP(makeObject);
PREP(makeShooter);
PREP(makeTarget);
PREP(missionManager);
PREP(initTaskStore);
PREP(protectedModule);
PREP(shootersModule);
PREP(spawnEnemyWave);

View File

@ -0,0 +1,16 @@
#include "script_component.hpp"
if (isServer) then { [] call FUNC(missionManager); };
["ace_explosives_defuse", {
private _taskID = "";
{
if (_x isEqualType objNull && { !isNull _x }) then {
_taskID = _x getVariable ["assignedTask", ""];
if (_taskID isNotEqualTo "") exitWith {};
};
} forEach _this;
if (_taskID isEqualTo "") exitWith {};
GVAR(TaskStore) call ["incrementDefuseCount", [_taskID]];
}] call CFUNC(addEventHandler);

View File

@ -0,0 +1,35 @@
#include "script_component.hpp"
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;
call FUNC(initTaskStore);
[QGVAR(requestTaskCatalog), {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith {
["WARNING", "Task catalog request received with empty UID."] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
if (_player isEqualTo objNull) exitWith {};
[CRPC(cad,responseTaskCatalog), [GVAR(TaskStore) call ["getActiveTaskCatalog", []]], _player] call CFUNC(targetEvent);
}] call CFUNC(addEventHandler);
[QGVAR(requestAcceptTask), {
params [["_uid", "", [""]], ["_taskID", "", [""]]];
if (_uid isEqualTo "" || { _taskID isEqualTo "" }) exitWith {
["WARNING", "Invalid task accept request payload."] call EFUNC(common,log);
};
private _player = [_uid] call EFUNC(common,getPlayer);
if (_player isEqualTo objNull) exitWith {};
private _result = GVAR(TaskStore) call ["acceptTask", [_taskID, _uid]];
[CRPC(cad,responseTaskAccept), [_result], _player] call CFUNC(targetEvent);
[CRPC(cad,responseTaskCatalog), [GVAR(TaskStore) call ["getActiveTaskCatalog", []]], _player] call CFUNC(targetEvent);
}] call CFUNC(addEventHandler);

View File

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

View File

@ -0,0 +1,23 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
author = AUTHOR;
authors[] = {"J.Schmidt"};
url = ECSTRING(main,url);
name = COMPONENT_NAME;
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {
"forge_server_main",
"forge_server_common"
};
units[] = {};
weapons[] = {};
VERSION_CONFIG;
};
};
#include "CfgEventHandlers.hpp"
#include "CfgFactionClasses.hpp"
#include "CfgVehicles.hpp"
#include "CfgMissions.hpp"

View File

@ -0,0 +1,116 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers an attack task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of targets escaped to fail the task <NUMBER>
* 2: Amount of targets eliminated to complete the task <NUMBER>
* 3: Amount of funds the company recieves if the task is successful <NUMBER> (default: 0)
* 4: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 5: Amount of rating the company and player recieve if the task is successful <NUMBER> (default: 0)
* 6: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 7: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 8: Amount of time before target(s) escape <NUMBER> (default: -1)
* 9: Equipment rewards <ARRAY> (default: [])
* 10: Supply rewards <ARRAY> (default: [])
* 11: Weapon rewards <ARRAY> (default: [])
* 12: Vehicle rewards <ARRAY> (default: [])
* 13: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["task_name", 1, 2, 1500000, -75, 375, false, false] spawn forge_server_task_fnc_attack;
* ["task_name", 1, 2, 1500000, -75, 375, false, false, 45] spawn forge_server_task_fnc_attack;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_limitFail", -1, [0]],
["_limitSuccess", -1, [0]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _result = 0;
private _targets = [];
waitUntil {
sleep 1;
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
count _targets > 0
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
private _targetsKilled = ({ !alive _x } count _targets);
if (_time isNotEqualTo -1) then {
private _timeExpired = (floor time - _startTime >= _time);
if (_targetsKilled < _limitSuccess && _timeExpired) then { _result = 1; };
(_result == 1) or (_targetsKilled >= _limitSuccess)
} else {
(_targetsKilled >= _limitSuccess)
};
};
if (_result == 1) then {
{ deleteVehicle _x } forEach _targets;
[_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _targets;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,51 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the attack module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_attackModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};
private _taskID = _logic getVariable ["TaskID", ""];
private _limitFail = _logic getVariable ["LimitFail", -1];
private _limitSuccess = _logic getVariable ["LimitSuccess", -1];
private _companyFunds = _logic getVariable ["CompanyFunds", 0];
private _ratingFail = _logic getVariable ["RatingFail", 0];
private _ratingSuccess = _logic getVariable ["RatingSuccess", 0];
private _endSuccess = _logic getVariable ["EndSuccess", false];
private _endFail = _logic getVariable ["EndFail", false];
private _timeLimit = _logic getVariable ["TimeLimit", 0];
["INFO", format ["Attack Module Parameters: TaskID: %1, LimitFail: %2, LimitSuccess: %3, Funds: %4, RatingFail: %5, RatingSuccess: %6, EndSuccess: %7, EndFail: %8, Time: %9", _taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _timeLimit]] call EFUNC(common,log);
private _syncedEntities = synchronizedObjects _logic;
["INFO", format ["Attack Module Synced Entities: %1", _syncedEntities]] call EFUNC(common,log);
{
[_x, _taskID] spawn FUNC(makeTarget);
} forEach _syncedEntities;
private _params = [_taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
if (_timeLimit != 0) then {
_params pushBack _timeLimit;
};
["attack", _params, 0, ""] spawn FUNC(handler);
deleteVehicle _logic;

View File

@ -0,0 +1,126 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers a defend task where players must hold a zone marked by a marker
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Defense zone marker name <STRING>
* 2: Time to defend in seconds <NUMBER>
* 3: Amount of funds the company receives if the task is successful <NUMBER> (default: 0)
* 4: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 5: Amount of rating the company and player receive if the task is successful <NUMBER> (default: 0)
* 6: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 7: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 8: Enemy wave count <NUMBER> (default: 3)
* 9: Time between waves in seconds <NUMBER> (default: 300)
* 10: Minimum BLUFOR units required in zone <NUMBER> (default: 1)
* 11: Equipment rewards <ARRAY> (default: [])
* 12: Supply rewards <ARRAY> (default: [])
* 13: Weapon rewards <ARRAY> (default: [])
* 14: Vehicle rewards <ARRAY> (default: [])
* 15: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["defend_zone_1", "defend_marker", 900, 500000, -100, 400, false, false, 3, 300, 1, ["ItemGPS"], ["FirstAidKit"], ["arifle_MX_F"], ["B_MRAP_01_F"], ["B_UAV_01_F"]] spawn forge_server_task_fnc_defend;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_defenseZone", "", [""]],
["_defendTime", 600, [0]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_waveCount", 3, [0]],
["_waveCooldown", 300, [0]],
["_minBlufor", 1, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
if (_defenseZone == "" || !(markerShape _defenseZone in ["RECTANGLE", "ELLIPSE"])) exitWith {
["ERROR", format ["Invalid defense zone marker: %1", _defenseZone]] call EFUNC(common,log);
};
private _result = 0;
private _startTime = time;
private _nextWaveTime = _startTime;
private _currentWave = 0;
private _zoneEmptyCounter = 0;
private _warningIssued = false;
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, [], _defenseZone, 0]];
private _bluforInZone = count (allUnits select { _x isKindOf "CAManBase" && { side _x == west } && { alive _x }} inAreaArray _defenseZone);
private _timeElapsed = time - _startTime;
if (_bluforInZone < _minBlufor) then {
_zoneEmptyCounter = _zoneEmptyCounter + 1;
if (_zoneEmptyCounter == 15 && !_warningIssued) then {
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", "Defense zone is empty. Return immediately."]];
_warningIssued = true;
};
} else {
_zoneEmptyCounter = 0;
_warningIssued = false;
};
if (_currentWave < _waveCount && time >= _nextWaveTime) then {
[_defenseZone, _taskID, _currentWave] call FUNC(spawnEnemyWave);
_currentWave = _currentWave + 1;
_nextWaveTime = time + _waveCooldown;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "info", "Tasks", format ["Enemy forces approaching. Wave %1 of %2.", _currentWave, _waveCount]]];
};
if (_zoneEmptyCounter >= 30) then { _result = 1; };
(_result == 1) or ((_bluforInZone >= _minBlufor) && (_timeElapsed >= _defendTime) && (_currentWave >= _waveCount));
};
if (_result == 1) then {
[_taskID, "FAILED"] call BFUNC(taskSetState);
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,61 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Creates a defend task module
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* call forge_server_task_fnc_defendModule;
*
* Public: No
*/
// Module category
private _category = "Forge Tasks";
private _subCategory = "Defense Tasks";
// Create the module
private _module = createDialog "RscDisplayAttributes";
_module setVariable ["category", _category];
_module setVariable ["subcategory", _subCategory];
_module setVariable ["description", "Configure a defend task"];
// Add fields for task configuration
[_module, "Task ID", "taskID", "", true] call BIS_fnc_addAttribute;
[_module, "Defense Zone Marker", "defenseZone", "", true] call BIS_fnc_addAttribute;
[_module, "Defense Time (seconds)", "defendTime", "600", true] call BIS_fnc_addAttribute;
[_module, "Min BLUFOR in Zone", "minBlufor", "1", true] call BIS_fnc_addAttribute;
[_module, "Company Funds Reward", "companyFunds", "500000", true] call BIS_fnc_addAttribute;
[_module, "Rating Loss on Fail", "ratingFail", "-100", true] call BIS_fnc_addAttribute;
[_module, "Rating Gain on Success", "ratingSuccess", "400", true] call BIS_fnc_addAttribute;
[_module, "End Mission on Success", "endSuccess", "false", false] call BIS_fnc_addAttribute;
[_module, "End Mission on Fail", "endFail", "false", false] call BIS_fnc_addAttribute;
[_module, "Enemy Wave Count", "waveCount", "3", false] call BIS_fnc_addAttribute;
[_module, "Time Between Waves (seconds)", "waveCooldown", "300", false] call BIS_fnc_addAttribute;
// Add confirm button handler
_module setVariable ["onConfirm", {
params ["_module"];
private _taskID = _module getVariable ["taskID", ""];
private _defenseZone = _module getVariable ["defenseZone", ""];
private _defendTime = parseNumber (_module getVariable ["defendTime", "600"]);
private _companyFunds = parseNumber (_module getVariable ["companyFunds", "500000"]);
private _ratingFail = parseNumber (_module getVariable ["ratingFail", "-100"]);
private _ratingSuccess = parseNumber (_module getVariable ["ratingSuccess", "400"]);
private _endSuccess = _module getVariable ["endSuccess", "false"] == "true";
private _endFail = _module getVariable ["endFail", "false"] == "true";
private _waveCount = parseNumber (_module getVariable ["waveCount", "3"]);
private _waveCooldown = parseNumber (_module getVariable ["waveCooldown", "300"]);
private _minBlufor = parseNumber (_module getVariable ["minBlufor", "1"]);
// Create the task
private _params = [_taskID, _defenseZone, _defendTime, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _waveCount, _waveCooldown, _minBlufor];
private _requesterUid = ["", getPlayerUID player] select hasInterface;
["defend", _params, 0, _requesterUid] spawn FUNC(handler);
}];

View File

@ -0,0 +1,114 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers a defuse task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of entities destroyed to fail the task <NUMBER>
* 2: Amount of ieds defused to complete the task <NUMBER>
* 3: Amount of funds the company recieves if the task is successful <NUMBER> (default: 0)
* 4: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 5: Amount of rating the company and player recieve if the task is successful <NUMBER> (default: 0)
* 6: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 7: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 8: Equipment rewards <ARRAY> (default: [])
* 9: Supply rewards <ARRAY> (default: [])
* 10: Weapon rewards <ARRAY> (default: [])
* 11: Vehicle rewards <ARRAY> (default: [])
* 12: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["task_name", 2, 3, 375000, -75, 300, false, false] spawn forge_server_task_fnc_defuse;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_limitFail", -1, [0]],
["_limitSuccess", -1, [0]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _result = 0;
private _ieds = [];
private _entities = [];
waitUntil {
sleep 1;
_ieds = GVAR(TaskStore) call ["getTaskEntities", ["ieds", _taskID]];
count _ieds > 0
};
waitUntil {
sleep 1;
_entities = GVAR(TaskStore) call ["getTaskEntities", ["entities", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _ieds + _entities, "", 250]];
count _entities > 0
};
_ieds = GVAR(TaskStore) call ["getTaskEntities", ["ieds", _taskID]];
_entities = GVAR(TaskStore) call ["getTaskEntities", ["entities", _taskID]];
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _ieds + _entities, "", 250]];
private _entitiesDestroyed = ({ !alive _x } count _entities);
if (_entitiesDestroyed >= _limitFail) then { _result = 1; };
(_result == 1) or ((GVAR(TaskStore) call ["getDefuseCount", [_taskID]]) >= _limitSuccess && (_entitiesDestroyed < _limitFail))
};
if (_result == 1) then {
{ deleteVehicle _x } forEach _ieds;
{ deleteVehicle _x } forEach _entities;
[_taskID, "FAILED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Task failed: %1 reputation", _ratingFail]]];
GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingFail]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _ieds;
{ deleteVehicle _x } forEach _entities;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] call EFUNC(common,formatNumber)]]];
GVAR(TaskStore) call ["applyRatingOutcome", [_taskID, _ratingSuccess]];
if (_endSuccess) then { ["MissionSuccess", true] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
};
GVAR(TaskStore) call ["clearTask", [_taskID]];

View File

@ -0,0 +1,64 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the defuse module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_defuseModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};
private _taskID = _logic getVariable ["TaskID", ""];
private _limitFail = _logic getVariable ["LimitFail", -1];
private _limitSuccess = _logic getVariable ["LimitSuccess", -1];
private _companyFunds = _logic getVariable ["CompanyFunds", 0];
private _ratingFail = _logic getVariable ["RatingFail", 0];
private _ratingSuccess = _logic getVariable ["RatingSuccess", 0];
private _endSuccess = _logic getVariable ["EndSuccess", false];
private _endFail = _logic getVariable ["EndFail", false];
private _timeLimit = _logic getVariable ["TimeLimit", 0];
["INFO", format ["Defuse Module Parameters: TaskID: %1, LimitFail: %2, LimitSuccess: %3, Funds: %4, RatingFail: %5, RatingSuccess: %6, EndSuccess: %7, EndFail: %8, Time: %9", _taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _timeLimit]] call EFUNC(common,log);
private _syncedModules = synchronizedObjects _logic;
["INFO", format ["Defuse Module Synced Modules: %1", _syncedModules]] call EFUNC(common,log);
private _iedModule = (_syncedModules select { typeOf _x == "FORGE_Module_Explosives" }) select 0;
private _protectedModule = (_syncedModules select { typeOf _x == "FORGE_Module_Protected" }) select 0;
private _explosiveEntities = synchronizedObjects _iedModule;
["INFO", format ["Defuse Module Explosive Entites: %1", _explosiveEntities]] call EFUNC(common,log);
private _protectedEntities = synchronizedObjects _protectedModule;
["INFO", format ["Defuse Module Protected Entities: %1", _protectedEntities]] call EFUNC(common,log);
{
if (!isNull _x) then {
[_x, _taskID, _timeLimit] spawn FUNC(makeIED);
};
} forEach _explosiveEntities;
{
if (!isNull _x) then {
[_x, _taskID] spawn FUNC(makeObject);
};
} forEach _protectedEntities;
private _params = [_taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
["defuse", _params, 0, ""] spawn FUNC(handler);
deleteVehicle _logic;

View File

@ -0,0 +1,120 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers a delivery task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of damaged cargo to fail the task <NUMBER>
* 2: Amount of cargo delivered to complete the task <NUMBER>
* 3: Marker name for the delivery zone <STRING>
* 4: Amount of funds the company receives if the task is successful <NUMBER> (default: 0)
* 5: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 6: Amount of rating the company and player receive if the task is successful <NUMBER> (default: 0)
* 7: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 8: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 9: Amount of time to complete delivery <NUMBER> (default: -1)
* 10: Equipment rewards <ARRAY> (default: [])
* 11: Supply rewards <ARRAY> (default: [])
* 12: Weapon rewards <ARRAY> (default: [])
* 13: Vehicle rewards <ARRAY> (default: [])
* 14: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["delivery_1", 1, 3, "delivery_zone", 250000, -75, 300, false, false] spawn forge_server_task_fnc_delivery;
* ["delivery_1", 1, 3, "delivery_zone", 250000, -75, 300, false, false, 900] spawn forge_server_task_fnc_delivery;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_limitFail", -1, [0]],
["_limitSuccess", -1, [0]],
["_deliveryZone", "", [""]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _result = 0;
private _cargo = [];
waitUntil {
sleep 1;
_cargo = GVAR(TaskStore) call ["getTaskEntities", ["cargo", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _cargo, _deliveryZone, 125]];
count _cargo > 0
};
_cargo = GVAR(TaskStore) call ["getTaskEntities", ["cargo", _taskID]];
private _startTime = if (_time isNotEqualTo -1) then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _cargo, _deliveryZone, 125]];
private _cargoDelivered = ({ _x inArea _deliveryZone && (damage _x) < 0.7 } count _cargo);
private _cargoDamaged = ({ damage _x >= 0.7 } count _cargo);
if (_time isNotEqualTo -1) then {
private _timeExpired = (floor time - _startTime >= _time);
if (_cargoDamaged >= _limitFail) then { _result = 1; };
if (_cargoDelivered < _limitSuccess && _timeExpired) then { _result = 1; };
(_result == 1) or ((_cargoDelivered >= _limitSuccess) && (_cargoDamaged < _limitFail))
} else {
if (_cargoDamaged >= _limitFail) then { _result = 1; };
(_result == 1) or ((_cargoDelivered >= _limitSuccess) && (_cargoDamaged < _limitFail))
};
};
if (_result == 1) then {
{ deleteVehicle _x } forEach _cargo;
[_taskID, "FAILED"] call BFUNC(taskSetState);
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _cargo;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,67 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Creates a delivery task module
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* call forge_server_task_fnc_deliveryModule;
*
* Public: No
*/
// Module category
private _category = "Forge Tasks";
private _subCategory = "Delivery Tasks";
// Create the module
private _module = createDialog "RscDisplayAttributes";
_module setVariable ["category", _category];
_module setVariable ["subcategory", _subCategory];
_module setVariable ["description", "Configure a delivery task"];
// Add fields for task configuration
[_module, "Task ID", "taskID", "", true] call BIS_fnc_addAttribute;
[_module, "Fail Limit", "limitFail", "1", true] call BIS_fnc_addAttribute;
[_module, "Success Count", "limitSuccess", "3", true] call BIS_fnc_addAttribute;
[_module, "Delivery Zone", "deliveryZone", "", true] call BIS_fnc_addAttribute;
[_module, "Company Funds Reward", "companyFunds", "250000", true] call BIS_fnc_addAttribute;
[_module, "Rating Loss on Fail", "ratingFail", "-75", true] call BIS_fnc_addAttribute;
[_module, "Rating Gain on Success", "ratingSuccess", "300", true] call BIS_fnc_addAttribute;
[_module, "End Mission on Success", "endSuccess", "false", false] call BIS_fnc_addAttribute;
[_module, "End Mission on Fail", "endFail", "false", false] call BIS_fnc_addAttribute;
[_module, "Time Limit (seconds)", "timeLimit", "", false] call BIS_fnc_addAttribute;
// Add confirm button handler
_module setVariable ["onConfirm", {
params ["_module"];
private _taskID = _module getVariable ["taskID", ""];
private _limitFail = parseNumber (_module getVariable ["limitFail", "1"]);
private _limitSuccess = parseNumber (_module getVariable ["limitSuccess", "3"]);
private _deliveryZone = _module getVariable ["deliveryZone", ""];
private _companyFunds = parseNumber (_module getVariable ["companyFunds", "250000"]);
private _ratingFail = parseNumber (_module getVariable ["ratingFail", "-75"]);
private _ratingSuccess = parseNumber (_module getVariable ["ratingSuccess", "300"]);
private _endSuccess = _module getVariable ["endSuccess", "false"] == "true";
private _endFail = _module getVariable ["endFail", "false"] == "true";
private _timeLimit = _module getVariable ["timeLimit", ""];
// Convert time limit to number or nil
private _timeLimitNum = if (_timeLimit == "") then { nil } else { parseNumber _timeLimit };
// Create the task
private _params = [_taskID, _limitFail, _limitSuccess, _deliveryZone, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
if (!isNil "_timeLimitNum") then {
_params pushBack _timeLimitNum;
};
private _requesterUid = ["", getPlayerUID player] select hasInterface;
["delivery", _params, 0, _requesterUid] spawn FUNC(handler);
}];

View File

@ -0,0 +1,114 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers an destroy task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of targets escaped to fail the task <NUMBER>
* 2: Amount of targets eliminated to complete the task <NUMBER>
* 3: Amount of funds the company recieves if the task is successful <NUMBER> (default: 0)
* 4: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 5: Amount of rating the company and player recieve if the task is successful <NUMBER> (default: 0)
* 6: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 7: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 8: Amount of time before target(s) escape <NUMBER> (default: -1)
* 9: Equipment rewards <ARRAY> (default: [])
* 10: Supply rewards <ARRAY> (default: [])
* 11: Weapon rewards <ARRAY> (default: [])
* 12: Vehicle rewards <ARRAY> (default: [])
* 13: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["task_name", 1, 2, 250000, -75, 300, false, false] spawn forge_server_task_fnc_destroy;
* ["task_name", 1, 2, 250000, -75, 300, false, false, 45] spawn forge_server_task_fnc_destroy;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_limitFail", -1, [0]],
["_limitSuccess", -1, [0]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _result = 0;
private _targets = [];
waitUntil {
sleep 1;
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
count _targets > 0
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
private _targetsDestroyed = ({ !alive _x } count _targets);
if (!isNil "_time") then {
private _timeExpired = (floor time - _startTime >= _time);
if (_targetsDestroyed < _limitSuccess && _timeExpired) then { _result = 1; };
(_result == 1) or (_targetsDestroyed >= _limitSuccess)
} else {
(_targetsDestroyed >= _limitSuccess)
};
};
if (_result == 1) then {
{ deleteVehicle _x } forEach _targets;
[_taskID, "FAILED"] call BFUNC(taskSetState);
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _targets;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,51 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the destroy module.
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_destroyModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};
private _taskID = _logic getVariable ["TaskID", ""];
private _limitFail = _logic getVariable ["LimitFail", -1];
private _limitSuccess = _logic getVariable ["LimitSuccess", -1];
private _companyFunds = _logic getVariable ["CompanyFunds", 0];
private _ratingFail = _logic getVariable ["RatingFail", 0];
private _ratingSuccess = _logic getVariable ["RatingSuccess", 0];
private _endSuccess = _logic getVariable ["EndSuccess", false];
private _endFail = _logic getVariable ["EndFail", false];
private _timeLimit = _logic getVariable ["TimeLimit", 0];
["INFO", format ["Destroy Module Parameters: TaskID: %1, LimitFail: %2, LimitSuccess: %3, Funds: %4, RatingFail: %5, RatingSuccess: %6, EndSuccess: %7, EndFail: %8, Time: %9", _taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _timeLimit]] call EFUNC(common,log);
private _syncedEntities = synchronizedObjects _logic;
["INFO", format ["Destroy Module Synced Entities: %1", _syncedEntities]] call EFUNC(common,log);
{
[_x, _taskID] spawn FUNC(makeTarget);
} forEach _syncedEntities;
private _params = [_taskID, _limitFail, _limitSuccess, _companyFunds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
if (_timeLimit != 0) then {
_params pushBack _timeLimit;
};
["destroy", _params, 0, ""] spawn FUNC(handler);
deleteVehicle _logic;

View File

@ -0,0 +1,23 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the explosives module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_explosivesModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};

View File

@ -0,0 +1,223 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Handles task completion rewards for organizations.
*
* Arguments:
* 0: Task ID <STRING>
* 1: Reward Data <HASHMAP>
* - funds: Amount of money to award <NUMBER>
* - equipment: Array of equipment classnames to award <ARRAY>
* - supplies: Array of supply classnames to award <ARRAY>
* - weapons: Array of weapon classnames to award <ARRAY>
* - vehicles: Array of vehicle classnames to award <ARRAY>
* - special: Array of special item classnames to award <ARRAY>
*
* Return Value:
* Success <BOOLEAN>
*
* Example:
* private _rewards = createHashMapFromArray [
* ["funds", 10000],
* ["reputation", 50],
* ["equipment", ["ItemGPS", "ItemCompass"]],
* ["supplies", ["FirstAidKit", "Medikit"]],
* ["weapons", ["arifle_MX_F"]],
* ["vehicles", ["B_MRAP_01_F"]],
* ["special", ["B_UAV_01_F"]]
* ];
* ["task_1", _rewards] call forge_server_task_fnc_handleTaskRewards;
*
* Public: No
*/
params [["_taskID", ""], ["_rewards", createHashMap]];
if (_taskID == "") exitWith {
["ERROR", "No task ID provided for rewards"] call EFUNC(common,log);
false
};
private _rewardContext = GVAR(TaskStore) call ["resolveRewardContext", [_taskID]];
private _requesterUid = _rewardContext getOrDefault ["requesterUid", ""];
private _orgID = _rewardContext getOrDefault ["orgID", ""];
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
if (_orgID isEqualTo "") exitWith {
["ERROR", format ["No organization reward context found for task %1.", _taskID]] call EFUNC(common,log);
false
};
private _success = true;
private _funds = _rewards getOrDefault ["funds", 0];
private _rewardMessages = [];
private _resolveRewardLabel = {
params [["_className", "", [""]]];
if (_className isEqualTo "") exitWith { "" };
{
private _cfg = _x >> _className;
if (isClass _cfg) exitWith {
private _displayName = getText (_cfg >> "displayName");
[_displayName, _className] select (_displayName isEqualTo "");
};
} forEach [
configFile >> "CfgWeapons",
configFile >> "CfgMagazines",
configFile >> "CfgVehicles",
configFile >> "CfgGlasses"
];
_className
};
private _notifyMembers = {
params [["_type", "info", [""]], ["_title", "Tasks", [""]], ["_message", "", [""]]];
if (_message isEqualTo "") exitWith {};
{
private _player = [_x] call EFUNC(common,getPlayer);
if (isNull _player) then { continue; };
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
} forEach _memberUids;
};
private _syncOrgPatch = {
params [["_patch", createHashMap, [createHashMap]]];
if (_patch isEqualTo createHashMap) exitWith {};
{
private _player = [_x] call EFUNC(common,getPlayer);
if (isNull _player) then { continue; };
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
} forEach _memberUids;
};
if (_funds > 0) then {
private _org = EGVAR(org,Registry) getOrDefault [_orgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
};
if (_org isEqualTo createHashMap) then {
["ERROR", format ["Failed to load organization %1 for task %2 funds reward.", _orgID, _taskID]] call EFUNC(common,log);
_success = false;
} else {
private _patch = EGVAR(org,OrgStore) call [
"set",
[
EGVAR(org,Registry),
"org:update",
_orgID,
"funds",
((_org getOrDefault ["funds", 0]) + _funds),
false
]
];
[_patch] call _syncOrgPatch;
_rewardMessages pushBack format ["$%1 org funds", [_funds] call EFUNC(common,formatNumber)];
};
};
private _grantOrgAssets = {
params [["_category", "items", [""]], ["_items", [], [[]]]];
if (_items isEqualTo []) exitWith {};
private _assetEntries = _items apply {
createHashMapFromArray [
["classname", _x],
["category", _category],
["quantity", 1]
]
};
private _grantResult = EGVAR(org,OrgStore) call ["addAssets", [_requesterUid, _assetEntries, false, _orgID]];
if !(_grantResult getOrDefault ["success", false]) then {
["ERROR", format ["Failed to award %1 assets for task %2: %3", _category, _taskID, _grantResult getOrDefault ["message", "Unknown error."]]] call EFUNC(common,log);
_success = false;
} else {
[_grantResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
private _labels = _items apply { [_x] call _resolveRewardLabel };
_rewardMessages pushBack format ["%1: %2", _category, _labels joinString ", "];
};
};
private _grantOrgFleet = {
params [["_vehicles", [], [[]]]];
if (_vehicles isEqualTo []) exitWith {};
private _vehicleEntries = _vehicles apply {
private _category = "other";
if (_x isKindOf "Car") then { _category = "cars"; };
if (_x isKindOf "Tank") then { _category = "armor"; };
if (_x isKindOf "Helicopter") then { _category = "helis"; };
if (_x isKindOf "Plane") then { _category = "planes"; };
if (_x isKindOf "Ship") then { _category = "naval"; };
createHashMapFromArray [
["classname", _x],
["category", _category]
]
};
private _fleetResult = EGVAR(org,OrgStore) call ["addFleetVehicles", [_requesterUid, _vehicleEntries, false, _orgID]];
if !(_fleetResult getOrDefault ["success", false]) then {
["ERROR", format ["Failed to award vehicle rewards for task %2: %1", _fleetResult getOrDefault ["message", "Unknown error."], _taskID]] call EFUNC(common,log);
_success = false;
} else {
[_fleetResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
private _labels = _vehicles apply { [_x] call _resolveRewardLabel };
_rewardMessages pushBack format ["vehicles: %1", _labels joinString ", "];
};
};
private _equipment = _rewards getOrDefault ["equipment", []];
if (count _equipment > 0) then {
["equipment", _equipment] call _grantOrgAssets;
};
private _supplies = _rewards getOrDefault ["supplies", []];
if (count _supplies > 0) then {
["supplies", _supplies] call _grantOrgAssets;
};
private _weapons = _rewards getOrDefault ["weapons", []];
if (count _weapons > 0) then {
["weapons", _weapons] call _grantOrgAssets;
};
private _special = _rewards getOrDefault ["special", []];
if (count _special > 0) then {
["special", _special] call _grantOrgAssets;
};
private _vehicles = _rewards getOrDefault ["vehicles", []];
if (count _vehicles > 0) then {
[_vehicles] call _grantOrgFleet;
};
if (_success) then {
private _orgName = "";
private _org = EGVAR(org,Registry) getOrDefault [_orgID, createHashMap];
if (_org isNotEqualTo createHashMap) then {
_orgName = _org getOrDefault ["name", _orgID];
};
if (_orgName isEqualTo "") then { _orgName = _orgID; };
private _message = format ["Task rewards added to %1.", _orgName];
if (_rewardMessages isNotEqualTo []) then {
_message = format ["%1 %2", _message, _rewardMessages joinString ", "];
};
["INFO", _message] call EFUNC(common,log);
["success", "Tasks", _message] call _notifyMembers;
} else {
["warning", "Tasks", format ["Task %1 completed, but one or more org rewards failed to apply.", _taskID]] call _notifyMembers;
};
_success

View File

@ -0,0 +1,108 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Server side task handler/spawner
*
* Arguments:
* 0: Type of task <STRING>
* 1: Arguments for task <ARRAY>
* 2: Minimum org reputation for task <NUMBER> (default: 0)
* 3: Requester UID <STRING> (default: "")
*
* Return Value:
* None
*
* Example:
* ["task_type", [_reward, _punish, _time, etc.....], minReputation, requesterUid] call forge_server_task_fnc_handler;
*
* Public: Yes
*/
params [["_taskType", "", [""]], ["_args", [], [[]]], ["_minRating", 0, [0]], ["_requesterUid", "", [""]]];
private _taskID = "";
if (_minRating > 0) then {
if (_requesterUid isEqualTo "") then {
["WARNING", format ["Task %1 requires minimum reputation %2 but no requester UID was provided, skipping reputation gate.", _taskType, _minRating]] call EFUNC(common,log);
} else {
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
if (_requesterActor isEqualTo createHashMap) then {
_requesterActor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
};
private _orgID = _requesterActor getOrDefault ["organization", "default"];
if (_orgID isEqualTo "") then { _orgID = "default"; };
private _org = EGVAR(org,Registry) getOrDefault [_orgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
};
private _orgReputation = _org getOrDefault ["reputation", 0];
if (_orgReputation < _minRating) exitWith {
private _message = format ["Organization reputation of %1 does not meet the minimum required reputation of %2.", _orgReputation, _minRating];
["WARNING", format ["Task %1 blocked: %2", _taskType, _message]] call EFUNC(common,log);
private _player = [_requesterUid] call EFUNC(common,getPlayer);
if (isNull _player) exitWith {};
[CRPC(notifications,recieveNotification), ["warning", "Tasks", _message], _player] call CFUNC(targetEvent);
};
};
};
if (_args isNotEqualTo [] && { (_args select 0) isEqualType "" }) then {
_taskID = _args select 0;
};
if (_taskID isNotEqualTo "") then {
private _ownershipResult = GVAR(TaskStore) call ["bindTaskOwnership", [_taskID, _requesterUid]];
if !(_ownershipResult getOrDefault ["success", false]) then {
["WARNING", format [
"Failed to bind task ownership for %1 (%2): %3",
_taskID,
_taskType,
_ownershipResult getOrDefault ["message", "Unknown error."]
]] call EFUNC(common,log);
};
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "active"]];
};
switch (_taskType) do {
case "attack": {
private _thread = _args spawn FUNC(attack);
waitUntil { sleep 2; scriptDone _thread };
};
case "defuse": {
private _thread = _args spawn FUNC(defuse);
waitUntil { sleep 2; scriptDone _thread };
};
case "destroy": {
private _thread = _args spawn FUNC(destroy);
waitUntil { sleep 2; scriptDone _thread };
};
case "delivery": {
private _thread = _args spawn FUNC(delivery);
waitUntil { sleep 2; scriptDone _thread };
};
case "defend": {
private _thread = _args spawn FUNC(defend);
waitUntil { sleep 2; scriptDone _thread };
};
case "hostage": {
private _thread = _args spawn FUNC(hostage);
waitUntil { sleep 2; scriptDone _thread };
};
case "hvt": {
private _thread = _args spawn FUNC(hvt);
waitUntil { sleep 2; scriptDone _thread };
};
default {
["ERROR", format ["Unknown Contract Type: %1", _taskType]] call EFUNC(common,log);
};
};
["INFO", "Mission Handler Done"] call EFUNC(common,log);

View File

@ -0,0 +1,68 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers Entity and starts heartbeat
*
* Arguments:
* 0: The entity <OBJECT>
* 1: Type of the entity <STRING>
* 2: The countdown timer <NUMBER>
*
* Return Value:
* None
*
* Example:
* [_entity, "entity_type", 30] spawn FUNC(heartBeat);
*
* Public: Yes
*/
params [["_entity", nil, [objNull, 0, [], sideUnknown, grpNull, ""]], ["_typeOf", "", [""]], ["_time", 0, [0]]];
private _nearPlayers = [];
switch (_typeOf) do {
case "hostage": {
_entity setCaptive true;
_entity enableAIFeature ["MOVE", false];
_entity playMove "acts_executionvictim_loop";
waitUntil {
sleep 1;
_nearPlayers = allPlayers inAreaArray [ASLToAGL getPosASL _entity, 2, 2, 0, false, 2];
count _nearPlayers > 0
};
private _nearPlayer = _nearPlayers select 0;
[_entity] joinSilent (group _nearPlayer);
_entity setCaptive false;
_entity enableAIFeature ["MOVE", true];
_entity playMove "acts_executionvictim_unbow";
};
case "hvt": {
waitUntil {
sleep 1;
_nearPlayers = allPlayers inAreaArray [ASLToAGL getPosASL _entity, 2, 2, 0, false, 2];
count _nearPlayers > 0
};
_entity setCaptive true;
doStop _entity;
};
case "ied": {
while { alive _entity && _time > 0} do {
if (_time > 10) then { _entity say3D "FORGE_timerBeep" };
if (_time <= 10 && _time > 5) then { _entity say3D "FORGE_timerBeepShort" };
if (_time <= 5) then { _entity say3D "FORGE_timerEnd" };
if (_time <= 0) exitWith { _entity setDamage 1 };
_time = _time -1;
sleep 1;
};
if (alive _entity && _time <= 0) then { _entity setDamage 1 };
};
};

View File

@ -0,0 +1,173 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers an hostage task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of hostages KIA to fail the task <NUMBER>
* 2: Amount of hostages rescued to complete the task <NUMBER>
* 3: Marker name for the extraction zone <STRING>
* 4: Amount of funds the company recieves if the task is successful <NUMBER> (default: 0)
* 5: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 6: Amount of rating the company and player recieve if the task is successful <NUMBER> (default: 0)
* 7: Subcategory of task <ARRAY> (default: [false, true])
* 8: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 9: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 10: Amount of time before hostage(s) die <NUMBER> (default: -1)
* 11: Marker name for the cbrn zone <STRING> (default: "")
* 12: Equipment rewards <ARRAY> (default: [])
* 13: Supply rewards <ARRAY> (default: [])
* 14: Weapon rewards <ARRAY> (default: [])
* 15: Vehicle rewards <ARRAY> (default: [])
* 16: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["task_name", 1, 2, "marker_name", 1500000, -75, 500, [false, true], false, false] spawn forge_server_task_fnc_hostage;
* ["task_name", 1, 2, "marker_name", 1500000, -75, 500, [false, true], false, false, 45] spawn forge_server_task_fnc_hostage;
* ["task_name", 1, 2, "marker_name", 1500000, -75, 500, [true, false], false, false, 45, "marker_name"] spawn forge_server_task_fnc_hostage;
*
* Public: Yes
*/
params [
["_taskID", ""],
["_limitFail", -1],
["_limitSuccess", -1],
["_extZone", ""],
["_companyFunds", 0],
["_ratingFail", 0],
["_ratingSuccess", 0],
["_type", [["_cbrn", false, [false]], ["_hostage", true, [false]]]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_cbrnZone", "", [""]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _cbrn = (_this select 7) select 0;
private _hostage = (_this select 7) select 1;
private _result = 0;
private _hostages = [];
private _shooters = [];
waitUntil {
sleep 1;
_hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]];
count _hostages > 0
};
waitUntil {
sleep 1;
_shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _hostages + _shooters, _extZone, 250]];
count _shooters > 0
};
_hostages = GVAR(TaskStore) call ["getTaskEntities", ["hostages", _taskID]];
_shooters = GVAR(TaskStore) call ["getTaskEntities", ["shooters", _taskID]];
private _startTime = if (_time isNotEqualTo -1) then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _hostages + _shooters, _extZone, 250]];
private _hostagesFreed = ({ !captive _x } count _hostages);
private _hostagesInZone = ({ _x inArea _extZone } count _hostages);
private _hostagesKilled = ({ !alive _x } count _hostages);
private _shootersAlive = ({ alive _x } count _shooters);
if (_time isNotEqualTo -1) then {
private _timeExpired = (floor time - _startTime >= _time);
if (_hostagesFreed < _limitSuccess && _timeExpired) then { _result = 1; };
if (_hostagesKilled >= _limitFail) then { _result = 1; };
(_result == 1) or
((_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail))
} else {
if (_hostagesKilled >= _limitFail) then { _result = 1; };
(_result == 1) or
((_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail)) or
((!isNil "_shooters") && (_shootersAlive <= 0) && (_hostagesInZone >= _limitSuccess) && (_hostagesKilled < _limitFail))
};
};
if (_result == 1) then {
if (_cbrn) then {
"SmokeShellYellow" createVehicle getMarkerPos _cbrnZone;
sleep 5;
{
if (captive _x) then {
_x setDamage 0.9;
_x playMove "acts_executionvictim_kill_end";
sleep 2.75;
_x setDamage 1;
}
} forEach _hostages;
};
if (_hostage) then {
{
_x enableAIFeature ["MOVE", true];
_x playMove "";
} forEach _shooters;
sleep 1;
{ _x setCaptive false; } forEach _hostages;
sleep 5;
};
{ deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters;
[_taskID, "FAILED"] call BFUNC(taskSetState);
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,76 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the hostage module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_hostageModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};
private _taskID = _logic getVariable ["TaskID", ""];
private _limitFail = _logic getVariable ["LimitFail", -1];
private _limitSuccess = _logic getVariable ["LimitSuccess", -1];
private _extraction = _logic getVariable ["ExtZone", ""];
private _companyFunds = _logic getVariable ["CompanyFunds", 0];
private _ratingFail = _logic getVariable ["RatingFail", 0];
private _ratingSuccess = _logic getVariable ["RatingSuccess", 0];
private _cbrn = _logic getVariable ["CBRN", false];
private _execution = _logic getVariable ["Execution", false];
private _endSuccess = _logic getVariable ["EndSuccess", false];
private _endFail = _logic getVariable ["EndFail", false];
private _timeLimit = _logic getVariable ["TimeLimit", 0];
private _cbrnZone = _logic getVariable ["CBRNZone", ""];
["INFO", format [
"Hostage Module Parameters: TaskID: %1, LimitFail: %2, LimitSuccess: %3, ExtractionZone: %4, Funds: %5, RatingFail: %6, RatingSuccess: %7, CBRN: %8, Execution: %9, EndSuccess: %10, EndFail: %11, Time: %12, CBRNZone: %13",
_taskID, _limitFail, _limitSuccess, _extraction, _companyFunds, _ratingFail, _ratingSuccess, _cbrn, _execution, _endSuccess, _endFail, _timeLimit, _cbrnZone
]] call EFUNC(common,log);
private _syncedModules = synchronizedObjects _logic;
["INFO", format ["Hostage Module Synced Entities: %1", _syncedModules]] call EFUNC(common,log);
private _hostageModule = (_syncedModules select { typeOf _x == "FORGE_Module_Hostages" }) select 0;
private _shooterModule = (_syncedModules select { typeOf _x == "FORGE_Module_Shooters" }) select 0;
private _hostageEntities = synchronizedObjects _hostageModule;
["INFO", format ["Hostage Module Hostage Entities: %1", _hostageEntities]] call EFUNC(common,log);
private _shooterEntities = synchronizedObjects _shooterModule;
["INFO", format ["Hostage Module Shooter Entities: %1", _shooterEntities]] call EFUNC(common,log);
{
if (!isNull _x && (_x isNotEqualTo str objNull)) then {
[_x, _taskID] spawn FUNC(makeHostage);
};
} forEach _hostageEntities;
{
if (!isNull _x && (_x isNotEqualTo str objNull)) then {
[_x, _taskID] spawn FUNC(makeShooter);
};
} forEach _shooterEntities;
private _params = [_taskID, _limitFail, _limitSuccess, _extraction, _companyFunds, _ratingFail, _ratingSuccess, [_cbrn, _execution], _endSuccess, _endFail];
if (_timeLimit != 0) then {
_params pushBack _timeLimit;
_params pushBack _cbrnZone;
};
["hostage", _params, 0, ""] spawn FUNC(handler);
deleteVehicle _logic;

View File

@ -0,0 +1,23 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the hostage module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_hostagesModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};

View File

@ -0,0 +1,128 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Registers an hvt task
*
* Arguments:
* 0: ID of the task <STRING>
* 1: Amount of HVTs KIA to fail the task <NUMBER>
* 2: Amount of HVTs Captured or KIA to complete the task <NUMBER>
* 3: Marker name for the extraction zone <STRING>
* 4: Amount of funds the company recieves if the task is successful <NUMBER> (default: 0)
* 5: Amount of rating the company and player lose if the task is failed <NUMBER> (default: 0)
* 6: Amount of rating the company and player recieve if the task is successful <NUMBER> (default: 0)
* 7: Subcategory of task <ARRAY> (default: [true, false])
* 8: Should the mission end (MissionSuccess) if the task is successful <BOOL> (default: false)
* 9: Should the mission end (MissionFailed) if the task is failed <BOOL> (default: false)
* 10: Amount of time before hvt(s) die <NUMBER> (default: -1)
* 11: Equipment rewards <ARRAY> (default: [])
* 12: Supply rewards <ARRAY> (default: [])
* 13: Weapon rewards <ARRAY> (default: [])
* 14: Vehicle rewards <ARRAY> (default: [])
* 15: Special rewards <ARRAY> (default: [])
*
* Return Value:
* None
*
* Example:
* ["task_name", 1, 1, "marker_name", 500000, -75, 300, [true, false], false, false] spawn forge_server_task_fnc_hvt;
* ["task_name", -1, 1, "", 500000, -75, 300, [false, true], false, false] spawn forge_server_task_fnc_hvt;
* ["task_name", 1, 1, "marker_name", 500000, -75, 300, [true, false], false, false, 45] spawn forge_server_task_fnc_hvt;
* ["task_name", -1, 1, "", 500000, -75, 300, [false, true], false, false, 45] spawn forge_server_task_fnc_hvt;
*
* Public: Yes
*/
params [
["_taskID", "", [""]],
["_limitFail", -1, [0]],
["_limitSuccess", -1, [0]],
["_extZone", "", [""]],
["_companyFunds", 0, [0]],
["_ratingFail", 0, [0]],
["_ratingSuccess", 0, [0]],
["_type", [["_capture", true, [false]], ["_eliminate", false, [false]]]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
["_vehicleRewards", [], [[]]],
["_specialRewards", [], [[]]]
];
private _capture = (_this select 7) select 0;
private _eliminate = (_this select 7) select 1;
private _result = 0;
private _hvts = [];
waitUntil {
sleep 1;
_hvts = GVAR(TaskStore) call ["getTaskEntities", ["hvts", _taskID]];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _hvts, _extZone, 250]];
count _hvts > 0
};
_hvts = GVAR(TaskStore) call ["getTaskEntities", ["hvts", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
waitUntil {
sleep 1;
GVAR(TaskStore) call ["trackParticipants", [_taskID, _hvts, _extZone, 250]];
private _hvtsCaptive = ({ captive _x } count _hvts);
private _hvtsKilled = ({ !alive _x } count _hvts);
private _hvtsInZone = ({ _x inArea _extZone } count _hvts);
if (!isNil "_time") then {
private _timeExpired = (floor time - _startTime >= _time);
if (_capture && _hvtsKilled >= _limitFail) then { _result = 1; };
if (_capture && _hvtsCaptive < _limitSuccess && _timeExpired) then { _result = 1; };
if (_eliminate && _hvtsKilled < _limitSuccess && _timeExpired) then { _result = 1; };
(_result == 1) or (_capture && (_hvtsInZone >= _limitSuccess) && (_hvtsKilled < _limitFail)) or (_eliminate && (_hvtsKilled >= _limitSuccess))
} else {
if (_capture && (_hvtsKilled >= _limitFail)) then { _result = 1; };
(_result == 1) or (_capture && (_hvtsInZone >= _limitSuccess) && (_hvtsKilled < _limitFail)) or (_eliminate && (_hvtsKilled >= _limitSuccess))
};
};
if (_result == 1) then {
{ deleteVehicle _x } forEach _hvts;
[_taskID, "FAILED"] call BFUNC(taskSetState);
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]];
if (_endFail) then { ["MissionFail", false] remoteExecCall ["BIS_fnc_endMission", playerSide]; };
} else {
{ deleteVehicle _x } forEach _hvts;
private _rewards = createHashMap;
_rewards set ["funds", _companyFunds];
if (_equipmentRewards isNotEqualTo []) then { _rewards set ["equipment", _equipmentRewards]; };
if (_supplyRewards isNotEqualTo []) then { _rewards set ["supplies", _supplyRewards]; };
if (_weaponRewards isNotEqualTo []) then { _rewards set ["weapons", _weaponRewards]; };
if (_vehicleRewards isNotEqualTo []) then { _rewards set ["vehicles", _vehicleRewards]; };
if (_specialRewards isNotEqualTo []) then { _rewards set ["special", _specialRewards]; };
[_taskID, _rewards] call FUNC(handleTaskRewards);
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
sleep 1;
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "success", "Tasks", format ["Task completed: %1 reputation, $%2 funds", _ratingSuccess, [_companyFunds] 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]; };
};

View File

@ -0,0 +1,59 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the hvt module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_hvtModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};
private _taskID = _logic getVariable ["TaskID", ""];
private _limitFail = _logic getVariable ["LimitFail", -1];
private _limitSuccess = _logic getVariable ["LimitSuccess", -1];
private _extraction = _logic getVariable ["ExtZone", ""];
private _companyFunds = _logic getVariable ["CompanyFunds", 0];
private _ratingFail = _logic getVariable ["RatingFail", 0];
private _ratingSuccess = _logic getVariable ["RatingSuccess", 0];
private _capture = _logic getVariable ["CaptureHVT", true];
private _eliminate = _logic getVariable ["EliminateHVT", false];
private _endSuccess = _logic getVariable ["EndSuccess", false];
private _endFail = _logic getVariable ["EndFail", false];
private _timeLimit = _logic getVariable ["TimeLimit", 0];
["INFO", format [
"HVT Module Parameters: TaskID: %1, LimitFail: %2, LimitSuccess: %3, ExtractionZone: %4, Funds: %5, RatingFail: %6, RatingSuccess: %7, CaptureHvt: %8, EliminateHvt: %9, EndSuccess: %10, EndFail: %11, Time: %12",
_taskID, _limitFail, _limitSuccess, _extraction, _companyFunds, _ratingFail, _ratingSuccess, _capture, _eliminate, _endSuccess, _endFail, _timeLimit
]] call EFUNC(common,log);
private _syncedEntities = synchronizedObjects _logic;
["INFO", format ["HVT Module Synced Entities: %1", _syncedEntities]] call EFUNC(common,log);
{
if (!isNull _x && (_x isNotEqualTo str objNull)) then {
[_x, _taskID] spawn FUNC(makeHVT);
};
} forEach _syncedEntities;
private _params = [_taskID, _limitFail, _limitSuccess, _extraction, _companyFunds, _ratingFail, _ratingSuccess, [_capture, _eliminate], _endSuccess, _endFail];
if (_timeLimit != 0) then {
_params pushBack _timeLimit;
};
["hvt", _params, 0, ""] spawn FUNC(handler);
deleteVehicle _logic;

View File

@ -0,0 +1,545 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the task store for task entity tracking, participant
* contribution tracking, and task outcome application.
*
* Arguments:
* None
*
* Return Value:
* Task store object [HASHMAP OBJECT]
*
* Example:
* call forge_server_task_fnc_initTaskStore
*
* Public: No
*/
#pragma hemtt ignore_variables ["_self"]
GVAR(TaskStore) = createHashMapObject [[
["#type", "TaskStore"],
["#create", compileFinal {
_self set ["participantRegistry", createHashMap];
_self set ["defuseRegistry", createHashMap];
_self set ["taskOwnershipRegistry", createHashMap];
_self set ["taskStatusRegistry", createHashMap];
_self set ["completedTaskStatusRegistry", createHashMap];
_self set ["taskCatalogRegistry", createHashMap];
_self set ["taskEntityRegistries", createHashMapFromArray [
["cargo", createHashMap],
["hostages", createHashMap],
["hvts", createHashMap],
["ieds", createHashMap],
["entities", createHashMap],
["shooters", createHashMap],
["targets", createHashMap]
]];
}],
["bindTaskOwnership", compileFinal {
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
["requesterUid", _requesterUid],
["orgID", "default"],
["message", ""]
];
if (_taskID isEqualTo "") exitWith {
_result set ["message", "Missing task ID."];
_result
};
if (_requesterUid isEqualTo "") exitWith {
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
_taskOwnershipRegistry set [_taskID, createHashMapFromArray [
["requesterUid", ""],
["orgID", "default"]
]];
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
_result set ["success", true];
_result set ["message", "No requester UID provided. Bound task to default organization."];
_result
};
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
if (_actor isEqualTo createHashMap) then {
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
};
if (_actor isEqualTo createHashMap) exitWith {
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
_result
};
private _orgID = _actor getOrDefault ["organization", ""];
if (_orgID isEqualTo "") then { _orgID = "default"; };
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
_taskOwnershipRegistry set [_taskID, createHashMapFromArray [
["requesterUid", _requesterUid],
["orgID", _orgID]
]];
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
private _catalogEntry = +(_taskCatalogRegistry getOrDefault [_taskID, createHashMap]);
if (_catalogEntry isNotEqualTo createHashMap) then {
_catalogEntry set ["requesterUid", _requesterUid];
_catalogEntry set ["orgID", _orgID];
_catalogEntry set ["accepted", true];
_taskCatalogRegistry set [_taskID, _catalogEntry];
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
};
_result set ["success", true];
_result set ["orgID", _orgID];
_result
}],
["registerTaskCatalogEntry", compileFinal {
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
if (_taskID isEqualTo "" || { _entry isEqualTo createHashMap }) exitWith { false };
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
_taskCatalogRegistry set [_taskID, +_entry];
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
true
}],
["getActiveTaskCatalog", compileFinal {
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
private _entries = [];
{
if ((_taskStatusRegistry getOrDefault [_x, ""]) isNotEqualTo "active") then { continue; };
private _entry = +_y;
_entry set ["taskID", _x];
_entry set ["status", "active"];
_entries pushBack _entry;
} forEach _taskCatalogRegistry;
_entries
}],
["acceptTask", compileFinal {
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
private _result = createHashMapFromArray [
["success", false],
["message", "Unable to accept task."],
["entry", createHashMap]
];
if (_taskID isEqualTo "" || { _requesterUid isEqualTo "" }) exitWith {
_result set ["message", "Missing task ID or requester UID."];
_result
};
if ((_self call ["getTaskStatus", [_taskID]]) isNotEqualTo "active") exitWith {
_result set ["message", "Task is no longer active."];
_result
};
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
private _entry = +(_taskCatalogRegistry getOrDefault [_taskID, createHashMap]);
if (_entry isEqualTo createHashMap) exitWith {
_result set ["message", "Task does not exist."];
_result
};
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
private _ownership = _taskOwnershipRegistry getOrDefault [_taskID, createHashMap];
private _currentRequesterUid = _ownership getOrDefault ["requesterUid", ""];
if (_currentRequesterUid isNotEqualTo "" && { _currentRequesterUid isNotEqualTo _requesterUid }) exitWith {
_result set ["message", "Task has already been accepted."];
_result set ["entry", _entry];
_result
};
private _bindResult = _self call ["bindTaskOwnership", [_taskID, _requesterUid]];
if !(_bindResult getOrDefault ["success", false]) exitWith {
_result set ["message", _bindResult getOrDefault ["message", "Failed to bind task ownership."]];
_result
};
private _updatedTaskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
private _updatedEntry = +(_updatedTaskCatalogRegistry getOrDefault [_taskID, _entry]);
_updatedEntry set ["accepted", true];
_updatedEntry set ["requesterUid", _requesterUid];
_updatedEntry set ["orgID", _bindResult getOrDefault ["orgID", "default"]];
_updatedTaskCatalogRegistry set [_taskID, _updatedEntry];
_self set ["taskCatalogRegistry", _updatedTaskCatalogRegistry];
_result set ["success", true];
_result set ["message", "Task accepted."];
_result set ["entry", _updatedEntry];
_result
}],
["setTaskStatus", compileFinal {
params [["_taskID", "", [""]], ["_status", "", [""]]];
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
_taskStatusRegistry set [_taskID, _status];
if (_status in ["succeeded", "failed"]) then {
_completedTaskStatusRegistry set [_taskID, _status];
} else {
_completedTaskStatusRegistry deleteAt _taskID;
};
_self set ["taskStatusRegistry", _taskStatusRegistry];
_self set ["completedTaskStatusRegistry", _completedTaskStatusRegistry];
true
}],
["getTaskStatus", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { "" };
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
private _status = _taskStatusRegistry getOrDefault [_taskID, ""];
if (_status isNotEqualTo "") exitWith { _status };
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
_completedTaskStatusRegistry getOrDefault [_taskID, ""]
}],
["clearTaskStatus", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
_taskStatusRegistry deleteAt _taskID;
_completedTaskStatusRegistry deleteAt _taskID;
_self set ["taskStatusRegistry", _taskStatusRegistry];
_self set ["completedTaskStatusRegistry", _completedTaskStatusRegistry];
true
}],
["registerTaskEntity", compileFinal {
params [["_registryKey", "", [""]], ["_taskID", "", [""]], ["_entity", objNull, [objNull]]];
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" } || { isNull _entity }) exitWith { false };
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
private _registry = +(_taskEntityRegistries getOrDefault [_registryKey, createHashMap]);
private _entities = +(_registry getOrDefault [_taskID, []]);
_entities pushBackUnique _entity;
_registry set [_taskID, _entities];
_taskEntityRegistries set [_registryKey, _registry];
_self set ["taskEntityRegistries", _taskEntityRegistries];
true
}],
["getTaskEntities", compileFinal {
params [["_registryKey", "", [""]], ["_taskID", "", [""]]];
if (_registryKey isEqualTo "" || { _taskID isEqualTo "" }) exitWith { [] };
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
private _registry = _taskEntityRegistries getOrDefault [_registryKey, createHashMap];
+(_registry getOrDefault [_taskID, []])
}],
["clearTaskEntities", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _taskEntityRegistries = _self getOrDefault ["taskEntityRegistries", createHashMap];
{
private _registry = +_y;
_registry deleteAt _taskID;
_taskEntityRegistries set [_x, _registry];
} forEach _taskEntityRegistries;
_self set ["taskEntityRegistries", _taskEntityRegistries];
true
}],
["trackParticipants", compileFinal {
params [["_taskID", "", [""]], ["_entities", [], [[]]], ["_marker", "", [""]], ["_radius", 300, [0]]];
if (_taskID isEqualTo "") exitWith { createHashMap };
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
private _activePlayers = allPlayers select {
alive _x
&& { side group _x isEqualTo west }
};
if (_marker isNotEqualTo "" && { markerShape _marker in ["RECTANGLE", "ELLIPSE"] }) then {
{
private _uid = getPlayerUID _x;
if (_uid isNotEqualTo "" && { _x inArea _marker }) then {
if !(_uid in _participantSnapshots) then {
_participantSnapshots set [_uid, createHashMapFromArray [
["startRating", rating _x]
]];
};
};
} forEach _activePlayers;
};
if (_radius > 0 && { _entities isNotEqualTo [] }) then {
{
private _entity = _x;
if (isNull _entity) then { continue; };
{
private _uid = getPlayerUID _x;
if (_uid isNotEqualTo "" && { (_x distance2D _entity) <= _radius }) then {
if !(_uid in _participantSnapshots) then {
_participantSnapshots set [_uid, createHashMapFromArray [
["startRating", rating _x]
]];
};
};
} forEach _activePlayers;
} forEach _entities;
};
_participantRegistry set [_taskID, _participantSnapshots];
_self set ["participantRegistry", _participantRegistry];
_participantSnapshots
}],
["resolveRewardContext", compileFinal {
params [["_taskID", "", [""]]];
private _result = createHashMapFromArray [
["requesterUid", ""],
["orgID", ""],
["memberUids", []]
];
if (_taskID isEqualTo "") exitWith { _result };
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
private _ownership = _taskOwnershipRegistry getOrDefault [_taskID, createHashMap];
if (_ownership isEqualTo createHashMap) exitWith { _result };
private _requesterUid = _ownership getOrDefault ["requesterUid", ""];
private _resolvedOrgID = _ownership getOrDefault ["orgID", ""];
if (_resolvedOrgID isEqualTo "") exitWith { _result };
private _org = EGVAR(org,Registry) getOrDefault [_resolvedOrgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = EGVAR(org,OrgStore) call ["loadById", [_resolvedOrgID]];
};
private _memberUids = [];
if (_org isNotEqualTo createHashMap) then {
_memberUids = EGVAR(org,OrgTreasuryService) call ["resolveOrgMemberUids", [_org, _requesterUid]];
};
_result set ["requesterUid", _requesterUid];
_result set ["orgID", _resolvedOrgID];
_result set ["memberUids", _memberUids];
_result
}],
["incrementDefuseCount", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { 0 };
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
private _nextCount = 1 + (_defuseRegistry getOrDefault [_taskID, 0]);
_defuseRegistry set [_taskID, _nextCount];
_self set ["defuseRegistry", _defuseRegistry];
_nextCount
}],
["getDefuseCount", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { 0 };
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
_defuseRegistry getOrDefault [_taskID, 0]
}],
["notifyParticipants", compileFinal {
params [
["_taskID", "", [""]],
["_type", "info", [""]],
["_title", "Tasks", [""]],
["_message", "", [""]]
];
if (_taskID isEqualTo "" || { _message isEqualTo "" }) exitWith { false };
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
if (_participantSnapshots isEqualTo createHashMap) exitWith { false };
{
private _player = [_x] call EFUNC(common,getPlayer);
if (isNull _player) then { continue; };
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
} forEach (keys _participantSnapshots);
true
}],
["clearTask", compileFinal {
params [["_taskID", "", [""]]];
if (_taskID isEqualTo "") exitWith { false };
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
_participantRegistry deleteAt _taskID;
_defuseRegistry deleteAt _taskID;
_taskOwnershipRegistry deleteAt _taskID;
_taskStatusRegistry deleteAt _taskID;
_taskCatalogRegistry deleteAt _taskID;
_self set ["participantRegistry", _participantRegistry];
_self set ["defuseRegistry", _defuseRegistry];
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
_self set ["taskStatusRegistry", _taskStatusRegistry];
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
_self call ["clearTaskEntities", [_taskID]];
true
}],
["applyRatingOutcome", compileFinal {
params [["_taskID", "", [""]], ["_delta", 0, [0]]];
private _result = createHashMapFromArray [
["participantUids", []],
["orgIds", []],
["contributions", createHashMap]
];
if (_taskID isEqualTo "" || { _delta isEqualTo 0 }) exitWith { _result };
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
private _participantSnapshots = +(_participantRegistry getOrDefault [_taskID, createHashMap]);
if (_participantSnapshots isEqualTo createHashMap) exitWith { _result };
private _participantUids = keys _participantSnapshots;
if (_participantUids isEqualTo []) exitWith { _result };
private _orgIds = [];
private _contributions = createHashMap;
private _totalContribution = 0;
{
private _uid = _x;
private _player = [_uid] call EFUNC(common,getPlayer);
if (isNull _player) then { continue; };
private _snapshot = _participantSnapshots getOrDefault [_uid, createHashMap];
private _startRating = _snapshot getOrDefault ["startRating", rating _player];
private _ratingDelta = (rating _player) - _startRating;
private _contribution = _ratingDelta max 0;
if (_delta < 0) then {
_contribution = (0 - _ratingDelta) max 0;
};
if (_contribution <= 0) then { continue; };
_contributions set [_uid, _contribution];
_totalContribution = _totalContribution + _contribution;
} forEach _participantUids;
if (_totalContribution <= 0) exitWith {
_self call ["clearTask", [_taskID]];
_result
};
{
private _uid = _x;
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
if (_actor isEqualTo createHashMap) then {
_actor = EGVAR(actor,ActorStore) call ["init", [_uid]];
};
private _orgID = _actor getOrDefault ["organization", ""];
if (_orgID isNotEqualTo "") then {
_orgIds pushBackUnique _orgID;
};
if (_delta > 0) then {
private _contribution = _contributions getOrDefault [_uid, 0];
if (_contribution <= 0) then { continue; };
private _account = EGVAR(bank,Registry) getOrDefault [_uid, createHashMap];
if (_account isEqualTo createHashMap) then {
_account = EGVAR(bank,BankStore) call ["init", [_uid]];
};
if (_account isNotEqualTo createHashMap) then {
private _earnings = _account getOrDefault ["earnings", 0];
private _earningsDelta = round ((_delta * _contribution) / _totalContribution);
if (_earningsDelta <= 0) then { continue; };
private _patch = EGVAR(bank,BankStore) call [
"mset",
[
EGVAR(bank,Registry),
"bank:update",
_uid,
createHashMapFromArray [["earnings", (_earnings + _earningsDelta)]],
false
]
];
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
};
};
} forEach _participantUids;
private _rewardContext = _self call ["resolveRewardContext", [_taskID]];
private _ownerOrgID = _rewardContext getOrDefault ["orgID", ""];
if (_ownerOrgID isNotEqualTo "") then {
private _org = EGVAR(org,Registry) getOrDefault [_ownerOrgID, createHashMap];
if (_org isEqualTo createHashMap) then {
_org = EGVAR(org,OrgStore) call ["loadById", [_ownerOrgID]];
};
if (_org isNotEqualTo createHashMap) then {
private _reputation = _org getOrDefault ["reputation", 0];
private _nextReputation = round (_reputation + _delta);
private _patch = EGVAR(org,OrgStore) call [
"set",
[
EGVAR(org,Registry),
"org:update",
_ownerOrgID,
"reputation",
_nextReputation,
false
]
];
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
{
private _player = [_x] call EFUNC(common,getPlayer);
if (isNull _player) then { continue; };
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
} forEach _memberUids;
_orgIds = [_ownerOrgID];
};
};
_result set ["participantUids", _participantUids];
_result set ["orgIds", _orgIds];
_result set ["contributions", _contributions];
_result
}]
]];
GVAR(TaskStore)

View File

@ -0,0 +1,41 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns cargo to a task for delivery
*
* Arguments:
* 0: Object to convert to delivery cargo <OBJECT>
* 1: Task ID to assign the cargo to <STRING>
*
* Return Value:
* None
*
* Example:
* [_cargoObject, "delivery_1"] call forge_server_task_fnc_makeCargo;
*
* Public: Yes
*/
params [["_cargo", objNull, [objNull]], ["_taskID", "", [""]]];
["INFO", format ["Make Cargo: %1", _this]] call EFUNC(common,log);
if (isNull _cargo) exitWith { ["ERROR", "Attempt to create cargo from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for cargo"] call EFUNC(common,log); };
SETPVAR(_cargo,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["cargo", _taskID, _cargo]];
_cargo addEventHandler ["Dammaged", {
params ["_unit", "_hitSelection", "_damage", "_hitPartIndex", "_hitPoint", "_shooter", "_projectile"];
if (damage _unit >= 0.7) then {
private _taskID = GETVAR(_unit,assignedTask,"");
if (_taskID isEqualTo "") exitWith {};
if (_unit getVariable [QGVAR(cargoDamageWarned), false]) exitWith {};
_unit setVariable [QGVAR(cargoDamageWarned), true];
GVAR(TaskStore) call ["notifyParticipants", [_taskID, "warning", "Tasks", format ["Cargo for task %1 has been severely damaged.", _taskID]]];
};
}];

View File

@ -0,0 +1,30 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an AI unit to a task as a hvt
*
* Arguments:
* 0: The AI unit <OBJECT>
* 1: ID of the task <STRING>
*
* Return Value:
* None
*
* Example:
* [this, "task_name"] spawn forge_server_task_fnc_makeHVT;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull, grpNull]], ["_taskID", "", [""]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
["INFO", format ["Make HVT: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["hvts", _taskID, _entity]];
if (alive _entity) then { [_entity, "hvt"] spawn FUNC(heartBeat); };

View File

@ -0,0 +1,30 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an AI unit to a task as a hostage
*
* Arguments:
* 0: The AI unit <OBJECT>
* 1: ID of the task <STRING>
*
* Return Value:
* None
*
* Example:
* [this, "task_name"] spawn forge_server_task_fnc_makeHostage;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull, grpNull]], ["_taskID", "", [""]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
["INFO", format ["Make Hostage: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["hostages", _taskID, _entity]];
if (alive _entity) then { [_entity, "hostage"] spawn FUNC(heartBeat); };

View File

@ -0,0 +1,32 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an IED to a task and starts countdown timer
*
* Arguments:
* 0: The object <OBJECT>
* 1: ID of the task <STRING>
* 2: The Countdown Timer <NUMBER>
*
* Return Value:
* None
*
* Example:
* [this, "task_name", 30] spawn forge_server_task_fnc_makeIED;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull]], ["_taskID", "", [""]], ["_time", 0, [0]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
if (_time < 0) exitWith { ["ERROR", "Invalid time provided for IED"] call EFUNC(common,log); };
["INFO", format ["Make IED: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["ieds", _taskID, _entity]];
if (alive _entity) then { [_entity, "ied", _time] spawn FUNC(heartBeat); };

View File

@ -0,0 +1,28 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an object to a task as a protected target
*
* Arguments:
* 0: The object <OBJECT>
* 1: ID of the task <STRING>
*
* Return Value:
* None
*
* Example:
* [this, "task_name"] spawn forge_server_task_fnc_makeObject;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull]], ["_taskID", "", [""]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
["INFO", format ["Make Object: %1", _this]] call EFUNC(common,log);
SETPVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["entities", _taskID, _entity]];

View File

@ -0,0 +1,28 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an AI unit to a task as a shooter
*
* Arguments:
* 0: The AI unit <OBJECT>
* 1: ID of the task <STRING>
*
* Return Value:
* None
*
* Example:
* [this, "task_name"] spawn forge_server_task_fnc_makeShooter;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull, grpNull]], ["_taskID", "", [""]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
["INFO", format ["Make Shooter: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["shooters", _taskID, _entity]];

View File

@ -0,0 +1,28 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Assigns an object to a task as a target
*
* Arguments:
* 0: The object <OBJECT>
* 1: ID of the task <STRING>
*
* Return Value:
* None
*
* Example:
* [this, "task_name"] spawn forge_server_task_fnc_makeTarget;
*
* Public: Yes
*/
params [["_entity", objNull, [objNull, grpNull]], ["_taskID", "", [""]]];
if (isNull _entity) exitWith { ["ERROR", "Attempt to create entity from null object"] call EFUNC(common,log); };
if (_taskID == "") exitWith { ["ERROR", "No task ID provided for entity"] call EFUNC(common,log); };
["INFO", format ["Make Target: %1", _this]] call EFUNC(common,log);
SETVAR(_entity,assignedTask,_taskID);
GVAR(TaskStore) call ["registerTaskEntity", ["targets", _taskID, _entity]];

View File

@ -0,0 +1,369 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Manages attack-only dynamic mission generation.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call forge_server_task_fnc_missionManager
*
* Public: No
*/
#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 ["activeMissionRegistry", createHashMap];
}],
["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; };
_maxConcurrent
}],
["getLocationReuseCooldown", compileFinal {
private _missionConfig = _self getOrDefault ["missionConfig", configNull];
private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
if (_cooldown <= 0) then { _cooldown = 900; };
_cooldown
}],
["getActiveMissionIds", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
keys _activeMissionRegistry
}],
["getActiveLocationKeys", compileFinal {
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
private _locationKeys = [];
{
private _locationKey = _y getOrDefault ["locationKey", ""];
if (_locationKey isNotEqualTo "") then {
_locationKeys pushBackUnique _locationKey;
};
} forEach _activeMissionRegistry;
_locationKeys
}],
["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
}],
["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;
{
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]]]
]
}],
["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 { 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 [
["vehicle", getText (_x >> "vehicle")],
["rank", getText (_x >> "rank")],
["position", getArray (_x >> "position")]
];
} forEach ("true" configClasses (_x >> "Units"));
} forEach _groups;
if (_unitPool isEqualTo []) exitWith {
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
}],
["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]
]
}],
["createAttackTask", compileFinal {
params [
["_taskID", "", [""]],
["_position", [0, 0, 0], [[]]],
["_locationConfig", configNull, [configNull]]
];
if (_taskID isEqualTo "" || { isNull _locationConfig }) exitWith { false };
private _locationKey = configName _locationConfig;
private _locationType = getText (_locationConfig >> "type");
if (_locationType isEqualTo "") then { _locationType = "area"; };
[
west,
_taskID,
[
format ["Eliminate hostile forces operating near %1.", _locationKey],
format ["Attack: %1", _locationKey],
_locationType
],
_position,
"CREATED",
1,
true,
"attack"
] call BFUNC(taskCreate);
true
}],
["startAttackMission", compileFinal {
private _attackConfig = _self getOrDefault ["attackConfig", configNull];
private _locationData = _self call ["selectAttackLocation"];
if (_locationData isEqualTo createHashMap) exitWith { "" };
private _location = _locationData getOrDefault ["config", configNull];
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)];
{
[_x, _taskID] call FUNC(makeTarget);
} forEach _units;
_self call ["createAttackTask", [_taskID, _position, _location]];
GVAR(TaskStore) call ["registerTaskCatalogEntry", [_taskID, createHashMapFromArray [
["type", "attack"],
["title", format ["Attack: %1", _locationKey]],
["description", format ["Eliminate hostile forces operating near %1.", _locationKey]],
["position", _position],
["locationKey", _locationKey],
["accepted", false],
["requesterUid", ""],
["orgID", "default"],
["source", "mission_manager"]
]]];
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 _params = [
_taskID,
0,
count _units,
_rewardRange call BFUNC(randomNum),
_penaltyRange call BFUNC(randomNum),
_reputationRange call BFUNC(randomNum),
false,
false,
_timeRange call BFUNC(randomNum),
_rewards get "equipment",
_rewards get "supplies",
_rewards get "weapons",
_rewards get "vehicles",
_rewards get "special"
];
private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
_activeMissionRegistry set [_taskID, createHashMapFromArray [
["locationKey", _locationKey],
["startedAt", serverTime]
]];
_self set ["activeMissionRegistry", _activeMissionRegistry];
["attack", _params, 0, ""] spawn FUNC(handler);
_taskID
}],
["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", ""];
_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];
};
true
}]
];
GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];
[{
{
private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
if (_status in ["succeeded", "failed"]) 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 {};
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);

View File

@ -0,0 +1,23 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the protected module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_protectedModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};

View File

@ -0,0 +1,23 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes the shooters module
*
* Arguments:
* 0: Logic <OBJECT> - The logic object
* 1: Units <ARRAY> - The array of units
* 2: Activated <BOOL> - Whether the module is activated
*
* Return Value:
* None
*
* Example:
* [logicObject, [unit1, unit2], true] call forge_server_task_fnc_shootersModule;
*
* Public: No
*/
params [["_logic", objNull, [objNull]], ["_units", [], [[]]], ["_activated", true, [true]]];
if !(_activated) exitWith {};

View File

@ -0,0 +1,83 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Spawns an enemy wave for a defense task
*
* Arguments:
* 0: Defense zone marker name <STRING>
* 1: Task ID <STRING>
* 2: Wave number (0-based) <NUMBER>
*
* Return Value:
* None
*
* Example:
* ["defend_marker", "defend_1", 0] call forge_server_task_fnc_spawnEnemyWave;
*
* Public: No
*/
params [["_defenseZone", "", [""]], ["_taskID", "", [""]], ["_waveNumber", 0, [0]]];
if (_defenseZone == "") exitWith { ["ERROR", "No defense zone provided for enemy wave spawn"] call EFUNC(common,log); };
// TODO: Add unit types to mission config
private _basicTypes = ["O_Soldier_F", "O_Soldier_AR_F", "O_Soldier_GL_F", "O_medic_F"];
private _specialTypes = ["O_Soldier_LAT_F", "O_soldier_M_F", "O_Soldier_TL_F", "O_Soldier_SL_F"];
private _eliteTypes = ["O_Soldier_HAT_F", "O_Soldier_AA_F", "O_engineer_F", "O_Sharpshooter_F"];
private _unitCount = 6 + (_waveNumber * 2); // TODO: Make this configurable in mission config
private _specialChance = 0.2 + (_waveNumber * 0.1); // TODO: Make this configurable in mission config
private _eliteChance = (_waveNumber * 0.05); // TODO: Make this configurable in mission config
private _center = getMarkerPos _defenseZone;
private _radius = (getMarkerSize _defenseZone select 0) max (getMarkerSize _defenseZone select 1);
private _spawnRadius = _radius + 150;
private _spawnPositions = [];
for "_i" from 0 to 3 do {
private _angle = _i * 90;
private _variance = 45;
private _spawnAngle = _angle + (random (_variance * 2) - _variance);
private _spawnDist = _spawnRadius + (random 50 - 25);
private _spawnX = (_center select 0) + (_spawnDist * cos _spawnAngle);
private _spawnY = (_center select 1) + (_spawnDist * sin _spawnAngle);
private _spawnPos = [_spawnX, _spawnY, 0];
private _safePos = _spawnPos findEmptyPosition [0, 50, "O_Soldier_F"];
if (count _safePos > 0) then {
_spawnPositions pushBack _safePos;
};
};
private _groups = [];
{
private _groupSize = ceil(_unitCount / (count _spawnPositions));
private _group = createGroup east;
_groups pushBack _group;
for "_i" from 1 to _groupSize do {
private _unitType = _basicTypes select (floor random count _basicTypes);
private _roll = random 1;
if (_roll < _eliteChance) then {
_unitType = _eliteTypes select (floor random count _eliteTypes);
} else {
if (_roll < _specialChance) then {
_unitType = _specialTypes select (floor random count _specialTypes);
};
};
private _unit = _group createUnit [_unitType, _x, [], 0, "NONE"];
_unit setVariable ["assignedTask", _taskID, true];
_unit setBehaviour "AWARE";
_unit setSpeedMode "NORMAL";
_unit enableDynamicSimulation true;
};
[_group, _center, _radius * 0.75] call CBA_fnc_taskDefend;
} forEach _spawnPositions;
["INFO", format ["Spawned defense wave %1 for task %2 with %3 units", _waveNumber + 1, _taskID, _unitCount]] call EFUNC(common,log);

View File

@ -0,0 +1,9 @@
#define COMPONENT task
#define COMPONENT_BEAUTIFIED Task
#include "\forge\forge_server\addons\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
// #define ENABLE_PERFORMANCE_COUNTERS
#include "\forge\forge_server\addons\main\script_macros.hpp"

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project name="FFE">
<Package name="Task">
<Key ID="STR_forge_server_task_displayName">
<English>Task</English>
</Key>
</Package>
</Project>

View File

@ -31,6 +31,18 @@ pub fn group() -> Group {
.command("update", update_org)
.command("exists", org_exists)
.command("delete", delete_org)
.group(
"assets",
Group::new()
.command("get", get_assets)
.command("update", update_assets),
)
.group(
"fleet",
Group::new()
.command("get", get_fleet)
.command("update", update_fleet),
)
.group(
"members",
Group::new()
@ -162,6 +174,56 @@ pub fn delete_org(key: String) -> String {
}
}
pub fn get_assets(key: String) -> String {
match ORG_SERVICE.get_assets(key) {
Ok(assets) => match serde_json::to_string(&assets) {
Ok(json) => json,
Err(e) => format!("Error: Failed to serialize org assets: {}", e),
},
Err(e) => format!("Error: {}", e),
}
}
pub fn update_assets(key: String, json_update: String) -> String {
let assets_value: serde_json::Value = match serde_json::from_str(&json_update) {
Ok(value) => value,
Err(e) => return format!("Error: Invalid JSON: {}", e),
};
match ORG_SERVICE.update_assets(key, assets_value) {
Ok(assets) => match serde_json::to_string(&assets) {
Ok(json) => json,
Err(e) => format!("Error: Failed to serialize org assets: {}", e),
},
Err(e) => format!("Error: {}", e),
}
}
pub fn get_fleet(key: String) -> String {
match ORG_SERVICE.get_fleet(key) {
Ok(fleet) => match serde_json::to_string(&fleet) {
Ok(json) => json,
Err(e) => format!("Error: Failed to serialize org fleet: {}", e),
},
Err(e) => format!("Error: {}", e),
}
}
pub fn update_fleet(key: String, json_update: String) -> String {
let fleet_value: serde_json::Value = match serde_json::from_str(&json_update) {
Ok(value) => value,
Err(e) => return format!("Error: Invalid JSON: {}", e),
};
match ORG_SERVICE.update_fleet(key, fleet_value) {
Ok(fleet) => match serde_json::to_string(&fleet) {
Ok(json) => json,
Err(e) => format!("Error: Failed to serialize org fleet: {}", e),
},
Err(e) => format!("Error: {}", e),
}
}
// ============================================================================
// Member Operations
// ============================================================================

View File

@ -39,13 +39,10 @@ pub fn hash_get(key: String, field: String) -> String {
pub fn hash_get_all(key: String) -> String {
redis_operation!(conn => {
match conn.hgetall::<_, HashMap<String, String>>(&key).await {
Ok(hash_map) => {
let formatted_pairs: Vec<String> = hash_map
.iter()
.map(|(k, v)| format!("{}, {}", k, v))
.collect();
formatted_pairs.join(", ")
}
Ok(hash_map) => match serde_json::to_string(&hash_map) {
Ok(json) => json,
Err(e) => format!("Error: Failed to serialize hash map: {}", e),
},
Err(e) => format!("Error: {}", e),
}
})

View File

@ -10,6 +10,6 @@ pub use actor::Actor;
pub use bank::Bank;
pub use garage::{Garage, HitPoints, Vehicle};
pub use locker::{Item, Locker};
pub use org::{CreditLineSummary, MemberSummary, Org};
pub use org::{CreditLineSummary, MemberSummary, Org, OrgAssetEntry, OrgFleetEntry};
pub use v_garage::{VGarage, VehicleCategory};
pub use v_locker::{EquipmentCategory, VLocker};

View File

@ -10,6 +10,24 @@ pub struct CreditLineSummary {
pub amount: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgAssetEntry {
pub classname: String,
#[serde(rename = "type")]
pub asset_type: String,
pub quantity: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgFleetEntry {
pub classname: String,
pub name: String,
#[serde(rename = "type")]
pub fleet_type: String,
pub status: String,
pub damage: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Org {
pub id: String,
@ -62,6 +80,12 @@ impl Org {
return Err(OrgValidationError::NegativeFunds);
}
if self.reputation < 0 {
return Err(OrgValidationError::InvalidName(
"Organization reputation cannot be negative".to_string(),
));
}
if !self.id.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err(OrgValidationError::InvalidId(self.id.clone()));
}

Some files were not shown because too many files have changed in this diff Show More