feat: Revamp admin panel, store, and arsenal systems

This commit introduces significant changes to the admin panel, store, and arsenal systems, focusing on improved functionality, UI enhancements, and code optimization.

**Admin Panel:**
- Migrated to a web-based UI for improved user experience and maintainability.
- Implemented dynamic player listing with filtering and search capabilities.
- Added functionality for managing player paygrades, sending messages, and transferring funds.
- Integrated server-side events for handling admin actions.

**Store:**
- Added `handleDelivery` event handler.
- Streamlined product selection and purchase processes.
- Improved handling of organization funds and player balances.
- Refactored code for better readability and maintainability.

**Arsenal:**
- Enhanced initialization process with improved data validation.
- Optimized item unlocking logic.

These changes aim to provide a more robust, user-friendly, and efficient experience for both administrators and players.
This commit is contained in:
Jacob Schmidt 2025-05-03 19:33:10 -05:00
parent 61abcf5200
commit 55f60dc71f
28 changed files with 1222 additions and 765 deletions

View File

@ -0,0 +1,23 @@
name: ctrlWebBrowserAction
description: Executes an action on a web browser control
groups:
- GUI Control
syntax:
- call: !Binary [control, action]
ret:
- Nothing
- Nothing
params:
- name: control
type: Control
- name: action
type: String
argument_loc: Local
effect_loc: Local
since:
arma_3:
major: 2
minor: 2
examples:
- <sqf>_control ctrlWebBrowserAction ["ExecJS", "document.getElementById('test').innerHTML = 'Hello World!'"];</sqf>
- <sqf>_control ctrlWebBrowserAction ["LoadURL", "https://community.bistudio.com"];</sqf>

View File

@ -1,7 +1,6 @@
PREP(adminMessage);
PREP(adminPromote);
PREP(adminRefresh); PREP(adminRefresh);
PREP(adminTransfer); PREP(handleTransfer);
PREP(initAdmin); PREP(initAdmin);
PREP(openAdmin); PREP(openAdmin);
PREP(printAddonName); PREP(sendMessage);
PREP(updatePaygrade);

View File

@ -1 +1,96 @@
#include "script_component.hpp" #include "script_component.hpp"
[QGVAR(handleEvents), {
params ["_control", "_isConfirmDialog", "_message"];
diag_log format ["[FORGE: Admin] Received event: %1", _message];
_message = fromJSON _message;
private _event = _message get "event";
private _data = _message get "data";
switch (_event) do {
case "REQUEST_PLAYER_DATA": {
private _playerData = createHashMap;
private _playerList = [];
{
private _player = _x;
private _uid = getPlayerUID _player;
private _name = name _player;
private _paygrade = GETVAR(_player,FORGE_PayGrade,QUOTE(E1));
private _funds = GETVAR(_player,FORGE_Bank,0);
private _playerInfo = createHashMapFromArray [
["uid", _uid],
["name", _name],
["paygrade", _paygrade],
["funds", _funds],
["side", str (side _player)]
];
_playerList pushBack _playerInfo;
} forEach allPlayers;
_playerData set ["players", _playerList];
_control ctrlWebBrowserAction ["ExecJS", format ["handlePlayerDataRequest(%1)", (toJSON _playerList)]];
};
case "REQUEST_PAYGRADE_DATA": {
private _payGrades = (missionConfigFile >> "CfgPaygrades" >> "payGrades") call BIS_fnc_getCfgData;
private _paygradeData = createHashMap;
private _paygradeList = [];
{
private _paygradeInfo = createHashMapFromArray [
["paygrade", _x select 0],
["bonus", _x select 1]
];
_paygradeList pushBack _paygradeInfo;
} forEach _payGrades;
_paygradeData set ["paygrades", _paygradeList];
_control ctrlWebBrowserAction ["ExecJS", format ["handlePaygradeDataRequest(%1)", (toJSON _paygradeList)]];
};
case "BROADCAST_MESSAGE": {
_data params ["_uid", "_message"];
if ((isNil "_message") || {_message isEqualTo ""}) exitWith { hintSilent "Message cannot be empty!"; };
["forge_server_admin_handleEvents", ["sendMessage", [_uid, _message]]] call CFUNC(serverEvent);
};
case "SEND_MESSAGE": {
_data params ["_uid", "_message"];
if ((isNil "_uid") || {_uid isEqualTo ""}) exitWith { hintSilent "You did not select a player!"; };
["forge_server_admin_handleEvents", ["sendMessage", [_uid, _message]]] call CFUNC(serverEvent);
};
case "UPDATE_PAYGRADE": {
private _uid = _data select 0;
private _paygrade = _data select 1;
if ((isNil "_uid") || {_uid isEqualTo ""}) exitWith { hintSilent "You did not select a player!"; };
["forge_server_admin_handleEvents", ["updatePaygrade", [_uid, _paygrade]]] call CFUNC(serverEvent);
};
case "HANDLE_TRANSFER": {
private _condition = _data select 0;
private _amount = _data select 1;
private _uid = _data select 2;
["forge_server_admin_handleEvents", ["handleTransfer", [_condition, _amount, _uid]]] call CFUNC(serverEvent);
};
case "ADVANCE_ALL": {
private _amount = _data select 0;
["forge_server_admin_handleEvents", ["advanceAll", [_amount]]] call CFUNC(serverEvent);
};
case "HANDLE_PAYDAY": {
["forge_server_admin_handleEvents", ["handlePayday"]] call CFUNC(serverEvent);
};
default {
diag_log format ["[FORGE: Admin] Unhandled event: %1", _event];
};
};
}] call CFUNC(addEventHandler);

View File

