Ahab / faster slots

// ==UserScript==
// @name         faster slots
// @version      2.5
// @author       Ahab [1735214]
// @match        https://www.torn.com/page.php?sid=slots
// @run-at       document-idle
// @grant        none
// @updateURL    https://openuserjs.org/meta/Ahab/faster_slots.meta.js
// @downloadURL  https://openuserjs.org/install/Ahab/faster_slots.user.js
// @license      MIT
// ==/UserScript==

$('<style>')
    .text(`
        :is(.notblured, .blured):not(
            #barrel0 > :nth-child(2) :is(.notblured, .blured),
            #barrel1 > :nth-child(2) :is(.notblured, .blured),
            #barrel2 > :nth-child(2) :is(.notblured, .blured)
        ) {
            display: none !important;
        }
    `)
    .appendTo('head');

let lastStake = null
let playerTokens = 0
let playerMoney = 0
let nextSpinAllowedAt = 0
let spinDelay = 500
let delayStep = 100
let count = 0
let sounds = {}
const tracksList = ['clickstart','reelspinend0','cashout','won1','won2','won3','won4','won5']

$(document).ajaxSuccess(function(event, xhr, settings) {
    if (!settings.url.includes('sid=slotsData')) return
    const response = JSON.parse(xhr.responseText)
    playerTokens = response.tokens
    playerMoney = response.money
})

function bet(stake){
    const barrel0 = $('#barrel0 > :nth-child(2)').find('.notblured')[0]
    const barrel1 = $('#barrel1 > :nth-child(2)').find('.notblured')[0]
    const barrel2 = $('#barrel2 > :nth-child(2)').find('.notblured')[0]
    $.get('https://www.torn.com/page.php?rfcv='+getRFC()+'&sid=slotsData&step=play&stake='+stake, function (response) {
        if(!response.error){
            playerTokens = response.tokens
            playerMoney = response.money
            updateBetButtons();
            $('span[id=moneyAmount]')[0].innerText = toNumberFormat(response.money)
            $('span[id=tokens]')[0].innerText = response.tokens
            $('span[id=moneyWon]')[0].innerText = toNumberFormat(response.moneyWon)
            barrel0.classList.replace(barrel0.classList[2], response.images[0].match(/\/([^/]+)\.[^.]+$/)?.[1])
            barrel1.classList.replace(barrel1.classList[2], response.images[1].match(/\/([^/]+)\.[^.]+$/)?.[1])
            barrel2.classList.replace(barrel2.classList[2], response.images[2].match(/\/([^/]+)\.[^.]+$/)?.[1])
            if(count == 5){
                spinDelay = Math.max(500, spinDelay - 10)
                count = 0
            }
            count += 1
            if(response.comboTitle != null){
                updateGameresult('win', response.comboTitle, {winAmount:response.moneyWon, totalMoney:response.moneyTotal, playerTokens:response.tokens})
            }else{
                updateGameresult('lose', "JACKPOT: $"+toNumberFormat(response.currentJackpot), {totalMoney:response.moneyTotal, playerTokens:response.tokens})
            }
        }else{
            if(response.error.toLowerCase().includes('slots spin is already in')){
                spinDelay += delayStep
            }
            updateGameresult('error', response.error)
        }
    }).fail(function (xhr, status, error) {
        console.error('GET failed:', status, error)
    });
}

