// ==UserScript==
// @name Dead Frontier Tooltip Details
// @author ils94
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description Add information from the wiki in the weapons infobox with bonus calculations
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=24
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=25
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=28*
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=35
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=50
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=59
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=82*
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=84
// @match https://fairview.deadfrontier.com/onlinezombiemmo/DF3D/DF3D_InventoryPage.php?page=31*
// @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=32*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
let dpsData = {};
let userStats = {
totalDamage: 0,
attackSpeed: 0,
meleeBonuses: 0,
chainsawBonuses: 0,
pistolBonuses: 0,
rifleBonuses: 0,
shotgunBonuses: 0,
smgBonuses: 0,
machineGunBonuses: 0,
explosiveBonuses: 0
};
let isShiftPressed = false;
// Track Shift key state
document.addEventListener('keydown', (e) => {
if (e.key === 'Shift') {
isShiftPressed = true;
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'Shift') {
isShiftPressed = false;
}
});
// Load saved stats from localStorage
function loadSavedStats() {
const saved = localStorage.getItem('deadFrontierUserStats');
if (saved) {
try {
const parsed = JSON.parse(saved);
Object.assign(userStats, parsed);
} catch (e) {
console.error('[DPS] Failed to parse saved stats:', e);
}
}
}
function createInputContainer() {
// Only create container if on page=25
if (window.location.href !== 'https://fairview.deadfrontier.com/onlinezombiemmo/index.php?page=25') {
return;
}
const container = document.createElement('div');
container.style.position = 'fixed';
container.style.bottom = '10px';
container.style.left = '10px';
container.style.backgroundColor = '#1a1a1a';
container.style.padding = '15px';
container.style.border = '2px solid #00FF00';
container.style.borderRadius = '8px';
container.style.zIndex = '1000';
container.style.color = '#00FF00';
container.style.fontFamily = 'Arial, sans-serif';
container.style.fontSize = '14px';
container.style.boxShadow = '0 4px 8px rgba(0, 255, 0, 0.3)';
container.style.width = '250px';
// Add the title
const title = document.createElement('div');
title.textContent = 'Implant and Weapons Bonuses';
title.style.fontSize = '16px';
title.style.fontWeight = 'bold';
title.style.textAlign = 'center';
title.style.marginBottom = '12px';
container.appendChild(title);
const inputs = [{
label: 'Total Inflicted Damage:',
key: 'totalDamage'
},
{
label: 'Total Attack Speed:',
key: 'attackSpeed'
},
{
label: 'Melee Bonuses:',
key: 'meleeBonuses'
},
{
label: 'Chainsaw Bonuses:',
key: 'chainsawBonuses'
},
{
label: 'Pistol Bonuses:',
key: 'pistolBonuses'
},
{
label: 'Rifle Bonuses:',
key: 'rifleBonuses'
},
{
label: 'Shotgun Bonuses:',
key: 'shotgunBonuses'
},
{
label: 'SMG Bonuses:',
key: 'smgBonuses'
},
{
label: 'Machine Gun Bonuses:',
key: 'machineGunBonuses'
},
{
label: 'Explosive Bonuses:',
key: 'explosiveBonuses'
}
];
inputs.forEach(input => {
const label = document.createElement('label');
label.textContent = input.label;
label.style.display = 'flex';
label.style.alignItems = 'center';
label.style.marginBottom = '8px';
label.style.fontSize = '12px';
const inputEl = document.createElement('input');
inputEl.type = 'text';
inputEl.inputMode = 'decimal';
inputEl.value = userStats[input.key];
inputEl.style.width = '80px';
inputEl.style.marginLeft = 'auto';
inputEl.style.backgroundColor = '#2a2a2a';
inputEl.style.color = '#00FF00';
inputEl.style.border = '1px solid #00FF00';
inputEl.style.borderRadius = '4px';
inputEl.style.padding = '4px';
inputEl.style.fontSize = '12px';
inputEl.style.outline = 'none';
inputEl.addEventListener('input', () => {
let cleaned = inputEl.value.replace(/[^0-9.]/g, '');
const parts = cleaned.split('.');
if (parts.length > 2) {
cleaned = parts[0] + '.' + parts.slice(1).join('');
}
inputEl.value = cleaned;
userStats[input.key] = parseFloat(inputEl.value) || 0;
});
label.appendChild(inputEl);
container.appendChild(label);
});
const saveButton = document.createElement('button');
saveButton.textContent = 'Save';
saveButton.style.display = 'block';
saveButton.style.margin = '10px auto 0';
saveButton.style.backgroundColor = '#00FF00';
saveButton.style.color = '#000';
saveButton.style.border = 'none';
saveButton.style.borderRadius = '5px';
saveButton.style.padding = '15px 15px';
saveButton.style.cursor = 'pointer';
saveButton.style.fontSize = '16px';
saveButton.style.fontWeight = 'bold';
saveButton.style.transition = 'background-color 0.2s, transform 0.1s';
saveButton.style.minWidth = '50px';
saveButton.style.minHeight = '25px';
saveButton.addEventListener('mouseover', () => {
saveButton.style.backgroundColor = '#00CC00';
});
saveButton.addEventListener('mouseout', () => {
saveButton.style.backgroundColor = '#00FF00';
});
saveButton.addEventListener('click', () => {
localStorage.setItem('deadFrontierUserStats', JSON.stringify(userStats));
injectDPSIntoStaticBoxes();
console.log('[DPS] User stats saved:', userStats);
});
container.appendChild(saveButton);
document.body.appendChild(container);
}
function loadDPS() {
if (typeof window.weaponData === 'undefined') {
console.error('[DPS] External weapon data not loaded');
return;
}
window.weaponData.weapons.forEach(weapon => {
const key = weapon.name.toLowerCase();
dpsData[key] = {
name: weapon.name,
category: weapon.category,
dps: weapon.stats.DPS || {},
dph: weapon.stats.DPH || {},
hps: weapon.stats.HPS || {}
};
});
console.log('[DPS] Loaded', Object.keys(dpsData).length, 'entries from external JSON');
loadSavedStats(); // Load saved stats for all pages
createInputContainer(); // Only creates container on page=25
startWatcher();
injectDPSIntoStaticBoxes();
}
function parseSumExpression(expr) {
if (!expr || typeof expr !== 'string') return {
terms: [],
total: null
};
// Match three-term sums like "96 + 32 + 32 = 160" or "12.12 + 28.28 + 60.6 = 101"
let match = expr.match(/([\d.]+)\s*\+\s*([\d.]+)\s*\+\s*([\d.]+)\s*=\s*([\d.]+)/);
if (match) {
const terms = [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3])];
const total = parseFloat(match[4]);
return {
terms,
total
};
}
// Match two-term sums like "100 + 50 = 150"
match = expr.match(/([\d.]+)\s*\+\s*([\d.]+)\s*=\s*([\d.]+)/);
if (match) {
const terms = [parseFloat(match[1]), parseFloat(match[2])];
const total = parseFloat(match[3]);
return {
terms,
total
};
}
// Fallback: try to parse as a single number (e.g., "50")
const singleNumber = parseFloat(expr);
if (!isNaN(singleNumber)) {
return {
terms: [singleNumber],
total: singleNumber
};
}
return {
terms: [],
total: null
};
}
function calculateBonuses(entry) {
const category = entry.category.toLowerCase();
let masteryBonus = 0;
if (category.includes('melee')) masteryBonus = userStats.meleeBonuses;
if (category.includes('chainsaw')) masteryBonus = userStats.chainsawBonuses;
if (category.includes('pistol')) masteryBonus = userStats.pistolBonuses;
if (category.includes('rifle')) masteryBonus = userStats.rifleBonuses;
if (category.includes('shotgun')) masteryBonus = userStats.shotgunBonuses;
if (category.includes('smg')) masteryBonus = userStats.smgBonuses;
if (category.includes('machine gun')) masteryBonus = userStats.machineGunBonuses;
if (category.includes('grenade launchers') || category.includes('flamethrowers')) masteryBonus = userStats.explosiveBonuses;
const damageMultiplier = 1 + (userStats.totalDamage + masteryBonus) / 100;
const speedMultiplier = 1 + userStats.attackSpeed / 100;
// Parse DPH total and critical
const dphTotalParsed = parseSumExpression(entry.dph.total);
const dphCriticalParsed = parseSumExpression(entry.dph.critical);
return {
dps: {
real: entry.dps.real ? (entry.dps.real * damageMultiplier * speedMultiplier).toFixed(2) : 'N/A',
theoretical: entry.dps.theoretical ? (entry.dps.theoretical * damageMultiplier * speedMultiplier).toFixed(2) : 'N/A',
critical: entry.dps.critical ? (entry.dps.critical * damageMultiplier * speedMultiplier).toFixed(2) : 'N/A',
theoretical_critical: entry.dps.theoretical_critical ? (entry.dps.theoretical_critical * damageMultiplier * speedMultiplier).toFixed(2) : 'N/A'
},
dph: {
total: dphTotalParsed.total !== null ? (dphTotalParsed.total * damageMultiplier).toFixed(2) : 'N/A',
critical: dphCriticalParsed.total !== null ? (dphCriticalParsed.total * damageMultiplier).toFixed(2) : 'N/A',
totalTerms: dphTotalParsed.terms.map(term => (term * damageMultiplier).toFixed(2)),
criticalTerms: dphCriticalParsed.terms.map(term => (term * damageMultiplier).toFixed(2))
},
hps: {
real: entry.hps.real ? (entry.hps.real * speedMultiplier).toFixed(2) : 'N/A',
theoretical: entry.hps.theoretical ? (entry.hps.theoretical * speedMultiplier).toFixed(2) : 'N/A'
}
};
}
function generateStatsHTML(entry) {
const bonuses = calculateBonuses(entry);
// Format DPH display based on whether it's a multiplier format or sum format
const dphTotalParsed = parseSumExpression(entry.dph.total);
const dphDisplay = entry.dph.base && entry.dph.multiplier ?
`${entry.dph.base} x ${entry.dph.multiplier} = ${entry.dph.total}` :
(dphTotalParsed.terms.length > 1 ? entry.dph.total : (entry.dph.total || 'N/A'));
const dphBonusDisplay = entry.dph.base && entry.dph.multiplier ?
`${(entry.dph.base * (1 + (userStats.totalDamage + (userStats[entry.category.toLowerCase().replace(' ', '') + 'Bonuses'] || 0)) / 100)).toFixed(2)} x ${entry.dph.multiplier} = ${bonuses.dph.total}` :
(dphTotalParsed.terms.length > 1 ? `${bonuses.dph.totalTerms.join(' + ')} = ${bonuses.dph.total}` : bonuses.dph.total);
// Format critical DPH display
const dphCriticalParsed = parseSumExpression(entry.dph.critical);
const dphCriticalDisplay = dphCriticalParsed.terms.length > 1 ? entry.dph.critical : (entry.dph.critical || 'N/A');
const dphCriticalBonusDisplay = dphCriticalParsed.terms.length > 1 ? `${bonuses.dph.criticalTerms.join(' + ')} = ${bonuses.dph.critical}` : bonuses.dph.critical;
// Determine labels based on weapon category
const isExplosive = entry.category === 'Grenade Launchers' || entry.category === 'Flamethrowers';
const dpsCriticalLabel = isExplosive ? 'Avg. DPS AoE' : 'Avg. DPS Critical';
const dpsCriticalTheoreticalLabel = isExplosive ? 'Avg. DPS AoE Theoretical' : 'Avg. DPS Critical Theoretical';
const dphCriticalLabel = isExplosive ? 'Damage per Hit AoE' : 'Damage per Hit Critical';
let statsHTML = `
<strong>Base Stats:</strong><br>
<br>Avg. DPS: ${entry.dps.real || 'N/A'}<br>
Avg. DPS Theoretical: ${entry.dps.theoretical || 'N/A'}<br>
${dpsCriticalLabel}: ${entry.dps.critical || 'N/A'}<br>
${dpsCriticalTheoreticalLabel}: ${entry.dps.theoretical_critical || 'N/A'}<br>
Damage per Hit: ${dphDisplay}<br>
${dphCriticalLabel}: ${dphCriticalDisplay}<br>
Hit(s) per Second: ${entry.hps.real || 'N/A'}<br>
Hit(s) per Second Theoretical: ${entry.hps.theoretical || 'N/A'}<br>
<br><strong>With Bonuses:</strong><br>
<br>Avg. DPS: ${bonuses.dps.real}<br>
Avg. DPS Theoretical: ${bonuses.dps.theoretical}<br>
${dpsCriticalLabel}: ${bonuses.dps.critical}<br>
${dpsCriticalTheoreticalLabel}: ${bonuses.dps.theoretical_critical}<br>
Damage per Hit: ${dphBonusDisplay}<br>
${dphCriticalLabel}: ${dphCriticalBonusDisplay}<br>
Hit(s) per Second: ${bonuses.hps.real}<br>
Hit(s) per Second Theoretical: ${bonuses.hps.theoretical}<br>
`;
return statsHTML;
}
function startWatcher() {
let tooltipWindow = null;
setInterval(() => {
const box = document.getElementById('infoBox');
if (!box || box.style.visibility === 'hidden') {
if (tooltipWindow) {
tooltipWindow.remove();
tooltipWindow = null;
}
return;
}
if (!isShiftPressed) {
if (tooltipWindow) {
tooltipWindow.remove();
tooltipWindow = null;
}
return;
}
const nameEl = box.querySelector('.itemName');
if (!nameEl) {
if (tooltipWindow) {
tooltipWindow.remove();
tooltipWindow = null;
}
return;
}
const weapon = nameEl.textContent.trim();
const key = weapon.toLowerCase();
const entry = dpsData[key];
if (!entry) {
console.log(`[DPS] ✗ ${weapon} (hover, no exact match)`);
if (tooltipWindow) {
tooltipWindow.remove();
tooltipWindow = null;
}
return;
}
if (!tooltipWindow) {
tooltipWindow = document.createElement('div');
tooltipWindow.className = 'dpsTooltip';
tooltipWindow.style.position = 'absolute';
tooltipWindow.style.backgroundColor = '#1a1a1a';
tooltipWindow.style.border = '1px solid #00FF00';
tooltipWindow.style.padding = '10px';
tooltipWindow.style.color = '#00FF00';
tooltipWindow.style.fontSize = '12px';
tooltipWindow.style.zIndex = '1001';
tooltipWindow.style.borderRadius = '4px';
tooltipWindow.style.boxShadow = '0 2px 4px rgba(0, 255, 0, 0.3)';
document.body.appendChild(tooltipWindow);
}
const boxRect = box.getBoundingClientRect();
tooltipWindow.style.left = `${boxRect.right + 10}px`;
tooltipWindow.style.top = `${boxRect.top}px`;
tooltipWindow.innerHTML = generateStatsHTML(entry);
console.log(`[DPS] ✔ ${weapon} (hover)`);
}, 100);
}
function injectDPSIntoStaticBoxes() {
const staticBoxes = document.querySelectorAll('.itemName');
staticBoxes.forEach(nameEl => {
const parent = nameEl.parentElement;
if (!parent) return;
const existing = parent.querySelector('.dpsInjected');
if (existing) existing.remove();
const weapon = nameEl.textContent.trim();
const key = weapon.toLowerCase();
const entry = dpsData[key];
if (!entry) {
console.log(`[DPS] ✗ ${weapon} (static, no exact match)`);
return;
}
const statsDiv = document.createElement('div');
statsDiv.className = 'itemData dpsInjected';
statsDiv.style.color = '#00FF00';
statsDiv.style.fontSize = '12px';
statsDiv.innerHTML = generateStatsHTML(entry);
parent.appendChild(statsDiv);
console.log(`[DPS] ✔ ${weapon} (static)`);
});
}
function loadExternalScript() {
const script = document.createElement('script');
script.src = 'dead_frontier_weapons.js';
script.onload = () => {
console.log('[DPS] External JSON script loaded');
loadDPS();
};
script.onerror = () => {
console.error('[DPS] Failed to load external JSON script');
};
document.head.appendChild(script);
}
loadExternalScript();
})();