您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Track racing data and show acceleration status
当前为
// ==UserScript== // @name Torn Racing Telemetry // @namespace https://www.torn.com/profiles.php?XID=2782979 // @version 1.7.3.3 // @description Track racing data and show acceleration status // @match https://www.torn.com/page.php?sid=racing* // @match https://www.torn.com/loader.php?sid=racing* // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (() => { // Default settings const defaultConfig = { updateInterval: 1000, showSpeed: true, showAcceleration: false, colorCode: true, animateChanges: true }; // Get or initialize settings let config = { ...defaultConfig, ...GM_getValue('racingTelemetryConfig', {}) }; // Create settings panel const createSettingsPanel = () => { const panel = document.createElement('div'); panel.className = 'telemetry-settings'; panel.innerHTML = ` <div class="settings-header"> <span>Racing Telemetry Settings</span> <button class="toggle-settings">▼</button> </div> <div class="settings-content" style="display:none"> <label> <input type="radio" name="speedAcceleration" value="speed" ${config.showSpeed ? 'checked' : ''} data-setting="showSpeed"> Show Speed </label> <label> <input type="radio" name="speedAcceleration" value="acceleration" ${config.showAcceleration ? 'checked' : ''} data-setting="showAcceleration"> Show Acceleration </label> <label> <input type="checkbox" ${config.colorCode ? 'checked' : ''} data-setting="colorCode"> Color Code Status Text </label> <label> <input type="checkbox" ${config.animateChanges ? 'checked' : ''} data-setting="animateChanges"> Animate Speed Changes </label> <label> Update Interval (ms): <input type="number" value="${config.updateInterval}" min="100" max="2000" step="100" data-setting="updateInterval"> <p>Higher = Smoother | Lower = Accurate</p> <p>Short/Official Races: 500ms-2000ms</p> <p>50-100 Lap Races: 2000ms-5000ms</p> <p>This update setting will be removed shortly when I figure out how to make it automatic.</p> </label> </div> `; // Add event listeners const content = panel.querySelector('.settings-content'); panel.querySelector('.toggle-settings').addEventListener('click', (e) => { content.style.display = content.style.display === 'none' ? 'block' : 'none'; e.target.textContent = content.style.display === 'none' ? '▼' : '▲'; }); panel.querySelectorAll('input').forEach(input => { input.addEventListener('change', () => { const setting = input.dataset.setting; if (input.type === 'radio') { config.speedAcceleration = input.value; config.showSpeed = input.value === "speed"; config.showAcceleration = input.value === "acceleration"; } else { config[setting] = input.checked ? input.checked : Number(input.value); } GM_setValue('racingTelemetryConfig', config); if (setting === 'updateInterval') { clearInterval(updateInterval); updateInterval = setInterval(update, config.updateInterval); } }); }); return panel; }; const prev = {}; const getTrack = () => { const info = document.querySelector('.track-info'); const len = info ? parseFloat(info.dataset.length.replace(/mi/, '')) : 3.4; // Convert miles to a number // Select the second .title-black element const titleBlack = document.querySelectorAll('.title-black')[1]; const lapsMatch = titleBlack?.textContent.match(/(\d+)\s+laps?/); const laps = lapsMatch ? parseInt(lapsMatch[1]) : 5; //console.log(`Track Length: ${len} miles, Laps: ${laps}`); return { total: len * laps }; }; const parsePercentage = pctStr => { return parseFloat(pctStr.replace('%', '')) / 100; }; const calcMetrics = (pctStr, id, now) => { const pct = parsePercentage(pctStr); //console.log(`ID: ${id}, PCT: ${pct}, Now: ${now}`); if (!prev[id]) { prev[id] = { pct, time: now, spd: 0, acc: 'calculating...' }; //console.log(`Initialized prev[${id}]:`, prev[id]); return { spd: 0, acc: 'calculating...' }; } const td = (now - prev[id].time) / 1000; //console.log(`Time Difference: ${td}`); if (td <= 0) { //console.log(`Time difference is zero or negative, skipping calculation for ID: ${id}`); return prev[id]; } const track = getTrack(); const distanceCovered = track.total * (pct - prev[id].pct); //console.log(`Distance Covered: ${distanceCovered}`); const spd = (distanceCovered / td) * 3600; // Speed in mph //console.log(`Speed: ${spd}`); const acc = Math.abs(spd - prev[id].spd) < 1 ? 'steady' : spd > prev[id].spd + 1 ? 'accelerating' : 'braking'; //console.log(`Acceleration: ${acc}`); prev[id] = { pct, time: now, spd, acc }; //console.log(`Updated prev[${id}]:`, prev[id]); return { spd: Math.abs(spd), acc }; }; const updateStatus = (el, status, spd, fin = false, d = null) => { el.querySelector('.status-text')?.remove(); if (!config.showSpeed && !config.showAcceleration) return; const stat = document.createElement('span'); stat.className = 'status-text'; stat.style.cssText = 'margin-left:10px;font-size:12px;'; if (fin) { const finishTime = d.querySelector('.pd-laptime').textContent; const finishTimeSecs = parseTime(finishTime); const avgSpeed = (getTrack().total / finishTimeSecs) * 3600; stat.textContent = `[Finished - Avg: ${avgSpeed.toFixed(1)} mph]`; stat.style.color = config.colorCode ? 'green' : 'gray'; // Setting color for finished status } else if (status === 'not started') { stat.textContent = '[Not Started]'; stat.style.color = config.colorCode ? 'gray' : 'gray'; // Setting color for not started status } else if (status === 'calculating...') { stat.textContent = 'calculating...'; stat.style.color = config.colorCode ? 'gray' : 'gray'; // Setting color for calculating status } else { const speedText = config.showSpeed ? `${Math.abs(spd).toFixed(1)} mph` : ''; const accText = config.showAcceleration ? status : ''; stat.textContent = `[${speedText}${speedText && accText ? ' - ' : ''}${accText}]`; // Apply color coding based on the settings if (config.colorCode) { stat.style.color = status === 'accelerating' ? 'green' : status === 'braking' ? 'red' : 'gray'; } else { stat.style.color = 'gray'; // Default color if color code is disabled } } el.appendChild(stat); }; const animate = (el, start, end, dur) => { // Apply color coding based on the settings if (config.colorCode) { el.style.color = start<(end-5) ? 'green' : start>(end+5) ? 'red' : 'gray'; } else { el.style.color = 'gray'; // Default color if color code is disabled } if (!config.animateChanges) { el.textContent = `[${end.toFixed(1)} mph]`; return; } const t0 = performance.now(); const update = t => { const p = Math.min((t - t0) / dur, 1); el.textContent = `[${(start + (end - start) * p).toFixed(1)} mph]`; p < 1 && requestAnimationFrame(update); }; requestAnimationFrame(update); }; const parseTime = t => { const [m, s] = t.split(':').map(Number); return m * 60 + s; }; const update = () => { document.querySelectorAll('#leaderBoard li[id^="lbr-"]').forEach(d => { const name = d.querySelector('.name'); const time = d.querySelector('.time'); if (!name || !time) return; const status = d.querySelector('.status-wrap div'); const fin = ['finished', 'gold', 'silver', 'bronze'].some(c => status?.classList.contains(c)); if (!time.textContent.trim()) { updateStatus(name, 'not started', 0); } else if (fin) { updateStatus(name, null, time.textContent, true, d); } else { const { spd, acc } = calcMetrics(time.textContent, d.id, Date.now()); const statEl = name.querySelector('.status-text'); if (statEl) { if (config.showSpeed) { const currentSpeed = parseFloat(statEl.textContent.replace(/[^\d.-]/g, '')); if (!isNaN(currentSpeed)) { animate(statEl, currentSpeed, spd, config.updateInterval); } else { //console.log(`Current speed is NaN, setting to ${spd.toFixed(1)} mph for ID: ${d.id}`); statEl.textContent = `[${spd.toFixed(1)} mph]`; } } else if (config.showAcceleration) { updateStatus(name, acc, spd); // Update with acceleration } } else { updateStatus(name, acc, spd); // Initial update } } }); }; // Add styles document.head.appendChild(Object.assign(document.createElement('style'), { textContent: ` .telemetry-settings { background: #f4f4f4; border: 1px solid #ddd; border-radius: 4px; margin: 10px; padding: 5px; color: #000000; text-shadow: 0 0px 0px rgba(0, 0, 0, 0); } .settings-header { display: flex; justify-content: space-between; align-items: center; padding: 5px; cursor: pointer; } .settings-content { padding: 10px; } .settings-content label { display: block; margin: 5px 0; } .toggle-settings { background: none; border: none; cursor: pointer; } .status-text { display: inline-block; font-family: Arial,sans-serif; padding: 2px 5px; border-radius: 3px; background: rgba(0,0,0,.05); } .status-text { display: inline-block; font-family: Arial, sans-serif; padding: 2px 5px; border-radius: 3px; background: rgba(0, 0, 0, .05); transition: color 5s ease; /* Add transition for color changes */ } ` })); // Insert settings panel const container = document.querySelector('.cont-black'); if (container) { container.insertBefore(createSettingsPanel(), container.firstChild); } // When the script loads const storedConfig = GM_getValue('racingTelemetryConfig', {}); config.speedAcceleration = storedConfig.speedAcceleration || "speed"; // Default to "speed" config.showSpeed = config.speedAcceleration === "speed"; config.showAcceleration = config.speedAcceleration === "acceleration"; // Start update interval let updateInterval = setInterval(update, config.updateInterval); update(); })();