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 @@