function updateGameresult(type, message, data = {}){
    const {winAmount = null, totalMoney = null, playerTokens = null} = data
    if(type == 'error'){
        soundit('clickstart')
        $('#gameResult').css('font-size', '16px')
        $('#gameResult').html("<span id='grv'></span><span id='grh'>"+message+"</span>")
        $('#gameResultSr').text("Error. "+message);
    }else if(type == 'win'){
        var rate = data.winAmount / lastStake;
        var sound = 'won';
        if (rate < 4)
        {
            sound += 1;
        }
        else if(rate < 8)
        {
            sound += 2;
        }
        else if(rate < 200)
        {
            sound += 3;
        }
        else if(rate < 2000)
        {
            sound += 4;
        }
        else
        {
            sound += 5;
        }
        soundit(sound);
        soundit('cashout')
        $('#gameResult').css('font-size', '34px')
        $('#gameResult').html("<span id='grv'></span><span id='grh'>"+message+"</span>")
        $('#gameResultSr').text(message + ". Won $" + toNumberFormat(data.winAmount) + ". Money $" + toNumberFormat(data.totalMoney) + ". Tokens left: " + data.playerTokens + ".")
    }else if(type == 'lose'){
        soundit('reelspinend0')
        $('#gameResult').css('font-size', '34px')
        $('#gameResult').html("<span id='grv'></span><span id='grh'>"+message+"</span>")
        $('#gameResultSr').text("No win. Money $" + toNumberFormat(data.totalMoney) + ". Tokens left: " + data.playerTokens + ".")
    }

    var inter = setInterval(function(){
        $('#gameResult #grv').append( $('#gameResult #grh').html().slice(0, 1))
        $('#gameResult #grh').html( $('#gameResult #grh').html().slice(1))
        if ( $('#gameResult #grh').html().length == 0) clearInterval(inter)
    }, 50)
}

function updateBetButtons() {
    $('.betbtn').each(function () {
        const stake = Number(this.dataset.bet);
        const disabled = playerTokens <= 0 || stake > playerMoney

        $(this)
            .toggleClass('disabled', disabled)
            .attr('aria-disabled', disabled ? 'true' : 'false')
    });
}

function betCheck(stake){
    const now = performance.now();

    if (now < nextSpinAllowedAt) {
        return
    }
    if(playerTokens == 0){
        updateGameresult('error', "YOU HAVE ALREADY USED YOUR TOKENS FOR TODAY!")
        return
    }
    else if(playerMoney < stake){
        updateGameresult('error', "YOU DON'T HAVE ENOUGH MONEY TO MAKE THIS STAKE!")
        return
    }
    else if(stake == 0){
        updateGameresult('error', "PLEASE SELECT A STAKE!")
        return
    }
    nextSpinAllowedAt = now + spinDelay
    bet(stake)
}

function loadSounds() {
    if (typeof window.buzz === 'undefined') {
        setTimeout(loadSounds, 100)
        return;
    }
    for (const track of tracksList) {
        sounds[track] = new buzz.sound(
            '/casino/slots/audio/' + track,
            {
                formats: ['ogg', 'mp3'],
                preload: true,
                autoplay: false
            }
        );
    }
}

function soundit(soundID, volume = 25){
    if ($('li[class*=btn-sound]').attr('aria-pressed') === 'false') return
    const sound = sounds[soundID]
    if (!sound) return
    sound.stop()
    sound.setVolume(volume)
    sound.play()
};

loadSounds()

window.addEventListener('pointerdown', event => {
    if (event.button === 2) return
    const button = event.target.closest?.('.betbtn')
    if (!button) return
    event.preventDefault()
    event.stopImmediatePropagation()
    $(button).addClass('pressed')
    lastStake = Number(button.dataset.bet)
    betCheck(lastStake)
}, true);

window.addEventListener('pointerup', event => {
    if (event.button === 2) return
    const button = event.target.closest?.('.betbtn')
    if (!button) return
    $(button).removeClass('pressed')
}, true);

window.addEventListener('keydown', event => {
    if ((event.key !== ' ' && event.key !== 'Enter') || event.target.closest('input, textarea, select,  button:not(.betbtn), a, [contenteditable], .slots-main-wrap .btn-sound, .btn-pay-table')) return
    event.preventDefault()
    event.stopImmediatePropagation()
     $('li[data-bet='+lastStake+']').addClass('pressed')
    if (event.repeat) return
    betCheck(lastStake)
}, true);

window.addEventListener('keyup', event => {
    if ((event.key !== ' ' && event.key !== 'Enter') || event.target.closest('input, textarea, select,  button:not(.betbtn), a, [contenteditable], .slots-main-wrap .btn-sound, .btn-pay-table')) return
    $('li[data-bet='+lastStake+']').removeClass('pressed')
}, true);