+
-
+
-
+
-
+
+
+
-
+
Global Actions
@@ -47,7 +76,7 @@
-
+
Give All Money
@@ -57,7 +86,7 @@
-
+
Broadcast Message
@@ -68,24 +97,37 @@
+
+
-
+
+
+
+
-
+
@@ -100,7 +142,7 @@
diff --git a/addons/admin/ui/_site/script.js b/addons/admin/ui/_site/script.js
index dd03a61..b65f751 100644
--- a/addons/admin/ui/_site/script.js
+++ b/addons/admin/ui/_site/script.js
@@ -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 `
-
+
`}).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();
- closeMoneyModal();
- }
+ 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();
- closeMoneyModal();
- }
+ 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}`);
- closeMessageModal();
- }
+ 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
\ No newline at end of file
diff --git a/addons/admin/ui/_site/styles.css b/addons/admin/ui/_site/styles.css
index c667f29..1cb3230 100644
--- a/addons/admin/ui/_site/styles.css
+++ b/addons/admin/ui/_site/styles.css
@@ -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 {
+ 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;
+ }
}
-.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 in header */
.admin-stats {
display: flex;
align-items: center;
@@ -68,58 +91,59 @@ header h1 {
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;
+ }
}
-.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;
-}
-
+/* 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);
+
+ &: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;
+ }
}
-.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 */
.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 */
+ &::-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);
+ }
+ }
}
-/* 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 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;
+
+ &: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-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 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 {
- background-color: #16a34a;
+
+ &:hover {
+ background-color: #16a34a;
+ }
}
.demote-btn {
background-color: #ef4444;
color: white;
-}
-
-.demote-btn:hover {
- background-color: #dc2626;
+
+ &:hover {
+ background-color: #dc2626;
+ }
}
.message-btn {
background-color: #3b82f6;
color: white;
+
+ &:hover {
+ background-color: #2563eb;
+ }
}
-.message-btn:hover {
- background-color: #2563eb;
-}
-
+/* Search and filter components */
.search-bar {
display: flex;
flex-direction: column;
@@ -317,49 +355,81 @@ header h1 {
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);
+ }
}
-.search-input:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+.filter-bar {
+ display: flex;
+ gap: 0.5rem;
}
-.search-input:hover {
- border-color: var(--primary-color);
+.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 {
- font-weight: 500;
- color: var(--text-secondary);
- font-size: 0.875rem;
-}
-
-.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;
-}
-
-.form-group input:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
-}
-
-.form-group input:hover {
- border-color: var(--primary-color);
+
+ & 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;
+
+ &: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);
+ }
+ }
}
.submit-btn {
@@ -374,21 +444,25 @@ header h1 {
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);
+ }
}
-.submit-btn:hover {
- background-color: var(--primary-hover);
- opacity: 1;
- transform: translateY(-2px);
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
-}
+/* =============================================================================
+ MODALS
+ ============================================================================= */
-.submit-btn:active {
- transform: translateY(0);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.modal {
+ .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 {
- font-size: 1.25rem;
- color: var(--text-primary);
- font-weight: 600;
+
+ & 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;
+ }
+}
\ No newline at end of file
diff --git a/addons/arsenal/functions/fnc_addArmoryItem.sqf b/addons/arsenal/functions/fnc_addArmoryItem.sqf
index 684097d..7eb4e40 100644
--- a/addons/arsenal/functions/fnc_addArmoryItem.sqf
+++ b/addons/arsenal/functions/fnc_addArmoryItem.sqf
@@ -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};
};
diff --git a/addons/arsenal/functions/fnc_initArsenal.sqf b/addons/arsenal/functions/fnc_initArsenal.sqf
index 6d6f7b9..8dadd56 100644
--- a/addons/arsenal/functions/fnc_initArsenal.sqf
+++ b/addons/arsenal/functions/fnc_initArsenal.sqf
@@ -5,25 +5,26 @@
* Author: IDSolutions
*
* [Description]
- * Initializes the arsenal system
+ * Initializes the arsenal system with armory and garage data
*
* Arguments:
- * 0: Armory data
- * 1: Garage data
+ * 0: Armory data - [items, weapons, magazines, backpacks]
+ * 1: Garage data - [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));
\ No newline at end of file
+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);
\ No newline at end of file
diff --git a/addons/init/functions/fnc_playerDBSave.sqf b/addons/init/functions/fnc_playerDBSave.sqf
index 700ae0d..880ba3b 100644
--- a/addons/init/functions/fnc_playerDBSave.sqf
+++ b/addons/init/functions/fnc_playerDBSave.sqf
@@ -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)],
diff --git a/addons/store/XEH_PREP.hpp b/addons/store/XEH_PREP.hpp
index bb692d1..a556f8c 100644
--- a/addons/store/XEH_PREP.hpp
+++ b/addons/store/XEH_PREP.hpp
@@ -2,6 +2,7 @@ PREP(buyItem);
PREP(buyVehicle);
PREP(changeFilter);
PREP(changePayment);
+PREP(handleDelivery);
PREP(handlePurchase);
PREP(initStore);
PREP(openStore);
diff --git a/addons/store/functions/fnc_buyItem.sqf b/addons/store/functions/fnc_buyItem.sqf
index 0e258ce..90f6a21 100644
--- a/addons/store/functions/fnc_buyItem.sqf
+++ b/addons/store/functions/fnc_buyItem.sqf
@@ -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);
diff --git a/addons/store/functions/fnc_changeFilter.sqf b/addons/store/functions/fnc_changeFilter.sqf
index 2357c1b..885c370 100644
--- a/addons/store/functions/fnc_changeFilter.sqf
+++ b/addons/store/functions/fnc_changeFilter.sqf
@@ -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");
diff --git a/addons/store/functions/fnc_handleDelivery.sqf b/addons/store/functions/fnc_handleDelivery.sqf
new file mode 100644
index 0000000..e280070
--- /dev/null
+++ b/addons/store/functions/fnc_handleDelivery.sqf
@@ -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
+ *
+ * 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 [
+ "Order Processing
Estimated delivery: %1 ",
+ [_deliveryTime, "MM:SS"] call BIS_fnc_secondsToString
+ ],
+ "info",
+ 3,
+ "right"
+ ] call EFUNC(misc,notify);
+
+ uiSleep (_deliveryTime / 2);
+
+ [
+ "Package in transit ",
+ "warning",
+ 2,
+ "left"
+ ] call EFUNC(misc,notify);
+
+ uiSleep (_deliveryTime / 2);
+
+ SETPVAR(player,FORGE_Locker,_newLocker);
+
+ [
+ "Order Delivered!
Check your locker ",
+ "success",
+ 5,
+ "left"
+ ] call EFUNC(misc,notify);
+
+ if (hasInterface) then { playSound "FD_Finish_F"; };
+} else {
+ SETPVAR(player,FORGE_Locker,_newLocker);
+
+ [
+ "Order Complete!
Items added to locker ",
+ "success",
+ 5,
+ "left"
+ ] call EFUNC(misc,notify);
+
+ if (hasInterface) then { playSound "FD_Finish_F"; };
+};
\ No newline at end of file
diff --git a/addons/store/functions/fnc_handlePurchase.sqf b/addons/store/functions/fnc_handlePurchase.sqf
index 6cc1cef..d04f3ea 100644
--- a/addons/store/functions/fnc_handlePurchase.sqf
+++ b/addons/store/functions/fnc_handlePurchase.sqf
@@ -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);
};
};
diff --git a/addons/store/functions/fnc_openStore.sqf b/addons/store/functions/fnc_openStore.sqf
index 66056a1..b60ba21 100644
--- a/addons/store/functions/fnc_openStore.sqf
+++ b/addons/store/functions/fnc_openStore.sqf
@@ -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;
{
diff --git a/addons/store/functions/fnc_selectProduct.sqf b/addons/store/functions/fnc_selectProduct.sqf
index d3c9200..0780a9a 100644
--- a/addons/store/functions/fnc_selectProduct.sqf
+++ b/addons/store/functions/fnc_selectProduct.sqf
@@ -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";
${player.name}
- Rank ${player.rank}
- $${player.money.toLocaleString()}
- Payday: $${paydayAmount.toLocaleString()}
+ ${player.paygrade}
+ ${parseInt(player.funds).toLocaleString()}
+ Payday: ${paydayAmount.toLocaleString()}
+ ${player.side}
- ${player.rank < adminData.maxRank ? `
-
- ` : ''}
- ${player.rank > 1 ? `
-
- ` : ''}
-
-
+
+
+
+