@ -16,3 +16,4 @@ class CfgPatches {
#include "CfgEventHandlers.hpp" #include "CfgEventHandlers.hpp"
#include "ui\RscCommon.hpp" #include "ui\RscCommon.hpp"
#include "ui\RscAdmin.hpp" #include "ui\RscAdmin.hpp"
#include "ui\RscWebAdmin.hpp"

View File

@ -1,49 +0,0 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_adminMessage
* Author: IDSolutions
*
* [Description]
* Admin Message Menu
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* None
*
* Public: Yes
*/
private ["_data", "_dialog", "_list", "_target", "_targetValue", "_textBox", "_textMessage"];
_dialog = findDisplay 202303;
_list = _dialog displayCtrl 2023001;
_textBox = _dialog displayCtrl 2023006;
_targetValue = lbCurSel _list;
_data = _list lbData _targetValue;
if ((isNil {_data})) exitWith { hintSilent "You did not select a player!" };
{
if (str (name (_x)) == str _data) then {
_target = _x;
};
} forEach playableUnits;
hintSilent format ["Player Selected. You have selected %1", _target];
if (isNil "_target") then {
hintSilent "Please Select an Active Player First!"
} else {
_textMessage = ctrlText _textBox;
[_target, _textMessage] remoteExec ["forge_server_misc_fnc_textMessage", 2];
// [format ["Message sent to %1: <br/>%2", _target, _textMessage], "blue-grey", 3] call EFUNC(misc,notify);
};
["dummy"] call FUNC(adminRefresh);

View File

@ -1,51 +0,0 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_adminPromote
* Author: IDSolutions
*
* [Description]
* Admin Promote Menu
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* None
*
* Public: Yes
*/
params [["_condition", "", [""]]];
private ["_data", "_data2", "_dialog", "_list", "_list2", "_paygrade", "_rankValue", "_target", "_targetValue"];
_dialog = findDisplay 202303;
_list = _dialog displayCtrl 2023001;
_list2 = _dialog displayCtrl 2023003;
_targetValue = lbCurSel _list;
_rankValue = lbCurSel _list2;
_data = _list lbData _targetValue;
_data2 = call compile format ["%1", (_list2 lbData _rankValue)];
_paygrade = _data2 select 0;
if ((isNil {_data})) exitWith { hintSilent "You did not select a player!" };
{
if (str (name (_x)) == str _data) then {
_target = _x;
};
} forEach playableUnits;
switch (_condition) do {
case ("promote"): {
SETPVAR(_target,FORGE_PayGrade,_paygrade)
};
case ("demote"): {
SETPVAR(_target,FORGE_PayGrade,_paygrade)
};
};
["dummy"] call FUNC(adminRefresh);

View File

@ -5,74 +5,39 @@
* Author: IDSolutions * Author: IDSolutions
* *
* [Description] * [Description]
* Admin Refresh Menu * Refreshes the admin interface player list and resets input fields.
* This function populates the player list with names and paygrades,
* storing player UIDs as data for each entry. Only shows players
* on the same side as the admin.
* *
* Arguments: * Arguments:
* None * 0: Dummy <ANY> - Optional parameter, not used (for compatibility with event handlers)
* *
* Return Value: * Return Value:
* None * None
* *
* Examples: * Examples:
* None * [] call forge_client_admin_fnc_adminRefresh;
* ["dummy"] call forge_client_admin_fnc_adminRefresh;
* *
* Public: Yes * Public: No - Called from admin dialog controls
*/ */
params [["_condition", "", [""]], ["_amount", 0, [0]]];
private _store = missionNamespace getVariable ["FORGE_ORG_STORE_REG", createHashMap];
private _org = _store call ["getOrg", []];
if (isNil "_org") exitWith { ["You are not in an organization!", "warning", 3] call EFUNC(misc,notify) };
private _orgFunds = _org get "funds";
private _newFunds = 0;
private _dialog = findDisplay 202303; private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001; private _list = _dialog displayCtrl 2023001;
switch (_condition) do {
case ("deduct"): {
_newFunds = _orgFunds - _amount;
ctrlSetText [2023002, format ["$%1", (_newFunds call EFUNC(misc,formatNumber))]];
};
case ("advance"): {
_newFunds = _orgFunds + _amount;
ctrlSetText [2023002, format ["$%1", (_newFunds call EFUNC(misc,formatNumber))]];
};
default {
lbClear _list;
{
if (str (side _x) == str (playerSide)) then {
private _name = name (_x);
private _defaultPaygrade = "E1";
private _paygrade = GETVAR(_x,FORGE_PayGrade,_defaultPaygrade);
private _index = _list lbAdd format["%1 - %2", _name, _paygrade];
_list lbSetData [_index, name (_x)];
};
} forEach playableUnits;
lbSetCurSel [2023001, 0];
ctrlSetText [2023005, ""];
ctrlSetText [2023006, ""];
};
};
lbClear _list; lbClear _list;
{ {
if (str (side _x) == str (playerSide)) then { if (str (side _x) == str (playerSide)) then {
private _name = name (_x); private _name = name (_x);
private _defaultPaygrade = "E1"; private _paygrade = GETVAR(_x,FORGE_PayGrade,QUOTE(E1));
private _paygrade = GETVAR(_x,FORGE_PayGrade,_defaultPaygrade);
private _index = _list lbAdd format["%1 - %2", _name, _paygrade]; private _index = _list lbAdd format["%1 - %2", _name, _paygrade];
_list lbSetData [_index, name (_x)]; _list lbSetData [_index, getPlayerUID _x];
}; };
} forEach playableUnits; } forEach allPlayers;
lbSetCurSel [2023001, 0]; lbSetCurSel [2023001, 0];
ctrlSetText [2023005, ""]; ctrlSetText [2023005, ""];

View File

@ -1,118 +0,0 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_adminTransfer
* Author: IDSolutions
*
* [Description]
* Admin Transfer Menu
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* None
*
* Public: Yes
*/
params [["_condition", "", [""]]];
private _store = missionNamespace getVariable ["FORGE_ORG_STORE_REG", createHashMap];
private _org = _store call ["getOrg", []];
if (isNil "_org") exitWith { ["You are not in an organization!", "warning", 3] call EFUNC(misc,notify) };
private _orgFunds = _org get "funds";
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _targetValue = lbCurSel _list;
private _data = _list lbData _targetValue;
private _amount = round (parseNumber (ctrlText 2023005));
if ((isNil {_data})) exitWith { hint "You did not select a player!" };
{
if (str (name (_x)) == str _data) then {
private _target = _x;
};
} count playableUnits;
switch (_condition) do {
case ("advance"): {
private _bank = GETVAR(_target,FORGE_Bank,0);
private _newBalance = _bank + _amount;
if (_amount > _orgFunds) exitWith { ["Not enough money in the organization's account!", "warning", 3] call EFUNC(misc,notify) };
SETPVAR(_target,FORGE_Bank,_newBalance);
["deduct", _amount] call FUNC(adminRefresh);
_store call ["updateFunds", -_amount];
};
case ("advanceAll"): {
private _count = count playableUnits;
if ((10000 * _count) > _orgFunds) exitWith { ["Not enough money in the organization's account!", "warning", 3] call EFUNC(misc,notify) };
{
private _bank = GETVAR(_x,FORGE_Bank,0);
private _newBalance = _bank + 10000;
SETPVAR(_x,FORGE_Bank,_newBalance);
} count playableUnits;
["deduct", (10000 * _count)] call FUNC(adminRefresh);
_store call ["updateFunds", -(10000 * _count)];
};
case ("deduct"): {
private _bank = GETVAR(_target,FORGE_Bank,0);
private _newBalance = _bank - _amount;
if (_amount > _bank) exitWith { ["Not enough money in the player's account!", "warning", 3] call EFUNC(misc,notify) };
SETPVAR(_target,FORGE_Bank,_newBalance);
["advance", _amount] call FUNC(adminRefresh);
_store call ["updateFunds", _amount];
};
case ("payday"): {
private _totalPayment = 0;
private _paymentToDo = [];
private _payGrades = (missionConfigFile >> "CfgPaygrades" >> "payGrades") call BIS_fnc_getCfgData;
{
private _player = _x;
private _payGrade = GETVAR(_player,FORGE_PayGrade,nil);
{
_x params ["_payGradeIndex", "_payGradeBonus"];
if ((_x select 0) == _payGrade) then {
_paymentToDo pushBack [_player, _payGradeBonus];
_totalPayment = _totalPayment + _payGradeBonus;
};
} forEach _payGrades;
} count playableUnits;
if (_totalPayment > _orgFunds) exitWith { ["Not enough money in the organization's account!", "warning", 3] call EFUNC(misc,notify) };
{
_x params ["_player", "_bonus"];
private _bank = GETVAR(_player,FORGE_Bank,0);
private _newBalance = _bank + _bonus;
SETPVAR(_player,FORGE_Bank,_newBalance);
} count _paymentToDo;
["deduct", _totalPayment] call FUNC(adminRefresh);
_store call ["updateFunds", -_totalPayment];
};
};
ctrlSetText [2023005, ""];

View File

@ -0,0 +1,40 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_handleTransfer
* Author: IDSolutions
*
* [Description]
* Handles fund transfers through the admin interface.
* This function retrieves the selected player's UID and amount
* from the admin dialog, then sends it to the server-side admin store.
* Supports multiple transfer types: advance (to single player),
* deduct (from single player), advanceAll (to all players),
* and payday (distribute based on paygrade).
*
* Arguments:
* 0: Condition <STRING> - The type of transfer to perform ("advance", "deduct", "advanceAll", "payday")
*
* Return Value:
* None
*
* Examples:
* ["advance"] call forge_client_admin_fnc_handleTransfer;
* ["payday"] call forge_client_admin_fnc_handleTransfer;
*
* Public: No - Called from admin dialog controls
*/
params [["_condition", "", [""]]];
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _index = lbCurSel _list;
private _uid = _list lbData _index;
private _amount = round (parseNumber (ctrlText 2023005));
if (_condition in ["advance", "deduct"] && ((isNil "_uid") || { _uid isEqualTo "" })) exitWith { hint "You did not select a player!"; };
["forge_server_admin_handleEvents", ["handleTransfer", [_condition, _amount, _uid]]] call CFUNC(serverEvent);
ctrlSetText [2023005, ""];

View File

@ -19,33 +19,47 @@
* Public: Yes * Public: Yes
*/ */
disableSerialization; private _productVersion = productVersion;
createDialog "RscAdmin"; private _steamBranchName = _productVersion select 8;
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _list2 = _dialog displayCtrl 2023003;
{
if (str (side _x) == str (playerSide)) then {
private _name = name (_x);
private _defaultPaygrade = "E1";
private _payGrade = GETVAR(_x,FORGE_PayGrade,_defaultPaygrade);
private _index = _list lbAdd format["%1 - %2", _name, _payGrade];
_list lbSetData [_index, _name];
};
} count (allPlayers);
lbSetCurSel [2023001, 0];
private _payGrades = (missionConfigFile >> "CfgPaygrades" >> "payGrades") call BFUNC(getCfgData); private _payGrades = (missionConfigFile >> "CfgPaygrades" >> "payGrades") call BFUNC(getCfgData);
{ if (_steamBranchName == "profiling") then {
private _display = (findDisplay 46) createDisplay "RscWebAdmin";
private _ctrl = _display displayCtrl 2025;
_ctrl ctrlAddEventHandler ["JSDialog", {
params ["_control", "_isConfirmDialog", "_message"];
[QGVAR(handleEvents), [_control, _isConfirmDialog, _message]] call CFUNC(localEvent);
}];
_ctrl ctrlWebBrowserAction ["LoadFile", QUOTE(PATHTOF(ui\_site\index.html))];
} else {
disableSerialization;
createDialog "RscAdmin";
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _list2 = _dialog displayCtrl 2023003;
{
if (str (side _x) == str (playerSide) && isPlayer _x) then {
private _name = name (_x);
private _uid = getPlayerUID _x;
private _payGrade = GETVAR(_x,FORGE_PayGrade,QUOTE(E1));
private _index = _list lbAdd format["%1 - %2", _name, _payGrade];
_list lbSetData [_index, _uid];
};
} count (allPlayers);
lbSetCurSel [2023001, 0];
{
private _index = _list2 lbAdd format ["%1 - $%2", (_x select 0), ((_x select 1) call EFUNC(misc,formatNumber))]; private _index = _list2 lbAdd format ["%1 - $%2", (_x select 0), ((_x select 1) call EFUNC(misc,formatNumber))];
_list2 lbSetData [_index, str _x]; _list2 lbSetData [_index, str _x];
} forEach _payGrades; } forEach _payGrades;
lbSetCurSel [2023003, 0]; lbSetCurSel [2023003, 0];
ctrlSetText [2023002, format ["$%1", (companyFunds call EFUNC(misc,formatNumber))]]; ctrlSetText [2023002, format ["$%1", (companyFunds call EFUNC(misc,formatNumber))]];
};

View File

@ -1,22 +0,0 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_printAddonName
* Author: IDSolutions
*
* [Description]
* Prints the name of the addon to the system chat.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* None
*
* Public: No
*/
systemChat format ["Thank you for using the %1", 'ADDON'];

View File

@ -0,0 +1,37 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_sendMessage
* Author: IDSolutions
*
* [Description]
* Sends a message to a selected player through the admin interface.
* This function retrieves the selected player's UID and message content
* from the admin dialog, then sends it to the server-side admin store.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* [] call forge_client_admin_fnc_sendMessage;
*
* Public: No - Called from admin dialog controls
*/
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _control = _dialog displayCtrl 2023006;
private _index = lbCurSel _list;
private _uid = _list lbData _index;
private _message = ctrlText _control;
if ((isNil "_uid") || {_uid isEqualTo ""}) exitWith { hintSilent "You did not select a player!"; };
["forge_server_admin_handleEvents", ["sendMessage", [_uid, _message]]] call CFUNC(serverEvent);
hintSilent format ["Message sent to UID %1: %2", _uid, _message];
["dummy"] call FUNC(adminRefresh);

View File

@ -0,0 +1,37 @@
#include "..\script_component.hpp"
/*
* Function: forge_client_admin_fnc_updatePaygrade
* Author: IDSolutions
*
* [Description]
* Updates a player's paygrade in the server's admin store.
* This function retrieves the selected player's UID and the target paygrade
* from the admin dialog, then sends it to the server-side admin store.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Examples:
* [] call forge_client_admin_fnc_updatePaygrade;
*
* Public: No - Called from admin dialog controls
*/
private _dialog = findDisplay 202303;
private _list = _dialog displayCtrl 2023001;
private _list2 = _dialog displayCtrl 2023003;
private _targetIndex = lbCurSel _list;
private _rankIndex = lbCurSel _list2;
private _uid = _list lbData _targetIndex;
private _rankData = call compile format ["%1", (_list2 lbData _rankIndex)];
private _paygrade = _rankData select 0;
if ((isNil "_uid") || {_uid isEqualTo ""}) exitWith { hintSilent "You did not select a player!" };
["forge_server_admin_handleEvents", ["updatePaygrade", [_uid, _paygrade]]] call CFUNC(serverEvent);
["dummy"] call FUNC(adminRefresh);

View File

@ -83,7 +83,7 @@ class RscAdmin {
class RscAdminPromote: RscButton { class RscAdminPromote: RscButton {
idc = -1; idc = -1;
colorText[] = {1,1,1,1}; colorText[] = {1,1,1,1};
onButtonClick = "['promote'] call forge_client_admin_fnc_adminPromote;"; onButtonClick = "[] call forge_client_admin_fnc_updatePaygrade;";
soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick",0.09,1}; soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick",0.09,1};
text = "Promote"; text = "Promote";
x = "0.675 * safezoneW + safezoneX"; x = "0.675 * safezoneW + safezoneX";
@ -163,7 +163,7 @@ class RscAdmin {
class RscAdminSend: RscButton { class RscAdminSend: RscButton {
idc = -1; idc = -1;
colorText[] = {1,1,1,1}; colorText[] = {1,1,1,1};
onButtonClick = "[] call forge_client_admin_fnc_adminMessage;"; onButtonClick = "[] call forge_client_admin_fnc_sendMessage;";
soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick",0.09,1}; soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick",0.09,1};
text = "Send Message"; text = "Send Message";
x = "0.6125 * safezoneW + safezoneX"; x = "0.6125 * safezoneW + safezoneX";

View File

@ -0,0 +1,17 @@
class RscWebAdmin {
idd = 20250502;
fadein = 0;
fadeout = 0;
duration = 1e+011;
class controls {
class Background: RscText {
type = 106;
idc = 2025;
x = "safeZoneY * (pixelW/pixelH) * 2.975";
y = "safeZoneY + (safeZoneH * 0.05)";
w = "safeZoneW * (pixelW/pixelH) * 1.17";
h = "safeZoneH * 0.875";
};
};
};

View File

@ -1,22 +1,44 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <!--
<meta name="viewport" content="width=device-width, initial-scale=1.0"> Dynamic Resource Loading
<title>Forge Admin Panel</title> The following script loads CSS and JavaScript files dynamically using the A3API
<link rel="preconnect" href="https://fonts.googleapis.com"> This approach is used instead of static HTML imports to work with Arma 3's file system
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <script>
<link rel="stylesheet" href="styles.css"> Promise.all([
<script src="script.js" defer></script> // Load CSS file
A3API.RequestFile("z\\forge_client\\addons\\admin\\ui\\_site\\styles.css"),
// Load JavaScript file
A3API.RequestFile("z\\forge_client\\addons\\admin\\ui\\_site\\script.js")
]).then(([css, js]) => {
// Apply CSS
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// Load and execute JavaScript
const script = document.createElement('script');
script.text = js;
document.head.appendChild(script);
// Initialize the admin interface
initializeAdmin();
});
</script>
</head> </head>
<body> <body>
<!--
Header Section
Contains the main title and server statistics
-->
<header> <header>
<div class="header-content"> <div class="header-content">
<h1>Admin Panel</h1> <h1>Admin Panel</h1>
<!-- Server Statistics Display -->
<div class="admin-stats"> <div class="admin-stats">
<div class="stat-item"> <div class="stat-item">
<div class="stat-icon">👥</div>
<div class="stat-info"> <div class="stat-info">
<span class="stat-label">Online Players</span> <span class="stat-label">Online Players</span>
<span class="stat-value" id="playerCount">0</span> <span class="stat-value" id="playerCount">0</span>
@ -24,7 +46,6 @@
</div> </div>
<div class="stat-divider"></div> <div class="stat-divider"></div>
<div class="stat-item"> <div class="stat-item">
<div class="stat-icon">👑</div>
<div class="stat-info"> <div class="stat-info">
<span class="stat-label">Staff Online</span> <span class="stat-label">Staff Online</span>
<span class="stat-value" id="staffCount">0</span> <span class="stat-value" id="staffCount">0</span>
@ -34,10 +55,18 @@
</div> </div>
</header> </header>
<!--
Main Content Area
Contains all admin functionality sections
-->
<main class="container"> <main class="container">
<div class="sections-grid"> <div class="sections-grid">
<!--
Admin Action Sections
Left column with various global admin actions
-->
<div class="action-sections"> <div class="action-sections">
<!-- Global Actions Section --> <!-- Global Payday Action -->
<div class="admin-section"> <div class="admin-section">
<h2>Global Actions</h2> <h2>Global Actions</h2>
<div class="form-group"> <div class="form-group">
@ -47,7 +76,7 @@
<button class="submit-btn" onclick="Payday()">Payday</button> <button class="submit-btn" onclick="Payday()">Payday</button>
</div> </div>
<!-- Give All Section --> <!-- Give All Money Action -->
<div class="admin-section"> <div class="admin-section">
<h2>Give All Money</h2> <h2>Give All Money</h2>
<div class="form-group"> <div class="form-group">
@ -57,7 +86,7 @@
<button class="submit-btn" onclick="giveAllMoney()">Give to All</button> <button class="submit-btn" onclick="giveAllMoney()">Give to All</button>
</div> </div>
<!-- Message System Section --> <!-- Broadcast Message System -->
<div class="admin-section"> <div class="admin-section">
<h2>Broadcast Message</h2> <h2>Broadcast Message</h2>
<div class="form-group"> <div class="form-group">
@ -68,24 +97,37 @@
</div> </div>
</div> </div>
<!-- Player List Section --> <!--
Player List Section
Right column showing all players with filtering options
-->
<div class="admin-section player-list-section"> <div class="admin-section player-list-section">
<!-- Search and Filter Controls -->
<div class="search-bar"> <div class="search-bar">
<input type="text" id="playerSearch" class="search-input" placeholder="Search players..."> <input type="text" id="playerSearch" class="search-input" placeholder="Search players...">
<div class="filter-bar"> <div class="filter-bar">
<button class="filter-btn active" data-filter="all">All</button> <button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="online">Online</button>
<button class="filter-btn" data-filter="staff">Staff</button> <button class="filter-btn" data-filter="staff">Staff</button>
<button class="filter-btn" data-filter="blufor">BLUFOR</button>
<button class="filter-btn" data-filter="opfor">OPFOR</button>
<button class="filter-btn" data-filter="independent">Independent</button>
<button class="filter-btn" data-filter="civilian">Civilian</button>
</div> </div>
</div> </div>
<!-- Dynamic Player List Container -->
<div class="player-list" id="playerList"> <div class="player-list" id="playerList">
<!-- Players will be populated dynamically --> <!-- Players will be populated dynamically via JavaScript -->
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<!-- Message Modal --> <!--
Modal Dialogs
Hidden by default, shown when specific actions are triggered
-->
<!-- Message Modal - For sending messages to individual players -->
<div id="messageModal" class="modal"> <div id="messageModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -100,7 +142,7 @@
</div> </div>
</div> </div>
<!-- Money Modal --> <!-- Money Modal - For modifying player funds -->
<div id="moneyModal" class="modal"> <div id="moneyModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">

View File

@ -1,60 +1,152 @@
// Simulated admin data - this would be replaced with actual game data /**
* Admin Panel Management Script
* This script handles the admin panel functionality for the Arma 3 game interface.
* It provides player management, money operations, messaging, and other admin functions.
*/
//=============================================================================
// #region DATA STRUCTURES AND VARIABLES
//=============================================================================
/**
* Admin data structure - will be populated from the game
* Contains player information and payday amount configuration
*/
let adminData = { let adminData = {
players: [ players: [], // List of all players with their details
{ paydayAmounts: {} // Map of paygrade to bonus amount
id: 1,
name: "John_Doe",
rank: 5,
money: 50000,
status: "online"
},
{
id: 2,
name: "Jane_Smith",
rank: 3,
money: 25000,
status: "online"
},
{
id: 3,
name: "Mike_Johnson",
rank: 1,
money: 10000,
status: "offline"
}
],
paydayAmounts: {
1: 1000, // Rank 1 (Player) payday amount
2: 2000, // Rank 2 payday amount
3: 3000, // Rank 3 payday amount
4: 4000, // Rank 4 payday amount
5: 5000 // Rank 5 (Admin) payday amount
},
maxRank: 5
}; };
/**
* Currently selected player ID for operations that require a player selection
* @type {string|null}
*/
let selectedPlayerId = null; let selectedPlayerId = null;
// Initialize the admin panel // #endregion
//=============================================================================
// #region INITIALIZATION AND DATA REQUESTS
//=============================================================================
/**
* Initialize the admin panel
* Sets up the UI, requests initial data from the game engine
*/
function initializeAdmin() { function initializeAdmin() {
updateStats(); updateStats();
setupFilterListeners(); setupFilterListeners();
requestPlayerData();
requestPaygradeData();
}
/**
* Request player data from the game engine
* Sends an event to fetch current player information
*/
function requestPlayerData() {
const message = {
event: "REQUEST_PLAYER_DATA",
data: {}
};
// Send request to the game engine
A3API.SendAlert(JSON.stringify(message));
}
/**
* Request paygrade data from the game engine
* Sends an event to fetch current paygrade configuration
*/
function requestPaygradeData() {
const message = {
event: "REQUEST_PAYGRADE_DATA",
data: {}
};
// Send request to the game engine
A3API.SendAlert(JSON.stringify(message));
}
/**
* Set up a timer to periodically refresh player data
* Ensures the admin panel shows up-to-date information
*/
function setupRefreshTimer() {
setInterval(requestPlayerData, 30000); // Refresh every 30 seconds
}
// #endregion
//=============================================================================
// #region DATA HANDLERS
//=============================================================================
/**
* Handle paygrade data received from the game engine
* Processes the paygrade list and updates the UI accordingly
*
* @param {Array} paygradeList - List of paygrade objects with paygrade and bonus properties
*/
function handlePaygradeDataRequest(paygradeList) {
try {
// Convert the paygrade list to a map for easier lookup
const paygradeMap = {};
paygradeList.forEach(item => {
paygradeMap[item.paygrade] = item.bonus;
});
adminData.paydayAmounts = paygradeMap;
// Update the player list if we already have player data
if (adminData.players.length > 0) {
updatePlayerList();
}
console.log("Paygrade data updated successfully");
} catch (error) {
console.error("Error updating paygrade data:", error);
}
}
/**
* Handle player data received from the game engine
* Updates the admin panel with current player information
*
* @param {Array} playerList - List of player objects with their details
*/
function handlePlayerDataRequest(playerList) {
adminData.players = playerList;
updateStats();
updatePlayerList(); updatePlayerList();
} }
// Update header statistics // #endregion
//=============================================================================
// #region UI UPDATES AND DISPLAY
//=============================================================================
/**
* Update header statistics
* Shows counts of online players and staff
*/
function updateStats() { function updateStats() {
const onlinePlayers = adminData.players.filter(p => p.status === "online").length; const onlinePlayers = adminData.players.length;
const onlineStaff = adminData.players.filter(p => p.status === "online" && p.rank > 1).length; const onlineStaff = adminData.players.filter(p => p.paygrade !== "E1").length;
document.getElementById('playerCount').textContent = onlinePlayers; document.getElementById('playerCount').textContent = onlinePlayers;
document.getElementById('staffCount').textContent = onlineStaff; document.getElementById('staffCount').textContent = onlineStaff;
} }
// Set up filter button listeners /**
* Set up filter button listeners
* Configures the filter buttons and search functionality
*/
function setupFilterListeners() { function setupFilterListeners() {
const filterButtons = document.querySelectorAll('.filter-btn'); const filterButtons = document.querySelectorAll('.filter-btn');
// Set up filter button click handlers
filterButtons.forEach(button => { filterButtons.forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active')); filterButtons.forEach(btn => btn.classList.remove('active'));
@ -71,15 +163,26 @@ function setupFilterListeners() {
}); });
} }
// Filter players based on category and search term /**
* Filter players based on category and search term
*
* @param {string} filter - The filter category (all, staff, blufor, etc.)
* @param {string} searchTerm - Optional search term to filter by name
*/
function filterPlayers(filter, searchTerm = '') { function filterPlayers(filter, searchTerm = '') {
let filteredPlayers = adminData.players; let filteredPlayers = adminData.players;
// Apply category filter // Apply category filter
if (filter === 'online') { if (filter === 'staff') {
filteredPlayers = filteredPlayers.filter(p => p.status === 'online'); filteredPlayers = filteredPlayers.filter(p => p.paygrade !== "E1");
} else if (filter === 'staff') { } else if (filter === 'blufor') {
filteredPlayers = filteredPlayers.filter(p => p.rank > 1); filteredPlayers = filteredPlayers.filter(p => p.side === "WEST");
} else if (filter === 'opfor') {
filteredPlayers = filteredPlayers.filter(p => p.side === "EAST");
} else if (filter === 'independent') {
filteredPlayers = filteredPlayers.filter(p => p.side === "GUER");
} else if (filter === 'civilian') {
filteredPlayers = filteredPlayers.filter(p => p.side === "CIV");
} }
// Apply search filter // Apply search filter
@ -93,61 +196,127 @@ function filterPlayers(filter, searchTerm = '') {
updatePlayerList(filteredPlayers); updatePlayerList(filteredPlayers);
} }
// Update the player list display /**
* Update the player list display
* Renders the filtered player list with all relevant information
*
* @param {Array} players - List of player objects to display, defaults to all players
*/
function updatePlayerList(players = adminData.players) { function updatePlayerList(players = adminData.players) {
const playerList = document.getElementById('playerList'); const playerList = document.getElementById('playerList');
playerList.innerHTML = players.map(player => { playerList.innerHTML = players.map(player => {
const paydayAmount = adminData.paydayAmounts[player.rank] || 1000; // Default to 1000 if rank not found const paydayAmount = adminData.paydayAmounts[player.paygrade] || 1000;
const rankClass = getRankClass(player.paygrade);
return ` return `
<div class="player-item" data-id="${player.id}"> <div class="player-item" data-id="${player.uid}">
<div class="player-info"> <div class="player-info">
<span class="player-name">${player.name}</span> <span class="player-name">${player.name}</span>
<span class="player-rank rank-${player.rank}">Rank ${player.rank}</span> <span class="player-rank rank-${rankClass}">${player.paygrade}</span>
<span class="player-money">$${player.money.toLocaleString()}</span> <span class="player-money">${parseInt(player.funds).toLocaleString()}</span>
<span class="player-payday">Payday: $${paydayAmount.toLocaleString()}</span> <span class="player-payday">Payday: ${paydayAmount.toLocaleString()}</span>
<span class="player-side side-${player.side.toLowerCase()}">${player.side}</span>
</div> </div>
<div class="player-actions"> <div class="player-actions">
${player.rank < adminData.maxRank ? ` <button class="action-btn promote-btn" onclick="updatePaygrade('${player.uid}', true)">
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
Promote Promote
</button> </button>
` : ''} <button class="action-btn demote-btn" onclick="updatePaygrade('${player.uid}', false)">
${player.rank > 1 ? `
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
Demote Demote
</button> </button>
` : ''} <button class="action-btn message-btn" onclick="openMessageModal('${player.uid}', '${player.name}')">Message</button>
<button class="action-btn message-btn" onclick="openMessageModal(${player.id})">Message</button> <button class="action-btn" onclick="openMoneyModal('${player.uid}')">Modify Money</button>
<button class="action-btn" onclick="openMoneyModal(${player.id})">Modify Money</button>
</div> </div>
</div> </div>
`}).join(''); `}).join('');
} }
// Rank management functions /**
function promotePlayer(playerId) { * Helper function to determine rank class based on paygrade
const player = adminData.players.find(p => p.id === playerId); * Used for styling different ranks with appropriate CSS classes
if (player && player.rank < adminData.maxRank) { *
player.rank++; * @param {string} paygrade - The player's paygrade code
updatePlayerList(); * @returns {string} CSS class name for the rank
*/
function getRankClass(paygrade) {
if (paygrade.startsWith('E')) {
return 'enlisted';
} else if (paygrade.startsWith('WO')) {
return 'warrant';
} else if (paygrade.startsWith('O') ||
paygrade.startsWith('1') ||
paygrade.startsWith('2') ||
paygrade.startsWith('C') ||
paygrade.startsWith('M')) {
return 'officer';
} else {
return 'enlisted'; // Default
} }
} }
function demotePlayer(playerId) { // #endregion
const player = adminData.players.find(p => p.id === playerId);
if (player && player.rank > 1) { //=============================================================================
player.rank--; // #region RANK MANAGEMENT
updatePlayerList(); //=============================================================================
/**
* Update a player's paygrade (promote or demote)
*
* @param {string} uid - Player's unique identifier
* @param {boolean} isPromotion - True for promotion, false for demotion
*/
function updatePaygrade(uid, isPromotion) {
const player = adminData.players.find(p => p.uid === uid);
if (!player) return;
// Use the paygrades from the configuration
const paygrades = Object.keys(adminData.paydayAmounts);
paygrades.sort((a, b) => adminData.paydayAmounts[a] - adminData.paydayAmounts[b]); // Sort by payment amount
const currentIndex = paygrades.indexOf(player.paygrade);
let newPaygrade;
if (isPromotion && currentIndex < paygrades.length - 1) {
newPaygrade = paygrades[currentIndex + 1];
} else if (!isPromotion && currentIndex > 0) {
newPaygrade = paygrades[currentIndex - 1];
} else {
return; // Can't promote/demote further
} }
const message = {
event: "UPDATE_PAYGRADE",
data: [uid, newPaygrade]
};
A3API.SendAlert(JSON.stringify(message));
// Optimistic update
player.paygrade = newPaygrade;
updatePlayerList();
} }
// Money management functions // #endregion
function openMoneyModal(playerId) {
selectedPlayerId = playerId; //=============================================================================
// #region MONEY MANAGEMENT
//=============================================================================
/**
* Open the money modification modal for a player
*
* @param {string} uid - Player's unique identifier
*/
function openMoneyModal(uid) {
selectedPlayerId = uid;
const modal = document.getElementById('moneyModal'); const modal = document.getElementById('moneyModal');
modal.style.display = 'block'; modal.style.display = 'block';
} }
/**
* Close the money modification modal
*/
function closeMoneyModal() { function closeMoneyModal() {
const modal = document.getElementById('moneyModal'); const modal = document.getElementById('moneyModal');
modal.style.display = 'none'; modal.style.display = 'none';
@ -155,39 +324,93 @@ function closeMoneyModal() {
selectedPlayerId = null; selectedPlayerId = null;
} }
/**
* Give money to the selected player
*/
function giveMoney() { function giveMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value); const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) { if (amount && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId); handleTransferFunds("advance", amount, selectedPlayerId);
if (player) {
player.money += amount;
updatePlayerList();
closeMoneyModal(); closeMoneyModal();
} }
}
} }
/**
* Give money to all players
*/
function giveAllMoney() {
const amount = parseInt(document.getElementById('giveAllAmount').value);
const message = {
event: "ADVANCE_ALL",
data: [amount]
}
A3API.SendAlert(JSON.stringify(message));
// Request updated player data after giving money to all players
setTimeout(requestPlayerData, 500); // Short delay to allow server processing
}
/**
* Take money from the selected player
*/
function takeMoney() { function takeMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value); const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) { if (amount && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId); handleTransferFunds("deduct", amount, selectedPlayerId);
if (player) {
player.money = Math.max(0, player.money - amount);
updatePlayerList();
closeMoneyModal(); closeMoneyModal();
} }
}
} }
// Message system functions /**
function openMessageModal(playerId) { * Handle funds transfer for a player
selectedPlayerId = playerId; *
const player = adminData.players.find(p => p.id === playerId); * @param {string} condition - "advance" to give money, "deduct" to take money
* @param {number} amount - Amount of money to transfer
* @param {string} uid - Player's unique identifier
*/
function handleTransferFunds(condition, amount, uid) {
const message = {
event: "HANDLE_TRANSFER",
data: [condition, amount, uid]
};
A3API.SendAlert(JSON.stringify(message));
// Optimistic update
const player = adminData.players.find(p => p.uid === uid);
if (player) {
if (condition === "advance") {
player.funds = parseInt(player.funds) + amount;
} else if (condition === "deduct") {
player.funds = Math.max(0, parseInt(player.funds) - amount);
}
updatePlayerList();
}
}
// #endregion
//=============================================================================
// #region MESSAGE SYSTEM
//=============================================================================
/**
* Open the message modal for a player
*
* @param {string} uid - Player's unique identifier
* @param {string} playerName - Player's name for display
*/
function openMessageModal(uid, playerName) {
selectedPlayerId = uid;
const modal = document.getElementById('messageModal'); const modal = document.getElementById('messageModal');
document.getElementById('messagePlayerName').textContent = player.name; document.getElementById('messagePlayerName').textContent = playerName;
modal.style.display = 'block'; modal.style.display = 'block';
} }
/**
* Close the message modal
*/
function closeMessageModal() { function closeMessageModal() {
const modal = document.getElementById('messageModal'); const modal = document.getElementById('messageModal');
modal.style.display = 'none'; modal.style.display = 'none';
@ -195,35 +418,71 @@ function closeMessageModal() {
selectedPlayerId = null; selectedPlayerId = null;
} }
/**
* Send a message to the selected player
*/
function sendPlayerMessage() { function sendPlayerMessage() {
const message = document.getElementById('messageInput').value; const message = document.getElementById('messageInput').value;
if (message && selectedPlayerId) { if (message && selectedPlayerId) {
const player = adminData.players.find(p => p.id === selectedPlayerId); const messageData = {
if (player) { event: "SEND_MESSAGE",
console.log(`Message sent to ${player.name}: ${message}`); data: [selectedPlayerId, message]
};
A3API.SendAlert(JSON.stringify(messageData));
closeMessageModal(); closeMessageModal();
} }
}
} }
/**
* Broadcast a message to all players
*/
function broadcastMessage() { function broadcastMessage() {
const message = document.getElementById('broadcastMessage').value; const message = document.getElementById('broadcastMessage').value;
if (message) { if (message) {
console.log(`Broadcasting message to all players: ${message}`); const messageData = {
event: "BROADCAST_MESSAGE",
data: ["", message]
};
A3API.SendAlert(JSON.stringify(messageData));
document.getElementById('broadcastMessage').value = ''; document.getElementById('broadcastMessage').value = '';
} }
} }
// Global actions // #endregion
//=============================================================================
// #region GLOBAL ACTIONS
//=============================================================================
/**
* Trigger a payday for all players
*/
function Payday() { function Payday() {
const amount = parseInt(document.getElementById('paydayAmount').value); const message = {
if (amount) { event: "HANDLE_PAYDAY",
adminData.players.forEach(player => { data: []
player.money += amount; };
});
updatePlayerList(); A3API.SendAlert(JSON.stringify(message));
}
// Request updated player data after payday
setTimeout(requestPlayerData, 500); // Short delay to allow server processing
} }
// Initialize when DOM is loaded // #endregion
document.addEventListener('DOMContentLoaded', initializeAdmin);
//=============================================================================
// #region EVENT LISTENERS
//=============================================================================
/**
* Initialize when DOM is loaded
*/
document.addEventListener('DOMContentLoaded', () => {
initializeAdmin();
setupRefreshTimer();
});
// #endregion

View File

@ -1,30 +1,46 @@
/* =============================================================================
BASE STYLES AND VARIABLES
============================================================================= */
/* Reset styles */
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
/* Color variables and theme configuration */
:root { :root {
/* Primary colors */
--primary-color: #3b82f6; --primary-color: #3b82f6;
--primary-hover: #2563eb; --primary-hover: #2563eb;
--secondary-color: #1e293b; --secondary-color: #1e293b;
/* Background colors */
--background-color: #f1f5f9; --background-color: #f1f5f9;
--card-background: #ffffff; --card-background: #ffffff;
--header-bg: #1e293b;
--tile-hover: #f8fafc;
/* Text colors */
--text-primary: #0f172a; --text-primary: #0f172a;
--text-secondary: #475569; --text-secondary: #475569;
--border-color: #e2e8f0; --header-text: #f8fafc;
/* Status colors */
--success-color: #16a34a; --success-color: #16a34a;
--success-hover: #15803d; --success-hover: #15803d;
--error-color: #dc2626; --error-color: #dc2626;
--error-hover: #b91c1c; --error-hover: #b91c1c;
--warning-color: #f59e0b; --warning-color: #f59e0b;
--warning-hover: #d97706; --warning-hover: #d97706;
--header-bg: #1e293b;
--header-text: #f8fafc; /* Utility colors */
--tile-hover: #f8fafc; --border-color: #e2e8f0;
--shadow-color: rgba(0, 0, 0, 0.1); --shadow-color: rgba(0, 0, 0, 0.1);
} }
/* Base body styles */
body { body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6; line-height: 1.6;
@ -32,6 +48,11 @@ body {
color: var(--text-primary); color: var(--text-primary);
} }
/* =============================================================================
LAYOUT COMPONENTS
============================================================================= */
/* Main container */
.container { .container {
max-width: 1280px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
@ -39,28 +60,30 @@ body {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
/* Header styles */
header { header {
background-color: var(--header-bg); background-color: var(--header-bg);
color: var(--header-text); color: var(--header-text);
padding: 1rem 0; padding: 1rem 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.header-content { & .header-content {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
max-width: 1280px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 0 1rem; padding: 0 1rem;
} }
header h1 { & h1 {
font-size: 1.75rem; font-size: 1.75rem;
font-weight: 600; font-weight: 600;
letter-spacing: -0.025em; letter-spacing: -0.025em;
}
} }
/* Admin stats in header */
.admin-stats { .admin-stats {
display: flex; display: flex;
align-items: center; align-items: center;
@ -68,9 +91,8 @@ header h1 {
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 8px; border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-item { & .stat-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
@ -78,48 +100,50 @@ header h1 {
border-radius: 6px; border-radius: 6px;
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
min-width: 140px; min-width: 140px;
}
.stat-item:hover { &:hover {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
} }
}
.stat-icon { & .stat-icon {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 24px; width: 24px;
height: 24px; height: 24px;
font-size: 1rem; font-size: 1rem;
} }
.stat-info { & .stat-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.125rem; gap: 0.125rem;
} }
.stat-label { & .stat-label {
font-size: 0.75rem; font-size: 0.75rem;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.025em; letter-spacing: 0.025em;
} }
.stat-value { & .stat-value {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
color: var(--header-text); color: var(--header-text);
} }
.stat-divider { & .stat-divider {
width: 1px; width: 1px;
height: 24px; height: 24px;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
margin: 0 0.25rem; margin: 0 0.25rem;
}
} }
/* Grid layout for sections */
.sections-grid { .sections-grid {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -133,6 +157,11 @@ header h1 {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
/* =============================================================================
ADMIN SECTION COMPONENTS
============================================================================= */
/* Admin section cards */
.admin-section { .admin-section {
background-color: var(--card-background); background-color: var(--card-background);
border-radius: 12px; border-radius: 12px;
@ -146,22 +175,23 @@ header h1 {
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
height: auto; height: auto;
max-height: calc(100vw / 3); max-height: calc(100vw / 3);
}
.admin-section:hover { &:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-4px); transform: translateY(-4px);
} }
.admin-section:active { &:active {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
} }
.admin-section.square-ratio { &.square-ratio {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
}
} }
/* Player list section */
.player-list-section { .player-list-section {
grid-column: span 1; grid-column: span 1;
height: auto; height: auto;
@ -174,27 +204,28 @@ header h1 {
flex: 1; flex: 1;
padding-right: 0.5rem; padding-right: 0.5rem;
margin-right: -0.5rem; margin-right: -0.5rem;
}
/* Customize scrollbar for webkit browsers */ /* Customize scrollbar for webkit browsers */
.player-list::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
} }
.player-list::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background: transparent; background: transparent;
margin: 0.5rem; margin: 0.5rem;
} }
.player-list::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
border-radius: 3px; border-radius: 3px;
}
.player-list::-webkit-scrollbar-thumb:hover { &:hover {
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
}
}
} }
/* Player item in the list */
.player-item { .player-item {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -206,30 +237,42 @@ header h1 {
border: none; border: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
}
.player-item:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.player-item:hover { &:hover {
background-color: var(--tile-hover); background-color: var(--tile-hover);
box-shadow: 0 4px 6px var(--shadow-color); box-shadow: 0 4px 6px var(--shadow-color);
transform: translateY(-2px); transform: translateY(-2px);
} }
.player-info { & .player-info {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
flex: 1; flex: 1;
} }
.player-name { & .player-name {
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
}
& .player-money {
font-size: 0.875rem;
color: var(--success-color);
font-weight: 500;
}
& .player-actions {
display: flex;
gap: 0.5rem;
}
} }
/* Player role badges */
.player-role { .player-role {
font-size: 0.75rem; font-size: 0.75rem;
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
@ -254,17 +297,11 @@ header h1 {
color: white; color: white;
} }
.player-money { /* =============================================================================
font-size: 0.875rem; BUTTONS AND INTERACTIVE ELEMENTS
color: var(--success-color); ============================================================================= */
font-weight: 500;
}
.player-actions {
display: flex;
gap: 0.5rem;
}
/* Action buttons */
.action-btn { .action-btn {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border-radius: 6px; border-radius: 6px;
@ -278,30 +315,31 @@ header h1 {
.promote-btn { .promote-btn {
background-color: #22c55e; background-color: #22c55e;
color: white; color: white;
}
.promote-btn:hover { &:hover {
background-color: #16a34a; background-color: #16a34a;
}
} }
.demote-btn { .demote-btn {
background-color: #ef4444; background-color: #ef4444;
color: white; color: white;
}
.demote-btn:hover { &:hover {
background-color: #dc2626; background-color: #dc2626;
}
} }
.message-btn { .message-btn {
background-color: #3b82f6; background-color: #3b82f6;
color: white; color: white;
}
.message-btn:hover { &:hover {
background-color: #2563eb; background-color: #2563eb;
}
} }
/* Search and filter components */
.search-bar { .search-bar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -317,32 +355,63 @@ header h1 {
color: var(--text-primary); color: var(--text-primary);
background-color: var(--card-background); background-color: var(--card-background);
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
}
.search-input:focus { &:focus {
outline: none; outline: none;
border-color: var(--primary-color); border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
&:hover {
border-color: var(--primary-color);
}
} }
.search-input:hover { .filter-bar {
border-color: var(--primary-color); display: flex;
gap: 0.5rem;
} }
.filter-btn {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 6px;
cursor: pointer;
background-color: var(--card-background);
color: var(--text-secondary);
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease-in-out;
&:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
&.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
}
/* =============================================================================
FORMS AND INPUTS
============================================================================= */
.form-group { .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
}
.form-group label { & label {
font-weight: 500; font-weight: 500;
color: var(--text-secondary); color: var(--text-secondary);
font-size: 0.875rem; font-size: 0.875rem;
} }
.form-group input { & input {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 8px; border-radius: 8px;
@ -350,16 +419,17 @@ header h1 {
color: var(--text-primary); color: var(--text-primary);
background-color: var(--card-background); background-color: var(--card-background);
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
}
.form-group input:focus { &:focus {
outline: none; outline: none;
border-color: var(--primary-color); border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
} }
.form-group input:hover { &:hover {
border-color: var(--primary-color); border-color: var(--primary-color);
}
}
} }
.submit-btn { .submit-btn {
@ -374,21 +444,25 @@ header h1 {
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
opacity: 0.9; opacity: 0.9;
margin-top: auto; margin-top: auto;
}
.submit-btn:hover { &:hover {
background-color: var(--primary-hover); background-color: var(--primary-hover);
opacity: 1; opacity: 1;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
.submit-btn:active { &:active {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
} }
.modal { /* =============================================================================
MODALS
============================================================================= */
.modal {
display: none; display: none;
position: fixed; position: fixed;
top: 0; top: 0;
@ -417,12 +491,12 @@ header h1 {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
}
.modal-header h2 { & h2 {
font-size: 1.25rem; font-size: 1.25rem;
color: var(--text-primary); color: var(--text-primary);
font-weight: 600; font-weight: 600;
}
} }
.close-modal { .close-modal {
@ -432,11 +506,15 @@ header h1 {
cursor: pointer; cursor: pointer;
color: var(--text-secondary); color: var(--text-secondary);
transition: color 0.2s ease-in-out; transition: color 0.2s ease-in-out;
&:hover {
color: var(--text-primary);
}
} }
.close-modal:hover { /* =============================================================================
color: var(--text-primary); BADGES AND STATUS INDICATORS
} ============================================================================= */
.badge { .badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
@ -447,34 +525,6 @@ header h1 {
letter-spacing: 0.025em; letter-spacing: 0.025em;
} }
.filter-bar {
display: flex;
gap: 0.5rem;
}
.filter-btn {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 6px;
cursor: pointer;
background-color: var(--card-background);
color: var(--text-secondary);
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease-in-out;
}
.filter-btn:hover {
border-color: var(--primary-color);
color: var(--primary-color);
}
.filter-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
/* Rank badges */ /* Rank badges */
.player-rank { .player-rank {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
@ -485,37 +535,44 @@ header h1 {
letter-spacing: 0.025em; letter-spacing: 0.025em;
} }
.rank-1 { /* Rank styling based on type */
.rank-enlisted {
background-color: #e2e8f0; background-color: #e2e8f0;
color: #475569; color: #475569;
} }
.rank-2 { .rank-warrant {
background-color: #bfdbfe; background-color: #bfdbfe;
color: #1e40af; color: #1e40af;
} }
.rank-3 { .rank-officer {
background-color: #93c5fd;
color: #1e40af;
}
.rank-4 {
background-color: #60a5fa;
color: #1e40af;
}
.rank-5 {
background-color: #3b82f6; background-color: #3b82f6;
color: #ffffff; color: #ffffff;
} }
.payday-description { /* Side indicators */
font-size: 0.875rem; .side-west {
color: var(--text-secondary); background-color: #3b82f6;
margin: 0.5rem 0; color: white;
} }
.side-east {
background-color: #ef4444;
color: white;
}
.side-guer {
background-color: #22c55e;
color: white;
}
.side-civ {
background-color: #a855f7;
color: white;
}
/* Player payday indicator */
.player-payday { .player-payday {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--success-color); color: var(--success-color);
@ -525,3 +582,64 @@ header h1 {
border-radius: 4px; border-radius: 4px;
margin-left: 0.5rem; margin-left: 0.5rem;
} }
/* Description text */
.payday-description {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0.5rem 0;
}
/* =============================================================================
RESPONSIVE ADJUSTMENTS
============================================================================= */
/* Adjustments for smaller screens */
@media (max-width: 768px) {
.action-sections {
grid-template-columns: 1fr;
}
.player-item {
flex-direction: column;
align-items: flex-start;
& .player-info {
margin-bottom: 1rem;
flex-wrap: wrap;
}
& .player-actions {
width: 100%;
justify-content: space-between;
}
}
.filter-bar {
overflow-x: auto;
padding-bottom: 0.5rem;
}
.modal-content {
min-width: 90%;
max-width: 90%;
}
}
/* Adjustments for very small screens */
@media (max-width: 480px) {
.admin-stats {
flex-direction: column;
& .stat-divider {
width: 80%;
height: 1px;
margin: 0.25rem 0;
}
}
.action-btn {
padding: 0.5rem;
font-size: 0.75rem;
}
}

View File

@ -26,15 +26,15 @@ private _default = [[],[],[],[]];
private _armory_unlocks = GETVAR(player,Armory_Unlocks,_default); private _armory_unlocks = GETVAR(player,Armory_Unlocks,_default);
private _typeToNumber = switch (_type) do { private _typeToNumber = switch (_type) do {
case "backpack": {3}; case "facewear";
case "facewear": {0}; case "headgear";
case "headgear": {0}; case "hmd";
case "hmd": {0}; case "item";
case "item": {0}; case "uniform";
case "magazine": {2};
case "uniform": {0};
case "vest": {0}; case "vest": {0};
case "weapon": {1}; case "weapon": {1};
case "magazine": {2};
case "backpack": {3};
default {0}; default {0};
}; };

View File

@ -5,25 +5,26 @@
* Author: IDSolutions * Author: IDSolutions
* *
* [Description] * [Description]
* Initializes the arsenal system * Initializes the arsenal system with armory and garage data
* *
* Arguments: * Arguments:
* 0: Armory data <ARRAY> * 0: Armory data <ARRAY> - [items, weapons, magazines, backpacks]
* 1: Garage data <ARRAY> * 1: Garage data <ARRAY> - [cars, armor, helicopters, planes, naval, static]
* *
* Return Value: * Return Value:
* None * BOOLEAN - true if initialization successful, false if invalid data
* *
* Examples: * Example:
* None * [[_items, _weapons, _magazines, _backpacks], [_cars, _armor, _helis, _planes, _naval, _statics]] call forge_client_arsenal_fnc_initArsenal
*
* Public: Yes
*/ */
params [["_armory_data", [], [[]]], ["_garage_data", [], [[]]]]; params [["_armory_data", [], [[]]], ["_garage_data", [], [[]]]];
if (count _armory_data isEqualTo [""]) then { _armory_data = [[],[],[],[]] }; private _defaultArmory = [[],[],[],[]];
if (count _garage_data isEqualTo [""]) then { _garage_data = [[],[],[],[],[],[]] }; private _defaultGarage = [[],[],[],[],[],[]];
if (!(_armory_data isEqualTypeArray _defaultArmory) || (count _armory_data != 4)) then { _armory_data = _defaultArmory; };
if (!(_garage_data isEqualTypeArray _defaultGarage) || (count _garage_data != 6)) then { _garage_data = _defaultGarage; };
if (GVAR(armory_type) == 0) then { if (GVAR(armory_type) == 0) then {
{ {
[GVAR(gear_box), _x, false, true, 1, _forEachIndex] call BFUNC(addVirtualItemCargo); [GVAR(gear_box), _x, false, true, 1, _forEachIndex] call BFUNC(addVirtualItemCargo);
@ -34,23 +35,29 @@ if (GVAR(armory_type) == 0) then {
} forEach _armory_data; } forEach _armory_data;
}; };
_armory_data params [["_items", [], [[]]], ["_weapons", [], [[]]], ["_magazines", [], [[]]], ["_backpacks", [], [[]]]];
_garage_data params [["_cars", [], [[]]], ["_armor", [], [[]]], ["_helis", [], [[]]], ["_planes", [], [[]]], ["_naval", [], [[]]], ["_statics", [], [[]]]];
GVAR(armory_unlocks) = _armory_data; GVAR(armory_unlocks) = _armory_data;
GVAR(garage_unlocks) = _garage_data; GVAR(garage_unlocks) = _garage_data;
GVAR(item_unlocks) = _armory_data select 0; GVAR(item_unlocks) = _items;
GVAR(weapon_unlocks) = _armory_data select 1; GVAR(weapon_unlocks) = _weapons;
GVAR(magazine_unlocks) = _armory_data select 2; GVAR(magazine_unlocks) = _magazines;
GVAR(backpack_unlocks) = _armory_data select 3; GVAR(backpack_unlocks) = _backpacks;
GVAR(car_unlocks) = _garage_data select 0; GVAR(car_unlocks) = _cars;
GVAR(armor_unlocks) = _garage_data select 1; GVAR(armor_unlocks) = _armor;
GVAR(heli_unlocks) = _garage_data select 2; GVAR(heli_unlocks) = _helis;
GVAR(plane_unlocks) = _garage_data select 3; GVAR(plane_unlocks) = _planes;
GVAR(naval_unlocks) = _garage_data select 4; GVAR(naval_unlocks) = _naval;
GVAR(static_unlocks) = _garage_data select 5; GVAR(static_unlocks) = _statics;
{ {
[_x] call FUNC(addVirtualVehicles); [_x] call FUNC(addVirtualVehicles);
} forEach GVAR(garage_unlocks); } forEach GVAR(garage_unlocks);
TRACE_2("Arsenal System Initialized with defaults",count GVAR(armory_unlocks),count GVAR(garage_unlocks)); private _armoryCount = count (_armory_data select { count _x > 0 });
private _garageCount = count (_garage_data select { count _x > 0 });
TRACE_2("Arsenal System Initialized",_armoryCount,_garageCount);

View File

@ -31,10 +31,10 @@ getPlayerUID player,
"garage", [GETVAR(player,FORGE_Garage,[])], "garage", [GETVAR(player,FORGE_Garage,[])],
"cash", [GETVAR(player,FORGE_Cash,0)], "cash", [GETVAR(player,FORGE_Cash,0)],
"bank", [GETVAR(player,FORGE_Bank,0)], "bank", [GETVAR(player,FORGE_Bank,0)],
"number", [GETVAR(player,FORGE_Phone_Number,"unknown")], "number", [GETVAR(player,FORGE_Phone_Number,QUOTE(unknown))],
"email", [GETVAR(player,FORGE_Email,"unknown@spearnet.mil")], "email", [GETVAR(player,FORGE_Email,QUOTE(unknown@spearnet.mil))],
"paygrade", [GETVAR(player,FORGE_PayGrade,"E1")], "paygrade", [GETVAR(player,FORGE_PayGrade,QUOTE(E1))],
"organization", [GETVAR(player,FORGE_Organization,"")], "organization", [GETVAR(player,FORGE_Organization,QUOTE(None))],
"reputation", [rating player], "reputation", [rating player],
"loadout", [getUnitLoadout player], "loadout", [getUnitLoadout player],
"holster", [GETVAR(player,FORGE_Holster_Weapon,true)], "holster", [GETVAR(player,FORGE_Holster_Weapon,true)],

View File

@ -2,6 +2,7 @@ PREP(buyItem);
PREP(buyVehicle); PREP(buyVehicle);
PREP(changeFilter); PREP(changeFilter);
PREP(changePayment); PREP(changePayment);
PREP(handleDelivery);
PREP(handlePurchase); PREP(handlePurchase);
PREP(initStore); PREP(initStore);
PREP(openStore); PREP(openStore);

View File

@ -30,10 +30,7 @@ private _locker = GETVAR(player,FORGE_Locker,[]);
if !([_price] call FUNC(handlePurchase)) exitWith {}; if !([_price] call FUNC(handlePurchase)) exitWith {};
switch (_configType) do { switch (_configType) do {
case "item": { case "item";
_displayName = getText (configFile >> "CfgWeapons" >> _className >> "displayName");
_locker pushBack [_itemType, _className];
};
case "weapon": { case "weapon": {
_displayName = getText (configFile >> "CfgWeapons" >> _className >> "displayName"); _displayName = getText (configFile >> "CfgWeapons" >> _className >> "displayName");
_locker pushBack [_itemType, _className]; _locker pushBack [_itemType, _className];
@ -48,7 +45,7 @@ switch (_configType) do {
}; };
}; };
SETPVAR(player,FORGE_Locker,_locker); [_locker] spawn FUNC(handleDelivery);
[_className, _itemType] call EFUNC(arsenal,addArmoryItem); [_className, _itemType] call EFUNC(arsenal,addArmoryItem);

View File

@ -38,14 +38,7 @@ private _items = _data select 1;
if (_category == _selectedCategory) then { if (_category == _selectedCategory) then {
switch (_configType) do { switch (_configType) do {
case "item": { case "item";
private _displayName = getText (configFile >> "CfgWeapons" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgWeapons" >> _item >> "picture");
_index = _productList lbAdd _displayName;
_productList lbSetData [_index, str [_item, _price, _category, _configType, _itemType]];
_productList lbSetPicture [_index, _picture];
};
case "weapon": { case "weapon": {
private _displayName = getText (configFile >> "CfgWeapons" >> _item >> "displayName"); private _displayName = getText (configFile >> "CfgWeapons" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgWeapons" >> _item >> "picture"); private _picture = getText (configFile >> "CfgWeapons" >> _item >> "picture");
@ -62,14 +55,7 @@ private _items = _data select 1;
_productList lbSetData [_index, str [_item, _price, _category, _configType, _itemType]]; _productList lbSetData [_index, str [_item, _price, _category, _configType, _itemType]];
_productList lbSetPicture [_index, _picture]; _productList lbSetPicture [_index, _picture];
}; };
case "backpack": { case "backpack";
private _displayName = getText (configFile >> "CfgVehicles" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgVehicles" >> _item >> "picture");
_index = _productList lbAdd _displayName;
_productList lbSetData [_index, str [_item, _price, _category, _configType, _itemType]];
_productList lbSetPicture [_index, _picture];
};
case "vehicle": { case "vehicle": {
private _displayName = getText (configFile >> "CfgVehicles" >> _item >> "displayName"); private _displayName = getText (configFile >> "CfgVehicles" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgVehicles" >> _item >> "picture"); private _picture = getText (configFile >> "CfgVehicles" >> _item >> "picture");

View File

@ -0,0 +1,66 @@
#include "..\script_component.hpp"
/*
* Function: forge_store_fnc_handleDelivery
* Description:
* Handles the delivery timer and locker updates for purchased items
*
* Parameters:
* 0: New Locker Contents <ARRAY>
*
* Returns:
* None
*
* Example:
* [_newLocker] spawn forge_store_fnc_handleDelivery
*/
params [["_newLocker", [], [[]]]];
private _deliveryTime = ["DT", 0] call BIS_fnc_getParamValue;
if (_newLocker isEqualTo []) exitWith {};
if (_deliveryTime > 0) then {
[
format [
"<t align='left'>Order Processing</t><br/><t size='0.8' align='left'>Estimated delivery: %1</t>",
[_deliveryTime, "MM:SS"] call BIS_fnc_secondsToString
],
"info",
3,
"right"
] call EFUNC(misc,notify);
uiSleep (_deliveryTime / 2);
[
"<t align='left'>Package in transit</t>",
"warning",
2,
"left"
] call EFUNC(misc,notify);
uiSleep (_deliveryTime / 2);
SETPVAR(player,FORGE_Locker,_newLocker);
[
"<t align='left'>Order Delivered!</t><br/><t size='0.8' align='left'>Check your locker</t>",
"success",
5,
"left"
] call EFUNC(misc,notify);
if (hasInterface) then { playSound "FD_Finish_F"; };
} else {
SETPVAR(player,FORGE_Locker,_newLocker);
[
"<t align='left'>Order Complete!</t><br/><t size='0.8' align='left'>Items added to locker</t>",
"success",
5,
"left"
] call EFUNC(misc,notify);
if (hasInterface) then { playSound "FD_Finish_F"; };
};

View File

@ -47,14 +47,13 @@ if (_payment select 0 == "Organization") then {
}; };
}; };
private _varType = _payment select 2; private _varType = toLower (_payment select 2);
private _varName = _payment param [1, "", [""]];
private _balance = switch (_varType) do { private _balance = switch (_varType) do {
case "organization": { case "organization": { _store call ["getFunds", []] };
_store call ["getFunds", []]; case "player": { GETVAR(player,_varName,0) };
}; case "mission": { GETVAR(missionNamespace,_varName,0) };
case "player": { player getVariable [_payment select 1, 0] }; default { diag_log "[FORGE Store] Error: Unknown payment type"; 0 };
case "mission": { missionNamespace getVariable [_payment select 1, 0] };
default { 0 };
}; };
if (_balance < _price) exitWith { if (_balance < _price) exitWith {
@ -63,14 +62,14 @@ if (_balance < _price) exitWith {
}; };
switch (_varType) do { switch (_varType) do {
case "organization": { case "organization": { _store call ["updateFunds", -_price] };
_store call ["updateFunds", -_price];
};
case "player": { case "player": {
player setVariable [_payment select 1, (_balance - _price), true]; private _newBalance = _balance - _price;
SETPVAR(player,_varName,_newBalance);
}; };
case "mission": { case "mission": {
missionNamespace setVariable [_payment select 1, (_balance - _price), true]; private _newBalance = _balance - _price;
SETPVAR(missionNamespace,_varName,_newBalance);
}; };
}; };

View File

@ -30,14 +30,11 @@ private _display = findDisplay IDD_STOREDIALOG;
private _categoryList = _display displayCtrl IDC_CATEGORYLIST; private _categoryList = _display displayCtrl IDC_CATEGORYLIST;
private _paymentList = _display displayCtrl IDC_PAYMENTLIST; private _paymentList = _display displayCtrl IDC_PAYMENTLIST;
private _storeName = _display displayCtrl IDC_DIALOGNAME; private _storeName = _display displayCtrl IDC_DIALOGNAME;
private _data = _store getVariable "storeData"; private _data = _store getVariable ["storeData", []];
private _categories = _data select 0;
private _products = _data select 1; _data params [["_categories", [], [[]]], ["_products", [], [[]]], ["_name", "", [""]], ["_paymentMethods", [], [[]]]];
private _name = _data select 2;
private _paymentMethods = _data select 3;
GVAR(currentStore) = _data; GVAR(currentStore) = _data;
_storeName ctrlSetText _name; _storeName ctrlSetText _name;
{ {

View File

@ -23,12 +23,9 @@ private _display = findDisplay IDD_STOREDIALOG;
private _productList = _display displayCtrl IDC_PRODUCTLIST; private _productList = _display displayCtrl IDC_PRODUCTLIST;
private _productIndex = lbCurSel _productList; private _productIndex = lbCurSel _productList;
private _productData = lbData [IDC_PRODUCTLIST, _productIndex]; private _productData = lbData [IDC_PRODUCTLIST, _productIndex];
private _product = call compile _productData; private _product = call compile _productData;
private _item = _product select 0;
private _price = _product select 1; _product params [["_item", "", [""]], ["_price", 0, [0]], ["_category", "", [""]], ["_configType", "", [""]], ["_itemType", "", [""]]];
private _configType = _product select 3;
private _itemType = _product select 4;
switch (_configType) do { switch (_configType) do {
case "item"; case "item";