From c676a9084e7f174d067205bec26c6c5622d5d3a2 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Sat, 6 Jun 2026 20:16:39 -0500 Subject: [PATCH] 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 --- README.md | 25 ++- arma/server/config.example.toml | 18 +- arma/server/extension/README.md | 18 +- arma/server/extension/config.example.toml | 18 +- bin/host/basic.example.cfg | 13 +- bin/host/config.example.toml | 71 ++++++++ bin/host/host.example.toml | 35 ---- bin/host/src-tauri/src/main.rs | 51 ++---- bin/host/src/app.js | 100 ++++++----- bin/icom/README.md | 13 +- bin/icom/config.example.toml | 29 ++-- docs/FRAMEWORK_ARCHITECTURE.md | 27 ++- docs/ICOM_USAGE_GUIDE.md | 46 ++--- docs/README.md | 9 +- docs/SURREALDB_SETUP.md | 15 +- docs/surrealdb-setup.md | 15 +- .../1.getting-started/1.architecture.md | 27 ++- .../1.getting-started/4.mission-designer.md | 16 +- .../1.getting-started/6.surrealdb-setup.md | 60 ++----- .../1.getting-started/8.git-workflow.md | 158 ------------------ docus/content/2.server-extension/4.icom.md | 46 ++--- docus/content/3.server-modules/10.store.md | 66 +++++++- docus/content/3.server-modules/11.task.md | 15 +- .../3.server-modules/12.transport-service.md | 9 +- docus/content/3.server-modules/4.economy.md | 19 +++ .../3.server-modules/8.owned-storage.md | 19 +++ 26 files changed, 504 insertions(+), 434 deletions(-) create mode 100644 bin/host/config.example.toml delete mode 100644 bin/host/host.example.toml delete mode 100644 docus/content/1.getting-started/8.git-workflow.md diff --git a/README.md b/README.md index 6746f9b..a13336d 100644 --- a/README.md +++ b/README.md @@ -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]`. diff --git a/arma/server/config.example.toml b/arma/server/config.example.toml index 67d1947..49e79b6 100644 --- a/arma/server/config.example.toml +++ b/arma/server/config.example.toml @@ -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 diff --git a/arma/server/extension/README.md b/arma/server/extension/README.md index 9eeda21..5f9e9ee 100644 --- a/arma/server/extension/README.md +++ b/arma/server/extension/README.md @@ -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 diff --git a/arma/server/extension/config.example.toml b/arma/server/extension/config.example.toml index 346efe3..49e79b6 100644 --- a/arma/server/extension/config.example.toml +++ b/arma/server/extension/config.example.toml @@ -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 diff --git a/bin/host/basic.example.cfg b/bin/host/basic.example.cfg index de25bba..aa3c221 100644 --- a/bin/host/basic.example.cfg +++ b/bin/host/basic.example.cfg @@ -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; diff --git a/bin/host/config.example.toml b/bin/host/config.example.toml new file mode 100644 index 0000000..3d378c9 --- /dev/null +++ b/bin/host/config.example.toml @@ -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 diff --git a/bin/host/host.example.toml b/bin/host/host.example.toml deleted file mode 100644 index 1ba1314..0000000 --- a/bin/host/host.example.toml +++ /dev/null @@ -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 diff --git a/bin/host/src-tauri/src/main.rs b/bin/host/src-tauri/src/main.rs index b51f01b..b98ddf3 100644 --- a/bin/host/src-tauri/src/main.rs +++ b/bin/host/src-tauri/src/main.rs @@ -202,7 +202,10 @@ impl AppState { fn load_example_config() -> Result { 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 { - 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(), ], diff --git a/bin/host/src/app.js b/bin/host/src/app.js index e3ce2d9..0783c76 100644 --- a/bin/host/src/app.js +++ b/bin/host/src/app.js @@ -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) {
${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 ` -
- -
- - - - -
-
- `; + 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 ` -
- - +
+ +
+ + + + +
`; } @@ -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('"', """); } -function selectedAttr(selected) { - return selected ? "selected" : ""; -} - refresh(); syncRefreshTimer(); loadAppVersion(); diff --git a/bin/icom/README.md b/bin/icom/README.md index df1fa5f..a708047 100644 --- a/bin/icom/README.md +++ b/bin/icom/README.md @@ -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 ``` diff --git a/bin/icom/config.example.toml b/bin/icom/config.example.toml index 39bbe18..64ddde3 100644 --- a/bin/icom/config.example.toml +++ b/bin/icom/config.example.toml @@ -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 diff --git a/docs/FRAMEWORK_ARCHITECTURE.md b/docs/FRAMEWORK_ARCHITECTURE.md index ace015c..96f582c 100644 --- a/docs/FRAMEWORK_ARCHITECTURE.md +++ b/docs/FRAMEWORK_ARCHITECTURE.md @@ -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). diff --git a/docs/ICOM_USAGE_GUIDE.md b/docs/ICOM_USAGE_GUIDE.md index 36a3bb0..022a40c 100644 --- a/docs/ICOM_USAGE_GUIDE.md +++ b/docs/ICOM_USAGE_GUIDE.md @@ -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: `. | -| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | +| 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: `. | +| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | 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] + } } ``` diff --git a/docs/README.md b/docs/README.md index 25c5aa4..31b962b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. diff --git a/docs/SURREALDB_SETUP.md b/docs/SURREALDB_SETUP.md index a94cfaf..329e2d7 100644 --- a/docs/SURREALDB_SETUP.md +++ b/docs/SURREALDB_SETUP.md @@ -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] diff --git a/docs/surrealdb-setup.md b/docs/surrealdb-setup.md index a94cfaf..329e2d7 100644 --- a/docs/surrealdb-setup.md +++ b/docs/surrealdb-setup.md @@ -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] diff --git a/docus/content/1.getting-started/1.architecture.md b/docus/content/1.getting-started/1.architecture.md index 1fc24a3..1533e4e 100644 --- a/docus/content/1.getting-started/1.architecture.md +++ b/docus/content/1.getting-started/1.architecture.md @@ -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). diff --git a/docus/content/1.getting-started/4.mission-designer.md b/docus/content/1.getting-started/4.mission-designer.md index 97ba1e3..33b4f67 100644 --- a/docus/content/1.getting-started/4.mission-designer.md +++ b/docus/content/1.getting-started/4.mission-designer.md @@ -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 diff --git a/docus/content/1.getting-started/6.surrealdb-setup.md b/docus/content/1.getting-started/6.surrealdb-setup.md index d63d7bc..b35ea40 100644 --- a/docus/content/1.getting-started/6.surrealdb-setup.md +++ b/docus/content/1.getting-started/6.surrealdb-setup.md @@ -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] diff --git a/docus/content/1.getting-started/8.git-workflow.md b/docus/content/1.getting-started/8.git-workflow.md deleted file mode 100644 index 4f3e87b..0000000 --- a/docus/content/1.getting-started/8.git-workflow.md +++ /dev/null @@ -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 -``` - diff --git a/docus/content/2.server-extension/4.icom.md b/docus/content/2.server-extension/4.icom.md index 778e75d..0a23844 100644 --- a/docus/content/2.server-extension/4.icom.md +++ b/docus/content/2.server-extension/4.icom.md @@ -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: `. | -| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | +| 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: `. | +| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | 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] + } } ``` diff --git a/docus/content/3.server-modules/10.store.md b/docus/content/3.server-modules/10.store.md index 051bb83..e66581e 100644 --- a/docus/content/3.server-modules/10.store.md +++ b/docus/content/3.server-modules/10.store.md @@ -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. diff --git a/docus/content/3.server-modules/11.task.md b/docus/content/3.server-modules/11.task.md index f1b662b..2a06361 100644 --- a/docus/content/3.server-modules/11.task.md +++ b/docus/content/3.server-modules/11.task.md @@ -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. diff --git a/docus/content/3.server-modules/12.transport-service.md b/docus/content/3.server-modules/12.transport-service.md index c1a86dd..af5faee 100644 --- a/docus/content/3.server-modules/12.transport-service.md +++ b/docus/content/3.server-modules/12.transport-service.md @@ -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 diff --git a/docus/content/3.server-modules/4.economy.md b/docus/content/3.server-modules/4.economy.md index 6a3df06..1d21b51 100644 --- a/docus/content/3.server-modules/4.economy.md +++ b/docus/content/3.server-modules/4.economy.md @@ -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 diff --git a/docus/content/3.server-modules/8.owned-storage.md b/docus/content/3.server-modules/8.owned-storage.md index 44a38ff..c300f40 100644 --- a/docus/content/3.server-modules/8.owned-storage.md +++ b/docus/content/3.server-modules/8.owned-storage.md @@ -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