// Main application logic let currentTelemetryData = null; let currentPlaybackSequence = null; let playbackInterval = null; let currentTick = 0; let canvas, ctx; // Initialize upload form document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('report-file'); const fileInfo = document.getElementById('file-info'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); const analyzeBtn = document.getElementById('analyze-btn'); const uploadForm = document.getElementById('upload-form'); // Handle file selection fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { const file = e.target.files[0]; fileName.textContent = file.name; fileSize.textContent = `(${(file.size / 1024).toFixed(1)} KB)`; fileInfo.classList.remove('hidden'); analyzeBtn.classList.remove('hidden'); } else { fileInfo.classList.add('hidden'); analyzeBtn.classList.add('hidden'); } } function updateStatsOverview() { if (!currentTelemetryData?.sequences) return; const sequences = currentTelemetryData.sequences; let totalShots = 0; let totalHits = 0; let totalSuspicion = 0; sequences.forEach(seq => { const stats = seq.sequence_stats || {}; totalShots += stats.total_shots || 0; totalHits += stats.hits || 0; totalSuspicion += calculateSuspicionScore(stats); }); const avgAccuracy = totalShots > 0 ? (totalHits / totalShots) : 0; const avgSuspicion = sequences.length > 0 ? (totalSuspicion / sequences.length) : 0; document.getElementById('total-sequences').textContent = sequences.length; document.getElementById('avg-accuracy').textContent = Math.round(avgAccuracy * 100) + '%'; document.getElementById('suspicion-score').textContent = Math.round(avgSuspicion); document.getElementById('total-shots').textContent = totalShots; } function initializeCharts() { if (!currentTelemetryData?.sequences) return; const sequences = currentTelemetryData.sequences; const accuracyData = sequences.map(seq => Math.round((seq.sequence_stats?.accuracy || 0) * 100)); const suspicionData = sequences.map((seq, index) => ({ x: index, y: calculateSuspicionScore(seq.sequence_stats || {}) })); // Accuracy distribution chart const accuracyCtx = document.getElementById('accuracy-chart').getContext('2d'); new Chart(accuracyCtx, { type: 'bar', data: { labels: accuracyData.map((_, i) => `Seq ${i + 1}`), datasets: [{ label: 'Accuracy %', data: accuracyData, backgroundColor: 'rgba(59, 130, 246, 0.5)', borderColor: 'rgba(59, 130, 246, 1)', borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, max: 100, ticks: { color: '#9ca3af' }, grid: { color: '#374151' } }, x: { ticks: { color: '#9ca3af' }, grid: { color: '#374151' } } } } }); // Suspicion timeline chart const suspicionCtx = document.getElementById('suspicion-chart').getContext('2d'); new Chart(suspicionCtx, { type: 'line', data: { datasets: [{ label: 'Suspicion Score', data: suspicionData, backgroundColor: 'rgba(239, 68, 68, 0.1)', borderColor: 'rgba(239, 68, 68, 1)', borderWidth: 2, tension: 0.4, fill: true }] }, options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, max: 100, ticks: { color: '#9ca3af' }, grid: { color: '#374151' } }, x: { ticks: { color: '#9ca3af' }, grid: { color: '#374151' } } } } }); } function sortSequences(sortBy) { if (!currentTelemetryData?.sequences) return; const container = document.getElementById('sequences-container'); const sequences = [...currentTelemetryData.sequences]; if (sortBy === 'accuracy') { sequences.sort((a, b) => (b.sequence_stats?.accuracy || 0) - (a.sequence_stats?.accuracy || 0)); } else if (sortBy === 'suspicion') { sequences.sort((a, b) => calculateSuspicionScore(b.sequence_stats || {}) - calculateSuspicionScore(a.sequence_stats || {})); } currentTelemetryData.sequences = sequences; renderSequences(); } function exportReport() { if (!currentTelemetryData) { alert('No data to export'); return; } const reportData = { player_name: currentTelemetryData.player_name, steam_id: currentTelemetryData.steam_id, timestamp: currentTelemetryData.timestamp, summary: { total_sequences: currentTelemetryData.sequences.length, avg_accuracy: document.getElementById('avg-accuracy').textContent, suspicion_score: document.getElementById('suspicion-score').textContent, total_shots: document.getElementById('total-shots').textContent }, sequences: currentTelemetryData.sequences.map(seq => ({ sequence_id: seq.sequence_id, accuracy: seq.sequence_stats?.accuracy || 0, hits: seq.sequence_stats?.hits || 0, total_shots: seq.sequence_stats?.total_shots || 0, suspicion_score: calculateSuspicionScore(seq.sequence_stats || {}) })) }; const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `aimbot-report-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); } function resetAnalysis() { currentTelemetryData = null; currentPlaybackSequence = null; stopPlayback(); document.getElementById('upload-section').classList.remove('hidden'); document.getElementById('dashboard').classList.add('hidden'); document.getElementById('report-file').value = ''; document.getElementById('file-info').classList.add('hidden'); // Clear charts Chart.getChart('accuracy-chart')?.destroy(); Chart.getChart('suspicion-chart')?.destroy(); } function showHelp() { document.getElementById('help-modal').classList.remove('hidden'); } function closeHelp() { document.getElementById('help-modal').classList.add('hidden'); } function showSettings() { alert('Settings panel coming soon!'); } function toggleFullscreen() { const canvas = document.getElementById('playback-canvas'); if (!document.fullscreenElement) { canvas.requestFullscreen(); } else { document.exitFullscreen(); } } ); // Handle form submission uploadForm.addEventListener('submit', (e) => { e.preventDefault(); if (fileInput.files.length === 0) return; const file = fileInput.files[0]; const reader = new FileReader(); analyzeBtn.disabled = true; analyzeBtn.innerHTML = 'Analyzing... '; window.feather.replace(); reader.onload = (e) => { try { const telemetryData = JSON.parse(e.target.result); initializeApp(telemetryData); document.getElementById('upload-section').classList.add('hidden'); document.getElementById('dashboard').classList.remove('hidden'); window.scrollTo({ top: 0, behavior: 'smooth' }); } catch (error) { alert('Error parsing report file. Please ensure it is valid JSON.'); console.error(error); } finally { analyzeBtn.disabled = false; analyzeBtn.innerHTML = 'Analyze Report '; window.feather.replace(); } }; reader.readAsText(file); }); }); function initializeApp(telemetryData) { try { if (!telemetryData || typeof telemetryData !== 'object') { throw new Error('Invalid telemetry data'); } currentTelemetryData = telemetryData; // Initialize canvas canvas = document.getElementById('playback-canvas'); ctx = canvas.getContext('2d'); resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Render player stats updatePlayerStats(); // Update stats overview updateStatsOverview(); // Render sequences renderSequences(); // Initialize charts initializeCharts(); } catch (error) { console.error('Error initializing app:', error); alert('Error initializing application. Please check the report file.'); } } function resizeCanvas() { const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; } function updatePlayerStats() { const playerStats = document.querySelector('custom-player-stats'); if (playerStats) { playerStats.setAttribute('name', currentTelemetryData.player_name); playerStats.setAttribute('steam-id', currentTelemetryData.steam_id); playerStats.setAttribute('timestamp', new Date(currentTelemetryData.timestamp * 1000).toLocaleString()); playerStats.setAttribute('sequence-count', currentTelemetryData.sequences.length.toString()); } } function renderSequences() { const container = document.getElementById('sequences-container'); container.innerHTML = ''; if (!currentTelemetryData?.sequences) return; currentTelemetryData.sequences.forEach((sequence, index) => { const stats = sequence?.sequence_stats || {}; const accuracy = stats?.accuracy || 0; const hits = stats.hits || 0; const totalShots = stats.total_shots || 0; const seqElement = document.createElement('div'); seqElement.className = 'sequence-card bg-gray-700 rounded-lg p-4 cursor-pointer hover:bg-gray-600'; seqElement.innerHTML = `

