Jacob Schmidt ebfe77a340 feat: implement complete Forge framework with Rust/Redis backend and Arma 3 integration
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>
2026-01-04 12:52:15 -06:00

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;