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
|
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,
|
from the current/executable directory in packaged use. Start from
|
||||||
it falls back to `bin/host/host.example.toml`; saving from the Settings view writes
|
`bin/host/config.example.toml` when you want one shared config for Forge Host,
|
||||||
the active shared `config.toml`. The shared file includes the host process sections
|
ICOM, and the Arma extension. Saving from the Settings view writes the active
|
||||||
plus the `[server]` section used by ICOM and the `[surreal]` section used by the
|
shared `config.toml`.
|
||||||
Arma extension.
|
|
||||||
|
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
|
# Forge Arma server extension 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
|
# Copy this file to `config.toml` beside `forge_server_x64.dll`, or use the
|
||||||
# aligned with the running database.
|
# 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]
|
[surreal]
|
||||||
|
# SurrealDB HTTP endpoint. This must match the running SurrealDB --bind value.
|
||||||
endpoint = "127.0.0.1:8000"
|
endpoint = "127.0.0.1:8000"
|
||||||
|
|
||||||
|
# Namespace and database selected by the extension after connecting.
|
||||||
namespace = "forge"
|
namespace = "forge"
|
||||||
database = "main"
|
database = "main"
|
||||||
|
|
||||||
|
# Local development defaults. Use a real password for shared or public servers.
|
||||||
username = "root"
|
username = "root"
|
||||||
password = "root"
|
password = "root"
|
||||||
|
|
||||||
|
# Initial connection timeout in milliseconds.
|
||||||
connect_timeout_ms = 5000
|
connect_timeout_ms = 5000
|
||||||
|
|||||||
@ -11,7 +11,10 @@ This extension build targets SurrealDB `3.x`.
|
|||||||
Before starting the Arma server with Forge enabled:
|
Before starting the Arma server with Forge enabled:
|
||||||
|
|
||||||
1. Start SurrealDB.
|
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
|
3. Match the `config.toml` endpoint, namespace, database, username, and password
|
||||||
to the running SurrealDB instance.
|
to the running SurrealDB instance.
|
||||||
|
|
||||||
@ -23,7 +26,8 @@ normal gameplay.
|
|||||||
|
|
||||||
- Register extension command groups for actor, bank, garage, locker, org,
|
- Register extension command groups for actor, bank, garage, locker, org,
|
||||||
phone, store, task, CAD, terrain, and transport systems.
|
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.
|
- Connect to SurrealDB and apply schema modules on startup.
|
||||||
- Keep SQF-facing command handlers thin while service crates own domain rules.
|
- Keep SQF-facing command handlers thin while service crates own domain rules.
|
||||||
|
|
||||||
@ -31,14 +35,24 @@ normal gameplay.
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[surreal]
|
[surreal]
|
||||||
|
# SurrealDB HTTP endpoint.
|
||||||
endpoint = "127.0.0.1:8000"
|
endpoint = "127.0.0.1:8000"
|
||||||
|
|
||||||
|
# Namespace and database selected after connecting.
|
||||||
namespace = "forge"
|
namespace = "forge"
|
||||||
database = "main"
|
database = "main"
|
||||||
|
|
||||||
|
# Local development credentials.
|
||||||
username = "root"
|
username = "root"
|
||||||
password = "root"
|
password = "root"
|
||||||
|
|
||||||
|
# Initial connection timeout in milliseconds.
|
||||||
connect_timeout_ms = 5000
|
connect_timeout_ms = 5000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The extension reads only `[surreal]`. Extra sections from the shared Forge Host
|
||||||
|
config are ignored.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
|
|||||||
@ -1,16 +1,22 @@
|
|||||||
# Forge Server Configuration
|
# Forge Arma server extension 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
|
# Copy this file to `config.toml` beside `forge_server_x64.dll`, or use the
|
||||||
# aligned with the running database.
|
# 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]
|
[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"
|
endpoint = "127.0.0.1:8000"
|
||||||
|
|
||||||
|
# Namespace and database selected by the extension after connecting.
|
||||||
namespace = "forge"
|
namespace = "forge"
|
||||||
database = "main"
|
database = "main"
|
||||||
|
|
||||||
# Optional authentication.
|
# Local development defaults. Use a real password for shared or public servers.
|
||||||
username = "root"
|
username = "root"
|
||||||
password = "root"
|
password = "root"
|
||||||
|
|
||||||
|
# Initial connection timeout in milliseconds.
|
||||||
connect_timeout_ms = 5000
|
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";
|
language = "English";
|
||||||
adapter = -1;
|
adapter = -1;
|
||||||
@ -6,12 +10,19 @@ adapter = -1;
|
|||||||
Resolution_W = 0;
|
Resolution_W = 0;
|
||||||
Resolution_H = 0;
|
Resolution_H = 0;
|
||||||
Resolution_Bpp = 32;
|
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;
|
MinBandwidth = 131072;
|
||||||
MaxBandwidth = 10000000000;
|
MaxBandwidth = 10000000000;
|
||||||
|
|
||||||
|
// Message sizing and send cadence. Higher values can improve responsiveness on
|
||||||
|
// healthy networks but may increase bandwidth and CPU pressure.
|
||||||
MaxMsgSend = 128;
|
MaxMsgSend = 128;
|
||||||
MaxSizeGuaranteed = 512;
|
MaxSizeGuaranteed = 512;
|
||||||
MaxSizeNonguaranteed = 256;
|
MaxSizeNonguaranteed = 256;
|
||||||
MinErrorToSend = 0.001;
|
MinErrorToSend = 0.001;
|
||||||
MinErrorToSendNear = 0.01;
|
MinErrorToSendNear = 0.01;
|
||||||
|
|
||||||
|
// Prevent clients from uploading custom face/sound files to the server.
|
||||||
MaxCustomFileSize = 0;
|
MaxCustomFileSize = 0;
|
||||||
Windowed = 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> {
|
fn load_example_config() -> Result<HostConfig, String> {
|
||||||
if let Some(repo_root) = find_repo_root() {
|
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);
|
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}"))?;
|
.map_err(|error| format!("Failed to create config directory: {error}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let template_path = resolve_arma_config_template(&template)?;
|
let content = 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()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
fs::write(&path, content).map_err(|error| format!("Failed to write server config: {error}"))
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_arma_config_template(template: &str) -> Result<PathBuf, String> {
|
fn arma_config_template(template: &str) -> Result<&'static str, String> {
|
||||||
let file_name = match template {
|
match template {
|
||||||
"basic" => "basic.example.cfg",
|
"basic" => Ok(include_str!("../../basic.example.cfg")),
|
||||||
"server" | "" => "server.example.cfg",
|
"server" | "" => Ok(include_str!("../../server.example.cfg")),
|
||||||
other => return Err(format!("Unknown Arma config template '{other}'")),
|
other => 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));
|
|
||||||
}
|
}
|
||||||
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 {
|
fn service_config(kind: ServiceKind, config: &HostConfig) -> ServiceConfig {
|
||||||
@ -971,7 +948,7 @@ fn default_surrealdb_service() -> ServiceConfig {
|
|||||||
"127.0.0.1:8000".to_string(),
|
"127.0.0.1:8000".to_string(),
|
||||||
"rocksdb://forge.db".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_host: "127.0.0.1".to_string(),
|
||||||
health_port: 8000,
|
health_port: 8000,
|
||||||
}
|
}
|
||||||
@ -982,7 +959,7 @@ fn default_icom_service() -> ServiceConfig {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
command: "target/release/forge-icom.exe".to_string(),
|
command: "target/release/forge-icom.exe".to_string(),
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
working_dir: "../..".to_string(),
|
working_dir: ".".to_string(),
|
||||||
health_host: "127.0.0.1".to_string(),
|
health_host: "127.0.0.1".to_string(),
|
||||||
health_port: 9090,
|
health_port: 9090,
|
||||||
}
|
}
|
||||||
@ -993,8 +970,10 @@ fn default_arma_service() -> ServiceConfig {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
command: "arma3server_x64.exe".to_string(),
|
command: "arma3server_x64.exe".to_string(),
|
||||||
args: vec![
|
args: vec![
|
||||||
|
"-config=server.cfg".to_string(),
|
||||||
|
"-cfg=basic.cfg".to_string(),
|
||||||
"-port=2302".to_string(),
|
"-port=2302".to_string(),
|
||||||
"-profiles=profiles".to_string(),
|
"-profiles=serverprofiles".to_string(),
|
||||||
"-name=server".to_string(),
|
"-name=server".to_string(),
|
||||||
"-noBattlEye".to_string(),
|
"-noBattlEye".to_string(),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -18,7 +18,6 @@ let activeSettingsService = "surrealdb";
|
|||||||
let configDirty = false;
|
let configDirty = false;
|
||||||
let activeEditorPath = "";
|
let activeEditorPath = "";
|
||||||
let surrealInstallInfo = null;
|
let surrealInstallInfo = null;
|
||||||
let selectedArmaConfigTemplate = "server";
|
|
||||||
|
|
||||||
const views = {
|
const views = {
|
||||||
overview: document.getElementById("overviewView"),
|
overview: document.getElementById("overviewView"),
|
||||||
@ -340,20 +339,24 @@ function renderSettings(force = false) {
|
|||||||
|
|
||||||
form.querySelectorAll("[data-create-server-config]").forEach((button) => {
|
form.querySelectorAll("[data-create-server-config]").forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
const template =
|
runAction(() =>
|
||||||
document.querySelector("[data-config-template]")?.value ||
|
createArmaServerConfig(
|
||||||
"server";
|
button.dataset.configTemplate || "server",
|
||||||
runAction(() => createArmaServerConfig(template));
|
button.dataset.configTarget || "config",
|
||||||
|
button.dataset.defaultFile || "server.cfg",
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
form.querySelectorAll("[data-edit-server-config]").forEach((button) => {
|
form.querySelectorAll("[data-edit-server-config]").forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
|
const target = button.dataset.configTarget || "config";
|
||||||
const path = document
|
const path = document
|
||||||
.querySelector('[data-arg-key="config"]')
|
.querySelector(`[data-arg-key="${target}"]`)
|
||||||
?.value.trim();
|
?.value.trim();
|
||||||
if (!path) {
|
if (!path) {
|
||||||
alert("Select or create a server config first.");
|
alert("Select or create a config first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runAction(() => openConfigEditor(resolveArmaConfigPath(path)));
|
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) {
|
if (name === "surrealdb" && !surrealInstallInfo) {
|
||||||
loadSurrealInstallInfo();
|
loadSurrealInstallInfo();
|
||||||
} else if (
|
} else if (
|
||||||
@ -716,7 +713,7 @@ function renderArmaSettings(config) {
|
|||||||
<label>Launch Options</label>
|
<label>Launch Options</label>
|
||||||
<div class="args-list arma-args" data-service="arma" data-field="args">
|
<div class="args-list arma-args" data-service="arma" data-field="args">
|
||||||
${serverConfigInput(parsed.config)}
|
${serverConfigInput(parsed.config)}
|
||||||
${serverConfigTemplateInput()}
|
${basicConfigInput(parsed.cfg)}
|
||||||
${argumentInput("Port", "port", parsed.port)}
|
${argumentInput("Port", "port", parsed.port)}
|
||||||
${argumentInput("Profiles", "profiles", parsed.profiles)}
|
${argumentInput("Profiles", "profiles", parsed.profiles)}
|
||||||
${argumentInput("Name", "name", parsed.name)}
|
${argumentInput("Name", "name", parsed.name)}
|
||||||
@ -766,27 +763,45 @@ function argumentInput(label, key, value, className = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function serverConfigInput(value) {
|
function serverConfigInput(value) {
|
||||||
return `
|
return configFileInput({
|
||||||
<div class="arg-field full">
|
label: "Server Config",
|
||||||
<label>Server Config</label>
|
key: "config",
|
||||||
<div class="input-action action-three">
|
value,
|
||||||
<input class="arg-input" data-arg-key="config" value="${escapeAttr(value)}" placeholder="server.cfg" />
|
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>
|
template: "server",
|
||||||
<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>
|
defaultFile: "server.cfg",
|
||||||
<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>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 `
|
return `
|
||||||
<div class="arg-field">
|
<div class="arg-field full">
|
||||||
<label>Config Template</label>
|
<label>${label}</label>
|
||||||
<select data-config-template>
|
<div class="input-action action-three">
|
||||||
<option value="server" ${selectedAttr(selectedArmaConfigTemplate === "server")}>Server</option>
|
<input class="arg-input" data-arg-key="${key}" value="${escapeAttr(value)}" placeholder="${placeholder}" />
|
||||||
<option value="basic" ${selectedAttr(selectedArmaConfigTemplate === "basic")}>Basic Network</option>
|
<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>
|
||||||
</select>
|
<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>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -819,6 +834,7 @@ function readArgumentFields(service, container) {
|
|||||||
if (service === "arma") {
|
if (service === "arma") {
|
||||||
const args = [];
|
const args = [];
|
||||||
if (value("config")) args.push(`-config=${value("config")}`);
|
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("port")) args.push(`-port=${value("port")}`);
|
||||||
if (value("profiles")) args.push(`-profiles=${value("profiles")}`);
|
if (value("profiles")) args.push(`-profiles=${value("profiles")}`);
|
||||||
if (value("name")) args.push(`-name=${value("name")}`);
|
if (value("name")) args.push(`-name=${value("name")}`);
|
||||||
@ -871,6 +887,7 @@ function parseSurrealArgs(args) {
|
|||||||
function parseArmaArgs(args) {
|
function parseArmaArgs(args) {
|
||||||
const parsed = {
|
const parsed = {
|
||||||
config: "",
|
config: "",
|
||||||
|
cfg: "",
|
||||||
port: "",
|
port: "",
|
||||||
profiles: "",
|
profiles: "",
|
||||||
name: "",
|
name: "",
|
||||||
@ -888,6 +905,8 @@ function parseArmaArgs(args) {
|
|||||||
|
|
||||||
if (lowerArg.startsWith("-config=")) {
|
if (lowerArg.startsWith("-config=")) {
|
||||||
parsed.config = arg.slice("-config=".length);
|
parsed.config = arg.slice("-config=".length);
|
||||||
|
} else if (lowerArg.startsWith("-cfg=")) {
|
||||||
|
parsed.cfg = arg.slice("-cfg=".length);
|
||||||
} else if (lowerArg.startsWith("-port=")) {
|
} else if (lowerArg.startsWith("-port=")) {
|
||||||
parsed.port = arg.slice("-port=".length);
|
parsed.port = arg.slice("-port=".length);
|
||||||
} else if (lowerArg.startsWith("-profiles=")) {
|
} else if (lowerArg.startsWith("-profiles=")) {
|
||||||
@ -936,7 +955,10 @@ async function pickPath(kind, targetSelector, service) {
|
|||||||
|
|
||||||
const input = document.querySelector(targetSelector);
|
const input = document.querySelector(targetSelector);
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
const finalPath = normalizePickedExecutable(service, selected);
|
const finalPath =
|
||||||
|
kind === "file"
|
||||||
|
? normalizePickedExecutable(service, selected)
|
||||||
|
: selected;
|
||||||
input.value = finalPath;
|
input.value = finalPath;
|
||||||
|
|
||||||
if (kind === "file" && targetSelector.includes('data-field="command"')) {
|
if (kind === "file" && targetSelector.includes('data-field="command"')) {
|
||||||
@ -966,7 +988,7 @@ function pickerFilters(kind) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createArmaServerConfig(template) {
|
async function createArmaServerConfig(template, targetKey, defaultFile) {
|
||||||
const save = window.__TAURI__?.dialog?.save;
|
const save = window.__TAURI__?.dialog?.save;
|
||||||
if (!save) {
|
if (!save) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -977,7 +999,9 @@ async function createArmaServerConfig(template) {
|
|||||||
const workingDir = document
|
const workingDir = document
|
||||||
.querySelector('[data-service="arma"][data-field="working_dir"]')
|
.querySelector('[data-service="arma"][data-field="working_dir"]')
|
||||||
?.value.trim();
|
?.value.trim();
|
||||||
const defaultPath = workingDir ? `${workingDir}\\server.cfg` : "server.cfg";
|
const defaultPath = workingDir
|
||||||
|
? `${workingDir}\\${defaultFile}`
|
||||||
|
: defaultFile;
|
||||||
const selected = await save({
|
const selected = await save({
|
||||||
defaultPath,
|
defaultPath,
|
||||||
filters: [{ name: "Arma Server Config", extensions: ["cfg"] }],
|
filters: [{ name: "Arma Server Config", extensions: ["cfg"] }],
|
||||||
@ -988,7 +1012,7 @@ async function createArmaServerConfig(template) {
|
|||||||
path: selected,
|
path: selected,
|
||||||
template,
|
template,
|
||||||
});
|
});
|
||||||
const input = document.querySelector('[data-arg-key="config"]');
|
const input = document.querySelector(`[data-arg-key="${targetKey}"]`);
|
||||||
if (input) {
|
if (input) {
|
||||||
input.value = selected;
|
input.value = selected;
|
||||||
configDirty = true;
|
configDirty = true;
|
||||||
@ -1075,10 +1099,6 @@ function escapeAttr(value) {
|
|||||||
return escapeHtml(value).replaceAll('"', """);
|
return escapeHtml(value).replaceAll('"', """);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectedAttr(selected) {
|
|
||||||
return selected ? "selected" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
syncRefreshTimer();
|
syncRefreshTimer();
|
||||||
loadAppVersion();
|
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
|
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
|
### Configuration Options
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[server]
|
[server]
|
||||||
# Host to bind to
|
# TCP address to bind.
|
||||||
# "0.0.0.0" = All interfaces (allows remote connections)
|
# "0.0.0.0" = all interfaces, allowing remote servers.
|
||||||
# "127.0.0.1" = Localhost only
|
# "127.0.0.1" = localhost only.
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
|
|
||||||
# Port to listen on
|
# TCP port used by extension `icom:connect`.
|
||||||
port = 9090
|
port = 9090
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,17 @@
|
|||||||
# Forge ICOM Server Configuration
|
# Forge ICOM hub configuration.
|
||||||
# Copy this file to config.toml and modify as needed
|
#
|
||||||
# Place this file in the same directory as the forge-icom executable
|
# 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]
|
[server]
|
||||||
# Host to bind to
|
# TCP address the ICOM hub binds to.
|
||||||
# - "0.0.0.0" = All network interfaces (default, allows remote connections)
|
# - "0.0.0.0" listens on all network interfaces and allows remote servers.
|
||||||
# - "127.0.0.1" = Localhost only (for local testing)
|
# - "127.0.0.1" listens only on localhost for same-machine testing.
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
|
|
||||||
# Port to listen on
|
# TCP port accepted by extension `icom:connect` calls.
|
||||||
port = 9090
|
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
|
## Configuration
|
||||||
|
|
||||||
The server extension reads `config.toml` next to the extension DLL. The current
|
The server extension reads `config.toml` from the server working directory,
|
||||||
persistence section is:
|
`@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
|
```toml
|
||||||
[surreal]
|
[surreal]
|
||||||
@ -143,12 +146,20 @@ password = "root"
|
|||||||
connect_timeout_ms = 5000
|
connect_timeout_ms = 5000
|
||||||
```
|
```
|
||||||
|
|
||||||
`config.toml` is a launch prerequisite for server owners and developers. The
|
`config.toml` is a launch prerequisite for server owners and developers. Use
|
||||||
file must exist beside `forge_server_x64.dll`, and SurrealDB must already be
|
`arma/server/extension/config.example.toml` for an extension-only config, or
|
||||||
running at the configured endpoint before starting a Forge-enabled dedicated
|
`bin/host/config.example.toml` for the shared host-managed config. SurrealDB must
|
||||||
server or local multiplayer test. Clients and mission designers do not run this
|
already be running at the configured endpoint before starting a Forge-enabled
|
||||||
configuration unless they are hosting locally, but the server they connect to
|
dedicated server or local multiplayer test. Clients and mission designers do not
|
||||||
must have it in place.
|
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
|
For install links and role-based setup guidance, see
|
||||||
[SurrealDB Setup](./surrealdb-setup.md).
|
[SurrealDB Setup](./surrealdb-setup.md).
|
||||||
|
|||||||
@ -20,12 +20,12 @@ through `arma/server/extension/src/icom.rs`.
|
|||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
| Component | Path | Role |
|
| Component | Path | Role |
|
||||||
| --- | --- | --- |
|
| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
|
||||||
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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
|
## Build and Run the Hub
|
||||||
|
|
||||||
@ -46,11 +46,17 @@ The default bind address is `0.0.0.0:9090`.
|
|||||||
## Hub Configuration
|
## Hub Configuration
|
||||||
|
|
||||||
Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom`
|
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
|
```toml
|
||||||
[server]
|
[server]
|
||||||
|
# Listen on all interfaces so remote Arma servers can connect.
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
|
|
||||||
|
# TCP port used by extension `icom:connect`.
|
||||||
port = 9090
|
port = 9090
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -62,11 +68,11 @@ layer.
|
|||||||
|
|
||||||
ICOM commands are exposed through the `icom` command group in `forge_server`.
|
ICOM commands are exposed through the `icom` command group in `forge_server`.
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
| Command | Arguments | Returns |
|
||||||
| --- | --- | --- |
|
| ----------------- | ------------------------------------------ | ----------------------------------------------------- |
|
||||||
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
|
| `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:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
|
||||||
| `icom:broadcast` | `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
|
The current extension connects when `icom:connect` is called. Start the ICOM hub
|
||||||
first, then connect each Arma server with a unique `server_id`.
|
first, then connect each Arma server with a unique `server_id`.
|
||||||
@ -144,8 +150,8 @@ registration payload:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "register",
|
"type": "register",
|
||||||
"server_id": "server_1"
|
"server_id": "server_1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -153,12 +159,12 @@ Targeted events use `type: "event"`:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"target_server": "server_2",
|
"target_server": "server_2",
|
||||||
"event_name": "supply_drop",
|
"event_name": "supply_drop",
|
||||||
"data": {
|
"data": {
|
||||||
"coords": [1234, 5678, 0]
|
"coords": [1234, 5678, 0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -28,11 +28,18 @@ Before starting a Forge-enabled dedicated server or local multiplayer test,
|
|||||||
server owners and developers must:
|
server owners and developers must:
|
||||||
|
|
||||||
1. Start SurrealDB.
|
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,
|
3. Keep the `config.toml` SurrealDB endpoint, namespace, database, username,
|
||||||
and password aligned with the running database.
|
and password aligned with the running database.
|
||||||
4. Load `@forge_mod` with the server's normal mod list and `@forge_server` as
|
4. Load `@forge_mod` with the server's normal mod list and `@forge_server` as
|
||||||
a server-only mod.
|
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
|
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.
|
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:
|
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.
|
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
|
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
|
||||||
`forge_server_x64.dll`.
|
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,
|
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
|
||||||
database, username, and password.
|
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
|
`root`/`root` is only the local development default. For a public or shared
|
||||||
server, set a real password and keep `config.toml` aligned.
|
server, set a real password and keep `config.toml` aligned.
|
||||||
|
|
||||||
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
|
Then create Forge's `config.toml` and keep the values aligned with the database
|
||||||
`forge_server_x64.dll` and keep the values aligned with the database you
|
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
|
||||||
started:
|
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
|
```toml
|
||||||
[surreal]
|
[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:
|
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.
|
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
|
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
|
||||||
`forge_server_x64.dll`.
|
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,
|
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
|
||||||
database, username, and password.
|
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
|
`root`/`root` is only the local development default. For a public or shared
|
||||||
server, set a real password and keep `config.toml` aligned.
|
server, set a real password and keep `config.toml` aligned.
|
||||||
|
|
||||||
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
|
Then create Forge's `config.toml` and keep the values aligned with the database
|
||||||
`forge_server_x64.dll` and keep the values aligned with the database you
|
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
|
||||||
started:
|
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
|
```toml
|
||||||
[surreal]
|
[surreal]
|
||||||
|
|||||||
@ -129,8 +129,11 @@ server addon fnc_extCall
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The server extension reads `config.toml` next to the extension DLL. The current
|
The server extension reads `config.toml` from the server working directory,
|
||||||
persistence section is:
|
`@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
|
```toml
|
||||||
[surreal]
|
[surreal]
|
||||||
@ -142,12 +145,20 @@ password = "root"
|
|||||||
connect_timeout_ms = 5000
|
connect_timeout_ms = 5000
|
||||||
```
|
```
|
||||||
|
|
||||||
`config.toml` is a launch prerequisite for server owners and developers. The
|
`config.toml` is a launch prerequisite for server owners and developers. Use
|
||||||
file must exist beside `forge_server_x64.dll`, and SurrealDB must already be
|
`arma/server/extension/config.example.toml` for an extension-only config, or
|
||||||
running at the configured endpoint before starting a Forge-enabled dedicated
|
`bin/host/config.example.toml` for the shared host-managed config. SurrealDB must
|
||||||
server or local multiplayer test. Clients and mission designers do not run this
|
already be running at the configured endpoint before starting a Forge-enabled
|
||||||
configuration unless they are hosting locally, but the server they connect to
|
dedicated server or local multiplayer test. Clients and mission designers do not
|
||||||
must have it in place.
|
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
|
For install links and role-based setup guidance, see
|
||||||
[SurrealDB Setup](/getting-started/surrealdb-setup).
|
[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
|
The optional framework mission setup UI lets the setup operator choose runtime
|
||||||
tuning such as opposing faction, mission cap, interval, location cooldown,
|
tuning such as opposing faction, mission cap, interval, location cooldown,
|
||||||
reward ranges, reputation ranges, penalty ranges, time limits, and a generator
|
reward ranges, reputation ranges, penalty ranges, time limits, and a generator
|
||||||
provider preference. It does not enable or disable generated missions; use the
|
provider preference. It also exposes service pricing for medical spawn, heal,
|
||||||
CBA setting for that policy.
|
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
|
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
|
applies settings. Cancel, X, and Escape apply default values from CBA, mission
|
||||||
parameters, and `CfgMissions`. There is no timeout that auto-applies defaults.
|
parameters, and `CfgMissions`. There is no timeout that auto-applies defaults.
|
||||||
After settings are applied, the setup UI cannot be reopened.
|
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
|
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
|
generated task requests use the task provider registry and route to the selected
|
||||||
provider. Custom generators should register a provider or create CAD-visible
|
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:
|
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.
|
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
|
2. Create Forge's `config.toml`. Use `bin/host/config.example.toml` at the repo
|
||||||
`forge_server_x64.dll`.
|
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,
|
3. Make sure `config.toml` matches the running SurrealDB endpoint, namespace,
|
||||||
database, username, and password.
|
database, username, and password.
|
||||||
|
|
||||||
@ -35,45 +37,12 @@ Official SurrealDB resources:
|
|||||||
- [SurrealDB install page](https://surrealdb.com/install)
|
- [SurrealDB install page](https://surrealdb.com/install)
|
||||||
- [SurrealDB CLI `start` reference](https://surrealdb.com/docs/surrealdb/cli/start)
|
- [SurrealDB CLI `start` reference](https://surrealdb.com/docs/surrealdb/cli/start)
|
||||||
|
|
||||||
Forge also includes helper scripts under `arma/server/surrealdb`:
|
Forge Host includes the recommended local setup path. Open the SurrealDB view
|
||||||
|
to install or update SurrealDB, configure the bind address and database path,
|
||||||
```powershell
|
and start or stop the local database. Enter `3` to install the latest compatible
|
||||||
cd arma/server/surrealdb
|
SurrealDB 3.x release, enter an exact version such as `v3.1.2` to pin a
|
||||||
.\UpdateMe.bat
|
release, or enter `latest` only after confirming compatibility with the Forge
|
||||||
.\RunMe.bat
|
server extension.
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
Install SurrealDB with the official method for your platform:
|
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
|
`root`/`root` is only the local development default. For a public or shared
|
||||||
server, set a real password and keep `config.toml` aligned.
|
server, set a real password and keep `config.toml` aligned.
|
||||||
|
|
||||||
Then copy `arma/server/extension/config.example.toml` to `config.toml` next to
|
Then create Forge's `config.toml` and keep the values aligned with the database
|
||||||
`forge_server_x64.dll` and keep the values aligned with the database you
|
you started. If you use Forge Host locally, copy `bin/host/config.example.toml`
|
||||||
started:
|
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
|
```toml
|
||||||
[surreal]
|
[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
|
## Components
|
||||||
|
|
||||||
| Component | Path | Role |
|
| Component | Path | Role |
|
||||||
| --- | --- | --- |
|
| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
|
||||||
| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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
|
## Build and Run the Hub
|
||||||
|
|
||||||
@ -45,11 +45,17 @@ The default bind address is `0.0.0.0:9090`.
|
|||||||
## Hub Configuration
|
## Hub Configuration
|
||||||
|
|
||||||
Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom`
|
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
|
```toml
|
||||||
[server]
|
[server]
|
||||||
|
# Listen on all interfaces so remote Arma servers can connect.
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
|
|
||||||
|
# TCP port used by extension `icom:connect`.
|
||||||
port = 9090
|
port = 9090
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -61,11 +67,11 @@ layer.
|
|||||||
|
|
||||||
ICOM commands are exposed through the `icom` command group in `forge_server`.
|
ICOM commands are exposed through the `icom` command group in `forge_server`.
|
||||||
|
|
||||||
| Command | Arguments | Returns |
|
| Command | Arguments | Returns |
|
||||||
| --- | --- | --- |
|
| ----------------- | ------------------------------------------ | ----------------------------------------------------- |
|
||||||
| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. |
|
| `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:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: <reason>`. |
|
||||||
| `icom:broadcast` | `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
|
The current extension connects when `icom:connect` is called. Start the ICOM hub
|
||||||
first, then connect each Arma server with a unique `server_id`.
|
first, then connect each Arma server with a unique `server_id`.
|
||||||
@ -143,8 +149,8 @@ registration payload:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "register",
|
"type": "register",
|
||||||
"server_id": "server_1"
|
"server_id": "server_1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -152,12 +158,12 @@ Targeted events use `type: "event"`:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"target_server": "server_2",
|
"target_server": "server_2",
|
||||||
"event_name": "supply_drop",
|
"event_name": "supply_drop",
|
||||||
"data": {
|
"data": {
|
||||||
"coords": [1234, 5678, 0]
|
"coords": [1234, 5678, 0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Store Usage Guide"
|
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
|
## 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
|
- `StorefrontStore` is the storefront workflow facade. It builds hydrate
|
||||||
payloads, validates checkout requests, calls the Rust `store:checkout`
|
payloads, validates checkout requests, calls the Rust `store:checkout`
|
||||||
command, syncs UI patches, asks related module stores to save hot state, and
|
command, syncs UI patches, and asks related module stores to save hot state.
|
||||||
spawns purchased units at discovered `unit_spawn` markers after the backend
|
|
||||||
charge succeeds.
|
|
||||||
- `StoreCatalogService` scans configured item and vehicle categories, builds
|
- `StoreCatalogService` scans configured item and vehicle categories, builds
|
||||||
catalog responses, resolves checkout entries, and calculates authoritative
|
catalog responses, resolves checkout entries, and calculates authoritative
|
||||||
prices. It also applies the optional mission `CfgStore` filter and overrides
|
prices.
|
||||||
before payloads or checkout validation use catalog entries.
|
|
||||||
|
|
||||||
Editor-placed store entities are initialized by `fnc_initStore` during store
|
Editor-placed store entities are initialized by `fnc_initStore` during store
|
||||||
post-init. The initializer matches non-null mission namespace objects whose
|
post-init. The initializer matches non-null mission namespace objects whose
|
||||||
@ -35,6 +32,26 @@ Include `CfgStore.hpp` from `description.ext`:
|
|||||||
```cpp
|
```cpp
|
||||||
class CfgStore {
|
class CfgStore {
|
||||||
mode = "allowlist"; // dynamic, allowlist, or denylist
|
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 {
|
class Categories {
|
||||||
primary[] = {"arifle_MX_F", "arifle_MXC_F"};
|
primary[] = {"arifle_MX_F", "arifle_MXC_F"};
|
||||||
@ -55,9 +72,40 @@ class CfgStore {
|
|||||||
`dynamic` keeps the full generated catalog. `allowlist` only shows classnames
|
`dynamic` keeps the full generated catalog. `allowlist` only shows classnames
|
||||||
listed for each category. `denylist` removes listed classnames. Overrides are
|
listed for each category. `denylist` removes listed classnames. Overrides are
|
||||||
server-side and are used by both the UI payload and checkout validation.
|
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
|
`units[]` uses the same filter behavior as every other category.
|
||||||
server-side unit spawn at a discovered `unit_spawn` marker after checkout
|
|
||||||
succeeds.
|
`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
|
The current filter is global for the mission. Revisit per-store profile support
|
||||||
if individual vendors need different inventories.
|
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
|
The mission setup UI does not enable or disable generated missions. It applies
|
||||||
runtime tuning such as faction, caps, intervals, reward ranges, rating ranges,
|
runtime tuning such as faction, caps, intervals, reward ranges, rating ranges,
|
||||||
penalties, time limits, and a generator provider preference. Generator
|
penalties, time limits, service pricing, and a generator provider preference.
|
||||||
enablement remains controlled by the CBA setting above.
|
Generator enablement remains controlled by the CBA setting above.
|
||||||
|
|
||||||
When `forge_server_task_enableMissionSetup` is enabled, the mission manager
|
When `forge_server_task_enableMissionSetup` is enabled, the mission manager
|
||||||
waits for setup settings before starting. There is no timeout auto-apply.
|
waits for setup settings before starting. There is no timeout auto-apply.
|
||||||
Pressing Cancel, X, or Escape applies default values from CBA, mission
|
Pressing Cancel, X, or Escape applies default values from CBA, mission
|
||||||
parameters, and `CfgMissions`.
|
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
|
The setup UI stores the provider preference in
|
||||||
`forge_server_task_generatorProvider` as `builtin` or `custom`. CAD/manual
|
`forge_server_task_generatorProvider` as `builtin` or `custom`. CAD/manual
|
||||||
generated task requests use the task provider registry and route to the selected
|
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;
|
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 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
|
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
|
IEDs are expected to have an active countdown. The Eden defuse module defaults
|
||||||
to `300` seconds.
|
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
|
Use `transport_vehicle*` names for the actual boat, ferry, aircraft, or set
|
||||||
dressing object that should not be moved as cargo.
|
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
|
## Optional Per-Node Overrides
|
||||||
|
|
||||||
The default naming convention should cover normal missions. If a specific
|
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];
|
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
|
## 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
|
player's organization, and fills the vehicle only after the organization charge
|
||||||
succeeds.
|
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
|
||||||
|
|
||||||
Repair is organization-funded.
|
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 client garage UI forwards selected nearby vehicle repair requests through
|
||||||
the same event.
|
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
|
||||||
|
|
||||||
Rearm is organization-funded.
|
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 client garage UI forwards selected nearby vehicle rearm requests through
|
||||||
the same event.
|
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
|
||||||
|
|
||||||
Medical is player-funded first.
|
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
|
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.
|
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 Debt Repayment
|
||||||
|
|
||||||
Medical fallback debt uses the existing organization credit-line 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:delete` | `uid` | `OK`. |
|
||||||
| `owned:garage:exists` | `uid` | `true` or `false`. |
|
| `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
|
## Add Virtual Arsenal Unlocks
|
||||||
|
|
||||||
```sqf
|
```sqf
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user