Torn Blackjack Helper

Heavy balance blur, session P&L tracker, bet multipliers. Cosmetic/QoL only.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Torn Blackjack Helper
// @namespace    https://www.torn.com/
// @version      2.0.0
// @description  Heavy balance blur, session P&L tracker, bet multipliers. Cosmetic/QoL only.
// @author       Muckduck
// @match        https://www.torn.com/loader.php?sid=blackjack*
// @match        https://www.torn.com/page.php?sid=blackjack*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // ─── Persisted prefs ───────────────────────────────────────────────────────
  const KEY_HIDE_BAL = 'bj_hideBalance';
  const KEY_SHOW_PNL = 'bj_showPnl';

  let hideBalance = GM_getValue(KEY_HIDE_BAL, false);
  let showPnl     = GM_getValue(KEY_SHOW_PNL, true);

  // ─── Session P&L state (in-memory only, resets on page load) ──────────────
  let sessionPnl  = 0;
  let lastMoney   = null;

  // ─── Balance element ───────────────────────────────────────────────────────
  function getMoneyEl() {
    return document.getElementById('user-money');
  }

  function getRawMoney() {
    const el = getMoneyEl();
    if (!el) return null;
    const raw = parseInt(el.getAttribute('data-money'), 10);
    return isNaN(raw) ? null : raw;
  }

  // ─── Blur / unblur ─────────────────────────────────────────────────────────
  function applyBalanceVisibility() {
    const el = getMoneyEl();
    if (!el) return;
    if (hideBalance) {
      el.style.filter     = 'blur(12px) brightness(0.4)';
      el.style.userSelect = 'none';
      el.style.transition = 'filter 0.1s ease';
    } else {
      el.style.filter     = '';
      el.style.userSelect = '';
      el.style.transition = '';
    }
  }

  // ─── Session P&L ───────────────────────────────────────────────────────────
  function initSession() {
    const money = getRawMoney();
    if (money === null) return;
    lastMoney  = money;
    sessionPnl = 0;
    updatePnlDisplay();
  }

  function checkMoneyChange() {
    const money = getRawMoney();
    if (money === null || lastMoney === null) return;
    if (money !== lastMoney) {
      sessionPnl += (money - lastMoney);
      lastMoney = money;
      updatePnlDisplay();
    }
  }

  function formatMoney(n) {
    const abs = Math.abs(n);
    let str;
    if (abs >= 1_000_000_000)      str = (abs / 1_000_000_000).toFixed(2) + 'B';
    else if (abs >= 1_000_000)     str = (abs / 1_000_000).toFixed(2) + 'M';
    else if (abs >= 1_000)         str = (abs / 1_000).toFixed(1) + 'K';
    else                           str = abs.toLocaleString();
    return (n >= 0 ? '+$' : '-$') + str;
  }

  function updatePnlDisplay() {
    const el = document.getElementById('bj-pnl-value');
    if (!el) return;
    el.textContent = formatMoney(sessionPnl);
    el.style.color = sessionPnl > 0 ? '#2ecc71' : sessionPnl < 0 ? '#e74c3c' : '#aaaaaa';
  }

  // ─── Bet helpers ───────────────────────────────────────────────────────────
  function getCurrentBet() {
    const input = document.querySelector(
      'input.bet, input[name="bet"], input[class*="bet-input"], input[class*="betInput"], input[placeholder*="bet" i]'
    );
    if (!input) return null;
    const val = parseFloat(input.value.replace(/[^0-9.]/g, ''));
    return isNaN(val) ? null : { input, val };
  }

  function setBet(newVal) {
    const result = getCurrentBet();
    if (!result) { showToast('Bet input not found.'); return; }
    const rounded = Math.floor(newVal);

    // Use React's internal setter if available (handles framework-controlled inputs)
    const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value');
    if (nativeSetter && nativeSetter.set) {
      nativeSetter.set.call(result.input, rounded);
    } else {
      result.input.value = rounded;
    }

    // Fire both input and change — React needs InputEvent, legacy code needs Event
    result.input.dispatchEvent(new InputEvent('input',  { bubbles: true, cancelable: true }));
    result.input.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
    result.input.focus();
  }

  // ─── Toast ─────────────────────────────────────────────────────────────────
  function showToast(msg) {
    const t = document.createElement('div');
    t.textContent = msg;
    Object.assign(t.style, {
      position: 'fixed', bottom: '80px', right: '20px', zIndex: 99999,
      background: '#1a1a2e', color: '#e0c97f', padding: '8px 14px',
      borderRadius: '6px', fontSize: '13px', fontFamily: 'monospace',
      boxShadow: '0 2px 12px rgba(0,0,0,0.6)', opacity: '1',
      transition: 'opacity 0.4s ease', pointerEvents: 'none'
    });
    document.body.appendChild(t);
    setTimeout(() => { t.style.opacity = '0'; }, 2000);
    setTimeout(() => t.remove(), 2500);
  }

  // ─── UI helpers ────────────────────────────────────────────────────────────
  function styleBtn(el, bg) {
    Object.assign(el.style, {
      background: bg, color: '#f0e6c0',
      border: '1px solid rgba(255,255,255,0.12)',
      borderRadius: '6px', padding: '6px 12px',
      fontSize: '13px', fontFamily: 'monospace', fontWeight: '600',
      cursor: 'pointer', boxShadow: '0 2px 6px rgba(0,0,0,0.4)',
      transition: 'filter 0.15s ease', whiteSpace: 'nowrap',
    });
    el.onmouseenter = () => el.style.filter = 'brightness(1.25)';
    el.onmouseleave = () => el.style.filter = '';
  }

  // ─── P&L widget ────────────────────────────────────────────────────────────
  function buildPnlWidget() {
    const wrap = document.createElement('div');
    wrap.id = 'bj-pnl-widget';
    Object.assign(wrap.style, {
      background: 'rgba(8,8,18,0.92)',
      border: '1px solid rgba(255,255,255,0.08)',
      borderRadius: '10px',
      padding: '10px 16px',
      display: showPnl ? 'flex' : 'none',
      flexDirection: 'column',
      alignItems: 'flex-end',
      gap: '3px',
      backdropFilter: 'blur(6px)',
      minWidth: '110px',
    });

    const title = document.createElement('div');
    title.textContent = 'THIS SESSION';
    Object.assign(title.style, {
      fontSize: '9px', color: '#555', fontFamily: 'monospace',
      letterSpacing: '2px', fontWeight: '700', textTransform: 'uppercase'
    });

    const val = document.createElement('div');
    val.id = 'bj-pnl-value';
    Object.assign(val.style, {
      fontSize: '20px', fontFamily: 'monospace', fontWeight: '700',
      color: '#aaaaaa', letterSpacing: '0.5px', lineHeight: '1.2'
    });
    val.textContent = '+$0';

    const resetBtn = document.createElement('button');
    resetBtn.textContent = '↺ reset';
    Object.assign(resetBtn.style, {
      background: 'none', border: 'none', color: '#444',
      fontSize: '10px', fontFamily: 'monospace', cursor: 'pointer',
      padding: '0', marginTop: '4px', letterSpacing: '0.5px'
    });
    resetBtn.onmouseenter = () => resetBtn.style.color = '#888';
    resetBtn.onmouseleave = () => resetBtn.style.color = '#444';
    resetBtn.addEventListener('click', () => {
      sessionPnl = 0;
      lastMoney  = getRawMoney();
      updatePnlDisplay();
    });

    wrap.appendChild(title);
    wrap.appendChild(val);
    wrap.appendChild(resetBtn);
    return wrap;
  }

  // ─── Main panel ────────────────────────────────────────────────────────────
  function injectUI() {
    if (document.getElementById('bj-helper-panel')) return;

    const panel = document.createElement('div');
    panel.id = 'bj-helper-panel';
    Object.assign(panel.style, {
      position: 'fixed', bottom: '20px', right: '20px',
      zIndex: '99998', display: 'flex', flexDirection: 'column',
      gap: '8px', alignItems: 'flex-end',
    });

    // P&L widget (built first so togglePnlBtn can reference it)
    const pnlWidget = buildPnlWidget();

    // Toggle balance blur
    const toggleBalBtn = document.createElement('button');
    styleBtn(toggleBalBtn, hideBalance ? '#6b1111' : '#0f4a22');
    toggleBalBtn.textContent = hideBalance ? '💰 Show Balance' : '🙈 Hide Balance';
    toggleBalBtn.addEventListener('click', () => {
      hideBalance = !hideBalance;
      GM_setValue(KEY_HIDE_BAL, hideBalance);
      styleBtn(toggleBalBtn, hideBalance ? '#6b1111' : '#0f4a22');
      toggleBalBtn.textContent = hideBalance ? '💰 Show Balance' : '🙈 Hide Balance';
      applyBalanceVisibility();
    });

    // Toggle P&L display
    const togglePnlBtn = document.createElement('button');
    styleBtn(togglePnlBtn, showPnl ? '#4a3800' : '#222222');
    togglePnlBtn.textContent = showPnl ? '📊 Hide P&L' : '📊 Show P&L';
    togglePnlBtn.addEventListener('click', () => {
      showPnl = !showPnl;
      GM_setValue(KEY_SHOW_PNL, showPnl);
      pnlWidget.style.display = showPnl ? 'flex' : 'none';
      styleBtn(togglePnlBtn, showPnl ? '#4a3800' : '#222222');
      togglePnlBtn.textContent = showPnl ? '📊 Hide P&L' : '📊 Show P&L';
    });

    // Bet multiplier buttons
    const betRow = document.createElement('div');
    Object.assign(betRow.style, { display: 'flex', gap: '6px' });
    [['× ½', 0.5], ['× 1.5', 1.5], ['× 2', 2]].forEach(([lbl, mult]) => {
      const btn = document.createElement('button');
      btn.textContent = lbl;
      btn.title = `Multiply bet by ${mult}`;
      styleBtn(btn, '#152050');
      btn.addEventListener('click', () => {
        const r = getCurrentBet();
        if (r) setBet(Math.max(1, r.val * mult));
        else showToast('Bet input not found.');
      });
      betRow.appendChild(btn);
    });

    // Label
    const label = document.createElement('div');
    label.textContent = '🃏 BJ Helper v2';
    Object.assign(label.style, {
      fontSize: '10px', color: '#333', textAlign: 'right',
      fontFamily: 'monospace', letterSpacing: '0.5px'
    });

    panel.appendChild(pnlWidget);
    panel.appendChild(toggleBalBtn);
    panel.appendChild(togglePnlBtn);
    panel.appendChild(betRow);
    panel.appendChild(label);
    document.body.appendChild(panel);

    applyBalanceVisibility();
    initSession();
  }

  // ─── MutationObserver ──────────────────────────────────────────────────────
  const observer = new MutationObserver(() => {
    if (hideBalance) applyBalanceVisibility();
    checkMoneyChange();
  });

  // ─── Init ──────────────────────────────────────────────────────────────────
  function init() {
    injectUI();
    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributeFilter: ['data-money']
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    setTimeout(init, 1200);
  }

})();