Greasy Fork

Better BiteFight

Adds an healthbar, energybar, links and other QOL to BiteFight

目前为 2024-11-20 提交的版本。查看 最新版本

// ==UserScript==
// @name         Better BiteFight
// @namespace    https://lobby.bitefight.gameforge.com/
// @version      0.6.3
// @description  Adds an healthbar, energybar, links and other QOL to BiteFight
// @author       Spychopat
// @match        https://*.bitefight.gameforge.com/*
// @icon         https://lobby.bitefight.gameforge.com/favicon.ico
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';
    // first, to avoid the page jump as much as possible
    insertCSS();
    addAdditionnalLink();

    // Script storage keys
    const KEY_CHARACTER = 'character';
    const pageLoadTime = Date.now();

    // Define character object
    const CHARACTER = GM_getValue(KEY_CHARACTER, {
        energy: 0,
        maxEnergy: 0,
        health: 0,
        maxHealth: 0,
        regenHealth: 0,
        potionCooldownEnd: 0,
        churchCooldownEnd: 0,
        autoRedirectGrotte: true
    });

    // Get Stats
    var allStatsElement = document.getElementsByClassName("gold")[0];
    var statsValues = allStatsElement.textContent.split("\n");
    statsValues = statsValues.map(value => value.trim());
    statsValues.shift();

    // Extract energy, fragments, gold, health, and hellStones
    var energy = statsValues[3].trim();
    var currentEnergy = energy.split("/")[0];
    var maxEnergy = energy.split("/")[1];

    if (currentEnergy && maxEnergy) {
        CHARACTER.energy = parseInt(currentEnergy); // Use parseFloat to preserve decimals
        CHARACTER.maxEnergy = parseInt(maxEnergy); // Use parseFloat to preserve decimals
    }

    var health = statsValues[4].trim();
    var currentHealth = formatNumber(health.split("/")[0]);
    var maxHealth = formatNumber(health.split("/")[1]);

    if (currentHealth && maxHealth) {
        CHARACTER.health = parseInt(currentHealth);
        CHARACTER.maxHealth = parseInt(maxHealth);
    }



    redirectAfterGrotteFight();
    updatePotionTimer();
    updateChurchCooldownTimer(); // Run the church cooldown update function if on the church page
    updateRegenHealth();
    updateCharacter();
    insertProgressBars(); // Insert progress bars after updating the character
    startHealthRegeneration(); // Start health regeneration on page load
    moveGameEventDiv(); // move down the game event div
    defaultNonPremiumShop();
    //console.log(CHARACTER);


    function insertCSS() {
        GM_addStyle(`
        #upgrademsg {
            display: none;
        }
        #premium > img {
            display: none;
        }
        #mmonetbar {
            display: none !important;
            visibility: hidden;
        }
    `);
    }

    // Format texts to return as numbers (no thousand separators)
    function formatNumber(value) {
        while (value.indexOf(".") > 0) value = value.replace(".", "");
        return value;
    }

    function updateRegenHealth() {
        if (!window.location.pathname.endsWith('/profile/index')) return;
        var elements = document.getElementsByClassName("triggerTooltip");

        // Loop through the elements
        for (let i = 0; i < elements.length; i++) {
            // Check if the inner text or inner HTML contains "/ h"
            if (elements[i].innerText.includes("/ h") || elements[i].innerHTML.includes("/ h")) {
                CHARACTER.regenHealth = parseInt(elements[i].textContent);
                //console.log("Regen per hour found : ", parseInt(elements[i].textContent));
                break; // Exit the loop once the element is found
            }
        }
    }

    // Update character in local storage
    function updateCharacter() {
        GM_setValue(KEY_CHARACTER, CHARACTER);
    }

    function updatePotionTimer() {
        if (!window.location.pathname.endsWith('/profile/index')) return; // Ensure this only runs on the right page
        // Get all elements with the class "inactive"
        const inactiveElements = document.getElementsByClassName('inactive');
        let targetElement = null;
        // Loop through each "inactive" element to find the target
        for (const inactiveElement of inactiveElements) {
            // Find elements with the class "countdown_row countdown_amount" within the current "inactive" element
            const matchingElements = inactiveElement.getElementsByClassName('countdown_row countdown_amount');
            if (matchingElements.length > 0) {
                targetElement = matchingElements[0]; // Take the first matching element
                break; // Stop once we find the target
            }
        }

        if (targetElement) {
            // Get the current time and add the potion cooldown to get the end time
            const currentTime = new Date().getTime() / 1000; // Current time in seconds
            const cooldownTime = timeToSeconds(targetElement.textContent); // Convert cooldown time to seconds
            const endTime = currentTime + cooldownTime; // Calculate the end time

            // Save the end time to the character object
            CHARACTER.potionCooldownEnd = endTime;
            updateCharacter(); // Save updated character data
        }
    }


    function timeToSeconds(timeStr) {
        const [hours, minutes, seconds] = timeStr.split(':').map(Number);
        return (hours * 3600) + (minutes * 60) + seconds;
    }

    function insertProgressBars() {
        // Check if the layout container is already present
        if (document.getElementById('progressBarsTimersContainer')) {
            return;
        }

        // Create the main container for the layout
        let mainContainer = document.createElement('div');
        mainContainer.id = 'progressBarsTimersContainer';
        mainContainer.style.display = 'flex';
        mainContainer.style.justifyContent = 'space-between';
        mainContainer.style.alignItems = 'center';
        mainContainer.style.marginTop = '3px';

        // Left section for progress bars
        let progressBarsContainer = document.createElement('div');
        progressBarsContainer.style.display = 'flex';
        progressBarsContainer.style.flexDirection = 'column';
        progressBarsContainer.style.alignItems = 'flex-start';
        progressBarsContainer.style.paddingLeft = '50px';

        // Create Health Progress Bar
        let healthBarContainer = document.createElement('div');
        healthBarContainer.style.width = '250px';
        healthBarContainer.style.position = 'relative';
        healthBarContainer.id = 'healthProgressBar';
        healthBarContainer.style.backgroundColor = 'black'; // Black background
        healthBarContainer.style.border = '3px solid rgb(117, 117, 117)'; // White outline

        let healthBar = document.createElement('div');
        healthBar.style.height = '20px';
        healthBar.style.width = `${(CHARACTER.health / CHARACTER.maxHealth) * 100}%`;
        healthBar.style.backgroundColor = '#b80000';

        let healthText = document.createElement('div');
        healthText.textContent = `${CHARACTER.health > 999 ? CHARACTER.health.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.') : CHARACTER.health}`;
        healthText.style.position = 'absolute';
        healthText.style.top = '50%';
        healthText.style.left = '50%';
        healthText.style.transform = 'translate(-50%, -50%)';
        healthText.style.color = 'white';
        healthText.style.fontSize = '12px';
        healthText.style.fontFamily = 'monospace';

        healthBarContainer.appendChild(healthBar);
        healthBarContainer.appendChild(healthText);

        // Create Energy Progress Bar
        let energyBarContainer = document.createElement('div');
        energyBarContainer.style.width = '250px';
        energyBarContainer.style.marginBottom = '5px';
        energyBarContainer.style.position = 'relative';
        energyBarContainer.id = 'energyProgressBar';
        energyBarContainer.style.backgroundColor = 'black'; // Black background
        energyBarContainer.style.borderLeft = '3px solid rgb(117, 117, 117)'; // White outline
        energyBarContainer.style.borderRight = '3px solid rgb(117, 117, 117)'; // White outline
        energyBarContainer.style.borderBottom = '3px solid rgb(117, 117, 117)'; // White outline

        let energyBar = document.createElement('div');
        energyBar.style.height = '20px';
        energyBar.style.width = `${(CHARACTER.energy / CHARACTER.maxEnergy) * 100}%`;
        energyBar.style.backgroundColor = '#0000a4';

        let energyText = document.createElement('div');
        energyText.textContent = `${CHARACTER.energy}`;
        energyText.style.position = 'absolute';
        energyText.style.top = '50%';
        energyText.style.left = '50%';
        energyText.style.transform = 'translate(-50%, -50%)';
        energyText.style.color = 'white';
        energyText.style.fontSize = '12px';
        energyText.style.fontFamily = 'monospace';

        energyBarContainer.appendChild(energyBar);
        energyBarContainer.appendChild(energyText);

        progressBarsContainer.appendChild(healthBarContainer);
        progressBarsContainer.appendChild(energyBarContainer);

        // Right section for timers
        let timersContainer = document.createElement('div');
        timersContainer.style.textAlign = 'center';
        timersContainer.style.paddingRight = '50px';
        timersContainer.style.width = '256px';

        // Ensure timers are stacked vertically
        timersContainer.style.display = 'flex';
        timersContainer.style.flexDirection = 'column'; // Stack vertically
        timersContainer.style.alignItems = 'center'; // Center align each timer

        // Potion Timer Link
        let potionTimer = document.createElement('a');
        potionTimer.id = 'potionCooldownTimer';
        potionTimer.style.color = 'white';
        potionTimer.style.fontSize = '14px';
        potionTimer.style.fontFamily = 'monospace';
        potionTimer.style.margin = '0px';
        potionTimer.href = '/profile/index#potions';
        potionTimer.textContent = 'Potion Cooldown: Calculating...';

        // Church Timer Link
        let churchTimer = document.createElement('a');
        churchTimer.id = 'churchCooldownTimer';
        churchTimer.style.color = 'white';
        churchTimer.style.fontSize = '14px';
        churchTimer.style.fontFamily = 'monospace';
        churchTimer.style.margin = '0px';
        churchTimer.href = '/city/church';
        churchTimer.textContent = 'Church Cooldown: Calculating...';

        timersContainer.appendChild(potionTimer);
        timersContainer.appendChild(churchTimer);

        // Add both sections to the main container
        mainContainer.appendChild(progressBarsContainer);
        mainContainer.appendChild(timersContainer);

        // Append the main container to the desired parent element
        document.getElementsByClassName("gold")[0].appendChild(mainContainer);

        // Start updating the timers
        updatePotionCooldownDisplay();
        setInterval(updatePotionCooldownDisplay, 1000);
        updateChurchCooldownDisplay();
        setInterval(updateChurchCooldownDisplay, 1000);
    }



    // Start real-time health regeneration
    function startHealthRegeneration() {
        const regenInterval = 200; // Update every 200 ms
        const regenPerSecond = CHARACTER.regenHealth / 3600; // Convert regenHealth from per hour to per second

        setInterval(() => {
            // Calculate the total health regenerated since the page loaded
            const elapsedSeconds = (Date.now() - pageLoadTime) / 1000; // Time elapsed in seconds
            const regeneratedHealth = regenPerSecond * elapsedSeconds;

            // Calculate the updated health, without modifying the original CHARACTER.health
            const updatedHealth = Math.min(
                CHARACTER.health + regeneratedHealth,
                CHARACTER.maxHealth
            );

            // Update the progress bar with the calculated health
            updateProgressBars(updatedHealth);
        }, regenInterval);
    }

    // Update the existing progress bars
    function updateProgressBars(calculatedHealth) {
        // Update Energy Progress Bar
        //const energyBar = document.getElementById('energyProgressBar').children[0];
        //energyBar.style.width = `${(CHARACTER.energy / CHARACTER.maxEnergy) * 100}%`;
        //const energyText = document.getElementById('energyProgressBar').children[1];
        //energyText.textContent = `${CHARACTER.energy}`;

        // Update Health Progress Bar
        const healthBar = document.getElementById('healthProgressBar').children[0];
        healthBar.style.width = `${(calculatedHealth / CHARACTER.maxHealth) * 100}%`;

        const healthText = document.getElementById('healthProgressBar').children[1];
        // Format the health value with thousands separators
        let healthWithoutDecimals = Math.floor(calculatedHealth);
        healthText.textContent = `${healthWithoutDecimals > 999 ? healthWithoutDecimals.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'): healthWithoutDecimals}`;
    }


    // Function to update the timer display
    function updatePotionCooldownDisplay() {
        const timerElement = document.getElementById('potionCooldownTimer');
        const currentTime = new Date().getTime() / 1000; // Current time in seconds

        if (CHARACTER.potionCooldownEnd > currentTime) {
            const remainingTime = CHARACTER.potionCooldownEnd - currentTime; // Remaining time in seconds
            const hours = Math.floor(remainingTime / 3600);
            const minutes = Math.floor((remainingTime % 3600) / 60);
            const seconds = Math.round(remainingTime % 60);
            timerElement.textContent = `Potion Cooldown: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        } else {
            timerElement.textContent = 'Potion Ready!';
        }
    }


    // Add the church cooldown timer display
    function updateChurchCooldownTimer() {
        if (!window.location.pathname.contains('/city/church')) return; // Ensure this only runs on the church page

        const churchCountdownElement = document.getElementsByClassName('hasCountdown')[0];
        if (churchCountdownElement) {
            const cooldownTime = churchCountdownElement.textContent.trim();
            const currentTime = new Date().getTime() / 1000; // Current time in seconds
            const cooldownSeconds = timeToSeconds(cooldownTime); // Convert cooldown time to seconds
            const endTime = currentTime + cooldownSeconds; // Calculate the end time

            // Save the end time to the character object
            CHARACTER.churchCooldownEnd = endTime;
            updateCharacter(); // Save updated character data
        }
    }


    // Function to update the church cooldown display
    function updateChurchCooldownDisplay() {
        const timerElement = document.getElementById('churchCooldownTimer');
        const currentTime = new Date().getTime() / 1000; // Current time in seconds

        if (CHARACTER.churchCooldownEnd > currentTime) {
            const remainingTime = CHARACTER.churchCooldownEnd - currentTime; // Remaining time in seconds
            const hours = Math.floor(remainingTime / 3600);
            const minutes = Math.floor((remainingTime % 3600) / 60);
            const seconds = Math.round(remainingTime % 60);
            timerElement.textContent = `Church Cooldown: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        } else {
            timerElement.textContent = 'Church Ready!';
        }
    }

    function addAdditionnalLink() {
        // Find the <li> containing the Chasse link
        const chasseListItem = document.querySelector('li.free-space > a[href$="/robbery/index"]');

        if (chasseListItem) {
            // Navigate to the parent <li> element
            const chasseLi = chasseListItem.closest('li');

            // Remove the class "free-space"
            chasseLi.removeAttribute('class');
            if (window.location.pathname.contains('robbery')){
                chasseLi.className = "active";
            }


            // Create a new <li> for the Grotte link
            const grotteLi = document.createElement('li');
            if (window.location.pathname.contains('/grotte')){
                if (document.getElementsByClassName("active")[0])document.getElementsByClassName("active")[0].removeAttribute('class');
                grotteLi.className = "active";
            }
            grotteLi.innerHTML = '<a href="/city/grotte" target="_top">Grotto</a>';


            const graveLi = document.createElement('li');
            if (window.location.pathname.contains('/city/graveyard') || window.location.pathname.contains('/user/working')){
                if (document.getElementsByClassName("active")[0])document.getElementsByClassName("active")[0].removeAttribute('class');
                graveLi.className = "active";
            }
            graveLi.innerHTML = '<a href="/city/graveyard" target="_top">Graveyard</a>';

            const shopLi = document.createElement('li');
            if (window.location.pathname.contains('/city/shop/')){
                if (document.getElementsByClassName("active")[0])document.getElementsByClassName("active")[0].removeAttribute('class');
                shopLi.className = "active";
            }
            shopLi.innerHTML = '<a href="/city/shop/potions/&page=1&premiumfilter=nonpremium" target="_top">Shop</a>';

            const questsLi = document.createElement('li');
            questsLi.className = "free-space";
            if (window.location.pathname.contains('/city/missions')){
                if (document.getElementsByClassName("active")[0])document.getElementsByClassName("active")[0].removeAttribute('class');
                questsLi.className = "active free-space";
            }
            questsLi.innerHTML = '<a href="/city/missions" target="_top">Quests</a>';

            // Insert the new links (in reverse)
            chasseLi.insertAdjacentElement('afterend', questsLi);
            chasseLi.insertAdjacentElement('afterend', shopLi);
            chasseLi.insertAdjacentElement('afterend', graveLi);
            chasseLi.insertAdjacentElement('afterend', grotteLi);
        }
    }
    function moveGameEventDiv() {
        if (!window.location.pathname.contains('/profile')) return;
        const gameEventDiv = document.getElementById('gameEvent');
        const itemsDiv = document.getElementById('items');

        if (gameEventDiv && itemsDiv) {
            itemsDiv.insertAdjacentElement('afterend', gameEventDiv);
        }
        //scroll up after upgrading a skill
        if (window.location.hash=='#tabs-2') window.scrollTo(0, 0);
        if (window.location.hash=='#potions') document.getElementsByClassName("ui-accordion-header")[2].scrollIntoView({ behavior: 'smooth' });
    }

    function defaultNonPremiumShop() {
        if (!window.location.pathname.contains('/city/shop')) return;

        const links = document.querySelectorAll('a'); // Select all anchor elements
        links.forEach(link => {
            // Check if the link href contains '/city/shop/'
            if (link.href.includes('/city/shop')) {
                // If the URL doesn't already have a query string, add it
                if (!link.href.includes('&premiumfilter=nonpremium')) {
                    link.href += '&premiumfilter=nonpremium';
                }
            }
        });

        /*
        var premiumfilter = document.querySelector('select[name="premiumfilter"]'); // Replace with the correct selector if necessary
        if (premiumfilter) {
            premiumfilter.value = 'nonpremium'; // Set default value to 'nonpremium'
        }*/
    }

    function redirectAfterGrotteFight() {
        if (CHARACTER.autoRedirectGrotte && window.location.href.includes('report/fightreport/') && window.location.href.includes('/grotte')) {
            // Redirect to '/city/grotte'
            window.location.href = '/city/grotte';
        }


        // all below is to add the button
        if (!window.location.pathname.contains('/city/grotte')) return;
        const buildingDescElement = document.getElementsByClassName('buildingDesc')[0];
        if (!buildingDescElement) return;
        // Create the switch container
        const switchContainer = document.createElement('div');
        switchContainer.classList.add('auto-redirect-switch');
        switchContainer.style.marginTop = '10px';

        // Create the label for the switch
        const label = document.createElement('label');
        label.textContent = 'Auto redirect after fight';
        label.style.marginRight = '10px';

        // Create the switch itself
        const switchInput = document.createElement('input');
        switchInput.type = 'checkbox';
        switchInput.style.transform = 'scale(1.2)'; // Optional: make it slightly bigger
        switchInput.style.display = 'block';
        switchInput.checked = CHARACTER.autoRedirectGrotte; // Get the saved state

        // Add change event to store the state of the switch
        switchInput.addEventListener('change', () => {
            CHARACTER.autoRedirectGrotte = switchInput.checked;
            updateCharacter();
            // Implement logic for auto redirection here if needed
        });

        // Append the label and input (switch) to the container
        switchContainer.appendChild(label);
        switchContainer.appendChild(switchInput);

        // Append the switch to the buildingDesc element
        buildingDescElement.appendChild(switchContainer);
    }
})();