Merge branch 'development' into master
This commit is contained in:
commit
5a032a45d9
@ -33,7 +33,7 @@ GVAR(ActorRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
[SRPC(actor,requestInitActor), [_uid]] call CFUNC(serverEvent);
|
[SRPC(actor,requestInitActor), [_uid]] call CFUNC(serverEvent);
|
||||||
_self set ["lastSave", time];
|
_self set ["lastSave", time];
|
||||||
|
|
||||||
systemChat format ["Actor loaded for %1", name player];
|
systemChat format ["Loading actor for %1", name player];
|
||||||
diag_log "[FORGE:Client:Actor] Actor Repository Initialized!";
|
diag_log "[FORGE:Client:Actor] Actor Repository Initialized!";
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
|
|||||||
@ -55,7 +55,7 @@ if (isNil QGVAR(VGRepository)) then { call FUNC(initVGRepository); };
|
|||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(bank,BankRepository) get "isLoaded";
|
EGVAR(actor,ActorRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initGarage), []] call CFUNC(localEvent);
|
[QGVAR(initGarage), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -36,7 +36,7 @@ if (isNil QGVAR(VARepository)) then { call FUNC(initVARepository); };
|
|||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(garage,GarageRepository) get "isLoaded";
|
EGVAR(actor,ActorRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initLocker), []] call CFUNC(localEvent);
|
[QGVAR(initLocker), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -50,8 +50,20 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); };
|
|||||||
GVAR(OrgUIBridge) call ["handleCreditLineResponse", [_payload]];
|
GVAR(OrgUIBridge) call ["handleCreditLineResponse", [_payload]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseInviteOrg), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleInviteResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseInviteDecision), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleInviteDecisionResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(locker,VARepository) get "isLoaded";
|
EGVAR(actor,ActorRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
[QGVAR(initOrg), []] call CFUNC(localEvent);
|
[QGVAR(initOrg), []] call CFUNC(localEvent);
|
||||||
}] call CFUNC(waitUntilAndExecute);
|
}] call CFUNC(waitUntilAndExecute);
|
||||||
|
|||||||
@ -46,6 +46,15 @@ switch (_event) do {
|
|||||||
case "org::credit::request": {
|
case "org::credit::request": {
|
||||||
GVAR(OrgUIBridge) call ["requestCreditLine", [_data]];
|
GVAR(OrgUIBridge) call ["requestCreditLine", [_data]];
|
||||||
};
|
};
|
||||||
|
case "org::invite::request": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestInvite", [_data]];
|
||||||
|
};
|
||||||
|
case "org::invite::accept": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestAcceptInvite", [_data]];
|
||||||
|
};
|
||||||
|
case "org::invite::decline": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestDeclineInvite", [_data]];
|
||||||
|
};
|
||||||
case "org::ready": {
|
case "org::ready": {
|
||||||
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -161,6 +161,26 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}],
|
}],
|
||||||
|
["handleInviteResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::invite::failure",
|
||||||
|
"org::invite::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
|
["handleInviteDecisionResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::invite::decision::failure",
|
||||||
|
"org::invite::decision::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
["requestDisband", compileFinal {
|
["requestDisband", compileFinal {
|
||||||
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
@ -176,6 +196,25 @@ 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);
|
||||||
}],
|
}],
|
||||||
|
["requestInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _targetUid = _data getOrDefault ["targetUid", ""];
|
||||||
|
private _targetName = _data getOrDefault ["targetName", ""];
|
||||||
|
[SRPC(org,requestInviteOrgMember), [getPlayerUID player, _targetUid, _targetName]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["requestAcceptInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _orgID = _data getOrDefault ["orgId", ""];
|
||||||
|
[SRPC(org,requestAcceptOrgInvite), [getPlayerUID player, _orgID]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["requestDeclineInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _orgID = _data getOrDefault ["orgId", ""];
|
||||||
|
[SRPC(org,requestDeclineOrgInvite), [getPlayerUID player, _orgID]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
["refreshPortal", compileFinal {
|
["refreshPortal", compileFinal {
|
||||||
_self call ["requestHydrate", ["org::sync"]]
|
_self call ["requestHydrate", ["org::sync"]]
|
||||||
}]
|
}]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -80,6 +80,57 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestInvitePlayer(payload) {
|
||||||
|
const sent = sendEvent("org::invite::request", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestAcceptInvite(payload) {
|
||||||
|
const sent = sendEvent("org::invite::accept", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestDeclineInvite(payload) {
|
||||||
|
const sent = sendEvent("org::invite::decline", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bridge.on("org::login::success", (payloadData) => {
|
bridge.on("org::login::success", (payloadData) => {
|
||||||
store.completeLogin(payloadData);
|
store.completeLogin(payloadData);
|
||||||
});
|
});
|
||||||
@ -128,6 +179,50 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::success", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
payloadData.message || "Organization invite sent.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::failure", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to send organization invite.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::decision::success", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
payloadData.message || "Organization invite updated.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::decision::failure", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to update organization invite.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bridge.on("org::member::creditUpdated", (payloadData) => {
|
bridge.on("org::member::creditUpdated", (payloadData) => {
|
||||||
const OrgPortal = window.OrgPortal;
|
const OrgPortal = window.OrgPortal;
|
||||||
if (!OrgPortal || !OrgPortal.store) {
|
if (!OrgPortal || !OrgPortal.store) {
|
||||||
@ -234,6 +329,9 @@
|
|||||||
requestDisbandOrg,
|
requestDisbandOrg,
|
||||||
requestLeaveOrg,
|
requestLeaveOrg,
|
||||||
requestCreditLine,
|
requestCreditLine,
|
||||||
|
requestInvitePlayer,
|
||||||
|
requestAcceptInvite,
|
||||||
|
requestDeclineInvite,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -19,6 +19,107 @@ ${scopeSelector} .org-name-list {
|
|||||||
scrollbar-color: #94a3b8 #e2e8f0;
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-kicker {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-tool-btn {
|
||||||
|
position: relative;
|
||||||
|
width: 2.4rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-tool-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -0.25rem;
|
||||||
|
right: -0.25rem;
|
||||||
|
min-width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #b91c1c;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.5rem);
|
||||||
|
right: 0;
|
||||||
|
width: min(24rem, 100%);
|
||||||
|
max-height: 22rem;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 18px 45px rgb(15 23 42 / 0.18);
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-menu-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-menu-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-menu-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-row,
|
||||||
${scopeSelector} .org-name-row {
|
${scopeSelector} .org-name-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -39,13 +140,56 @@ ${scopeSelector} .org-name-row button {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-inline-actions,
|
||||||
|
${scopeSelector} .org-invite-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-empty {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
${scopeSelector} .org-name-row {
|
${scopeSelector} .org-members-head {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
${scopeSelector} .org-name-row button {
|
${scopeSelector} .org-members-tools {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-invite-menu {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row,
|
||||||
|
${scopeSelector} .org-invite-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-row button,
|
||||||
|
${scopeSelector} .org-inline-actions,
|
||||||
|
${scopeSelector} .org-invite-actions {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +200,8 @@ ${scopeSelector} .org-name-row button {
|
|||||||
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
||||||
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
|
const pendingInvites = store.getPendingInvites();
|
||||||
|
const inviteMenuOpen = store.getInviteMenuOpen();
|
||||||
const allowMemberManagement = getters.canManageMembers();
|
const allowMemberManagement = getters.canManageMembers();
|
||||||
ensureScopedStyle("portal-members-card", membersCardCss);
|
ensureScopedStyle("portal-members-card", membersCardCss);
|
||||||
|
|
||||||
@ -68,6 +214,217 @@ ${scopeSelector} .org-name-row button {
|
|||||||
body: h(
|
body: h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-name-list" },
|
{ className: "org-name-list" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-copy" },
|
||||||
|
h("h4", { className: "org-members-kicker" }, "Roster"),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
{ className: "org-members-subtitle" },
|
||||||
|
"Manage membership and review incoming organization invites.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-tools" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
"org-secondary-btn org-icon-btn org-tool-btn",
|
||||||
|
title: "Pending invitations",
|
||||||
|
"aria-label": "Pending invitations",
|
||||||
|
onClick: () => actions.toggleInviteMenu(),
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
className: "org-icon",
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
h("path", {
|
||||||
|
d: "M15 17h5l-1.4-1.4A2 2 0 0 1 18 14.2V11a6 6 0 1 0-12 0v3.2a2 2 0 0 1-.6 1.4L4 17h5",
|
||||||
|
}),
|
||||||
|
h("path", { d: "M9.73 21a2 2 0 0 0 4.54 0" }),
|
||||||
|
),
|
||||||
|
pendingInvites.length > 0
|
||||||
|
? h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-tool-badge" },
|
||||||
|
String(pendingInvites.length),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
allowMemberManagement
|
||||||
|
? h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
"org-secondary-btn org-icon-btn org-tool-btn",
|
||||||
|
title: "Invite player",
|
||||||
|
"aria-label": "Invite player",
|
||||||
|
onClick: () =>
|
||||||
|
actions.openModal("invite"),
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
className: "org-icon",
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
h("path", { d: "M12 5v14" }),
|
||||||
|
h("path", { d: "M5 12h14" }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
inviteMenuOpen
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-invite-menu" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-invite-menu-head" },
|
||||||
|
h(
|
||||||
|
"h4",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-invite-menu-title",
|
||||||
|
},
|
||||||
|
"Pending Invites",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
"org-secondary-btn org-icon-btn org-tool-btn",
|
||||||
|
title: "Close invites",
|
||||||
|
"aria-label": "Close invites",
|
||||||
|
onClick: () =>
|
||||||
|
actions.closeInviteMenu(),
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
className: "org-icon",
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
h("path", { d: "M18 6 6 18" }),
|
||||||
|
h("path", { d: "m6 6 12 12" }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pendingInvites.length === 0
|
||||||
|
? h(
|
||||||
|
"p",
|
||||||
|
{
|
||||||
|
className: "org-members-empty",
|
||||||
|
},
|
||||||
|
"No incoming organization invites.",
|
||||||
|
)
|
||||||
|
: h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-invite-menu-list",
|
||||||
|
},
|
||||||
|
...pendingInvites.map((invite) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-invite-row",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-name-copy",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
invite.orgName ||
|
||||||
|
"Unknown Organization",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-name-meta",
|
||||||
|
},
|
||||||
|
"Invited by ",
|
||||||
|
invite.inviterName ||
|
||||||
|
"Unknown",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"org-invite-actions",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className:
|
||||||
|
"org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.declineInvite(
|
||||||
|
String(
|
||||||
|
invite.orgId ||
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"Decline",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
onClick: () =>
|
||||||
|
actions.acceptInvite(
|
||||||
|
String(
|
||||||
|
invite.orgId ||
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"Accept",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
...members.map((member) => {
|
...members.map((member) => {
|
||||||
const canRemoveMember =
|
const canRemoveMember =
|
||||||
allowMemberManagement &&
|
allowMemberManagement &&
|
||||||
@ -76,7 +433,18 @@ ${scopeSelector} .org-name-row button {
|
|||||||
return h(
|
return h(
|
||||||
"article",
|
"article",
|
||||||
{ className: "org-name-row" },
|
{ className: "org-name-row" },
|
||||||
h("strong", null, member.name),
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-name-copy" },
|
||||||
|
h("strong", null, member.name),
|
||||||
|
member.uid
|
||||||
|
? h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-name-meta" },
|
||||||
|
member.uid,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
canRemoveMember
|
canRemoveMember
|
||||||
? h(
|
? h(
|
||||||
"button",
|
"button",
|
||||||
|
|||||||
@ -15,8 +15,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
|
const inviteablePlayers = store.getInviteablePlayers();
|
||||||
const memberSelectProps =
|
const memberSelectProps =
|
||||||
members.length === 0 ? { disabled: true } : {};
|
members.length === 0 ? { disabled: true } : {};
|
||||||
|
const inviteSelectProps =
|
||||||
|
inviteablePlayers.length === 0 ? { disabled: true } : {};
|
||||||
|
|
||||||
let title = "";
|
let title = "";
|
||||||
let body = null;
|
let body = null;
|
||||||
@ -211,6 +214,72 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (modal.type === "invite") {
|
||||||
|
title = "Invite Player";
|
||||||
|
body = h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-form" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("label", null, "Online Player"),
|
||||||
|
h(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
id: "org-invite-player",
|
||||||
|
...inviteSelectProps,
|
||||||
|
},
|
||||||
|
...inviteablePlayers.map((player) =>
|
||||||
|
h(
|
||||||
|
"option",
|
||||||
|
{ value: player.uid },
|
||||||
|
player.name || player.uid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inviteablePlayers.length === 0
|
||||||
|
? h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"No eligible online players are currently available for invites.",
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-actions" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () => actions.closeModal(),
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
...inviteSelectProps,
|
||||||
|
onClick: () => {
|
||||||
|
if (
|
||||||
|
actions.sendInvite(
|
||||||
|
String(
|
||||||
|
actions.getInputValue(
|
||||||
|
"org-invite-player",
|
||||||
|
) || "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
actions.closeModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Send Invite",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else if (modal.type === "disband") {
|
} else if (modal.type === "disband") {
|
||||||
title = "Disband Organization";
|
title = "Disband Organization";
|
||||||
body = h(
|
body = h(
|
||||||
|
|||||||
@ -62,6 +62,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "invite" && !getters.canManageMembers()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can invite players.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "disband" && !getters.canDisbandOrg()) {
|
if (type === "disband" && !getters.canDisbandOrg()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -77,6 +85,14 @@
|
|||||||
store.setModal(null);
|
store.setModal(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleInviteMenu() {
|
||||||
|
store.setInviteMenuOpen(!store.getInviteMenuOpen());
|
||||||
|
}
|
||||||
|
|
||||||
|
closeInviteMenu() {
|
||||||
|
store.setInviteMenuOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
removeMember(member) {
|
removeMember(member) {
|
||||||
if (!getters.canManageMembers()) {
|
if (!getters.canManageMembers()) {
|
||||||
return false;
|
return false;
|
||||||
@ -294,6 +310,79 @@
|
|||||||
amount,
|
amount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendInvite(targetUid) {
|
||||||
|
if (!getters.canManageMembers()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can invite players.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = store
|
||||||
|
.getInviteablePlayers()
|
||||||
|
.find((entry) => String(entry.uid || "") === String(targetUid));
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select an online player to invite.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestInvitePlayer !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge.requestInvitePlayer({
|
||||||
|
targetUid: String(target.uid || ""),
|
||||||
|
targetName: String(target.name || ""),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptInvite(orgId) {
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestAcceptInvite !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeInviteMenu();
|
||||||
|
return bridge.requestAcceptInvite({ orgId });
|
||||||
|
}
|
||||||
|
|
||||||
|
declineInvite(orgId) {
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestDeclineInvite !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeInviteMenu();
|
||||||
|
return bridge.requestDeclineInvite({ orgId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OrgPortal.actions = new OrgPortalActions();
|
OrgPortal.actions = new OrgPortalActions();
|
||||||
|
|||||||
@ -74,6 +74,8 @@
|
|||||||
reputation: 0,
|
reputation: 0,
|
||||||
creditLines: [],
|
creditLines: [],
|
||||||
members: [],
|
members: [],
|
||||||
|
pendingInvites: [],
|
||||||
|
inviteablePlayers: [],
|
||||||
fleet: [],
|
fleet: [],
|
||||||
assets: [],
|
assets: [],
|
||||||
activity: [],
|
activity: [],
|
||||||
@ -126,6 +128,14 @@
|
|||||||
this.portalData.members,
|
this.portalData.members,
|
||||||
normalizeCollection(payload.portalData.members),
|
normalizeCollection(payload.portalData.members),
|
||||||
);
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.pendingInvites,
|
||||||
|
normalizeCollection(payload.portalData.pendingInvites),
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.inviteablePlayers,
|
||||||
|
normalizeCollection(payload.portalData.inviteablePlayers),
|
||||||
|
);
|
||||||
replaceArray(
|
replaceArray(
|
||||||
this.portalData.fleet,
|
this.portalData.fleet,
|
||||||
normalizeCollection(payload.portalData.fleet),
|
normalizeCollection(payload.portalData.fleet),
|
||||||
|
|||||||
@ -51,6 +51,11 @@
|
|||||||
[this.getMembers, this.setMembers] = createSignal([
|
[this.getMembers, this.setMembers] = createSignal([
|
||||||
...portalData.members,
|
...portalData.members,
|
||||||
]);
|
]);
|
||||||
|
[this.getPendingInvites, this.setPendingInvites] = createSignal([
|
||||||
|
...portalData.pendingInvites,
|
||||||
|
]);
|
||||||
|
[this.getInviteablePlayers, this.setInviteablePlayers] =
|
||||||
|
createSignal([...portalData.inviteablePlayers]);
|
||||||
[this.getCreditLines, this.setCreditLines] = createSignal([
|
[this.getCreditLines, this.setCreditLines] = createSignal([
|
||||||
...portalData.creditLines,
|
...portalData.creditLines,
|
||||||
]);
|
]);
|
||||||
@ -68,6 +73,8 @@
|
|||||||
text: "",
|
text: "",
|
||||||
});
|
});
|
||||||
[this.getModal, this.setModal] = createSignal(null);
|
[this.getModal, this.setModal] = createSignal(null);
|
||||||
|
[this.getInviteMenuOpen, this.setInviteMenuOpen] =
|
||||||
|
createSignal(false);
|
||||||
[this.getOrgDisbanded, this.setOrgDisbanded] = createSignal(false);
|
[this.getOrgDisbanded, this.setOrgDisbanded] = createSignal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +84,12 @@
|
|||||||
this.setFunds(nextPortalData.funds || 0);
|
this.setFunds(nextPortalData.funds || 0);
|
||||||
this.setReputation(nextPortalData.reputation || 0);
|
this.setReputation(nextPortalData.reputation || 0);
|
||||||
this.setMembers([...normalizeCollection(nextPortalData.members)]);
|
this.setMembers([...normalizeCollection(nextPortalData.members)]);
|
||||||
|
this.setPendingInvites([
|
||||||
|
...normalizeCollection(nextPortalData.pendingInvites),
|
||||||
|
]);
|
||||||
|
this.setInviteablePlayers([
|
||||||
|
...normalizeCollection(nextPortalData.inviteablePlayers),
|
||||||
|
]);
|
||||||
this.setCreditLines([
|
this.setCreditLines([
|
||||||
...normalizeCollection(nextPortalData.creditLines),
|
...normalizeCollection(nextPortalData.creditLines),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -4,8 +4,6 @@ PREP_RECOMPILE_START;
|
|||||||
#include "XEH_PREP.hpp"
|
#include "XEH_PREP.hpp"
|
||||||
PREP_RECOMPILE_END;
|
PREP_RECOMPILE_END;
|
||||||
|
|
||||||
// private _category = [QUOTE(MOD_NAME), LLSTRING(displayName)];
|
|
||||||
|
|
||||||
[QGVAR(requestInitActor), {
|
[QGVAR(requestInitActor), {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
@ -18,7 +16,7 @@ PREP_RECOMPILE_END;
|
|||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
||||||
|
|
||||||
private _finalData = GVAR(ActorStore) call ["get", [GVAR(Registry), _uid, _field]];
|
private _finalData = GVAR(ActorStore) call ["get", [_uid, _field]];
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
|
||||||
[CRPC(actor,responseSyncActor), [_finalData], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseSyncActor), [_finalData], _player] call CFUNC(targetEvent);
|
||||||
@ -29,7 +27,7 @@ PREP_RECOMPILE_END;
|
|||||||
|
|
||||||
if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID or Key!" };
|
if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID or Key!" };
|
||||||
|
|
||||||
private _hashMap = GVAR(ActorStore) call ["set", [GVAR(Registry), "actor:update", _uid, _field, _value, _sync]];
|
private _hashMap = GVAR(ActorStore) call ["set", [_uid, _field, _value, _sync]];
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
|
||||||
[CRPC(actor,responseSyncActor), [_hashMap], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseSyncActor), [_hashMap], _player] call CFUNC(targetEvent);
|
||||||
@ -41,7 +39,7 @@ PREP_RECOMPILE_END;
|
|||||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
||||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid field pairs!" };
|
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid field pairs!" };
|
||||||
|
|
||||||
private _hashMap = GVAR(ActorStore) call ["mset", [GVAR(Registry), "actor:update", _uid, _fieldValuePairs, _sync]];
|
private _hashMap = GVAR(ActorStore) call ["mset", [_uid, _fieldValuePairs, _sync]];
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
|
||||||
[CRPC(actor,responseSyncActor), [_hashMap], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseSyncActor), [_hashMap], _player] call CFUNC(targetEvent);
|
||||||
@ -53,7 +51,7 @@ PREP_RECOMPILE_END;
|
|||||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
||||||
|
|
||||||
GVAR(ActorStore) call ["snapshot", [_uid]];
|
GVAR(ActorStore) call ["snapshot", [_uid]];
|
||||||
private _finalData = GVAR(ActorStore) call ["save", [GVAR(Registry), "actor:update", _uid]];
|
private _finalData = GVAR(ActorStore) call ["save", [_uid]];
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
|
||||||
[CRPC(actor,responseSyncActor), [_finalData], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseSyncActor), [_finalData], _player] call CFUNC(targetEvent);
|
||||||
@ -63,5 +61,5 @@ PREP_RECOMPILE_END;
|
|||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Actor] Empty/Invalid UID!" };
|
||||||
GVAR(ActorStore) call ["remove", [GVAR(Registry), _uid]];
|
GVAR(ActorStore) call ["remove", [_uid]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|||||||
@ -4,13 +4,13 @@
|
|||||||
* File: fnc_initActorStore.sqf
|
* File: fnc_initActorStore.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-17
|
* Date: 2025-12-17
|
||||||
* Last Update: 2026-04-01
|
* Last Update: 2026-04-05
|
||||||
* Public: Yes
|
* Public: Yes
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
* Initializes the actor store for managing player actor data.
|
* Initializes the actor store for managing player actor data.
|
||||||
* Actor hot state is owned by the extension; SQF maintains a compatibility
|
* Actor hot state is owned by the extension; SQF acts as a thin bridge for
|
||||||
* mirror for engine-adjacent consumers.
|
* engine-adjacent reads, snapshots, and response fan-out.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
@ -38,7 +38,7 @@ GVAR(ActorModel) = compileFinal createHashMapObject [[
|
|||||||
_actor set ["state", "HEALTHY"];
|
_actor set ["state", "HEALTHY"];
|
||||||
_actor set ["phone_number", ""];
|
_actor set ["phone_number", ""];
|
||||||
_actor set ["email", ""];
|
_actor set ["email", ""];
|
||||||
_actor set ["organization", ""];
|
_actor set ["organization", "default"];
|
||||||
_actor set ["holster", true];
|
_actor set ["holster", true];
|
||||||
|
|
||||||
_actor
|
_actor
|
||||||
@ -109,7 +109,6 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["#base", EGVAR(common,BaseStore)],
|
["#base", EGVAR(common,BaseStore)],
|
||||||
["#type", "ActorBaseStore"],
|
["#type", "ActorBaseStore"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
GVAR(Registry) = createHashMap;
|
|
||||||
["INFO", "Actor Store Initialized!"] call EFUNC(common,log);
|
["INFO", "Actor Store Initialized!"] call EFUNC(common,log);
|
||||||
}],
|
}],
|
||||||
["cacheActor", compileFinal {
|
["cacheActor", compileFinal {
|
||||||
@ -117,9 +116,7 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
if (_uid isEqualTo "" || { !(_actor isEqualType createHashMap) }) exitWith { createHashMap };
|
if (_uid isEqualTo "" || { !(_actor isEqualType createHashMap) }) exitWith { createHashMap };
|
||||||
|
|
||||||
private _finalActor = GVAR(ActorModel) call ["migrate", [+_actor]];
|
GVAR(ActorModel) call ["migrate", [+_actor]]
|
||||||
GVAR(Registry) set [_uid, _finalActor];
|
|
||||||
_finalActor
|
|
||||||
}],
|
}],
|
||||||
["callHotActor", compileFinal {
|
["callHotActor", compileFinal {
|
||||||
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||||
@ -138,86 +135,167 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
||||||
_data
|
_data
|
||||||
}],
|
}],
|
||||||
|
["listHotUids", compileFinal {
|
||||||
|
["actor:hot:keys", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
|
if !(_isSuccess) exitWith { [] };
|
||||||
|
if !(_result isEqualType "") exitWith { [] };
|
||||||
|
if ((_result find "Error:") == 0) exitWith {
|
||||||
|
["ERROR", format ["Actor extension call '%1' failed: %2", "actor:hot:keys", _result]] call EFUNC(common,log);
|
||||||
|
[]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _uids = fromJSON _result;
|
||||||
|
if !(_uids isEqualType []) exitWith { [] };
|
||||||
|
|
||||||
|
_uids select { _x isEqualType "" && { _x isNotEqualTo "" } }
|
||||||
|
}],
|
||||||
["loadHotActor", compileFinal {
|
["loadHotActor", compileFinal {
|
||||||
params [["_uid", "", [""]], ["_initialize", false, [false]]];
|
params [["_uid", "", [""]], ["_initialize", false, [false]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||||
|
if (_initialize) then {
|
||||||
|
// Missing actors should be created explicitly from a server snapshot
|
||||||
|
// before the hot cache is initialized.
|
||||||
|
private _ensureResult = _self call ["ensurePersistentActor", [_uid]];
|
||||||
|
if !(_ensureResult isEqualType true && { _ensureResult }) exitWith { createHashMap };
|
||||||
|
};
|
||||||
|
|
||||||
private _command = ["actor:hot:get", "actor:hot:init"] select _initialize;
|
private _command = ["actor:hot:get", "actor:hot:init"] select _initialize;
|
||||||
private _actor = _self call ["callHotActor", [_command, [_uid]]];
|
private _actor = _self call ["callHotActor", [_command, [_uid]]];
|
||||||
if (_actor isEqualTo createHashMap) exitWith { _actor };
|
if (_actor isEqualTo createHashMap) exitWith { _actor };
|
||||||
|
|
||||||
_self call ["cacheActor", [_uid, _actor]]
|
_self call ["hydrateActorIfNeeded", [_uid, _actor, true]]
|
||||||
}],
|
}],
|
||||||
["normalizeGetArgs", compileFinal {
|
["ensurePersistentActor", compileFinal {
|
||||||
params ["_rawArguments"];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
if ((_rawArguments param [0, createHashMap]) isEqualType createHashMap) exitWith {
|
if (_uid isEqualTo "") exitWith { false };
|
||||||
[
|
|
||||||
_rawArguments param [1, "", [""]],
|
["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_existsResult", "_existsSuccess"];
|
||||||
_rawArguments param [2, "", [""]]
|
if (!_existsSuccess || { !(_existsResult isEqualType "") }) exitWith {
|
||||||
]
|
["ERROR", format ["Failed to verify persistent actor state for %1.", _uid]] call EFUNC(common,log);
|
||||||
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
[
|
if (_existsResult isEqualTo "true") exitWith { true };
|
||||||
_rawArguments param [0, "", [""]],
|
|
||||||
_rawArguments param [1, "", [""]]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["normalizeSetArgs", compileFinal {
|
|
||||||
params ["_rawArguments"];
|
|
||||||
|
|
||||||
if ((_rawArguments param [0, createHashMap]) isEqualType createHashMap) exitWith {
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
[
|
private _actor = GVAR(ActorModel) call ["fromPlayer", [_player]];
|
||||||
_rawArguments param [2, "", [""]],
|
_actor set ["uid", _uid];
|
||||||
_rawArguments param [3, "", [""]],
|
|
||||||
_rawArguments param [4, nil, [0, "", [], false, createHashMap, objNull, grpNull]],
|
if ((_actor getOrDefault ["organization", ""]) isEqualTo "") then {
|
||||||
_rawArguments param [5, false, [false]]
|
_actor set ["organization", "default"];
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[
|
private _json = _self call ["toJSON", [_actor]];
|
||||||
_rawArguments param [0, "", [""]],
|
["actor:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];
|
||||||
_rawArguments param [1, "", [""]],
|
|
||||||
_rawArguments param [2, nil, [0, "", [], false, createHashMap, objNull, grpNull]],
|
|
||||||
_rawArguments param [3, false, [false]]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["normalizeMSetArgs", compileFinal {
|
|
||||||
params ["_rawArguments"];
|
|
||||||
|
|
||||||
if ((_rawArguments param [0, createHashMap]) isEqualType createHashMap) exitWith {
|
if (!_createSuccess || { !(_createResult isEqualType "") }) exitWith {
|
||||||
[
|
["ERROR", format ["Failed to create actor %1 from server snapshot.", _uid]] call EFUNC(common,log);
|
||||||
_rawArguments param [2, "", [""]],
|
false
|
||||||
_rawArguments param [3, createHashMap, [createHashMap]],
|
|
||||||
_rawArguments param [4, false, [false]]
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[
|
if ((_createResult find "Error:") == 0) exitWith {
|
||||||
_rawArguments param [0, "", [""]],
|
["ERROR", format ["Actor create for %1 failed: %2", _uid, _createResult]] call EFUNC(common,log);
|
||||||
_rawArguments param [1, createHashMap, [createHashMap]],
|
false
|
||||||
_rawArguments param [2, false, [false]]
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
["normalizeUidArg", compileFinal {
|
|
||||||
params ["_rawArguments"];
|
|
||||||
|
|
||||||
if ((_rawArguments param [0, createHashMap]) isEqualType createHashMap) exitWith {
|
|
||||||
_rawArguments param [1, "", [""]]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_rawArguments param [0, "", [""]]
|
true
|
||||||
|
}],
|
||||||
|
["hydrateActorIfNeeded", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_actor", createHashMap, [createHashMap]], ["_save", true, [false]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { !(_actor isEqualType createHashMap) } || { _actor isEqualTo createHashMap }) exitWith {
|
||||||
|
createHashMap
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hot actor reads can still surface older partial records. Repair them
|
||||||
|
// from the live player snapshot when possible and persist the result.
|
||||||
|
private _hydratedActor = GVAR(ActorModel) call ["migrate", [+_actor]];
|
||||||
|
private _defaults = GVAR(ActorModel) call ["defaults", []];
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
private _needsPersist = false;
|
||||||
|
|
||||||
|
if ((_hydratedActor getOrDefault ["uid", ""]) isEqualTo "") then {
|
||||||
|
_hydratedActor set ["uid", _uid];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
if ((_hydratedActor getOrDefault ["organization", ""]) isEqualTo "") then {
|
||||||
|
_hydratedActor set ["organization", "default"];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
private _value = _hydratedActor getOrDefault [_x, ""];
|
||||||
|
if !(_value isEqualType "") then {
|
||||||
|
_hydratedActor set [_x, _defaults getOrDefault [_x, ""]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
} forEach ["phone_number", "email"];
|
||||||
|
|
||||||
|
if (_player isNotEqualTo objNull) then {
|
||||||
|
private _snapshot = GVAR(ActorModel) call ["fromPlayer", [_player]];
|
||||||
|
private _name = _hydratedActor getOrDefault ["name", ""];
|
||||||
|
if (
|
||||||
|
!(_name isEqualType "")
|
||||||
|
|| { _name isEqualTo "" }
|
||||||
|
|| { toLowerANSI _name isEqualTo "unknown" }
|
||||||
|
) then {
|
||||||
|
_hydratedActor set ["name", _snapshot getOrDefault ["name", name _player]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _position = _hydratedActor getOrDefault ["position", []];
|
||||||
|
if !(_position isEqualType [] && { count _position isEqualTo 3 }) then {
|
||||||
|
_hydratedActor set ["position", _snapshot getOrDefault ["position", getPosASL _player]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _direction = _hydratedActor getOrDefault ["direction", 0];
|
||||||
|
if !(_direction isEqualType 0) then {
|
||||||
|
_hydratedActor set ["direction", _snapshot getOrDefault ["direction", getDir _player]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
private _fieldValue = _hydratedActor getOrDefault [_x, ""];
|
||||||
|
if (!(_fieldValue isEqualType "") || { _fieldValue isEqualTo "" }) then {
|
||||||
|
_hydratedActor set [_x, _snapshot getOrDefault [_x, _defaults getOrDefault [_x, ""]]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
} forEach ["stance", "rank", "state"];
|
||||||
|
|
||||||
|
private _loadout = _hydratedActor getOrDefault ["loadout", []];
|
||||||
|
if !(_loadout isEqualType [] && { count _loadout > 0 }) then {
|
||||||
|
_hydratedActor set ["loadout", getUnitLoadout _player];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
private _fieldValue = _hydratedActor getOrDefault [_x, ""];
|
||||||
|
if (!(_fieldValue isEqualType "") || { _fieldValue isEqualTo "" }) then {
|
||||||
|
_hydratedActor set [_x, _defaults getOrDefault [_x, ""]];
|
||||||
|
_needsPersist = true;
|
||||||
|
};
|
||||||
|
} forEach ["stance", "rank", "state"];
|
||||||
|
};
|
||||||
|
|
||||||
|
if !_needsPersist exitWith {
|
||||||
|
_self call ["cacheActor", [_uid, _hydratedActor]]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _updatedActor = _self call ["override", [_uid, _hydratedActor, _save]];
|
||||||
|
if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) exitWith {
|
||||||
|
_self call ["cacheActor", [_uid, _updatedActor]]
|
||||||
|
};
|
||||||
|
|
||||||
|
["WARNING", format ["Failed to hydrate actor %1 from player snapshot.", _uid]] call EFUNC(common,log);
|
||||||
|
_self call ["cacheActor", [_uid, _hydratedActor]]
|
||||||
}],
|
}],
|
||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
private _cached = GVAR(Registry) getOrDefault [_uid, nil];
|
|
||||||
if !(isNil { _cached }) exitWith {
|
|
||||||
[CRPC(actor,responseInitActor), [_cached], _player] call CFUNC(targetEvent);
|
|
||||||
_cached
|
|
||||||
};
|
|
||||||
|
|
||||||
["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
["actor:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
if !(_isSuccess) exitWith {
|
if !(_isSuccess) exitWith {
|
||||||
@ -236,14 +314,11 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_finalActor = _self call ["loadHotActor", [_uid, true]];
|
_finalActor = _self call ["loadHotActor", [_uid, true]];
|
||||||
["INFO", format ["Found actor for %1", _uid]] call EFUNC(common,log);
|
["INFO", format ["Found actor for %1", _uid]] call EFUNC(common,log);
|
||||||
} else {
|
} else {
|
||||||
_finalActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
|
if !(_self call ["ensurePersistentActor", [_uid]]) exitWith {
|
||||||
_finalActor set ["uid", _uid];
|
|
||||||
|
|
||||||
private _json = _self call ["toJSON", [_finalActor]];
|
|
||||||
["actor:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];
|
|
||||||
if (!_createSuccess) exitWith {
|
|
||||||
["ERROR", format ["Failed to create actor %1! Using fallback actor.", _uid]] call EFUNC(common,log);
|
["ERROR", format ["Failed to create actor %1! Using fallback actor.", _uid]] call EFUNC(common,log);
|
||||||
|
|
||||||
|
_finalActor = GVAR(ActorModel) call ["fromPlayer", [_player]];
|
||||||
|
_finalActor set ["uid", _uid];
|
||||||
_finalActor = _self call ["cacheActor", [_uid, _finalActor]];
|
_finalActor = _self call ["cacheActor", [_uid, _finalActor]];
|
||||||
[CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseInitActor), [_finalActor], _player] call CFUNC(targetEvent);
|
||||||
_finalActor
|
_finalActor
|
||||||
@ -264,16 +339,57 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_finalActor
|
_finalActor
|
||||||
}],
|
}],
|
||||||
["get", compileFinal {
|
["get", compileFinal {
|
||||||
call (_self get "normalizeGetArgs") params ["_uid", "_field"];
|
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||||
|
|
||||||
private _actor = _self call ["loadHotActor", [_uid, false]];
|
private _actor = _self call ["loadHotActor", [_uid, false]];
|
||||||
if (_actor isEqualTo createHashMap) then {
|
|
||||||
_actor = _self call ["loadHotActor", [_uid, true]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_field isEqualTo "") exitWith { _actor };
|
if (_field isEqualTo "") exitWith { _actor };
|
||||||
_actor getOrDefault [_field, nil]
|
_actor getOrDefault [_field, nil]
|
||||||
}],
|
}],
|
||||||
|
["load", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
private _actor = _self call ["get", [_uid, ""]];
|
||||||
|
if !(_actor isEqualType createHashMap) exitWith { createHashMap };
|
||||||
|
|
||||||
|
_actor
|
||||||
|
}],
|
||||||
|
["getFieldOrDefault", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_field", "", [""]], ["_default", nil]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { _default };
|
||||||
|
|
||||||
|
private _actor = _self call ["load", [_uid]];
|
||||||
|
if !(_actor isEqualType createHashMap) exitWith { _default };
|
||||||
|
if (_actor isEqualTo createHashMap) exitWith { _default };
|
||||||
|
|
||||||
|
_actor getOrDefault [_field, _default]
|
||||||
|
}],
|
||||||
|
["getOrganization", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_default", "default", [""]]];
|
||||||
|
|
||||||
|
private _orgID = _self call ["getFieldOrDefault", [_uid, "organization", _default]];
|
||||||
|
if !(_orgID isEqualType "") exitWith { _default };
|
||||||
|
if (_orgID isEqualTo "") exitWith { _default };
|
||||||
|
|
||||||
|
_orgID
|
||||||
|
}],
|
||||||
|
["getName", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_default", "", [""]]];
|
||||||
|
|
||||||
|
private _name = _self call ["getFieldOrDefault", [_uid, "name", _default]];
|
||||||
|
if !(_name isEqualType "") exitWith { _default };
|
||||||
|
|
||||||
|
_name
|
||||||
|
}],
|
||||||
|
["getPhoneNumber", compileFinal {
|
||||||
|
params [["_uid", "", [""]], ["_default", "", [""]]];
|
||||||
|
|
||||||
|
private _phoneNumber = _self call ["getFieldOrDefault", [_uid, "phone_number", _default]];
|
||||||
|
if !(_phoneNumber isEqualType "") exitWith { _default };
|
||||||
|
|
||||||
|
_phoneNumber
|
||||||
|
}],
|
||||||
["override", compileFinal {
|
["override", compileFinal {
|
||||||
params [
|
params [
|
||||||
["_uid", "", [""]],
|
["_uid", "", [""]],
|
||||||
@ -297,7 +413,12 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_self call ["cacheActor", [_uid, _actor]]
|
_self call ["cacheActor", [_uid, _actor]]
|
||||||
}],
|
}],
|
||||||
["set", compileFinal {
|
["set", compileFinal {
|
||||||
call (_self get "normalizeSetArgs") params ["_uid", "_field", "_value", "_sync"];
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_field", "", [""]],
|
||||||
|
["_value", nil, [0, "", [], false, createHashMap, objNull, grpNull]],
|
||||||
|
["_sync", false, [false]]
|
||||||
|
];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||||
|
|
||||||
@ -312,7 +433,7 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
createHashMapFromArray [[_field, _updatedActor getOrDefault [_field, _value]]]
|
createHashMapFromArray [[_field, _updatedActor getOrDefault [_field, _value]]]
|
||||||
}],
|
}],
|
||||||
["mset", compileFinal {
|
["mset", compileFinal {
|
||||||
call (_self get "normalizeMSetArgs") params ["_uid", "_fieldValuePairs", "_sync"];
|
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { !(_fieldValuePairs isEqualType createHashMap) }) exitWith { createHashMap };
|
if (_uid isEqualTo "" || { !(_fieldValuePairs isEqualType createHashMap) }) exitWith { createHashMap };
|
||||||
|
|
||||||
@ -327,7 +448,7 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
+_fieldValuePairs
|
+_fieldValuePairs
|
||||||
}],
|
}],
|
||||||
["save", compileFinal {
|
["save", compileFinal {
|
||||||
private _uid = call (_self get "normalizeUidArg");
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||||
private _actor = _self call ["callHotActor", ["actor:hot:save", [_uid]]];
|
private _actor = _self call ["callHotActor", ["actor:hot:save", [_uid]]];
|
||||||
@ -336,11 +457,10 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_self call ["cacheActor", [_uid, _actor]]
|
_self call ["cacheActor", [_uid, _actor]]
|
||||||
}],
|
}],
|
||||||
["remove", compileFinal {
|
["remove", compileFinal {
|
||||||
private _uid = call (_self get "normalizeUidArg");
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { false };
|
if (_uid isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
GVAR(Registry) deleteAt _uid;
|
|
||||||
["actor:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
["actor:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
_isSuccess && { _result isEqualTo "OK" }
|
_isSuccess && { _result isEqualTo "OK" }
|
||||||
}],
|
}],
|
||||||
@ -364,6 +484,9 @@ GVAR(ActorBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_finalActor set ["rank", rank _player];
|
_finalActor set ["rank", rank _player];
|
||||||
_finalActor set ["state", lifeState _player];
|
_finalActor set ["state", lifeState _player];
|
||||||
_finalActor set ["loadout", getUnitLoadout _player];
|
_finalActor set ["loadout", getUnitLoadout _player];
|
||||||
|
if ((_finalActor getOrDefault ["organization", ""]) isEqualTo "") then {
|
||||||
|
_finalActor set ["organization", "default"];
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
["WARNING", format ["No player object found for %1 during actor snapshot, using cached values.", _uid]] call EFUNC(common,log);
|
["WARNING", format ["No player object found for %1 during actor snapshot, using cached values.", _uid]] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -62,10 +62,7 @@ GVAR(BankPayloadBuilder) = createHashMapObject [[
|
|||||||
];
|
];
|
||||||
if (_uid isEqualTo "") exitWith { _defaultState };
|
if (_uid isEqualTo "") exitWith { _defaultState };
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
||||||
if (_org isEqualTo createHashMap) then {
|
if (_org isEqualTo createHashMap) then {
|
||||||
_org = EGVAR(org,OrgStore) call ["loadById", ["default"]];
|
_org = EGVAR(org,OrgStore) call ["loadById", ["default"]];
|
||||||
|
|||||||
@ -24,16 +24,20 @@
|
|||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(BankSessionManager) = createHashMapObject [[
|
GVAR(BankSessionManager) = createHashMapObject [[
|
||||||
["#type", "BankSessionManager"],
|
["#type", "BankSessionManager"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["sessions", createHashMap];
|
||||||
|
}],
|
||||||
["getSessionState", compileFinal {
|
["getSessionState", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
private _session = GVAR(SessionRegistry) getOrDefault [_uid, createHashMap];
|
private _sessions = _self getOrDefault ["sessions", createHashMap];
|
||||||
|
private _session = _sessions getOrDefault [_uid, createHashMap];
|
||||||
if (_session isEqualTo createHashMap) then {
|
if (_session isEqualTo createHashMap) then {
|
||||||
_session = createHashMapFromArray [
|
_session = createHashMapFromArray [
|
||||||
["atmAuthorized", false],
|
["atmAuthorized", false],
|
||||||
["mode", "bank"]
|
["mode", "bank"]
|
||||||
];
|
];
|
||||||
GVAR(SessionRegistry) set [_uid, _session];
|
_sessions set [_uid, _session];
|
||||||
};
|
};
|
||||||
|
|
||||||
_session
|
_session
|
||||||
@ -44,9 +48,10 @@ GVAR(BankSessionManager) = createHashMapObject [[
|
|||||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||||
|
|
||||||
private _session = +(_self call ["getSessionState", [_uid]]);
|
private _session = +(_self call ["getSessionState", [_uid]]);
|
||||||
|
private _sessions = _self getOrDefault ["sessions", createHashMap];
|
||||||
{ _session set [_x, _y]; } forEach _fieldValuePairs;
|
{ _session set [_x, _y]; } forEach _fieldValuePairs;
|
||||||
|
|
||||||
GVAR(SessionRegistry) set [_uid, _session];
|
_sessions set [_uid, _session];
|
||||||
_session
|
_session
|
||||||
}],
|
}],
|
||||||
["resolveMode", compileFinal {
|
["resolveMode", compileFinal {
|
||||||
|
|||||||
@ -18,7 +18,6 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["#base", EGVAR(common,BaseStore)],
|
["#base", EGVAR(common,BaseStore)],
|
||||||
["#type", "BankBaseStore"],
|
["#type", "BankBaseStore"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
GVAR(SessionRegistry) = createHashMap;
|
|
||||||
["INFO", "Bank Store Initialized!"] call EFUNC(common,log);
|
["INFO", "Bank Store Initialized!"] call EFUNC(common,log);
|
||||||
}],
|
}],
|
||||||
["normalizeAccount", compileFinal {
|
["normalizeAccount", compileFinal {
|
||||||
@ -228,6 +227,17 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _persistenceFailures = [];
|
||||||
|
private _savedBank = _self call ["save", [_uid]];
|
||||||
|
if (_savedBank isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "bank";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgPersistenceMessage = _orgResult getOrDefault ["persistenceMessage", ""];
|
||||||
|
if !(_orgResult getOrDefault ["persisted", false]) then {
|
||||||
|
_persistenceFailures pushBack "organization";
|
||||||
|
};
|
||||||
|
|
||||||
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _bankPatch]];
|
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _bankPatch]];
|
||||||
GVAR(BankMessenger) call ["sendNotification", [_uid, "info", "Bank", _orgResult getOrDefault ["message", format ["Repaid $%1 toward the organization credit line.", [_amount] call EFUNC(common,formatNumber)]]]];
|
GVAR(BankMessenger) call ["sendNotification", [_uid, "info", "Bank", _orgResult getOrDefault ["message", format ["Repaid $%1 toward the organization credit line.", [_amount] call EFUNC(common,formatNumber)]]]];
|
||||||
|
|
||||||
@ -241,6 +251,19 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
} forEach (_orgResult getOrDefault ["memberUids", []]);
|
} forEach (_orgResult getOrDefault ["memberUids", []]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_persistenceFailures isNotEqualTo []) then {
|
||||||
|
private _warning = format [
|
||||||
|
"Credit repayment posted, but durable save failed for: %1.",
|
||||||
|
_persistenceFailures joinString ", "
|
||||||
|
];
|
||||||
|
if (_orgPersistenceMessage isNotEqualTo "") then {
|
||||||
|
_warning = format ["%1 %2", _warning, _orgPersistenceMessage];
|
||||||
|
};
|
||||||
|
|
||||||
|
["ERROR", format ["Credit repayment for %1 completed with persistence failures: %2", _uid, _persistenceFailures joinString ", "]] call EFUNC(common,log);
|
||||||
|
GVAR(BankMessenger) call ["sendAlert", [_uid, "warning", _warning]];
|
||||||
|
};
|
||||||
|
|
||||||
_self call ["hydrateSession", [_uid, "", false]];
|
_self call ["hydrateSession", [_uid, "", false]];
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
|||||||
@ -22,48 +22,19 @@
|
|||||||
#pragma hemtt ignore_variables ["_self"]
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
GVAR(ActivityRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(ActivityRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "CadActivityRepositoryBaseClass"],
|
["#type", "CadActivityRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
|
||||||
_self set ["activityRegistry", []];
|
|
||||||
_self set ["persistenceLoaded", false];
|
|
||||||
}],
|
|
||||||
["restorePersistedActivity", compileFinal {
|
|
||||||
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
|
||||||
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
|
||||||
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
|
||||||
|
|
||||||
private _result = _persistenceService call ["loadActivity", []];
|
|
||||||
if !(_result getOrDefault ["success", false]) exitWith { false };
|
|
||||||
|
|
||||||
_self set ["activityRegistry", +(_result getOrDefault ["data", []])];
|
|
||||||
_self set ["persistenceLoaded", true];
|
|
||||||
true
|
|
||||||
}],
|
|
||||||
["appendEntry", compileFinal {
|
["appendEntry", compileFinal {
|
||||||
params [["_entry", createHashMap, [createHashMap]]];
|
params [["_entry", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
if (_entry isEqualTo createHashMap) exitWith { false };
|
if (_entry isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
_self call ["restorePersistedActivity", []];
|
|
||||||
|
|
||||||
private _activityRegistry = +(_self getOrDefault ["activityRegistry", []]);
|
|
||||||
private _finalEntry = +_entry;
|
private _finalEntry = +_entry;
|
||||||
if ((_finalEntry getOrDefault ["timestamp", -1]) < 0) then {
|
if ((_finalEntry getOrDefault ["timestamp", -1]) < 0) then {
|
||||||
_finalEntry set ["timestamp", serverTime];
|
_finalEntry set ["timestamp", serverTime];
|
||||||
};
|
};
|
||||||
|
|
||||||
_activityRegistry pushBack _finalEntry;
|
|
||||||
|
|
||||||
if ((count _activityRegistry) > 50) then {
|
|
||||||
_activityRegistry deleteRange [0, (count _activityRegistry) - 50];
|
|
||||||
};
|
|
||||||
|
|
||||||
_self set ["activityRegistry", _activityRegistry];
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
if (_persistenceService isNotEqualTo createHashMap) then {
|
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
||||||
_persistenceService call ["appendActivity", [_finalEntry]];
|
|
||||||
};
|
_persistenceService call ["appendActivity", [_finalEntry]]
|
||||||
true
|
|
||||||
}],
|
}],
|
||||||
["appendActivity", compileFinal {
|
["appendActivity", compileFinal {
|
||||||
params [
|
params [
|
||||||
@ -85,8 +56,13 @@ GVAR(ActivityRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_self call ["appendEntry", [_entry]]
|
_self call ["appendEntry", [_entry]]
|
||||||
}],
|
}],
|
||||||
["getActivity", compileFinal {
|
["getActivity", compileFinal {
|
||||||
_self call ["restorePersistedActivity", []];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
+(_self getOrDefault ["activityRegistry", []])
|
if (_persistenceService isEqualTo createHashMap) exitWith { [] };
|
||||||
|
|
||||||
|
private _result = _persistenceService call ["loadActivity", []];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { [] };
|
||||||
|
|
||||||
|
+(_result getOrDefault ["data", []])
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -24,15 +24,51 @@
|
|||||||
GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "CadAssignmentRepositoryBaseClass"],
|
["#type", "CadAssignmentRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["assignmentRegistry", createHashMap];
|
_self set ["ownershipHydrated", false];
|
||||||
_self set ["dispatchOrderRegistry", createHashMap];
|
}],
|
||||||
_self set ["persistenceLoaded", false];
|
["loadState", compileFinal {
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["assignments", createHashMap],
|
||||||
|
["dispatchOrders", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith { _result };
|
||||||
|
|
||||||
|
private _assignmentsResult = _persistenceService call ["loadAssignments", []];
|
||||||
|
if !(_assignmentsResult getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
private _ordersResult = _persistenceService call ["loadDispatchOrders", []];
|
||||||
|
if !(_ordersResult getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
private _assignmentRegistry = +(_assignmentsResult getOrDefault ["data", createHashMap]);
|
||||||
|
private _dispatchOrderRegistry = +(_ordersResult getOrDefault ["data", createHashMap]);
|
||||||
|
|
||||||
|
if !(_self getOrDefault ["ownershipHydrated", false]) then {
|
||||||
|
{
|
||||||
|
if ((_y getOrDefault ["state", ""]) isNotEqualTo "acknowledged") then { continue; };
|
||||||
|
if ((_y getOrDefault ["acknowledgedByUid", ""]) isEqualTo "") then { continue; };
|
||||||
|
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then { continue; };
|
||||||
|
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
||||||
|
|
||||||
|
EGVAR(task,TaskStore) call ["bindTaskOwnership", [_x, _y getOrDefault ["acknowledgedByUid", ""]]];
|
||||||
|
} forEach _assignmentRegistry;
|
||||||
|
|
||||||
|
_self set ["ownershipHydrated", true];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["assignments", _assignmentRegistry];
|
||||||
|
_result set ["dispatchOrders", _dispatchOrderRegistry];
|
||||||
|
_result
|
||||||
}],
|
}],
|
||||||
["pruneAssignments", compileFinal {
|
["pruneAssignments", compileFinal {
|
||||||
_self call ["restorePersistedState", []];
|
private _state = _self call ["loadState", []];
|
||||||
|
if !(_state getOrDefault ["success", false]) exitWith { 0 };
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
|
||||||
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
|
||||||
private _keysToRemove = [];
|
private _keysToRemove = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -46,12 +82,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
} forEach _assignmentRegistry;
|
} forEach _assignmentRegistry;
|
||||||
|
|
||||||
{
|
|
||||||
_assignmentRegistry deleteAt _x;
|
|
||||||
} forEach _keysToRemove;
|
|
||||||
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
|
||||||
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
if (_persistenceService isNotEqualTo createHashMap) then {
|
if (_persistenceService isNotEqualTo createHashMap) then {
|
||||||
{
|
{
|
||||||
@ -62,50 +92,73 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
count _keysToRemove
|
count _keysToRemove
|
||||||
}],
|
}],
|
||||||
["getAssignments", compileFinal {
|
["getAssignments", compileFinal {
|
||||||
_self call ["restorePersistedState", []];
|
private _state = _self call ["loadState", []];
|
||||||
values (_self getOrDefault ["assignmentRegistry", createHashMap])
|
if !(_state getOrDefault ["success", false]) exitWith { [] };
|
||||||
|
|
||||||
|
values (_state getOrDefault ["assignments", createHashMap])
|
||||||
}],
|
}],
|
||||||
["isDispatchOrder", compileFinal {
|
["isDispatchOrder", compileFinal {
|
||||||
params [["_taskID", "", [""]]];
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
if (_taskID isEqualTo "") exitWith { false };
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
((_self getOrDefault ["dispatchOrderRegistry", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap
|
private _state = _self call ["loadState", []];
|
||||||
|
if !(_state getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
|
((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap
|
||||||
}],
|
}],
|
||||||
["restorePersistedState", compileFinal {
|
["getAssignmentByTaskId", compileFinal {
|
||||||
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||||
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
|
||||||
|
|
||||||
private _assignmentsResult = _persistenceService call ["loadAssignments", []];
|
private _state = _self call ["loadState", []];
|
||||||
if !(_assignmentsResult getOrDefault ["success", false]) exitWith { false };
|
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
|
||||||
|
|
||||||
private _ordersResult = _persistenceService call ["loadDispatchOrders", []];
|
+((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap])
|
||||||
if !(_ordersResult getOrDefault ["success", false]) exitWith { false };
|
}],
|
||||||
|
["getDispatchOrderByTaskId", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
private _assignmentRegistry = +(_assignmentsResult getOrDefault ["data", createHashMap]);
|
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||||
private _dispatchOrderRegistry = +(_ordersResult getOrDefault ["data", createHashMap]);
|
|
||||||
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
private _state = _self call ["loadState", []];
|
||||||
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
|
||||||
_self set ["persistenceLoaded", true];
|
|
||||||
|
+((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap])
|
||||||
|
}],
|
||||||
|
["getCurrentTaskIdForGroup", compileFinal {
|
||||||
|
params [["_groupID", "", [""]]];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "") exitWith { "" };
|
||||||
|
|
||||||
|
private _state = _self call ["loadState", []];
|
||||||
|
if !(_state getOrDefault ["success", false]) exitWith { "" };
|
||||||
|
|
||||||
|
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
|
||||||
|
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
|
||||||
|
private _taskID = "";
|
||||||
|
|
||||||
{
|
{
|
||||||
if ((_y getOrDefault ["state", ""]) isNotEqualTo "acknowledged") then { continue; };
|
if ((_y getOrDefault ["groupId", ""]) isNotEqualTo _groupID) then { continue; };
|
||||||
if (((_y getOrDefault ["acknowledgedByUid", ""]) isEqualTo "")) then { continue; };
|
if !((_y getOrDefault ["state", ""]) in ["assigned", "acknowledged"]) then { continue; };
|
||||||
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then { continue; };
|
|
||||||
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
private _dispatchOrder = +(_dispatchOrderRegistry getOrDefault [_x, createHashMap]);
|
||||||
EGVAR(task,TaskStore) call ["bindTaskOwnership", [_x, _y getOrDefault ["acknowledgedByUid", ""]]];
|
if (_dispatchOrder isEqualTo createHashMap) then {
|
||||||
|
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
||||||
|
_taskID = _x;
|
||||||
|
} else {
|
||||||
|
_taskID = _dispatchOrder getOrDefault ["title", _x];
|
||||||
|
};
|
||||||
} forEach _assignmentRegistry;
|
} forEach _assignmentRegistry;
|
||||||
|
|
||||||
true
|
_taskID
|
||||||
}],
|
}],
|
||||||
["buildDispatchOrderEntry", compileFinal {
|
["buildDispatchOrderEntry", compileFinal {
|
||||||
params [
|
params [
|
||||||
["_taskID", "", [""]],
|
["_taskID", "", [""]],
|
||||||
["_order", createHashMap, [createHashMap]],
|
["_order", createHashMap, [createHashMap]],
|
||||||
["_assignmentRegistry", createHashMap, [createHashMap]],
|
["_assignment", createHashMap, [createHashMap]],
|
||||||
["_groupRepository", createHashMap, [createHashMap]]
|
["_groupRepository", createHashMap, [createHashMap]]
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -127,7 +180,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
private _assignment = _assignmentRegistry getOrDefault [_taskID, createHashMap];
|
|
||||||
_entry set ["taskId", _taskID];
|
_entry set ["taskId", _taskID];
|
||||||
_entry set ["taskID", _taskID];
|
_entry set ["taskID", _taskID];
|
||||||
_entry set ["type", _entry getOrDefault ["type", "dispatch_order"]];
|
_entry set ["type", _entry getOrDefault ["type", "dispatch_order"]];
|
||||||
@ -136,6 +188,23 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_entry set ["assignmentState", [_assignment getOrDefault ["state", ""], "unassigned"] select (_assignment isEqualTo createHashMap)];
|
_entry set ["assignmentState", [_assignment getOrDefault ["state", ""], "unassigned"] select (_assignment isEqualTo createHashMap)];
|
||||||
_entry
|
_entry
|
||||||
}],
|
}],
|
||||||
|
["buildDispatchOrderEntryForTask", compileFinal {
|
||||||
|
params [
|
||||||
|
["_taskID", "", [""]],
|
||||||
|
["_groupRepository", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _state = _self call ["loadState", []];
|
||||||
|
if !(_state getOrDefault ["success", false]) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _order = +((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]);
|
||||||
|
if (_order isEqualTo createHashMap) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
|
||||||
|
_self call ["buildDispatchOrderEntry", [_taskID, _order, _assignment, _groupRepository]]
|
||||||
|
}],
|
||||||
["assignTaskToGroup", compileFinal {
|
["assignTaskToGroup", compileFinal {
|
||||||
params [
|
params [
|
||||||
["_requesterUid", "", [""]],
|
["_requesterUid", "", [""]],
|
||||||
@ -150,16 +219,20 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["assignment", createHashMap]
|
["assignment", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
_result set ["message", "You are not authorized to assign contracts."];
|
_result set ["message", "You are not authorized to assign contracts."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
private _state = _self call ["loadState", []];
|
||||||
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
if !(_state getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _assignmentRegistry = _state getOrDefault ["assignments", createHashMap];
|
||||||
|
private _dispatchOrderRegistry = _state getOrDefault ["dispatchOrders", createHashMap];
|
||||||
private _isDispatchOrder = (_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
|
private _isDispatchOrder = (_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
|
||||||
|
|
||||||
if (!_isDispatchOrder && { (EGVAR(task,TaskStore) call ["getTaskStatus", [_taskID]]) isNotEqualTo "active" }) exitWith {
|
if (!_isDispatchOrder && { (EGVAR(task,TaskStore) call ["getTaskStatus", [_taskID]]) isNotEqualTo "active" }) exitWith {
|
||||||
@ -221,9 +294,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
_assignmentRegistry set [_taskID, _assignment];
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_assignData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_assignData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
@ -235,6 +305,9 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["assignment", _assignment];
|
_result set ["assignment", _assignment];
|
||||||
_result set ["leaderUid", _leaderUid];
|
_result set ["leaderUid", _leaderUid];
|
||||||
_result set ["isDispatchOrder", _isDispatchOrder];
|
_result set ["isDispatchOrder", _isDispatchOrder];
|
||||||
|
if (_isDispatchOrder) then {
|
||||||
|
_result set ["order", +(_dispatchOrderRegistry getOrDefault [_taskID, createHashMap])];
|
||||||
|
};
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
["createDispatchOrder", compileFinal {
|
["createDispatchOrder", compileFinal {
|
||||||
@ -254,8 +327,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["order", createHashMap]
|
["order", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
_result set ["message", "You are not authorized to create dispatch orders."];
|
_result set ["message", "You are not authorized to create dispatch orders."];
|
||||||
@ -337,14 +408,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
|
||||||
_dispatchOrderRegistry set [_taskID, _order];
|
|
||||||
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
|
||||||
_assignmentRegistry set [_taskID, _assignment];
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_createData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_createData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
@ -368,23 +431,25 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["assignment", createHashMap]
|
["assignment", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
_result set ["message", "You are not authorized to close dispatch orders."];
|
_result set ["message", "You are not authorized to close dispatch orders."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
private _state = _self call ["loadState", []];
|
||||||
private _order = +(_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]);
|
if !(_state getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _order = +((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]);
|
||||||
if (_order isEqualTo createHashMap) exitWith {
|
if (_order isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", "Dispatch order could not be resolved."];
|
_result set ["message", "Dispatch order could not be resolved."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
|
||||||
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
|
||||||
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
if (_persistenceService isEqualTo createHashMap) exitWith {
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
@ -399,14 +464,8 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
||||||
_order = +(_closeData getOrDefault ["order", _order]);
|
|
||||||
_assignment = +(_closeData getOrDefault ["assignment", _assignment]);
|
_assignment = +(_closeData getOrDefault ["assignment", _assignment]);
|
||||||
|
|
||||||
_dispatchOrderRegistry deleteAt _taskID;
|
|
||||||
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
|
||||||
_assignmentRegistry deleteAt _taskID;
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
_activityEntry set ["actorUid", _requesterUid];
|
_activityEntry set ["actorUid", _requesterUid];
|
||||||
@ -430,12 +489,14 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
];
|
];
|
||||||
|
|
||||||
private _transition = _this param [2, "acknowledge", [""]];
|
private _transition = _this param [2, "acknowledge", [""]];
|
||||||
|
private _state = _self call ["loadState", []];
|
||||||
|
if !(_state getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
private _assignment = +((_state getOrDefault ["assignments", createHashMap]) getOrDefault [_taskID, createHashMap]);
|
||||||
|
private _isDispatchOrder = ((_state getOrDefault ["dispatchOrders", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
|
||||||
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
|
||||||
private _isDispatchOrder = _self call ["isDispatchOrder", [_taskID]];
|
|
||||||
if (_assignment isEqualTo createHashMap) exitWith {
|
if (_assignment isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", "Task is not assigned."];
|
_result set ["message", "Task is not assigned."];
|
||||||
_result
|
_result
|
||||||
@ -508,16 +569,6 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (_transition) do {
|
|
||||||
case "decline": {
|
|
||||||
_assignmentRegistry deleteAt _taskID;
|
|
||||||
};
|
|
||||||
default {
|
|
||||||
_assignmentRegistry set [_taskID, _assignment];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_transitionData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_transitionData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
if (_isDispatchOrder) then {
|
if (_isDispatchOrder) then {
|
||||||
|
|||||||
@ -10,6 +10,11 @@
|
|||||||
* Initializes the CAD store as a coordinator over activity, group,
|
* Initializes the CAD store as a coordinator over activity, group,
|
||||||
* assignment, and permission domain objects.
|
* assignment, and permission domain objects.
|
||||||
*
|
*
|
||||||
|
* CAD operational state is extension-backed but intentionally transient.
|
||||||
|
* Orders, requests, assignments, hydrate state, and recent activity are
|
||||||
|
* scoped to the active server/mission lifecycle and start fresh after a
|
||||||
|
* restart.
|
||||||
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
@ -133,17 +138,13 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _leaderUid = _result getOrDefault ["leaderUid", ""];
|
private _leaderUid = _result getOrDefault ["leaderUid", ""];
|
||||||
if (_leaderUid isEqualTo "") exitWith { false };
|
if (_leaderUid isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
private _assignmentRepository = _self get "AssignmentRepository";
|
||||||
private _message = if (_result getOrDefault ["isDispatchOrder", false]) then {
|
private _message = if (_result getOrDefault ["isDispatchOrder", false]) then {
|
||||||
private _order = _result getOrDefault ["order", createHashMap];
|
private _order = _result getOrDefault ["order", createHashMap];
|
||||||
if (_order isEqualTo createHashMap) then {
|
if (_order isEqualTo createHashMap) then {
|
||||||
private _assignment = _result getOrDefault ["assignment", createHashMap];
|
private _assignment = _result getOrDefault ["assignment", createHashMap];
|
||||||
private _taskID = _assignment getOrDefault ["taskId", ""];
|
private _taskID = _assignment getOrDefault ["taskId", ""];
|
||||||
_order = (_self get "AssignmentRepository") call ["buildDispatchOrderEntry", [
|
_order = _assignmentRepository call ["buildDispatchOrderEntryForTask", [_taskID, _self get "GroupRepository"]];
|
||||||
_taskID,
|
|
||||||
((_self get "AssignmentRepository") getOrDefault ["dispatchOrderRegistry", createHashMap]) getOrDefault [_taskID, createHashMap],
|
|
||||||
(_self get "AssignmentRepository") getOrDefault ["assignmentRegistry", createHashMap],
|
|
||||||
_self get "GroupRepository"
|
|
||||||
]];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
format ["Dispatch order assigned: %1. Open CAD to review and acknowledge.", _order getOrDefault ["title", "Dispatch Order"]]
|
format ["Dispatch order assigned: %1. Open CAD to review and acknowledge.", _order getOrDefault ["title", "Dispatch Order"]]
|
||||||
@ -203,15 +204,10 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _permissionService = _self get "PermissionService";
|
private _permissionService = _self get "PermissionService";
|
||||||
private _groupRepository = _self get "GroupRepository";
|
private _groupRepository = _self get "GroupRepository";
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
|
||||||
if (_actor isEqualTo createHashMap && { _uid isNotEqualTo "" }) then {
|
|
||||||
_actor = EGVAR(actor,ActorStore) call ["init", [_uid]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _groupID = _groupRepository call ["getPlayerGroupId", [_uid]];
|
private _groupID = _groupRepository call ["getPlayerGroupId", [_uid]];
|
||||||
private _session = createHashMapFromArray [
|
private _session = createHashMapFromArray [
|
||||||
["uid", _uid],
|
["uid", _uid],
|
||||||
["orgId", _actor getOrDefault ["organization", "default"]],
|
["orgId", EGVAR(actor,ActorStore) call ["getOrganization", [_uid]]],
|
||||||
["isDispatcher", _permissionService call ["canDispatch", [_uid]]],
|
["isDispatcher", _permissionService call ["canDispatch", [_uid]]],
|
||||||
["groupId", _groupID],
|
["groupId", _groupID],
|
||||||
["isLeader", _groupRepository call ["isGroupLeader", [_uid, _groupID]]]
|
["isLeader", _groupRepository call ["isGroupLeader", [_uid, _groupID]]]
|
||||||
|
|||||||
@ -24,8 +24,6 @@
|
|||||||
GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "CadGroupRepositoryBaseClass"],
|
["#type", "CadGroupRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["groupRegistry", createHashMap];
|
|
||||||
_self set ["groupProfileRegistry", createHashMap];
|
|
||||||
_self set ["validStatuses", [
|
_self set ["validStatuses", [
|
||||||
"available",
|
"available",
|
||||||
"en_route",
|
"en_route",
|
||||||
@ -63,31 +61,11 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
if (_groupID isEqualTo "") exitWith { "" };
|
if (_groupID isEqualTo "") exitWith { "" };
|
||||||
|
|
||||||
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
||||||
private _assignmentRegistry = _assignmentRepository getOrDefault ["assignmentRegistry", createHashMap];
|
if (_assignmentRepository isEqualTo createHashMap) exitWith { "" };
|
||||||
private _dispatchOrderRegistry = _assignmentRepository getOrDefault ["dispatchOrderRegistry", createHashMap];
|
|
||||||
private _taskID = "";
|
|
||||||
|
|
||||||
{
|
_assignmentRepository call ["getCurrentTaskIdForGroup", [_groupID]]
|
||||||
if ((_y getOrDefault ["groupId", ""]) isNotEqualTo _groupID) then { continue; };
|
|
||||||
if !((_y getOrDefault ["state", ""]) in ["assigned", "acknowledged"]) then { continue; };
|
|
||||||
private _dispatchOrder = +(_dispatchOrderRegistry getOrDefault [_x, createHashMap]);
|
|
||||||
if (_dispatchOrder isEqualTo createHashMap) then {
|
|
||||||
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
|
||||||
_taskID = _x;
|
|
||||||
} else {
|
|
||||||
_taskID = _dispatchOrder getOrDefault ["title", _x];
|
|
||||||
};
|
|
||||||
|
|
||||||
} forEach _assignmentRegistry;
|
|
||||||
|
|
||||||
_taskID
|
|
||||||
}],
|
}],
|
||||||
["syncGroups", compileFinal {
|
["syncGroups", compileFinal {
|
||||||
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
|
||||||
if (_assignmentRepository isNotEqualTo createHashMap) then {
|
|
||||||
_assignmentRepository call ["restorePersistedState", []];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _liveGroups = [];
|
private _liveGroups = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -106,16 +84,9 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
if (_groupID isEqualTo "") then { continue; };
|
if (_groupID isEqualTo "") then { continue; };
|
||||||
|
|
||||||
private _leaderUid = getPlayerUID _leader;
|
private _leaderUid = getPlayerUID _leader;
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_leaderUid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_leaderUid]];
|
||||||
if (_actor isEqualTo createHashMap && { _leaderUid isNotEqualTo "" }) then {
|
|
||||||
_actor = EGVAR(actor,ActorStore) call ["init", [_leaderUid]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
private _memberUids = [];
|
private _memberUids = [];
|
||||||
private _memberRoster = [];
|
private _memberRoster = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
private _memberUid = getPlayerUID _x;
|
private _memberUid = getPlayerUID _x;
|
||||||
private _memberState = toLowerANSI (lifeState _x);
|
private _memberState = toLowerANSI (lifeState _x);
|
||||||
@ -158,7 +129,6 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _nextRegistry = createHashMap;
|
private _nextRegistry = createHashMap;
|
||||||
private _profileRegistry = createHashMap;
|
|
||||||
{
|
{
|
||||||
if !(_x isEqualType createHashMap) then { continue; };
|
if !(_x isEqualType createHashMap) then { continue; };
|
||||||
private _groupID = _x getOrDefault ["groupId", ""];
|
private _groupID = _x getOrDefault ["groupId", ""];
|
||||||
@ -166,15 +136,8 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _groupRecord = +_x;
|
private _groupRecord = +_x;
|
||||||
_nextRegistry set [_groupID, _groupRecord];
|
_nextRegistry set [_groupID, _groupRecord];
|
||||||
_profileRegistry set [_groupID, createHashMapFromArray [
|
|
||||||
["groupId", _groupID],
|
|
||||||
["role", _groupRecord getOrDefault ["role", "infantry"]],
|
|
||||||
["status", _groupRecord getOrDefault ["status", "available"]]
|
|
||||||
]];
|
|
||||||
} forEach _mergedGroups;
|
} forEach _mergedGroups;
|
||||||
|
|
||||||
_self set ["groupProfileRegistry", _profileRegistry];
|
|
||||||
_self set ["groupRegistry", _nextRegistry];
|
|
||||||
_nextRegistry
|
_nextRegistry
|
||||||
}],
|
}],
|
||||||
["getGroupRecord", compileFinal {
|
["getGroupRecord", compileFinal {
|
||||||
@ -309,12 +272,6 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_groupRecord set ["status", _profile getOrDefault ["status", _groupRecord getOrDefault ["status", "available"]]];
|
_groupRecord set ["status", _profile getOrDefault ["status", _groupRecord getOrDefault ["status", "available"]]];
|
||||||
_groupRecord set ["lastUpdate", serverTime];
|
_groupRecord set ["lastUpdate", serverTime];
|
||||||
|
|
||||||
private _profileRegistry = _self getOrDefault ["groupProfileRegistry", createHashMap];
|
|
||||||
_groupRegistry set [_groupID, _groupRecord];
|
|
||||||
_self set ["groupRegistry", _groupRegistry];
|
|
||||||
_profileRegistry set [_groupID, _profile];
|
|
||||||
_self set ["groupProfileRegistry", _profileRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_profileData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_profileData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
|
|||||||
@ -27,10 +27,7 @@ GVAR(PermissionServiceBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { false };
|
if (_uid isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
if (_actor isEqualTo createHashMap) exitWith { false };
|
|
||||||
|
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
|
||||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
||||||
if (_org isEqualTo createHashMap) exitWith { false };
|
if (_org isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,10 @@
|
|||||||
* Initializes the CAD extension-state service that bridges live SQF
|
* Initializes the CAD extension-state service that bridges live SQF
|
||||||
* state to the Rust extension for hot CAD storage and recent history.
|
* state to the Rust extension for hot CAD storage and recent history.
|
||||||
*
|
*
|
||||||
|
* This is a live operational cache, not a durable persistence layer.
|
||||||
|
* CAD extension state is expected to reset with the current server or
|
||||||
|
* mission lifecycle.
|
||||||
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
|
|||||||
@ -24,8 +24,6 @@
|
|||||||
GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
["#type", "CadRequestRepositoryBaseClass"],
|
["#type", "CadRequestRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["requestRegistry", createHashMap];
|
|
||||||
_self set ["persistenceLoaded", false];
|
|
||||||
_self set ["validTypes", [
|
_self set ["validTypes", [
|
||||||
"medevac_9line",
|
"medevac_9line",
|
||||||
"ace_lace",
|
"ace_lace",
|
||||||
@ -39,20 +37,14 @@ GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
"emergency"
|
"emergency"
|
||||||
]];
|
]];
|
||||||
}],
|
}],
|
||||||
["restorePersistedState", compileFinal {
|
["loadRequestRegistry", compileFinal {
|
||||||
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
|
||||||
|
|
||||||
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
if (_persistenceService isEqualTo createHashMap) exitWith { createHashMap };
|
||||||
|
|
||||||
private _result = _persistenceService call ["loadRequests", []];
|
private _result = _persistenceService call ["loadRequests", []];
|
||||||
if !(_result getOrDefault ["success", false]) exitWith { false };
|
if !(_result getOrDefault ["success", false]) exitWith { createHashMap };
|
||||||
|
|
||||||
private _requestRegistry = +(_result getOrDefault ["data", createHashMap]);
|
+(_result getOrDefault ["data", createHashMap])
|
||||||
|
|
||||||
_self set ["requestRegistry", _requestRegistry];
|
|
||||||
_self set ["persistenceLoaded", true];
|
|
||||||
true
|
|
||||||
}],
|
}],
|
||||||
["submitRequest", compileFinal {
|
["submitRequest", compileFinal {
|
||||||
params [
|
params [
|
||||||
@ -68,8 +60,6 @@ GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["request", createHashMap]
|
["request", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
|
||||||
|
|
||||||
private _finalType = toLowerANSI _type;
|
private _finalType = toLowerANSI _type;
|
||||||
if !(_finalType in (_self getOrDefault ["validTypes", []])) exitWith {
|
if !(_finalType in (_self getOrDefault ["validTypes", []])) exitWith {
|
||||||
_result set ["message", "Invalid support request type."];
|
_result set ["message", "Invalid support request type."];
|
||||||
@ -132,10 +122,6 @@ GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _requestRegistry = _self getOrDefault ["requestRegistry", createHashMap];
|
|
||||||
_requestRegistry set [_requestID, _request];
|
|
||||||
_self set ["requestRegistry", _requestRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_submitData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_submitData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
@ -156,9 +142,7 @@ GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["request", createHashMap]
|
["request", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
_self call ["restorePersistedState", []];
|
private _requestRegistry = _self call ["loadRequestRegistry", []];
|
||||||
|
|
||||||
private _requestRegistry = _self getOrDefault ["requestRegistry", createHashMap];
|
|
||||||
private _request = +(_requestRegistry getOrDefault [_requestID, createHashMap]);
|
private _request = +(_requestRegistry getOrDefault [_requestID, createHashMap]);
|
||||||
if (_request isEqualTo createHashMap) exitWith {
|
if (_request isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", "Support request could not be resolved."];
|
_result set ["message", "Support request could not be resolved."];
|
||||||
@ -188,8 +172,6 @@ GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
||||||
_request = +(_closeData getOrDefault ["request", _request]);
|
_request = +(_closeData getOrDefault ["request", _request]);
|
||||||
_requestRegistry deleteAt _requestID;
|
|
||||||
_self set ["requestRegistry", _requestRegistry];
|
|
||||||
|
|
||||||
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
||||||
if (_activityEntry isNotEqualTo createHashMap) then {
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
|
|||||||
["#type", "IFuelEconomy"],
|
["#type", "IFuelEconomy"],
|
||||||
["#create", {
|
["#create", {
|
||||||
GVAR(FuelCost) = 5;
|
GVAR(FuelCost) = 5;
|
||||||
GVAR(FuelRegistry) = createHashMap;
|
_self set ["fuelRegistry", createHashMap];
|
||||||
|
|
||||||
["INFO", "Fuel Store Initialized!", nil, nil] call EFUNC(common,log);
|
["INFO", "Fuel Store Initialized!", nil, nil] call EFUNC(common,log);
|
||||||
}],
|
}],
|
||||||
@ -34,15 +34,17 @@ GVAR(FEconomyStore) = createHashMapObject [[
|
|||||||
|
|
||||||
private _index = netId _target;
|
private _index = netId _target;
|
||||||
private _uid = getPlayerUID _unit;
|
private _uid = getPlayerUID _unit;
|
||||||
|
private _fuelRegistry = _self getOrDefault ["fuelRegistry", createHashMap];
|
||||||
|
|
||||||
GVAR(FuelRegistry) set [_index, _uid];
|
_fuelRegistry set [_index, _uid];
|
||||||
SETVAR(_target,liters,0);
|
SETVAR(_target,liters,0);
|
||||||
}],
|
}],
|
||||||
["stop", {
|
["stop", {
|
||||||
params ["_source", "_target"];
|
params ["_source", "_target"];
|
||||||
|
|
||||||
private _index = netId _target;
|
private _index = netId _target;
|
||||||
private _uid = GVAR(FuelRegistry) get _index;
|
private _fuelRegistry = _self getOrDefault ["fuelRegistry", createHashMap];
|
||||||
|
private _uid = _fuelRegistry get _index;
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
|
||||||
private _totalLiters = GETVAR(_target,liters,0);
|
private _totalLiters = GETVAR(_target,liters,0);
|
||||||
@ -51,7 +53,7 @@ GVAR(FEconomyStore) = createHashMapObject [[
|
|||||||
private _formattedTotalLiters = _totalLiters toFixed 2;
|
private _formattedTotalLiters = _totalLiters toFixed 2;
|
||||||
|
|
||||||
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Total Cost: $%2", _formattedTotalLiters, _formattedTotalCost]], _player] call CFUNC(targetEvent);
|
[CRPC(notifications,recieveNotification), ["info", "Refueling", format ["Refueling complete: %1L<br />Total Cost: $%2", _formattedTotalLiters, _formattedTotalCost]], _player] call CFUNC(targetEvent);
|
||||||
GVAR(FuelRegistry) deleteAt _index;
|
_fuelRegistry deleteAt _index;
|
||||||
}]
|
}]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
|||||||
@ -31,16 +31,18 @@ private _chunkPrefix = "FORGE_TRANSPORT_CHUNK:";
|
|||||||
private _chunkPrefixLength = count toArray _chunkPrefix;
|
private _chunkPrefixLength = count toArray _chunkPrefix;
|
||||||
private _unsupportedRoutePrefix = "Error: Unsupported transport route";
|
private _unsupportedRoutePrefix = "Error: Unsupported transport route";
|
||||||
private _requestChunkSize = 12000;
|
private _requestChunkSize = 12000;
|
||||||
|
// Keep bootstrap create/update calls on the direct extension path by default.
|
||||||
|
// Actor/bank initialization payloads are small enough for normal callExtension
|
||||||
|
// usage, and their correctness depends on preserving the native argument shape
|
||||||
|
// of [uid, json]. Transport remains available automatically for genuinely large
|
||||||
|
// requests through the chunked-request path below.
|
||||||
private _transportResponseFunctions = [
|
private _transportResponseFunctions = [
|
||||||
"actor:get",
|
"actor:get",
|
||||||
"actor:create",
|
|
||||||
"actor:update",
|
|
||||||
"actor:hot:init",
|
"actor:hot:init",
|
||||||
"actor:hot:get",
|
"actor:hot:get",
|
||||||
|
"actor:hot:keys",
|
||||||
"actor:hot:save",
|
"actor:hot:save",
|
||||||
"bank:get",
|
"bank:get",
|
||||||
"bank:create",
|
|
||||||
"bank:update",
|
|
||||||
"bank:hot:init",
|
"bank:hot:init",
|
||||||
"bank:hot:get",
|
"bank:hot:get",
|
||||||
"bank:hot:save",
|
"bank:hot:save",
|
||||||
@ -126,7 +128,10 @@ private _checkRedisAvailability = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _buildTransportArgumentsJson = {
|
private _buildTransportArgumentsJson = {
|
||||||
params [["_rawArguments", [], [[]]]];
|
private _rawArguments = _this;
|
||||||
|
if !(_rawArguments isEqualType []) then {
|
||||||
|
_rawArguments = [_rawArguments];
|
||||||
|
};
|
||||||
|
|
||||||
private _stringArguments = _rawArguments apply {
|
private _stringArguments = _rawArguments apply {
|
||||||
if (_x isEqualType "") exitWith { _x };
|
if (_x isEqualType "") exitWith { _x };
|
||||||
@ -161,10 +166,12 @@ if (_functionLower in ["status", "version"]) exitWith {
|
|||||||
[_function, _arguments] call _callExtensionCommand
|
[_function, _arguments] call _callExtensionCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
private _argumentsJson = [_arguments] call _buildTransportArgumentsJson;
|
private _argumentsJson = _arguments call _buildTransportArgumentsJson;
|
||||||
private _usesTransportResponse = _functionLower in _transportResponseFunctions;
|
private _usesTransportResponse = _functionLower in _transportResponseFunctions;
|
||||||
private _usesChunkedRequest = (count toArray _argumentsJson) > _requestChunkSize;
|
private _usesChunkedRequest = (count toArray _argumentsJson) > _requestChunkSize;
|
||||||
|
|
||||||
|
// Most calls should stay direct unless they either need chunked response
|
||||||
|
// assembly or the request body is large enough to require staging.
|
||||||
if !(_usesTransportResponse || { _usesChunkedRequest }) exitWith {
|
if !(_usesTransportResponse || { _usesChunkedRequest }) exitWith {
|
||||||
[_function, _arguments] call _callExtensionCommand
|
[_function, _arguments] call _callExtensionCommand
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initVAStore.sqf
|
* File: fnc_initVAStore.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2025-12-17
|
* Date: 2025-12-17
|
||||||
* Last Update: 2026-04-01
|
* Last Update: 2026-04-05
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -28,7 +28,7 @@ GVAR(VArsenalModel) = compileFinal createHashMapObject [[
|
|||||||
private _vArsenal = createHashMap;
|
private _vArsenal = createHashMap;
|
||||||
|
|
||||||
_vArsenal set ["backpacks", ["B_AssaultPack_rgr"]];
|
_vArsenal set ["backpacks", ["B_AssaultPack_rgr"]];
|
||||||
_vArsenal set ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_IG_Guerrilla_6_1", "V_TacVest_oli", "ACE_EarPlugs"]];
|
_vArsenal set ["items", ["FirstAidKit", "G_Combat", "H_Cap_blk_ION", "H_HelmetB", "ItemCompass", "ItemGPS", "ItemMap", "ItemRadio", "ItemWatch", "U_BG_Guerrilla_6_1", "V_TacVest_oli", "ACE_EarPlugs"]];
|
||||||
_vArsenal set ["magazines", ["16Rnd_9x21_Mag", "30Rnd_65x39_caseless_black_mag", "Chemlight_blue", "Chemlight_green", "Chemlight_red", "Chemlight_yellow", "HandGrenade", "SmokeShell", "SmokeShellBlue", "SmokeShellGreen", "SmokeShellOrange", "SmokeShellPurple", "SmokeShellRed", "SmokeShellYellow"]];
|
_vArsenal set ["magazines", ["16Rnd_9x21_Mag", "30Rnd_65x39_caseless_black_mag", "Chemlight_blue", "Chemlight_green", "Chemlight_red", "Chemlight_yellow", "HandGrenade", "SmokeShell", "SmokeShellBlue", "SmokeShellGreen", "SmokeShellOrange", "SmokeShellPurple", "SmokeShellRed", "SmokeShellYellow"]];
|
||||||
_vArsenal set ["weapons", ["arifle_MX_F", "hgun_P07_F"]];
|
_vArsenal set ["weapons", ["arifle_MX_F", "hgun_P07_F"]];
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
PREP(initStores);
|
PREP(initStores);
|
||||||
|
PREP(initValidationHarness);
|
||||||
PREP(saveHotState);
|
PREP(saveHotState);
|
||||||
|
|||||||
@ -47,3 +47,6 @@ if (isNil QEGVAR(org,OrgStore)) then { call EFUNC(org,initOrgStore); };
|
|||||||
|
|
||||||
// Store
|
// Store
|
||||||
if (isNil QEGVAR(store,StoreStore)) then { call EFUNC(store,initStoreStore); };
|
if (isNil QEGVAR(store,StoreStore)) then { call EFUNC(store,initStoreStore); };
|
||||||
|
|
||||||
|
// Validation Harness
|
||||||
|
if (isNil QGVAR(ValidationHarness)) then { call FUNC(initValidationHarness); };
|
||||||
|
|||||||
213
arma/server/addons/main/functions/fnc_initValidationHarness.sqf
Normal file
213
arma/server/addons/main/functions/fnc_initValidationHarness.sqf
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Initializes the server-side validation harness for targeted runtime smoke
|
||||||
|
* checks around high-risk multi-module flows.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Validation harness object <HASHMAP OBJECT>
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_server_main_fnc_initValidationHarness;
|
||||||
|
*
|
||||||
|
* Public: No
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(ValidationHarness) = createHashMapObject [[
|
||||||
|
["#type", "ValidationHarness"],
|
||||||
|
["buildResult", compileFinal {
|
||||||
|
params [
|
||||||
|
["_action", "", [""]],
|
||||||
|
["_success", false, [false]],
|
||||||
|
["_message", "", [""]],
|
||||||
|
["_data", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
createHashMapFromArray [
|
||||||
|
["action", _action],
|
||||||
|
["success", _success],
|
||||||
|
["message", _message],
|
||||||
|
["data", _data]
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
["logResult", compileFinal {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_result isEqualTo createHashMap) exitWith { _result };
|
||||||
|
|
||||||
|
private _level = ["WARNING", "INFO"] select (_result getOrDefault ["success", false]);
|
||||||
|
private _action = _result getOrDefault ["action", "validation"];
|
||||||
|
private _message = _result getOrDefault ["message", ""];
|
||||||
|
[_level, format ["Validation harness '%1': %2", _action, _message]] call EFUNC(common,log);
|
||||||
|
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["normalizeMapArg", compileFinal {
|
||||||
|
params [
|
||||||
|
["_value", createHashMap, [createHashMap, ""]],
|
||||||
|
["_fallback", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_value isEqualType createHashMap) exitWith { +_value };
|
||||||
|
if !(_value isEqualType "") exitWith { +_fallback };
|
||||||
|
if (_value isEqualTo "") exitWith { +_fallback };
|
||||||
|
|
||||||
|
private _parsed = fromJSON _value;
|
||||||
|
if !(_parsed isEqualType createHashMap) exitWith { +_fallback };
|
||||||
|
|
||||||
|
_parsed
|
||||||
|
}],
|
||||||
|
["run", compileFinal {
|
||||||
|
params [["_action", "", [""]], ["_arguments", [], [[]]]];
|
||||||
|
|
||||||
|
private _actionLower = toLowerANSI _action;
|
||||||
|
if (_actionLower isEqualTo "") exitWith {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", ["unknown", false, "A validation action is required.", createHashMap]]]]
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (_actionLower) do {
|
||||||
|
case "save_hot_state": {
|
||||||
|
_arguments params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
private _success = [_uid] call FUNC(saveHotState);
|
||||||
|
private _message = [
|
||||||
|
format ["Hot-state save failed for '%1'.", _uid],
|
||||||
|
format ["Hot-state save completed for '%1'.", [_uid, "all hot state"] select (_uid isEqualTo "")]
|
||||||
|
] select _success;
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [
|
||||||
|
_actionLower,
|
||||||
|
_success,
|
||||||
|
_message,
|
||||||
|
createHashMapFromArray [["uid", _uid]]
|
||||||
|
]]]]
|
||||||
|
};
|
||||||
|
case "store_checkout": {
|
||||||
|
_arguments params [["_uid", "", [""]], ["_payload", createHashMap, [createHashMap, ""]]];
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_uid isEqualTo "" || { isNull _player }) exitWith {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, false, "A valid online player UID is required for store checkout validation.", createHashMap]]]]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _payloadMap = _self call ["normalizeMapArg", [_payload, createHashMap]];
|
||||||
|
if (_payloadMap isEqualTo createHashMap) exitWith {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, false, "Store checkout validation payload was invalid.", createHashMap]]]]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _result = EGVAR(store,StoreStore) call ["checkout", [_uid, _player, toJSON _payloadMap]];
|
||||||
|
private _success = _result getOrDefault ["success", false];
|
||||||
|
private _message = _result getOrDefault ["message", "Store checkout validation completed."];
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, _success, _message, _result]]]]
|
||||||
|
};
|
||||||
|
case "org_assign_credit_line": {
|
||||||
|
_arguments params [
|
||||||
|
["_requesterUid", "", [""]],
|
||||||
|
["_memberUid", "", [""]],
|
||||||
|
["_memberName", "", [""]],
|
||||||
|
["_amount", 0, [0]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = EGVAR(org,OrgStore) call ["assignCreditLine", [_requesterUid, _memberUid, _memberName, _amount]];
|
||||||
|
private _success = _result getOrDefault ["success", false];
|
||||||
|
private _message = _result getOrDefault ["message", "Credit line validation completed."];
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, _success, _message, _result]]]]
|
||||||
|
};
|
||||||
|
case "bank_credit_repayment": {
|
||||||
|
_arguments params [["_uid", "", [""]], ["_amount", 0, [0]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, false, "A valid UID is required for bank credit repayment validation.", createHashMap]]]]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _beforeAccount = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||||
|
private _beforeOrgState = EGVAR(bank,BankPayloadBuilder) call ["resolveOrgState", [_uid]];
|
||||||
|
private _success = EGVAR(bank,BankStore) call ["repayCreditLine", [_uid, _amount]];
|
||||||
|
private _afterAccount = EGVAR(bank,BankStore) call ["get", [_uid, ""]];
|
||||||
|
private _afterOrgState = EGVAR(bank,BankPayloadBuilder) call ["resolveOrgState", [_uid]];
|
||||||
|
|
||||||
|
private _message = [
|
||||||
|
format ["Bank credit repayment validation failed for %1.", _uid],
|
||||||
|
format ["Bank credit repayment validation completed for %1.", _uid]
|
||||||
|
] select _success;
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [
|
||||||
|
_actionLower,
|
||||||
|
_success,
|
||||||
|
_message,
|
||||||
|
createHashMapFromArray [
|
||||||
|
["beforeAccount", _beforeAccount],
|
||||||
|
["afterAccount", _afterAccount],
|
||||||
|
["beforeOrgState", _beforeOrgState],
|
||||||
|
["afterOrgState", _afterOrgState]
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
};
|
||||||
|
case "task_reward_context": {
|
||||||
|
_arguments params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
private _context = EGVAR(task,TaskStore) call ["resolveRewardContext", [_taskID]];
|
||||||
|
private _success = _taskID isNotEqualTo "" && { (_context getOrDefault ["orgID", ""]) isNotEqualTo "" };
|
||||||
|
private _message = [
|
||||||
|
format ["No reward context was available for task %1.", _taskID],
|
||||||
|
format ["Resolved reward context for task %1.", _taskID]
|
||||||
|
] select _success;
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, _success, _message, _context]]]]
|
||||||
|
};
|
||||||
|
case "task_apply_rating": {
|
||||||
|
_arguments params [["_taskID", "", [""]], ["_delta", 0, [0]]];
|
||||||
|
|
||||||
|
private _result = EGVAR(task,TaskStore) call ["applyRatingOutcome", [_taskID, _delta]];
|
||||||
|
private _success = _result getOrDefault ["success", true];
|
||||||
|
private _message = [
|
||||||
|
_result getOrDefault ["message", format ["Task rating validation failed for %1.", _taskID]],
|
||||||
|
format ["Task rating validation completed for %1.", _taskID]
|
||||||
|
] select _success;
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, _success, _message, _result]]]]
|
||||||
|
};
|
||||||
|
case "task_apply_rewards": {
|
||||||
|
_arguments params [["_taskID", "", [""]], ["_rewards", createHashMap, [createHashMap, ""]]];
|
||||||
|
|
||||||
|
private _rewardsMap = _self call ["normalizeMapArg", [_rewards, createHashMap]];
|
||||||
|
if (_taskID isEqualTo "" || { _rewardsMap isEqualTo createHashMap }) exitWith {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, false, "Task reward validation requires a task ID and reward payload.", createHashMap]]]]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _rewardContext = EGVAR(task,TaskStore) call ["resolveRewardContext", [_taskID]];
|
||||||
|
private _beforeOrg = EGVAR(org,OrgStore) call ["loadById", [_rewardContext getOrDefault ["orgID", ""]]];
|
||||||
|
private _success = [_taskID, _rewardsMap] call EFUNC(task,handleTaskRewards);
|
||||||
|
private _afterOrg = EGVAR(org,OrgStore) call ["loadById", [_rewardContext getOrDefault ["orgID", ""]]];
|
||||||
|
|
||||||
|
private _message = [
|
||||||
|
format ["Task reward validation failed for %1.", _taskID],
|
||||||
|
format ["Task reward validation completed for %1.", _taskID]
|
||||||
|
] select _success;
|
||||||
|
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [
|
||||||
|
_actionLower,
|
||||||
|
_success,
|
||||||
|
_message,
|
||||||
|
createHashMapFromArray [
|
||||||
|
["rewardContext", _rewardContext],
|
||||||
|
["beforeOrg", _beforeOrg],
|
||||||
|
["afterOrg", _afterOrg]
|
||||||
|
]
|
||||||
|
]]]]
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
_self call ["logResult", [_self call ["buildResult", [_actionLower, false, format ["Unknown validation action '%1'.", _actionLower], createHashMap]]]]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
]];
|
||||||
|
|
||||||
|
GVAR(ValidationHarness)
|
||||||
@ -28,12 +28,12 @@ if (_uid isEqualTo "") then {
|
|||||||
};
|
};
|
||||||
} forEach allPlayers;
|
} forEach allPlayers;
|
||||||
|
|
||||||
if !(isNil QEGVAR(actor,Registry)) then {
|
if !(isNil QEGVAR(actor,ActorStore)) then {
|
||||||
{
|
{
|
||||||
if (_x isNotEqualTo "") then {
|
if (_x isNotEqualTo "") then {
|
||||||
_uids pushBackUnique _x;
|
_uids pushBackUnique _x;
|
||||||
};
|
};
|
||||||
} forEach keys EGVAR(actor,Registry);
|
} forEach (EGVAR(actor,ActorStore) call ["listHotUids", []]);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
_uids pushBack _uid;
|
_uids pushBack _uid;
|
||||||
|
|||||||
@ -85,6 +85,127 @@ PREP_RECOMPILE_END;
|
|||||||
]], _requester] call CFUNC(targetEvent);
|
]], _requester] call CFUNC(targetEvent);
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestInviteOrgMember), {
|
||||||
|
params [["_uid", "", [""]], ["_targetUid", "", [""]], ["_targetName", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _targetUid isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid org invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requester = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_requester isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["inviteMember", [_uid, _targetUid, _targetName]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach [_uid, _result getOrDefault ["targetUid", _targetUid]];
|
||||||
|
|
||||||
|
private _targetPlayer = [_result getOrDefault ["targetUid", _targetUid]] call EFUNC(common,getPlayer);
|
||||||
|
if (_targetPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(notifications,recieveNotification), [
|
||||||
|
"info",
|
||||||
|
"Organization Invite",
|
||||||
|
"You received an organization invite. Open the organization portal to accept or decline it.",
|
||||||
|
7000
|
||||||
|
], _targetPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteOrg), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to send organization invite."]]
|
||||||
|
]], _requester] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestAcceptOrgInvite), {
|
||||||
|
params [["_uid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid accept invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["acceptInvite", [_uid, _orgID]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _actorPatch = _result getOrDefault ["actorPatch", createHashMap];
|
||||||
|
if (_actorPatch isNotEqualTo createHashMap) then {
|
||||||
|
[CRPC(actor,responseSyncActor), [_actorPatch], _player] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _syncTargets = [_uid];
|
||||||
|
{
|
||||||
|
private _orgData = GVAR(OrgStore) call ["loadById", [_x]];
|
||||||
|
if !(_orgData isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberUid = _y getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
_syncTargets pushBackUnique _memberUid;
|
||||||
|
};
|
||||||
|
} forEach (_orgData getOrDefault ["members", createHashMap]);
|
||||||
|
} forEach (_result getOrDefault ["affectedOrgIds", []]);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach _syncTargets;
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteDecision), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to accept organization invite."]],
|
||||||
|
["action", "accept"]
|
||||||
|
]], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestDeclineOrgInvite), {
|
||||||
|
params [["_uid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid decline invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["declineInvite", [_uid, _orgID]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _syncTargets = [_uid];
|
||||||
|
{
|
||||||
|
private _orgData = GVAR(OrgStore) call ["loadById", [_x]];
|
||||||
|
if !(_orgData isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberUid = _y getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
_syncTargets pushBackUnique _memberUid;
|
||||||
|
};
|
||||||
|
} forEach (_orgData getOrDefault ["members", createHashMap]);
|
||||||
|
} forEach (_result getOrDefault ["affectedOrgIds", []]);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach _syncTargets;
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteDecision), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to decline organization invite."]],
|
||||||
|
["action", "decline"]
|
||||||
|
]], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestLeaveOrg), {
|
[QGVAR(requestLeaveOrg), {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initOrgStore.sqf
|
* File: fnc_initOrgStore.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-02-13
|
* Date: 2026-02-13
|
||||||
* Last Update: 2026-04-01
|
* Last Update: 2026-04-04
|
||||||
* Public: Yes
|
* Public: Yes
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -36,6 +36,7 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
_org set ["assets", createHashMap];
|
_org set ["assets", createHashMap];
|
||||||
_org set ["fleet", createHashMap];
|
_org set ["fleet", createHashMap];
|
||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
|
_org set ["pending_invites", createHashMap];
|
||||||
|
|
||||||
_org
|
_org
|
||||||
}],
|
}],
|
||||||
@ -111,6 +112,13 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
|
|
||||||
_org set ["credit_lines", _creditLines];
|
_org set ["credit_lines", _creditLines];
|
||||||
|
|
||||||
|
private _pendingInvites = _org getOrDefault ["pending_invites", createHashMap];
|
||||||
|
if !(_pendingInvites isEqualType createHashMap) then {
|
||||||
|
_pendingInvites = createHashMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
_org set ["pending_invites", _pendingInvites];
|
||||||
|
|
||||||
_org
|
_org
|
||||||
}],
|
}],
|
||||||
["validate", compileFinal {
|
["validate", compileFinal {
|
||||||
@ -158,7 +166,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
_defaultOrg
|
_defaultOrg
|
||||||
};
|
};
|
||||||
@ -173,7 +182,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _defaultJson = _self call ["toJSON", [_defaultOrg]];
|
private _defaultJson = _self call ["toJSON", [_defaultOrg]];
|
||||||
@ -191,7 +201,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -240,6 +251,24 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_data
|
_data
|
||||||
}],
|
}],
|
||||||
|
["callHotOrgArray", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||||
|
|
||||||
|
if (_function isEqualTo "") exitWith { [] };
|
||||||
|
|
||||||
|
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
|
if !(_isSuccess) exitWith { [] };
|
||||||
|
if !(_result isEqualType "") exitWith { [] };
|
||||||
|
if ((_result find "Error:") == 0) exitWith {
|
||||||
|
["ERROR", format ["Org extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||||
|
[]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _data = fromJSON _result;
|
||||||
|
if !(_data isEqualType []) exitWith { [] };
|
||||||
|
|
||||||
|
_data
|
||||||
|
}],
|
||||||
["syncHotOrg", compileFinal {
|
["syncHotOrg", compileFinal {
|
||||||
params [["_org", createHashMap, [createHashMap]]];
|
params [["_org", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
@ -256,16 +285,13 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
if (_uid isEqualTo "") exitWith { "default" };
|
if (_uid isEqualTo "") exitWith { "default" };
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
EGVAR(actor,ActorStore) call ["getOrganization", [_uid]]
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
_orgID
|
|
||||||
}],
|
}],
|
||||||
["resolveActorName", compileFinal {
|
["resolveActorName", compileFinal {
|
||||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _memberName = _actor getOrDefault ["name", ""];
|
private _memberName = _actor getOrDefault ["name", ""];
|
||||||
if (_memberName isEqualTo "" && { _player isNotEqualTo objNull }) then {
|
if ((_memberName isEqualTo "" || { toLowerANSI _memberName isEqualTo "unknown" }) && { _player isNotEqualTo objNull }) then {
|
||||||
_memberName = name _player;
|
_memberName = name _player;
|
||||||
};
|
};
|
||||||
if (_memberName isEqualTo "") then { _memberName = "Unknown"; };
|
if (_memberName isEqualTo "") then { _memberName = "Unknown"; };
|
||||||
@ -276,8 +302,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith { createHashMap };
|
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith { createHashMap };
|
||||||
|
|
||||||
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, false]];
|
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [_uid, "organization", _orgID, true]];
|
||||||
private _updatedActor = EGVAR(actor,ActorStore) call ["get", [_uid, ""]];
|
private _updatedActor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
if (
|
if (
|
||||||
!(_updatedActor isEqualType createHashMap)
|
!(_updatedActor isEqualType createHashMap)
|
||||||
|| { _updatedActor isEqualTo createHashMap }
|
|| { _updatedActor isEqualTo createHashMap }
|
||||||
@ -290,7 +316,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
_forcedActor set ["organization", _orgID];
|
_forcedActor set ["organization", _orgID];
|
||||||
_updatedActor = EGVAR(actor,ActorStore) call ["override", [_uid, _forcedActor, false]];
|
_updatedActor = EGVAR(actor,ActorStore) call ["override", [_uid, _forcedActor, true]];
|
||||||
if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) then {
|
if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) then {
|
||||||
_actorPatch = createHashMapFromArray [["organization", _orgID]];
|
_actorPatch = createHashMapFromArray [["organization", _orgID]];
|
||||||
};
|
};
|
||||||
@ -359,6 +385,203 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_self call ["callHotOrg", ["org:hot:ensure_member", [toJSON _context]]]
|
_self call ["callHotOrg", ["org:hot:ensure_member", [toJSON _context]]]
|
||||||
}],
|
}],
|
||||||
|
["listMemberInvites", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith { [] };
|
||||||
|
|
||||||
|
_self call ["callHotOrgArray", ["org:hot:member_invites", [_uid]]]
|
||||||
|
}],
|
||||||
|
["inviteMember", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_targetUid", "", [""]], ["_targetName", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["targetUid", _targetUid],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _targetUid isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid organization invite target is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _requesterIsDefaultOrgCeo = (
|
||||||
|
_requesterPlayer isNotEqualTo objNull
|
||||||
|
&& { _requesterOrgID isEqualTo "default" }
|
||||||
|
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" }
|
||||||
|
);
|
||||||
|
private _targetOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_targetUid]];
|
||||||
|
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _requesterOrgID],
|
||||||
|
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||||
|
["targetUid", _targetUid],
|
||||||
|
["targetName", _targetName],
|
||||||
|
["targetOrgId", _targetOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:invite_member", [toJSON _context]]];
|
||||||
|
if (_envelope isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Unable to send organization invite."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Invitation sent."]];
|
||||||
|
_result set ["targetUid", _envelope getOrDefault ["targetUid", _targetUid]];
|
||||||
|
_self call ["persistMutationResult", [_requesterOrgID, _result, "Organization invite"]]
|
||||||
|
}],
|
||||||
|
["acceptInvite", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["actorPatch", createHashMap],
|
||||||
|
["affectedOrgIds", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid invite selection is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _existingOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _orgID],
|
||||||
|
["existingOrgId", _existingOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
["org:hot:accept_invite", [toJSON _context]] call EFUNC(extension,extCall) params ["_rawResult", "_isSuccess"];
|
||||||
|
if !_isSuccess exitWith {
|
||||||
|
_result set ["message", "Organization invite service was unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if !(_rawResult isEqualType "") exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned an invalid response."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if ((_rawResult find "Error:") == 0) exitWith {
|
||||||
|
_result set ["message", _rawResult select [7]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _envelope = fromJSON _rawResult;
|
||||||
|
if !(_envelope isEqualType createHashMap) exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned malformed data."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _invitedOrg = _self call ["syncHotOrg", [_envelope getOrDefault ["invitedOrg", createHashMap]]];
|
||||||
|
if (_invitedOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["invitedOrg", _invitedOrg];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _previousOrgData = _envelope getOrDefault ["previousOrg", createHashMap];
|
||||||
|
if (_previousOrgData isEqualType createHashMap && { _previousOrgData isNotEqualTo createHashMap }) then {
|
||||||
|
private _syncedPreviousOrg = _self call ["syncHotOrg", [_previousOrgData]];
|
||||||
|
if (_syncedPreviousOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["previousOrg", _syncedPreviousOrg];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _actorOrg = _envelope getOrDefault ["actorOrganization", _orgID];
|
||||||
|
private _actorPatch = _self call ["applyActorOrganization", [_requesterUid, _actorOrg, _requesterActor]];
|
||||||
|
if (_actorPatch isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Failed to assign the player to the invited organization."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _affectedOrgIds = [_actorOrg];
|
||||||
|
private _previousOrg = _envelope getOrDefault ["previousOrg", createHashMap];
|
||||||
|
if (_previousOrg isEqualType createHashMap && { _previousOrg isNotEqualTo createHashMap }) then {
|
||||||
|
private _previousOrgID = _previousOrg getOrDefault ["id", ""];
|
||||||
|
if (_previousOrgID isNotEqualTo "") then {
|
||||||
|
_affectedOrgIds pushBackUnique _previousOrgID;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
_self call ["saveById", [_x]];
|
||||||
|
} forEach _affectedOrgIds;
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Organization invite accepted."]];
|
||||||
|
_result set ["actorPatch", _actorPatch];
|
||||||
|
_result set ["affectedOrgIds", _affectedOrgIds];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["declineInvite", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["affectedOrgIds", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid invite selection is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _existingOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _orgID],
|
||||||
|
["existingOrgId", _existingOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
["org:hot:decline_invite", [toJSON _context]] call EFUNC(extension,extCall) params ["_rawResult", "_isSuccess"];
|
||||||
|
if !_isSuccess exitWith {
|
||||||
|
_result set ["message", "Organization invite service was unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if !(_rawResult isEqualType "") exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned an invalid response."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if ((_rawResult find "Error:") == 0) exitWith {
|
||||||
|
_result set ["message", _rawResult select [7]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _envelope = fromJSON _rawResult;
|
||||||
|
if !(_envelope isEqualType createHashMap) exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned malformed data."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _invitedOrg = _self call ["syncHotOrg", [_envelope getOrDefault ["invitedOrg", createHashMap]]];
|
||||||
|
if (_invitedOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["invitedOrg", _invitedOrg];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["saveById", [_orgID]];
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Organization invite declined."]];
|
||||||
|
_result set ["affectedOrgIds", [_orgID]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["leave", compileFinal {
|
["leave", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
@ -375,8 +598,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
||||||
private _context = createHashMapFromArray [
|
private _context = createHashMapFromArray [
|
||||||
["requesterUid", _uid],
|
["requesterUid", _uid],
|
||||||
@ -418,8 +641,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
||||||
private _context = createHashMapFromArray [
|
private _context = createHashMapFromArray [
|
||||||
["requesterUid", _uid],
|
["requesterUid", _uid],
|
||||||
@ -438,7 +661,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
private _memberUid = _x getOrDefault ["uid", ""];
|
private _memberUid = _x getOrDefault ["uid", ""];
|
||||||
if (_memberUid isEqualTo "") then { continue; };
|
if (_memberUid isEqualTo "") then { continue; };
|
||||||
|
|
||||||
private _memberActor = EGVAR(actor,Registry) getOrDefault [_memberUid, createHashMap];
|
private _memberActor = EGVAR(actor,ActorStore) call ["load", [_memberUid]];
|
||||||
private _actorPatch = _self call ["applyActorOrganization", [_memberUid, _x getOrDefault ["actorOrganization", "default"], _memberActor]];
|
private _actorPatch = _self call ["applyActorOrganization", [_memberUid, _x getOrDefault ["actorOrganization", "default"], _memberActor]];
|
||||||
if (_actorPatch isEqualTo createHashMap) then {
|
if (_actorPatch isEqualTo createHashMap) then {
|
||||||
["WARNING", format ["Failed to restore actor organization for %1 after org disband.", _memberUid]] call EFUNC(common,log);
|
["WARNING", format ["Failed to restore actor organization for %1 after org disband.", _memberUid]] call EFUNC(common,log);
|
||||||
@ -471,7 +694,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["success", false],
|
["success", false],
|
||||||
["message", ""],
|
["message", ""],
|
||||||
["patch", createHashMap],
|
["patch", createHashMap],
|
||||||
["memberUids", []]
|
["memberUids", []],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_requesterUid isEqualTo "" || { _memberUid isEqualTo "" } || { _amount <= 0 }) exitWith {
|
if (_requesterUid isEqualTo "" || { _memberUid isEqualTo "" } || { _amount <= 0 }) exitWith {
|
||||||
@ -479,10 +704,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
private _requesterIsDefaultOrgCeo = (
|
private _requesterIsDefaultOrgCeo = (
|
||||||
_requesterPlayer isNotEqualTo objNull
|
_requesterPlayer isNotEqualTo objNull
|
||||||
@ -509,7 +731,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["message", _envelope getOrDefault ["message", "Credit line assigned."]];
|
_result set ["message", _envelope getOrDefault ["message", "Credit line assigned."]];
|
||||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||||
_result
|
_self call ["persistMutationResult", [_orgID, _result, "Credit line assignment"]]
|
||||||
}],
|
}],
|
||||||
["repayCreditLine", compileFinal {
|
["repayCreditLine", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_amount", 0, [0]]];
|
params [["_requesterUid", "", [""]], ["_amount", 0, [0]]];
|
||||||
@ -518,7 +740,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["success", false],
|
["success", false],
|
||||||
["message", ""],
|
["message", ""],
|
||||||
["patch", createHashMap],
|
["patch", createHashMap],
|
||||||
["memberUids", []]
|
["memberUids", []],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_requesterUid isEqualTo "" || { _amount <= 0 }) exitWith {
|
if (_requesterUid isEqualTo "" || { _amount <= 0 }) exitWith {
|
||||||
@ -526,10 +750,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _context = createHashMapFromArray [
|
private _context = createHashMapFromArray [
|
||||||
["requesterUid", _requesterUid],
|
["requesterUid", _requesterUid],
|
||||||
["orgId", _orgID],
|
["orgId", _orgID],
|
||||||
@ -546,13 +767,38 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["message", _envelope getOrDefault ["message", "Credit repayment posted."]];
|
_result set ["message", _envelope getOrDefault ["message", "Credit repayment posted."]];
|
||||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||||
_result
|
_self call ["persistMutationResult", [_orgID, _result, "Credit repayment"]]
|
||||||
}],
|
}],
|
||||||
["buildPortalPayload", compileFinal {
|
["buildPortalPayload", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
GVAR(OrgPayloadBuilder) call ["buildPortalPayload", [_uid]]
|
GVAR(OrgPayloadBuilder) call ["buildPortalPayload", [_uid]]
|
||||||
}],
|
}],
|
||||||
|
["persistMutationResult", compileFinal {
|
||||||
|
params [
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_result", createHashMap, [createHashMap]],
|
||||||
|
["_actionLabel", "Organization update", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "" || { _result isEqualTo createHashMap }) exitWith { _result };
|
||||||
|
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
_result set ["persisted", false];
|
||||||
|
_result set ["persistenceMessage", ""];
|
||||||
|
|
||||||
|
private _savedOrg = _self call ["saveById", [_orgID]];
|
||||||
|
if (_savedOrg isEqualTo createHashMap) exitWith {
|
||||||
|
private _message = format ["%1 applied, but durable save failed for organization %2.", _actionLabel, _orgID];
|
||||||
|
["ERROR", _message] call EFUNC(common,log);
|
||||||
|
_result set ["persistenceMessage", _message];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["persisted", true];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["chargeCheckout", compileFinal {
|
["chargeCheckout", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]], ["_source", "org_funds", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
params [["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]], ["_source", "org_funds", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
||||||
|
|
||||||
@ -560,13 +806,12 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to process organization payment."],
|
["message", "Unable to process organization payment."],
|
||||||
["patch", createHashMap],
|
["patch", createHashMap],
|
||||||
["memberUids", []]
|
["memberUids", []],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _requesterIsDefaultOrgCeo = (
|
private _requesterIsDefaultOrgCeo = (
|
||||||
_requesterPlayer isNotEqualTo objNull
|
_requesterPlayer isNotEqualTo objNull
|
||||||
&& { _orgID isEqualTo "default" }
|
&& { _orgID isEqualTo "default" }
|
||||||
@ -589,7 +834,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||||
_result
|
_self call ["persistMutationResult", [_orgID, _result, "Organization checkout charge"]]
|
||||||
}],
|
}],
|
||||||
["saveById", compileFinal {
|
["saveById", compileFinal {
|
||||||
params [["_orgID", "", [""]]];
|
params [["_orgID", "", [""]]];
|
||||||
@ -605,7 +850,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to update organization assets."],
|
["message", "Unable to update organization assets."],
|
||||||
["patch", createHashMap],
|
["patch", createHashMap],
|
||||||
["memberUids", []]
|
["memberUids", []],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_assets isEqualTo []) exitWith {
|
if (_assets isEqualTo []) exitWith {
|
||||||
@ -616,8 +863,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _resolvedOrgID = _orgID;
|
private _resolvedOrgID = _orgID;
|
||||||
if (_resolvedOrgID isEqualTo "") then {
|
if (_resolvedOrgID isEqualTo "") then {
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
_resolvedOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
_resolvedOrgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
};
|
};
|
||||||
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
||||||
|
|
||||||
@ -644,7 +890,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||||
_result
|
_self call ["persistMutationResult", [_resolvedOrgID, _result, "Organization asset update"]]
|
||||||
}],
|
}],
|
||||||
["addFleetVehicles", compileFinal {
|
["addFleetVehicles", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_vehicles", [], [[]]], ["_commit", false, [false]], ["_orgID", "", [""]]];
|
params [["_requesterUid", "", [""]], ["_vehicles", [], [[]]], ["_commit", false, [false]], ["_orgID", "", [""]]];
|
||||||
@ -653,7 +899,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to update organization fleet."],
|
["message", "Unable to update organization fleet."],
|
||||||
["patch", createHashMap],
|
["patch", createHashMap],
|
||||||
["memberUids", []]
|
["memberUids", []],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_vehicles isEqualTo []) exitWith {
|
if (_vehicles isEqualTo []) exitWith {
|
||||||
@ -664,8 +912,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _resolvedOrgID = _orgID;
|
private _resolvedOrgID = _orgID;
|
||||||
if (_resolvedOrgID isEqualTo "") then {
|
if (_resolvedOrgID isEqualTo "") then {
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
_resolvedOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
_resolvedOrgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
};
|
};
|
||||||
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
||||||
|
|
||||||
@ -691,7 +938,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||||
_result
|
_self call ["persistMutationResult", [_resolvedOrgID, _result, "Organization fleet update"]]
|
||||||
}],
|
}],
|
||||||
["loadById", compileFinal {
|
["loadById", compileFinal {
|
||||||
params [["_orgID", "", [""]]];
|
params [["_orgID", "", [""]]];
|
||||||
@ -715,10 +962,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
private _existingOrgID = _actor getOrDefault ["organization", ""];
|
private _existingOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid, ""]];
|
||||||
|
private _orgID = EGVAR(actor,ActorStore) call ["getPhoneNumber", [_uid]];
|
||||||
private _orgID = _actor getOrDefault ["phone_number", ""];
|
|
||||||
if (_orgID isEqualTo "") exitWith {
|
if (_orgID isEqualTo "") exitWith {
|
||||||
_result set ["message", "Player phone number was not available for organization registration."];
|
_result set ["message", "Player phone number was not available for organization registration."];
|
||||||
_result
|
_result
|
||||||
@ -732,12 +978,35 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["existingOrgId", _existingOrgID]
|
["existingOrgId", _existingOrgID]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:register", [toJSON _context]]];
|
["org:hot:register", [toJSON _context]] call EFUNC(extension,extCall) params ["_rawResult", "_isSuccess"];
|
||||||
if (_envelope isEqualTo createHashMap) exitWith {
|
if !_isSuccess exitWith {
|
||||||
_result set ["message", "Organization registration failed."];
|
_result set ["message", "Organization service was unavailable during registration."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !(_rawResult isEqualType "") exitWith {
|
||||||
|
_result set ["message", "Organization service returned an invalid registration response."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((_rawResult find "Error:") == 0) exitWith {
|
||||||
|
_result set ["message", _rawResult select [7]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _envelope = fromJSON _rawResult;
|
||||||
|
if !(_envelope isEqualType createHashMap) exitWith {
|
||||||
|
_result set ["message", "Organization service returned malformed registration data."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if ("org" in _envelope) then {
|
||||||
|
private _syncedOrg = _self call ["syncHotOrg", [_envelope getOrDefault ["org", createHashMap]]];
|
||||||
|
if (_syncedOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["org", _syncedOrg];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
private _actorPatch = _self call ["applyActorOrganization", [_uid, _envelope getOrDefault ["actorOrganization", _orgID], _actor]];
|
private _actorPatch = _self call ["applyActorOrganization", [_uid, _envelope getOrDefault ["actorOrganization", _orgID], _actor]];
|
||||||
if (_actorPatch isEqualTo createHashMap) exitWith {
|
if (_actorPatch isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", "Failed to assign the player to the new organization."];
|
_result set ["message", "Failed to assign the player to the new organization."];
|
||||||
@ -754,12 +1023,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
private _actor = EGVAR(actor,Registry) get _uid;
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
private _orgID = _actor get "organization";
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
if (_orgID isEqualTo "") then {
|
|
||||||
_orgID = "default";
|
|
||||||
};
|
|
||||||
|
|
||||||
private _finalOrg = _self call ["loadById", [_orgID]];
|
private _finalOrg = _self call ["loadById", [_orgID]];
|
||||||
if (_finalOrg isEqualTo createHashMap) then {
|
if (_finalOrg isEqualTo createHashMap) then {
|
||||||
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
||||||
|
|||||||
@ -152,6 +152,64 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
|
|
||||||
_creditLinesList
|
_creditLinesList
|
||||||
}],
|
}],
|
||||||
|
["buildPendingInvitesList", compileFinal {
|
||||||
|
params [["_pendingInvitesRaw", [], [[]]]];
|
||||||
|
|
||||||
|
private _pendingInvites = [];
|
||||||
|
{
|
||||||
|
if !(_x isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
_pendingInvites pushBack [
|
||||||
|
["orgId", _x getOrDefault ["orgId", ""]],
|
||||||
|
["orgName", _x getOrDefault ["orgName", "Unknown Organization"]],
|
||||||
|
["inviterUid", _x getOrDefault ["inviterUid", ""]],
|
||||||
|
["inviterName", _x getOrDefault ["inviterName", "Unknown"]],
|
||||||
|
["targetUid", _x getOrDefault ["targetUid", ""]],
|
||||||
|
["targetName", _x getOrDefault ["targetName", "Unknown"]]
|
||||||
|
];
|
||||||
|
} forEach _pendingInvitesRaw;
|
||||||
|
|
||||||
|
_pendingInvites
|
||||||
|
}],
|
||||||
|
["buildInviteablePlayers", compileFinal {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_membersRaw", createHashMap, [createHashMap]],
|
||||||
|
["_pendingInvitesRaw", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _memberUids = [];
|
||||||
|
{
|
||||||
|
_memberUids pushBackUnique (_y getOrDefault ["uid", ""]);
|
||||||
|
} forEach _membersRaw;
|
||||||
|
|
||||||
|
private _pendingInviteUids = [];
|
||||||
|
{
|
||||||
|
_pendingInviteUids pushBackUnique (_x);
|
||||||
|
} forEach _pendingInvitesRaw;
|
||||||
|
|
||||||
|
private _players = [];
|
||||||
|
{
|
||||||
|
private _player = _x;
|
||||||
|
if (isNull _player) then { continue; };
|
||||||
|
|
||||||
|
private _playerUid = getPlayerUID _player;
|
||||||
|
if (_playerUid isEqualTo "" || { _playerUid isEqualTo _uid }) then { continue; };
|
||||||
|
if (_playerUid in _memberUids || { _playerUid in _pendingInviteUids }) then { continue; };
|
||||||
|
|
||||||
|
private _playerOrgID = GVAR(OrgStore) call ["resolveOrgIdForUid", [_playerUid]];
|
||||||
|
if (_playerOrgID isNotEqualTo "default") then { continue; };
|
||||||
|
|
||||||
|
_players pushBack [
|
||||||
|
["uid", _playerUid],
|
||||||
|
["name", name _player],
|
||||||
|
["orgId", _playerOrgID]
|
||||||
|
];
|
||||||
|
} forEach allPlayers;
|
||||||
|
|
||||||
|
_players
|
||||||
|
}],
|
||||||
["buildPortalPayload", compileFinal {
|
["buildPortalPayload", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
@ -160,10 +218,8 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
if (isNull _player) exitWith { createHashMap };
|
if (isNull _player) exitWith { createHashMap };
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_uid]];
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _org = _self call ["resolveOrgForUid", [_uid]];
|
private _org = _self call ["resolveOrgForUid", [_uid]];
|
||||||
if (_org isEqualTo createHashMap) exitWith { createHashMap };
|
if (_org isEqualTo createHashMap) exitWith { createHashMap };
|
||||||
|
|
||||||
@ -179,9 +235,14 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
||||||
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
||||||
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
||||||
|
private _pendingInvitesRaw = _org getOrDefault ["pending_invites", createHashMap];
|
||||||
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
||||||
|| { toLowerANSI _id isEqualTo "default" }
|
|| { toLowerANSI _id isEqualTo "default" }
|
||||||
|| { toLowerANSI _ownerUid isEqualTo "server" };
|
|| { toLowerANSI _ownerUid isEqualTo "server" };
|
||||||
|
private _memberInvites = [];
|
||||||
|
if (_isDefaultOrg) then {
|
||||||
|
_memberInvites = GVAR(OrgStore) call ["listMemberInvites", [_uid]];
|
||||||
|
};
|
||||||
|
|
||||||
private _playerName = name _player;
|
private _playerName = name _player;
|
||||||
private _playerVar = vehicleVarName _player;
|
private _playerVar = vehicleVarName _player;
|
||||||
@ -211,6 +272,8 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
["reputation", _reputation],
|
["reputation", _reputation],
|
||||||
["creditLines", _self call ["buildCreditLinesList", [_creditLinesRaw]]],
|
["creditLines", _self call ["buildCreditLinesList", [_creditLinesRaw]]],
|
||||||
["members", _memberShape getOrDefault ["members", []]],
|
["members", _memberShape getOrDefault ["members", []]],
|
||||||
|
["pendingInvites", _self call ["buildPendingInvitesList", [_memberInvites]]],
|
||||||
|
["inviteablePlayers", _self call ["buildInviteablePlayers", [_uid, _id, _membersRaw, _pendingInvitesRaw]]],
|
||||||
["fleet", _self call ["buildFleetList", [_fleetRaw]]],
|
["fleet", _self call ["buildFleetList", [_fleetRaw]]],
|
||||||
["assets", _self call ["buildAssetsList", [_assetsRaw]]],
|
["assets", _self call ["buildAssetsList", [_assetsRaw]]],
|
||||||
["activity", []]
|
["activity", []]
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initStoreStore.sqf
|
* File: fnc_initStoreStore.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-03-12
|
* Date: 2026-03-12
|
||||||
* Last Update: 2026-03-14
|
* Last Update: 2026-04-04
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -50,10 +50,7 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_bankBalance = _bankAccount getOrDefault ["bank", 0];
|
_bankBalance = _bankAccount getOrDefault ["bank", 0];
|
||||||
};
|
};
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _orgId = EGVAR(actor,ActorStore) call ["getOrganization", [_uid]];
|
||||||
private _orgId = _actor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgId isEqualTo "") then { _orgId = "default"; };
|
|
||||||
|
|
||||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgId]];
|
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgId]];
|
||||||
if (_org isEqualTo createHashMap) then {
|
if (_org isEqualTo createHashMap) then {
|
||||||
_org = EGVAR(org,OrgStore) call ["loadById", ["default"]];
|
_org = EGVAR(org,OrgStore) call ["loadById", ["default"]];
|
||||||
@ -160,7 +157,10 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["vehicleGranted", []],
|
["vehicleGranted", []],
|
||||||
["bankPatch", createHashMap],
|
["bankPatch", createHashMap],
|
||||||
["orgPatch", createHashMap],
|
["orgPatch", createHashMap],
|
||||||
["orgTargetUids", []]
|
["orgTargetUids", []],
|
||||||
|
["persistenceSucceeded", false],
|
||||||
|
["persistenceFailures", []],
|
||||||
|
["persistenceMessage", ""]
|
||||||
]
|
]
|
||||||
}],
|
}],
|
||||||
["formatCurrency", compileFinal {
|
["formatCurrency", compileFinal {
|
||||||
@ -255,6 +255,69 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
|
["persistCheckoutState", compileFinal {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_backendResult", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", true],
|
||||||
|
["failures", []],
|
||||||
|
["message", ""]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _backendResult isEqualTo createHashMap }) exitWith {
|
||||||
|
_result set ["success", false];
|
||||||
|
_result set ["failures", ["checkout"]];
|
||||||
|
_result set ["message", "Checkout persistence context was invalid."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _persistenceFailures = [];
|
||||||
|
|
||||||
|
if ((keys (_backendResult getOrDefault ["lockerPatch", createHashMap])) isNotEqualTo []) then {
|
||||||
|
if ((EGVAR(locker,LockerStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "locker";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((keys (_backendResult getOrDefault ["vaPatch", createHashMap])) isNotEqualTo []) then {
|
||||||
|
if ((EGVAR(locker,VAStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "virtual_arsenal";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((keys (_backendResult getOrDefault ["vgaragePatch", createHashMap])) isNotEqualTo []) then {
|
||||||
|
if ((EGVAR(garage,VGarageStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "virtual_garage";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((keys (_backendResult getOrDefault ["bankPatch", createHashMap])) isNotEqualTo []) then {
|
||||||
|
if ((EGVAR(bank,BankStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "bank";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_orgID isNotEqualTo "" && { (keys (_backendResult getOrDefault ["orgPatch", createHashMap])) isNotEqualTo [] }) then {
|
||||||
|
if ((EGVAR(org,OrgStore) call ["saveById", [_orgID]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBack "organization";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_persistenceFailures isNotEqualTo []) then {
|
||||||
|
_result set ["success", false];
|
||||||
|
_result set ["failures", _persistenceFailures];
|
||||||
|
_result set ["message", format [
|
||||||
|
"Checkout completed, but durable save failed for: %1.",
|
||||||
|
_persistenceFailures joinString ", "
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["checkout", compileFinal {
|
["checkout", compileFinal {
|
||||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_payloadJson", "", [""]]];
|
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_payloadJson", "", [""]]];
|
||||||
|
|
||||||
@ -310,6 +373,14 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
_self call ["syncCheckoutResult", [_player, _backendResult]];
|
_self call ["syncCheckoutResult", [_player, _backendResult]];
|
||||||
|
private _persistenceResult = _self call [
|
||||||
|
"persistCheckoutState",
|
||||||
|
[
|
||||||
|
_uid,
|
||||||
|
_checkoutContext getOrDefault ["orgId", ""],
|
||||||
|
_backendResult
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["message", _backendResult getOrDefault ["message", format [
|
_result set ["message", _backendResult getOrDefault ["message", format [
|
||||||
@ -320,6 +391,16 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
]]];
|
]]];
|
||||||
_result set ["lockerGranted", _backendResult getOrDefault ["lockerGranted", []]];
|
_result set ["lockerGranted", _backendResult getOrDefault ["lockerGranted", []]];
|
||||||
_result set ["vehicleGranted", _backendResult getOrDefault ["vehicleGranted", []]];
|
_result set ["vehicleGranted", _backendResult getOrDefault ["vehicleGranted", []]];
|
||||||
|
_result set ["persistenceSucceeded", _persistenceResult getOrDefault ["success", false]];
|
||||||
|
_result set ["persistenceFailures", _persistenceResult getOrDefault ["failures", []]];
|
||||||
|
_result set ["persistenceMessage", _persistenceResult getOrDefault ["message", ""]];
|
||||||
|
|
||||||
|
if !(_persistenceResult getOrDefault ["success", false]) then {
|
||||||
|
private _warning = _persistenceResult getOrDefault ["message", "Checkout completed with persistence failures."];
|
||||||
|
["ERROR", format ["Store checkout for %1 completed with persistence failures: %2", _uid, (_persistenceResult getOrDefault ["failures", []]) joinString ", "]] call EFUNC(common,log);
|
||||||
|
_result set ["message", format ["%1 %2", _result get "message", _warning]];
|
||||||
|
};
|
||||||
|
|
||||||
_result
|
_result
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
## Overview
|
## Overview
|
||||||
The task addon is a server-owned mission/task system for Forge. It manages task execution, task-owned state, participant tracking, contribution-based player earnings, and org-owned rewards.
|
The task addon is a server-owned mission/task system for Forge. It manages task execution, task-owned state, participant tracking, contribution-based player earnings, and org-owned rewards.
|
||||||
|
|
||||||
|
Task operational state is mission-scoped. The extension-backed task catalog,
|
||||||
|
ownership, status, and defuse state are reset on task store startup, so the
|
||||||
|
system intentionally starts clean after each server or mission restart.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- spawn and monitor task flows on the server
|
- spawn and monitor task flows on the server
|
||||||
- track per-task entities through `TaskStore`
|
- track per-task entities through `TaskStore`
|
||||||
@ -95,6 +99,7 @@ If you want the accepting player's org to own the task rewards, use `fnc_handler
|
|||||||
- the dynamic mission manager in `fnc_missionManager.sqf` is now limited to attack missions only
|
- the dynamic mission manager in `fnc_missionManager.sqf` is now limited to attack missions only
|
||||||
- it starts server-owned tasks through `fnc_handler.sqf` and binds them to the `default` org
|
- it starts server-owned tasks through `fnc_handler.sqf` and binds them to the `default` org
|
||||||
- task lifecycle for the mission manager is tracked through `TaskStore` status entries
|
- task lifecycle for the mission manager is tracked through `TaskStore` status entries
|
||||||
|
- task backend state is intentionally transient and resets with the active server/mission lifecycle
|
||||||
- task rewards are org-owned, not player-owned
|
- task rewards are org-owned, not player-owned
|
||||||
- participant notifications are sent through the notifications module, not through local server UI
|
- participant notifications are sent through the notifications module, not through local server UI
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,7 @@ if (_orgID isEqualTo "") exitWith {
|
|||||||
private _success = true;
|
private _success = true;
|
||||||
private _funds = _rewards getOrDefault ["funds", 0];
|
private _funds = _rewards getOrDefault ["funds", 0];
|
||||||
private _rewardMessages = [];
|
private _rewardMessages = [];
|
||||||
|
private _failureMessages = [];
|
||||||
|
|
||||||
private _resolveRewardLabel = {
|
private _resolveRewardLabel = {
|
||||||
params [["_className", "", [""]]];
|
params [["_className", "", [""]]];
|
||||||
@ -115,8 +116,15 @@ if (_funds > 0) then {
|
|||||||
if (_updatedOrg isEqualTo createHashMap) then {
|
if (_updatedOrg isEqualTo createHashMap) then {
|
||||||
["ERROR", format ["Failed to update organization %1 funds for task %2.", _orgID, _taskID]] call EFUNC(common,log);
|
["ERROR", format ["Failed to update organization %1 funds for task %2.", _orgID, _taskID]] call EFUNC(common,log);
|
||||||
_success = false;
|
_success = false;
|
||||||
|
_failureMessages pushBack "org funds update";
|
||||||
} else {
|
} else {
|
||||||
private _patch = createHashMapFromArray [["funds", _nextFunds]];
|
private _patch = createHashMapFromArray [["funds", _nextFunds]];
|
||||||
|
private _savedOrg = EGVAR(org,OrgStore) call ["saveById", [_orgID]];
|
||||||
|
if (_savedOrg isEqualTo createHashMap) then {
|
||||||
|
["ERROR", format ["Task %1 updated organization %2 funds, but durable save failed.", _taskID, _orgID]] call EFUNC(common,log);
|
||||||
|
_success = false;
|
||||||
|
_failureMessages pushBack "org funds persistence";
|
||||||
|
};
|
||||||
|
|
||||||
[_patch] call _syncOrgPatch;
|
[_patch] call _syncOrgPatch;
|
||||||
_rewardMessages pushBack format ["$%1 org funds", [_funds] call EFUNC(common,formatNumber)];
|
_rewardMessages pushBack format ["$%1 org funds", [_funds] call EFUNC(common,formatNumber)];
|
||||||
@ -141,8 +149,15 @@ private _grantOrgAssets = {
|
|||||||
if !(_grantResult getOrDefault ["success", false]) then {
|
if !(_grantResult getOrDefault ["success", false]) then {
|
||||||
["ERROR", format ["Failed to award %1 assets for task %2: %3", _category, _taskID, _grantResult getOrDefault ["message", "Unknown error."]]] call EFUNC(common,log);
|
["ERROR", format ["Failed to award %1 assets for task %2: %3", _category, _taskID, _grantResult getOrDefault ["message", "Unknown error."]]] call EFUNC(common,log);
|
||||||
_success = false;
|
_success = false;
|
||||||
|
_failureMessages pushBack format ["%1 asset update", _category];
|
||||||
} else {
|
} else {
|
||||||
[_grantResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
|
[_grantResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
|
||||||
|
if !(_grantResult getOrDefault ["persisted", false]) then {
|
||||||
|
private _persistenceMessage = _grantResult getOrDefault ["persistenceMessage", format ["%1 assets updated, but durable save failed.", _category]];
|
||||||
|
["ERROR", format ["Task %1 %2", _taskID, _persistenceMessage]] call EFUNC(common,log);
|
||||||
|
_success = false;
|
||||||
|
_failureMessages pushBack format ["%1 asset persistence", _category];
|
||||||
|
};
|
||||||
private _labels = _items apply { [_x] call _resolveRewardLabel };
|
private _labels = _items apply { [_x] call _resolveRewardLabel };
|
||||||
_rewardMessages pushBack format ["%1: %2", _category, _labels joinString ", "];
|
_rewardMessages pushBack format ["%1: %2", _category, _labels joinString ", "];
|
||||||
};
|
};
|
||||||
@ -171,8 +186,15 @@ private _grantOrgFleet = {
|
|||||||
if !(_fleetResult getOrDefault ["success", false]) then {
|
if !(_fleetResult getOrDefault ["success", false]) then {
|
||||||
["ERROR", format ["Failed to award vehicle rewards for task %2: %1", _fleetResult getOrDefault ["message", "Unknown error."], _taskID]] call EFUNC(common,log);
|
["ERROR", format ["Failed to award vehicle rewards for task %2: %1", _fleetResult getOrDefault ["message", "Unknown error."], _taskID]] call EFUNC(common,log);
|
||||||
_success = false;
|
_success = false;
|
||||||
|
_failureMessages pushBack "fleet update";
|
||||||
} else {
|
} else {
|
||||||
[_fleetResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
|
[_fleetResult getOrDefault ["patch", createHashMap]] call _syncOrgPatch;
|
||||||
|
if !(_fleetResult getOrDefault ["persisted", false]) then {
|
||||||
|
private _persistenceMessage = _fleetResult getOrDefault ["persistenceMessage", "Fleet updated, but durable save failed."];
|
||||||
|
["ERROR", format ["Task %1 %2", _taskID, _persistenceMessage]] call EFUNC(common,log);
|
||||||
|
_success = false;
|
||||||
|
_failureMessages pushBack "fleet persistence";
|
||||||
|
};
|
||||||
private _labels = _vehicles apply { [_x] call _resolveRewardLabel };
|
private _labels = _vehicles apply { [_x] call _resolveRewardLabel };
|
||||||
_rewardMessages pushBack format ["vehicles: %1", _labels joinString ", "];
|
_rewardMessages pushBack format ["vehicles: %1", _labels joinString ", "];
|
||||||
};
|
};
|
||||||
@ -219,7 +241,12 @@ if (_success) then {
|
|||||||
["INFO", _message] call EFUNC(common,log);
|
["INFO", _message] call EFUNC(common,log);
|
||||||
["success", "Tasks", _message] call _notifyMembers;
|
["success", "Tasks", _message] call _notifyMembers;
|
||||||
} else {
|
} else {
|
||||||
["warning", "Tasks", format ["Task %1 completed, but one or more org rewards failed to apply.", _taskID]] call _notifyMembers;
|
private _warningMessage = format ["Task %1 completed, but one or more org rewards failed to apply.", _taskID];
|
||||||
|
if (_failureMessages isNotEqualTo []) then {
|
||||||
|
_warningMessage = format ["%1 Failed areas: %2.", _warningMessage, _failureMessages joinString ", "];
|
||||||
|
};
|
||||||
|
|
||||||
|
["warning", "Tasks", _warningMessage] call _notifyMembers;
|
||||||
};
|
};
|
||||||
|
|
||||||
_success
|
_success
|
||||||
|
|||||||
@ -27,14 +27,7 @@ if (_minRating > 0) then {
|
|||||||
if (_requesterUid isEqualTo "") then {
|
if (_requesterUid isEqualTo "") then {
|
||||||
["WARNING", format ["Task %1 requires minimum reputation %2 but no requester UID was provided, skipping reputation gate.", _taskType, _minRating]] call EFUNC(common,log);
|
["WARNING", format ["Task %1 requires minimum reputation %2 but no requester UID was provided, skipping reputation gate.", _taskType, _minRating]] call EFUNC(common,log);
|
||||||
} else {
|
} else {
|
||||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
if (_requesterActor isEqualTo createHashMap) then {
|
|
||||||
_requesterActor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
private _org = EGVAR(org,OrgStore) call ["loadById", [_orgID]];
|
||||||
private _orgReputation = _org getOrDefault ["reputation", 0];
|
private _orgReputation = _org getOrDefault ["reputation", 0];
|
||||||
if (_orgReputation < _minRating) exitWith {
|
if (_orgReputation < _minRating) exitWith {
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
* Initializes the task store for task entity tracking, participant
|
* Initializes the task store for task entity tracking, participant
|
||||||
* contribution tracking, and task outcome application.
|
* contribution tracking, and task outcome application.
|
||||||
*
|
*
|
||||||
|
* Task metadata is extension-backed but intentionally transient. The
|
||||||
|
* task backend is reset when this store is created so task/catalog/status
|
||||||
|
* state starts clean for each server or mission lifecycle.
|
||||||
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
@ -32,6 +36,8 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
["targets", createHashMap]
|
["targets", createHashMap]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
// Task extension state is mission-scoped and intentionally reset on
|
||||||
|
// startup rather than being treated as durable account data.
|
||||||
["task:reset", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
["task:reset", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
if (
|
if (
|
||||||
!_isSuccess
|
!_isSuccess
|
||||||
@ -99,18 +105,14 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
private _orgID = "default";
|
private _orgID = "default";
|
||||||
|
|
||||||
if (_requesterUid isNotEqualTo "") then {
|
if (_requesterUid isNotEqualTo "") then {
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
if (_actor isEqualTo createHashMap) then {
|
|
||||||
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_actor isEqualTo createHashMap) exitWith {
|
if (_actor isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
_orgID = _actor getOrDefault ["organization", ""];
|
_orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private _context = createHashMapFromArray [
|
private _context = createHashMapFromArray [
|
||||||
@ -166,6 +168,14 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
|
|
||||||
_entries
|
_entries
|
||||||
}],
|
}],
|
||||||
|
["hasTaskCatalogEntry", compileFinal {
|
||||||
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
private _entry = _self call ["callTaskState", ["task:catalog:get", [_taskID], objNull]];
|
||||||
|
_entry isEqualType createHashMap
|
||||||
|
}],
|
||||||
["acceptTask", compileFinal {
|
["acceptTask", compileFinal {
|
||||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||||
|
|
||||||
@ -180,17 +190,13 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
private _actor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
if (_actor isEqualTo createHashMap) then {
|
|
||||||
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
|
||||||
};
|
|
||||||
if (_actor isEqualTo createHashMap) exitWith {
|
if (_actor isEqualTo createHashMap) exitWith {
|
||||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _orgID = _actor getOrDefault ["organization", ""];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
|
||||||
|
|
||||||
private _context = createHashMapFromArray [
|
private _context = createHashMapFromArray [
|
||||||
["requesterUid", _requesterUid],
|
["requesterUid", _requesterUid],
|
||||||
@ -418,6 +424,10 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
|
|
||||||
if (_taskID isEqualTo "") exitWith { false };
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
if !(isNil QGVAR(MissionManager)) then {
|
||||||
|
GVAR(MissionManager) call ["completeMission", [_taskID]];
|
||||||
|
};
|
||||||
|
|
||||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||||
_participantRegistry deleteAt _taskID;
|
_participantRegistry deleteAt _taskID;
|
||||||
_self set ["participantRegistry", _participantRegistry];
|
_self set ["participantRegistry", _participantRegistry];
|
||||||
@ -431,7 +441,11 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
private _result = createHashMapFromArray [
|
private _result = createHashMapFromArray [
|
||||||
["participantUids", []],
|
["participantUids", []],
|
||||||
["orgIds", []],
|
["orgIds", []],
|
||||||
["contributions", createHashMap]
|
["contributions", createHashMap],
|
||||||
|
["success", true],
|
||||||
|
["mutationFailures", []],
|
||||||
|
["persistenceFailures", []],
|
||||||
|
["message", ""]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_taskID isEqualTo "" || { _delta isEqualTo 0 }) exitWith { _result };
|
if (_taskID isEqualTo "" || { _delta isEqualTo 0 }) exitWith { _result };
|
||||||
@ -462,6 +476,8 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
private _orgIds = [];
|
private _orgIds = [];
|
||||||
private _contributions = createHashMap;
|
private _contributions = createHashMap;
|
||||||
private _totalContribution = 0;
|
private _totalContribution = 0;
|
||||||
|
private _mutationFailures = [];
|
||||||
|
private _persistenceFailures = [];
|
||||||
|
|
||||||
if (_delta > 0) then {
|
if (_delta > 0) then {
|
||||||
{
|
{
|
||||||
@ -481,12 +497,7 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
|
|
||||||
{
|
{
|
||||||
private _uid = _x;
|
private _uid = _x;
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _orgID = EGVAR(actor,ActorStore) call ["getOrganization", [_uid, ""]];
|
||||||
if (_actor isEqualTo createHashMap) then {
|
|
||||||
_actor = EGVAR(actor,ActorStore) call ["init", [_uid]];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _orgID = _actor getOrDefault ["organization", ""];
|
|
||||||
if (_orgID isNotEqualTo "") then {
|
if (_orgID isNotEqualTo "") then {
|
||||||
_orgIds pushBackUnique _orgID;
|
_orgIds pushBackUnique _orgID;
|
||||||
};
|
};
|
||||||
@ -517,6 +528,10 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
if (_patch isEqualTo createHashMap) then { continue; };
|
if (_patch isEqualTo createHashMap) then { continue; };
|
||||||
|
|
||||||
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
|
EGVAR(bank,BankMessenger) call ["sendAccountSync", [_uid, _patch]];
|
||||||
|
if ((EGVAR(bank,BankStore) call ["save", [_uid]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBackUnique format ["bank:%1", _uid];
|
||||||
|
["ERROR", format ["Task %1 updated bank earnings for %2, but durable save failed.", _taskID, _uid]] call EFUNC(common,log);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
} forEach _participantUids;
|
} forEach _participantUids;
|
||||||
@ -547,8 +562,13 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
} forEach _memberUids;
|
} forEach _memberUids;
|
||||||
|
|
||||||
_orgIds = [_ownerOrgID];
|
_orgIds = [_ownerOrgID];
|
||||||
|
if ((EGVAR(org,OrgStore) call ["saveById", [_ownerOrgID]]) isEqualTo createHashMap) then {
|
||||||
|
_persistenceFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||||
|
["ERROR", format ["Task %1 updated reputation for organization %2, but durable save failed.", _taskID, _ownerOrgID]] call EFUNC(common,log);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
["ERROR", format ["Failed to update organization %1 reputation for task %2.", _ownerOrgID, _taskID]] call EFUNC(common,log);
|
["ERROR", format ["Failed to update organization %1 reputation for task %2.", _ownerOrgID, _taskID]] call EFUNC(common,log);
|
||||||
|
_mutationFailures pushBackUnique format ["organization:%1", _ownerOrgID];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -556,6 +576,19 @@ GVAR(TaskStore) = createHashMapObject [[
|
|||||||
_result set ["participantUids", _participantUids];
|
_result set ["participantUids", _participantUids];
|
||||||
_result set ["orgIds", _orgIds];
|
_result set ["orgIds", _orgIds];
|
||||||
_result set ["contributions", _contributions];
|
_result set ["contributions", _contributions];
|
||||||
|
_result set ["success", (_mutationFailures isEqualTo []) && { _persistenceFailures isEqualTo [] }];
|
||||||
|
_result set ["mutationFailures", _mutationFailures];
|
||||||
|
_result set ["persistenceFailures", _persistenceFailures];
|
||||||
|
if (_mutationFailures isNotEqualTo [] || { _persistenceFailures isNotEqualTo [] }) then {
|
||||||
|
private _messageParts = [];
|
||||||
|
if (_mutationFailures isNotEqualTo []) then {
|
||||||
|
_messageParts pushBack format ["mutation failures: %1", _mutationFailures joinString ", "];
|
||||||
|
};
|
||||||
|
if (_persistenceFailures isNotEqualTo []) then {
|
||||||
|
_messageParts pushBack format ["persistence failures: %1", _persistenceFailures joinString ", "];
|
||||||
|
};
|
||||||
|
_result set ["message", _messageParts joinString "; "];
|
||||||
|
};
|
||||||
_result
|
_result
|
||||||
}]
|
}]
|
||||||
]];
|
]];
|
||||||
|
|||||||
@ -38,8 +38,25 @@ GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["getMaxConcurrentMissions", compileFinal {
|
["getMaxConcurrentMissions", compileFinal {
|
||||||
private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1];
|
private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1];
|
||||||
if (_maxConcurrent <= 0) then { _maxConcurrent = 1; };
|
if (_maxConcurrent <= 0) then { _maxConcurrent = 1; };
|
||||||
|
private _attackLocationCount = _self call ["getAttackLocationCount", []];
|
||||||
|
if (_attackLocationCount > 0) then {
|
||||||
|
_maxConcurrent = _maxConcurrent min _attackLocationCount;
|
||||||
|
};
|
||||||
_maxConcurrent
|
_maxConcurrent
|
||||||
}],
|
}],
|
||||||
|
["getAttackLocationCount", compileFinal {
|
||||||
|
private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
|
||||||
|
if (isNull _locationsConfig) exitWith { 0 };
|
||||||
|
|
||||||
|
private _count = 0;
|
||||||
|
{
|
||||||
|
if ("attack" in getArray (_x >> "suitable")) then {
|
||||||
|
_count = _count + 1;
|
||||||
|
};
|
||||||
|
} forEach ("true" configClasses _locationsConfig);
|
||||||
|
|
||||||
|
_count
|
||||||
|
}],
|
||||||
["getLocationReuseCooldown", compileFinal {
|
["getLocationReuseCooldown", compileFinal {
|
||||||
private _missionConfig = _self getOrDefault ["missionConfig", configNull];
|
private _missionConfig = _self getOrDefault ["missionConfig", configNull];
|
||||||
private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
|
private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
|
||||||
@ -352,7 +369,8 @@ GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];
|
|||||||
[{
|
[{
|
||||||
{
|
{
|
||||||
private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
|
private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
|
||||||
if (_status in ["succeeded", "failed"]) then {
|
private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_x]];
|
||||||
|
if (_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then {
|
||||||
GVAR(MissionManager) call ["completeMission", [_x]];
|
GVAR(MissionManager) call ["completeMission", [_x]];
|
||||||
GVAR(TaskStore) call ["clearTaskStatus", [_x]];
|
GVAR(TaskStore) call ["clearTaskStatus", [_x]];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -54,6 +54,7 @@ pub fn group() -> Group {
|
|||||||
Group::new()
|
Group::new()
|
||||||
.command("init", init_hot_actor)
|
.command("init", init_hot_actor)
|
||||||
.command("get", get_hot_actor)
|
.command("get", get_hot_actor)
|
||||||
|
.command("keys", list_hot_actor_keys)
|
||||||
.command("override", override_hot_actor)
|
.command("override", override_hot_actor)
|
||||||
.command("save", save_hot_actor)
|
.command("save", save_hot_actor)
|
||||||
.command("remove", remove_hot_actor),
|
.command("remove", remove_hot_actor),
|
||||||
@ -91,6 +92,16 @@ pub(crate) fn get_hot_actor(call_context: CallContext, key: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_hot_actor_keys() -> String {
|
||||||
|
match HOT_ACTOR_SERVICE.list_actor_keys() {
|
||||||
|
Ok(keys) => match serde_json::to_string(&keys) {
|
||||||
|
Ok(json) => json,
|
||||||
|
Err(error) => format!("Error: Failed to serialize actor hot-state keys: {}", error),
|
||||||
|
},
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn override_hot_actor(
|
pub(crate) fn override_hot_actor(
|
||||||
call_context: CallContext,
|
call_context: CallContext,
|
||||||
key: String,
|
key: String,
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
//! The extension owns the in-memory CAD state store, while the shared service
|
//! The extension owns the in-memory CAD state store, while the shared service
|
||||||
//! layer handles mutation rules and hydrate shaping. This keeps the extension
|
//! layer handles mutation rules and hydrate shaping. This keeps the extension
|
||||||
//! surface thin and aligned with the workspace architecture.
|
//! surface thin and aligned with the workspace architecture.
|
||||||
|
//!
|
||||||
|
//! CAD state is intentionally transient operational state. It follows the
|
||||||
|
//! active server or mission lifecycle and is not treated as durable player or
|
||||||
|
//! organization persistence.
|
||||||
|
|
||||||
use arma_rs::Group;
|
use arma_rs::Group;
|
||||||
use forge_repositories::InMemoryCadRepository;
|
use forge_repositories::InMemoryCadRepository;
|
||||||
|
|||||||
@ -7,8 +7,9 @@ use arma_rs::Group;
|
|||||||
use forge_models::{
|
use forge_models::{
|
||||||
HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandResult,
|
||||||
OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, OrgLeaveResult,
|
OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgInviteContext,
|
||||||
OrgRegisterContext,
|
OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord, OrgInviteResult,
|
||||||
|
OrgLeaveContext, OrgLeaveResult, OrgRegisterContext,
|
||||||
};
|
};
|
||||||
use forge_repositories::{InMemoryOrgHotRepository, RedisOrgRepository};
|
use forge_repositories::{InMemoryOrgHotRepository, RedisOrgRepository};
|
||||||
use forge_services::{OrgHotStateService, OrgService};
|
use forge_services::{OrgHotStateService, OrgService};
|
||||||
@ -58,7 +59,11 @@ pub fn group() -> Group {
|
|||||||
.command("get", get_hot_org)
|
.command("get", get_hot_org)
|
||||||
.command("override", override_hot_org)
|
.command("override", override_hot_org)
|
||||||
.command("ensure_member", ensure_hot_org_member)
|
.command("ensure_member", ensure_hot_org_member)
|
||||||
|
.command("member_invites", get_hot_org_member_invites)
|
||||||
.command("register", register_hot_org)
|
.command("register", register_hot_org)
|
||||||
|
.command("invite_member", invite_hot_org_member)
|
||||||
|
.command("accept_invite", accept_hot_org_invite)
|
||||||
|
.command("decline_invite", decline_hot_org_invite)
|
||||||
.command("assign_credit_line", assign_credit_line_hot_org)
|
.command("assign_credit_line", assign_credit_line_hot_org)
|
||||||
.command("repay_credit_line", repay_credit_line_hot_org)
|
.command("repay_credit_line", repay_credit_line_hot_org)
|
||||||
.command("charge_checkout", charge_checkout_hot_org)
|
.command("charge_checkout", charge_checkout_hot_org)
|
||||||
@ -142,6 +147,13 @@ pub(crate) fn ensure_hot_org_member(json_data: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_hot_org_member_invites(member_uid: String) -> String {
|
||||||
|
match HOT_ORG_SERVICE.get_member_invites(member_uid) {
|
||||||
|
Ok(invites) => serialize_result::<Vec<OrgInviteRecord>>(&invites, "org invite list"),
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn register_hot_org(json_data: String) -> String {
|
pub(crate) fn register_hot_org(json_data: String) -> String {
|
||||||
let context: OrgRegisterContext = match serde_json::from_str(&json_data) {
|
let context: OrgRegisterContext = match serde_json::from_str(&json_data) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
@ -154,6 +166,46 @@ pub(crate) fn register_hot_org(json_data: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invite_hot_org_member(json_data: String) -> String {
|
||||||
|
let context: OrgInviteContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.invite_member(context) {
|
||||||
|
Ok(result) => serialize_result::<OrgInviteResult>(&result, "org invite result"),
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn accept_hot_org_invite(json_data: String) -> String {
|
||||||
|
let context: OrgInviteDecisionContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite decision JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.accept_invite(context) {
|
||||||
|
Ok(result) => {
|
||||||
|
serialize_result::<OrgInviteDecisionResult>(&result, "org invite decision result")
|
||||||
|
}
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decline_hot_org_invite(json_data: String) -> String {
|
||||||
|
let context: OrgInviteDecisionContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite decision JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.decline_invite(context) {
|
||||||
|
Ok(result) => {
|
||||||
|
serialize_result::<OrgInviteDecisionResult>(&result, "org invite decision result")
|
||||||
|
}
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn assign_credit_line_hot_org(json_data: String) -> String {
|
pub(crate) fn assign_credit_line_hot_org(json_data: String) -> String {
|
||||||
let context: OrgCreditLineContext = match serde_json::from_str(&json_data) {
|
let context: OrgCreditLineContext = match serde_json::from_str(&json_data) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! The extension owns portable task metadata while SQF keeps Arma-only runtime
|
//! The extension owns portable task metadata while SQF keeps Arma-only runtime
|
||||||
//! state such as entity references and participant tracking.
|
//! state such as entity references and participant tracking.
|
||||||
|
//!
|
||||||
|
//! This state is intentionally transient and is reset during server task-store
|
||||||
|
//! initialization so tasks start clean for each server or mission lifecycle.
|
||||||
|
|
||||||
use arma_rs::Group;
|
use arma_rs::Group;
|
||||||
use forge_repositories::InMemoryTaskRepository;
|
use forge_repositories::InMemoryTaskRepository;
|
||||||
|
|||||||
@ -185,6 +185,10 @@ fn route_command(
|
|||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(actor::get_hot_actor(call_context, arguments[0].clone()))
|
Ok(actor::get_hot_actor(call_context, arguments[0].clone()))
|
||||||
}
|
}
|
||||||
|
"actor:hot:keys" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 0)?;
|
||||||
|
Ok(actor::list_hot_actor_keys())
|
||||||
|
}
|
||||||
"actor:hot:override" => {
|
"actor:hot:override" => {
|
||||||
expect_arg_count(function_name, &arguments, 2)?;
|
expect_arg_count(function_name, &arguments, 2)?;
|
||||||
Ok(actor::override_hot_actor(
|
Ok(actor::override_hot_actor(
|
||||||
@ -355,10 +359,26 @@ fn route_command(
|
|||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::ensure_hot_org_member(arguments[0].clone()))
|
Ok(org::ensure_hot_org_member(arguments[0].clone()))
|
||||||
}
|
}
|
||||||
|
"org:hot:member_invites" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::get_hot_org_member_invites(arguments[0].clone()))
|
||||||
|
}
|
||||||
"org:hot:register" => {
|
"org:hot:register" => {
|
||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::register_hot_org(arguments[0].clone()))
|
Ok(org::register_hot_org(arguments[0].clone()))
|
||||||
}
|
}
|
||||||
|
"org:hot:invite_member" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::invite_hot_org_member(arguments[0].clone()))
|
||||||
|
}
|
||||||
|
"org:hot:accept_invite" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::accept_hot_org_invite(arguments[0].clone()))
|
||||||
|
}
|
||||||
|
"org:hot:decline_invite" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::decline_hot_org_invite(arguments[0].clone()))
|
||||||
|
}
|
||||||
"org:hot:assign_credit_line" => {
|
"org:hot:assign_credit_line" => {
|
||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
|
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
|
||||||
|
|||||||
@ -61,7 +61,7 @@ impl Actor {
|
|||||||
state: "HEALTHY".to_string(),
|
state: "HEALTHY".to_string(),
|
||||||
holster: true,
|
holster: true,
|
||||||
rank: None,
|
rank: None,
|
||||||
organization: "".to_string(),
|
organization: "default".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
actor.validate()?;
|
actor.validate()?;
|
||||||
@ -171,7 +171,7 @@ impl FromArma for Actor {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
if actor.organization.trim().is_empty() {
|
if actor.organization.trim().is_empty() {
|
||||||
actor.organization = String::new();
|
actor.organization = "default".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(actor)
|
Ok(actor)
|
||||||
|
|||||||
@ -27,7 +27,9 @@ pub use org::{
|
|||||||
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
||||||
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
||||||
OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
OrgInviteContext, OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord,
|
||||||
|
OrgInviteResult, OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext,
|
||||||
|
OrgRegisterResult,
|
||||||
};
|
};
|
||||||
pub use store::{
|
pub use store::{
|
||||||
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
||||||
|
|||||||
@ -69,6 +69,17 @@ pub struct MemberSummary {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteRecord {
|
||||||
|
pub org_id: String,
|
||||||
|
pub org_name: String,
|
||||||
|
pub inviter_uid: String,
|
||||||
|
pub inviter_name: String,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub target_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct HotOrgRecord {
|
pub struct HotOrgRecord {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@ -84,6 +95,8 @@ pub struct HotOrgRecord {
|
|||||||
pub fleet: HashMap<String, OrgFleetEntry>,
|
pub fleet: HashMap<String, OrgFleetEntry>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub members: HashMap<String, MemberSummary>,
|
pub members: HashMap<String, MemberSummary>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pending_invites: HashMap<String, OrgInviteRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -112,6 +125,44 @@ pub struct OrgRegisterResult {
|
|||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteContext {
|
||||||
|
pub requester_uid: String,
|
||||||
|
pub requester_name: String,
|
||||||
|
pub org_id: String,
|
||||||
|
pub requester_is_default_org_ceo: bool,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub target_name: String,
|
||||||
|
pub target_org_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteDecisionContext {
|
||||||
|
pub requester_uid: String,
|
||||||
|
pub requester_name: String,
|
||||||
|
pub org_id: String,
|
||||||
|
pub existing_org_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteResult {
|
||||||
|
pub org: HotOrgRecord,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteDecisionResult {
|
||||||
|
pub invited_org: HotOrgRecord,
|
||||||
|
pub previous_org: Option<HotOrgRecord>,
|
||||||
|
pub actor_organization: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OrgCreditLineContext {
|
pub struct OrgCreditLineContext {
|
||||||
@ -329,6 +380,7 @@ impl HotOrgRecord {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|member| (member.uid.clone(), member))
|
.map(|member| (member.uid.clone(), member))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
pending_invites: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ impl VLocker {
|
|||||||
"ItemMap".to_string(),
|
"ItemMap".to_string(),
|
||||||
"ItemRadio".to_string(),
|
"ItemRadio".to_string(),
|
||||||
"ItemWatch".to_string(),
|
"ItemWatch".to_string(),
|
||||||
"U_IG_Guerrilla_6_1".to_string(),
|
"U_BG_Guerrilla_6_1".to_string(),
|
||||||
"V_TacVest_oli".to_string(),
|
"V_TacVest_oli".to_string(),
|
||||||
],
|
],
|
||||||
weapons: vec!["arifle_MX_F".to_string(), "hgun_P07_F".to_string()],
|
weapons: vec!["arifle_MX_F".to_string(), "hgun_P07_F".to_string()],
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub trait ActorRepository: Send + Sync {
|
|||||||
|
|
||||||
pub trait ActorHotRepository: Send + Sync {
|
pub trait ActorHotRepository: Send + Sync {
|
||||||
fn get(&self, id: &str) -> Result<Option<Actor>, String>;
|
fn get(&self, id: &str) -> Result<Option<Actor>, String>;
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String>;
|
||||||
fn save(&self, actor: &Actor) -> Result<(), String>;
|
fn save(&self, actor: &Actor) -> Result<(), String>;
|
||||||
fn delete(&self, id: &str) -> Result<(), String>;
|
fn delete(&self, id: &str) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
@ -57,6 +58,13 @@ impl ActorHotRepository for InMemoryActorHotRepository {
|
|||||||
.map_err(|_| "Actor hot state lock poisoned.".to_string())
|
.map_err(|_| "Actor hot state lock poisoned.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.keys().cloned().collect())
|
||||||
|
.map_err(|_| "Actor hot state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn save(&self, actor: &Actor) -> Result<(), String> {
|
fn save(&self, actor: &Actor) -> Result<(), String> {
|
||||||
self.state
|
self.state
|
||||||
.write()
|
.write()
|
||||||
|
|||||||
@ -66,6 +66,7 @@ pub trait OrgRepository: Send + Sync {
|
|||||||
|
|
||||||
pub trait OrgHotRepository: Send + Sync {
|
pub trait OrgHotRepository: Send + Sync {
|
||||||
fn get(&self, id: &str) -> Result<Option<HotOrgRecord>, String>;
|
fn get(&self, id: &str) -> Result<Option<HotOrgRecord>, String>;
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String>;
|
||||||
fn save(&self, org: &HotOrgRecord) -> Result<(), String>;
|
fn save(&self, org: &HotOrgRecord) -> Result<(), String>;
|
||||||
fn delete(&self, id: &str) -> Result<(), String>;
|
fn delete(&self, id: &str) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
@ -89,6 +90,13 @@ impl OrgHotRepository for InMemoryOrgHotRepository {
|
|||||||
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.keys().cloned().collect())
|
||||||
|
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn save(&self, org: &HotOrgRecord) -> Result<(), String> {
|
fn save(&self, org: &HotOrgRecord) -> Result<(), String> {
|
||||||
self.state
|
self.state
|
||||||
.write()
|
.write()
|
||||||
|
|||||||
@ -26,6 +26,14 @@ graph TD
|
|||||||
- **Error Handling:** Converts technical errors into business-friendly messages.
|
- **Error Handling:** Converts technical errors into business-friendly messages.
|
||||||
- **Data Transformation:** Handles JSON parsing and model conversion.
|
- **Data Transformation:** Handles JSON parsing and model conversion.
|
||||||
|
|
||||||
|
## Operational State Policy
|
||||||
|
|
||||||
|
Most hot-state services in Forge back durable player or organization records and
|
||||||
|
are expected to flush through the save path. `CAD` and `Task` are the current
|
||||||
|
exceptions: they are extension-backed operational state services that are
|
||||||
|
intentionally transient and restart clean with the active server or mission
|
||||||
|
lifecycle.
|
||||||
|
|
||||||
## Actor Service
|
## Actor Service
|
||||||
|
|
||||||
The `ActorService` manages player lifecycle and state.
|
The `ActorService` manages player lifecycle and state.
|
||||||
|
|||||||
@ -42,13 +42,27 @@ impl<R: ActorRepository, H: ActorHotRepository> ActorHotStateService<R, H> {
|
|||||||
return Ok(actor);
|
return Ok(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
let actor = self.service.get_actor(key)?;
|
let actor = self
|
||||||
|
.service
|
||||||
|
.repository
|
||||||
|
.get_by_id(&key)?
|
||||||
|
.ok_or_else(|| format!("Actor with UID '{}' was not found", key))?;
|
||||||
self.repository.save(&actor)?;
|
self.repository.save(&actor)?;
|
||||||
Ok(actor)
|
Ok(actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_actor(&self, key: String) -> Result<Actor, String> {
|
pub fn get_actor(&self, key: String) -> Result<Actor, String> {
|
||||||
self.init_actor(key)
|
if let Some(actor) = self.repository.get(&key)? {
|
||||||
|
return Ok(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actor = self
|
||||||
|
.service
|
||||||
|
.repository
|
||||||
|
.get_by_id(&key)?
|
||||||
|
.ok_or_else(|| format!("Actor with UID '{}' was not found", key))?;
|
||||||
|
self.repository.save(&actor)?;
|
||||||
|
Ok(actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn override_actor(&self, key: String, json_data: String) -> Result<Actor, String> {
|
pub fn override_actor(&self, key: String, json_data: String) -> Result<Actor, String> {
|
||||||
@ -77,6 +91,10 @@ impl<R: ActorRepository, H: ActorHotRepository> ActorHotStateService<R, H> {
|
|||||||
Ok(saved_actor)
|
Ok(saved_actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_actor_keys(&self) -> Result<Vec<String>, String> {
|
||||||
|
self.repository.keys()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_actor(&self, key: String) -> Result<(), String> {
|
pub fn remove_actor(&self, key: String) -> Result<(), String> {
|
||||||
self.repository.delete(&key)
|
self.repository.delete(&key)
|
||||||
}
|
}
|
||||||
@ -114,6 +132,9 @@ impl<R: ActorRepository> ActorService<R> {
|
|||||||
if actor.email.is_empty() {
|
if actor.email.is_empty() {
|
||||||
actor.email = generate_email(&actor.phone_number);
|
actor.email = generate_email(&actor.phone_number);
|
||||||
}
|
}
|
||||||
|
if actor.organization.trim().is_empty() {
|
||||||
|
actor.organization = "default".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
// Validate before persisting
|
// Validate before persisting
|
||||||
actor
|
actor
|
||||||
|
|||||||
@ -10,7 +10,9 @@ use forge_models::{
|
|||||||
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
||||||
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
||||||
OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
OrgInviteContext, OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord,
|
||||||
|
OrgInviteResult, OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext,
|
||||||
|
OrgRegisterResult,
|
||||||
};
|
};
|
||||||
use forge_repositories::{OrgHotRepository, OrgRepository};
|
use forge_repositories::{OrgHotRepository, OrgRepository};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
@ -365,6 +367,26 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
|||||||
self.init_org(id)
|
self.init_org(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_member_invites(&self, member_uid: String) -> Result<Vec<OrgInviteRecord>, String> {
|
||||||
|
if member_uid.trim().is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invites = Vec::new();
|
||||||
|
for org_id in self.repository.keys()? {
|
||||||
|
let Some(org) = self.repository.get(&org_id)? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(invite) = org.pending_invites.get(&member_uid) {
|
||||||
|
invites.push(invite.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invites.sort_by(|left, right| left.org_name.cmp(&right.org_name));
|
||||||
|
Ok(invites)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn override_org(
|
pub fn override_org(
|
||||||
&self,
|
&self,
|
||||||
id: String,
|
id: String,
|
||||||
@ -423,12 +445,22 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut org = self.get_org(context.org_id)?;
|
let mut org = self.get_org(context.org_id)?;
|
||||||
if !org.members.contains_key(&context.member_uid) {
|
let member_name = if context.member_name.trim().is_empty() {
|
||||||
let member_name = if context.member_name.trim().is_empty() {
|
"Unknown".to_string()
|
||||||
"Unknown".to_string()
|
} else {
|
||||||
} else {
|
context.member_name
|
||||||
context.member_name
|
};
|
||||||
};
|
let should_refresh_member_name = org
|
||||||
|
.members
|
||||||
|
.get(&context.member_uid)
|
||||||
|
.map(|member| {
|
||||||
|
let existing_name = member.name.trim();
|
||||||
|
!member_name.eq_ignore_ascii_case("unknown")
|
||||||
|
&& (existing_name.is_empty() || existing_name.eq_ignore_ascii_case("unknown"))
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !org.members.contains_key(&context.member_uid) || should_refresh_member_name {
|
||||||
org.members.insert(
|
org.members.insert(
|
||||||
context.member_uid.clone(),
|
context.member_uid.clone(),
|
||||||
MemberSummary {
|
MemberSummary {
|
||||||
@ -502,6 +534,162 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invite_member(&self, context: OrgInviteContext) -> Result<OrgInviteResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty()
|
||||||
|
|| context.target_uid.trim().is_empty()
|
||||||
|
|| context.org_id.trim().is_empty()
|
||||||
|
{
|
||||||
|
return Err("A valid organization invite request is required.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut org = self.get_org(context.org_id.clone())?;
|
||||||
|
if !can_manage_treasury(
|
||||||
|
&org,
|
||||||
|
&context.requester_uid,
|
||||||
|
context.requester_is_default_org_ceo,
|
||||||
|
) {
|
||||||
|
return Err(
|
||||||
|
"Only the organization leader or CEO can send organization invites.".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if context.target_uid == context.requester_uid {
|
||||||
|
return Err("You cannot invite yourself to the organization.".to_string());
|
||||||
|
}
|
||||||
|
if org.members.contains_key(&context.target_uid) {
|
||||||
|
return Err("Selected player is already a member of this organization.".to_string());
|
||||||
|
}
|
||||||
|
if !context.target_org_id.trim().is_empty()
|
||||||
|
&& !context.target_org_id.eq_ignore_ascii_case("default")
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"Selected player must leave their current organization before joining another."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_name = if context.target_name.trim().is_empty() {
|
||||||
|
"Unknown".to_string()
|
||||||
|
} else {
|
||||||
|
context.target_name.clone()
|
||||||
|
};
|
||||||
|
let inviter_name = if context.requester_name.trim().is_empty() {
|
||||||
|
"Unknown".to_string()
|
||||||
|
} else {
|
||||||
|
context.requester_name.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
org.pending_invites.insert(
|
||||||
|
context.target_uid.clone(),
|
||||||
|
OrgInviteRecord {
|
||||||
|
org_id: org.id.clone(),
|
||||||
|
org_name: org.name.clone(),
|
||||||
|
inviter_uid: context.requester_uid,
|
||||||
|
inviter_name,
|
||||||
|
target_uid: context.target_uid.clone(),
|
||||||
|
target_name: target_name.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.repository.save(&org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteResult {
|
||||||
|
org,
|
||||||
|
target_uid: context.target_uid,
|
||||||
|
message: format!("Invitation sent to {}.", target_name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_invite(
|
||||||
|
&self,
|
||||||
|
context: OrgInviteDecisionContext,
|
||||||
|
) -> Result<OrgInviteDecisionResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||||
|
return Err("A valid organization invite acceptance is required.".to_string());
|
||||||
|
}
|
||||||
|
if !context.existing_org_id.trim().is_empty()
|
||||||
|
&& !context.existing_org_id.eq_ignore_ascii_case("default")
|
||||||
|
&& !context
|
||||||
|
.existing_org_id
|
||||||
|
.eq_ignore_ascii_case(&context.org_id)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"Leave your current organization before accepting another invite.".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invited_org = self.get_org(context.org_id.clone())?;
|
||||||
|
let invite = invited_org
|
||||||
|
.pending_invites
|
||||||
|
.remove(&context.requester_uid)
|
||||||
|
.ok_or_else(|| "That organization invite is no longer available.".to_string())?;
|
||||||
|
|
||||||
|
if invited_org.members.contains_key(&context.requester_uid) {
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
return Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org: None,
|
||||||
|
actor_organization: invited_org.id.clone(),
|
||||||
|
message: "You are already a member of that organization.".to_string(),
|
||||||
|
invited_org,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let requester_name = if context.requester_name.trim().is_empty() {
|
||||||
|
invite.target_name
|
||||||
|
} else {
|
||||||
|
context.requester_name
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut previous_org = None;
|
||||||
|
if !context.existing_org_id.trim().is_empty()
|
||||||
|
&& !context
|
||||||
|
.existing_org_id
|
||||||
|
.eq_ignore_ascii_case(&invited_org.id)
|
||||||
|
{
|
||||||
|
let mut current_org = self.init_org(context.existing_org_id.clone())?;
|
||||||
|
current_org.members.remove(&context.requester_uid);
|
||||||
|
self.repository.save(¤t_org)?;
|
||||||
|
previous_org = Some(current_org);
|
||||||
|
}
|
||||||
|
|
||||||
|
invited_org.members.insert(
|
||||||
|
context.requester_uid.clone(),
|
||||||
|
MemberSummary {
|
||||||
|
uid: context.requester_uid,
|
||||||
|
name: requester_name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org,
|
||||||
|
actor_organization: invited_org.id.clone(),
|
||||||
|
message: format!("You joined {}.", invited_org.name),
|
||||||
|
invited_org,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decline_invite(
|
||||||
|
&self,
|
||||||
|
context: OrgInviteDecisionContext,
|
||||||
|
) -> Result<OrgInviteDecisionResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||||
|
return Err("A valid organization invite decline is required.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invited_org = self.get_org(context.org_id.clone())?;
|
||||||
|
let invite = invited_org
|
||||||
|
.pending_invites
|
||||||
|
.remove(&context.requester_uid)
|
||||||
|
.ok_or_else(|| "That organization invite is no longer available.".to_string())?;
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org: None,
|
||||||
|
actor_organization: context.existing_org_id,
|
||||||
|
message: format!("Invitation from {} declined.", invite.org_name),
|
||||||
|
invited_org,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn assign_credit_line(
|
pub fn assign_credit_line(
|
||||||
&self,
|
&self,
|
||||||
context: OrgCreditLineContext,
|
context: OrgCreditLineContext,
|
||||||
@ -1007,6 +1195,8 @@ fn current_org_field_value(org: &HotOrgRecord, field: &str) -> Result<Value, Str
|
|||||||
.map_err(|error| format!("Failed to serialize org fleet: {}", error)),
|
.map_err(|error| format!("Failed to serialize org fleet: {}", error)),
|
||||||
"members" => serde_json::to_value(&org.members)
|
"members" => serde_json::to_value(&org.members)
|
||||||
.map_err(|error| format!("Failed to serialize org members: {}", error)),
|
.map_err(|error| format!("Failed to serialize org members: {}", error)),
|
||||||
|
"pending_invites" => serde_json::to_value(&org.pending_invites)
|
||||||
|
.map_err(|error| format!("Failed to serialize org invites: {}", error)),
|
||||||
_ => Err(format!("Unknown field: {}", field)),
|
_ => Err(format!("Unknown field: {}", field)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user