// ==UserScript== // @name Better BiteFight // @namespace https://lobby.bitefight.gameforge.com/ // @version 0.8.0 // @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 // @downloadURL none // ==/UserScript== (function () { 'use strict'; // Script storage keys const KEY_SERVER_DOMAIN = window.location.hostname; const pageLoadTime = Date.now(); // uncomment to reset character data if needed //GM_setValue(KEY_SERVER_DOMAIN, {}) //GM_setValue(KEY_SERVER_DOMAIN+"/grotto_stats", {}) // Define character object const CHARACTER = GM_getValue(KEY_SERVER_DOMAIN, { energy: 0, maxEnergy: 0, health: 0, maxHealth: 0, regenHealth: 0, potionCooldownEnd: 0, churchCooldownEnd: 0, jobCooldownEnd: 0, autoRedirectGrotte: false, autoGrotto: [false, false, false], lastGrottoClick: -1 }); const GROTTO_STATS = GM_getValue(KEY_SERVER_DOMAIN+"/grotto_stats", { goldEarned: { 0: [], 1: [], 2: [] }, dmgTaken: { 0: [], 1: [], 2: [] }, xpEarned: { 0: [], 1: [], 2: [] } }); //console.log(parseInt(formatNumber(document.getElementsByClassName("gold")[1].firstChild.textContent.split("\n")[1]))); redirectAfterGrotteFight(); // run it first, because it has chances to redirect, so we don't work uselessly // early, to avoid the page jump as much as possible insertCSS(); addAdditionnalLink(); extractCharacterStats(); insertHealthEnergyBars(); // Insert progress bars after updating the character startHealthRegeneration(); // Start health regeneration on page load if (window.location.pathname.contains('/profile')){ if (window.location.hash=='#potion')autoUseBestPotion(); moveGameEventDiv(); // move down the game event div updateRegenHealthEnergy(); updatePotionTimer(); } else if (window.location.pathname.contains('/city/church')){ updateChurchCooldownTimer(); } else if (window.location.pathname.contains('/user/working')){ updateJobCooldownTimer(); } else if (window.location.pathname.contains('/city/graveyard')){ CHARACTER.jobCooldownEnd = 0; //the job can't be in progress if we're on this page, meaning that user may have canceled it updateJobCooldownDisplay(); } else if (window.location.pathname.contains('/city/grotte')){ addGrottoAutoRedirectCheckBox(); addAutoGrottoButton(); autoFightGrotto(); } else if (window.location.pathname.contains('/city/shop')){ defaultNonPremiumShop(); } updateCharacter(); //console.log(CHARACTER); function insertCSS() { GM_addStyle(` #upgrademsg { display: none; } #premium > img { display: none; } #mmonetbar { display: none !important; visibility: hidden; } .premiumButton { color: #FFCC33 !important; font-weight: bold !important; background-position: 0 -184px !important; text-shadow: 0 0 5px #000 !important; } input:disabled{ background-color: #50323200 !important; } `); } function extractCharacterStats(){ // Get Stats //var allStatsElement = document.getElementsByClassName("gold")[0]; var allStatsElement = document.querySelector("#infobar > div.wrap-left.clearfix > div > div.gold") 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); } // not used anymore //CHARACTER.level = parseInt(statsValues[5].trim().split(" ")[0]); // Now remove the parts containing health and energy // Update the text content by filtering out the specific lines var elements = allStatsElement.innerHTML.split("\n"); elements.splice(4, 2); // Removes the energy (index 4) and health (index 5) values // Create two new div elements var div1 = document.createElement('div'); var div2 = document.createElement('div'); var div3 = document.createElement('div'); // Fill div1 with elements 1, 2, 3 div1.innerHTML = elements.slice(1, 4).join(""); // Fill div2 with elements 4, 5 div2.innerHTML = elements.slice(4).join(""); div1.style.minWidth = "193px"; div2.style.minWidth = "193px"; div3.appendChild(div1); div3.appendChild(div2); div3.style.paddingRight = '104px'; div3.style.display = 'flex'; div3.style.justifyContent = 'space-between'; // Clear the existing content and append the two divs allStatsElement.innerHTML = ''; allStatsElement.appendChild(div3); allStatsElement.style.display = 'block'; } // Format texts to return as numbers (no thousand separators) function formatNumber(value) { while (value.indexOf(".") > 0) value = value.replace(".", ""); return value; } function updateRegenHealthEnergy() { //if (!window.location.pathname.endsWith('/profile/index')) return; CHARACTER.regenHealth = parseInt(document.querySelector("#skillmodis_tab > div.wrap-left.clearfix > div > div > table > tbody div.triggerTooltip").textContent.match(/\d+/g)); CHARACTER.regenEnergy = parseInt(document.getElementById('actionpointRegeneration').parentElement.lastChild.textContent.match(/\d+/g)); } // Update character in local storage function updateCharacter() { GM_setValue(KEY_SERVER_DOMAIN, CHARACTER); } function updateGrottoStats(){ GM_setValue(KEY_SERVER_DOMAIN+"/grotto_stats", GROTTO_STATS); } function updatePotionTimer() { var timerElement = document.querySelector("#item_cooldown2_20 > span"); if(!timerElement){ timerElement = document.querySelector("#item_cooldown2_1 > span"); } if(!timerElement){ timerElement = document.querySelector("#item_cooldown2_2 > span"); } if(!timerElement){ // couldn't find the potion timer, it means player is out of potions, or the potion is already ready if(getUseHealthPotionButtons().length>0){ CHARACTER.potionCooldownEnd = 1; // the potion is ready to use ! } else { CHARACTER.potionCooldownEnd = -1; // i put -1 so i can display later that that no more potions } updatePotionCooldownDisplay(); } else { // Get the current time and add the potion cooldown to get the end time const currentTime = pageLoadTime / 1000; // Current time in seconds const cooldownTime = timeToSeconds(timerElement.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; updatePotionCooldownDisplay(); //refresh } } function autoUseBestPotion(){ const hpPotions = getUseHealthPotionButtons(); if (hpPotions.length > 0) { hpPotions[hpPotions.length-1].click(); } else { // si plus de potion, ou si potion en cours de cooldown window.location.href = '/city/shop/potions/&page=1&premiumfilter=nonpremium'; } } function getUseHealthPotionButtons(){ // returns a table containing all (if any) buttons to use the health potions return Array.from(document.querySelectorAll("#accordion > div > table > tbody > tr > td > div > div > a")).filter(button => button.href.includes("useItem/2/1") || button.href.includes("useItem/2/2") || button.href.includes("useItem/2/20") ); } function timeToSeconds(timeStr) { const [hours, minutes, seconds] = timeStr.split(':').map(Number); return (hours * 3600) + (minutes * 60) + seconds; } function insertHealthEnergyBars() { if (document.getElementById('progressBarsTimersContainer')) { return; } let mainContainer = document.createElement('div'); mainContainer.id = 'progressBarsTimersContainer'; mainContainer.style.display = 'flex'; mainContainer.style.flexDirection = 'column'; // Stack the bars and timers vertically mainContainer.style.alignItems = 'center'; mainContainer.style.marginTop = '3px'; let progressBarsContainer = document.createElement('div'); progressBarsContainer.style.display = 'flex'; // Changed to row to place bars side by side progressBarsContainer.style.flexDirection = 'row'; // Set to row for horizontal alignment progressBarsContainer.style.alignItems = 'center'; // Align items vertically in the middle progressBarsContainer.style.paddingBottom = '8px'; progressBarsContainer.style.gap = '60px'; // Added gap between the bars // Health Bar let healthBarContainer = document.createElement('div'); healthBarContainer.style.width = '250px'; healthBarContainer.style.position = 'relative'; healthBarContainer.style.background = 'linear-gradient(to right, #1a1a1a, #333)'; healthBarContainer.style.borderImage = 'linear-gradient(to right, #ff4d4d, #b80000) 1'; healthBarContainer.style.borderRadius = '6px'; healthBarContainer.style.boxShadow = 'inset 0 10px 5px rgba(0, 0, 0, 0.5), 0 0 10px rgba(255, 77, 77, 1)'; healthBarContainer.style.overflow = 'hidden'; healthBarContainer.id = 'healthProgressBar'; let healthBar = document.createElement('div'); healthBar.style.height = '20px'; healthBar.style.width = `${(CHARACTER.health / CHARACTER.maxHealth) * 100}%`; healthBar.style.background = 'linear-gradient(to right, #ff4d4d, #b80000)'; healthBar.style.transition = 'width 0.3s ease-in-out'; healthBar.style.boxShadow = 'inset 0 10px 5px rgba(0, 0, 0, 0.5)'; 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); // Energy Bar let energyBarContainer = document.createElement('div'); energyBarContainer.style.width = '250px'; energyBarContainer.style.position = 'relative'; energyBarContainer.style.background = 'linear-gradient(to right, #1a1a1a, #333)'; energyBarContainer.style.borderImage = 'linear-gradient(to right, #4d94ff, #0000a4) 1'; energyBarContainer.style.borderRadius = '6px'; energyBarContainer.style.boxShadow = 'inset 0 10px 5px rgba(0, 0, 0, 0.5), 0 0 10px rgba(77, 148, 255, 1)'; energyBarContainer.style.overflow = 'hidden'; energyBarContainer.id = 'energyProgressBar'; let energyBar = document.createElement('div'); energyBar.style.height = '20px'; energyBar.style.width = `${(CHARACTER.energy / CHARACTER.maxEnergy) * 100}%`; energyBar.style.background = 'linear-gradient(to right, #4d94ff, #0000a4)'; energyBar.style.transition = 'width 0.3s ease-in-out'; energyBar.style.boxShadow = 'inset 0 10px 5px rgba(0, 0, 0, 0.5)'; 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); mainContainer.appendChild(progressBarsContainer); document.querySelector("#infobar > div.wrap-left.clearfix > div > div.gold").appendChild(mainContainer); } function calculateCurrentHealth(){ const regenPerSecond = CHARACTER.regenHealth / 3600; // Convert regenHealth from per hour to per second // 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 ); return updatedHealth; } // Start real-time health regeneration function startHealthRegeneration() { const regenInterval = 200; // Update every 200 ms setInterval(() => { // Update the progress bar with the calculated health updateProgressBars(calculateCurrentHealth()); }, 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 timerButton = 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 : ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerButton.textContent = `Potion : ${hours > 0 ? hours.toString().padStart(2, '0') + ':' : ''}${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerButton.className = ""; } else if (CHARACTER.potionCooldownEnd < 0) { timerButton.textContent = 'Out of potions'; // i've disabled the change of url, because if you are out, buy new, then go back to overview, it will still redirect to the shop, unless i check for this every second, which i prefer to avoid //timerButton.href = "/city/shop/potions/&page=1&premiumfilter=nonpremium"; } else { timerButton.textContent = 'Potion'; timerButton.className = "premiumButton"; } } function updateChurchCooldownTimer() { const churchCountdownElement = document.querySelector("#church_healing_countdown > span"); 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 updateChurchCooldownDisplay(); //refresh } } function updateJobCooldownTimer() { const jobCountdownElement = document.querySelector("#graveyardCount > span"); if (jobCountdownElement) { const cooldownTime = jobCountdownElement.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.jobCooldownEnd = endTime; updateCharacter(); // Save updated character data updateJobCooldownDisplay(); //refresh } } function updateJobCooldownDisplay() { const timerElement = document.getElementById('jobCooldownTimer'); const currentTime = new Date().getTime() / 1000; // Current time in seconds if (CHARACTER.jobCooldownEnd > currentTime) { const remainingTime = CHARACTER.jobCooldownEnd - 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 : ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerElement.textContent = `Job : ${hours > 0 ? hours.toString().padStart(2, '0') + ':' : ''}${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerElement.className = "premiumButton"; timerElement.style.lineHeight = "37px"; } else { timerElement.textContent = 'Graveyard'; timerElement.className = ""; } } // 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 : ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerElement.textContent = `Church : ${hours > 0 ? hours.toString().padStart(2, '0') + ':' : ''}${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; timerElement.className = ""; } else { timerElement.textContent = 'Church'; timerElement.className = "premiumButton"; } } function addAdditionnalLink() { // make the overview link open the attributes by default document.querySelectorAll('#menuHead li a')[1].href="/profile/index#tabs-2"; // make the voodoo shop less flashy document.getElementById("premium").querySelector("img").remove(); document.getElementById("premium").removeAttribute("id"); //translation of all the menu in english, so everything is the same document.querySelectorAll('#menuHead li a')[0].textContent="News"; document.querySelectorAll('#menuHead li a')[1].textContent="Overview"; document.querySelectorAll('#menuHead li a')[2].textContent="Messages"; document.querySelectorAll('#menuHead li a')[3].textContent="Hideout"; document.querySelectorAll('#menuHead li a')[4].textContent="City"; document.querySelectorAll('#menuHead li a')[5].textContent="Hunt"; document.querySelectorAll('#menuHead li a')[6].textContent="Voodoo Shop"; document.querySelectorAll('#menuHead li a')[7].textContent="Clan"; document.querySelectorAll('#menuHead li a')[8].textContent="Buddy list"; document.querySelectorAll('#menuHead li a')[9].textContent="Notepad"; document.querySelectorAll('#menuHead li a')[10].textContent="Settings"; document.querySelectorAll('#menuHead li a')[11].textContent="Forum"; document.querySelectorAll('#menuHead li a')[12].textContent="Highscore"; document.querySelectorAll('#menuHead li a')[13].textContent="Search"; document.querySelectorAll('#menuHead li a')[14].textContent="Support"; document.querySelectorAll('#menuHead li a')[15].textContent="Leave game"; // Find the