// ==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);
}
})();