// ==UserScript==
// @name TornPDA RR Tracker v4.8 (Auto-Hide Toggle)
// @namespace https://greasyfork.org/users/1493252
// @version 4.9
// @description RR panel with wins/losses, winrate, streak, profit — visible on menu, mini-icons + colored compact profit when collapsed; drag & collapse always work; remembers collapsed state, position, and auto-hide preference; tracks during play.
// @match https://www.torn.com/page.php?sid=russianRoulette*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function waitUntilReady() {
const PANEL_ID = 'rr-tracker-panel';
const STORAGE = 'torn_rr_tracker_results';
const PROFIT_STORAGE = 'torn_rr_total_profit';
const POS_KEY = 'rr_panelPos';
const COLLAPSE_KEY = 'rr_panelCollapsed';
const AUTOHIDE_KEY = 'rr_autoHide';
const MAX_DISPLAY_KEY = 'rr_maxDisplayMatches'; // New storage key for max matches
if (document.getElementById(PANEL_ID)) return;
if (!document.body.innerText.includes('Password') &&
!document.body.innerText.includes('POT MONEY')) {
return setTimeout(waitUntilReady, 200);
}
let lastPot = 0;
let roundActive = true;
let hasTracked = false;
let results = JSON.parse(localStorage.getItem(STORAGE) || '[]');
let totalProfit = parseFloat(localStorage.getItem(PROFIT_STORAGE) || '0');
let collapsed = JSON.parse(localStorage.getItem(COLLAPSE_KEY) || 'false');
let autoHide = JSON.parse(localStorage.getItem(AUTOHIDE_KEY) || 'true');
let showSettings = false;
// Initialize maxDisplayMatches from localStorage, default to 100
let maxDisplayMatches = parseInt(localStorage.getItem(MAX_DISPLAY_KEY) || '100', 10);
if (isNaN(maxDisplayMatches) || maxDisplayMatches < 1) { // Ensure it's a valid number and at least 1
maxDisplayMatches = 100;
localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString());
}
const panel = document.createElement('div');
panel.id = PANEL_ID;
Object.assign(panel.style, {
position: 'fixed',
top: '12px',
left: '12px',
background: 'rgba(0,0,0,0.6)',
color: '#fff',
fontFamily: 'monospace',
fontSize: '14px',
padding: '36px 12px 12px',
borderRadius: '10px',
boxShadow: '0 0 12px rgba(255,0,0,0.3)',
zIndex: '9999999',
userSelect: 'none',
display: 'flex',
flexDirection: 'column',
gap: '8px',
minWidth: '140px'
});
document.body.appendChild(panel);
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 {}
const miniBar = document.createElement('div');
Object.assign(miniBar.style, {
display: 'none',
flexWrap: 'wrap',
gap: '2px',
padding: '4px 0'
});
panel.appendChild(miniBar);
const profitMini = document.createElement('div');
Object.assign(profitMini.style, {
display: 'none',
fontSize: '14px',
fontFamily: 'monospace',
margin: '2px 0'
});
panel.appendChild(profitMini);
const statusDiv = document.createElement('div');
Object.assign(statusDiv.style, {
position: 'absolute', top: '8px', left: '8px',
width: '20px', height: '20px',
fontSize: '18px', cursor: 'pointer',
color: 'rgba(255,255,255,0.7)'
});
panel.appendChild(statusDiv);
const dragHandle = document.createElement('div');
dragHandle.textContent = '☰';
Object.assign(dragHandle.style, {
position: 'absolute',
top: '8px',
right: '8px',
width: '20px',
height: '20px',
fontSize: '18px',
cursor: 'move',
color: 'rgba(255,255,255,0.7)',
touchAction: 'none'
});
panel.appendChild(dragHandle);
// --- Main Stats Group ---
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');
statsGroup.append(profitDiv, winrateDiv, streakDiv);
const resultsContainer = document.createElement('div');
Object.assign(resultsContainer.style, {
maxHeight: '140px',
overflowY: 'auto',
marginTop: '4px'
});
statsGroup.appendChild(resultsContainer);
// --- Settings Panel ---
const settingsPanel = document.createElement('div');
Object.assign(settingsPanel.style, {
display: 'none',
flexDirection: 'column',
gap: '8px',
padding: '12px 0'
});
panel.appendChild(settingsPanel);
const settingsTitle = document.createElement('div');
settingsTitle.textContent = 'Settings';
Object.assign(settingsTitle.style, {
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '4px'
});
settingsPanel.appendChild(settingsTitle);
const backButton = document.createElement('button');
backButton.textContent = '← Back';
Object.assign(backButton.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginBottom: '8px'
});
backButton.onmouseenter = () => backButton.style.background = 'rgba(255,255,255,0.2)';
backButton.onmouseleave = () => backButton.style.background = 'rgba(255,255,255,0.1)';
backButton.onclick = () => {
showSettings = false;
refreshAll();
};
settingsPanel.appendChild(backButton);
// New: Max Matches Displayed Setting
const maxMatchesSettingDiv = document.createElement('div');
Object.assign(maxMatchesSettingDiv.style, {
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '8px'
});
const maxMatchesLabel = document.createElement('label');
maxMatchesLabel.textContent = 'Max Matches Displayed:';
maxMatchesLabel.htmlFor = 'max-matches-input';
Object.assign(maxMatchesLabel.style, { flexShrink: '0' });
const maxMatchesInput = document.createElement('input');
maxMatchesInput.type = 'number';
maxMatchesInput.id = 'max-matches-input';
maxMatchesInput.min = '1'; // Minimum 1 match
maxMatchesInput.max = '500'; // Reasonable upper limit
maxMatchesInput.value = maxDisplayMatches;
Object.assign(maxMatchesInput.style, {
width: '60px',
padding: '4px',
border: '1px solid #555',
borderRadius: '4px',
background: 'rgba(255,255,255,0.1)',
color: '#fff'
});
maxMatchesInput.onchange = () => {
let newValue = parseInt(maxMatchesInput.value, 10);
if (isNaN(newValue) || newValue < 1) {
newValue = 1; // Default to 1 if invalid
}
if (newValue > 500) newValue = 500; // Cap at 500
maxDisplayMatches = newValue;
maxMatchesInput.value = maxDisplayMatches; // Update input field to sanitized value
localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString());
// If we reduce the max matches, also trim the results array immediately
while (results.length > maxDisplayMatches) {
results.pop();
}
saveResults();
refreshAll();
};
maxMatchesSettingDiv.append(maxMatchesLabel, maxMatchesInput);
settingsPanel.appendChild(maxMatchesSettingDiv);
const resetBtn = document.createElement('button');
resetBtn.textContent = '🔄 Reset Data';
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'
});
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 = [];
totalProfit = 0;
saveResults();
saveTotalProfit();
lastPot = 0;
roundActive = true;
hasTracked = false;
refreshAll();
}
};
settingsPanel.appendChild(resetBtn);
const autoHideBtn = document.createElement('button');
autoHideBtn.textContent = autoHide ? 'Auto-Hide: On' : 'Auto-Hide: Off';
Object.assign(autoHideBtn.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginTop: '4px'
});
autoHideBtn.onmouseenter = () => autoHideBtn.style.background = 'rgba(255,255,255,0.2)';
autoHideBtn.onmouseleave = () => autoHideBtn.style.background = 'rgba(255,255,255,0.1)';
autoHideBtn.onclick = () => {
autoHide = !autoHide;
localStorage.setItem(AUTOHIDE_KEY, JSON.stringify(autoHide));
autoHideBtn.textContent = autoHide ? 'Auto-Hide: On' : 'Auto-Hide: Off';
refreshAll();
};
settingsPanel.appendChild(autoHideBtn);
// --- Settings Button for Main Panel ---
const settingsButton = document.createElement('button');
settingsButton.textContent = '⚙️ Settings';
Object.assign(settingsButton.style, {
alignSelf: 'flex-start',
background: 'rgba(255,255,255,0.1)',
color: '#fff',
border: 'none',
borderRadius:'6px',
padding: '4px 8px',
cursor: 'pointer',
marginTop: '4px'
});
settingsButton.onmouseenter = () => settingsButton.style.background = 'rgba(255,255,255,0.2)';
settingsButton.onmouseleave = () => settingsButton.style.background = 'rgba(255,255,255,0.1)';
settingsButton.onclick = () => {
showSettings = true;
refreshAll();
};
panel.appendChild(settingsButton);
const saveResults = () => localStorage.setItem(STORAGE, JSON.stringify(results));
const saveTotalProfit = () => localStorage.setItem(PROFIT_STORAGE, totalProfit.toString());
const saveCollapsed = () => localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed));
const makeCircle = color => {
const el = document.createElement('span');
Object.assign(el.style, {
display: 'inline-block',
width: '12px',
height: '12px',
borderRadius: '50%',
backgroundColor: color,
marginRight: '2px'
});
return el;
};
function updateStatus() {
statusDiv.textContent = collapsed ? '▪' : (roundActive ? '►' : '▸');
}
function updatePanelVisibility() {
if (!autoHide) {
panel.style.display = 'flex';
return;
}
const onMenu = document.body.innerText.includes('Password');
panel.style.display = onMenu ? 'flex' : 'none';
}
function refreshAll() {
const sign = totalProfit >= 0 ? '+' : '–';
profitMini.textContent = `${sign}${Math.abs(totalProfit).toLocaleString()}`;
profitMini.style.color = totalProfit >= 0 ? '#4CAF50' : '#E53935';
profitDiv.textContent = `💰 Profit: ${sign}$${Math.abs(totalProfit).toLocaleString()}`;
profitDiv.style.color = totalProfit >= 0 ? '#4CAF50' : '#E53935';
const wins = results.filter(r => r.result==='win').length;
const tot = results.length;
winrateDiv.textContent = `🎯 Win Rate: ${tot?((wins/tot)*100).toFixed(1):'0.0'}% (${wins}/${tot})`;
let w=0,l=0;
for (const r of results) {
if (r.result==='win') { if(l) break; w++; }
else { if(w) break; l++; }
}
streakDiv.textContent = w?`🔥 Streak: ${w}`: l?`💀 Streak: ${l}`:'⏸️ No streak';
resultsContainer.innerHTML = '';
// Display only up to maxDisplayMatches
results.slice(0, maxDisplayMatches).forEach((r,i) => {
const row = document.createElement('div');
row.append(
makeCircle(r.result==='win'? '#4CAF50':'#E53935'),
document.createTextNode(`${i+1}. ${r.result.toUpperCase()} — $${r.bet.toLocaleString()}`)
);
resultsContainer.appendChild(row);
});
miniBar.innerHTML = '';
// Mini-bar still shows up to 10 for compact view, regardless of maxDisplayMatches
results.slice(0,10).forEach(r => miniBar.append(makeCircle(r.result==='win'? '#4CAF50':'#E53935')));
// --- Visibility Logic for Main Panel vs. Settings Panel ---
if (showSettings) {
statsGroup.style.display = 'none';
settingsButton.style.display = 'none';
settingsPanel.style.display = 'flex';
miniBar.style.display = 'none';
profitMini.style.display = 'none';
} else if (collapsed) {
miniBar.style.display = 'flex';
profitMini.style.display = 'block';
statsGroup.style.display = 'none';
settingsButton.style.display = 'none';
settingsPanel.style.display = 'none';
} else {
miniBar.style.display = 'none';
profitMini.style.display = 'none';
statsGroup.style.display = 'flex';
settingsButton.style.display = 'inline-block';
settingsPanel.style.display = 'none';
}
updateStatus();
updatePanelVisibility();
}
function addResult(type) {
if (!roundActive) return;
if (type === 'win') {
totalProfit += lastPot;
} else {
totalProfit -= lastPot;
}
saveTotalProfit();
results.unshift({ result: type, bet: lastPot });
// Now truncate based on maxDisplayMatches
if (results.length > maxDisplayMatches) {
results.pop();
}
saveResults();
roundActive = false;
hasTracked = true;
refreshAll();
}
function scanPot() {
document.querySelectorAll('body *').forEach(el => {
const txt = el.innerText?.trim();
if (txt?.includes('POT MONEY:$')) {
const m = txt.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 txt = el.innerText?.trim();
if (txt?.includes('You take your winnings')) addResult('win');
if (txt?.includes('BANG! You fall down')) addResult('lose');
});
}
function scanStart() {
if (hasTracked && document.body.innerText.includes('Waiting:')) {
roundActive = true;
hasTracked = false;
updateStatus();
}
}
(function() {
let mx, my;
const savePos = () => localStorage.setItem(POS_KEY, JSON.stringify({
top: panel.style.top, left: panel.style.left
}));
dragHandle.addEventListener('mousedown', e => {
e.preventDefault(); mx = e.clientX; my = e.clientY;
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
function onMove(ev) {
const dx = ev.clientX - mx, dy = ev.clientY - my;
mx = ev.clientX; my = ev.clientY;
panel.style.top = panel.offsetTop + dy + 'px';
panel.style.left = panel.offsetLeft + dx + 'px';
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
savePos();
}
});
dragHandle.addEventListener('touchstart', e => {
mx = e.touches[0].clientX; my = e.touches[0].clientY;
document.addEventListener('touchmove', onTmove);
document.addEventListener('touchend', onTend);
function onTmove(ev) {
const dx = ev.touches[0].clientX - mx, dy = ev.touches[0].clientY - my;
mx = ev.touches[0].clientX; my = ev.touches[0].clientY;
panel.style.top = panel.offsetTop + dy + 'px';
panel.style.left = panel.offsetLeft + dx + 'px';
}
function onTend() {
document.removeEventListener('touchmove', onTmove);
document.removeEventListener('touchend', onTend);
savePos();
}
});
})();
statusDiv.addEventListener('click', () => {
collapsed = !collapsed;
if (showSettings && collapsed) {
showSettings = false;
}
localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed));
refreshAll();
});
refreshAll();
setInterval(() => {
updatePanelVisibility();
scanStart();
scanPot();
scanResult();
}, 500);
})();