Compare commits
2 Commits
b8f2b17d11
...
771b5f1c32
Author | SHA1 | Date | |
---|---|---|---|
![]() |
771b5f1c32 | ||
![]() |
55f60dc71f |
23
.hemtt/commands/ctrlWebBrowserAction.yml
Normal file
23
.hemtt/commands/ctrlWebBrowserAction.yml
Normal 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>
|
@ -1,7 +1,6 @@
|
||||
PREP(adminMessage);
|
||||
PREP(adminPromote);
|
||||
PREP(adminRefresh);
|
||||
PREP(adminTransfer);
|
||||
PREP(handleTransfer);
|
||||
PREP(initAdmin);
|
||||
PREP(openAdmin);
|
||||
PREP(printAddonName);
|
||||
PREP(sendMessage);
|
||||
PREP(updatePaygrade);
|
@ -1 +1,96 @@
|
||||
#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);
|
@ -16,3 +16,4 @@ class CfgPatches {
|
||||
#include "CfgEventHandlers.hpp"
|
||||
#include "ui\RscCommon.hpp"
|
||||
#include "ui\RscAdmin.hpp"
|
||||
#include "ui\RscWebAdmin.hpp"
|
@ -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);
|
@ -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);
|
@ -5,74 +5,39 @@
|
||||
* Author: IDSolutions
|
||||
*
|
||||
* [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:
|
||||
* None
|
||||
* 0: Dummy <ANY> - Optional parameter, not used (for compatibility with event handlers)
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* 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 _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 _defaultPaygrade = "E1";
|
||||
private _paygrade = GETVAR(_x,FORGE_PayGrade,_defaultPaygrade);
|
||||
private _paygrade = GETVAR(_x,FORGE_PayGrade,QUOTE(E1));
|
||||
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];
|
||||
ctrlSetText [2023005, ""];
|
||||
|
@ -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, ""];
|
40
addons/admin/functions/fnc_handleTransfer.sqf
Normal file
40
addons/admin/functions/fnc_handleTransfer.sqf
Normal 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, ""];
|
@ -19,33 +19,47 @@
|
||||
* Public: Yes
|
||||
*/
|
||||
|
||||
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 _productVersion = productVersion;
|
||||
private _steamBranchName = _productVersion select 8;
|
||||
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))];
|
||||
|
||||
_list2 lbSetData [_index, str _x];
|
||||
} forEach _payGrades;
|
||||
} 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))]];
|
||||
};
|
@ -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'];
|
37
addons/admin/functions/fnc_sendMessage.sqf
Normal file
37
addons/admin/functions/fnc_sendMessage.sqf
Normal 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);
|
37
addons/admin/functions/fnc_updatePaygrade.sqf
Normal file
37
addons/admin/functions/fnc_updatePaygrade.sqf
Normal 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);
|
@ -83,7 +83,7 @@ class RscAdmin {
|
||||
class RscAdminPromote: RscButton {
|
||||
idc = -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};
|
||||
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_adminMessage;";
|
||||
onButtonClick = "[] call forge_client_admin_fnc_sendMessage;";
|
||||
soundClick[] = {"\A3\ui_f\data\sound\RscButton\soundClick",0.09,1};
|
||||
text = "Send Message";
|
||||
x = "0.6125 * safezoneW + safezoneX";
|
||||
|
17
addons/admin/ui/RscWebAdmin.hpp
Normal file
17
addons/admin/ui/RscWebAdmin.hpp
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
@ -1,22 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
<!--
|
||||
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>
|
||||
</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>
|
||||
@ -24,7 +46,6 @@
|
||||
</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>
|
||||
@ -34,10 +55,18 @@
|
||||
</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 Actions Section -->
|
||||
<!-- Global Payday Action -->
|
||||
<div class="admin-section">
|
||||
<h2>Global Actions</h2>
|
||||
<div class="form-group">
|
||||
@ -47,7 +76,7 @@
|
||||
<button class="submit-btn" onclick="Payday()">Payday</button>
|
||||
</div>
|
||||
|
||||
<!-- Give All Section -->
|
||||
<!-- Give All Money Action -->
|
||||
<div class="admin-section">
|
||||
<h2>Give All Money</h2>
|
||||
<div class="form-group">
|
||||
@ -57,7 +86,7 @@
|
||||
<button class="submit-btn" onclick="giveAllMoney()">Give to All</button>
|
||||
</div>
|
||||
|
||||
<!-- Message System Section -->
|
||||
<!-- Broadcast Message System -->
|
||||
<div class="admin-section">
|
||||
<h2>Broadcast Message</h2>
|
||||
<div class="form-group">
|
||||
@ -68,24 +97,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player List Section -->
|
||||
<!--
|
||||
Player List Section
|
||||
Right column showing all players with filtering options
|
||||
-->
|
||||
<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 -->
|
||||
<!-- Players will be populated dynamically via JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -100,7 +142,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Money Modal -->
|
||||
<!-- Money Modal - For modifying player funds -->
|
||||
<div id="moneyModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -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 = {
|
||||
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
|
||||
players: [], // List of all players with their details
|
||||
paydayAmounts: {} // Map of paygrade to bonus amount
|
||||
};
|
||||
|
||||
/**
|
||||
* Currently selected player ID for operations that require a player selection
|
||||
* @type {string|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() {
|
||||
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();
|
||||
}
|
||||
|
||||
// Update header statistics
|
||||
// #endregion
|
||||
|
||||
//=============================================================================
|
||||
// #region UI UPDATES AND DISPLAY
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Update header statistics
|
||||
* Shows counts of online players and staff
|
||||
*/
|
||||
function updateStats() {
|
||||
const onlinePlayers = adminData.players.filter(p => p.status === "online").length;
|
||||
const onlineStaff = adminData.players.filter(p => p.status === "online" && p.rank > 1).length;
|
||||
const onlinePlayers = adminData.players.length;
|
||||
const onlineStaff = adminData.players.filter(p => p.paygrade !== "E1").length;
|
||||
|
||||
document.getElementById('playerCount').textContent = onlinePlayers;
|
||||
document.getElementById('staffCount').textContent = onlineStaff;
|
||||
}
|
||||
|
||||
// Set up filter button listeners
|
||||
/**
|
||||
* Set up filter button listeners
|
||||
* Configures the filter buttons and search functionality
|
||||
*/
|
||||
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'));
|
||||
@ -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 = '') {
|
||||
let filteredPlayers = adminData.players;
|
||||
|
||||
// Apply category filter
|
||||
if (filter === 'online') {
|
||||
filteredPlayers = filteredPlayers.filter(p => p.status === 'online');
|
||||
} else if (filter === 'staff') {
|
||||
filteredPlayers = filteredPlayers.filter(p => p.rank > 1);
|
||||
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");
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
@ -93,61 +196,127 @@ function filterPlayers(filter, searchTerm = '') {
|
||||
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) {
|
||||
const playerList = document.getElementById('playerList');
|
||||
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 `
|
||||
<div class="player-item" data-id="${player.id}">
|
||||
<div class="player-item" data-id="${player.uid}">
|
||||
<div class="player-info">
|
||||
<span class="player-name">${player.name}</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>
|
||||
<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>
|
||||
</div>
|
||||
<div class="player-actions">
|
||||
${player.rank < adminData.maxRank ? `
|
||||
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
|
||||
<button class="action-btn promote-btn" onclick="updatePaygrade('${player.uid}', true)">
|
||||
Promote
|
||||
</button>
|
||||
` : ''}
|
||||
${player.rank > 1 ? `
|
||||
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
|
||||
<button class="action-btn demote-btn" onclick="updatePaygrade('${player.uid}', false)">
|
||||
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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
`}).join('');
|
||||
}
|
||||
|
||||
// Rank management functions
|
||||
function promotePlayer(playerId) {
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
if (player && player.rank < adminData.maxRank) {
|
||||
player.rank++;
|
||||
updatePlayerList();
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
function demotePlayer(playerId) {
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
if (player && player.rank > 1) {
|
||||
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
|
||||
}
|
||||
|
||||
const message = {
|
||||
event: "UPDATE_PAYGRADE",
|
||||
data: [uid, newPaygrade]
|
||||
};
|
||||
|
||||
A3API.SendAlert(JSON.stringify(message));
|
||||
|
||||
// Optimistic update
|
||||
player.paygrade = newPaygrade;
|
||||
updatePlayerList();
|
||||
}
|
||||
|
||||
// Money management functions
|
||||
function openMoneyModal(playerId) {
|
||||
selectedPlayerId = playerId;
|
||||
// #endregion
|
||||
|
||||
//=============================================================================
|
||||
// #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');
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the money modification modal
|
||||
*/
|
||||
function closeMoneyModal() {
|
||||
const modal = document.getElementById('moneyModal');
|
||||
modal.style.display = 'none';
|
||||
@ -155,39 +324,93 @@ function closeMoneyModal() {
|
||||
selectedPlayerId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give money to the selected player
|
||||
*/
|
||||
function giveMoney() {
|
||||
const amount = parseInt(document.getElementById('moneyAmount').value);
|
||||
if (amount && selectedPlayerId) {
|
||||
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||
if (player) {
|
||||
player.money += amount;
|
||||
updatePlayerList();
|
||||
handleTransferFunds("advance", amount, selectedPlayerId);
|
||||
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) {
|
||||
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||
if (player) {
|
||||
player.money = Math.max(0, player.money - amount);
|
||||
updatePlayerList();
|
||||
handleTransferFunds("deduct", amount, selectedPlayerId);
|
||||
closeMoneyModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message system functions
|
||||
function openMessageModal(playerId) {
|
||||
selectedPlayerId = playerId;
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
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');
|
||||
document.getElementById('messagePlayerName').textContent = player.name;
|
||||
document.getElementById('messagePlayerName').textContent = playerName;
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the message modal
|
||||
*/
|
||||
function closeMessageModal() {
|
||||
const modal = document.getElementById('messageModal');
|
||||
modal.style.display = 'none';
|
||||
@ -195,35 +418,71 @@ function closeMessageModal() {
|
||||
selectedPlayerId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the selected player
|
||||
*/
|
||||
function sendPlayerMessage() {
|
||||
const message = document.getElementById('messageInput').value;
|
||||
if (message && selectedPlayerId) {
|
||||
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||
if (player) {
|
||||
console.log(`Message sent to ${player.name}: ${message}`);
|
||||
const messageData = {
|
||||
event: "SEND_MESSAGE",
|
||||
data: [selectedPlayerId, message]
|
||||
};
|
||||
|
||||
A3API.SendAlert(JSON.stringify(messageData));
|
||||
closeMessageModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message to all players
|
||||
*/
|
||||
function broadcastMessage() {
|
||||
const message = document.getElementById('broadcastMessage').value;
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Global actions
|
||||
// #endregion
|
||||
|
||||
//=============================================================================
|
||||
// #region GLOBAL ACTIONS
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Trigger a payday for all players
|
||||
*/
|
||||
function Payday() {
|
||||
const amount = parseInt(document.getElementById('paydayAmount').value);
|
||||
if (amount) {
|
||||
adminData.players.forEach(player => {
|
||||
player.money += amount;
|
||||
});
|
||||
updatePlayerList();
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeAdmin);
|
||||
// #endregion
|
||||
|
||||
//=============================================================================
|
||||
// #region EVENT LISTENERS
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Initialize when DOM is loaded
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeAdmin();
|
||||
setupRefreshTimer();
|
||||
});
|
||||
|
||||
// #endregion
|
@ -1,30 +1,46 @@
|
||||
/* =============================================================================
|
||||
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;
|
||||
--border-color: #e2e8f0;
|
||||
--header-text: #f8fafc;
|
||||
|
||||
/* Status colors */
|
||||
--success-color: #16a34a;
|
||||
--success-hover: #15803d;
|
||||
--error-color: #dc2626;
|
||||
--error-hover: #b91c1c;
|
||||
--warning-color: #f59e0b;
|
||||
--warning-hover: #d97706;
|
||||
--header-bg: #1e293b;
|
||||
--header-text: #f8fafc;
|
||||
--tile-hover: #f8fafc;
|
||||
|
||||
/* Utility colors */
|
||||
--border-color: #e2e8f0;
|
||||
--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;
|
||||
@ -32,6 +48,11 @@ body {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
LAYOUT COMPONENTS
|
||||
============================================================================= */
|
||||
|
||||
/* Main container */
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
@ -39,28 +60,30 @@ 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 {
|
||||
& .header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
header h1 {
|
||||
& h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Admin stats in header */
|
||||
.admin-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -68,9 +91,8 @@ header h1 {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
& .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
@ -78,48 +100,50 @@ header h1 {
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
& .stat-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
& .stat-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
& .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 {
|
||||
& .stat-value {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--header-text);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
& .stat-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid layout for sections */
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@ -133,6 +157,11 @@ header h1 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
ADMIN SECTION COMPONENTS
|
||||
============================================================================= */
|
||||
|
||||
/* Admin section cards */
|
||||
.admin-section {
|
||||
background-color: var(--card-background);
|
||||
border-radius: 12px;
|
||||
@ -146,22 +175,23 @@ header h1 {
|
||||
transition: all 0.3s ease-in-out;
|
||||
height: auto;
|
||||
max-height: calc(100vw / 3);
|
||||
}
|
||||
|
||||
.admin-section:hover {
|
||||
&:hover {
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-section:active {
|
||||
&:active {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-section.square-ratio {
|
||||
&.square-ratio {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Player list section */
|
||||
.player-list-section {
|
||||
grid-column: span 1;
|
||||
height: auto;
|
||||
@ -174,27 +204,28 @@ header h1 {
|
||||
flex: 1;
|
||||
padding-right: 0.5rem;
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
/* Customize scrollbar for webkit browsers */
|
||||
.player-list::-webkit-scrollbar {
|
||||
/* Customize scrollbar for webkit browsers */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.player-list::-webkit-scrollbar-track {
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.player-list::-webkit-scrollbar-thumb {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.player-list::-webkit-scrollbar-thumb:hover {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Player item in the list */
|
||||
.player-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -206,30 +237,42 @@ header h1 {
|
||||
border: none;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.player-item:last-child {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.player-item:hover {
|
||||
&:hover {
|
||||
background-color: var(--tile-hover);
|
||||
box-shadow: 0 4px 6px var(--shadow-color);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.player-info {
|
||||
& .player-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.player-name {
|
||||
& .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-role {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
@ -254,17 +297,11 @@ header h1 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.player-money {
|
||||
font-size: 0.875rem;
|
||||
color: var(--success-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.player-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
/* =============================================================================
|
||||
BUTTONS AND INTERACTIVE ELEMENTS
|
||||
============================================================================= */
|
||||
|
||||
/* Action buttons */
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
@ -278,30 +315,31 @@ header h1 {
|
||||
.promote-btn {
|
||||
background-color: #22c55e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.promote-btn:hover {
|
||||
&:hover {
|
||||
background-color: #16a34a;
|
||||
}
|
||||
}
|
||||
|
||||
.demote-btn {
|
||||
background-color: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demote-btn:hover {
|
||||
&:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
.message-btn {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message-btn:hover {
|
||||
&:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
/* Search and filter components */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -317,32 +355,63 @@ header h1 {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--card-background);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
&: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);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input:hover {
|
||||
border-color: var(--primary-color);
|
||||
.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;
|
||||
|
||||
&: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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
& label {
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
& input {
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
@ -350,16 +419,17 @@ header h1 {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--card-background);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.form-group input:hover {
|
||||
&:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
@ -374,21 +444,25 @@ header h1 {
|
||||
transition: all 0.2s ease-in-out;
|
||||
opacity: 0.9;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
&:hover {
|
||||
background-color: var(--primary-hover);
|
||||
opacity: 1;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
/* =============================================================================
|
||||
MODALS
|
||||
============================================================================= */
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -417,12 +491,12 @@ header h1 {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
& h2 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
@ -432,11 +506,15 @@ header h1 {
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.close-modal:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
/* =============================================================================
|
||||
BADGES AND STATUS INDICATORS
|
||||
============================================================================= */
|
||||
|
||||
.badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
@ -447,34 +525,6 @@ header h1 {
|
||||
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;
|
||||
@ -485,37 +535,44 @@ header h1 {
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.rank-1 {
|
||||
/* Rank styling based on type */
|
||||
.rank-enlisted {
|
||||
background-color: #e2e8f0;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.rank-2 {
|
||||
.rank-warrant {
|
||||
background-color: #bfdbfe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.rank-3 {
|
||||
background-color: #93c5fd;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.rank-4 {
|
||||
background-color: #60a5fa;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.rank-5 {
|
||||
.rank-officer {
|
||||
background-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.payday-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
margin: 0.5rem 0;
|
||||
/* Side indicators */
|
||||
.side-west {
|
||||
background-color: #3b82f6;
|
||||
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 {
|
||||
font-size: 0.75rem;
|
||||
color: var(--success-color);
|
||||
@ -525,3 +582,64 @@ header h1 {
|
||||
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;
|
||||
}
|
||||
}
|
@ -26,15 +26,15 @@ private _default = [[],[],[],[]];
|
||||
private _armory_unlocks = GETVAR(player,Armory_Unlocks,_default);
|
||||
|
||||
private _typeToNumber = switch (_type) do {
|
||||
case "backpack": {3};
|
||||
case "facewear": {0};
|
||||
case "headgear": {0};
|
||||
case "hmd": {0};
|
||||
case "item": {0};
|
||||
case "magazine": {2};
|
||||
case "uniform": {0};
|
||||
case "facewear";
|
||||
case "headgear";
|
||||
case "hmd";
|
||||
case "item";
|
||||
case "uniform";
|
||||
case "vest": {0};
|
||||
case "weapon": {1};
|
||||
case "magazine": {2};
|
||||
case "backpack": {3};
|
||||
default {0};
|
||||
};
|
||||
|
||||
|
@ -5,25 +5,26 @@
|
||||
* Author: IDSolutions
|
||||
*
|
||||
* [Description]
|
||||
* Initializes the arsenal system
|
||||
* Initializes the arsenal system with armory and garage data
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Armory data <ARRAY>
|
||||
* 1: Garage data <ARRAY>
|
||||
* 0: Armory data <ARRAY> - [items, weapons, magazines, backpacks]
|
||||
* 1: Garage data <ARRAY> - [cars, armor, helicopters, planes, naval, static]
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
* BOOLEAN - true if initialization successful, false if invalid data
|
||||
*
|
||||
* Examples:
|
||||
* None
|
||||
*
|
||||
* Public: Yes
|
||||
* Example:
|
||||
* [[_items, _weapons, _magazines, _backpacks], [_cars, _armor, _helis, _planes, _naval, _statics]] call forge_client_arsenal_fnc_initArsenal
|
||||
*/
|
||||
|
||||
params [["_armory_data", [], [[]]], ["_garage_data", [], [[]]]];
|
||||
|
||||
if (count _armory_data isEqualTo [""]) then { _armory_data = [[],[],[],[]] };
|
||||
if (count _garage_data isEqualTo [""]) then { _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 (GVAR(armory_type) == 0) then {
|
||||
{
|
||||
[GVAR(gear_box), _x, false, true, 1, _forEachIndex] call BFUNC(addVirtualItemCargo);
|
||||
@ -34,23 +35,29 @@ 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) = _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(item_unlocks) = _items;
|
||||
GVAR(weapon_unlocks) = _weapons;
|
||||
GVAR(magazine_unlocks) = _magazines;
|
||||
GVAR(backpack_unlocks) = _backpacks;
|
||||
|
||||
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;
|
||||
GVAR(car_unlocks) = _cars;
|
||||
GVAR(armor_unlocks) = _armor;
|
||||
GVAR(heli_unlocks) = _helis;
|
||||
GVAR(plane_unlocks) = _planes;
|
||||
GVAR(naval_unlocks) = _naval;
|
||||
GVAR(static_unlocks) = _statics;
|
||||
|
||||
{
|
||||
[_x] call FUNC(addVirtualVehicles);
|
||||
} 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);
|
@ -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,"unknown")],
|
||||
"email", [GETVAR(player,FORGE_Email,"unknown@spearnet.mil")],
|
||||
"paygrade", [GETVAR(player,FORGE_PayGrade,"E1")],
|
||||
"organization", [GETVAR(player,FORGE_Organization,"")],
|
||||
"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))],
|
||||
"reputation", [rating player],
|
||||
"loadout", [getUnitLoadout player],
|
||||
"holster", [GETVAR(player,FORGE_Holster_Weapon,true)],
|
||||
|
@ -2,6 +2,7 @@ PREP(buyItem);
|
||||
PREP(buyVehicle);
|
||||
PREP(changeFilter);
|
||||
PREP(changePayment);
|
||||
PREP(handleDelivery);
|
||||
PREP(handlePurchase);
|
||||
PREP(initStore);
|
||||
PREP(openStore);
|
||||
|
@ -30,10 +30,7 @@ private _locker = GETVAR(player,FORGE_Locker,[]);
|
||||
if !([_price] call FUNC(handlePurchase)) exitWith {};
|
||||
|
||||
switch (_configType) do {
|
||||
case "item": {
|
||||
_displayName = getText (configFile >> "CfgWeapons" >> _className >> "displayName");
|
||||
_locker pushBack [_itemType, _className];
|
||||
};
|
||||
case "item";
|
||||
case "weapon": {
|
||||
_displayName = getText (configFile >> "CfgWeapons" >> _className >> "displayName");
|
||||
_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);
|
||||
|
||||
|
@ -38,14 +38,7 @@ private _items = _data select 1;
|
||||
|
||||
if (_category == _selectedCategory) then {
|
||||
switch (_configType) do {
|
||||
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 "item";
|
||||
case "weapon": {
|
||||
private _displayName = getText (configFile >> "CfgWeapons" >> _item >> "displayName");
|
||||
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 lbSetPicture [_index, _picture];
|
||||
};
|
||||
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 "backpack";
|
||||
case "vehicle": {
|
||||
private _displayName = getText (configFile >> "CfgVehicles" >> _item >> "displayName");
|
||||
private _picture = getText (configFile >> "CfgVehicles" >> _item >> "picture");
|
||||
|
66
addons/store/functions/fnc_handleDelivery.sqf
Normal file
66
addons/store/functions/fnc_handleDelivery.sqf
Normal 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"; };
|
||||
};
|
@ -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 {
|
||||
case "organization": {
|
||||
_store call ["getFunds", []];
|
||||
};
|
||||
case "player": { player getVariable [_payment select 1, 0] };
|
||||
case "mission": { missionNamespace getVariable [_payment select 1, 0] };
|
||||
default { 0 };
|
||||
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 };
|
||||
};
|
||||
|
||||
if (_balance < _price) exitWith {
|
||||
@ -63,14 +62,14 @@ if (_balance < _price) exitWith {
|
||||
};
|
||||
|
||||
switch (_varType) do {
|
||||
case "organization": {
|
||||
_store call ["updateFunds", -_price];
|
||||
};
|
||||
case "organization": { _store call ["updateFunds", -_price] };
|
||||
case "player": {
|
||||
player setVariable [_payment select 1, (_balance - _price), true];
|
||||
private _newBalance = _balance - _price;
|
||||
SETPVAR(player,_varName,_newBalance);
|
||||
};
|
||||
case "mission": {
|
||||
missionNamespace setVariable [_payment select 1, (_balance - _price), true];
|
||||
private _newBalance = _balance - _price;
|
||||
SETPVAR(missionNamespace,_varName,_newBalance);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -30,14 +30,11 @@ 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";
|
||||
private _categories = _data select 0;
|
||||
private _products = _data select 1;
|
||||
private _name = _data select 2;
|
||||
private _paymentMethods = _data select 3;
|
||||
private _data = _store getVariable ["storeData", []];
|
||||
|
||||
_data params [["_categories", [], [[]]], ["_products", [], [[]]], ["_name", "", [""]], ["_paymentMethods", [], [[]]]];
|
||||
|
||||
GVAR(currentStore) = _data;
|
||||
|
||||
_storeName ctrlSetText _name;
|
||||
|
||||
{
|
||||
|
@ -23,12 +23,9 @@ 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;
|
||||
private _item = _product select 0;
|
||||
private _price = _product select 1;
|
||||
private _configType = _product select 3;
|
||||
private _itemType = _product select 4;
|
||||
|
||||
_product params [["_item", "", [""]], ["_price", 0, [0]], ["_category", "", [""]], ["_configType", "", [""]], ["_itemType", "", [""]]];
|
||||
|
||||
switch (_configType) do {
|
||||
case "item";
|
||||
|
Loading…
x
Reference in New Issue
Block a user