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:
parent
c3531a5839
commit
c676a9084e
25
README.md
25
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]`.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
71
bin/host/config.example.toml
Normal file
71
bin/host/config.example.toml
Normal 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
|
||||
@ -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
|
||||
@ -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(),
|
||||
],
|
||||
|
||||
@ -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('"', """);
|
||||
}
|
||||
|
||||
function selectedAttr(selected) {
|
||||
return selected ? "selected" : "";
|
||||
}
|
||||
|
||||
refresh();
|
||||
syncRefreshTimer();
|
||||
loadAppVersion();
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user