Consolidate shared Forge config and Arma cfg templates

- Add shared `bin/host/config.example.toml` and remove the old host example
- Split Arma config creation between `server.cfg` and `basic.cfg`
- Update docs and examples to reflect shared host-managed config
This commit is contained in:
Jacob Schmidt 2026-06-06 20:16:39 -05:00
parent c3531a5839
commit c676a9084e
26 changed files with 504 additions and 434 deletions

View File

@ -85,8 +85,23 @@ npm run host:build
```
The app reads the shared `config.toml` from the repo root during development, or
from the current/executable directory in packaged use. If no shared config exists,
it falls back to `bin/host/host.example.toml`; saving from the Settings view writes
the active shared `config.toml`. The shared file includes the host process sections
plus the `[server]` section used by ICOM and the `[surreal]` section used by the
Arma extension.
from the current/executable directory in packaged use. Start from
`bin/host/config.example.toml` when you want one shared config for Forge Host,
ICOM, and the Arma extension. Saving from the Settings view writes the active
shared `config.toml`.
The shared file includes:
- `[server]` for the ICOM hub bind address.
- `[surreal]` for the Arma extension SurrealDB connection.
- `[surrealdb]`, `[icom]`, and `[arma]` for Forge Host managed processes.
Forge Host manages the two Arma dedicated server config files separately:
`server.cfg` is launched with `-config` for server rules, missions, passwords,
and admins, while `basic.cfg` is launched with `-cfg` for network tuning. The
Arma Server settings view can create either file from `bin/host/server.example.cfg`
or `bin/host/basic.example.cfg`, then open it in the built-in editor.
Component-specific examples still exist for narrow deployments:
`bin/icom/config.example.toml` contains only `[server]`, and
`arma/server/extension/config.example.toml` contains only `[surreal]`.

View File

@ -1,12 +1,22 @@
# Forge Server Configuration
# Copy this file to config.toml and place it beside forge_server_x64.dll.
# Start SurrealDB before launching the Arma server, and keep these values
# aligned with the running database.
# Forge Arma server extension configuration.
#
# Copy this file to `config.toml` beside `forge_server_x64.dll`, or use the
# shared config generated from `bin/host/config.example.toml`.
#
# The Arma extension reads the [surreal] section. Extra host/ICOM sections in a
# shared config are ignored by the extension.
[surreal]
# SurrealDB HTTP endpoint. This must match the running SurrealDB --bind value.
endpoint = "127.0.0.1:8000"
# Namespace and database selected by the extension after connecting.
namespace = "forge"
database = "main"
# Local development defaults. Use a real password for shared or public servers.
username = "root"
password = "root"
# Initial connection timeout in milliseconds.
connect_timeout_ms = 5000

View File

@ -11,7 +11,10 @@ This extension build targets SurrealDB `3.x`.
Before starting the Arma server with Forge enabled:
1. Start SurrealDB.
2. Copy `config.example.toml` to `config.toml` beside `forge_server_x64.dll`.
2. Create Forge's `config.toml`. Copy `config.example.toml` beside
`forge_server_x64.dll` for an extension-only deployment, or use the shared
repo-root config from `bin/host/config.example.toml` when Forge Host manages
local services.
3. Match the `config.toml` endpoint, namespace, database, username, and password
to the running SurrealDB instance.
@ -23,7 +26,8 @@ normal gameplay.
- Register extension command groups for actor, bank, garage, locker, org,
phone, store, task, CAD, terrain, and transport systems.
- Load extension configuration from `@forge_server/config.toml`.
- Load extension configuration from `@forge_server/config.toml`, `config.toml`
in the working directory, or `config.toml` beside the extension DLL.
- Connect to SurrealDB and apply schema modules on startup.
- Keep SQF-facing command handlers thin while service crates own domain rules.
@ -31,14 +35,24 @@ normal gameplay.
```toml
[surreal]
# SurrealDB HTTP endpoint.
endpoint = "127.0.0.1:8000"
# Namespace and database selected after connecting.
namespace = "forge"
database = "main"
# Local development credentials.
username = "root"
password = "root"
# Initial connection timeout in milliseconds.
connect_timeout_ms = 5000
```
The extension reads only `[surreal]`. Extra sections from the shared Forge Host
config are ignored.
## Status
```sqf

View File

@ -1,16 +1,22 @@
# Forge Server Configuration
# Copy this file to config.toml and place it beside forge_server_x64.dll.
# Start SurrealDB before launching the Arma server, and keep these values
# aligned with the running database.
# Forge Arma server extension configuration.
#
# Copy this file to `config.toml` beside `forge_server_x64.dll`, or use the
# shared config generated from `bin/host/config.example.toml`.
#
# The Arma extension reads the [surreal] section. Extra host/ICOM sections in a
# shared config are ignored by the extension.
[surreal]
# SurrealDB HTTP endpoint. Use "127.0.0.1:8000" for a local server.
# SurrealDB HTTP endpoint. This must match the running SurrealDB --bind value.
endpoint = "127.0.0.1:8000"
# Namespace and database selected by the extension after connecting.
namespace = "forge"
database = "main"
# Optional authentication.
# Local development defaults. Use a real password for shared or public servers.
username = "root"
password = "root"
# Initial connection timeout in milliseconds.
connect_timeout_ms = 5000

View File

@ -1,4 +1,8 @@
// Basic Network Configuration
// Arma 3 dedicated server basic.cfg
//
// Forge Host launches this file with -cfg=basic.cfg. Use it for network and
// performance tuning. Gameplay rules, missions, passwords, and admin settings
// belong in server.cfg, which is launched with -config=server.cfg.
language = "English";
adapter = -1;
@ -6,12 +10,19 @@ adapter = -1;
Resolution_W = 0;
Resolution_H = 0;
Resolution_Bpp = 32;
// Bandwidth limits in bits per second. Raise MaxBandwidth for hosted servers
// with a reliable uplink; keep MinBandwidth conservative for local testing.
MinBandwidth = 131072;
MaxBandwidth = 10000000000;
// Message sizing and send cadence. Higher values can improve responsiveness on
// healthy networks but may increase bandwidth and CPU pressure.
MaxMsgSend = 128;
MaxSizeGuaranteed = 512;
MaxSizeNonguaranteed = 256;
MinErrorToSend = 0.001;
MinErrorToSendNear = 0.01;
// Prevent clients from uploading custom face/sound files to the server.
MaxCustomFileSize = 0;
Windowed = 0;

