Enhance documentation and functionality across various server addons

- Updated README.md files for extension, garage, locker, main, organization, phone, store, and task addons to provide clearer overviews, dependencies, main components, and usage notes.
- Improved task module documentation to clarify timer semantics and server task flows, ensuring accurate usage of time limits.
- Adjusted default values for task time limits and IED timers to enforce positive countdown requirements.
- Added new CAD addon for dispatch coordination, including its overview, dependencies, main components, and event handling.
This commit is contained in:
Jacob Schmidt 2026-04-18 11:55:19 -05:00
parent 5576cc4746
commit 9d789440e0
29 changed files with 507 additions and 102 deletions

View File

@ -1,3 +1,32 @@
# forge_server_actor
# Forge Server Actor
Description for this addon
## Overview
The actor addon is the server-side bridge for player identity and character
state. It keeps Arma-facing actor snapshots in SQF while durable and hot actor
state are owned by the Rust extension.
Actor records include UID, name, loadout, position, direction, stance, rank,
life state, phone number, email, organization, and holster state.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for actor extension calls
- `forge_client_actor` for response RPCs
## Main Components
- `fnc_initActorStore.sqf` initializes `ActorModel` and `ActorStore`.
- `ActorModel` provides defaults, player snapshot conversion, migration, and
validation.
- `ActorStore` wraps extension hot-state calls and exposes event-facing actor
operations.
## Runtime Behavior
- Missing persistent actors can be created from live player snapshots.
- Hot actor reads are migrated and hydrated before use.
- `saveHotState` in the main addon snapshots and saves actor state on player
disconnect and mission end.
## Event Surface
The addon handles server events for actor init, get, set, multi-set, save, and
remove requests, then replies to the requesting player through client actor RPCs.

View File

@ -1,3 +1,39 @@
# forge_server_bank
# Forge Server Bank
Description for this addon
## Overview
The bank addon owns the SQF bridge for player accounts, cash and bank balances,
PIN/session handling, transfers, checkout charging, earnings deposits, and
credit-line repayment.
Account truth lives in the extension hot cache. SQF handles Arma-facing
validation, client messaging, session state, and payment integration with other
server addons.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for bank extension calls
- `forge_server_org` at runtime for credit-line repayment
- `forge_client_bank` and `forge_client_notifications` for response RPCs
## Main Components
- `fnc_initBank.sqf` initializes all bank stores and helpers.
- `fnc_initModel.sqf` defines account defaults and migration behavior.
- `fnc_initPayloadBuilder.sqf` builds UI, checkout, and organization payment
context.
- `fnc_initSessionManager.sqf` manages PIN and authorization session state.
- `fnc_initMessenger.sqf` sends account syncs, alerts, and notifications.
- `fnc_initStore.sqf` wraps hot bank calls and account mutations.
## Supported Operations
- initialize and hydrate player bank state
- deposit, withdraw, transfer, and deposit earnings
- validate PIN-backed sessions
- charge checkout previews and committed purchases
- repay organization credit lines with rollback on failure
- save hot bank state to durable storage
## Runtime Notes
`forge_server_main_fnc_saveHotState` saves bank hot state on disconnect and
mission shutdown. Store checkout and task rewards use this addon for
authoritative player balance changes.

View File

