Compare commits

..

No commits in common. "771b5f1c323ec164ee0a3196fbb1f17169b8a082" and "b8f2b17d1109508b52162e30454ea6072e45d0db" have entirely different histories.

28 changed files with 764 additions and 1221 deletions

View File

@ -1,23 +0,0 @@
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,6 +1,7 @@
PREP(adminMessage);
PREP(adminPromote);
PREP(adminRefresh);
PREP(handleTransfer);
PREP(adminTransfer);
PREP(initAdmin);
PREP(openAdmin);
PREP(sendMessage);
PREP(updatePaygrade);
PREP(printAddonName);

View File

@ -1,96 +1 @@
#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,4 +16,3 @@ class CfgPatches {
#include "CfgEventHandlers.hpp"
#include "ui\RscCommon.hpp"
#include "ui\RscAdmin.hpp"
#include "ui\RscWebAdmin.hpp"

View File

@ -0,0 +1,49 @@
#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

@ -0,0 +1,51 @@
#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,39 +5,74 @@
* Author: IDSolutions
*
* [Description]
* 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.
* Admin Refresh Menu
*
* Arguments:
* 0: Dummy <ANY> - Optional parameter, not used (for compatibility with event handlers)
* None
*
* Return Value:
* None
*
* Examples:
* [] call forge_client_admin_fnc_adminRefresh;
* ["dummy"] call forge_client_admin_fnc_adminRefresh;
* None
*
* Public: No - Called from admin dialog controls
* Public: Yes
*/
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 _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;
{
if (str (side _x) == str (playerSide)) then {
private _name = name (_x);
private _paygrade = GETVAR(_x,FORGE_PayGrade,QUOTE(E1));
private _defaultPaygrade = "E1";
private _paygrade = GETVAR(_x,FORGE_PayGrade,_defaultPaygrade);
private _index = _list lbAdd format["%1 - %2", _name, _paygrade];
_list lbSetData [_index, getPlayerUID _x];
_list lbSetData [_index, name (_x)];
};
} forEach allPlayers;
} forEach playableUnits;
lbSetCurSel [2023001, 0];
ctrlSetText [2023005, ""];

View File

@ -0,0 +1,118 @@
#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

@ -1,40 +0,0 @@
#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,47 +19,33 @@
* Public: Yes
*/
private _productVersion = productVersion;
private _steamBranchName = _productVersion select 8;
disableSerialization;
createDialog "RscAdmin";
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);
if (_steamBranchName == "profiling") then {
private _display = (findDisplay 46) createDisplay "RscWebAdmin";
private _ctrl = _display displayCtrl 2025;
{
private _index = _list2 lbAdd format ["%1 - $%2", (_x select 0), ((_x select 1) call EFUNC(misc,formatNumber))];
_ctrl ctrlAddEventHandler ["JSDialog", {
params ["_control", "_isConfirmDialog", "_message"];
[QGVAR(handleEvents), [_control, _isConfirmDialog, _message]] call CFUNC(localEvent);
}];
_list2 lbSetData [_index, str _x];
} forEach _payGrades;
_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))];
_list2 lbSetData [_index, str _x];
} forEach _payGrades;
lbSetCurSel [2023003, 0];
ctrlSetText [2023002, format ["$%1", (companyFunds call EFUNC(misc,formatNumber))]];
};
lbSetCurSel [2023003, 0];
ctrlSetText [2023002, format ["$%1", (companyFunds call EFUNC(misc,formatNumber))]];

View File

@ -0,0 +1,22 @@
#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

@ -1,37 +0,0 @@
#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

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

View File

