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.";
};
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 [
["label", _data getOrDefault ["label", "Transport"]],
["nodePrefix", _data getOrDefault ["nodePrefix", "transport"]],
["vehiclePrefix", _data getOrDefault ["vehiclePrefix", "transport_vehicle"]],
["arrivalPrefix", _data getOrDefault ["arrivalPrefix", "transport_arrival"]],
["maxIndexedNodes", _data getOrDefault ["maxIndexedNodes", 10]],
["baseFare", _data getOrDefault ["baseFare", 100]],
["pricePerKm", _data getOrDefault ["pricePerKm", 50]],
["baseFare", _data getOrDefault ["baseFare", ["transportBaseFare", 100] call _transportSetting]],
["pricePerKm", _data getOrDefault ["pricePerKm", ["transportPricePerKm", 50] call _transportSetting]],
["cargoRadius", _data getOrDefault ["cargoRadius", 25]],
["includeCargo", _data getOrDefault ["includeCargo", true]]
];

View File

@ -142,8 +142,25 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
if (_isTransport) then {
private _fromTransportNode = _x;
private _maxIndexedNodes = _x getVariable ["transportMaxIndexedNodes", 10];
private _baseFare = _x getVariable ["transportBaseFare", 100];
private _pricePerKm = _x getVariable ["transportPricePerKm", 50];
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 _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 _arrivalPrefix = _x getVariable ["transportArrivalPrefix", format ["%1_arrival", _transportPrefix]];
private _nodeNames = [_transportPrefix];

View File

@ -151,10 +151,21 @@ GVAR(MissionSetupRepositoryBaseClass) = compileFinal createHashMapFromArray [
private _paramOrDefault = {
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 };
_value
};
private _serviceDefault = {
params ["_varName", "_default"];
private _serviceConfig = _missionConfig >> "ServicePricing";
if (isNumber (_serviceConfig >> _varName)) exitWith {
getNumber (_serviceConfig >> _varName)
};
_default
};
private _factions = [];
{
@ -197,6 +208,13 @@ GVAR(MissionSetupRepositoryBaseClass) = compileFinal createHashMapFromArray [
["penaltyMax", ["penaltyMax", -25] call _paramOrDefault],
["timeLimitMin", ["timeLimitMin", 600] 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")]
]]
]

View File

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

View File

@ -14,6 +14,13 @@
penaltyMax: -25,
timeLimitMin: 600,
timeLimitMax: 900,
medicalSpawnCost: 100,
medicalHealCost: 100,
serviceRepairCost: 500,
serviceRearmCost: 500,
fuelCost: 5,
transportBaseFare: 100,
transportPricePerKm: 50,
generatorProvider: "builtin",
},
error: "",
@ -47,6 +54,13 @@
penaltyMax: fieldNumber("penaltyMax"),
timeLimitMin: fieldNumber("timeLimitMin"),
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",
};
}
@ -86,6 +100,21 @@
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 = "";
send("missionSetup::apply", settings);
}
@ -186,6 +215,43 @@
</div>
</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">
<div class="panel-head">
<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 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>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>` : ""}
</div>
</aside>

View File

@ -25,13 +25,38 @@ life state, phone number, email, organization, and holster state.
## Runtime Behavior
- 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
account.
- Hot actor reads are migrated and hydrated before use.
- `saveHotState` in the main addon snapshots and saves actor state on player
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
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.

View File

@ -4,7 +4,7 @@
* File: fnc_initActorStore.sqf
* Author: IDSolutions
* Date: 2025-12-17
* Last Update: 2026-05-16
* Last Update: 2026-06-03
* Public: Yes
*
* Description:
@ -25,12 +25,23 @@
#pragma hemtt ignore_variables ["_self"]
GVAR(ActorModel) = compileFinal createHashMapObject [[
["#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 {
private _actor = createHashMap;
_actor set ["uid", ""];
_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 ["direction", 0];
_actor set ["stance", "STAND"];

View File

@ -9,6 +9,21 @@ inventory handling.
Current stores cover fuel tracking, medical service behavior, and service
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
- `forge_server_main`
- `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
patch, and rolls fuel back to the starting level when organization funds
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
charges use player bank/cash first, then organization funds with repayable
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);
}],
["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", {
params ["_source", "_target", "_unit"];
@ -100,7 +117,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
if (_fuelCapacity <= 0) then { _fuelCapacity = 100; };
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"]];
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."]]];
@ -130,7 +147,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
private _player = [_uid] call EFUNC(common,getPlayer);
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 _formattedTotalLiters = _totalLiters toFixed 2;

View File

@ -4,7 +4,7 @@
* File: fnc_initMEconomyStore.sqf
* Author: IDSolutions
* Date: 2025-12-20
* Last Update: 2026-05-15
* Last Update: 2026-06-03
* Public: No
*
* Description:
@ -30,8 +30,26 @@ GVAR(MEconomyStore) = createHashMapObject [[
_self set ["mSpawns", createHashMap];
GVAR(occupancyTriggers) = [];
GVAR(SpawnCost) = 100;
["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", {
private _mSpawns = (_self get "mSpawns");
private _prefix = "med_spawn";
@ -166,40 +184,61 @@ GVAR(MEconomyStore) = createHashMapObject [[
_result set ["message", ""];
_result
}],
["onHealed", {
params [["_unit", objNull, [objNull]]];
if (isNull _unit) exitWith { ["WARNING", format ["Invalid unit provided: %1", (name _unit)], nil, nil] call EFUNC(common,log); };
["chargeMedicalService", {
params [
["_unit", objNull, [objNull]],
["_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;
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 {
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]]];
[CRPC(actor,onActorHealed), [], _unit] call CFUNC(targetEvent);
_self call ["notify", [_unit, "info", "Medical Billing", format ["%1 charged $%2 from your %3.", _serviceLabel, [_amount] call EFUNC(common,formatNumber), _sourceLabel]]];
true
};
if !(_personalCharge getOrDefault ["fallbackEligible", false]) exitWith {
private _message = _personalCharge getOrDefault ["message", "Personal funds could not be charged for medical service."];
_self call ["notify", [_unit, "danger", "Medical Billing", _message]];
!_requirePayment
};
if (isNil QGVAR(SEconomyStore)) exitWith {
["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."]];
!_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 {
private _message = _chargeResult getOrDefault ["message", "Organization funds cannot cover this medical service."];
_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);
}],
["onRespawn", {
@ -214,6 +253,8 @@ GVAR(MEconomyStore) = createHashMapObject [[
deleteVehicle _corpse;
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);
}],
["onKilled", {

View File

@ -30,6 +30,23 @@ GVAR(SEconomyStore) = createHashMapObject [[
GVAR(ServiceRearmCost) = 500;
["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", {
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Service", [""]], ["_message", "", [""]]];
@ -148,7 +165,7 @@ GVAR(SEconomyStore) = createHashMapObject [[
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"]];
if !(_charge getOrDefault ["success", false]) exitWith {
_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 };
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"]];
if !(_charge getOrDefault ["success", false]) exitWith {
_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
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"]
GVAR(VGarageModel) = compileFinal createHashMapObject [[
["#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 {
private _vGarage = createHashMap;
_vGarage set ["armor", []];
_vGarage set ["cars", ["B_Quadbike_01_F"]];
_vGarage set ["helis", []];
_vGarage set ["naval", []];
_vGarage set ["other", []];
_vGarage set ["planes", []];
_vGarage set ["armor", _self call ["getStartingUnlocks", ["armor", []]]];
_vGarage set ["cars", _self call ["getStartingUnlocks", ["cars", ["B_Quadbike_01_F"]]]];
_vGarage set ["helis", _self call ["getStartingUnlocks", ["helis", []]]];
_vGarage set ["naval", _self call ["getStartingUnlocks", ["naval", []]]];
_vGarage set ["other", _self call ["getStartingUnlocks", ["other", []]]];
_vGarage set ["planes", _self call ["getStartingUnlocks", ["planes", []]]];
_vGarage
}]
@ -71,17 +84,46 @@ GVAR(VGBaseStore) = compileFinal ([
private _command = ["owned:garage:hot:fetch", "owned:garage:hot:init"] select _initialize;
_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 {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
if (isNull _player) exitWith { createHashMap };
private _hasPersistentGarage = _self call ["isPersistentVGarageInitialized", [_uid]];
private _garage = _self call ["loadHotVGarage", [_uid, true]];
if (_garage isEqualTo createHashMap) then {
_garage = GVAR(VGarageModel) call ["defaults", []];
["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);
_garage

View File

@ -35,3 +35,24 @@ Locker listens for sync events through the event bus:
- `notification.requested` - storage and item modification alerts
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"]
GVAR(VArsenalModel) = compileFinal createHashMapObject [[
["#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 {
private _vArsenal = createHashMap;
_vArsenal set ["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 ["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 ["backpacks", _self call ["getStartingUnlocks", ["backpacks", ["B_AssaultPack_rgr"]]]];
_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", _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", _self call ["getStartingUnlocks", ["weapons", ["arifle_MX_F", "hgun_P07_F"]]]];
_vArsenal
}]
@ -69,17 +82,46 @@ GVAR(VABaseStore) = compileFinal ([
private _command = ["owned:locker:hot:fetch", "owned:locker:hot:init"] select _initialize;
_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 {
params [["_uid", "", [""]]];
private _player = [_uid] call EFUNC(common,getPlayer);
if (isNull _player) exitWith { createHashMap };
private _hasPersistentArsenal = _self call ["isPersistentVArsenalInitialized", [_uid]];
private _arsenal = _self call ["loadHotVArsenal", [_uid, true]];
if (_arsenal isEqualTo createHashMap) then {
_arsenal = GVAR(VArsenalModel) call ["defaults", []];
["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);
_arsenal

View File

@ -34,6 +34,22 @@ generated catalog without changing the addon.
```cpp
class CfgStore {
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 {
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
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
item and vehicle categories. Unit purchases are immediate spawn grants, not
durable virtual garage unlocks.

View File

@ -28,6 +28,132 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
_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 {
params [["_category", "", [""]]];
@ -93,16 +219,16 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
private _mode = _self call ["getMissionStoreMode", []];
private _classNames = _self call ["getMissionStoreCategoryList", [_category]];
private _filteredItems = +_items;
private _filteredItems = _self call ["applyMissionStoreModFilter", [_items]];
switch (_mode) do {
case "allowlist": {
_filteredItems = _items select {
_filteredItems = _filteredItems select {
(toLowerANSI (_x getOrDefault ["className", ""])) in _classNames
};
};
case "denylist": {
_filteredItems = _items select {
_filteredItems = _filteredItems select {
!((toLowerANSI (_x getOrDefault ["className", ""])) in _classNames)
};
};
@ -175,6 +301,8 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
private _className = configName _cfg;
private _displayName = getText (_cfg >> "displayName");
private _sourceAddons = configSourceAddonList _cfg;
private _sourceMod = configSourceMod _cfg;
private _picture = getText (_cfg >> _imageField);
if (_picture isEqualTo "" && { _imageField isNotEqualTo "picture" }) then {
_picture = getText (_cfg >> "picture");
@ -190,7 +318,9 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
["price", _self call ["formatCurrency", [_priceValue]]],
["priceValue", _priceValue],
["image", _picture],
["type", _typeLabel]
["type", _typeLabel],
["sourceAddons", _sourceAddons],
["sourceMod", _sourceMod]
]
}],
["appendCfgWeaponsByItemInfoType", compileFinal {

View File

@ -12,6 +12,9 @@
* Generator behavior:
* - maxConcurrentMissions and missionInterval are copied into
* 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
* forge_server_task_fnc_getMissionSettingRange so UI overrides and config fallbacks
* use the same path.
@ -26,6 +29,18 @@ class CfgMissions {
// Seconds before a generated mission location can be reused.
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_SIDE for server-side generators.
class EnemyFactionConfig {

View File

@ -80,7 +80,18 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
_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 = [
@ -104,6 +115,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
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 _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")];
if !(_generatorProvider isEqualType "") then { _generatorProvider = str _generatorProvider; };
_generatorProvider = toLowerANSI _generatorProvider;
@ -131,6 +149,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
_timeMin = _timeMin max 1;
_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 [
["useMenuSettings", true],
@ -145,6 +170,13 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
["penaltyMax", _penMax],
["timeLimitMin", _timeMin],
["timeLimitMax", _timeMax],
["medicalSpawnCost", _medicalSpawnCost],
["medicalHealCost", _medicalHealCost],
["serviceRepairCost", _serviceRepairCost],
["serviceRearmCost", _serviceRearmCost],
["fuelCost", _fuelCost],
["transportBaseFare", _transportBaseFare],
["transportPricePerKm", _transportPricePerKm],
["enemyFaction", _enemyFaction],
["generatorProvider", _generatorProvider]
];
@ -152,6 +184,17 @@ GVAR(MissionSetupServiceBaseClass) = compileFinal createHashMapFromArray [
SETMPVAR(GVAR(missionSetup_settings),_settings);
SETMPVAR(GVAR(missionSetup_settingsApplied),true);
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]];
ENEMY_SIDE = _side;

View File

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

View File

@ -96,10 +96,10 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
!(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
};
true
_self call ["isTaskStoreOpen", []]
}],
["tick", compileFinal {
private _startedAt = _self getOrDefault ["startedAt", -1];
@ -139,7 +139,7 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
_self call ["refreshTargetsFromStore", []];
private _targets = _self getOrDefault ["targets", []];
GVAR(TaskStore) call ["trackParticipants", [_taskID, _targets, "", 300]];
count _targets > 0
!(_self call ["isTaskStoreOpen", []]) || { count _targets > 0 }
};
} else {
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", []];
while { (_self call ["getStatus", []]) isEqualTo "active" } do {
while { _self call ["isTaskLoopActive", []] } do {
private _targets = _self getOrDefault ["targets", []];
if (_useTaskStore) then {
@ -186,10 +196,8 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
sleep 1;
};
if ((_self call ["getStatus", []]) isEqualTo "failed") then {
private _targets = _self getOrDefault ["targets", []];
{ deleteVehicle _x } forEach _targets;
private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
if (_useTaskStore) then {
[_taskID, "FAILED"] call BFUNC(taskSetState);
GVAR(TaskStore) call ["setTaskStatus", [_taskID, "failed"]];
@ -202,10 +210,9 @@ GVAR(AttackTaskBaseClass) merge [createHashMapFromArray [
};
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 {
[_taskID, "SUCCEEDED"] call BFUNC(taskSetState);
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), ""]];
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 {};
_unit setVariable [QGVAR(cargoDamageWarned), true];
@ -70,7 +72,13 @@ GVAR(CargoEntityController) merge [createHashMapFromArray [
waitUntil {
sleep 1;
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", []];

View File

@ -51,10 +51,10 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
waitUntil {
sleep 1;
GVAR(TaskStore) call ["isTaskAccepted", [_taskID]]
!(_self call ["isTaskStoreOpen", []]) || { GVAR(TaskStore) call ["isTaskAccepted", [_taskID]] }
};
true
_self call ["isTaskStoreOpen", []]
}],
["countBluforInZone", compileFinal {
private _defenseZone = _self getOrDefault ["defenseZone", ""];
@ -68,6 +68,7 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
waitUntil {
sleep 1;
_self call ["trackParticipants", []];
if !(_self call ["isTaskStoreOpen", []]) exitWith { true };
private _ready = (_self call ["countBluforInZone", []]) >= _minBlufor;
if (_ready) then {
@ -82,7 +83,7 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
_ready
};
true
_self call ["isTaskStoreOpen", []]
}],
["tick", compileFinal {
private _taskID = _self getOrDefault ["taskID", ""];
@ -186,10 +187,18 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
false
};
_self call ["waitForAssignment", []];
_self call ["waitForDefenseStart", []];
if !(_self call ["waitForAssignment", []]) exitWith {
_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", []];
private _snapshot = _self call ["tick", []];
@ -204,9 +213,12 @@ GVAR(DefendTaskBaseClass) merge [createHashMapFromArray [
sleep 1;
};
if ((_self call ["getStatus", []]) isEqualTo "failed") then {
private _finalStatus = _self call ["getStatus", []];
if (_finalStatus isEqualTo "failed") then {
_self call ["handleFailureOutcome", []];
} else {
};
if (_finalStatus isEqualTo "succeeded") then {
_self call ["handleSuccessOutcome", []];
};

View File

@ -42,7 +42,13 @@ GVAR(DefenseEnemyController) merge [createHashMapFromArray [
_self call ["markActive", []];
waitUntil {
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", []];

View File

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

View File

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

View File

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

View File

@ -76,6 +76,20 @@ GVAR(EntityControllerBaseClass) = createHashMapFromArray [
private _entity = _self getOrDefault ["entity", objNull];
!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 {
private _entity = _self getOrDefault ["entity", objNull];
private _taskID = _self getOrDefault ["taskID", ""];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,13 @@ GVAR(ProtectedEntityController) merge [createHashMapFromArray [
_self call ["markActive", []];
waitUntil {
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", []];

View File

@ -31,7 +31,13 @@ GVAR(ShooterEntityController) merge [createHashMapFromArray [
_self call ["markActive", []];
waitUntil {
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", []];

View File

@ -31,7 +31,13 @@ GVAR(TargetEntityController) merge [createHashMapFromArray [
_self call ["markActive", []];
waitUntil {
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", []];

View File

@ -123,6 +123,11 @@ GVAR(TaskCatalogStore) = createHashMapObject [[
(_self call ["getTaskStatus", [_taskID]]) isEqualTo "succeeded"
}],
["isTerminalStatus", compileFinal {
params [["_status", "", [""]]];
(toLowerANSI _status) in ["failed", "succeeded"]
}],
["areTaskPrerequisitesSatisfied", compileFinal {
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
@ -359,6 +364,28 @@ GVAR(TaskCatalogStore) = createHashMapObject [[
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
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 _runtimeEntry = +(_runtimeCatalogRegistry getOrDefault [_taskID, createHashMap]);
if (_runtimeEntry isNotEqualTo createHashMap) then {

View File

@ -83,6 +83,50 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["getStatus", compileFinal {
_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 {
_self getOrDefault ["rewardData", createHashMap]
}],
@ -162,6 +206,8 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["markSucceeded", compileFinal {
params [["_resultSnapshot", createHashMap, [createHashMap]]];
if !(_self call ["canTransitionToTerminal", ["succeeded"]]) exitWith { false };
_self set ["status", "succeeded"];
_self set ["finishedAt", serverTime];
_self set ["resultSnapshot", _resultSnapshot];
@ -173,6 +219,8 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
["markFailed", compileFinal {
params [["_reason", "", [""]], ["_resultSnapshot", createHashMap, [createHashMap]]];
if !(_self call ["canTransitionToTerminal", ["failed"]]) exitWith { false };
_self set ["status", "failed"];
_self set ["finishedAt", serverTime];
_self set ["failureReason", _reason];
@ -182,6 +230,14 @@ GVAR(TaskInstanceBaseClass) = createHashMapFromArray [
};
true
}],
["markAborted", compileFinal {
params [["_reason", "", [""]]];
_self set ["status", "aborted"];
_self set ["finishedAt", serverTime];
_self set ["failureReason", _reason];
true
}],
["cleanup", compileFinal {
_self call ["unregisterInstance", []]
}],

View File

@ -39,6 +39,23 @@ GVAR(TransportServiceBase) = compileFinal createHashMapFromArray [
["INFO", "Transport Service Initialized!"] call EFUNC(common,log);
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 {
params [["_unit", objNull, [objNull]], ["_type", "info", [""]], ["_title", "Transport", [""]], ["_message", "", [""]]];
@ -120,8 +137,10 @@ GVAR(TransportServiceBase) = compileFinal createHashMapFromArray [
["getCost", compileFinal {
params [["_fromNode", objNull, [objNull]], ["_toNode", objNull, [objNull]], ["_options", createHashMap, [createHashMap]]];
private _baseFare = _options getOrDefault ["baseFare", _self getOrDefault ["baseFare", 100]];
private _pricePerKm = _options getOrDefault ["pricePerKm", _self getOrDefault ["pricePerKm", 50]];
private _baseFareDefault = _self call ["numberSetting", ["transportBaseFare", _self getOrDefault ["baseFare", 100]]];
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;
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
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 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 same event.
The default repair charge is controlled by `serviceRepairCost`. A direct
service event can still pass a concrete `_cost` to override that request.
## Rearm
Rearm is organization-funded.
@ -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 same event.
The default rearm charge is controlled by `serviceRearmCost`. A direct service
event can still pass a concrete `_cost` to override that request.
## Medical
Medical is player-funded first.
@ -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
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 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
tuning such as opposing faction, mission cap, interval, location cooldown,
reward ranges, reputation ranges, penalty ranges, time limits, and a generator
provider preference. It does not enable or disable generated missions; use the
CBA setting for that policy.
provider preference. It also exposes service pricing for medical spawn, heal,
repair, rearm, refuel, and transport defaults. It does not enable or disable
generated missions; use the CBA setting for that policy.
If mission setup is enabled, the mission manager waits until the setup operator
applies settings. Cancel, X, and Escape apply default values from CBA, mission
parameters, and `CfgMissions`. There is no timeout that auto-applies defaults.
After settings are applied, the setup UI cannot be reopened.
Service pricing fallback values live 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
generated task requests use the task provider registry and route to the selected
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
runtime tuning such as faction, caps, intervals, reward ranges, rating ranges,
penalties, time limits, and a generator provider preference. Generator
enablement remains controlled by the CBA setting above.
penalties, time limits, service pricing, and a generator provider preference.
Generator enablement remains controlled by the CBA setting above.
When `forge_server_task_enableMissionSetup` is enabled, the mission manager
waits for setup settings before starting. There is no timeout auto-apply.
Pressing Cancel, X, or Escape applies default values from CBA, mission
parameters, and `CfgMissions`.
Service price defaults are stored in `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
`forge_server_task_generatorProvider` as `builtin` or `custom`. CAD/manual
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
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
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];
```
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

View File

@ -1,7 +1,4 @@
use arma_rs::{
FromArma, IntoArma,
loadout::{AssignedItems, InventoryItem, Loadout as ArmaLoadout},
};
use arma_rs::{FromArma, IntoArma, loadout::Loadout as ArmaLoadout};
use forge_shared::{
ActorValidationError, arma_value_to_json, generate_email, generate_phone_number,
};
@ -128,26 +125,7 @@ impl Actor {
}
fn default_loadout_json() -> serde_json::Value {
let mut loadout = ArmaLoadout::default();
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)
serde_json::Value::Array(Vec::new())
}
pub fn get_loadout(&self) -> Result<ArmaLoadout, String> {

View File

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

View File

@ -19,43 +19,11 @@ pub struct VLocker {
impl VLocker {
pub fn new() -> Self {
Self::default_unlocks()
}
fn default_unlocks() -> Self {
Self {
items: vec![
"FirstAidKit".to_string(),
"G_Combat".to_string(),
"H_Cap_blk_ION".to_string(),
"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()],
items: Vec::new(),
weapons: Vec::new(),
magazines: Vec::new(),
backpacks: Vec::new(),
}
}