// ==UserScript==
// @name DOTV Battle Log Beautifier
// @version 1.1
// @license MIT
// @namespace https://greasyfork.org/users/1159361
// @description Beautifies and groups DOTV battle logs with collapsible UI and functional buttons, including toggle for auto-hide.
// @author Zaregoto_Gaming
// @match https://play.dragonsofthevoid.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const processedAttr = 'data-beautified';
let autoCollapse = true;
function enhanceLine(entry) {
if (entry.hasAttribute(processedAttr)) return;
let html = entry.innerHTML;
// Detect group-start entries early and hide them
if (/did ([\d,]+) damage/.test(html) || /crit ([\d,]+) damage/.test(html) || /strikes you for/.test(html) || /nimbly evaded/.test(html)) {
entry.setAttribute('data-group-start', 'true');
entry.style.display = 'none';
}
html = html.replace(/<span[^>]*?style="[^"]*?color: rgb\(33, 150, 243\)[^"]*?"[^>]*?>(.*?)<\/span>/g,
'<strong style="color: #2196F3;">⚔️ $1</strong>');
html = html.replace(/contributed ([\d,]+) damage/g,
'💥 <span style="color:red;font-weight:bold;">$1</span> damage');
html = html.replace(/did ([\d,]+) damage/g,
'💥 did <span style="color:orange;font-weight:bold;">$1</span> damage');
html = html.replace(/crit ([\d,]+) damage/g,
'❗<span style="color:#e91e63;font-weight:bold;">CRIT $1</span>');
html = html.replace(/restored ([\d,]+) of your Health/g,
'❤️ <span style="color:green;font-weight:bold;">+$1 HP</span>');
html = html.replace(/reduced the damage(.*?)took/g,
'🛡️ <em>damage mitigated</em>');
html = html.replace(/earned (\d+) experience/g,
'🧠 <span style="color:#00bcd4;">+$1 XP</span>');
html = html.replace(/([^\s]+(?: [^\s]+)*) strikes you for ([\d,]+) damage/g,
'🩸 $1 <span style="color:darkred;">attacks you for $2 damage</span>');
html = html.replace(/nimbly evaded(.*?)attack!/g,
'🌀 <span style="color:#9c27b0;">Evaded the attack!</span>');
entry.innerHTML = html;
entry.setAttribute(processedAttr, 'true');
}
function makeCollapsibleGroup(startEntry) {
const parent = startEntry.parentElement;
if (!parent || startEntry.hasAttribute('data-collapsed')) return;
const details = document.createElement('details');
details.className = 'log-group';
details.open = !autoCollapse;
const summary = document.createElement('summary');
summary.innerHTML = startEntry.innerHTML;
Object.assign(details.style, {
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: '5px',
padding: '4px',
margin: '4px 0',
background: 'rgba(255,255,255,0.03)',
});
details.appendChild(summary);
const nextSiblings = [];
let next = startEntry.nextElementSibling;
while (next && !next.hasAttribute('data-group-start')) {
const temp = next.nextElementSibling;
nextSiblings.push(next);
next = temp;
}
nextSiblings.forEach(el => {
details.appendChild(el);
});
parent.insertBefore(details, startEntry.nextSibling);
startEntry.style.display = 'none';
startEntry.setAttribute('data-collapsed', 'true');
}
function collapseAllLogs(container) {
container.querySelectorAll('.log-group').forEach(d => d.open = false);
}
function expandAllLogs(container) {
container.querySelectorAll('.log-group').forEach(d => d.open = true);
}
function addGlobalControls(container) {
const existing = container.parentElement.querySelector('.log-controls');
if (existing) existing.remove();
const controls = document.createElement('div');
controls.className = 'log-controls';
Object.assign(controls.style, {
marginBottom: '8px',
display: 'flex',
gap: '10px'
});
const collapseBtn = document.createElement('button');
collapseBtn.textContent = 'Collapse All';
collapseBtn.onclick = () => collapseAllLogs(container);
const expandBtn = document.createElement('button');
expandBtn.textContent = 'Expand All';
expandBtn.onclick = () => expandAllLogs(container);
const toggleAutoBtn = document.createElement('button');
toggleAutoBtn.textContent = `Auto-Hide: ${autoCollapse ? 'On' : 'Off'}`;
toggleAutoBtn.onclick = () => {
autoCollapse = !autoCollapse;
toggleAutoBtn.textContent = `Auto-Hide: ${autoCollapse ? 'On' : 'Off'}`;
};
[collapseBtn, expandBtn, toggleAutoBtn].forEach(btn => {
Object.assign(btn.style, {
padding: '4px 8px',
background: '#333',
color: '#fff',
border: '1px solid #666',
cursor: 'pointer',
borderRadius: '4px',
fontSize: '12px',
});
});
controls.append(collapseBtn, expandBtn, toggleAutoBtn);
container.parentElement.insertBefore(controls, container);
}
function fullScanAndGroup(container) {
const entries = [...container.querySelectorAll(':scope > div')];
addGlobalControls(container);
entries.forEach(enhanceLine);
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.hasAttribute('data-group-start')) {
makeCollapsibleGroup(entry);
}
}
}
function observeLog(container) {
if (!container) return;
const observer = new MutationObserver(mutations => {
let needsGrouping = false;
for (const mutation of mutations) {
mutation.addedNodes.forEach(node => {
if (!(node instanceof HTMLElement)) return;
node.classList.add('invisible-log');
enhanceLine(node);
needsGrouping = true;
});
}
if (needsGrouping) {
setTimeout(() => {
fullScanAndGroup(container);
container.querySelectorAll('.invisible-log').forEach(el => el.classList.remove('invisible-log'));
}, 10);
}
});
observer.observe(container, { childList: true });
}
function initializeContainer(container) {
if (!container || container.dataset.enhanced) return;
container.dataset.enhanced = 'true';
observeLog(container);
fullScanAndGroup(container);
}
const globalObserver = new MutationObserver(() => {
const logs = document.querySelectorAll('.battle-log-container');
logs.forEach(initializeContainer);
});
globalObserver.observe(document.body, { childList: true, subtree: true });
window.addEventListener('load', () => {
document.querySelectorAll('.battle-log-container').forEach(initializeContainer);
});
const style = document.createElement('style');
style.textContent = `
.battle-log-container details summary {
cursor: pointer;
list-style: revert;
}
.battle-log-container details {
border: 1px solid rgba(255,255,255,0.1);
border-radius: 5px;
padding: 4px;
margin: 4px 0;
background: rgba(255,255,255,0.03);
}
.battle-log-container details > div {
padding-left: 2.2em;
}
.invisible-log {
display: none !important;
}
`;
document.head.appendChild(style);
})();