@ -0,0 +1,38 @@
# Forge Server CAD
## Overview
The CAD addon coordinates dispatch-facing operational state: groups,
assignments, dispatch orders, support requests, task assignment, permissions,
hydrate payloads, and recent activity.
CAD state is extension-backed but intentionally transient. It is scoped to the
active server or mission lifecycle and starts fresh after restart.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_actor`
- `forge_server_org`
- `forge_server_task`
- `forge_server_extension` at runtime for CAD extension calls
- `forge_client_cad` and `forge_client_notifications` for response RPCs
## Main Components
- `fnc_initCadStore.sqf` coordinates repositories and request handling.
- `fnc_initActivityRepository.sqf` records recent CAD activity.
- `fnc_initAssignmentRepository.sqf` manages task assignments and dispatch
orders.
- `fnc_initGroupRepository.sqf` manages group membership, role, and status.
- `fnc_initPermissionService.sqf` resolves dispatch permissions.
- `fnc_initPersistenceService.sqf` bridges SQF state to extension hot CAD
storage.
- `fnc_initRequestRepository.sqf` manages support requests.
## Event Surface
The addon handles hydrate, task assignment, dispatch order, support request,
task acknowledge/decline, and group update events. Successful mutations can
invalidate CAD state globally so clients refresh their views.
## Notes
CAD hydrate payloads include active task catalog entries from `TaskStore` and
organization context from `ActorStore`.

View File

@ -1,3 +1,23 @@
# forge_server_common
# Forge Server Common
Common functionality shared between addons.
## Overview
The common addon provides shared SQF utilities used by server-side Forge
addons. It contains lightweight helpers only; gameplay domain state belongs in
the specific domain addons or the Rust extension.
## Dependencies
- `forge_server_main`
## Main Components
- `fnc_baseStore.sqf` provides shared hash-map object behavior such as JSON
conversion.
- `fnc_log.sqf` standardizes server log messages.
- `fnc_getPlayer.sqf` resolves online players by UID.
- `fnc_formatNumber.sqf` formats numeric values for notifications and UI text.
- `fnc_generateHash.sqf` and `fnc_generateSecureData.sqf` provide hashing and
random data helpers.
- `fnc_timeToSeconds.sqf` converts time values into seconds.
## Notes
Keep this addon free of domain-specific behavior. If a helper needs actor,
bank, org, task, store, or CAD state, it belongs in that addon instead.

View File

@ -1,3 +1,31 @@
# forge_server_economy
# Forge Server Economy
Description for this addon
## Overview
The economy addon contains server-side systems for world economic interactions
that are still implemented in SQF.
Current stores cover fuel tracking, medical service behavior, and a placeholder
service economy store.
## Dependencies
- `forge_server_main`
- `forge_server_common` at runtime for logging, formatting, and player lookup
- `forge_server_bank` at runtime for medical service charges
- `forge_client_actor` and `forge_client_notifications` for response RPCs
## Main Components
- `fnc_initFEconomyStore.sqf` tracks active refueling sessions and reports fuel
totals.
- `fnc_initMEconomyStore.sqf` manages medical spawn occupancy, healing charges,
respawn placement, death inventory handling, and body-bag transfer.
- `fnc_initSEconomyStore.sqf` initializes the service economy placeholder.
## Event Surface
The addon registers CBA server events for fuel start/tick/stop, player killed,
player respawn, and healing. Medical store initialization runs after post-init
to discover configured medical spawn objects.
## Notes
The service economy store is currently a stub. Fuel and medical behavior should
stay server-authoritative because they mutate money, inventory, and respawn
state.

View File

@ -1,3 +1,29 @@
# forge_server_extension
# Forge Server Extension
Extension functionality shared between addons.
## Overview
The extension addon is the SQF bridge to the `forge_server` arma-rs extension.
It normalizes `callExtension` responses, routes large payloads through the
transport layer, and exposes helper functions used by extension-backed server
addons.
## Dependencies
- `forge_server_main`
## Main Components
- `fnc_extCall.sqf` is the primary extension call wrapper.
- `fnc_transport.sqf` stages large requests and assembles chunked responses.
- `fnc_setHandler.sqf` registers local SQF handlers for extension callback
integration.
## Transport Behavior
Most commands use direct `callExtension`. Commands that can return large
payloads, or requests whose encoded arguments exceed the chunk threshold, are
routed through `transport:invoke` or staged transport requests.
The wrapper falls back to direct calls if the transport route is unsupported and
the request was not chunked.
## Notes
Domain addons should call `EFUNC(extension,extCall)` instead of calling the
extension directly. This keeps response handling, chunking, and error logging
consistent.

View File

@ -1,3 +1,30 @@
# forge_server_garage
# Forge Server Garage
Description for this addon
## Overview
The garage addon is the server-side bridge for player vehicle storage and
owner-scoped vehicle unlock storage.
Garage hot state is owned by the extension. SQF validates Arma-facing requests,
serializes vehicle payloads, sends client syncs, and marks editor-placed garage
objects.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for garage extension calls
- `forge_client_garage` for response RPCs
## Main Components
- `fnc_initGarage.sqf` initializes garage world objects.
- `fnc_initGarageStore.sqf` manages player garage hot state.
- `fnc_initVGStore.sqf` manages owner-scoped vehicle unlock state.
## Supported Operations
- initialize player garage data
- save player and owner-scoped garage state
- store and retrieve player vehicles
- initialize and save owner-scoped vehicle storage
## Runtime Notes
`forge_server_main_fnc_saveHotState` saves both `GarageStore` and
`VGarageStore` on disconnect and mission shutdown.

View File

@ -1,3 +1,30 @@
# forge_server_locker
# Forge Server Locker
Description for this addon
## Overview
The locker addon is the server-side bridge for player item storage and
owner-scoped arsenal unlock storage.
Locker hot state is owned by the extension. SQF handles client events, payload
validation, synchronization, and save calls.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for locker extension calls
- `forge_client_locker` for response RPCs
## Main Components
- `fnc_initLocker.sqf` initializes locker world objects.
- `fnc_initLockerStore.sqf` manages player locker hot state.
- `fnc_initVAStore.sqf` manages owner-scoped arsenal unlock state.
## Supported Operations
- initialize player locker data
- save player and owner-scoped locker state
- override locker data from trusted server-side callers
- initialize and save owner-scoped arsenal storage
## Runtime Notes
`forge_server_main_fnc_saveHotState` saves both `LockerStore` and `VAStore` on
disconnect and mission shutdown. Store checkout and task rewards can grant
assets into organization-owned storage through the org addon.

View File

@ -1,3 +1,29 @@
# forge_server_main
# Forge Server Main
Main Addon for forge-server
## Overview
The main addon owns server-side bootstrap behavior for Forge. It prepares
functions, wires extension callbacks and ICom events, initializes shared stores,
and flushes hot state when players disconnect or the mission ends.
## Dependencies
- `cba_main`
- `ace_main`
## Main Components
- `fnc_initStores.sqf` initializes core server stores in dependency order.
- `fnc_saveHotState.sqf` snapshots and saves extension-backed hot state.
- `fnc_initValidationHarness.sqf` provides targeted runtime smoke checks for
multi-module flows.
- `XEH_preInit.sqf` registers ICom and extension callback handlers.
- `XEH_postInit.sqf` starts store initialization.
## Store Initialization
The main addon initializes shared base stores, actor, bank, garage, locker,
organization, store, and validation harness state. Some addons initialize their
own state in pre-init or post-init when they are intentionally independent of
the main bootstrap flow.
## Hot State Flush
On player disconnect, mission ended, and MP ended events, `saveHotState`
persists actor, bank, locker, virtual arsenal, garage, virtual garage, and
organization hot state for the relevant UID or all known UIDs.

View File

@ -1,3 +1,36 @@
# forge_server_org
# Forge Server Organization
Description for this addon
## Overview
The organization addon is the server-side bridge for player organizations,
membership, treasury funds, reputation, credit lines, shared assets, fleet
entries, and invitations.
Organization hot state is owned by the extension. SQF coordinates Arma-facing
events, UI payloads, membership syncs, and integration with actor, bank, store,
and task flows.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for organization extension calls
- `forge_server_actor` at runtime for organization membership lookups
- `forge_client_org` and `forge_client_notifications` for response RPCs
## Main Components
- `fnc_initOrgStore.sqf` initializes `OrgModel` and `OrgStore`.
- `fnc_initPayloadBuilder.sqf` builds portal, organization, member, asset, and
fleet payloads.
## Supported Operations
- initialize and hydrate organization portal data
- register, leave, and disband organizations
- invite, accept, and decline members
- assign and repay credit lines
- update funds and reputation
- grant assets and fleet vehicles
- save organization hot state
## Runtime Notes
The addon ensures the `default` organization exists during store creation.
Task rewards and store checkout both rely on `OrgStore` for authoritative
organization-owned state.

View File

@ -1,19 +1,33 @@
forge_server_phone
===================
# Forge Server Phone
This addon provides the phone user interface and functionality for the in-game phone system. It handles all phone-related features including the UI display, interactions, and core phone operations.
## Overview
The phone addon is the server-side bridge for contacts, SMS messages, and email.
Phone runtime state is owned by the extension. SQF stores preserve the
event-facing API and synchronize client UI state.
Server State
------------
## Dependencies
- `forge_server_main`
- `forge_server_common` at runtime for online player lookup
- `forge_server_extension` at runtime for phone extension calls
- `forge_client_phone` for response RPCs
Phone contacts, messages, and emails are owned by the server extension. SQF phone stores act as bridge objects for CBA events and UI sync only.
## Main Components
- `fnc_initPhoneStore.sqf` coordinates the phone facade.
- `fnc_initContactStore.sqf` manages contacts.
- `fnc_initMessageStore.sqf` manages SMS messages and threads.
- `fnc_initEmailStore.sqf` manages email messages.
- `fnc_initPlayer.sqf` initializes phone data for a player.
Persistent SurrealDB tables:
## Persistent Extension Tables
- `phone_user`: owner row for an initialized phone profile
- `phone_contact`: per-owner contact rows keyed by owner UID and contact UID
- `phone_message`: shared message records
- `phone_message_index`: per-owner message visibility and read state
- `phone_email`: shared email records
- `phone_email_index`: per-owner email visibility and read state
- `phone_sequence`: global sequence state for generated message and email IDs
- `phone_user`: owner row for an initialized phone profile.
- `phone_contact`: per-owner contact rows keyed by owner UID and contact UID.
- `phone_message`: shared message records.
- `phone_message_index`: per-owner message visibility and read state.
- `phone_email`: shared email records.
- `phone_email_index`: per-owner email visibility and read state.
- `phone_sequence`: global sequence state for generated message and email IDs.
## Event Surface
The addon handles client requests to initialize phone state, add/remove/refresh
contacts, send/read/delete messages, send/read/delete emails, and remove phone
state.

View File

@ -1,18 +1,35 @@
# forge_server_store
# Forge Server Store
Server-side SQF module for storefront entities, catalog hydration, and checkout
coordination.
## Overview
The store addon manages server-side storefront entities, catalog hydration, and
checkout coordination.
## Stores and Services
SQF owns Arma-facing storefront discovery and request validation. The Rust
extension owns authoritative checkout calculation through `store:checkout`.
- `StorefrontStore` builds storefront hydrate payloads, validates checkout
requests, calls the Rust `store:checkout` backend, syncs UI patches, and saves
hot state for related modules.
- `StoreCatalogService` scans live Arma config categories, builds catalog
responses, resolves checkout entries, and calculates authoritative prices.
## Dependencies
- `forge_server_main`
- `forge_server_common`
- `forge_server_extension` at runtime for checkout calls
- `forge_server_actor`, `forge_server_bank`, and `forge_server_org` at runtime
for checkout context and payment state
- `forge_client_store` for response RPCs
## Main Components
- `fnc_initStore.sqf` marks editor-placed store objects with `isStore = true`.
- `fnc_initCatalogService.sqf` scans live Arma config categories, builds
catalog responses, resolves checkout entries, and calculates authoritative
catalog prices.
- `fnc_initStorefrontStore.sqf` builds hydrate payloads, validates checkout
requests, calls `store:checkout`, syncs client patches, and coordinates
related bank/org persistence.
## Editor Entities
`fnc_initStore` matches non-null mission namespace objects whose variable names
contain `store`, mirroring the garage entity initialization pattern.
`fnc_initStore` marks editor-placed store objects with `isStore = true`. It
matches non-null mission namespace objects whose variable names contain
`store`, mirroring the garage entity initialization pattern.
## Checkout Flow
Store checkout can charge cash, bank balance, organization funds, or approved
credit lines depending on the hydrated session context. Checkout results can
grant locker assets, organization assets, and fleet vehicles through the
related domain stores.

View File

@ -355,9 +355,9 @@ class CfgVehicles {
class TimeLimit: Edit {
property = "FORGE_Module_Defuse_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before detenation (0 for no limit)";
tooltip = "Time in seconds before detonation; must be greater than 0";
typeName = "NUMBER";
defaultValue = 0;
defaultValue = 300;
};
};
@ -608,7 +608,7 @@ class CfgVehicles {
class TimeLimit: Edit {
property = "FORGE_Module_Hostage_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before HVTs escape (0 for no limit)";
tooltip = "Time in seconds before hostages are executed (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};
@ -730,9 +730,9 @@ class CfgVehicles {
class TimeLimit: Edit {
property = "FORGE_Module_Delivery_TimeLimit";
displayName = "Time Limit";
tooltip = "Seconds to complete delivery (-1 for no limit)";
tooltip = "Seconds to complete delivery (0 for no limit)";
typeName = "NUMBER";
defaultValue = -1;
defaultValue = 0;
};
};
@ -907,7 +907,7 @@ class CfgVehicles {
class TimeLimit: Edit {
property = "FORGE_Module_HVT_TimeLimit";
displayName = "Time Limit";
tooltip = "Time in seconds before HVTs escape (0 for no limit)";
tooltip = "Time in seconds before the HVT task fails (0 for no limit)";
typeName = "NUMBER";
defaultValue = 0;
};

View File

@ -16,6 +16,7 @@ system intentionally starts clean after each server or mission restart.
- notify task participants and sync org updates to online members
## Dependencies
- `forge_server_extension`
- `forge_server_common`
- `forge_server_actor`
- `forge_server_bank`
@ -58,6 +59,10 @@ Org rewards always go to the bound owner org. Player earnings still use per-play
## Usage
Task time limits use `0` for no limit on attack, destroy, delivery, hostage,
and HVT tasks. Defuse IED timers are different: each IED must have a positive
countdown value.
### Start Through The Handler
Use the handler when you want reputation gating and task ownership binding.

View File

@ -13,7 +13,7 @@
* 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)
* 8: Amount of time before target(s) escape <NUMBER> (default: 0, 0 = no limit)
* 9: Equipment rewards <ARRAY> (default: [])
* 10: Supply rewards <ARRAY> (default: [])
* 11: Weapon rewards <ARRAY> (default: [])
@ -39,7 +39,7 @@ params [
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_timeLimit", 0, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
@ -58,7 +58,7 @@ waitUntil {
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
@ -66,8 +66,8 @@ waitUntil {
private _targetsKilled = ({ !alive _x } count _targets);
if (_time isNotEqualTo -1) then {
private _timeExpired = (floor time - _startTime >= _time);
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_targetsKilled < _limitSuccess && _timeExpired) then { _result = 1; };

View File

@ -53,7 +53,7 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", -1]]
["timeLimit", _logic getVariable ["TimeLimit", 0]]
]
] call FUNC(startTask);

View File

@ -36,7 +36,7 @@ private _protectedEntities = if (!isNull _protectedModule) then { synchronizedOb
["INFO", format [
"Defuse Module: TaskID: %1, IEDs: %2, Protected: %3, IED timer: %4s",
_taskID, count _iedEntities, count _protectedEntities,
_logic getVariable ["TimeLimit", 0]
_logic getVariable ["TimeLimit", 300]
]] call EFUNC(common,log);
private _taskPos = if (_iedEntities isNotEqualTo []) then {
@ -63,7 +63,7 @@ private _taskPos = if (_iedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["iedTimer", _logic getVariable ["TimeLimit", 0]]
["iedTimer", _logic getVariable ["TimeLimit", 300]]
]
] call FUNC(startTask);

View File

@ -14,7 +14,7 @@
* 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)
* 9: Amount of time to complete delivery <NUMBER> (default: 0)
* 10: Equipment rewards <ARRAY> (default: [])
* 11: Supply rewards <ARRAY> (default: [])
* 12: Weapon rewards <ARRAY> (default: [])
@ -41,7 +41,7 @@ params [
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_timeLimit", 0, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
@ -60,7 +60,7 @@ waitUntil {
};
_cargo = GVAR(TaskStore) call ["getTaskEntities", ["cargo", _taskID]];
private _startTime = if (_time isNotEqualTo -1) then { floor(time) } else { nil };
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
@ -69,8 +69,8 @@ waitUntil {
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 (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_cargoDamaged >= _limitFail) then { _result = 1; };
if (_cargoDelivered < _limitSuccess && _timeExpired) then { _result = 1; };

View File

@ -63,7 +63,7 @@ private _taskPos = if (_cargoEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", -1]],
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["deliveryZone", _logic getVariable ["DeliveryZone", ""]]
]
] call FUNC(startTask);

View File

@ -13,7 +13,7 @@
* 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)
* 8: Amount of time before target(s) escape <NUMBER> (default: 0, 0 = no limit)
* 9: Equipment rewards <ARRAY> (default: [])
* 10: Supply rewards <ARRAY> (default: [])
* 11: Weapon rewards <ARRAY> (default: [])
@ -39,7 +39,7 @@ params [
["_ratingSuccess", 0, [0]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_timeLimit", 0, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
@ -58,7 +58,7 @@ waitUntil {
};
_targets = GVAR(TaskStore) call ["getTaskEntities", ["targets", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
@ -66,8 +66,8 @@ waitUntil {
private _targetsDestroyed = ({ !alive _x } count _targets);
if (!isNil "_time") then {
private _timeExpired = (floor time - _startTime >= _time);
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_targetsDestroyed < _limitSuccess && _timeExpired) then { _result = 1; };

View File

@ -53,7 +53,7 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", -1]]
["timeLimit", _logic getVariable ["TimeLimit", 0]]
]
] call FUNC(startTask);

View File

@ -15,7 +15,7 @@
* 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)
* 10: Amount of time before hostage(s) are executed <NUMBER> (default: 0, 0 = no limit)
* 11: Marker name for the cbrn zone <STRING> (default: "")
* 12: Equipment rewards <ARRAY> (default: [])
* 13: Supply rewards <ARRAY> (default: [])
@ -45,7 +45,7 @@ params [
["_type", [["_cbrn", false, [false]], ["_hostage", true, [false]]]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_timeLimit", 0, [0]],
["_cbrnZone", "", [""]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
@ -75,7 +75,7 @@ waitUntil {
_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 };
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
@ -86,8 +86,8 @@ waitUntil {
private _hostagesKilled = ({ !alive _x } count _hostages);
private _shootersAlive = ({ alive _x } count _shooters);
if (_time isNotEqualTo -1) then {
private _timeExpired = (floor time - _startTime >= _time);
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_hostagesFreed < _limitSuccess && _timeExpired) then { _result = 1; };
if (_hostagesKilled >= _limitFail) then { _result = 1; };

View File

@ -67,7 +67,7 @@ private _taskPos = if (_hostageEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", -1]],
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["extractionZone", _logic getVariable ["ExtZone", ""]],
["cbrn", _logic getVariable ["CBRN", false]],
["execution", _logic getVariable ["Execution", false]],

View File

@ -15,7 +15,7 @@
* 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)
* 10: Amount of time before HVT task fails <NUMBER> (default: 0, 0 = no limit)
* 11: Equipment rewards <ARRAY> (default: [])
* 12: Supply rewards <ARRAY> (default: [])
* 13: Weapon rewards <ARRAY> (default: [])
@ -45,7 +45,7 @@ params [
["_type", [["_capture", true, [false]], ["_eliminate", false, [false]]]],
["_endSuccess", false, [false]],
["_endFail", false, [false]],
["_time", -1, [0]],
["_timeLimit", 0, [0]],
["_equipmentRewards", [], [[]]],
["_supplyRewards", [], [[]]],
["_weaponRewards", [], [[]]],
@ -66,7 +66,7 @@ waitUntil {
};
_hvts = GVAR(TaskStore) call ["getTaskEntities", ["hvts", _taskID]];
private _startTime = if (!isNil "_time") then { floor(time) } else { nil };
private _startTime = if (_timeLimit isNotEqualTo 0) then { floor(time) } else { nil };
waitUntil {
sleep 1;
@ -76,8 +76,8 @@ waitUntil {
private _hvtsKilled = ({ !alive _x } count _hvts);
private _hvtsInZone = ({ _x inArea _extZone } count _hvts);
if (!isNil "_time") then {
private _timeExpired = (floor time - _startTime >= _time);
if (_timeLimit isNotEqualTo 0) then {
private _timeExpired = (floor time - _startTime >= _timeLimit);
if (_capture && _hvtsKilled >= _limitFail) then { _result = 1; };
if (_capture && _hvtsCaptive < _limitSuccess && _timeExpired) then { _result = 1; };

View File

@ -59,7 +59,7 @@ private _taskPos = if (_syncedEntities isNotEqualTo []) then {
["ratingSuccess", _logic getVariable ["RatingSuccess", 0]],
["endSuccess", _logic getVariable ["EndSuccess", false]],
["endFail", _logic getVariable ["EndFail", false]],
["timeLimit", _logic getVariable ["TimeLimit", -1]],
["timeLimit", _logic getVariable ["TimeLimit", 0]],
["extractionZone", _logic getVariable ["ExtZone", ""]],
["captureHvt", _logic getVariable ["CaptureHVT", true]]
]

View File

@ -2,12 +2,12 @@
/*
* Author: IDSolutions
* Assigns an IED to a task and starts countdown timer
* Assigns an IED to a task and starts its required countdown timer
*
* Arguments:
* 0: The object <OBJECT>
* 1: ID of the task <STRING>
* 2: The Countdown Timer <NUMBER>
* 2: Countdown timer in seconds <NUMBER> (must be greater than 0)
*
* Return Value:
* None
@ -22,7 +22,7 @@ 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); };
if (_time <= 0) exitWith { ["ERROR", "Invalid time provided for IED"] call EFUNC(common,log); };
["INFO", format ["Make IED: %1", _this]] call EFUNC(common,log);

View File

@ -29,12 +29,12 @@
* "ratingSuccess" <NUMBER> (default: 0)
* "endSuccess" <BOOL> (default: false)
* "endFail" <BOOL> (default: false)
* "timeLimit" <NUMBER> (default: -1, -1 = no limit)
* "timeLimit" <NUMBER> (default: 0, 0 = no limit)
* Reward keys:
* "equipment" <ARRAY>, "supplies" <ARRAY>, "weapons" <ARRAY>,
* "vehicles" <ARRAY>, "special" <ARRAY>
* Type-specific keys:
* defuse: "iedTimer" <NUMBER> -- IED countdown in seconds (default: 0)
* defuse: "iedTimer" <NUMBER> -- required IED countdown in seconds (> 0)
* delivery: "deliveryZone" <STRING> -- marker name
* hostage: "extractionZone" <STRING> -- marker name
* "cbrn" <BOOL> (default: false)
@ -138,7 +138,7 @@ private _ratingFail = _taskParams getOrDefault ["ratingFail", 0];
private _ratingSuccess = _taskParams getOrDefault ["ratingSuccess", 0];
private _endSuccess = _taskParams getOrDefault ["endSuccess", false];
private _endFail = _taskParams getOrDefault ["endFail", false];
private _timeLimit = _taskParams getOrDefault ["timeLimit", -1];
private _timeLimit = _taskParams getOrDefault ["timeLimit", 0];
private _equipRewards = _taskParams getOrDefault ["equipment", []];
private _supplyRewards = _taskParams getOrDefault ["supplies", []];
private _weaponRewards = _taskParams getOrDefault ["weapons", []];
@ -150,8 +150,7 @@ private _rewardTail = [_equipRewards, _supplyRewards, _weaponRewards, _vehicleRe
private _handlerArgs = switch (_taskType) do {
case "attack";
case "destroy": {
private _args = [_taskID, _limitFail, _limitSuccess, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
if (_timeLimit >= 0) then { _args pushBack _timeLimit; };
private _args = [_taskID, _limitFail, _limitSuccess, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _timeLimit];
_args + _rewardTail
};
case "defuse": {
@ -159,8 +158,7 @@ private _handlerArgs = switch (_taskType) do {
};
case "delivery": {
private _deliveryZone = _taskParams getOrDefault ["deliveryZone", ""];
private _args = [_taskID, _limitFail, _limitSuccess, _deliveryZone, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail];
if (_timeLimit >= 0) then { _args pushBack _timeLimit; };
private _args = [_taskID, _limitFail, _limitSuccess, _deliveryZone, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _timeLimit];
_args + _rewardTail
};
case "hostage": {
@ -168,18 +166,14 @@ private _handlerArgs = switch (_taskType) do {
private _cbrn = _taskParams getOrDefault ["cbrn", false];
private _execution = _taskParams getOrDefault ["execution", false];
private _cbrnZone = _taskParams getOrDefault ["cbrnZone", ""];
private _args = [_taskID, _limitFail, _limitSuccess, _extZone, _funds, _ratingFail, _ratingSuccess, [_cbrn, _execution], _endSuccess, _endFail];
if (_timeLimit >= 0) then {
_args pushBack _timeLimit;
_args pushBack _cbrnZone;
};
private _args = [_taskID, _limitFail, _limitSuccess, _extZone, _funds, _ratingFail, _ratingSuccess, [_cbrn, _execution], _endSuccess, _endFail, _timeLimit];
_args pushBack _cbrnZone;
_args + _rewardTail
};
case "hvt": {
private _extZone = _taskParams getOrDefault ["extractionZone", ""];
private _captureHvt = _taskParams getOrDefault ["captureHvt", true];
private _args = [_taskID, _limitFail, _limitSuccess, _extZone, _funds, _ratingFail, _ratingSuccess, [_captureHvt, !_captureHvt], _endSuccess, _endFail];
if (_timeLimit >= 0) then { _args pushBack _timeLimit; };
private _args = [_taskID, _limitFail, _limitSuccess, _extZone, _funds, _ratingFail, _ratingSuccess, [_captureHvt, !_captureHvt], _endSuccess, _endFail, _timeLimit];
_args + _rewardTail
};
case "defend": {
@ -188,8 +182,7 @@ private _handlerArgs = switch (_taskType) do {
private _waveCount = _taskParams getOrDefault ["waveCount", 3];
private _waveCooldown = _taskParams getOrDefault ["waveCooldown", 300];
private _minBlufor = _taskParams getOrDefault ["minBlufor", 1];
[_taskID, _defenseZone, _defendTime, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail,
_waveCount, _waveCooldown, _minBlufor] + _rewardTail
[_taskID, _defenseZone, _defendTime, _funds, _ratingFail, _ratingSuccess, _endSuccess, _endFail, _waveCount, _waveCooldown, _minBlufor] + _rewardTail
};
default {
["ERROR", format ["startTask: unknown task type '%1'.", _taskType]] call EFUNC(common,log);

View File

@ -29,7 +29,7 @@ docs/ Framework-level documentation
| Organization | Player organizations, membership, treasury, credit lines, shared assets, and fleet data. | `arma/client/addons/org` | `arma/server/addons/org` | `lib/models/src/org.rs`, `lib/services/src/org.rs` | `org:*`, `org:hot:*` |
| Phone | Contacts, messages, and email state. | `arma/client/addons/phone` | `arma/server/addons/phone` | `lib/models/src/phone.rs`, `lib/services/src/phone.rs` | `phone:*` |
| Store | Storefront entity setup, catalog hydration, checkout workflows, and checkout charging integration. | `arma/client/addons/store` | `arma/server/addons/store` | `lib/models/src/store.rs`, `lib/services/src/store.rs` | `store:checkout` |
| Task | Mission/task catalog, ownership, status, reward context, and task counters. | none | `arma/server/addons/task` | `lib/models/src/task.rs`, `lib/services/src/task.rs` | `task:*` |
| Task | Server-owned mission/task flows, catalog, ownership, status, participant tracking, rewards, and defuse counters. | none | `arma/server/addons/task` | `lib/models/src/task.rs`, `lib/services/src/task.rs` | `task:*` |
| Owned Garage | Organization or owner-scoped vehicle unlock storage. | via garage/org UI | server extension only | `lib/models/src/v_garage.rs`, `lib/services/src/v_garage.rs` | `owned:garage:*` |
| Owned Locker | Organization or owner-scoped arsenal unlock storage. | via locker/org UI | server extension only | `lib/models/src/v_locker.rs`, `lib/services/src/v_locker.rs` | `owned:locker:*` |

View File

@ -4,6 +4,20 @@ The task module stores transient mission task metadata for active server or
mission lifecycle workflows. SQF still owns Arma-only runtime state such as
objects and participants.
The server addon at `arma/server/addons/task` also owns task execution:
creating BIS tasks, registering task entities, tracking participants, binding
task ownership, applying player/org rewards, and clearing task state when a
task completes.
Runtime dependencies:
- `forge_server_extension`
- `forge_server_common`
- `forge_server_actor`
- `forge_server_bank`
- `forge_server_org`
- `forge_client_notifications`
## Data Model
Catalog entries are flexible JSON objects. The service normalizes these fields
@ -102,6 +116,78 @@ private _context = fromJSON (_result select 0);
The reward context contains `requesterUid` and `orgId`.
## Server Task Flows
The task addon provides these server-owned task flows:
- `attack`
- `defend`
- `defuse`
- `delivery`
- `destroy`
- `hostage`
- `hvt`
Use `forge_server_task_fnc_startTask` when creating tasks from modules,
mission scripts, or generated mission-manager content. It registers task
entities, creates the BIS task, stores the catalog entry, then dispatches
through `forge_server_task_fnc_handler`.
```sqf
[
"attack",
"compound_attack_01",
getPosATL leader1,
"Attack: East Compound",
"Eliminate all hostile forces.",
createHashMapFromArray [["targets", [unit1, unit2, unit3]]],
createHashMapFromArray [
["limitFail", 0],
["limitSuccess", 3],
["funds", 50000],
["ratingFail", -10],
["ratingSuccess", 20],
["timeLimit", 900]
],
0,
getPlayerUID player,
"script"
] call forge_server_task_fnc_startTask;
```
Use `forge_server_task_fnc_handler` directly when the task entities are already
registered and you want reputation gating plus ownership binding:
```sqf
[
"delivery",
["delivery_1", 1, 3, "delivery_zone", 250000, -75, 300, false, false, 900],
250,
getPlayerUID player
] call forge_server_task_fnc_handler;
```
Direct task function calls still work for mission-authored or server-owned
tasks, but they do not provide a requester UID. Ownership falls back to the
`default` org.
## Timer Semantics
Task time limits use `0` for no limit:
- attack `timeLimit`
- destroy `timeLimit`
- delivery `timeLimit`
- hostage `timeLimit`
- HVT `timeLimit`
Positive values are measured in seconds. Do not pass `-1` as a no-limit value;
the task runtime treats any non-zero task time limit as active.
Defuse IED timers are different. `iedTimer` must be greater than `0`, because
IEDs are expected to have an active countdown. The Eden defuse module defaults
to `300` seconds.
## Defuse Counter
```sqf