Implement org credit line debt and bank repayment flow #2
@ -1,3 +1,3 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initActorClass);
|
PREP(initRepository);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -23,17 +23,17 @@ player addEventHandler ["Respawn", {
|
|||||||
[SRPC(economy,onRespawn), [_unit, _corpse, _uid]] call CFUNC(serverEvent);
|
[SRPC(economy,onRespawn), [_unit, _corpse, _uid]] call CFUNC(serverEvent);
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if (isNil QGVAR(ActorClass)) then { call FUNC(initActorClass); };
|
if (isNil QGVAR(ActorRepository)) then { call FUNC(initRepository); };
|
||||||
|
|
||||||
[QGVAR(initActor), {
|
[QGVAR(initActor), {
|
||||||
GVAR(ActorClass) call ["init", []];
|
GVAR(ActorRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(onActorRespawn), {
|
[QGVAR(onActorRespawn), {
|
||||||
params [["_loadout", [], [[]]], ["_medSpawnPos", [0,0,0], [[]]], ["_medSpawnDir", 0, [0]]];
|
params [["_loadout", [], [[]]], ["_medSpawnPos", [0,0,0], [[]]], ["_medSpawnDir", 0, [0]]];
|
||||||
|
|
||||||
private _message = ["warning", "Medical Alert", "You have been revived at a medical facility.", 5000];
|
private _message = ["warning", "Medical Alert", "You have been revived at a medical facility.", 5000];
|
||||||
EGVAR(notifications,NotificationClass) call ["create", _message];
|
EGVAR(notifications,NotificationService) call ["create", _message];
|
||||||
|
|
||||||
player setUnitLoadout _loadout;
|
player setUnitLoadout _loadout;
|
||||||
player setPosATL _medSpawnPos;
|
player setPosATL _medSpawnPos;
|
||||||
@ -53,14 +53,14 @@ if (isNil QGVAR(ActorClass)) then { call FUNC(initActorClass); };
|
|||||||
[QGVAR(responseInitActor), {
|
[QGVAR(responseInitActor), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(ActorClass) call ["sync", [_data, true]];
|
GVAR(ActorRepository) call ["sync", [_data, true]];
|
||||||
cutText ["", "PLAIN", 1];
|
cutText ["", "PLAIN", 1];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncActor), {
|
[QGVAR(responseSyncActor), {
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(ActorClass) call ["sync", [_data, _jip]];
|
GVAR(ActorRepository) call ["sync", [_data, _jip]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(initActor), []] call CFUNC(localEvent);
|
[QGVAR(initActor), []] call CFUNC(localEvent);
|
||||||
@ -68,6 +68,6 @@ if (isNil QGVAR(ActorClass)) then { call FUNC(initActorClass); };
|
|||||||
[{
|
[{
|
||||||
GETVAR(player,FORGE_isLoaded,false)
|
GETVAR(player,FORGE_isLoaded,false)
|
||||||
}, {
|
}, {
|
||||||
private _holster = GVAR(ActorClass) call ["get", ["holster", true]];
|
private _holster = GVAR(ActorRepository) call ["get", ["holster", true]];
|
||||||
if (_holster) then { [player] call AFUNC(weaponselect,putWeaponAway); };
|
if (_holster) then { [player] call AFUNC(weaponselect,putWeaponAway); };
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_handleUIEvents.sqf
|
* File: fnc_handleUIEvents.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-01-28
|
* Date: 2026-01-28
|
||||||
* Last Update: 2026-02-17
|
* Last Update: 2026-03-28
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -31,10 +31,11 @@ private _data = _alert get "data";
|
|||||||
diag_log format ["[FORGE:Client:Actor] Handling UI event: %1 with data: %2", _event, _data];
|
diag_log format ["[FORGE:Client:Actor] Handling UI event: %1 with data: %2", _event, _data];
|
||||||
|
|
||||||
switch (_event) do {
|
switch (_event) do {
|
||||||
case "actor::get::actions": { GVAR(ActorClass) call ["getNearbyActions", [_control]]; };
|
case "actor::get::actions": { GVAR(ActorRepository) call ["getNearbyActions", [_control]]; };
|
||||||
case "actor::close::menu": { closeDialog 1; };
|
case "actor::close::menu": { closeDialog 1; };
|
||||||
case "actor::open::atm": { [true] spawn EFUNC(bank,openUI); };
|
case "actor::open::atm": { [true] spawn EFUNC(bank,openUI); };
|
||||||
case "actor::open::bank": { [] spawn EFUNC(bank,openUI); };
|
case "actor::open::bank": { [] spawn EFUNC(bank,openUI); };
|
||||||
|
case "actor::open::cad": { [] spawn EFUNC(cad,openUI); };
|
||||||
case "actor::open::device": { hint "Device interaction is not yet implemented."; };
|
case "actor::open::device": { hint "Device interaction is not yet implemented."; };
|
||||||
case "actor::open::garage": { [] spawn EFUNC(garage,openUI); };
|
case "actor::open::garage": { [] spawn EFUNC(garage,openUI); };
|
||||||
case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); };
|
case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); };
|
||||||
|
|||||||
@ -1,29 +1,27 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initActorClass.sqf
|
* File: fnc_initRepository.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-01-28
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-02-17
|
* Public: No
|
||||||
* Public: Yes
|
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the actor class for managing player data.
|
* Initializes the actor repository for managing player actor data.
|
||||||
* Provides methods for saving, loading, and applying actor data.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* Actor class object [HASHMAP OBJECT]
|
* Actor repository object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_actor_fnc_initActorClass
|
* call forge_client_actor_fnc_initRepository;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "ActorBaseClass"],
|
["#type", "ActorRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["uid", getPlayerUID player];
|
_self set ["uid", getPlayerUID player];
|
||||||
_self set ["actor", createHashMap];
|
_self set ["actor", createHashMap];
|
||||||
@ -33,9 +31,10 @@ GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
[SRPC(actor,requestInitActor), [_uid]] call CFUNC(serverEvent);
|
[SRPC(actor,requestInitActor), [_uid]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
systemChat format ["Actor loaded for %1", (name player)];
|
systemChat format ["Actor loaded for %1", name player];
|
||||||
diag_log "[FORGE:Client:Actor] Actor Class Initialized!";
|
diag_log "[FORGE:Client:Actor] Actor Repository Initialized!";
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
params [["_sync", false, [false]]];
|
params [["_sync", false, [false]]];
|
||||||
@ -68,29 +67,23 @@ GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_self set ["actor", _actor];
|
_self set ["actor", _actor];
|
||||||
SETPVAR(player,FORGE_isLoaded,true);
|
SETPVAR(player,FORGE_isLoaded,true);
|
||||||
|
|
||||||
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
||||||
diag_log "[FORGE:Client:Actor] Sync completed";
|
diag_log "[FORGE:Client:Actor] Sync completed";
|
||||||
}],
|
}],
|
||||||
["get", compileFinal {
|
["get", compileFinal {
|
||||||
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
||||||
|
|
||||||
private _actor = _self get "actor";
|
private _actor = _self get "actor";
|
||||||
_actor getOrDefault [_key, _default];
|
_actor getOrDefault [_key, _default];
|
||||||
}],
|
}],
|
||||||
["applyPosition", compileFinal {
|
["applyPosition", compileFinal {
|
||||||
private _position = _self call ["get", ["position", [0, 0, 0]]];
|
private _position = _self call ["get", ["position", [0, 0, 0]]];
|
||||||
|
|
||||||
if (GVAR(enableLoc)) then {
|
if (GVAR(enableLoc)) then {
|
||||||
player setPosASL _position;
|
player setPosASL _position;
|
||||||
|
|
||||||
private _pAlt = ((getPosATLVisual player) select 2);
|
private _pAlt = ((getPosATLVisual player) select 2);
|
||||||
private _pVelZ = ((velocity player) select 2);
|
private _pVelZ = ((velocity player) select 2);
|
||||||
|
|
||||||
if (_pAlt > 5 && _pVelZ < 0) then {
|
if (_pAlt > 5 && _pVelZ < 0) then {
|
||||||
player setVelocity [0, 0, 0];
|
player setVelocity [0, 0, 0];
|
||||||
player setPosATL [((getPosATLVisual player) select 0), ((getPosATLVisual player) select 1), 1];
|
player setPosATL [((getPosATLVisual player) select 0), ((getPosATLVisual player) select 1), 1];
|
||||||
|
|
||||||
hint "You logged off mid air. You were moved to a safe position on the ground";
|
hint "You logged off mid air. You were moved to a safe position on the ground";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -113,9 +106,7 @@ GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}],
|
}],
|
||||||
["getNearbyActions", compileFinal {
|
["getNearbyActions", compileFinal {
|
||||||
params [["_control", controlNull, [controlNull]]];
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
private _nearbyActions = [];
|
private _nearbyActions = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
private _storeType = _x getVariable ["storeType", ""];
|
private _storeType = _x getVariable ["storeType", ""];
|
||||||
private _isAtm = _x getVariable ["isAtm", false];
|
private _isAtm = _x getVariable ["isAtm", false];
|
||||||
@ -140,5 +131,5 @@ GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(ActorClass) = createHashMapObject [GVAR(ActorBaseClass)];
|
GVAR(ActorRepository) = createHashMapObject [GVAR(ActorRepositoryBaseClass)];
|
||||||
GVAR(ActorClass)
|
GVAR(ActorRepository)
|
||||||
@ -100,6 +100,12 @@ const actions = {
|
|||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
const baseMenuItems = [
|
const baseMenuItems = [
|
||||||
|
{
|
||||||
|
id: "cad",
|
||||||
|
title: "CAD",
|
||||||
|
description: "Access CAD (Computer Aided Dispatch)",
|
||||||
|
action: "actor::open::cad",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "phone",
|
id: "phone",
|
||||||
title: "Phone",
|
title: "Phone",
|
||||||
@ -133,6 +139,12 @@ const actionDefinitions = {
|
|||||||
description: "Access your bank account and manage finances",
|
description: "Access your bank account and manage finances",
|
||||||
action: "actor::open::bank",
|
action: "actor::open::bank",
|
||||||
},
|
},
|
||||||
|
cad: {
|
||||||
|
id: "cad",
|
||||||
|
title: "CAD",
|
||||||
|
description: "Access the CAD",
|
||||||
|
action: "actor::open::cad",
|
||||||
|
},
|
||||||
phone: {
|
phone: {
|
||||||
id: "phone",
|
id: "phone",
|
||||||
title: "Phone",
|
title: "Phone",
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initClass);
|
PREP(initRepository);
|
||||||
PREP(initSessionService);
|
|
||||||
PREP(initUIBridge);
|
PREP(initUIBridge);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -1,33 +1,48 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
if (isNil QGVAR(BankClass)) then { call FUNC(initClass); };
|
if (isNil QGVAR(BankRepository)) then { call FUNC(initRepository); };
|
||||||
if (isNil QGVAR(BankSessionService)) then { call FUNC(initSessionService); };
|
|
||||||
if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); };
|
if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); };
|
||||||
|
|
||||||
[QGVAR(initBank), {
|
[QGVAR(initBank), {
|
||||||
GVAR(BankClass) call ["init", []];
|
GVAR(BankRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitBank), {
|
[QGVAR(responseInitBank), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(BankClass) call ["sync", [_data, true]];
|
GVAR(BankRepository) call ["markLoaded", []];
|
||||||
if !(isNil QGVAR(BankUIBridge)) then {
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
GVAR(BankUIBridge) call ["refreshSession", []];
|
GVAR(BankUIBridge) call ["handleAccountSyncResponse", [_data]];
|
||||||
};
|
};
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncBank), {
|
[QGVAR(responseSyncBank), {
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(BankClass) call ["sync", [_data, _jip]];
|
GVAR(BankRepository) call ["markLoaded", []];
|
||||||
if !(isNil QGVAR(BankUIBridge)) then {
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
GVAR(BankUIBridge) call ["refreshSession", []];
|
GVAR(BankUIBridge) call ["handleAccountSyncResponse", [_data]];
|
||||||
|
};
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseHydrateBank), {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
|
GVAR(BankUIBridge) call ["handleHydrateResponse", [_data, "bank::hydrate"]];
|
||||||
|
};
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseBankNotice), {
|
||||||
|
params [["_type", "error", [""]], ["_message", "", [""]]];
|
||||||
|
|
||||||
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
|
GVAR(BankUIBridge) call ["handleNoticeResponse", [_type, _message]];
|
||||||
};
|
};
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(org,OrgClass) get "isLoaded";
|
EGVAR(actor,ActorRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initBank), []] call CFUNC(localEvent);
|
[QGVAR(initBank), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
|
|||||||
@ -68,6 +68,16 @@ switch (_event) do {
|
|||||||
GVAR(BankUIBridge) call ["handleDepositEarningsRequest", [_data]];
|
GVAR(BankUIBridge) call ["handleDepositEarningsRequest", [_data]];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
case "bank::repayCreditLine::request": {
|
||||||
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
|
GVAR(BankUIBridge) call ["handleRepayCreditLineRequest", [_data]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "bank::pin::request": {
|
||||||
|
if !(isNil QGVAR(BankUIBridge)) then {
|
||||||
|
GVAR(BankUIBridge) call ["handleSubmitPinRequest", [_data]];
|
||||||
|
};
|
||||||
|
};
|
||||||
default {
|
default {
|
||||||
hint format ["Unhandled bank UI event: %1", _event];
|
hint format ["Unhandled bank UI event: %1", _event];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File: fnc_initClass.sqf
|
|
||||||
* Author: IDSolutions
|
|
||||||
* Public: No
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Initializes the bank class for account sync and access helpers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
|
||||||
GVAR(BankBaseClass) = compileFinal createHashMapFromArray [
|
|
||||||
["#type", "BankBaseClass"],
|
|
||||||
["#create", compileFinal {
|
|
||||||
_self set ["uid", getPlayerUID player];
|
|
||||||
_self set ["account", createHashMapFromArray [
|
|
||||||
["bank", 0],
|
|
||||||
["cash", 0],
|
|
||||||
["earnings", 0],
|
|
||||||
["pin", 1234],
|
|
||||||
["transactions", []]
|
|
||||||
]];
|
|
||||||
_self set ["isLoaded", false];
|
|
||||||
_self set ["lastSave", time];
|
|
||||||
}],
|
|
||||||
["getAccountState", compileFinal {
|
|
||||||
_self getOrDefault ["account", createHashMap]
|
|
||||||
}],
|
|
||||||
["get", compileFinal {
|
|
||||||
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
|
||||||
|
|
||||||
private _account = _self getOrDefault ["account", createHashMap];
|
|
||||||
_account getOrDefault [_key, _default]
|
|
||||||
}],
|
|
||||||
["init", compileFinal {
|
|
||||||
[SRPC(bank,requestInitBank), [getPlayerUID player]] call CFUNC(serverEvent);
|
|
||||||
_self set ["lastSave", time];
|
|
||||||
}],
|
|
||||||
["save", compileFinal {
|
|
||||||
[SRPC(bank,requestSaveBank), [getPlayerUID player]] call CFUNC(serverEvent);
|
|
||||||
_self set ["lastSave", time];
|
|
||||||
}],
|
|
||||||
["sync", compileFinal {
|
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
|
||||||
|
|
||||||
private _account = _self getOrDefault ["account", createHashMap];
|
|
||||||
{
|
|
||||||
_account set [_x, _y];
|
|
||||||
} forEach _data;
|
|
||||||
|
|
||||||
_self set ["account", _account];
|
|
||||||
if !(_self getOrDefault ["isLoaded", false]) then {
|
|
||||||
_self set ["isLoaded", true];
|
|
||||||
};
|
|
||||||
|
|
||||||
true
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
GVAR(BankClass) = createHashMapObject [GVAR(BankBaseClass)];
|
|
||||||
GVAR(BankClass)
|
|
||||||
44
arma/client/addons/bank/functions/fnc_initRepository.sqf
Normal file
44
arma/client/addons/bank/functions/fnc_initRepository.sqf
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initRepository.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the bank repository for client bank lifecycle state.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Bank repository object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_bank_fnc_initRepository;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(BankRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "BankRepositoryBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["uid", getPlayerUID player];
|
||||||
|
_self set ["isLoaded", false];
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
}],
|
||||||
|
["init", compileFinal {
|
||||||
|
[SRPC(bank,requestInitBank), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
|
systemChat format ["Bank loaded for %1", name player];
|
||||||
|
diag_log "[FORGE:Client:Bank] Bank Repository Initialized!";
|
||||||
|
}],
|
||||||
|
["markLoaded", compileFinal {
|
||||||
|
if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; };
|
||||||
|
true
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(BankRepository) = createHashMapObject [GVAR(BankRepositoryBaseClass)];
|
||||||
|
GVAR(BankRepository)
|
||||||
@ -1,80 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File: fnc_initSessionService.sqf
|
|
||||||
* Author: IDSolutions
|
|
||||||
* Public: No
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Initializes the bank session service that shapes the browser payload.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
|
||||||
GVAR(BankSessionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|
||||||
["#type", "BankSessionServiceBaseClass"],
|
|
||||||
["buildTransferTargets", compileFinal {
|
|
||||||
private _targets = [];
|
|
||||||
|
|
||||||
{
|
|
||||||
if (isNull _x || { _x isEqualTo player }) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _uid = getPlayerUID _x;
|
|
||||||
private _name = name _x;
|
|
||||||
if (_uid isEqualTo "" || { _name isEqualTo "" }) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
_targets pushBack (createHashMapFromArray [
|
|
||||||
["name", _name],
|
|
||||||
["uid", _uid]
|
|
||||||
]);
|
|
||||||
} forEach allPlayers;
|
|
||||||
|
|
||||||
private _targetPairs = _targets apply {
|
|
||||||
[toLowerANSI (_x getOrDefault ["name", ""]), _x]
|
|
||||||
};
|
|
||||||
_targetPairs sort true;
|
|
||||||
_targetPairs apply {
|
|
||||||
_x param [1, createHashMap]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
["buildPayload", compileFinal {
|
|
||||||
params [["_mode", "bank", [""]]];
|
|
||||||
|
|
||||||
private _account = if (isNil QGVAR(BankClass)) then {
|
|
||||||
createHashMap
|
|
||||||
} else {
|
|
||||||
GVAR(BankClass) call ["getAccountState", []]
|
|
||||||
};
|
|
||||||
|
|
||||||
private _orgFunds = 0;
|
|
||||||
private _orgName = "";
|
|
||||||
if !(isNil QEGVAR(org,OrgClass)) then {
|
|
||||||
_orgFunds = EGVAR(org,OrgClass) call ["get", ["funds", 0]];
|
|
||||||
_orgName = EGVAR(org,OrgClass) call ["get", ["name", ""]];
|
|
||||||
};
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["session", createHashMapFromArray [
|
|
||||||
["mode", ["bank", "atm"] select (toLowerANSI _mode isEqualTo "atm")],
|
|
||||||
["orgFunds", _orgFunds],
|
|
||||||
["orgName", _orgName],
|
|
||||||
["playerName", name player],
|
|
||||||
["transferTargets", _self call ["buildTransferTargets", []]],
|
|
||||||
["uid", getPlayerUID player]
|
|
||||||
]],
|
|
||||||
["account", createHashMapFromArray [
|
|
||||||
["bank", _account getOrDefault ["bank", 0]],
|
|
||||||
["cash", _account getOrDefault ["cash", 0]],
|
|
||||||
["earnings", _account getOrDefault ["earnings", 0]],
|
|
||||||
["pin", str (_account getOrDefault ["pin", 1234])],
|
|
||||||
["transactions", _account getOrDefault ["transactions", []]]
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
GVAR(BankSessionService) = createHashMapObject [GVAR(BankSessionServiceBaseClass)];
|
|
||||||
GVAR(BankSessionService)
|
|
||||||
@ -3,10 +3,20 @@
|
|||||||
/*
|
/*
|
||||||
* File: fnc_initUIBridge.sqf
|
* File: fnc_initUIBridge.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the bank web UI bridge.
|
* Initializes the bank UI bridge for browser control state and bank UI events.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Bank UI bridge object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_bank_fnc_initUIBridge;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
@ -19,9 +29,6 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["mode", "bank"];
|
_self set ["mode", "bank"];
|
||||||
}],
|
}],
|
||||||
["buildPayload", compileFinal {
|
|
||||||
GVAR(BankSessionService) call ["buildPayload", [_self call ["getMode", []]]]
|
|
||||||
}],
|
|
||||||
["getActiveBrowserControl", compileFinal {
|
["getActiveBrowserControl", compileFinal {
|
||||||
private _display = uiNamespace getVariable ["RscBank", displayNull];
|
private _display = uiNamespace getVariable ["RscBank", displayNull];
|
||||||
if (isNull _display) exitWith {
|
if (isNull _display) exitWith {
|
||||||
@ -36,14 +43,16 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["getMode", compileFinal {
|
["getMode", compileFinal {
|
||||||
_self getOrDefault ["mode", "bank"]
|
_self getOrDefault ["mode", "bank"]
|
||||||
}],
|
}],
|
||||||
|
["hasOpenScreen", compileFinal {
|
||||||
|
private _screen = _self call ["getScreen", []];
|
||||||
|
private _control = _self call ["getActiveBrowserControl", []];
|
||||||
|
|
||||||
|
!(isNull _control) && { _screen call ["isReady", []] }
|
||||||
|
}],
|
||||||
["handleDepositEarningsRequest", compileFinal {
|
["handleDepositEarningsRequest", compileFinal {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _amount = floor (_data getOrDefault ["amount", 0]);
|
private _amount = floor (_data getOrDefault ["amount", 0]);
|
||||||
if (_amount <= 0) exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "No earnings are available to deposit."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
[SRPC(bank,requestDepositEarnings), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
[SRPC(bank,requestDepositEarnings), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
@ -51,22 +60,53 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _amount = floor (_data getOrDefault ["amount", 0]);
|
private _amount = floor (_data getOrDefault ["amount", 0]);
|
||||||
if (_amount <= 0) exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "Enter a valid deposit amount."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
[SRPC(bank,requestDeposit), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
[SRPC(bank,requestDeposit), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
["handleRepayCreditLineRequest", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _amount = floor (_data getOrDefault ["amount", 0]);
|
||||||
|
[SRPC(bank,requestRepayCreditLine), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["handleHydrateResponse", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]], ["_event", "bank::hydrate", [""]]];
|
||||||
|
|
||||||
|
if !(_self call ["hasOpenScreen", []]) exitWith { false };
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_event, _data, _self call ["getActiveBrowserControl", []]]]
|
||||||
|
}],
|
||||||
|
["handleAccountSyncResponse", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if !(_self call ["hasOpenScreen", []]) exitWith { false };
|
||||||
|
|
||||||
|
_self call ["sendEvent", ["bank::sync", _data, _self call ["getActiveBrowserControl", []]]]
|
||||||
|
}],
|
||||||
|
["handleNoticeResponse", compileFinal {
|
||||||
|
params [["_type", "error", [""]], ["_message", "", [""]]];
|
||||||
|
|
||||||
|
_self call ["sendNotice", [_type, _message]]
|
||||||
|
}],
|
||||||
["handleReady", compileFinal {
|
["handleReady", compileFinal {
|
||||||
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
|
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _screen = _self call ["getScreen", []];
|
private _screen = _self call ["getScreen", []];
|
||||||
_screen call ["setControl", [_control]];
|
_screen call ["setControl", [_control]];
|
||||||
_screen call ["markReady", [true]];
|
_screen call ["markReady", [true]];
|
||||||
|
|
||||||
_self call ["flushPendingEvents", []];
|
_self call ["flushPendingEvents", []];
|
||||||
_self call ["sendEvent", ["bank::hydrate", _self call ["buildPayload", []], _control]];
|
|
||||||
|
_self call ["requestHydrate", [true]]
|
||||||
|
}],
|
||||||
|
["handleSubmitPinRequest", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _pin = _data getOrDefault ["pin", ""];
|
||||||
|
if !(_pin isEqualType "") then { _pin = str _pin; };
|
||||||
|
|
||||||
|
[SRPC(bank,requestSubmitPin), [getPlayerUID player, _pin]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
}],
|
}],
|
||||||
["handleTransferRequest", compileFinal {
|
["handleTransferRequest", compileFinal {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
@ -75,18 +115,6 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _target = _data getOrDefault ["target", ""];
|
private _target = _data getOrDefault ["target", ""];
|
||||||
private _from = toLowerANSI (_data getOrDefault ["from", "bank"]);
|
private _from = toLowerANSI (_data getOrDefault ["from", "bank"]);
|
||||||
|
|
||||||
if (_target isEqualTo "") exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "Select a transfer recipient."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_target isEqualTo getPlayerUID player) exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "You cannot transfer funds to yourself."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_amount <= 0) exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "Enter a valid transfer amount."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
[SRPC(bank,requestTransfer), [getPlayerUID player, _target, _from, _amount]] call CFUNC(serverEvent);
|
[SRPC(bank,requestTransfer), [getPlayerUID player, _target, _from, _amount]] call CFUNC(serverEvent);
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
@ -94,23 +122,24 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _amount = floor (_data getOrDefault ["amount", 0]);
|
private _amount = floor (_data getOrDefault ["amount", 0]);
|
||||||
if (_amount <= 0) exitWith {
|
|
||||||
_self call ["sendNotice", ["error", "Enter a valid withdrawal amount."]];
|
|
||||||
};
|
|
||||||
|
|
||||||
[SRPC(bank,requestWithdraw), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
[SRPC(bank,requestWithdraw), [getPlayerUID player, _amount]] call CFUNC(serverEvent);
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
["refreshSession", compileFinal {
|
["refreshSession", compileFinal {
|
||||||
private _control = _self call ["getActiveBrowserControl", []];
|
_self call ["requestHydrate", [false]]
|
||||||
if (isNull _control) exitWith { false };
|
}],
|
||||||
|
["requestHydrate", compileFinal {
|
||||||
|
params [["_resetAuthorization", false, [false]]];
|
||||||
|
|
||||||
_self call ["sendEvent", ["bank::sync", _self call ["buildPayload", []], _control]]
|
if !(_self call ["hasOpenScreen", []]) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(bank,requestHydrateBank), [getPlayerUID player, _self call ["getMode", []], _resetAuthorization]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
}],
|
}],
|
||||||
["sendNotice", compileFinal {
|
["sendNotice", compileFinal {
|
||||||
params [["_type", "error", [""]], ["_message", "", [""]], ["_control", controlNull, [controlNull]]];
|
params [["_type", "error", [""]], ["_message", "", [""]], ["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
if (_message isEqualTo "") exitWith { false };
|
if (_message isEqualTo "" || { !(_self call ["hasOpenScreen", []]) }) exitWith { false };
|
||||||
|
|
||||||
_self call ["sendEvent", ["bank::notice", createHashMapFromArray [
|
_self call ["sendEvent", ["bank::notice", createHashMapFromArray [
|
||||||
["message", _message],
|
["message", _message],
|
||||||
@ -121,9 +150,7 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
params [["_mode", "bank", [""]]];
|
params [["_mode", "bank", [""]]];
|
||||||
|
|
||||||
private _finalMode = toLowerANSI _mode;
|
private _finalMode = toLowerANSI _mode;
|
||||||
if !(_finalMode in ["bank", "atm"]) then {
|
if !(_finalMode in ["bank", "atm"]) then { _finalMode = "bank"; };
|
||||||
_finalMode = "bank";
|
|
||||||
};
|
|
||||||
|
|
||||||
_self set ["mode", _finalMode];
|
_self set ["mode", _finalMode];
|
||||||
_finalMode
|
_finalMode
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -12,9 +12,15 @@
|
|||||||
store.hydrateFromPayload(payloadData);
|
store.hydrateFromPayload(payloadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncAccount(payloadData) {
|
||||||
|
BankApp.data.applyAccountPatch(payloadData);
|
||||||
|
store.syncAccountPatch();
|
||||||
|
}
|
||||||
|
|
||||||
bridge.on("bank::hydrate", hydrate);
|
bridge.on("bank::hydrate", hydrate);
|
||||||
bridge.on("bank::sync", hydrate);
|
bridge.on("bank::sync", syncAccount);
|
||||||
bridge.on("bank::notice", (payloadData) => {
|
bridge.on("bank::notice", (payloadData) => {
|
||||||
|
store.finishAction();
|
||||||
if (BankApp.actions) {
|
if (BankApp.actions) {
|
||||||
BankApp.actions.showNotice(
|
BankApp.actions.showNotice(
|
||||||
payloadData.type || "error",
|
payloadData.type || "error",
|
||||||
@ -37,9 +43,15 @@
|
|||||||
requestDepositEarnings(payload) {
|
requestDepositEarnings(payload) {
|
||||||
return bridge.send("bank::depositEarnings::request", payload);
|
return bridge.send("bank::depositEarnings::request", payload);
|
||||||
},
|
},
|
||||||
|
requestRepayCreditLine(payload) {
|
||||||
|
return bridge.send("bank::repayCreditLine::request", payload);
|
||||||
|
},
|
||||||
requestRefresh() {
|
requestRefresh() {
|
||||||
return bridge.send("bank::refresh", {});
|
return bridge.send("bank::refresh", {});
|
||||||
},
|
},
|
||||||
|
requestSubmitPin(payload) {
|
||||||
|
return bridge.send("bank::pin::request", payload);
|
||||||
|
},
|
||||||
requestTransfer(payload) {
|
requestTransfer(payload) {
|
||||||
return bridge.send("bank::transfer::request", payload);
|
return bridge.send("bank::transfer::request", payload);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,6 +2,14 @@
|
|||||||
const BankApp = (window.BankApp = window.BankApp || {});
|
const BankApp = (window.BankApp = window.BankApp || {});
|
||||||
|
|
||||||
const defaultSession = {
|
const defaultSession = {
|
||||||
|
atmAuthorized: false,
|
||||||
|
creditLine: {
|
||||||
|
amountDue: 0,
|
||||||
|
approvedAmount: 0,
|
||||||
|
availableAmount: 0,
|
||||||
|
interestRate: 0.1,
|
||||||
|
outstandingPrincipal: 0,
|
||||||
|
},
|
||||||
mode: "bank",
|
mode: "bank",
|
||||||
orgFunds: 0,
|
orgFunds: 0,
|
||||||
orgName: "",
|
orgName: "",
|
||||||
@ -14,7 +22,6 @@
|
|||||||
bank: 0,
|
bank: 0,
|
||||||
cash: 0,
|
cash: 0,
|
||||||
earnings: 0,
|
earnings: 0,
|
||||||
pin: "1234",
|
|
||||||
transactions: [],
|
transactions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +37,13 @@
|
|||||||
BankApp.data = {
|
BankApp.data = {
|
||||||
account: Object.assign({}, defaultAccount),
|
account: Object.assign({}, defaultAccount),
|
||||||
session: Object.assign({}, defaultSession),
|
session: Object.assign({}, defaultSession),
|
||||||
|
applyAccountPatch(patch) {
|
||||||
|
const nextAccount = Object.assign({}, this.account, patch || {});
|
||||||
|
replaceObject(
|
||||||
|
this.account,
|
||||||
|
Object.assign({}, defaultAccount, nextAccount),
|
||||||
|
);
|
||||||
|
},
|
||||||
applyHydratePayload(payload) {
|
applyHydratePayload(payload) {
|
||||||
replaceObject(
|
replaceObject(
|
||||||
this.session,
|
this.session,
|
||||||
|
|||||||
@ -89,6 +89,16 @@
|
|||||||
"Reference value pulled from the organization treasury.",
|
"Reference value pulled from the organization treasury.",
|
||||||
session.orgFunds > 0 ? "success" : "",
|
session.orgFunds > 0 ? "success" : "",
|
||||||
),
|
),
|
||||||
|
metricCard(
|
||||||
|
"Credit Due",
|
||||||
|
formatCurrency(session.creditLine?.amountDue || 0),
|
||||||
|
Number(session.creditLine?.amountDue || 0) > 0
|
||||||
|
? `Outstanding principal ${formatCurrency(session.creditLine?.outstandingPrincipal || 0)} at ${Math.round(Number(session.creditLine?.interestRate || 0) * 100)}% interest.`
|
||||||
|
: "No active credit repayment is currently due.",
|
||||||
|
Number(session.creditLine?.amountDue || 0) > 0
|
||||||
|
? "warning"
|
||||||
|
: "",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -238,6 +248,63 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
h(
|
||||||
|
"section",
|
||||||
|
{ className: "bank-page-section" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "bank-section-header" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("span", { className: "bank-eyebrow" }, "Credit"),
|
||||||
|
h(
|
||||||
|
"h2",
|
||||||
|
{ className: "bank-section-title" },
|
||||||
|
"Repay Org Credit",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "bank-form-stack" },
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
{ className: "bank-card-copy" },
|
||||||
|
Number(session.creditLine?.amountDue || 0) > 0
|
||||||
|
? `Outstanding due ${formatCurrency(session.creditLine.amountDue || 0)}. Available reserved credit ${formatCurrency(session.creditLine.availableAmount || 0)}.`
|
||||||
|
: "No repayment is currently due on the assigned organization credit line.",
|
||||||
|
),
|
||||||
|
h("input", {
|
||||||
|
id: "bank-credit-line-amount",
|
||||||
|
className: "bank-input",
|
||||||
|
type: "number",
|
||||||
|
min: "1",
|
||||||
|
placeholder: "Enter repayment amount",
|
||||||
|
}),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "bank-btn bank-btn-primary",
|
||||||
|
disabled:
|
||||||
|
pending("repaycreditline") ||
|
||||||
|
Number(session.creditLine?.amountDue || 0) <= 0,
|
||||||
|
onClick: () => {
|
||||||
|
const sent = actions.requestRepayCreditLine(
|
||||||
|
readInputValue("bank-credit-line-amount"),
|
||||||
|
);
|
||||||
|
if (sent) {
|
||||||
|
clearInputValue("bank-credit-line-amount");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending("repaycreditline")
|
||||||
|
? "Posting Repayment..."
|
||||||
|
: "Repay Credit Line",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,6 @@
|
|||||||
|
|
||||||
let noticeTimer = null;
|
let noticeTimer = null;
|
||||||
|
|
||||||
function getAccount() {
|
|
||||||
return BankApp.data?.account || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSession() {
|
|
||||||
return BankApp.data?.session || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeAmount(value) {
|
function normalizeAmount(value) {
|
||||||
const amount = Math.floor(Number(value || 0));
|
const amount = Math.floor(Number(value || 0));
|
||||||
return Number.isFinite(amount) ? amount : 0;
|
return Number.isFinite(amount) ? amount : 0;
|
||||||
@ -58,18 +50,6 @@
|
|||||||
|
|
||||||
function requestDeposit(amountValue) {
|
function requestDeposit(amountValue) {
|
||||||
const amount = normalizeAmount(amountValue);
|
const amount = normalizeAmount(amountValue);
|
||||||
const account = getAccount();
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
showNotice("error", "Enter a valid deposit amount.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > Number(account.cash || 0)) {
|
|
||||||
showNotice("error", "Cash on hand cannot cover that deposit.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridge = BankApp.bridge;
|
const bridge = BankApp.bridge;
|
||||||
if (!bridge || typeof bridge.requestDeposit !== "function") {
|
if (!bridge || typeof bridge.requestDeposit !== "function") {
|
||||||
showNotice("error", "Deposit bridge is unavailable.");
|
showNotice("error", "Deposit bridge is unavailable.");
|
||||||
@ -89,18 +69,6 @@
|
|||||||
|
|
||||||
function requestWithdraw(amountValue) {
|
function requestWithdraw(amountValue) {
|
||||||
const amount = normalizeAmount(amountValue);
|
const amount = normalizeAmount(amountValue);
|
||||||
const account = getAccount();
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
showNotice("error", "Enter a valid withdrawal amount.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > Number(account.bank || 0)) {
|
|
||||||
showNotice("error", "Bank balance cannot cover that withdrawal.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridge = BankApp.bridge;
|
const bridge = BankApp.bridge;
|
||||||
if (!bridge || typeof bridge.requestWithdraw !== "function") {
|
if (!bridge || typeof bridge.requestWithdraw !== "function") {
|
||||||
showNotice("error", "Withdraw bridge is unavailable.");
|
showNotice("error", "Withdraw bridge is unavailable.");
|
||||||
@ -120,30 +88,8 @@
|
|||||||
|
|
||||||
function requestTransfer(targetUid, amountValue) {
|
function requestTransfer(targetUid, amountValue) {
|
||||||
const amount = normalizeAmount(amountValue);
|
const amount = normalizeAmount(amountValue);
|
||||||
const session = getSession();
|
|
||||||
const account = getAccount();
|
|
||||||
const targetId = String(targetUid || "").trim();
|
const targetId = String(targetUid || "").trim();
|
||||||
|
|
||||||
if (!targetId) {
|
|
||||||
showNotice("error", "Select a transfer recipient.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetId === String(session.uid || "")) {
|
|
||||||
showNotice("error", "You cannot transfer funds to yourself.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
showNotice("error", "Enter a valid transfer amount.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > Number(account.bank || 0)) {
|
|
||||||
showNotice("error", "Bank balance cannot cover that transfer.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridge = BankApp.bridge;
|
const bridge = BankApp.bridge;
|
||||||
if (!bridge || typeof bridge.requestTransfer !== "function") {
|
if (!bridge || typeof bridge.requestTransfer !== "function") {
|
||||||
showNotice("error", "Transfer bridge is unavailable.");
|
showNotice("error", "Transfer bridge is unavailable.");
|
||||||
@ -167,21 +113,6 @@
|
|||||||
|
|
||||||
function requestDepositEarnings(amountValue) {
|
function requestDepositEarnings(amountValue) {
|
||||||
const amount = normalizeAmount(amountValue);
|
const amount = normalizeAmount(amountValue);
|
||||||
const account = getAccount();
|
|
||||||
|
|
||||||
if (amount <= 0) {
|
|
||||||
showNotice("error", "No earnings are available to deposit.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > Number(account.earnings || 0)) {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
"Pending earnings cannot cover that deposit request.",
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bridge = BankApp.bridge;
|
const bridge = BankApp.bridge;
|
||||||
if (!bridge || typeof bridge.requestDepositEarnings !== "function") {
|
if (!bridge || typeof bridge.requestDepositEarnings !== "function") {
|
||||||
showNotice("error", "Earnings bridge is unavailable.");
|
showNotice("error", "Earnings bridge is unavailable.");
|
||||||
@ -199,6 +130,25 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestRepayCreditLine(amountValue) {
|
||||||
|
const amount = normalizeAmount(amountValue);
|
||||||
|
const bridge = BankApp.bridge;
|
||||||
|
if (!bridge || typeof bridge.requestRepayCreditLine !== "function") {
|
||||||
|
showNotice("error", "Credit repayment bridge is unavailable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.startAction("repaycreditline");
|
||||||
|
const sent = bridge.requestRepayCreditLine({ amount });
|
||||||
|
if (!sent) {
|
||||||
|
store.finishAction();
|
||||||
|
showNotice("error", "Credit repayment bridge is unavailable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function appendPinDigit(digit) {
|
function appendPinDigit(digit) {
|
||||||
const nextDigit = String(digit || "").trim();
|
const nextDigit = String(digit || "").trim();
|
||||||
if (!nextDigit) {
|
if (!nextDigit) {
|
||||||
@ -224,21 +174,21 @@
|
|||||||
|
|
||||||
function submitPin() {
|
function submitPin() {
|
||||||
const enteredPin = String(store.getEnteredPin() || "");
|
const enteredPin = String(store.getEnteredPin() || "");
|
||||||
const actualPin = String(getAccount().pin || "1234");
|
const bridge = BankApp.bridge;
|
||||||
|
if (!bridge || typeof bridge.requestSubmitPin !== "function") {
|
||||||
if (enteredPin.length !== 4) {
|
showNotice("error", "PIN bridge is unavailable.");
|
||||||
showNotice("error", "Enter your four-digit access PIN.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enteredPin !== actualPin) {
|
store.startAction("pin");
|
||||||
clearPin();
|
const sent = bridge.requestSubmitPin({ pin: enteredPin });
|
||||||
showNotice("error", "Incorrect PIN.");
|
if (!sent) {
|
||||||
|
store.finishAction();
|
||||||
|
showNotice("error", "PIN bridge is unavailable.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPin();
|
clearPin();
|
||||||
store.setAtmView("menu");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +249,6 @@
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
store.setCustomAmount("");
|
store.setCustomAmount("");
|
||||||
store.setAtmView("menu");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@ -314,10 +263,6 @@
|
|||||||
? requestDeposit(amount)
|
? requestDeposit(amount)
|
||||||
: requestWithdraw(amount);
|
: requestWithdraw(amount);
|
||||||
|
|
||||||
if (success) {
|
|
||||||
store.setAtmView("menu");
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +278,7 @@
|
|||||||
requestAtmAmount,
|
requestAtmAmount,
|
||||||
requestDeposit,
|
requestDeposit,
|
||||||
requestDepositEarnings,
|
requestDepositEarnings,
|
||||||
|
requestRepayCreditLine,
|
||||||
requestTransfer,
|
requestTransfer,
|
||||||
requestWithdraw,
|
requestWithdraw,
|
||||||
selectAtmView,
|
selectAtmView,
|
||||||
|
|||||||
@ -25,25 +25,46 @@
|
|||||||
const mode = String(payload?.session?.mode || "bank")
|
const mode = String(payload?.session?.mode || "bank")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
const atmAuthorized = Boolean(payload?.session?.atmAuthorized);
|
||||||
const currentMode = this.getMode();
|
const currentMode = this.getMode();
|
||||||
const currentAtmView = this.getAtmView();
|
const currentAtmView = this.getAtmView();
|
||||||
|
const currentPendingAction = this.getPendingAction();
|
||||||
|
|
||||||
this.setMode(mode === "atm" ? "atm" : "bank");
|
this.setMode(mode === "atm" ? "atm" : "bank");
|
||||||
this.setPendingAction("");
|
this.setPendingAction("");
|
||||||
this.setNotice({ text: "", type: "" });
|
|
||||||
this.setEnteredPin("");
|
this.setEnteredPin("");
|
||||||
this.setCustomAmount("");
|
this.setCustomAmount("");
|
||||||
this.setAccountVersion(this.getAccountVersion() + 1);
|
this.setAccountVersion(this.getAccountVersion() + 1);
|
||||||
this.setSessionVersion(this.getSessionVersion() + 1);
|
this.setSessionVersion(this.getSessionVersion() + 1);
|
||||||
|
|
||||||
if (mode === "atm") {
|
if (mode === "atm") {
|
||||||
this.setAtmView(currentMode === "atm" ? currentAtmView : "pin");
|
if (!atmAuthorized) {
|
||||||
|
this.setAtmView("pin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentPendingAction === "deposit" ||
|
||||||
|
currentPendingAction === "withdraw" ||
|
||||||
|
currentAtmView === "pin" ||
|
||||||
|
currentMode !== "atm"
|
||||||
|
) {
|
||||||
|
this.setAtmView("menu");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setAtmView(currentAtmView);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAtmView("dashboard");
|
this.setAtmView("dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncAccountPatch() {
|
||||||
|
this.setPendingAction("");
|
||||||
|
this.setAccountVersion(this.getAccountVersion() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
resetAtm() {
|
resetAtm() {
|
||||||
this.setEnteredPin("");
|
this.setEnteredPin("");
|
||||||
this.setCustomAmount("");
|
this.setCustomAmount("");
|
||||||
|
|||||||
1
arma/client/addons/cad/$PBOPREFIX$
Normal file
1
arma/client/addons/cad/$PBOPREFIX$
Normal file
@ -0,0 +1 @@
|
|||||||
|
forge\forge_client\addons\cad
|
||||||
11
arma/client/addons/cad/CfgEventHandlers.hpp
Normal file
11
arma/client/addons/cad/CfgEventHandlers.hpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class Extended_PreInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Extended_PostInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
clientInit = QUOTE(call COMPILE_SCRIPT(XEH_postInitClient));
|
||||||
|
};
|
||||||
|
};
|
||||||
214
arma/client/addons/cad/MAP_README.md
Normal file
214
arma/client/addons/cad/MAP_README.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# Integrated Map Display System (A3API Pattern)
|
||||||
|
|
||||||
|
This system integrates the Arma 3 native map control (`RscMapControl`) within an HTML/CSS/JS UI using Arma's proper WebBrowser control (type 106) and A3API communication pattern.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Layered Architecture
|
||||||
|
|
||||||
|
1. **IFrame Control (type 106)** - Loads HTML content using `ctrlWebBrowserAction`
|
||||||
|
2. **Map Control (RscMapControl)** - Native Arma map positioned behind/within the UI
|
||||||
|
3. **A3API Communication** - Bidirectional communication between JavaScript and SQF
|
||||||
|
|
||||||
|
### Communication Flow
|
||||||
|
|
||||||
|
**JavaScript → SQF:**
|
||||||
|
```javascript
|
||||||
|
// Send alert (no response expected)
|
||||||
|
A3API.SendAlert(JSON.stringify({
|
||||||
|
event: "map::zoomIn",
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Send confirm (expects response via ExecJS)
|
||||||
|
A3API.SendConfirm(JSON.stringify({
|
||||||
|
event: "map::getPosition",
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
**SQF → JavaScript:**
|
||||||
|
```sqf
|
||||||
|
_control ctrlWebBrowserAction ["ExecJS", "updateMapState({center: [1000, 2000], scale: 0.5});"];
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
UI/map/
|
||||||
|
├── _site/
|
||||||
|
│ ├── index.html # HTML with A3API dynamic loading
|
||||||
|
│ ├── script.js # JavaScript using A3API
|
||||||
|
│ └── style.css # Styling
|
||||||
|
└── MAP_README.md # This file
|
||||||
|
|
||||||
|
functions/map/
|
||||||
|
├── fn_openMap.sqf # Opens the display
|
||||||
|
├── fn_mapHandleUIEvents.sqf # Handles JS events
|
||||||
|
├── fn_mapDisplay.sqf # Display initialization
|
||||||
|
└── fn_mapDisplayUpdate.sqf # Update loop
|
||||||
|
|
||||||
|
UI/MapDisplay.h # Dialog definition
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Opening the Map
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
[] call FORGE_fnc_openMap;
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Init or Action
|
||||||
|
|
||||||
|
```sqf
|
||||||
|
// Add player action
|
||||||
|
player addAction ["Open Map", {[] call FORGE_fnc_openMap;}];
|
||||||
|
|
||||||
|
// In init.sqf
|
||||||
|
[] call FORGE_fnc_openMap;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences from Standard HTML/CSS/JS
|
||||||
|
|
||||||
|
### 1. Dynamic Resource Loading
|
||||||
|
|
||||||
|
Instead of `<link>` and `<script>` tags, files are loaded using A3API:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
Promise.all([
|
||||||
|
A3API.RequestFile("UI\\map\\_site\\style.css"),
|
||||||
|
A3API.RequestFile("UI\\map\\_site\\script.js")
|
||||||
|
]).then(([css, js]) => {
|
||||||
|
// Apply CSS
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Execute JavaScript
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.text = js;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event Communication
|
||||||
|
|
||||||
|
Use **A3API.SendAlert()** for one-way messages:
|
||||||
|
```javascript
|
||||||
|
A3API.SendAlert(JSON.stringify({event: "map::action", data: value}));
|
||||||
|
```
|
||||||
|
|
||||||
|
Use **A3API.SendConfirm()** for messages expecting a response:
|
||||||
|
```javascript
|
||||||
|
A3API.SendConfirm(JSON.stringify({event: "map::getdata", data: null}));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pointer Events
|
||||||
|
|
||||||
|
UI elements need `pointer-events: auto` while the body has `pointer-events: none`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
body {
|
||||||
|
pointer-events: none; /* Allows clicks through to map */
|
||||||
|
}
|
||||||
|
|
||||||
|
#topBar {
|
||||||
|
pointer-events: auto; /* UI elements catch clicks */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dialog Definition Pattern
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class RscMapDisplay {
|
||||||
|
idd = 9000;
|
||||||
|
onLoad = "['onLoad', _this] call FORGE_fnc_mapDisplay;";
|
||||||
|
|
||||||
|
class Controls {
|
||||||
|
class Browser: RscText {
|
||||||
|
type = 106; // IFrame control type
|
||||||
|
idc = 9001;
|
||||||
|
x = "safeZoneX";
|
||||||
|
y = "safeZoneY";
|
||||||
|
w = "safeZoneW";
|
||||||
|
h = "safeZoneH";
|
||||||
|
};
|
||||||
|
|
||||||
|
class MapControl: RscMapControl {
|
||||||
|
idc = 9002;
|
||||||
|
// Position to fit within HTML UI
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Handler Pattern
|
||||||
|
|
||||||
|
In `fn_openMap.sqf`:
|
||||||
|
```sqf
|
||||||
|
private _ctrl = _display displayCtrl 9001;
|
||||||
|
|
||||||
|
// Add JSDialog event handler
|
||||||
|
_ctrl ctrlAddEventHandler ["JSDialog", {
|
||||||
|
params ["_control", "_isConfirmDialog", "_message"];
|
||||||
|
[_control, _isConfirmDialog, _message] call FORGE_fnc_mapHandleUIEvents;
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Load HTML file
|
||||||
|
_ctrl ctrlWebBrowserAction ["LoadFile", "UI\\map\\_site\\index.html"];
|
||||||
|
```
|
||||||
|
|
||||||
|
In `fn_mapHandleUIEvents.sqf`:
|
||||||
|
```sqf
|
||||||
|
params ["_control", "_isConfirmDialog", "_message"];
|
||||||
|
|
||||||
|
private _eventData = fromJSON _message;
|
||||||
|
private _event = _eventData get "event";
|
||||||
|
private _data = _eventData get "data";
|
||||||
|
|
||||||
|
switch (_event) do {
|
||||||
|
case "map::ready": {
|
||||||
|
// Initialize
|
||||||
|
};
|
||||||
|
case "map::zoomIn": {
|
||||||
|
// Handle zoom
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits of This Pattern
|
||||||
|
|
||||||
|
1. **Proper Arma Integration** - Uses native WebBrowser control (type 106)
|
||||||
|
2. **File System Compatibility** - A3API.RequestFile() works with Arma's file system
|
||||||
|
3. **Reliable Communication** - JSDialog event handler is more stable than htmlLoad
|
||||||
|
4. **Modular** - CSS and JS in separate files, dynamically loaded
|
||||||
|
5. **Consistent** - Matches bank module pattern used in FORGE
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Files not loading:**
|
||||||
|
- Check paths use double backslashes: `"UI\\map\\_site\\style.css"`
|
||||||
|
- Verify files exist in the correct directory
|
||||||
|
- Check .rpt log for file loading errors
|
||||||
|
|
||||||
|
**Events not firing:**
|
||||||
|
- Verify JSDialog event handler is attached
|
||||||
|
- Check JSON formatting in A3API calls
|
||||||
|
- Look for JavaScript console errors (use OpenDevConsole)
|
||||||
|
|
||||||
|
**Map not showing:**
|
||||||
|
- Verify MapControl idc matches (9002)
|
||||||
|
- Check map control positioning in MapDisplay.h
|
||||||
|
- Ensure map control is rendered after browser control
|
||||||
|
|
||||||
|
## Developer Tools
|
||||||
|
|
||||||
|
Enable dev console in `fn_openMap.sqf`:
|
||||||
|
```sqf
|
||||||
|
_ctrl ctrlWebBrowserAction ["OpenDevConsole"];
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens Chromium dev tools for debugging JavaScript, CSS, and network requests.
|
||||||
5
arma/client/addons/cad/XEH_PREP.hpp
Normal file
5
arma/client/addons/cad/XEH_PREP.hpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
PREP(handleUIEvents);
|
||||||
|
PREP(initRepository);
|
||||||
|
PREP(initUIBridge);
|
||||||
|
PREP(initUI);
|
||||||
|
PREP(openUI);
|
||||||
40
arma/client/addons/cad/XEH_postInitClient.sqf
Normal file
40
arma/client/addons/cad/XEH_postInitClient.sqf
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
if (isNil QGVAR(CADRepository)) then { call FUNC(initRepository); };
|
||||||
|
if (isNil QGVAR(CADUIBridge)) then { call FUNC(initUIBridge); };
|
||||||
|
|
||||||
|
[QGVAR(openCAD), {
|
||||||
|
call FUNC(openUI);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseHydrateCad), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["handleHydrateResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseCadAssignment), {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["handleAssignmentResponse", [_result]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseCadGroupUpdate), {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["handleGroupUpdateResponse", [_result]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseCadRequest), {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["handleRequestResponse", [_result]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(invalidateCadState), {
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith {};
|
||||||
|
if !(GVAR(CADRepository) getOrDefault ["isOpen", false]) exitWith {};
|
||||||
|
if (isNil QGVAR(CADUIBridge)) exitWith {};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestHydrate", []];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
5
arma/client/addons/cad/XEH_preInit.sqf
Normal file
5
arma/client/addons/cad/XEH_preInit.sqf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
PREP_RECOMPILE_START;
|
||||||
|
#include "XEH_PREP.hpp"
|
||||||
|
PREP_RECOMPILE_END;
|
||||||
1
arma/client/addons/cad/XEH_preInitClient.sqf
Normal file
1
arma/client/addons/cad/XEH_preInitClient.sqf
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
21
arma/client/addons/cad/config.cpp
Normal file
21
arma/client/addons/cad/config.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
class CfgPatches {
|
||||||
|
class ADDON {
|
||||||
|
author = AUTHOR;
|
||||||
|
authors[] = {"IDSolutions"};
|
||||||
|
url = ECSTRING(main,url);
|
||||||
|
name = COMPONENT_NAME;
|
||||||
|
requiredVersion = REQUIRED_VERSION;
|
||||||
|
requiredAddons[] = {
|
||||||
|
"forge_client_main"
|
||||||
|
};
|
||||||
|
units[] = {};
|
||||||
|
weapons[] = {};
|
||||||
|
VERSION_CONFIG;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "CfgEventHandlers.hpp"
|
||||||
|
#include "ui\RscCommon.hpp"
|
||||||
|
#include "ui\RscMapUI.hpp"
|
||||||
229
arma/client/addons/cad/functions/fnc_handleUIEvents.sqf
Normal file
229
arma/client/addons/cad/functions/fnc_handleUIEvents.sqf
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_handleUIEvents.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-28
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Handles CAD browser UI events.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* 0: Control [CONTROL]
|
||||||
|
* 1: Confirm dialog flag [BOOL]
|
||||||
|
* 2: Browser message [STRING]
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* UI event handled [BOOL]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* [_control, false, _message] call forge_client_cad_fnc_handleUIEvents
|
||||||
|
*/
|
||||||
|
|
||||||
|
params ["_control", "_isConfirmDialog", "_message"];
|
||||||
|
|
||||||
|
private _alert = fromJSON _message;
|
||||||
|
private _event = _alert getOrDefault ["event", ""];
|
||||||
|
private _data = _alert getOrDefault ["data", nil];
|
||||||
|
|
||||||
|
diag_log format ["[FORGE:Client:CAD] Handling UI event: %1", _event];
|
||||||
|
|
||||||
|
if (_isConfirmDialog) exitWith { true };
|
||||||
|
|
||||||
|
switch (_event) do {
|
||||||
|
case "cad::topbar::ready": {
|
||||||
|
GVAR(CADUIBridge) call ["handleTopBarReady", []];
|
||||||
|
};
|
||||||
|
case "cad::ready": {
|
||||||
|
GVAR(CADUIBridge) call ["handleReady", [_control, _data]];
|
||||||
|
};
|
||||||
|
case "cad::dispatcher::ready": {
|
||||||
|
GVAR(CADUIBridge) call ["handleDispatcherReady", []];
|
||||||
|
};
|
||||||
|
case "cad::mode::set": {
|
||||||
|
private _mode = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_mode = _data getOrDefault ["mode", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["setMode", [_mode]];
|
||||||
|
};
|
||||||
|
case "cad::dispatchView::set": {
|
||||||
|
private _dispatchView = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_dispatchView = _data getOrDefault ["dispatchView", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["setDispatchView", [_dispatchView]];
|
||||||
|
};
|
||||||
|
case "cad::refresh": {
|
||||||
|
GVAR(CADUIBridge) call ["requestHydrate", []];
|
||||||
|
};
|
||||||
|
case "cad::tasks::assign": {
|
||||||
|
private _taskID = "";
|
||||||
|
private _groupID = "";
|
||||||
|
private _note = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_taskID = _data getOrDefault ["taskID", ""];
|
||||||
|
_groupID = _data getOrDefault ["groupID", ""];
|
||||||
|
_note = _data getOrDefault ["note", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestAssignTask", [_taskID, _groupID, _note]];
|
||||||
|
};
|
||||||
|
case "cad::dispatchOrder::create": {
|
||||||
|
private _assigneeGroupID = "";
|
||||||
|
private _targetGroupID = "";
|
||||||
|
private _note = "";
|
||||||
|
private _priority = "priority";
|
||||||
|
private _request = createHashMap;
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_assigneeGroupID = _data getOrDefault ["assigneeGroupID", ""];
|
||||||
|
_targetGroupID = _data getOrDefault ["targetGroupID", ""];
|
||||||
|
_note = _data getOrDefault ["note", ""];
|
||||||
|
_priority = _data getOrDefault ["priority", "priority"];
|
||||||
|
_request = _data getOrDefault ["request", createHashMap];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestCreateDispatchOrder", [_assigneeGroupID, _targetGroupID, _note, _priority, _request]];
|
||||||
|
};
|
||||||
|
case "cad::supportRequest::submit": {
|
||||||
|
private _type = "";
|
||||||
|
private _fields = createHashMap;
|
||||||
|
private _priority = "priority";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_type = _data getOrDefault ["type", ""];
|
||||||
|
_fields = _data getOrDefault ["fields", createHashMap];
|
||||||
|
_priority = _data getOrDefault ["priority", "priority"];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestSubmitSupportRequest", [_type, _fields, _priority]];
|
||||||
|
};
|
||||||
|
case "cad::dispatchOrder::close": {
|
||||||
|
private _taskID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_taskID = _data getOrDefault ["taskID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestCloseDispatchOrder", [_taskID]];
|
||||||
|
};
|
||||||
|
case "cad::supportRequest::close": {
|
||||||
|
private _requestID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_requestID = _data getOrDefault ["requestID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestCloseSupportRequest", [_requestID]];
|
||||||
|
};
|
||||||
|
case "cad::tasks::acknowledge": {
|
||||||
|
private _taskID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_taskID = _data getOrDefault ["taskID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestAcknowledgeTask", [_taskID]];
|
||||||
|
};
|
||||||
|
case "cad::tasks::decline": {
|
||||||
|
private _taskID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_taskID = _data getOrDefault ["taskID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestDeclineTask", [_taskID]];
|
||||||
|
};
|
||||||
|
case "cad::groups::status": {
|
||||||
|
private _groupID = "";
|
||||||
|
private _status = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_groupID = _data getOrDefault ["groupID", ""];
|
||||||
|
_status = _data getOrDefault ["status", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestGroupStatus", [_groupID, _status]];
|
||||||
|
};
|
||||||
|
case "cad::groups::role": {
|
||||||
|
private _groupID = "";
|
||||||
|
private _role = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_groupID = _data getOrDefault ["groupID", ""];
|
||||||
|
_role = _data getOrDefault ["role", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestGroupRole", [_groupID, _role]];
|
||||||
|
};
|
||||||
|
case "cad::groups::profile": {
|
||||||
|
private _groupID = "";
|
||||||
|
private _status = "";
|
||||||
|
private _role = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_groupID = _data getOrDefault ["groupID", ""];
|
||||||
|
_status = _data getOrDefault ["status", ""];
|
||||||
|
_role = _data getOrDefault ["role", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["requestGroupProfile", [_groupID, _status, _role]];
|
||||||
|
};
|
||||||
|
case "cad::groups::focus": {
|
||||||
|
private _groupID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_groupID = _data getOrDefault ["groupID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["focusGroup", [_groupID]];
|
||||||
|
};
|
||||||
|
case "cad::tasks::focus": {
|
||||||
|
private _taskID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_taskID = _data getOrDefault ["taskID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["focusTask", [_taskID]];
|
||||||
|
};
|
||||||
|
case "cad::requests::focus": {
|
||||||
|
private _requestID = "";
|
||||||
|
if (_data isEqualType createHashMap) then {
|
||||||
|
_requestID = _data getOrDefault ["requestID", ""];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) call ["focusRequest", [_requestID]];
|
||||||
|
};
|
||||||
|
case "map::zoomIn": {
|
||||||
|
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
|
||||||
|
if (isNull _mapCtrl) exitWith {};
|
||||||
|
|
||||||
|
private _currentZoom = ctrlMapScale _mapCtrl;
|
||||||
|
private _newZoom = (_currentZoom * 0.5) max 0.001;
|
||||||
|
private _center = _mapCtrl ctrlMapScreenToWorld [0.5, 0.5];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.3, _newZoom, _center];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
};
|
||||||
|
case "map::zoomOut": {
|
||||||
|
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
|
||||||
|
if (isNull _mapCtrl) exitWith {};
|
||||||
|
|
||||||
|
private _currentZoom = ctrlMapScale _mapCtrl;
|
||||||
|
private _newZoom = (_currentZoom * 2) min 1;
|
||||||
|
private _center = _mapCtrl ctrlMapScreenToWorld [0.5, 0.5];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.3, _newZoom, _center];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
};
|
||||||
|
case "map::search": {
|
||||||
|
private _query = str _data;
|
||||||
|
private _bottomBar = uiNamespace getVariable [QGVAR(BottomBarCtrl), controlNull];
|
||||||
|
if (isNull _bottomBar) exitWith {};
|
||||||
|
|
||||||
|
_bottomBar ctrlWebBrowserAction ["ExecJS", format ["updateStatus('Search not yet implemented: %1');", _query]];
|
||||||
|
};
|
||||||
|
case "map::close": {
|
||||||
|
if !(isNil QGVAR(CADUIBridge)) then {
|
||||||
|
GVAR(CADUIBridge) call ["handleClose", []];
|
||||||
|
};
|
||||||
|
closeDialog 1;
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
diag_log format ["[FORGE:Client:CAD] WARNING: Unhandled UI event: %1", _event];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
true
|
||||||
105
arma/client/addons/cad/functions/fnc_initRepository.sqf
Normal file
105
arma/client/addons/cad/functions/fnc_initRepository.sqf
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initRepository.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-28
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the CAD repository for lightweight client lifecycle state.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* CAD repository object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_cad_fnc_initRepository
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(CADRepository) = createHashMapObject [[
|
||||||
|
["#type", "CADRepository"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["isLoaded", true];
|
||||||
|
_self set ["isOpen", false];
|
||||||
|
_self set ["groups", []];
|
||||||
|
_self set ["contracts", []];
|
||||||
|
_self set ["requests", []];
|
||||||
|
_self set ["assignments", []];
|
||||||
|
_self set ["activity", []];
|
||||||
|
_self set ["session", createHashMap];
|
||||||
|
_self set ["mode", "operations"];
|
||||||
|
_self set ["dispatchView", "board"];
|
||||||
|
}],
|
||||||
|
["getHydratePayload", compileFinal {
|
||||||
|
createHashMapFromArray [
|
||||||
|
["groups", +(_self getOrDefault ["groups", []])],
|
||||||
|
["contracts", +(_self getOrDefault ["contracts", []])],
|
||||||
|
["requests", +(_self getOrDefault ["requests", []])],
|
||||||
|
["assignments", +(_self getOrDefault ["assignments", []])],
|
||||||
|
["activity", +(_self getOrDefault ["activity", []])],
|
||||||
|
["session", +(_self getOrDefault ["session", createHashMap])],
|
||||||
|
["mode", _self getOrDefault ["mode", "operations"]],
|
||||||
|
["dispatchView", _self getOrDefault ["dispatchView", "board"]]
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
["getCurrentGroup", compileFinal {
|
||||||
|
private _session = _self getOrDefault ["session", createHashMap];
|
||||||
|
private _groupID = _session getOrDefault ["groupId", ""];
|
||||||
|
if (_groupID isEqualTo "") exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _groups = _self getOrDefault ["groups", []];
|
||||||
|
private _group = _groups findIf { (_x getOrDefault ["groupId", ""]) isEqualTo _groupID };
|
||||||
|
if (_group < 0) exitWith { createHashMap };
|
||||||
|
|
||||||
|
+(_groups # _group)
|
||||||
|
}],
|
||||||
|
["pushHydratePayload", compileFinal {
|
||||||
|
params [["_bridge", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_bridge isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
_bridge call ["sendEvent", ["cad::hydrate", _self call ["getHydratePayload", []]]]
|
||||||
|
}],
|
||||||
|
["setHydratePayload", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
_self set ["groups", +(_payload getOrDefault ["groups", []])];
|
||||||
|
_self set ["contracts", +(_payload getOrDefault ["contracts", []])];
|
||||||
|
_self set ["requests", +(_payload getOrDefault ["requests", []])];
|
||||||
|
_self set ["assignments", +(_payload getOrDefault ["assignments", []])];
|
||||||
|
_self set ["activity", +(_payload getOrDefault ["activity", []])];
|
||||||
|
_self set ["session", +(_payload getOrDefault ["session", createHashMap])];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["setMode", compileFinal {
|
||||||
|
params [["_mode", "operations", [""]]];
|
||||||
|
|
||||||
|
if !(_mode in ["operations", "dispatch"]) then {
|
||||||
|
_mode = "operations";
|
||||||
|
};
|
||||||
|
|
||||||
|
_self set ["mode", _mode];
|
||||||
|
_mode
|
||||||
|
}],
|
||||||
|
["setDispatchView", compileFinal {
|
||||||
|
params [["_dispatchView", "board", [""]]];
|
||||||
|
|
||||||
|
if !(_dispatchView in ["board", "map"]) then {
|
||||||
|
_dispatchView = "board";
|
||||||
|
};
|
||||||
|
|
||||||
|
_self set ["dispatchView", _dispatchView];
|
||||||
|
_dispatchView
|
||||||
|
}],
|
||||||
|
["setOpen", compileFinal {
|
||||||
|
params [["_isOpen", false, [false]]];
|
||||||
|
_self set ["isOpen", _isOpen];
|
||||||
|
true
|
||||||
|
}]
|
||||||
|
]];
|
||||||
|
|
||||||
|
GVAR(CADRepository)
|
||||||
51
arma/client/addons/cad/functions/fnc_initUI.sqf
Normal file
51
arma/client/addons/cad/functions/fnc_initUI.sqf
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initUI.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-28
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the CAD map dialog controls and local map event handling.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* 0: Display [DISPLAY]
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* UI initialized [BOOL]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* [_display] call forge_client_cad_fnc_initUI
|
||||||
|
*/
|
||||||
|
|
||||||
|
params [["_display", displayNull, [displayNull]]];
|
||||||
|
|
||||||
|
if (isNull _display) exitWith { false };
|
||||||
|
|
||||||
|
private _mapCtrl = _display displayCtrl 1001;
|
||||||
|
private _topBarCtrl = _display displayCtrl 1002;
|
||||||
|
private _bottomBarCtrl = _display displayCtrl 1003;
|
||||||
|
private _sidePanelCtrl = _display displayCtrl 1005;
|
||||||
|
private _dispatcherCtrl = _display displayCtrl 1006;
|
||||||
|
|
||||||
|
uiNamespace setVariable [QGVAR(Display), _display];
|
||||||
|
uiNamespace setVariable [QGVAR(MapCtrl), _mapCtrl];
|
||||||
|
uiNamespace setVariable [QGVAR(TopBarCtrl), _topBarCtrl];
|
||||||
|
uiNamespace setVariable [QGVAR(BottomBarCtrl), _bottomBarCtrl];
|
||||||
|
uiNamespace setVariable [QGVAR(SidePanelCtrl), _sidePanelCtrl];
|
||||||
|
uiNamespace setVariable [QGVAR(DispatcherCtrl), _dispatcherCtrl];
|
||||||
|
|
||||||
|
_dispatcherCtrl ctrlShow false;
|
||||||
|
|
||||||
|
private _center = if (isNull player) then {
|
||||||
|
[worldSize / 2, worldSize / 2, 0]
|
||||||
|
} else {
|
||||||
|
getPosATL player
|
||||||
|
};
|
||||||
|
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0, 0.2, _center];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
|
||||||
|
diag_log "[FORGE:Client:CAD] CAD UI initialized.";
|
||||||
|
true
|
||||||
450
arma/client/addons/cad/functions/fnc_initUIBridge.sqf
Normal file
450
arma/client/addons/cad/functions/fnc_initUIBridge.sqf
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initUIBridge.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-28
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the CAD UI bridge for sidepanel browser state and CAD event routing.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* CAD UI bridge object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_cad_fnc_initUIBridge
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
private _webUIDeclarations = call EFUNC(common,initWebUIBridge);
|
||||||
|
private _webUIBridgeDeclaration = _webUIDeclarations get "bridgeDeclaration";
|
||||||
|
|
||||||
|
GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#base", _webUIBridgeDeclaration],
|
||||||
|
["#type", "CADUIBridgeBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["dispatcherReady", false];
|
||||||
|
_self set ["topBarReady", false];
|
||||||
|
}],
|
||||||
|
["getActiveBrowserControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
|
||||||
|
if (isNull _display) exitWith {
|
||||||
|
_self call ["setActiveBrowserControl", [controlNull]];
|
||||||
|
controlNull
|
||||||
|
};
|
||||||
|
|
||||||
|
private _control = _display displayCtrl 1005;
|
||||||
|
_self call ["setActiveBrowserControl", [_control]];
|
||||||
|
_control
|
||||||
|
}],
|
||||||
|
["getTopBarControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
|
||||||
|
if (isNull _display) exitWith { controlNull };
|
||||||
|
|
||||||
|
_display displayCtrl 1002
|
||||||
|
}],
|
||||||
|
["getBottomBarControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
|
||||||
|
if (isNull _display) exitWith { controlNull };
|
||||||
|
|
||||||
|
_display displayCtrl 1003
|
||||||
|
}],
|
||||||
|
["getMapControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
|
||||||
|
if (isNull _display) exitWith { controlNull };
|
||||||
|
|
||||||
|
_display displayCtrl 1001
|
||||||
|
}],
|
||||||
|
["getDispatcherControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable [QGVAR(Display), displayNull];
|
||||||
|
if (isNull _display) exitWith { controlNull };
|
||||||
|
|
||||||
|
_display displayCtrl 1006
|
||||||
|
}],
|
||||||
|
["hasOpenScreen", compileFinal {
|
||||||
|
private _screen = _self call ["getScreen", []];
|
||||||
|
private _control = _self call ["getActiveBrowserControl", []];
|
||||||
|
!(isNull _control) && { _screen call ["isReady", []] }
|
||||||
|
}],
|
||||||
|
["isDispatcher", compileFinal {
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _session = GVAR(CADRepository) getOrDefault ["session", createHashMap];
|
||||||
|
_session getOrDefault ["isDispatcher", false]
|
||||||
|
}],
|
||||||
|
["applyLayout", compileFinal {
|
||||||
|
private _mode = if (isNil QGVAR(CADRepository)) then {
|
||||||
|
"operations"
|
||||||
|
} else {
|
||||||
|
GVAR(CADRepository) getOrDefault ["mode", "operations"]
|
||||||
|
};
|
||||||
|
private _dispatchView = if (isNil QGVAR(CADRepository)) then {
|
||||||
|
"board"
|
||||||
|
} else {
|
||||||
|
GVAR(CADRepository) getOrDefault ["dispatchView", "board"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _mapCtrl = _self call ["getMapControl", []];
|
||||||
|
private _bottomBarCtrl = _self call ["getBottomBarControl", []];
|
||||||
|
private _sidePanelCtrl = _self call ["getActiveBrowserControl", []];
|
||||||
|
private _dispatcherCtrl = _self call ["getDispatcherControl", []];
|
||||||
|
|
||||||
|
private _showMapLayout = (_mode isEqualTo "operations") || { _mode isEqualTo "dispatch" && { _dispatchView isEqualTo "map" } };
|
||||||
|
|
||||||
|
if !(isNull _mapCtrl) then { _mapCtrl ctrlShow _showMapLayout; };
|
||||||
|
if !(isNull _bottomBarCtrl) then { _bottomBarCtrl ctrlShow true; };
|
||||||
|
if !(isNull _sidePanelCtrl) then { _sidePanelCtrl ctrlShow _showMapLayout; };
|
||||||
|
if !(isNull _dispatcherCtrl) then { _dispatcherCtrl ctrlShow (_mode isEqualTo "dispatch" && { _dispatchView isEqualTo "board" }); };
|
||||||
|
|
||||||
|
_self call ["refreshHydrate", []];
|
||||||
|
_self call ["refreshTopBarState", []];
|
||||||
|
_self call ["refreshDispatcher", []];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["setMode", compileFinal {
|
||||||
|
params [["_mode", "operations", [""]]];
|
||||||
|
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _targetMode = _mode;
|
||||||
|
if !(_targetMode in ["operations", "dispatch"]) then {
|
||||||
|
_targetMode = "operations";
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_targetMode isEqualTo "dispatch" && !(_self call ["isDispatcher", []])) then {
|
||||||
|
_targetMode = "operations";
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CADRepository) call ["setMode", [_targetMode]];
|
||||||
|
if (_targetMode isEqualTo "dispatch") then {
|
||||||
|
GVAR(CADRepository) call ["setDispatchView", ["board"]];
|
||||||
|
};
|
||||||
|
_self call ["applyLayout", []]
|
||||||
|
}],
|
||||||
|
["setDispatchView", compileFinal {
|
||||||
|
params [["_dispatchView", "board", [""]]];
|
||||||
|
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
if ((GVAR(CADRepository) getOrDefault ["mode", "operations"]) isNotEqualTo "dispatch") exitWith { false };
|
||||||
|
if !(_self call ["isDispatcher", []]) exitWith { false };
|
||||||
|
|
||||||
|
GVAR(CADRepository) call ["setDispatchView", [_dispatchView]];
|
||||||
|
_self call ["applyLayout", []]
|
||||||
|
}],
|
||||||
|
["refreshTopBarState", compileFinal {
|
||||||
|
if !(_self getOrDefault ["topBarReady", false]) exitWith { false };
|
||||||
|
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _topBarCtrl = _self call ["getTopBarControl", []];
|
||||||
|
if (isNull _topBarCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _session = +(GVAR(CADRepository) getOrDefault ["session", createHashMap]);
|
||||||
|
private _currentGroup = GVAR(CADRepository) call ["getCurrentGroup", []];
|
||||||
|
private _payload = createHashMapFromArray [
|
||||||
|
["mode", GVAR(CADRepository) getOrDefault ["mode", "operations"]],
|
||||||
|
["dispatchView", GVAR(CADRepository) getOrDefault ["dispatchView", "board"]],
|
||||||
|
["session", _session],
|
||||||
|
["currentGroup", _currentGroup]
|
||||||
|
];
|
||||||
|
|
||||||
|
_topBarCtrl ctrlWebBrowserAction ["ExecJS", format [
|
||||||
|
"window.cadTopbar && window.cadTopbar.receiveState(%1);",
|
||||||
|
toJSON _payload
|
||||||
|
]];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["refreshDispatcher", compileFinal {
|
||||||
|
if !(_self getOrDefault ["dispatcherReady", false]) exitWith { false };
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _dispatcherCtrl = _self call ["getDispatcherControl", []];
|
||||||
|
if (isNull _dispatcherCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _payload = GVAR(CADRepository) call ["getHydratePayload", []];
|
||||||
|
_dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [
|
||||||
|
"window.cadDispatcher && window.cadDispatcher.receiveHydrate(%1);",
|
||||||
|
toJSON _payload
|
||||||
|
]];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["handleReady", compileFinal {
|
||||||
|
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _screen = _self call ["getScreen", []];
|
||||||
|
_screen call ["setControl", [_control]];
|
||||||
|
_screen call ["markReady", [true]];
|
||||||
|
_self call ["flushPendingEvents", []];
|
||||||
|
|
||||||
|
_self call ["requestHydrate", []];
|
||||||
|
_self call ["refreshHydrate", []];
|
||||||
|
_self call ["refreshTopBarState", []];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["handleClose", compileFinal {
|
||||||
|
_self set ["dispatcherReady", false];
|
||||||
|
_self set ["topBarReady", false];
|
||||||
|
|
||||||
|
private _screen = _self call ["getScreen", []];
|
||||||
|
_screen call ["dispose", []];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["handleTopBarReady", compileFinal {
|
||||||
|
_self set ["topBarReady", true];
|
||||||
|
_self call ["refreshTopBarState", []]
|
||||||
|
}],
|
||||||
|
["handleDispatcherReady", compileFinal {
|
||||||
|
_self set ["dispatcherReady", true];
|
||||||
|
_self call ["refreshDispatcher", []]
|
||||||
|
}],
|
||||||
|
["requestHydrate", compileFinal {
|
||||||
|
[SRPC(cad,requestHydrateCad), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestAssignTask", compileFinal {
|
||||||
|
params [["_taskID", "", [""]], ["_groupID", "", [""]], ["_note", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "" || { _groupID isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestAssignCadTask), [getPlayerUID player, _taskID, _groupID, _note]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestCreateDispatchOrder", compileFinal {
|
||||||
|
params [
|
||||||
|
["_assigneeGroupID", "", [""]],
|
||||||
|
["_targetGroupID", "", [""]],
|
||||||
|
["_note", "", [""]],
|
||||||
|
["_priority", "priority", [""]],
|
||||||
|
["_request", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestCreateCadDispatchOrder), [getPlayerUID player, _assigneeGroupID, _targetGroupID, _note, _priority, _request]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestSubmitSupportRequest", compileFinal {
|
||||||
|
params [
|
||||||
|
["_type", "", [""]],
|
||||||
|
["_fields", createHashMap, [createHashMap]],
|
||||||
|
["_priority", "priority", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_type isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestSubmitCadSupportRequest), [getPlayerUID player, _type, _fields, _priority]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestCloseDispatchOrder", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestCloseCadDispatchOrder), [getPlayerUID player, _taskID]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestCloseSupportRequest", compileFinal {
|
||||||
|
params [["_requestID", "", [""]]];
|
||||||
|
|
||||||
|
if (_requestID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestCloseCadSupportRequest), [getPlayerUID player, _requestID]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestAcknowledgeTask", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestAcknowledgeCadTask), [getPlayerUID player, _taskID]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestDeclineTask", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestDeclineCadTask), [getPlayerUID player, _taskID]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestGroupStatus", compileFinal {
|
||||||
|
params [["_groupID", "", [""]], ["_status", "", [""]]];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestUpdateCadGroupStatus), [getPlayerUID player, _groupID, _status]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestGroupRole", compileFinal {
|
||||||
|
params [["_groupID", "", [""]], ["_role", "", [""]]];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "" || { _role isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestUpdateCadGroupRole), [getPlayerUID player, _groupID, _role]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["requestGroupProfile", compileFinal {
|
||||||
|
params [["_groupID", "", [""]], ["_status", "", [""]], ["_role", "", [""]]];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "") exitWith { false };
|
||||||
|
if (_status isEqualTo "" && { _role isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[SRPC(cad,requestUpdateCadGroupProfile), [getPlayerUID player, _groupID, _status, _role]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["focusGroup", compileFinal {
|
||||||
|
params [["_groupID", "", [""]]];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "") exitWith { false };
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _groups = GVAR(CADRepository) getOrDefault ["groups", []];
|
||||||
|
private _groupIndex = _groups findIf { (_x getOrDefault ["groupId", ""]) isEqualTo _groupID };
|
||||||
|
if (_groupIndex < 0) exitWith { false };
|
||||||
|
|
||||||
|
private _group = _groups # _groupIndex;
|
||||||
|
private _position = _group getOrDefault ["position", []];
|
||||||
|
if !(_position isEqualType []) exitWith { false };
|
||||||
|
if ((count _position) < 2) exitWith { false };
|
||||||
|
|
||||||
|
private _mapCtrl = _self call ["getMapControl", []];
|
||||||
|
if (isNull _mapCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _targetPosition = [_position # 0, _position # 1, 0];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["focusTask", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _contracts = GVAR(CADRepository) getOrDefault ["contracts", []];
|
||||||
|
private _taskIndex = _contracts findIf {
|
||||||
|
private _entryTaskID = _x getOrDefault ["taskId", _x getOrDefault ["taskID", ""]];
|
||||||
|
_entryTaskID isEqualTo _taskID
|
||||||
|
};
|
||||||
|
if (_taskIndex < 0) exitWith { false };
|
||||||
|
|
||||||
|
private _task = _contracts # _taskIndex;
|
||||||
|
private _position = _task getOrDefault ["position", []];
|
||||||
|
if !(_position isEqualType []) exitWith { false };
|
||||||
|
if ((count _position) < 2) exitWith { false };
|
||||||
|
|
||||||
|
private _mapCtrl = _self call ["getMapControl", []];
|
||||||
|
if (isNull _mapCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _targetPosition = [_position # 0, _position # 1, 0];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["focusRequest", compileFinal {
|
||||||
|
params [["_requestID", "", [""]]];
|
||||||
|
|
||||||
|
if (_requestID isEqualTo "") exitWith { false };
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
private _requests = GVAR(CADRepository) getOrDefault ["requests", []];
|
||||||
|
private _requestIndex = _requests findIf { (_x getOrDefault ["requestId", ""]) isEqualTo _requestID };
|
||||||
|
if (_requestIndex < 0) exitWith { false };
|
||||||
|
|
||||||
|
private _request = _requests # _requestIndex;
|
||||||
|
private _position = _request getOrDefault ["position", []];
|
||||||
|
if !(_position isEqualType []) exitWith { false };
|
||||||
|
if ((count _position) < 2) exitWith { false };
|
||||||
|
|
||||||
|
private _mapCtrl = _self call ["getMapControl", []];
|
||||||
|
if (isNull _mapCtrl) exitWith { false };
|
||||||
|
|
||||||
|
private _targetPosition = [_position # 0, _position # 1, 0];
|
||||||
|
_mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition];
|
||||||
|
ctrlMapAnimCommit _mapCtrl;
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["refreshHydrate", compileFinal {
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
GVAR(CADRepository) call ["pushHydratePayload", [_self]]
|
||||||
|
}],
|
||||||
|
["handleHydrateResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
|
|
||||||
|
GVAR(CADRepository) call ["setHydratePayload", [_payload]];
|
||||||
|
if !(_self call ["isDispatcher", []]) then {
|
||||||
|
GVAR(CADRepository) call ["setMode", ["operations"]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["refreshHydrate", []];
|
||||||
|
_self call ["refreshTopBarState", []];
|
||||||
|
_self call ["refreshDispatcher", []];
|
||||||
|
_self call ["applyLayout", []]
|
||||||
|
}],
|
||||||
|
["handleAssignmentResponse", compileFinal {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_self getOrDefault ["dispatcherReady", false]) then {
|
||||||
|
private _dispatcherCtrl = _self call ["getDispatcherControl", []];
|
||||||
|
if !(isNull _dispatcherCtrl) then {
|
||||||
|
_dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [
|
||||||
|
"window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);",
|
||||||
|
str (_result getOrDefault ["message", "Task request processed."]),
|
||||||
|
str ([ "error", "success" ] select (_result getOrDefault ["success", false]))
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["sendEvent", ["cad::assignment::response", createHashMapFromArray [
|
||||||
|
["message", _result getOrDefault ["message", "Task request processed."]],
|
||||||
|
["success", _result getOrDefault ["success", false]]
|
||||||
|
]]]
|
||||||
|
}],
|
||||||
|
["handleGroupUpdateResponse", compileFinal {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_self getOrDefault ["dispatcherReady", false]) then {
|
||||||
|
private _dispatcherCtrl = _self call ["getDispatcherControl", []];
|
||||||
|
if !(isNull _dispatcherCtrl) then {
|
||||||
|
_dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [
|
||||||
|
"window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);",
|
||||||
|
str (_result getOrDefault ["message", "Group update processed."]),
|
||||||
|
str ([ "error", "success" ] select (_result getOrDefault ["success", false]))
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["sendEvent", ["cad::group::response", createHashMapFromArray [
|
||||||
|
["message", _result getOrDefault ["message", "Group update processed."]],
|
||||||
|
["success", _result getOrDefault ["success", false]]
|
||||||
|
]]]
|
||||||
|
}],
|
||||||
|
["handleRequestResponse", compileFinal {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_self getOrDefault ["dispatcherReady", false]) then {
|
||||||
|
private _dispatcherCtrl = _self call ["getDispatcherControl", []];
|
||||||
|
if !(isNull _dispatcherCtrl) then {
|
||||||
|
_dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [
|
||||||
|
"window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);",
|
||||||
|
str (_result getOrDefault ["message", "Request processed."]),
|
||||||
|
str (["error", "success"] select (_result getOrDefault ["success", false]))
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["sendEvent", ["cad::request::response", createHashMapFromArray [
|
||||||
|
["message", _result getOrDefault ["message", "Request processed."]],
|
||||||
|
["success", _result getOrDefault ["success", false]]
|
||||||
|
]]]
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(CADUIBridge) = createHashMapObject [GVAR(CADUIBridgeBaseClass)];
|
||||||
|
GVAR(CADUIBridge)
|
||||||
49
arma/client/addons/cad/functions/fnc_openUI.sqf
Normal file
49
arma/client/addons/cad/functions/fnc_openUI.sqf
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_openUI.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-28
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Opens the CAD map interface.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* UI opened [BOOL]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_cad_fnc_openUI
|
||||||
|
*/
|
||||||
|
|
||||||
|
private _display = createDialog ["RscMapUI", true];
|
||||||
|
if (isNull _display) exitWith {
|
||||||
|
diag_log "[FORGE:Client:CAD] ERROR: Failed to create CAD dialog.";
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
private _topBarCtrl = _display displayCtrl 1002;
|
||||||
|
private _bottomBarCtrl = _display displayCtrl 1003;
|
||||||
|
private _sidePanelCtrl = _display displayCtrl 1005;
|
||||||
|
private _dispatcherCtrl = _display displayCtrl 1006;
|
||||||
|
|
||||||
|
{
|
||||||
|
_x ctrlAddEventHandler ["JSDialog", {
|
||||||
|
params ["_control", "_isConfirmDialog", "_message"];
|
||||||
|
[_control, _isConfirmDialog, _message] call FUNC(handleUIEvents);
|
||||||
|
}];
|
||||||
|
} forEach [_topBarCtrl, _bottomBarCtrl, _sidePanelCtrl, _dispatcherCtrl];
|
||||||
|
|
||||||
|
_topBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\topbar.html)];
|
||||||
|
_bottomBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\bottombar.html)];
|
||||||
|
_sidePanelCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\sidepanel.html)];
|
||||||
|
_dispatcherCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\dispatcher.html)];
|
||||||
|
|
||||||
|
if !(isNil QGVAR(CADRepository)) then {
|
||||||
|
GVAR(CADRepository) call ["setOpen", [true]];
|
||||||
|
};
|
||||||
|
|
||||||
|
true
|
||||||
9
arma/client/addons/cad/script_component.hpp
Normal file
9
arma/client/addons/cad/script_component.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#define COMPONENT cad
|
||||||
|
#define COMPONENT_BEAUTIFIED CAD
|
||||||
|
#include "\forge\forge_client\addons\main\script_mod.hpp"
|
||||||
|
|
||||||
|
// #define DEBUG_MODE_FULL
|
||||||
|
// #define DISABLE_COMPILE_CACHE
|
||||||
|
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||||
|
|
||||||
|
#include "\forge\forge_client\addons\main\script_macros.hpp"
|
||||||
6
arma/client/addons/cad/ui/RscCommon.hpp
Normal file
6
arma/client/addons/cad/ui/RscCommon.hpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Control types
|
||||||
|
#define CT_STATIC 0
|
||||||
|
#define CT_MAP 100
|
||||||
|
|
||||||
|
class RscText;
|
||||||
|
class RscMapControl;
|
||||||
109
arma/client/addons/cad/ui/RscMapUI.hpp
Normal file
109
arma/client/addons/cad/ui/RscMapUI.hpp
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
class RscMapUI {
|
||||||
|
idd = 1004;
|
||||||
|
movingEnable = 0;
|
||||||
|
enableSimulation = 1;
|
||||||
|
fadein = 0;
|
||||||
|
fadeout = 0;
|
||||||
|
duration = 1e+011;
|
||||||
|
onLoad = "uiNamespace setVariable ['forge_client_cad_Display', _this select 0]; [_this select 0] call forge_client_cad_fnc_initUI;";
|
||||||
|
onUnLoad = "uiNamespace setVariable ['forge_client_cad_Display', nil]; uiNamespace setVariable ['forge_client_cad_MapCtrl', nil]; uiNamespace setVariable ['forge_client_cad_TopBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_BottomBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_SidePanelCtrl', nil]; uiNamespace setVariable ['forge_client_cad_DispatcherCtrl', nil]; if !(isNil 'forge_client_cad_CADRepository') then { forge_client_cad_CADRepository set ['isOpen', false]; };";
|
||||||
|
|
||||||
|
class controlsBackground {
|
||||||
|
class SurfaceBackground: RscText {
|
||||||
|
idc = -1;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1)";
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.1)";
|
||||||
|
w = "safeZoneW * 0.8";
|
||||||
|
h = "safeZoneH * 0.8";
|
||||||
|
colorBackground[] = {0.04, 0.06, 0.09, 0.96};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MapControl: RscMapControl {
|
||||||
|
idc = 1001;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1)"; // 10% margin (80% width centered)
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; // 10% margin + 56px visible top bar
|
||||||
|
w = "safeZoneW * 0.8"; // 80% width
|
||||||
|
h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // 80% height minus visible top and bottom bars
|
||||||
|
|
||||||
|
// Map specific settings
|
||||||
|
maxSatelliteAlpha = 0.85;
|
||||||
|
alphaFadeStartScale = 0.35;
|
||||||
|
alphaFadeEndScale = 0.4;
|
||||||
|
colorBackground[] = {0.969, 0.957, 0.949, 1};
|
||||||
|
colorSea[] = {0.467, 0.631, 0.851, 0.5};
|
||||||
|
colorForest[] = {0.624, 0.78, 0.388, 0.5};
|
||||||
|
colorRocks[] = {0, 0, 0, 0};
|
||||||
|
colorCountlines[] = {0.572, 0.354, 0.318, 0.25};
|
||||||
|
colorMainCountlines[] = {0.572, 0.354, 0.318, 0.5};
|
||||||
|
colorCountlinesWater[] = {0.491, 0.577, 0.702, 0.3};
|
||||||
|
colorMainCountlinesWater[] = {0.491, 0.577, 0.702, 0.6};
|
||||||
|
colorForestBorder[] = {0, 0, 0, 0};
|
||||||
|
colorRocksBorder[] = {0, 0, 0, 0};
|
||||||
|
colorPowerLines[] = {0.1, 0.1, 0.1, 1};
|
||||||
|
colorRailWay[] = {0.8, 0.2, 0, 1};
|
||||||
|
colorNames[] = {0.1, 0.1, 0.1, 0.9};
|
||||||
|
colorInactive[] = {1, 1, 1, 0.5};
|
||||||
|
colorLevels[] = {0.286, 0.177, 0.094, 0.5};
|
||||||
|
colorTracks[] = {0.84, 0.76, 0.65, 0.15};
|
||||||
|
colorRoads[] = {0.7, 0.7, 0.7, 1};
|
||||||
|
colorMainRoads[] = {0.9, 0.5, 0.3, 1};
|
||||||
|
colorTracksFill[] = {0.84, 0.76, 0.65, 1};
|
||||||
|
colorRoadsFill[] = {1, 1, 1, 1};
|
||||||
|
colorMainRoadsFill[] = {1, 0.6, 0.4, 1};
|
||||||
|
colorGrid[] = {0.1, 0.1, 0.1, 0.6};
|
||||||
|
colorGridMap[] = {0.1, 0.1, 0.1, 0.6};
|
||||||
|
colorText[] = {1, 1, 1, 1};
|
||||||
|
font = "PuristaMedium";
|
||||||
|
sizeEx = 0.04;
|
||||||
|
showCountourInterval = 0;
|
||||||
|
scaleMin = 0.001;
|
||||||
|
scaleMax = 1;
|
||||||
|
scaleDefault = 0.16;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class controls {
|
||||||
|
// Top bar browser
|
||||||
|
class TopBarBrowser: RscText {
|
||||||
|
type = 106;
|
||||||
|
idc = 1002;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1)";
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.1)";
|
||||||
|
w = "safeZoneW * 0.8";
|
||||||
|
h = "0.24076"; // 130px, allows dropdowns to open over the map
|
||||||
|
colorBackground[] = {0, 0, 0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bottom bar browser
|
||||||
|
class BottomBarBrowser: RscText {
|
||||||
|
type = 106;
|
||||||
|
idc = 1003;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1)";
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.9) - 0.0556";
|
||||||
|
w = "safeZoneW * 0.8";
|
||||||
|
h = "0.0556"; // 30px
|
||||||
|
colorBackground[] = {0, 0, 0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Side panel browser (overlays from right side of 80% box)
|
||||||
|
class SidePanelBrowser: RscText {
|
||||||
|
type = 106;
|
||||||
|
idc = 1005;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1) + (safeZoneW * 0.8) - 0.5550"; // Right edge of 80% box minus panel width
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; // Below visible top bar
|
||||||
|
w = "0.5550"; // Wider panel for four-tab operations layout
|
||||||
|
h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // Full height minus visible bars
|
||||||
|
colorBackground[] = {0, 0, 0, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
class DispatcherBrowser: RscText {
|
||||||
|
type = 106;
|
||||||
|
idc = 1006;
|
||||||
|
x = "safeZoneX + (safeZoneW * 0.1)";
|
||||||
|
y = "safeZoneY + (safeZoneH * 0.1) + 0.10372";
|
||||||
|
w = "safeZoneW * 0.8";
|
||||||
|
h = "(safeZoneH * 0.8) - 0.10372 - 0.0556";
|
||||||
|
colorBackground[] = {0, 0, 0, 0};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
1
arma/client/addons/cad/ui/_site/bottombar.html
Normal file
1
arma/client/addons/cad/ui/_site/bottombar.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html><head><meta charset="UTF-8"></head><body><span class="footer-brand">CAD Systems by IDS</span> <span class="footer-version">v1.0.0</span><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const o=document.createElement("style");o.textContent=e,document.head.appendChild(o)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,o)=>e.then(()=>o.endsWith(".css")?this.loadCSS(o):o.endsWith(".js")?this.loadJS(o):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.js"]).catch(e=>console.error("[BOTTOMBAR] Load error:",e))</script></body></html>
|
||||||
1
arma/client/addons/cad/ui/_site/cad-bottombar.css
Normal file
1
arma/client/addons/cad/ui/_site/cad-bottombar.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
body{-webkit-backdrop-filter:blur(18px);background:linear-gradient(90deg,#0e131bf5,#121720ed 55%,#0d1219f5);border-top:1px solid #ffffff24;justify-content:space-between;align-items:center;min-height:36px;padding:0 20px;display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden;box-shadow:0 -12px 26px #0000003d}.footer-brand,.footer-version{color:#f5f8ffcc;text-shadow:0 1px 10px #00000047;font-size:12px}.footer-brand{color:var(--accent);letter-spacing:.08em;text-transform:uppercase;font-weight:600}.footer-version{color:#f5f8ff9e}
|
||||||
1
arma/client/addons/cad/ui/_site/cad-bottombar.js
Normal file
1
arma/client/addons/cad/ui/_site/cad-bottombar.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
window.CADBottombar=window.CADBottombar||{init:()=>!0},window.CADBottombar.init();
|
||||||
1
arma/client/addons/cad/ui/_site/cad-common.css
Normal file
1
arma/client/addons/cad/ui/_site/cad-common.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
:root{--bg:#090c12d1;--panel:#141821e6;--panel2:#11151ed1;--stroke:#ffffff1f;--stroke2:#fff3;--text:#f5f8ffeb;--muted:#f5f8ff9e;--muted2:#f5f8ff6b;--accent:#68c4fff2;--danger:#ff6060f2;--shadow:0 20px 60px #0000008c;--radius:14px;--radius2:10px;--font:ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);color:var(--text);background:var(--bg);-webkit-backdrop-filter:blur(16px)}.btn{border-radius:var(--radius2);color:var(--text);cursor:pointer;user-select:none;background:#ffffff08;border:1px solid #ffffff1a;padding:8px 16px;font-size:14px;transition:background .16s,border-color .16s,transform .16s}.btn:hover{background:#ffffff12;border-color:#ffffff29}.btn:active{transform:scale(.98)}.btn-close{color:#ffdcdcf2;background:#ff60601a;border-color:#ff606040;font-weight:700}.btn-close:hover{background:#ff606033;border-color:#ff606059}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-thumb{background:#ffffff1a;border:2px solid #0000001a;border-radius:999px}
|
||||||
1
arma/client/addons/cad/ui/_site/cad-dispatcher.css
Normal file
1
arma/client/addons/cad/ui/_site/cad-dispatcher.css
Normal file
File diff suppressed because one or more lines are too long
1
arma/client/addons/cad/ui/_site/cad-dispatcher.js
Normal file
1
arma/client/addons/cad/ui/_site/cad-dispatcher.js
Normal file
File diff suppressed because one or more lines are too long
1
arma/client/addons/cad/ui/_site/cad-shared.js
Normal file
1
arma/client/addons/cad/ui/_site/cad-shared.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
window.mapUIState={layersPanelVisible:!0,sidePanelElement:null},window.mapUI={formatGridCoordinate:t=>Math.round(Number(t)||0).toString().padStart(4,"0"),formatPosition(t){const e=Array.isArray(t)?t:[0,0,0];return`X: ${this.formatGridCoordinate(e[0])} Y: ${this.formatGridCoordinate(e[1])}`},sendEvent(t,e){A3API.SendAlert(JSON.stringify({event:t,data:e}))},updateCoordinates(t,e){const n=document.getElementById("coordsDisplay");n&&(n.textContent=this.formatPosition([t,e,0]))},updateScale(t){const e=document.getElementById("scaleDisplay");e&&(e.textContent=`Scale: 1:${Math.round(t)}`)},updateStatus(t){const e=document.getElementById("statusText");e&&(e.textContent=t)}},window.updateCoordinates=window.mapUI.updateCoordinates,window.updateScale=window.mapUI.updateScale,window.updateStatus=window.mapUI.updateStatus,window.ForgeBridge=window.ForgeBridge||{_handlers:{},on(t,e){this._handlers[t]=this._handlers[t]||[],this._handlers[t].push(e)},ready:t=>(window.mapUI.sendEvent("cad::ready",t||{}),!0),receive(t){if(!t||"object"!=typeof t)return;(this._handlers[t.event]||[]).forEach(e=>e(t.data||{}))},send:(t,e)=>(window.mapUI.sendEvent(t,e||{}),!0),close:t=>(window.mapUI.sendEvent("map::close",t||{}),!0)};
|
||||||
1
arma/client/addons/cad/ui/_site/cad-sidepanel.css
Normal file
1
arma/client/addons/cad/ui/_site/cad-sidepanel.css
Normal file
File diff suppressed because one or more lines are too long
1
arma/client/addons/cad/ui/_site/cad-sidepanel.js
Normal file
1
arma/client/addons/cad/ui/_site/cad-sidepanel.js
Normal file
File diff suppressed because one or more lines are too long
1
arma/client/addons/cad/ui/_site/cad-topbar.css
Normal file
1
arma/client/addons/cad/ui/_site/cad-topbar.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
body{background:0 0;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;column-gap:16px;height:60px;padding:0 16px;display:grid;position:absolute;top:0;left:0;right:0;overflow:visible}body[data-mode=operations]{grid-template-columns:auto minmax(0,1fr) auto auto}body[data-mode=dispatch]{grid-template-columns:auto minmax(0,1fr) auto auto auto}body:before{content:"";height:60px;box-shadow:none;-webkit-backdrop-filter:blur(18px);z-index:0;pointer-events:none;background:linear-gradient(90deg,#10161ff5,#131a24f0 55%,#0f141cf5);border-bottom:none;position:absolute;inset:0 0 auto}body>*{z-index:1;position:relative}.logo{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;text-shadow:0 1px 12px #00000059;font-size:15px;font-weight:650}.header-main{align-items:center;gap:12px;min-width:0;display:flex}.title-block{flex-direction:column;flex:none;gap:1px;min-width:0;display:flex}.title-kicker{color:#dae3ec8f;text-transform:uppercase;letter-spacing:.12em;font-size:10px}.title-main{color:#f5f8ffeb;font-size:15px;font-weight:600}.operator-strip{flex:auto;align-items:center;gap:8px;min-width:0;display:flex}.operator-strip.is-hidden,.operator-controls.is-hidden{display:none}.operator-info{flex-direction:column;gap:0;min-width:88px;display:flex}.operator-label{color:#dae3ec80;text-transform:uppercase;letter-spacing:.12em;font-size:9px}.operator-info strong{color:#f5f8ffe6;font-size:12px;font-weight:550}.operator-controls{align-items:center;gap:6px;min-width:0;display:flex}.operator-select{min-width:92px;max-width:112px;color:var(--text);background:#0e141cf5;border:1px solid #ffffff24;padding:5px 8px;font-size:11px}.btn-operator{text-transform:uppercase;letter-spacing:.08em;min-width:84px;font-size:10px}.mode-controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-controls.is-hidden{display:none}.dispatch-view-controls{justify-self:end;align-items:center;gap:6px;display:flex}.dispatch-view-controls.is-hidden{display:none}.controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-text{color:#e9f1f8b8;text-transform:uppercase;letter-spacing:.1em;font-size:10px}.mode-switch{align-items:center;width:54px;height:28px;display:inline-flex;position:relative}.mode-switch input{opacity:0;pointer-events:none;position:absolute}.mode-slider{background:#161d27eb;border:1px solid #ffffff24;border-radius:999px;width:54px;height:28px;transition:border-color .16s,background .16s;position:relative;box-shadow:inset 0 1px 10px #00000038}.mode-slider:after{content:"";background:linear-gradient(#edf4fbfa,#bdcdddeb);border-radius:50%;width:20px;height:20px;transition:transform .16s,background .16s;position:absolute;top:3px;left:3px;box-shadow:0 4px 12px #00000042}.mode-switch input:checked+.mode-slider{background:#0e2538f2;border-color:#5bbbff6b}.mode-switch input:checked+.mode-slider:after{background:linear-gradient(#83d4fffa,#48aae7f0);transform:translate(26px)}.btn-close{min-width:42px}.btn-dispatch-view{text-transform:uppercase;letter-spacing:.08em;min-width:66px;padding:6px 10px;font-size:10px}.btn-icon{justify-content:center;align-items:center;width:34px;min-width:34px;height:30px;padding:0;font-size:16px;line-height:1;display:inline-flex}.btn-refresh{width:40px;min-width:40px;font-size:17px;font-weight:600}.btn-dispatch-view.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.btn-close{font-size:14px}body{pointer-events:none}body .logo,body .title-block,body .operator-strip,body .operator-controls,body .mode-controls,body .dispatch-view-controls,body .controls,body .mode-switch,body .mode-switch *,body button,body select,body label{pointer-events:auto}
|
||||||
1
arma/client/addons/cad/ui/_site/cad-topbar.js
Normal file
1
arma/client/addons/cad/ui/_site/cad-topbar.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
window.cadTopbar={mode:"operations",dispatchView:"board",currentGroup:null,session:{},init(){document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("modeToggle").addEventListener("change",e=>{window.mapUI.sendEvent("cad::mode::set",{mode:e.target.checked?"dispatch":"operations"})}),document.getElementById("dispatchRefreshBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatchBoardBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"board"})}),document.getElementById("dispatchMapBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"map"})}),document.getElementById("operatorRoleBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::role",{groupID:this.currentGroup.groupId||"",role:document.getElementById("operatorRoleSelect").value})}),document.getElementById("operatorStatusBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::status",{groupID:this.currentGroup.groupId||"",status:document.getElementById("operatorStatusSelect").value})}),window.mapUI.sendEvent("cad::topbar::ready",{})},formatLocation(e){const t=Array.isArray(e?.position)?e.position:[0,0,0];return window.mapUI.formatPosition(t)},receiveState(e){this.session=e&&e.session&&"object"==typeof e.session?e.session:{},this.mode=e&&"string"==typeof e.mode?e.mode:"operations",this.dispatchView=e&&"string"==typeof e.dispatchView?e.dispatchView:"board",this.currentGroup=e&&e.currentGroup&&"object"==typeof e.currentGroup?e.currentGroup:null;const t=document.getElementById("modeControls"),o=!!this.session.isDispatcher,s=!(!this.currentGroup||!this.session.isLeader&&!this.session.isDispatcher),n=document.getElementById("operatorStrip"),d=document.getElementById("operatorControls"),i=document.getElementById("dispatchViewControls"),r=document.getElementById("dispatchRefreshBtn"),a=document.getElementById("dispatchBoardBtn"),c=document.getElementById("dispatchMapBtn");t.classList.toggle("is-hidden",!o),i.classList.toggle("is-hidden",!o||"dispatch"!==this.mode),n.classList.toggle("is-hidden","operations"!==this.mode||!this.currentGroup),d.classList.toggle("is-hidden",!s),document.body.dataset.mode=this.mode,document.body.dataset.dispatcher=o?"true":"false",document.getElementById("modeToggle").checked="dispatch"===this.mode,a.classList.toggle("is-active","board"===this.dispatchView),c.classList.toggle("is-active","map"===this.dispatchView),r.title="dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD",r.setAttribute("aria-label","dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD"),document.getElementById("operatorGroupName").textContent=this.currentGroup?this.currentGroup.callsign||this.currentGroup.groupId||"Current Group":"No Group",document.getElementById("operatorLocation").textContent=this.currentGroup?this.formatLocation(this.currentGroup):"Unavailable",this.currentGroup&&(document.getElementById("operatorRoleSelect").value=this.currentGroup.role||"infantry",document.getElementById("operatorStatusSelect").value=this.currentGroup.status||"available")}},window.cadTopbar.init();
|
||||||
1
arma/client/addons/cad/ui/_site/dispatcher.html
Normal file
1
arma/client/addons/cad/ui/_site/dispatcher.html
Normal file
File diff suppressed because one or more lines are too long
1
arma/client/addons/cad/ui/_site/sidepanel.html
Normal file
1
arma/client/addons/cad/ui/_site/sidepanel.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="panel-header"><h3>CAD System</h3></div><div class="panel-content"><div id="cadStatusMessage" class="task-status-message"></div><div id="cadDangerAlert" class="cad-danger-alert is-hidden"></div><div id="cadRequestAlert" class="cad-warning-alert is-hidden"></div><div class="cad-tabs" role="tablist" aria-label="CAD Sections"><button id="tabContractsBtn" class="cad-tab is-active" type="button" data-tab="contracts">Contracts</button> <button id="tabRosterBtn" class="cad-tab" type="button" data-tab="roster">Roster</button> <button id="tabRequestsBtn" class="cad-tab" type="button" data-tab="requests">Requests</button> <button id="tabActivityBtn" class="cad-tab" type="button" data-tab="activity">Activity</button></div><div class="cad-tab-panels"><div id="contractsPanel" class="cad-section is-active" data-panel="contracts"><div class="cad-section-header">Contracts</div><div id="taskList" class="task-list"><div class="placeholder-message"><p>Loading contracts...</p></div></div></div><div id="rosterPanel" class="cad-section" data-panel="roster"><div class="cad-section-header">Roster</div><div id="rosterList" class="task-list"><div class="placeholder-message"><p>Loading roster...</p></div></div></div><div id="requestsPanel" class="cad-section" data-panel="requests"><div class="cad-section-header">Support Requests</div><div id="requestList" class="task-list"><div class="placeholder-message"><p>No support requests.</p></div></div></div><div id="activityPanel" class="cad-section" data-panel="activity"><div class="cad-section-header">Activity</div><div id="activityList" class="task-list"><div class="placeholder-message"><p>No recent activity.</p></div></div></div></div></div><div id="cadRequestModal" class="cad-modal is-hidden"><div class="cad-modal-backdrop"></div><div class="cad-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="cadRequestModalTitle"><div class="cad-modal-header"><div><div class="cad-section-header">Support Request</div><h3 id="cadRequestModalTitle">Submit Request</h3></div><button id="cadRequestModalCloseBtn" class="cad-icon-btn" type="button" aria-label="Close support request form">x</button></div><div class="cad-modal-body"><div class="cad-modal-fields"><label class="cad-field"><span>Priority</span> <select id="cadRequestPrioritySelect" class="cad-select"><option value="routine">routine</option><option value="priority" selected>priority</option><option value="emergency">emergency</option></select></label><div id="cadRequestFields" class="cad-modal-fields"></div></div></div><div class="cad-modal-actions"><button id="cadRequestModalSaveBtn" type="button" class="task-accept-btn">Submit Request</button></div></div></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const d=document.createElement("style");d.textContent=e,document.head.appendChild(d)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,d)=>e.then(()=>d.endsWith(".css")?this.loadCSS(d):d.endsWith(".js")?this.loadJS(d):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js"]).catch(e=>console.error("[SIDEPANEL] Load error:",e))</script></body></html>
|
||||||
1
arma/client/addons/cad/ui/_site/topbar.html
Normal file
1
arma/client/addons/cad/ui/_site/topbar.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="logo">FORGE OS</div><div class="header-main"><div class="title-block"><span class="title-kicker">Cad Systems</span> <strong class="title-main">FORGE Command & Dispatch</strong></div><div id="operatorStrip" class="operator-strip is-hidden"><div class="operator-info"><span class="operator-label">Current Group</span> <strong id="operatorGroupName">No Group</strong></div><div class="operator-info"><span class="operator-label">Location</span> <strong id="operatorLocation">Unavailable</strong></div><div id="operatorControls" class="operator-controls is-hidden"><select id="operatorRoleSelect" class="operator-select"><option value="infantry">infantry</option><option value="recon">recon</option><option value="armor">armor</option><option value="air">air</option><option value="logistics">logistics</option><option value="support">support</option></select> <button id="operatorRoleBtn" class="btn btn-operator" type="button">Update Role</button> <select id="operatorStatusSelect" class="operator-select"><option value="available">available</option><option value="en_route">en route</option><option value="on_task">on task</option><option value="holding">holding</option><option value="danger">danger</option><option value="unavailable">unavailable</option></select> <button id="operatorStatusBtn" class="btn btn-operator" type="button">Update Status</button></div></div></div><div id="modeControls" class="mode-controls is-hidden"><span class="mode-text">Ops</span> <label class="mode-switch" for="modeToggle"><input id="modeToggle" type="checkbox"> <span class="mode-slider"></span></label> <span class="mode-text">Dispatch</span></div><div id="dispatchViewControls" class="dispatch-view-controls is-hidden"><button id="dispatchBoardBtn" class="btn btn-dispatch-view is-active" type="button">Board</button> <button id="dispatchMapBtn" class="btn btn-dispatch-view" type="button">Map</button></div><div class="controls"><button id="dispatchRefreshBtn" class="btn btn-icon btn-refresh" type="button" aria-label="Refresh board" title="Refresh board">↻</button> <button id="btnClose" class="btn btn-icon btn-close">X</button></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const o=document.createElement("style");o.textContent=e,document.head.appendChild(o)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,o)=>e.then(()=>o.endsWith(".css")?this.loadCSS(o):o.endsWith(".js")?this.loadJS(o):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.js"]).catch(e=>console.error("[TOPBAR] Load error:",e))</script></body></html>
|
||||||
49
arma/client/addons/cad/ui/src/bottombar.html
Normal file
49
arma/client/addons/cad/ui/src/bottombar.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="footer-brand">CAD Systems by IDS</span>
|
||||||
|
<span class="footer-version">v1.0.0</span>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.MapLoader = {
|
||||||
|
loadCSS(path) {
|
||||||
|
return A3API.RequestFile(path).then((css) => {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadJS(path) {
|
||||||
|
return A3API.RequestFile(path).then((js) => {
|
||||||
|
eval(js);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadAll(resources) {
|
||||||
|
return resources.reduce((promise, resource) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
if (resource.endsWith(".css")) {
|
||||||
|
return this.loadCSS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.endsWith(".js")) {
|
||||||
|
return this.loadJS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
MapLoader.loadAll([
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-bottombar.js",
|
||||||
|
]).catch((err) => console.error("[BOTTOMBAR] Load error:", err));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
arma/client/addons/cad/ui/src/bottombar.js
Normal file
7
arma/client/addons/cad/ui/src/bottombar.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
window.CADBottombar = window.CADBottombar || {
|
||||||
|
init() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.CADBottombar.init();
|
||||||
372
arma/client/addons/cad/ui/src/dispatcher.html
Normal file
372
arma/client/addons/cad/ui/src/dispatcher.html
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="dispatch-shell">
|
||||||
|
<header class="dispatch-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Dispatch Dashboard</p>
|
||||||
|
<h2>Operational Board</h2>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="dispatcherStatusMessage" class="dispatch-status"></div>
|
||||||
|
<div
|
||||||
|
id="dispatcherDangerAlert"
|
||||||
|
class="dispatch-danger-alert is-hidden"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestAlert"
|
||||||
|
class="dispatch-warning-alert is-hidden"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<section class="dispatch-metrics">
|
||||||
|
<div class="metric-card">
|
||||||
|
<span class="metric-label">Open Contracts</span>
|
||||||
|
<strong id="metricOpenContracts">0</strong>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<span class="metric-label">Assigned Contracts</span>
|
||||||
|
<strong id="metricAssignedContracts">0</strong>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<span class="metric-label">Active Groups</span>
|
||||||
|
<strong id="metricActiveGroups">0</strong>
|
||||||
|
</div>
|
||||||
|
<div id="metricOpenRequestsCard" class="metric-card">
|
||||||
|
<span class="metric-label">Open Requests</span>
|
||||||
|
<strong id="metricOpenRequests">0</strong>
|
||||||
|
</div>
|
||||||
|
<div id="metricDangerGroupsCard" class="metric-card">
|
||||||
|
<span class="metric-label">Groups In Danger</span>
|
||||||
|
<strong id="metricDangerGroups">0</strong>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="dispatch-grid">
|
||||||
|
<section class="dispatch-panel dispatch-panel-open">
|
||||||
|
<div class="dispatch-panel-header">
|
||||||
|
<h3>Available Contracts</h3>
|
||||||
|
<button
|
||||||
|
id="dispatcherCreateOrderBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
aria-label="Create dispatch order"
|
||||||
|
title="Create dispatch order"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="dispatcherOpenContracts"
|
||||||
|
class="dispatch-list"
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dispatch-panel dispatch-panel-assigned">
|
||||||
|
<div class="dispatch-panel-header">
|
||||||
|
<h3>Assigned Contracts</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="dispatcherAssignedContracts"
|
||||||
|
class="dispatch-list"
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dispatch-panel dispatch-panel-groups">
|
||||||
|
<div class="dispatch-panel-header">
|
||||||
|
<h3>Group Board</h3>
|
||||||
|
</div>
|
||||||
|
<div id="dispatcherGroups" class="dispatch-list"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dispatch-panel dispatch-panel-activity">
|
||||||
|
<div class="dispatch-panel-header">
|
||||||
|
<h3>Requests & Activity</h3>
|
||||||
|
</div>
|
||||||
|
<div id="dispatcherActivity" class="dispatch-list"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dispatcherGroupModal" class="dispatch-modal is-hidden">
|
||||||
|
<div class="dispatch-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="dispatch-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="dispatcherGroupModalTitle"
|
||||||
|
>
|
||||||
|
<div class="dispatch-modal-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Group Editor</p>
|
||||||
|
<h3 id="dispatcherGroupModalTitle">Manage Group</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="dispatcherGroupModalCloseBtn"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close group editor"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-body">
|
||||||
|
<div class="dispatch-meta-grid">
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Callsign</span>
|
||||||
|
<strong id="dispatcherModalGroupCallsign"
|
||||||
|
>-</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Leader</span>
|
||||||
|
<strong id="dispatcherModalGroupLeader"
|
||||||
|
>-</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Current Task</span>
|
||||||
|
<strong id="dispatcherModalGroupTask"
|
||||||
|
>None</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Org</span>
|
||||||
|
<strong id="dispatcherModalGroupOrg"
|
||||||
|
>default</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-fields">
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Role</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherModalRoleSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Status</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherModalStatusSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-actions">
|
||||||
|
<button
|
||||||
|
id="dispatcherGroupModalSaveBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dispatcherOrderModal" class="dispatch-modal is-hidden">
|
||||||
|
<div class="dispatch-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="dispatch-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="dispatcherOrderModalTitle"
|
||||||
|
>
|
||||||
|
<div class="dispatch-modal-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Dispatch Order</p>
|
||||||
|
<h3 id="dispatcherOrderModalTitle">
|
||||||
|
Create Support Order
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="dispatcherOrderModalCloseBtn"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close dispatch order editor"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-body">
|
||||||
|
<div class="dispatch-modal-fields">
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Assignee Group</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderAssigneeSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Target Group</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderTargetSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Priority</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderPrioritySelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
>
|
||||||
|
<option value="routine">routine</option>
|
||||||
|
<option value="priority" selected>
|
||||||
|
priority
|
||||||
|
</option>
|
||||||
|
<option value="emergency">emergency</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Order Note</span>
|
||||||
|
<textarea
|
||||||
|
id="dispatcherOrderNoteInput"
|
||||||
|
class="dispatch-textarea"
|
||||||
|
rows="4"
|
||||||
|
placeholder="Optional order note for the assigned group."
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-actions">
|
||||||
|
<button
|
||||||
|
id="dispatcherOrderModalSaveBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
>
|
||||||
|
Create Order
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dispatcherRequestModal" class="dispatch-modal is-hidden">
|
||||||
|
<div class="dispatch-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="dispatch-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="dispatcherRequestModalTitle"
|
||||||
|
>
|
||||||
|
<div class="dispatch-modal-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Support Request</p>
|
||||||
|
<h3 id="dispatcherRequestModalTitle">
|
||||||
|
Request Details
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="dispatcherRequestModalCloseBtn"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close support request details"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-body">
|
||||||
|
<div class="dispatch-meta-grid">
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Title</span>
|
||||||
|
<strong id="dispatcherRequestTitle"
|
||||||
|
>Support Request</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Priority</span>
|
||||||
|
<strong id="dispatcherRequestPriority"
|
||||||
|
>priority</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Group</span>
|
||||||
|
<strong id="dispatcherRequestGroup"
|
||||||
|
>Unknown</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Type</span>
|
||||||
|
<strong id="dispatcherRequestType"
|
||||||
|
>request</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-field">
|
||||||
|
<span>Summary</span>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestSummary"
|
||||||
|
class="dispatch-detail-block"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-field">
|
||||||
|
<span>Submitted Fields</span>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestFields"
|
||||||
|
class="dispatch-detail-list"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-actions">
|
||||||
|
<button
|
||||||
|
id="dispatcherRequestConvertBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn dispatch-btn-secondary"
|
||||||
|
>
|
||||||
|
Convert to Order
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="dispatcherRequestModalDoneBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.MapLoader = {
|
||||||
|
loadCSS(path) {
|
||||||
|
return A3API.RequestFile(path).then((css) => {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadJS(path) {
|
||||||
|
return A3API.RequestFile(path).then((js) => {
|
||||||
|
eval(js);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadAll(resources) {
|
||||||
|
return resources.reduce((promise, resource) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
if (resource.endsWith(".css")) {
|
||||||
|
return this.loadCSS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.endsWith(".js")) {
|
||||||
|
return this.loadJS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
MapLoader.loadAll([
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.js",
|
||||||
|
]).catch((err) => console.error("[DISPATCHER] Load error:", err));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
120
arma/client/addons/cad/ui/src/dispatcher/formatters.js
Normal file
120
arma/client/addons/cad/ui/src/dispatcher/formatters.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
window.cadDispatcherFormatters = {
|
||||||
|
getDangerGroups() {
|
||||||
|
return this.groups.filter((group) => (group.status || "") === "danger");
|
||||||
|
},
|
||||||
|
getSupportAlertRequests() {
|
||||||
|
return this.requests.filter((request) =>
|
||||||
|
["medevac_9line", "fire_support", "air_support"].includes(
|
||||||
|
request.type || "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
buildSupportAlertMessage() {
|
||||||
|
const alertRequests = this.getSupportAlertRequests();
|
||||||
|
if (!alertRequests.length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = alertRequests.map((request) => {
|
||||||
|
const groupLabel =
|
||||||
|
request.groupCallsign || request.groupId || "Unknown Group";
|
||||||
|
const typeLabel = this.getRequestTypeLabel(
|
||||||
|
request.type || "request",
|
||||||
|
);
|
||||||
|
return `${groupLabel} ${typeLabel}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return `Support request alert: ${labels.join(", ")}`;
|
||||||
|
},
|
||||||
|
getSortedGroups() {
|
||||||
|
return this.groups.slice().sort((left, right) => {
|
||||||
|
const leftDanger = (left.status || "") === "danger" ? 0 : 1;
|
||||||
|
const rightDanger = (right.status || "") === "danger" ? 0 : 1;
|
||||||
|
|
||||||
|
if (leftDanger !== rightDanger) {
|
||||||
|
return leftDanger - rightDanger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftCallsign = left.callsign || left.groupId || "";
|
||||||
|
const rightCallsign = right.callsign || right.groupId || "";
|
||||||
|
return leftCallsign.localeCompare(rightCallsign);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isDispatchOrder(entry) {
|
||||||
|
return (
|
||||||
|
!!entry.isDispatchOrder || (entry.type || "") === "dispatch_order"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
formatTypeLabel(entry) {
|
||||||
|
const typeLabel = (entry.type || "task").replaceAll("_", " ");
|
||||||
|
return this.isDispatchOrder(entry) ? "dispatch order" : typeLabel;
|
||||||
|
},
|
||||||
|
getRequestTypeLabel(typeID) {
|
||||||
|
switch (typeID) {
|
||||||
|
case "medevac_9line":
|
||||||
|
return "9-Line MEDEVAC";
|
||||||
|
case "ace_lace":
|
||||||
|
return "ACE/LACE";
|
||||||
|
case "fire_support":
|
||||||
|
return "Fire Support";
|
||||||
|
case "air_support":
|
||||||
|
return "Air Support";
|
||||||
|
case "logreq":
|
||||||
|
return "LOGREQ";
|
||||||
|
default:
|
||||||
|
return (typeID || "request").replaceAll("_", " ");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildGroupOptions(selectedGroupID) {
|
||||||
|
return this.getSortedGroups()
|
||||||
|
.map((group) => {
|
||||||
|
const groupID = group.groupId || "";
|
||||||
|
return `<option value="${groupID}" ${groupID === selectedGroupID ? "selected" : ""}>${group.callsign || groupID}</option>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
|
formatRequestFieldLabel(fieldID) {
|
||||||
|
return (fieldID || "field")
|
||||||
|
.replaceAll("_", " ")
|
||||||
|
.replace(/\b\w/g, (character) => character.toUpperCase());
|
||||||
|
},
|
||||||
|
formatRequestFieldValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
return text || "Not provided";
|
||||||
|
},
|
||||||
|
buildRequestOrderNote(request) {
|
||||||
|
const typeLabel = this.getRequestTypeLabel(request.type || "request");
|
||||||
|
const groupLabel =
|
||||||
|
request.groupCallsign || request.groupId || "Unknown Group";
|
||||||
|
const summary = (request.summary || "").trim();
|
||||||
|
const fieldDetails =
|
||||||
|
request.fields && typeof request.fields === "object"
|
||||||
|
? Object.entries(request.fields)
|
||||||
|
.map(([fieldID, value]) => {
|
||||||
|
const fieldValue =
|
||||||
|
this.formatRequestFieldValue(value);
|
||||||
|
if (fieldValue === "Not provided") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.formatRequestFieldLabel(fieldID)} ${fieldValue}`;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const details = fieldDetails.length
|
||||||
|
? fieldDetails
|
||||||
|
: [summary].filter(Boolean);
|
||||||
|
|
||||||
|
return details.length
|
||||||
|
? `${typeLabel} requested by ${groupLabel}. ${details.join(" | ")}`
|
||||||
|
: `${typeLabel} requested by ${groupLabel}.`;
|
||||||
|
},
|
||||||
|
};
|
||||||
274
arma/client/addons/cad/ui/src/dispatcher/index.js
Normal file
274
arma/client/addons/cad/ui/src/dispatcher/index.js
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
const dispatcherFormatters = window.cadDispatcherFormatters || {};
|
||||||
|
const dispatcherModals = window.cadDispatcherModals || {};
|
||||||
|
const dispatcherRender = window.cadDispatcherRender || {};
|
||||||
|
|
||||||
|
window.cadDispatcher = {
|
||||||
|
contracts: [],
|
||||||
|
requests: [],
|
||||||
|
groups: [],
|
||||||
|
activity: [],
|
||||||
|
session: {},
|
||||||
|
editingGroupId: "",
|
||||||
|
viewingRequestId: "",
|
||||||
|
convertingRequestId: "",
|
||||||
|
statuses: [
|
||||||
|
"available",
|
||||||
|
"en_route",
|
||||||
|
"on_task",
|
||||||
|
"holding",
|
||||||
|
"danger",
|
||||||
|
"unavailable",
|
||||||
|
],
|
||||||
|
roles: ["infantry", "recon", "armor", "air", "logistics", "support"],
|
||||||
|
...dispatcherFormatters,
|
||||||
|
...dispatcherModals,
|
||||||
|
...dispatcherRender,
|
||||||
|
init() {
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherCreateOrderBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.openOrderModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherGroupModalCloseBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeGroupModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherGroupModalSaveBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.applyGroupUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector("#dispatcherGroupModal .dispatch-modal-backdrop")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeGroupModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherOrderModalCloseBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeOrderModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherOrderModalSaveBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.createDispatchOrder();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector("#dispatcherOrderModal .dispatch-modal-backdrop")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeOrderModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModalCloseBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeRequestModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModalDoneBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeRequestModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestConvertBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.convertViewedRequestToOrder();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector("#dispatcherRequestModal .dispatch-modal-backdrop")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.closeRequestModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.mapUI.sendEvent("cad::dispatcher::ready", {});
|
||||||
|
},
|
||||||
|
receiveHydrate(payload) {
|
||||||
|
this.contracts = Array.isArray(payload.contracts)
|
||||||
|
? payload.contracts
|
||||||
|
: [];
|
||||||
|
this.requests = Array.isArray(payload.requests) ? payload.requests : [];
|
||||||
|
this.groups = Array.isArray(payload.groups) ? payload.groups : [];
|
||||||
|
this.activity = Array.isArray(payload.activity) ? payload.activity : [];
|
||||||
|
this.session =
|
||||||
|
payload.session && typeof payload.session === "object"
|
||||||
|
? payload.session
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const statusEl = document.getElementById("dispatcherStatusMessage");
|
||||||
|
if (
|
||||||
|
statusEl &&
|
||||||
|
(!statusEl.dataset.type || statusEl.dataset.type === "info")
|
||||||
|
) {
|
||||||
|
this.setStatus("", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncOpenModal();
|
||||||
|
this.syncOrderModal();
|
||||||
|
this.syncRequestModal();
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
setStatus(message, type) {
|
||||||
|
const statusEl = document.getElementById("dispatcherStatusMessage");
|
||||||
|
if (!statusEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusEl.textContent = message || "";
|
||||||
|
statusEl.dataset.type = type || "";
|
||||||
|
},
|
||||||
|
createDispatchOrder() {
|
||||||
|
const assigneeGroupID = document.getElementById(
|
||||||
|
"dispatcherOrderAssigneeSelect",
|
||||||
|
).value;
|
||||||
|
const targetGroupID = document.getElementById(
|
||||||
|
"dispatcherOrderTargetSelect",
|
||||||
|
).value;
|
||||||
|
const priority = document.getElementById(
|
||||||
|
"dispatcherOrderPrioritySelect",
|
||||||
|
).value;
|
||||||
|
const note = document.getElementById("dispatcherOrderNoteInput").value;
|
||||||
|
const sourceRequest = this.convertingRequestId
|
||||||
|
? this.requests.find(
|
||||||
|
(entry) =>
|
||||||
|
(entry.requestId || "") === this.convertingRequestId,
|
||||||
|
) || null
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!assigneeGroupID || !targetGroupID) {
|
||||||
|
this.setStatus(
|
||||||
|
"Select both an assignee and a target group.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assigneeGroupID === targetGroupID) {
|
||||||
|
this.setStatus(
|
||||||
|
"Assignee and target groups must be different.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus(
|
||||||
|
this.convertingRequestId
|
||||||
|
? "Creating dispatch order from request..."
|
||||||
|
: "Creating dispatch order...",
|
||||||
|
"info",
|
||||||
|
);
|
||||||
|
window.mapUI.sendEvent("cad::dispatchOrder::create", {
|
||||||
|
assigneeGroupID: assigneeGroupID,
|
||||||
|
targetGroupID: targetGroupID,
|
||||||
|
note: note.trim(),
|
||||||
|
priority: priority,
|
||||||
|
request: sourceRequest
|
||||||
|
? {
|
||||||
|
requestId: sourceRequest.requestId || "",
|
||||||
|
type: sourceRequest.type || "",
|
||||||
|
title: sourceRequest.title || "",
|
||||||
|
summary: sourceRequest.summary || "",
|
||||||
|
fields:
|
||||||
|
sourceRequest.fields &&
|
||||||
|
typeof sourceRequest.fields === "object"
|
||||||
|
? sourceRequest.fields
|
||||||
|
: {},
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeOrderModal();
|
||||||
|
},
|
||||||
|
assignTask(taskID) {
|
||||||
|
const selector = document.getElementById(
|
||||||
|
`dispatcher-assign-group-${taskID}`,
|
||||||
|
);
|
||||||
|
if (!selector || !selector.value) {
|
||||||
|
this.setStatus(
|
||||||
|
"Select a group before assigning a contract.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Submitting assignment...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::tasks::assign", {
|
||||||
|
taskID: taskID,
|
||||||
|
groupID: selector.value,
|
||||||
|
note: "",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
applyGroupUpdates() {
|
||||||
|
if (!this.editingGroupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = this.groups.find(
|
||||||
|
(entry) => entry.groupId === this.editingGroupId,
|
||||||
|
);
|
||||||
|
if (!group) {
|
||||||
|
this.closeGroupModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleValue = document.getElementById(
|
||||||
|
"dispatcherModalRoleSelect",
|
||||||
|
).value;
|
||||||
|
const statusValue = document.getElementById(
|
||||||
|
"dispatcherModalStatusSelect",
|
||||||
|
).value;
|
||||||
|
const nextRole =
|
||||||
|
roleValue && roleValue !== (group.role || "") ? roleValue : "";
|
||||||
|
const nextStatus =
|
||||||
|
statusValue && statusValue !== (group.status || "")
|
||||||
|
? statusValue
|
||||||
|
: "";
|
||||||
|
const hasChanges = nextRole || nextStatus;
|
||||||
|
|
||||||
|
if (!hasChanges) {
|
||||||
|
this.setStatus("No group changes to save.", "info");
|
||||||
|
this.closeGroupModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Updating group profile...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::groups::profile", {
|
||||||
|
groupID: this.editingGroupId,
|
||||||
|
role: nextRole,
|
||||||
|
status: nextStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeGroupModal();
|
||||||
|
},
|
||||||
|
closeDispatchOrder(taskID) {
|
||||||
|
if (!taskID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Closing dispatch order...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::dispatchOrder::close", {
|
||||||
|
taskID: taskID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
closeSupportRequest(requestID) {
|
||||||
|
if (!requestID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Closing support request...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::supportRequest::close", {
|
||||||
|
requestID: requestID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.cadDispatcher.init();
|
||||||
269
arma/client/addons/cad/ui/src/dispatcher/modals.js
Normal file
269
arma/client/addons/cad/ui/src/dispatcher/modals.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
window.cadDispatcherModals = {
|
||||||
|
openOrderModal() {
|
||||||
|
this.convertingRequestId = "";
|
||||||
|
this.populateOrderModal();
|
||||||
|
document.getElementById("dispatcherOrderModalTitle").textContent =
|
||||||
|
"Create Support Order";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherOrderModal")
|
||||||
|
.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
closeOrderModal() {
|
||||||
|
this.convertingRequestId = "";
|
||||||
|
document.getElementById("dispatcherOrderNoteInput").value = "";
|
||||||
|
document.getElementById("dispatcherOrderPrioritySelect").value =
|
||||||
|
"priority";
|
||||||
|
document.getElementById("dispatcherOrderModalTitle").textContent =
|
||||||
|
"Create Support Order";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherOrderModal")
|
||||||
|
.classList.add("is-hidden");
|
||||||
|
},
|
||||||
|
openRequestModal(requestID) {
|
||||||
|
const request = this.requests.find(
|
||||||
|
(entry) => entry.requestId === requestID,
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewingRequestId = requestID;
|
||||||
|
this.populateRequestModal(request);
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModal")
|
||||||
|
.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
closeRequestModal() {
|
||||||
|
this.viewingRequestId = "";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModal")
|
||||||
|
.classList.add("is-hidden");
|
||||||
|
},
|
||||||
|
syncRequestModal() {
|
||||||
|
if (!this.viewingRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = this.requests.find(
|
||||||
|
(entry) => entry.requestId === this.viewingRequestId,
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
this.closeRequestModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.populateRequestModal(request);
|
||||||
|
},
|
||||||
|
populateRequestModal(request) {
|
||||||
|
const fields =
|
||||||
|
request.fields && typeof request.fields === "object"
|
||||||
|
? Object.entries(request.fields)
|
||||||
|
: [];
|
||||||
|
const fieldsHTML = fields.length
|
||||||
|
? fields
|
||||||
|
.map(
|
||||||
|
([fieldID, value]) => `
|
||||||
|
<div class="dispatch-detail-row">
|
||||||
|
<span class="dispatch-detail-label">${this.formatRequestFieldLabel(fieldID)}</span>
|
||||||
|
<span class="dispatch-detail-value">${this.formatRequestFieldValue(value)}</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No submitted fields.</p></div>';
|
||||||
|
|
||||||
|
document.getElementById("dispatcherRequestTitle").textContent =
|
||||||
|
request.title || request.requestId || "Support Request";
|
||||||
|
document.getElementById("dispatcherRequestPriority").textContent = (
|
||||||
|
request.priority || "priority"
|
||||||
|
).replaceAll("_", " ");
|
||||||
|
document.getElementById("dispatcherRequestGroup").textContent =
|
||||||
|
request.groupCallsign || request.groupId || "Unknown";
|
||||||
|
document.getElementById("dispatcherRequestType").textContent =
|
||||||
|
this.getRequestTypeLabel(request.type || "request");
|
||||||
|
document.getElementById("dispatcherRequestSummary").textContent =
|
||||||
|
request.summary || "No summary provided.";
|
||||||
|
document.getElementById("dispatcherRequestFields").innerHTML =
|
||||||
|
fieldsHTML;
|
||||||
|
},
|
||||||
|
convertRequestToOrder(requestID) {
|
||||||
|
const request = this.requests.find(
|
||||||
|
(entry) => (entry.requestId || "") === requestID,
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
this.setStatus("Selected request is no longer available.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetGroupID = request.groupId || "";
|
||||||
|
if (!targetGroupID) {
|
||||||
|
this.setStatus(
|
||||||
|
"Selected request has no owning group to target.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetGroup = this.groups.find(
|
||||||
|
(group) => (group.groupId || "") === targetGroupID,
|
||||||
|
);
|
||||||
|
if (!targetGroup) {
|
||||||
|
this.setStatus(
|
||||||
|
"Selected request group is no longer available.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.convertingRequestId = requestID;
|
||||||
|
this.populateOrderModal({
|
||||||
|
selectedAssigneeID:
|
||||||
|
this.getSortedGroups().find(
|
||||||
|
(group) => (group.groupId || "") !== targetGroupID,
|
||||||
|
)?.groupId || "",
|
||||||
|
selectedTargetID: targetGroupID,
|
||||||
|
note: this.buildRequestOrderNote(request),
|
||||||
|
priority: request.priority || "priority",
|
||||||
|
});
|
||||||
|
document.getElementById("dispatcherOrderModalTitle").textContent =
|
||||||
|
"Create Order From Request";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherOrderModal")
|
||||||
|
.classList.remove("is-hidden");
|
||||||
|
this.setStatus("Preparing dispatch order from request...", "info");
|
||||||
|
},
|
||||||
|
convertViewedRequestToOrder() {
|
||||||
|
if (!this.viewingRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestID = this.viewingRequestId;
|
||||||
|
this.closeRequestModal();
|
||||||
|
this.convertRequestToOrder(requestID);
|
||||||
|
},
|
||||||
|
populateOrderModal(options = {}) {
|
||||||
|
const sortedGroups = this.getSortedGroups();
|
||||||
|
const assigneeSelect = document.getElementById(
|
||||||
|
"dispatcherOrderAssigneeSelect",
|
||||||
|
);
|
||||||
|
const targetSelect = document.getElementById(
|
||||||
|
"dispatcherOrderTargetSelect",
|
||||||
|
);
|
||||||
|
const noteInput = document.getElementById("dispatcherOrderNoteInput");
|
||||||
|
const prioritySelect = document.getElementById(
|
||||||
|
"dispatcherOrderPrioritySelect",
|
||||||
|
);
|
||||||
|
if (!assigneeSelect || !targetSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedAssigneeID = options.selectedAssigneeID || "";
|
||||||
|
const selectedTargetID = options.selectedTargetID || "";
|
||||||
|
const fallbackAssignee =
|
||||||
|
selectedAssigneeID ||
|
||||||
|
sortedGroups.find(
|
||||||
|
(group) => (group.groupId || "") !== selectedTargetID,
|
||||||
|
)?.groupId ||
|
||||||
|
sortedGroups[0]?.groupId ||
|
||||||
|
"";
|
||||||
|
const fallbackTarget =
|
||||||
|
selectedTargetID ||
|
||||||
|
sortedGroups.find(
|
||||||
|
(group) => (group.groupId || "") !== fallbackAssignee,
|
||||||
|
)?.groupId ||
|
||||||
|
sortedGroups[0]?.groupId ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
assigneeSelect.innerHTML = this.buildGroupOptions(fallbackAssignee);
|
||||||
|
targetSelect.innerHTML = this.buildGroupOptions(fallbackTarget);
|
||||||
|
if (noteInput) {
|
||||||
|
noteInput.value = options.note || "";
|
||||||
|
}
|
||||||
|
if (prioritySelect) {
|
||||||
|
prioritySelect.value = options.priority || "priority";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncOrderModal() {
|
||||||
|
const modalEl = document.getElementById("dispatcherOrderModal");
|
||||||
|
if (!modalEl || modalEl.classList.contains("is-hidden")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.populateOrderModal({
|
||||||
|
selectedAssigneeID:
|
||||||
|
document.getElementById("dispatcherOrderAssigneeSelect")
|
||||||
|
?.value || "",
|
||||||
|
selectedTargetID:
|
||||||
|
document.getElementById("dispatcherOrderTargetSelect")?.value ||
|
||||||
|
"",
|
||||||
|
note:
|
||||||
|
document.getElementById("dispatcherOrderNoteInput")?.value ||
|
||||||
|
"",
|
||||||
|
priority:
|
||||||
|
document.getElementById("dispatcherOrderPrioritySelect")
|
||||||
|
?.value || "priority",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openGroupModal(groupID) {
|
||||||
|
const group = this.groups.find((entry) => entry.groupId === groupID);
|
||||||
|
if (!group) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editingGroupId = groupID;
|
||||||
|
document.getElementById("dispatcherModalGroupCallsign").textContent =
|
||||||
|
group.callsign || group.groupId || "Unknown";
|
||||||
|
document.getElementById("dispatcherModalGroupLeader").textContent =
|
||||||
|
group.leaderName || "Unknown";
|
||||||
|
document.getElementById("dispatcherModalGroupTask").textContent =
|
||||||
|
group.currentTaskId || "None";
|
||||||
|
document.getElementById("dispatcherModalGroupOrg").textContent =
|
||||||
|
group.orgId || "default";
|
||||||
|
document.getElementById("dispatcherModalRoleSelect").innerHTML =
|
||||||
|
this.roles
|
||||||
|
.map(
|
||||||
|
(role) =>
|
||||||
|
`<option value="${role}" ${role === group.role ? "selected" : ""}>${role.replaceAll("_", " ")}</option>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
document.getElementById("dispatcherModalStatusSelect").innerHTML =
|
||||||
|
this.statuses
|
||||||
|
.map(
|
||||||
|
(status) =>
|
||||||
|
`<option value="${status}" ${status === group.status ? "selected" : ""}>${status.replaceAll("_", " ")}</option>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherGroupModal")
|
||||||
|
.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
closeGroupModal() {
|
||||||
|
this.editingGroupId = "";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherGroupModal")
|
||||||
|
.classList.add("is-hidden");
|
||||||
|
},
|
||||||
|
syncOpenModal() {
|
||||||
|
if (!this.editingGroupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = this.groups.find(
|
||||||
|
(entry) => entry.groupId === this.editingGroupId,
|
||||||
|
);
|
||||||
|
if (!group) {
|
||||||
|
this.closeGroupModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("dispatcherModalGroupCallsign").textContent =
|
||||||
|
group.callsign || group.groupId || "Unknown";
|
||||||
|
document.getElementById("dispatcherModalGroupLeader").textContent =
|
||||||
|
group.leaderName || "Unknown";
|
||||||
|
document.getElementById("dispatcherModalGroupTask").textContent =
|
||||||
|
group.currentTaskId || "None";
|
||||||
|
document.getElementById("dispatcherModalGroupOrg").textContent =
|
||||||
|
group.orgId || "default";
|
||||||
|
},
|
||||||
|
};
|
||||||
325
arma/client/addons/cad/ui/src/dispatcher/render.js
Normal file
325
arma/client/addons/cad/ui/src/dispatcher/render.js
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
window.cadDispatcherRender = {
|
||||||
|
updateDangerAlert() {
|
||||||
|
const alertEl = document.getElementById("dispatcherDangerAlert");
|
||||||
|
if (!alertEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dangerGroups = this.getDangerGroups();
|
||||||
|
if (!dangerGroups.length) {
|
||||||
|
alertEl.textContent = "";
|
||||||
|
alertEl.classList.add("is-hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callsigns = dangerGroups.map(
|
||||||
|
(group) => group.callsign || group.groupId || "Unknown Group",
|
||||||
|
);
|
||||||
|
alertEl.textContent = `Danger alert active: ${callsigns.join(", ")}`;
|
||||||
|
alertEl.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
updateRequestAlert() {
|
||||||
|
const alertEl = document.getElementById("dispatcherRequestAlert");
|
||||||
|
if (!alertEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertMessage = this.buildSupportAlertMessage();
|
||||||
|
if (!alertMessage) {
|
||||||
|
alertEl.textContent = "";
|
||||||
|
alertEl.classList.add("is-hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alertEl.textContent = alertMessage;
|
||||||
|
alertEl.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
buildGroupEditorButton(groupID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
onclick="window.cadDispatcher.openGroupModal('${groupID}')"
|
||||||
|
aria-label="Edit group"
|
||||||
|
title="Edit group"
|
||||||
|
>
|
||||||
|
⚙
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
buildCloseOrderButton(taskID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn dispatch-btn-secondary"
|
||||||
|
onclick="window.cadDispatcher.closeDispatchOrder('${taskID}')"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
buildCloseRequestButton(requestID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn dispatch-btn-secondary"
|
||||||
|
onclick="event.stopPropagation(); window.cadDispatcher.closeSupportRequest('${requestID}')"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
buildConvertRequestButton(requestID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
onclick="event.stopPropagation(); window.cadDispatcher.convertRequestToOrder('${requestID}')"
|
||||||
|
>
|
||||||
|
Convert to Order
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
renderMetrics() {
|
||||||
|
const assignedContracts = this.contracts.filter(
|
||||||
|
(entry) => (entry.assignmentState || "unassigned") !== "unassigned",
|
||||||
|
);
|
||||||
|
const openContracts = this.contracts.filter(
|
||||||
|
(entry) => (entry.assignmentState || "unassigned") === "unassigned",
|
||||||
|
);
|
||||||
|
const openRequests = this.requests.length;
|
||||||
|
const supportAlertRequests = this.getSupportAlertRequests();
|
||||||
|
const dangerGroups = this.groups.filter(
|
||||||
|
(group) => (group.status || "") === "danger",
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById("metricOpenContracts").textContent =
|
||||||
|
openContracts.length;
|
||||||
|
document.getElementById("metricAssignedContracts").textContent =
|
||||||
|
assignedContracts.length;
|
||||||
|
document.getElementById("metricActiveGroups").textContent =
|
||||||
|
this.groups.length;
|
||||||
|
document.getElementById("metricOpenRequests").textContent =
|
||||||
|
openRequests;
|
||||||
|
document.getElementById("metricDangerGroups").textContent =
|
||||||
|
dangerGroups.length;
|
||||||
|
|
||||||
|
const dangerMetricCard = document.getElementById(
|
||||||
|
"metricDangerGroupsCard",
|
||||||
|
);
|
||||||
|
if (dangerMetricCard) {
|
||||||
|
dangerMetricCard.classList.toggle(
|
||||||
|
"is-danger",
|
||||||
|
dangerGroups.length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestMetricCard = document.getElementById(
|
||||||
|
"metricOpenRequestsCard",
|
||||||
|
);
|
||||||
|
if (requestMetricCard) {
|
||||||
|
requestMetricCard.classList.toggle(
|
||||||
|
"is-warning",
|
||||||
|
supportAlertRequests.length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderOpenContracts() {
|
||||||
|
const container = document.getElementById("dispatcherOpenContracts");
|
||||||
|
const openContracts = this.contracts.filter(
|
||||||
|
(entry) => (entry.assignmentState || "unassigned") === "unassigned",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!openContracts.length) {
|
||||||
|
container.innerHTML =
|
||||||
|
'<div class="placeholder-message"><p>No open contracts.</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupOptions = this.buildGroupOptions("");
|
||||||
|
|
||||||
|
container.innerHTML = openContracts
|
||||||
|
.map((task) => {
|
||||||
|
const taskId = task.taskId || task.taskID || "";
|
||||||
|
const position = Array.isArray(task.position)
|
||||||
|
? task.position
|
||||||
|
: [0, 0, 0];
|
||||||
|
const targetGroup = this.groups.find(
|
||||||
|
(group) => group.groupId === (task.targetGroupId || ""),
|
||||||
|
);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="dispatch-card">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<strong>${task.title || taskId}</strong>
|
||||||
|
<span class="dispatch-badge">${this.formatTypeLabel(task)}</span>
|
||||||
|
</header>
|
||||||
|
<p class="dispatch-description">${task.description || ""}</p>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Unassigned</span>
|
||||||
|
<span>${window.mapUI.formatPosition(position)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Target: ${targetGroup ? targetGroup.callsign : task.targetGroupCallsign || "None"}</span>
|
||||||
|
<span>Priority: ${(task.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-actions">
|
||||||
|
<select id="dispatcher-assign-group-${taskId}" class="dispatch-select">
|
||||||
|
<option value="">Assign to group</option>
|
||||||
|
${groupOptions}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="dispatch-btn" onclick="window.cadDispatcher.assignTask('${taskId}')">Assign</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
|
renderAssignedContracts() {
|
||||||
|
const container = document.getElementById(
|
||||||
|
"dispatcherAssignedContracts",
|
||||||
|
);
|
||||||
|
const assignedContracts = this.contracts.filter(
|
||||||
|
(entry) => (entry.assignmentState || "unassigned") !== "unassigned",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!assignedContracts.length) {
|
||||||
|
container.innerHTML =
|
||||||
|
'<div class="placeholder-message"><p>No assigned contracts.</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = assignedContracts
|
||||||
|
.map((task) => {
|
||||||
|
const taskId = task.taskId || task.taskID || "";
|
||||||
|
const assignedGroup = this.groups.find(
|
||||||
|
(group) => group.groupId === (task.assignedGroupId || ""),
|
||||||
|
);
|
||||||
|
const targetGroup = this.groups.find(
|
||||||
|
(group) => group.groupId === (task.targetGroupId || ""),
|
||||||
|
);
|
||||||
|
const isDispatchOrder = this.isDispatchOrder(task);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="dispatch-card">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<strong>${task.title || taskId}</strong>
|
||||||
|
<span class="dispatch-badge">${task.assignmentState || "assigned"}</span>
|
||||||
|
</header>
|
||||||
|
<p class="dispatch-description">${task.description || ""}</p>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Group: ${assignedGroup ? assignedGroup.callsign : task.assignedGroupId || "Unknown"}</span>
|
||||||
|
<span>Type: ${this.formatTypeLabel(task)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Target: ${targetGroup ? targetGroup.callsign : task.targetGroupCallsign || "None"}</span>
|
||||||
|
<span>Priority: ${(task.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
|
</div>
|
||||||
|
${isDispatchOrder ? `<div class="dispatch-actions dispatch-actions-split">${this.buildCloseOrderButton(taskId)}</div>` : ""}
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
|
renderGroups() {
|
||||||
|
const container = document.getElementById("dispatcherGroups");
|
||||||
|
if (!this.groups.length) {
|
||||||
|
container.innerHTML =
|
||||||
|
'<div class="placeholder-message"><p>No active groups available.</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = this.getSortedGroups()
|
||||||
|
.map((group) => {
|
||||||
|
const isDanger = (group.status || "") === "danger";
|
||||||
|
return `
|
||||||
|
<article class="dispatch-card dispatch-card-group ${isDanger ? "is-danger" : ""}">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<div class="dispatch-card-header-main">
|
||||||
|
<strong>${group.callsign || group.groupId}</strong>
|
||||||
|
<span class="dispatch-badge">${group.role || "group"}</span>
|
||||||
|
${isDanger ? '<span class="dispatch-alert-badge">Danger</span>' : ""}
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-card-header-actions">
|
||||||
|
${this.buildGroupEditorButton(group.groupId)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Leader: ${group.leaderName || "Unknown"}</span>
|
||||||
|
<span>Status: ${group.status || "unknown"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Org: ${group.orgId || "default"}</span>
|
||||||
|
<span>Task: ${group.currentTaskId || "None"}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
|
renderActivity() {
|
||||||
|
const container = document.getElementById("dispatcherActivity");
|
||||||
|
const requestsHTML = this.requests.length
|
||||||
|
? this.requests
|
||||||
|
.map(
|
||||||
|
(request) => `
|
||||||
|
<article class="dispatch-card dispatch-card-interactive ${["medevac_9line", "fire_support", "air_support"].includes(request.type || "") ? "is-warning" : ""}" onclick="window.cadDispatcher.openRequestModal('${request.requestId || ""}')">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<strong>${request.title || request.requestId || "Support Request"}</strong>
|
||||||
|
<span class="dispatch-badge">${(request.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
|
</header>
|
||||||
|
<p class="dispatch-description">${request.summary || ""}</p>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Group: ${request.groupCallsign || request.groupId || "Unknown"}</span>
|
||||||
|
<span>${this.getRequestTypeLabel(request.type || "request")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-actions dispatch-actions-split">
|
||||||
|
${this.buildConvertRequestButton(request.requestId || "")}
|
||||||
|
${this.buildCloseRequestButton(request.requestId || "")}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No active support requests.</p></div>';
|
||||||
|
|
||||||
|
const activityHTML = this.activity.length
|
||||||
|
? this.activity
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.slice(0, 8)
|
||||||
|
.map(
|
||||||
|
(entry) => `
|
||||||
|
<article class="dispatch-card">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<strong>${entry.type || "activity"}</strong>
|
||||||
|
<span class="dispatch-badge">${Math.round(entry.timestamp || 0)}s</span>
|
||||||
|
</header>
|
||||||
|
<p class="dispatch-description">${entry.message || ""}</p>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No recent activity.</p></div>';
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="dispatch-inline-section">
|
||||||
|
<div class="dispatch-inline-header">Support Requests</div>
|
||||||
|
${requestsHTML}
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-inline-section">
|
||||||
|
<div class="dispatch-inline-header">Recent Activity</div>
|
||||||
|
${activityHTML}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
this.updateDangerAlert();
|
||||||
|
this.updateRequestAlert();
|
||||||
|
this.renderMetrics();
|
||||||
|
this.renderOpenContracts();
|
||||||
|
this.renderAssignedContracts();
|
||||||
|
this.renderGroups();
|
||||||
|
this.renderActivity();
|
||||||
|
},
|
||||||
|
};
|
||||||
74
arma/client/addons/cad/ui/src/shared.js
Normal file
74
arma/client/addons/cad/ui/src/shared.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Shared JavaScript for Map UI
|
||||||
|
* Provides common utilities and state management across all UI components
|
||||||
|
*/
|
||||||
|
|
||||||
|
window.mapUIState = {
|
||||||
|
layersPanelVisible: true,
|
||||||
|
sidePanelElement: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.mapUI = {
|
||||||
|
formatGridCoordinate(value) {
|
||||||
|
return Math.round(Number(value) || 0)
|
||||||
|
.toString()
|
||||||
|
.padStart(4, "0");
|
||||||
|
},
|
||||||
|
formatPosition(position) {
|
||||||
|
const safePosition = Array.isArray(position) ? position : [0, 0, 0];
|
||||||
|
return `X: ${this.formatGridCoordinate(safePosition[0])} Y: ${this.formatGridCoordinate(safePosition[1])}`;
|
||||||
|
},
|
||||||
|
sendEvent(event, data) {
|
||||||
|
A3API.SendAlert(JSON.stringify({ event: event, data: data }));
|
||||||
|
},
|
||||||
|
updateCoordinates(x, y) {
|
||||||
|
const coordDisplay = document.getElementById("coordsDisplay");
|
||||||
|
if (coordDisplay) {
|
||||||
|
coordDisplay.textContent = this.formatPosition([x, y, 0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateScale(scale) {
|
||||||
|
const scaleDisplay = document.getElementById("scaleDisplay");
|
||||||
|
if (scaleDisplay) {
|
||||||
|
scaleDisplay.textContent = `Scale: 1:${Math.round(scale)}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateStatus(text) {
|
||||||
|
const statusText = document.getElementById("statusText");
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.updateCoordinates = window.mapUI.updateCoordinates;
|
||||||
|
window.updateScale = window.mapUI.updateScale;
|
||||||
|
window.updateStatus = window.mapUI.updateStatus;
|
||||||
|
|
||||||
|
window.ForgeBridge = window.ForgeBridge || {
|
||||||
|
_handlers: {},
|
||||||
|
on(event, handler) {
|
||||||
|
this._handlers[event] = this._handlers[event] || [];
|
||||||
|
this._handlers[event].push(handler);
|
||||||
|
},
|
||||||
|
ready(payload) {
|
||||||
|
window.mapUI.sendEvent("cad::ready", payload || {});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
receive(payload) {
|
||||||
|
if (!payload || typeof payload !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = this._handlers[payload.event] || [];
|
||||||
|
handlers.forEach((handler) => handler(payload.data || {}));
|
||||||
|
},
|
||||||
|
send(event, data) {
|
||||||
|
window.mapUI.sendEvent(event, data || {});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
close(data) {
|
||||||
|
window.mapUI.sendEvent("map::close", data || {});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
190
arma/client/addons/cad/ui/src/sidepanel.html
Normal file
190
arma/client/addons/cad/ui/src/sidepanel.html
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>CAD System</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div id="cadStatusMessage" class="task-status-message"></div>
|
||||||
|
<div id="cadDangerAlert" class="cad-danger-alert is-hidden"></div>
|
||||||
|
<div id="cadRequestAlert" class="cad-warning-alert is-hidden"></div>
|
||||||
|
<div class="cad-tabs" role="tablist" aria-label="CAD Sections">
|
||||||
|
<button
|
||||||
|
id="tabContractsBtn"
|
||||||
|
class="cad-tab is-active"
|
||||||
|
type="button"
|
||||||
|
data-tab="contracts"
|
||||||
|
>
|
||||||
|
Contracts
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="tabRosterBtn"
|
||||||
|
class="cad-tab"
|
||||||
|
type="button"
|
||||||
|
data-tab="roster"
|
||||||
|
>
|
||||||
|
Roster
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="tabRequestsBtn"
|
||||||
|
class="cad-tab"
|
||||||
|
type="button"
|
||||||
|
data-tab="requests"
|
||||||
|
>
|
||||||
|
Requests
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="tabActivityBtn"
|
||||||
|
class="cad-tab"
|
||||||
|
type="button"
|
||||||
|
data-tab="activity"
|
||||||
|
>
|
||||||
|
Activity
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="cad-tab-panels">
|
||||||
|
<div
|
||||||
|
id="contractsPanel"
|
||||||
|
class="cad-section is-active"
|
||||||
|
data-panel="contracts"
|
||||||
|
>
|
||||||
|
<div class="cad-section-header">Contracts</div>
|
||||||
|
<div id="taskList" class="task-list">
|
||||||
|
<div class="placeholder-message">
|
||||||
|
<p>Loading contracts...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="rosterPanel" class="cad-section" data-panel="roster">
|
||||||
|
<div class="cad-section-header">Roster</div>
|
||||||
|
<div id="rosterList" class="task-list">
|
||||||
|
<div class="placeholder-message">
|
||||||
|
<p>Loading roster...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="requestsPanel"
|
||||||
|
class="cad-section"
|
||||||
|
data-panel="requests"
|
||||||
|
>
|
||||||
|
<div class="cad-section-header">Support Requests</div>
|
||||||
|
<div id="requestList" class="task-list">
|
||||||
|
<div class="placeholder-message">
|
||||||
|
<p>No support requests.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="activityPanel"
|
||||||
|
class="cad-section"
|
||||||
|
data-panel="activity"
|
||||||
|
>
|
||||||
|
<div class="cad-section-header">Activity</div>
|
||||||
|
<div id="activityList" class="task-list">
|
||||||
|
<div class="placeholder-message">
|
||||||
|
<p>No recent activity.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cadRequestModal" class="cad-modal is-hidden">
|
||||||
|
<div class="cad-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="cad-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="cadRequestModalTitle"
|
||||||
|
>
|
||||||
|
<div class="cad-modal-header">
|
||||||
|
<div>
|
||||||
|
<div class="cad-section-header">Support Request</div>
|
||||||
|
<h3 id="cadRequestModalTitle">Submit Request</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="cadRequestModalCloseBtn"
|
||||||
|
class="cad-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close support request form"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="cad-modal-body">
|
||||||
|
<div class="cad-modal-fields">
|
||||||
|
<label class="cad-field">
|
||||||
|
<span>Priority</span>
|
||||||
|
<select
|
||||||
|
id="cadRequestPrioritySelect"
|
||||||
|
class="cad-select"
|
||||||
|
>
|
||||||
|
<option value="routine">routine</option>
|
||||||
|
<option value="priority" selected>
|
||||||
|
priority
|
||||||
|
</option>
|
||||||
|
<option value="emergency">emergency</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
id="cadRequestFields"
|
||||||
|
class="cad-modal-fields"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cad-modal-actions">
|
||||||
|
<button
|
||||||
|
id="cadRequestModalSaveBtn"
|
||||||
|
type="button"
|
||||||
|
class="task-accept-btn"
|
||||||
|
>
|
||||||
|
Submit Request
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.MapLoader = {
|
||||||
|
loadCSS(path) {
|
||||||
|
return A3API.RequestFile(path).then((css) => {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadJS(path) {
|
||||||
|
return A3API.RequestFile(path).then((js) => {
|
||||||
|
eval(js);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadAll(resources) {
|
||||||
|
return resources.reduce((promise, resource) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
if (resource.endsWith(".css")) {
|
||||||
|
return this.loadCSS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.endsWith(".js")) {
|
||||||
|
return this.loadJS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
MapLoader.loadAll([
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js",
|
||||||
|
]).catch((err) => console.error("[SIDEPANEL] Load error:", err));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1238
arma/client/addons/cad/ui/src/sidepanel.js
Normal file
1238
arma/client/addons/cad/ui/src/sidepanel.js
Normal file
File diff suppressed because it is too large
Load Diff
40
arma/client/addons/cad/ui/src/styles/bottombar.css
Normal file
40
arma/client/addons/cad/ui/src/styles/bottombar.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
body {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
min-height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 20px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(14, 19, 27, 0.96),
|
||||||
|
rgba(18, 23, 32, 0.93) 55%,
|
||||||
|
rgba(13, 18, 25, 0.96)
|
||||||
|
);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
box-shadow: 0 -12px 26px rgba(0, 0, 0, 0.24);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
-webkit-backdrop-filter: blur(18px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand,
|
||||||
|
.footer-version {
|
||||||
|
color: rgba(245, 248, 255, 0.8);
|
||||||
|
font-size: 12px;
|
||||||
|
text-shadow: 0 1px 10px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-brand {
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-version {
|
||||||
|
color: rgba(245, 248, 255, 0.62);
|
||||||
|
}
|
||||||
78
arma/client/addons/cad/ui/src/styles/common.css
Normal file
78
arma/client/addons/cad/ui/src/styles/common.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
:root {
|
||||||
|
--bg: rgba(9, 12, 18, 0.82);
|
||||||
|
--panel: rgba(20, 24, 33, 0.9);
|
||||||
|
--panel2: rgba(17, 21, 30, 0.82);
|
||||||
|
--stroke: rgba(255, 255, 255, 0.12);
|
||||||
|
--stroke2: rgba(255, 255, 255, 0.2);
|
||||||
|
--text: rgba(245, 248, 255, 0.92);
|
||||||
|
--muted: rgba(245, 248, 255, 0.62);
|
||||||
|
--muted2: rgba(245, 248, 255, 0.42);
|
||||||
|
--accent: rgba(104, 196, 255, 0.95);
|
||||||
|
--danger: rgba(255, 96, 96, 0.95);
|
||||||
|
--shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
|
||||||
|
--radius: 14px;
|
||||||
|
--radius2: 10px;
|
||||||
|
--font:
|
||||||
|
ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial,
|
||||||
|
sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font);
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius2);
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.16s ease,
|
||||||
|
border-color 0.16s ease,
|
||||||
|
transform 0.16s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
border-color: rgba(255, 255, 255, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
background: rgba(255, 96, 96, 0.1);
|
||||||
|
border-color: rgba(255, 96, 96, 0.25);
|
||||||
|
color: rgba(255, 220, 220, 0.95);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close:hover {
|
||||||
|
background: rgba(255, 96, 96, 0.2);
|
||||||
|
border-color: rgba(255, 96, 96, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 2px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
562
arma/client/addons/cad/ui/src/styles/dispatcher.css
Normal file
562
arma/client/addons/cad/ui/src/styles/dispatcher.css
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
radial-gradient(
|
||||||
|
circle at top left,
|
||||||
|
rgba(41, 69, 93, 0.18),
|
||||||
|
transparent 30%
|
||||||
|
),
|
||||||
|
linear-gradient(180deg, rgba(9, 14, 20, 0.96), rgba(15, 22, 31, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-shell {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 18px;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-kicker {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-header button,
|
||||||
|
.dispatch-btn,
|
||||||
|
.dispatch-select {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(24, 31, 40, 0.9);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-header button,
|
||||||
|
.dispatch-btn {
|
||||||
|
padding: 10px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-btn-secondary {
|
||||||
|
background: rgba(53, 40, 39, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-status {
|
||||||
|
min-height: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(233, 241, 248, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-status[data-type="success"] {
|
||||||
|
color: #79d28a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-status[data-type="error"] {
|
||||||
|
color: #ff8a80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-danger-alert {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.38);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(92, 18, 18, 0.94),
|
||||||
|
rgba(128, 29, 29, 0.82)
|
||||||
|
);
|
||||||
|
color: #ffd4cf;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-danger-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-warning-alert {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(246, 198, 84, 0.42);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(89, 64, 12, 0.94),
|
||||||
|
rgba(125, 92, 18, 0.84)
|
||||||
|
);
|
||||||
|
color: #ffe9b2;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-warning-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card {
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(13, 19, 26, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: rgba(233, 241, 248, 0.6);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card strong {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(74, 17, 17, 0.86),
|
||||||
|
rgba(22, 13, 16, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.12);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card.is-warning {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(92, 65, 14, 0.86),
|
||||||
|
rgba(29, 22, 11, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(246, 198, 84, 0.12);
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-grid {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
|
grid-auto-rows: minmax(0, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(11, 17, 24, 0.78);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-open {
|
||||||
|
grid-column: span 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-assigned {
|
||||||
|
grid-column: span 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-groups {
|
||||||
|
grid-column: span 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-activity {
|
||||||
|
grid-column: span 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-panel-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-inline-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-inline-header {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
background: rgba(19, 26, 34, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-interactive:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.2);
|
||||||
|
background: rgba(23, 31, 40, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-header,
|
||||||
|
.dispatch-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-header-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-header {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-description {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: rgba(241, 246, 251, 0.82);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-meta {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(229, 237, 244, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-badge {
|
||||||
|
padding: 3px 7px;
|
||||||
|
border: 1px solid rgba(91, 187, 255, 0.18);
|
||||||
|
background: rgba(16, 43, 61, 0.7);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-alert-badge {
|
||||||
|
padding: 3px 7px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.44);
|
||||||
|
background: rgba(95, 23, 23, 0.88);
|
||||||
|
color: #ffd8d1;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-icon-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
background: rgba(24, 31, 40, 0.92);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-icon-btn:hover {
|
||||||
|
background: rgba(32, 42, 52, 0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.1);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-danger .dispatch-meta,
|
||||||
|
.dispatch-card.is-danger .dispatch-description {
|
||||||
|
color: rgba(255, 232, 228, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-warning {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(86, 64, 17, 0.78),
|
||||||
|
rgba(34, 27, 16, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(246, 198, 84, 0.1);
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-warning .dispatch-meta,
|
||||||
|
.dispatch-card.is-warning .dispatch-description {
|
||||||
|
color: rgba(255, 243, 214, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-actions-split {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 9px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 92px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(24, 31, 40, 0.92);
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-message {
|
||||||
|
padding: 18px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(233, 241, 248, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 30;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 32px 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(4, 8, 12, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-dialog {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: min(560px, calc(100% - 48px));
|
||||||
|
max-height: calc(100vh - 64px);
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(11, 17, 24, 0.98);
|
||||||
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-header,
|
||||||
|
.dispatch-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-header {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-meta-grid strong {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-field {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-field span {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: rgba(233, 241, 248, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-modal-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-block,
|
||||||
|
.dispatch-detail-list {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(19, 26, 34, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-block {
|
||||||
|
padding: 12px;
|
||||||
|
color: rgba(241, 246, 251, 0.82);
|
||||||
|
line-height: 1.45;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 180px) minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(14, 20, 28, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-label {
|
||||||
|
color: rgba(233, 241, 248, 0.64);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-value {
|
||||||
|
color: rgba(241, 246, 251, 0.84);
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-danger-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 107, 107, 0.08),
|
||||||
|
0 0 0 rgba(255, 107, 107, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 141, 141, 0.22),
|
||||||
|
0 0 18px rgba(255, 107, 107, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-warning-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(246, 198, 84, 0.08),
|
||||||
|
0 0 0 rgba(246, 198, 84, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(251, 212, 118, 0.22),
|
||||||
|
0 0 18px rgba(246, 198, 84, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
554
arma/client/addons/cad/ui/src/styles/sidepanel.css
Normal file
554
arma/client/addons/cad/ui/src/styles/sidepanel.css
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--panel);
|
||||||
|
border-left: 1px solid var(--stroke);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 14px;
|
||||||
|
border-bottom: 1px solid var(--stroke);
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 255, 255, 0.05),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h3 {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 650;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: 14px;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-message {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-message p {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tabs.is-two-col {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tabs.is-three-col {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tab {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 7px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
background: rgba(20, 27, 33, 0.88);
|
||||||
|
color: rgba(243, 246, 249, 0.78);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
font-size: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tab:hover {
|
||||||
|
background: rgba(31, 40, 47, 0.94);
|
||||||
|
color: #f3f6f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tab.is-active {
|
||||||
|
border-color: rgba(91, 187, 255, 0.42);
|
||||||
|
background: rgba(15, 40, 58, 0.96);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tab-panels {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-section.is-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-section-header {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-accept-btn,
|
||||||
|
.task-secondary-btn,
|
||||||
|
.cad-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(30, 37, 43, 0.9);
|
||||||
|
color: #f3f6f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-accept-btn,
|
||||||
|
.task-secondary-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-accept-btn:hover,
|
||||||
|
.task-secondary-btn:hover {
|
||||||
|
background: rgba(46, 57, 66, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-accept-btn:disabled,
|
||||||
|
.task-secondary-btn:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-status-message {
|
||||||
|
min-height: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #cdd6dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-status-message[data-type="success"] {
|
||||||
|
color: #79d28a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-status-message[data-type="error"] {
|
||||||
|
color: #ff8a80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(4, 8, 12, 0.76);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-dialog {
|
||||||
|
position: relative;
|
||||||
|
width: min(480px, calc(100% - 28px));
|
||||||
|
margin: 32px auto 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(11, 17, 24, 0.98);
|
||||||
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header,
|
||||||
|
.cad-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header h3 {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-body {
|
||||||
|
padding: 14px;
|
||||||
|
max-height: 62vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-field {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-field span {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: rgba(233, 241, 248, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-input,
|
||||||
|
.cad-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(30, 37, 43, 0.9);
|
||||||
|
color: #f3f6f9;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-textarea {
|
||||||
|
min-height: 74px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-icon-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
background: rgba(24, 31, 40, 0.92);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-danger-alert {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.36);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(92, 18, 18, 0.94),
|
||||||
|
rgba(128, 29, 29, 0.82)
|
||||||
|
);
|
||||||
|
color: #ffd4cf;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-danger-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-warning-alert {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(246, 198, 84, 0.4);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(89, 64, 12, 0.94),
|
||||||
|
rgba(125, 92, 18, 0.84)
|
||||||
|
);
|
||||||
|
color: #ffe9b2;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-warning-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-request-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-request-btn {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-action-stack,
|
||||||
|
.task-action-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-action-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(12, 16, 20, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.is-danger,
|
||||||
|
.roster-summary-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.1);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-type {
|
||||||
|
opacity: 0.7;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-description {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-secondary-btn {
|
||||||
|
background: rgba(60, 48, 45, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roster-summary-card {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(16, 23, 29, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-alert-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.44);
|
||||||
|
background: rgba(95, 23, 23, 0.88);
|
||||||
|
color: #ffd8d1;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roster-member-card {
|
||||||
|
background: rgba(12, 16, 20, 0.74);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 120ms ease,
|
||||||
|
background 120ms ease,
|
||||||
|
transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card .task-type {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card .task-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.26);
|
||||||
|
background: rgba(18, 29, 38, 0.9);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-selected {
|
||||||
|
border-color: rgba(91, 187, 255, 0.52);
|
||||||
|
background: rgba(15, 40, 58, 0.92);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(91, 187, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-danger:not(.is-selected) {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-danger .task-meta,
|
||||||
|
.roster-summary-card.is-danger .task-meta {
|
||||||
|
color: rgba(255, 232, 228, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 120ms ease,
|
||||||
|
background 120ms ease,
|
||||||
|
transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-type {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-description {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.26);
|
||||||
|
background: rgba(18, 29, 38, 0.9);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-selected {
|
||||||
|
border-color: rgba(91, 187, 255, 0.52);
|
||||||
|
background: rgba(15, 40, 58, 0.92);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(91, 187, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-warning:not(.is-selected) {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(86, 64, 17, 0.78),
|
||||||
|
rgba(34, 27, 16, 0.92)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-warning .task-meta,
|
||||||
|
.dispatch-map-card.is-warning .task-description {
|
||||||
|
color: rgba(255, 243, 214, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.roster-leader-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border: 1px solid rgba(91, 187, 255, 0.28);
|
||||||
|
background: rgba(15, 40, 58, 0.82);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-danger-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 107, 107, 0.08),
|
||||||
|
0 0 0 rgba(255, 107, 107, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 141, 141, 0.22),
|
||||||
|
0 0 14px rgba(255, 107, 107, 0.14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-warning-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(246, 198, 84, 0.08),
|
||||||
|
0 0 0 rgba(246, 198, 84, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(251, 212, 118, 0.22),
|
||||||
|
0 0 18px rgba(246, 198, 84, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
296
arma/client/addons/cad/ui/src/styles/topbar.css
Normal file
296
arma/client/addons/cad/ui/src/styles/topbar.css
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
body {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto auto auto;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 16px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-mode="operations"] {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-mode="dispatch"] {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto 0;
|
||||||
|
height: 60px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(16, 22, 31, 0.96),
|
||||||
|
rgba(19, 26, 36, 0.94) 55%,
|
||||||
|
rgba(15, 20, 28, 0.96)
|
||||||
|
);
|
||||||
|
border-bottom: none;
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
-webkit-backdrop-filter: blur(18px);
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 650;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-main {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-kicker {
|
||||||
|
color: rgba(218, 227, 236, 0.56);
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-main {
|
||||||
|
color: rgba(245, 248, 255, 0.92);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-strip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-strip.is-hidden,
|
||||||
|
.operator-controls.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 88px;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-label {
|
||||||
|
color: rgba(218, 227, 236, 0.5);
|
||||||
|
font-size: 9px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-info strong {
|
||||||
|
color: rgba(245, 248, 255, 0.9);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-select {
|
||||||
|
min-width: 92px;
|
||||||
|
max-width: 112px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
background: rgba(14, 20, 28, 0.96);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-operator {
|
||||||
|
min-width: 84px;
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-controls.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-view-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-view-controls.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-text {
|
||||||
|
color: rgba(233, 241, 248, 0.72);
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch {
|
||||||
|
position: relative;
|
||||||
|
width: 54px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-slider {
|
||||||
|
position: relative;
|
||||||
|
width: 54px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(22, 29, 39, 0.92);
|
||||||
|
box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.22);
|
||||||
|
transition:
|
||||||
|
border-color 0.16s ease,
|
||||||
|
background 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-slider::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(237, 244, 251, 0.98),
|
||||||
|
rgba(189, 205, 221, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.26);
|
||||||
|
transition:
|
||||||
|
transform 0.16s ease,
|
||||||
|
background 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch input:checked + .mode-slider {
|
||||||
|
border-color: rgba(91, 187, 255, 0.42);
|
||||||
|
background: rgba(14, 37, 56, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-switch input:checked + .mode-slider::after {
|
||||||
|
transform: translateX(26px);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(131, 212, 255, 0.98),
|
||||||
|
rgba(72, 170, 231, 0.94)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
min-width: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dispatch-view {
|
||||||
|
min-width: 66px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
min-width: 34px;
|
||||||
|
width: 34px;
|
||||||
|
height: 30px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-refresh {
|
||||||
|
min-width: 40px;
|
||||||
|
width: 40px;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dispatch-view.is-active {
|
||||||
|
border-color: rgba(91, 187, 255, 0.42);
|
||||||
|
background: rgba(15, 40, 58, 0.96);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .logo,
|
||||||
|
body .title-block,
|
||||||
|
body .operator-strip,
|
||||||
|
body .operator-controls,
|
||||||
|
body .mode-controls,
|
||||||
|
body .dispatch-view-controls,
|
||||||
|
body .controls,
|
||||||
|
body .mode-switch,
|
||||||
|
body .mode-switch *,
|
||||||
|
body button,
|
||||||
|
body select,
|
||||||
|
body label {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
132
arma/client/addons/cad/ui/src/topbar.html
Normal file
132
arma/client/addons/cad/ui/src/topbar.html
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="logo">FORGE OS</div>
|
||||||
|
<div class="header-main">
|
||||||
|
<div class="title-block">
|
||||||
|
<span class="title-kicker">Cad Systems</span>
|
||||||
|
<strong class="title-main">FORGE Command & Dispatch</strong>
|
||||||
|
</div>
|
||||||
|
<div id="operatorStrip" class="operator-strip is-hidden">
|
||||||
|
<div class="operator-info">
|
||||||
|
<span class="operator-label">Current Group</span>
|
||||||
|
<strong id="operatorGroupName">No Group</strong>
|
||||||
|
</div>
|
||||||
|
<div class="operator-info">
|
||||||
|
<span class="operator-label">Location</span>
|
||||||
|
<strong id="operatorLocation">Unavailable</strong>
|
||||||
|
</div>
|
||||||
|
<div id="operatorControls" class="operator-controls is-hidden">
|
||||||
|
<select id="operatorRoleSelect" class="operator-select">
|
||||||
|
<option value="infantry">infantry</option>
|
||||||
|
<option value="recon">recon</option>
|
||||||
|
<option value="armor">armor</option>
|
||||||
|
<option value="air">air</option>
|
||||||
|
<option value="logistics">logistics</option>
|
||||||
|
<option value="support">support</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
id="operatorRoleBtn"
|
||||||
|
class="btn btn-operator"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Update Role
|
||||||
|
</button>
|
||||||
|
<select id="operatorStatusSelect" class="operator-select">
|
||||||
|
<option value="available">available</option>
|
||||||
|
<option value="en_route">en route</option>
|
||||||
|
<option value="on_task">on task</option>
|
||||||
|
<option value="holding">holding</option>
|
||||||
|
<option value="danger">danger</option>
|
||||||
|
<option value="unavailable">unavailable</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
id="operatorStatusBtn"
|
||||||
|
class="btn btn-operator"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Update Status
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="modeControls" class="mode-controls is-hidden">
|
||||||
|
<span class="mode-text">Ops</span>
|
||||||
|
<label class="mode-switch" for="modeToggle">
|
||||||
|
<input id="modeToggle" type="checkbox" />
|
||||||
|
<span class="mode-slider"></span>
|
||||||
|
</label>
|
||||||
|
<span class="mode-text">Dispatch</span>
|
||||||
|
</div>
|
||||||
|
<div id="dispatchViewControls" class="dispatch-view-controls is-hidden">
|
||||||
|
<button
|
||||||
|
id="dispatchBoardBtn"
|
||||||
|
class="btn btn-dispatch-view is-active"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Board
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="dispatchMapBtn"
|
||||||
|
class="btn btn-dispatch-view"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Map
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<button
|
||||||
|
id="dispatchRefreshBtn"
|
||||||
|
class="btn btn-icon btn-refresh"
|
||||||
|
type="button"
|
||||||
|
aria-label="Refresh board"
|
||||||
|
title="Refresh board"
|
||||||
|
>
|
||||||
|
↻
|
||||||
|
</button>
|
||||||
|
<button id="btnClose" class="btn btn-icon btn-close">X</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.MapLoader = {
|
||||||
|
loadCSS(path) {
|
||||||
|
return A3API.RequestFile(path).then((css) => {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadJS(path) {
|
||||||
|
return A3API.RequestFile(path).then((js) => {
|
||||||
|
eval(js);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadAll(resources) {
|
||||||
|
return resources.reduce((promise, resource) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
if (resource.endsWith(".css")) {
|
||||||
|
return this.loadCSS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.endsWith(".js")) {
|
||||||
|
return this.loadJS(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
MapLoader.loadAll([
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.css",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
||||||
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-topbar.js",
|
||||||
|
]).catch((err) => console.error("[TOPBAR] Load error:", err));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
162
arma/client/addons/cad/ui/src/topbar.js
Normal file
162
arma/client/addons/cad/ui/src/topbar.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
window.cadTopbar = {
|
||||||
|
mode: "operations",
|
||||||
|
dispatchView: "board",
|
||||||
|
currentGroup: null,
|
||||||
|
session: {},
|
||||||
|
init() {
|
||||||
|
document.getElementById("btnClose").addEventListener("click", () => {
|
||||||
|
window.mapUI.sendEvent("map::close", null);
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("modeToggle")
|
||||||
|
.addEventListener("change", (event) => {
|
||||||
|
window.mapUI.sendEvent("cad::mode::set", {
|
||||||
|
mode: event.target.checked ? "dispatch" : "operations",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatchRefreshBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
window.mapUI.sendEvent("cad::refresh", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatchBoardBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
window.mapUI.sendEvent("cad::dispatchView::set", {
|
||||||
|
dispatchView: "board",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("dispatchMapBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
window.mapUI.sendEvent("cad::dispatchView::set", {
|
||||||
|
dispatchView: "map",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("operatorRoleBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
if (!this.currentGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.mapUI.sendEvent("cad::groups::role", {
|
||||||
|
groupID: this.currentGroup.groupId || "",
|
||||||
|
role: document.getElementById("operatorRoleSelect").value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("operatorStatusBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
if (!this.currentGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.mapUI.sendEvent("cad::groups::status", {
|
||||||
|
groupID: this.currentGroup.groupId || "",
|
||||||
|
status: document.getElementById("operatorStatusSelect")
|
||||||
|
.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.mapUI.sendEvent("cad::topbar::ready", {});
|
||||||
|
},
|
||||||
|
formatLocation(group) {
|
||||||
|
const position = Array.isArray(group?.position)
|
||||||
|
? group.position
|
||||||
|
: [0, 0, 0];
|
||||||
|
return window.mapUI.formatPosition(position);
|
||||||
|
},
|
||||||
|
receiveState(payload) {
|
||||||
|
this.session =
|
||||||
|
payload && payload.session && typeof payload.session === "object"
|
||||||
|
? payload.session
|
||||||
|
: {};
|
||||||
|
this.mode =
|
||||||
|
payload && typeof payload.mode === "string"
|
||||||
|
? payload.mode
|
||||||
|
: "operations";
|
||||||
|
this.dispatchView =
|
||||||
|
payload && typeof payload.dispatchView === "string"
|
||||||
|
? payload.dispatchView
|
||||||
|
: "board";
|
||||||
|
this.currentGroup =
|
||||||
|
payload &&
|
||||||
|
payload.currentGroup &&
|
||||||
|
typeof payload.currentGroup === "object"
|
||||||
|
? payload.currentGroup
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const modeControls = document.getElementById("modeControls");
|
||||||
|
const canDispatch = !!this.session.isDispatcher;
|
||||||
|
const canOperateGroup =
|
||||||
|
!!this.currentGroup &&
|
||||||
|
(!!this.session.isLeader || !!this.session.isDispatcher);
|
||||||
|
const operatorStrip = document.getElementById("operatorStrip");
|
||||||
|
const operatorControls = document.getElementById("operatorControls");
|
||||||
|
const dispatchViewControls = document.getElementById(
|
||||||
|
"dispatchViewControls",
|
||||||
|
);
|
||||||
|
const dispatchRefreshBtn =
|
||||||
|
document.getElementById("dispatchRefreshBtn");
|
||||||
|
const dispatchBoardBtn = document.getElementById("dispatchBoardBtn");
|
||||||
|
const dispatchMapBtn = document.getElementById("dispatchMapBtn");
|
||||||
|
|
||||||
|
modeControls.classList.toggle("is-hidden", !canDispatch);
|
||||||
|
dispatchViewControls.classList.toggle(
|
||||||
|
"is-hidden",
|
||||||
|
!canDispatch || this.mode !== "dispatch",
|
||||||
|
);
|
||||||
|
operatorStrip.classList.toggle(
|
||||||
|
"is-hidden",
|
||||||
|
this.mode !== "operations" || !this.currentGroup,
|
||||||
|
);
|
||||||
|
operatorControls.classList.toggle("is-hidden", !canOperateGroup);
|
||||||
|
|
||||||
|
document.body.dataset.mode = this.mode;
|
||||||
|
document.body.dataset.dispatcher = canDispatch ? "true" : "false";
|
||||||
|
|
||||||
|
document.getElementById("modeToggle").checked =
|
||||||
|
this.mode === "dispatch";
|
||||||
|
dispatchBoardBtn.classList.toggle(
|
||||||
|
"is-active",
|
||||||
|
this.dispatchView === "board",
|
||||||
|
);
|
||||||
|
dispatchMapBtn.classList.toggle(
|
||||||
|
"is-active",
|
||||||
|
this.dispatchView === "map",
|
||||||
|
);
|
||||||
|
dispatchRefreshBtn.title =
|
||||||
|
this.mode === "dispatch" ? "Refresh dispatch board" : "Refresh CAD";
|
||||||
|
dispatchRefreshBtn.setAttribute(
|
||||||
|
"aria-label",
|
||||||
|
this.mode === "dispatch" ? "Refresh dispatch board" : "Refresh CAD",
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById("operatorGroupName").textContent = this
|
||||||
|
.currentGroup
|
||||||
|
? this.currentGroup.callsign ||
|
||||||
|
this.currentGroup.groupId ||
|
||||||
|
"Current Group"
|
||||||
|
: "No Group";
|
||||||
|
document.getElementById("operatorLocation").textContent = this
|
||||||
|
.currentGroup
|
||||||
|
? this.formatLocation(this.currentGroup)
|
||||||
|
: "Unavailable";
|
||||||
|
|
||||||
|
if (this.currentGroup) {
|
||||||
|
document.getElementById("operatorRoleSelect").value =
|
||||||
|
this.currentGroup.role || "infantry";
|
||||||
|
document.getElementById("operatorStatusSelect").value =
|
||||||
|
this.currentGroup.status || "available";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.cadTopbar.init();
|
||||||
89
arma/client/addons/cad/ui/ui.config.mjs
Normal file
89
arma/client/addons/cad/ui/ui.config.mjs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
export default {
|
||||||
|
addonName: "cad",
|
||||||
|
title: "FORGE CAD",
|
||||||
|
logLabel: "CAD UI",
|
||||||
|
outputDir: "_site",
|
||||||
|
generateIndex: false,
|
||||||
|
jsBundles: [
|
||||||
|
{
|
||||||
|
name: "CAD shared bridge/runtime",
|
||||||
|
output: "cad-shared.js",
|
||||||
|
sources: ["src/shared.js"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD topbar app",
|
||||||
|
output: "cad-topbar.js",
|
||||||
|
sources: ["src/topbar.js"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD sidepanel app",
|
||||||
|
output: "cad-sidepanel.js",
|
||||||
|
sources: ["src/sidepanel.js"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD dispatcher app",
|
||||||
|
output: "cad-dispatcher.js",
|
||||||
|
sources: [
|
||||||
|
"src/dispatcher/formatters.js",
|
||||||
|
"src/dispatcher/modals.js",
|
||||||
|
"src/dispatcher/render.js",
|
||||||
|
"src/dispatcher/index.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD bottombar app",
|
||||||
|
output: "cad-bottombar.js",
|
||||||
|
sources: ["src/bottombar.js"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cssBundles: [
|
||||||
|
{
|
||||||
|
name: "CAD common styles",
|
||||||
|
output: "cad-common.css",
|
||||||
|
sources: ["src/styles/common.css"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD topbar styles",
|
||||||
|
output: "cad-topbar.css",
|
||||||
|
sources: ["src/styles/topbar.css"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD sidepanel styles",
|
||||||
|
output: "cad-sidepanel.css",
|
||||||
|
sources: ["src/styles/sidepanel.css"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD dispatcher styles",
|
||||||
|
output: "cad-dispatcher.css",
|
||||||
|
sources: ["src/styles/dispatcher.css"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD bottombar styles",
|
||||||
|
output: "cad-bottombar.css",
|
||||||
|
sources: ["src/styles/bottombar.css"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
htmlTemplates: [
|
||||||
|
{
|
||||||
|
name: "CAD topbar page",
|
||||||
|
output: "topbar.html",
|
||||||
|
source: "src/topbar.html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD sidepanel page",
|
||||||
|
output: "sidepanel.html",
|
||||||
|
source: "src/sidepanel.html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD dispatcher page",
|
||||||
|
output: "dispatcher.html",
|
||||||
|
source: "src/dispatcher.html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CAD bottombar page",
|
||||||
|
output: "bottombar.html",
|
||||||
|
source: "src/bottombar.html",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
site: {},
|
||||||
|
};
|
||||||
@ -1,3 +1,2 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initCatalogService);
|
PREP(initActionService);
|
||||||
PREP(initClass);
|
PREP(initContextService);
|
||||||
PREP(initSessionService);
|
PREP(initHelperService);
|
||||||
|
PREP(initPayloadService);
|
||||||
|
PREP(initRepository);
|
||||||
PREP(initUIBridge);
|
PREP(initUIBridge);
|
||||||
PREP(initVGClass);
|
PREP(initVGRepository);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
PREP(openVG);
|
PREP(openVG);
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
if (isNil QGVAR(GarageCatalogService)) then { call FUNC(initCatalogService); };
|
if (isNil QGVAR(GarageHelperService)) then { call FUNC(initHelperService); };
|
||||||
if (isNil QGVAR(GarageClass)) then { call FUNC(initClass); };
|
if (isNil QGVAR(GarageRepository)) then { call FUNC(initRepository); };
|
||||||
if (isNil QGVAR(GarageSessionService)) then { call FUNC(initSessionService); };
|
if (isNil QGVAR(GarageContextService)) then { call FUNC(initContextService); };
|
||||||
|
if (isNil QGVAR(GaragePayloadService)) then { call FUNC(initPayloadService); };
|
||||||
|
if (isNil QGVAR(GarageActionService)) then { call FUNC(initActionService); };
|
||||||
if (isNil QGVAR(GarageUIBridge)) then { call FUNC(initUIBridge); };
|
if (isNil QGVAR(GarageUIBridge)) then { call FUNC(initUIBridge); };
|
||||||
if (isNil QGVAR(VGClass)) then { call FUNC(initVGClass); };
|
if (isNil QGVAR(VGRepository)) then { call FUNC(initVGRepository); };
|
||||||
|
|
||||||
[QGVAR(initGarage), {
|
[QGVAR(initGarage), {
|
||||||
GVAR(GarageClass) call ["init", []];
|
GVAR(GarageRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitGarage), {
|
[QGVAR(responseInitGarage), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(GarageClass) call ["sync", [_data]];
|
GVAR(GarageRepository) call ["sync", [_data]];
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||||
};
|
};
|
||||||
@ -22,7 +24,7 @@ if (isNil QGVAR(VGClass)) then { call FUNC(initVGClass); };
|
|||||||
[QGVAR(responseSyncGarage), {
|
[QGVAR(responseSyncGarage), {
|
||||||
params [["_data", createHashMap, [createHashMap, []]]];
|
params [["_data", createHashMap, [createHashMap, []]]];
|
||||||
|
|
||||||
GVAR(GarageClass) call ["sync", [_data]];
|
GVAR(GarageRepository) call ["sync", [_data]];
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||||
};
|
};
|
||||||
@ -31,35 +33,35 @@ if (isNil QGVAR(VGClass)) then { call FUNC(initVGClass); };
|
|||||||
[QGVAR(responseGarageAction), {
|
[QGVAR(responseGarageAction), {
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageActionService)) then {
|
||||||
GVAR(GarageUIBridge) call ["handleActionResponse", [_payload]];
|
GVAR(GarageActionService) call ["handleActionResponse", [_payload]];
|
||||||
};
|
};
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(initVG), {
|
[QGVAR(initVG), {
|
||||||
GVAR(VGClass) call ["init", []];
|
GVAR(VGRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitVG), {
|
[QGVAR(responseInitVG), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(VGClass) call ["sync", [_data]];
|
GVAR(VGRepository) call ["sync", [_data]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncVG), {
|
[QGVAR(responseSyncVG), {
|
||||||
params [["_data", createHashMap, [createHashMap, []]]];
|
params [["_data", createHashMap, [createHashMap, []]]];
|
||||||
|
|
||||||
GVAR(VGClass) call ["sync", [_data]];
|
GVAR(VGRepository) call ["sync", [_data]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(bank,BankClass) get "isLoaded";
|
EGVAR(bank,BankRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initGarage), []] call CFUNC(localEvent);
|
[QGVAR(initGarage), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
GVAR(GarageClass) get "isLoaded";
|
GVAR(GarageRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initVG), []] call CFUNC(localEvent);
|
[QGVAR(initVG), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -44,13 +44,13 @@ switch (_event) do {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
case "garage::vehicle::retrieve::request": {
|
case "garage::vehicle::retrieve::request": {
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageActionService)) then {
|
||||||
GVAR(GarageUIBridge) call ["handleRetrieveRequest", [_data]];
|
GVAR(GarageActionService) call ["handleRetrieveRequest", [_data]];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
case "garage::vehicle::store::request": {
|
case "garage::vehicle::store::request": {
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
if !(isNil QGVAR(GarageActionService)) then {
|
||||||
GVAR(GarageUIBridge) call ["handleStoreRequest", [_data]];
|
GVAR(GarageActionService) call ["handleStoreRequest", [_data]];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
case "garage::refresh": {
|
case "garage::refresh": {
|
||||||
|
|||||||
133
arma/client/addons/garage/functions/fnc_initActionService.sqf
Normal file
133
arma/client/addons/garage/functions/fnc_initActionService.sqf
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initActionService.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the garage action service for retrieve and store world actions.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Garage action service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_garage_fnc_initActionService;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(GarageActionServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "GarageActionServiceBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["pendingStoreVehicle", objNull];
|
||||||
|
_self set ["pendingRetrieve", createHashMap];
|
||||||
|
}],
|
||||||
|
["handleRetrieveRequest", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _plate = _data getOrDefault ["plate", ""];
|
||||||
|
if (_plate isEqualTo "") exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Select a stored vehicle to retrieve."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _garageMap = if (isNil QGVAR(GarageRepository)) then { createHashMap } else { GVAR(GarageRepository) call ["getState", []] };
|
||||||
|
private _vehicleData = _garageMap getOrDefault [_plate, createHashMap];
|
||||||
|
if (_vehicleData isEqualTo createHashMap) exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record could not be found."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _context = GVAR(GarageContextService) call ["getContext", []];
|
||||||
|
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
||||||
|
private _spawnHeading = _context getOrDefault ["spawnHeading", getDir player];
|
||||||
|
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
||||||
|
private _blockingVehicles = [];
|
||||||
|
{ _blockingVehicles pushBackUnique _x; } forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
||||||
|
{ _blockingVehicles pushBackUnique _x; } forEach (nearestObjects [_spawnPosition, ["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
||||||
|
if (_blockingVehicles isNotEqualTo []) exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "The garage spawn area is blocked."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _className = _vehicleData getOrDefault ["classname", ""];
|
||||||
|
if (_className isEqualTo "") exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [["message", "Stored vehicle record is missing a classname."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _vehicle = createVehicle [_className, _spawnPosition, [], 0, "CAN_COLLIDE"];
|
||||||
|
_vehicle setDir _spawnHeading;
|
||||||
|
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);
|
||||||
|
_vehicle setDamage (_vehicleData getOrDefault ["damage", 0]);
|
||||||
|
|
||||||
|
private _hitPoints = _vehicleData getOrDefault ["hit_points", createHashMap];
|
||||||
|
private _hitPointNames = _hitPoints getOrDefault ["names", []];
|
||||||
|
private _hitPointValues = _hitPoints getOrDefault ["values", []];
|
||||||
|
for "_index" from 0 to ((count _hitPointNames) - 1) do {
|
||||||
|
_vehicle setHitPointDamage [_hitPointNames param [_index, ""], _hitPointValues param [_index, 0]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_vehicle setVariable ["forge_garage_plate", _plate, true];
|
||||||
|
_vehicle setVariable ["forge_garage_owner_uid", getPlayerUID player, true];
|
||||||
|
|
||||||
|
_self set ["pendingRetrieve", createHashMapFromArray [["plate", _plate], ["vehicle", _vehicle]]];
|
||||||
|
[SRPC(garage,requestRetrieveVehicle), [getPlayerUID player, _plate]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["handleStoreRequest", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _netId = _data getOrDefault ["netId", ""];
|
||||||
|
if (_netId isEqualTo "") exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::store::failure", createHashMapFromArray [["message", "Select a nearby vehicle to store."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _vehicle = objectFromNetId _netId;
|
||||||
|
if (isNull _vehicle) exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::store::failure", createHashMapFromArray [["message", "The selected vehicle is no longer available."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (crew _vehicle isNotEqualTo []) exitWith {
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", ["garage::store::failure", createHashMapFromArray [["message", "All crew must exit the vehicle before storing it."]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _rawHitPoints = getAllHitPointsDamage _vehicle;
|
||||||
|
private _hitPointsJson = toJSON (createHashMapFromArray [["names", _rawHitPoints param [0, []]], ["selections", _rawHitPoints param [1, []]], ["values", _rawHitPoints param [2, []]]]);
|
||||||
|
|
||||||
|
_self set ["pendingStoreVehicle", _vehicle];
|
||||||
|
[SRPC(garage,requestStoreVehicle), [getPlayerUID player, typeOf _vehicle, fuel _vehicle, damage _vehicle, _hitPointsJson]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["handleActionResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _action = _payload getOrDefault ["action", ""];
|
||||||
|
private _success = _payload getOrDefault ["success", false];
|
||||||
|
private _message = _payload getOrDefault ["message", "Garage action failed."];
|
||||||
|
|
||||||
|
switch (_action) do {
|
||||||
|
case "retrieve": {
|
||||||
|
private _pendingRetrieve = _self getOrDefault ["pendingRetrieve", createHashMap];
|
||||||
|
private _vehicle = _pendingRetrieve getOrDefault ["vehicle", objNull];
|
||||||
|
if (!_success && { !isNull _vehicle }) then { deleteVehicle _vehicle; };
|
||||||
|
_self set ["pendingRetrieve", createHashMap];
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", [[ "garage::retrieve::failure", "garage::retrieve::success" ] select _success, createHashMapFromArray [["message", _message]]]];
|
||||||
|
};
|
||||||
|
case "store": {
|
||||||
|
private _vehicle = _self getOrDefault ["pendingStoreVehicle", objNull];
|
||||||
|
if (_success && { !isNull _vehicle }) then { deleteVehicle _vehicle; };
|
||||||
|
_self set ["pendingStoreVehicle", objNull];
|
||||||
|
GVAR(GarageUIBridge) call ["sendEvent", [[ "garage::store::failure", "garage::store::success" ] select _success, createHashMapFromArray [["message", _message]]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[] spawn {
|
||||||
|
sleep 0.05;
|
||||||
|
if !(isNil QGVAR(GarageUIBridge)) then {
|
||||||
|
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(GarageActionService) = createHashMapObject [GVAR(GarageActionServiceBaseClass)];
|
||||||
|
GVAR(GarageActionService)
|
||||||
146
arma/client/addons/garage/functions/fnc_initContextService.sqf
Normal file
146
arma/client/addons/garage/functions/fnc_initContextService.sqf
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initContextService.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the garage context service for local garage context and nearby state.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Garage context service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_garage_fnc_initContextService;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(GarageContextServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "GarageContextServiceBaseClass"],
|
||||||
|
["#create", compileFinal { _self set ["lastContext", createHashMap]; }],
|
||||||
|
["#delete", compileFinal { _self set ["lastContext", createHashMap]; }],
|
||||||
|
["createDefaultContext", compileFinal {
|
||||||
|
createHashMapFromArray [
|
||||||
|
["name", "Vehicle Garage"],
|
||||||
|
["anchorPosition", getPosATL player],
|
||||||
|
["sourceObject", objNull],
|
||||||
|
["spawnHeading", getDir player],
|
||||||
|
["spawnPosition", player getPos [8, getDir player]],
|
||||||
|
["spawnRadius", 6],
|
||||||
|
["nearbyRadius", 30]
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
["scanEntryValues", compileFinal {
|
||||||
|
params [["_values", [], [[]]], ["_state", createHashMap, [createHashMap]]];
|
||||||
|
{
|
||||||
|
if (_x isEqualType "" && { (_state getOrDefault ["name", "Vehicle Garage"]) isEqualTo "Vehicle Garage" }) then { _state set ["name", _x]; };
|
||||||
|
if (_x isEqualType "") then {
|
||||||
|
private _resolvedObject = _state getOrDefault ["sourceObject", objNull];
|
||||||
|
if (isNull _resolvedObject) then {
|
||||||
|
private _namedObject = missionNamespace getVariable [_x, objNull];
|
||||||
|
if (!isNull _namedObject) then { _state set ["sourceObject", _namedObject]; };
|
||||||
|
};
|
||||||
|
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo [] && { _x in allMapMarkers }) then { _state set ["anchorPosition", markerPos _x]; };
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (_x isEqualType objNull && { isNull (_state getOrDefault ["sourceObject", objNull]) }) then {
|
||||||
|
_state set ["sourceObject", _x];
|
||||||
|
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", getPosATL _x]; };
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (_x isEqualType 0 && { (_state getOrDefault ["spawnHeading", -1]) < 0 }) then { _state set ["spawnHeading", _x]; continue; };
|
||||||
|
if (_x isEqualType [] && { count _x > 0 }) then {
|
||||||
|
if ({ _x isEqualType 0 } count _x >= 2 && { ((_state getOrDefault ["offset", []]) isEqualTo []) || ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) }) then {
|
||||||
|
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then { _state set ["anchorPosition", _x]; } else { _state set ["offset", _x]; };
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
_self call ["scanEntryValues", [_x, _state]];
|
||||||
|
};
|
||||||
|
} forEach _values;
|
||||||
|
_state
|
||||||
|
}],
|
||||||
|
["resolveEntry", compileFinal {
|
||||||
|
params [["_entry", [], [[]]]];
|
||||||
|
private _state = createHashMapFromArray [["name", "Vehicle Garage"], ["anchorPosition", []], ["sourceObject", objNull], ["offset", []], ["spawnHeading", -1]];
|
||||||
|
_self call ["scanEntryValues", [_entry, _state]];
|
||||||
|
private _anchorPosition = _state getOrDefault ["anchorPosition", []];
|
||||||
|
private _offset = _state getOrDefault ["offset", []];
|
||||||
|
private _spawnPosition = if (_anchorPosition isEqualTo []) then { [] } else { if (_offset isEqualTo []) then { _anchorPosition } else { _anchorPosition vectorAdd _offset } };
|
||||||
|
createHashMapFromArray [["name", _state getOrDefault ["name", "Vehicle Garage"]], ["anchorPosition", _anchorPosition], ["sourceObject", _state getOrDefault ["sourceObject", objNull]], ["spawnHeading", _state getOrDefault ["spawnHeading", -1]], ["spawnPosition", _spawnPosition]]
|
||||||
|
}],
|
||||||
|
["resolveContext", compileFinal {
|
||||||
|
private _context = _self call ["createDefaultContext", []];
|
||||||
|
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
||||||
|
if !(_locations isEqualType []) exitWith { _self set ["lastContext", _context]; _context };
|
||||||
|
|
||||||
|
private _nearestEntry = [];
|
||||||
|
private _nearestDistance = 1e10;
|
||||||
|
{
|
||||||
|
private _entry = _self call ["resolveEntry", [_x]];
|
||||||
|
private _anchorPosition = _entry getOrDefault ["anchorPosition", []];
|
||||||
|
if (_anchorPosition isEqualTo []) then { continue; };
|
||||||
|
private _distance = player distance2D _anchorPosition;
|
||||||
|
if (_distance < _nearestDistance) then { _nearestDistance = _distance; _nearestEntry = _entry; };
|
||||||
|
} forEach _locations;
|
||||||
|
|
||||||
|
if (_nearestEntry isEqualTo []) exitWith { _self set ["lastContext", _context]; _context };
|
||||||
|
|
||||||
|
private _anchorPosition = _nearestEntry getOrDefault ["anchorPosition", []];
|
||||||
|
private _garageObject = _nearestEntry getOrDefault ["sourceObject", objNull];
|
||||||
|
private _garageName = _nearestEntry getOrDefault ["name", "Vehicle Garage"];
|
||||||
|
private _spawnHeading = _nearestEntry getOrDefault ["spawnHeading", getDir player];
|
||||||
|
if (_spawnHeading < 0) then { _spawnHeading = if (!isNull _garageObject) then { getDir _garageObject } else { getDir player }; };
|
||||||
|
|
||||||
|
private _spawnPosition = _nearestEntry getOrDefault ["spawnPosition", []];
|
||||||
|
if (_spawnPosition isEqualTo []) then { _spawnPosition = if (_anchorPosition isEqualTo []) then { player getPos [8, _spawnHeading] } else { _anchorPosition }; };
|
||||||
|
|
||||||
|
_context set ["name", _garageName];
|
||||||
|
_context set ["anchorPosition", _anchorPosition];
|
||||||
|
_context set ["sourceObject", _garageObject];
|
||||||
|
_context set ["spawnHeading", _spawnHeading];
|
||||||
|
_context set ["spawnPosition", _spawnPosition];
|
||||||
|
_self set ["lastContext", _context];
|
||||||
|
_context
|
||||||
|
}],
|
||||||
|
["getContext", compileFinal { _self call ["resolveContext", []] }],
|
||||||
|
["buildNearbyState", compileFinal {
|
||||||
|
private _context = _self call ["getContext", []];
|
||||||
|
private _anchorPosition = _context getOrDefault ["anchorPosition", []];
|
||||||
|
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
||||||
|
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
||||||
|
private _nearbyRadius = _context getOrDefault ["nearbyRadius", 30];
|
||||||
|
private _nearbyOrigin = [_anchorPosition, _spawnPosition] select (_anchorPosition isEqualTo []);
|
||||||
|
private _nearbyVehicles = [];
|
||||||
|
private _nearbyEntities = [];
|
||||||
|
private _candidateVehicles = [];
|
||||||
|
{ _candidateVehicles pushBackUnique _x; } forEach (_nearbyOrigin nearEntities [["Car", "Tank", "Air", "Ship"], _nearbyRadius]);
|
||||||
|
{ _candidateVehicles pushBackUnique _x; } forEach ((getPosATL player) nearEntities [["Car", "Tank", "Air", "Ship"], _nearbyRadius]);
|
||||||
|
{ _candidateVehicles pushBackUnique _x; } forEach (nearestObjects [_nearbyOrigin, ["AllVehicles"], _nearbyRadius]);
|
||||||
|
{ _candidateVehicles pushBackUnique _x; } forEach (nearestObjects [getPosATL player, ["AllVehicles"], _nearbyRadius]);
|
||||||
|
{
|
||||||
|
if (isNull _x) then { continue; };
|
||||||
|
if (_x isKindOf "CAManBase") then { continue; };
|
||||||
|
if !(_x isKindOf "Car" || _x isKindOf "Tank" || _x isKindOf "Air" || _x isKindOf "Ship") then { continue; };
|
||||||
|
_nearbyEntities pushBackUnique _x;
|
||||||
|
} forEach _candidateVehicles;
|
||||||
|
{
|
||||||
|
if (isNull _x) then { continue; };
|
||||||
|
private _builtVehicle = GVAR(GarageHelperService) call ["buildNearbyVehicle", [_x, _nearbyOrigin]];
|
||||||
|
if (_builtVehicle isEqualTo createHashMap) then { continue; };
|
||||||
|
_nearbyVehicles pushBack _builtVehicle;
|
||||||
|
} forEach _nearbyEntities;
|
||||||
|
private _nearbyVehiclePairs = _nearbyVehicles apply { [_x getOrDefault ["distance", 0], _x] };
|
||||||
|
_nearbyVehiclePairs sort true;
|
||||||
|
_nearbyVehicles = _nearbyVehiclePairs apply { _x param [1, createHashMap] };
|
||||||
|
private _spawnBlocked = ((_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]) + (nearestObjects [_spawnPosition, ["Car", "Tank", "Air", "Ship"], _spawnRadius])) isNotEqualTo [];
|
||||||
|
createHashMapFromArray [["session", createHashMapFromArray [["garageName", _context getOrDefault ["name", "Vehicle Garage"]], ["nearbyCount", count _nearbyVehicles], ["spawnBlocked", _spawnBlocked], ["spawnStatus", ["Ready", "Blocked"] select _spawnBlocked]]], ["nearby", createHashMapFromArray [["vehicles", _nearbyVehicles]]]]
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(GarageContextService) = createHashMapObject [GVAR(GarageContextServiceBaseClass)];
|
||||||
|
GVAR(GarageContextService)
|
||||||
@ -1,18 +1,27 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initCatalogService.sqf
|
* File: fnc_initHelperService.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-03-14
|
* Date: 2026-03-27
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the garage catalog service for vehicle metadata and UI-friendly shaping.
|
* Initializes the garage helper service for vehicle metadata and UI-friendly shaping.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Garage helper service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_garage_fnc_initHelperService;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(GarageCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(GarageHelperServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "GarageCatalogServiceBaseClass"],
|
["#type", "GarageHelperServiceBaseClass"],
|
||||||
["resolveCategory", compileFinal {
|
["resolveCategory", compileFinal {
|
||||||
params [["_className", "", [""]]];
|
params [["_className", "", [""]]];
|
||||||
|
|
||||||
@ -156,5 +165,5 @@ GVAR(GarageCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(GarageCatalogService) = createHashMapObject [GVAR(GarageCatalogServiceBaseClass)];
|
GVAR(GarageHelperService) = createHashMapObject [GVAR(GarageHelperServiceBaseClass)];
|
||||||
GVAR(GarageCatalogService)
|
GVAR(GarageHelperService)
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initPayloadService.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the garage payload service for browser hydrate payload composition.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Garage payload service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_garage_fnc_initPayloadService;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(GaragePayloadServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "GaragePayloadServiceBaseClass"],
|
||||||
|
["buildStoredVehicles", compileFinal {
|
||||||
|
private _garageMap = if (isNil QGVAR(GarageRepository)) then { createHashMap } else { GVAR(GarageRepository) call ["getState", []] };
|
||||||
|
private _storedVehicles = [];
|
||||||
|
{ _storedVehicles pushBack (GVAR(GarageHelperService) call ["buildStoredVehicle", [_x, _y]]); } forEach _garageMap;
|
||||||
|
private _storedVehiclePairs = _storedVehicles apply { [toLowerANSI (_x getOrDefault ["displayName", ""]), _x] };
|
||||||
|
_storedVehiclePairs sort true;
|
||||||
|
_storedVehiclePairs apply { _x param [1, createHashMap] }
|
||||||
|
}],
|
||||||
|
["buildPayload", compileFinal {
|
||||||
|
private _localState = GVAR(GarageContextService) call ["buildNearbyState", []];
|
||||||
|
private _storedVehicles = _self call ["buildStoredVehicles", []];
|
||||||
|
private _session = +(_localState getOrDefault ["session", createHashMap]);
|
||||||
|
_session set ["capacityUsed", count _storedVehicles];
|
||||||
|
_session set ["capacityMax", 5];
|
||||||
|
createHashMapFromArray [["session", _session], ["garage", createHashMapFromArray [["vehicles", _storedVehicles]]], ["nearby", +(_localState getOrDefault ["nearby", createHashMap])]]
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(GaragePayloadService) = createHashMapObject [GVAR(GaragePayloadServiceBaseClass)];
|
||||||
|
GVAR(GaragePayloadService)
|
||||||
@ -1,48 +1,44 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initClass.sqf
|
* File: fnc_initRepository.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-17
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-02-13
|
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the Garage class for managing player vehicles.
|
* Initializes the garage repository for persisted stored vehicle records.
|
||||||
* Provides methods for syncing, saving, and applying vehicles to the player's garage.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* Garage class object [HASHMAP OBJECT]
|
* Garage repository object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_garage_fnc_initClass
|
* call forge_client_garage_fnc_initRepository;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(GarageBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(GarageRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "GarageBaseClass"],
|
["#type", "GarageRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["uid", (getPlayerUID player)];
|
_self set ["uid", getPlayerUID player];
|
||||||
_self set ["garage", createHashMap];
|
_self set ["garage", createHashMap];
|
||||||
_self set ["isLoaded", false];
|
_self set ["isLoaded", false];
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
}],
|
}],
|
||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
private _garage = _self get "garage";
|
[SRPC(garage,requestInitGarage), [_uid]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
[SRPC(garage,requestInitGarage), [_uid, _garage]] call CFUNC(serverEvent);
|
systemChat format ["Garage loaded for %1", name player];
|
||||||
|
diag_log "[FORGE:Client:Garage] Garage Repository Initialized!";
|
||||||
systemChat format ["Garage loaded for %1", (name player)];
|
|
||||||
diag_log "[FORGE:Client:Garage] Garage Class Initialized!";
|
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
[SRPC(garage,requestSaveGarage), [_uid]] call CFUNC(serverEvent);
|
[SRPC(garage,requestSaveGarage), [_uid]] call CFUNC(serverEvent);
|
||||||
|
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
}],
|
}],
|
||||||
["sync", compileFinal {
|
["sync", compileFinal {
|
||||||
@ -50,14 +46,13 @@ GVAR(GarageBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _isLoaded = _self get "isLoaded";
|
private _isLoaded = _self get "isLoaded";
|
||||||
private _garage = createHashMap;
|
private _garage = createHashMap;
|
||||||
|
|
||||||
{ _garage set [_x, _y]; } forEach _data;
|
{ _garage set [_x, _y]; } forEach _data;
|
||||||
_self set ["garage", _garage];
|
_self set ["garage", _garage];
|
||||||
|
|
||||||
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
||||||
diag_log "[FORGE:Client:Garage] Sync completed";
|
diag_log "[FORGE:Client:Garage] Repository sync completed";
|
||||||
}],
|
}],
|
||||||
["getGarageState", compileFinal {
|
["getState", compileFinal {
|
||||||
_self getOrDefault ["garage", createHashMap]
|
_self getOrDefault ["garage", createHashMap]
|
||||||
}],
|
}],
|
||||||
["get", compileFinal {
|
["get", compileFinal {
|
||||||
@ -68,5 +63,5 @@ GVAR(GarageBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(GarageClass) = createHashMapObject [GVAR(GarageBaseClass)];
|
GVAR(GarageRepository) = createHashMapObject [GVAR(GarageRepositoryBaseClass)];
|
||||||
GVAR(GarageClass)
|
GVAR(GarageRepository)
|
||||||
@ -1,298 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File: fnc_initSessionService.sqf
|
|
||||||
* Author: IDSolutions
|
|
||||||
* Date: 2026-03-14
|
|
||||||
* Public: No
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Initializes the typed garage session service responsible for resolving the
|
|
||||||
* active garage context and building the browser hydrate payload.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
|
||||||
|
|
||||||
GVAR(GarageSessionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|
||||||
["#type", "GarageSessionServiceBaseClass"],
|
|
||||||
["#create", compileFinal {
|
|
||||||
_self set ["lastContext", createHashMap];
|
|
||||||
}],
|
|
||||||
["#delete", compileFinal {
|
|
||||||
_self set ["lastContext", createHashMap];
|
|
||||||
}],
|
|
||||||
["createDefaultContext", compileFinal {
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", "Vehicle Garage"],
|
|
||||||
["anchorPosition", getPosATL player],
|
|
||||||
["sourceObject", objNull],
|
|
||||||
["spawnHeading", getDir player],
|
|
||||||
["spawnPosition", player getPos [8, getDir player]],
|
|
||||||
["spawnRadius", 6],
|
|
||||||
["nearbyRadius", 30]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["scanEntryValues", compileFinal {
|
|
||||||
params [
|
|
||||||
["_values", [], [[]]],
|
|
||||||
["_state", createHashMap, [createHashMap]]
|
|
||||||
];
|
|
||||||
|
|
||||||
{
|
|
||||||
if (_x isEqualType "" && { (_state getOrDefault ["name", "Vehicle Garage"]) isEqualTo "Vehicle Garage" }) then {
|
|
||||||
_state set ["name", _x];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_x isEqualType "") then {
|
|
||||||
private _resolvedObject = _state getOrDefault ["sourceObject", objNull];
|
|
||||||
if (isNull _resolvedObject) then {
|
|
||||||
private _namedObject = missionNamespace getVariable [_x, objNull];
|
|
||||||
if (!isNull _namedObject) then {
|
|
||||||
_state set ["sourceObject", _namedObject];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo [] && { _x in allMapMarkers }) then {
|
|
||||||
_state set ["anchorPosition", markerPos _x];
|
|
||||||
};
|
|
||||||
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_x isEqualType objNull && { isNull (_state getOrDefault ["sourceObject", objNull]) }) then {
|
|
||||||
_state set ["sourceObject", _x];
|
|
||||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then {
|
|
||||||
_state set ["anchorPosition", getPosATL _x];
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_x isEqualType 0 && { (_state getOrDefault ["spawnHeading", -1]) < 0 }) then {
|
|
||||||
_state set ["spawnHeading", _x];
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_x isEqualType [] && { count _x > 0 }) then {
|
|
||||||
if (
|
|
||||||
{ _x isEqualType 0 } count _x >= 2 &&
|
|
||||||
{
|
|
||||||
((_state getOrDefault ["offset", []]) isEqualTo []) ||
|
|
||||||
((_state getOrDefault ["anchorPosition", []]) isEqualTo [])
|
|
||||||
}
|
|
||||||
) then {
|
|
||||||
if ((_state getOrDefault ["anchorPosition", []]) isEqualTo []) then {
|
|
||||||
_state set ["anchorPosition", _x];
|
|
||||||
} else {
|
|
||||||
_state set ["offset", _x];
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
_self call ["scanEntryValues", [_x, _state]];
|
|
||||||
};
|
|
||||||
} forEach _values;
|
|
||||||
|
|
||||||
_state
|
|
||||||
}],
|
|
||||||
["resolveEntry", compileFinal {
|
|
||||||
params [["_entry", [], [[]]]];
|
|
||||||
|
|
||||||
private _state = createHashMapFromArray [
|
|
||||||
["name", "Vehicle Garage"],
|
|
||||||
["anchorPosition", []],
|
|
||||||
["sourceObject", objNull],
|
|
||||||
["offset", []],
|
|
||||||
["spawnHeading", -1]
|
|
||||||
];
|
|
||||||
|
|
||||||
_self call ["scanEntryValues", [_entry, _state]];
|
|
||||||
|
|
||||||
private _anchorPosition = _state getOrDefault ["anchorPosition", []];
|
|
||||||
private _offset = _state getOrDefault ["offset", []];
|
|
||||||
private _spawnPosition = if (_anchorPosition isEqualTo []) then {
|
|
||||||
[]
|
|
||||||
} else {
|
|
||||||
if (_offset isEqualTo []) then {
|
|
||||||
_anchorPosition
|
|
||||||
} else {
|
|
||||||
_anchorPosition vectorAdd _offset
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["name", _state getOrDefault ["name", "Vehicle Garage"]],
|
|
||||||
["anchorPosition", _anchorPosition],
|
|
||||||
["sourceObject", _state getOrDefault ["sourceObject", objNull]],
|
|
||||||
["spawnHeading", _state getOrDefault ["spawnHeading", -1]],
|
|
||||||
["spawnPosition", _spawnPosition]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["resolveContext", compileFinal {
|
|
||||||
private _context = _self call ["createDefaultContext", []];
|
|
||||||
private _locations = (missionConfigFile >> "FORGE_CfgGarages" >> "locations") call BFUNC(getCfgData);
|
|
||||||
if !(_locations isEqualType []) exitWith {
|
|
||||||
_self set ["lastContext", _context];
|
|
||||||
_context
|
|
||||||
};
|
|
||||||
|
|
||||||
private _nearestEntry = [];
|
|
||||||
private _nearestDistance = 1e10;
|
|
||||||
|
|
||||||
{
|
|
||||||
private _entry = _self call ["resolveEntry", [_x]];
|
|
||||||
private _anchorPosition = _entry getOrDefault ["anchorPosition", []];
|
|
||||||
if (_anchorPosition isEqualTo []) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _distance = player distance2D _anchorPosition;
|
|
||||||
if (_distance < _nearestDistance) then {
|
|
||||||
_nearestDistance = _distance;
|
|
||||||
_nearestEntry = _entry;
|
|
||||||
};
|
|
||||||
} forEach _locations;
|
|
||||||
|
|
||||||
if (_nearestEntry isEqualTo []) exitWith {
|
|
||||||
_self set ["lastContext", _context];
|
|
||||||
_context
|
|
||||||
};
|
|
||||||
|
|
||||||
private _anchorPosition = _nearestEntry getOrDefault ["anchorPosition", []];
|
|
||||||
private _garageObject = _nearestEntry getOrDefault ["sourceObject", objNull];
|
|
||||||
private _garageName = _nearestEntry getOrDefault ["name", "Vehicle Garage"];
|
|
||||||
private _spawnHeading = _nearestEntry getOrDefault ["spawnHeading", getDir player];
|
|
||||||
if (_spawnHeading < 0) then {
|
|
||||||
_spawnHeading = if (!isNull _garageObject) then { getDir _garageObject } else { getDir player };
|
|
||||||
};
|
|
||||||
|
|
||||||
private _spawnPosition = _nearestEntry getOrDefault ["spawnPosition", []];
|
|
||||||
if (_spawnPosition isEqualTo []) then {
|
|
||||||
_spawnPosition = if (_anchorPosition isEqualTo []) then {
|
|
||||||
player getPos [8, _spawnHeading]
|
|
||||||
} else {
|
|
||||||
_anchorPosition
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
_context set ["name", _garageName];
|
|
||||||
_context set ["anchorPosition", _anchorPosition];
|
|
||||||
_context set ["sourceObject", _garageObject];
|
|
||||||
_context set ["spawnHeading", _spawnHeading];
|
|
||||||
_context set ["spawnPosition", _spawnPosition];
|
|
||||||
|
|
||||||
_self set ["lastContext", _context];
|
|
||||||
_context
|
|
||||||
}],
|
|
||||||
["getContext", compileFinal {
|
|
||||||
_self call ["resolveContext", []]
|
|
||||||
}],
|
|
||||||
["buildPayload", compileFinal {
|
|
||||||
private _context = _self call ["getContext", []];
|
|
||||||
private _garageMap = if (isNil QGVAR(GarageClass)) then {
|
|
||||||
createHashMap
|
|
||||||
} else {
|
|
||||||
GVAR(GarageClass) call ["getGarageState", []]
|
|
||||||
};
|
|
||||||
|
|
||||||
private _anchorPosition = _context getOrDefault ["anchorPosition", []];
|
|
||||||
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
|
||||||
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
|
||||||
private _nearbyRadius = _context getOrDefault ["nearbyRadius", 30];
|
|
||||||
private _nearbyOrigin = [_anchorPosition, _spawnPosition] select (_anchorPosition isEqualTo []);
|
|
||||||
|
|
||||||
private _storedVehicles = [];
|
|
||||||
private _nearbyVehicles = [];
|
|
||||||
private _nearbyEntities = [];
|
|
||||||
private _candidateVehicles = [];
|
|
||||||
|
|
||||||
{
|
|
||||||
_candidateVehicles pushBackUnique _x;
|
|
||||||
} forEach (_nearbyOrigin nearEntities [["Car", "Tank", "Air", "Ship"], _nearbyRadius]);
|
|
||||||
{
|
|
||||||
_candidateVehicles pushBackUnique _x;
|
|
||||||
} forEach ((getPosATL player) nearEntities [["Car", "Tank", "Air", "Ship"], _nearbyRadius]);
|
|
||||||
{
|
|
||||||
_candidateVehicles pushBackUnique _x;
|
|
||||||
} forEach (nearestObjects [_nearbyOrigin, ["AllVehicles"], _nearbyRadius]);
|
|
||||||
{
|
|
||||||
_candidateVehicles pushBackUnique _x;
|
|
||||||
} forEach (nearestObjects [getPosATL player, ["AllVehicles"], _nearbyRadius]);
|
|
||||||
|
|
||||||
{
|
|
||||||
if (isNull _x) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_x isKindOf "CAManBase") then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !(
|
|
||||||
_x isKindOf "Car" ||
|
|
||||||
_x isKindOf "Tank" ||
|
|
||||||
_x isKindOf "Air" ||
|
|
||||||
_x isKindOf "Ship"
|
|
||||||
) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
_nearbyEntities pushBackUnique _x;
|
|
||||||
} forEach _candidateVehicles;
|
|
||||||
|
|
||||||
{
|
|
||||||
_storedVehicles pushBack (
|
|
||||||
GVAR(GarageCatalogService) call ["buildStoredVehicle", [_x, _y]]
|
|
||||||
);
|
|
||||||
} forEach _garageMap;
|
|
||||||
|
|
||||||
private _storedVehiclePairs = _storedVehicles apply {
|
|
||||||
[toLowerANSI (_x getOrDefault ["displayName", ""]), _x]
|
|
||||||
};
|
|
||||||
_storedVehiclePairs sort true;
|
|
||||||
_storedVehicles = _storedVehiclePairs apply { _x param [1, createHashMap] };
|
|
||||||
|
|
||||||
{
|
|
||||||
if (isNull _x) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _builtVehicle = GVAR(GarageCatalogService) call ["buildNearbyVehicle", [_x, _nearbyOrigin]];
|
|
||||||
if (_builtVehicle isEqualTo createHashMap) then {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
_nearbyVehicles pushBack _builtVehicle;
|
|
||||||
} forEach _nearbyEntities;
|
|
||||||
|
|
||||||
private _nearbyVehiclePairs = _nearbyVehicles apply {
|
|
||||||
[_x getOrDefault ["distance", 0], _x]
|
|
||||||
};
|
|
||||||
_nearbyVehiclePairs sort true;
|
|
||||||
_nearbyVehicles = _nearbyVehiclePairs apply { _x param [1, createHashMap] };
|
|
||||||
|
|
||||||
private _spawnBlocked = (
|
|
||||||
(_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]) +
|
|
||||||
(nearestObjects [_spawnPosition, ["Car", "Tank", "Air", "Ship"], _spawnRadius])
|
|
||||||
) isNotEqualTo [];
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["session", createHashMapFromArray [
|
|
||||||
["garageName", _context getOrDefault ["name", "Vehicle Garage"]],
|
|
||||||
["capacityUsed", count _storedVehicles],
|
|
||||||
["capacityMax", 5],
|
|
||||||
["nearbyCount", count _nearbyVehicles],
|
|
||||||
["spawnBlocked", _spawnBlocked],
|
|
||||||
["spawnStatus", ["Ready", "Blocked"] select _spawnBlocked]
|
|
||||||
]],
|
|
||||||
["garage", createHashMapFromArray [
|
|
||||||
["vehicles", _storedVehicles]
|
|
||||||
]],
|
|
||||||
["nearby", createHashMapFromArray [
|
|
||||||
["vehicles", _nearbyVehicles]
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
GVAR(GarageSessionService) = createHashMapObject [GVAR(GarageSessionServiceBaseClass)];
|
|
||||||
GVAR(GarageSessionService)
|
|
||||||
@ -3,11 +3,20 @@
|
|||||||
/*
|
/*
|
||||||
* File: fnc_initUIBridge.sqf
|
* File: fnc_initUIBridge.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-03-14
|
* Date: 2026-03-27
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the garage UI bridge for browser control state and retrieve/store actions.
|
* Initializes the garage UI bridge for browser control state and UI events.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Garage UI bridge object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_garage_fnc_initUIBridge;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
@ -17,10 +26,6 @@ private _webUIBridgeDeclaration = _webUIDeclarations get "bridgeDeclaration";
|
|||||||
GVAR(GarageUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(GarageUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#base", _webUIBridgeDeclaration],
|
["#base", _webUIBridgeDeclaration],
|
||||||
["#type", "GarageUIBridgeBaseClass"],
|
["#type", "GarageUIBridgeBaseClass"],
|
||||||
["#create", compileFinal {
|
|
||||||
_self set ["pendingStoreVehicle", objNull];
|
|
||||||
_self set ["pendingRetrieve", createHashMap];
|
|
||||||
}],
|
|
||||||
["getActiveBrowserControl", compileFinal {
|
["getActiveBrowserControl", compileFinal {
|
||||||
private _display = uiNamespace getVariable ["RscGarage", displayNull];
|
private _display = uiNamespace getVariable ["RscGarage", displayNull];
|
||||||
if (isNull _display) exitWith {
|
if (isNull _display) exitWith {
|
||||||
@ -40,164 +45,13 @@ GVAR(GarageUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_screen call ["markReady", [true]];
|
_screen call ["markReady", [true]];
|
||||||
|
|
||||||
_self call ["flushPendingEvents", []];
|
_self call ["flushPendingEvents", []];
|
||||||
_self call ["sendEvent", ["garage::hydrate", GVAR(GarageSessionService) call ["buildPayload", []], _control]];
|
_self call ["sendEvent", ["garage::hydrate", GVAR(GaragePayloadService) call ["buildPayload", []], _control]];
|
||||||
}],
|
}],
|
||||||
["refreshGarage", compileFinal {
|
["refreshGarage", compileFinal {
|
||||||
private _control = _self call ["getActiveBrowserControl", []];
|
private _control = _self call ["getActiveBrowserControl", []];
|
||||||
if (isNull _control) exitWith { false };
|
if (isNull _control) exitWith { false };
|
||||||
|
|
||||||
_self call ["sendEvent", ["garage::sync", GVAR(GarageSessionService) call ["buildPayload", []], _control]]
|
_self call ["sendEvent", ["garage::sync", GVAR(GaragePayloadService) call ["buildPayload", []], _control]]
|
||||||
}],
|
|
||||||
["handleRetrieveRequest", compileFinal {
|
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
|
||||||
|
|
||||||
private _plate = _data getOrDefault ["plate", ""];
|
|
||||||
if (_plate isEqualTo "") exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [
|
|
||||||
["message", "Select a stored vehicle to retrieve."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _garageMap = if (isNil QGVAR(GarageClass)) then {
|
|
||||||
createHashMap
|
|
||||||
} else {
|
|
||||||
GVAR(GarageClass) call ["getGarageState", []]
|
|
||||||
};
|
|
||||||
private _vehicleData = _garageMap getOrDefault [_plate, createHashMap];
|
|
||||||
if (_vehicleData isEqualTo createHashMap) exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [
|
|
||||||
["message", "Stored vehicle record could not be found."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _context = GVAR(GarageSessionService) call ["getContext", []];
|
|
||||||
private _spawnPosition = _context getOrDefault ["spawnPosition", getPosATL player];
|
|
||||||
private _spawnHeading = _context getOrDefault ["spawnHeading", getDir player];
|
|
||||||
private _spawnRadius = _context getOrDefault ["spawnRadius", 6];
|
|
||||||
private _blockingVehicles = [];
|
|
||||||
{
|
|
||||||
_blockingVehicles pushBackUnique _x;
|
|
||||||
} forEach (_spawnPosition nearEntities [["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
|
||||||
{
|
|
||||||
_blockingVehicles pushBackUnique _x;
|
|
||||||
} forEach (nearestObjects [_spawnPosition, ["Car", "Tank", "Air", "Ship"], _spawnRadius]);
|
|
||||||
if (_blockingVehicles isNotEqualTo []) exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [
|
|
||||||
["message", "The garage spawn area is blocked."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _className = _vehicleData getOrDefault ["classname", ""];
|
|
||||||
if (_className isEqualTo "") exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::retrieve::failure", createHashMapFromArray [
|
|
||||||
["message", "Stored vehicle record is missing a classname."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _vehicle = createVehicle [_className, _spawnPosition, [], 0, "CAN_COLLIDE"];
|
|
||||||
_vehicle setDir _spawnHeading;
|
|
||||||
_vehicle setFuel (_vehicleData getOrDefault ["fuel", 0]);
|
|
||||||
_vehicle setDamage (_vehicleData getOrDefault ["damage", 0]);
|
|
||||||
|
|
||||||
private _hitPoints = _vehicleData getOrDefault ["hit_points", createHashMap];
|
|
||||||
private _hitPointNames = _hitPoints getOrDefault ["names", []];
|
|
||||||
private _hitPointValues = _hitPoints getOrDefault ["values", []];
|
|
||||||
for "_index" from 0 to ((count _hitPointNames) - 1) do {
|
|
||||||
_vehicle setHitPointDamage [_hitPointNames param [_index, ""], _hitPointValues param [_index, 0]];
|
|
||||||
};
|
|
||||||
|
|
||||||
_vehicle setVariable ["forge_garage_plate", _plate, true];
|
|
||||||
_vehicle setVariable ["forge_garage_owner_uid", getPlayerUID player, true];
|
|
||||||
|
|
||||||
_self set ["pendingRetrieve", createHashMapFromArray [
|
|
||||||
["plate", _plate],
|
|
||||||
["vehicle", _vehicle]
|
|
||||||
]];
|
|
||||||
|
|
||||||
[SRPC(garage,requestRetrieveVehicle), [getPlayerUID player, _plate]] call CFUNC(serverEvent);
|
|
||||||
}],
|
|
||||||
["handleStoreRequest", compileFinal {
|
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
|
||||||
|
|
||||||
private _netId = _data getOrDefault ["netId", ""];
|
|
||||||
if (_netId isEqualTo "") exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::store::failure", createHashMapFromArray [
|
|
||||||
["message", "Select a nearby vehicle to store."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _vehicle = objectFromNetId _netId;
|
|
||||||
if (isNull _vehicle) exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::store::failure", createHashMapFromArray [
|
|
||||||
["message", "The selected vehicle is no longer available."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (crew _vehicle isNotEqualTo []) exitWith {
|
|
||||||
_self call ["sendEvent", ["garage::store::failure", createHashMapFromArray [
|
|
||||||
["message", "All crew must exit the vehicle before storing it."]
|
|
||||||
]]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _rawHitPoints = getAllHitPointsDamage _vehicle;
|
|
||||||
private _hitPointsJson = toJSON (createHashMapFromArray [
|
|
||||||
["names", _rawHitPoints param [0, []]],
|
|
||||||
["selections", _rawHitPoints param [1, []]],
|
|
||||||
["values", _rawHitPoints param [2, []]]
|
|
||||||
]);
|
|
||||||
|
|
||||||
_self set ["pendingStoreVehicle", _vehicle];
|
|
||||||
[SRPC(garage,requestStoreVehicle), [
|
|
||||||
getPlayerUID player,
|
|
||||||
typeOf _vehicle,
|
|
||||||
fuel _vehicle,
|
|
||||||
damage _vehicle,
|
|
||||||
_hitPointsJson
|
|
||||||
]] call CFUNC(serverEvent);
|
|
||||||
}],
|
|
||||||
["handleActionResponse", compileFinal {
|
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
|
||||||
|
|
||||||
private _action = _payload getOrDefault ["action", ""];
|
|
||||||
private _success = _payload getOrDefault ["success", false];
|
|
||||||
private _message = _payload getOrDefault ["message", "Garage action failed."];
|
|
||||||
|
|
||||||
switch (_action) do {
|
|
||||||
case "retrieve": {
|
|
||||||
private _pendingRetrieve = _self getOrDefault ["pendingRetrieve", createHashMap];
|
|
||||||
private _vehicle = _pendingRetrieve getOrDefault ["vehicle", objNull];
|
|
||||||
|
|
||||||
if (!_success && { !isNull _vehicle }) then {
|
|
||||||
deleteVehicle _vehicle;
|
|
||||||
};
|
|
||||||
|
|
||||||
_self set ["pendingRetrieve", createHashMap];
|
|
||||||
_self call ["sendEvent", [[
|
|
||||||
"garage::retrieve::failure",
|
|
||||||
"garage::retrieve::success"
|
|
||||||
] select _success, createHashMapFromArray [["message", _message]]]];
|
|
||||||
};
|
|
||||||
case "store": {
|
|
||||||
private _vehicle = _self getOrDefault ["pendingStoreVehicle", objNull];
|
|
||||||
|
|
||||||
if (_success && { !isNull _vehicle }) then {
|
|
||||||
deleteVehicle _vehicle;
|
|
||||||
};
|
|
||||||
|
|
||||||
_self set ["pendingStoreVehicle", objNull];
|
|
||||||
_self call ["sendEvent", [[
|
|
||||||
"garage::store::failure",
|
|
||||||
"garage::store::success"
|
|
||||||
] select _success, createHashMapFromArray [["message", _message]]]];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
[] spawn {
|
|
||||||
sleep 0.05;
|
|
||||||
if !(isNil QGVAR(GarageUIBridge)) then {
|
|
||||||
GVAR(GarageUIBridge) call ["refreshGarage", []];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,50 +1,45 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initVGClass.sqf
|
* File: fnc_initVGRepository.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-16
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-02-13
|
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the Virtual Garage class for managing player garage unlocks.
|
* Initializes the virtual garage repository for BIS virtual garage state.
|
||||||
* Provides methods for syncing, saving, and applying virtual items to BIS Garage.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* vGarage class object [HASHMAP OBJECT]
|
* Virtual garage repository object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_garage_fnc_initVGClass;
|
* call forge_client_garage_fnc_initVGRepository;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(VGBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(VGRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "VGBaseClass"],
|
["#type", "VGRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
GVAR(isPreLoaded) = false;
|
GVAR(isPreLoaded) = false;
|
||||||
|
_self set ["uid", getPlayerUID player];
|
||||||
_self set ["uid", (getPlayerUID player)];
|
|
||||||
_self set ["vGarage", createHashMap];
|
_self set ["vGarage", createHashMap];
|
||||||
_self set ["isLoaded", false];
|
_self set ["isLoaded", false];
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
}],
|
}],
|
||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
private _vGarage = _self get "vGarage";
|
[SRPC(garage,requestInitVG), [_uid]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
[SRPC(garage,requestInitVG), [_uid, _vGarage]] call CFUNC(serverEvent);
|
systemChat format ["VGarage loaded for %1", name player];
|
||||||
|
diag_log "[FORGE:Client:VGarage] Repository Initialized!";
|
||||||
systemChat format ["VGarage loaded for %1", (name player)];
|
|
||||||
diag_log "[FORGE:Client:VGarage] VGarage Class Initialized!";
|
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
[SRPC(garage,requestSaveVG), [_uid]] call CFUNC(serverEvent);
|
[SRPC(garage,requestSaveVG), [_uid]] call CFUNC(serverEvent);
|
||||||
|
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
}],
|
}],
|
||||||
["sync", compileFinal {
|
["sync", compileFinal {
|
||||||
@ -55,7 +50,6 @@ GVAR(VGBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
{
|
{
|
||||||
_vGarage set [_x, _y];
|
_vGarage set [_x, _y];
|
||||||
|
|
||||||
switch (_x) do {
|
switch (_x) do {
|
||||||
case "cars": { _self call ["apply", ["cars"]]; };
|
case "cars": { _self call ["apply", ["cars"]]; };
|
||||||
case "armor": { _self call ["apply", ["armor"]]; };
|
case "armor": { _self call ["apply", ["armor"]]; };
|
||||||
@ -68,9 +62,8 @@ GVAR(VGBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
} forEach _data;
|
} forEach _data;
|
||||||
|
|
||||||
_self set ["vGarage", _vGarage];
|
_self set ["vGarage", _vGarage];
|
||||||
|
|
||||||
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
||||||
diag_log "[FORGE:Client:VGarage] Sync completed";
|
diag_log "[FORGE:Client:VGarage] Repository sync completed";
|
||||||
}],
|
}],
|
||||||
["get", compileFinal {
|
["get", compileFinal {
|
||||||
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
||||||
@ -83,7 +76,6 @@ GVAR(VGBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _vehicles = _self call ["get", [_key, []]];
|
private _vehicles = _self call ["get", [_key, []]];
|
||||||
private _appliedVehicles = [];
|
private _appliedVehicles = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
_appliedVehicles append [getText (configFile >> "CfgVehicles" >> _x >> "model"), [configFile >> "CfgVehicles" >> _x]];
|
_appliedVehicles append [getText (configFile >> "CfgVehicles" >> _x >> "model"), [configFile >> "CfgVehicles" >> _x]];
|
||||||
} forEach _vehicles;
|
} forEach _vehicles;
|
||||||
@ -100,5 +92,5 @@ GVAR(VGBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(VGClass) = createHashMapObject [GVAR(VGBaseClass)];
|
GVAR(VGRepository) = createHashMapObject [GVAR(VGRepositoryBaseClass)];
|
||||||
GVAR(VGClass)
|
GVAR(VGRepository)
|
||||||
@ -89,7 +89,7 @@ if !(GVAR(isPreLoaded)) then {
|
|||||||
private _nearVehicles = FORGE_VehSpawnPos nearEntities [["Car", "Tank", "Air", "Ship"], 5];
|
private _nearVehicles = FORGE_VehSpawnPos nearEntities [["Car", "Tank", "Air", "Ship"], 5];
|
||||||
if (_nearVehicles isNotEqualTo []) exitWith {
|
if (_nearVehicles isNotEqualTo []) exitWith {
|
||||||
private _params = ["warning", "Virtual Garage", "Vehicle spawn position is blocked. Please move the vehicle before accessing the garage.", 3000];
|
private _params = ["warning", "Virtual Garage", "Vehicle spawn position is blocked. Please move the vehicle before accessing the garage.", 3000];
|
||||||
EGVAR(notifications,NotificationClass) call ["create", _params];
|
EGVAR(notifications,NotificationService) call ["create", _params];
|
||||||
};
|
};
|
||||||
|
|
||||||
["Open", true] call BFUNC(garage);
|
["Open", true] call BFUNC(garage);
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
PREP(initLockerClass);
|
PREP(initRepository);
|
||||||
PREP(initVAClass);
|
PREP(initVARepository);
|
||||||
|
|||||||
@ -1,48 +1,48 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
if (isNil QGVAR(LockerClass)) then { call FUNC(initLockerClass); };
|
if (isNil QGVAR(LockerRepository)) then { call FUNC(initRepository); };
|
||||||
if (isNil QGVAR(VAClass)) then { call FUNC(initVAClass); };
|
if (isNil QGVAR(VARepository)) then { call FUNC(initVARepository); };
|
||||||
|
|
||||||
[QGVAR(initLocker), {
|
[QGVAR(initLocker), {
|
||||||
GVAR(LockerClass) call ["init", []];
|
GVAR(LockerRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitLocker), {
|
[QGVAR(responseInitLocker), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(LockerClass) call ["sync", [_data]];
|
GVAR(LockerRepository) call ["sync", [_data]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncLocker), {
|
[QGVAR(responseSyncLocker), {
|
||||||
params [["_data", createHashMap, [createHashMap, []]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap, []]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(LockerClass) call ["sync", [_data, _jip]];
|
GVAR(LockerRepository) call ["sync", [_data, _jip]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(initVA), {
|
[QGVAR(initVA), {
|
||||||
GVAR(VAClass) call ["init", []];
|
GVAR(VARepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitVA), {
|
[QGVAR(responseInitVA), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(VAClass) call ["sync", [_data]];
|
GVAR(VARepository) call ["sync", [_data]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncVA), {
|
[QGVAR(responseSyncVA), {
|
||||||
params [["_data", createHashMap, [createHashMap, []]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap, []]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(VAClass) call ["sync", [_data, _jip]];
|
GVAR(VARepository) call ["sync", [_data, _jip]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(garage,GarageClass) get "isLoaded";
|
EGVAR(garage,GarageRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initLocker), []] call CFUNC(localEvent);
|
[QGVAR(initLocker), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
GVAR(LockerClass) get "isLoaded";
|
GVAR(LockerRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initVA), []] call CFUNC(localEvent);
|
[QGVAR(initVA), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -1,31 +1,29 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initLockerClass.sqf
|
* File: fnc_initRepository.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-17
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-02-13
|
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the Locker class for managing player locker items.
|
* Initializes the locker repository for managing player locker items.
|
||||||
* Provides methods for syncing, saving, and applying locker items to the player's locker.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* Locker class object [HASHMAP OBJECT]
|
* Locker repository object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_locker_fnc_initLockerClass
|
* call forge_client_locker_fnc_initRepository;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(LockerRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "LockerBaseClass"],
|
["#type", "LockerRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["uid", (getPlayerUID player)];
|
_self set ["uid", getPlayerUID player];
|
||||||
_self set ["isLoaded", false];
|
_self set ["isLoaded", false];
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
_self set ["locker", createHashMap];
|
_self set ["locker", createHashMap];
|
||||||
@ -34,9 +32,10 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
|
|
||||||
[SRPC(locker,requestInitLocker), [_uid]] call CFUNC(serverEvent);
|
[SRPC(locker,requestInitLocker), [_uid]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
systemChat format ["Locker loaded for %1", (name player)];
|
systemChat format ["Locker loaded for %1", name player];
|
||||||
diag_log "[FORGE:Client:Locker] Locker Class Initialized!";
|
diag_log "[FORGE:Client:Locker] Locker Repository Initialized!";
|
||||||
}],
|
}],
|
||||||
["get", compileFinal {
|
["get", compileFinal {
|
||||||
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
||||||
@ -83,8 +82,8 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _cfgWeapons = configFile >> "CfgWeapons" >> _containerClass;
|
private _cfgWeapons = configFile >> "CfgWeapons" >> _containerClass;
|
||||||
private _itemInfoType = getNumber (_cfgWeapons >> "ItemInfo" >> "type");
|
private _itemInfoType = getNumber (_cfgWeapons >> "ItemInfo" >> "type");
|
||||||
private _isBackpack = isClass _cfgVehicles;
|
private _isBackpack = isClass _cfgVehicles;
|
||||||
private _isUniform = isClass _cfgWeapons && {_itemInfoType == TYPE_UNIFORM};
|
private _isUniform = isClass _cfgWeapons && { _itemInfoType == TYPE_UNIFORM };
|
||||||
private _isVest = isClass _cfgWeapons && {_itemInfoType == TYPE_VEST};
|
private _isVest = isClass _cfgWeapons && { _itemInfoType == TYPE_VEST };
|
||||||
|
|
||||||
if (!_isBackpack && !_isVest && !_isUniform) then { continue; };
|
if (!_isBackpack && !_isVest && !_isUniform) then { continue; };
|
||||||
|
|
||||||
@ -141,7 +140,6 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _weaponItems = weaponsItemsCargo _container;
|
private _weaponItems = weaponsItemsCargo _container;
|
||||||
{
|
{
|
||||||
// private _weapon = _x param [0, ""];
|
|
||||||
private _muzzle = _x param [1, ""];
|
private _muzzle = _x param [1, ""];
|
||||||
private _pointer = _x param [2, ""];
|
private _pointer = _x param [2, ""];
|
||||||
private _optic = _x param [3, ""];
|
private _optic = _x param [3, ""];
|
||||||
@ -149,7 +147,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _underbarrel = _x param [5, ""];
|
private _underbarrel = _x param [5, ""];
|
||||||
private _bipod = _x param [6, ""];
|
private _bipod = _x param [6, ""];
|
||||||
private _secondaryMag = _x param [7, ["", 0]];
|
private _secondaryMag = _x param [7, ["", 0]];
|
||||||
private _attachments = [_muzzle, _pointer, _optic, _underbarrel, _bipod] select {(_x isEqualType "") && {_x != ""}};
|
private _attachments = [_muzzle, _pointer, _optic, _underbarrel, _bipod] select { (_x isEqualType "") && { _x != "" } };
|
||||||
{
|
{
|
||||||
private _existing = _locker getOrDefault [_x, createHashMap];
|
private _existing = _locker getOrDefault [_x, createHashMap];
|
||||||
private _existingCount = _existing getOrDefault ["amount", 0];
|
private _existingCount = _existing getOrDefault ["amount", 0];
|
||||||
@ -162,7 +160,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
} forEach _attachments;
|
} forEach _attachments;
|
||||||
|
|
||||||
if (_primaryMag isNotEqualTo ["", 0]) then {
|
if (_primaryMag isNotEqualTo ["", 0]) then {
|
||||||
_primaryMag params ["_magClass", "_ammoCount"]; // TODO: Add ammo count to locker
|
_primaryMag params ["_magClass", "_ammoCount"];
|
||||||
if (_magClass != "") then {
|
if (_magClass != "") then {
|
||||||
private _existing = _locker getOrDefault [_magClass, createHashMap];
|
private _existing = _locker getOrDefault [_magClass, createHashMap];
|
||||||
private _existingCount = _existing getOrDefault ["amount", 0];
|
private _existingCount = _existing getOrDefault ["amount", 0];
|
||||||
@ -176,7 +174,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (_secondaryMag isNotEqualTo ["", 0]) then {
|
if (_secondaryMag isNotEqualTo ["", 0]) then {
|
||||||
_secondaryMag params ["_magClass", "_ammoCount"]; // TODO: Add ammo count to locker
|
_secondaryMag params ["_magClass", "_ammoCount"];
|
||||||
if (_magClass != "") then {
|
if (_magClass != "") then {
|
||||||
private _existing = _locker getOrDefault [_magClass, createHashMap];
|
private _existing = _locker getOrDefault [_magClass, createHashMap];
|
||||||
private _existingCount = _existing getOrDefault ["amount", 0];
|
private _existingCount = _existing getOrDefault ["amount", 0];
|
||||||
@ -204,7 +202,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_locker addEventHandler ["ContainerOpened", {
|
_locker addEventHandler ["ContainerOpened", {
|
||||||
params ["_container", "_unit"];
|
params ["_container", "_unit"];
|
||||||
|
|
||||||
private _index = GVAR(LockerClass) get "locker";
|
private _index = GVAR(LockerRepository) get "locker";
|
||||||
|
|
||||||
clearBackpackCargo _container;
|
clearBackpackCargo _container;
|
||||||
clearItemCargo _container;
|
clearItemCargo _container;
|
||||||
@ -227,7 +225,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
if (count _index > 25) then {
|
if (count _index > 25) then {
|
||||||
private _params = ["warning", "Over Capacity", "Locker has more then 25 items, please remove some items", 3000];
|
private _params = ["warning", "Over Capacity", "Locker has more then 25 items, please remove some items", 3000];
|
||||||
GVAR(NotificationClass) call ["create", _params];
|
GVAR(NotificationService) call ["create", _params];
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@ -235,17 +233,17 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
params ["_container", "_unit"];
|
params ["_container", "_unit"];
|
||||||
|
|
||||||
private _newLocker = createHashMap;
|
private _newLocker = createHashMap;
|
||||||
_newLocker = GVAR(LockerClass) call ["getCargo", [_container, _newLocker]];
|
_newLocker = GVAR(LockerRepository) call ["getCargo", [_container, _newLocker]];
|
||||||
_newLocker = GVAR(LockerClass) call ["getContainerItems", [_container, _newLocker]];
|
_newLocker = GVAR(LockerRepository) call ["getContainerItems", [_container, _newLocker]];
|
||||||
_newLocker = GVAR(LockerClass) call ["getAttachments", [_container, _newLocker]];
|
_newLocker = GVAR(LockerRepository) call ["getAttachments", [_container, _newLocker]];
|
||||||
|
|
||||||
private _uid = getPlayerUID _unit;
|
private _uid = getPlayerUID _unit;
|
||||||
[SRPC(locker,requestOverrideLocker), [_uid, _newLocker]] call CFUNC(serverEvent);
|
[SRPC(locker,requestOverrideLocker), [_uid, _newLocker]] call CFUNC(serverEvent);
|
||||||
GVAR(LockerClass) set ["locker", _newLocker];
|
GVAR(LockerRepository) set ["locker", _newLocker];
|
||||||
|
|
||||||
if (count _newLocker > 25) then {
|
if (count _newLocker > 25) then {
|
||||||
private _params = ["warning", "Over Capacity", "Locker has more then 25 items, please remove some items", 3000];
|
private _params = ["warning", "Over Capacity", "Locker has more then 25 items, please remove some items", 3000];
|
||||||
GVAR(NotificationClass) call ["create", _params];
|
GVAR(NotificationService) call ["create", _params];
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
}],
|
}],
|
||||||
@ -295,5 +293,5 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(LockerClass) = createHashMapObject [GVAR(LockerBaseClass)];
|
GVAR(LockerRepository) = createHashMapObject [GVAR(LockerRepositoryBaseClass)];
|
||||||
GVAR(LockerClass)
|
GVAR(LockerRepository)
|
||||||
@ -1,31 +1,29 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_init.sqf
|
* File: fnc_initVARepository.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-16
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-02-13
|
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the Virtual Arsenal class for managing player arsenal unlocks.
|
* Initializes the virtual arsenal repository for managing player arsenal unlocks.
|
||||||
* Provides methods for syncing, saving, and applying virtual items to BIS Arsenal.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* vArsenal class object [HASHMAP OBJECT]
|
* Virtual arsenal repository object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_locker_fnc_init;
|
* call forge_client_locker_fnc_initVARepository;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(VABaseClass) = compileFinal createHashMapFromArray [
|
GVAR(VARepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "VABaseClass"],
|
["#type", "VARepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["uid", (getPlayerUID player)];
|
_self set ["uid", getPlayerUID player];
|
||||||
_self set ["vArsenal", createHashMap];
|
_self set ["vArsenal", createHashMap];
|
||||||
_self set ["isLoaded", false];
|
_self set ["isLoaded", false];
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
@ -34,9 +32,10 @@ GVAR(VABaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
FORGE_Locker_Box = "ReammoBox_F" createVehicleLocal [0, 0, -999];
|
FORGE_Locker_Box = "ReammoBox_F" createVehicleLocal [0, 0, -999];
|
||||||
[SRPC(locker,requestInitVA), [_uid]] call CFUNC(serverEvent);
|
[SRPC(locker,requestInitVA), [_uid]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
systemChat format ["VArsenal loaded for %1", (name player)];
|
systemChat format ["VArsenal loaded for %1", name player];
|
||||||
diag_log "[FORGE:Client:VArsenal] VArsenal Class Initialized!";
|
diag_log "[FORGE:Client:VArsenal] Repository Initialized!";
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
private _uid = _self get "uid";
|
private _uid = _self get "uid";
|
||||||
@ -91,5 +90,5 @@ GVAR(VABaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
GVAR(VAClass) = createHashMapObject [GVAR(VABaseClass)];
|
GVAR(VARepository) = createHashMapObject [GVAR(VARepositoryBaseClass)];
|
||||||
GVAR(VAClass)
|
GVAR(VARepository)
|
||||||
@ -1,3 +1,3 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initNotificationClass);
|
PREP(initService);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(actor,ActorClass) get "isLoaded";
|
EGVAR(locker,VARepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
("NotificationHudLayer" call BFUNC(rscLayer)) cutRsc ["RscNotifications", "PLAIN"];
|
("NotificationHudLayer" call BFUNC(rscLayer)) cutRsc ["RscNotifications", "PLAIN"];
|
||||||
call FUNC(openUI);
|
call FUNC(openUI);
|
||||||
if (isNil QGVAR(NotificationClass)) then { call FUNC(initNotificationClass); };
|
if (isNil QGVAR(NotificationService)) then { call FUNC(initService); };
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|
||||||
[QGVAR(recieveNotification), {
|
[QGVAR(recieveNotification), {
|
||||||
params [["_type", "", [""]], ["_title", "", [""]], ["_content", "", [""]], ["_duration", 4000, [4000]]];
|
params [["_type", "", [""]], ["_title", "", [""]], ["_content", "", [""]], ["_duration", 4000, [4000]]];
|
||||||
|
|
||||||
playSound QGVAR(notify);
|
playSound QGVAR(notify);
|
||||||
GVAR(NotificationClass) call ["create", [_type, _title, _content, _duration]];
|
GVAR(NotificationService) call ["create", [_type, _title, _content, _duration]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
|
|||||||
@ -32,7 +32,7 @@ diag_log format ["[FORGE:Client:Notifications] Handling UI event: %1 with data:
|
|||||||
|
|
||||||
switch (_event) do {
|
switch (_event) do {
|
||||||
case "notifications::ready": {
|
case "notifications::ready": {
|
||||||
GVAR(NotificationClass) call ["init", []];
|
GVAR(NotificationService) call ["init", []];
|
||||||
};
|
};
|
||||||
default { hint format ["[FORGE:Client:Notifications] Unhandled event: %1", _event]; };
|
default { hint format ["[FORGE:Client:Notifications] Unhandled event: %1", _event]; };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,29 +1,27 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* File: fnc_initNotificationClass.sqf
|
* File: fnc_initService.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-01-28
|
* Date: 2026-03-27
|
||||||
* Last Update: 2026-01-30
|
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the notification class for managing player notifications.
|
* Initializes the notification service for client notification display.
|
||||||
* Provides methods for creating and displaying notifications.
|
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* Notification class object [HASHMAP OBJECT]
|
* Notification service object [HASHMAP OBJECT]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_notifications_fnc_initNotificationClass
|
* call forge_client_notifications_fnc_initService;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(NotificationClass) = createHashMapObject [[
|
GVAR(NotificationService) = createHashMapObject [[
|
||||||
["#type", "INotificationClass"],
|
["#type", "INotificationService"],
|
||||||
["#create", {
|
["#create", {
|
||||||
private _display = uiNamespace getVariable ["RscNotifications", nil];
|
private _display = uiNamespace getVariable ["RscNotifications", nil];
|
||||||
private _control = _display displayCtrl 1004;
|
private _control = _display displayCtrl 1004;
|
||||||
@ -37,8 +35,8 @@ GVAR(NotificationClass) = createHashMapObject [[
|
|||||||
_self call ["create", _params];
|
_self call ["create", _params];
|
||||||
_self set ["isLoaded", true];
|
_self set ["isLoaded", true];
|
||||||
|
|
||||||
systemChat format ["Notifications loaded for %1", (name player)];
|
systemChat format ["Notifications loaded for %1", name player];
|
||||||
diag_log "[FORGE:Client:Notifications] Notification Class Initialized!";
|
diag_log "[FORGE:Client:Notifications] Notification Service Initialized!";
|
||||||
}],
|
}],
|
||||||
["create", {
|
["create", {
|
||||||
params [["_type", "", ["info"]], ["_title", "", [""]], ["_content", "", [""]], ["_duration", 4000]];
|
params [["_type", "", ["info"]], ["_title", "", [""]], ["_content", "", [""]], ["_duration", 4000]];
|
||||||
@ -55,4 +53,4 @@ GVAR(NotificationClass) = createHashMapObject [[
|
|||||||
}]
|
}]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
GVAR(NotificationClass)
|
GVAR(NotificationService)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initClass);
|
PREP(initRepository);
|
||||||
PREP(initUIBridge);
|
PREP(initUIBridge);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -1,26 +1,31 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
if (isNil QGVAR(OrgClass)) then { call FUNC(initClass); };
|
if (isNil QGVAR(OrgRepository)) then { call FUNC(initRepository); };
|
||||||
if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); };
|
if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); };
|
||||||
|
|
||||||
[QGVAR(initOrg), {
|
[QGVAR(initOrg), {
|
||||||
GVAR(OrgClass) call ["init", []];
|
GVAR(OrgRepository) call ["init", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseInitOrg), {
|
[QGVAR(responseInitOrg), {
|
||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(OrgClass) call ["sync", [_data, true]];
|
GVAR(OrgRepository) call ["markLoaded", []];
|
||||||
GVAR(OrgUIBridge) call ["refreshPortal", []];
|
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncOrg), {
|
[QGVAR(responseSyncOrg), {
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(OrgClass) call ["sync", [_data, _jip]];
|
GVAR(OrgRepository) call ["markLoaded", []];
|
||||||
GVAR(OrgUIBridge) call ["refreshPortal", []];
|
GVAR(OrgUIBridge) call ["refreshPortal", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseHydrateOrg), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "org::sync", [""]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleHydrateResponse", [_payload, _bridgeEvent]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseCreateOrg), {
|
[QGVAR(responseCreateOrg), {
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
@ -46,7 +51,7 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); };
|
|||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(actor,ActorClass) get "isLoaded";
|
EGVAR(locker,VARepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initOrg), []] call CFUNC(localEvent);
|
[QGVAR(initOrg), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* File: fnc_handleUIEvents.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Handles the UI events.
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Handles the org UI events.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* 0: [CONTROL] - The control that triggered the event
|
||||||
|
* 1: [BOOL] - Whether the event is from a confirm dialog
|
||||||
|
* 2: [STRING] - The message containing the event data
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* None
|
* UI events handled [BOOL]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_org_fnc_handleUIEvents;
|
* call forge_client_org_fnc_handleUIEvents;
|
||||||
*
|
|
||||||
* Public: No
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
params ["_control", "_isConfirmDialog", "_message"];
|
params ["_control", "_isConfirmDialog", "_message"];
|
||||||
|
|||||||
@ -1,181 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File: fnc_initClass.sqf
|
|
||||||
* Author: IDSolutions
|
|
||||||
* Date: 2026-02-13
|
|
||||||
* Last Update: 2026-02-13
|
|
||||||
* Public: No
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Initializes the org class.
|
|
||||||
*
|
|
||||||
* Arguments:
|
|
||||||
* None
|
|
||||||
*
|
|
||||||
* Return Value:
|
|
||||||
* Org class object [HASHMAP OBJECT]
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* call forge_client_org_fnc_initClass
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma hemtt ignore_variables ["_self"]
|
|
||||||
GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|
||||||
["#type", "OrgBaseClass"],
|
|
||||||
["#create", compileFinal {
|
|
||||||
_self set ["uid", getPlayerUID player];
|
|
||||||
_self set ["org", createHashMap];
|
|
||||||
_self set ["isLoaded", false];
|
|
||||||
_self set ["lastSave", time];
|
|
||||||
|
|
||||||
private _org = createHashMap;
|
|
||||||
_org set ["id", ""];
|
|
||||||
_org set ["owner", ""];
|
|
||||||
_org set ["name", ""];
|
|
||||||
_org set ["funds", 0];
|
|
||||||
_org set ["reputation", 0];
|
|
||||||
_org set ["credit_lines", createHashMap];
|
|
||||||
_org set ["assets", createHashMap];
|
|
||||||
_org set ["fleet", createHashMap];
|
|
||||||
_org set ["members", createHashMap];
|
|
||||||
|
|
||||||
_self set ["org", _org];
|
|
||||||
}],
|
|
||||||
["init", compileFinal {
|
|
||||||
private _uid = _self get "uid";
|
|
||||||
private _org = _self get "org";
|
|
||||||
|
|
||||||
[SRPC(org,requestInitOrg), [_uid, _org]] call CFUNC(serverEvent);
|
|
||||||
|
|
||||||
systemChat format ["Org loaded for %1", (name player)];
|
|
||||||
diag_log "[FORGE:Client:Org] Org Class Initialized!";
|
|
||||||
}],
|
|
||||||
["save", compileFinal {
|
|
||||||
params [["_sync", false, [false]]];
|
|
||||||
|
|
||||||
private _uid = _self get "uid";
|
|
||||||
[SRPC(org,requestSaveOrg), [_uid, _sync]] call CFUNC(serverEvent);
|
|
||||||
|
|
||||||
_self set ["lastSave", time];
|
|
||||||
}],
|
|
||||||
["sync", compileFinal {
|
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
|
||||||
|
|
||||||
private _isLoaded = _self get "isLoaded";
|
|
||||||
private _org = _self get "org";
|
|
||||||
|
|
||||||
{ _org set [_x, _y]; } forEach _data;
|
|
||||||
_self set ["org", _org];
|
|
||||||
|
|
||||||
if !(_isLoaded) then { _self set ["isLoaded", true]; };
|
|
||||||
diag_log "[FORGE:Client:Org] Sync completed";
|
|
||||||
}],
|
|
||||||
["buildPortalPayload", compileFinal {
|
|
||||||
private _orgData = _self get "org";
|
|
||||||
|
|
||||||
private _name = _orgData get "name";
|
|
||||||
private _id = _orgData get "id";
|
|
||||||
private _ownerUid = _orgData get "owner";
|
|
||||||
private _funds = _orgData get "funds";
|
|
||||||
private _reputation = _orgData get "reputation";
|
|
||||||
private _creditLinesRaw = _orgData getOrDefault ["credit_lines", createHashMap];
|
|
||||||
private _assetsRaw = _orgData get "assets";
|
|
||||||
private _fleetRaw = _orgData get "fleet";
|
|
||||||
private _membersRaw = _orgData get "members";
|
|
||||||
private _isDefaultOrg = (_orgData getOrDefault ["default", false])
|
|
||||||
|| {toLower _id isEqualTo "default"}
|
|
||||||
|| {toLower _ownerUid isEqualTo "server"};
|
|
||||||
|
|
||||||
private _playerName = name player;
|
|
||||||
private _playerUid = getPlayerUID player;
|
|
||||||
private _playerVar = vehicleVarName player;
|
|
||||||
private _sessionRole = "Member";
|
|
||||||
private _sessionIsCeo = _isDefaultOrg && {_playerVar isEqualTo "ceo"};
|
|
||||||
private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server");
|
|
||||||
|
|
||||||
private _membersList = [];
|
|
||||||
{
|
|
||||||
private _memberData = _y;
|
|
||||||
private _memberName = _memberData getOrDefault ["name", "Unknown"];
|
|
||||||
private _memberUid = _memberData getOrDefault ["uid", ""];
|
|
||||||
|
|
||||||
if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then { _ownerName = _memberName; };
|
|
||||||
if (_memberUid isEqualTo _playerUid) then { _sessionRole = "Member"; };
|
|
||||||
|
|
||||||
_membersList pushBack (createHashMapFromArray [
|
|
||||||
["uid", _memberUid],
|
|
||||||
["name", _memberName]
|
|
||||||
]);
|
|
||||||
} forEach _membersRaw;
|
|
||||||
|
|
||||||
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _playerUid }) then { _ownerName = _playerName; };
|
|
||||||
if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; };
|
|
||||||
if (_ownerUid isEqualTo _playerUid) then { _sessionRole = "Leader"; };
|
|
||||||
|
|
||||||
private _assetsList = [];
|
|
||||||
{
|
|
||||||
private _assetData = _y;
|
|
||||||
_assetsList pushBack (createHashMapFromArray [
|
|
||||||
["name", _assetData getOrDefault ["name", "Unknown Asset"]],
|
|
||||||
["type", _assetData getOrDefault ["type", "items"]],
|
|
||||||
["quantity", str (_assetData getOrDefault ["quantity", 0])]
|
|
||||||
]);
|
|
||||||
} forEach _assetsRaw;
|
|
||||||
|
|
||||||
private _fleetList = [];
|
|
||||||
{
|
|
||||||
private _vehicleData = _y;
|
|
||||||
_fleetList pushBack (createHashMapFromArray [
|
|
||||||
["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]],
|
|
||||||
["type", _vehicleData getOrDefault ["type", "other"]],
|
|
||||||
["status", _vehicleData getOrDefault ["status", "Unknown"]],
|
|
||||||
["damage", _vehicleData getOrDefault ["damage", "0%"]]
|
|
||||||
]);
|
|
||||||
} forEach _fleetRaw;
|
|
||||||
|
|
||||||
private _creditLinesList = [];
|
|
||||||
{
|
|
||||||
private _creditLineData = _y;
|
|
||||||
_creditLinesList pushBack (createHashMapFromArray [
|
|
||||||
["uid", _creditLineData getOrDefault ["uid", _x]],
|
|
||||||
["member", _creditLineData getOrDefault ["name", "Unknown Member"]],
|
|
||||||
["amount", _creditLineData getOrDefault ["amount", 0]]
|
|
||||||
]);
|
|
||||||
} forEach _creditLinesRaw;
|
|
||||||
|
|
||||||
createHashMapFromArray [
|
|
||||||
["session", createHashMapFromArray [
|
|
||||||
["actorName", _playerName],
|
|
||||||
["actorUid", _playerUid],
|
|
||||||
["role", _sessionRole],
|
|
||||||
["ceo", _sessionIsCeo]
|
|
||||||
]],
|
|
||||||
["portalData", createHashMapFromArray [
|
|
||||||
["org", createHashMapFromArray [
|
|
||||||
["name", _name],
|
|
||||||
["tag", _id],
|
|
||||||
["owner", _ownerName],
|
|
||||||
["ownerUid", _ownerUid],
|
|
||||||
["isDefault", _isDefaultOrg]
|
|
||||||
]],
|
|
||||||
["funds", _funds],
|
|
||||||
["reputation", _reputation],
|
|
||||||
["creditLines", _creditLinesList],
|
|
||||||
["members", _membersList],
|
|
||||||
["fleet", _fleetList],
|
|
||||||
["assets", _assetsList],
|
|
||||||
["activity", []]
|
|
||||||
]]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["get", compileFinal {
|
|
||||||
params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]];
|
|
||||||
|
|
||||||
private _org = _self get "org";
|
|
||||||
_org getOrDefault [_key, _default];
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
GVAR(OrgClass) = createHashMapObject [GVAR(OrgBaseClass)];
|
|
||||||
GVAR(OrgClass)
|
|
||||||
44
arma/client/addons/org/functions/fnc_initRepository.sqf
Normal file
44
arma/client/addons/org/functions/fnc_initRepository.sqf
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initRepository.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the org repository for client org lifecycle state.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Org repository object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_client_org_fnc_initRepository;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(OrgRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "OrgRepositoryBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["uid", getPlayerUID player];
|
||||||
|
_self set ["isLoaded", false];
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
}],
|
||||||
|
["init", compileFinal {
|
||||||
|
[SRPC(org,requestInitOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
|
systemChat format ["Org loaded for %1", name player];
|
||||||
|
diag_log "[FORGE:Client:Org] Org Repository Initialized!";
|
||||||
|
}],
|
||||||
|
["markLoaded", compileFinal {
|
||||||
|
if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; };
|
||||||
|
true
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(OrgRepository) = createHashMapObject [GVAR(OrgRepositoryBaseClass)];
|
||||||
|
GVAR(OrgRepository)
|
||||||
@ -50,20 +50,42 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_self call ["setActiveBrowserControl", [_control]];
|
_self call ["setActiveBrowserControl", [_control]];
|
||||||
_control
|
_control
|
||||||
}],
|
}],
|
||||||
|
["hasOpenScreen", compileFinal {
|
||||||
|
private _screen = _self call ["getScreen", []];
|
||||||
|
private _control = _self call ["getActiveBrowserControl", []];
|
||||||
|
|
||||||
|
!(isNull _control) && { _screen call ["isReady", []] }
|
||||||
|
}],
|
||||||
|
["requestHydrate", compileFinal {
|
||||||
|
params [["_bridgeEvent", "org::sync", [""]]];
|
||||||
|
|
||||||
|
if !(_self call ["hasOpenScreen", []]) exitWith { false };
|
||||||
|
|
||||||
|
private _event = _bridgeEvent;
|
||||||
|
if !(_event in ["org::login::success", "org::create::success", "org::sync"]) then {
|
||||||
|
_event = "org::sync";
|
||||||
|
};
|
||||||
|
|
||||||
|
[SRPC(org,requestHydrateOrg), [getPlayerUID player, _event]] call CFUNC(serverEvent);
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["handleHydrateResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "org::sync", [""]]];
|
||||||
|
|
||||||
|
if !(_self call ["hasOpenScreen", []]) exitWith { false };
|
||||||
|
|
||||||
|
private _event = _bridgeEvent;
|
||||||
|
if !(_event in ["org::login::success", "org::create::success", "org::sync"]) then {
|
||||||
|
_event = "org::sync";
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_event, _payload, _self call ["getActiveBrowserControl", []]]]
|
||||||
|
}],
|
||||||
["handleLoginRequest", compileFinal {
|
["handleLoginRequest", compileFinal {
|
||||||
params [["_control", controlNull, [controlNull]]];
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
private _orgData = GVAR(OrgClass) get "org";
|
_self call ["setActiveBrowserControl", [_control]];
|
||||||
private _orgId = _orgData getOrDefault ["id", ""];
|
_self call ["requestHydrate", ["org::login::success"]];
|
||||||
private _orgName = _orgData getOrDefault ["name", ""];
|
|
||||||
|
|
||||||
if (_orgId isEqualTo "" && { _orgName isEqualTo "" }) exitWith {
|
|
||||||
_self call ["sendEvent", ["org::login::failure", createHashMapFromArray [
|
|
||||||
["message", "No organization data is available for this player."]
|
|
||||||
], _control]];
|
|
||||||
};
|
|
||||||
|
|
||||||
_self call ["sendEvent", ["org::login::success", GVAR(OrgClass) call ["buildPortalPayload", []], _control]];
|
|
||||||
}],
|
}],
|
||||||
["handleCreateRequest", compileFinal {
|
["handleCreateRequest", compileFinal {
|
||||||
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
|
params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]];
|
||||||
@ -91,11 +113,11 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
], _control]];
|
], _control]];
|
||||||
};
|
};
|
||||||
|
|
||||||
private _orgData = _payload getOrDefault ["org", createHashMap];
|
if !(isNull _control) then {
|
||||||
GVAR(OrgClass) call ["sync", [_orgData, true]];
|
_self call ["setActiveBrowserControl", [_control]];
|
||||||
|
};
|
||||||
|
|
||||||
if (isNull _control) exitWith {};
|
_self call ["requestHydrate", ["org::create::success"]];
|
||||||
_self call ["sendEvent", ["org::create::success", GVAR(OrgClass) call ["buildPortalPayload", []], _control]];
|
|
||||||
}],
|
}],
|
||||||
["handleDisbandResponse", compileFinal {
|
["handleDisbandResponse", compileFinal {
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
@ -155,10 +177,7 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
[SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent);
|
[SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
["refreshPortal", compileFinal {
|
["refreshPortal", compileFinal {
|
||||||
private _control = _self call ["getActiveBrowserControl", []];
|
_self call ["requestHydrate", ["org::sync"]]
|
||||||
if (isNull _control) exitWith { false };
|
|
||||||
|
|
||||||
_self call ["sendEvent", ["org::sync", GVAR(OrgClass) call ["buildPortalPayload", []], _control]]
|
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
#include "..\script_component.hpp"
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* File: fnc_openUI.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Opens the player interaction interface.
|
* Date: 2026-03-27
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Opens the org UI.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* None
|
* UI opened [BOOL]
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* call forge_client_org_fnc_openUI;
|
* call forge_client_org_fnc_openUI;
|
||||||
*
|
|
||||||
* Public: No
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private _display = createDialog ["RscOrg", true];
|
private _display = createDialog ["RscOrg", true];
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -136,8 +136,18 @@
|
|||||||
|
|
||||||
OrgPortal.store.setCreditLines((currentLines) => {
|
OrgPortal.store.setCreditLines((currentLines) => {
|
||||||
const nextLine = {
|
const nextLine = {
|
||||||
amount: payloadData.amount || 0,
|
amount: payloadData.availableAmount || payloadData.amount || 0,
|
||||||
|
amountDue: payloadData.amountDue || 0,
|
||||||
|
approvedAmount:
|
||||||
|
payloadData.approvedAmount ||
|
||||||
|
payloadData.availableAmount ||
|
||||||
|
payloadData.amount ||
|
||||||
|
0,
|
||||||
|
availableAmount:
|
||||||
|
payloadData.availableAmount || payloadData.amount || 0,
|
||||||
|
interestRate: payloadData.interestRate || 0.1,
|
||||||
member: payloadData.memberName || "",
|
member: payloadData.memberName || "",
|
||||||
|
outstandingPrincipal: payloadData.outstandingPrincipal || 0,
|
||||||
uid: payloadData.memberUid || "",
|
uid: payloadData.memberUid || "",
|
||||||
};
|
};
|
||||||
const matchIndex = currentLines.findIndex(
|
const matchIndex = currentLines.findIndex(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user