Implemented features: - High-performance Rust extension with Redis persistence - Actor/player management with loadout, position, and state tracking - Banking system with deposit, withdraw, and transfer operations - Physical and virtual garage/locker systems for vehicle and equipment storage - Organization management with member tracking and permissions - Client-side UI with React-like state management - Server-side event-driven architecture with CBA Events - Security: Self-transfer prevention at multiple layers - Logging system with per-module log files - ICOM module for inter-server communication Co-Authored-By: Warp <agent@warp.dev>
318 lines
11 KiB
JavaScript
318 lines
11 KiB
JavaScript
/**
|
|
* Vehicle Garage Interface
|
|
* Handles vehicle management with spawn and store actions
|
|
*/
|
|
|
|
// Mock data - sample vehicles
|
|
const mockData = {
|
|
vehicles: [
|
|
// Cars
|
|
{ id: 1, name: "Sedan", type: "car", icon: "🚗", status: "stored", condition: 95, fuel: 80, location: "Garage A", seats: 4, speed: "180 km/h", cargo: "200 kg" },
|
|
{ id: 2, name: "Sports Car", type: "car", icon: "🏎️", status: "stored", condition: 100, fuel: 100, location: "Garage A", seats: 2, speed: "250 km/h", cargo: "50 kg" },
|
|
{ id: 3, name: "SUV", type: "car", icon: "🚙", status: "active", condition: 85, fuel: 60, location: "In Use", seats: 6, speed: "160 km/h", cargo: "400 kg" },
|
|
{ id: 4, name: "Hatchback", type: "car", icon: "🚗", status: "stored", condition: 90, fuel: 75, location: "Garage B", seats: 4, speed: "170 km/h", cargo: "250 kg" },
|
|
|
|
// Trucks
|
|
{ id: 5, name: "Pickup Truck", type: "truck", icon: "🚛", status: "stored", condition: 88, fuel: 70, location: "Garage A", seats: 2, speed: "140 km/h", cargo: "800 kg" },
|
|
{ id: 6, name: "Delivery Van", type: "truck", icon: "🚚", status: "stored", condition: 92, fuel: 85, location: "Garage B", seats: 3, speed: "130 km/h", cargo: "1200 kg" },
|
|
{ id: 7, name: "Heavy Truck", type: "truck", icon: "🚛", status: "active", condition: 75, fuel: 50, location: "In Use", seats: 2, speed: "120 km/h", cargo: "2000 kg" },
|
|
{ id: 8, name: "Box Truck", type: "truck", icon: "📦", status: "stored", condition: 80, fuel: 65, location: "Garage A", seats: 3, speed: "110 km/h", cargo: "1500 kg" },
|
|
|
|
// Aircraft
|
|
{ id: 9, name: "Helicopter", type: "air", icon: "🚁", status: "stored", condition: 95, fuel: 90, location: "Helipad", seats: 6, speed: "280 km/h", cargo: "500 kg" },
|
|
{ id: 10, name: "Light Plane", type: "air", icon: "✈️", status: "stored", condition: 100, fuel: 100, location: "Hangar", seats: 4, speed: "320 km/h", cargo: "300 kg" },
|
|
|
|
// Boats
|
|
{ id: 11, name: "Speedboat", type: "sea", icon: "🚤", status: "stored", condition: 93, fuel: 80, location: "Marina", seats: 4, speed: "100 km/h", cargo: "150 kg" },
|
|
{ id: 12, name: "Yacht", type: "sea", icon: "🛥️", status: "stored", condition: 98, fuel: 95, location: "Marina", seats: 12, speed: "60 km/h", cargo: "800 kg" }
|
|
]
|
|
};
|
|
|
|
// State
|
|
let selectedVehicle = null;
|
|
let statusFilter = 'all';
|
|
let typeFilter = 'all';
|
|
let searchQuery = '';
|
|
|
|
// Icons by type
|
|
const typeIcons = {
|
|
car: '🚗',
|
|
truck: '🚛',
|
|
air: '🚁',
|
|
sea: '🚤'
|
|
};
|
|
|
|
// Initialize
|
|
function initGarage() {
|
|
console.log('Garage interface initializing...');
|
|
|
|
setupEventHandlers();
|
|
renderVehicles();
|
|
updateStats();
|
|
|
|
console.log('Garage interface initialized');
|
|
}
|
|
|
|
// Event Handlers
|
|
function setupEventHandlers() {
|
|
// Close button
|
|
const closeBtn = document.querySelector('.close-btn');
|
|
if (closeBtn) {
|
|
closeBtn.addEventListener('click', () => {
|
|
console.log('Closing garage...');
|
|
sendEvent('garage::close', {});
|
|
});
|
|
}
|
|
|
|
// Status filters
|
|
const filterBtns = document.querySelectorAll('.filter-btn');
|
|
filterBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
filterBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
statusFilter = btn.dataset.filter;
|
|
renderVehicles();
|
|
});
|
|
});
|
|
|
|
// Type filters
|
|
const typeItems = document.querySelectorAll('.type-item');
|
|
typeItems.forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
typeItems.forEach(i => i.classList.remove('active'));
|
|
item.classList.add('active');
|
|
typeFilter = item.dataset.type;
|
|
renderVehicles();
|
|
});
|
|
});
|
|
|
|
// Search
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', (e) => {
|
|
searchQuery = e.target.value.toLowerCase();
|
|
renderVehicles();
|
|
});
|
|
}
|
|
|
|
// Spawn button
|
|
const spawnBtn = document.getElementById('spawnBtn');
|
|
if (spawnBtn) {
|
|
spawnBtn.addEventListener('click', () => {
|
|
if (selectedVehicle) {
|
|
spawnVehicle(selectedVehicle);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Store button
|
|
const storeBtn = document.getElementById('storeBtn');
|
|
if (storeBtn) {
|
|
storeBtn.addEventListener('click', () => {
|
|
if (selectedVehicle) {
|
|
storeVehicle(selectedVehicle);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Render vehicles
|
|
function renderVehicles() {
|
|
const vehiclesGrid = document.getElementById('vehiclesGrid');
|
|
if (!vehiclesGrid) return;
|
|
|
|
vehiclesGrid.innerHTML = '';
|
|
|
|
// Filter vehicles
|
|
let filtered = mockData.vehicles;
|
|
|
|
// Status filter
|
|
if (statusFilter !== 'all') {
|
|
filtered = filtered.filter(v => v.status === statusFilter);
|
|
}
|
|
|
|
// Type filter
|
|
if (typeFilter !== 'all') {
|
|
filtered = filtered.filter(v => v.type === typeFilter);
|
|
}
|
|
|
|
// Search filter
|
|
if (searchQuery) {
|
|
filtered = filtered.filter(v =>
|
|
v.name.toLowerCase().includes(searchQuery) ||
|
|
v.type.toLowerCase().includes(searchQuery)
|
|
);
|
|
}
|
|
|
|
// Render vehicles
|
|
filtered.forEach(vehicle => {
|
|
const card = document.createElement('div');
|
|
card.className = 'vehicle-card';
|
|
if (selectedVehicle && selectedVehicle.id === vehicle.id) {
|
|
card.classList.add('selected');
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<div class="vehicle-icon">${vehicle.icon}</div>
|
|
<div class="vehicle-name">${vehicle.name}</div>
|
|
<div class="vehicle-type">${vehicle.type}</div>
|
|
<div class="vehicle-status ${vehicle.status}">${vehicle.status}</div>
|
|
`;
|
|
|
|
card.addEventListener('click', () => selectVehicle(vehicle));
|
|
vehiclesGrid.appendChild(card);
|
|
});
|
|
|
|
console.log(`Rendered ${filtered.length} vehicles`);
|
|
}
|
|
|
|
// Select vehicle
|
|
function selectVehicle(vehicle) {
|
|
selectedVehicle = vehicle;
|
|
|
|
// Update selected state in grid
|
|
document.querySelectorAll('.vehicle-card').forEach(card => {
|
|
card.classList.remove('selected');
|
|
});
|
|
event.currentTarget.classList.add('selected');
|
|
|
|
// Show details
|
|
showVehicleDetails(vehicle);
|
|
}
|
|
|
|
// Show vehicle details
|
|
function showVehicleDetails(vehicle) {
|
|
const noSelection = document.getElementById('noSelection');
|
|
const vehicleDetails = document.getElementById('vehicleDetails');
|
|
const spawnBtn = document.getElementById('spawnBtn');
|
|
const storeBtn = document.getElementById('storeBtn');
|
|
|
|
if (noSelection) noSelection.style.display = 'none';
|
|
if (vehicleDetails) vehicleDetails.style.display = 'flex';
|
|
|
|
// Update details
|
|
document.getElementById('detailIcon').textContent = vehicle.icon;
|
|
document.getElementById('detailName').textContent = vehicle.name;
|
|
document.getElementById('detailType').textContent = vehicle.type;
|
|
document.getElementById('detailStatus').textContent = vehicle.status;
|
|
document.getElementById('detailCondition').textContent = `${vehicle.condition}%`;
|
|
document.getElementById('detailFuel').textContent = `${vehicle.fuel}%`;
|
|
document.getElementById('detailLocation').textContent = vehicle.location;
|
|
document.getElementById('detailSeats').textContent = vehicle.seats;
|
|
document.getElementById('detailSpeed').textContent = vehicle.speed;
|
|
document.getElementById('detailCargo').textContent = vehicle.cargo;
|
|
|
|
// Show/hide action buttons based on status
|
|
if (vehicle.status === 'stored') {
|
|
spawnBtn.style.display = 'flex';
|
|
storeBtn.style.display = 'none';
|
|
} else {
|
|
spawnBtn.style.display = 'none';
|
|
storeBtn.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
// Spawn vehicle
|
|
function spawnVehicle(vehicle) {
|
|
console.log('Spawning vehicle:', vehicle.name);
|
|
|
|
// Update local state
|
|
vehicle.status = 'active';
|
|
vehicle.location = 'In Use';
|
|
|
|
sendEvent('garage::spawn', {
|
|
vehicleId: vehicle.id,
|
|
vehicleName: vehicle.name,
|
|
vehicleType: vehicle.type
|
|
});
|
|
|
|
// Re-render
|
|
renderVehicles();
|
|
updateStats();
|
|
if (selectedVehicle && selectedVehicle.id === vehicle.id) {
|
|
showVehicleDetails(vehicle);
|
|
}
|
|
}
|
|
|
|
// Store vehicle
|
|
function storeVehicle(vehicle) {
|
|
console.log('Storing vehicle:', vehicle.name);
|
|
|
|
// Update local state
|
|
vehicle.status = 'stored';
|
|
vehicle.location = 'Garage A';
|
|
|
|
sendEvent('garage::store', {
|
|
vehicleId: vehicle.id,
|
|
vehicleName: vehicle.name,
|
|
vehicleType: vehicle.type
|
|
});
|
|
|
|
// Re-render
|
|
renderVehicles();
|
|
updateStats();
|
|
if (selectedVehicle && selectedVehicle.id === vehicle.id) {
|
|
showVehicleDetails(vehicle);
|
|
}
|
|
}
|
|
|
|
// Update stats
|
|
function updateStats() {
|
|
const stored = mockData.vehicles.filter(v => v.status === 'stored').length;
|
|
const active = mockData.vehicles.filter(v => v.status === 'active').length;
|
|
const capacity = mockData.vehicles.length + 6; // Mock capacity
|
|
|
|
document.getElementById('storedCount').textContent = stored;
|
|
document.getElementById('activeCount').textContent = active;
|
|
document.getElementById('capacityCount').textContent = capacity;
|
|
}
|
|
|
|
// Update garage data from external source
|
|
function updateGarageData(data) {
|
|
if (data.vehicles) {
|
|
mockData.vehicles = data.vehicles;
|
|
renderVehicles();
|
|
updateStats();
|
|
|
|
// Update selected vehicle if it still exists
|
|
if (selectedVehicle) {
|
|
const updated = mockData.vehicles.find(v => v.id === selectedVehicle.id);
|
|
if (updated) {
|
|
selectedVehicle = updated;
|
|
showVehicleDetails(updated);
|
|
} else {
|
|
selectedVehicle = null;
|
|
const noSelection = document.getElementById('noSelection');
|
|
const vehicleDetails = document.getElementById('vehicleDetails');
|
|
if (noSelection) noSelection.style.display = 'flex';
|
|
if (vehicleDetails) vehicleDetails.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send event to Arma
|
|
function sendEvent(event, data) {
|
|
if (typeof A3API !== 'undefined') {
|
|
A3API.SendAlert(JSON.stringify({
|
|
event: event,
|
|
data: data
|
|
}));
|
|
} else {
|
|
console.log('Event:', event, 'Data:', data);
|
|
}
|
|
}
|
|
|
|
// Auto-initialize
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initGarage);
|
|
} else {
|
|
initGarage();
|
|
}
|
|
|
|
// Expose functions globally
|
|
window.updateGarageData = updateGarageData;
|
|
window.selectVehicle = selectVehicle;
|
|
window.spawnVehicle = spawnVehicle;
|
|
window.storeVehicle = storeVehicle;
|