From db565d1e5193c731fe94cb30db02315439da1dac Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Wed, 25 Mar 2026 20:29:51 -0500 Subject: [PATCH] Refactor module hydration and init flow --- arma/client/addons/actor/XEH_preStart.sqf | 1 - .../actor/functions/fnc_initActorClass.sqf | 3 +- .../client/addons/bank/XEH_postInitClient.sqf | 11 +- arma/client/addons/bank/XEH_preStart.sqf | 1 - .../addons/bank/functions/fnc_initClass.sqf | 40 +---- .../bank/functions/fnc_initUIBridge.sqf | 8 +- arma/client/addons/bank/ui/_site/bank-ui.js | 2 +- arma/client/addons/bank/ui/src/bridge.js | 1 + arma/client/addons/common/XEH_preStart.sqf | 1 - .../addons/garage/functions/fnc_initClass.sqf | 3 +- .../garage/functions/fnc_initVGClass.sqf | 3 +- .../locker/functions/fnc_initLockerClass.sqf | 3 +- .../locker/functions/fnc_initVAClass.sqf | 3 +- .../notifications/XEH_postInitClient.sqf | 2 +- .../addons/notifications/XEH_preStart.sqf | 1 - arma/client/addons/org/XEH_postInitClient.sqf | 14 +- arma/client/addons/org/XEH_preStart.sqf | 1 - .../addons/org/functions/fnc_initClass.sqf | 165 ++---------------- .../addons/org/functions/fnc_initUIBridge.sqf | 57 ++++-- arma/client/addons/store/XEH_PREP.hpp | 2 - .../addons/store/XEH_postInitClient.sqf | 7 +- .../store/functions/fnc_buildUIPayload.sqf | 125 ------------- .../addons/store/functions/fnc_initClass.sqf | 42 ----- .../store/functions/fnc_initUIBridge.sqf | 25 ++- arma/server/addons/main/XEH_preInit.sqf | 10 ++ arma/server/addons/org/XEH_preInit.sqf | 18 ++ .../addons/org/functions/fnc_initOrgStore.sqf | 112 ++++++++++++ arma/server/addons/store/XEH_preInit.sqf | 20 +++ .../store/functions/fnc_initStoreStore.sqf | 121 +++++++++++++ 29 files changed, 397 insertions(+), 405 deletions(-) delete mode 100644 arma/client/addons/store/functions/fnc_buildUIPayload.sqf delete mode 100644 arma/client/addons/store/functions/fnc_initClass.sqf diff --git a/arma/client/addons/actor/XEH_preStart.sqf b/arma/client/addons/actor/XEH_preStart.sqf index 0228885..a51262a 100644 --- a/arma/client/addons/actor/XEH_preStart.sqf +++ b/arma/client/addons/actor/XEH_preStart.sqf @@ -1,3 +1,2 @@ #include "script_component.hpp" - #include "XEH_PREP.hpp" diff --git a/arma/client/addons/actor/functions/fnc_initActorClass.sqf b/arma/client/addons/actor/functions/fnc_initActorClass.sqf index 729b790..294d20f 100644 --- a/arma/client/addons/actor/functions/fnc_initActorClass.sqf +++ b/arma/client/addons/actor/functions/fnc_initActorClass.sqf @@ -4,7 +4,7 @@ * File: fnc_initActorClass.sqf * Author: IDSolutions * Date: 2026-01-28 - * Last Update: 2026-02-17 + * Last Update: 2026-03-25 * Public: Yes * * Description: @@ -33,6 +33,7 @@ GVAR(ActorBaseClass) = compileFinal createHashMapFromArray [ ["init", compileFinal { private _uid = _self get "uid"; [SRPC(actor,requestInitActor), [_uid]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["Actor loaded for %1", (name player)]; diag_log "[FORGE:Client:Actor] Actor Class Initialized!"; diff --git a/arma/client/addons/bank/XEH_postInitClient.sqf b/arma/client/addons/bank/XEH_postInitClient.sqf index b779e98..5eb9fc0 100644 --- a/arma/client/addons/bank/XEH_postInitClient.sqf +++ b/arma/client/addons/bank/XEH_postInitClient.sqf @@ -10,7 +10,7 @@ if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; [QGVAR(responseInitBank), { params [["_data", createHashMap, [createHashMap]]]; - GVAR(BankClass) call ["sync", [_data, true]]; + GVAR(BankClass) call ["markLoaded", []]; if !(isNil QGVAR(BankUIBridge)) then { GVAR(BankUIBridge) call ["refreshSession", []]; }; @@ -19,7 +19,7 @@ if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; [QGVAR(responseSyncBank), { params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]]; - GVAR(BankClass) call ["sync", [_data, _jip]]; + GVAR(BankClass) call ["markLoaded", []]; if !(isNil QGVAR(BankUIBridge)) then { GVAR(BankUIBridge) call ["refreshSession", []]; }; @@ -34,10 +34,7 @@ if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; }] call CFUNC(addEventHandler); [QGVAR(responseBankNotice), { - params [ - ["_type", "error", [""]], - ["_message", "", [""]] - ]; + params [["_type", "error", [""]], ["_message", "", [""]]]; if !(isNil QGVAR(BankUIBridge)) then { GVAR(BankUIBridge) call ["handleNoticeResponse", [_type, _message]]; @@ -45,7 +42,7 @@ if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; }] call CFUNC(addEventHandler); [{ - getPlayerUID player isNotEqualTo ""; + EGVAR(actor,ActorClass) get "isLoaded"; }, { [QGVAR(initBank), []] call CFUNC(localEvent); }] call CFUNC(waitUntilAndExecute); diff --git a/arma/client/addons/bank/XEH_preStart.sqf b/arma/client/addons/bank/XEH_preStart.sqf index 0228885..a51262a 100644 --- a/arma/client/addons/bank/XEH_preStart.sqf +++ b/arma/client/addons/bank/XEH_preStart.sqf @@ -1,3 +1,2 @@ #include "script_component.hpp" - #include "XEH_PREP.hpp" diff --git a/arma/client/addons/bank/functions/fnc_initClass.sqf b/arma/client/addons/bank/functions/fnc_initClass.sqf index 2a46590..1643c0d 100644 --- a/arma/client/addons/bank/functions/fnc_initClass.sqf +++ b/arma/client/addons/bank/functions/fnc_initClass.sqf @@ -6,7 +6,7 @@ * Public: No * * Description: - * Initializes the bank class for account sync and access helpers. + * Initializes the bank class for lifecycle and save helpers. */ #pragma hemtt ignore_variables ["_self"] @@ -14,46 +14,24 @@ GVAR(BankBaseClass) = compileFinal createHashMapFromArray [ ["#type", "BankBaseClass"], ["#create", compileFinal { _self set ["uid", getPlayerUID player]; - _self set ["account", createHashMapFromArray [ - ["bank", 0], - ["cash", 0], - ["earnings", 0], - ["transactions", []] - ]]; _self set ["isLoaded", false]; _self set ["lastSave", time]; }], - ["getAccountState", compileFinal { - _self getOrDefault ["account", createHashMap] - }], - ["get", compileFinal { - params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]]; - - private _account = _self getOrDefault ["account", createHashMap]; - _account getOrDefault [_key, _default] - }], ["init", compileFinal { [SRPC(bank,requestInitBank), [getPlayerUID player]] call CFUNC(serverEvent); _self set ["lastSave", time]; + + systemChat format ["Bank loaded for %1", (name player)]; + diag_log "[FORGE:Client:Bank] Bank Class Initialized!"; + }], + ["markLoaded", compileFinal { + if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; }; + + true }], ["save", compileFinal { [SRPC(bank,requestSaveBank), [getPlayerUID player]] call CFUNC(serverEvent); _self set ["lastSave", time]; - }], - ["sync", compileFinal { - params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]]; - - private _account = _self getOrDefault ["account", createHashMap]; - { - _account set [_x, _y]; - } forEach _data; - - _self set ["account", _account]; - if !(_self getOrDefault ["isLoaded", false]) then { - _self set ["isLoaded", true]; - }; - - true }] ]; diff --git a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf index 3b7a0d6..4d82ee9 100644 --- a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf @@ -79,9 +79,7 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ params [["_data", createHashMap, [createHashMap]]]; private _pin = _data getOrDefault ["pin", ""]; - if !(_pin isEqualType "") then { - _pin = str _pin; - }; + if !(_pin isEqualType "") then { _pin = str _pin; }; [SRPC(bank,requestSubmitPin), [getPlayerUID player, _pin]] call CFUNC(serverEvent); true @@ -128,9 +126,7 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ params [["_mode", "bank", [""]]]; private _finalMode = toLowerANSI _mode; - if !(_finalMode in ["bank", "atm"]) then { - _finalMode = "bank"; - }; + if !(_finalMode in ["bank", "atm"]) then { _finalMode = "bank"; }; _self set ["mode", _finalMode]; _finalMode diff --git a/arma/client/addons/bank/ui/_site/bank-ui.js b/arma/client/addons/bank/ui/_site/bank-ui.js index f4f99bd..ace527e 100644 --- a/arma/client/addons/bank/ui/_site/bank-ui.js +++ b/arma/client/addons/bank/ui/_site/bank-ui.js @@ -1 +1 @@ -!function(){const n=window.ForgeWebUI;(window.BankApp=window.BankApp||{}).runtime=n,window.AppRuntime=n}(),function(){const n=window.BankApp=window.BankApp||{},e={atmAuthorized:!1,mode:"bank",orgFunds:0,orgName:"",playerName:"",transferTargets:[],uid:""},t={bank:0,cash:0,earnings:0,transactions:[]};function a(n,e){var t;Object.keys(n).forEach(e=>delete n[e]),Object.assign(n,(t=e,JSON.parse(JSON.stringify(t))))}n.data={account:Object.assign({},t),session:Object.assign({},e),applyHydratePayload(n){a(this.session,Object.assign({},e,n?.session||{})),a(this.account,Object.assign({},t,n?.account||{}))}}}(),function(){const n=window.BankApp=window.BankApp||{},{createSignal:e}=n.runtime;n.store=new class{constructor(){[this.getMode,this.setMode]=e("bank"),[this.getNotice,this.setNotice]=e({text:"",type:""}),[this.getPendingAction,this.setPendingAction]=e(""),[this.getAtmView,this.setAtmView]=e("pin"),[this.getEnteredPin,this.setEnteredPin]=e(""),[this.getCustomAmount,this.setCustomAmount]=e(""),[this.getAccountVersion,this.setAccountVersion]=e(0),[this.getSessionVersion,this.setSessionVersion]=e(0)}finishAction(){this.setPendingAction("")}hydrateFromPayload(n){const e=String(n?.session?.mode||"bank").trim().toLowerCase(),t=Boolean(n?.session?.atmAuthorized),a=this.getMode(),s=this.getAtmView(),i=this.getPendingAction();if(this.setMode("atm"===e?"atm":"bank"),this.setPendingAction(""),this.setEnteredPin(""),this.setCustomAmount(""),this.setAccountVersion(this.getAccountVersion()+1),this.setSessionVersion(this.getSessionVersion()+1),"atm"===e)return t?"deposit"===i||"withdraw"===i||"pin"===s||"atm"!==a?void this.setAtmView("menu"):void this.setAtmView(s):void this.setAtmView("pin");this.setAtmView("dashboard")}resetAtm(){this.setEnteredPin(""),this.setCustomAmount(""),this.setAtmView("pin")}startAction(n){this.setPendingAction(String(n||"").trim().toLowerCase())}}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store,t=window.ForgeWebUI.createBridge({closeEvent:"bank::close",globalName:"ForgeBridge",readyEvent:"bank::ready"});function a(t){n.data.applyHydratePayload(t),e.hydrateFromPayload(t)}t.on("bank::hydrate",a),t.on("bank::sync",a),t.on("bank::notice",e=>{n.actions&&n.actions.showNotice(e.type||"error",e.message||"Bank notice received.")}),n.bridge={notifyReady:()=>t.ready({loaded:!0}),receive:t.receive,requestClose:()=>t.close({}),requestDeposit:n=>t.send("bank::deposit::request",n),requestDepositEarnings:n=>t.send("bank::depositEarnings::request",n),requestRefresh:()=>t.send("bank::refresh",{}),requestSubmitPin:n=>t.send("bank::pin::request",n),requestTransfer:n=>t.send("bank::transfer::request",n),requestWithdraw:n=>t.send("bank::withdraw::request",n),sendEvent:t.send}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store;let t=null;function a(n){const e=Math.floor(Number(n||0));return Number.isFinite(e)?e:0}function s(n,a){e.setNotice({type:n,text:a}),t&&clearTimeout(t),t=setTimeout(()=>{e.setNotice({text:"",type:""}),t=null},3200)}function i(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestDeposit)return s("error","Deposit bridge is unavailable."),!1;e.startAction("deposit");return!!o.requestDeposit({amount:i})||(e.finishAction(),s("error","Deposit bridge is unavailable."),!1)}function o(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestWithdraw)return s("error","Withdraw bridge is unavailable."),!1;e.startAction("withdraw");return!!o.requestWithdraw({amount:i})||(e.finishAction(),s("error","Withdraw bridge is unavailable."),!1)}function r(){e.setEnteredPin("")}n.actions={appendCustomAmountDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getCustomAmount()||"");a.length>=7||e.setCustomAmount(a+t)},appendPinDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getEnteredPin()||"");a.length>=4||e.setEnteredPin(a+t)},backspaceCustomAmount:function(){const n=String(e.getCustomAmount()||"");e.setCustomAmount(n.slice(0,-1))},backspacePin:function(){const n=String(e.getEnteredPin()||"");e.setEnteredPin(n.slice(0,-1))},clearCustomAmount:function(){e.setCustomAmount("")},clearPin:r,closeBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestClose){if(e.requestClose())return!0}return s("error","Bank bridge is unavailable."),!1},refreshBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestRefresh){if(e.requestRefresh())return!0}return s("error","Bank refresh bridge is unavailable."),!1},requestAtmAmount:function(n,e){return"deposit"===String(n||"").trim().toLowerCase()?i(e):o(e)},requestDeposit:i,requestDepositEarnings:function(t){const i=a(t),o=n.bridge;return o&&"function"==typeof o.requestDepositEarnings?(e.startAction("depositearnings"),!!o.requestDepositEarnings({amount:i})||(e.finishAction(),s("error","Earnings bridge is unavailable."),!1)):(s("error","Earnings bridge is unavailable."),!1)},requestTransfer:function(t,i){const o=a(i),r=String(t||"").trim(),c=n.bridge;return c&&"function"==typeof c.requestTransfer?(e.startAction("transfer"),!!c.requestTransfer({amount:o,from:"bank",target:r})||(e.finishAction(),s("error","Transfer bridge is unavailable."),!1)):(s("error","Transfer bridge is unavailable."),!1)},requestWithdraw:o,selectAtmView:function(n){const t=String(n||"").trim();return!!t&&("pin"===t?(e.resetAtm(),!0):(e.setCustomAmount(""),e.setAtmView(t),!0))},showNotice:s,submitCustomAmount:function(n){const t=a(e.getCustomAmount()),r=String(n||"").trim().toLowerCase();if(t<=0)return s("error","Enter a valid transaction amount."),!1;const c="deposit"===r?i(t):o(t);return c&&e.setCustomAmount(""),c},submitPin:function(){const t=String(e.getEnteredPin()||""),a=n.bridge;return a&&"function"==typeof a.requestSubmitPin?(e.startAction("pin"),a.requestSubmitPin({pin:t})?(r(),!0):(e.finishAction(),s("error","PIN bridge is unavailable."),!1)):(s("error","PIN bridge is unavailable."),!1)}}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a}=n.data;function s(n){return`$${Math.round(Number(n||0)).toLocaleString()}`}n.componentFns=n.componentFns||{},Object.assign(n.componentFns,{clearInputValue:function(n){const e=document.getElementById(n);e&&(e.value="")},formatCurrency:s,keypad:function(n,t,a,s){return e("div",{className:"bank-keypad"},["1","2","3","4","5","6","7","8","9"].map(t=>e("button",{type:"button",className:"bank-key",onClick:()=>n(t)},t)),e("button",{type:"button",className:"bank-key is-muted",onClick:a},"C"),e("button",{type:"button",className:"bank-key",onClick:()=>n("0")},"0"),e("button",{type:"button",className:"bank-key is-accent",onClick:s},"Enter"),e("button",{type:"button",className:"bank-key is-wide",onClick:t},"Backspace"))},metricCard:function(n,t,a,s=""){return e("div",{className:s?`bank-metric-card is-${s}`:"bank-metric-card"},e("span",{className:"bank-eyebrow"},n),e("span",{className:"bank-metric-value"},t),e("span",{className:"bank-metric-copy"},a))},pending:function(n){return t.getPendingAction()===n},pinIndicators:function(n){const t=String(n||"");return e("div",{className:"bank-pin-indicators"},[0,1,2,3].map(n=>e("span",{className:ne("div",{className:"bank-history-row"},e("div",{className:"bank-history-copy"},e("span",{className:"bank-history-title"},n.type||"Transaction"),e("span",{className:"bank-history-meta"},n.date||"Pending timestamp")),e("span",{className:"bank-history-value"},s(n.amount||0)))))}})}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{formatCurrency:o,statCard:r}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankSidebar=function(){return t.getAccountVersion(),t.getSessionVersion(),e("aside",{className:"bank-sidebar"},e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Account"),e("h2",{className:"bank-section-title"},"Balances")),e("span",{className:"bank-pill"},"Live")),e("div",{className:"bank-summary-grid"},r("Bank",o(s.bank),"accent"),r("Cash",o(s.cash)),r("Earnings",o(s.earnings),s.earnings>0?"warning":""),r("Org Funds",o(i.orgFunds),i.orgFunds>0?"success":""))),e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Profile"),e("h2",{className:"bank-section-title"},"Account Holder")),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.refreshBank()},"Refresh")),e("div",{className:"bank-profile-stack"},r("Name",i.playerName||"Unknown"),r("UID",i.uid||"-"),r("Organization",i.orgName||"No active organization"))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a,session:s}=n.data,{formatCurrency:i}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankFooter=function(){t.getAccountVersion(),t.getSessionVersion();const n=[{title:"Banking Resources",items:["Account Access Policy","Transfer & Wire Guidelines","Cash Handling Schedule","Terminal Security Notice"]},{title:"Bank Support",items:s.orgName?[`Organization: ${s.orgName}`,`Treasury Reference: ${i(s.orgFunds)}`,`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`]:["Organization: No active treasury link",`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`,`Cash On Hand: ${i(a.cash)}`]}];return e("footer",{className:"bank-footer-bar"},e("div",{className:"bank-footer"},...n.map(n=>e("div",{className:"bank-footer-block"},e("h3",{className:"bank-footer-title"},n.title),e("ul",{className:"bank-footer-list"},...(n.items||[]).map(n=>e("li",{className:"bank-footer-copy"},n)))))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{clearInputValue:o,formatCurrency:r,metricCard:c,pending:u,readInputValue:l,transactionRows:m}=n.componentFns;function d(){t.getAccountVersion()}function b(){t.getSessionVersion()}n.componentFns=n.componentFns||{},n.componentFns.BankPageHeader=function(){return b(),e("div",{className:"bank-page-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Treasury Desk"),e("h1",{className:"bank-title"},"Personal Banking")),e("span",{className:"bank-pill"},i.playerName||"Account Holder"))},n.componentFns.BankSummarySection=function(){return d(),b(),e("section",{className:"bank-page-section bank-summary-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Overview"),e("h2",{className:"bank-section-title"},"Financial Position")),e("span",{className:"bank-pill"},"Banking Desk")),e("div",{className:"bank-summary-band"},c("Primary Balance",r(s.bank),"Available for transfers and withdrawals.","accent"),c("Cash On Hand",r(s.cash),"Funds currently carried by the player."),c("Pending Earnings",r(s.earnings),"Ready to sweep into the main account ledger.",s.earnings>0?"warning":""),c("Org Snapshot",r(i.orgFunds),"Reference value pulled from the organization treasury.",i.orgFunds>0?"success":"")))},n.componentFns.BankActionSections=function(){return b(),e("div",{className:"bank-action-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Movement"),e("h2",{className:"bank-section-title"},"Deposit / Withdraw"))),e("div",{className:"bank-form-stack"},e("input",{id:"bank-amount-input",className:"bank-input",type:"number",min:"1",placeholder:"Enter amount"}),e("div",{className:"bank-action-row"},e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("deposit"),onClick:()=>{a.requestDeposit(l("bank-amount-input"))&&o("bank-amount-input")}},u("deposit")?"Depositing...":"Deposit"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",disabled:u("withdraw"),onClick:()=>{a.requestWithdraw(l("bank-amount-input"))&&o("bank-amount-input")}},u("withdraw")?"Withdrawing...":"Withdraw")))),e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Transfer"),e("h2",{className:"bank-section-title"},"Wire Funds"))),e("div",{className:"bank-form-stack"},e("select",{id:"bank-transfer-target",className:"bank-select"},e("option",{value:""},i.transferTargets.length>0?"Select recipient":"No available recipients"),i.transferTargets.map(n=>e("option",{value:n.uid},n.name||n.uid))),e("input",{id:"bank-transfer-amount",className:"bank-input",type:"number",min:"1",placeholder:"Enter transfer amount"}),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("transfer")||0===i.transferTargets.length,onClick:()=>{a.requestTransfer(l("bank-transfer-target"),l("bank-transfer-amount"))&&o("bank-transfer-amount")}},u("transfer")?"Transferring...":"Transfer Funds"))))},n.componentFns.BankSupportSection=function(){return d(),e("div",{className:"bank-support-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Sweep"),e("h2",{className:"bank-section-title"},"Deposit Earnings"))),e("p",{className:"bank-card-copy"},"Sweep pending earnings into the primary account when you want them reflected in the main balance."),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("depositearnings")||Number(s.earnings||0)<=0,onClick:()=>a.requestDepositEarnings(s.earnings)},u("depositearnings")?"Depositing...":"Deposit Earnings")))},n.componentFns.BankHistorySection=function(){return d(),e("section",{className:"bank-page-section bank-history-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"History"),e("h2",{className:"bank-section-title"},"Recent Transactions"))),m())}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s}=n.data,{formatCurrency:i,keypad:o,pinIndicators:r}=n.componentFns;function c(n){const t="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-action-grid"},[20,50,100,500].map(s=>e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.requestAtmAmount(n,s)},`${t} ${i(s)}`)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("deposit"===n?"customDeposit":"customWithdraw")},"Custom Amount"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},"Back"))}function u(n){const s="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},t.getCustomAmount()?i(t.getCustomAmount()):"$0"),o(a.appendCustomAmountDigit,a.backspaceCustomAmount,a.clearCustomAmount,()=>a.submitCustomAmount(n)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},`Cancel ${s}`))}n.componentFns=n.componentFns||{},n.componentFns.ATMView=function(){t.getAccountVersion();const n=t.getAtmView(),l=String(t.getEnteredPin()||"");let m="Terminal Access",d="Authenticate with the four-digit account PIN before using the terminal.",b=null;switch(n){case"menu":m="ATM Menu",d="Select a banking action. The ATM can deposit, withdraw, and show the live account balance.",b=e("div",{className:"bank-atm-action-grid"},e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("withdraw")},"Withdraw Cash"),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("deposit")},"Deposit Cash"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("balance")},"Check Balance"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"));break;case"withdraw":m="Withdraw Cash",d="Choose a preset amount or enter a custom amount for withdrawal.",b=c("withdraw");break;case"deposit":m="Deposit Cash",d="Move cash on hand back into the main bank balance from the terminal.",b=c("deposit");break;case"customWithdraw":m="Custom Withdraw",d="Enter the exact withdrawal amount.",b=u("withdraw");break;case"customDeposit":m="Custom Deposit",d="Enter the exact deposit amount.",b=u("deposit");break;case"balance":m="Available Balance",d="Current bank balance available at this terminal.",b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-balance-display"},i(s.bank)),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("menu")},"Return to Menu"));break;default:b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},r(l)),o(a.appendPinDigit,a.backspacePin,a.clearPin,a.submitPin),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"))}return e("div",{className:"bank-atm-shell"},e("section",{className:"bank-atm-panel"},e("div",{className:"bank-panel-header"},e("div",null,e("span",{className:"bank-eyebrow"},"ATM"),e("h1",{className:"bank-title"},m)),e("span",{className:"bank-pill"},"Secure Terminal")),e("p",{className:"bank-panel-copy"},d),b))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=window.SharedUI.componentFns.WindowTitleBar,a=n.store,s=n.actions;n.componentFns=n.componentFns||{},n.componentFns.NoticeLayer=function(){const n=a.getNotice();return n.text?e("div",{className:"bank-notice-stack"},e("div",{className:"error"===n.type?"bank-notice is-error":"bank-notice is-success"},n.text)):null},n.components=n.components||{},n.components.App=function(){const n=a.getMode();return e("div",{className:"atm"===n?"bank-shell is-atm":"bank-shell"},"atm"===n?null:t({kicker:"FORGE Finance",title:"Global Banking Network",onClose:()=>s.closeBank(),closeLabel:"Close banking interface"}),e("div",{id:"bank-notice-root"}),"atm"===n?e("div",{id:"bank-atm-root"}):[e("div",{className:"bank-scroll-shell","data-preserve-scroll-id":"bank-page-scroll"},[e("div",{className:"bank-layout"},e("div",{id:"bank-sidebar-root"}),e("main",{className:"bank-main"},e("div",{className:"bank-page"},e("div",{id:"bank-page-header-root"}),e("p",{className:"bank-page-copy"},"Manage deposits, withdrawals, transfers, and earnings sweeps from the same shared financial console."),e("div",{className:"bank-page-divider"}),e("div",{className:"bank-page-body"},e("div",{id:"bank-summary-section-root"}),e("div",{id:"bank-action-sections-root"}),e("div",{id:"bank-support-section-root"}),e("div",{id:"bank-history-section-root"}))))),e("div",{id:"bank-footer-root"})])])}}(),function(){const n=window.ForgeWebUI,e=window.BankApp,t=[{id:"bank-notice-root",preserveScroll:!1,render:()=>e.componentFns.NoticeLayer()},{id:"bank-sidebar-root",preserveScroll:!1,render:()=>e.componentFns.BankSidebar()},{id:"bank-page-header-root",preserveScroll:!1,render:()=>e.componentFns.BankPageHeader()},{id:"bank-summary-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSummarySection()},{id:"bank-action-sections-root",preserveScroll:!1,render:()=>e.componentFns.BankActionSections()},{id:"bank-support-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSupportSection()},{id:"bank-history-section-root",preserveScroll:!1,render:()=>e.componentFns.BankHistorySection()},{id:"bank-atm-root",preserveScroll:!1,render:()=>e.componentFns.ATMView()},{id:"bank-footer-root",preserveScroll:!1,render:()=>e.componentFns.BankFooter()}];n.createApp({name:"bank",root:"#app",setup({root:a}){const s=function(){const e=new Map;return{sync:function(){t.forEach(t=>{const a=document.getElementById(t.id),s=e.get(t.id);if(!a)return void(s&&(s.handle.dispose(),e.delete(t.id)));if(s&&s.container===a)return;s&&s.handle.dispose();const i=n.mount(a,t.render,{preserveScroll:t.preserveScroll});e.set(t.id,{container:a,handle:i})})}}}();n.mount(a,()=>e.components.App(),{preserveScroll:!1}),e.bridge&&e.bridge.notifyReady(),n.effect(()=>{e.store.getMode(),requestAnimationFrame(()=>{s.sync()})})}}).start()}(); \ No newline at end of file +!function(){const n=window.ForgeWebUI;(window.BankApp=window.BankApp||{}).runtime=n,window.AppRuntime=n}(),function(){const n=window.BankApp=window.BankApp||{},e={atmAuthorized:!1,mode:"bank",orgFunds:0,orgName:"",playerName:"",transferTargets:[],uid:""},t={bank:0,cash:0,earnings:0,transactions:[]};function a(n,e){var t;Object.keys(n).forEach(e=>delete n[e]),Object.assign(n,(t=e,JSON.parse(JSON.stringify(t))))}n.data={account:Object.assign({},t),session:Object.assign({},e),applyHydratePayload(n){a(this.session,Object.assign({},e,n?.session||{})),a(this.account,Object.assign({},t,n?.account||{}))}}}(),function(){const n=window.BankApp=window.BankApp||{},{createSignal:e}=n.runtime;n.store=new class{constructor(){[this.getMode,this.setMode]=e("bank"),[this.getNotice,this.setNotice]=e({text:"",type:""}),[this.getPendingAction,this.setPendingAction]=e(""),[this.getAtmView,this.setAtmView]=e("pin"),[this.getEnteredPin,this.setEnteredPin]=e(""),[this.getCustomAmount,this.setCustomAmount]=e(""),[this.getAccountVersion,this.setAccountVersion]=e(0),[this.getSessionVersion,this.setSessionVersion]=e(0)}finishAction(){this.setPendingAction("")}hydrateFromPayload(n){const e=String(n?.session?.mode||"bank").trim().toLowerCase(),t=Boolean(n?.session?.atmAuthorized),a=this.getMode(),s=this.getAtmView(),i=this.getPendingAction();if(this.setMode("atm"===e?"atm":"bank"),this.setPendingAction(""),this.setEnteredPin(""),this.setCustomAmount(""),this.setAccountVersion(this.getAccountVersion()+1),this.setSessionVersion(this.getSessionVersion()+1),"atm"===e)return t?"deposit"===i||"withdraw"===i||"pin"===s||"atm"!==a?void this.setAtmView("menu"):void this.setAtmView(s):void this.setAtmView("pin");this.setAtmView("dashboard")}resetAtm(){this.setEnteredPin(""),this.setCustomAmount(""),this.setAtmView("pin")}startAction(n){this.setPendingAction(String(n||"").trim().toLowerCase())}}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store,t=window.ForgeWebUI.createBridge({closeEvent:"bank::close",globalName:"ForgeBridge",readyEvent:"bank::ready"});function a(t){n.data.applyHydratePayload(t),e.hydrateFromPayload(t)}t.on("bank::hydrate",a),t.on("bank::sync",a),t.on("bank::notice",t=>{e.finishAction(),n.actions&&n.actions.showNotice(t.type||"error",t.message||"Bank notice received.")}),n.bridge={notifyReady:()=>t.ready({loaded:!0}),receive:t.receive,requestClose:()=>t.close({}),requestDeposit:n=>t.send("bank::deposit::request",n),requestDepositEarnings:n=>t.send("bank::depositEarnings::request",n),requestRefresh:()=>t.send("bank::refresh",{}),requestSubmitPin:n=>t.send("bank::pin::request",n),requestTransfer:n=>t.send("bank::transfer::request",n),requestWithdraw:n=>t.send("bank::withdraw::request",n),sendEvent:t.send}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store;let t=null;function a(n){const e=Math.floor(Number(n||0));return Number.isFinite(e)?e:0}function s(n,a){e.setNotice({type:n,text:a}),t&&clearTimeout(t),t=setTimeout(()=>{e.setNotice({text:"",type:""}),t=null},3200)}function i(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestDeposit)return s("error","Deposit bridge is unavailable."),!1;e.startAction("deposit");return!!o.requestDeposit({amount:i})||(e.finishAction(),s("error","Deposit bridge is unavailable."),!1)}function o(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestWithdraw)return s("error","Withdraw bridge is unavailable."),!1;e.startAction("withdraw");return!!o.requestWithdraw({amount:i})||(e.finishAction(),s("error","Withdraw bridge is unavailable."),!1)}function r(){e.setEnteredPin("")}n.actions={appendCustomAmountDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getCustomAmount()||"");a.length>=7||e.setCustomAmount(a+t)},appendPinDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getEnteredPin()||"");a.length>=4||e.setEnteredPin(a+t)},backspaceCustomAmount:function(){const n=String(e.getCustomAmount()||"");e.setCustomAmount(n.slice(0,-1))},backspacePin:function(){const n=String(e.getEnteredPin()||"");e.setEnteredPin(n.slice(0,-1))},clearCustomAmount:function(){e.setCustomAmount("")},clearPin:r,closeBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestClose){if(e.requestClose())return!0}return s("error","Bank bridge is unavailable."),!1},refreshBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestRefresh){if(e.requestRefresh())return!0}return s("error","Bank refresh bridge is unavailable."),!1},requestAtmAmount:function(n,e){return"deposit"===String(n||"").trim().toLowerCase()?i(e):o(e)},requestDeposit:i,requestDepositEarnings:function(t){const i=a(t),o=n.bridge;return o&&"function"==typeof o.requestDepositEarnings?(e.startAction("depositearnings"),!!o.requestDepositEarnings({amount:i})||(e.finishAction(),s("error","Earnings bridge is unavailable."),!1)):(s("error","Earnings bridge is unavailable."),!1)},requestTransfer:function(t,i){const o=a(i),r=String(t||"").trim(),c=n.bridge;return c&&"function"==typeof c.requestTransfer?(e.startAction("transfer"),!!c.requestTransfer({amount:o,from:"bank",target:r})||(e.finishAction(),s("error","Transfer bridge is unavailable."),!1)):(s("error","Transfer bridge is unavailable."),!1)},requestWithdraw:o,selectAtmView:function(n){const t=String(n||"").trim();return!!t&&("pin"===t?(e.resetAtm(),!0):(e.setCustomAmount(""),e.setAtmView(t),!0))},showNotice:s,submitCustomAmount:function(n){const t=a(e.getCustomAmount()),r=String(n||"").trim().toLowerCase();if(t<=0)return s("error","Enter a valid transaction amount."),!1;const c="deposit"===r?i(t):o(t);return c&&e.setCustomAmount(""),c},submitPin:function(){const t=String(e.getEnteredPin()||""),a=n.bridge;return a&&"function"==typeof a.requestSubmitPin?(e.startAction("pin"),a.requestSubmitPin({pin:t})?(r(),!0):(e.finishAction(),s("error","PIN bridge is unavailable."),!1)):(s("error","PIN bridge is unavailable."),!1)}}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a}=n.data;function s(n){return`$${Math.round(Number(n||0)).toLocaleString()}`}n.componentFns=n.componentFns||{},Object.assign(n.componentFns,{clearInputValue:function(n){const e=document.getElementById(n);e&&(e.value="")},formatCurrency:s,keypad:function(n,t,a,s){return e("div",{className:"bank-keypad"},["1","2","3","4","5","6","7","8","9"].map(t=>e("button",{type:"button",className:"bank-key",onClick:()=>n(t)},t)),e("button",{type:"button",className:"bank-key is-muted",onClick:a},"C"),e("button",{type:"button",className:"bank-key",onClick:()=>n("0")},"0"),e("button",{type:"button",className:"bank-key is-accent",onClick:s},"Enter"),e("button",{type:"button",className:"bank-key is-wide",onClick:t},"Backspace"))},metricCard:function(n,t,a,s=""){return e("div",{className:s?`bank-metric-card is-${s}`:"bank-metric-card"},e("span",{className:"bank-eyebrow"},n),e("span",{className:"bank-metric-value"},t),e("span",{className:"bank-metric-copy"},a))},pending:function(n){return t.getPendingAction()===n},pinIndicators:function(n){const t=String(n||"");return e("div",{className:"bank-pin-indicators"},[0,1,2,3].map(n=>e("span",{className:ne("div",{className:"bank-history-row"},e("div",{className:"bank-history-copy"},e("span",{className:"bank-history-title"},n.type||"Transaction"),e("span",{className:"bank-history-meta"},n.date||"Pending timestamp")),e("span",{className:"bank-history-value"},s(n.amount||0)))))}})}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{formatCurrency:o,statCard:r}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankSidebar=function(){return t.getAccountVersion(),t.getSessionVersion(),e("aside",{className:"bank-sidebar"},e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Account"),e("h2",{className:"bank-section-title"},"Balances")),e("span",{className:"bank-pill"},"Live")),e("div",{className:"bank-summary-grid"},r("Bank",o(s.bank),"accent"),r("Cash",o(s.cash)),r("Earnings",o(s.earnings),s.earnings>0?"warning":""),r("Org Funds",o(i.orgFunds),i.orgFunds>0?"success":""))),e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Profile"),e("h2",{className:"bank-section-title"},"Account Holder")),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.refreshBank()},"Refresh")),e("div",{className:"bank-profile-stack"},r("Name",i.playerName||"Unknown"),r("UID",i.uid||"-"),r("Organization",i.orgName||"No active organization"))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a,session:s}=n.data,{formatCurrency:i}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankFooter=function(){t.getAccountVersion(),t.getSessionVersion();const n=[{title:"Banking Resources",items:["Account Access Policy","Transfer & Wire Guidelines","Cash Handling Schedule","Terminal Security Notice"]},{title:"Bank Support",items:s.orgName?[`Organization: ${s.orgName}`,`Treasury Reference: ${i(s.orgFunds)}`,`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`]:["Organization: No active treasury link",`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`,`Cash On Hand: ${i(a.cash)}`]}];return e("footer",{className:"bank-footer-bar"},e("div",{className:"bank-footer"},...n.map(n=>e("div",{className:"bank-footer-block"},e("h3",{className:"bank-footer-title"},n.title),e("ul",{className:"bank-footer-list"},...(n.items||[]).map(n=>e("li",{className:"bank-footer-copy"},n)))))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{clearInputValue:o,formatCurrency:r,metricCard:c,pending:u,readInputValue:l,transactionRows:m}=n.componentFns;function d(){t.getAccountVersion()}function b(){t.getSessionVersion()}n.componentFns=n.componentFns||{},n.componentFns.BankPageHeader=function(){return b(),e("div",{className:"bank-page-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Treasury Desk"),e("h1",{className:"bank-title"},"Personal Banking")),e("span",{className:"bank-pill"},i.playerName||"Account Holder"))},n.componentFns.BankSummarySection=function(){return d(),b(),e("section",{className:"bank-page-section bank-summary-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Overview"),e("h2",{className:"bank-section-title"},"Financial Position")),e("span",{className:"bank-pill"},"Banking Desk")),e("div",{className:"bank-summary-band"},c("Primary Balance",r(s.bank),"Available for transfers and withdrawals.","accent"),c("Cash On Hand",r(s.cash),"Funds currently carried by the player."),c("Pending Earnings",r(s.earnings),"Ready to sweep into the main account ledger.",s.earnings>0?"warning":""),c("Org Snapshot",r(i.orgFunds),"Reference value pulled from the organization treasury.",i.orgFunds>0?"success":"")))},n.componentFns.BankActionSections=function(){return b(),e("div",{className:"bank-action-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Movement"),e("h2",{className:"bank-section-title"},"Deposit / Withdraw"))),e("div",{className:"bank-form-stack"},e("input",{id:"bank-amount-input",className:"bank-input",type:"number",min:"1",placeholder:"Enter amount"}),e("div",{className:"bank-action-row"},e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("deposit"),onClick:()=>{a.requestDeposit(l("bank-amount-input"))&&o("bank-amount-input")}},u("deposit")?"Depositing...":"Deposit"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",disabled:u("withdraw"),onClick:()=>{a.requestWithdraw(l("bank-amount-input"))&&o("bank-amount-input")}},u("withdraw")?"Withdrawing...":"Withdraw")))),e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Transfer"),e("h2",{className:"bank-section-title"},"Wire Funds"))),e("div",{className:"bank-form-stack"},e("select",{id:"bank-transfer-target",className:"bank-select"},e("option",{value:""},i.transferTargets.length>0?"Select recipient":"No available recipients"),i.transferTargets.map(n=>e("option",{value:n.uid},n.name||n.uid))),e("input",{id:"bank-transfer-amount",className:"bank-input",type:"number",min:"1",placeholder:"Enter transfer amount"}),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("transfer")||0===i.transferTargets.length,onClick:()=>{a.requestTransfer(l("bank-transfer-target"),l("bank-transfer-amount"))&&o("bank-transfer-amount")}},u("transfer")?"Transferring...":"Transfer Funds"))))},n.componentFns.BankSupportSection=function(){return d(),e("div",{className:"bank-support-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Sweep"),e("h2",{className:"bank-section-title"},"Deposit Earnings"))),e("p",{className:"bank-card-copy"},"Sweep pending earnings into the primary account when you want them reflected in the main balance."),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("depositearnings")||Number(s.earnings||0)<=0,onClick:()=>a.requestDepositEarnings(s.earnings)},u("depositearnings")?"Depositing...":"Deposit Earnings")))},n.componentFns.BankHistorySection=function(){return d(),e("section",{className:"bank-page-section bank-history-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"History"),e("h2",{className:"bank-section-title"},"Recent Transactions"))),m())}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s}=n.data,{formatCurrency:i,keypad:o,pinIndicators:r}=n.componentFns;function c(n){const t="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-action-grid"},[20,50,100,500].map(s=>e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.requestAtmAmount(n,s)},`${t} ${i(s)}`)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("deposit"===n?"customDeposit":"customWithdraw")},"Custom Amount"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},"Back"))}function u(n){const s="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},t.getCustomAmount()?i(t.getCustomAmount()):"$0"),o(a.appendCustomAmountDigit,a.backspaceCustomAmount,a.clearCustomAmount,()=>a.submitCustomAmount(n)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},`Cancel ${s}`))}n.componentFns=n.componentFns||{},n.componentFns.ATMView=function(){t.getAccountVersion();const n=t.getAtmView(),l=String(t.getEnteredPin()||"");let m="Terminal Access",d="Authenticate with the four-digit account PIN before using the terminal.",b=null;switch(n){case"menu":m="ATM Menu",d="Select a banking action. The ATM can deposit, withdraw, and show the live account balance.",b=e("div",{className:"bank-atm-action-grid"},e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("withdraw")},"Withdraw Cash"),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("deposit")},"Deposit Cash"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("balance")},"Check Balance"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"));break;case"withdraw":m="Withdraw Cash",d="Choose a preset amount or enter a custom amount for withdrawal.",b=c("withdraw");break;case"deposit":m="Deposit Cash",d="Move cash on hand back into the main bank balance from the terminal.",b=c("deposit");break;case"customWithdraw":m="Custom Withdraw",d="Enter the exact withdrawal amount.",b=u("withdraw");break;case"customDeposit":m="Custom Deposit",d="Enter the exact deposit amount.",b=u("deposit");break;case"balance":m="Available Balance",d="Current bank balance available at this terminal.",b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-balance-display"},i(s.bank)),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("menu")},"Return to Menu"));break;default:b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},r(l)),o(a.appendPinDigit,a.backspacePin,a.clearPin,a.submitPin),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"))}return e("div",{className:"bank-atm-shell"},e("section",{className:"bank-atm-panel"},e("div",{className:"bank-panel-header"},e("div",null,e("span",{className:"bank-eyebrow"},"ATM"),e("h1",{className:"bank-title"},m)),e("span",{className:"bank-pill"},"Secure Terminal")),e("p",{className:"bank-panel-copy"},d),b))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=window.SharedUI.componentFns.WindowTitleBar,a=n.store,s=n.actions;n.componentFns=n.componentFns||{},n.componentFns.NoticeLayer=function(){const n=a.getNotice();return n.text?e("div",{className:"bank-notice-stack"},e("div",{className:"error"===n.type?"bank-notice is-error":"bank-notice is-success"},n.text)):null},n.components=n.components||{},n.components.App=function(){const n=a.getMode();return e("div",{className:"atm"===n?"bank-shell is-atm":"bank-shell"},"atm"===n?null:t({kicker:"FORGE Finance",title:"Global Banking Network",onClose:()=>s.closeBank(),closeLabel:"Close banking interface"}),e("div",{id:"bank-notice-root"}),"atm"===n?e("div",{id:"bank-atm-root"}):[e("div",{className:"bank-scroll-shell","data-preserve-scroll-id":"bank-page-scroll"},[e("div",{className:"bank-layout"},e("div",{id:"bank-sidebar-root"}),e("main",{className:"bank-main"},e("div",{className:"bank-page"},e("div",{id:"bank-page-header-root"}),e("p",{className:"bank-page-copy"},"Manage deposits, withdrawals, transfers, and earnings sweeps from the same shared financial console."),e("div",{className:"bank-page-divider"}),e("div",{className:"bank-page-body"},e("div",{id:"bank-summary-section-root"}),e("div",{id:"bank-action-sections-root"}),e("div",{id:"bank-support-section-root"}),e("div",{id:"bank-history-section-root"}))))),e("div",{id:"bank-footer-root"})])])}}(),function(){const n=window.ForgeWebUI,e=window.BankApp,t=[{id:"bank-notice-root",preserveScroll:!1,render:()=>e.componentFns.NoticeLayer()},{id:"bank-sidebar-root",preserveScroll:!1,render:()=>e.componentFns.BankSidebar()},{id:"bank-page-header-root",preserveScroll:!1,render:()=>e.componentFns.BankPageHeader()},{id:"bank-summary-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSummarySection()},{id:"bank-action-sections-root",preserveScroll:!1,render:()=>e.componentFns.BankActionSections()},{id:"bank-support-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSupportSection()},{id:"bank-history-section-root",preserveScroll:!1,render:()=>e.componentFns.BankHistorySection()},{id:"bank-atm-root",preserveScroll:!1,render:()=>e.componentFns.ATMView()},{id:"bank-footer-root",preserveScroll:!1,render:()=>e.componentFns.BankFooter()}];n.createApp({name:"bank",root:"#app",setup({root:a}){const s=function(){const e=new Map;return{sync:function(){t.forEach(t=>{const a=document.getElementById(t.id),s=e.get(t.id);if(!a)return void(s&&(s.handle.dispose(),e.delete(t.id)));if(s&&s.container===a)return;s&&s.handle.dispose();const i=n.mount(a,t.render,{preserveScroll:t.preserveScroll});e.set(t.id,{container:a,handle:i})})}}}();n.mount(a,()=>e.components.App(),{preserveScroll:!1}),e.bridge&&e.bridge.notifyReady(),n.effect(()=>{e.store.getMode(),requestAnimationFrame(()=>{s.sync()})})}}).start()}(); \ No newline at end of file diff --git a/arma/client/addons/bank/ui/src/bridge.js b/arma/client/addons/bank/ui/src/bridge.js index 41f872b..02d3113 100644 --- a/arma/client/addons/bank/ui/src/bridge.js +++ b/arma/client/addons/bank/ui/src/bridge.js @@ -15,6 +15,7 @@ bridge.on("bank::hydrate", hydrate); bridge.on("bank::sync", hydrate); bridge.on("bank::notice", (payloadData) => { + store.finishAction(); if (BankApp.actions) { BankApp.actions.showNotice( payloadData.type || "error", diff --git a/arma/client/addons/common/XEH_preStart.sqf b/arma/client/addons/common/XEH_preStart.sqf index 0228885..a51262a 100644 --- a/arma/client/addons/common/XEH_preStart.sqf +++ b/arma/client/addons/common/XEH_preStart.sqf @@ -1,3 +1,2 @@ #include "script_component.hpp" - #include "XEH_PREP.hpp" diff --git a/arma/client/addons/garage/functions/fnc_initClass.sqf b/arma/client/addons/garage/functions/fnc_initClass.sqf index 841550b..6bb114e 100644 --- a/arma/client/addons/garage/functions/fnc_initClass.sqf +++ b/arma/client/addons/garage/functions/fnc_initClass.sqf @@ -4,7 +4,7 @@ * File: fnc_initClass.sqf * Author: IDSolutions * Date: 2025-12-17 - * Last Update: 2026-02-13 + * Last Update: 2026-03-25 * Public: No * * Description: @@ -35,6 +35,7 @@ GVAR(GarageBaseClass) = compileFinal createHashMapFromArray [ private _garage = _self get "garage"; [SRPC(garage,requestInitGarage), [_uid, _garage]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["Garage loaded for %1", (name player)]; diag_log "[FORGE:Client:Garage] Garage Class Initialized!"; diff --git a/arma/client/addons/garage/functions/fnc_initVGClass.sqf b/arma/client/addons/garage/functions/fnc_initVGClass.sqf index f3901f9..e5370d7 100644 --- a/arma/client/addons/garage/functions/fnc_initVGClass.sqf +++ b/arma/client/addons/garage/functions/fnc_initVGClass.sqf @@ -4,7 +4,7 @@ * File: fnc_initVGClass.sqf * Author: IDSolutions * Date: 2025-12-16 - * Last Update: 2026-02-13 + * Last Update: 2026-03-25 * Public: No * * Description: @@ -37,6 +37,7 @@ GVAR(VGBaseClass) = compileFinal createHashMapFromArray [ private _vGarage = _self get "vGarage"; [SRPC(garage,requestInitVG), [_uid, _vGarage]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["VGarage loaded for %1", (name player)]; diag_log "[FORGE:Client:VGarage] VGarage Class Initialized!"; diff --git a/arma/client/addons/locker/functions/fnc_initLockerClass.sqf b/arma/client/addons/locker/functions/fnc_initLockerClass.sqf index 68c5a94..4012c52 100644 --- a/arma/client/addons/locker/functions/fnc_initLockerClass.sqf +++ b/arma/client/addons/locker/functions/fnc_initLockerClass.sqf @@ -4,7 +4,7 @@ * File: fnc_initLockerClass.sqf * Author: IDSolutions * Date: 2025-12-17 - * Last Update: 2026-02-13 + * Last Update: 2026-03-25 * Public: No * * Description: @@ -34,6 +34,7 @@ GVAR(LockerBaseClass) = compileFinal createHashMapFromArray [ private _uid = _self get "uid"; [SRPC(locker,requestInitLocker), [_uid]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["Locker loaded for %1", (name player)]; diag_log "[FORGE:Client:Locker] Locker Class Initialized!"; diff --git a/arma/client/addons/locker/functions/fnc_initVAClass.sqf b/arma/client/addons/locker/functions/fnc_initVAClass.sqf index 5711d63..d6c0749 100644 --- a/arma/client/addons/locker/functions/fnc_initVAClass.sqf +++ b/arma/client/addons/locker/functions/fnc_initVAClass.sqf @@ -4,7 +4,7 @@ * File: fnc_init.sqf * Author: IDSolutions * Date: 2025-12-16 - * Last Update: 2026-02-13 + * Last Update: 2026-03-25 * Public: No * * Description: @@ -34,6 +34,7 @@ GVAR(VABaseClass) = compileFinal createHashMapFromArray [ private _uid = _self get "uid"; FORGE_Locker_Box = "ReammoBox_F" createVehicleLocal [0, 0, -999]; [SRPC(locker,requestInitVA), [_uid]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["VArsenal loaded for %1", (name player)]; diag_log "[FORGE:Client:VArsenal] VArsenal Class Initialized!"; diff --git a/arma/client/addons/notifications/XEH_postInitClient.sqf b/arma/client/addons/notifications/XEH_postInitClient.sqf index 6eada6a..9981181 100644 --- a/arma/client/addons/notifications/XEH_postInitClient.sqf +++ b/arma/client/addons/notifications/XEH_postInitClient.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" [{ - EGVAR(actor,ActorClass) get "isLoaded"; + EGVAR(locker,VAClass) get "isLoaded"; }, { ("NotificationHudLayer" call BFUNC(rscLayer)) cutRsc ["RscNotifications", "PLAIN"]; call FUNC(openUI); diff --git a/arma/client/addons/notifications/XEH_preStart.sqf b/arma/client/addons/notifications/XEH_preStart.sqf index 0228885..a51262a 100644 --- a/arma/client/addons/notifications/XEH_preStart.sqf +++ b/arma/client/addons/notifications/XEH_preStart.sqf @@ -1,3 +1,2 @@ #include "script_component.hpp" - #include "XEH_PREP.hpp" diff --git a/arma/client/addons/org/XEH_postInitClient.sqf b/arma/client/addons/org/XEH_postInitClient.sqf index 3f9e4d9..1d0d2c2 100644 --- a/arma/client/addons/org/XEH_postInitClient.sqf +++ b/arma/client/addons/org/XEH_postInitClient.sqf @@ -3,24 +3,24 @@ if (isNil QGVAR(OrgClass)) then { call FUNC(initClass); }; if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); }; -[QGVAR(initOrg), { - GVAR(OrgClass) call ["init", []]; -}] call CFUNC(addEventHandler); - [QGVAR(responseInitOrg), { params [["_data", createHashMap, [createHashMap]]]; - GVAR(OrgClass) call ["sync", [_data, true]]; GVAR(OrgUIBridge) call ["refreshPortal", []]; }] call CFUNC(addEventHandler); [QGVAR(responseSyncOrg), { params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]]; - GVAR(OrgClass) call ["sync", [_data, _jip]]; GVAR(OrgUIBridge) call ["refreshPortal", []]; }] call CFUNC(addEventHandler); +[QGVAR(responseHydrateOrg), { + params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "org::sync", [""]]]; + + GVAR(OrgUIBridge) call ["handleHydrateResponse", [_payload, _bridgeEvent]]; +}] call CFUNC(addEventHandler); + [QGVAR(responseCreateOrg), { params [["_payload", createHashMap, [createHashMap]]]; @@ -46,7 +46,7 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); }; }] call CFUNC(addEventHandler); [{ - EGVAR(actor,ActorClass) get "isLoaded"; + EGVAR(locker,VAClass) get "isLoaded"; }, { [QGVAR(initOrg), []] call CFUNC(localEvent); }] call CFUNC(waitUntilAndExecute); diff --git a/arma/client/addons/org/XEH_preStart.sqf b/arma/client/addons/org/XEH_preStart.sqf index 0228885..a51262a 100644 --- a/arma/client/addons/org/XEH_preStart.sqf +++ b/arma/client/addons/org/XEH_preStart.sqf @@ -1,3 +1,2 @@ #include "script_component.hpp" - #include "XEH_PREP.hpp" diff --git a/arma/client/addons/org/functions/fnc_initClass.sqf b/arma/client/addons/org/functions/fnc_initClass.sqf index dab354d..52695a5 100644 --- a/arma/client/addons/org/functions/fnc_initClass.sqf +++ b/arma/client/addons/org/functions/fnc_initClass.sqf @@ -3,21 +3,21 @@ /* * File: fnc_initClass.sqf * Author: IDSolutions - * Date: 2026-02-13 - * Last Update: 2026-02-13 + * Date: 2026-03-25 + * Last Update: 2026-03-25 * Public: No * * Description: - * Initializes the org class. + * No description added yet. * - * Arguments: - * None + * Parameter(s): + * N/A * - * Return Value: - * Org class object [HASHMAP OBJECT] + * Returns: + * Something [BOOL] * - * Examples: - * call forge_client_org_fnc_initClass + * Example(s): + * [parameter] call forge_x_component_fnc_myFunction */ #pragma hemtt ignore_variables ["_self"] @@ -25,155 +25,24 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [ ["#type", "OrgBaseClass"], ["#create", compileFinal { _self set ["uid", getPlayerUID player]; - _self set ["org", createHashMap]; _self set ["isLoaded", false]; _self set ["lastSave", time]; - - private _org = createHashMap; - _org set ["id", ""]; - _org set ["owner", ""]; - _org set ["name", ""]; - _org set ["funds", 0]; - _org set ["reputation", 0]; - _org set ["credit_lines", createHashMap]; - _org set ["assets", createHashMap]; - _org set ["fleet", createHashMap]; - _org set ["members", createHashMap]; - - _self set ["org", _org]; }], ["init", compileFinal { - private _uid = _self get "uid"; - private _org = _self get "org"; - - [SRPC(org,requestInitOrg), [_uid, _org]] call CFUNC(serverEvent); + [SRPC(org,requestInitOrg), [getPlayerUID player]] call CFUNC(serverEvent); + _self set ["lastSave", time]; systemChat format ["Org loaded for %1", (name player)]; diag_log "[FORGE:Client:Org] Org Class Initialized!"; }], + ["markLoaded", compileFinal { + if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; }; + + true + }], ["save", compileFinal { - params [["_sync", false, [false]]]; - - private _uid = _self get "uid"; - [SRPC(org,requestSaveOrg), [_uid, _sync]] call CFUNC(serverEvent); - + [SRPC(bank,requestSaveOrg), [getPlayerUID player]] call CFUNC(serverEvent); _self set ["lastSave", time]; - }], - ["sync", compileFinal { - params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]]; - - private _isLoaded = _self get "isLoaded"; - private _org = _self get "org"; - - { _org set [_x, _y]; } forEach _data; - _self set ["org", _org]; - - if !(_isLoaded) then { _self set ["isLoaded", true]; }; - diag_log "[FORGE:Client:Org] Sync completed"; - }], - ["buildPortalPayload", compileFinal { - private _orgData = _self get "org"; - - private _name = _orgData get "name"; - private _id = _orgData get "id"; - private _ownerUid = _orgData get "owner"; - private _funds = _orgData get "funds"; - private _reputation = _orgData get "reputation"; - private _creditLinesRaw = _orgData getOrDefault ["credit_lines", createHashMap]; - private _assetsRaw = _orgData get "assets"; - private _fleetRaw = _orgData get "fleet"; - private _membersRaw = _orgData get "members"; - private _isDefaultOrg = (_orgData getOrDefault ["default", false]) - || {toLower _id isEqualTo "default"} - || {toLower _ownerUid isEqualTo "server"}; - - private _playerName = name player; - private _playerUid = getPlayerUID player; - private _playerVar = vehicleVarName player; - private _sessionRole = "Member"; - private _sessionIsCeo = _isDefaultOrg && {_playerVar isEqualTo "ceo"}; - private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server"); - - private _membersList = []; - { - private _memberData = _y; - private _memberName = _memberData getOrDefault ["name", "Unknown"]; - private _memberUid = _memberData getOrDefault ["uid", ""]; - - if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then { _ownerName = _memberName; }; - if (_memberUid isEqualTo _playerUid) then { _sessionRole = "Member"; }; - - _membersList pushBack (createHashMapFromArray [ - ["uid", _memberUid], - ["name", _memberName] - ]); - } forEach _membersRaw; - - if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _playerUid }) then { _ownerName = _playerName; }; - if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; }; - if (_ownerUid isEqualTo _playerUid) then { _sessionRole = "Leader"; }; - - private _assetsList = []; - { - private _assetData = _y; - _assetsList pushBack (createHashMapFromArray [ - ["name", _assetData getOrDefault ["name", "Unknown Asset"]], - ["type", _assetData getOrDefault ["type", "items"]], - ["quantity", str (_assetData getOrDefault ["quantity", 0])] - ]); - } forEach _assetsRaw; - - private _fleetList = []; - { - private _vehicleData = _y; - _fleetList pushBack (createHashMapFromArray [ - ["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]], - ["type", _vehicleData getOrDefault ["type", "other"]], - ["status", _vehicleData getOrDefault ["status", "Unknown"]], - ["damage", _vehicleData getOrDefault ["damage", "0%"]] - ]); - } forEach _fleetRaw; - - private _creditLinesList = []; - { - private _creditLineData = _y; - _creditLinesList pushBack (createHashMapFromArray [ - ["uid", _creditLineData getOrDefault ["uid", _x]], - ["member", _creditLineData getOrDefault ["name", "Unknown Member"]], - ["amount", _creditLineData getOrDefault ["amount", 0]] - ]); - } forEach _creditLinesRaw; - - createHashMapFromArray [ - ["session", createHashMapFromArray [ - ["actorName", _playerName], - ["actorUid", _playerUid], - ["role", _sessionRole], - ["ceo", _sessionIsCeo] - ]], - ["portalData", createHashMapFromArray [ - ["org", createHashMapFromArray [ - ["name", _name], - ["tag", _id], - ["owner", _ownerName], - ["ownerUid", _ownerUid], - ["isDefault", _isDefaultOrg] - ]], - ["funds", _funds], - ["reputation", _reputation], - ["creditLines", _creditLinesList], - ["members", _membersList], - ["fleet", _fleetList], - ["assets", _assetsList], - ["activity", []] - ]] - ] - }], - ["get", compileFinal { - params [["_key", "", [""]], ["_default", nil, [[], "", 0, false, createHashMap]]]; - - private _org = _self get "org"; - _org getOrDefault [_key, _default]; }] ]; diff --git a/arma/client/addons/org/functions/fnc_initUIBridge.sqf b/arma/client/addons/org/functions/fnc_initUIBridge.sqf index cfc5875..a5593df 100644 --- a/arma/client/addons/org/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/org/functions/fnc_initUIBridge.sqf @@ -50,20 +50,42 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [ _self call ["setActiveBrowserControl", [_control]]; _control }], + ["hasOpenScreen", compileFinal { + private _screen = _self call ["getScreen", []]; + private _control = _self call ["getActiveBrowserControl", []]; + + !(isNull _control) && { _screen call ["isReady", []] } + }], + ["requestHydrate", compileFinal { + params [["_bridgeEvent", "org::sync", [""]]]; + + if !(_self call ["hasOpenScreen", []]) exitWith { false }; + + private _event = _bridgeEvent; + if !(_event in ["org::login::success", "org::create::success", "org::sync"]) then { + _event = "org::sync"; + }; + + [SRPC(org,requestHydrateOrg), [getPlayerUID player, _event]] call CFUNC(serverEvent); + true + }], + ["handleHydrateResponse", compileFinal { + params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "org::sync", [""]]]; + + if !(_self call ["hasOpenScreen", []]) exitWith { false }; + + private _event = _bridgeEvent; + if !(_event in ["org::login::success", "org::create::success", "org::sync"]) then { + _event = "org::sync"; + }; + + _self call ["sendEvent", [_event, _payload, _self call ["getActiveBrowserControl", []]]] + }], ["handleLoginRequest", compileFinal { params [["_control", controlNull, [controlNull]]]; - private _orgData = GVAR(OrgClass) get "org"; - private _orgId = _orgData getOrDefault ["id", ""]; - private _orgName = _orgData getOrDefault ["name", ""]; - - if (_orgId isEqualTo "" && { _orgName isEqualTo "" }) exitWith { - _self call ["sendEvent", ["org::login::failure", createHashMapFromArray [ - ["message", "No organization data is available for this player."] - ], _control]]; - }; - - _self call ["sendEvent", ["org::login::success", GVAR(OrgClass) call ["buildPortalPayload", []], _control]]; + _self call ["setActiveBrowserControl", [_control]]; + _self call ["requestHydrate", ["org::login::success"]]; }], ["handleCreateRequest", compileFinal { params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]]; @@ -91,11 +113,11 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ], _control]]; }; - private _orgData = _payload getOrDefault ["org", createHashMap]; - GVAR(OrgClass) call ["sync", [_orgData, true]]; + if !(isNull _control) then { + _self call ["setActiveBrowserControl", [_control]]; + }; - if (isNull _control) exitWith {}; - _self call ["sendEvent", ["org::create::success", GVAR(OrgClass) call ["buildPortalPayload", []], _control]]; + _self call ["requestHydrate", ["org::create::success"]]; }], ["handleDisbandResponse", compileFinal { params [["_payload", createHashMap, [createHashMap]]]; @@ -155,10 +177,7 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent); }], ["refreshPortal", compileFinal { - private _control = _self call ["getActiveBrowserControl", []]; - if (isNull _control) exitWith { false }; - - _self call ["sendEvent", ["org::sync", GVAR(OrgClass) call ["buildPortalPayload", []], _control]] + _self call ["requestHydrate", ["org::sync"]] }] ]; diff --git a/arma/client/addons/store/XEH_PREP.hpp b/arma/client/addons/store/XEH_PREP.hpp index 868066a..339e665 100644 --- a/arma/client/addons/store/XEH_PREP.hpp +++ b/arma/client/addons/store/XEH_PREP.hpp @@ -1,5 +1,3 @@ -PREP(buildUIPayload); PREP(handleUIEvents); -PREP(initClass); PREP(initUIBridge); PREP(openUI); diff --git a/arma/client/addons/store/XEH_postInitClient.sqf b/arma/client/addons/store/XEH_postInitClient.sqf index ac1eb81..44eb8c8 100644 --- a/arma/client/addons/store/XEH_postInitClient.sqf +++ b/arma/client/addons/store/XEH_postInitClient.sqf @@ -1,6 +1,5 @@ #include "script_component.hpp" -if (isNil QGVAR(StoreClass)) then { call FUNC(initClass); }; if (isNil QGVAR(StoreUIBridge)) then { call FUNC(initUIBridge); }; [QGVAR(responseCategory), { @@ -9,6 +8,12 @@ if (isNil QGVAR(StoreUIBridge)) then { call FUNC(initUIBridge); }; GVAR(StoreUIBridge) call ["handleCategoryResponse", [_payload]]; }] call CFUNC(addEventHandler); +[QGVAR(responseHydrateStore), { + params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "store::hydrate", [""]]]; + + GVAR(StoreUIBridge) call ["handleHydrateResponse", [_payload, _bridgeEvent]]; +}] call CFUNC(addEventHandler); + [QGVAR(responseCheckout), { params [["_payload", createHashMap, [createHashMap]]]; diff --git a/arma/client/addons/store/functions/fnc_buildUIPayload.sqf b/arma/client/addons/store/functions/fnc_buildUIPayload.sqf deleted file mode 100644 index 3b748ce..0000000 --- a/arma/client/addons/store/functions/fnc_buildUIPayload.sqf +++ /dev/null @@ -1,125 +0,0 @@ -#include "..\script_component.hpp" - -/* - * File: fnc_buildUIPayload.sqf - * Author: IDSolutions - * Date: 2026-03-13 - * Public: No - * - * Description: - * Builds the browser hydrate payload for the store UI from current client state. - * - * Arguments: - * None - * - * Return Value: - * Store UI payload [HASHMAP] - */ - -private _storeState = createHashMap; -private _budget = 50000; -private _creditLine = 0; -private _cashBalance = 0; -private _bankBalance = 0; -private _orgFunds = 0; -private _orgId = ""; -private _orgName = ""; -private _orgOwnerUid = ""; -private _orgCreditLines = createHashMap; -private _playerUid = getPlayerUID player; -private _playerVar = toLowerANSI (vehicleVarName player); -private _isOrgLeader = false; -private _isDefaultOrg = false; -private _isDefaultOrgCeo = false; - -if !(isNil QGVAR(StoreClass)) then { - _storeState = GVAR(StoreClass) call ["getStoreState", []]; - _budget = _storeState getOrDefault ["budget", _budget]; -}; - -if !(isNil QEGVAR(bank,BankClass)) then { - _cashBalance = EGVAR(bank,BankClass) call ["get", ["cash", 0]]; - _bankBalance = EGVAR(bank,BankClass) call ["get", ["bank", 0]]; -}; - -if !(isNil QEGVAR(org,OrgClass)) then { - _orgId = EGVAR(org,OrgClass) call ["get", ["id", ""]]; - _orgName = EGVAR(org,OrgClass) call ["get", ["name", ""]]; - _orgOwnerUid = EGVAR(org,OrgClass) call ["get", ["owner", ""]]; - _orgFunds = EGVAR(org,OrgClass) call ["get", ["funds", 0]]; - _orgCreditLines = EGVAR(org,OrgClass) call ["get", ["credit_lines", createHashMap]]; - _isDefaultOrg = (_orgId isEqualTo "default") || { toLowerANSI _orgOwnerUid isEqualTo "server" }; - _isOrgLeader = _orgOwnerUid isEqualTo _playerUid; - _isDefaultOrgCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" }; -}; - -if (_orgCreditLines isEqualType createHashMap) then { - private _playerCreditLine = _orgCreditLines getOrDefault [_playerUid, createHashMap]; - if (_playerCreditLine isEqualType createHashMap) then { - _creditLine = _playerCreditLine getOrDefault ["amount", 0]; - }; -}; - -private _canUseOrgFunds = _isOrgLeader || _isDefaultOrgCeo; -private _orgFundsEnabled = _canUseOrgFunds && { _orgFunds > 0 }; -private _paymentSources = [ - createHashMapFromArray [ - ["id", "cash"], - ["label", "Cash"], - ["balance", _cashBalance], - ["enabled", _cashBalance > 0], - ["detail", "Use on-hand cash carried by the player."] - ], - createHashMapFromArray [ - ["id", "bank"], - ["label", "Bank"], - ["balance", _bankBalance], - ["enabled", _bankBalance > 0], - ["detail", "Charge the player bank account."] - ], - createHashMapFromArray [ - ["id", "org_funds"], - ["label", "Org Funds"], - ["balance", _orgFunds], - ["enabled", _orgFundsEnabled], - ["detail", [ - "Only organization leaders or the default-org CEO can use treasury funds.", - [ - "Charge organization treasury funds.", - "No organization funds are currently available." - ] select _orgFundsEnabled - ] select _canUseOrgFunds] - ], - createHashMapFromArray [ - ["id", "credit_line"], - ["label", "Credit Line"], - ["balance", _creditLine], - ["enabled", _creditLine > 0], - ["detail", [ - "No approved credit line is assigned to this member.", - "Use the approved procurement credit line." - ] select (_creditLine > 0)] - ] -]; - -createHashMapFromArray [ - ["session", createHashMapFromArray [ - ["actorName", name player], - ["actorUid", _playerUid], - ["approval", "Field Access"], - ["orgId", _orgId], - ["orgName", _orgName], - ["orgLeader", _isOrgLeader], - ["defaultOrgCeo", _isDefaultOrgCeo], - ["canUseOrgFunds", _canUseOrgFunds] - ]], - ["storeConfig", createHashMapFromArray [ - ["budget", _budget], - ["creditLine", _creditLine], - ["availability", _storeState getOrDefault ["availability", "In-Stock"]], - ["moduleState", _storeState getOrDefault ["moduleState", "Preview"]], - ["paymentSources", _paymentSources], - ["defaultPaymentSource", "cash"] - ]], - ["cartItems", []] -] diff --git a/arma/client/addons/store/functions/fnc_initClass.sqf b/arma/client/addons/store/functions/fnc_initClass.sqf deleted file mode 100644 index f88d2ff..0000000 --- a/arma/client/addons/store/functions/fnc_initClass.sqf +++ /dev/null @@ -1,42 +0,0 @@ -#include "..\script_component.hpp" - -/* - * File: fnc_initClass.sqf - * Author: IDSolutions - * Date: 2026-01-28 - * Last Update: 2026-03-12 - * Public: Yes - * - * Description: - * Initializes the store class for managing store data. - * - * Arguments: - * None - * - * Return Value: - * Store class object [HASHMAP OBJECT] - * - * Example: - * call forge_client_store_fnc_initClass - */ - -#pragma hemtt ignore_variables ["_self"] -GVAR(StoreBaseClass) = compileFinal createHashMapFromArray [ - ["#type", "StoreBaseClass"], - ["#create", compileFinal { - _self set ["uid", getPlayerUID player]; - _self set ["store", createHashMapFromArray [ - ["budget", 50000], - ["availability", "In-Stock"], - ["moduleState", "Preview"] - ]]; - _self set ["isLoaded", false]; - _self set ["lastSave", time]; - }], - ["getStoreState", compileFinal { - _self getOrDefault ["store", createHashMap] - }] -]; - -GVAR(StoreClass) = createHashMapObject [GVAR(StoreBaseClass)]; -GVAR(StoreClass) diff --git a/arma/client/addons/store/functions/fnc_initUIBridge.sqf b/arma/client/addons/store/functions/fnc_initUIBridge.sqf index 2e707d0..e4a1b3c 100644 --- a/arma/client/addons/store/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/store/functions/fnc_initUIBridge.sqf @@ -4,7 +4,7 @@ * File: fnc_initUIBridge.sqf * Author: IDSolutions * Date: 2026-03-10 - * Last Update: 2026-03-12 + * Last Update: 2026-03-25 * Public: No * * Description: @@ -47,8 +47,12 @@ GVAR(StoreUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["handleReady", compileFinal { params [["_control", controlNull, [controlNull]]]; - private _payload = call FUNC(buildUIPayload); - _self call ["sendBridgeEvent", ["store::hydrate", _payload, _control]]; + private _uid = getPlayerUID player; + if (_uid isEqualTo "") exitWith { + _self call ["sendBridgeEvent", ["store::hydrate", createHashMap, _control]]; + }; + + [SRPC(store,requestHydrateStore), [_uid, "store::hydrate"]] call CFUNC(serverEvent); }], ["handleCategoryRequest", compileFinal { params [["_data", createHashMap, [createHashMap]]]; @@ -79,8 +83,19 @@ GVAR(StoreUIBridgeBaseClass) = compileFinal createHashMapFromArray [ _self call ["sendBridgeEvent", [_bridgeEvent, _payload]]; }], ["refreshStoreConfig", compileFinal { - private _payload = call FUNC(buildUIPayload); - _self call ["sendBridgeEvent", ["store::config::hydrate", _payload]]; + private _uid = getPlayerUID player; + if (_uid isEqualTo "") exitWith { false }; + + [SRPC(store,requestHydrateStore), [_uid, "store::config::hydrate"]] call CFUNC(serverEvent); + true + }], + ["handleHydrateResponse", compileFinal { + params [["_payload", createHashMap, [createHashMap]], ["_bridgeEvent", "store::hydrate", [""]]]; + + private _event = _bridgeEvent; + if !(_event in ["store::hydrate", "store::config::hydrate"]) then { _event = "store::hydrate"; }; + + _self call ["sendBridgeEvent", [_event, _payload]] }], ["handleCheckoutRequest", compileFinal { params [["_data", createHashMap, [createHashMap]]]; diff --git a/arma/server/addons/main/XEH_preInit.sqf b/arma/server/addons/main/XEH_preInit.sqf index dcc00c2..5e34c8a 100644 --- a/arma/server/addons/main/XEH_preInit.sqf +++ b/arma/server/addons/main/XEH_preInit.sqf @@ -4,6 +4,8 @@ PREP_RECOMPILE_START; #include "XEH_PREP.hpp" PREP_RECOMPILE_END; +GVAR(PlayerBootstrapRegistry) = createHashMap; + ["forge_icom_event", { params [["_event", "", [""]], ["_data", createHashMap, [createHashMap]]]; @@ -54,3 +56,11 @@ addMissionEventHandler ["ExtensionCallback", { }]); }; }]; + +addMissionEventHandler ["PlayerConnected", { + params ["_id", "_uid", "_name", "_jip", "_owner", "_idStr"]; +}]; + +addMissionEventHandler ["PlayerDisconnected", { + params ["_id", "_uid", "_name", "_jip", "_owner", "_idStr"]; +}]; diff --git a/arma/server/addons/org/XEH_preInit.sqf b/arma/server/addons/org/XEH_preInit.sqf index 41fab57..807e24b 100644 --- a/arma/server/addons/org/XEH_preInit.sqf +++ b/arma/server/addons/org/XEH_preInit.sqf @@ -14,6 +14,24 @@ PREP_RECOMPILE_END; GVAR(OrgStore) call ["init", [_uid]]; }] call CFUNC(addEventHandler); +[QGVAR(requestHydrateOrg), { + params [["_uid", "", [""]], ["_bridgeEvent", "org::sync", [""]]]; + + if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" }; + + if !(_bridgeEvent in ["org::login::success", "org::create::success", "org::sync"]) then { + _bridgeEvent = "org::sync"; + }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (_player isEqualTo objNull) exitWith {}; + + private _payload = GVAR(OrgStore) call ["buildPortalPayload", [_uid]]; + if (_payload isEqualTo createHashMap) exitWith {}; + + [CRPC(org,responseHydrateOrg), [_payload, _bridgeEvent], _player] call CFUNC(targetEvent); +}] call CFUNC(addEventHandler); + [QGVAR(requestCreateOrg), { params [["_uid", "", [""]], ["_orgName", "", [""]]]; diff --git a/arma/server/addons/org/functions/fnc_initOrgStore.sqf b/arma/server/addons/org/functions/fnc_initOrgStore.sqf index 7e73c65..cd01078 100644 --- a/arma/server/addons/org/functions/fnc_initOrgStore.sqf +++ b/arma/server/addons/org/functions/fnc_initOrgStore.sqf @@ -167,6 +167,118 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [ ["assignCreditLine", compileFinal { GVAR(OrgTreasuryService) call ["assignCreditLine", _this] }], + ["buildPortalPayload", compileFinal { + params [["_uid", "", [""]]]; + + if (_uid isEqualTo "") exitWith { createHashMap }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { createHashMap }; + + private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap]; + private _orgID = _actor getOrDefault ["organization", "default"]; + if (_orgID isEqualTo "") then { _orgID = "default"; }; + + private _org = _self call ["loadById", [_orgID]]; + if (_org isEqualTo createHashMap) then { + _org = _self call ["init", [_uid]]; + }; + if (_org isEqualTo createHashMap) exitWith { createHashMap }; + + private _name = _org getOrDefault ["name", ""]; + private _id = _org getOrDefault ["id", _orgID]; + private _ownerUid = _org getOrDefault ["owner", ""]; + private _funds = _org getOrDefault ["funds", 0]; + private _reputation = _org getOrDefault ["reputation", 0]; + private _creditLinesRaw = _org getOrDefault ["credit_lines", createHashMap]; + private _assetsRaw = _org getOrDefault ["assets", createHashMap]; + private _fleetRaw = _org getOrDefault ["fleet", createHashMap]; + private _membersRaw = _org getOrDefault ["members", createHashMap]; + private _isDefaultOrg = (_org getOrDefault ["default", false]) + || { toLower _id isEqualTo "default" } + || { toLower _ownerUid isEqualTo "server" }; + + private _playerName = name _player; + private _playerVar = vehicleVarName _player; + private _sessionRole = "Member"; + private _sessionIsCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" }; + private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server"); + + private _membersList = []; + { + private _memberData = _y; + private _memberName = _memberData getOrDefault ["name", "Unknown"]; + private _memberUid = _memberData getOrDefault ["uid", ""]; + + if (_memberUid isEqualTo _ownerUid && { _ownerName isEqualTo "" }) then { _ownerName = _memberName; }; + if (_memberUid isEqualTo _uid) then { _sessionRole = "Member"; }; + + _membersList pushBack (createHashMapFromArray [ + ["uid", _memberUid], + ["name", _memberName] + ]); + } forEach _membersRaw; + + if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _uid }) then { _ownerName = _playerName; }; + if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; }; + if (_ownerUid isEqualTo _uid) then { _sessionRole = "Leader"; }; + + private _assetsList = []; + { + private _assetData = _y; + _assetsList pushBack (createHashMapFromArray [ + ["name", _assetData getOrDefault ["name", "Unknown Asset"]], + ["type", _assetData getOrDefault ["type", "items"]], + ["quantity", str (_assetData getOrDefault ["quantity", 0])] + ]); + } forEach _assetsRaw; + + private _fleetList = []; + { + private _vehicleData = _y; + _fleetList pushBack (createHashMapFromArray [ + ["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]], + ["type", _vehicleData getOrDefault ["type", "other"]], + ["status", _vehicleData getOrDefault ["status", "Unknown"]], + ["damage", _vehicleData getOrDefault ["damage", "0%"]] + ]); + } forEach _fleetRaw; + + private _creditLinesList = []; + { + private _creditLineData = _y; + _creditLinesList pushBack (createHashMapFromArray [ + ["uid", _creditLineData getOrDefault ["uid", _x]], + ["member", _creditLineData getOrDefault ["name", "Unknown Member"]], + ["amount", _creditLineData getOrDefault ["amount", 0]] + ]); + } forEach _creditLinesRaw; + + createHashMapFromArray [ + ["session", createHashMapFromArray [ + ["actorName", _playerName], + ["actorUid", _uid], + ["role", _sessionRole], + ["ceo", _sessionIsCeo] + ]], + ["portalData", createHashMapFromArray [ + ["org", createHashMapFromArray [ + ["name", _name], + ["tag", _id], + ["owner", _ownerName], + ["ownerUid", _ownerUid], + ["isDefault", _isDefaultOrg] + ]], + ["funds", _funds], + ["reputation", _reputation], + ["creditLines", _creditLinesList], + ["members", _membersList], + ["fleet", _fleetList], + ["assets", _assetsList], + ["activity", []] + ]] + ] + }], ["buildChargeResult", compileFinal { GVAR(OrgTreasuryService) call ["buildChargeResult", _this] }], diff --git a/arma/server/addons/store/XEH_preInit.sqf b/arma/server/addons/store/XEH_preInit.sqf index 54bfe32..ed11957 100644 --- a/arma/server/addons/store/XEH_preInit.sqf +++ b/arma/server/addons/store/XEH_preInit.sqf @@ -24,6 +24,26 @@ PREP_RECOMPILE_END; [CRPC(store,responseCategory), [_result], _player] call CFUNC(targetEvent); }] call CFUNC(addEventHandler); +[QGVAR(requestHydrateStore), { + params [["_uid", "", [""]], ["_bridgeEvent", "store::hydrate", [""]]]; + + if (_uid isEqualTo "") exitWith { + diag_log "[FORGE:Server:Store] Invalid hydrate request payload." + }; + + if !(_bridgeEvent in ["store::hydrate", "store::config::hydrate"]) then { + _bridgeEvent = "store::hydrate"; + }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (_player isEqualTo objNull) exitWith {}; + + private _payload = GVAR(StoreStore) call ["buildHydratePayload", [_uid]]; + if (_payload isEqualTo createHashMap) exitWith {}; + + [CRPC(store,responseHydrateStore), [_payload, _bridgeEvent], _player] call CFUNC(targetEvent); +}] call CFUNC(addEventHandler); + [QGVAR(requestCheckout), { params [["_uid", "", [""]], ["_payloadJson", "", [""]]]; diff --git a/arma/server/addons/store/functions/fnc_initStoreStore.sqf b/arma/server/addons/store/functions/fnc_initStoreStore.sqf index 6e99752..7526eb6 100644 --- a/arma/server/addons/store/functions/fnc_initStoreStore.sqf +++ b/arma/server/addons/store/functions/fnc_initStoreStore.sqf @@ -19,6 +19,127 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [ ["#create", compileFinal { ["INFO", "Store checkout service initialized!"] call EFUNC(common,log); }], + ["buildHydratePayload", compileFinal { + params [["_uid", "", [""]]]; + + if (_uid isEqualTo "") exitWith { createHashMap }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { createHashMap }; + + private _budget = 50000; + private _creditLine = 0; + private _cashBalance = 0; + private _bankBalance = 0; + private _orgFunds = 0; + private _orgName = ""; + private _orgOwnerUid = ""; + private _orgCreditLines = createHashMap; + private _playerVar = toLowerANSI (vehicleVarName _player); + private _isOrgLeader = false; + private _isDefaultOrg = false; + private _isDefaultOrgCeo = false; + + private _bankAccount = EGVAR(bank,Registry) getOrDefault [_uid, createHashMap]; + if (_bankAccount isEqualTo createHashMap) then { + _bankAccount = EGVAR(bank,BankStore) call ["init", [_uid]]; + }; + if (_bankAccount isNotEqualTo createHashMap) then { + _cashBalance = _bankAccount getOrDefault ["cash", 0]; + _bankBalance = _bankAccount getOrDefault ["bank", 0]; + }; + + private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap]; + private _orgId = _actor getOrDefault ["organization", "default"]; + if (_orgId isEqualTo "") then { _orgId = "default"; }; + + private _org = EGVAR(org,OrgStore) call ["loadById", [_orgId]]; + if (_org isEqualTo createHashMap) then { + _org = EGVAR(org,OrgStore) call ["loadById", ["default"]]; + _orgId = _org getOrDefault ["id", "default"]; + }; + + if (_org isNotEqualTo createHashMap) then { + _orgName = _org getOrDefault ["name", ""]; + _orgOwnerUid = _org getOrDefault ["owner", ""]; + _orgFunds = _org getOrDefault ["funds", 0]; + _orgCreditLines = _org getOrDefault ["credit_lines", createHashMap]; + _isDefaultOrg = (_orgId isEqualTo "default") || { toLowerANSI _orgOwnerUid isEqualTo "server" }; + _isOrgLeader = _orgOwnerUid isEqualTo _uid; + _isDefaultOrgCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" }; + }; + + if (_orgCreditLines isEqualType createHashMap) then { + private _playerCreditLine = _orgCreditLines getOrDefault [_uid, createHashMap]; + if (_playerCreditLine isEqualType createHashMap) then { + _creditLine = _playerCreditLine getOrDefault ["amount", 0]; + }; + }; + + private _canUseOrgFunds = _isOrgLeader || _isDefaultOrgCeo; + private _orgFundsEnabled = _canUseOrgFunds && { _orgFunds > 0 }; + private _paymentSources = [ + createHashMapFromArray [ + ["id", "cash"], + ["label", "Cash"], + ["balance", _cashBalance], + ["enabled", _cashBalance > 0], + ["detail", "Use on-hand cash carried by the player."] + ], + createHashMapFromArray [ + ["id", "bank"], + ["label", "Bank"], + ["balance", _bankBalance], + ["enabled", _bankBalance > 0], + ["detail", "Charge the player bank account."] + ], + createHashMapFromArray [ + ["id", "org_funds"], + ["label", "Org Funds"], + ["balance", _orgFunds], + ["enabled", _orgFundsEnabled], + ["detail", [ + "Only organization leaders or the default-org CEO can use treasury funds.", + [ + "Charge organization treasury funds.", + "No organization funds are currently available." + ] select _orgFundsEnabled + ] select _canUseOrgFunds] + ], + createHashMapFromArray [ + ["id", "credit_line"], + ["label", "Credit Line"], + ["balance", _creditLine], + ["enabled", _creditLine > 0], + ["detail", [ + "No approved credit line is assigned to this member.", + "Use the approved procurement credit line." + ] select (_creditLine > 0)] + ] + ]; + + createHashMapFromArray [ + ["session", createHashMapFromArray [ + ["actorName", name _player], + ["actorUid", _uid], + ["approval", "Field Access"], + ["orgId", _orgId], + ["orgName", _orgName], + ["orgLeader", _isOrgLeader], + ["defaultOrgCeo", _isDefaultOrgCeo], + ["canUseOrgFunds", _canUseOrgFunds] + ]], + ["storeConfig", createHashMapFromArray [ + ["budget", _budget], + ["creditLine", _creditLine], + ["availability", "In-Stock"], + ["moduleState", "Preview"], + ["paymentSources", _paymentSources], + ["defaultPaymentSource", "cash"] + ]], + ["cartItems", []] + ] + }], ["buildResult", compileFinal { params [["_message", "Checkout failed.", [""]], ["_paymentMethod", "cash", [""]]];