refactor: Remove tasks.json and update documentation in store functions
All checks were successful
Build / Build (push) Successful in 28s
This commit removes the `tasks.json` file from the `.vscode` directory. Additionally, it enhances the documentation in `fnc_buyItem.sqf` and `fnc_buyVehicle.sqf` by providing clearer descriptions of item and vehicle types. The `fnc_handlePurchase.sqf` has also been updated to improve variable scoping for better code clarity.
24
.vscode/tasks.json
vendored
@ -1,24 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Start Arma 3 Server",
|
||||
"type": "process",
|
||||
"command": "wscript.exe",
|
||||
"args": [
|
||||
"D:\\SteamLibrary\\steamapps\\common\\Arma 3\\start_serverhub_hidden.vbs"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
107
addons/admin/ui/_site/index.html
Normal file
@ -0,0 +1,107 @@
|
||||
<!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="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1>Admin Panel</h1>
|
||||
<div class="admin-stats">
|
||||
<div class="stat-item">
|
||||
<span>👥</span>
|
||||
<div>
|
||||
<div>Online Players</div>
|
||||
<div id="playerCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>👑</span>
|
||||
<div>
|
||||
<div>Staff Online</div>
|
||||
<div id="staffCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header> <main class="container">
|
||||
<div class="sections-grid">
|
||||
<div class="action-sections">
|
||||
<!-- Global Actions Section -->
|
||||
<div class="admin-section">
|
||||
<h2>Global Actions</h2>
|
||||
<div class="form-group">
|
||||
<label for="paydayAmount">Payday Amount</label>
|
||||
<input type="number" id="paydayAmount" min="0" value="1000">
|
||||
</div>
|
||||
<button class="submit-btn" onclick="triggerPayday()">Trigger Payday</button>
|
||||
</div>
|
||||
|
||||
<!-- Message System Section -->
|
||||
<div class="admin-section">
|
||||
<h2>Broadcast Message</h2>
|
||||
<div class="form-group">
|
||||
<label for="broadcastMessage">Message</label>
|
||||
<input type="text" id="broadcastMessage" placeholder="Enter message to broadcast...">
|
||||
</div>
|
||||
<button class="submit-btn" onclick="broadcastMessage()">Send to All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player List Section -->
|
||||
<div class="admin-section player-list-section">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-list" id="playerList">
|
||||
<!-- Players will be populated dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Message Modal -->
|
||||
<div id="messageModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Send Message</h2>
|
||||
<button class="close-modal" onclick="closeMessageModal()">×</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="messageInput">Message to <span id="messagePlayerName"></span></label>
|
||||
<input type="text" id="messageInput" placeholder="Enter your message...">
|
||||
</div>
|
||||
<button class="submit-btn" onclick="sendPlayerMessage()">Send Message</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Money Modal -->
|
||||
<div id="moneyModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Modify Money</h2>
|
||||
<button class="close-modal" onclick="closeMoneyModal()">×</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="moneyAmount">Amount</label>
|
||||
<input type="number" id="moneyAmount" placeholder="Enter amount...">
|
||||
</div>
|
||||
<div class="player-actions">
|
||||
<button class="action-btn promote-btn" onclick="giveMoney()">Give Money</button>
|
||||
<button class="action-btn demote-btn" onclick="takeMoney()">Take Money</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
228
addons/admin/ui/_site/script.js
Normal file
@ -0,0 +1,228 @@
|
||||
// Simulated admin data - this would be replaced with actual game data
|
||||
let adminData = {
|
||||
players: [
|
||||
{
|
||||
id: 1,
|
||||
name: "John_Doe",
|
||||
role: "admin",
|
||||
money: 50000,
|
||||
status: "online"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Jane_Smith",
|
||||
role: "mod",
|
||||
money: 25000,
|
||||
status: "online"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Mike_Johnson",
|
||||
role: "player",
|
||||
money: 10000,
|
||||
status: "offline"
|
||||
}
|
||||
],
|
||||
paydayAmount: 1000
|
||||
};
|
||||
|
||||
let selectedPlayerId = null;
|
||||
|
||||
// Initialize the admin panel
|
||||
function initializeAdmin() {
|
||||
updateStats();
|
||||
setupFilterListeners();
|
||||
updatePlayerList();
|
||||
}
|
||||
|
||||
// Update header statistics
|
||||
function updateStats() {
|
||||
const onlinePlayers = adminData.players.filter(p => p.status === "online").length;
|
||||
const onlineStaff = adminData.players.filter(p => p.status === "online" && (p.role === "admin" || p.role === "mod")).length;
|
||||
|
||||
document.getElementById('playerCount').textContent = onlinePlayers;
|
||||
document.getElementById('staffCount').textContent = onlineStaff;
|
||||
}
|
||||
|
||||
// Set up filter button listeners
|
||||
function setupFilterListeners() {
|
||||
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||
|
||||
filterButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||
button.classList.add('active');
|
||||
filterPlayers(button.dataset.filter);
|
||||
});
|
||||
});
|
||||
|
||||
// Set up search functionality
|
||||
const searchInput = document.getElementById('playerSearch');
|
||||
searchInput.addEventListener('input', () => {
|
||||
const activeFilter = document.querySelector('.filter-btn.active').dataset.filter;
|
||||
filterPlayers(activeFilter, searchInput.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter players based on category and search term
|
||||
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.role === 'admin' || p.role === 'moderator');
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (searchTerm) {
|
||||
const term = searchTerm.toLowerCase();
|
||||
filteredPlayers = filteredPlayers.filter(p =>
|
||||
p.name.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
|
||||
updatePlayerList(filteredPlayers);
|
||||
}
|
||||
|
||||
// Update the player list display
|
||||
function updatePlayerList(players = adminData.players) {
|
||||
const playerList = document.getElementById('playerList');
|
||||
playerList.innerHTML = players.map(player => `
|
||||
<div class="player-item" data-id="${player.id}">
|
||||
<div class="player-info">
|
||||
<span class="player-name">${player.name}</span>
|
||||
<span class="player-role role-${player.role}">${player.role}</span>
|
||||
<span class="badge badge-${player.status}">${player.status}</span>
|
||||
<span class="player-money">$${player.money.toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="player-actions">
|
||||
${player.role !== 'admin' ? `
|
||||
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
|
||||
${player.role === 'player' ? 'Promote to Mod' : 'Promote to Admin'}
|
||||
</button>
|
||||
` : ''}
|
||||
${player.role !== 'player' ? `
|
||||
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
|
||||
${player.role === 'admin' ? 'Demote to Mod' : 'Demote to Player'}
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="action-btn message-btn" onclick="openMessageModal(${player.id})">Message</button>
|
||||
<button class="action-btn" onclick="openMoneyModal(${player.id})">Modify Money</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Role management functions
|
||||
function promotePlayer(playerId) {
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
if (player) {
|
||||
if (player.role === 'player') {
|
||||
player.role = 'moderator';
|
||||
} else if (player.role === 'moderator') {
|
||||
player.role = 'admin';
|
||||
}
|
||||
updatePlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
function demotePlayer(playerId) {
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
if (player) {
|
||||
if (player.role === 'admin') {
|
||||
player.role = 'moderator';
|
||||
} else if (player.role === 'moderator') {
|
||||
player.role = 'player';
|
||||
}
|
||||
updatePlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
// Money management functions
|
||||
function openMoneyModal(playerId) {
|
||||
selectedPlayerId = playerId;
|
||||
const modal = document.getElementById('moneyModal');
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
function closeMoneyModal() {
|
||||
const modal = document.getElementById('moneyModal');
|
||||
modal.style.display = 'none';
|
||||
document.getElementById('moneyAmount').value = '';
|
||||
selectedPlayerId = null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message system functions
|
||||
function openMessageModal(playerId) {
|
||||
selectedPlayerId = playerId;
|
||||
const player = adminData.players.find(p => p.id === playerId);
|
||||
const modal = document.getElementById('messageModal');
|
||||
document.getElementById('messagePlayerName').textContent = player.name;
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
function closeMessageModal() {
|
||||
const modal = document.getElementById('messageModal');
|
||||
modal.style.display = 'none';
|
||||
document.getElementById('messageInput').value = '';
|
||||
selectedPlayerId = null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastMessage() {
|
||||
const message = document.getElementById('broadcastMessage').value;
|
||||
if (message) {
|
||||
console.log(`Broadcasting message to all players: ${message}`);
|
||||
document.getElementById('broadcastMessage').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Global actions
|
||||
function triggerPayday() {
|
||||
const amount = parseInt(document.getElementById('paydayAmount').value);
|
||||
if (amount) {
|
||||
adminData.players.forEach(player => {
|
||||
player.money += amount;
|
||||
});
|
||||
updatePlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeAdmin);
|
313
addons/admin/ui/_site/styles.css
Normal file
@ -0,0 +1,313 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.admin-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.action-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.admin-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.player-list-section {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.player-list {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
height: 0;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.player-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.player-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.player-role {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.role-admin {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-mod {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.role-player {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.player-money {
|
||||
font-size: 0.875rem;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.player-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.promote-btn {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.promote-btn:hover:not(:disabled) {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.demote-btn {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demote-btn:hover:not(:disabled) {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.message-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message-btn:hover:not(:disabled) {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.close-modal:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.badge-online {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-offline {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: #007bff;
|
||||
}
|
99
addons/bank/ui/_site/index.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FORGE - FDIC</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1>Federal Deposit Insurance Corporation</h1>
|
||||
<div class="balance-display">
|
||||
<div class="balance-item">
|
||||
<span>💰</span>
|
||||
<div>
|
||||
<div>Wallet</div>
|
||||
<div id="walletBalance">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="balance-item">
|
||||
<span>🏦</span>
|
||||
<div>
|
||||
<div>Account</div>
|
||||
<div id="accountBalance">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="actions-grid"> <!-- Transfer Between Accounts -->
|
||||
<div class="action-tile">
|
||||
<h2>Transfer Money</h2>
|
||||
<form id="transferForm">
|
||||
<div class="form-group">
|
||||
<label for="transferType">Transfer From</label>
|
||||
<select id="transferType" required>
|
||||
<option value="to_wallet">Account to Wallet</option>
|
||||
<option value="to_account">Wallet to Account</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="transferAmount">Amount</label>
|
||||
<input type="number" id="transferAmount" min="1" step="1" required>
|
||||
</div>
|
||||
<button type="submit" class="submit-btn">Transfer</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Transfer to Player -->
|
||||
<div class="action-tile">
|
||||
<h2>Transfer to Player</h2>
|
||||
<form id="transferPlayerForm">
|
||||
<div class="form-group">
|
||||
<label for="playerSelect">Player</label>
|
||||
<select id="playerSelect" required>
|
||||
<option value="" disabled selected>Select Player</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="playerTransferAmount">Amount</label>
|
||||
<input type="number" id="playerTransferAmount" min="1" step="1" required>
|
||||
</div>
|
||||
<button type="submit" class="submit-btn">Send Money</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Submit Timesheet -->
|
||||
<div class="action-tile">
|
||||
<h2>Submit Timesheet</h2>
|
||||
<form id="timesheetForm">
|
||||
<div class="form-group">
|
||||
<label for="hoursWorked">Hours Worked</label>
|
||||
<input type="number" id="hoursWorked" min="1" step="0.5" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="hourlyRate">Hourly Rate</label>
|
||||
<input type="number" id="hourlyRate" min="1" step="1" required>
|
||||
</div>
|
||||
<button type="submit" class="submit-btn">Submit Timesheet</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transaction History -->
|
||||
<section class="history-section">
|
||||
<h2>Transaction History</h2>
|
||||
<ul class="history-list" id="transactionHistory">
|
||||
<!-- Transactions will be added here dynamically -->
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
BIN
addons/bank/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/bank/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/bank/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/bank/ui/_site/public/fms_co.paa
Normal file
BIN
addons/bank/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/bank/ui/_site/public/gms_co.paa
Normal file
147
addons/bank/ui/_site/script.js
Normal file
@ -0,0 +1,147 @@
|
||||
// Simulated data - this would be replaced with actual game data
|
||||
let bankState = {
|
||||
wallet: 1000,
|
||||
account: 5000,
|
||||
players: [
|
||||
{ id: 1, name: "Player 1" },
|
||||
{ id: 2, name: "Player 2" },
|
||||
{ id: 3, name: "Player 3" }
|
||||
],
|
||||
transactions: []
|
||||
};
|
||||
|
||||
// Initialize the interface
|
||||
function initializeBank() {
|
||||
updateBalanceDisplays();
|
||||
populatePlayerList();
|
||||
setupEventListeners();
|
||||
loadTransactionHistory();
|
||||
}
|
||||
|
||||
// Update balance displays in the header
|
||||
function updateBalanceDisplays() {
|
||||
document.getElementById('walletBalance').textContent = `$${bankState.wallet.toLocaleString()}`;
|
||||
document.getElementById('accountBalance').textContent = `$${bankState.account.toLocaleString()}`;
|
||||
}
|
||||
|
||||
// Populate the player selection dropdown
|
||||
function populatePlayerList() {
|
||||
const playerSelect = document.getElementById('playerSelect');
|
||||
playerSelect.innerHTML = '<option value="" disabled selected>Select Player</option>';
|
||||
|
||||
bankState.players.forEach(player => {
|
||||
const option = document.createElement('option');
|
||||
option.value = player.id;
|
||||
option.textContent = player.name;
|
||||
playerSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a new transaction to history
|
||||
function addTransaction(type, amount, details = '') {
|
||||
const transaction = {
|
||||
type,
|
||||
amount,
|
||||
details,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
bankState.transactions.unshift(transaction);
|
||||
updateTransactionHistory();
|
||||
}
|
||||
|
||||
// Update the transaction history display
|
||||
function updateTransactionHistory() {
|
||||
const historyList = document.getElementById('transactionHistory');
|
||||
historyList.innerHTML = '';
|
||||
|
||||
bankState.transactions.forEach(transaction => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'history-item';
|
||||
|
||||
const isNegative = ['transfer_out', 'to_wallet'].includes(transaction.type);
|
||||
const amountClass = isNegative ? 'amount-negative' : 'amount-positive';
|
||||
const amountPrefix = isNegative ? '-' : '+';
|
||||
|
||||
li.innerHTML = `
|
||||
<span class="transaction-type">${formatTransactionType(transaction.type)}</span>
|
||||
<span class="transaction-details">${transaction.details}</span>
|
||||
<span class="amount ${amountClass}">${amountPrefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
|
||||
`;
|
||||
|
||||
historyList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// Format transaction type for display
|
||||
function formatTransactionType(type) {
|
||||
const types = {
|
||||
'to_wallet': 'To Wallet',
|
||||
'to_account': 'To Account',
|
||||
'transfer_out': 'Transfer Out',
|
||||
'transfer_in': 'Transfer In',
|
||||
'timesheet': 'Timesheet'
|
||||
};
|
||||
return types[type] || type;
|
||||
}
|
||||
|
||||
// Set up all form event listeners
|
||||
function setupEventListeners() { // Handle transfers between wallet and account
|
||||
document.getElementById('transferForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const amount = parseInt(document.getElementById('transferAmount').value);
|
||||
const transferType = document.getElementById('transferType').value;
|
||||
|
||||
if (transferType === 'to_wallet') {
|
||||
if (amount > bankState.account) {
|
||||
alert('Insufficient funds in account');
|
||||
return;
|
||||
}
|
||||
bankState.account -= amount;
|
||||
bankState.wallet += amount;
|
||||
} else {
|
||||
if (amount > bankState.wallet) {
|
||||
alert('Insufficient funds in wallet');
|
||||
return;
|
||||
}
|
||||
bankState.wallet -= amount;
|
||||
bankState.account += amount;
|
||||
}
|
||||
|
||||
addTransaction(transferType, amount);
|
||||
updateBalanceDisplays();
|
||||
e.target.reset();
|
||||
}); // Transfer to Player
|
||||
document.getElementById('transferPlayerForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const amount = parseInt(document.getElementById('playerTransferAmount').value);
|
||||
const playerId = document.getElementById('playerSelect').value;
|
||||
const playerName = bankState.players.find(p => p.id.toString() === playerId)?.name;
|
||||
|
||||
if (amount > bankState.account) {
|
||||
alert('Insufficient funds in account');
|
||||
return;
|
||||
}
|
||||
|
||||
bankState.account -= amount;
|
||||
addTransaction('transfer_out', amount, `To ${playerName}`);
|
||||
updateBalanceDisplays();
|
||||
e.target.reset();
|
||||
});
|
||||
|
||||
// Submit Timesheet
|
||||
document.getElementById('timesheetForm').addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const hours = parseFloat(document.getElementById('hoursWorked').value);
|
||||
const rate = parseInt(document.getElementById('hourlyRate').value);
|
||||
const amount = Math.floor(hours * rate);
|
||||
|
||||
bankState.account += amount;
|
||||
addTransaction('timesheet', amount, `${hours} hours @ $${rate}/hr`);
|
||||
updateBalanceDisplays();
|
||||
e.target.reset();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeBank);
|
163
addons/bank/ui/_site/styles.css
Normal file
@ -0,0 +1,163 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.balance-display {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.balance-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.balance-item i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.action-tile {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
aspect-ratio: 1;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.action-tile:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.action-tile h2 {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.form-group input, .form-group select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
margin-top: 2rem;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.history-list {
|
||||
list-style: none;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.history-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.transaction-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.amount-positive {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.amount-negative {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #28a745;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
45
addons/garage/ui/_site/index.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FORGE - Garage</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1>Garage</h1>
|
||||
<div class="garage-stats">
|
||||
<div class="stat-item">
|
||||
<i>🚗</i>
|
||||
<div>
|
||||
<div>Vehicles</div>
|
||||
<div id="vehicleCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<i>🔧</i>
|
||||
<div>
|
||||
<div>In Maintenance</div>
|
||||
<div id="maintenanceCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="category-filters" id="categoryFilters">
|
||||
<!-- Categories will be populated dynamically -->
|
||||
</div>
|
||||
|
||||
<div class="vehicles-grid" id="vehiclesGrid">
|
||||
<!-- Vehicles will be populated dynamically -->
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
184
addons/garage/ui/_site/script.js
Normal file
@ -0,0 +1,184 @@
|
||||
// Simulated garage data - this would be replaced with actual game data
|
||||
let garageData = {
|
||||
vehicles: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Offroad Vehicle",
|
||||
category: "Land",
|
||||
type: "Transport",
|
||||
status: "available",
|
||||
fuel: 100,
|
||||
damage: 0,
|
||||
maintenance: 95,
|
||||
lastUsed: "2025-04-10T15:30:00",
|
||||
image: "placeholder.jpg"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Transport Helicopter",
|
||||
category: "Air",
|
||||
type: "Transport",
|
||||
status: "in-use",
|
||||
fuel: 75,
|
||||
damage: 15,
|
||||
maintenance: 80,
|
||||
lastUsed: "2025-04-12T09:15:00",
|
||||
image: "placeholder.jpg"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Patrol Boat",
|
||||
category: "Sea",
|
||||
type: "Patrol",
|
||||
status: "maintenance",
|
||||
fuel: 50,
|
||||
damage: 35,
|
||||
maintenance: 45,
|
||||
lastUsed: "2025-04-11T18:45:00",
|
||||
image: "placeholder.jpg"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Armed SUV",
|
||||
category: "Land",
|
||||
type: "Combat",
|
||||
status: "available",
|
||||
fuel: 90,
|
||||
damage: 5,
|
||||
maintenance: 88,
|
||||
lastUsed: "2025-04-12T11:20:00",
|
||||
image: "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Initialize the garage interface
|
||||
function initializeGarage() {
|
||||
updateStats();
|
||||
setupCategoryFilters();
|
||||
displayVehicles();
|
||||
}
|
||||
|
||||
// Update garage statistics
|
||||
function updateStats() {
|
||||
const totalVehicles = garageData.vehicles.length;
|
||||
const inMaintenance = garageData.vehicles.filter(v => v.status === 'maintenance').length;
|
||||
|
||||
document.getElementById('vehicleCount').textContent = totalVehicles;
|
||||
document.getElementById('maintenanceCount').textContent = inMaintenance;
|
||||
}
|
||||
|
||||
// Set up category filters
|
||||
function setupCategoryFilters() {
|
||||
const categories = ['All', ...new Set(garageData.vehicles.map(v => v.category))];
|
||||
const filtersContainer = document.getElementById('categoryFilters');
|
||||
|
||||
categories.forEach(category => {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'filter-btn' + (category === 'All' ? ' active' : '');
|
||||
button.textContent = category;
|
||||
button.addEventListener('click', () => filterVehicles(category));
|
||||
filtersContainer.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter vehicles by category
|
||||
function filterVehicles(category) {
|
||||
// Update active filter button
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.textContent === category);
|
||||
});
|
||||
|
||||
// Filter and display vehicles
|
||||
const filteredVehicles = category === 'All'
|
||||
? garageData.vehicles
|
||||
: garageData.vehicles.filter(v => v.category === category);
|
||||
|
||||
displayVehicles(filteredVehicles);
|
||||
}
|
||||
|
||||
// Display vehicles in the grid
|
||||
function displayVehicles(vehicles = garageData.vehicles) {
|
||||
const grid = document.getElementById('vehiclesGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
vehicles.forEach(vehicle => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'vehicle-card';
|
||||
|
||||
const statusText = {
|
||||
'available': 'Available',
|
||||
'in-use': 'In Use',
|
||||
'maintenance': 'Maintenance'
|
||||
}[vehicle.status];
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="vehicle-image" style="background-image: url(${vehicle.image})"></div>
|
||||
<div class="vehicle-info">
|
||||
<div class="vehicle-header">
|
||||
<div class="vehicle-name">${vehicle.name}</div>
|
||||
<span class="vehicle-status status-${vehicle.status}">${statusText}</span>
|
||||
</div>
|
||||
<div class="vehicle-details">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Type</span>
|
||||
<span class="detail-value">${vehicle.type}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Category</span>
|
||||
<span class="detail-value">${vehicle.category}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Fuel</span>
|
||||
<div class="vehicle-stats">
|
||||
<span class="stat-dot ${getFuelStatusColor(vehicle.fuel)}"></span>
|
||||
<span>${vehicle.fuel}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Condition</span>
|
||||
<div class="vehicle-stats">
|
||||
<span class="stat-dot ${getConditionStatusColor(vehicle.maintenance)}"></span>
|
||||
<span>${vehicle.maintenance}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-actions">
|
||||
<button class="action-btn spawn-btn"
|
||||
onclick="spawnVehicle(${vehicle.id})"
|
||||
${vehicle.status !== 'available' ? 'disabled' : ''}>
|
||||
Spawn Vehicle
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Get color class for fuel status
|
||||
function getFuelStatusColor(fuelLevel) {
|
||||
if (fuelLevel > 66) return 'stat-green';
|
||||
if (fuelLevel > 33) return 'stat-yellow';
|
||||
return 'stat-red';
|
||||
}
|
||||
|
||||
// Get color class for vehicle condition
|
||||
function getConditionStatusColor(condition) {
|
||||
if (condition > 66) return 'stat-green';
|
||||
if (condition > 33) return 'stat-yellow';
|
||||
return 'stat-red';
|
||||
}
|
||||
|
||||
// Handle vehicle spawn
|
||||
function spawnVehicle(vehicleId) {
|
||||
const vehicle = garageData.vehicles.find(v => v.id === vehicleId);
|
||||
if (vehicle && vehicle.status === 'available') {
|
||||
alert(`Spawning vehicle: ${vehicle.name}`);
|
||||
// Here you would typically integrate with your game's vehicle spawning system
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeGarage);
|
233
addons/garage/ui/_site/styles.css
Normal file
@ -0,0 +1,233 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.garage-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.vehicles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.vehicle-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vehicle-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.vehicle-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #eee;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.vehicle-info {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.vehicle-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.vehicle-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.vehicle-status {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-available {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-in-use {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.vehicle-details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.vehicle-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.spawn-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spawn-btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.spawn-btn:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.spawn-btn:disabled:hover {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
.maintain-btn {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.maintain-btn:hover {
|
||||
background-color: #d39e00;
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.vehicle-stats {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.stat-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.stat-green {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.stat-yellow {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
|
||||
.stat-red {
|
||||
background-color: #dc3545;
|
||||
}
|
74
addons/locker/ui/_site/index.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FORGE - Locker</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1>Locker</h1>
|
||||
<div class="storage-stats">
|
||||
<div class="stat-item">
|
||||
<span>📦</span>
|
||||
<div>
|
||||
<div>Storage Space</div>
|
||||
<div id="storageSpace">0/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>🎒</span>
|
||||
<div>
|
||||
<div>Items Stored</div>
|
||||
<div id="itemCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="category-filters" id="categoryFilters">
|
||||
<button class="filter-btn active" data-category="all">All Items</button>
|
||||
<button class="filter-btn" data-category="weapons">Weapons</button>
|
||||
<button class="filter-btn" data-category="clothing">Clothing</button>
|
||||
<button class="filter-btn" data-category="equipment">Equipment</button>
|
||||
<button class="filter-btn" data-category="magazines">Magazines</button>
|
||||
</div>
|
||||
|
||||
<div class="sections-grid">
|
||||
<!-- Current Equipment Section -->
|
||||
<div class="equipment-section player-equipment">
|
||||
<h2>Current Equipment</h2>
|
||||
<div class="equipment-list" id="playerEquipment">
|
||||
<!-- Player equipment will be populated dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Storage Section -->
|
||||
<div class="equipment-section stored-equipment">
|
||||
<h2>Stored Equipment</h2>
|
||||
<div class="equipment-list" id="storedEquipment">
|
||||
<!-- Stored equipment will be populated dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Details Section -->
|
||||
<div class="equipment-section">
|
||||
<h2>Equipment Details</h2>
|
||||
<div id="equipmentDetails">
|
||||
<!-- Equipment details will be populated when an item is selected -->
|
||||
<div class="detail-placeholder">
|
||||
Select an item to view its details
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
236
addons/locker/ui/_site/script.js
Normal file
@ -0,0 +1,236 @@
|
||||
// Simulated locker data - this would be replaced with actual game data
|
||||
let lockerData = {
|
||||
storageSpace: {
|
||||
used: 45,
|
||||
total: 100
|
||||
},
|
||||
playerEquipment: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Combat Uniform",
|
||||
type: "clothing",
|
||||
category: "Uniform",
|
||||
condition: 95
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Tactical Vest",
|
||||
type: "clothing",
|
||||
category: "Vest",
|
||||
condition: 88
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Combat Backpack",
|
||||
type: "clothing",
|
||||
category: "Backpack",
|
||||
condition: 90
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Assault Rifle",
|
||||
type: "weapons",
|
||||
category: "Primary",
|
||||
condition: 92,
|
||||
attachments: ["Scope", "Grip"]
|
||||
}
|
||||
],
|
||||
storedItems: [
|
||||
{
|
||||
id: 5,
|
||||
name: "Pistol",
|
||||
type: "weapons",
|
||||
category: "Secondary",
|
||||
condition: 100
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Medical Kit",
|
||||
type: "equipment",
|
||||
category: "Medical",
|
||||
quantity: 3
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Rifle Magazines",
|
||||
type: "magazines",
|
||||
category: "Magazine",
|
||||
quantity: 5
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Combat Helmet",
|
||||
type: "clothing",
|
||||
category: "Headgear",
|
||||
condition: 85
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Initialize the locker interface
|
||||
function initializeLocker() {
|
||||
updateStats();
|
||||
setupFilterListeners();
|
||||
updateEquipmentLists();
|
||||
}
|
||||
|
||||
// Update storage statistics
|
||||
function updateStats() {
|
||||
document.getElementById('storageSpace').textContent =
|
||||
`${lockerData.storageSpace.used}/${lockerData.storageSpace.total}`;
|
||||
document.getElementById('itemCount').textContent =
|
||||
lockerData.storedItems.length;
|
||||
}
|
||||
|
||||
// Set up category filter listeners
|
||||
function setupFilterListeners() {
|
||||
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||
|
||||
filterButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
// Update active state
|
||||
filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||
button.classList.add('active');
|
||||
|
||||
// Filter items
|
||||
const category = button.dataset.category;
|
||||
filterEquipment(category);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Filter equipment by category
|
||||
function filterEquipment(category) {
|
||||
const playerItems = category === 'all'
|
||||
? lockerData.playerEquipment
|
||||
: lockerData.playerEquipment.filter(item => item.type === category);
|
||||
|
||||
const storedItems = category === 'all'
|
||||
? lockerData.storedItems
|
||||
: lockerData.storedItems.filter(item => item.type === category);
|
||||
|
||||
updateEquipmentLists(playerItems, storedItems);
|
||||
}
|
||||
|
||||
// Update equipment lists display
|
||||
function updateEquipmentLists(playerItems = lockerData.playerEquipment, storedItems = lockerData.storedItems) {
|
||||
const playerList = document.getElementById('playerEquipment');
|
||||
const storedList = document.getElementById('storedEquipment');
|
||||
|
||||
// Update player equipment
|
||||
playerList.innerHTML = playerItems.map(item => `
|
||||
<div class="equipment-item" data-id="${item.id}">
|
||||
<div class="item-info">
|
||||
<span class="item-name">${item.name}</span>
|
||||
<span class="item-type">${item.category}</span>
|
||||
${item.condition ? `
|
||||
<span class="item-quantity">
|
||||
${item.condition}%
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
<button class="action-btn store-btn" onclick="storeItem(${item.id})">
|
||||
Store
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Update stored equipment
|
||||
storedList.innerHTML = storedItems.map(item => `
|
||||
<div class="equipment-item" data-id="${item.id}">
|
||||
<div class="item-info">
|
||||
<span class="item-name">${item.name}</span>
|
||||
<span class="item-type">${item.category}</span>
|
||||
${item.quantity ? `
|
||||
<span class="item-quantity">
|
||||
x${item.quantity}
|
||||
</span>
|
||||
` : item.condition ? `
|
||||
<span class="item-quantity">
|
||||
${item.condition}%
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
<button class="action-btn equip-btn" onclick="equipItem(${item.id})">
|
||||
Equip
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Add click listeners for showing details
|
||||
document.querySelectorAll('.equipment-item').forEach(item => {
|
||||
item.addEventListener('click', () => showItemDetails(item.dataset.id));
|
||||
});
|
||||
}
|
||||
|
||||
// Show item details
|
||||
function showItemDetails(itemId) {
|
||||
const item = lockerData.playerEquipment.find(i => i.id === parseInt(itemId)) ||
|
||||
lockerData.storedItems.find(i => i.id === parseInt(itemId));
|
||||
|
||||
if (!item) return;
|
||||
|
||||
const detailsDiv = document.getElementById('equipmentDetails');
|
||||
detailsDiv.innerHTML = `
|
||||
<h3>${item.name}</h3>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Category:</span>
|
||||
<span class="detail-value">${item.category}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Type:</span>
|
||||
<span class="detail-value">${item.type}</span>
|
||||
</div>
|
||||
${item.condition ? `
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Condition:</span>
|
||||
<span class="detail-value">${item.condition}%</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${item.quantity ? `
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Quantity:</span>
|
||||
<span class="detail-value">x${item.quantity}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${item.attachments ? `
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Attachments:</span>
|
||||
<span class="detail-value">${item.attachments.join(', ')}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Store an item in the locker
|
||||
function storeItem(itemId) {
|
||||
const itemIndex = lockerData.playerEquipment.findIndex(item => item.id === itemId);
|
||||
if (itemIndex === -1) return;
|
||||
|
||||
const item = lockerData.playerEquipment[itemIndex];
|
||||
lockerData.playerEquipment.splice(itemIndex, 1);
|
||||
lockerData.storedItems.push(item);
|
||||
lockerData.storageSpace.used += 1;
|
||||
|
||||
updateStats();
|
||||
updateEquipmentLists();
|
||||
}
|
||||
|
||||
// Equip an item from the locker
|
||||
function equipItem(itemId) {
|
||||
const itemIndex = lockerData.storedItems.findIndex(item => item.id === itemId);
|
||||
if (itemIndex === -1) return;
|
||||
|
||||
const item = lockerData.storedItems[itemIndex];
|
||||
lockerData.storedItems.splice(itemIndex, 1);
|
||||
lockerData.playerEquipment.push(item);
|
||||
lockerData.storageSpace.used -= 1;
|
||||
|
||||
updateStats();
|
||||
updateEquipmentLists();
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeLocker);
|
194
addons/locker/ui/_site/styles.css
Normal file
@ -0,0 +1,194 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.storage-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: auto auto;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
min-height: calc(100vh - 200px); /* Account for header and padding */
|
||||
}
|
||||
|
||||
.equipment-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.equipment-section h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.equipment-list {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
height: 0; /* This forces the flex-grow to work properly */
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.equipment-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.equipment-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.equipment-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.item-type {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-quantity {
|
||||
font-size: 0.875rem;
|
||||
color: #28a745;
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.store-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.store-btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.equip-btn {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.equip-btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.equip-btn:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.equipment-section.player-equipment {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.equipment-section.stored-equipment {
|
||||
grid-column: span 1;
|
||||
grid-row: span 2;
|
||||
}
|
0
addons/org/ui/BaseControl.hpp
Normal file
90
addons/org/ui/_site/index.html
Normal file
@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FORGE - ORG</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1 id="orgName">Organization Name</h1>
|
||||
<div class="org-stats">
|
||||
<div class="stat-item">
|
||||
<span>⭐</span>
|
||||
<div>
|
||||
<div>Reputation</div>
|
||||
<div id="orgReputation">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>💰</span>
|
||||
<div>
|
||||
<div>Funds</div>
|
||||
<div id="orgFunds">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="sections-grid">
|
||||
<!-- Members Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Members</h2>
|
||||
<ul class="member-list" id="membersList">
|
||||
<!-- Members will be populated dynamically -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Vehicles Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Vehicles</h2>
|
||||
<ul class="asset-list" id="vehiclesList">
|
||||
<!-- Vehicles will be populated dynamically -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Equipment</h2>
|
||||
<ul class="asset-list" id="equipmentList">
|
||||
<!-- Equipment will be populated dynamically -->
|
||||
</ul>
|
||||
</div> <!-- Properties Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Properties</h2>
|
||||
<ul class="asset-list" id="propertiesList">
|
||||
<!-- Properties will be populated dynamically -->
|
||||
</ul>
|
||||
</div> <!-- Supplies Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Supplies</h2>
|
||||
<ul class="asset-list" id="suppliesList">
|
||||
<!-- Supplies will be populated dynamically -->
|
||||
</ul>
|
||||
</div> <!-- Transactions Section -->
|
||||
<div class="section-tile">
|
||||
<h2>Transactions</h2>
|
||||
<ul class="transaction-list" id="transactionsList">
|
||||
<!-- Transactions will be populated dynamically -->
|
||||
</ul>
|
||||
</div> <!-- Memos Section -->
|
||||
<div class="section-tile memo-section">
|
||||
<h2>Memos & Logs</h2>
|
||||
<div class="memo-controls">
|
||||
<button id="addMemoBtn" class="add-btn">Add Memo</button>
|
||||
</div>
|
||||
<ul class="memo-list" id="memosList">
|
||||
<!-- Memos will be populated dynamically -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
BIN
addons/org/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/org/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/org/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/org/ui/_site/public/fms_co.paa
Normal file
BIN
addons/org/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/org/ui/_site/public/gms_co.paa
Normal file
302
addons/org/ui/_site/script.js
Normal file
@ -0,0 +1,302 @@
|
||||
// Simulated organization data - this would be replaced with actual game data
|
||||
let orgData = {
|
||||
name: "Black Rifle Company",
|
||||
reputation: 1250,
|
||||
funds: 75000,
|
||||
transactions: [
|
||||
{ id: 1, type: "deposit", amount: 5000, description: "Weekly income", date: "2025-04-12T10:30:00" },
|
||||
{ id: 2, type: "withdrawal", amount: -2000, description: "Vehicle maintenance", date: "2025-04-11T15:45:00" },
|
||||
{ id: 3, type: "deposit", amount: 3000, description: "Property rent", date: "2025-04-10T09:15:00" },
|
||||
{ id: 4, type: "withdrawal", amount: -1500, description: "Equipment purchase", date: "2025-04-09T14:20:00" }
|
||||
],
|
||||
members: [
|
||||
{ id: 1, name: "John Doe", role: "owner", status: "online" },
|
||||
{ id: 2, name: "Jane Smith", role: "admin", status: "online" },
|
||||
{ id: 3, name: "Mike Johnson", role: "member", status: "offline" },
|
||||
{ id: 4, name: "Sarah Wilson", role: "member", status: "online" }
|
||||
],
|
||||
vehicles: [
|
||||
{ id: 1, name: "Transport Truck", type: "Vehicle", value: 25000 },
|
||||
{ id: 2, name: "Patrol Car", type: "Vehicle", value: 15000 }
|
||||
],
|
||||
equipment: [
|
||||
{ id: 1, name: "Combat Gear Set", type: "Equipment", value: 5000 },
|
||||
{ id: 3, name: "Radio Equipment", type: "Equipment", value: 3000 }
|
||||
],
|
||||
properties: [
|
||||
{ id: 1, name: "Main Base", type: "Property", value: 100000 },
|
||||
{ id: 2, name: "Storage Facility", type: "Property", value: 50000 }
|
||||
],
|
||||
supplies: [
|
||||
{ id: 1, name: "Medical Supplies", type: "Supply", value: 2000, quantity: 50 },
|
||||
{ id: 2, name: "Ammunition", type: "Supply", value: 5000, quantity: 1000 },
|
||||
{ id: 3, name: "Food Rations", type: "Supply", value: 1000, quantity: 100 },
|
||||
{ id: 4, name: "Repair Kits", type: "Supply", value: 3000, quantity: 25 }
|
||||
],
|
||||
memos: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Weekly Mission Update",
|
||||
content: "New assignments available in the northern sector. All teams please check your mission boards.",
|
||||
author: "John Doe",
|
||||
date: "2025-04-12T08:30:00",
|
||||
priority: "high"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Equipment Maintenance",
|
||||
content: "Regular maintenance check required for all vehicles by end of week.",
|
||||
author: "Jane Smith",
|
||||
date: "2025-04-11T14:15:00",
|
||||
priority: "medium"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Initialize the organization interface
|
||||
function initializeOrg() {
|
||||
updateOrgInfo();
|
||||
updateMembers();
|
||||
updateAssets();
|
||||
updateTransactions();
|
||||
updateMemos();
|
||||
setupMemoControls();
|
||||
}
|
||||
|
||||
// Update organization info in the header
|
||||
function updateOrgInfo() {
|
||||
document.getElementById('orgName').textContent = orgData.name;
|
||||
document.getElementById('orgReputation').textContent = orgData.reputation.toLocaleString();
|
||||
document.getElementById('orgFunds').textContent = `$${orgData.funds.toLocaleString()}`;
|
||||
}
|
||||
|
||||
// Update members list
|
||||
function updateMembers() {
|
||||
const membersList = document.getElementById('membersList');
|
||||
membersList.innerHTML = '';
|
||||
|
||||
orgData.members.forEach(member => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'member-item';
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="member-info">
|
||||
<span class="member-status status-${member.status}"></span>
|
||||
<span>${member.name}</span>
|
||||
<span class="member-role role-${member.role}">${member.role}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
membersList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// Update assets lists (vehicles, equipment, properties)
|
||||
function updateAssets() {
|
||||
// Update vehicles
|
||||
const vehiclesList = document.getElementById('vehiclesList');
|
||||
vehiclesList.innerHTML = '';
|
||||
orgData.vehicles.forEach(vehicle => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'asset-item';
|
||||
li.innerHTML = `
|
||||
<div class="asset-info">
|
||||
<span>${vehicle.name}</span>
|
||||
<span class="asset-type">${vehicle.type}</span>
|
||||
</div>
|
||||
<span class="asset-value">$${vehicle.value.toLocaleString()}</span>
|
||||
`;
|
||||
vehiclesList.appendChild(li);
|
||||
});
|
||||
|
||||
// Update equipment
|
||||
const equipmentList = document.getElementById('equipmentList');
|
||||
equipmentList.innerHTML = '';
|
||||
orgData.equipment.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'asset-item';
|
||||
li.innerHTML = `
|
||||
<div class="asset-info">
|
||||
<span>${item.name}</span>
|
||||
<span class="asset-type">${item.type}</span>
|
||||
</div>
|
||||
<span class="asset-value">$${item.value.toLocaleString()}</span>
|
||||
`;
|
||||
equipmentList.appendChild(li);
|
||||
}); // Update properties
|
||||
const propertiesList = document.getElementById('propertiesList');
|
||||
propertiesList.innerHTML = '';
|
||||
orgData.properties.forEach(property => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'asset-item';
|
||||
li.innerHTML = `
|
||||
<div class="asset-info">
|
||||
<span>${property.name}</span>
|
||||
<span class="asset-type">${property.type}</span>
|
||||
</div>
|
||||
<span class="asset-value">$${property.value.toLocaleString()}</span>
|
||||
`;
|
||||
propertiesList.appendChild(li);
|
||||
});
|
||||
|
||||
// Update supplies
|
||||
const suppliesList = document.getElementById('suppliesList');
|
||||
suppliesList.innerHTML = '';
|
||||
orgData.supplies.forEach(supply => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'asset-item';
|
||||
li.innerHTML = `
|
||||
<div class="asset-info">
|
||||
<span>${supply.name}</span>
|
||||
<div class="supply-details">
|
||||
<span class="asset-type">${supply.type}</span>
|
||||
<span class="supply-quantity">Qty: ${supply.quantity}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="asset-value">$${supply.value.toLocaleString()}</span>
|
||||
`;
|
||||
suppliesList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// Update transactions list
|
||||
function updateTransactions() {
|
||||
const transactionsList = document.getElementById('transactionsList');
|
||||
transactionsList.innerHTML = '';
|
||||
|
||||
orgData.transactions.forEach(transaction => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'transaction-item';
|
||||
|
||||
const date = new Date(transaction.date);
|
||||
const formattedDate = date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
const amountClass = transaction.amount >= 0 ? 'amount-positive' : 'amount-negative';
|
||||
const prefix = transaction.amount >= 0 ? '+' : '';
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="transaction-info">
|
||||
<div>${transaction.description}</div>
|
||||
<div class="transaction-date">${formattedDate}</div>
|
||||
</div>
|
||||
<span class="transaction-amount ${amountClass}">${prefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
|
||||
`;
|
||||
|
||||
transactionsList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// Update memos list
|
||||
function updateMemos() {
|
||||
const memosList = document.getElementById('memosList');
|
||||
memosList.innerHTML = '';
|
||||
|
||||
orgData.memos.forEach(memo => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'memo-item';
|
||||
|
||||
const date = new Date(memo.date);
|
||||
const formattedDate = date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
li.innerHTML = `
|
||||
<div class="memo-header">
|
||||
<div class="memo-title">${memo.title}</div>
|
||||
<div class="memo-metadata">
|
||||
<span class="memo-author">by ${memo.author}</span>
|
||||
<span class="memo-date">${formattedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memo-content">${memo.content}</div>
|
||||
`;
|
||||
|
||||
memosList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
// Set up memo controls and dialog
|
||||
function setupMemoControls() {
|
||||
const addMemoBtn = document.getElementById('addMemoBtn');
|
||||
|
||||
addMemoBtn.addEventListener('click', () => {
|
||||
showMemoDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Show memo creation dialog
|
||||
function showMemoDialog() {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'memo-dialog';
|
||||
|
||||
dialog.innerHTML = `
|
||||
<form class="memo-form" id="memoForm">
|
||||
<div class="form-group">
|
||||
<label for="memoTitle">Title</label><br/>
|
||||
<input type="text" id="memoTitle" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="memoContent">Content</label><br/>
|
||||
<textarea id="memoContent" required></textarea>
|
||||
</div>
|
||||
<div class="memo-form-buttons">
|
||||
<button type="button" class="cancel-btn" id="cancelMemo">Cancel</button>
|
||||
<button type="submit" class="submit-btn">Add Memo</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'memo-dialog-overlay';
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
const form = dialog.querySelector('#memoForm');
|
||||
const cancelBtn = dialog.querySelector('#cancelMemo');
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const newMemo = {
|
||||
id: orgData.memos.length + 1,
|
||||
title: document.getElementById('memoTitle').value,
|
||||
content: document.getElementById('memoContent').value,
|
||||
author: orgData.members.find(m => m.role === 'owner').name,
|
||||
date: new Date().toISOString(),
|
||||
priority: "normal"
|
||||
};
|
||||
|
||||
orgData.memos.unshift(newMemo);
|
||||
updateMemos();
|
||||
|
||||
closeMemoDialog(dialog, overlay);
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
closeMemoDialog(dialog, overlay);
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', () => {
|
||||
closeMemoDialog(dialog, overlay);
|
||||
});
|
||||
}
|
||||
|
||||
// Close memo dialog
|
||||
function closeMemoDialog(dialog, overlay) {
|
||||
document.body.removeChild(dialog);
|
||||
document.body.removeChild(overlay);
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initializeOrg);
|
359
addons/org/ui/_site/styles.css
Normal file
@ -0,0 +1,359 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.org-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.stat-item i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Make memo section span full width */
|
||||
.section-tile.memo-section {
|
||||
grid-column: 1 / -1;
|
||||
aspect-ratio: unset;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.section-tile {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
aspect-ratio: 1;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-tile:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.section-tile h2 {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.member-list, .asset-list {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.member-item, .asset-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.member-item:last-child, .asset-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.member-info, .asset-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.member-role {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.role-owner {
|
||||
background-color: #ffd700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.role-admin {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-member {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.asset-type {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.supply-details {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.supply-quantity {
|
||||
font-size: 0.875rem;
|
||||
color: #28a745;
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.asset-value {
|
||||
color: #007bff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.transaction-list {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.transaction-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.transaction-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.transaction-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.transaction-description {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.transaction-date {
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.transaction-amount {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.amount-positive {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.amount-negative {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.memo-controls {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.memo-list {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.memo-item {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.memo-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.memo-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.memo-title {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.memo-metadata {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.memo-author {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.memo-content {
|
||||
color: #444;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.memo-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.memo-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.memo-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.memo-form input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.memo-form textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.memo-form-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
width: 100%;
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
.member-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #dc3545;
|
||||
}
|
@ -11,13 +11,13 @@
|
||||
* 0: Class Name <STRING> - The classname of the item to purchase
|
||||
* 1: Price <NUMBER> - The price of the item
|
||||
* 2: Config Type <STRING> - The type of config ("item", "weapon", "magazine", "backpack")
|
||||
* 3: Item Type <STRING> - The type of item for locker storage
|
||||
* 3: Item Type <STRING> - The type of item for locker storage ("backpack", "facewear", "headgear", "hmd", "item", "magazine", "uniform", "vest", "weapon")
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
*
|
||||
* Example:
|
||||
* ["arifle_MX_F", 1000, "weapon", "primary"] call forge_store_fnc_buyItem
|
||||
* ["arifle_MX_F", 1000, "weapon", "weapon"] call forge_store_fnc_buyItem
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
|
@ -10,7 +10,7 @@
|
||||
* Arguments:
|
||||
* 0: Class Name <STRING> - The classname of the vehicle to purchase
|
||||
* 1: Price <NUMBER> - The price of the vehicle
|
||||
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "air", "ship", "tank")
|
||||
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "armor", "heli", "plane", "naval", "static")
|
||||
*
|
||||
* Return Value:
|
||||
* None
|
||||
|
@ -23,6 +23,7 @@ params ["_price"];
|
||||
|
||||
private _paymentData = GVAR(activePayment);
|
||||
private _payment = call compile _paymentData;
|
||||
private _store = nil;
|
||||
|
||||
scopeName "main";
|
||||
|
||||
@ -36,7 +37,7 @@ if (count _payment > 3) then {
|
||||
};
|
||||
|
||||
if (_payment select 0 == "Organization") then {
|
||||
private _store = call EFUNC(org,verifyOrgStore);
|
||||
_store = call EFUNC(org,verifyOrgStore);
|
||||
private _org = _store call ["getOrg", []];
|
||||
private _ownerUID = _org get "owner";
|
||||
|
||||
@ -49,7 +50,6 @@ if (_payment select 0 == "Organization") then {
|
||||
private _varType = _payment select 2;
|
||||
private _balance = switch (_varType) do {
|
||||
case "organization": {
|
||||
private _store = call EFUNC(org,verifyOrgStore);
|
||||
_store call ["getFunds", []];
|
||||
};
|
||||
case "player": { player getVariable [_payment select 1, 0] };
|
||||
@ -64,7 +64,6 @@ if (_balance < _price) exitWith {
|
||||
|
||||
switch (_varType) do {
|
||||
case "organization": {
|
||||
private _store = call EFUNC(org,verifyOrgStore);
|
||||
_store call ["updateFunds", -_price];
|
||||
};
|
||||
case "player": {
|
||||
|
150
addons/store/ui/_site/data/categories.json
Normal file
@ -0,0 +1,150 @@
|
||||
{
|
||||
"paymentMethods": [
|
||||
{
|
||||
"id": "cash",
|
||||
"name": "Cash",
|
||||
"icon": "💵"
|
||||
},
|
||||
{
|
||||
"id": "bank",
|
||||
"name": "Bank",
|
||||
"icon": "🏦"
|
||||
}
|
||||
],
|
||||
"Weapons": {
|
||||
"icon": "",
|
||||
"subcategories": {
|
||||
"Rifles": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Combat Rifle",
|
||||
"price": 1500,
|
||||
"image": "placeholder.jpg"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Sniper Rifle",
|
||||
"price": 2500,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Pistols": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Tactical Pistol",
|
||||
"price": 800,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Equipment": {
|
||||
"icon": "",
|
||||
"subcategories": {
|
||||
"Vests": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Tactical Vest",
|
||||
"price": 800,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Headwear": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Combat Helmet",
|
||||
"price": 1200,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Facewear": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Balaclava",
|
||||
"price": 500,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Hmd": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Night Vision Goggles",
|
||||
"price": 2000,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Backpacks": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Combat Backpack",
|
||||
"price": 450,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Uniforms": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Combat Fatigues",
|
||||
"price": 1000,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Vehicles": {
|
||||
"icon": "",
|
||||
"subcategories": {
|
||||
"Wheeled": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Transport Vehicle",
|
||||
"price": 25000,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Supplies": {
|
||||
"icon": "",
|
||||
"subcategories": {
|
||||
"Medical": {
|
||||
"icon": "",
|
||||
"products": [
|
||||
{
|
||||
"id": 11,
|
||||
"name": "First Aid Kit",
|
||||
"price": 300,
|
||||
"image": "placeholder.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
addons/store/ui/_site/index.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FORGE - GMS</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<h1>General Military Surplus</h1>
|
||||
<div class="payment-method">
|
||||
<select id="paymentMethod" class="payment-select">
|
||||
<option value="" disabled selected>Select Payment Method</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="container">
|
||||
<!-- Content will be dynamically populated -->
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
BIN
addons/store/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/store/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/store/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/store/ui/_site/public/fms_co.paa
Normal file
BIN
addons/store/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/store/ui/_site/public/gms_co.paa
Normal file
174
addons/store/ui/_site/script.js
Normal file
@ -0,0 +1,174 @@
|
||||
// Store data will be loaded from JSON
|
||||
let categories = null;
|
||||
let currentCategory = null;
|
||||
let currentSubcategory = null;
|
||||
let selectedPaymentMethod = null;
|
||||
|
||||
function initializePaymentMethods() {
|
||||
const paymentSelect = document.getElementById('paymentMethod');
|
||||
paymentSelect.innerHTML = `
|
||||
<option value="" disabled ${!selectedPaymentMethod ? 'selected' : ''}>Select Payment Method</option>
|
||||
${categories.paymentMethods.map(method => `
|
||||
<option value="${method.id}" ${method.id === selectedPaymentMethod ? 'selected' : ''}>
|
||||
${method.icon} ${method.name}
|
||||
</option>
|
||||
`).join('')}
|
||||
`;
|
||||
paymentSelect.addEventListener('change', (e) => {
|
||||
selectedPaymentMethod = e.target.value;
|
||||
updateBuyButtons();
|
||||
});
|
||||
}
|
||||
|
||||
function updateBuyButtons() {
|
||||
const buyButtons = document.querySelectorAll('.buy-btn');
|
||||
buyButtons.forEach(button => {
|
||||
button.disabled = !selectedPaymentMethod;
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuantityChange(productId, change, quantityDisplay) {
|
||||
const currentQty = parseInt(quantityDisplay.textContent);
|
||||
const newQty = Math.max(1, currentQty + change);
|
||||
quantityDisplay.textContent = newQty;
|
||||
}
|
||||
|
||||
function handlePurchase(product, quantity) {
|
||||
if (!selectedPaymentMethod) {
|
||||
alert('Please select a payment method first');
|
||||
return;
|
||||
}
|
||||
|
||||
const total = product.price * quantity;
|
||||
alert(`Purchase Summary:
|
||||
Product: ${product.name}
|
||||
Quantity: ${quantity}
|
||||
Total: $${total.toLocaleString()}
|
||||
Payment Method: ${categories.paymentMethods.find(m => m.id === selectedPaymentMethod).name}`);
|
||||
// Here you would typically integrate with your game's purchasing system
|
||||
}
|
||||
|
||||
function showMainCategories() {
|
||||
const mainContent = document.querySelector('main');
|
||||
currentCategory = null;
|
||||
currentSubcategory = null;
|
||||
|
||||
mainContent.innerHTML = `
|
||||
<div class="categories-grid"></div>
|
||||
`;
|
||||
|
||||
const categoriesGrid = document.querySelector('.categories-grid');
|
||||
Object.entries(categories)
|
||||
.filter(([key]) => key !== 'paymentMethods')
|
||||
.forEach(([categoryName, categoryData]) => {
|
||||
const categoryTile = document.createElement('button');
|
||||
categoryTile.className = 'category-tile';
|
||||
categoryTile.innerHTML = `
|
||||
<span class="icon">${categoryData.icon}</span>
|
||||
<span class="name">${categoryName}</span>
|
||||
`;
|
||||
categoryTile.addEventListener('click', () => showSubcategories(categoryName));
|
||||
categoriesGrid.appendChild(categoryTile);
|
||||
});
|
||||
}
|
||||
|
||||
function showSubcategories(categoryName) {
|
||||
const mainContent = document.querySelector('main');
|
||||
currentCategory = categoryName;
|
||||
currentSubcategory = null;
|
||||
|
||||
mainContent.innerHTML = `
|
||||
<button class="back-button">← Back to Categories</button>
|
||||
<h2>${categoryName}</h2>
|
||||
<div class="subcategories-grid"></div>
|
||||
`;
|
||||
|
||||
const subcategoriesGrid = document.querySelector('.subcategories-grid');
|
||||
const backButton = document.querySelector('.back-button');
|
||||
backButton.addEventListener('click', showMainCategories);
|
||||
|
||||
Object.entries(categories[categoryName].subcategories).forEach(([subCategoryName, subCategoryData]) => {
|
||||
const subCategoryTile = document.createElement('button');
|
||||
subCategoryTile.className = 'subcategory-tile';
|
||||
subCategoryTile.innerHTML = `
|
||||
<span class="icon">${subCategoryData.icon}</span>
|
||||
<span class="name">${subCategoryName}</span>
|
||||
`;
|
||||
subCategoryTile.addEventListener('click', () => showProducts(categoryName, subCategoryName));
|
||||
subcategoriesGrid.appendChild(subCategoryTile);
|
||||
});
|
||||
}
|
||||
|
||||
function showProducts(categoryName, subCategoryName) {
|
||||
const mainContent = document.querySelector('main');
|
||||
currentSubcategory = subCategoryName;
|
||||
|
||||
mainContent.innerHTML = `
|
||||
<button class="back-button">← Back to ${categoryName}</button>
|
||||
<h2>${categoryName} > ${subCategoryName}</h2>
|
||||
<div class="products-grid"></div>
|
||||
`;
|
||||
|
||||
const productsGrid = document.querySelector('.products-grid');
|
||||
const backButton = document.querySelector('.back-button');
|
||||
backButton.addEventListener('click', () => showSubcategories(categoryName));
|
||||
|
||||
const products = categories[categoryName].subcategories[subCategoryName].products;
|
||||
products.forEach(product => {
|
||||
const productCard = document.createElement('div');
|
||||
productCard.className = 'product-card';
|
||||
productCard.innerHTML = `
|
||||
<div class="product-image"></div>
|
||||
<div class="product-info">
|
||||
<h3 class="product-name">${product.name}</h3>
|
||||
<p class="product-price">$${product.price.toLocaleString()}</p>
|
||||
<div class="product-controls">
|
||||
<div class="quantity-controls">
|
||||
<button class="quantity-btn minus">-</button>
|
||||
<span class="quantity-display">1</span>
|
||||
<button class="quantity-btn plus">+</button>
|
||||
</div>
|
||||
<button class="buy-btn" ${!selectedPaymentMethod ? 'disabled' : ''}>
|
||||
Buy Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const quantityDisplay = productCard.querySelector('.quantity-display');
|
||||
const minusBtn = productCard.querySelector('.quantity-btn.minus');
|
||||
const plusBtn = productCard.querySelector('.quantity-btn.plus');
|
||||
const buyBtn = productCard.querySelector('.buy-btn');
|
||||
|
||||
minusBtn.addEventListener('click', () => handleQuantityChange(product.id, -1, quantityDisplay));
|
||||
plusBtn.addEventListener('click', () => handleQuantityChange(product.id, 1, quantityDisplay));
|
||||
buyBtn.addEventListener('click', () => handlePurchase(product, parseInt(quantityDisplay.textContent)));
|
||||
|
||||
productsGrid.appendChild(productCard);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the store
|
||||
async function initializeStore() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:8000/data/categories.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load store data');
|
||||
}
|
||||
categories = await response.json();
|
||||
initializePaymentMethods();
|
||||
showMainCategories();
|
||||
} catch (error) {
|
||||
console.error('Error loading store data:', error);
|
||||
document.querySelector('main').innerHTML = `
|
||||
<div class="error-message">
|
||||
<h2>Error Loading Store Data</h2>
|
||||
<p>Please try again later.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeStore();
|
||||
});
|
16
addons/store/ui/_site/server.py
Normal file
@ -0,0 +1,16 @@
|
||||
import http.server
|
||||
import socketserver
|
||||
|
||||
PORT = 8000
|
||||
DIRECTORY = '.'
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||
|
||||
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
||||
print(f"Serving at http://localhost:{PORT}")
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer stopped.")
|
200
addons/store/ui/_site/styles.css
Normal file
@ -0,0 +1,200 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.categories-grid, .subcategories-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.category-tile, .subcategory-tile {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.category-tile:hover, .subcategory-tile:hover {
|
||||
background-color: #666;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.category-tile.active, .subcategory-tile.active {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.category-tile i, .subcategory-tile i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.products-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 256px;
|
||||
object-fit: cover;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 1.2rem;
|
||||
color: #007bff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.payment-select {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.product-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.quantity-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quantity-btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.quantity-display {
|
||||
font-size: 1.1rem;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
flex: 1;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.buy-btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.buy-btn:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|