Update framework mission configuration systems

This commit is contained in:
Jacob Schmidt 2026-06-03 17:36:13 -05:00
parent 6229f56ba4
commit 623f718caf
46 changed files with 1044 additions and 239 deletions

View File

@ -79,14 +79,32 @@ switch (_event) do {
hint "Transport destination is no longer available."; hint "Transport destination is no longer available.";
}; };
private _transportSetting = {
params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
};
private _options = createHashMapFromArray [ private _options = createHashMapFromArray [
["label", _data getOrDefault ["label", "Transport"]], ["label", _data getOrDefault ["label", "Transport"]],
["nodePrefix", _data getOrDefault ["nodePrefix", "transport"]], ["nodePrefix", _data getOrDefault ["nodePrefix", "transport"]],
["vehiclePrefix", _data getOrDefault ["vehiclePrefix", "transport_vehicle"]], ["vehiclePrefix", _data getOrDefault ["vehiclePrefix", "transport_vehicle"]],
["arrivalPrefix", _data getOrDefault ["arrivalPrefix", "transport_arrival"]], ["arrivalPrefix", _data getOrDefault ["arrivalPrefix", "transport_arrival"]],
["maxIndexedNodes", _data getOrDefault ["maxIndexedNodes", 10]], ["maxIndexedNodes", _data getOrDefault ["maxIndexedNodes", 10]],
["baseFare", _data getOrDefault ["baseFare", 100]], ["baseFare", _data getOrDefault ["baseFare", ["transportBaseFare", 100] call _transportSetting]],
["pricePerKm", _data getOrDefault ["pricePerKm", 50]], ["pricePerKm", _data getOrDefault ["pricePerKm", ["transportPricePerKm", 50] call _transportSetting]],
["cargoRadius", _data getOrDefault ["cargoRadius", 25]], ["cargoRadius", _data getOrDefault ["cargoRadius", 25]],
["includeCargo", _data getOrDefault ["includeCargo", true]] ["includeCargo", _data getOrDefault ["includeCargo", true]]
]; ];

View File

@ -142,8 +142,25 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
if (_isTransport) then { if (_isTransport) then {
private _fromTransportNode = _x; private _fromTransportNode = _x;
private _maxIndexedNodes = _x getVariable ["transportMaxIndexedNodes", 10]; private _maxIndexedNodes = _x getVariable ["transportMaxIndexedNodes", 10];
private _baseFare = _x getVariable ["transportBaseFare", 100]; private _transportSetting = {
private _pricePerKm = _x getVariable ["transportPricePerKm", 50]; params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
};
private _baseFare = _x getVariable ["transportBaseFare", ["transportBaseFare", 100] call _transportSetting];
private _pricePerKm = _x getVariable ["transportPricePerKm", ["transportPricePerKm", 50] call _transportSetting];
private _vehiclePrefix = _x getVariable ["transportVehiclePrefix", format ["%1_vehicle", _transportPrefix]]; private _vehiclePrefix = _x getVariable ["transportVehiclePrefix", format ["%1_vehicle", _transportPrefix]];
private _arrivalPrefix = _x getVariable ["transportArrivalPrefix", format ["%1_arrival", _transportPrefix]]; private _arrivalPrefix = _x getVariable ["transportArrivalPrefix", format ["%1_arrival", _transportPrefix]];
private _nodeNames = [_transportPrefix]; private _nodeNames = [_transportPrefix];

View File

@ -151,10 +151,21 @@ GVAR(MissionSetupRepositoryBaseClass) = compileFinal createHashMapFromArray [
private _paramOrDefault = { private _paramOrDefault = {
params ["_varName", "_default"]; params ["_varName", "_default"];
private _value = missionNamespace getVariable [_varName, _default]; private _paramValue = [_varName, _default] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_varName, _paramValue];
if (_value isEqualType "") exitWith { parseNumber _value }; if (_value isEqualType "") exitWith { parseNumber _value };
_value _value
}; };
private _serviceDefault = {
params ["_varName", "_default"];
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _varName)) exitWith {
getNumber (_serviceConfig >> _varName)
};
_default
};
private _factions = []; private _factions = [];
{ {
@ -197,6 +208,13 @@ GVAR(MissionSetupRepositoryBaseClass) = compileFinal createHashMapFromArray [
["penaltyMax", ["penaltyMax", -25] call _paramOrDefault], ["penaltyMax", ["penaltyMax", -25] call _paramOrDefault],
["timeLimitMin", ["timeLimitMin", 600] call _paramOrDefault], ["timeLimitMin", ["timeLimitMin", 600] call _paramOrDefault],
["timeLimitMax", ["timeLimitMax", 900] call _paramOrDefault], ["timeLimitMax", ["timeLimitMax", 900] call _paramOrDefault],
["medicalSpawnCost", ["medicalSpawnCost", ["medicalSpawnCost", 100] call _serviceDefault] call _paramOrDefault],
["medicalHealCost", ["medicalHealCost", ["medicalHealCost", 100] call _serviceDefault] call _paramOrDefault],
["serviceRepairCost", ["serviceRepairCost", ["serviceRepairCost", 500] call _serviceDefault] call _paramOrDefault],
["serviceRearmCost", ["serviceRearmCost", ["serviceRearmCost", 500] call _serviceDefault] call _paramOrDefault],
["fuelCost", ["fuelCost", ["fuelCost", 5] call _serviceDefault] call _paramOrDefault],
["transportBaseFare", ["transportBaseFare", ["transportBaseFare", 100] call _serviceDefault] call _paramOrDefault],
["transportPricePerKm", ["transportPricePerKm", ["transportPricePerKm", 50] call _serviceDefault] call _paramOrDefault],
["generatorProvider", GETMVAR(forge_server_task_generatorProvider,"builtin")] ["generatorProvider", GETMVAR(forge_server_task_generatorProvider,"builtin")]
]] ]]
] ]

View File

