diff --git a/Cargo.toml b/Cargo.toml index aabe7fe..080237f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "arma/server/extension", + "bin/icom", "lib/models", "lib/repositories", "lib/services", diff --git a/README.md b/README.md index 0ae66ab..7d1392a 100644 --- a/README.md +++ b/README.md @@ -271,13 +271,13 @@ Logs are automatically created in `@forge_server/logs/`: ## License -[Your License Here] +View the License [here](LICENSE.md). ## Support - **Issues**: [Gitea Issues](https://gitea.innovativedevsolutions.org/IDSolutions/forge/issues) - **Documentation**: See individual module READMEs -- **Architecture**: [FORGE_Architecture_Diagram.md](FORGE_Architecture_Diagram.md) +- **Architecture**: [Diagram](Architecture_Diagram.md) ## Roadmap diff --git a/arma/client/.hemtt/launch.toml b/arma/client/.hemtt/launch.toml index 1e2e390..136dc12 100644 --- a/arma/client/.hemtt/launch.toml +++ b/arma/client/.hemtt/launch.toml @@ -3,11 +3,19 @@ workshop = [ "450814997", # CBA_A3 "3499977893", # Advanced Dev Tools "623475643", # 3DEN Enhanced + "3023395342", # 3DEN Attributes Fast Load ] presets = [] dlc = [] optionals = [] -parameters = [] +parameters = [ + "-skipIntro", + "-noSplash", + "-showScriptErrors", + "-debug", + "-filePatching", + "-world=empty", +] [ace] extends = "default" diff --git a/arma/client/.hemtt/project.toml b/arma/client/.hemtt/project.toml index 7c308fc..1beed3e 100644 --- a/arma/client/.hemtt/project.toml +++ b/arma/client/.hemtt/project.toml @@ -11,10 +11,10 @@ git_hash = 0 include = [ "mod.cpp", "meta.cpp", - "logo_forge_client.png", - "logo_forge_client_over.png", - "logo_forge_client_ca.paa", - "logo_forge_client_over_ca.paa", + "icon_64_ca.paa", + "icon_128_ca.paa", + "icon_128_highlight_ca.paa", + "title_ca.paa", "LICENSE.md", "README.md", ] diff --git a/arma/client/addons/actor/XEH_postInitClient.sqf b/arma/client/addons/actor/XEH_postInitClient.sqf index d22f14d..df53600 100644 --- a/arma/client/addons/actor/XEH_postInitClient.sqf +++ b/arma/client/addons/actor/XEH_postInitClient.sqf @@ -8,21 +8,52 @@ removeBackpack player; removeGoggles player; removeHeadgear player; -SETPVAR(player,FORGE_actorIsLoaded,false); +SETPVAR(player,FORGE_isLoaded,false); cutText ["Loading In...", "BLACK", 1]; +player addEventHandler ["Killed", { + params ["_unit", "_killer", "_instigator", "_useEffects"]; + [SRPC(economy,onKilled), [_unit]] call CFUNC(serverEvent); +}]; + +player addEventHandler ["Respawn", { + params ["_unit", "_corpse"]; + + private _uid = getPlayerUID player; + [SRPC(economy,onRespawn), [_unit, _corpse, _uid]] call CFUNC(serverEvent); +}]; + if (isNil QGVAR(ActorClass)) then { [] call FUNC(initActorClass); }; [QGVAR(initActor), { GVAR(ActorClass) call ["init", []]; }] call CFUNC(addEventHandler); +[QGVAR(onActorRespawn), { + params [["_loadout", [], [[]]], ["_medSpawnPos", [0,0,0], [[]]], ["_medSpawnDir", 0, [0]]]; + + private _message = ["warning", "Medical Alert", "You have been revived at a medical facility.", 5000]; + EGVAR(notifications,NotificationClass) call ["create", _message]; + + player setUnitLoadout _loadout; + player setPosATL _medSpawnPos; + player setDir _medSpawnDir; + player switchMove "Acts_LyingWounded_loop"; + + ["Initialize", [player, [], false, true, true, true, true, true, false, false]] call BFUNC(EGSpectator); + + [SRPC(economy,onHealed), [player]] call CFUNC(serverEvent); +}] call CFUNC(addEventHandler); + +[QGVAR(onActorHealed), { + player switchMove ""; + ["Terminate"] call BFUNC(EGSpectator); +}] call CFUNC(addEventHandler); + [QGVAR(responseInitActor), { params [["_data", createHashMap, [createHashMap]]]; GVAR(ActorClass) call ["sync", [_data, true]]; - - SETPVAR(player,FORGE_isLoaded,true); cutText ["", "PLAIN", 1]; }] call CFUNC(addEventHandler); @@ -35,7 +66,7 @@ if (isNil QGVAR(ActorClass)) then { [] call FUNC(initActorClass); }; [QGVAR(initActor), []] call CFUNC(localEvent); [{ - GETVAR(player,FORGE_actorIsLoaded,false) + GETVAR(player,FORGE_isLoaded,false) }, { private _holster = GVAR(ActorClass) call ["get", ["holster", true]]; if (_holster) then { [player] call AFUNC(weaponselect,putWeaponAway); }; diff --git a/arma/client/addons/actor/XEH_preInit.sqf b/arma/client/addons/actor/XEH_preInit.sqf index 8d45ad6..630ca03 100644 --- a/arma/client/addons/actor/XEH_preInit.sqf +++ b/arma/client/addons/actor/XEH_preInit.sqf @@ -8,3 +8,18 @@ private _category = [QUOTE(MOD_NAME), LLSTRING(displayName)]; #include "initSettings.inc.sqf" #include "initKeybinds.inc.sqf" + +["ace_refuel_started", { + params ["_source", "_target", "", "_unit"]; + [SRPC(economy,FuelStart), [_source, _target, _unit]] call CFUNC(serverEvent); +}] call CFUNC(addEventHandler); + +["ace_refuel_tick", { + params ["_source", "_target", "_amount"]; + [SRPC(economy,FuelTick), [_source, _target, _amount]] call CFUNC(serverEvent); +}] call CFUNC(addEventHandler); + +["ace_refuel_stopped", { + params ["_source", "_target"]; + [SRPC(economy,FuelStop), [_source, _target]] call CFUNC(serverEvent); +}] call CFUNC(addEventHandler); diff --git a/arma/client/addons/actor/functions/fnc_handleUIEvents.sqf b/arma/client/addons/actor/functions/fnc_handleUIEvents.sqf index bcde6ec..415a5f2 100644 --- a/arma/client/addons/actor/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/actor/functions/fnc_handleUIEvents.sqf @@ -27,12 +27,14 @@ diag_log format ["[FORGE:Client:Actor] Handling UI event: %1 with data: %2", _ev switch (_event) do { case "actor::get::actions": { GVAR(ActorClass) call ["getNearbyActions", [_control]]; }; - // case "actor::open::bank": { [] spawn EFUNC(bank,openUI); }; + case "actor::open::atm": { [true] spawn EFUNC(bank,openUI); }; case "actor::open::bank": { [] spawn EFUNC(bank,openUI); }; case "actor::open::device": { hint "Device interaction is not yet implemented."; }; // TODO: Implement device interaction case "actor::open::garage": { hint "Garage interaction is not yet implemented."; }; // TODO: Implement garage interaction + case "actor::open::vgarage": { [] spawn EFUNC(garage,openVG); }; case "actor::open::org": { [] spawn EFUNC(org,openUI); }; case "actor::open::locker": { hint "Locker interaction is not yet implemented."; }; // TODO: Implement locker interaction + case "actor::open::vlocker": { ["Open", [false, FORGE_Locker_Box, player]] spawn BFUNC(arsenal) }; // case "actor::open::phone": { [] spawn EFUNC(phone,openUI) }; case "actor::open::phone": { hint "Phone interaction is not yet implemented."; }; // TODO: Implement phone interaction case "actor::open::iplayer": { hint "Player interaction is not yet implemented." }; // TODO: Implement player interaction diff --git a/arma/client/addons/actor/functions/fnc_initActorClass.sqf b/arma/client/addons/actor/functions/fnc_initActorClass.sqf index b0119ec..1e008cc 100644 --- a/arma/client/addons/actor/functions/fnc_initActorClass.sqf +++ b/arma/client/addons/actor/functions/fnc_initActorClass.sqf @@ -64,7 +64,6 @@ GVAR(ActorClass) = createHashMapObject [[ private _actor = _self get "actor"; private _isLoaded = _self get "isLoaded"; - if !(_isLoaded) then { _self set ["isLoaded", true]; }; if (_data isEqualTo createHashMap) exitWith { diag_log "[FORGE:Client:Actor] Empty data received for sync, skipping."; }; @@ -82,12 +81,12 @@ GVAR(ActorClass) = createHashMapObject [[ default {}; }; }; - } forEach _data; _self set ["actor", _actor]; + SETPVAR(player,FORGE_isLoaded,true); - SETPVAR(player,FORGE_actorIsLoaded,true); + if !(_isLoaded) then { _self set ["isLoaded", true]; }; diag_log "[FORGE:Client:Actor] Sync completed"; }], ["get", { diff --git a/arma/client/addons/actor/initSettings.inc.sqf b/arma/client/addons/actor/initSettings.inc.sqf index 3ff3391..08775e9 100644 --- a/arma/client/addons/actor/initSettings.inc.sqf +++ b/arma/client/addons/actor/initSettings.inc.sqf @@ -14,11 +14,11 @@ [ QGVAR(enableVA), "CHECKBOX", [LSTRING(enableVA), LSTRING(enableVATooltip)], - _category, false, true + _category, true, true ] call CBA_fnc_addSetting; [ QGVAR(enableVG), "CHECKBOX", [LSTRING(enableVG), LSTRING(enableVGTooltip)], - _category, false, true + _category, true, true ] call CBA_fnc_addSetting; diff --git a/arma/client/addons/actor/ui/_site/script.js b/arma/client/addons/actor/ui/_site/script.js index 7f0a62f..88b4334 100644 --- a/arma/client/addons/actor/ui/_site/script.js +++ b/arma/client/addons/actor/ui/_site/script.js @@ -47,6 +47,13 @@ const actions = { //============================================================================= const baseMenuItems = [ + { + id: "atm", + title: "ATM", + description: "Access the ATM", + icon: "", + action: "actor::open::atm", + }, { id: "bank", title: "Banking Services", @@ -111,7 +118,7 @@ const actionDefinitions = { title: "Virtual Arsenal", description: "Access your virtual arsenal", icon: "", - action: "actor::open::arsenal", + action: "actor::open::vlocker", }, vg: { id: "vg", @@ -141,7 +148,7 @@ function actorReducer(state = initialState, action) { actionArray.forEach((actionItem) => { if (Array.isArray(actionItem) && actionItem.length === 2) { const [type, value] = actionItem; - const definition = state.actionDefinitions[value]; + const definition = state.actionDefinitions[type]; if (definition) { newMenuItems.push(definition); } else { diff --git a/arma/client/addons/bank/XEH_postInitClient.sqf b/arma/client/addons/bank/XEH_postInitClient.sqf index 92b0995..ee3a2c2 100644 --- a/arma/client/addons/bank/XEH_postInitClient.sqf +++ b/arma/client/addons/bank/XEH_postInitClient.sqf @@ -10,9 +10,6 @@ if (isNil QGVAR(BankClass)) then { [] call FUNC(initBankClass); }; params [["_data", createHashMap, [createHashMap]]]; GVAR(BankClass) call ["sync", [_data, true]]; - - SETPVAR(player,FORGE_isLoaded,true); - cutText ["", "PLAIN", 1]; }] call CFUNC(addEventHandler); [QGVAR(responseSyncBank), { diff --git a/arma/client/addons/bank/config.cpp b/arma/client/addons/bank/config.cpp index efc69e3..bce4c33 100644 --- a/arma/client/addons/bank/config.cpp +++ b/arma/client/addons/bank/config.cpp @@ -3,7 +3,7 @@ class CfgPatches { class ADDON { author = AUTHOR; - authors[] = {"J.Schmidt"}; + authors[] = {"IDSolutions"}; url = ECSTRING(main,url); name = COMPONENT_NAME; requiredVersion = REQUIRED_VERSION; diff --git a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf index 6ac4f9a..0d0ec69 100644 --- a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf @@ -23,11 +23,89 @@ private _event = _alert get "event"; private _data = _alert get "data"; private _display = displayChild findDisplay 46; +private _uid = GVAR(BankClass) get "uid"; +private _account = GVAR(BankClass) get "account"; +private _cash = _account get "cash"; +private _bank = _account get "bank"; +private _pin = _account get "pin"; + diag_log format ["[FORGE:Client:Bank] Handling UI event: %1 with data: %2", _event, _data]; switch (_event) do { - case "bank::close": { _display closeDisplay 1; }; - default { hint format ["Unhandled UI event: %1", _event]; }; + // ======================================================================== + // DATA REQUESTS + // ======================================================================== + case "bank::sync": { + private _org = 0; // TODO: Get org balance + private _players = SREG(bank,NameRegistry); + private _accountData = createHashMapFromArray [ + ["uid", _uid], + ["cash", _cash], + ["bank", _bank], + ["org", _org], + ["pin", _pin], + ["players", _players] + ]; + + _control ctrlWebBrowserAction ["ExecJS", format ["syncDataFromArma(%1)", toJSON _accountData]]; + }; + + // ======================================================================== + // BANK OPERATIONS + // ======================================================================== + case "bank::deposit": { + private _amount = _data get "amount"; + if (_amount > _cash) exitWith { hint "Insufficient cash!"; }; + + [SRPC(bank,requestDeposit), [_uid, _amount]] call CFUNC(serverEvent); + }; + case "bank::withdraw": { + private _amount = _data get "amount"; + if (_amount > _bank) exitWith { hint "Insufficient funds!"; }; + + [SRPC(bank,requestWithdraw), [_uid, _amount]] call CFUNC(serverEvent); + }; + case "bank::transfer": { + private _amount = _data get "amount"; + private _from = _data get "from"; + private _target = _data get "target"; + + // Prevent self-transfers + if (_target isEqualTo _uid) exitWith { + hint "Cannot transfer to yourself!"; + diag_log "[FORGE:Client:Bank] Attempted self-transfer blocked"; + }; + + private _fromAmount = _account get _from; + if (_amount > _fromAmount) exitWith { hint "Insufficient funds!"; }; + + [SRPC(bank,requestTransfer), [_uid, _target, _from, _amount]] call CFUNC(serverEvent); + }; + case "bank::close": { + _display closeDisplay 1; + }; + + // ======================================================================== + // ATM OPERATIONS + // ======================================================================== + case "atm::withdraw": { + private _amount = _data get "amount"; + if (_amount > _bank) exitWith { hint "Insufficient funds!"; }; + + [SRPC(bank,requestWithdraw), [_uid, _amount]] call CFUNC(serverEvent); + }; + case "atm::deposit": { + private _amount = _data get "amount"; + if (_amount > _cash) exitWith { hint "Insufficient cash!"; }; + + [SRPC(bank,requestDeposit), [_uid, _amount]] call CFUNC(serverEvent); + }; + case "atm::close": { + _display closeDisplay 1; + }; + default { + diag_log format ["[FORGE:Client:Bank] Unhandled UI event: %1", _event]; + }; }; true; diff --git a/arma/client/addons/bank/functions/fnc_initBankClass.sqf b/arma/client/addons/bank/functions/fnc_initBankClass.sqf index edd6258..6776935 100644 --- a/arma/client/addons/bank/functions/fnc_initBankClass.sqf +++ b/arma/client/addons/bank/functions/fnc_initBankClass.sqf @@ -25,19 +25,14 @@ GVAR(BankClass) = createHashMapObject [[ _self set ["isLoaded", false]; _self set ["lastSave", time]; - private _actor = EGVAR(actor,ActorClass) get "actor"; - private _phone_number = _actor get "phone_number"; - private _email = _actor get "email"; - private _account = createHashMap; _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 set ["phone_number", _phone_number]; - _account set ["email", _email]; _self set ["account", _account]; }], @@ -64,7 +59,6 @@ GVAR(BankClass) = createHashMapObject [[ private _account = _self get "account"; private _isLoaded = _self get "isLoaded"; - if !(_isLoaded) then { _self set ["isLoaded", true]; }; if (_data isEqualTo createHashMap) exitWith { diag_log "[FORGE:Client:Bank] Empty data received for sync, skipping."; }; @@ -74,6 +68,8 @@ GVAR(BankClass) = createHashMapObject [[ } forEach _data; _self set ["account", _account]; + + if !(_isLoaded) then { _self set ["isLoaded", true]; }; diag_log "[FORGE:Client:Bank] Sync completed"; }], ["get", { diff --git a/arma/client/addons/bank/functions/fnc_openUI.sqf b/arma/client/addons/bank/functions/fnc_openUI.sqf index bf40ab3..6a65db4 100644 --- a/arma/client/addons/bank/functions/fnc_openUI.sqf +++ b/arma/client/addons/bank/functions/fnc_openUI.sqf @@ -16,6 +16,8 @@ * Public: No */ +params [["_isATM", false, [false]]]; + private _display = (findDisplay 46) createDisplay "RscBank"; private _ctrl = (_display displayCtrl 1002); @@ -25,7 +27,11 @@ _ctrl ctrlAddEventHandler ["JSDialog", { [_control, _isConfirmDialog, _message] call FUNC(handleUIEvents); }]; -_ctrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\index.html)]; +if (_isATM) then { + _ctrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\atm.html)]; +} else { + _ctrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\bank.html)]; +}; // _ctrl ctrlWebBrowserAction ["OpenDevConsole"]; true; diff --git a/arma/client/addons/bank/ui/_site/atm.html b/arma/client/addons/bank/ui/_site/atm.html index 6fba7a5..42b5df9 100644 --- a/arma/client/addons/bank/ui/_site/atm.html +++ b/arma/client/addons/bank/ui/_site/atm.html @@ -5,7 +5,8 @@ ATM - + + @@ -95,19 +92,15 @@ @@ -143,7 +136,7 @@ - - + diff --git a/arma/client/addons/bank/ui/_site/atm.js b/arma/client/addons/bank/ui/_site/atm.js index 89ed2df..2ec2fb6 100644 --- a/arma/client/addons/bank/ui/_site/atm.js +++ b/arma/client/addons/bank/ui/_site/atm.js @@ -3,18 +3,17 @@ * Handles banking transactions with PIN authentication */ -// Mock data -const mockData = { - cash: 2500, - bank: 45750, - pin: '1234' // For demo purposes -}; +// ============================================================================ +// STATE +// ============================================================================ -// State let enteredPin = ''; let currentView = 'welcomeView'; +let previousView = 'welcomeView'; +// ============================================================================ +// VIEW MANAGEMENT +// ============================================================================ -// View Management function showView(viewId) { // Hide all views document.querySelectorAll('.atm-view').forEach(view => { @@ -25,6 +24,7 @@ function showView(viewId) { const view = document.getElementById(viewId); if (view) { view.style.display = 'flex'; + previousView = currentView; currentView = viewId; // Update balance displays when showing certain views @@ -34,7 +34,52 @@ function showView(viewId) { } } -// PIN Entry +// ============================================================================ +// PIN AUTHENTICATION +// ============================================================================ + +function generateKeypad() { + const keypad = document.getElementById('keypad'); + if (!keypad) return; + + // Define keypad layout + const keys = [ + { value: '1', label: '1', type: 'number' }, + { value: '2', label: '2', type: 'number' }, + { value: '3', label: '3', type: 'number' }, + { value: '4', label: '4', type: 'number' }, + { value: '5', label: '5', type: 'number' }, + { value: '6', label: '6', type: 'number' }, + { value: '7', label: '7', type: 'number' }, + { value: '8', label: '8', type: 'number' }, + { value: '9', label: '9', type: 'number' }, + { value: 'clear', label: 'Clear', type: 'action', class: 'key-clear' }, + { value: '0', label: '0', type: 'number' }, + { value: 'enter', label: 'Enter', type: 'action', class: 'key-enter' } + ]; + + // Clear existing keypad + keypad.innerHTML = ''; + + // Generate buttons + keys.forEach(key => { + const button = document.createElement('button'); + button.className = `key-btn${key.class ? ' ' + key.class : ''}`; + button.textContent = key.label; + + // Add click handler + if (key.type === 'number') { + button.onclick = () => enterPin(key.value); + } else if (key.value === 'clear') { + button.onclick = () => clearPin(); + } else if (key.value === 'enter') { + button.onclick = () => submitPin(); + } + + keypad.appendChild(button); + }); +} + function enterPin(digit) { if (enteredPin.length < 4) { enteredPin += digit; @@ -65,7 +110,8 @@ function submitPin() { } // In a real implementation, this would validate with the server - if (enteredPin === mockData.pin) { + const currentState = store.getState(); + if (enteredPin === currentState.pin) { enteredPin = ''; updatePinDisplay(); showView('menuView'); @@ -75,40 +121,48 @@ function submitPin() { } } -// Balance Updates +// ============================================================================ +// BALANCE MANAGEMENT +// ============================================================================ + function updateBalances() { + const currentState = store.getState(); + // Update all balance displays const cashElements = ['cashBalance', 'cashBalanceDetail', 'availableCash']; const bankElements = ['bankBalance', 'bankBalanceDetail']; cashElements.forEach(id => { const el = document.getElementById(id); - if (el) el.textContent = `$${mockData.cash.toLocaleString()}`; + if (el) el.textContent = `$${currentState.accounts.cash.toLocaleString()}`; }); bankElements.forEach(id => { const el = document.getElementById(id); - if (el) el.textContent = `$${mockData.bank.toLocaleString()}`; + if (el) el.textContent = `$${currentState.accounts.bank.toLocaleString()}`; }); const totalEl = document.getElementById('totalBalance'); if (totalEl) { - const total = mockData.cash + mockData.bank; + const total = currentState.accounts.cash + currentState.accounts.bank; totalEl.textContent = `$${total.toLocaleString()}`; } } -// Withdraw Functions +// ============================================================================ +// WITHDRAW OPERATIONS +// ============================================================================ + function withdrawAmount(amount) { - if (amount > mockData.bank) { + const currentState = store.getState(); + + if (amount > currentState.accounts.bank) { showError('Insufficient funds'); return; } - mockData.bank -= amount; - mockData.cash += amount; - - // sendEvent('atm::withdraw', { amount: amount }); + store.dispatch(withdraw(amount)); + sendEvent('atm::withdraw', { amount: amount }); showSuccess(`Withdrew $${amount.toLocaleString()}`); } @@ -121,20 +175,26 @@ function withdrawCustom() { return; } - if (amount > mockData.bank) { + const currentState = store.getState(); + if (amount > currentState.accounts.bank) { showError('Insufficient funds'); return; } - mockData.bank -= amount; - mockData.cash += amount; - - // sendEvent('atm::withdraw', { amount: amount }); + store.dispatch(withdraw(amount)); + sendEvent('atm::withdraw', { amount: amount }); input.value = ''; showSuccess(`Withdrew $${amount.toLocaleString()}`); } -// Deposit Functions +// ============================================================================ +// DEPOSIT OPERATIONS +// ============================================================================ + +/** + * Deposits specified amount into bank account + * @deprecated Use store actions instead + */ function depositAmount() { const input = document.getElementById('depositInput'); const amount = parseFloat(input.value); @@ -144,34 +204,42 @@ function depositAmount() { return; } - if (amount > mockData.cash) { + const currentState = store.getState(); + if (amount > currentState.accounts.cash) { showError('Insufficient cash'); return; } - mockData.cash -= amount; - mockData.bank += amount; - - // sendEvent('atm::deposit', { amount: amount }); + store.dispatch(deposit(amount)); + sendEvent('atm::deposit', { amount: amount }); input.value = ''; showSuccess(`Deposited $${amount.toLocaleString()}`); } - +/** + * Deposits all available cash into bank account + * @deprecated Use store actions instead + */ function depositAll() { - if (mockData.cash <= 0) { + const currentState = store.getState(); + + if (currentState.accounts.cash <= 0) { showError('No cash to deposit'); return; } - const amount = mockData.cash; - mockData.cash = 0; - mockData.bank += amount; - - // sendEvent('atm::deposit', { amount: amount }); + const amount = currentState.accounts.cash; + store.dispatch(deposit(amount)); + sendEvent('atm::deposit', { amount: amount }); showSuccess(`Deposited $${amount.toLocaleString()}`); } -// Transfer Function +// ============================================================================ +// TRANSFER OPERATIONS +// ============================================================================ +/** + * Transfers specified amount from bank account to player account + * @deprecated Use store actions instead + */ function transferFunds() { const playerIdInput = document.getElementById('transferPlayerId'); const amountInput = document.getElementById('transferAmount'); @@ -189,17 +257,17 @@ function transferFunds() { return; } - if (amount > mockData.bank) { + const currentState = store.getState(); + if (amount > currentState.accounts.bank) { showError('Insufficient funds'); return; } - mockData.bank -= amount; - - // sendEvent('atm::transfer', { - // playerId: playerId, - // amount: amount - // }); + store.dispatch(transfer('bank', amount, 'player')); + sendEvent('atm::transfer', { + playerId: playerId, + amount: amount + }); playerIdInput.value = ''; amountInput.value = ''; @@ -207,7 +275,10 @@ function transferFunds() { showSuccess(`Transferred $${amount.toLocaleString()} to Player ${playerId}`); } -// Result Screens +// ============================================================================ +// RESULT SCREENS +// ============================================================================ + function showSuccess(message) { document.getElementById('successMessage').textContent = message; showView('successView'); @@ -219,15 +290,36 @@ function showError(message) { showView('errorView'); } -// Exit ATM +function goBackFromError() { + // If error happened during PIN entry, go back to PIN view + // Otherwise go back to menu view + if (previousView === 'pinView') { + showView('pinView'); + } else { + showView('menuView'); + } +} + +// ============================================================================ +// ATM CONTROL +// ============================================================================ + function exitATM() { enteredPin = ''; updatePinDisplay(); - sendEvent('atm::exit', {}); + sendEvent('atm::close', {}); showView('welcomeView'); } -// Send event to Arma +// ============================================================================ +// ARMA 3 INTEGRATION +// ============================================================================ + +/** + * Sends an event to Arma 3 + * @param {string} event - Event name + * @param {Object} data - Event data + */ function sendEvent(event, data) { if (typeof A3API !== 'undefined') { A3API.SendAlert(JSON.stringify({ @@ -239,20 +331,20 @@ function sendEvent(event, data) { } } -// Update ATM data from external source -function updateATMData(data) { - if (data.cash !== undefined) { - mockData.cash = data.cash; - } - if (data.bank !== undefined) { - mockData.bank = data.bank; - } - updateBalances(); -} +// ============================================================================ +// INITIALIZATION +// ============================================================================ -// Initialize function initATM() { - console.log('ATM interface initializing...'); + // Subscribe to store updates + if (typeof store !== 'undefined') { + store.subscribe(() => { + updateBalances(); + }); + } + + // Generate keypad + generateKeypad(); // Show welcome screen showView('welcomeView'); @@ -260,7 +352,7 @@ function initATM() { // Update initial balances updateBalances(); - console.log('ATM interface initialized'); + console.log('[ATM] Interface initialized'); } // Auto-initialize @@ -270,8 +362,12 @@ if (document.readyState === 'loading') { initATM(); } -// Expose functions globally +// ============================================================================ +// GLOBAL EXPORTS +// ============================================================================ + window.showView = showView; +window.generateKeypad = generateKeypad; window.enterPin = enterPin; window.clearPin = clearPin; window.submitPin = submitPin; @@ -280,5 +376,5 @@ window.withdrawCustom = withdrawCustom; window.depositAmount = depositAmount; window.depositAll = depositAll; window.transferFunds = transferFunds; +window.goBackFromError = goBackFromError; window.exitATM = exitATM; -window.updateATMData = updateATMData; diff --git a/arma/client/addons/bank/ui/_site/bank.css b/arma/client/addons/bank/ui/_site/bank.css new file mode 100644 index 0000000..1c53a31 --- /dev/null +++ b/arma/client/addons/bank/ui/_site/bank.css @@ -0,0 +1,449 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + height: 100vh; + width: 100vw; + background: rgba(0, 0, 0, 0.7); + font-family: Arial, sans-serif; + color: rgba(200, 220, 240, 0.95); + overflow: hidden; +} + +.bank-container { + height: 100vh; + width: 100vw; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.bank-header { + display: flex; + align-items: center; + gap: 1.5rem; + padding: 1.25rem 1.5rem; + background: rgba(15, 20, 30, 0.9); + border: 1px solid rgba(100, 150, 200, 0.4); + border-radius: 4px; + box-shadow: + 0 0 20px rgba(100, 150, 200, 0.15), + 0 4px 16px rgba(0, 0, 0, 0.8); +} + +.bank-logo { + width: 60px; + height: 60px; + background: rgba(20, 30, 45, 0.8); + border: 2px solid rgba(100, 150, 200, 0.5); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; +} + +.logo-icon { + font-size: 2rem; +} + +.bank-info { + flex: 1; +} + +.bank-title { + font-size: 1.5rem; + font-weight: 600; + letter-spacing: 0.5px; + text-transform: uppercase; + color: rgba(200, 220, 255, 1); + margin-bottom: 0.25rem; +} + +.bank-subtitle { + font-size: 0.875rem; + color: rgba(140, 160, 180, 0.8); + letter-spacing: 0.5px; +} + +.header-actions { + display: flex; + gap: 0.75rem; +} + +.action-btn { + padding: 0.625rem 1.25rem; + background: rgba(20, 30, 45, 0.7); + border: 1px solid rgba(100, 150, 200, 0.4); + border-radius: 4px; + color: rgba(200, 220, 240, 0.95); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.5px; + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background: rgba(30, 45, 70, 0.9); + border-color: rgba(150, 200, 255, 0.7); + box-shadow: + 0 0 15px rgba(100, 150, 200, 0.2), + inset 0 0 20px rgba(100, 150, 200, 0.05); + } + + &-primary { + background: rgba(100, 150, 200, 0.2); + border-color: rgba(100, 150, 200, 0.5); + width: 100%; + margin-top: 0.5rem; + + &:hover { + background: rgba(100, 150, 200, 0.3); + border-color: rgba(150, 200, 255, 0.7); + } + } +} + +.close-btn { + border-color: rgba(200, 100, 100, 0.4); + + &:hover { + border-color: rgba(255, 100, 100, 0.7); + box-shadow: + 0 0 15px rgba(200, 100, 100, 0.2), + inset 0 0 20px rgba(200, 100, 100, 0.05); + } +} + +.bank-content { + flex: 1; + display: grid; + grid-template-columns: 300px 1fr 350px; + gap: 1.5rem; + overflow: hidden; +} + +.bank-panel { + background: rgba(15, 20, 30, 0.9); + border: 1px solid rgba(100, 150, 200, 0.4); + border-left: 3px solid rgba(100, 150, 200, 0.5); + border-radius: 4px; + display: flex; + flex-direction: column; + box-shadow: + 0 0 20px rgba(100, 150, 200, 0.1), + 0 4px 16px rgba(0, 0, 0, 0.6); + + &-main { + grid-column: 2; + } +} + +.panel-header { + padding: 1.25rem 1.5rem; + border-bottom: 1px solid rgba(100, 150, 200, 0.2); +} + +.panel-title { + font-size: 1rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(200, 220, 255, 1); +} + +.panel-content { + flex: 1; + padding: 1.5rem; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 8px; + + &-track { + background: rgba(15, 20, 30, 0.5); + border-radius: 4px; + } + + &-thumb { + background: rgba(100, 150, 200, 0.3); + border-radius: 4px; + + &:hover { + background: rgba(100, 150, 200, 0.5); + } + } + } +} + +.account-card { + padding: 1.25rem; + margin-bottom: 1rem; + background: rgba(20, 30, 45, 0.6); + border: 1px solid rgba(100, 150, 200, 0.3); + border-left: 3px solid rgba(100, 150, 200, 0.4); + border-radius: 4px; + transition: all 0.15s ease; + + &:last-child { + margin-bottom: 0; + } + + .account-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + + .account-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + + .account-name { + font-size: 1rem; + font-weight: 600; + color: rgba(200, 220, 255, 1); + } + + .account-type { + font-size: 0.75rem; + color: rgba(140, 160, 180, 0.8); + } + } + } + + .account-balance { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid rgba(100, 150, 200, 0.2); + + .balance-label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(140, 160, 180, 0.8); + } + + .balance-amount { + font-size: 1.25rem; + font-weight: 600; + color: rgba(100, 200, 150, 1); + } + } +} + +.action-section { + margin-bottom: 2rem; + + &:last-child { + margin-bottom: 0; + } + + .section-title { + font-size: 0.875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(180, 200, 220, 0.9); + margin-bottom: 1rem; + } + + .transfer-form { + display: flex; + flex-direction: column; + gap: 1rem; + + .form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; + + .form-label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: rgba(140, 160, 180, 0.9); + } + + .form-select, + .form-input { + padding: 0.75rem 1rem; + background: rgba(20, 30, 45, 0.7); + border: 1px solid rgba(100, 150, 200, 0.3); + border-radius: 4px; + color: rgba(200, 220, 240, 0.95); + font-size: 0.875rem; + transition: all 0.15s ease; + + &:focus { + outline: none; + border-color: rgba(150, 200, 255, 0.6); + box-shadow: + 0 0 15px rgba(100, 150, 200, 0.15), + inset 0 0 20px rgba(100, 150, 200, 0.05); + } + } + + .form-select { + padding-right: 2.5rem; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2396C8FF' d='M1 1l5 5 5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 12px 8px; + cursor: pointer; + } + + .form-input { + &::placeholder { + color: rgba(100, 120, 140, 0.6); + } + } + } + } +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + margin: 0; + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +} + +.quick-actions { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 1rem; + + .quick-action-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + padding: 1.25rem; + background: rgba(20, 30, 45, 0.6); + border: 1px solid rgba(100, 150, 200, 0.3); + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background: rgba(30, 45, 70, 0.8); + border-color: rgba(150, 200, 255, 0.5); + box-shadow: + 0 0 15px rgba(100, 150, 200, 0.15), + inset 0 0 20px rgba(100, 150, 200, 0.05); + } + + .quick-action-label { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + text-align: center; + color: rgba(180, 200, 220, 0.9); + } + } +} + +.transaction-list { + display: flex; + flex-direction: column; + gap: 0.75rem; + + .transaction-item { + padding: 1rem; + background: rgba(20, 30, 45, 0.6); + border: 1px solid rgba(100, 150, 200, 0.2); + border-left: 3px solid rgba(100, 150, 200, 0.4); + border-radius: 4px; + transition: all 0.15s ease; + } + + .transaction-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + + .transaction-type { + padding: 0.25rem 0.625rem; + border-radius: 3px; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; + + &.deposit { + background: rgba(100, 200, 150, 0.2); + border: 1px solid rgba(100, 200, 150, 0.4); + color: rgba(150, 255, 200, 0.9); + } + + &.withdrawal { + background: rgba(200, 150, 100, 0.2); + border: 1px solid rgba(200, 150, 100, 0.4); + color: rgba(255, 200, 150, 0.9); + } + + &.transfer { + background: rgba(100, 150, 200, 0.2); + border: 1px solid rgba(100, 150, 200, 0.4); + color: rgba(150, 200, 255, 0.9); + } + } + + .transaction-amount { + font-size: 1rem; + font-weight: 600; + + &.positive { + color: rgba(100, 200, 150, 1); + } + + &.negative { + color: rgba(220, 100, 100, 1); + } + } + } + + .transaction-details { + display: flex; + justify-content: space-between; + align-items: center; + + .transaction-time { + font-size: 0.7rem; + color: rgba(100, 150, 200, 0.7); + text-transform: uppercase; + letter-spacing: 0.5px; + } + } +} + +@media (max-width: 1400px) { + .bank-content { + grid-template-columns: 280px 1fr 300px; + } +} + +@media (max-width: 1200px) { + .bank-content { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr auto; + } + + .panel-main { + grid-column: 1; + } +} diff --git a/arma/client/addons/bank/ui/_site/index.html b/arma/client/addons/bank/ui/_site/bank.html similarity index 56% rename from arma/client/addons/bank/ui/_site/index.html rename to arma/client/addons/bank/ui/_site/bank.html index 0e64929..c565653 100644 --- a/arma/client/addons/bank/ui/_site/index.html +++ b/arma/client/addons/bank/ui/_site/bank.html @@ -5,7 +5,8 @@ Banking Services - + +

Banking Services

@@ -56,9 +64,8 @@
-