Sequence #${index + 1}

${sequence.sequence_id || 'Unknown ID'}

${Math.round(accuracy * 100)}% accuracy
${hits} hits ${totalShots} shots
Suspicion level ${calculateSuspicionScore(stats)}/100
`; seqElement.addEventListener('click', () => { loadSequenceForPlayback(sequence); }); container.appendChild(seqElement); }); } function calculateSuspicionScore(stats = {}) { // Simple heuristic for suspicion score let score = 0; // High accuracy is suspicious score += Math.min(30, (stats.accuracy || 0) * 50); // Micro-smoothing detection score += (stats.micro_smoothing_counts_r2 || 0) * 5; // Flick hits are suspicious score += (stats.flick_hits || 0) * 3; // Teleport shots are very suspicious score += (stats.teleport_shots_ratio || 0) * 20; return Math.min(100, Math.round(score)); } function loadSequenceForPlayback(sequence) { stopPlayback(); currentPlaybackSequence = sequence; currentTick = 0; // Highlight selected sequence document.querySelectorAll('.sequence-card').forEach(card => { card.classList.remove('active-playback', 'bg-gray-600'); }); event.currentTarget.classList.add('active-playback', 'bg-gray-600'); // Update playback controls const controls = document.querySelector('custom-playback-controls'); if (controls) { controls.setAttribute('duration', sequence.sequence_stats.duration_ticks); controls.setAttribute('current-tick', '0'); } // Render first frame renderTick(currentTick); } function startPlayback() { if (!currentPlaybackSequence) return; const tickDuration = 1000 / 30; // 30 ticks per second let lastTime = performance.now(); playbackInterval = setInterval(() => { const now = performance.now(); const delta = now - lastTime; lastTime = now; const ticksToAdvance = Math.floor(delta / tickDuration); currentTick = Math.min(currentTick + ticksToAdvance, currentPlaybackSequence.sequence_stats.duration_ticks - 1); renderTick(currentTick); // Update controls const controls = document.querySelector('custom-playback-controls'); if (controls) { controls.setAttribute('current-tick', currentTick.toString()); } if (currentTick >= currentPlaybackSequence.sequence_stats.duration_ticks - 1) { stopPlayback(); } }, tickDuration); } function stopPlayback() { if (playbackInterval) { clearInterval(playbackInterval); playbackInterval = null; } } function renderTick(tick) { if (!currentPlaybackSequence || !canvas) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw background grid drawGrid(); // Find the tick data (this is simplified - real implementation would interpolate) const tickData = findTickData(tick); if (!tickData) return; // Draw player and aiming data drawPlayer(tickData); // If target is in sight, draw target if (tickData.is_target_in_sight) { drawTarget(tickData); } // Draw shot information if this is a shot tick if (tickData.shot_fired) { drawShot(tickData); } } function findTickData(tick) { // Simplified - in a real implementation you'd interpolate between ticks const tickStr = tick.toString(); return currentPlaybackSequence.tick_data[tickStr] || currentPlaybackSequence.pre_shot_ticks.find(t => t.tick === tick); } function drawGrid() { const gridSize = 50; const width = canvas.width; const height = canvas.height; ctx.strokeStyle = '#333'; ctx.lineWidth = 1; // Vertical lines for (let x = 0; x <= width; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke(); } // Horizontal lines for (let y = 0; y <= height; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } } function drawPlayer(tickData) { const centerX = canvas.width / 2; const centerY = canvas.height / 2; // Draw player (simple circle) ctx.fillStyle = '#3b82f6'; ctx.beginPath(); ctx.arc(centerX, centerY, 10, 0, Math.PI * 2); ctx.fill(); // Draw aiming direction const angle = tickData.flick_angle || 0; const length = 30; const endX = centerX + Math.cos(angle) * length; const endY = centerY + Math.sin(angle) * length; ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo(endX, endY); ctx.stroke(); // Draw recoil compensation if (tickData.recoil_compensation_x || tickData.recoil_compensation_y) { const compX = tickData.recoil_compensation_x * 20; const compY = tickData.recoil_compensation_y * 20; ctx.strokeStyle = '#ef4444'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(endX, endY); ctx.lineTo(endX + compX, endY + compY); ctx.stroke(); // Draw compensation point ctx.fillStyle = '#ef4444'; ctx.beginPath(); ctx.arc(endX + compX, endY + compY, 3, 0, Math.PI * 2); ctx.fill(); } } function drawTarget(tickData) { const centerX = canvas.width / 2 + 100; // Fixed position for demo const centerY = canvas.height / 2; // Draw target (circle with crosshair) ctx.fillStyle = '#ef4444'; ctx.beginPath(); ctx.arc(centerX, centerY, 15, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; // Crosshair const crossSize = 10; ctx.beginPath(); ctx.moveTo(centerX - crossSize, centerY); ctx.lineTo(centerX + crossSize, centerY); ctx.moveTo(centerX, centerY - crossSize); ctx.lineTo(centerX, centerY + crossSize); ctx.stroke(); // Velocity vector if available if (tickData.target_speed) { const velX = Math.cos(0) * tickData.target_speed * 0.1; // Simplified const velY = Math.sin(0) * tickData.target_speed * 0.1; // Simplified ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo(centerX + velX, centerY + velY); ctx.stroke(); } } function drawShot(tickData) { const centerX = canvas.width / 2; const centerY = canvas.height / 2; const targetX = canvas.width / 2 + 100; const targetY = canvas.height / 2; // Draw shot line ctx.strokeStyle = tickData.shot_hit ? '#10b981' : '#ef4444'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo(targetX, targetY); ctx.stroke(); // Draw hit/miss indicator const indicatorX = targetX; const indicatorY = targetY; ctx.fillStyle = tickData.shot_hit ? '#10b981' : '#ef4444'; ctx.beginPath(); if (tickData.shot_hit) { ctx.arc(indicatorX, indicatorY, 8, 0, Math.PI * 2); } else { ctx.moveTo(indicatorX - 8, indicatorY - 8); ctx.lineTo(indicatorX + 8, indicatorY + 8); ctx.moveTo(indicatorX + 8, indicatorY - 8); ctx.lineTo(indicatorX - 8, indicatorY + 8); } ctx.fill(); } // Expose functions to web components window.playbackControls = { play: startPlayback, pause: stopPlayback, seek: (tick) => { currentTick = tick; renderTick(currentTick); } };