@ -54,8 +54,8 @@ button {
} }
.titlebar { .titlebar {
min-height: 2.75rem; min-height: 2.5rem;
padding: 0 1.35rem; padding: 0 1.1rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -98,18 +98,19 @@ option {
.content { .content {
min-height: 0; min-height: 0;
padding: 1rem 1.25rem; padding: 0.75rem 1rem;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.grid { .grid {
max-width: 78rem; width: min(94rem, 100%);
max-width: 94rem;
margin: 0 auto; margin: 0 auto;
display: grid; display: grid;
grid-template-columns: 1.1fr 0.9fr; grid-template-columns: minmax(28rem, 1.35fr) minmax(18rem, 0.8fr) minmax(20rem, 0.85fr);
gap: 1rem; gap: 0.75rem;
} }
.panel { .panel {
@ -120,22 +121,26 @@ option {
} }
.panel-head { .panel-head {
padding: 0.85rem 1rem; padding: 0.65rem 0.85rem;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
.panel-head h1, .panel-head h1,
.panel-head h2 { .panel-head h2 {
margin: 0.2rem 0 0; margin: 0.2rem 0 0;
font-size: 1.18rem; font-size: 1.02rem;
letter-spacing: 0; letter-spacing: 0;
} }
.form { .form {
padding: 0.9rem 1rem 1rem; padding: 0.7rem 0.85rem 0.85rem;
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 0.68rem; gap: 0.5rem;
}
.form.compact {
gap: 0.55rem;
} }
.field { .field {
@ -149,7 +154,7 @@ option {
label { label {
color: var(--text-subtle); color: var(--text-subtle);
font-size: 0.68rem; font-size: 0.62rem;
font-weight: 800; font-weight: 800;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
@ -172,8 +177,8 @@ label {
} }
.provider-toggle { .provider-toggle {
min-height: 2.25rem; min-height: 2rem;
padding: 0 0.75rem; padding: 0 0.65rem;
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
align-items: center; align-items: center;
@ -249,8 +254,8 @@ label {
input, input,
select { select {
width: 100%; width: 100%;
min-height: 2.25rem; min-height: 2rem;
padding: 0 0.75rem; padding: 0 0.65rem;
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(24, 31, 40, 0.9); background: rgba(24, 31, 40, 0.9);
color: var(--text-main); color: var(--text-main);
@ -264,16 +269,16 @@ button:focus-visible {
} }
.summary { .summary {
padding: 0.9rem 1rem 1rem; padding: 0.7rem 0.85rem 0.85rem;
display: grid; display: grid;
gap: 0.55rem; gap: 0.42rem;
} }
.summary-row { .summary-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 1rem; gap: 1rem;
padding-bottom: 0.55rem; padding-bottom: 0.42rem;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
@ -294,7 +299,7 @@ button:focus-visible {
} }
.actions { .actions {
padding: 0.75rem 1.25rem; padding: 0.6rem 1rem;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 0.75rem; gap: 0.75rem;
@ -303,7 +308,7 @@ button:focus-visible {
} }
.btn { .btn {
min-height: 2.25rem; min-height: 2rem;
padding: 0.55rem 0.9rem; padding: 0.55rem 0.9rem;
border: 1px solid var(--border-strong); border: 1px solid var(--border-strong);
background: rgba(24, 31, 40, 0.9); background: rgba(24, 31, 40, 0.9);

View File

@ -14,6 +14,13 @@
penaltyMax: -25, penaltyMax: -25,
timeLimitMin: 600, timeLimitMin: 600,
timeLimitMax: 900, timeLimitMax: 900,
medicalSpawnCost: 100,
medicalHealCost: 100,
serviceRepairCost: 500,
serviceRearmCost: 500,
fuelCost: 5,
transportBaseFare: 100,
transportPricePerKm: 50,
generatorProvider: "builtin", generatorProvider: "builtin",
}, },
error: "", error: "",
@ -47,6 +54,13 @@
penaltyMax: fieldNumber("penaltyMax"), penaltyMax: fieldNumber("penaltyMax"),
timeLimitMin: fieldNumber("timeLimitMin"), timeLimitMin: fieldNumber("timeLimitMin"),
timeLimitMax: fieldNumber("timeLimitMax"), timeLimitMax: fieldNumber("timeLimitMax"),
medicalSpawnCost: fieldNumber("medicalSpawnCost"),
medicalHealCost: fieldNumber("medicalHealCost"),
serviceRepairCost: fieldNumber("serviceRepairCost"),
serviceRearmCost: fieldNumber("serviceRearmCost"),
fuelCost: fieldNumber("fuelCost"),
transportBaseFare: fieldNumber("transportBaseFare"),
transportPricePerKm: fieldNumber("transportPricePerKm"),
generatorProvider: document.getElementById("generatorProviderCustom")?.checked ? "custom" : "builtin", generatorProvider: document.getElementById("generatorProviderCustom")?.checked ? "custom" : "builtin",
}; };
} }
@ -86,6 +100,21 @@
return; return;
} }
const costFields = [
settings.medicalSpawnCost,
settings.medicalHealCost,
settings.serviceRepairCost,
settings.serviceRearmCost,
settings.fuelCost,
settings.transportBaseFare,
settings.transportPricePerKm,
];
if (costFields.some((value) => value < 0)) {
state.error = "Service pricing cannot use negative values.";
render();
return;
}
state.error = ""; state.error = "";
send("missionSetup::apply", settings); send("missionSetup::apply", settings);
} }
@ -186,6 +215,43 @@
</div> </div>
</section> </section>
<aside class="panel">
<div class="panel-head">
<span class="kicker">Service Pricing</span>
<h2>Economy Settings</h2>
</div>
<div class="form compact">
<div class="field">
<label for="medicalSpawnCost">Medical Spawn</label>
<input id="medicalSpawnCost" type="number" min="0" step="50" value="${settings.medicalSpawnCost}" />
</div>
<div class="field">
<label for="medicalHealCost">Medical Heal</label>
<input id="medicalHealCost" type="number" min="0" step="50" value="${settings.medicalHealCost}" />
</div>
<div class="field">
<label for="serviceRepairCost">Repair</label>
<input id="serviceRepairCost" type="number" min="0" step="50" value="${settings.serviceRepairCost}" />
</div>
<div class="field">
<label for="serviceRearmCost">Rearm</label>
<input id="serviceRearmCost" type="number" min="0" step="50" value="${settings.serviceRearmCost}" />
</div>
<div class="field">
<label for="fuelCost">Fuel / Liter</label>
<input id="fuelCost" type="number" min="0" step="1" value="${settings.fuelCost}" />
</div>
<div class="field">
<label for="transportBaseFare">Transport Base</label>
<input id="transportBaseFare" type="number" min="0" step="25" value="${settings.transportBaseFare}" />
</div>
<div class="field wide">
<label for="transportPricePerKm">Transport / KM</label>
<input id="transportPricePerKm" type="number" min="0" step="25" value="${settings.transportPricePerKm}" />
</div>
</div>
</aside>
<aside class="panel"> <aside class="panel">
<div class="panel-head"> <div class="panel-head">
<span class="kicker">Current Selection</span> <span class="kicker">Current Selection</span>
@ -201,6 +267,9 @@
<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>${settings.timeLimitMin}s - ${settings.timeLimitMax}s</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>Medical</span><strong>$${Number(settings.medicalSpawnCost).toLocaleString()} spawn / $${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>

View File

@ -25,13 +25,38 @@ life state, phone number, email, organization, and holster state.
## Runtime Behavior ## Runtime Behavior
- Missing persistent actors can be created from live player snapshots. - Missing persistent actors can be created from live player snapshots.
- Newly created actors receive a Field Commander job orientation email, two - Newly created actors receive their starting loadout from mission
`CfgStartingEquipment`, plus a Field Commander job orientation email, two
Field Commander text messages, and a `$2,000` starting credit in their bank Field Commander text messages, and a `$2,000` starting credit in their bank
account. account.
- Hot actor reads are migrated and hydrated before use. - Hot actor reads are migrated and hydrated before use.
- `saveHotState` in the main addon snapshots and saves actor state on player - `saveHotState` in the main addon snapshots and saves actor state on player
disconnect and mission end. disconnect and mission end.
## Starting Equipment
Missions can include `CfgStartingEquipment.hpp` from `description.ext` to
override starter actor gear without recompiling the addon or extension.
```cpp
class CfgStartingEquipment {
loadout[] = {
{},
{},
{},
{"U_BG_Guerrilla_6_1", {{"FirstAidKit", 2}}},
{},
{},
"H_Cap_blk_ION",
"",
{},
{"ItemMap", "ItemGPS", "ItemRadio", "ItemCompass", "ItemWatch", ""}
};
};
```
The Rust actor model no longer hardcodes a starter loadout. SQF supplies the
mission-configured loadout when it creates a missing actor record.
## Event Surface ## Event Surface
The addon handles server events for actor init, get, set, multi-set, save, and The addon handles server events for actor init, get, set, multi-set, save, and
remove requests, then replies to the requesting player through client actor RPCs. remove requests, then replies to the requesting player through client actor RPCs.

View File

@ -4,7 +4,7 @@
* File: fnc_initActorStore.sqf * File: fnc_initActorStore.sqf
* Author: IDSolutions * Author: IDSolutions
* Date: 2025-12-17 * Date: 2025-12-17
* Last Update: 2026-05-16 * Last Update: 2026-06-03
* Public: Yes * Public: Yes
* *
* Description: * Description:
@ -25,12 +25,23 @@
#pragma hemtt ignore_variables ["_self"] #pragma hemtt ignore_variables ["_self"]
GVAR(ActorModel) = compileFinal createHashMapObject [[ GVAR(ActorModel) = compileFinal createHashMapObject [[
["#type", "ActorModel"], ["#type", "ActorModel"],
["getStartingConfig", compileFinal {
missionConfigFile >> "CfgStartingEquipment"
}],
["getDefaultLoadout", compileFinal {
private _config = _self call ["getStartingConfig", []];
private _loadoutConfig = _config >> "loadout";
if (isArray _loadoutConfig) exitWith { getArray _loadoutConfig };
[[],[],[],["U_BG_Guerrilla_6_1",[["FirstAidKit", 2]]],[],[],"H_Cap_blk_ION","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]
}],
["defaults", compileFinal { ["defaults", compileFinal {
private _actor = createHashMap; private _actor = createHashMap;
_actor set ["uid", ""]; _actor set ["uid", ""];
_actor set ["name", ""]; _actor set ["name", ""];
_actor set ["loadout", [[],[],[],["U_BG_Guerrilla_6_1",[["FirstAidKit", 2]]],[],[],"H_Cap_blk_ION","",[],["ItemMap","ItemGPS","ItemRadio","ItemCompass","ItemWatch",""]]]; _actor set ["loadout", _self call ["getDefaultLoadout", []]];
_actor set ["position", [0,0,0]]; _actor set ["position", [0,0,0]];
_actor set ["direction", 0]; _actor set ["direction", 0];
_actor set ["stance", "STAND"]; _actor set ["stance", "STAND"];

View File

@ -9,6 +9,21 @@ inventory handling.
Current stores cover fuel tracking, medical service behavior, and service Current stores cover fuel tracking, medical service behavior, and service
charges such as repairs and rearming. charges such as repairs and rearming.
## Configurable Prices
Service prices are read dynamically from mission namespace values so the
framework mission setup UI can override them at startup. If the UI is cancelled
or unavailable, mission `Params` with matching names are used as the backup;
if no param is defined, `CfgMissions >> ServicePricing` provides the fallback.
Supported setting names:
- `medicalSpawnCost` - best-effort medical respawn charge; default `100`
- `medicalHealCost` - heal charge; default `100`
- `serviceRepairCost` - default repair service charge; default `500`
- `serviceRearmCost` - default rearm service charge; default `500`
- `fuelCost` - refuel price per liter; default `5`
- `transportBaseFare` - transport fare base price; default `100`
- `transportPricePerKm` - transport distance price; default `50`
## Dependencies ## Dependencies
- `forge_server_main` - `forge_server_main`
- `forge_server_common` for logging, formatting, and player lookup - `forge_server_common` for logging, formatting, and player lookup
@ -23,7 +38,8 @@ Note: Bank and Org are runtime-only dependencies (not compile-time requiredAddon
totals, charges the player's organization through `OrgStore`, syncs the org totals, charges the player's organization through `OrgStore`, syncs the org
patch, and rolls fuel back to the starting level when organization funds patch, and rolls fuel back to the starting level when organization funds
cannot cover the refuel. cannot cover the refuel.
- `fnc_initMEconomyStore.sqf` manages medical spawn occupancy, healing charges, - `fnc_initMEconomyStore.sqf` manages medical spawn occupancy, medical spawn
billing, healing charges,
respawn placement, death inventory handling, and body-bag transfer. Medical respawn placement, death inventory handling, and body-bag transfer. Medical
charges use player bank/cash first, then organization funds with repayable charges use player bank/cash first, then organization funds with repayable
member debt only when the player cannot cover the service. member debt only when the player cannot cover the service.

View File

@ -32,6 +32,23 @@ GVAR(FEconomyStore) = createHashMapObject [[
["INFO", "Fuel Store Initialized!", nil, nil] call EFUNC(common,log); ["INFO", "Fuel Store Initialized!", nil, nil] call EFUNC(common,log);
}], }],
["numberSetting", {
params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
}],
["start", { ["start", {
params ["_source", "_target", "_unit"]; params ["_source", "_target", "_unit"];
@ -100,7 +117,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
if (_fuelCapacity <= 0) then { _fuelCapacity = 100; }; if (_fuelCapacity <= 0) then { _fuelCapacity = 100; };
private _totalLiters = _missingFuel * _fuelCapacity; private _totalLiters = _missingFuel * _fuelCapacity;
private _totalCost = _totalLiters * GVAR(FuelCost); private _totalCost = _totalLiters * (_self call ["numberSetting", ["fuelCost", GVAR(FuelCost)]]);
private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _totalCost, "Refueling"]]; private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _totalCost, "Refueling"]];
if !(_chargeResult getOrDefault ["success", false]) exitWith { if !(_chargeResult getOrDefault ["success", false]) exitWith {
_self call ["notify", [_unit, "danger", "Refueling", _chargeResult getOrDefault ["message", "Organization funds cannot cover this refuel. Refueling was not completed."]]]; _self call ["notify", [_unit, "danger", "Refueling", _chargeResult getOrDefault ["message", "Organization funds cannot cover this refuel. Refueling was not completed."]]];
@ -130,7 +147,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
private _player = [_uid] call EFUNC(common,getPlayer); private _player = [_uid] call EFUNC(common,getPlayer);
private _totalLiters = GETVAR(_target,liters,0); private _totalLiters = GETVAR(_target,liters,0);
private _totalCost = _totalLiters * GVAR(FuelCost); private _totalCost = _totalLiters * (_self call ["numberSetting", ["fuelCost", GVAR(FuelCost)]]);
private _formattedTotalCost = [_totalCost] call EFUNC(common,formatNumber); private _formattedTotalCost = [_totalCost] call EFUNC(common,formatNumber);
private _formattedTotalLiters = _totalLiters toFixed 2; private _formattedTotalLiters = _totalLiters toFixed 2;

View File

@ -4,7 +4,7 @@
* File: fnc_initMEconomyStore.sqf * File: fnc_initMEconomyStore.sqf
* Author: IDSolutions * Author: IDSolutions
* Date: 2025-12-20 * Date: 2025-12-20
* Last Update: 2026-05-15 * Last Update: 2026-06-03
* Public: No * Public: No
* *
* Description: * Description:
@ -30,8 +30,26 @@ GVAR(MEconomyStore) = createHashMapObject [[
_self set ["mSpawns", createHashMap]; _self set ["mSpawns", createHashMap];
GVAR(occupancyTriggers) = []; GVAR(occupancyTriggers) = [];
GVAR(SpawnCost) = 100;
["INFO", "Medical Store Initialized!", nil, nil] call EFUNC(common,log); ["INFO", "Medical Store Initialized!", nil, nil] call EFUNC(common,log);
}], }],
["numberSetting", {
params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
}],
["init", { ["init", {
private _mSpawns = (_self get "mSpawns"); private _mSpawns = (_self get "mSpawns");
private _prefix = "med_spawn"; private _prefix = "med_spawn";
@ -166,40 +184,61 @@ GVAR(MEconomyStore) = createHashMapObject [[
_result set ["message", ""]; _result set ["message", ""];
_result _result
}], }],
["onHealed", { ["chargeMedicalService", {
params [["_unit", objNull, [objNull]]]; params [
["_unit", objNull, [objNull]],
if (isNull _unit) exitWith { ["WARNING", format ["Invalid unit provided: %1", (name _unit)], nil, nil] call EFUNC(common,log); }; ["_amount", 0, [0]],
["_serviceLabel", "Medical service", [""]],
["_requirePayment", true, [true]]
];
if (isNull _unit) exitWith {
["WARNING", format ["Invalid unit provided: %1", (name _unit)], nil, nil] call EFUNC(common,log);
false
};
private _uid = getPlayerUID _unit; private _uid = getPlayerUID _unit;
if (_uid isEqualTo "") exitWith { ["WARNING", "Unable to charge medical service for unit without UID.", nil, nil] call EFUNC(common,log); }; if (_uid isEqualTo "") exitWith {
["WARNING", "Unable to charge medical service for unit without UID.", nil, nil] call EFUNC(common,log);
!_requirePayment
};
private _healCost = 100; if (_amount <= 0) exitWith { true };
private _personalCharge = _self call ["chargePlayer", [_uid, _healCost]]; private _personalCharge = _self call ["chargePlayer", [_uid, _amount]];
if (_personalCharge getOrDefault ["success", false]) exitWith { if (_personalCharge getOrDefault ["success", false]) exitWith {
private _sourceLabel = ["cash", "bank"] select ((_personalCharge getOrDefault ["source", "bank"]) isEqualTo "bank"); private _sourceLabel = ["cash", "bank"] select ((_personalCharge getOrDefault ["source", "bank"]) isEqualTo "bank");
_self call ["notify", [_unit, "info", "Medical Billing", format ["Medical service charged $%1 from your %2.", [_healCost] call EFUNC(common,formatNumber), _sourceLabel]]]; _self call ["notify", [_unit, "info", "Medical Billing", format ["%1 charged $%2 from your %3.", _serviceLabel, [_amount] call EFUNC(common,formatNumber), _sourceLabel]]];
[CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent); true
}; };
if !(_personalCharge getOrDefault ["fallbackEligible", false]) exitWith { if !(_personalCharge getOrDefault ["fallbackEligible", false]) exitWith {
private _message = _personalCharge getOrDefault ["message", "Personal funds could not be charged for medical service."]; private _message = _personalCharge getOrDefault ["message", "Personal funds could not be charged for medical service."];
_self call ["notify", [_unit, "danger", "Medical Billing", _message]]; _self call ["notify", [_unit, "danger", "Medical Billing", _message]];
!_requirePayment
}; };
if (isNil QGVAR(SEconomyStore)) exitWith { if (isNil QGVAR(SEconomyStore)) exitWith {
["ERROR", "Service economy store unavailable for medical organization fallback charge.", nil, nil] call EFUNC(common,log); ["ERROR", "Service economy store unavailable for medical organization fallback charge.", nil, nil] call EFUNC(common,log);
_self call ["notify", [_unit, "danger", "Medical Billing", "Organization billing is unavailable. Medical service cannot complete."]]; _self call ["notify", [_unit, "danger", "Medical Billing", "Organization billing is unavailable. Medical service cannot complete."]];
!_requirePayment
}; };
private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _healCost, "Medical", true]]; private _chargeResult = GVAR(SEconomyStore) call ["chargeOrg", [_unit, _amount, "Medical", true]];
if !(_chargeResult getOrDefault ["success", false]) exitWith { if !(_chargeResult getOrDefault ["success", false]) exitWith {
private _message = _chargeResult getOrDefault ["message", "Organization funds cannot cover this medical service."]; private _message = _chargeResult getOrDefault ["message", "Organization funds cannot cover this medical service."];
_self call ["notify", [_unit, "danger", "Medical Billing", _message]]; _self call ["notify", [_unit, "danger", "Medical Billing", _message]];
!_requirePayment
}; };
_self call ["notify", [_unit, "info", "Medical Billing", format ["Personal funds could not cover medical service. Organization charged $%1; repay it through your organization credit line.", [_healCost] call EFUNC(common,formatNumber)]]]; _self call ["notify", [_unit, "info", "Medical Billing", format ["Personal funds could not cover %1. Organization charged $%2; repay it through your organization credit line.", _serviceLabel, [_amount] call EFUNC(common,formatNumber)]]];
true
}],
["onHealed", {
params [["_unit", objNull, [objNull]]];
private _healCost = _self call ["numberSetting", ["medicalHealCost", 100]];
if !(_self call ["chargeMedicalService", [_unit, _healCost, "Medical service", true]]) exitWith {};
[CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent); [CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent);
}], }],
["onRespawn", { ["onRespawn", {
@ -214,6 +253,8 @@ GVAR(MEconomyStore) = createHashMapObject [[
deleteVehicle _corpse; deleteVehicle _corpse;
private _player = [_uid] call EFUNC(common,getPlayer); private _player = [_uid] call EFUNC(common,getPlayer);
private _spawnCost = _self call ["numberSetting", ["medicalSpawnCost", GVAR(SpawnCost)]];
_self call ["chargeMedicalService", [_player, _spawnCost, "Medical spawn", false]];
[CRPC(actor,onActorRespawn), [_loadout, _medSpawnPos, _medSpawnDir], _player] call CFUNC(targetEvent); [CRPC(actor,onActorRespawn), [_loadout, _medSpawnPos, _medSpawnDir], _player] call CFUNC(targetEvent);
}], }],
["onKilled", { ["onKilled", {

View File

@ -30,6 +30,23 @@ GVAR(SEconomyStore) = createHashMapObject [[
GVAR(ServiceRearmCost) = 500; GVAR(ServiceRearmCost) = 500;
["INFO", "Service Store Initialized!", nil, nil] call EFUNC(common,log); ["INFO", "Service Store Initialized!", nil, nil] call EFUNC(common,log);
}], }],
["numberSetting", {
params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
}],
["notify", { ["notify", {
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Service", [""]], ["_message", "", [""]]]; params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Service", [""]], ["_message", "", [""]]];
@ -148,7 +165,7 @@ GVAR(SEconomyStore) = createHashMapObject [[
if (isNull _target || { isNull _unit }) exitWith { false }; if (isNull _target || { isNull _unit }) exitWith { false };
private _repairCost = [_cost, GVAR(ServiceRepairCost)] select (_cost < 0); private _repairCost = [_cost, _self call ["numberSetting", ["serviceRepairCost", GVAR(ServiceRepairCost)]]] select (_cost < 0);
private _charge = _self call ["chargeOrg", [_unit, _repairCost, "Repair"]]; private _charge = _self call ["chargeOrg", [_unit, _repairCost, "Repair"]];
if !(_charge getOrDefault ["success", false]) exitWith { if !(_charge getOrDefault ["success", false]) exitWith {
_self call ["notify", [_unit, "danger", "Repair", _charge getOrDefault ["message", "Organization funds cannot cover this repair."]]]; _self call ["notify", [_unit, "danger", "Repair", _charge getOrDefault ["message", "Organization funds cannot cover this repair."]]];
@ -164,7 +181,7 @@ GVAR(SEconomyStore) = createHashMapObject [[
if (isNull _target || { isNull _unit }) exitWith { false }; if (isNull _target || { isNull _unit }) exitWith { false };
private _rearmCost = [_cost, GVAR(ServiceRearmCost)] select (_cost < 0); private _rearmCost = [_cost, _self call ["numberSetting", ["serviceRearmCost", GVAR(ServiceRearmCost)]]] select (_cost < 0);
private _charge = _self call ["chargeOrg", [_unit, _rearmCost, "Rearm"]]; private _charge = _self call ["chargeOrg", [_unit, _rearmCost, "Rearm"]];
if !(_charge getOrDefault ["success", false]) exitWith { if !(_charge getOrDefault ["success", false]) exitWith {
_self call ["notify", [_unit, "danger", "Rearm", _charge getOrDefault ["message", "Organization funds cannot cover this rearm."]]]; _self call ["notify", [_unit, "danger", "Rearm", _charge getOrDefault ["message", "Organization funds cannot cover this rearm."]]];

View File

@ -34,3 +34,26 @@ Garage listens for sync events through the event bus:
- `notification.requested` - storage and vehicle modification alerts - `notification.requested` - storage and vehicle modification alerts
The store module emits these events when granting vehicles; garage applies the changes to player state. The store module emits these events when granting vehicles; garage applies the changes to player state.
## Starting Unlocks
Missions can include `CfgStartingEquipment.hpp` from `description.ext` to
configure initial virtual garage unlocks for new players.
```cpp
class CfgStartingEquipment {
class Unlocks {
class Garage {
cars[] = {"B_Quadbike_01_F"};
armor[] = {};
helis[] = {};
planes[] = {};
naval[] = {};
other[] = {};
};
};
};
```
The extension virtual garage default is intentionally empty. The server addon
seeds `CfgStartingEquipment` unlocks only when a player does not already have a
persistent owner-scoped garage record.

View File

@ -24,15 +24,28 @@
#pragma hemtt ignore_variables ["_self"] #pragma hemtt ignore_variables ["_self"]
GVAR(VGarageModel) = compileFinal createHashMapObject [[ GVAR(VGarageModel) = compileFinal createHashMapObject [[
["#type", "VGarageModel"], ["#type", "VGarageModel"],
["getStartingUnlocksConfig", compileFinal {
missionConfigFile >> "CfgStartingEquipment" >> "Unlocks" >> "Garage"
}],
["getStartingUnlocks", compileFinal {
params [["_category", "", [""]], ["_fallback", [], [[]]]];
private _config = _self call ["getStartingUnlocksConfig", []];
private _categoryConfig = _config >> _category;
if (isArray _categoryConfig) exitWith { getArray _categoryConfig };
+_fallback
}],
["defaults", compileFinal { ["defaults", compileFinal {
private _vGarage = createHashMap; private _vGarage = createHashMap;
_vGarage set ["armor", []]; _vGarage set ["armor", _self call ["getStartingUnlocks", ["armor", []]]];
_vGarage set ["cars", ["B_Quadbike_01_F"]]; _vGarage set ["cars", _self call ["getStartingUnlocks", ["cars", ["B_Quadbike_01_F"]]]];
_vGarage set ["helis", []]; _vGarage set ["helis", _self call ["getStartingUnlocks", ["helis", []]]];
_vGarage set ["naval", []]; _vGarage set ["naval", _self call ["getStartingUnlocks", ["naval", []]]];
_vGarage set ["other", []]; _vGarage set ["other", _self call ["getStartingUnlocks", ["other", []]]];
_vGarage set ["planes", []]; _vGarage set ["planes", _self call ["getStartingUnlocks", ["planes", []]]];
_vGarage _vGarage
}] }]
@ -71,17 +84,46 @@ GVAR(VGBaseStore) = compileFinal ([
private _command = ["owned:garage:hot:fetch", "owned:garage:hot:init"] select _initialize; private _command = ["owned:garage:hot:fetch", "owned:garage:hot:init"] select _initialize;
_self call ["callHotVGarage", [_command, [_uid]]] _self call ["callHotVGarage", [_command, [_uid]]]
}], }],
["isPersistentVGarageInitialized", compileFinal {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { false };
["owned:garage:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
_isSuccess && { _result isEqualTo "true" }
}],
["seedStartingUnlocks", compileFinal {
params [["_uid", "", [""]], ["_garage", createHashMap, [createHashMap]]];
if (_uid isEqualTo "" || { _garage isEqualTo createHashMap }) exitWith { _garage };
private _defaults = GVAR(VGarageModel) call ["defaults", []];
private _seeded = +_garage;
{
_seeded set [_x, +_y];
} forEach _defaults;
private _updated = _self call ["callHotVGarage", ["owned:garage:hot:override", [_uid, toJSON _seeded]]];
if (_updated isEqualTo createHashMap) exitWith { _seeded };
_self call ["callHotVGarage", ["owned:garage:hot:save", [_uid]]];
_updated
}],
["init", compileFinal { ["init", compileFinal {
params [["_uid", "", [""]]]; params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer); private _player = [_uid] call EFUNC(common,getPlayer);
if (isNull _player) exitWith { createHashMap }; if (isNull _player) exitWith { createHashMap };
private _hasPersistentGarage = _self call ["isPersistentVGarageInitialized", [_uid]];
private _garage = _self call ["loadHotVGarage", [_uid, true]]; private _garage = _self call ["loadHotVGarage", [_uid, true]];
if (_garage isEqualTo createHashMap) then { if (_garage isEqualTo createHashMap) then {
_garage = GVAR(VGarageModel) call ["defaults", []]; _garage = GVAR(VGarageModel) call ["defaults", []];
["ERROR", format ["Failed to initialize virtual garage for %1! Using fallback virtual garage.", _uid]] call EFUNC(common,log); ["ERROR", format ["Failed to initialize virtual garage for %1! Using fallback virtual garage.", _uid]] call EFUNC(common,log);
}; };
if !(_hasPersistentGarage) then {
_garage = _self call ["seedStartingUnlocks", [_uid, _garage]];
};
[CRPC(garage,responseInitVG), [_garage], _player] call CFUNC(targetEvent); [CRPC(garage,responseInitVG), [_garage], _player] call CFUNC(targetEvent);
_garage _garage

View File

@ -35,3 +35,24 @@ Locker listens for sync events through the event bus:
- `notification.requested` - storage and item modification alerts - `notification.requested` - storage and item modification alerts
The store module emits these events when granting items; locker applies the changes to player state. The store module emits these events when granting items; locker applies the changes to player state.
## Starting Unlocks
Missions can include `CfgStartingEquipment.hpp` from `description.ext` to
configure initial virtual arsenal unlocks for new players.
```cpp
class CfgStartingEquipment {
class Unlocks {
class Locker {
items[] = {"FirstAidKit", "ItemMap", "ItemCompass"};
weapons[] = {"hgun_P07_F"};
magazines[] = {"16Rnd_9x21_Mag"};
backpacks[] = {};
};
};
};
```
The extension virtual locker default is intentionally empty. The server addon
seeds `CfgStartingEquipment` unlocks only when a player does not already have a
persistent owner-scoped locker record.

View File

@ -24,13 +24,26 @@
#pragma hemtt ignore_variables ["_self"] #pragma hemtt ignore_variables ["_self"]
GVAR(VArsenalModel) = compileFinal createHashMapObject [[ GVAR(VArsenalModel) = compileFinal createHashMapObject [[
["#type", "VArsenalModel"], ["#type", "VArsenalModel"],
["getStartingUnlocksConfig", compileFinal {
missionConfigFile >> "CfgStartingEquipment" >> "Unlocks" >> "Locker"
}],
["getStartingUnlocks", compileFinal {
params [["_category", "", [""]], ["_fallback", [], [[]]]];
private _config = _self call ["getStartingUnlocksConfig", []];
private _categoryConfig = _config >> _category;
if (isArray _categoryConfig) exitWith { getArray _categoryConfig };
+_fallback
}],
["defaults", compileFinal { ["defaults", compileFinal {
private _vArsenal = createHashMap; private _vArsenal = createHashMap;
_vArsenal set ["backpacks", ["B_AssaultPack_rgr"]]; _vArsenal set ["backpacks", _self call ["getStartingUnlocks", ["backpacks", ["B_AssaultPack_rgr"]]]];
_vArsenal set ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_BG_Guerrilla_6_1", "V_TacVest_oli", "ACE_EarPlugs"]]; _vArsenal set ["items", _self call ["getStartingUnlocks", ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_BG_Guerrilla_6_1", "V_TacVest_oli", "ACE_EarPlugs"]]]];
_vArsenal set ["magazines", ["16Rnd_9x21_Mag", "30Rnd_65x39_caseless_black_mag", "Chemlight_blue", "Chemlight_green", "Chemlight_red", "Chemlight_yellow", "HandGrenade", "SmokeShell", "SmokeShellBlue", "SmokeShellGreen", "SmokeShellOrange", "SmokeShellPurple", "SmokeShellRed", "SmokeShellYellow"]]; _vArsenal set ["magazines", _self call ["getStartingUnlocks", ["magazines", ["16Rnd_9x21_Mag", "30Rnd_65x39_caseless_black_mag", "Chemlight_blue", "Chemlight_green", "Chemlight_red", "Chemlight_yellow", "HandGrenade", "SmokeShell", "SmokeShellBlue", "SmokeShellGreen", "SmokeShellOrange", "SmokeShellPurple", "SmokeShellRed", "SmokeShellYellow"]]]];
_vArsenal set ["weapons", ["arifle_MX_F", "hgun_P07_F"]]; _vArsenal set ["weapons", _self call ["getStartingUnlocks", ["weapons", ["arifle_MX_F", "hgun_P07_F"]]]];
_vArsenal _vArsenal
}] }]
@ -69,17 +82,46 @@ GVAR(VABaseStore) = compileFinal ([
private _command = ["owned:locker:hot:fetch", "owned:locker:hot:init"] select _initialize; private _command = ["owned:locker:hot:fetch", "owned:locker:hot:init"] select _initialize;
_self call ["callHotVArsenal", [_command, [_uid]]] _self call ["callHotVArsenal", [_command, [_uid]]]
}], }],
["isPersistentVArsenalInitialized", compileFinal {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { false };
["owned:locker:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
_isSuccess && { _result isEqualTo "true" }
}],
["seedStartingUnlocks", compileFinal {
params [["_uid", "", [""]], ["_arsenal", createHashMap, [createHashMap]]];
if (_uid isEqualTo "" || { _arsenal isEqualTo createHashMap }) exitWith { _arsenal };
private _defaults = GVAR(VArsenalModel) call ["defaults", []];
private _seeded = +_arsenal;
{
_seeded set [_x, +_y];
} forEach _defaults;
private _updated = _self call ["callHotVArsenal", ["owned:locker:hot:override", [_uid, toJSON _seeded]]];
if (_updated isEqualTo createHashMap) exitWith { _seeded };
_self call ["callHotVArsenal", ["owned:locker:hot:save", [_uid]]];
_updated
}],
["init", compileFinal { ["init", compileFinal {
params [["_uid", "", [""]]]; params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer); private _player = [_uid] call EFUNC(common,getPlayer);
if (isNull _player) exitWith { createHashMap }; if (isNull _player) exitWith { createHashMap };
private _hasPersistentArsenal = _self call ["isPersistentVArsenalInitialized", [_uid]];
private _arsenal = _self call ["loadHotVArsenal", [_uid, true]]; private _arsenal = _self call ["loadHotVArsenal", [_uid, true]];
if (_arsenal isEqualTo createHashMap) then { if (_arsenal isEqualTo createHashMap) then {
_arsenal = GVAR(VArsenalModel) call ["defaults", []]; _arsenal = GVAR(VArsenalModel) call ["defaults", []];
["ERROR", format ["Failed to initialize virtual arsenal for %1! Using fallback virtual arsenal.", _uid]] call EFUNC(common,log); ["ERROR", format ["Failed to initialize virtual arsenal for %1! Using fallback virtual arsenal.", _uid]] call EFUNC(common,log);
}; };
if !(_hasPersistentArsenal) then {
_arsenal = _self call ["seedStartingUnlocks", [_uid, _arsenal]];
};
[CRPC(locker,responseInitVA), [_arsenal], _player] call CFUNC(targetEvent); [CRPC(locker,responseInitVA), [_arsenal], _player] call CFUNC(targetEvent);
_arsenal _arsenal

View File

@ -34,6 +34,22 @@ generated catalog without changing the addon.
```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 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_"};
};
class ace3 {
patches[] = {"ace_main"};
addons[] = {"ace_"};
prefixes[] = {"ace_"};
};
};
class Categories { class Categories {
primary[] = {"arifle_MX_F", "arifle_MXC_F"}; primary[] = {"arifle_MX_F", "arifle_MXC_F"};
@ -56,6 +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
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 config source addon/source mod names or classname
prefixes, and `prefixes[]` for classname 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
durable virtual garage unlocks. durable virtual garage unlocks.

View File

@ -28,6 +28,132 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
_mode _mode
}], }],
["getMissionStoreModMode", compileFinal {
private _storeConfig = _self call ["getMissionStoreConfig", []];
private _mode = toLowerANSI getText (_storeConfig >> "modMode");
if !(_mode in ["allowlist", "denylist", "dynamic"]) then { _mode = "dynamic"; };
_mode
}],
["getMissionStoreModList", compileFinal {
private _storeConfig = _self call ["getMissionStoreConfig", []];
private _mods = [];
if (isArray (_storeConfig >> "mods")) then {
_mods = getArray (_storeConfig >> "mods");
};
_mods apply {
private _modID = "";
if (_x isEqualType "") then {
_modID = _x;
} else {
_modID = str _x;
};
toLowerANSI _modID
}
}],
["getMissionStoreModSourceValues", compileFinal {
params [["_modID", "", [""]], ["_key", "", [""]]];
private _storeConfig = _self call ["getMissionStoreConfig", []];
private _sourceConfig = _storeConfig >> "ModSources" >> _modID;
private _values = [];
if (isArray (_sourceConfig >> _key)) then {
_values = getArray (_sourceConfig >> _key);
};
_values apply {
if (_x isEqualType "") then {
_x
} else {
str _x
}
}
}],
["isMissionStoreModLoaded", compileFinal {
params [["_modID", "", [""]]];
private _patches = _self call ["getMissionStoreModSourceValues", [_modID, "patches"]];
if (_patches isEqualTo []) exitWith { true };
private _loaded = false;
{
if (isClass (configFile >> "CfgPatches" >> _x)) exitWith { _loaded = true; };
} forEach _patches;
_loaded
}],
["doesValueMatchAnyPrefix", compileFinal {
params [["_value", "", [""]], ["_prefixes", [], [[]]]];
private _normalizedValue = toLowerANSI _value;
private _matches = false;
{
private _prefix = toLowerANSI _x;
if (_prefix isEqualTo "") then { continue; };
if ((_normalizedValue select [0, count _prefix]) isEqualTo _prefix) exitWith { _matches = true; };
} forEach _prefixes;
_matches
}],
["doesItemMatchMissionStoreMod", compileFinal {
params [["_item", createHashMap, [createHashMap]], ["_modID", "", [""]]];
if (_item isEqualTo createHashMap || { _modID isEqualTo "" }) exitWith { false };
if !(_self call ["isMissionStoreModLoaded", [_modID]]) exitWith { false };
private _className = _item getOrDefault ["className", ""];
private _sourceAddons = (_item getOrDefault ["sourceAddons", []]) apply { toLowerANSI _x };
private _sourceMod = _item getOrDefault ["sourceMod", ""];
private _addons = (_self call ["getMissionStoreModSourceValues", [_modID, "addons"]]) apply { toLowerANSI _x };
private _prefixes = (_self call ["getMissionStoreModSourceValues", [_modID, "prefixes"]]) apply { toLowerANSI _x };
private _sourceModLower = toLowerANSI _sourceMod;
if (_sourceModLower in _addons) exitWith { true };
private _sourceAddonMatched = false;
{
if (_x in _addons) exitWith { _sourceAddonMatched = true; };
if (_self call ["doesValueMatchAnyPrefix", [_x, _addons]]) exitWith { _sourceAddonMatched = true; };
} forEach _sourceAddons;
if (_sourceAddonMatched) exitWith { true };
if (_self call ["doesValueMatchAnyPrefix", [_className, _addons + _prefixes]]) exitWith { true };
if (_self call ["doesValueMatchAnyPrefix", [_sourceMod, _addons]]) exitWith { true };
false
}],
["doesItemMatchMissionStoreMods", compileFinal {
params [["_item", createHashMap, [createHashMap]], ["_mods", [], [[]]]];
private _matches = false;
{
if (_self call ["doesItemMatchMissionStoreMod", [_item, _x]]) exitWith { _matches = true; };
} forEach _mods;
_matches
}],
["applyMissionStoreModFilter", compileFinal {
params [["_items", [], [[]]]];
private _mode = _self call ["getMissionStoreModMode", []];
private _mods = _self call ["getMissionStoreModList", []];
if (_mode isEqualTo "dynamic" || { _mods isEqualTo [] }) exitWith { +_items };
switch (_mode) do {
case "allowlist": {
_items select { _self call ["doesItemMatchMissionStoreMods", [_x, _mods]] }
};
case "denylist": {
_items select { !(_self call ["doesItemMatchMissionStoreMods", [_x, _mods]]) }
};
default {
+_items
};
}
}],
["getMissionStoreCategoryList", compileFinal { ["getMissionStoreCategoryList", compileFinal {
params [["_category", "", [""]]]; params [["_category", "", [""]]];
@ -93,16 +219,16 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
private _mode = _self call ["getMissionStoreMode", []]; private _mode = _self call ["getMissionStoreMode", []];
private _classNames = _self call ["getMissionStoreCategoryList", [_category]]; private _classNames = _self call ["getMissionStoreCategoryList", [_category]];
private _filteredItems = +_items; private _filteredItems = _self call ["applyMissionStoreModFilter", [_items]];
switch (_mode) do { switch (_mode) do {
case "allowlist": { case "allowlist": {
_filteredItems = _items select { _filteredItems = _filteredItems select {
(toLowerANSI (_x getOrDefault ["className", ""])) in _classNames (toLowerANSI (_x getOrDefault ["className", ""])) in _classNames
}; };
}; };
case "denylist": { case "denylist": {
_filteredItems = _items select { _filteredItems = _filteredItems select {
!((toLowerANSI (_x getOrDefault ["className", ""])) in _classNames) !((toLowerANSI (_x getOrDefault ["className", ""])) in _classNames)
}; };
}; };
@ -175,6 +301,8 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
private _className = configName _cfg; private _className = configName _cfg;
private _displayName = getText (_cfg >> "displayName"); private _displayName = getText (_cfg >> "displayName");
private _sourceAddons = configSourceAddonList _cfg;
private _sourceMod = configSourceMod _cfg;
private _picture = getText (_cfg >> _imageField); private _picture = getText (_cfg >> _imageField);
if (_picture isEqualTo "" && { _imageField isNotEqualTo "picture" }) then { if (_picture isEqualTo "" && { _imageField isNotEqualTo "picture" }) then {
_picture = getText (_cfg >> "picture"); _picture = getText (_cfg >> "picture");
@ -190,7 +318,9 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
["price", _self call ["formatCurrency", [_priceValue]]], ["price", _self call ["formatCurrency", [_priceValue]]],
["priceValue", _priceValue], ["priceValue", _priceValue],
["image", _picture], ["image", _picture],
["type", _typeLabel] ["type", _typeLabel],
["sourceAddons", _sourceAddons],
["sourceMod", _sourceMod]
] ]
}], }],
["appendCfgWeaponsByItemInfoType", compileFinal { ["appendCfgWeaponsByItemInfoType", compileFinal {

View File

@ -12,6 +12,9 @@
* Generator behavior: * Generator behavior:
* - maxConcurrentMissions and missionInterval are copied into * - maxConcurrentMissions and missionInterval are copied into
* forge_server_task_missionSetup_settings by the framework mission setup service. * forge_server_task_missionSetup_settings by the framework mission setup service.
* - ServicePricing values are copied to missionNamespace by the framework
* mission setup service so economy and transport stores can read UI or
* mission-param overrides at runtime.
* - Reward, reputation, penalty, and timeLimit ranges are read through * - Reward, reputation, penalty, and timeLimit ranges are read through
* forge_server_task_fnc_getMissionSettingRange so UI overrides and config fallbacks * forge_server_task_fnc_getMissionSettingRange so UI overrides and config fallbacks
* use the same path. * use the same path.
@ -26,6 +29,18 @@ class CfgMissions {
// Seconds before a generated mission location can be reused. // Seconds before a generated mission location can be reused.
locationReuseCooldown = 900; locationReuseCooldown = 900;
// Economy and service defaults. Mission Params with matching names override
// these values before the setup UI opens; submitted UI values override both.
class ServicePricing {
medicalSpawnCost = 100;
medicalHealCost = 100;
serviceRepairCost = 500;
serviceRearmCost = 500;
fuelCost = 5;
transportBaseFare = 100;
transportPricePerKm = 50;
};
// Enemy faction selection is ultimately exported to ENEMY_FACTION_STR and // Enemy faction selection is ultimately exported to ENEMY_FACTION_STR and
// ENEMY_SIDE for server-side generators. // ENEMY_SIDE for server-side generators.
class EnemyFactionConfig { class EnemyFactionConfig {

View File

@ -80,7 +80,18 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
_overrides getOrDefault [_varName, _default] _overrides getOrDefault [_varName, _default]
}; };
missionNamespace getVariable [_varName, _default] private _paramValue = [_varName, _default] call BIS_fnc_getParamValue;
missionNamespace getVariable [_varName, _paramValue]
};
private _serviceDefault = {
params ["_varName", "_default"];
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _varName)) exitWith {
getNumber (_serviceConfig >> _varName)
};
_default
}; };
private _maxConcurrent = [ private _maxConcurrent = [
@ -104,6 +115,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
private _penMax = [["penaltyMax", -25, _overrides] call _paramOrDefault, -25] call (_self get "numberOrDefault"); private _penMax = [["penaltyMax", -25, _overrides] call _paramOrDefault, -25] call (_self get "numberOrDefault");
private _timeMin = [["timeLimitMin", 600, _overrides] call _paramOrDefault, 600] call (_self get "numberOrDefault"); private _timeMin = [["timeLimitMin", 600, _overrides] call _paramOrDefault, 600] call (_self get "numberOrDefault");
private _timeMax = [["timeLimitMax", 900, _overrides] call _paramOrDefault, 900] call (_self get "numberOrDefault"); private _timeMax = [["timeLimitMax", 900, _overrides] call _paramOrDefault, 900] call (_self get "numberOrDefault");
private _medicalSpawnCost = [["medicalSpawnCost", ["medicalSpawnCost", 100] call _serviceDefault, _overrides] call _paramOrDefault, 100] call (_self get "numberOrDefault");
private _medicalHealCost = [["medicalHealCost", ["medicalHealCost", 100] call _serviceDefault, _overrides] call _paramOrDefault, 100] call (_self get "numberOrDefault");
private _serviceRepairCost = [["serviceRepairCost", ["serviceRepairCost", 500] call _serviceDefault, _overrides] call _paramOrDefault, 500] call (_self get "numberOrDefault");
private _serviceRearmCost = [["serviceRearmCost", ["serviceRearmCost", 500] call _serviceDefault, _overrides] call _paramOrDefault, 500] call (_self get "numberOrDefault");
private _fuelCost = [["fuelCost", ["fuelCost", 5] call _serviceDefault, _overrides] call _paramOrDefault, 5] call (_self get "numberOrDefault");
private _transportBaseFare = [["transportBaseFare", ["transportBaseFare", 100] call _serviceDefault, _overrides] call _paramOrDefault, 100] call (_self get "numberOrDefault");
private _transportPricePerKm = [["transportPricePerKm", ["transportPricePerKm", 50] call _serviceDefault, _overrides] call _paramOrDefault, 50] call (_self get "numberOrDefault");
private _generatorProvider = _overrides getOrDefault ["generatorProvider", GETGVAR(generatorProvider,"builtin")]; private _generatorProvider = _overrides getOrDefault ["generatorProvider", GETGVAR(generatorProvider,"builtin")];
if !(_generatorProvider isEqualType "") then { _generatorProvider = str _generatorProvider; }; if !(_generatorProvider isEqualType "") then { _generatorProvider = str _generatorProvider; };
_generatorProvider = toLowerANSI _generatorProvider; _generatorProvider = toLowerANSI _generatorProvider;
@ -131,6 +149,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
_timeMin = _timeMin max 1; _timeMin = _timeMin max 1;
_timeMax = _timeMax max _timeMin; _timeMax = _timeMax max _timeMin;
_medicalSpawnCost = _medicalSpawnCost max 0;
_medicalHealCost = _medicalHealCost max 0;
_serviceRepairCost = _serviceRepairCost max 0;
_serviceRearmCost = _serviceRearmCost max 0;
_fuelCost = _fuelCost max 0;
_transportBaseFare = _transportBaseFare max 0;
_transportPricePerKm = _transportPricePerKm max 0;
private _settings = createHashMapFromArray [ private _settings = createHashMapFromArray [
["useMenuSettings", true], ["useMenuSettings", true],
@ -145,6 +170,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
["penaltyMax", _penMax], ["penaltyMax", _penMax],
["timeLimitMin", _timeMin], ["timeLimitMin", _timeMin],
["timeLimitMax", _timeMax], ["timeLimitMax", _timeMax],
["medicalSpawnCost", _medicalSpawnCost],
["medicalHealCost", _medicalHealCost],
["serviceRepairCost", _serviceRepairCost],
["serviceRearmCost", _serviceRearmCost],
["fuelCost", _fuelCost],
["transportBaseFare", _transportBaseFare],
["transportPricePerKm", _transportPricePerKm],
["enemyFaction", _enemyFaction], ["enemyFaction", _enemyFaction],
["generatorProvider", _generatorProvider] ["generatorProvider", _generatorProvider]
]; ];
@ -152,6 +184,17 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
SETMPVAR(GVAR(missionSetup_settings),_settings); SETMPVAR(GVAR(missionSetup_settings),_settings);
SETMPVAR(GVAR(missionSetup_settingsApplied),true); SETMPVAR(GVAR(missionSetup_settingsApplied),true);
SETMPVAR(GVAR(generatorProvider),_generatorProvider); SETMPVAR(GVAR(generatorProvider),_generatorProvider);
{
missionNamespace setVariable [_x, _settings getOrDefault [_x, 0], true];
} forEach [
"medicalSpawnCost",
"medicalHealCost",
"serviceRepairCost",
"serviceRearmCost",
"fuelCost",
"transportBaseFare",
"transportPricePerKm"
];
private _side = _self call ["resolveFactionSide", [_enemyFaction, east]]; private _side = _self call ["resolveFactionSide", [_enemyFaction, east]];
ENEMY_SIDE = _side; ENEMY_SIDE = _side;

View File

@ -56,6 +56,9 @@ GVAR(TaskStore) = createHashMapObject [[
["isTaskCompleted", compileFinal { ["isTaskCompleted", compileFinal {
GVAR(TaskCatalogStore) call ["isTaskCompleted", _this] GVAR(TaskCatalogStore) call ["isTaskCompleted", _this]
}], }],
["isTerminalStatus", compileFinal {
GVAR(TaskCatalogStore) call ["isTerminalStatus", _this]
}],
["areTaskPrerequisitesSatisfied", compileFinal { ["areTaskPrerequisitesSatisfied", compileFinal {
GVAR(TaskCatalogStore) call ["areTaskPrerequisitesSatisfied", _this] GVAR(TaskCatalogStore) call ["areTaskPrerequisitesSatisfied", _this]
}], }],

View File

@ -96,10 +96,10 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["tick", compileFinal { ["tick", compileFinal {
private _startedAt = _self getOrDefault ["startedAt", -1]; private _startedAt = _self getOrDefault ["startedAt", -1];
@ -139,7 +139,7 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
_self call ["refreshTargetsFromStore", []]; _self call ["refreshTargetsFromStore", []];
private _targets = _self getOrDefault ["targets", []]; private _targets = _self getOrDefault ["targets", []];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]]; GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
count _targets > 0 !(_self call ["isTaskStoreOpen", []]) || { count _targets > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -148,10 +148,20 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
_self call ["waitForAssignment", []]; if !(_self call ["isTaskStoreOpen", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before targets registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
private _targets = _self getOrDefault ["targets", []]; private _targets = _self getOrDefault ["targets", []];
if (_useTaskStore) then { if (_useTaskStore) then {
@ -186,10 +196,8 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
private _targets = _self getOrDefault ["targets", []]; if (_finalStatus isEqualTo "failed") then {
{ deleteVehicle _x } forEach _targets;
if (_useTaskStore) then { if (_useTaskStore) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -202,10 +210,9 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
}; };
if (_endFail) then { "EveryoneLost" call BFUNC(endMissionServer); }; if (_endFail) then { "EveryoneLost" call BFUNC(endMissionServer); };
} else { };
private _targets = _self getOrDefault ["targets", []];
{ deleteVehicle _x } forEach _targets;
if (_finalStatus isEqualTo "succeeded") then {
if (_useTaskStore) then { if (_useTaskStore) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];

View File

@ -38,6 +38,8 @@ GVAR(CargoEntityController) merge [createHashMapFromArray [
private _taskID = _unit getVariable ["assignedTask", _unit getVariable [QGVAR(assignedTask), ""]]; private _taskID = _unit getVariable ["assignedTask", _unit getVariable [QGVAR(assignedTask), ""]];
if (_taskID isEqualTo "") exitWith {}; if (_taskID isEqualTo "") exitWith {};
private _taskStatus = GVAR(TaskStore) call ["getTaskStatus", [_taskID]];
if (GVAR(TaskStore) call ["isTerminalStatus", [_taskStatus]]) exitWith {};
if (_unit getVariable [QGVAR(cargoDamageWarned), false]) exitWith {}; if (_unit getVariable [QGVAR(cargoDamageWarned), false]) exitWith {};
_unit setVariable [QGVAR(cargoDamageWarned), true]; _unit setVariable [QGVAR(cargoDamageWarned), true];
@ -70,7 +72,13 @@ GVAR(CargoEntityController) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
private _entity = _self getOrDefault ["entity", objNull]; private _entity = _self getOrDefault ["entity", objNull];
isNull _entity || { !alive _entity } || { damage _entity >= (_self getOrDefault ["damageThreshold", 0.7]) } !(_self call ["isAssignedTaskOpen", []]) || { isNull _entity } || { !alive _entity } || { damage _entity >= (_self getOrDefault ["damageThreshold", 0.7]) }
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
}; };
_self call ["markFinished", []]; _self call ["markFinished", []];

View File

@ -51,10 +51,10 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["countBluforInZone", compileFinal { ["countBluforInZone", compileFinal {
private _defenseZone = _self getOrDefault ["defenseZone", ""]; private _defenseZone = _self getOrDefault ["defenseZone", ""];
@ -68,6 +68,7 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
if !(_self call ["isTaskStoreOpen", []]) exitWith { true };
private _ready = (_self call ["countBluforInZone", []]) >= _minBlufor; private _ready = (_self call ["countBluforInZone", []]) >= _minBlufor;
if (_ready) then { if (_ready) then {
@ -82,7 +83,7 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
_ready _ready
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["tick", compileFinal { ["tick", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
@ -186,10 +187,18 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
false false
}; };
_self call ["waitForAssignment", []]; if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["waitForDefenseStart", []]; _self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForDefenseStart", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before defense started."]];
_self call ["cleanup", []];
false
};
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -204,9 +213,12 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -42,7 +42,13 @@ GVAR(DefenseEnemyController) merge [createHashMapFromArray [
_self call ["markActive", []]; _self call ["markActive", []];
waitUntil { waitUntil {
sleep 1; sleep 1;
!(_self call ["isEntityUsable", []]) !(_self call ["isAssignedTaskOpen", []]) || { !(_self call ["isEntityUsable", []]) }
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
}; };
_self call ["markFinished", []]; _self call ["markFinished", []];

View File

@ -103,7 +103,7 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
count (_self getOrDefault ["ieds", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["ieds", []]) > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -112,6 +112,8 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
true true
}], }],
["waitForAssignment", compileFinal { ["waitForAssignment", compileFinal {
@ -121,10 +123,10 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["startIedControllers", compileFinal { ["startIedControllers", compileFinal {
if ((_self getOrDefault ["iedControllers", []]) isNotEqualTo []) exitWith { true }; if ((_self getOrDefault ["iedControllers", []]) isNotEqualTo []) exitWith { true };
@ -194,15 +196,10 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleFailureOutcome", compileFinal { ["handleFailureOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _ieds = _self getOrDefault ["ieds", []];
private _protected = _self getOrDefault ["protected", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; private _ratingFail = _rewardData getOrDefault ["ratingFail", 0];
private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false];
{ deleteVehicle _x } forEach _ieds;
{ deleteVehicle _x } forEach _protected;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -219,16 +216,11 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleSuccessOutcome", compileFinal { ["handleSuccessOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _ieds = _self getOrDefault ["ieds", []];
private _protected = _self getOrDefault ["protected", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
{ deleteVehicle _x } forEach _ieds;
{ deleteVehicle _x } forEach _protected;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
@ -245,12 +237,20 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
true true
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
_self call ["waitForRequiredEntities", []]; if !(_self call ["waitForRequiredEntities", []]) exitWith {
_self call ["waitForAssignment", []]; _self call ["markAborted", ["Task reached terminal status before required entities registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["startIedControllers", []]; _self call ["startIedControllers", []];
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -265,9 +265,12 @@ GVAR(DefuseTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -57,7 +57,7 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
count (_self getOrDefault ["cargo", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["cargo", []]) > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -66,6 +66,8 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
private _cargo = _self getOrDefault ["cargo", []]; private _cargo = _self getOrDefault ["cargo", []];
private _taskParams = _self getOrDefault ["taskParams", createHashMap]; private _taskParams = _self getOrDefault ["taskParams", createHashMap];
private _requiredDelivered = _taskParams getOrDefault ["limitSuccess", -1]; private _requiredDelivered = _taskParams getOrDefault ["limitSuccess", -1];
@ -85,10 +87,10 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["countDeliveredCargo", compileFinal { ["countDeliveredCargo", compileFinal {
private _deliveryZone = _self getOrDefault ["deliveryZone", ""]; private _deliveryZone = _self getOrDefault ["deliveryZone", ""];
@ -126,13 +128,10 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleFailureOutcome", compileFinal { ["handleFailureOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _cargo = _self getOrDefault ["cargo", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; private _ratingFail = _rewardData getOrDefault ["ratingFail", 0];
private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false];
{ deleteVehicle _x } forEach _cargo;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -149,14 +148,11 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleSuccessOutcome", compileFinal { ["handleSuccessOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _cargo = _self getOrDefault ["cargo", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
{ deleteVehicle _x } forEach _cargo;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
@ -173,11 +169,19 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
true true
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
_self call ["waitForRequiredEntities", []]; if !(_self call ["waitForRequiredEntities", []]) exitWith {
_self call ["waitForAssignment", []]; _self call ["markAborted", ["Task reached terminal status before required entities registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -192,9 +196,12 @@ GVAR(DeliveryTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -52,7 +52,7 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
count (_self getOrDefault ["targets", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["targets", []]) > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -61,6 +61,8 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
private _targets = _self getOrDefault ["targets", []]; private _targets = _self getOrDefault ["targets", []];
private _taskParams = _self getOrDefault ["taskParams", createHashMap]; private _taskParams = _self getOrDefault ["taskParams", createHashMap];
private _requiredDestroyed = _taskParams getOrDefault ["limitSuccess", -1]; private _requiredDestroyed = _taskParams getOrDefault ["limitSuccess", -1];
@ -76,10 +78,10 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["countDestroyedTargets", compileFinal { ["countDestroyedTargets", compileFinal {
private _targets = _self getOrDefault ["targets", []]; private _targets = _self getOrDefault ["targets", []];
@ -106,13 +108,10 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleFailureOutcome", compileFinal { ["handleFailureOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _targets = _self getOrDefault ["targets", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; private _ratingFail = _rewardData getOrDefault ["ratingFail", 0];
private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false];
{ deleteVehicle _x } forEach _targets;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -129,14 +128,11 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleSuccessOutcome", compileFinal { ["handleSuccessOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _targets = _self getOrDefault ["targets", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
{ deleteVehicle _x } forEach _targets;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
@ -153,11 +149,19 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
true true
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
_self call ["waitForRequiredEntities", []]; if !(_self call ["waitForRequiredEntities", []]) exitWith {
_self call ["waitForAssignment", []]; _self call ["markAborted", ["Task reached terminal status before required entities registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -172,9 +176,12 @@ GVAR(DestroyTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -76,6 +76,20 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
private _entity = _self getOrDefault ["entity", objNull]; private _entity = _self getOrDefault ["entity", objNull];
!isNull _entity && { alive _entity } !isNull _entity && { alive _entity }
}], }],
["isTerminalStatus", compileFinal {
params [["_status", "", [""]]];
(toLowerANSI _status) in ["failed", "succeeded"]
}],
["isAssignedTaskOpen", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""];
if (_taskID isEqualTo "" || { isNil QGVAR(TaskStore) }) exitWith { true };
private _status = GVAR(TaskStore) call ["getTaskStatus", [_taskID]];
if (_status isEqualTo "") exitWith { true };
!(_self call ["isTerminalStatus", [_status]])
}],
["assignTaskVariable", compileFinal { ["assignTaskVariable", compileFinal {
private _entity = _self getOrDefault ["entity", objNull]; private _entity = _self getOrDefault ["entity", objNull];
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];

View File

@ -52,12 +52,19 @@ GVAR(HVTEntityController) merge [createHashMapFromArray [
private _capturer = objNull; private _capturer = objNull;
waitUntil { waitUntil {
sleep 1; sleep 1;
if !(_self call ["isAssignedTaskOpen", []]) exitWith { true };
if !(_self call ["isEntityUsable", []]) exitWith { true }; if !(_self call ["isEntityUsable", []]) exitWith { true };
_capturer = _self call ["findNearbyCapturer", []]; _capturer = _self call ["findNearbyCapturer", []];
!isNull _capturer !isNull _capturer
}; };
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
};
if !(_self call ["isEntityUsable", []]) exitWith { if !(_self call ["isEntityUsable", []]) exitWith {
_self call ["markAborted", []]; _self call ["markAborted", []];
_self call ["cleanup", []]; _self call ["cleanup", []];

View File

@ -70,7 +70,7 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
count (_self getOrDefault ["hvts", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["hvts", []]) > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -79,6 +79,8 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
private _hvts = _self getOrDefault ["hvts", []]; private _hvts = _self getOrDefault ["hvts", []];
private _taskParams = _self getOrDefault ["taskParams", createHashMap]; private _taskParams = _self getOrDefault ["taskParams", createHashMap];
private _required = _taskParams getOrDefault ["limitSuccess", -1]; private _required = _taskParams getOrDefault ["limitSuccess", -1];
@ -122,10 +124,10 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["tick", compileFinal { ["tick", compileFinal {
private _startedAt = _self getOrDefault ["startedAt", -1]; private _startedAt = _self getOrDefault ["startedAt", -1];
@ -161,13 +163,10 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleFailureOutcome", compileFinal { ["handleFailureOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _hvts = _self getOrDefault ["hvts", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingFail = _rewardData getOrDefault ["ratingFail", 0]; private _ratingFail = _rewardData getOrDefault ["ratingFail", 0];
private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false]; private _endFail = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endFail", false];
{ deleteVehicle _x } forEach _hvts;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -184,14 +183,11 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleSuccessOutcome", compileFinal { ["handleSuccessOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _hvts = _self getOrDefault ["hvts", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
{ deleteVehicle _x } forEach _hvts;
if (_self getOrDefault ["useTaskStore", false]) then { if (_self getOrDefault ["useTaskStore", false]) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
@ -208,12 +204,20 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
true true
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
_self call ["waitForRequiredEntities", []]; if !(_self call ["waitForRequiredEntities", []]) exitWith {
_self call ["waitForAssignment", []]; _self call ["markAborted", ["Task reached terminal status before required entities registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["startHvtControllers", []]; _self call ["startHvtControllers", []];
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -228,9 +232,12 @@ GVAR(HVTTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -103,12 +103,19 @@ GVAR(HostageEntityController) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
if !(_self call ["isAssignedTaskOpen", []]) exitWith { true };
if (isNull _entity || { !alive _entity }) exitWith { true }; if (isNull _entity || { !alive _entity }) exitWith { true };
_rescuer = _self call ["findNearbyRescuer", []]; _rescuer = _self call ["findNearbyRescuer", []];
!isNull _rescuer !isNull _rescuer
}; };
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
};
if (isNull _entity || { !alive _entity }) exitWith { if (isNull _entity || { !alive _entity }) exitWith {
_self call ["markAborted", []]; _self call ["markAborted", []];
_self call ["cleanup", []]; _self call ["cleanup", []];

View File

@ -150,14 +150,15 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
count (_self getOrDefault ["hostages", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["hostages", []]) > 0 }
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
waitUntil { waitUntil {
sleep 1; sleep 1;
_self call ["refreshEntitiesFromStore", []]; _self call ["refreshEntitiesFromStore", []];
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
count (_self getOrDefault ["shooters", []]) > 0 !(_self call ["isTaskStoreOpen", []]) || { count (_self getOrDefault ["shooters", []]) > 0 }
}; };
} else { } else {
waitUntil { waitUntil {
@ -171,6 +172,8 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
}; };
}; };
if !(_self call ["isTaskStoreOpen", []]) exitWith { false };
private _hostages = _self getOrDefault ["hostages", []]; private _hostages = _self getOrDefault ["hostages", []];
private _taskParams = _self getOrDefault ["taskParams", createHashMap]; private _taskParams = _self getOrDefault ["taskParams", createHashMap];
private _requiredRescues = _taskParams getOrDefault ["limitSuccess", -1]; private _requiredRescues = _taskParams getOrDefault ["limitSuccess", -1];
@ -190,10 +193,10 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isTaskStoreOpen", []]
}], }],
["countFreedHostages", compileFinal { ["countFreedHostages", compileFinal {
private _playerGroups = allPlayers apply { group _x }; private _playerGroups = allPlayers apply { group _x };
@ -290,11 +293,9 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
sleep 5; sleep 5;
}; };
{ deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters;
if (_useTaskStore) then { if (_useTaskStore) then {
[_taskID, "FAILED"] call BFUNC(taskSetState); [_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
sleep 1; sleep 1;
@ -308,17 +309,12 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
}], }],
["handleSuccessOutcome", compileFinal { ["handleSuccessOutcome", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""]; private _taskID = _self getOrDefault ["taskID", ""];
private _hostages = _self getOrDefault ["hostages", []];
private _shooters = _self getOrDefault ["shooters", []];
private _rewardData = _self getOrDefault ["rewardData", createHashMap]; private _rewardData = _self getOrDefault ["rewardData", createHashMap];
private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0]; private _ratingSuccess = _rewardData getOrDefault ["ratingSuccess", 0];
private _funds = _rewardData getOrDefault ["funds", 0]; private _funds = _rewardData getOrDefault ["funds", 0];
private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false]; private _endSuccess = (_self getOrDefault ["taskParams", createHashMap]) getOrDefault ["endSuccess", false];
private _useTaskStore = _self getOrDefault ["useTaskStore", false]; private _useTaskStore = _self getOrDefault ["useTaskStore", false];
{ deleteVehicle _x } forEach _hostages;
{ deleteVehicle _x } forEach _shooters;
if (_useTaskStore) then { if (_useTaskStore) then {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState); [_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]]; GVAR(TaskStore) call ["setTaskStatus", [_taskID, "succeeded"]];
@ -335,12 +331,20 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
true true
}], }],
["runLoop", compileFinal { ["runLoop", compileFinal {
_self call ["waitForRequiredEntities", []]; if !(_self call ["waitForRequiredEntities", []]) exitWith {
_self call ["waitForAssignment", []]; _self call ["markAborted", ["Task reached terminal status before required entities registered."]];
_self call ["cleanup", []];
false
};
if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", ["Task reached terminal status before assignment."]];
_self call ["cleanup", []];
false
};
_self call ["startHostageControllers", []]; _self call ["startHostageControllers", []];
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do { while { _self call ["isTaskLoopActive", []] } do {
_self call ["trackParticipants", []]; _self call ["trackParticipants", []];
private _snapshot = _self call ["tick", []]; private _snapshot = _self call ["tick", []];
@ -355,9 +359,12 @@ GVAR(HostageTaskBaseClass) merge [createHashMapFromArray [
sleep 1; sleep 1;
}; };
if ((_self call ["getStatus", []]) isEqualTo "failed") then { private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []]; _self call ["handleFailureOutcome", []];
} else { };
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []]; _self call ["handleSuccessOutcome", []];
}; };

View File

@ -29,10 +29,10 @@ GVAR(IEDEntityController) merge [createHashMapFromArray [
waitUntil { waitUntil {
sleep 1; sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] !(_self call ["isAssignedTaskOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
}; };
true _self call ["isAssignedTaskOpen", []]
}], }],
["playCountdownSound", compileFinal { ["playCountdownSound", compileFinal {
params [["_timeRemaining", 0, [0]]]; params [["_timeRemaining", 0, [0]]];
@ -67,20 +67,35 @@ GVAR(IEDEntityController) merge [createHashMapFromArray [
false false
}; };
_self call ["waitForAssignment", []]; if !(_self call ["waitForAssignment", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
};
_self call ["markActive", []]; _self call ["markActive", []];
while { (_self call ["isEntityUsable", []]) && { _countdown > 0 } } do { while { (_self call ["isAssignedTaskOpen", []]) && { (_self call ["isEntityUsable", []]) && { _countdown > 0 } } } do {
_self call ["playCountdownSound", [_countdown]]; _self call ["playCountdownSound", [_countdown]];
_countdown = _countdown - 1; _countdown = _countdown - 1;
_self set ["countdown", _countdown]; _self set ["countdown", _countdown];
sleep 1; sleep 1;
}; };
if ((_self call ["isEntityUsable", []]) && { _countdown <= 0 }) then { if ((_self call ["isAssignedTaskOpen", []]) && { (_self call ["isEntityUsable", []]) && { _countdown <= 0 } }) then {
_self call ["detonate", []]; _self call ["detonate", []];
}; };
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
};
_self call ["markFinished", []]; _self call ["markFinished", []];
_self call ["cleanup", []]; _self call ["cleanup", []];
true true

View File

@ -31,7 +31,13 @@ GVAR(ProtectedEntityController) merge [createHashMapFromArray [
_self call ["markActive", []]; _self call ["markActive", []];
waitUntil { waitUntil {
sleep 1; sleep 1;
!(_self call ["isEntityUsable", []]) !(_self call ["isAssignedTaskOpen", []]) || { !(_self call ["isEntityUsable", []]) }
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
}; };
_self call ["markFinished", []]; _self call ["markFinished", []];

View File

@ -31,7 +31,13 @@ GVAR(ShooterEntityController) merge [createHashMapFromArray [
_self call ["markActive", []]; _self call ["markActive", []];
waitUntil { waitUntil {
sleep 1; sleep 1;
!(_self call ["isEntityUsable", []]) !(_self call ["isAssignedTaskOpen", []]) || { !(_self call ["isEntityUsable", []]) }
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
}; };
_self call ["markFinished", []]; _self call ["markFinished", []];

View File

@ -31,7 +31,13 @@ GVAR(TargetEntityController) merge [createHashMapFromArray [
_self call ["markActive", []]; _self call ["markActive", []];
waitUntil { waitUntil {
sleep 1; sleep 1;
!(_self call ["isEntityUsable", []]) !(_self call ["isAssignedTaskOpen", []]) || { !(_self call ["isEntityUsable", []]) }
};
if !(_self call ["isAssignedTaskOpen", []]) exitWith {
_self call ["markAborted", []];
_self call ["cleanup", []];
false
}; };
_self call ["markFinished", []]; _self call ["markFinished", []];

View File

@ -123,6 +123,11 @@ GVAR(TaskCatalogStore) = createHashMapObject [[
(_self call ["getTaskStatus", [_taskID]]) isEqualTo "succeeded" (_self call ["getTaskStatus", [_taskID]]) isEqualTo "succeeded"
}], }],
["isTerminalStatus", compileFinal {
params [["_status", "", [""]]];
(toLowerANSI _status) in ["failed", "succeeded"]
}],
["areTaskPrerequisitesSatisfied", compileFinal { ["areTaskPrerequisitesSatisfied", compileFinal {
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]]; params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
@ -359,6 +364,28 @@ GVAR(TaskCatalogStore) = createHashMapObject [[
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false }; if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
private _normalizedStatus = toLowerANSI _status; private _normalizedStatus = toLowerANSI _status;
private _currentStatus = toLowerANSI (_self call ["getTaskStatus", [_taskID]]);
private _currentIsTerminal = _self call ["isTerminalStatus", [_currentStatus]];
private _nextIsTerminal = _self call ["isTerminalStatus", [_normalizedStatus]];
if (_currentIsTerminal && { _currentStatus isNotEqualTo _normalizedStatus }) exitWith {
["WARNING", format [
"Task status transition blocked for %1: terminal status %2 cannot be changed to %3 without clearing task state first.",
_taskID,
_currentStatus,
_normalizedStatus
]] call EFUNC(common,log);
false
};
if (_currentIsTerminal && { _nextIsTerminal }) exitWith {
if (_normalizedStatus isEqualTo "succeeded") then {
_self call ["markTaskCompleted", [_taskID]];
_self call ["unlockDependentTasks", [_taskID]];
};
true
};
private _runtimeCatalogRegistry = _self getOrDefault ["runtimeCatalogRegistry", createHashMap]; private _runtimeCatalogRegistry = _self getOrDefault ["runtimeCatalogRegistry", createHashMap];
private _runtimeEntry = +(_runtimeCatalogRegistry getOrDefault [_taskID, createHashMap]); private _runtimeEntry = +(_runtimeCatalogRegistry getOrDefault [_taskID, createHashMap]);
if (_runtimeEntry isNotEqualTo createHashMap) then { if (_runtimeEntry isNotEqualTo createHashMap) then {

View File

@ -83,6 +83,50 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["getStatus", compileFinal { ["getStatus", compileFinal {
_self getOrDefault ["status", "created"] _self getOrDefault ["status", "created"]
}], }],
["isTerminalStatus", compileFinal {
params [["_status", "", [""]]];
(toLowerANSI _status) in ["failed", "succeeded"]
}],
["getStoreStatus", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""];
if (_taskID isEqualTo "" || { !(_self getOrDefault ["useTaskStore", false]) } || { isNil QGVAR(TaskStore) }) exitWith { "" };
GVAR(TaskStore) call ["getTaskStatus", [_taskID]]
}],
["canTransitionToTerminal", compileFinal {
params [["_nextStatus", "", [""]]];
private _normalizedNext = toLowerANSI _nextStatus;
if !(_self call ["isTerminalStatus", [_normalizedNext]]) exitWith { true };
private _currentStatus = toLowerANSI (_self getOrDefault ["status", "created"]);
if ((_self call ["isTerminalStatus", [_currentStatus]]) && { _currentStatus isNotEqualTo _normalizedNext }) exitWith { false };
private _storeStatus = toLowerANSI (_self call ["getStoreStatus", []]);
if ((_self call ["isTerminalStatus", [_storeStatus]]) && { _storeStatus isNotEqualTo _normalizedNext }) exitWith { false };
true
}],
["isTaskLoopActive", compileFinal {
if ((_self call ["getStatus", []]) isNotEqualTo "active") exitWith { false };
private _storeStatus = toLowerANSI (_self call ["getStoreStatus", []]);
if (_storeStatus isEqualTo "") exitWith { true };
if (_self call ["isTerminalStatus", [_storeStatus]]) exitWith {
_self call ["markAborted", [format ["Task store reached terminal status '%1'.", _storeStatus]]];
false
};
true
}],
["isTaskStoreOpen", compileFinal {
private _storeStatus = toLowerANSI (_self call ["getStoreStatus", []]);
if (_storeStatus isEqualTo "") exitWith { true };
!(_self call ["isTerminalStatus", [_storeStatus]])
}],
["getRewardData", compileFinal { ["getRewardData", compileFinal {
_self getOrDefault ["rewardData", createHashMap] _self getOrDefault ["rewardData", createHashMap]
}], }],
@ -162,6 +206,8 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["markSucceeded", compileFinal { ["markSucceeded", compileFinal {
params [["_resultSnapshot", createHashMap, [createHashMap]]]; params [["_resultSnapshot", createHashMap, [createHashMap]]];
if !(_self call ["canTransitionToTerminal", ["succeeded"]]) exitWith { false };
_self set ["status", "succeeded"]; _self set ["status", "succeeded"];
_self set ["finishedAt", serverTime]; _self set ["finishedAt", serverTime];
_self set ["resultSnapshot", _resultSnapshot]; _self set ["resultSnapshot", _resultSnapshot];
@ -173,6 +219,8 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["markFailed", compileFinal { ["markFailed", compileFinal {
params [["_reason", "", [""]], ["_resultSnapshot", createHashMap, [createHashMap]]]; params [["_reason", "", [""]], ["_resultSnapshot", createHashMap, [createHashMap]]];
if !(_self call ["canTransitionToTerminal", ["failed"]]) exitWith { false };
_self set ["status", "failed"]; _self set ["status", "failed"];
_self set ["finishedAt", serverTime]; _self set ["finishedAt", serverTime];
_self set ["failureReason", _reason]; _self set ["failureReason", _reason];
@ -182,6 +230,14 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
}; };
true true
}], }],
["markAborted", compileFinal {
params [["_reason", "", [""]]];
_self set ["status", "aborted"];
_self set ["finishedAt", serverTime];
_self set ["failureReason", _reason];
true
}],
["cleanup", compileFinal { ["cleanup", compileFinal {
_self call ["unregisterInstance", []] _self call ["unregisterInstance", []]
}], }],

View File

@ -39,6 +39,23 @@ GVAR(TransportServiceBase) = compileFinal createHashMapFromArray [
["INFO", "Transport Service Initialized!"] call EFUNC(common,log); ["INFO", "Transport Service Initialized!"] call EFUNC(common,log);
true true
}], }],
["numberSetting", compileFinal {
params [["_name", "", [""]], ["_default", 0, [0]]];
private _configDefault = _default;
private _missionConfig = missionConfigFile >> "CfgMissions";
if !(isClass _missionConfig) then { _missionConfig = configFile >> "CfgMissions"; };
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _name)) then {
_configDefault = getNumber (_serviceConfig >> _name);
};
private _paramValue = [_name, _configDefault] call BIS_fnc_getParamValue;
private _value = missionNamespace getVariable [_name, _paramValue];
if (_value isEqualType "") exitWith { (parseNumber _value) max 0 };
if (_value isEqualType 0) exitWith { _value max 0 };
_configDefault
}],
["notify", compileFinal { ["notify", compileFinal {
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Transport", [""]], ["_message", "", [""]]]; params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Transport", [""]], ["_message", "", [""]]];
@ -120,8 +137,10 @@ GVAR(TransportServiceBase) = compileFinal createHashMapFromArray [
["getCost", compileFinal { ["getCost", compileFinal {
params [["_fromNode", objNull, [objNull]], ["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]]; params [["_fromNode", objNull, [objNull]], ["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]];
private _baseFare = _options getOrDefault ["baseFare", _self getOrDefault ["baseFare", 100]]; private _baseFareDefault = _self call ["numberSetting", ["transportBaseFare", _self getOrDefault ["baseFare", 100]]];
private _pricePerKm = _options getOrDefault ["pricePerKm", _self getOrDefault ["pricePerKm", 50]]; private _pricePerKmDefault = _self call ["numberSetting", ["transportPricePerKm", _self getOrDefault ["pricePerKm", 50]]];
private _baseFare = _options getOrDefault ["baseFare", _baseFareDefault];
private _pricePerKm = _options getOrDefault ["pricePerKm", _pricePerKmDefault];
private _distanceMeters = _fromNode distance2D _toNode; private _distanceMeters = _fromNode distance2D _toNode;
round (_baseFare + ((_distanceMeters / 1000) * _pricePerKm)) round (_baseFare + ((_distanceMeters / 1000) * _pricePerKm))

View File

@ -29,6 +29,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 `CfgMissions >> ServicePricing >> fuelCost` is used.
## Repair ## Repair
Repair is organization-funded. Repair is organization-funded.
@ -45,6 +49,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.
@ -63,6 +70,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.
@ -79,6 +89,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
`CfgMissions >> ServicePricing`.
## 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

View File

@ -755,14 +755,21 @@ 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.
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 under `CfgMissions >> ServicePricing`.
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

View File

@ -188,14 +188,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 `CfgMissions >> ServicePricing`. 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

View File

@ -89,6 +89,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, `CfgMissions >> ServicePricing` 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
@ -108,7 +114,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.
## Reference Images ## Reference Images

View File

@ -1,7 +1,4 @@
use arma_rs::{ use arma_rs::{FromArma, IntoArma, loadout::Loadout as ArmaLoadout};
FromArma, IntoArma,
loadout::{AssignedItems, InventoryItem, Loadout as ArmaLoadout},
};
use forge_shared::{ use forge_shared::{
ActorValidationError, arma_value_to_json, generate_email, generate_phone_number, ActorValidationError, arma_value_to_json, generate_email, generate_phone_number,
}; };
@ -128,26 +125,7 @@ impl Actor {
} }
fn default_loadout_json() -> serde_json::Value { fn default_loadout_json() -> serde_json::Value {
let mut loadout = ArmaLoadout::default(); serde_json::Value::Array(Vec::new())
let uniform = loadout.uniform_mut();
uniform.set_class("U_BG_Guerrilla_6_1".to_string());
let uniform_items = uniform.items_mut().unwrap();
uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 1));
loadout.set_headgear("H_Cap_blk_ION".to_string());
let mut items = AssignedItems::default();
items.set_map("ItemMap".to_string());
items.set_terminal("ItemGPS".to_string());
items.set_radio("ItemRadio".to_string());
items.set_compass("ItemCompass".to_string());
items.set_watch("ItemWatch".to_string());
loadout.set_assigned_items(items);
let arma_value = loadout.to_arma();
arma_value_to_json(&arma_value)
} }
pub fn get_loadout(&self) -> Result<ArmaLoadout, String> { pub fn get_loadout(&self) -> Result<ArmaLoadout, String> {

View File

@ -23,12 +23,8 @@ pub struct VGarage {
impl VGarage { impl VGarage {
pub fn new() -> Self { pub fn new() -> Self {
Self::default_unlocks()
}
fn default_unlocks() -> Self {
Self { Self {
cars: vec!["B_Quadbike_01_F".to_string()], cars: Vec::new(),
armor: Vec::new(), armor: Vec::new(),
helis: Vec::new(), helis: Vec::new(),
planes: Vec::new(), planes: Vec::new(),

View File

@ -19,43 +19,11 @@ pub struct VLocker {
impl VLocker { impl VLocker {
pub fn new() -> Self { pub fn new() -> Self {
Self::default_unlocks()
}
fn default_unlocks() -> Self {
Self { Self {
items: vec![ items: Vec::new(),
"FirstAidKit".to_string(), weapons: Vec::new(),
"G_Combat".to_string(), magazines: Vec::new(),
"H_Cap_blk_ION".to_string(), backpacks: Vec::new(),
"H_HelmetB".to_string(),
"ACE_EarPlugs".to_string(),
"ItemCompass".to_string(),
"ItemGPS".to_string(),
"ItemMap".to_string(),
"ItemRadio".to_string(),
"ItemWatch".to_string(),
"U_BG_Guerrilla_6_1".to_string(),
"V_TacVest_oli".to_string(),
],
weapons: vec!["arifle_MX_F".to_string(), "hgun_P07_F".to_string()],
magazines: vec![
"16Rnd_9x21_Mag".to_string(),
"30Rnd_65x39_caseless_black_mag".to_string(),
"Chemlight_blue".to_string(),
"Chemlight_green".to_string(),
"Chemlight_red".to_string(),
"Chemlight_yellow".to_string(),
"HandGrenade".to_string(),
"SmokeShell".to_string(),
"SmokeShellBlue".to_string(),
"SmokeShellGreen".to_string(),
"SmokeShellOrange".to_string(),
"SmokeShellPurple".to_string(),
"SmokeShellRed".to_string(),
"SmokeShellYellow".to_string(),
],
backpacks: vec!["B_AssaultPack_rgr".to_string()],
} }
} }