@ -1,17 +0,0 @@
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,44 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!--
Dynamic Resource Loading
The following script loads CSS and JavaScript files dynamically using the A3API
This approach is used instead of static HTML imports to work with Arma 3's file system
-->
<script>
Promise.all([
// 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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forge Admin Panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<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">
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<!--
Header Section
Contains the main title and server statistics
-->
<header>
<div class="header-content">
<h1>Admin Panel</h1>
<!-- Server Statistics Display -->
<div class="admin-stats">
<div class="stat-item">
<div class="stat-icon">👥</div>
<div class="stat-info">
<span class="stat-label">Online Players</span>
<span class="stat-value" id="playerCount">0</span>
@ -46,6 +24,7 @@
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<div class="stat-icon">👑</div>
<div class="stat-info">
<span class="stat-label">Staff Online</span>
<span class="stat-value" id="staffCount">0</span>
@ -55,18 +34,10 @@
</div>
</header>
<!--
Main Content Area
Contains all admin functionality sections
-->
<main class="container">
<div class="sections-grid">
<!--
Admin Action Sections
Left column with various global admin actions
-->
<div class="action-sections">
<!-- Global Payday Action -->
<!-- Global Actions Section -->
<div class="admin-section">
<h2>Global Actions</h2>
<div class="form-group">
@ -76,7 +47,7 @@
<button class="submit-btn" onclick="Payday()">Payday</button>
</div>
<!-- Give All Money Action -->
<!-- Give All Section -->
<div class="admin-section">
<h2>Give All Money</h2>
<div class="form-group">
@ -86,7 +57,7 @@
<button class="submit-btn" onclick="giveAllMoney()">Give to All</button>
</div>
<!-- Broadcast Message System -->
<!-- Message System Section -->
<div class="admin-section">
<h2>Broadcast Message</h2>
<div class="form-group">
@ -97,37 +68,24 @@
</div>
</div>
<!--
Player List Section
Right column showing all players with filtering options
-->
<!-- Player List Section -->
<div class="admin-section player-list-section">
<!-- Search and Filter Controls -->
<div class="search-bar">
<input type="text" id="playerSearch" class="search-input" placeholder="Search players...">
<div class="filter-bar">
<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="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>
<!-- Dynamic Player List Container -->
<div class="player-list" id="playerList">
<!-- Players will be populated dynamically via JavaScript -->
<!-- Players will be populated dynamically -->
</div>
</div>
</div>
</main>
<!--
Modal Dialogs
Hidden by default, shown when specific actions are triggered
-->
<!-- Message Modal - For sending messages to individual players -->
<!-- Message Modal -->
<div id="messageModal" class="modal">
<div class="modal-content">
<div class="modal-header">
@ -142,7 +100,7 @@
</div>
</div>
<!-- Money Modal - For modifying player funds -->
<!-- Money Modal -->
<div id="moneyModal" class="modal">
<div class="modal-content">
<div class="modal-header">

View File

@ -1,152 +1,60 @@
/**
* 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
*/
// Simulated admin data - this would be replaced with actual game data
let adminData = {
players: [], // List of all players with their details
paydayAmounts: {} // Map of paygrade to bonus amount
players: [
{
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;
// #endregion
//=============================================================================
// #region INITIALIZATION AND DATA REQUESTS
//=============================================================================
/**
* Initialize the admin panel
* Sets up the UI, requests initial data from the game engine
*/
// Initialize the admin panel
function initializeAdmin() {
updateStats();
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();
}
// #endregion
//=============================================================================
// #region UI UPDATES AND DISPLAY
//=============================================================================
/**
* Update header statistics
* Shows counts of online players and staff
*/
// Update header statistics
function updateStats() {
const onlinePlayers = adminData.players.length;
const onlineStaff = adminData.players.filter(p => p.paygrade !== "E1").length;
const onlinePlayers = adminData.players.filter(p => p.status === "online").length;
const onlineStaff = adminData.players.filter(p => p.status === "online" && p.rank > 1).length;
document.getElementById('playerCount').textContent = onlinePlayers;
document.getElementById('staffCount').textContent = onlineStaff;
}
/**
* Set up filter button listeners
* Configures the filter buttons and search functionality
*/
// Set up filter button listeners
function setupFilterListeners() {
const filterButtons = document.querySelectorAll('.filter-btn');
// Set up filter button click handlers
filterButtons.forEach(button => {
button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active'));
@ -163,26 +71,15 @@ function setupFilterListeners() {
});
}
/**
* 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
*/
// Filter players based on category and search term
function filterPlayers(filter, searchTerm = '') {
let filteredPlayers = adminData.players;
// Apply category filter
if (filter === 'staff') {
filteredPlayers = filteredPlayers.filter(p => p.paygrade !== "E1");
} else if (filter === 'blufor') {
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");
if (filter === 'online') {
filteredPlayers = filteredPlayers.filter(p => p.status === 'online');
} else if (filter === 'staff') {
filteredPlayers = filteredPlayers.filter(p => p.rank > 1);
}
// Apply search filter
@ -196,127 +93,61 @@ function filterPlayers(filter, searchTerm = '') {
updatePlayerList(filteredPlayers);
}
/**
* 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
*/
// Update the player list display
function updatePlayerList(players = adminData.players) {
const playerList = document.getElementById('playerList');
playerList.innerHTML = players.map(player => {
const paydayAmount = adminData.paydayAmounts[player.paygrade] || 1000;
const rankClass = getRankClass(player.paygrade);
const paydayAmount = adminData.paydayAmounts[player.rank] || 1000; // Default to 1000 if rank not found
return `
<div class="player-item" data-id="${player.uid}">
<div class="player-item" data-id="${player.id}">
<div class="player-info">
<span class="player-name">${player.name}</span>
<span class="player-rank rank-${rankClass}">${player.paygrade}</span>
<span class="player-money">${parseInt(player.funds).toLocaleString()}</span>
<span class="player-payday">Payday: ${paydayAmount.toLocaleString()}</span>
<span class="player-side side-${player.side.toLowerCase()}">${player.side}</span>
<span class="player-rank rank-${player.rank}">Rank ${player.rank}</span>
<span class="player-money">$${player.money.toLocaleString()}</span>
<span class="player-payday">Payday: $${paydayAmount.toLocaleString()}</span>
</div>
<div class="player-actions">
<button class="action-btn promote-btn" onclick="updatePaygrade('${player.uid}', true)">
Promote
</button>
<button class="action-btn demote-btn" onclick="updatePaygrade('${player.uid}', false)">
Demote
</button>
<button class="action-btn message-btn" onclick="openMessageModal('${player.uid}', '${player.name}')">Message</button>
<button class="action-btn" onclick="openMoneyModal('${player.uid}')">Modify Money</button>
${player.rank < adminData.maxRank ? `
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
Promote
</button>
` : ''}
${player.rank > 1 ? `
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
Demote
</button>
` : ''}
<button class="action-btn message-btn" onclick="openMessageModal(${player.id})">Message</button>
<button class="action-btn" onclick="openMoneyModal(${player.id})">Modify Money</button>
</div>
</div>
`}).join('');
}
/**
* Helper function to determine rank class based on paygrade
* Used for styling different ranks with appropriate CSS classes
*
* @param {string} paygrade - The player's paygrade code
* @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
// Rank management functions
function promotePlayer(playerId) {
const player = adminData.players.find(p => p.id === playerId);
if (player && player.rank < adminData.maxRank) {
player.rank++;
updatePlayerList();
}
}
// #endregion
//=============================================================================
// #region RANK MANAGEMENT
//=============================================================================
/**
* 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
function demotePlayer(playerId) {
const player = adminData.players.find(p => p.id === playerId);
if (player && player.rank > 1) {
player.rank--;
updatePlayerList();
}
const message = {
event: "UPDATE_PAYGRADE",
data: [uid, newPaygrade]
};
A3API.SendAlert(JSON.stringify(message));
// Optimistic update
player.paygrade = newPaygrade;
updatePlayerList();
}
// #endregion
//=============================================================================
// #region MONEY MANAGEMENT
//=============================================================================
/**
* Open the money modification modal for a player
*
* @param {string} uid - Player's unique identifier
*/
function openMoneyModal(uid) {
selectedPlayerId = uid;
// Money management functions
function openMoneyModal(playerId) {
selectedPlayerId = playerId;
const modal = document.getElementById('moneyModal');
modal.style.display = 'block';
}
/**
* Close the money modification modal
*/
function closeMoneyModal() {
const modal = document.getElementById('moneyModal');
modal.style.display = 'none';
@ -324,93 +155,39 @@ function closeMoneyModal() {
selectedPlayerId = null;
}
/**
* Give money to the selected player
*/
function giveMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
handleTransferFunds("advance", amount, selectedPlayerId);
closeMoneyModal();
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
player.money += amount;
updatePlayerList();
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() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
handleTransferFunds("deduct", amount, selectedPlayerId);
closeMoneyModal();
}
}
/**
* Handle funds transfer for a player
*
* @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);
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
player.money = Math.max(0, player.money - amount);
updatePlayerList();
closeMoneyModal();
}
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;
// Message system functions
function openMessageModal(playerId) {
selectedPlayerId = playerId;
const player = adminData.players.find(p => p.id === playerId);
const modal = document.getElementById('messageModal');
document.getElementById('messagePlayerName').textContent = playerName;
document.getElementById('messagePlayerName').textContent = player.name;
modal.style.display = 'block';
}
/**
* Close the message modal
*/
function closeMessageModal() {
const modal = document.getElementById('messageModal');
modal.style.display = 'none';
@ -418,71 +195,35 @@ function closeMessageModal() {
selectedPlayerId = null;
}
/**
* Send a message to the selected player
*/
function sendPlayerMessage() {
const message = document.getElementById('messageInput').value;
if (message && selectedPlayerId) {
const messageData = {
event: "SEND_MESSAGE",
data: [selectedPlayerId, message]
};
A3API.SendAlert(JSON.stringify(messageData));
closeMessageModal();
const player = adminData.players.find(p => p.id === selectedPlayerId);
if (player) {
console.log(`Message sent to ${player.name}: ${message}`);
closeMessageModal();
}
}
}
/**
* Broadcast a message to all players
*/
function broadcastMessage() {
const message = document.getElementById('broadcastMessage').value;
if (message) {
const messageData = {
event: "BROADCAST_MESSAGE",
data: ["", message]
};
A3API.SendAlert(JSON.stringify(messageData));
console.log(`Broadcasting message to all players: ${message}`);
document.getElementById('broadcastMessage').value = '';
}
}
// #endregion
//=============================================================================
// #region GLOBAL ACTIONS
//=============================================================================
/**
* Trigger a payday for all players
*/
// Global actions
function Payday() {
const message = {
event: "HANDLE_PAYDAY",
data: []
};
A3API.SendAlert(JSON.stringify(message));
// Request updated player data after payday
setTimeout(requestPlayerData, 500); // Short delay to allow server processing
const amount = parseInt(document.getElementById('paydayAmount').value);
if (amount) {
adminData.players.forEach(player => {
player.money += amount;
});
updatePlayerList();
}
}
// #endregion
//=============================================================================
// #region EVENT LISTENERS
//=============================================================================
/**
* Initialize when DOM is loaded
*/
document.addEventListener('DOMContentLoaded', () => {
initializeAdmin();
setupRefreshTimer();
});
// #endregion
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeAdmin);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,14 @@ private _items = _data select 1;
if (_category == _selectedCategory) then {
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": {
private _displayName = getText (configFile >> "CfgWeapons" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgWeapons" >> _item >> "picture");
@ -55,7 +62,14 @@ private _items = _data select 1;
_productList lbSetData [_index, str [_item, _price, _category, _configType, _itemType]];
_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": {
private _displayName = getText (configFile >> "CfgVehicles" >> _item >> "displayName");
private _picture = getText (configFile >> "CfgVehicles" >> _item >> "picture");

View File

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

View File

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

View File

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