View File

@ -0,0 +1,71 @@
# Forge shared host configuration.
#
# Copy this file to `config.toml` at the repository root for development, or
# place it beside the packaged Forge Host executable for deployment. Forge Host
# writes the active config to that shared `config.toml`.
#
# This file intentionally contains:
# - [server], used by the standalone ICOM hub.
# - [surreal], used by the Arma server extension.
# - [surrealdb], [icom], and [arma], used by Forge Host to manage processes.
[server]
# ICOM hub bind address. Use "127.0.0.1" for same-machine testing only, or
# "0.0.0.0" when remote Arma servers need to connect through the firewall.
host = "0.0.0.0"
port = 9090
[surreal]
# SurrealDB HTTP endpoint used by the Arma server extension.
# Keep this aligned with [surrealdb].args --bind.
endpoint = "127.0.0.1:8000"
# Namespace and database selected after the extension connects.
namespace = "forge"
database = "main"
# Local development defaults. Use a real password for shared or public servers.
username = "root"
password = "root"
# How long the extension waits for the initial SurrealDB connection.
connect_timeout_ms = 5000
[surrealdb]
# Managed SurrealDB process. Forge Host can install SurrealDB into the user's
# local app data directory and will prefer that executable when available.
enabled = true
command = "surreal"
# Persistent local database using RocksDB. The bind address must match
# [surreal].endpoint and health_host/health_port.
args = ["start", "--user", "root", "--pass", "root", "--bind", "127.0.0.1:8000", "rocksdb://forge.db"]
# Relative paths are resolved from the active config.toml directory.
working_dir = "arma/server/surrealdb"
health_host = "127.0.0.1"
health_port = 8000
[icom]
# Managed ICOM hub process. Build it first with:
# cargo build --release -p forge-icom
enabled = true
command = "target/release/forge-icom.exe"
args = []
working_dir = "."
health_host = "127.0.0.1"
health_port = 9090
[arma]
# Managed Arma 3 dedicated server process. This is disabled by default because
# every installation needs its own arma3server_x64.exe path and server config.
enabled = false
command = "arma3server_x64.exe"
# Forge Host can create/edit both Arma config files:
# - -config=server.cfg controls game server rules, missions, passwords, and admins.
# - -cfg=basic.cfg controls dedicated server network tuning.
args = ["-config=server.cfg", "-cfg=basic.cfg", "-port=2302", "-profiles=serverprofiles", "-name=server", "-noBattlEye"]
working_dir = ""
health_host = "127.0.0.1"
health_port = 2302

View File

@ -1,35 +0,0 @@
[server]
host = "0.0.0.0"
port = 9090
[surreal]
endpoint = "127.0.0.1:8000"
namespace = "forge"
database = "main"
username = "root"
password = "root"
connect_timeout_ms = 5000
[surrealdb]
enabled = true
command = "surreal"
args = ["start", "--user", "root", "--pass", "root", "--bind", "127.0.0.1:8000", "rocksdb://forge.db"]
working_dir = "../../arma/server/surrealdb"
health_host = "127.0.0.1"
health_port = 8000
[icom]
enabled = true
command = "target/release/forge-icom.exe"
args = []
working_dir = "../.."
health_host = "127.0.0.1"
health_port = 9090
[arma]
enabled = false
command = "arma3server_x64.exe"
args = ["-port=2302", "-profiles=serverprofiles", "-name=server", "-noBattlEye"]
working_dir = ""
health_host = "127.0.0.1"
health_port = 2302

View File

