SHAM UI

Evil UI made by shamans used by shamans. - With felis help, pickle and mutant

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         SHAM UI
// @namespace    https://greasyfork.org/users/paozzh
// @match        https://hordes.io/play
// @icon         https://hordes.io/data/ui/classes/3.avif
// @grant        none
// @version      1.2.1
// @description  Evil UI made by shamans used by shamans. - With felis help, pickle and mutant
// @author       Paozzh
// ==/UserScript==

(function() {
    'use strict';
    const AUTO_CC_ICONS = ["stunBuff.avif", "deepFrozen.avif", "root.avif", "14.avif", "37.avif", "49.avif", "50.avif"];
    const MANAGED_BUFFS = {
        "25.avif": "Temp",
        "19.avif": "WC",
        "20.avif": "Crus",
        "22.avif": "Aura",
        "24.avif": "Ench",
        "18.avif": "Bleed",
        "29.avif": "Poison",
        "obeliskbuff.avif": "Obelisk Buff"
    };
    const IMPORTANT_BUFFS = {
        "11.avif": "Invig",
        "27.avif": "Pathfind",
        "31.avif": "Swift",
        "38.avif": "Dash",
        "16.avif": "Hypo",
        "extraBolt.avif": "Bolt Passive",
        "13.avif": "Mimirs",
        "17.avif": "Enrage"
    };
    const DEFAULTS = {
        isEnabled: true,
        mouseOverTarget: false,
        hpThreshold: 80,
        pulseSpeed: 0.3,
        pulseStrength: 0.9,
        borderSize: 2,
        activeCCs: { "25.avif": true, "19.avif": true, "20.avif": true, "22.avif": true, "24.avif": true, "18.avif": true, "29.avif": true, "obeliskbuff.avif": true },
        activeImportants: { "11.avif": true, "27.avif": true, "31.avif": true, "38.avif": true, "16.avif": true, "extraBolt.avif": true, "13.avif": true, "17.avif": true }
    };
    let config = JSON.parse(localStorage.getItem('shamConfig')) || DEFAULTS;

    const style = document.createElement('style');
    document.head.appendChild(style);

    function updateStyles() {
        const str = config.pulseStrength || 0.9;
        const bdr = config.borderSize || 2;
        style.innerHTML = `
            @keyframes shamRedPulse { 0% { background-color: rgba(255,0,0,${str * 0.2}); } 50% { background-color: rgba(255,0,0,${str}); } 100% { background-color: rgba(255,0,0,${str * 0.2}); } }
            @keyframes shamYellowPulse { 0% { background-color: rgba(255,255,0,${str * 0.2}); } 50% { background-color: rgba(255,255,0,${str}); } 100% { background-color: rgba(255,255,0,${str * 0.2}); } }
            div[class*="panel-black"][class*="barsInner"].hp-panic-active {
                animation: shamRedPulse ${config.pulseSpeed}s infinite ease-in-out !important;
                border: ${bdr}px solid red !important;
                box-shadow: 0 0 20px rgba(255,0,0,0.9) !important;
                background-image: none !important;
            }
            div[class*="panel-black"][class*="barsInner"].cc-active-pulse {
                animation: shamYellowPulse ${config.pulseSpeed}s infinite ease-in-out !important;
                border: ${bdr}px solid yellow !important;
                box-shadow: 0 0 20px rgba(255,255,0,0.9) !important;
                background-image: none !important;
            }
            .sham-panel {
                position: fixed;
                width: 310px;
                height: 500px;
                background: rgba(8, 4, 16, 0.98);
                backdrop-filter: blur(12px);
                border: 1px solid #3c2a5c;
                border-top: 3px solid #9d4edd;
                border-radius: 8px;
                box-shadow: 0 20px 60px rgba(0,0,0,0.95);
                color: #eee;
                font-family: 'Segoe UI', Tahoma, sans-serif;
                z-index: 1000000;
                display: none;
                flex-direction: column;
                user-select: none;
                opacity: 0;
                transform: scale(0.95);
                transition: opacity 0.25s ease, transform 0.25s ease;
            }
            .sham-panel.active {
                opacity: 1;
                transform: scale(1);
            }
            .sham-header {
                flex: 0 0 auto;
                padding: 12px 18px 10px;
                border-bottom: 1px solid #2a1b3d;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: move;
            }
            .header-left {
                display: flex;
                align-items: center;
                gap: 10px;
            }
            .header-left img {
                width: 30px;
                height: 30px;
                border-radius: 6px;
                border: 1px solid #9d4edd;
                box-shadow: 0 0 10px rgba(157, 78, 221, 0.4);
            }
            .sham-header h3 {
                margin: 0;
                color: #e0aaff;
                font-size: 20px;
                text-shadow: 0 0 12px rgba(157, 78, 221, 0.5);
                letter-spacing: 1px;
            }
            .sham-close {
                font-size: 28px;
                font-weight: bold;
                cursor: pointer;
                color: #baa0ff;
                transition: all 0.3s ease;
            }
            .sham-close:hover {
                color: #e0aaff;
                transform: scale(1.2) rotate(90deg);
            }
            .sham-content {
                flex: 1;
                padding: 14px 18px;
                display: flex;
                flex-direction: column;
                gap: 12px;
                overflow: hidden;
            }
            .sham-desc {
                font-size: 11px;
                color: #c8b6ff;
                line-height: 1.5;
                background: rgba(157, 78, 221, 0.08);
                padding: 9px;
                border-radius: 6px;
                border-left: 2px solid #9d4edd;
            }
            .sham-warning {
                font-size: 10px;
                color: #ffaaaa;
                font-weight: bold;
                text-align: center;
                background: rgba(255, 100, 100, 0.15);
                padding: 6px;
                border-radius: 6px;
                border: 1px solid #ff6666;
                margin-bottom: 8px;
                line-height: 1.3;
            }
            .sham-list {
                flex: 1;
                overflow-y: auto;
                padding-right: 5px;
            }
            .sham-list::-webkit-scrollbar { width: 6px; }
            .sham-list::-webkit-scrollbar-track { background: #1a1226; border-radius: 3px; }
            .sham-list::-webkit-scrollbar-thumb { background: #5a189a; border-radius: 3px; }
            .sham-list::-webkit-scrollbar-thumb:hover { background: #9d4edd; }
            .toggle-row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 7px 10px;
                font-size: 13px;
                color: #ddd;
                border-radius: 6px;
                transition: background 0.3s ease;
            }
            .toggle-row:hover {
                background: rgba(157, 78, 221, 0.12);
            }
            .toggle-row.enabled {
                background: rgba(157, 78, 221, 0.28);
            }
            .toggle-row.enabled .module-name {
                color: #e0aaff;
            }
            .toggle-label {
                display: flex;
                align-items: center;
                flex: 1;
            }
            .module-name {
                font-weight: 600;
            }
            .toggle-switch {
                position: relative;
                width: 38px;
                height: 20px;
            }
            .toggle-switch input { opacity: 0; width: 0; height: 0; }
            .slider {
                position: absolute;
                cursor: pointer;
                top: 0; left: 0; right: 0; bottom: 0;
                background-color: #1a1226;
                transition: .4s;
                border-radius: 20px;
                border: 1px solid #3c2a5c;
            }
            .slider:before {
                position: absolute;
                content: "";
                height: 14px;
                width: 14px;
                left: 3px;
                bottom: 3px;
                background-color: #5a189a;
                transition: .4s;
                border-radius: 50%;
            }
            input:checked + .slider { background-color: #3c096c; border-color: #9d4edd; }
            input:checked + .slider:before { transform: translateX(18px); background-color: #ffffff; box-shadow: 0 0 8px #9d4edd; }
            .sham-label {
                display: flex;
                justify-content: space-between;
                font-size: 12px;
                color: #b79ced;
                font-weight: 600;
            }
            input[type=range] {
                width: 100%;
                background: #1a1226;
                height: 5px;
                -webkit-appearance: none;
                border-radius: 8px;
                outline: none;
                margin: 7px 0;
            }
            input[type=range]::-webkit-slider-runnable-track {
                background: linear-gradient(90deg, #5a189a, #9d4edd);
                border-radius: 8px;
                height: 5px;
            }
            input[type=range]::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 15px;
                height: 15px;
                background: #ffffff;
                border: 2px solid #9d4edd;
                border-radius: 50%;
                cursor: pointer;
                margin-top: -5px;
                box-shadow: 0 0 8px rgba(157, 78, 221, 0.8);
            }
            .sham-btn {
                width: 100%;
                padding: 12px;
                font-weight: 800;
                border-radius: 6px;
                border: none;
                cursor: pointer;
                background: #9d4edd;
                color: #fff;
                margin-top: auto;
                transition: all 0.3s ease;
                text-transform: uppercase;
                letter-spacing: 1.1px;
                font-size: 13px;
            }
            .sham-btn:hover {
                background: #e0aaff;
                color: #240046;
                box-shadow: 0 0 20px rgba(157, 78, 221, 0.5);
            }
            .stagger {
                opacity: 0;
                transform: translateY(8px);
                transition: opacity 0.35s ease-out, transform 0.35s ease-out;
            }
            .stagger.visible {
                opacity: 1;
                transform: translateY(0);
            }
        `;
    }
    updateStyles();

    const generalMenu = document.createElement('div');
    generalMenu.id = 'sham-general';
    generalMenu.className = 'sham-panel';
    document.body.appendChild(generalMenu);

    const impMenu = document.createElement('div');
    impMenu.id = 'sham-imp';
    impMenu.className = 'sham-panel';
    document.body.appendChild(impMenu);

    const ccMenu = document.createElement('div');
    ccMenu.id = 'sham-cc';
    ccMenu.className = 'sham-panel';
    document.body.appendChild(ccMenu);

    function makeDraggable(panel) {
        let isDragging = false;
        let offset = { x: 0, y: 0 };
        const header = panel.querySelector('.sham-header');
        header.addEventListener('mousedown', (e) => {
            if (e.target.closest('.sham-close')) return;
            isDragging = true;
            const rect = panel.getBoundingClientRect();
            offset.x = e.clientX - rect.left;
            offset.y = e.clientY - rect.top;
        });
        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                panel.style.left = (e.clientX - offset.x) + 'px';
                panel.style.top = (e.clientY - offset.y) + 'px';
            }
        });
        document.addEventListener('mouseup', () => isDragging = false);
    }

    let impHTML = "";
    Object.keys(IMPORTANT_BUFFS).forEach(file => {
        impHTML += `<div class="toggle-row stagger">
            <div class="toggle-label">
                <span class="module-name">${IMPORTANT_BUFFS[file]}</span>
            </div>
            <label class="toggle-switch">
                <input type="checkbox" data-type="imp" data-file="${file}" ${config.activeImportants[file] ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>`;
    });

    let ccHTML = "";
    Object.keys(MANAGED_BUFFS).forEach(file => {
        ccHTML += `<div class="toggle-row stagger">
            <div class="toggle-label">
                <span class="module-name">${MANAGED_BUFFS[file]}</span>
            </div>
            <label class="toggle-switch">
                <input type="checkbox" data-type="cc" data-file="${file}" ${config.activeCCs[file] ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>`;
    });

    function renderPanels() {
        generalMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left">
                    <img src="https://hordes.io/data/ui/classes/3.avif">
                    <h3>SHAM UI</h3>
                </div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-warning stagger">CC Indicator only works if you use 1 player per party row or else it'll cause bugs.</div>
                <div class="toggle-row stagger">
                    <div class="toggle-label">
                        <span class="module-name"><strong>Enable UI</strong></span>
                    </div>
                    <label class="toggle-switch">
                        <input type="checkbox" id="master-switch" ${config.isEnabled ? 'checked' : ''}>
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="toggle-row stagger">
                    <div class="toggle-label">
                        <span class="module-name"><strong>Mouse-Over</strong></span>
                    </div>
                    <label class="toggle-switch">
                        <input type="checkbox" id="mouse-switch" ${config.mouseOverTarget ? 'checked' : ''}>
                        <span class="slider"></span>
                    </label>
                </div>

                <div class="sham-label stagger"><span>HP Threshold</span> <span id="val-hp">${config.hpThreshold}%</span></div>
                <input class="stagger" type="range" id="cfg-hp" min="10" max="100" value="${config.hpThreshold}" oninput="document.getElementById('val-hp').innerText = this.value + '%'">

                <div class="sham-label stagger"><span>Pulse Speed</span> <span id="val-speed">${config.pulseSpeed}s</span></div>
                <input class="stagger" type="range" id="cfg-speed" min="0.1" max="1.0" step="0.1" value="${config.pulseSpeed}" oninput="document.getElementById('val-speed').innerText = this.value + 's'">

                <div class="sham-label stagger"><span>Pulse Intensity</span> <span id="val-strength">${Math.round(config.pulseStrength * 100)}%</span></div>
                <input class="stagger" type="range" id="cfg-strength" min="0.1" max="1.0" step="0.05" value="${config.pulseStrength}" oninput="document.getElementById('val-strength').innerText = Math.round(this.value * 100) + '%'">

                <div class="sham-label stagger"><span>Border Size</span> <span id="val-border">${config.borderSize}px</span></div>
                <input class="stagger" type="range" id="cfg-border" min="0" max="10" step="1" value="${config.borderSize}" oninput="document.getElementById('val-border').innerText = this.value + 'px'">

                <button id="cfg-save" class="sham-btn stagger">SAVE SETTINGS</button>
            </div>
        `;

        impMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left"><h3>Important Buffs</h3></div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-desc stagger">Uncheck to permanently hide these key buffs/passives from your hotbar.</div>
                <div class="sham-list">${impHTML}</div>
            </div>
        `;

        ccMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left"><h3>Icon Manager</h3></div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-desc stagger">Uncheck to permanently hide these managed icons from your hotbar.</div>
                <div class="sham-list">${ccHTML}</div>
            </div>
        `;

        document.querySelectorAll('.sham-close').forEach(btn => btn.onclick = hideMenus);
        document.querySelectorAll('.toggle-switch input[type="checkbox"]').forEach(input => {
            const row = input.closest('.toggle-row');
            if (row) {
                const update = () => {
                    row.classList.toggle('enabled', input.checked);
                };
                update();
                input.addEventListener('change', update);
            }
        });

        document.getElementById('cfg-save').onclick = () => {
            config.isEnabled = document.getElementById('master-switch').checked;
            config.mouseOverTarget = document.getElementById('mouse-switch').checked;
            config.hpThreshold = parseInt(document.getElementById('cfg-hp').value);
            config.pulseSpeed = parseFloat(document.getElementById('cfg-speed').value);
            config.pulseStrength = parseFloat(document.getElementById('cfg-strength').value);
            config.borderSize = parseInt(document.getElementById('cfg-border').value);

            document.querySelectorAll('#sham-imp input[data-type="imp"]').forEach(cb => {
                config.activeImportants[cb.dataset.file] = cb.checked;
            });
            document.querySelectorAll('#sham-cc input[data-type="cc"]').forEach(cb => {
                config.activeCCs[cb.dataset.file] = cb.checked;
            });

            localStorage.setItem('shamConfig', JSON.stringify(config));
            updateStyles();
            hideMenus();
        };
    }

    renderPanels();
    makeDraggable(generalMenu);
    makeDraggable(impMenu);
    makeDraggable(ccMenu);

    function showMenus() {
        const panelWidth = 310;
        const gap = 15;
        const totalWidth = panelWidth * 3 + gap * 2;
        let startLeft = Math.max(10, (window.innerWidth - totalWidth) / 2);

        generalMenu.style.left = startLeft + 'px';
        impMenu.style.left = (startLeft + panelWidth + gap) + 'px';
        ccMenu.style.left = (startLeft + panelWidth * 2 + gap * 2) + 'px';

        const topPos = Math.max(20, (window.innerHeight - 500) / 2);
        [generalMenu, impMenu, ccMenu].forEach(menu => {
            menu.style.top = topPos + 'px';
            menu.style.display = 'flex';
            void menu.offsetWidth;
            menu.classList.add('active');
        });

        [generalMenu, impMenu, ccMenu].forEach(menu => {
            const items = menu.querySelectorAll('.stagger');
            items.forEach((item, index) => {
                setTimeout(() => item.classList.add('visible'), 120 + index * 45);
            });
        });
    }

    function hideMenus() {
        [generalMenu, impMenu, ccMenu].forEach(menu => {
            menu.querySelectorAll('.stagger').forEach(item => item.classList.remove('visible'));
            menu.classList.remove('active');
        });
        setTimeout(() => {
            [generalMenu, impMenu, ccMenu].forEach(menu => menu.style.display = 'none');
        }, 300);
    }

    window.addEventListener('keydown', (e) => {
        if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;

        if (e.shiftKey && e.key.toLowerCase() === 'n') {
            e.preventDefault();
            if (generalMenu.style.display === 'flex') hideMenus();
            else showMenus();
        } else if (e.key === 'Escape' && generalMenu.style.display === 'flex') {
            e.preventDefault();
            hideMenus();
        }
    });

    document.addEventListener('mouseover', (e) => {
        if (config.isEnabled && config.mouseOverTarget) {
            const t = e.target.closest('div[class*="targetable"]');
            if (t) t.click();
        }
    });

    let lastCCState = false;

    // Main pulse logic (kept exactly as in your working script, including spatial CC matching)
    setInterval(() => {
        if (!config.isEnabled) return;

        document.querySelectorAll('.hp-panic-active, .cc-active-pulse').forEach(el => {
            el.classList.remove('hp-panic-active', 'cc-active-pulse');
        });

        const allBars = Array.from(document.querySelectorAll('div[class*="panel-black"][class*="barsInner"]'));
        const ccIcons = Array.from(document.getElementsByTagName('img')).filter(img => AUTO_CC_ICONS.some(cc => img.src.includes(cc)));

        let currentCCDetected = false;

        ccIcons.forEach(icon => {
            const iconRect = icon.getBoundingClientRect();
            if (iconRect.width === 0) return;
            const iconCenter = { x: iconRect.left + iconRect.width / 2, y: iconRect.top + iconRect.height / 2 };

            let closestBar = null;
            let minDistance = Infinity;

            allBars.forEach(bar => {
                const barRect = bar.getBoundingClientRect();
                const dist = Math.hypot(iconCenter.x - (barRect.left + barRect.width / 2), iconCenter.y - (barRect.top + barRect.height / 2));
                if (dist < minDistance) {
                    minDistance = dist;
                    closestBar = bar;
                }
            });

            if (closestBar && minDistance < 250) {
                closestBar.classList.add('cc-active-pulse');
                currentCCDetected = true;
            }
        });

        allBars.forEach(bar => {
            if (bar.classList.contains('cc-active-pulse')) return;
            const bgHealth = bar.querySelector('div[class*="bghealth"]');
            if (bgHealth && bgHealth.style.width) {
                const hpWidth = parseFloat(bgHealth.style.width);
                if (hpWidth <= config.hpThreshold && hpWidth > 0) {
                    bar.classList.add('hp-panic-active');
                }
            }
        });

        if (!currentCCDetected) lastCCState = false;
    }, 150);

    // Separate fast icon deletion (as in your script for performance)
    setInterval(() => {
        if (!config.isEnabled) return;

        const allImgs = document.getElementsByTagName('img');
        for (let i = allImgs.length - 1; i >= 0; i--) {
            const img = allImgs[i];
            const srcRaw = img.src.split('/').pop() || "";
            const src = srcRaw.split('?')[0];

            const shouldHideCC = MANAGED_BUFFS[src] && config.activeCCs[src] === false;
            const shouldHideImp = IMPORTANT_BUFFS[src] && config.activeImportants[src] === false;

            if (shouldHideCC || shouldHideImp) {
                const slot = img.closest('.slot');
                if (slot) slot.remove();
                else img.remove();
            }
        }
    }, 50);
})();