您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
a RR panal that shows wins/loses,winrate,win/lose streak and total profit
当前为
// ==UserScript== // @name TornPDA RR Tracker v4.0 (Position Memory + No-Pull Drag) // @match https://www.torn.com/page.php?sid=russianRoulette* // @grant none // @description a RR panal that shows wins/loses,winrate,win/lose streak and total profit // @run-at document-idle // @version v1 // @license MIT // @namespace https://greasyfork.org/users/1493252 // ==/UserScript== (() => { const STORAGE = 'torn_rr_tracker_results'; const POS_KEY = 'rr_panelPos'; const MAX = 100; let lastPot = 0; let roundActive = true; let hasTrackedRound = false; let results = JSON.parse(localStorage.getItem(STORAGE) || '[]'); let collapsed = false; // ─── PANEL ───────────────────────────────────────────────────────── const panel = document.createElement('div'); Object.assign(panel.style, { position: 'fixed', top: '12px', left: '12px', width: 'auto', minWidth: '100px', background: 'rgba(0,0,0,0.5)', color: '#fff', fontFamily: 'monospace', fontSize: '14px', padding: '36px 12px 12px 12px', // top padding for icons borderRadius: '10px', boxShadow: '0 0 12px rgba(255,0,0,0.3)', zIndex: '9999999', userSelect: 'none', display: 'flex', flexDirection: 'column', gap: '8px', }); document.body.appendChild(panel); // Restore saved position try { const pos = JSON.parse(localStorage.getItem(POS_KEY) || '{}'); if (pos.top && pos.left) { panel.style.top = pos.top; panel.style.left = pos.left; } } catch {} // ─── ICONS ───────────────────────────────────────────────────────── const statusDiv = document.createElement('div'); Object.assign(statusDiv.style, { position: 'absolute', top: '12px', left: '12px', width: '20px', height: '20px', fontSize: '18px', lineHeight: '20px', cursor: 'pointer', userSelect: 'none', color: 'rgba(255,255,255,0.6)', }); panel.appendChild(statusDiv); const dragHandle = document.createElement('div'); dragHandle.textContent = '☰'; Object.assign(dragHandle.style, { position: 'absolute', top: '12px', right: '12px', width: '20px', height: '20px', fontSize: '18px', lineHeight: '20px', cursor: 'move', userSelect: 'none', color: 'rgba(255,255,255,0.6)', touchAction: 'none', // disable native pull-to-refresh }); panel.appendChild(dragHandle); // Drag & save position (() => { let mx, my; const savePos = () => { localStorage.setItem(POS_KEY, JSON.stringify({ top: panel.style.top, left: panel.style.left })); }; dragHandle.onmousedown = e => { e.preventDefault(); mx = e.clientX; my = e.clientY; document.onmouseup = () => { document.onmousemove = document.onmouseup = null; savePos(); }; document.onmousemove = e => { const dx = e.clientX - mx, dy = e.clientY - my; mx = e.clientX; my = e.clientY; panel.style.top = panel.offsetTop + dy + 'px'; panel.style.left = panel.offsetLeft + dx + 'px'; }; }; dragHandle.ontouchstart = e => { mx = e.touches[0].clientX; my = e.touches[0].clientY; document.ontouchend = () => { document.ontouchmove = document.ontouchend = null; savePos(); }; document.ontouchmove = e => { const dx = e.touches[0].clientX - mx, dy = e.touches[0].clientY - my; mx = e.touches[0].clientX; my = e.touches[0].clientY; panel.style.top = panel.offsetTop + dy + 'px'; panel.style.left = panel.offsetLeft + dx + 'px'; }; }; })(); // Collapse/expand statusDiv.addEventListener('click', () => { collapsed = !collapsed; statsGroup.style.display = resultsContainer.style.display = resetBtn.style.display = collapsed ? 'none' : ''; panel.style.height = collapsed ? '40px' : ''; updateStatus(); }); // ─── STATS ───────────────────────────────────────────────────────── const statsGroup = document.createElement('div'); Object.assign(statsGroup.style, { display: 'flex', flexDirection: 'column', gap: '4px', }); panel.appendChild(statsGroup); const profitDiv = document.createElement('div'); const winrateDiv = document.createElement('div'); const streakDiv = document.createElement('div'); [profitDiv, winrateDiv, streakDiv].forEach(d => statsGroup.appendChild(d)); // ─── RESULTS ──────────────────────────────────────────────────────── const resultsContainer = document.createElement('div'); Object.assign(resultsContainer.style, { maxHeight: '160px', overflowY: 'auto', marginTop: '4px', }); panel.appendChild(resultsContainer); // ─── RESET BUTTON ─────────────────────────────────────────────────── const resetBtn = document.createElement('button'); resetBtn.innerHTML = '🔄 Reset'; Object.assign(resetBtn.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', userSelect: 'none', }); resetBtn.onmouseenter = () => resetBtn.style.background = 'rgba(255,255,255,0.2)'; resetBtn.onmouseleave = () => resetBtn.style.background = 'rgba(255,255,255,0.1)'; resetBtn.onclick = () => { if (confirm('Clear all results and reset profit?')) { results = []; saveResults(); lastPot = 0; roundActive = true; hasTrackedRound = false; refreshAll(); } }; panel.appendChild(resetBtn); // ─── HELPERS ──────────────────────────────────────────────────────── const saveResults = () => localStorage.setItem(STORAGE, JSON.stringify(results)); const circle = color => { const s = document.createElement('span'); Object.assign(s.style, { display: 'inline-block', width: '12px', height: '12px', borderRadius: '50%', backgroundColor: color, marginRight: '6px', verticalAlign: 'middle', }); return s; }; function updateStatus() { if (collapsed) { statusDiv.textContent = '▪'; } else { statusDiv.textContent = roundActive ? '►' : '▸'; } } function refreshAll() { // Profit with sign const profit = results.reduce((a,v) => v.result==='win'? a+v.bet : a-v.bet, 0); const sign = profit>=0? '+' : '–'; profitDiv.textContent = `💰 Total Profit: ${sign}$${Math.abs(profit).toLocaleString()}`; profitDiv.style.color = profit>=0? '#4CAF50':'#E53935'; // Win rate const wins = results.filter(r=>r.result==='win').length; const tot = results.length; const rate = tot? ((wins/tot)*100).toFixed(1): '0.0'; winrateDiv.textContent = `🎯 Win Rate: ${rate}% (${wins}/${tot})`; // Streak let w=0,l=0; for (let r of results) { if (r.result==='win') { if (l) break; w++; } else { if (w) break; l++; } } streakDiv.textContent = w? `🔥 Win Streak: ${w}`: l? `💀 Lose Streak: ${l}`:'⏸️ No streak'; // Results list resultsContainer.innerHTML = ''; results.forEach((r,i)=>{ const row = document.createElement('div'); const dot = circle(r.result==='win'? '#4CAF50':'#E53935'); const txt = document.createElement('span'); txt.textContent = `${i+1}. ${r.result.toUpperCase()} — $${r.bet.toLocaleString()}`; row.append(dot, txt); resultsContainer.append(row); }); updateStatus(); } // ─── TRACKING ──────────────────────────────────────────────────────── function addResult(type) { if (!roundActive) return; results.unshift({ result:type, bet:lastPot }); if (results.length>MAX) results.pop(); saveResults(); roundActive = false; hasTrackedRound = true; refreshAll(); } function scanPot() { document.querySelectorAll('body *').forEach(el=>{ const t = el.innerText?.trim(); if (t?.includes('POT MONEY:$')) { const m = t.match(/POT MONEY:\$\s*([\d,]+)/); if (m) lastPot = Math.floor(parseInt(m[1].replace(/,/g,''),10)/2); } }); } function scanResult() { if (!roundActive) return; document.querySelectorAll('body *').forEach(el=>{ const t = el.innerText?.trim(); if (t?.includes('You take your winnings')) addResult('win'); if (t?.includes('BANG! You fall down')) addResult('lose'); }); } function scanStart() { if (!hasTrackedRound) return; document.querySelectorAll('body *').forEach(el=>{ if (el.innerText?.trim().includes('Waiting:')) { roundActive = true; hasTrackedRound = false; updateStatus(); } }); } function loop() { scanStart(); scanPot(); scanResult(); } // ─── INIT ─────────────────────────────────────────────────────────── refreshAll(); setInterval(loop,100); })();