diff --git a/arma/client/addons/bank/XEH_PREP.hpp b/arma/client/addons/bank/XEH_PREP.hpp index f1a55dc..7d71bae 100644 --- a/arma/client/addons/bank/XEH_PREP.hpp +++ b/arma/client/addons/bank/XEH_PREP.hpp @@ -1,5 +1,4 @@ PREP(handleUIEvents); PREP(initClass); -PREP(initSessionService); PREP(initUIBridge); PREP(openUI); diff --git a/arma/client/addons/bank/XEH_postInitClient.sqf b/arma/client/addons/bank/XEH_postInitClient.sqf index a4cf8d6..b779e98 100644 --- a/arma/client/addons/bank/XEH_postInitClient.sqf +++ b/arma/client/addons/bank/XEH_postInitClient.sqf @@ -1,7 +1,6 @@ #include "script_component.hpp" if (isNil QGVAR(BankClass)) then { call FUNC(initClass); }; -if (isNil QGVAR(BankSessionService)) then { call FUNC(initSessionService); }; if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; [QGVAR(initBank), { @@ -26,8 +25,27 @@ if (isNil QGVAR(BankUIBridge)) then { call FUNC(initUIBridge); }; }; }] call CFUNC(addEventHandler); +[QGVAR(responseHydrateBank), { + params [["_data", createHashMap, [createHashMap]]]; + + if !(isNil QGVAR(BankUIBridge)) then { + GVAR(BankUIBridge) call ["handleHydrateResponse", [_data, "bank::hydrate"]]; + }; +}] call CFUNC(addEventHandler); + +[QGVAR(responseBankNotice), { + params [ + ["_type", "error", [""]], + ["_message", "", [""]] + ]; + + if !(isNil QGVAR(BankUIBridge)) then { + GVAR(BankUIBridge) call ["handleNoticeResponse", [_type, _message]]; + }; +}] call CFUNC(addEventHandler); + [{ - EGVAR(org,OrgClass) get "isLoaded"; + getPlayerUID player isNotEqualTo ""; }, { [QGVAR(initBank), []] call CFUNC(localEvent); }] call CFUNC(waitUntilAndExecute); diff --git a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf index b2fcd53..68237d0 100644 --- a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf @@ -68,6 +68,11 @@ switch (_event) do { GVAR(BankUIBridge) call ["handleDepositEarningsRequest", [_data]]; }; }; + case "bank::pin::request": { + if !(isNil QGVAR(BankUIBridge)) then { + GVAR(BankUIBridge) call ["handleSubmitPinRequest", [_data]]; + }; + }; default { hint format ["Unhandled bank UI event: %1", _event]; }; diff --git a/arma/client/addons/bank/functions/fnc_initClass.sqf b/arma/client/addons/bank/functions/fnc_initClass.sqf index ede4cc8..2a46590 100644 --- a/arma/client/addons/bank/functions/fnc_initClass.sqf +++ b/arma/client/addons/bank/functions/fnc_initClass.sqf @@ -18,7 +18,6 @@ GVAR(BankBaseClass) = compileFinal createHashMapFromArray [ ["bank", 0], ["cash", 0], ["earnings", 0], - ["pin", 1234], ["transactions", []] ]]; _self set ["isLoaded", false]; diff --git a/arma/client/addons/bank/functions/fnc_initSessionService.sqf b/arma/client/addons/bank/functions/fnc_initSessionService.sqf deleted file mode 100644 index 155652b..0000000 --- a/arma/client/addons/bank/functions/fnc_initSessionService.sqf +++ /dev/null @@ -1,80 +0,0 @@ -#include "..\script_component.hpp" - -/* - * File: fnc_initSessionService.sqf - * Author: IDSolutions - * Public: No - * - * Description: - * Initializes the bank session service that shapes the browser payload. - */ - -#pragma hemtt ignore_variables ["_self"] -GVAR(BankSessionServiceBaseClass) = compileFinal createHashMapFromArray [ - ["#type", "BankSessionServiceBaseClass"], - ["buildTransferTargets", compileFinal { - private _targets = []; - - { - if (isNull _x || { _x isEqualTo player }) then { - continue; - }; - - private _uid = getPlayerUID _x; - private _name = name _x; - if (_uid isEqualTo "" || { _name isEqualTo "" }) then { - continue; - }; - - _targets pushBack (createHashMapFromArray [ - ["name", _name], - ["uid", _uid] - ]); - } forEach allPlayers; - - private _targetPairs = _targets apply { - [toLowerANSI (_x getOrDefault ["name", ""]), _x] - }; - _targetPairs sort true; - _targetPairs apply { - _x param [1, createHashMap] - } - }], - ["buildPayload", compileFinal { - params [["_mode", "bank", [""]]]; - - private _account = if (isNil QGVAR(BankClass)) then { - createHashMap - } else { - GVAR(BankClass) call ["getAccountState", []] - }; - - private _orgFunds = 0; - private _orgName = ""; - if !(isNil QEGVAR(org,OrgClass)) then { - _orgFunds = EGVAR(org,OrgClass) call ["get", ["funds", 0]]; - _orgName = EGVAR(org,OrgClass) call ["get", ["name", ""]]; - }; - - createHashMapFromArray [ - ["session", createHashMapFromArray [ - ["mode", ["bank", "atm"] select (toLowerANSI _mode isEqualTo "atm")], - ["orgFunds", _orgFunds], - ["orgName", _orgName], - ["playerName", name player], - ["transferTargets", _self call ["buildTransferTargets", []]], - ["uid", getPlayerUID player] - ]], - ["account", createHashMapFromArray [ - ["bank", _account getOrDefault ["bank", 0]], - ["cash", _account getOrDefault ["cash", 0]], - ["earnings", _account getOrDefault ["earnings", 0]], - ["pin", str (_account getOrDefault ["pin", 1234])], - ["transactions", _account getOrDefault ["transactions", []]] - ]] - ] - }] -]; - -GVAR(BankSessionService) = createHashMapObject [GVAR(BankSessionServiceBaseClass)]; -GVAR(BankSessionService) diff --git a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf index 32e1b0b..3b7a0d6 100644 --- a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf @@ -19,9 +19,6 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["#create", compileFinal { _self set ["mode", "bank"]; }], - ["buildPayload", compileFinal { - GVAR(BankSessionService) call ["buildPayload", [_self call ["getMode", []]]] - }], ["getActiveBrowserControl", compileFinal { private _display = uiNamespace getVariable ["RscBank", displayNull]; if (isNull _display) exitWith { @@ -36,14 +33,16 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["getMode", compileFinal { _self getOrDefault ["mode", "bank"] }], + ["hasOpenScreen", compileFinal { + private _screen = _self call ["getScreen", []]; + private _control = _self call ["getActiveBrowserControl", []]; + + !(isNull _control) && { _screen call ["isReady", []] } + }], ["handleDepositEarningsRequest", compileFinal { params [["_data", createHashMap, [createHashMap]]]; private _amount = floor (_data getOrDefault ["amount", 0]); - if (_amount <= 0) exitWith { - _self call ["sendNotice", ["error", "No earnings are available to deposit."]]; - }; - [SRPC(bank,requestDepositEarnings), [getPlayerUID player, _amount]] call CFUNC(serverEvent); true }], @@ -51,22 +50,41 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ params [["_data", createHashMap, [createHashMap]]]; private _amount = floor (_data getOrDefault ["amount", 0]); - if (_amount <= 0) exitWith { - _self call ["sendNotice", ["error", "Enter a valid deposit amount."]]; - }; - [SRPC(bank,requestDeposit), [getPlayerUID player, _amount]] call CFUNC(serverEvent); true }], + ["handleHydrateResponse", compileFinal { + params [["_data", createHashMap, [createHashMap]], ["_event", "bank::hydrate", [""]]]; + + if !(_self call ["hasOpenScreen", []]) exitWith { false }; + + _self call ["sendEvent", [_event, _data, _self call ["getActiveBrowserControl", []]]] + }], + ["handleNoticeResponse", compileFinal { + params [["_type", "error", [""]], ["_message", "", [""]]]; + + _self call ["sendNotice", [_type, _message]] + }], ["handleReady", compileFinal { params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]]; private _screen = _self call ["getScreen", []]; _screen call ["setControl", [_control]]; _screen call ["markReady", [true]]; - _self call ["flushPendingEvents", []]; - _self call ["sendEvent", ["bank::hydrate", _self call ["buildPayload", []], _control]]; + + _self call ["requestHydrate", [true]] + }], + ["handleSubmitPinRequest", compileFinal { + params [["_data", createHashMap, [createHashMap]]]; + + private _pin = _data getOrDefault ["pin", ""]; + if !(_pin isEqualType "") then { + _pin = str _pin; + }; + + [SRPC(bank,requestSubmitPin), [getPlayerUID player, _pin]] call CFUNC(serverEvent); + true }], ["handleTransferRequest", compileFinal { params [["_data", createHashMap, [createHashMap]]]; @@ -75,18 +93,6 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ private _target = _data getOrDefault ["target", ""]; private _from = toLowerANSI (_data getOrDefault ["from", "bank"]); - if (_target isEqualTo "") exitWith { - _self call ["sendNotice", ["error", "Select a transfer recipient."]]; - }; - - if (_target isEqualTo getPlayerUID player) exitWith { - _self call ["sendNotice", ["error", "You cannot transfer funds to yourself."]]; - }; - - if (_amount <= 0) exitWith { - _self call ["sendNotice", ["error", "Enter a valid transfer amount."]]; - }; - [SRPC(bank,requestTransfer), [getPlayerUID player, _target, _from, _amount]] call CFUNC(serverEvent); true }], @@ -94,23 +100,24 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ params [["_data", createHashMap, [createHashMap]]]; private _amount = floor (_data getOrDefault ["amount", 0]); - if (_amount <= 0) exitWith { - _self call ["sendNotice", ["error", "Enter a valid withdrawal amount."]]; - }; - [SRPC(bank,requestWithdraw), [getPlayerUID player, _amount]] call CFUNC(serverEvent); true }], ["refreshSession", compileFinal { - private _control = _self call ["getActiveBrowserControl", []]; - if (isNull _control) exitWith { false }; + _self call ["requestHydrate", [false]] + }], + ["requestHydrate", compileFinal { + params [["_resetAuthorization", false, [false]]]; - _self call ["sendEvent", ["bank::sync", _self call ["buildPayload", []], _control]] + if !(_self call ["hasOpenScreen", []]) exitWith { false }; + + [SRPC(bank,requestHydrateBank), [getPlayerUID player, _self call ["getMode", []], _resetAuthorization]] call CFUNC(serverEvent); + true }], ["sendNotice", compileFinal { params [["_type", "error", [""]], ["_message", "", [""]], ["_control", controlNull, [controlNull]]]; - if (_message isEqualTo "") exitWith { false }; + if (_message isEqualTo "" || { !(_self call ["hasOpenScreen", []]) }) exitWith { false }; _self call ["sendEvent", ["bank::notice", createHashMapFromArray [ ["message", _message], diff --git a/arma/client/addons/bank/ui/_site/bank-ui.js b/arma/client/addons/bank/ui/_site/bank-ui.js index cf05616..f4f99bd 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={mode:"bank",orgFunds:0,orgName:"",playerName:"",transferTargets:[],uid:""},t={bank:0,cash:0,earnings:0,pin:"1234",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=this.getMode(),a=this.getAtmView();this.setMode("atm"===e?"atm":"bank"),this.setPendingAction(""),this.setNotice({text:"",type:""}),this.setEnteredPin(""),this.setCustomAmount(""),this.setAccountVersion(this.getAccountVersion()+1),this.setSessionVersion(this.getSessionVersion()+1),"atm"!==e?this.setAtmView("dashboard"):this.setAtmView("atm"===t?a:"pin")}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",{}),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(){return n.data?.account||{}}function s(n){const e=Math.floor(Number(n||0));return Number.isFinite(e)?e:0}function i(n,a){e.setNotice({type:n,text:a}),t&&clearTimeout(t),t=setTimeout(()=>{e.setNotice({text:"",type:""}),t=null},3200)}function o(t){const o=s(t),r=a();if(o<=0)return i("error","Enter a valid deposit amount."),!1;if(o>Number(r.cash||0))return i("error","Cash on hand cannot cover that deposit."),!1;const c=n.bridge;if(!c||"function"!=typeof c.requestDeposit)return i("error","Deposit bridge is unavailable."),!1;e.startAction("deposit");return!!c.requestDeposit({amount:o})||(e.finishAction(),i("error","Deposit bridge is unavailable."),!1)}function r(t){const o=s(t),r=a();if(o<=0)return i("error","Enter a valid withdrawal amount."),!1;if(o>Number(r.bank||0))return i("error","Bank balance cannot cover that withdrawal."),!1;const c=n.bridge;if(!c||"function"!=typeof c.requestWithdraw)return i("error","Withdraw bridge is unavailable."),!1;e.startAction("withdraw");return!!c.requestWithdraw({amount:o})||(e.finishAction(),i("error","Withdraw bridge is unavailable."),!1)}function c(){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:c,closeBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestClose){if(e.requestClose())return!0}return i("error","Bank bridge is unavailable."),!1},refreshBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestRefresh){if(e.requestRefresh())return!0}return i("error","Bank refresh bridge is unavailable."),!1},requestAtmAmount:function(n,t){const a="deposit"===String(n||"").trim().toLowerCase()?o(t):r(t);return a&&e.setAtmView("menu"),a},requestDeposit:o,requestDepositEarnings:function(t){const o=s(t),r=a();if(o<=0)return i("error","No earnings are available to deposit."),!1;if(o>Number(r.earnings||0))return i("error","Pending earnings cannot cover that deposit request."),!1;const c=n.bridge;return c&&"function"==typeof c.requestDepositEarnings?(e.startAction("depositearnings"),!!c.requestDepositEarnings({amount:o})||(e.finishAction(),i("error","Earnings bridge is unavailable."),!1)):(i("error","Earnings bridge is unavailable."),!1)},requestTransfer:function(t,o){const r=s(o),c=n.data?.session||{},u=a(),l=String(t||"").trim();if(!l)return i("error","Select a transfer recipient."),!1;if(l===String(c.uid||""))return i("error","You cannot transfer funds to yourself."),!1;if(r<=0)return i("error","Enter a valid transfer amount."),!1;if(r>Number(u.bank||0))return i("error","Bank balance cannot cover that transfer."),!1;const m=n.bridge;return m&&"function"==typeof m.requestTransfer?(e.startAction("transfer"),!!m.requestTransfer({amount:r,from:"bank",target:l})||(e.finishAction(),i("error","Transfer bridge is unavailable."),!1)):(i("error","Transfer bridge is unavailable."),!1)},requestWithdraw:r,selectAtmView:function(n){const t=String(n||"").trim();return!!t&&("pin"===t?(e.resetAtm(),!0):(e.setCustomAmount(""),e.setAtmView(t),!0))},showNotice:i,submitCustomAmount:function(n){const t=s(e.getCustomAmount()),a=String(n||"").trim().toLowerCase();if(t<=0)return i("error","Enter a valid transaction amount."),!1;const c="deposit"===a?o(t):r(t);return c&&(e.setCustomAmount(""),e.setAtmView("menu")),c},submitPin:function(){const n=String(e.getEnteredPin()||""),t=String(a().pin||"1234");return 4!==n.length?(i("error","Enter your four-digit access PIN."),!1):n!==t?(c(),i("error","Incorrect PIN."),!1):(c(),e.setAtmView("menu"),!0)}}}(),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",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 diff --git a/arma/client/addons/bank/ui/src/bridge.js b/arma/client/addons/bank/ui/src/bridge.js index 1ceed4e..41f872b 100644 --- a/arma/client/addons/bank/ui/src/bridge.js +++ b/arma/client/addons/bank/ui/src/bridge.js @@ -40,6 +40,9 @@ requestRefresh() { return bridge.send("bank::refresh", {}); }, + requestSubmitPin(payload) { + return bridge.send("bank::pin::request", payload); + }, requestTransfer(payload) { return bridge.send("bank::transfer::request", payload); }, diff --git a/arma/client/addons/bank/ui/src/data.js b/arma/client/addons/bank/ui/src/data.js index 856ca90..c3558b8 100644 --- a/arma/client/addons/bank/ui/src/data.js +++ b/arma/client/addons/bank/ui/src/data.js @@ -2,6 +2,7 @@ const BankApp = (window.BankApp = window.BankApp || {}); const defaultSession = { + atmAuthorized: false, mode: "bank", orgFunds: 0, orgName: "", @@ -14,7 +15,6 @@ bank: 0, cash: 0, earnings: 0, - pin: "1234", transactions: [], }; diff --git a/arma/client/addons/bank/ui/src/registry/events.js b/arma/client/addons/bank/ui/src/registry/events.js index 01facaa..70c5413 100644 --- a/arma/client/addons/bank/ui/src/registry/events.js +++ b/arma/client/addons/bank/ui/src/registry/events.js @@ -4,14 +4,6 @@ let noticeTimer = null; - function getAccount() { - return BankApp.data?.account || {}; - } - - function getSession() { - return BankApp.data?.session || {}; - } - function normalizeAmount(value) { const amount = Math.floor(Number(value || 0)); return Number.isFinite(amount) ? amount : 0; @@ -58,18 +50,6 @@ function requestDeposit(amountValue) { const amount = normalizeAmount(amountValue); - const account = getAccount(); - - if (amount <= 0) { - showNotice("error", "Enter a valid deposit amount."); - return false; - } - - if (amount > Number(account.cash || 0)) { - showNotice("error", "Cash on hand cannot cover that deposit."); - return false; - } - const bridge = BankApp.bridge; if (!bridge || typeof bridge.requestDeposit !== "function") { showNotice("error", "Deposit bridge is unavailable."); @@ -89,18 +69,6 @@ function requestWithdraw(amountValue) { const amount = normalizeAmount(amountValue); - const account = getAccount(); - - if (amount <= 0) { - showNotice("error", "Enter a valid withdrawal amount."); - return false; - } - - if (amount > Number(account.bank || 0)) { - showNotice("error", "Bank balance cannot cover that withdrawal."); - return false; - } - const bridge = BankApp.bridge; if (!bridge || typeof bridge.requestWithdraw !== "function") { showNotice("error", "Withdraw bridge is unavailable."); @@ -120,30 +88,8 @@ function requestTransfer(targetUid, amountValue) { const amount = normalizeAmount(amountValue); - const session = getSession(); - const account = getAccount(); const targetId = String(targetUid || "").trim(); - if (!targetId) { - showNotice("error", "Select a transfer recipient."); - return false; - } - - if (targetId === String(session.uid || "")) { - showNotice("error", "You cannot transfer funds to yourself."); - return false; - } - - if (amount <= 0) { - showNotice("error", "Enter a valid transfer amount."); - return false; - } - - if (amount > Number(account.bank || 0)) { - showNotice("error", "Bank balance cannot cover that transfer."); - return false; - } - const bridge = BankApp.bridge; if (!bridge || typeof bridge.requestTransfer !== "function") { showNotice("error", "Transfer bridge is unavailable."); @@ -167,21 +113,6 @@ function requestDepositEarnings(amountValue) { const amount = normalizeAmount(amountValue); - const account = getAccount(); - - if (amount <= 0) { - showNotice("error", "No earnings are available to deposit."); - return false; - } - - if (amount > Number(account.earnings || 0)) { - showNotice( - "error", - "Pending earnings cannot cover that deposit request.", - ); - return false; - } - const bridge = BankApp.bridge; if (!bridge || typeof bridge.requestDepositEarnings !== "function") { showNotice("error", "Earnings bridge is unavailable."); @@ -224,21 +155,21 @@ function submitPin() { const enteredPin = String(store.getEnteredPin() || ""); - const actualPin = String(getAccount().pin || "1234"); - - if (enteredPin.length !== 4) { - showNotice("error", "Enter your four-digit access PIN."); + const bridge = BankApp.bridge; + if (!bridge || typeof bridge.requestSubmitPin !== "function") { + showNotice("error", "PIN bridge is unavailable."); return false; } - if (enteredPin !== actualPin) { - clearPin(); - showNotice("error", "Incorrect PIN."); + store.startAction("pin"); + const sent = bridge.requestSubmitPin({ pin: enteredPin }); + if (!sent) { + store.finishAction(); + showNotice("error", "PIN bridge is unavailable."); return false; } clearPin(); - store.setAtmView("menu"); return true; } @@ -299,7 +230,6 @@ if (success) { store.setCustomAmount(""); - store.setAtmView("menu"); } return success; @@ -314,10 +244,6 @@ ? requestDeposit(amount) : requestWithdraw(amount); - if (success) { - store.setAtmView("menu"); - } - return success; } diff --git a/arma/client/addons/bank/ui/src/registry/store.js b/arma/client/addons/bank/ui/src/registry/store.js index 56b7233..2acf40b 100644 --- a/arma/client/addons/bank/ui/src/registry/store.js +++ b/arma/client/addons/bank/ui/src/registry/store.js @@ -25,19 +25,35 @@ const mode = String(payload?.session?.mode || "bank") .trim() .toLowerCase(); + const atmAuthorized = Boolean(payload?.session?.atmAuthorized); const currentMode = this.getMode(); const currentAtmView = this.getAtmView(); + const currentPendingAction = this.getPendingAction(); this.setMode(mode === "atm" ? "atm" : "bank"); this.setPendingAction(""); - this.setNotice({ text: "", type: "" }); this.setEnteredPin(""); this.setCustomAmount(""); this.setAccountVersion(this.getAccountVersion() + 1); this.setSessionVersion(this.getSessionVersion() + 1); if (mode === "atm") { - this.setAtmView(currentMode === "atm" ? currentAtmView : "pin"); + if (!atmAuthorized) { + this.setAtmView("pin"); + return; + } + + if ( + currentPendingAction === "deposit" || + currentPendingAction === "withdraw" || + currentAtmView === "pin" || + currentMode !== "atm" + ) { + this.setAtmView("menu"); + return; + } + + this.setAtmView(currentAtmView); return; } diff --git a/arma/server/addons/bank/XEH_PREP.hpp b/arma/server/addons/bank/XEH_PREP.hpp index fae036d..c0f781b 100644 --- a/arma/server/addons/bank/XEH_PREP.hpp +++ b/arma/server/addons/bank/XEH_PREP.hpp @@ -1,2 +1,6 @@ PREP(initBank); -PREP(initBankStore); +PREP(initMessenger); +PREP(initModel); +PREP(initSessionManager); +PREP(initStore); +PREP(initValidator); diff --git a/arma/server/addons/bank/XEH_preInit.sqf b/arma/server/addons/bank/XEH_preInit.sqf index b652346..141a418 100644 --- a/arma/server/addons/bank/XEH_preInit.sqf +++ b/arma/server/addons/bank/XEH_preInit.sqf @@ -13,15 +13,24 @@ PREP_RECOMPILE_END; GVAR(BankStore) call ["init", [_uid]]; }] call CFUNC(addEventHandler); +[QGVAR(requestHydrateBank), { + params [["_uid", "", [""]], ["_mode", "bank", [""]], ["_resetAuthorization", false, [false]]]; + + if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID!" }; + GVAR(BankStore) call ["hydrateSession", [_uid, _mode, _resetAuthorization]]; +}] call CFUNC(addEventHandler); + [QGVAR(requestGetBank), { params [["_uid", "", [""]], ["_field", "", [""]]]; if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID!" }; private _finalData = GVAR(BankStore) call ["get", [GVAR(Registry), _uid, _field]]; - private _player = [_uid] call EFUNC(common,getPlayer); + if (_field isNotEqualTo "") then { + _finalData = createHashMapFromArray [[_field, _finalData]]; + }; - [CRPC(bank,responseSyncBank), [_finalData], _player] call CFUNC(targetEvent); + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalData]]; }] call CFUNC(addEventHandler); [QGVAR(requestSetBank), { @@ -30,9 +39,7 @@ PREP_RECOMPILE_END; if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID or Key!" }; private _hashMap = GVAR(BankStore) call ["set", [GVAR(Registry), "bank:update", _uid, _field, _value, _sync]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - [CRPC(bank,responseSyncBank), [_hashMap], _player] call CFUNC(targetEvent); + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _hashMap]]; }] call CFUNC(addEventHandler); [QGVAR(requestMSetBank), { @@ -42,9 +49,7 @@ PREP_RECOMPILE_END; if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid field pairs!" }; private _hashMap = GVAR(BankStore) call ["mset", [GVAR(Registry), "bank:update", _uid, _fieldValuePairs, _sync]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - [CRPC(bank,responseSyncBank), [_hashMap], _player] call CFUNC(targetEvent); + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _hashMap]]; }] call CFUNC(addEventHandler); [QGVAR(requestSaveBank), { @@ -53,9 +58,7 @@ PREP_RECOMPILE_END; if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID!" }; private _finalData = GVAR(BankStore) call ["save", [GVAR(Registry), "bank:update", _uid]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - [CRPC(bank,responseSyncBank), [_finalData], _player] call CFUNC(targetEvent); + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalData]]; }] call CFUNC(addEventHandler); [QGVAR(requestRemoveBank), { @@ -68,44 +71,47 @@ PREP_RECOMPILE_END; [QGVAR(requestDeposit), { params [["_uid", "", [""]], ["_amount", 0, [0]]]; - if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID or Amount!" }; - GVAR(BankStore) call ["deposit", [_uid, _amount]]; + private _context = GVAR(BankValidator) call ["validateDeposit", [_uid, _amount]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankStore) call ["deposit", [_uid, _amount, _context]]; }] call CFUNC(addEventHandler); [QGVAR(requestPayment), { params [["_uid", "", [""]], ["_amount", 0, [0]]]; - if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID or Amount!" }; - GVAR(BankStore) call ["payment", [_uid, _amount]]; + private _context = GVAR(BankValidator) call ["validatePayment", [_uid, _amount]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankStore) call ["payment", [_uid, _amount, _context]]; +}] call CFUNC(addEventHandler); + +[QGVAR(requestSubmitPin), { + params [["_uid", "", [""]], ["_pin", "", [""]]]; + + private _context = GVAR(BankValidator) call ["validateSubmitPin", [_uid, _pin]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankSessionManager) call ["submitPin", [_uid, _context]]; }] call CFUNC(addEventHandler); [QGVAR(requestTransfer), { params [["_uid", "", [""]], ["_target", "", [""]], ["_from", "", [""]], ["_amount", 0, [0]]]; - if (_uid isEqualTo "" || _target isEqualTo "" || _from isEqualTo "" || _amount isEqualTo 0) exitWith { - diag_log "[FORGE:Server:Bank] Empty/Invalid UID, Target, From Account, or Amount!" - }; - - if (_uid isEqualTo _target) exitWith { - diag_log format ["[FORGE:Server:Bank] SECURITY: Player %1 attempted self-transfer!", _uid]; - - private _player = [_uid] call EFUNC(common,getPlayer); - [CRPC(notifications,recieveNotification), ["error", "Bank", "Cannot transfer to yourself!"], _player] call CFUNC(targetEvent); - }; - - GVAR(BankStore) call ["transfer", [_uid, _target, _from, _amount]]; + private _context = GVAR(BankValidator) call ["validateTransfer", [_uid, _target, _from, _amount]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankStore) call ["transfer", [_uid, _target, _amount, _context]]; }] call CFUNC(addEventHandler); [QGVAR(requestWithdraw), { params [["_uid", "", [""]], ["_amount", 0, [0]]]; - if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID or Amount!" }; - GVAR(BankStore) call ["withdraw", [_uid, _amount]]; + private _context = GVAR(BankValidator) call ["validateWithdraw", [_uid, _amount]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankStore) call ["withdraw", [_uid, _amount, _context]]; }] call CFUNC(addEventHandler); [QGVAR(requestDepositEarnings), { params [["_uid", "", [""]], ["_amount", 0, [0]]]; - if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID or Amount!" }; - GVAR(BankStore) call ["depositEarnings", [_uid, _amount]]; + private _context = GVAR(BankValidator) call ["validateDepositEarnings", [_uid, _amount]]; + if (_context isEqualTo false) exitWith {}; + GVAR(BankStore) call ["depositEarnings", [_uid, _amount, _context]]; }] call CFUNC(addEventHandler); diff --git a/arma/server/addons/bank/functions/fnc_initBankStore.sqf b/arma/server/addons/bank/functions/fnc_initBankStore.sqf deleted file mode 100644 index f130777..0000000 --- a/arma/server/addons/bank/functions/fnc_initBankStore.sqf +++ /dev/null @@ -1,326 +0,0 @@ -#include "..\script_component.hpp" - -/* - * File: fnc_initBankStore.sqf - * Author: IDSolutions - * Date: 2025-12-17 - * Last Update: 2026-02-17 - * Public: Yes - * - * Description: - * Initializes the bank store for managing player bank accounts. - * Provides methods for syncing, saving, and applying bank accounts to the player. - * - * Arguments: - * None - * - * Return Value: - * Bank store object [HASHMAP OBJECT] - * - * Example: - * call forge_server_bank_fnc_initBankStore - */ - -#pragma hemtt ignore_variables ["_self"] -GVAR(BankModel) = compileFinal createHashMapObject [[ - ["#type", "BankModel"], - ["defaults", compileFinal { - private _account = createHashMap; - - _account set ["uid", ""]; - _account set ["name", ""]; - _account set ["bank", 0]; - _account set ["cash", 0]; - _account set ["earnings", 0]; - _account set ["pin", 1234]; - _account set ["transactions", []]; - - _account - }], - ["fromPlayer", compileFinal { - params [["_player", objNull, [objNull]]]; - - if (_player isEqualTo objNull) exitWith { _self call ["defaults", []] }; - - private _account = _self call ["defaults", []]; - - _account set ["uid", getPlayerUID _player]; - _account set ["name", name _player]; - _account set ["bank", 0]; - _account set ["cash", 0]; - _account set ["earnings", 0]; - _account set ["pin", 1234]; - _account set ["transactions", []]; - - _account - }], - ["migrate", compileFinal { - params [["_account", createHashMap, [createHashMap]]]; - - private _defaults = _self call ["defaults", []]; - - { - if !(_x in _account) then { _account set [_x, _y]; }; - } forEach _defaults; - - _account - }], - ["validate", compileFinal { - params [["_account", createHashMap, [createHashMap]]]; - - private _uid = _account getOrDefault ["uid", ""]; - private _name = _account getOrDefault ["name", ""]; - private _bank = _account getOrDefault ["bank", 0]; - private _cash = _account getOrDefault ["cash", 0]; - private _earnings = _account getOrDefault ["earnings", 0]; - private _pin = _account getOrDefault ["pin", 1234]; - - [_uid, _name, _bank, _cash, _earnings, _pin] try { - if (_uid isEqualTo "" || !(_uid isEqualType "")) then { throw "Invalid UID!"; }; - if (_name isEqualTo "" || !(_name isEqualType "")) then { throw "Invalid Name!"; }; - if (_bank < 0 || !(_bank isEqualType 0)) then { throw "Invalid Bank!"; }; - if (_cash < 0 || !(_cash isEqualType 0)) then { throw "Invalid Cash!"; }; - if (_earnings < 0 || !(_earnings isEqualType 0)) then { throw "Invalid Earnings!"; }; - if (_pin < 1000 || _pin > 9999 || !(_pin isEqualType 0)) then { throw "Invalid Pin!"; }; - } catch { - ["ERROR", format ["Failed to validate account %1!", _exception]] call EFUNC(common,log); - false - }; - - true - }] -]]; - -GVAR(BankBaseStore) = compileFinal createHashMapFromArray [ - ["#base", EGVAR(common,BaseStore)], - ["#type", "BankBaseStore"], - ["#create", compileFinal { - GVAR(IndexRegistry) = createHashMap; - GVAR(Registry) = createHashMap; - ["INFO", "Bank Store Initialized!"] call EFUNC(common,log); - }], - ["init", compileFinal { - params [["_uid", "", [""]]]; - - private _player = [_uid] call EFUNC(common,getPlayer); - private _cached = GVAR(Registry) getOrDefault [_uid, nil]; - if !(isNil { _cached }) exitWith { [CRPC(bank,responseInitBank), [_cached], _player] call CFUNC(targetEvent); _cached }; - - ["bank:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"]; - if !(_isSuccess) exitWith { - ["ERROR", format ["Failed to check if bank account %1 exists! Using fallback account.", _uid]] call EFUNC(common,log); - - private _fallbackAccount = GVAR(BankModel) call ["fromPlayer", [_player]]; - _fallbackAccount set ["uid", _uid]; - - private _regEntry = createHashMapFromArray [["uid", _uid], ["name", (name _player)]]; - GVAR(IndexRegistry) set [_uid, _regEntry]; - - GVAR(Registry) set [_uid, _fallbackAccount]; - [CRPC(bank,responseInitBank), [_fallbackAccount], _player] call CFUNC(targetEvent); - - _fallbackAccount - }; - - private _finalAccount = createHashMap; - - if (_result == "true") then { - _finalAccount = _self call ["fetch", ["bank:get", _uid]]; - ["INFO", format ["Found bank account for %1", _uid]] call EFUNC(common,log); - } else { - _finalAccount = GVAR(BankModel) call ["fromPlayer", [_player]]; - _finalAccount set ["uid", _uid]; - - private _json = _self call ["toJSON", [_finalAccount]]; - ["bank:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"]; - if !(_isSuccess) exitWith { - ["ERROR", format ["Failed to create bank account %1! Using fallback account.", _uid]] call EFUNC(common,log); - - private _regEntry = createHashMapFromArray [["uid", _uid], ["name", (name _player)]]; - GVAR(IndexRegistry) set [_uid, _regEntry]; - - GVAR(Registry) set [_uid, _finalAccount]; - [CRPC(bank,responseInitBank), [_finalAccount], _player] call CFUNC(targetEvent); - - _finalAccount - }; - - ["INFO", format ["Created new bank account for %1", _uid]] call EFUNC(common,log); - }; - - - private _regEntry = createHashMapFromArray [["uid", _uid], ["name", (name _player)]]; - GVAR(IndexRegistry) set [_uid, _regEntry]; - - // _finalAccount = GVAR(BankModel) call ["migrate", [_finalAccount]]; - GVAR(Registry) set [_uid, _finalAccount]; - [CRPC(bank,responseInitBank), [_finalAccount], _player] call CFUNC(targetEvent); - - _finalAccount - }], - ["deposit", compileFinal { - params [["_uid", "", [""]], ["_amount", 0, [0]]]; - - ["INFO", format ["Deposit %1, for %2", _amount, _uid]] call EFUNC(common,log); - - private _account = GVAR(Registry) getOrDefault [_uid, nil]; - if (isNil "_account") exitWith { ["ERROR", "Empty/Invalid Account!"] call EFUNC(common,log); }; - - private _bank = _account getOrDefault ["bank", 0]; - private _cash = _account getOrDefault ["cash", 0]; - if (_cash < _amount) exitWith { ["WARNING", "Insufficient Funds!"] call EFUNC(common,log); }; - - private _finalAccount = createHashMapFromArray [["bank", (_bank + _amount)], ["cash", (_cash - _amount)]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - GVAR(Registry) set [_uid, _finalAccount]; - - [CRPC(bank,responseSyncBank), [_finalAccount], _player] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Deposited $%1", _amount]], _player] call CFUNC(targetEvent); - }], - ["payment", compileFinal { - params [["_uid", "", [""]], ["_amount", 0, [0]]]; - - ["INFO", format ["Payment %1, for %2", _amount, _uid]] call EFUNC(common,log); - - private _account = GVAR(Registry) getOrDefault [_uid, nil]; - if (isNil "_account") exitWith { ["ERROR", "Empty/Invalid Account!"] call EFUNC(common,log); }; - - private _bank = _account getOrDefault ["bank", 0]; - private _finalAccount = createHashMapFromArray [["bank", (_bank + _amount)]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - GVAR(Registry) set [_uid, _finalAccount]; - - [CRPC(bank,responseSyncBank), [_finalAccount], _player] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Paid $%1", _amount]], _player] call CFUNC(targetEvent); - }], - ["buildChargeResult", compileFinal { - params [["_message", "Unable to process bank payment.", [""]]]; - - createHashMapFromArray [ - ["success", false], - ["message", _message], - ["patch", createHashMap] - ] - }], - ["chargeCheckout", compileFinal { - params [ - ["_uid", "", [""]], - ["_source", "cash", [""]], - ["_amount", 0, [0]], - ["_commit", false, [false]] - ]; - - private _result = _self call ["buildChargeResult", []]; - private _field = switch (toLowerANSI _source) do { - case "cash": { "cash" }; - case "bank": { "bank" }; - default { "" }; - }; - - if (_field isEqualTo "") exitWith { - _result set ["message", "Selected bank payment source is unsupported."]; - _result - }; - - private _account = GVAR(Registry) getOrDefault [_uid, createHashMap]; - if (_account isEqualTo createHashMap) exitWith { - _result set ["message", "Bank account data is unavailable for checkout."]; - _result - }; - - private _balance = _account getOrDefault [_field, 0]; - if (_balance < _amount) exitWith { - private _message = [ - "Bank balance cannot cover this checkout.", - "Cash on hand cannot cover this checkout." - ] select (_field isEqualTo "cash"); - - _result set ["message", _message]; - _result - }; - - private _patch = createHashMapFromArray [[_field, (_balance - _amount)]]; - if (_commit) then { - _patch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; - }; - - _result set ["success", true]; - _result set ["message", ""]; - _result set ["patch", _patch]; - _result - }], - ["transfer", compileFinal { - params [["_uid", "", [""]], ["_target", "", [""]], ["_from", "", [""]], ["_amount", 0, [0]]]; - - if (_uid isEqualTo _target) exitWith { ["WARNING", format ["Self-transfer attempt blocked for %1", _uid]] call EFUNC(common,log); }; - - private _account = GVAR(Registry) getOrDefault [_uid, nil]; - if (isNil "_account") exitWith { ["ERROR", "Empty/Invalid Account!"] call EFUNC(common,log); }; - - private _targetAccount = GVAR(Registry) getOrDefault [_target, nil]; - if (isNil "_targetAccount") exitWith { ["ERROR", "Empty/Invalid Target Account!"] call EFUNC(common,log); }; - - private _selected = _account getOrDefault [_from, 0]; - if (_selected < _amount) exitWith { ["WARNING", "Insufficient Funds!"] call EFUNC(common,log); }; - - private _targetBank = _targetAccount getOrDefault ["bank", 0]; - private _finalAccount = createHashMapFromArray [[_from, (_selected - _amount)]]; - private _finalTargetBank = createHashMapFromArray [["bank", (_targetBank + _amount)]]; - - GVAR(Registry) set [_uid, _finalAccount]; - GVAR(Registry) set [_target, _finalTargetBank]; - - private _player = [_uid] call EFUNC(common,getPlayer); - private _targetPlayer = [_target] call EFUNC(common,getPlayer); - - [CRPC(bank,responseSyncBank), [_finalAccount], _player] call CFUNC(targetEvent); - [CRPC(bank,responseSyncBank), [_finalTargetBank], _targetPlayer] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Transferred $%1 to %2", _amount, (name _targetPlayer)]], _player] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Received $%1 from %2", _amount, (name _player)]], _targetPlayer] call CFUNC(targetEvent); - }], - ["withdraw", compileFinal { - params [["_uid", "", [""]], ["_amount", 0, [0]]]; - - ["INFO", format ["Withdraw %1, for %2", _amount, _uid]] call EFUNC(common,log); - - private _account = GVAR(Registry) getOrDefault [_uid, nil]; - if (isNil "_account") exitWith { ["ERROR", "Empty/Invalid Account!"] call EFUNC(common,log); }; - - private _bank = _account getOrDefault ["bank", 0]; - private _cash = _account getOrDefault ["cash", 0]; - if (_bank < _amount) exitWith { ["WARNING", "Insufficient Funds!"] call EFUNC(common,log); }; - - private _finalAccount = createHashMapFromArray [["bank", (_bank - _amount)], ["cash", (_cash + _amount)]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - GVAR(Registry) set [_uid, _finalAccount]; - - [CRPC(bank,responseSyncBank), [_finalAccount], _player] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Withdrew $%1", _amount]], _player] call CFUNC(targetEvent); - }], - ["depositEarnings", compileFinal { - params [["_uid", "", [""]], ["_amount", 0, [0]]]; - - ["INFO", format ["Deposit Earnings %1, for %2", _amount, _uid]] call EFUNC(common,log); - - private _account = GVAR(Registry) getOrDefault [_uid, nil]; - if (isNil "_account") exitWith { ["ERROR", "Empty/Invalid Account!"] call EFUNC(common,log); }; - - private _bank = _account getOrDefault ["bank", 0]; - private _earnings = _account getOrDefault ["earnings", 0]; - if (_earnings < _amount) exitWith { ["WARNING", "Insufficient Earnings!"] call EFUNC(common,log); }; - - private _finalAccount = createHashMapFromArray [["bank", (_bank + _amount)], ["earnings", (_earnings - _amount)]]; - private _player = [_uid] call EFUNC(common,getPlayer); - - GVAR(Registry) set [_uid, _finalAccount]; - - [CRPC(bank,responseSyncBank), [_finalAccount], _player] call CFUNC(targetEvent); - [CRPC(notifications,recieveNotification), ["info", "Bank", format ["Deposited $%1 from earnings", _amount]], _player] call CFUNC(targetEvent); - }] -]; - -GVAR(BankStore) = createHashMapObject [GVAR(BankBaseStore)]; -GVAR(BankStore) diff --git a/arma/server/addons/bank/functions/fnc_initMessenger.sqf b/arma/server/addons/bank/functions/fnc_initMessenger.sqf new file mode 100644 index 0000000..c3e0b90 --- /dev/null +++ b/arma/server/addons/bank/functions/fnc_initMessenger.sqf @@ -0,0 +1,75 @@ +#include "..\script_component.hpp" + +/* + * File: fnc_initMessenger.sqf + * Author: IDSolutions + * Date: 2026-03-16 + * Last Update: 2026-03-16 + * Public: No + * + * Description: + * Initializes the bank messenger for all server-to-client + * communication including account syncs, toast notifications, + * and inline bank UI notices. + * + * Parameter(s): + * None + * + * Returns: + * Messenger object [HASHMAP OBJECT] + * + * Example(s): + * call forge_server_bank_fnc_initMessenger + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(BankMessenger) = createHashMapObject [[ + ["#type", "BankMessenger"], + ["buildClientAccountPatch", compileFinal { + params [["_account", createHashMap, [createHashMap]]]; + + private _patch = createHashMap; + { + if (_x in _account) then { + _patch set [_x, _account get _x]; + }; + } forEach ["uid", "name", "bank", "cash", "earnings", "transactions"]; + + _patch + }], + ["sendAccountSync", compileFinal { + params [["_uid", "", [""]], ["_account", createHashMap, [createHashMap]], ["_event", CRPC(bank,responseSyncBank), [""]]]; + + if (_uid isEqualTo "" || { _account isEqualTo createHashMap }) exitWith { false }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { false }; + + [_event, [_self call ["buildClientAccountPatch", [_account]]], _player] call CFUNC(targetEvent); + true + }], + ["sendClientNotification", compileFinal { + params [["_uid", "", [""]], ["_type", "info", [""]], ["_title", "Bank", [""]], ["_message", "", [""]]]; + + if (_uid isEqualTo "" || { _message isEqualTo "" }) exitWith { false }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { false }; + + [CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent); + true + }], + ["sendNotice", compileFinal { + params [["_uid", "", [""]], ["_type", "error", [""]], ["_message", "", [""]]]; + + if (_uid isEqualTo "" || { _message isEqualTo "" }) exitWith { false }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { false }; + + [CRPC(bank,responseBankNotice), [_type, _message], _player] call CFUNC(targetEvent); + true + }] +]]; + +GVAR(BankMessenger) diff --git a/arma/server/addons/bank/functions/fnc_initModel.sqf b/arma/server/addons/bank/functions/fnc_initModel.sqf new file mode 100644 index 0000000..77b32eb --- /dev/null +++ b/arma/server/addons/bank/functions/fnc_initModel.sqf @@ -0,0 +1,91 @@ +#include "..\script_component.hpp" + +/* + * File: fnc_initModel.sqf + * Author: IDSolutions + * Date: 2026-03-16 + * Last Update: 2026-03-16 + * Public: No + * + * Description: + * Initializes the bank account data model. Provides default account + * schema, player-based account creation, schema migration for + * existing accounts, and field-level validation. + * + * Parameter(s): + * None + * + * Returns: + * Bank model object [HASHMAP OBJECT] + * + * Example(s): + * call forge_server_bank_fnc_initModel + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(BankModel) = compileFinal createHashMapObject [[ + ["#type", "BankModel"], + ["defaults", compileFinal { + private _account = createHashMap; + + _account set ["uid", ""]; + _account set ["name", ""]; + _account set ["bank", 0]; + _account set ["cash", 0]; + _account set ["earnings", 0]; + _account set ["pin", 1234]; + _account set ["transactions", []]; + + _account + }], + ["fromPlayer", compileFinal { + params [["_player", objNull, [objNull]]]; + + if (_player isEqualTo objNull) exitWith { _self call ["defaults", []] }; + + private _account = _self call ["defaults", []]; + + _account set ["uid", getPlayerUID _player]; + _account set ["name", name _player]; + + _account + }], + ["migrate", compileFinal { + params [["_account", createHashMap, [createHashMap]]]; + + private _defaults = _self call ["defaults", []]; + { + if !(_x in _account) then { + _account set [_x, _y]; + }; + } forEach _defaults; + + _account + }], + ["validate", compileFinal { + params [["_account", createHashMap, [createHashMap]]]; + + private _uid = _account getOrDefault ["uid", ""]; + private _name = _account getOrDefault ["name", ""]; + private _bank = _account getOrDefault ["bank", 0]; + private _cash = _account getOrDefault ["cash", 0]; + private _earnings = _account getOrDefault ["earnings", 0]; + private _pin = _account getOrDefault ["pin", 1234]; + + [_uid, _name, _bank, _cash, _earnings, _pin] try { + if (_uid isEqualTo "" || !(_uid isEqualType "")) then { throw "Invalid UID!"; }; + if (_name isEqualTo "" || !(_name isEqualType "")) then { throw "Invalid Name!"; }; + if (_bank < 0 || !(_bank isEqualType 0)) then { throw "Invalid Bank!"; }; + if (_cash < 0 || !(_cash isEqualType 0)) then { throw "Invalid Cash!"; }; + if (_earnings < 0 || !(_earnings isEqualType 0)) then { throw "Invalid Earnings!"; }; + if (_pin < 1000 || _pin > 9999 || !(_pin isEqualType 0)) then { throw "Invalid Pin!"; }; + } catch { + ["ERROR", format ["Failed to validate account %1!", _exception]] call EFUNC(common,log); + false + }; + + true + }] +]]; + +GVAR(BankModel) diff --git a/arma/server/addons/bank/functions/fnc_initSessionManager.sqf b/arma/server/addons/bank/functions/fnc_initSessionManager.sqf new file mode 100644 index 0000000..75ea131 --- /dev/null +++ b/arma/server/addons/bank/functions/fnc_initSessionManager.sqf @@ -0,0 +1,94 @@ +#include "..\script_component.hpp" + +/* + * File: fnc_initSessionManager.sqf + * Author: IDSolutions + * Date: 2026-03-16 + * Last Update: 2026-03-16 + * Public: No + * + * Description: + * Initializes the bank session manager for managing ATM/bank + * session state, mode resolution, and PIN authorization. + * + * Parameter(s): + * None + * + * Returns: + * Session manager object [HASHMAP OBJECT] + * + * Example(s): + * call forge_server_bank_fnc_initSessionManager + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(BankSessionManager) = createHashMapObject [[ + ["#type", "BankSessionManager"], + ["getSessionState", compileFinal { + params [["_uid", "", [""]]]; + + private _session = GVAR(SessionRegistry) getOrDefault [_uid, createHashMap]; + if (_session isEqualTo createHashMap) then { + _session = createHashMapFromArray [ + ["atmAuthorized", false], + ["mode", "bank"] + ]; + GVAR(SessionRegistry) set [_uid, _session]; + }; + + _session + }], + ["setSessionState", compileFinal { + params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]]]; + + if (_uid isEqualTo "") exitWith { createHashMap }; + + private _session = +(_self call ["getSessionState", [_uid]]); + { _session set [_x, _y]; } forEach _fieldValuePairs; + + GVAR(SessionRegistry) set [_uid, _session]; + _session + }], + ["resolveMode", compileFinal { + params [["_mode", "bank", [""]]]; + + private _finalMode = toLowerANSI _mode; + if !(_finalMode in ["atm", "bank"]) then { _finalMode = "bank"; }; + + _finalMode + }], + ["syncSessionMode", compileFinal { + params [["_uid", "", [""]], ["_mode", "", [""]], ["_resetAuthorization", false, [false]]]; + + private _current = _self call ["getSessionState", [_uid]]; + private _finalMode = if (_mode isEqualTo "") then { + _current getOrDefault ["mode", "bank"] + } else { + _self call ["resolveMode", [_mode]] + }; + private _atmAuthorized = _current getOrDefault ["atmAuthorized", false]; + + if (_finalMode isEqualTo "atm") then { + if (_resetAuthorization || { (_current getOrDefault ["mode", "bank"]) isNotEqualTo "atm" }) then { + _atmAuthorized = false; + }; + } else { + _atmAuthorized = false; + }; + + _self call ["setSessionState", [_uid, createHashMapFromArray [ + ["atmAuthorized", _atmAuthorized], + ["mode", _finalMode] + ]]] + }], + ["submitPin", compileFinal { + params [["_uid", "", [""]], ["_context", createHashMap, [createHashMap]]]; + + _self call ["setSessionState", [_uid, createHashMapFromArray [["atmAuthorized", true], ["mode", "atm"]]]]; + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", "ATM access granted."]]; + GVAR(BankStore) call ["hydrateSession", [_uid, "atm", false]]; + true + }] +]]; + +GVAR(BankSessionManager) diff --git a/arma/server/addons/bank/functions/fnc_initStore.sqf b/arma/server/addons/bank/functions/fnc_initStore.sqf new file mode 100644 index 0000000..fdaaefa --- /dev/null +++ b/arma/server/addons/bank/functions/fnc_initStore.sqf @@ -0,0 +1,350 @@ +#include "..\script_component.hpp" + +/* + * File: fnc_initStore.sqf + * Author: IDSolutions + * Date: 2025-12-17 + * Last Update: 2026-03-16 + * Public: No + * + * Description: + * Initializes the bank store for managing player bank accounts. + * Handles account lifecycle (init/fetch/create/migrate), transaction + * mutations, checkout charges, and session hydration. + * + * Parameter(s): + * None + * + * Returns: + * Bank store object [HASHMAP OBJECT] + * + * Example(s): + * call forge_server_bank_fnc_initStore + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(BankBaseStore) = compileFinal createHashMapFromArray [ + ["#base", EGVAR(common,BaseStore)], + ["#type", "BankBaseStore"], + ["#create", compileFinal { + GVAR(IndexRegistry) = createHashMap; + GVAR(Registry) = createHashMap; + GVAR(SessionRegistry) = createHashMap; + ["INFO", "Bank Store Initialized!"] call EFUNC(common,log); + }], + ["buildChargeResult", compileFinal { + params [["_message", "Unable to process bank payment.", [""]]]; + + createHashMapFromArray [ + ["success", false], + ["message", _message], + ["patch", createHashMap] + ] + }], + ["buildHydratePayload", compileFinal { + params [["_uid", "", [""]], ["_mode", "", [""]], ["_resetAuthorization", false, [false]]]; + + if (_uid isEqualTo "") exitWith { createHashMap }; + + private _account = GVAR(Registry) getOrDefault [_uid, createHashMap]; + if (_account isEqualTo createHashMap) then { _account = _self call ["init", [_uid]]; }; + if (_account isEqualTo createHashMap) exitWith { createHashMap }; + + private _session = GVAR(BankSessionManager) call ["syncSessionMode", [_uid, _mode, _resetAuthorization]]; + private _orgState = _self call ["resolveOrgState", [_uid]]; + private _player = [_uid] call EFUNC(common,getPlayer); + private _playerName = if (isNull _player) then { + _account getOrDefault ["name", "Unknown"] + } else { + name _player + }; + + createHashMapFromArray [ + ["session", createHashMapFromArray [ + ["atmAuthorized", _session getOrDefault ["atmAuthorized", false]], + ["mode", _session getOrDefault ["mode", "bank"]], + ["orgFunds", _orgState getOrDefault ["funds", 0]], + ["orgName", _orgState getOrDefault ["name", ""]], + ["playerName", _playerName], + ["transferTargets", _self call ["buildTransferTargets", [_uid]]], + ["uid", _uid] + ]], + ["account", GVAR(BankMessenger) call ["buildClientAccountPatch", [_account]]] + ] + }], + ["buildTransferTargets", compileFinal { + params [["_sourceUid", "", [""]]]; + + private _targets = []; + { + if (isNull _x) then { continue; }; + + private _targetUid = getPlayerUID _x; + private _targetName = name _x; + if (_targetUid isEqualTo "" || { _targetUid isEqualTo _sourceUid } || { _targetName isEqualTo "" }) then { continue; }; + + _targets pushBack (createHashMapFromArray [ + ["name", _targetName], + ["uid", _targetUid] + ]); + } forEach allPlayers; + + private _targetPairs = _targets apply { [toLowerANSI (_x getOrDefault ["name", ""]), _x] }; + _targetPairs sort true; + _targetPairs apply { _x param [1, createHashMap] } + }], + ["chargeCheckout", compileFinal { + params [["_uid", "", [""]], ["_source", "cash", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]]; + + private _result = _self call ["buildChargeResult", []]; + private _field = switch (toLowerANSI _source) do { + case "cash": { "cash" }; + case "bank": { "bank" }; + default { "" }; + }; + + if (_field isEqualTo "") exitWith { + _result set ["message", "Selected bank payment source is unsupported."]; + _result + }; + + private _account = GVAR(Registry) getOrDefault [_uid, createHashMap]; + if (_account isEqualTo createHashMap) exitWith { + _result set ["message", "Bank account data is unavailable for checkout."]; + _result + }; + + private _balance = _account getOrDefault [_field, 0]; + if (_balance < _amount) exitWith { + private _message = [ + "Bank balance cannot cover this checkout.", + "Cash on hand cannot cover this checkout." + ] select (_field isEqualTo "cash"); + + _result set ["message", _message]; + _result + }; + + private _patch = createHashMapFromArray [[_field, (_balance - _amount)]]; + if (_commit) then { + _patch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; + }; + + _result set ["success", true]; + _result set ["message", ""]; + _result set ["patch", _patch]; + _result + }], + ["deposit", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]]; + + ["INFO", format ["Deposit %1, for %2", _amount, _uid]] call EFUNC(common,log); + + private _bank = _context getOrDefault ["bank", 0]; + private _cash = _context getOrDefault ["cash", 0]; + + private _patch = createHashMapFromArray [ + ["bank", (_bank + _amount)], + ["cash", (_cash - _amount)] + ]; + private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]]; + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1", _amount]]]; + true + }], + ["hydrateSession", compileFinal { + params [["_uid", "", [""]], ["_mode", "", [""]], ["_resetAuthorization", false, [false]]]; + + private _payload = _self call ["buildHydratePayload", [_uid, _mode, _resetAuthorization]]; + if (_payload isEqualTo createHashMap) exitWith { false }; + + private _player = [_uid] call EFUNC(common,getPlayer); + if (isNull _player) exitWith { false }; + + [CRPC(bank,responseHydrateBank), [_payload], _player] call CFUNC(targetEvent); + true + }], + ["init", compileFinal { + params [["_uid", "", [""]]]; + + if (_uid isEqualTo "") exitWith { createHashMap }; + + private _player = [_uid] call EFUNC(common,getPlayer); + private _playerName = if (isNull _player) then { "Unknown" } else { name _player }; + private _cached = GVAR(Registry) getOrDefault [_uid, createHashMap]; + if (_cached isNotEqualTo createHashMap) exitWith { + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _cached, CRPC(bank,responseInitBank)]]; + _cached + }; + + ["bank:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"]; + if !(_isSuccess) exitWith { + ["ERROR", format ["Failed to check if bank account %1 exists! Using fallback account.", _uid]] call EFUNC(common,log); + + private _fallbackAccount = GVAR(BankModel) call ["fromPlayer", [_player]]; + _fallbackAccount set ["uid", _uid]; + if ((_fallbackAccount getOrDefault ["name", ""]) isEqualTo "") then { + _fallbackAccount set ["name", _playerName]; + }; + + private _regEntry = createHashMapFromArray [["uid", _uid], ["name", _playerName]]; + GVAR(IndexRegistry) set [_uid, _regEntry]; + GVAR(Registry) set [_uid, _fallbackAccount]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _fallbackAccount, CRPC(bank,responseInitBank)]]; + _fallbackAccount + }; + + private _finalAccount = createHashMap; + if (_result isEqualTo "true") then { + _finalAccount = _self call ["fetch", ["bank:get", _uid]]; + ["INFO", format ["Found bank account for %1", _uid]] call EFUNC(common,log); + } else { + _finalAccount = GVAR(BankModel) call ["fromPlayer", [_player]]; + _finalAccount set ["uid", _uid]; + if ((_finalAccount getOrDefault ["name", ""]) isEqualTo "") then { + _finalAccount set ["name", _playerName]; + }; + + private _json = _self call ["toJSON", [_finalAccount]]; + ["bank:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"]; + if (!_createSuccess) exitWith { + ["ERROR", format ["Failed to create bank account %1! Using fallback account.", _uid]] call EFUNC(common,log); + + private _regEntry = createHashMapFromArray [["uid", _uid], ["name", _playerName]]; + GVAR(IndexRegistry) set [_uid, _regEntry]; + GVAR(Registry) set [_uid, _finalAccount]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalAccount, CRPC(bank,responseInitBank)]]; + _finalAccount + }; + + ["INFO", format ["Created new bank account for %1", _uid]] call EFUNC(common,log); + }; + + _finalAccount = GVAR(BankModel) call ["migrate", [_finalAccount]]; + if ((_finalAccount getOrDefault ["uid", ""]) isEqualTo "") then { + _finalAccount set ["uid", _uid]; + }; + if ((_finalAccount getOrDefault ["name", ""]) isEqualTo "") then { + _finalAccount set ["name", _playerName]; + }; + + GVAR(IndexRegistry) set [_uid, createHashMapFromArray [["uid", _uid], ["name", _playerName]]]; + GVAR(Registry) set [_uid, _finalAccount]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalAccount, CRPC(bank,responseInitBank)]]; + _finalAccount + }], + ["payment", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]]; + + ["INFO", format ["Payment %1, for %2", _amount, _uid]] call EFUNC(common,log); + + private _bank = _context getOrDefault ["bank", 0]; + private _patch = createHashMapFromArray [["bank", (_bank + _amount)]]; + private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]]; + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Paid $%1", _amount]]]; + true + }], + ["resolveOrgState", compileFinal { + params [["_uid", "", [""]]]; + + private _defaultState = createHashMapFromArray [ + ["funds", 0], + ["name", ""] + ]; + if (_uid isEqualTo "") exitWith { _defaultState }; + + 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"]]; + }; + if (_org isEqualTo createHashMap) exitWith { _defaultState }; + + createHashMapFromArray [ + ["funds", _org getOrDefault ["funds", 0]], + ["name", _org getOrDefault ["name", ""]] + ] + }], + ["transfer", compileFinal { + params [["_uid", "", [""]], ["_target", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]]; + + private _account = _context getOrDefault ["account", createHashMap]; + private _targetAccount = _context getOrDefault ["targetAccount", createHashMap]; + private _sourceField = _context getOrDefault ["sourceField", "bank"]; + private _selected = _context getOrDefault ["sourceBalance", 0]; + private _targetBank = _context getOrDefault ["targetBank", 0]; + + private _sourcePatch = createHashMapFromArray [[_sourceField, (_selected - _amount)]]; + private _targetPatch = createHashMapFromArray [["bank", (_targetBank + _amount)]]; + private _finalSourcePatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _sourcePatch, false]]; + private _finalTargetPatch = _self call ["mset", [GVAR(Registry), "bank:update", _target, _targetPatch, false]]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalSourcePatch]]; + GVAR(BankMessenger) call ["sendAccountSync", [_target, _finalTargetPatch]]; + + private _targetPlayer = [_target] call EFUNC(common,getPlayer); + private _targetName = if (isNull _targetPlayer) then { + _targetAccount getOrDefault ["name", "Recipient"] + } else { + name _targetPlayer + }; + private _player = [_uid] call EFUNC(common,getPlayer); + private _playerName = if (isNull _player) then { + _account getOrDefault ["name", "Unknown"] + } else { + name _player + }; + + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Transferred $%1 to %2", _amount, _targetName]]]; + GVAR(BankMessenger) call ["sendClientNotification", [_target, "info", "Bank", format ["Received $%1 from %2", _amount, _playerName]]]; + true + }], + ["withdraw", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]]; + + ["INFO", format ["Withdraw %1, for %2", _amount, _uid]] call EFUNC(common,log); + + private _bank = _context getOrDefault ["bank", 0]; + private _cash = _context getOrDefault ["cash", 0]; + + private _patch = createHashMapFromArray [ + ["bank", (_bank - _amount)], + ["cash", (_cash + _amount)] + ]; + private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]]; + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Withdrew $%1", _amount]]]; + true + }], + ["depositEarnings", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]]; + + ["INFO", format ["Deposit Earnings %1, for %2", _amount, _uid]] call EFUNC(common,log); + + private _bank = _context getOrDefault ["bank", 0]; + private _earnings = _context getOrDefault ["earnings", 0]; + + private _patch = createHashMapFromArray [ + ["bank", (_bank + _amount)], + ["earnings", (_earnings - _amount)] + ]; + private _finalPatch = _self call ["mset", [GVAR(Registry), "bank:update", _uid, _patch, false]]; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalPatch]]; + GVAR(BankMessenger) call ["sendClientNotification", [_uid, "info", "Bank", format ["Deposited $%1 from earnings", _amount]]]; + true + }] +]; + +GVAR(BankStore) = createHashMapObject [GVAR(BankBaseStore)]; +GVAR(BankStore) diff --git a/arma/server/addons/bank/functions/fnc_initValidator.sqf b/arma/server/addons/bank/functions/fnc_initValidator.sqf new file mode 100644 index 0000000..0bf06da --- /dev/null +++ b/arma/server/addons/bank/functions/fnc_initValidator.sqf @@ -0,0 +1,259 @@ +#include "..\script_component.hpp" + +/* + * File: fnc_validator.sqf + * Author: IDSolutions + * Date: 2026-03-16 + * Last Update: 2026-03-16 + * Public: No + * + * Description: + * Initializes the bank validator for pre-checking action payloads + * before they reach the bank store. Each method uses try/catch to + * validate inputs and state, sending a notice to the player on + * failure and returning false. On success returns a context hashmap + * containing resolved data (account, balances, etc.) for the store. + * + * Parameter(s): + * None + * + * Returns: + * Validator object [HASHMAP OBJECT] + * + * Example(s): + * call forge_server_bank_fnc_validator + */ + +#pragma hemtt ignore_variables ["_self"] +GVAR(BankValidator) = createHashMapObject [[ + ["#type", "BankValidator"], + ["resolveAccount", compileFinal { + params [["_uid", "", [""]]]; + + private _account = GVAR(Registry) getOrDefault [_uid, createHashMap]; + if (_account isEqualTo createHashMap) then { + throw "Bank account data is unavailable."; + }; + + _account + }], + ["validateDeposit", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + private _context = createHashMap; + + [_uid, _amount] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + if (_amount <= 0) then { throw "Enter a valid deposit amount." }; + + private _session = GVAR(BankSessionManager) call ["getSessionState", [_uid]]; + if ((_session getOrDefault ["mode", "bank"]) isEqualTo "atm") then { + if !(_session getOrDefault ["atmAuthorized", false]) then { + throw "ATM authorization is required before deposit."; + }; + }; + + private _account = _self call ["resolveAccount", [_uid]]; + private _bank = _account getOrDefault ["bank", 0]; + private _cash = _account getOrDefault ["cash", 0]; + + if (_cash < _amount) then { throw "Cash on hand cannot cover that deposit." }; + + _context set ["account", _account]; + _context set ["bank", _bank]; + _context set ["cash", _cash]; + } catch { + ["ERROR", format ["Deposit validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }], + ["validateWithdraw", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + private _context = createHashMap; + + [_uid, _amount] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + if (_amount <= 0) then { throw "Enter a valid withdrawal amount." }; + + private _session = GVAR(BankSessionManager) call ["getSessionState", [_uid]]; + if ((_session getOrDefault ["mode", "bank"]) isEqualTo "atm") then { + if !(_session getOrDefault ["atmAuthorized", false]) then { + throw "ATM authorization is required before withdrawal."; + }; + }; + + private _account = _self call ["resolveAccount", [_uid]]; + private _bank = _account getOrDefault ["bank", 0]; + private _cash = _account getOrDefault ["cash", 0]; + + if (_bank < _amount) then { throw "Bank balance cannot cover that withdrawal." }; + + _context set ["account", _account]; + _context set ["bank", _bank]; + _context set ["cash", _cash]; + } catch { + ["ERROR", format ["Withdraw validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }], + ["validateTransfer", compileFinal { + params [["_uid", "", [""]], ["_target", "", [""]], ["_from", "", [""]], ["_amount", 0, [0]]]; + + private _context = createHashMap; + + [_uid, _target, _from, _amount] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + if (_uid isEqualTo _target) then { throw "You cannot transfer funds to yourself." }; + if (_amount <= 0) then { throw "Enter a valid transfer amount." }; + + private _session = GVAR(BankSessionManager) call ["getSessionState", [_uid]]; + if ((_session getOrDefault ["mode", "bank"]) isNotEqualTo "bank") then { + throw "Transfers are only available from the full bank interface."; + }; + + private _account = _self call ["resolveAccount", [_uid]]; + + private _targetAccount = GVAR(Registry) getOrDefault [_target, createHashMap]; + if (_targetAccount isEqualTo createHashMap) then { + _targetAccount = GVAR(BankStore) call ["init", [_target]]; + }; + if (_targetAccount isEqualTo createHashMap) then { + throw "Selected transfer recipient is unavailable."; + }; + + private _sourceField = ["bank", "cash"] select (toLowerANSI _from isEqualTo "cash"); + private _selected = _account getOrDefault [_sourceField, 0]; + if (_selected < _amount) then { + private _message = [ + "Bank balance cannot cover that transfer.", + "Cash on hand cannot cover that transfer." + ] select (_sourceField isEqualTo "cash"); + throw _message; + }; + + _context set ["account", _account]; + _context set ["targetAccount", _targetAccount]; + _context set ["sourceField", _sourceField]; + _context set ["sourceBalance", _selected]; + _context set ["targetBank", _targetAccount getOrDefault ["bank", 0]]; + } catch { + ["ERROR", format ["Transfer validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }], + ["validateDepositEarnings", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + private _context = createHashMap; + + [_uid, _amount] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + + private _session = GVAR(BankSessionManager) call ["getSessionState", [_uid]]; + if ((_session getOrDefault ["mode", "bank"]) isNotEqualTo "bank") then { + throw "Earnings deposits are only available from the full bank interface."; + }; + + if (_amount <= 0) then { throw "No earnings are available to deposit." }; + + private _account = _self call ["resolveAccount", [_uid]]; + private _bank = _account getOrDefault ["bank", 0]; + private _earnings = _account getOrDefault ["earnings", 0]; + + if (_earnings < _amount) then { throw "Pending earnings cannot cover that deposit request." }; + + _context set ["account", _account]; + _context set ["bank", _bank]; + _context set ["earnings", _earnings]; + } catch { + ["ERROR", format ["DepositEarnings validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }], + ["validatePayment", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + private _context = createHashMap; + + [_uid, _amount] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + if (_amount <= 0) then { throw "Enter a valid payment amount." }; + + private _account = _self call ["resolveAccount", [_uid]]; + private _bank = _account getOrDefault ["bank", 0]; + + _context set ["account", _account]; + _context set ["bank", _bank]; + } catch { + ["ERROR", format ["Payment validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }], + ["validateSubmitPin", compileFinal { + params [["_uid", "", [""]], ["_pin", "", [""]]]; + + private _context = createHashMap; + + [_uid, _pin] try { + if (_uid isEqualTo "") then { throw "Empty/Invalid UID!" }; + + private _session = GVAR(BankSessionManager) call ["getSessionState", [_uid]]; + if ((_session getOrDefault ["mode", "bank"]) isNotEqualTo "atm") then { + _session = GVAR(BankSessionManager) call ["setSessionState", [_uid, createHashMapFromArray [ + ["atmAuthorized", false], + ["mode", "atm"] + ]]]; + }; + + private _account = GVAR(Registry) getOrDefault [_uid, createHashMap]; + if (_account isEqualTo createHashMap) then { + _account = GVAR(BankStore) call ["init", [_uid]]; + }; + if (_account isEqualTo createHashMap) then { + throw "Bank account data is unavailable."; + }; + + private _enteredPin = _pin; + if !(_enteredPin isEqualType "") then { + _enteredPin = str _enteredPin; + }; + if ((count _enteredPin) isNotEqualTo 4) then { + throw "Enter your four-digit access PIN."; + }; + + private _accountPin = str (_account getOrDefault ["pin", 1234]); + if (_enteredPin isNotEqualTo _accountPin) then { + GVAR(BankSessionManager) call ["setSessionState", [_uid, createHashMapFromArray [["atmAuthorized", false]]]]; + throw "Incorrect PIN."; + }; + + _context set ["account", _account]; + _context set ["session", _session]; + } catch { + ["ERROR", format ["SubmitPin validation failed: %1", _exception]] call EFUNC(common,log); + GVAR(BankMessenger) call ["sendNotice", [_uid, "error", _exception]]; + GVAR(BankStore) call ["hydrateSession", [_uid, "atm", false]]; + }; + + if (_context isEqualTo createHashMap) exitWith { false }; + _context + }] +]]; + +GVAR(BankValidator) diff --git a/arma/server/addons/locker/functions/fnc_initLockerStore.sqf b/arma/server/addons/locker/functions/fnc_initLockerStore.sqf index 352c940..3a9c5bf 100644 --- a/arma/server/addons/locker/functions/fnc_initLockerStore.sqf +++ b/arma/server/addons/locker/functions/fnc_initLockerStore.sqf @@ -91,33 +91,33 @@ GVAR(LockerBaseStore) = compileFinal createHashMapFromArray [ private _category = toLowerANSI (_x getOrDefault ["category", ""]); private _quantity = floor ((_x getOrDefault ["quantity", 0]) max 0); private _lockerCategory = switch (_category) do { - case "item": { "item" }; + case "item"; + case "attachment": { "item" }; case "weapon": { "weapon" }; case "magazine": { "magazine" }; case "backpack": { "backpack" }; default { "" }; }; - if (_className isEqualTo "" || { _lockerCategory isEqualTo "" } || { _quantity <= 0 }) exitWith { - _result set ["message", "Checkout item was missing a valid classname, category, or quantity."]; - _result set ["success", false]; + if (_className isEqualTo "" || { _lockerCategory isEqualTo "" } || { _quantity <= 0 }) then { + ["WARN", format ["Skipping invalid locker grant entry: %1 (category: %2)", _className, _category]] call EFUNC(common,log); + } else { + private _entry = +(_locker getOrDefault [_className, createHashMap]); + private _amount = _entry getOrDefault ["amount", 0]; + private _updatedEntry = createHashMapFromArray [ + ["amount", (_amount + _quantity)], + ["classname", _className], + ["category", _lockerCategory] + ]; + + _locker set [_className, _updatedEntry]; + _patch set [_className, _updatedEntry]; + _granted pushBack (createHashMapFromArray [ + ["classname", _className], + ["category", _lockerCategory], + ["quantity", _quantity] + ]); }; - - private _entry = +(_locker getOrDefault [_className, createHashMap]); - private _amount = _entry getOrDefault ["amount", 0]; - private _updatedEntry = createHashMapFromArray [ - ["amount", (_amount + _quantity)], - ["classname", _className], - ["category", _lockerCategory] - ]; - - _locker set [_className, _updatedEntry]; - _patch set [_className, _updatedEntry]; - _granted pushBack (createHashMapFromArray [ - ["classname", _className], - ["category", _lockerCategory], - ["quantity", _quantity] - ]); } forEach _items; if ((count (keys _locker)) > 25) exitWith { diff --git a/arma/server/addons/main/functions/fnc_initStores.sqf b/arma/server/addons/main/functions/fnc_initStores.sqf index 9093c32..1d37eb8 100644 --- a/arma/server/addons/main/functions/fnc_initStores.sqf +++ b/arma/server/addons/main/functions/fnc_initStores.sqf @@ -23,7 +23,11 @@ if (isNil QEGVAR(common,BaseStore)) then { call EFUNC(common,baseStore); }; if (isNil QEGVAR(actor,ActorStore)) then { call EFUNC(actor,initActorStore); }; // Bank -if (isNil QEGVAR(bank,BankStore)) then { call EFUNC(bank,initBankStore); }; +if (isNil QEGVAR(bank,BankSessionManager)) then { call EFUNC(bank,initSessionManager); }; +if (isNil QEGVAR(bank,BankMessenger)) then { call EFUNC(bank,initMessenger); }; +if (isNil QEGVAR(bank,BankModel)) then { call EFUNC(bank,initModel); }; +if (isNil QEGVAR(bank,BankStore)) then { call EFUNC(bank,initStore); }; +if (isNil QEGVAR(bank,BankValidator)) then { call EFUNC(bank,initValidator); }; // Garage if (isNil QEGVAR(garage,GarageStore)) then { call EFUNC(garage,initGarageStore); };