@ -202,7 +202,10 @@ impl AppState {
fn load_example_config() -> Result<HostConfig, String> {
if let Some(repo_root) = find_repo_root() {
let example = repo_root.join("bin").join("host").join("host.example.toml");
let example = repo_root
.join("bin")
.join("host")
.join("config.example.toml");
return load_config(&example);
}
@ -426,13 +429,7 @@ fn create_arma_server_config(path: String, template: String) -> Result<(), Strin
.map_err(|error| format!("Failed to create config directory: {error}"))?;
}
let template_path = resolve_arma_config_template(&template)?;
let content = fs::read_to_string(&template_path).map_err(|error| {
format!(
"Failed to read config template {}: {error}",
template_path.display()
)
})?;
let content = arma_config_template(&template)?;
fs::write(&path, content).map_err(|error| format!("Failed to write server config: {error}"))
}
@ -690,32 +687,12 @@ fn ensure_surreal_on_user_path(_install_dir: &Path) -> Result<(), String> {
Ok(())
}
fn resolve_arma_config_template(template: &str) -> Result<PathBuf, String> {
let file_name = match template {
"basic" => "basic.example.cfg",
"server" | "" => "server.example.cfg",
other => return Err(format!("Unknown Arma config template '{other}'")),
};
let mut candidates = Vec::new();
if let Some(repo_root) = find_repo_root() {
candidates.push(repo_root.join("bin").join("host").join(file_name));
fn arma_config_template(template: &str) -> Result<&'static str, String> {
match template {
"basic" => Ok(include_str!("../../basic.example.cfg")),
"server" | "" => Ok(include_str!("../../server.example.cfg")),
other => Err(format!("Unknown Arma config template '{other}'")),
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
candidates.push(dir.join(file_name));
candidates.push(dir.join("bin").join("host").join(file_name));
}
}
if let Ok(cwd) = std::env::current_dir() {
candidates.push(cwd.join(file_name));
candidates.push(cwd.join("bin").join("host").join(file_name));
}
candidates
.into_iter()
.find(|path| path.exists())
.ok_or_else(|| format!("Unable to find Arma config template '{file_name}'"))
}
fn service_config(kind: ServiceKind, config: &HostConfig) -> ServiceConfig {
@ -971,7 +948,7 @@ fn default_surrealdb_service() -> ServiceConfig {
"127.0.0.1:8000".to_string(),
"rocksdb://forge.db".to_string(),
],
working_dir: "../../arma/server/surrealdb".to_string(),
working_dir: "arma/server/surrealdb".to_string(),
health_host: "127.0.0.1".to_string(),
health_port: 8000,
}
@ -982,7 +959,7 @@ fn default_icom_service() -> ServiceConfig {
enabled: true,
command: "target/release/forge-icom.exe".to_string(),
args: Vec::new(),
working_dir: "../..".to_string(),
working_dir: ".".to_string(),
health_host: "127.0.0.1".to_string(),
health_port: 9090,
}
@ -993,8 +970,10 @@ fn default_arma_service() -> ServiceConfig {
enabled: false,
command: "arma3server_x64.exe".to_string(),
args: vec![
"-config=server.cfg".to_string(),
"-cfg=basic.cfg".to_string(),
"-port=2302".to_string(),
"-profiles=profiles".to_string(),
"-profiles=serverprofiles".to_string(),
"-name=server".to_string(),
"-noBattlEye".to_string(),
],

View File

@ -18,7 +18,6 @@ let activeSettingsService = "surrealdb";
let configDirty = false;
let activeEditorPath = "";
let surrealInstallInfo = null;
let selectedArmaConfigTemplate = "server";
const views = {
overview: document.getElementById("overviewView"),
@ -340,20 +339,24 @@ function renderSettings(force = false) {
form.querySelectorAll("[data-create-server-config]").forEach((button) => {
button.addEventListener("click", () => {
const template =
document.querySelector("[data-config-template]")?.value ||
"server";
runAction(() => createArmaServerConfig(template));
runAction(() =>
createArmaServerConfig(
button.dataset.configTemplate || "server",
button.dataset.configTarget || "config",
button.dataset.defaultFile || "server.cfg",
),
);
});
});
form.querySelectorAll("[data-edit-server-config]").forEach((button) => {
button.addEventListener("click", () => {
const target = button.dataset.configTarget || "config";
const path = document
.querySelector('[data-arg-key="config"]')
.querySelector(`[data-arg-key="${target}"]`)
?.value.trim();
if (!path) {
alert("Select or create a server config first.");
alert("Select or create a config first.");
return;
}
runAction(() => openConfigEditor(resolveArmaConfigPath(path)));
@ -373,12 +376,6 @@ function renderSettings(force = false) {
});
});
form.querySelectorAll("[data-config-template]").forEach((select) => {
select.addEventListener("change", () => {
selectedArmaConfigTemplate = select.value;
});
});
if (name === "surrealdb" && !surrealInstallInfo) {
loadSurrealInstallInfo();
} else if (
@ -716,7 +713,7 @@ function renderArmaSettings(config) {
<label>Launch Options</label>
<div class="args-list arma-args" data-service="arma" data-field="args">
${serverConfigInput(parsed.config)}
${serverConfigTemplateInput()}
${basicConfigInput(parsed.cfg)}
${argumentInput("Port", "port", parsed.port)}
${argumentInput("Profiles", "profiles", parsed.profiles)}
${argumentInput("Name", "name", parsed.name)}
@ -766,27 +763,45 @@ function argumentInput(label, key, value, className = "") {
}
function serverConfigInput(value) {
return `
<div class="arg-field full">
<label>Server Config</label>
<div class="input-action action-three">
<input class="arg-input" data-arg-key="config" value="${escapeAttr(value)}" placeholder="server.cfg" />
<button class="icon-button" type="button" data-picker="config" data-service-target="arma" data-target='[data-arg-key="config"]' title="Select config" aria-label="Select config"><span class="svg-icon icon-browse"></span></button>
<button class="icon-button" type="button" data-create-server-config title="Create default config" aria-label="Create default config"><span class="svg-icon icon-new"></span></button>
<button class="icon-button" type="button" data-edit-server-config title="Edit config" aria-label="Edit config"><span class="svg-icon icon-edit"></span></button>
</div>
</div>
`;
return configFileInput({
label: "Server Config",
key: "config",
value,
placeholder: "server.cfg",
template: "server",
defaultFile: "server.cfg",
});
}
function serverConfigTemplateInput() {
function basicConfigInput(value) {
return configFileInput({
label: "Basic Network Config",
key: "cfg",
value,
placeholder: "basic.cfg",
template: "basic",
defaultFile: "basic.cfg",
});
}
function configFileInput({
label,
key,
value,
placeholder,
template,
defaultFile,
}) {
const target = `[data-arg-key="${key}"]`;
return `
<div class="arg-field">
<label>Config Template</label>
<select data-config-template>
<option value="server" ${selectedAttr(selectedArmaConfigTemplate === "server")}>Server</option>
<option value="basic" ${selectedAttr(selectedArmaConfigTemplate === "basic")}>Basic Network</option>
</select>
<div class="arg-field full">
<label>${label}</label>
<div class="input-action action-three">
<input class="arg-input" data-arg-key="${key}" value="${escapeAttr(value)}" placeholder="${placeholder}" />
<button class="icon-button" type="button" data-picker="config" data-service-target="arma" data-target='${target}' title="Select config" aria-label="Select config"><span class="svg-icon icon-browse"></span></button>
<button class="icon-button" type="button" data-create-server-config data-config-template="${template}" data-config-target="${key}" data-default-file="${defaultFile}" title="Create default config" aria-label="Create default config"><span class="svg-icon icon-new"></span></button>
<button class="icon-button" type="button" data-edit-server-config data-config-target="${key}" title="Edit config" aria-label="Edit config"><span class="svg-icon icon-edit"></span></button>
</div>
</div>
`;
}
@ -819,6 +834,7 @@ function readArgumentFields(service, container) {
if (service === "arma") {
const args = [];
if (value("config")) args.push(`-config=${value("config")}`);
if (value("cfg")) args.push(`-cfg=${value("cfg")}`);
if (value("port")) args.push(`-port=${value("port")}`);
if (value("profiles")) args.push(`-profiles=${value("profiles")}`);
if (value("name")) args.push(`-name=${value("name")}`);
@ -871,6 +887,7 @@ function parseSurrealArgs(args) {
function parseArmaArgs(args) {
const parsed = {
config: "",
cfg: "",
port: "",
profiles: "",
name: "",
@ -888,6 +905,8 @@ function parseArmaArgs(args) {
if (lowerArg.startsWith("-config=")) {
parsed.config = arg.slice("-config=".length);
} else if (lowerArg.startsWith("-cfg=")) {
parsed.cfg = arg.slice("-cfg=".length);
} else if (lowerArg.startsWith("-port=")) {
parsed.port = arg.slice("-port=".length);
} else if (lowerArg.startsWith("-profiles=")) {
@ -936,7 +955,10 @@ async function pickPath(kind, targetSelector, service) {
const input = document.querySelector(targetSelector);
if (!input) return;
const finalPath = normalizePickedExecutable(service, selected);
const finalPath =
kind === "file"
? normalizePickedExecutable(service, selected)
: selected;
input.value = finalPath;
if (kind === "file" && targetSelector.includes('data-field="command"')) {
@ -966,7 +988,7 @@ function pickerFilters(kind) {
return undefined;
}
async function createArmaServerConfig(template) {
async function createArmaServerConfig(template, targetKey, defaultFile) {
const save = window.__TAURI__?.dialog?.save;
if (!save) {
throw new Error(
@ -977,7 +999,9 @@ async function createArmaServerConfig(template) {
const workingDir = document
.querySelector('[data-service="arma"][data-field="working_dir"]')
?.value.trim();
const defaultPath = workingDir ? `${workingDir}\\server.cfg` : "server.cfg";
const defaultPath = workingDir
? `${workingDir}\\${defaultFile}`
: defaultFile;
const selected = await save({
defaultPath,
filters: [{ name: "Arma Server Config", extensions: ["cfg"] }],
@ -988,7 +1012,7 @@ async function createArmaServerConfig(template) {
path: selected,
template,
});
const input = document.querySelector('[data-arg-key="config"]');
const input = document.querySelector(`[data-arg-key="${targetKey}"]`);
if (input) {
input.value = selected;
configDirty = true;
@ -1075,10 +1099,6 @@ function escapeAttr(value) {
return escapeHtml(value).replaceAll('"', "&quot;");
}
function selectedAttr(selected) {
return selected ? "selected" : "";
}
refresh();
syncRefreshTimer();
loadAppVersion();

View File

@ -20,18 +20,21 @@ The ICOM server can be configured using a `config.toml` file. Create one from th
cp bin/icom/config.example.toml config.toml
```
Place `config.toml` in the same directory as the `forge-icom` executable or in the current working directory.
Place `config.toml` in the same directory as the `forge-icom` executable or in
the current working directory. If Forge Host manages the hub, use the shared
repo-root `config.toml` created from `bin/host/config.example.toml`; it includes
the same `[server]` section and the ICOM hub ignores unrelated sections.
### Configuration Options
```toml
[server]
# Host to bind to
# "0.0.0.0" = All interfaces (allows remote connections)
# "127.0.0.1" = Localhost only
# TCP address to bind.
# "0.0.0.0" = all interfaces, allowing remote servers.
# "127.0.0.1" = localhost only.
host = "0.0.0.0"
# Port to listen on
# TCP port used by extension `icom:connect`.
port = 9090
```

View File

@ -1,22 +1,17 @@
# Forge ICOM Server Configuration
# Copy this file to config.toml and modify as needed
# Place this file in the same directory as the forge-icom executable
# Forge ICOM hub configuration.
#
# Copy this file to `config.toml` beside `forge-icom.exe`, into the working
# directory used to launch the hub, or use the shared repo-root `config.toml`
# generated from `bin/host/config.example.toml`.
#
# The ICOM loader only reads the [server] section. Extra sections in the shared
# host config are ignored by the standalone hub.
[server]
# Host to bind to
# - "0.0.0.0" = All network interfaces (default, allows remote connections)
# - "127.0.0.1" = Localhost only (for local testing)
# TCP address the ICOM hub binds to.
# - "0.0.0.0" listens on all network interfaces and allows remote servers.
# - "127.0.0.1" listens only on localhost for same-machine testing.
host = "0.0.0.0"
# Port to listen on
# TCP port accepted by extension `icom:connect` calls.
port = 9090
# Example configurations for different environments:
# Development (localhost only)
# host = "127.0.0.1"
# port = 9090
# Production (all interfaces, custom port)
# host = "0.0.0.0"
# port = 19090

View File

@ -130,8 +130,11 @@ server addon fnc_extCall
## Configuration
The server extension reads `config.toml` next to the extension DLL. The current
persistence section is:
The server extension reads `config.toml` from the server working directory,
`@forge_server/config.toml`, or beside the extension DLL. When Forge Host is
used during development, the repo-root `config.toml` can be shared across Forge
Host, ICOM, and the extension. The extension reads the `[surreal]` section and
ignores the host-only sections. The current persistence section is:
```toml
[surreal]
@ -143,12 +146,20 @@ password = "root"
connect_timeout_ms = 5000
```
`config.toml` is a launch prerequisite for server owners and developers. The
file must exist beside `forge_server_x64.dll`, and SurrealDB must already be
running at the configured endpoint before starting a Forge-enabled dedicated
server or local multiplayer test. Clients and mission designers do not run this
configuration unless they are hosting locally, but the server they connect to
must have it in place.
`config.toml` is a launch prerequisite for server owners and developers. Use
`arma/server/extension/config.example.toml` for an extension-only config, or
`bin/host/config.example.toml` for the shared host-managed config. SurrealDB must
already be running at the configured endpoint before starting a Forge-enabled
dedicated server or local multiplayer test. Clients and mission designers do not
run this configuration unless they are hosting locally, but the server they
connect to must have it in place.
Arma's own dedicated server files remain separate from Forge's TOML config.
Launch `server.cfg` with `-config` for server rules, mission rotation, passwords,
and admin settings. Launch `basic.cfg` with `-cfg` for network and performance
tuning. Forge Host exposes both paths in the Arma Server settings view and can
create them from `bin/host/server.example.cfg` and
`bin/host/basic.example.cfg`.
For install links and role-based setup guidance, see
[SurrealDB Setup](./surrealdb-setup.md).

View File

@ -20,12 +20,12 @@ through `arma/server/extension/src/icom.rs`.
## Components
| Component | Path | Role |
| --- | --- | --- |
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. |
| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. |
| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. |
| Component | Path | Role |
| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. |
| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. |
| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. |
## Build and Run the Hub
@ -46,11 +46,17 @@ The default bind address is `0.0.0.0:9090`.
## Hub Configuration
Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom`
executable or into the working directory used to launch it.
executable or into the working directory used to launch it. If you are using
Forge Host, prefer the shared repo-root config from `bin/host/config.example.toml`;
it contains the same `[server]` section and the standalone ICOM hub ignores the
extra host and extension sections.
```toml
[server]
# Listen on all interfaces so remote Arma servers can connect.
host = "0.0.0.0"
# TCP port used by extension `icom:connect`.
port = 9090
```
@ -62,11 +68,11 @@ layer.
ICOM commands are exposed through the `icom` command group in `forge_server`.
| Command | Arguments | Returns |
| --- | --- | --- |
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| Command | Arguments | Returns |
| ----------------- | ------------------------------------------ | ----------------------------------------------------- |
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
The current extension connects when `icom:connect` is called. Start the ICOM hub
first, then connect each Arma server with a unique `server_id`.
@ -144,8 +150,8 @@ registration payload:
```json
{
"type": "register",
"server_id": "server_1"
"type": "register",
"server_id": "server_1"
}
```
@ -153,12 +159,12 @@ Targeted events use `type: "event"`:
```json
{
"type": "event",
"target_server": "server_2",
"event_name": "supply_drop",
"data": {
"coords": [1234, 5678, 0]
}
"type": "event",
"target_server": "server_2",
"event_name": "supply_drop",
"data": {
"coords": [1234, 5678, 0]
}
}
```

View File

@ -28,11 +28,18 @@ Before starting a Forge-enabled dedicated server or local multiplayer test,
server owners and developers must:
1. Start SurrealDB.
2. Place `config.toml` beside `forge_server_x64.dll`.
2. Place a Forge `config.toml` where the extension can find it. Use
`arma/server/extension/config.example.toml` for an extension-only config, or
use `bin/host/config.example.toml` at the repo root when Forge Host should
manage SurrealDB, ICOM, and the Arma server.
3. Keep the `config.toml` SurrealDB endpoint, namespace, database, username,
and password aligned with the running database.
4. Load `@forge_mod` with the server's normal mod list and `@forge_server` as
a server-only mod.
5. Start Arma with both dedicated server config files: `server.cfg` through
`-config` for server rules and mission rotation, and `basic.cfg` through
`-cfg` for network tuning. Forge Host can create both files from
`bin/host/server.example.cfg` and `bin/host/basic.example.cfg`.
Mission designers and players do not need to run SurrealDB unless they are
hosting locally, but they do need `@forge_mod` for Forge mission config classes.

View File

@ -9,8 +9,10 @@ comes down to running a reachable database and matching the Forge config.
Before launching an Arma server or local multiplayer test with Forge enabled:
1. Start SurrealDB and confirm it is listening on the endpoint Forge will use.
2. Copy `arma/server/extension/config.example.toml` to `config.toml` beside
`forge_server_x64.dll`.
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
root when Forge Host manages local services, or copy
`arma/server/extension/config.example.toml` beside `forge_server_x64.dll`
for an extension-only deployment.
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
database, username, and password.
@ -70,9 +72,12 @@ surreal start --user root --pass root --bind 127.0.0.1:8000 rocksdb://forge.db
`root`/`root` is only the local development default. For a public or shared
server, set a real password and keep `config.toml` aligned.
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
`forge_server_x64.dll` and keep the values aligned with the database you
started:
Then create Forge's `config.toml` and keep the values aligned with the database
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
to the repo root as `config.toml`; the host will use the same file for managed
processes, ICOM, and extension settings. If you only need the Arma extension
config, copy `arma/server/extension/config.example.toml` next to
`forge_server_x64.dll`.
```toml
[surreal]

View File

@ -9,8 +9,10 @@ comes down to running a reachable database and matching the Forge config.
Before launching an Arma server or local multiplayer test with Forge enabled:
1. Start SurrealDB and confirm it is listening on the endpoint Forge will use.
2. Copy `arma/server/extension/config.example.toml` to `config.toml` beside
`forge_server_x64.dll`.
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
root when Forge Host manages local services, or copy
`arma/server/extension/config.example.toml` beside `forge_server_x64.dll`
for an extension-only deployment.
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
database, username, and password.
@ -70,9 +72,12 @@ surreal start --user root --pass root --bind 127.0.0.1:8000 rocksdb://forge.db
`root`/`root` is only the local development default. For a public or shared
server, set a real password and keep `config.toml` aligned.
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
`forge_server_x64.dll` and keep the values aligned with the database you
started:
Then create Forge's `config.toml` and keep the values aligned with the database
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
to the repo root as `config.toml`; the host will use the same file for managed
processes, ICOM, and extension settings. If you only need the Arma extension
config, copy `arma/server/extension/config.example.toml` next to
`forge_server_x64.dll`.
```toml
[surreal]

View File

@ -129,8 +129,11 @@ server addon fnc_extCall
## Configuration
The server extension reads `config.toml` next to the extension DLL. The current
persistence section is:
The server extension reads `config.toml` from the server working directory,
`@forge_server/config.toml`, or beside the extension DLL. When Forge Host is
used during development, the repo-root `config.toml` can be shared across Forge
Host, ICOM, and the extension. The extension reads the `[surreal]` section and
ignores the host-only sections. The current persistence section is:
```toml
[surreal]
@ -142,12 +145,20 @@ password = "root"
connect_timeout_ms = 5000
```
`config.toml` is a launch prerequisite for server owners and developers. The
file must exist beside `forge_server_x64.dll`, and SurrealDB must already be
running at the configured endpoint before starting a Forge-enabled dedicated
server or local multiplayer test. Clients and mission designers do not run this
configuration unless they are hosting locally, but the server they connect to
must have it in place.
`config.toml` is a launch prerequisite for server owners and developers. Use
`arma/server/extension/config.example.toml` for an extension-only config, or
`bin/host/config.example.toml` for the shared host-managed config. SurrealDB must
already be running at the configured endpoint before starting a Forge-enabled
dedicated server or local multiplayer test. Clients and mission designers do not
run this configuration unless they are hosting locally, but the server they
connect to must have it in place.
Arma's own dedicated server files remain separate from Forge's TOML config.
Launch `server.cfg` with `-config` for server rules, mission rotation, passwords,
and admin settings. Launch `basic.cfg` with `-cfg` for network and performance
tuning. Forge Host exposes both paths in the Arma Server settings view and can
create them from `bin/host/server.example.cfg` and
`bin/host/basic.example.cfg`.
For install links and role-based setup guidance, see
[SurrealDB Setup](/getting-started/surrealdb-setup).

View File

@ -763,14 +763,26 @@ CAD dispatcher-requested generation.
The optional framework mission setup UI lets the setup operator choose runtime
tuning such as opposing faction, mission cap, interval, location cooldown,
reward ranges, reputation ranges, penalty ranges, time limits, and a generator
provider preference. It does not enable or disable generated missions; use the
CBA setting for that policy.
provider preference. It also exposes service pricing for medical spawn, heal,
repair, rearm, refuel, and transport defaults. It does not enable or disable
generated missions; use the CBA setting for that policy.
Task time limits can be disabled from the setup UI by turning off the task
timer. That stores `timeLimitMin = 0` and `timeLimitMax = 0`, which generated
tasks treat as no timer. Positive min/max values enable task timers and are
rolled in seconds.
If mission setup is enabled, the mission manager waits until the setup operator
applies settings. Cancel, X, and Escape apply default values from CBA, mission
parameters, and `CfgMissions`. There is no timeout that auto-applies defaults.
After settings are applied, the setup UI cannot be reopened.
Service pricing fallback values live in mission-local `CfgServicePricing.hpp`.
Mission `Params` with matching names, such as `medicalHealCost`,
`serviceRepairCost`, `serviceRearmCost`, `fuelCost`, `transportBaseFare`, and
`transportPricePerKm`, are read before the setup UI hydrates so mission makers
can keep a non-UI backup.
The setup UI stores the provider preference as `builtin` or `custom`. CAD/manual
generated task requests use the task provider registry and route to the selected
provider. Custom generators should register a provider or create CAD-visible

View File

@ -8,8 +8,10 @@ description: "Forge uses SurrealDB for durable storage. The Rust server extensio
Before launching an Arma server or local multiplayer test with Forge enabled:
1. Start SurrealDB and confirm it is listening on the endpoint Forge will use.
2. Copy `arma/server/extension/config.example.toml` to `config.toml` beside
`forge_server_x64.dll`.
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
root when Forge Host manages local services, or copy
`arma/server/extension/config.example.toml` beside `forge_server_x64.dll`
for an extension-only deployment.
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
database, username, and password.
@ -35,45 +37,12 @@ Official SurrealDB resources:
- [SurrealDB install page](https://surrealdb.com/install)
- [SurrealDB CLI `start` reference](https://surrealdb.com/docs/surrealdb/cli/start)
Forge also includes helper scripts under `arma/server/surrealdb`:
```powershell
cd arma/server/surrealdb
.\UpdateMe.bat
.\RunMe.bat
```
On Windows, `UpdateMe.bat` is a wrapper around `UpdateSurrealDB.ps1`. By
default it installs or updates to the newest compatible SurrealDB 3.x release
reported by SurrealDB's official version endpoint. You can also pin an exact
release:
```powershell
.\UpdateMe.bat v3.1.2
.\UpdateSurrealDB.ps1 -Version v3.1.2
```
To intentionally install the latest stable SurrealDB release regardless of
major version, run:
```powershell
.\UpdateMe.bat latest
```
The `latest` option prompts for confirmation because a newer SurrealDB major
version can require rebuilding the Forge server extension from source with a
compatible `surrealdb` Rust crate.
`RunMe.bat` is a wrapper around `RunSurrealDB.ps1`, which starts the local
Forge database with the same defaults shown below.
On Linux or macOS:
```bash
cd arma/server/surrealdb
./setup.sh
./run.sh
```
Forge Host includes the recommended local setup path. Open the SurrealDB view
to install or update SurrealDB, configure the bind address and database path,
and start or stop the local database. Enter `3` to install the latest compatible
SurrealDB 3.x release, enter an exact version such as `v3.1.2` to pin a
release, or enter `latest` only after confirming compatibility with the Forge
server extension.
Install SurrealDB with the official method for your platform:
@ -102,9 +71,12 @@ surreal start --user root --pass root --bind 127.0.0.1:8000 rocksdb://forge.db
`root`/`root` is only the local development default. For a public or shared
server, set a real password and keep `config.toml` aligned.
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
`forge_server_x64.dll` and keep the values aligned with the database you
started:
Then create Forge's `config.toml` and keep the values aligned with the database
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
to the repo root as `config.toml`; the host will use the same file for managed
processes, ICOM, and extension settings. If you only need the Arma extension
config, copy `arma/server/extension/config.example.toml` next to
`forge_server_x64.dll`.
```toml
[surreal]

View File

@ -1,158 +0,0 @@
---
title: "Git Workflow"
description: "This repository uses `master` as the clean framework branch. Mission folders are kept off `master` so the framework can be versioned without bundling local test missions or playable mission copies."
---
## Workflow Helper
The repository includes a small helper for the common branch checks and branch
switching commands:
```powershell
npm run workflow -- status
npm run workflow -- doctor
npm run workflow -- switch dev
npm run workflow -- switch missions
npm run workflow -- start-feature cad-task-request
npm run workflow -- release-check
```
The helper refuses branch switches and feature branch creation when the working
tree has uncommitted changes. Use the manual Git commands below when you need
more control.
## Branch Roles
- `master`: framework source, addon code, Rust extension code, docs, tooling,
and release tags.
- `missions/local-mission-copies`: local mission folders used for testing and
mission iteration. This branch is not pushed unless intentionally needed.
- `archive/pre-v0.1-history`: read-only archive of the previous full `master`
history before the `v0.1.0` baseline cleanup.
## Daily Framework Work
Start from the clean framework branch.
```powershell
git switch master
git pull
git status --short --branch
```
Create a short-lived feature branch for framework work.
```powershell
git switch -c feature/garage-marker-selection
```
Make the change, validate it, then commit.
```powershell
git status --short --branch
git add arma/client/addons/garage/functions/fnc_initContextService.sqf
git commit -m "Improve garage spawn marker selection"
```
Merge the work back into `master`. Squash merges keep future `master` history
compact.
```powershell
git switch master
git merge --squash feature/garage-marker-selection
git commit -m "Improve garage spawn marker selection"
git push
```
Remove the local feature branch when it is no longer needed.
```powershell
git branch -D feature/garage-marker-selection
```
## Mission Work
Switch to the local mission branch before editing mission folders.
```powershell
git switch missions/local-mission-copies
git status --short --branch
```
Mission folders currently tracked on that branch:
```text
arma/forge_framework.Malden
arma/forge_pmc_simulator.Tanoa
arma/forge_pmc_simulator_v2.Tanoa
```
Commit mission-only changes on the mission branch.
```powershell
git add arma/forge_pmc_simulator.Tanoa
git commit -m "Update PMC simulator mission setup"
```
Do not merge the mission branch into `master`. If a mission change becomes
framework code, copy only the reusable files or logic onto a framework feature
branch created from `master`.
Example:
```powershell
git switch master
git switch -c feature/cad-on-demand-task-request
# Bring over only the framework files needed from the mission branch.
git checkout missions/local-mission-copies -- arma/client/addons/cad/functions/fnc_initUIBridge.sqf
git checkout missions/local-mission-copies -- arma/server/addons/cad/XEH_preInit.sqf
git add arma/client/addons/cad/functions/fnc_initUIBridge.sqf arma/server/addons/cad/XEH_preInit.sqf
git commit -m "Add CAD on-demand mission task request bridge"
```
## Release Versioning
Use tags to mark framework releases.
Version guideline:
- Patch, such as `v0.1.1`: fixes and small compatible changes.
- Minor, such as `v0.2.0`: new modules or features.
- Major, such as `v1.0.0`: stable release line or breaking changes.
Create a release tag from `master`.
```powershell
git switch master
git pull
git status --short --branch
git tag -a v0.1.1 -m "v0.1.1"
git push origin master
git push origin v0.1.1
```
## Safety Checks
Before committing on `master`, check that no mission folders are staged.
```powershell
git status --short --branch
```
On `master`, these paths should not appear:
```text
arma/forge_framework.Malden
arma/forge_pmc_simulator.Tanoa
arma/forge_pmc_simulator_v2.Tanoa
```
If mission files appear while on `master`, stop and switch to the mission
branch before continuing.
```powershell
git switch missions/local-mission-copies
```

View File

@ -19,12 +19,12 @@ through `arma/server/extension/src/icom.rs`.
## Components
| Component | Path | Role |
| --- | --- | --- |
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. |
| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. |
| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. |
| Component | Path | Role |
| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. |
| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. |
| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. |
## Build and Run the Hub
@ -45,11 +45,17 @@ The default bind address is `0.0.0.0:9090`.
## Hub Configuration
Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom`
executable or into the working directory used to launch it.
executable or into the working directory used to launch it. If you are using
Forge Host, prefer the shared repo-root config from `bin/host/config.example.toml`;
it contains the same `[server]` section and the standalone ICOM hub ignores the
extra host and extension sections.
```toml
[server]
# Listen on all interfaces so remote Arma servers can connect.
host = "0.0.0.0"
# TCP port used by extension `icom:connect`.
port = 9090
```
@ -61,11 +67,11 @@ layer.
ICOM commands are exposed through the `icom` command group in `forge_server`.
| Command | Arguments | Returns |
| --- | --- | --- |
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| Command | Arguments | Returns |
| ----------------- | ------------------------------------------ | ----------------------------------------------------- |
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
The current extension connects when `icom:connect` is called. Start the ICOM hub
first, then connect each Arma server with a unique `server_id`.
@ -143,8 +149,8 @@ registration payload:
```json
{
"type": "register",
"server_id": "server_1"
"type": "register",
"server_id": "server_1"
}
```
@ -152,12 +158,12 @@ Targeted events use `type: "event"`:
```json
{
"type": "event",
"target_server": "server_2",
"event_name": "supply_drop",
"data": {
"coords": [1234, 5678, 0]
}
"type": "event",
"target_server": "server_2",
"event_name": "supply_drop",
"data": {
"coords": [1234, 5678, 0]
}
}
```

View File

@ -1,6 +1,6 @@
---
title: "Store Usage Guide"
description: "The store module processes checkout requests. It charges a payment source and grants purchased items to the player locker, virtual arsenal locker, virtual garage unlocks, and immediate unit spawn grants."
description: "The store module processes checkout requests. It charges a payment source and grants purchased items to the player locker, virtual arsenal locker, and virtual garage unlocks. Unit purchases are fulfilled as immediate server-side spawn grants at discovered `unit_spawn` markers."
---
## Server SQF Module
@ -9,13 +9,10 @@ The server addon uses two long-lived module objects:
- `StorefrontStore` is the storefront workflow facade. It builds hydrate
payloads, validates checkout requests, calls the Rust `store:checkout`
command, syncs UI patches, asks related module stores to save hot state, and
spawns purchased units at discovered `unit_spawn` markers after the backend
charge succeeds.
command, syncs UI patches, and asks related module stores to save hot state.
- `StoreCatalogService` scans configured item and vehicle categories, builds
catalog responses, resolves checkout entries, and calculates authoritative
prices. It also applies the optional mission `CfgStore` filter and overrides
before payloads or checkout validation use catalog entries.
prices.
Editor-placed store entities are initialized by `fnc_initStore` during store
post-init. The initializer matches non-null mission namespace objects whose
@ -35,6 +32,26 @@ Include `CfgStore.hpp` from `description.ext`:
```cpp
class CfgStore {
mode = "allowlist"; // dynamic, allowlist, or denylist
modMode = "dynamic"; // dynamic, allowlist, or denylist
mods[] = {}; // ModSources child class names used when modMode is not dynamic
class ModSources {
class rhs {
patches[] = {"rhs_main", "rhsusf_main"};
addons[] = {"rhs_", "rhsusf_", "rhsgref_", "rhsafrf_"};
prefixes[] = {"rhs_", "rhsusf_", "rhsgref_", "rhsafrf_"};
contains[] = {"rhs_", "rhsusf_", "rhsgref_", "rhsafrf_"};
dlcs[] = {};
};
class ace3 {
patches[] = {"ace_main"};
addons[] = {"ace_"};
prefixes[] = {"ace_"};
contains[] = {"ace_"};
dlcs[] = {};
};
};
class Categories {
primary[] = {"arifle_MX_F", "arifle_MXC_F"};
@ -55,9 +72,40 @@ class CfgStore {
`dynamic` keeps the full generated catalog. `allowlist` only shows classnames
listed for each category. `denylist` removes listed classnames. Overrides are
server-side and are used by both the UI payload and checkout validation.
`units[]` follows the same filter behavior and is fulfilled as an immediate
server-side unit spawn at a discovered `unit_spawn` marker after checkout
succeeds.
`units[]` uses the same filter behavior as every other category.
`modMode` applies before category filtering. `dynamic` means no mod-source
filtering. `allowlist` only keeps generated entries that match one of the
configured `mods[]`; `denylist` removes matching entries. Each `ModSources`
child can define `patches[]` to detect whether the mod is loaded, `addons[]`
for exact config source addon/source mod names, `prefixes[]` for classname,
source addon, or source mod prefixes, `contains[]` for classname/source
metadata tokens that can appear anywhere, and `dlcs[]` for DLC/source/author
labels used by Creator DLC content. If a mod source defines no patches, it is
treated as available and only the source/prefix/contains/DLC checks are used.
For example, to show only RHS-sourced generated inventory:
```cpp
modMode = "allowlist";
mods[] = {"rhs"};
```
The matching `class rhs` must exist under `ModSources`. Category `mode` is still
applied afterward, so leave `mode = "dynamic"` if the mod filter should be the
only inventory filter.
For Creator DLCs such as RF or WS, prefer both prefixes and DLC labels:
```cpp
class rf {
patches[] = {};
addons[] = {"lxrf_", "rf_"};
prefixes[] = {"lxrf_", "rf_"};
contains[] = {"lxrf", "_rf_", "_rf", "rf_"};
dlcs[] = {"rf", "reactionforces", "reaction forces"};
};
```
The current filter is global for the mission. Revisit per-store profile support
if individual vendors need different inventories.

View File

@ -187,14 +187,21 @@ server-side.
The mission setup UI does not enable or disable generated missions. It applies
runtime tuning such as faction, caps, intervals, reward ranges, rating ranges,
penalties, time limits, and a generator provider preference. Generator
enablement remains controlled by the CBA setting above.
penalties, time limits, service pricing, and a generator provider preference.
Generator enablement remains controlled by the CBA setting above.
When `forge_server_task_enableMissionSetup` is enabled, the mission manager
waits for setup settings before starting. There is no timeout auto-apply.
Pressing Cancel, X, or Escape applies default values from CBA, mission
parameters, and `CfgMissions`.
Service price defaults are stored in `CfgServicePricing`. Mission
`Params` with matching names override those defaults before the UI opens, and
submitted UI values override both. The supported names are
`medicalSpawnCost`, `medicalHealCost`, `serviceRepairCost`,
`serviceRearmCost`, `fuelCost`, `transportBaseFare`, and
`transportPricePerKm`.
The setup UI stores the provider preference in
`forge_server_task_generatorProvider` as `builtin` or `custom`. CAD/manual
generated task requests use the task provider registry and route to the selected
@ -623,6 +630,10 @@ Task time limits use `0` for no limit:
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.
The mission setup UI uses the same rule. Turning off the task timer stores
`timeLimitMin = 0` and `timeLimitMax = 0`; turning it on uses the configured
positive min/max range for generated tasks.
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.

View File

@ -88,6 +88,12 @@ nearby vehicles, ships, aircraft, and player units. The scan ignores:
Use `transport_vehicle*` names for the actual boat, ferry, aircraft, or set
dressing object that should not be moved as cargo.
## Pricing
Default transport pricing comes from the mission setup UI or matching mission
`Params` entries named `transportBaseFare` and `transportPricePerKm`. If neither
is set, `CfgServicePricing` provides the fallback.
## Optional Per-Node Overrides
The default naming convention should cover normal missions. If a specific
@ -107,7 +113,8 @@ this setVariable ["transportCargoRadius", 25, true];
this setVariable ["transportIncludeCargo", true, true];
```
Only use overrides when the default `transport*` convention is not appropriate.
Only use overrides when the default `transport*` convention or mission-level
pricing is not appropriate.
## Image Checklist

View File

@ -27,6 +27,10 @@ calculates missing fuel from the vehicle config `fuelCapacity`, charges the
player's organization, and fills the vehicle only after the organization charge
succeeds.
The refuel price per liter is controlled by `fuelCost`. The mission setup UI
can override it at startup; otherwise a mission `Params` entry named
`fuelCost` or `CfgServicePricing >> fuelCost` is used.
## Repair
Repair is organization-funded.
@ -43,6 +47,9 @@ The target is only repaired after the organization charge succeeds.
The client garage UI forwards selected nearby vehicle repair requests through
the same event.
The default repair charge is controlled by `serviceRepairCost`. A direct
service event can still pass a concrete `_cost` to override that request.
## Rearm
Rearm is organization-funded.
@ -61,6 +68,9 @@ turrets, so the service broadcasts the ammo reset after billing succeeds.
The client garage UI forwards selected nearby vehicle rearm requests through
the same event.
The default rearm charge is controlled by `serviceRearmCost`. A direct service
event can still pass a concrete `_cost` to override that request.
## Medical
Medical is player-funded first.
@ -77,6 +87,15 @@ The heal only completes after one of those charges succeeds. If personal
billing is unavailable, the heal does not fall back to organization funds
because the server cannot verify that the player is unable to cover the fee.
Medical pricing uses:
- `medicalHealCost` for heal billing.
- `medicalSpawnCost` for medical respawn billing. Respawn billing is
best-effort so a failed charge does not block the respawn flow.
Both values can be set in the mission setup UI, mission `Params`, or
`CfgServicePricing`.
## Medical Debt Repayment
Medical fallback debt uses the existing organization credit-line repayment

View File

@ -79,6 +79,25 @@ New owned garages are created with default unlocks from the Rust model.
| `owned:garage:delete` | `uid` | `OK`. |
| `owned:garage:exists` | `uid` | `true` or `false`. |
## Starting Equipment And Unlocks
Missions can include `CfgStartingEquipment.hpp` from `description.ext` to set
new-player loadout and initial virtual arsenal or garage unlocks without
recompiling the framework.
```cpp
#include "CfgStartingEquipment.hpp"
```
`loadout[]` uses the standard Arma loadout array shape. `Unlocks.Locker`
supports `items[]`, `weapons[]`, `magazines[]`, and `backpacks[]`.
`Unlocks.Garage` supports `cars[]`, `armor[]`, `helis[]`, `planes[]`,
`naval[]`, and `other[]`.
The extension defaults are intentionally empty. The server seeds mission
starting unlocks only when a player does not already have persistent owned
locker or garage records.
## Add Virtual Arsenal Unlocks
```sqf