您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
RR tracker with cool features, now with integrated Martingale bet auto-start!
当前为
// ==UserScript== // @name RR Tracker v8.0 (smart bets introduced) // @namespace https://greasyfork.org/users/1493252 // @version 8.0 // Enhanced Martingale UI, Dedicated Edit Panel, Cash Pull Fixed, 11th Bet Added, Persistent Bets Fixed // @description RR tracker with cool features, now with integrated Martingale bet auto-start! //###Your Torn RR Tracker: Quick Guide //###Auto-Tracking //###Just open Russian Roulette, and your tracker starts automatically. It records every win and loss, showing your profit, win rate, and streaks. //###Panel Controls //###* Drag: Click and drag the ☰ icon to move the panel anywhere you like. //###* Shift + Drag: Hold Shift and drag with your mouse from anywhere on the panel to move it. //###* Collapse/Expand: Click the ► (or ▪) icon to shrink or expand the panel. //###* Hide/Show: If Auto-Hide is on in settings, the tracker hides when you leave RR and reappears when you return. //###Settings (⚙️ icon) //###* Panel Opacity: Adjust how see-through the panel is. //###* Profit/Loss Alerts: Set targets to be notified when you hit a certain profit or loss. //###* Mini-Bar Count: Choose how many recent games show in the collapsed view. //###* Reset Data: Clear all your tracked stats and profit. //###* Edit Bets: Customize your Martingale bet amounts in a dedicated panel. //###Bets (💰 icon) - NEW! //###* Martingale Bet Auto-Start: Click a button to automatically set the bet and start a match. // @match https://www.torn.com/page.php?sid=russianRoulette* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function waitUntilReady() { 'use strict'; 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'; const OPACITY_KEY = 'rr_panelOpacity'; const PROFIT_TARGET_KEY = 'rr_profitTarget'; const LOSS_LIMIT_KEY = 'rr_lossLimit'; const ALERT_SHOWN_PROFIT_KEY= 'rr_alertShownProfit'; const ALERT_SHOWN_LOSS_KEY = 'rr_alertShownLoss'; // <-- FIXED TYPO HERE const MINI_BAR_COUNT_KEY = 'rr_miniBarCount'; // NEW: Martingale Bet Constants & Variables const MARTINGALE_BETS_STORAGE_KEY = 'RRBets'; // Default Martingale sequence for 11 bets (approximately 2x multiplier) // CORRECTED 5th bet (index 4) from 16000 to 160000 for proper x2 progression const DEFAULT_MARTINGALE_BETS = [10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000, 10240000]; const EXPECTED_BET_COUNT = DEFAULT_MARTINGALE_BETS.length; // Now dynamically set to 11 let currentMartingaleBets = []; // This will hold the bet sequence let showBetsPanel = false; // New state to control Bets panel visibility let showEditBetsPanel = false; // New state for dedicated Edit Bets panel // Dragging Variables let isDragging = false; let dragMouseX = 0; let dragMouseY = 0; // Two-Finger Dragging Variables let isTwoFingerDragging = false; let initialTouchMidX = 0; let initialTouchMidY = 0; let initialPanelX = 0; let initialPanelY = 0; // Universal way to set input value and trigger events const nativeSet = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set; if (document.getElementById(PANEL_ID)) return; // Check for specific text that indicates we are on the RR page // Added more robust checks for common elements on the RR page if (!document.body.innerText.includes('Password') && !document.body.innerText.includes('POT MONEY') && !document.body.innerText.includes('Shoot') && !document.body.innerText.includes('Players') && !document.querySelector('.create-game-section') && // Check for "Create Game" container !document.querySelector('.russian-roulette-container')) { // General RR container 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) || 'false'); let showSettings = false; let maxDisplayMatches = parseInt(localStorage.getItem(MAX_DISPLAY_KEY) || '100', 10); if (isNaN(maxDisplayMatches) || maxDisplayMatches < 1) { maxDisplayMatches = 100; localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString()); } let currentOpacity = parseFloat(localStorage.getItem(OPACITY_KEY) || '0.6'); if (isNaN(currentOpacity) || currentOpacity < 0.1 || currentOpacity > 1.0) { currentOpacity = 0.6; localStorage.setItem(OPACITY_KEY, currentOpacity.toString()); } let profitTarget = parseFloat(localStorage.getItem(PROFIT_TARGET_KEY) || '0'); let lossLimit = parseFloat(localStorage.getItem(LOSS_LIMIT_KEY) || '0'); let alertShownProfit = JSON.parse(localStorage.getItem(ALERT_SHOWN_PROFIT_KEY) || 'false'); let alertShownLoss = JSON.parse(localStorage.getItem(ALERT_SHOWN_LOSS_KEY) || 'false'); // <-- FIXED TYPO HERE let miniBarCount = parseInt(localStorage.getItem(MINI_BAR_COUNT_KEY) || '10', 10); if (isNaN(miniBarCount) || miniBarCount < 1 || miniBarCount > 50) { miniBarCount = 10; localStorage.setItem(MINI_BAR_COUNT_KEY, miniBarCount.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,${currentOpacity})`, 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 alertMessageDiv = document.createElement('div'); alertMessageDiv.id = 'rr-alert-message'; Object.assign(alertMessageDiv.style, { display: 'none', padding: '8px', marginBottom: '8px', borderRadius: '6px', textAlign: 'center', fontWeight: 'bold', fontSize: '16px', color: 'white', cursor: 'pointer', border: '1px solid transparent', transition: 'background-color 0.3s, border-color 0.3s' }); panel.appendChild(alertMessageDiv); const miniBar = document.createElement('div'); Object.assign(miniBar.style, { display: 'none', // Managed by refreshAll() flexDirection: 'column', // Now a column to stack circles then bets gap: '2px', padding: '4px 0' }); panel.appendChild(miniBar); const profitMini = document.createElement('div'); Object.assign(profitMini.style, { display: 'none', // Managed by refreshAll() 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' // Prevent default touch actions on this handle }); panel.appendChild(dragHandle); const statsGroup = document.createElement('div'); Object.assign(statsGroup.style, { display: 'flex', // Managed by refreshAll() 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); const settingsPanel = document.createElement('div'); Object.assign(settingsPanel.style, { display: 'none', // Managed by refreshAll() 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 backButtonSettings = document.createElement('button'); backButtonSettings.textContent = '← Back'; Object.assign(backButtonSettings.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginBottom: '8px' }); backButtonSettings.onmouseenter = () => backButtonSettings.style.background = 'rgba(255,255,255,0.2)'; backButtonSettings.onmouseleave = () => backButtonSettings.style.background = 'rgba(255,255,255,0.1)'; backButtonSettings.onclick = () => { showSettings = false; showBetsPanel = false; showEditBetsPanel = false; // Ensure other panels are closed refreshAll(); }; settingsPanel.appendChild(backButtonSettings); 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'; maxMatchesInput.max = '500'; 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; } if (newValue > 500) newValue = 500; maxDisplayMatches = newValue; maxMatchesInput.value = maxDisplayMatches; localStorage.setItem(MAX_DISPLAY_KEY, maxDisplayMatches.toString()); while (results.length > maxDisplayMatches) { results.pop(); } saveResults(); refreshAll(); }; maxMatchesSettingDiv.append(maxMatchesLabel, maxMatchesInput); settingsPanel.appendChild(maxMatchesSettingDiv); const transparencySettingDiv = document.createElement('div'); Object.assign(transparencySettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const transparencyLabel = document.createElement('label'); transparencyLabel.textContent = 'Panel Opacity:'; transparencyLabel.htmlFor = 'transparency-slider'; Object.assign(transparencyLabel.style, { flexShrink: '0' }); const transparencySlider = document.createElement('input'); transparencySlider.type = 'range'; transparencySlider.id = 'transparency-slider'; transparencySlider.min = '0.1'; transparencySlider.max = '1.0'; transparencySlider.step = '0.05'; transparencySlider.value = currentOpacity; Object.assign(transparencySlider.style, { width: '100px', padding: '4px', border: 'none', background: 'transparent', cursor: 'pointer' }); transparencySlider.oninput = () => { currentOpacity = parseFloat(transparencySlider.value); panel.style.background = `rgba(0,0,0,${currentOpacity})`; localStorage.setItem(OPACITY_KEY, currentOpacity.toString()); }; transparencySettingDiv.append(transparencyLabel, transparencySlider); settingsPanel.appendChild(transparencySettingDiv); const profitTargetSettingDiv = document.createElement('div'); Object.assign(profitTargetSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const profitTargetLabel = document.createElement('label'); profitTargetLabel.textContent = 'Profit Target ($):'; profitTargetLabel.htmlFor = 'profit-target-input'; Object.assign(profitTargetLabel.style, { flexShrink: '0' }); const profitTargetInput = document.createElement('input'); profitTargetInput.type = 'number'; profitTargetInput.id = 'profit-target-input'; profitTargetInput.min = '0'; profitTargetInput.value = profitTarget; Object.assign(profitTargetInput.style, { width: '80px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); profitTargetInput.onchange = () => { let newValue = parseInt(profitTargetInput.value, 10); if (isNaN(newValue) || newValue < 0) newValue = 0; profitTarget = newValue; profitTargetInput.value = profitTarget; localStorage.setItem(PROFIT_TARGET_KEY, profitTarget.toString()); alertShownProfit = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); refreshAll(); }; profitTargetSettingDiv.append(profitTargetLabel, profitTargetInput); settingsPanel.appendChild(profitTargetSettingDiv); const lossLimitSettingDiv = document.createElement('div'); Object.assign(lossLimitSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const lossLimitLabel = document.createElement('label'); lossLimitLabel.textContent = 'Loss Limit ($):'; lossLimitLabel.htmlFor = 'loss-limit-input'; Object.assign(lossLimitLabel.style, { flexShrink: '0' }); const lossLimitInput = document.createElement('input'); lossLimitInput.type = 'number'; lossLimitInput.id = 'loss-limit-input'; lossLimitInput.min = '0'; lossLimitInput.value = lossLimit; Object.assign(lossLimitInput.style, { width: '80px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); lossLimitInput.onchange = () => { let newValue = parseInt(lossLimitInput.value, 10); if (isNaN(newValue) || newValue < 0) newValue = 0; lossLimit = newValue; lossLimitInput.value = lossLimit; localStorage.setItem(LOSS_LIMIT_KEY, lossLimit.toString()); alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); refreshAll(); }; lossLimitSettingDiv.append(lossLimitLabel, lossLimitInput); settingsPanel.appendChild(lossLimitSettingDiv); const clearAlertsBtn = document.createElement('button'); clearAlertsBtn.textContent = '✔️ Clear Alerts'; Object.assign(clearAlertsBtn.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); clearAlertsBtn.onmouseenter = () => clearAlertsBtn.style.background = 'rgba(255,255,255,0.2)'; clearAlertsBtn.onmouseleave = () => clearAlertsBtn.style.background = 'rgba(255,255,255,0.1)'; clearAlertsBtn.onclick = () => { alertShownProfit = false; alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); alertMessageDiv.style.display = 'none'; refreshAll(); }; settingsPanel.appendChild(clearAlertsBtn); const miniBarCountSettingDiv = document.createElement('div'); Object.assign(miniBarCountSettingDiv.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }); const miniBarCountLabel = document.createElement('label'); miniBarCountLabel.textContent = 'Mini-Bar Count:'; miniBarCountLabel.htmlFor = 'mini-bar-count-input'; Object.assign(miniBarCountLabel.style, { flexShrink: '0' }); const miniBarCountInput = document.createElement('input'); miniBarCountInput.type = 'number'; miniBarCountInput.id = 'mini-bar-count-input'; miniBarCountInput.min = '1'; miniBarCountInput.max = '50'; miniBarCountInput.value = miniBarCount; Object.assign(miniBarCountInput.style, { width: '60px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); miniBarCountInput.onchange = () => { let newValue = parseInt(miniBarCountInput.value, 10); if (isNaN(newValue) || newValue < 1) newValue = 1; if (newValue > 50) newValue = 50; miniBarCount = newValue; miniBarCountInput.value = miniBarCount; localStorage.setItem(MINI_BAR_COUNT_KEY, miniBarCount.toString()); refreshAll(); }; miniBarCountSettingDiv.append(miniBarCountLabel, miniBarCountInput); settingsPanel.appendChild(miniBarCountSettingDiv); // NEW: Button to open Edit Bets Panel const editBetsButton = document.createElement('button'); editBetsButton.textContent = '✏️ Edit Bets'; Object.assign(editBetsButton.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); editBetsButton.onmouseenter = () => editBetsButton.style.background = 'rgba(255,255,255,0.2)'; editBetsButton.onmouseleave = () => editBetsButton.style.background = 'rgba(255,255,255,0.1)'; editBetsButton.onclick = () => { showSettings = false; // Close settings panel showBetsPanel = false; // Close bets panel showEditBetsPanel = true; // Open edit bets panel refreshAll(); }; settingsPanel.appendChild(editBetsButton); 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', marginTop: '4px' }); 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; alertShownProfit = false; alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); // Also reset Martingale bets to default when resetting all data currentMartingaleBets = [...DEFAULT_MARTINGALE_BETS]; saveMartingaleBets(currentMartingaleBets); 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 (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 = !showSettings; // Toggle settings visibility showBetsPanel = false; // Close bets panel if settings is opened showEditBetsPanel = false; // Close edit bets panel refreshAll(); }; panel.appendChild(settingsButton); // NEW: Bets Panel elements const betsPanel = document.createElement('div'); Object.assign(betsPanel.style, { display: 'none', // Managed by refreshAll() flexDirection: 'column', gap: '8px', padding: '12px 0' }); panel.appendChild(betsPanel); const betsTitle = document.createElement('div'); betsTitle.textContent = 'Martingale Auto-Start'; Object.assign(betsTitle.style, { fontSize: '16px', fontWeight: 'bold', marginBottom: '4px' }); betsPanel.appendChild(betsTitle); const backButtonBets = document.createElement('button'); backButtonBets.textContent = '← Back'; Object.assign(backButtonBets.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginBottom: '8px' }); backButtonBets.onmouseenter = () => backButtonBets.style.background = 'rgba(255,255,255,0.2)'; backButtonBets.onmouseleave = () => backButtonBets.style.background = 'rgba(255,255,255,0.1)'; backButtonBets.onclick = () => { showBetsPanel = false; showSettings = false; // Ensure settings panel is also closed showEditBetsPanel = false; // Ensure edit bets panel is also closed refreshAll(); }; betsPanel.appendChild(backButtonBets); const betButtonsContainer = document.createElement('div'); Object.assign(betButtonsContainer.style, { display: 'flex', flexDirection: 'column', // Make rows stack vertically gap: '6px', marginTop: '6px' }); betsPanel.appendChild(betButtonsContainer); // NEW: Edit Bets Panel elements const editBetsPanel = document.createElement('div'); Object.assign(editBetsPanel.style, { display: 'none', // Managed by refreshAll() flexDirection: 'column', gap: '8px', padding: '12px 0' }); panel.appendChild(editBetsPanel); const editBetsTitle = document.createElement('div'); editBetsTitle.textContent = 'Edit Martingale Bets'; Object.assign(editBetsTitle.style, { fontSize: '16px', fontWeight: 'bold', marginBottom: '4px' }); editBetsPanel.appendChild(editBetsTitle); const backButtonEditBets = document.createElement('button'); backButtonEditBets.textContent = '← Back to Settings'; Object.assign(backButtonEditBets.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginBottom: '8px' }); backButtonEditBets.onmouseenter = () => backButtonEditBets.style.background = 'rgba(255,255,255,0.2)'; backButtonEditBets.onmouseleave = () => backButtonEditBets.style.background = 'rgba(255,255,255,0.1)'; backButtonEditBets.onclick = () => { showEditBetsPanel = false; showSettings = true; // Go back to settings panel refreshAll(); }; editBetsPanel.appendChild(backButtonEditBets); const martingaleEditGrid = document.createElement('div'); Object.assign(martingaleEditGrid.style, { display: 'grid', gridTemplateColumns: 'auto 1fr', // Label then input gap: '6px 10px' }); editBetsPanel.appendChild(martingaleEditGrid); // Dynamically create 11 input fields for editing Martingale bets let martingaleEditInputs = []; for (let i = 0; i < EXPECTED_BET_COUNT; i++) { const label = document.createElement('label'); label.textContent = `Bet ${i + 1}:`; Object.assign(label.style, { fontSize: '13px', paddingTop: '4px' }); martingaleEditGrid.appendChild(label); const input = document.createElement('input'); input.type = 'text'; // Use text to allow K/M/B suffixes input.placeholder = `e.g., ${i === 0 ? '10k' : (i === 1 ? '30k' : '')}`; // Adjusted for 10k base Object.assign(input.style, { width: '80px', padding: '4px', border: '1px solid #555', borderRadius: '4px', background: 'rgba(255,255,255,0.1)', color: '#fff' }); input.onchange = (e) => { const parsed = parseBetInput(e.target.value); if (parsed !== null) { currentMartingaleBets[i] = parsed; saveMartingaleBets(currentMartingaleBets); e.target.value = formatNumberToKMB(parsed); // Format back to KMB for display refreshAll(); // Update buttons in Bets panel } else if (e.target.value !== '') { // If not empty but invalid alert('Invalid input. Please enter a positive number or use K/M/B suffixes (e.g., 25k, 2.5m).'); e.target.value = formatNumberToKMB(currentMartingaleBets[i]); // Revert to old value } }; martingaleEditGrid.appendChild(input); martingaleEditInputs.push(input); } // NEW: Bets Button (main panel) const betsButton = document.createElement('button'); betsButton.textContent = '💰 Bets'; Object.assign(betsButton.style, { alignSelf: 'flex-start', background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 8px', cursor: 'pointer', marginTop: '4px' }); betsButton.onmouseenter = () => betsButton.style.background = 'rgba(255,255,255,0.2)'; betsButton.onmouseleave = () => betsButton.style.background = 'rgba(255,255,255,0.1)'; betsButton.onclick = () => { showBetsPanel = !showBetsPanel; // Toggle bets panel visibility showSettings = false; // Close settings panel if bets is opened showEditBetsPanel = false; // Close edit bets panel refreshAll(); }; panel.appendChild(betsButton); 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)); // --- NEW: Martingale Bet Helper Functions --- function parseBetInput(str) { str = str.toLowerCase().replace(/,/g,'').trim(); let m = 1; if (str.endsWith('k')) { m=1e3; str=str.slice(0,-1); } else if (str.endsWith('m')) { m=1e6; str=str.slice(0,-1); } else if (str.endsWith('b')) { m=1e9; str=str.slice(0,-1); } let v = parseFloat(str); return (isNaN(v)||v<=0) ? null : Math.floor(v*m); } function formatNumberToKMB(num) { if (num === null || isNaN(num)) return ''; // Handle cases where num might be invalid if (num >= 1e9) { return (num / 1e9).toFixed(2).replace(/\.00$/, '') + 'b'; } if (num >= 1e6) { return (num / 1e6).toFixed(2).replace(/\.00$/, '') + 'm'; } if (num >= 1e3) { return (num / 1e3).toFixed(2).replace(/\.00$/, '') + 'k'; } return num.toLocaleString(); // Fallback for numbers less than 1000 } function loadMartingaleBets() { let storedBetsArray = []; try { const storedJson = localStorage.getItem(MARTINGALE_BETS_STORAGE_KEY); if (storedJson) { const parsed = JSON.parse(storedJson); if (Array.isArray(parsed)) { storedBetsArray = parsed; } } } catch (e) { console.error("Error parsing stored martingale bets:", e); // If parsing fails, storedBetsArray remains empty, leading to default values. } // Start with a copy of the default values to ensure correct length and initial values let finalBets = [...DEFAULT_MARTINGALE_BETS]; // Overlay valid stored bets onto the default array storedBetsArray.forEach((bet, index) => { if (index < EXPECTED_BET_COUNT) { // Only process up to the expected count const num = parseInt(bet, 10); // Use the stored value if it's a valid positive number if (!isNaN(num) && num > 0) { finalBets[index] = num; } // If it's not valid, the default value at that index is retained from 'finalBets' initialization } }); return finalBets; } function saveMartingaleBets(a){ localStorage.setItem(MARTINGALE_BETS_STORAGE_KEY, JSON.stringify(a)); } // MODIFIED startMatch to correctly compare to the input box value *after* filling it. function startMatch(originalButtonBetValue) { const inputBox = document.querySelector('input[aria-label="Money value"]'); if (!inputBox) { console.warn("RR Tracker: Bet input box not found. Cannot start match."); return; } // First, set the input box value based on the button clicked nativeSet.call(inputBox, originalButtonBetValue.toLocaleString('en-US')); inputBox.dispatchEvent(new Event('input',{bubbles:true})); inputBox.dispatchEvent(new Event('change',{bubbles:true})); // NOW, after the input box has been filled, read its value for comparison const currentInputBoxBet = parseBetInput(inputBox.value); // Provide immediate feedback if input box value is manipulated by Torn itself to be less if (currentInputBoxBet === null || currentInputBoxBet <= 0) { alert("Invalid bet amount currently in the input box. Please ensure it's a valid number."); return; } // This check might seem redundant *after* setting the value, but it's crucial if the input // box has any external listeners or restrictions that might alter the value after `nativeSet.call`. // Keeping it for exact replication of the second script's logic flow. if (currentInputBoxBet < originalButtonBetValue) { alert(`The bet amount in the box ($${currentInputBoxBet.toLocaleString()}) ` + `is less than the button's intended value ($${originalButtonBetValue.toLocaleString()}).\n` + `Match not started.`); return; } let start = [...document.querySelectorAll('button')] .find(b=>b.textContent.trim().toLowerCase()==='start'); if (!start) { console.warn("RR Tracker: 'Start' button not found. Perhaps already in a match or not on create screen."); return; } start.click(); //setTimeout(()=>{ //let yes = [...document.querySelectorAll('button')] //.find(b=>b.textContent.trim().toLowerCase()==='yes'); //if (yes) yes.click(); //}, 400); // Small delay for confirmation pop-up } // --- New Function to Build/Rebuild Martingale Bet Buttons --- function buildBetButtons() { currentMartingaleBets = loadMartingaleBets(); // Load latest bets // Populate main Bets panel betButtonsContainer.innerHTML = ''; // Clear existing buttons // Row for the first bet (Base Bet) const firstBetRow = document.createElement('div'); Object.assign(firstBetRow.style, { display: 'flex', justifyContent: 'center', // Center the single button width: '100%' }); let b0 = document.createElement('button'); b0.textContent = formatNumberToKMB(currentMartingaleBets[0]); b0.title = `$${currentMartingaleBets[0].toLocaleString()}`; Object.assign(b0.style, { background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 10px', cursor: 'pointer', fontSize: '11px', minWidth: '80px' // Ensure a consistent width }); b0.onmouseenter = () => b0.style.background = 'rgba(255,255,255,0.2)'; b0.onmouseleave = () => b0.style.background = 'rgba(255,255,255,0.1)'; b0.onclick = () => startMatch(currentMartingaleBets[0]); firstBetRow.appendChild(b0); betButtonsContainer.appendChild(firstBetRow); // Rows for the remaining 10 bets (2 buttons per row) - Changed from 9 to 10 for (let i = 1; i < EXPECTED_BET_COUNT; i += 2) { const row = document.createElement('div'); Object.assign(row.style, { display: 'flex', justifyContent: 'space-around', // Space out the two buttons gap: '6px', width: '100%' }); // Button 1 let b1 = document.createElement('button'); b1.textContent = formatNumberToKMB(currentMartingaleBets[i]); b1.title = `$${currentMartingaleBets[i].toLocaleString()}`; Object.assign(b1.style, { background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 10px', cursor: 'pointer', fontSize: '11px', flex: '1', // Allow them to grow and fill space minWidth: '70px' }); b1.onmouseenter = () => b1.style.background = 'rgba(255,255,255,0.2)'; b1.onmouseleave = () => b1.style.background = 'rgba(255,255,255,0.1)'; b1.onclick = () => startMatch(currentMartingaleBets[i]); row.appendChild(b1); // Button 2 (if exists) if (i + 1 < EXPECTED_BET_COUNT) { let b2 = document.createElement('button'); b2.textContent = formatNumberToKMB(currentMartingaleBets[i+1]); b2.title = `$${currentMartingaleBets[i+1].toLocaleString()}`; Object.assign(b2.style, { background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '4px 10px', cursor: 'pointer', fontSize: '11px', flex: '1', minWidth: '70px' }); b2.onmouseenter = () => b2.style.background = 'rgba(255,255,255,0.2)'; b2.onmouseleave = () => b2.style.background = 'rgba(255,255,255,0.1)'; b2.onclick = () => startMatch(currentMartingaleBets[i+1]); row.appendChild(b2); } betButtonsContainer.appendChild(row); } // Populate Martingale editing inputs in dedicated Edit Bets panel martingaleEditInputs.forEach((input, i) => { // Ensure that currentMartingaleBets[i] exists before formatting. input.value = formatNumberToKMB(currentMartingaleBets[i]); }); // NEW: Populate Mini-Bar with Win/Lose Circles and Martingale Buttons miniBar.innerHTML = ''; // Clear existing mini-bar content const miniCirclesContainer = document.createElement('div'); Object.assign(miniCirclesContainer.style, { display: 'flex', flexWrap: 'wrap', gap: '2px', justifyContent: 'center', // No margin-bottom here, as it's handled by the parent miniBar gap }); results.slice(0, miniBarCount).forEach((r, idx) => miniCirclesContainer.append(makeCircle(r.result, r.bet, idx))); miniBar.appendChild(miniCirclesContainer); // Add betting buttons to mini-bar only when collapsed if (collapsed && !showBetsPanel && !showSettings && !showEditBetsPanel) { const miniBetContainerTopRow = document.createElement('div'); Object.assign(miniBetContainerTopRow.style, { display: 'flex', flexWrap: 'nowrap', // Ensure they stay in one row gap: '2px', justifyContent: 'center', marginBottom: '2px' // Small gap between the two rows of bets }); const miniBetContainerBottomRow = document.createElement('div'); Object.assign(miniBetContainerBottomRow.style, { display: 'flex', flexWrap: 'nowrap', // Ensure they stay in one row gap: '2px', justifyContent: 'center' }); // Loop through all 11 bets currentMartingaleBets.forEach((bet, i) => { let miniB = document.createElement('button'); miniB.textContent = formatNumberToKMB(bet); miniB.title = `$${bet.toLocaleString()}`; Object.assign(miniB.style, { background: 'rgba(255,255,255,0.1)', color: '#fff', border: 'none', borderRadius:'6px', padding: '3px 6px', // Smaller padding for mini-bar cursor: 'pointer', fontSize: '9px', // Smaller font size for mini-bar width: 'auto' // Allow buttons to size naturally }); miniB.onmouseenter = () => miniB.style.background = 'rgba(255,255,255,0.2)'; miniB.onmouseleave = () => miniB.style.background = 'rgba(255,255,255,0.1)'; miniB.onclick = () => startMatch(bet); // Distribute into two rows: first row has 6 (Base + 5), second has 5 if (i < 6) { // First 6 buttons (index 0-5) go to top row miniBetContainerTopRow.appendChild(miniB); } else { // Remaining 5 (index 6-10) go to bottom row miniBetContainerBottomRow.appendChild(miniB); } }); miniBar.appendChild(miniBetContainerTopRow); miniBar.appendChild(miniBetContainerBottomRow); } } const makeCircle = (result, bet, index) => { const container = document.createElement('span'); Object.assign(container.style, { display: 'inline-block', width: '14px', height: '14px', borderRadius: '50%', backgroundColor: result === 'win' ? '#4CAF50' : '#E53935', marginRight: '2px', cursor: 'pointer', position: 'relative', }); container.title = `${result.toUpperCase()}: $${bet.toLocaleString()}`; container.addEventListener('click', (e) => { e.stopPropagation(); if (isDragging || isTwoFingerDragging) return; Array.from(container.children).forEach(child => { if (child.classList.contains('rr-temp-popup')) { container.removeChild(child); } }); const tempPopup = document.createElement('div'); tempPopup.classList.add('rr-temp-popup'); tempPopup.textContent = `${result.toUpperCase()}: $${bet.toLocaleString()}`; Object.assign(tempPopup.style, { position: 'absolute', background: 'rgba(0,0,0,0.9)', border: '1px solid #555', color: 'white', padding: '4px 8px', borderRadius: '4px', fontSize: '12px', whiteSpace: 'nowrap', zIndex: '10000000', top: '-28px', left: '50%', transform: 'translateX(-50%)', pointerEvents: 'none', opacity: '0', transition: 'opacity 0.2s ease-in-out', }); container.appendChild(tempPopup); setTimeout(() => tempPopup.style.opacity = '1', 10); setTimeout(() => { tempPopup.style.opacity = '0'; setTimeout(() => { if (container.contains(tempPopup)) { container.removeChild(tempPopup); } }, 200); }, 1500); }); return container; }; function updateStatus() { statusDiv.textContent = collapsed ? '▪' : (roundActive ? '►' : '▸'); } function updatePanelVisibility() { // Determine if we're on an "RR page" const onRRPage = document.body.innerText.includes('POT MONEY') || document.body.innerText.includes('Waiting:') || document.body.innerText.includes('You take your winnings') || document.body.innerText.includes('BANG! You fall down'); // Condition 1: If autoHide is explicitly false, the panel always shows. if (!autoHide) { panel.style.display = 'flex'; return; // Exit the function, no further checks needed. } // Condition 2: If autoHide is true (we only reach here if !autoHide was false), // show the panel IF we are NOT on an RR page. Otherwise, hide it. panel.style.display = !onRRPage ? 'flex' : 'none'; } function refreshAll() { alertMessageDiv.style.display = 'none'; let showAlert = false; let alertText = ''; let alertBackgroundColor = ''; let alertBorderColor = ''; if (lossLimit > 0 && totalProfit <= -lossLimit) { if (!alertShownLoss) { alertShownLoss = true; localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'true'); } showAlert = true; alertText = `🚨 LOSS LIMIT REACHED! -$${lossLimit.toLocaleString()}`; alertBackgroundColor = 'rgba(229, 57, 53, 0.8)'; alertBorderColor = '#E53935'; } else if (profitTarget > 0 && totalProfit >= profitTarget) { if (!alertShownProfit) { alertShownProfit = true; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'true'); } showAlert = true; alertText = `🎯 PROFIT TARGET REACHED! +$${profitTarget.toLocaleString()}`; alertBackgroundColor = 'rgba(76, 175, 80, 0.8)'; alertBorderColor = '#4CAF50'; } if (showAlert) { alertMessageDiv.textContent = alertText; Object.assign(alertMessageDiv.style, { background: alertBackgroundColor, borderColor: alertBorderColor, display: 'block' }); } 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 = ''; results.slice(0, maxDisplayMatches).forEach((r,i) => { const row = document.createElement('div'); row.append( makeCircle(r.result, r.bet, i), document.createTextNode(`${i+1}. ${r.result.toUpperCase()} — $${r.bet.toLocaleString()}`) ); resultsContainer.appendChild(row); }); // Handle panel visibility based on current state statsGroup.style.display = 'none'; settingsPanel.style.display = 'none'; betsPanel.style.display = 'none'; editBetsPanel.style.display = 'none'; miniBar.style.display = 'none'; profitMini.style.display = 'none'; // *** MODIFICATION START *** // Always hide settings and bets buttons initially settingsButton.style.display = 'none'; betsButton.style.display = 'none'; if (showSettings) { settingsPanel.style.display = 'flex'; } else if (showBetsPanel) { betsPanel.style.display = 'flex'; } else if (showEditBetsPanel) { editBetsPanel.style.display = 'flex'; } else if (collapsed) { miniBar.style.display = 'flex'; profitMini.style.display = 'block'; // When collapsed, settings/bets buttons are still hidden. // They only appear in the expanded main view. } else { // This is the expanded main view statsGroup.style.display = 'flex'; settingsButton.style.display = 'inline-block'; // Show settings button betsButton.style.display = 'inline-block'; // Show bets button } // *** MODIFICATION END *** // Call buildBetButtons to ensure both main and mini-bar bet buttons are updated buildBetButtons(); updateStatus(); updatePanelVisibility(); } function addResult(type) { if (!roundActive) return; if (type === 'win') { totalProfit += lastPot; } else { totalProfit -= lastPot; } saveTotalProfit(); results.unshift({ result: type, bet: lastPot }); if (results.length > maxDisplayMatches) { results.pop(); } saveResults(); roundActive = false; hasTracked = true; // Reset alertShown flags if profit/loss goes back within limits if (profitTarget > 0 && alertShownProfit && totalProfit < profitTarget) { alertShownProfit = false; localStorage.setItem(ALERT_SHOWN_PROFIT_KEY, 'false'); } if (lossLimit > 0 && alertShownLoss && totalProfit > -lossLimit) { alertShownLoss = false; localStorage.setItem(ALERT_SHOWN_LOSS_KEY, 'false'); } 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() { // Check for "Waiting:", or "Create Game" if a new game can be started // Added check for button.start-game which might be present on some create game screens if (hasTracked && (document.body.innerText.includes('Waiting:') || document.body.innerText.includes('Create Game') || document.querySelector('button.start-game'))) { roundActive = true; hasTracked = false; updateStatus(); } } // --- Unified Drag and Move Logic --- const savePos = () => localStorage.setItem(POS_KEY, JSON.stringify({ top: panel.style.top, left: panel.style.left })); function onDragMove(e) { if (!isDragging) return; e.preventDefault(); const moveEvent = e.touches ? e.touches[0] : e; if (typeof moveEvent.clientX === 'undefined') return; const dx = moveEvent.clientX - dragMouseX; const dy = moveEvent.clientY - dragMouseY; dragMouseX = moveEvent.clientX; dragMouseY = moveEvent.clientY; panel.style.left = (panel.offsetLeft + dx) + 'px'; panel.style.top = (panel.offsetTop + dy) + 'px'; } function onDragEnd() { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', onDragMove); document.removeEventListener('mouseup', onDragEnd); document.removeEventListener('touchmove', onDragMove); document.removeEventListener('touchend', onDragEnd); savePos(); panel.style.cursor = ''; document.body.style.cursor = ''; } function startDrag(e) { if (isTwoFingerDragging) return; e.preventDefault(); const startEvent = e.touches ? e.touches[0] : e; if (typeof startEvent.clientX === 'undefined') return; isDragging = true; dragMouseX = startEvent.clientX; dragMouseY = startEvent.clientY; panel.style.cursor = 'grabbing'; document.body.style.cursor = 'grabbing'; document.addEventListener('mousemove', onDragMove); document.addEventListener('mouseup', onDragEnd); document.addEventListener('touchmove', onDragMove, { passive: false }); document.addEventListener('touchend', onDragEnd); } dragHandle.addEventListener('mousedown', startDrag); dragHandle.addEventListener('touchstart', e => { if (e.touches.length === 1) { startDrag(e); } }); panel.addEventListener('mousedown', e => { if (e.shiftKey && e.target !== dragHandle) { startDrag(e); } }); // --- Two-Finger Drag Implementation for the entire panel --- panel.addEventListener('touchstart', e => { if (e.touches.length === 2) { e.preventDefault(); isTwoFingerDragging = true; isDragging = false; // Ensure single-drag is off initialTouchMidX = (e.touches[0].clientX + e.touches[1].clientX) / 2; initialTouchMidY = (e.touches[0].clientY + e.touches[1].clientY) / 2; initialPanelX = panel.offsetLeft; initialPanelY = panel.offsetTop; panel.style.cursor = 'grabbing'; document.body.style.cursor = 'grabbing'; document.addEventListener('touchmove', onTwoFingerMove, { passive: false }); document.addEventListener('touchend', onTwoFingerEnd); } }); function onTwoFingerMove(e) { if (!isTwoFingerDragging || e.touches.length !== 2) { onTwoFingerEnd(e); return; } e.preventDefault(); const currentTouchMidX = (e.touches[0].clientX + e.touches[1].clientX) / 2; const currentTouchMidY = (e.touches[0].clientY + e.touches[1].clientY) / 2; const dx = currentTouchMidX - initialTouchMidX; const dy = currentTouchMidY - initialTouchMidY; panel.style.left = (initialPanelX + dx) + 'px'; panel.style.top = (initialPanelY + dy) + 'px'; } function onTwoFingerEnd(e) { if (!isTwoFingerDragging) return; if (e.touches.length < 2) { document.removeEventListener('touchmove', onTwoFingerMove); document.removeEventListener('touchend', onTwoFingerEnd); savePos(); isTwoFingerDragging = false; panel.style.cursor = ''; document.body.style.cursor = ''; } } statusDiv.addEventListener('click', () => { if (isDragging || isTwoFingerDragging) return; collapsed = !collapsed; // When collapsing, ensure settings/bets/edit-bets panels are hidden if (collapsed) { showSettings = false; showBetsPanel = false; showEditBetsPanel = false; } localStorage.setItem(COLLAPSE_KEY, JSON.stringify(collapsed)); refreshAll(); }); alertMessageDiv.addEventListener('click', () => { alertMessageDiv.style.display = 'none'; }); refreshAll(); // Initial render setInterval(() => { updatePanelVisibility(); scanStart(); scanPot(); scanResult(); }, 500); })();