Backport framework docs and store filter updates

This commit is contained in:
Jacob Schmidt 2026-06-03 22:48:59 -05:00
parent d61cb86d3a
commit 4f54edf467
16 changed files with 320 additions and 63 deletions

View File

@ -152,6 +152,12 @@ option {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
.timer-row {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.95fr) minmax(0, 0.95fr);
gap: 0.5rem;
}
label { label {
color: var(--text-subtle); color: var(--text-subtle);
font-size: 0.62rem; font-size: 0.62rem;
@ -261,6 +267,10 @@ select {
color: var(--text-main); color: var(--text-main);
} }
input:disabled {
opacity: 0.45;
}
input:focus, input:focus,
select:focus, select:focus,
button:focus-visible { button:focus-visible {

View File

@ -12,6 +12,7 @@
reputationMax: 100, reputationMax: 100,
penaltyMin: -5, penaltyMin: -5,
penaltyMax: -25, penaltyMax: -25,
timeLimitEnabled: true,
timeLimitMin: 600, timeLimitMin: 600,
timeLimitMax: 900, timeLimitMax: 900,
medicalSpawnCost: 100, medicalSpawnCost: 100,
@ -41,6 +42,8 @@
} }
function readSettings() { function readSettings() {
const timeLimitEnabled = document.getElementById("timeLimitEnabled")?.checked !== false;
return { return {
enemyFaction: String(document.getElementById("enemyFaction")?.value || "IND_G_F"), enemyFaction: String(document.getElementById("enemyFaction")?.value || "IND_G_F"),
maxConcurrentMissions: fieldNumber("maxConcurrentMissions"), maxConcurrentMissions: fieldNumber("maxConcurrentMissions"),
@ -52,8 +55,9 @@
reputationMax: fieldNumber("reputationMax"), reputationMax: fieldNumber("reputationMax"),
penaltyMin: fieldNumber("penaltyMin"), penaltyMin: fieldNumber("penaltyMin"),
penaltyMax: fieldNumber("penaltyMax"), penaltyMax: fieldNumber("penaltyMax"),
timeLimitMin: fieldNumber("timeLimitMin"), timeLimitEnabled,
timeLimitMax: fieldNumber("timeLimitMax"), timeLimitMin: timeLimitEnabled ? fieldNumber("timeLimitMin") : 0,
timeLimitMax: timeLimitEnabled ? fieldNumber("timeLimitMax") : 0,
medicalSpawnCost: fieldNumber("medicalSpawnCost"), medicalSpawnCost: fieldNumber("medicalSpawnCost"),
medicalHealCost: fieldNumber("medicalHealCost"), medicalHealCost: fieldNumber("medicalHealCost"),
serviceRepairCost: fieldNumber("serviceRepairCost"), serviceRepairCost: fieldNumber("serviceRepairCost"),
@ -74,6 +78,16 @@
.replace(/'/g, "'"); .replace(/'/g, "'");
} }
function normalizeSettings(settings) {
const next = Object.assign({}, settings);
next.timeLimitMin = Number(next.timeLimitMin || 0);
next.timeLimitMax = Number(next.timeLimitMax || 0);
if (typeof next.timeLimitEnabled !== "boolean") {
next.timeLimitEnabled = next.timeLimitMax > 0;
}
return next;
}
function apply() { function apply() {
const settings = readSettings(); const settings = readSettings();
if (settings.moneyMax < settings.moneyMin) { if (settings.moneyMax < settings.moneyMin) {
@ -94,10 +108,18 @@
return; return;
} }
if (settings.timeLimitMax < settings.timeLimitMin) { if (settings.timeLimitEnabled) {
state.error = "Time limit max must be greater than or equal to time limit min."; if (settings.timeLimitMin < 1 || settings.timeLimitMax < 1) {
render(); state.error = "Time limits must be positive seconds when task timers are enabled.";
return; render();
return;
}
if (settings.timeLimitMax < settings.timeLimitMin) {
state.error = "Time limit max must be greater than or equal to time limit min.";
render();
return;
}
} }
const costFields = [ const costFields = [
@ -134,6 +156,15 @@
const factionLabel = faction ? faction.display : settings.enemyFaction; const factionLabel = faction ? faction.display : settings.enemyFaction;
const generatorProviderLabel = settings.generatorProvider === "custom" ? "Custom" : "Built-in"; const generatorProviderLabel = settings.generatorProvider === "custom" ? "Custom" : "Built-in";
const generatorProviderChecked = settings.generatorProvider === "custom" ? " checked" : ""; const generatorProviderChecked = settings.generatorProvider === "custom" ? " checked" : "";
const timeLimitEnabled = settings.timeLimitEnabled !== false;
const timeLimitChecked = timeLimitEnabled ? " checked" : "";
const timeLimitDisabled = timeLimitEnabled ? "" : " disabled";
const timeLimitLabel = timeLimitEnabled ? "Enabled" : "No Limit";
const timeLimitMinValue = timeLimitEnabled ? settings.timeLimitMin : 600;
const timeLimitMaxValue = timeLimitEnabled ? settings.timeLimitMax : 900;
const timeLimitSummary = timeLimitEnabled
? `${settings.timeLimitMin}s - ${settings.timeLimitMax}s`
: "No limit";
document.getElementById("app").innerHTML = ` document.getElementById("app").innerHTML = `
<div class="shell"> <div class="shell">
@ -204,13 +235,26 @@
<label for="penaltyMax">Max Rep Hit</label> <label for="penaltyMax">Max Rep Hit</label>
<input id="penaltyMax" type="number" max="0" step="1" value="${settings.penaltyMax}" /> <input id="penaltyMax" type="number" max="0" step="1" value="${settings.penaltyMax}" />
</div> </div>
<div class="field"> <div class="timer-row wide">
<label for="timeLimitMin">Min Time</label> <div class="field">
<input id="timeLimitMin" type="number" min="1" step="60" value="${settings.timeLimitMin}" /> <label for="timeLimitEnabled">Task Timer</label>
</div> <label class="provider-toggle" for="timeLimitEnabled">
<div class="field"> <input id="timeLimitEnabled" type="checkbox"${timeLimitChecked} />
<label for="timeLimitMax">Max Time</label> <span class="switch" aria-hidden="true"></span>
<input id="timeLimitMax" type="number" min="1" step="60" value="${settings.timeLimitMax}" /> <span class="provider-copy">
<strong>${timeLimitLabel}</strong>
<small>Time Limits</small>
</span>
</label>
</div>
<div class="field">
<label for="timeLimitMin">Min Time</label>
<input id="timeLimitMin" type="number" min="1" step="60" value="${timeLimitMinValue}"${timeLimitDisabled} />
</div>
<div class="field">
<label for="timeLimitMax">Max Time</label>
<input id="timeLimitMax" type="number" min="1" step="60" value="${timeLimitMaxValue}"${timeLimitDisabled} />
</div>
</div> </div>
</div> </div>
</section> </section>
@ -222,7 +266,7 @@
</div> </div>
<div class="form compact"> <div class="form compact">
<div class="field"> <div class="field">
<label for="medicalSpawnCost">Medical Spawn</label> <label for="medicalSpawnCost">Medical Respawn</label>
<input id="medicalSpawnCost" type="number" min="0" step="50" value="${settings.medicalSpawnCost}" /> <input id="medicalSpawnCost" type="number" min="0" step="50" value="${settings.medicalSpawnCost}" />
</div> </div>
<div class="field"> <div class="field">
@ -266,10 +310,10 @@
<div class="summary-row"><span>Reward Range</span><strong>$${Number(settings.moneyMin).toLocaleString()} - $${Number(settings.moneyMax).toLocaleString()}</strong></div> <div class="summary-row"><span>Reward Range</span><strong>$${Number(settings.moneyMin).toLocaleString()} - $${Number(settings.moneyMax).toLocaleString()}</strong></div>
<div class="summary-row"><span>Reputation</span><strong>${settings.reputationMin} - ${settings.reputationMax}</strong></div> <div class="summary-row"><span>Reputation</span><strong>${settings.reputationMin} - ${settings.reputationMax}</strong></div>
<div class="summary-row"><span>Reputation Hit</span><strong>${settings.penaltyMin} to ${settings.penaltyMax}</strong></div> <div class="summary-row"><span>Reputation Hit</span><strong>${settings.penaltyMin} to ${settings.penaltyMax}</strong></div>
<div class="summary-row"><span>Time Limit</span><strong>${settings.timeLimitMin}s - ${settings.timeLimitMax}s</strong></div> <div class="summary-row"><span>Time Limit</span><strong>${timeLimitSummary}</strong></div>
<div class="summary-row"><span>Repair / Rearm</span><strong>$${Number(settings.serviceRepairCost).toLocaleString()} / $${Number(settings.serviceRearmCost).toLocaleString()}</strong></div> <div class="summary-row"><span>Repair / Rearm</span><strong>$${Number(settings.serviceRepairCost).toLocaleString()} / $${Number(settings.serviceRearmCost).toLocaleString()}</strong></div>
<div class="summary-row"><span>Fuel</span><strong>$${Number(settings.fuelCost).toLocaleString()} / L</strong></div> <div class="summary-row"><span>Fuel</span><strong>$${Number(settings.fuelCost).toLocaleString()} / L</strong></div>
<div class="summary-row"><span>Medical</span><strong>$${Number(settings.medicalSpawnCost).toLocaleString()} spawn / $${Number(settings.medicalHealCost).toLocaleString()} heal</strong></div> <div class="summary-row"><span>Medical Billing</span><strong>$${Number(settings.medicalSpawnCost).toLocaleString()} respawn / $${Number(settings.medicalHealCost).toLocaleString()} heal</strong></div>
${state.error ? `<div class="notice">${state.error}</div>` : ""} ${state.error ? `<div class="notice">${state.error}</div>` : ""}
</div> </div>
</aside> </aside>
@ -315,7 +359,7 @@
return true; return true;
}); });
state.factions = factions; state.factions = factions;
state.settings = Object.assign({}, state.settings, payload.data?.settings || {}); state.settings = normalizeSettings(Object.assign({}, state.settings, payload.data?.settings || {}));
render(); render();
return true; return true;
} }

View File

@ -34,7 +34,7 @@ GVAR(ActorModel) = compileFinal createHashMapObject [[
if (isArray _loadoutConfig) exitWith { getArray _loadoutConfig }; if (isArray _loadoutConfig) exitWith { getArray _loadoutConfig };
[[],[],[],["U_BG_Guerrilla_6_1",[["FirstAidKit", 2]]],[],[],"H_Cap_blk_ION","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]] [[],[],["hgun_P07_F","","","",["16Rnd_9x21_Mag",4,17],[],""],["U_BG_Guerrilla_6_1",[["FirstAidKit", 2],["ACE_EarPlugs",1]]],["V_Rangemaster_belt",[["16Rnd_9x21_Mag",4]]],[],"H_Cap_blk_ION","",["Binocular","","","",[],[],""],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]
}], }],
["defaults", compileFinal { ["defaults", compileFinal {
private _actor = createHashMap; private _actor = createHashMap;
@ -116,13 +116,11 @@ GVAR(ActorModel) = compileFinal createHashMapObject [[
}] }]
]]; ]];
GVAR(ActorBaseStore) = compileFinal ([ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
EGVAR(common,BaseStore), ["#base", EGVAR(common,BaseStore)],
createHashMapFromArray [
["#type", "ActorBaseStore"], ["#type", "ActorBaseStore"],
["#create", compileFinal { ["#create", compileFinal {
["INFO", "Actor Store Initialized!"] call EFUNC(common,log); ["INFO", "Actor Store Initialized!"] call EFUNC(common,log);
true
}], }],
["cacheActor", compileFinal { ["cacheActor", compileFinal {
params [["_uid", "", [""]], ["_actor", createHashMap, [createHashMap]]]; params [["_uid", "", [""]], ["_actor", createHashMap, [createHashMap]]];
@ -574,13 +572,7 @@ GVAR(ActorBaseStore) = compileFinal ([
_self call ["override", [_uid, _finalActor, false]] _self call ["override", [_uid, _finalActor, false]]
}] }]
]] call { ];
params ["_base", "_child"];
private _merged = +_base; GVAR(ActorStore) = createHashMapObject [GVAR(ActorBaseStore)];
{ _merged set [_x, _y]; } forEach _child; GVAR(ActorStore)
_merged
});
GVAR(ActorStore) = createHashMapObject [GVAR(ActorBaseStore), []];
true

View File

@ -4,6 +4,8 @@ PREP_RECOMPILE_START;
#include "XEH_PREP.hpp" #include "XEH_PREP.hpp"
PREP_RECOMPILE_END; PREP_RECOMPILE_END;
if (isServer) then { "forge_server" callExtension ["surreal:reconnect", []]; };
GVAR(PlayerBootstrapRegistry) = createHashMap; GVAR(PlayerBootstrapRegistry) = createHashMap;
["forge_icom_event", { ["forge_icom_event", {

View File

@ -72,12 +72,13 @@ listed for the requested category. `denylist` removes listed classnames from the
generated category. Overrides are applied server-side, so checkout validation generated category. Overrides are applied server-side, so checkout validation
uses the same prices and descriptions the UI displays. uses the same prices and descriptions the UI displays.
`modMode` applies before category filtering. `allowlist` only keeps generated `modMode` applies before category filtering. `dynamic` means no mod-source
entries that match one of the configured `mods[]`; `denylist` removes matching filtering. `allowlist` only keeps generated entries that match one of the
entries. Each `ModSources` child can define `patches[]` to detect whether the configured `mods[]`; `denylist` removes matching entries. Each `ModSources`
mod is loaded, `addons[]` for config source addon/source mod names or classname child can define `patches[]` to detect whether the mod is loaded, `addons[]`
prefixes, and `prefixes[]` for classname prefixes. If a mod source defines no for exact config source addon/source mod names, and `prefixes[]` for classname,
patches, it is treated as available and only the source/prefix checks are used. source addon, or source mod prefixes. If a mod source defines no patches, it is
treated as available and only the source/prefix checks are used.
`units[]` follows the same `dynamic`, `allowlist`, and `denylist` behavior as `units[]` follows the same `dynamic`, `allowlist`, and `denylist` behavior as
item and vehicle categories. Unit purchases are immediate spawn grants, not item and vehicle categories. Unit purchases are immediate spawn grants, not

View File

@ -111,17 +111,18 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
private _sourceMod = _item getOrDefault ["sourceMod", ""]; private _sourceMod = _item getOrDefault ["sourceMod", ""];
private _addons = (_self call ["getMissionStoreModSourceValues", [_modID, "addons"]]) apply { toLowerANSI _x }; private _addons = (_self call ["getMissionStoreModSourceValues", [_modID, "addons"]]) apply { toLowerANSI _x };
private _prefixes = (_self call ["getMissionStoreModSourceValues", [_modID, "prefixes"]]) apply { toLowerANSI _x }; private _prefixes = (_self call ["getMissionStoreModSourceValues", [_modID, "prefixes"]]) apply { toLowerANSI _x };
private _matchPrefixes = _addons + _prefixes;
private _sourceModLower = toLowerANSI _sourceMod; private _sourceModLower = toLowerANSI _sourceMod;
if (_sourceModLower in _addons) exitWith { true }; if (_sourceModLower in _addons) exitWith { true };
private _sourceAddonMatched = false; private _sourceAddonMatched = false;
{ {
if (_x in _addons) exitWith { _sourceAddonMatched = true; }; if (_x in _addons) exitWith { _sourceAddonMatched = true; };
if (_self call ["doesValueMatchAnyPrefix", [_x, _addons]]) exitWith { _sourceAddonMatched = true; }; if (_self call ["doesValueMatchAnyPrefix", [_x, _matchPrefixes]]) exitWith { _sourceAddonMatched = true; };
} forEach _sourceAddons; } forEach _sourceAddons;
if (_sourceAddonMatched) exitWith { true }; if (_sourceAddonMatched) exitWith { true };
if (_self call ["doesValueMatchAnyPrefix", [_className, _addons + _prefixes]]) exitWith { true }; if (_self call ["doesValueMatchAnyPrefix", [_className, _matchPrefixes]]) exitWith { true };
if (_self call ["doesValueMatchAnyPrefix", [_sourceMod, _addons]]) exitWith { true }; if (_self call ["doesValueMatchAnyPrefix", [_sourceMod, _matchPrefixes]]) exitWith { true };
false false
}], }],

View File

@ -154,7 +154,7 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
_penMin = _penMin min 0; _penMin = _penMin min 0;
_penMax = _penMax min 0; _penMax = _penMax min 0;
_timeMin = _timeMin max 1; _timeMin = _timeMin max 0;
_timeMax = _timeMax max _timeMin; _timeMax = _timeMax max _timeMin;
_medicalSpawnCost = _medicalSpawnCost max 0; _medicalSpawnCost = _medicalSpawnCost max 0;
_medicalHealCost = _medicalHealCost max 0; _medicalHealCost = _medicalHealCost max 0;

View File

@ -164,9 +164,13 @@ airports, bus stops, teleport terminals, or any other mission transport system.
The framework owns the menu, billing, cargo scan, and movement logic. The The framework owns the menu, billing, cargo scan, and movement logic. The
mission only needs placed objects and optional arrival markers. mission only needs placed objects and optional arrival markers.
![Placeholder: Eden transport node object placement](images/eden/transport_node_obj.svg) ![Eden transport location one](images/eden/transport_loc_1.jpg)
![Placeholder: Eden transport node variable name](images/eden/transport_node_var.svg) ![Eden transport location two](images/eden/transport_loc_2.jpg)
![Eden transport node object placement](images/eden/transport_obj_1.jpg)
![Eden transport node variable name](images/eden/transport_obj_1_var.jpg)
Place transport node objects with these variable names: Place transport node objects with these variable names:
@ -188,7 +192,9 @@ transport_arrival_2
transport_arrival_10 transport_arrival_10
``` ```
![Placeholder: Eden transport arrival marker placement](images/eden/transport_arrival_marker.svg) ![Eden transport arrival marker placement](images/eden/transport_arrival_mrkr.jpg)
![Eden transport arrival marker variable name](images/eden/transport_arrival_mrkr_var.jpg)
Objects that should be excluded from the nearby cargo scan, such as the actual Objects that should be excluded from the nearby cargo scan, such as the actual
boat or transport vehicle used as set dressing, should use: boat or transport vehicle used as set dressing, should use:
@ -201,7 +207,9 @@ transport_vehicle_2
transport_vehicle_10 transport_vehicle_10
``` ```
![Placeholder: Eden transport vehicle exclusion object variable name](images/eden/transport_vehicle_var.svg) ![Eden transport vehicle exclusion object placement](images/eden/transport_veh_obj.jpg)
![Eden transport vehicle exclusion object variable name](images/eden/transport_veh_obj_var.jpg)
Minimum Eden setup: Minimum Eden setup:
@ -759,6 +767,11 @@ provider preference. It also exposes service pricing for medical spawn, heal,
repair, rearm, refuel, and transport defaults. It does not enable or disable repair, rearm, refuel, and transport defaults. It does not enable or disable
generated missions; use the CBA setting for that policy. 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.

View File

@ -227,7 +227,7 @@ Player workflow:
1. Stand near a transport point. 1. Stand near a transport point.
2. Open the actor interaction menu. 2. Open the actor interaction menu.
3. Select Transport. 3. Select Transport.
4. Select a destination from the transport submenu, or select Back to return 4. Select a destination from the transport submenu, or select Close to return
to the default interaction menu. to the default interaction menu.
![Transport destination submenu](images/player/transport_destination_menu.jpg) ![Transport destination submenu](images/player/transport_destination_menu.jpg)

View File

@ -72,12 +72,24 @@ 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[]` uses the same filter behavior as every other category. `units[]` uses the same filter behavior as every other category.
`modMode` applies before category filtering. `allowlist` only keeps generated `modMode` applies before category filtering. `dynamic` means no mod-source
entries that match one of the configured `mods[]`; `denylist` removes matching filtering. `allowlist` only keeps generated entries that match one of the
entries. Each `ModSources` child can define `patches[]` to detect whether the configured `mods[]`; `denylist` removes matching entries. Each `ModSources`
mod is loaded, `addons[]` for config source addon/source mod names or classname child can define `patches[]` to detect whether the mod is loaded, `addons[]`
prefixes, and `prefixes[]` for classname prefixes. If a mod source defines no for exact config source addon/source mod names, and `prefixes[]` for classname,
patches, it is treated as available and only the source/prefix checks are used. source addon, or source mod prefixes. If a mod source defines no patches, it is
treated as available and only the source/prefix 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.
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.

View File

@ -631,6 +631,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.

View File

@ -2,7 +2,7 @@
The transport service provides paid point-to-point travel for players and The transport service provides paid point-to-point travel for players and
nearby vehicles or passengers. It is framework-owned: missions only need placed nearby vehicles or passengers. It is framework-owned: missions only need placed
transport objects and arrival markers with the expected variable names. transport objects and optional arrival markers with the expected variable names.
## Mission Contract ## Mission Contract
@ -117,9 +117,9 @@ this setVariable ["transportIncludeCargo", true, true];
Only use overrides when the default `transport*` convention or mission-level Only use overrides when the default `transport*` convention or mission-level
pricing is not appropriate. pricing is not appropriate.
## Reference Images ## Image Checklist
These screenshots show the default transport setup and player workflow: Replace these placeholder image references after screenshots are captured:
![Eden transport location one](images/eden/transport_loc_1.jpg) ![Eden transport location one](images/eden/transport_loc_1.jpg)
@ -142,3 +142,9 @@ These screenshots show the default transport setup and player workflow:
![Player transport destination submenu](images/player/transport_destination_menu.jpg) ![Player transport destination submenu](images/player/transport_destination_menu.jpg)
![Player transport completion notification](images/player/transport_complete.jpg) ![Player transport completion notification](images/player/transport_complete.jpg)
## Mission-Side Code Requirement
No mission-side transport service, addAction script, or server event bridge is
required. The framework handles menu discovery, destination selection, pricing,
billing, cargo movement, and EventBus notifications.

View File

@ -164,9 +164,13 @@ airports, bus stops, teleport terminals, or any other mission transport system.
The framework owns the menu, billing, cargo scan, and movement logic. The The framework owns the menu, billing, cargo scan, and movement logic. The
mission only needs placed objects and optional arrival markers. mission only needs placed objects and optional arrival markers.
![Placeholder: Eden transport node object placement](images/eden/transport_node_obj.svg) ![Eden transport location one](images/eden/transport_loc_1.jpg)
![Placeholder: Eden transport node variable name](images/eden/transport_node_var.svg) ![Eden transport location two](images/eden/transport_loc_2.jpg)
![Eden transport node object placement](images/eden/transport_obj_1.jpg)
![Eden transport node variable name](images/eden/transport_obj_1_var.jpg)
Place transport node objects with these variable names: Place transport node objects with these variable names:
@ -188,7 +192,9 @@ transport_arrival_2
transport_arrival_10 transport_arrival_10
``` ```
![Placeholder: Eden transport arrival marker placement](images/eden/transport_arrival_marker.svg) ![Eden transport arrival marker placement](images/eden/transport_arrival_mrkr.jpg)
![Eden transport arrival marker variable name](images/eden/transport_arrival_mrkr_var.jpg)
Objects that should be excluded from the nearby cargo scan, such as the actual Objects that should be excluded from the nearby cargo scan, such as the actual
boat or transport vehicle used as set dressing, should use: boat or transport vehicle used as set dressing, should use:
@ -201,7 +207,9 @@ transport_vehicle_2
transport_vehicle_10 transport_vehicle_10
``` ```
![Placeholder: Eden transport vehicle exclusion object variable name](images/eden/transport_vehicle_var.svg) ![Eden transport vehicle exclusion object placement](images/eden/transport_veh_obj.jpg)
![Eden transport vehicle exclusion object variable name](images/eden/transport_veh_obj_var.jpg)
Minimum Eden setup: Minimum Eden setup:

View File

@ -226,7 +226,7 @@ Player workflow:
1. Stand near a transport point. 1. Stand near a transport point.
2. Open the actor interaction menu. 2. Open the actor interaction menu.
3. Select Transport. 3. Select Transport.
4. Select a destination from the transport submenu, or select Back to return 4. Select a destination from the transport submenu, or select Close to return
to the default interaction menu. to the default interaction menu.
![Transport destination submenu](images/player/transport_destination_menu.jpg) ![Transport destination submenu](images/player/transport_destination_menu.jpg)

View File

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

View File

@ -1,6 +1,6 @@
--- ---
title: "Transport Service Guide" title: "Transport Service Guide"
description: "The transport service provides paid point-to-point travel for players and nearby vehicles or passengers. It is framework-owned: missions only need placed transport objects and arrival markers with the expected variable names." description: "The transport service provides paid point-to-point travel for players and nearby vehicles or passengers. It is framework-owned: missions only need placed transport objects and optional arrival markers with the expected variable names."
--- ---
## Mission Contract ## Mission Contract
@ -109,9 +109,9 @@ this setVariable ["transportIncludeCargo", true, true];
Only use overrides when the default `transport*` convention is not appropriate. Only use overrides when the default `transport*` convention is not appropriate.
## Reference Images ## Image Checklist
These screenshots show the default transport setup and player workflow: Replace these placeholder image references after screenshots are captured:
![Eden transport location one](images/eden/transport_loc_1.jpg) ![Eden transport location one](images/eden/transport_loc_1.jpg)
@ -134,3 +134,9 @@ These screenshots show the default transport setup and player workflow:
![Player transport destination submenu](images/player/transport_destination_menu.jpg) ![Player transport destination submenu](images/player/transport_destination_menu.jpg)
![Player transport completion notification](images/player/transport_complete.jpg) ![Player transport completion notification](images/player/transport_complete.jpg)
## Mission-Side Code Requirement
No mission-side transport service, addAction script, or server event bridge is
required. The framework handles menu discovery, destination selection, pricing,
billing, cargo movement, and EventBus notifications.