Gats.io Aimbot & ESP v17.1.0 (v10.33 Logic Core)

Aimbot prediction logic restored from stable v14.10 (v10.33 Core). Features a new Drag & Drop UI for Auto Skill Upgrade.

// ==UserScript==
// @name         Gats.io Aimbot & ESP v17.1.0 (v10.33 Logic Core)
// @namespace    discord @zeroarcop
// @version      17.1.0
// @description  Aimbot prediction logic restored from stable v14.10 (v10.33 Core). Features a new Drag & Drop UI for Auto Skill Upgrade.
// @author       
// @match        https://gats.io/
// @grant        GM_addStyle
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Global Constants & Logger ---
    const SETTINGS_KEY = 'gats_mod_settings_v17_00'; // Keep key for settings compatibility
    const LOG_PREFIX_MOD = "[GatsModV17.1]";

    function modLog(message, isError = false) {
        const finalMessage = `${LOG_PREFIX_MOD} ${message}`;
        if (isError) console.error(finalMessage);
        else console.log(finalMessage);
    }

    // --- Core Helper Functions ---
    function getDistance(p1, p2) {
        if (!p1 || !p2) return Infinity;
        return Math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2);
    }

    // --- GUI Classes (Based on v17, adapted for v17.1 features) ---
    class ColorCustomizerGUI {
        constructor() {
            this.container = document.createElement('div');
            this.container.id = 'gemini-gats-color-gui-v17';
            this.container.style.display = 'none';
            document.body.appendChild(this.container);
            this.applyStyles();
            const head = document.createElement('h4');
            head.innerText = 'ESP Color Settings';
            this.container.appendChild(head);
            this.makeDraggable(head, this.container);
            this.addColorPicker('Enemy Box/Line', 'enemyEspColor');
            this.addColorPicker('Low HP Enemy Box/Line', 'lowHpEnemyEspColor');
            this.addColorPicker('Teammate Box/Line', 'teammateEspColor');
            this.addColorPicker('Cloaked Enemy Text', 'cloakedTextColor');
            this.addColorPicker('Enemy Name', 'enemyNameColor');
            this.addColorPicker('Teammate Name', 'teammateNameColor');
            this.addColorPicker('HP Bar (High)', 'hpBarHighColor');
            this.addColorPicker('HP Bar (Medium)', 'hpBarMediumColor');
            this.addColorPicker('HP Bar (Low)', 'hpBarLowColor');
            this.addColorPicker('Facing Line', 'facingLineColor');
            this.addColorPicker('Bullet Dot', 'bulletDotColor');
            this.addColorPicker('Aimbot Target Line', 'aimbotTargetLineColor');
            const closeBtn = this.addButton('Close Colors (0)', () => { this.container.style.display = 'none'; }, this.container, 'custom-btn-color-gui');
            closeBtn.style.marginTop = '15px';
        }
        applyStyles() { GM_addStyle(`#${this.container.id}{position:fixed;left:calc(20px + 950px + 10px);top:70px;background-color:var(--main-bg,rgba(18,18,18,0.97));color:var(--text-color-light,#fff);padding:12px;border-radius:6px;font-family:"Segoe UI",Arial,sans-serif;font-size:12px;z-index:100001;border:2px solid var(--accent-border,#b00000);box-shadow:0 3px 10px rgba(0,0,0,.5);width:280px;max-height:calc(100vh - 100px);overflow-y:auto;user-select:none}#${this.container.id} h4{text-align:center;color:var(--accent-color,#f00);font-weight:700;font-size:14px;margin-top:0;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--accent-border,#b00000);cursor:move}#${this.container.id} .color-picker-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;padding:3px}#${this.container.id} .color-picker-row label{color:var(--text-color-dim,#aaa);font-size:11.5px;margin-right:8px}#${this.container.id} input[type=color]{border:1px solid var(--accent-border,#b00000);border-radius:4px;width:70px;height:28px;cursor:pointer;background-color:var(--secondary-bg,#1e1e1e);padding:2px}#${this.container.id} button.custom-btn-color-gui{background-color:var(--btn-profile-bg,#2d2d2d);color:var(--btn-profile-text,#e0e0e0);border:1px solid var(--btn-profile-border,#f00);padding:6px 10px;display:block;width:100%;border-radius:3px;cursor:pointer;font-weight:500;font-size:12px}#${this.container.id} button.custom-btn-color-gui:hover{filter:brightness(var(--hover-brightness,120%))}#${this.container.id}::-webkit-scrollbar{width:8px}#${this.container.id}::-webkit-scrollbar-track{background:var(--scrollbar-track,#2d2d2d);border-radius:4px}#${this.container.id}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb,#aa0000);border-radius:4px}#${this.container.id}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover,#ff3333)}`); }
        makeDraggable(dragHandle, draggableElement) { let offsetX, offsetY, isDragging = false; const onMouseMove = (ev) => { if (!isDragging) return; draggableElement.style.left = (ev.clientX - offsetX) + 'px'; draggableElement.style.top = (ev.clientY - offsetY) + 'px'; }; const onMouseUp = () => { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; dragHandle.addEventListener('mousedown', (e) => { if (e.target.closest('button, input, select, a')) return; isDragging = true; offsetX = e.clientX - draggableElement.offsetLeft; offsetY = e.clientY - draggableElement.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); }); }
        addButton(label, onClick, parent, className = 'custom-btn') { const btn = document.createElement('button'); btn.innerText = label; btn.className = className; btn.onclick = onClick; parent.appendChild(btn); return btn; }
        addColorPicker(label, settingKey) { const row = document.createElement('div'); row.className = 'color-picker-row'; const lbl = document.createElement('label'); lbl.htmlFor = `${settingKey}-color-v17`; lbl.innerText = label + ":"; row.appendChild(lbl); const picker = document.createElement('input'); picker.type = 'color'; picker.id = `${settingKey}-color-v17`; picker.value = (GatsModCore.SETTINGS?.espColors?.[settingKey]) || '#FFFFFF'; picker.oninput = () => { if (GatsModCore.SETTINGS?.espColors) GatsModCore.SETTINGS.espColors[settingKey] = picker.value; }; picker.onchange = () => { if (GatsModCore.SETTINGS?.espColors) { GatsModCore.SETTINGS.espColors[settingKey] = picker.value; GatsModCore.saveSettings?.(); } }; row.appendChild(picker); this.container.appendChild(row); }
    }

    class SimpleGUI {
        constructor() {
            this.presetEditButtons = [];
            this.container = document.createElement('div');
            this.container.id = 'gemini-gats-gui-v17';
            this.container.style.display = 'none';
            document.body.appendChild(this.container);
            const mainContentWrapper = document.createElement('div'); mainContentWrapper.id = 'gui-main-content-wrapper'; this.container.appendChild(mainContentWrapper);
            const guiHead = document.createElement('h3'); guiHead.innerText = 'Gats.io Mod v17.1.0 (10.33 Core)'; mainContentWrapper.appendChild(guiHead);
            this.makeDraggable(guiHead, this.container);
            this.statusDisplay = document.createElement('div'); this.statusDisplay.id = 'gui-status-bar'; mainContentWrapper.appendChild(this.statusDisplay);
            const hotkeyInfo = document.createElement('p'); hotkeyInfo.id = 'gui-hotkey-info'; hotkeyInfo.innerHTML = `F: ESP | G: Aimbot | N: Spinbot | 0: GUI(s) | 1-9: Preset Chat`; mainContentWrapper.appendChild(hotkeyInfo);
            const topBarWrapper = document.createElement('div'); topBarWrapper.id = 'gui-top-bar-wrapper'; mainContentWrapper.appendChild(topBarWrapper);
            this.addSearchBox(topBarWrapper); this.addProfileManager(topBarWrapper);
            const mainTogglesWrapper = document.createElement('div'); mainTogglesWrapper.id = 'gui-main-toggles-wrapper'; mainContentWrapper.appendChild(mainTogglesWrapper);
            this.addCheckbox('ESP Enabled', 'espEnabled', mainTogglesWrapper, 'Toggle all visual assistance features.');
            this.addCheckbox('Aimbot Enabled', 'aimbotEnabled', mainTogglesWrapper, 'Enable player-targeting aimbot.');
            this.addCheckbox('Spinbot Enabled', 'spinbotEnabled', mainTogglesWrapper, 'Enable rapid aim spinning (overridden by click/Aimbot).');
            this.addCheckbox('Auto Skill Upgrade', 'autoSkillUpgrade', mainTogglesWrapper, 'Automatically select skills based on priority list.');
            const columnsWrapper = document.createElement('div'); columnsWrapper.id = 'gui-columns-wrapper'; mainContentWrapper.appendChild(columnsWrapper);
            this.column1 = document.createElement('div'); this.column1.className = 'gui-column'; columnsWrapper.appendChild(this.column1);
            this.column2 = document.createElement('div'); this.column2.className = 'gui-column'; columnsWrapper.appendChild(this.column2);
            this.column3 = document.createElement('div'); this.column3.className = 'gui-column'; columnsWrapper.appendChild(this.column3);
        }
        makeDraggable(dragHandle, draggableElement) { let offsetX, offsetY, isDragging = false; const onMouseMove = (ev) => { if (!isDragging) return; draggableElement.style.left = (ev.clientX - offsetX) + 'px'; draggableElement.style.top = (ev.clientY - offsetY) + 'px'; }; const onMouseUp = () => { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; dragHandle.addEventListener('mousedown', (e) => { if (e.target.closest('button, input, select, a')) return; isDragging = true; offsetX = e.clientX - draggableElement.offsetLeft; offsetY = e.clientY - draggableElement.offsetTop; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); }); }
        addCheckbox(label, settingKey, parent, tooltipText = null) { const div = document.createElement('div'); if (parent.id === 'gui-main-toggles-wrapper') { div.style.cssText = 'display: flex; flex-direction: column; align-items: center; padding: 2px 5px; margin: 0 5px; min-width: 90px; text-align: center;'; } else { div.style.cssText = 'display: flex; align-items: center; justify-content: space-between; padding: 3px 0;'; } div.dataset.settingName = label.toLowerCase().replace(/\s+/g, '-'); const lbl = document.createElement('label'); lbl.htmlFor = `${settingKey}-v17`; lbl.innerText = label; if (parent.id !== 'gui-main-toggles-wrapper') { lbl.style.flexGrow = '1'; } else { lbl.style.marginBottom = '2px'; } const cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = `${settingKey}-v17`; cb.onchange = () => { if (GatsModCore.SETTINGS) { GatsModCore.SETTINGS[settingKey] = cb.checked; if (settingKey === 'followBotEnabled' && !cb.checked) GatsModCore.stopFollowingPlayer?.(); if (settingKey === 'chatScrollEnabled' && !cb.checked) GatsModCore.stopChatScroll?.(); GatsModCore.saveSettings?.(); gatsModInstance?.simpleGui?.updateStatusDisplay?.(); } }; div.appendChild(lbl); if (tooltipText) this.addTooltip(lbl, tooltipText); div.appendChild(cb); parent.appendChild(div); return div; }
        addCollapsibleSection(title, parent, className = '') { const details = document.createElement('details'); if (className) details.className = className; details.dataset.settingName = title.toLowerCase().replace(/\s+/g, '-'); details.open = false; const summary = document.createElement('summary'); summary.innerText = title; details.appendChild(summary); const content = document.createElement('div'); details.appendChild(content); parent.appendChild(details); return content; }
        addSliderInput(label, settingKey, opts, objToUpdate, parent, tooltipText = null) { const wrapper = document.createElement('div'); wrapper.className = 'settings-group-item'; wrapper.dataset.settingName = label.toLowerCase().replace(/\s+/g, '-'); const itemContainer = document.createElement('div'); itemContainer.style.cssText = 'display: flex; flex-direction: column; margin-bottom: 5px;'; const labelContainer = document.createElement('div'); labelContainer.style.display = 'flex'; labelContainer.style.alignItems = 'center'; const labelElement = document.createElement('label'); labelElement.htmlFor = `${settingKey}-slider-v17`; labelElement.innerText = label; labelElement.style.marginBottom = '3px'; labelContainer.appendChild(labelElement); if (tooltipText) this.addTooltip(labelContainer, tooltipText); itemContainer.appendChild(labelContainer); const controlsContainer = document.createElement('div'); controlsContainer.style.cssText = 'display: flex; align-items: center; width: 100%;'; const slider = document.createElement('input'); slider.type = 'range'; slider.id = `${settingKey}-slider-v17`; slider.min = opts.min; slider.max = opts.max; slider.step = opts.step; slider.value = objToUpdate[settingKey] ?? opts.defaultVal ?? opts.min; controlsContainer.appendChild(slider); const valueDisplay = document.createElement('input'); valueDisplay.type = 'number'; valueDisplay.className = 'value-display'; valueDisplay.style.width = '55px'; valueDisplay.min = opts.min; valueDisplay.max = opts.max; valueDisplay.step = opts.step; valueDisplay.value = slider.value; controlsContainer.appendChild(valueDisplay); const updateValue = (newValue, fromSlider = false) => { let numVal = parseFloat(newValue); if (isNaN(numVal)) numVal = opts.defaultVal ?? parseFloat(opts.min); numVal = Math.max(parseFloat(opts.min), Math.min(parseFloat(opts.max), numVal)); const decimals = opts.step.toString().includes('.') ? opts.step.toString().split('.')[1].length : 0; const fixedVal = numVal.toFixed(decimals); if (fromSlider) valueDisplay.value = fixedVal; else slider.value = fixedVal; objToUpdate[settingKey] = parseFloat(fixedVal); GatsModCore.saveSettings?.(); }; slider.oninput = (e) => updateValue(e.target.value, true); valueDisplay.onchange = (e) => updateValue(e.target.value, false); valueDisplay.onfocus = () => { if (GatsModCore) GatsModCore.isInputActive = true; }; valueDisplay.onblur = () => { if (GatsModCore) GatsModCore.isInputActive = false; updateValue(valueDisplay.value, false); }; itemContainer.appendChild(controlsContainer); wrapper.appendChild(itemContainer); parent.appendChild(wrapper); return wrapper; }
        addTextInput(label, settingKey, objToUpdate, parent, onSaveCallback = null, useSaveButton = false, tooltipText = null) { const wrapper = document.createElement('div'); wrapper.className = 'settings-group-item'; wrapper.dataset.settingName = label.toLowerCase().replace(/\s+/g, '-'); const labelContainer = document.createElement('div'); labelContainer.style.display = 'flex'; labelContainer.style.alignItems = 'center'; const lbl = document.createElement('label'); lbl.htmlFor = `${settingKey}-text-v17`; lbl.innerText = label; lbl.style.display = 'block'; lbl.style.marginBottom = '3px'; labelContainer.appendChild(lbl); if (tooltipText) this.addTooltip(labelContainer, tooltipText); wrapper.appendChild(labelContainer); const input = document.createElement('input'); input.type = 'text'; input.id = `${settingKey}-text-v17`; input.value = objToUpdate[settingKey] || ""; input.className = 'general-text-input'; input.style.width = 'calc(100% - 0px)'; input.onfocus = () => { if (GatsModCore) GatsModCore.isInputActive = true; }; input.onblur = () => { if (GatsModCore) GatsModCore.isInputActive = false; if (!useSaveButton) { objToUpdate[settingKey] = input.value; GatsModCore.saveSettings?.(); onSaveCallback?.(input.value); } }; wrapper.appendChild(input); if (useSaveButton) { this.addButton("Save", () => { objToUpdate[settingKey] = input.value; GatsModCore.saveSettings?.(); onSaveCallback?.(input.value); if (this.currentScrollingTextDisplay && settingKey === 'chatScrollText') this.updateScrollingTextDisplay(input.value); const originalPlaceholder = input.placeholder; input.placeholder = "Saved!"; setTimeout(() => { input.placeholder = originalPlaceholder; }, 1200); }, wrapper, 'action-btn-small'); } parent.appendChild(wrapper); return wrapper; }
        addButton(label, onClickAction, parent, className = 'action-btn') { const button = document.createElement('button'); button.innerText = label; button.className = className; button.onclick = onClickAction; parent.appendChild(button); return button; }
        addTooltip(parentLabelContainer, text) { const tooltipTrigger = document.createElement('span'); tooltipTrigger.className = 'tooltip-trigger'; tooltipTrigger.innerText = '?'; const tooltipTextElement = document.createElement('span'); tooltipTextElement.className = 'tooltip-text'; tooltipTextElement.innerText = text; tooltipTrigger.appendChild(tooltipTextElement); parentLabelContainer.appendChild(tooltipTrigger); }
        applyStyles() { GM_addStyle(`:root{--main-bg:rgba(18,18,18,0.97);--secondary-bg:#1e1e1e;--border-color:#f00;--text-color:#fff;--text-color-light:#fff;--text-color-dim:#aaa;--accent-color:#f00;--accent-border:#b00000;--hover-brightness:130%;--input-accent:#f00;--status-on:#0f0;--status-off:#f00;--status-neutral:#aaa;--tooltip-bg:#101010;--tooltip-text:#fff;--tooltip-border:var(--accent-color);--btn-action-bg:#d00000;--btn-action-border:#a00000;--btn-profile-bg:#2d2d2d;--btn-profile-text:#e0e0e0;--btn-profile-border:var(--accent-color);--btn-alt-bg:#f0f0f0;--btn-alt-text:#1a1a1a;--btn-alt-border:#aaa;--modal-bg:rgba(0,0,0,0.8);--modal-content-bg:#1a1a1a;--modal-content-border:var(--accent-color);--scrollbar-track:#2d2d2d;--scrollbar-thumb:#aa0000;--scrollbar-thumb-hover:#ff3333;--skill-list-bg:rgba(0,0,0,0.2);--skill-item-bg:var(--secondary-bg);--skill-item-border:var(--accent-border);--skill-item-hover-bg:var(--accent-color)}#${this.container.id}{position:fixed;left:20px;top:70px;background-color:var(--main-bg);color:var(--text-color);padding:10px;border-radius:6px;font-family:"Segoe UI",Arial,sans-serif;font-size:12.5px;z-index:100002;border:2px solid var(--accent-color);box-shadow:0 5px 20px rgba(255,0,0,.4);width:950px;max-height:calc(100vh - 90px);overflow-y:auto;user-select:none}#${this.container.id} #gui-main-content-wrapper{display:flex;flex-direction:column}#${this.container.id} h3{margin:0 0 8px;text-align:center;border-bottom:1px solid var(--accent-border);padding-bottom:10px;color:var(--accent-color);font-weight:700;cursor:move;font-size:16px;text-shadow:0 0 5px var(--accent-color)}#${this.container.id} #gui-status-bar{background-color:rgba(10,10,10,0.85);color:#fff;padding:6px 12px;margin-bottom:10px;text-align:center;font-size:12.5px;font-weight:700;border-radius:4px;border:1px solid var(--accent-border)}#${this.container.id} #gui-status-bar .status-on{color:var(--status-on);font-weight:700}#${this.container.id} #gui-status-bar .status-off{color:var(--status-off);font-weight:700}#${this.container.id} #gui-status-bar .status-neutral{color:var(--status-neutral)}#${this.container.id} #gui-hotkey-info{font-size:11px;text-align:center;margin:2px 0 10px;color:var(--text-color-dim)}#${this.container.id} #gui-top-bar-wrapper{display:flex;gap:10px;margin-bottom:10px;border-bottom:1px solid var(--accent-border);padding-bottom:10px}#${this.container.id} #settings-search-box{flex-grow:1;background-color:var(--secondary-bg);color:var(--text-color-light);border:1px solid var(--accent-border);border-radius:3px;padding:5px 8px}#${this.container.id} #profile-manager{display:flex;gap:5px;align-items:center}#${this.container.id} #profile-manager button,#${this.container.id} #profile-manager input,#${this.container.id} #profile-manager select{font-size:11px;padding:4px;background-color:var(--btn-profile-bg);color:var(--btn-profile-text);border:1px solid var(--btn-profile-border);border-radius:3px}#${this.container.id} #profile-manager button{cursor:pointer;background-color:var(--accent-color);border-color:var(--accent-border);color:var(--text-color-light)}#${this.container.id} #profile-manager button:hover{filter:brightness(var(--hover-brightness))}#${this.container.id} #gui-main-toggles-wrapper{display:flex;flex-wrap:wrap;justify-content:space-around;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--accent-border)}#${this.container.id} #gui-main-toggles-wrapper>div{display:flex;flex-direction:column;align-items:center;padding:2px 5px;margin:0 5px;min-width:90px;text-align:center}#${this.container.id} #gui-main-toggles-wrapper label{color:var(--text-color-light);font-size:11.5px;margin-bottom:2px}#${this.container.id} #gui-main-toggles-wrapper input[type=checkbox]{margin-top:1px;accent-color:var(--input-accent)}#${this.container.id} #gui-columns-wrapper{display:flex;flex-direction:row;justify-content:space-between;gap:10px}#${this.container.id} .gui-column{width:calc(33.33% - 7px);display:flex;flex-direction:column;gap:5px}#${this.container.id} details{border:1px solid var(--border-color);border-radius:4px;padding:0;margin:0 0 8px}#${this.container.id} details{background-color:rgba(30,30,30,0.75)}#${this.container.id} summary{cursor:pointer;outline:0;font-weight:600;color:var(--accent-color);padding:6px 8px;font-size:13px;border-radius:3px 3px 0 0;transition:background-color .2s;border-bottom:1px solid transparent}#${this.container.id} details[open]>summary{border-bottom:1px solid var(--accent-border);background-color:rgba(255,0,0,0.05)}#${this.container.id} details>div{padding:10px 8px 8px}#${this.container.id} .settings-group-item{margin-bottom:8px}#${this.container.id} .settings-group-item label{color:var(--text-color-light);margin-left:0;flex-shrink:0;display:block;min-width:100px;font-size:12px;margin-bottom:3px}#${this.container.id} .settings-group-item div[style*="display: flex; align-items: center; justify-content: space-between;"] label{display:inline-block;flex-grow:1;margin-bottom:0}#${this.container.id} .settings-group-item input[type=checkbox]{accent-color:var(--input-accent);border:1px solid var(--accent-border);vertical-align:middle;margin-left:5px}#${this.container.id} input[type=number].value-display{width:55px;background-color:var(--secondary-bg);color:var(--text-color);border:1px solid var(--accent-border);border-radius:3px;padding:4px 5px;text-align:right;font-family:"Segoe UI",Arial,sans-serif;margin:0 4px;font-size:11.5px}#${this.container.id} input[type=range]{flex-grow:1;margin:0 4px;accent-color:var(--input-accent);height:22px}#${this.container.id} .settings-group-item div[style*="display: flex; align-items: center; width: 100%;"]{height:26px}#${this.container.id} input[type=text].general-text-input,#${this.container.id} input[type=text][id^=aimbotExcludeInput-text-v17],#${this.container.id} input[type=text][id^=obstacleEspTypes-text-v17]{width:calc(100% - 0px);box-sizing:border-box;background-color:var(--secondary-bg);color:var(--text-color-light);border:1px solid var(--accent-border);border-radius:3px;padding:5px;margin-bottom:5px}#${this.container.id} button.action-btn,#${this.container.id} button.custom-btn{background-color:var(--btn-action-bg);color:#fff;border:1px solid var(--btn-action-border);margin-top:10px;padding:7px 10px;display:block;width:100%;box-sizing:border-box;border-radius:3px;cursor:pointer;font-weight:500;font-size:12.5px;text-transform:uppercase}#${this.container.id} button.action-btn-small{background-color:var(--btn-action-bg);color:#fff;border:1px solid var(--btn-action-border);padding:4px 8px;font-size:11px;margin-top:5px;width:auto;border-radius:3px;cursor:pointer}#${this.container.id} button.action-btn-third{width:calc(33.33% - 4px);display:inline-block;margin:2px;background-color:var(--btn-alt-bg);color:var(--btn-alt-text);border:1px solid var(--btn-alt-border);padding:5px 8px;font-size:11.5px;text-transform:none;border-radius:3px;cursor:pointer}#${this.container.id} #bot-control-panel button.action-btn{background-color:var(--btn-action-bg);color:#fff;border:1px solid var(--btn-action-border)}#${this.container.id} button.action-btn-half{width:calc(50% - 5px);margin:2px;padding:5px;font-size:11.5px;background-color:var(--btn-action-bg);color:#fff;border:1px solid var(--btn-action-border);border-radius:3px;cursor:pointer}#${this.container.id} button.edit-preset-btn-item,#${this.container.id} button.preset-btn-item{min-width:auto;width:auto;margin:0;padding:4px 7px;display:inline-block;background-color:var(--btn-profile-bg);color:var(--btn-profile-text);font-size:11px;line-height:1.4;border:1px solid var(--btn-profile-border);border-radius:3px;cursor:pointer}#${this.container.id} button.edit-preset-btn-item{padding:3px 6px;font-size:10px;background-color:var(--accent-color);color:#fff}#${this.container.id} button:hover{filter:brightness(var(--hover-brightness))}#${this.container.id} button.action-btn-third:hover{background-color:#e0e0e0;filter:brightness(95%)}#${this.container.id} .tooltip-trigger{display:inline-block;margin-left:6px;color:var(--text-color-dim);background-color:var(--secondary-bg);border:1px solid var(--accent-border);border-radius:50%;width:14px;height:14px;font-size:10px;text-align:center;line-height:14px;cursor:help;position:relative}#${this.container.id} .tooltip-text{visibility:hidden;width:220px;background-color:var(--tooltip-bg);color:var(--tooltip-text);text-align:center;border-radius:6px;padding:8px;position:absolute;z-index:100003;bottom:125%;left:50%;margin-left:-110px;opacity:0;transition:opacity .3s;border:1px solid var(--tooltip-border);font-size:11px;line-height:1.4}#${this.container.id} .tooltip-trigger:hover .tooltip-text{visibility:visible;opacity:1}#player-list-modal{display:none;position:fixed;z-index:100003;left:0;top:0;width:100%;height:100%;background-color:var(--modal-bg);justify-content:center;align-items:center}#player-list-content{background-color:var(--modal-content-bg);padding:20px;border:1px solid var(--modal-content-border);border-radius:8px;width:80%;max-width:500px;max-height:80vh;overflow-y:auto;box-shadow:0 0 15px rgba(255,0,0,0.5)}#player-list-content h4{margin-top:0;color:var(--accent-color);text-align:center;font-size:1.2em}#player-list-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px}.player-list-button{background-color:var(--secondary-bg);color:var(--text-color-light);border:1px solid var(--accent-border);padding:8px;text-align:center;border-radius:4px;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.player-list-button:hover{background-color:var(--accent-color);color:#fff}#${this.container.id}::-webkit-scrollbar{width:10px}#${this.container.id}::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:4px}#${this.container.id}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:4px}#${this.container.id}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}#${this.container.id} .settings-group-item>div[style*="flex-direction: column"]{margin-bottom:0}#auto-skill-upgrade-ui .skill-tier-container{margin-bottom:12px;border:1px solid var(--accent-border);border-radius:4px;padding:8px;background-color:rgba(0,0,0,0.15)}#auto-skill-upgrade-ui .skill-tier-header{font-weight:bold;color:var(--text-color-light);margin-bottom:8px;padding-bottom:5px;border-bottom:1px solid var(--accent-border)}#auto-skill-upgrade-ui .skill-columns{display:flex;gap:10px}#auto-skill-upgrade-ui .skill-list{width:50%;min-height:100px;background-color:var(--skill-list-bg);border:1px dashed #444;border-radius:3px;padding:5px}#auto-skill-upgrade-ui .skill-list.drag-over{border-color:var(--status-on);background-color:rgba(0,255,0,0.1)}#auto-skill-upgrade-ui h5{font-size:11px;margin:0 0 5px;text-align:center;color:var(--text-color-dim)}#auto-skill-upgrade-ui .skill-item{background-color:var(--skill-item-bg);border:1px solid var(--skill-item-border);color:var(--text-color-light);padding:4px 6px;margin-bottom:4px;border-radius:3px;cursor:grab;font-size:11.5px;text-align:center;transition:background-color .2s}#auto-skill-upgrade-ui .skill-item:hover{background-color:var(--skill-item-hover-bg);color:#fff}#auto-skill-upgrade-ui .skill-item:last-child{margin-bottom:0}#auto-skill-upgrade-ui .skill-item.dragging{opacity:0.5;border-style:dashed}`); }
        populateColumn1_ESP() { if (!this.column1 || !GatsModCore.SETTINGS) return; const espMasterSection = this.addCollapsibleSection('ESP Configuration', this.column1, 'settings-group-master'); const visualEspOptions = this.addCollapsibleSection('Player ESP Visuals', espMasterSection, 'settings-sub-group'); this.addCheckbox('Show Enemy HP', 'espShowHP', visualEspOptions, 'Display health bars above players.'); this.addCheckbox('Highlight Low HP Enemies', 'espHighlightLowHP', visualEspOptions, 'Change ESP color for enemies with low health.'); this.addSliderInput('Low HP Threshold (%)', 'lowHPThreshold', {min: 1, max: 99, step: 1, defaultVal: 30}, GatsModCore.SETTINGS, visualEspOptions, 'Health % below which an enemy is considered low HP.'); this.addCheckbox('Show Enemy Facing Line', 'espShowFacingLine', visualEspOptions, 'Draw a line indicating player aim direction.'); this.addCheckbox('Highlight Cloaked Enemies', 'espHighlightCloaked', visualEspOptions, 'Special indicator for cloaked enemies.'); this.addCheckbox('Show Teammates (TDM)', 'espShowTeammates', visualEspOptions, 'Enable ESP for your teammates.'); const espPositioningSection = this.addCollapsibleSection('ESP Positioning & Visual Scale', espMasterSection, 'settings-sub-group'); this.addSliderInput('X Offset (Global)', 'espOffsetX', {min: -200, max: 200, step: 1, defaultVal: 0}, GatsModCore.SETTINGS, espPositioningSection, 'Shift all ESP elements horizontally.'); this.addSliderInput('Y Offset (Global)', 'espOffsetY', {min: -200, max: 200, step: 1, defaultVal: 0}, GatsModCore.SETTINGS, espPositioningSection, 'Shift all ESP elements vertically.'); this.addSliderInput('ESP Visual Scale', 'espScale', {min: 0.1, max: 5.0, step: 0.05, defaultVal: 0.89}, GatsModCore.SETTINGS, espPositioningSection, 'Global visual scale for ESP elements (boxes, lines, text).'); const otherEspOptions = this.addCollapsibleSection('Other ESP Features', espMasterSection, 'settings-sub-group'); this.addCheckbox('Show Enemy Bullet Dots', 'espShowBulletDots', otherEspOptions, 'Display a small dot for each enemy bullet.'); this.addSliderInput('Bullet Dot Radius (px)', 'bulletEspDotRadius', {min:1, max:10, step:1, defaultVal: 2}, GatsModCore.SETTINGS, otherEspOptions, 'Screen-space size of the bullet ESP dots.'); }
        populateColumn2_Aimbot() { if (!this.column2 || !GatsModCore.SETTINGS) return; const aimbotMasterSection = this.addCollapsibleSection('Aimbot & Spinbot Config', this.column2, 'settings-group-master'); const generalAimbotOptions = this.addCollapsibleSection('Aimbot - General Targeting', aimbotMasterSection, 'settings-sub-group'); this.addCheckbox('Always Aim (No Mouse Press)', 'alwaysAim', generalAimbotOptions, 'Aimbot aims even if mouse button is not pressed.'); this.addCheckbox('Activate Aimbot Only On Mouse Press', 'aimbotOnMousePress', generalAimbotOptions, 'Aimbot only activates on mouse press (unless "Always Aim" is on).'); this.addCheckbox('Target Closest to Mouse', 'aimAtMouseClosest', generalAimbotOptions, 'Prioritizes the enemy closest to your mouse cursor.'); this.addSliderInput('Aimbot FOV', 'aimbotFov', {min: 10, max: 5000, step: 10, defaultVal: 2250}, GatsModCore.SETTINGS, generalAimbotOptions, 'Field of View for the aimbot.'); const spinbotOptions = this.addCollapsibleSection('Spinbot Settings', aimbotMasterSection, 'settings-sub-group'); this.addSliderInput('Spinbot Speed (ms)', 'spinbotSpeedMs', {min: 10, max: 500, step: 10, defaultVal: 75}, GatsModCore.SETTINGS, spinbotOptions, "Time between spinbot aim changes (lower is faster)."); this.addSliderInput('Spinbot Distance', 'spinbotDistance', {min: 50, max: 500, step: 10, defaultVal: 150}, GatsModCore.SETTINGS, spinbotOptions, "Distance from player for spin target points (world units)."); const predictionSettings = this.addCollapsibleSection('Aimbot - Prediction (v10.33)', aimbotMasterSection, 'settings-sub-group'); this.addCheckbox('Prediction Enabled', 'predictionEnabled', predictionSettings, 'Aimbot will predict enemy movement.'); this.addSliderInput('Max Prediction Factor', 'predictionFactor', {min: 0.0, max: 5.0, step: 0.1, defaultVal: 2.5}, GatsModCore.SETTINGS, predictionSettings, 'Main multiplier for movement prediction.'); this.addCheckbox('Dynamic Prediction Scaling', 'enableDynamicPredictionFactor', predictionSettings, 'Adjust prediction factor based on distance to target.'); this.addSliderInput('Min Prediction Dist', 'minPredictionDistance', {min: 0, max: 1000, step: 10, defaultVal: 0}, GatsModCore.SETTINGS, predictionSettings, 'Distance where dynamic prediction scaling starts.'); this.addSliderInput('Max Prediction Dist', 'maxPredictionDistance', {min: 50, max: 2000, step: 10, defaultVal: 200}, GatsModCore.SETTINGS, predictionSettings, 'Distance where prediction factor reaches its max value.'); this.addSliderInput('Factor at Min Dist', 'predictionFactorAtMinDistance', {min: 0.0, max: 2.0, step: 0.1, defaultVal: 0.0}, GatsModCore.SETTINGS, predictionSettings, 'Prediction multiplier used at (or below) the minimum distance.'); const originTuning = this.addCollapsibleSection('Aimbot - Bullet Origin Point', aimbotMasterSection, 'settings-sub-group'); this.addCheckbox('Use Custom Bullet Origin', 'useCustomAimbotOrigin', originTuning, 'Fine-tune the aimbot\'s calculation of where your bullets fire from.'); this.addSliderInput('Gun Offset Forward', 'aimbotOriginForwardOffset', {min: -50, max: 100, step: 1, defaultVal: -5}, GatsModCore.SETTINGS, originTuning, 'Adjust bullet origin point forward/backward.'); this.addSliderInput('Gun Offset Sideways', 'aimbotOriginSidewaysOffset', {min: -50, max: 50, step: 1, defaultVal: -18}, GatsModCore.SETTINGS, originTuning, 'Adjust bullet origin point left/right.'); this.addAimbotExclusionListToColumn2(aimbotMasterSection); }
        addAimbotExclusionListToColumn2(aimbotMasterSection) { if (!aimbotMasterSection || !GatsModCore.SETTINGS) return; const aimbotExclusionSection = this.addCollapsibleSection('Aimbot - Target Exclusion List', aimbotMasterSection, 'settings-sub-group'); const mainDiv = document.createElement('div'); mainDiv.className = 'settings-group-item'; const inputLabel = document.createElement('label'); inputLabel.htmlFor = 'aimbotExcludeInput-text-v17'; inputLabel.innerText = 'Player Name to Ignore:'; inputLabel.style.display = 'block'; inputLabel.style.marginBottom = '3px'; mainDiv.appendChild(inputLabel); const input = document.createElement('input'); input.type = 'text'; input.id = 'aimbotExcludeInput-text-v17'; input.placeholder = 'Enter exact player name'; input.className = 'general-text-input'; mainDiv.appendChild(input); const addButton = this.addButton("Add to Ignore List", () => { const name = input.value.trim(); if (name && GatsModCore.SETTINGS.aimbotIgnoreList) { if (!GatsModCore.SETTINGS.aimbotIgnoreList.includes(name)) { GatsModCore.SETTINGS.aimbotIgnoreList.push(name); GatsModCore.saveSettings(); this.updateAimbotExclusionListDisplay(); input.value = ''; } else { alert(`Player "${name}" is already on the ignore list.`); } } }, mainDiv, 'action-btn-small'); addButton.style.display = 'inline-block'; addButton.style.marginLeft = '5px'; const listLabel = document.createElement('p'); listLabel.innerText = 'Currently Ignored Players:'; listLabel.style.marginTop = '10px'; listLabel.style.fontWeight = 'bold'; mainDiv.appendChild(listLabel); this.aimbotExclusionListDiv = document.createElement('div'); this.aimbotExclusionListDiv.id = 'aimbot-exclusion-list-display'; this.aimbotExclusionListDiv.style.cssText = 'max-height: 100px; overflow-y: auto; border: 1px solid var(--accent-border, #B00000); padding: 5px; border-radius: 3px; margin-top: 5px; background-color: var(--secondary-bg, #1E1E1E);'; mainDiv.appendChild(this.aimbotExclusionListDiv); aimbotExclusionSection.appendChild(mainDiv); this.updateAimbotExclusionListDisplay(); }
        updateAimbotExclusionListDisplay() { if (!this.aimbotExclusionListDiv || !GatsModCore.SETTINGS?.aimbotIgnoreList) { if (this.aimbotExclusionListDiv) this.aimbotExclusionListDiv.innerHTML = '<span>Not available</span>'; return; } this.aimbotExclusionListDiv.innerHTML = ''; if (GatsModCore.SETTINGS.aimbotIgnoreList.length === 0) { const noItems = document.createElement('span'); noItems.textContent = 'None'; noItems.style.color = 'var(--text-color-dim)'; this.aimbotExclusionListDiv.appendChild(noItems); return; } GatsModCore.SETTINGS.aimbotIgnoreList.forEach(name => { const itemDiv = document.createElement('div'); itemDiv.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 3px 2px; border-bottom: 1px solid var(--accent-border)'; const nameSpan = document.createElement('span'); nameSpan.textContent = name; nameSpan.style.cssText = 'overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 10px;'; nameSpan.title = name; const removeBtn = document.createElement('button'); removeBtn.textContent = 'X'; removeBtn.title = `Remove ${name} from ignore list`; removeBtn.style.cssText = 'color: var(--text-color-light, white); background-color: var(--btn-action-bg, #D00000); border: 1px solid var(--btn-action-border, #A00000); padding: 1px 5px; font-size: 10px; cursor: pointer; border-radius: 3px; line-height: 1;'; removeBtn.onclick = () => { GatsModCore.SETTINGS.aimbotIgnoreList = GatsModCore.SETTINGS.aimbotIgnoreList.filter(n => n !== name); GatsModCore.saveSettings(); this.updateAimbotExclusionListDisplay(); }; itemDiv.appendChild(nameSpan); itemDiv.appendChild(removeBtn); this.aimbotExclusionListDiv.appendChild(itemDiv); }); }
        populateColumn3_Utilities() { if (!this.column3 || !GatsModCore.SETTINGS) return; const utilitiesMasterSection = this.addCollapsibleSection('Utilities', this.column3, 'settings-group-master'); const autoSkillSection = this.addCollapsibleSection('Auto Skill Upgrade', utilitiesMasterSection, 'settings-sub-group'); this.createSkillUpgradeUI(autoSkillSection); const weaponSettingsSection = this.addCollapsibleSection('Weapon Settings', utilitiesMasterSection, 'settings-sub-group'); const bulletSpeedSubSection = this.addCollapsibleSection('Bullet Speed Settings', weaponSettingsSection, 'settings-sub-group'); if (GatsModCore.SETTINGS.weaponBulletSpeeds) { for (const weapon in GatsModCore.SETTINGS.weaponBulletSpeeds) { this.addSliderInput(weapon.charAt(0).toUpperCase() + weapon.slice(1), weapon, { min: 0.1, max: 100, step: 0.1, defaultVal: GatsModCore.SETTINGS.weaponBulletSpeeds[weapon] }, GatsModCore.SETTINGS.weaponBulletSpeeds, bulletSpeedSubSection, `Sets the bullet velocity used for aimbot prediction for the ${weapon}.`); } } const botControlPanel = this.addCollapsibleSection('Follow Bot Control', utilitiesMasterSection, 'settings-sub-group'); botControlPanel.id = 'bot-control-panel'; this.addCheckbox('Enable Follow Bot', 'followBotEnabled', botControlPanel, 'Enable the simple follow bot.'); this.addSliderInput('Bot Attack Radius', 'followBotAttackRadius', {min: 0, max: 800, step: 10, defaultVal: 500}, GatsModCore.SETTINGS, botControlPanel, 'Follow Bot will attack enemies within this radius.'); this.followBotStatusDisplay = document.createElement('div'); this.followBotStatusDisplay.style.textAlign = 'center'; this.followBotStatusDisplay.style.marginTop = '5px'; botControlPanel.appendChild(this.followBotStatusDisplay); const followButtons = document.createElement('div'); followButtons.style.cssText = 'display: flex; justify-content: space-around; margin-top: 5px; flex-wrap: wrap;'; this.addButton("Set Target by Name", () => GatsModCore.setFollowTargetName?.(), followButtons, 'action-btn-third'); this.addButton("Select Target from List", () => GatsModCore.showPlayerList?.(), followButtons, 'action-btn-third'); this.addButton("Start Following", () => gatsModInstance?.startFollowingPlayer?.(), followButtons, 'action-btn-third'); this.addButton("Stop Following", () => GatsModCore.stopFollowingPlayer?.(), followButtons, 'action-btn'); botControlPanel.appendChild(followButtons); const chatScrollerMasterSection = this.addCollapsibleSection('Chat Scroller (Enhanced)', utilitiesMasterSection, 'settings-sub-group'); this.addCheckbox('Enable Chat Scroller', 'chatScrollEnabled', chatScrollerMasterSection, 'Enables the auto chat message scroller.'); this.currentScrollingTextDisplay = document.createElement('div'); this.currentScrollingTextDisplay.style.cssText = 'margin: 5px 0; padding: 5px; background-color: var(--secondary-bg); border: 1px solid var(--accent-border); border-radius: 3px; font-style: italic; color: var(--text-color-dim); word-break: break-all; min-height: 20px; font-size: 11px;'; chatScrollerMasterSection.appendChild(this.currentScrollingTextDisplay); const scrollButtons = document.createElement('div'); scrollButtons.style.cssText = 'display: flex; justify-content: space-around; margin-top: 5px;'; this.addButton("Start Scroll", () => GatsModCore.startChatScroll?.(), scrollButtons, 'action-btn-half'); this.addButton("Stop Scroll", () => GatsModCore.stopChatScroll?.(), scrollButtons, 'action-btn-half'); chatScrollerMasterSection.appendChild(scrollButtons); const customMsgSection = this.addCollapsibleSection('Custom Message & Presets', chatScrollerMasterSection, 'settings-sub-group'); this.addTextInput('Scroll Text', 'chatScrollText', GatsModCore.SETTINGS, customMsgSection, (newText) => { if (GatsModCore.SETTINGS) { GatsModCore.SETTINGS.chatScrollText = newText; gatsModInstance?.simpleGui?.updateScrollingTextDisplay(newText); GatsModCore.saveSettings(); } }, true, "The text to be scrolled in chat."); const presetLabel = document.createElement('p'); presetLabel.innerText = 'Presets (1-9 to use / Edit):'; presetLabel.style.cssText = 'text-align: left; margin-top: 10px; color: var(--text-color-light);'; customMsgSection.appendChild(presetLabel); const presetButtonContainer = document.createElement('div'); presetButtonContainer.style.cssText = 'display: grid; grid-template-columns: 1fr auto; gap: 5px; align-items: center;'; if (GatsModCore.SETTINGS.chatPresetMessages?.length) { GatsModCore.SETTINGS.chatPresetMessages.forEach((msg, i) => { const presetDiv = document.createElement('div'); presetDiv.style.cssText = 'display: flex; align-items: center; margin-bottom: 3px;'; const useBtn = this.addButton( `${i+1}: ${msg.substring(0,12)}${msg.length > 12 ? '...' : ''}`, () => GatsModCore.setScrollPreset?.(i), presetDiv, 'preset-btn-item' ); useBtn.title = `Use: ${msg}`; useBtn.style.cssText = 'flex-grow: 1; margin-right: 5px; text-align: left;'; const editBtn = this.addButton( "Edit", (e) => { e.stopPropagation(); GatsModCore.editScrollPreset?.(i); }, presetDiv, 'edit-preset-btn-item' ); this.presetEditButtons[i] = { useBtn: useBtn, originalText: msg }; presetButtonContainer.appendChild(presetDiv); }); } customMsgSection.appendChild(presetButtonContainer); const speedOptionsSection = this.addCollapsibleSection('Scroller Speed & Options', chatScrollerMasterSection, 'settings-sub-group'); this.addSliderInput('Scroll Speed (ms)', 'chatScrollSpeed', {min: 50, max: 2000, step: 10, defaultVal: 200}, GatsModCore.SETTINGS, speedOptionsSection, "Delay between scroll updates."); this.addSliderInput('Max Chars Displayed', 'chatScrollMaxLength', {min: 5, max: 50, step: 1, defaultVal: 28}, GatsModCore.SETTINGS, speedOptionsSection, "Max characters per message."); }
        // *** NEW METHOD FOR SKILL UPGRADE UI ***
        createSkillUpgradeUI(parent) {
            const skillDefinitions = {
                1: { name: "Tier 1", skills: ["bipod", "longRange", "thickSkin", "lightweight", "grip", "thermal", "optics", "silencer"] },
                2: { name: "Tier 2", skills: ["knife", "dash", "medKit", "gasGrenade", "grenade", "fragGrenade", "landMine", "ghillie"] },
                3: { name: "Tier 3", skills: ["extended", "silencer", "lightweight", "bipod", "longRange", "thickSkin", "grip", "thermal", "optics"] }
            };

            const mainContainer = document.createElement('div');
            mainContainer.id = 'auto-skill-upgrade-ui';
            parent.appendChild(mainContainer);

            let draggedItem = null;

            const updatePrioritySetting = (tier) => {
                const priorityList = mainContainer.querySelector(`.skill-priority-list[data-tier="${tier}"]`);
                const skills = Array.from(priorityList.children).map(item => item.dataset.skillName);
                GatsModCore.SETTINGS[`skillPriority${tier}`] = skills.join(',');
                GatsModCore.saveSettings();
            };

            for (const tier in skillDefinitions) {
                const tierContainer = document.createElement('div');
                tierContainer.className = 'skill-tier-container';
                tierContainer.innerHTML = `<div class="skill-tier-header">${skillDefinitions[tier].name}</div>`;

                const columns = document.createElement('div');
                columns.className = 'skill-columns';

                // Priority List (Left)
                const priorityColumn = document.createElement('div');
                priorityColumn.style.width = '50%';
                priorityColumn.innerHTML = `<h5>Priority Order (Drag to Reorder)</h5>`;
                const priorityList = document.createElement('div');
                priorityList.className = 'skill-list skill-priority-list';
                priorityList.dataset.tier = tier;
                priorityColumn.appendChild(priorityList);

                // Available List (Right)
                const availableColumn = document.createElement('div');
                availableColumn.style.width = '50%';
                availableColumn.innerHTML = `<h5>Available Skills (Drag to Add)</h5>`;
                const availableList = document.createElement('div');
                availableList.className = 'skill-list skill-available-list';
                availableList.dataset.tier = tier;
                availableColumn.appendChild(availableList);

                columns.append(priorityColumn, availableColumn);
                tierContainer.appendChild(columns);
                mainContainer.appendChild(tierContainer);

                // Populate lists based on current settings
                const currentPriority = GatsModCore.SETTINGS[`skillPriority${tier}`]?.split(',').filter(Boolean) || [];
                const allSkillsForTier = skillDefinitions[tier].skills;
                const availableSkills = allSkillsForTier.filter(s => !currentPriority.includes(s));

                currentPriority.forEach(skillName => {
                    const item = document.createElement('div');
                    item.className = 'skill-item';
                    item.textContent = skillName;
                    item.dataset.skillName = skillName;
                    item.draggable = true;
                    priorityList.appendChild(item);
                });

                availableSkills.forEach(skillName => {
                    const item = document.createElement('div');
                    item.className = 'skill-item';
                    item.textContent = skillName;
                    item.dataset.skillName = skillName;
                    item.draggable = true;
                    availableList.appendChild(item);
                });

                // Add D&D event listeners to lists
                [priorityList, availableList].forEach(list => {
                    list.addEventListener('dragstart', (e) => {
                        draggedItem = e.target;
                        setTimeout(() => e.target.classList.add('dragging'), 0);
                    });
                    list.addEventListener('dragend', (e) => {
                        draggedItem.classList.remove('dragging');
                        draggedItem = null;
                    });
                    list.addEventListener('dragover', (e) => {
                        e.preventDefault();
                        list.classList.add('drag-over');
                    });
                    list.addEventListener('dragleave', () => {
                        list.classList.remove('drag-over');
                    });
                    list.addEventListener('drop', (e) => {
                        e.preventDefault();
                        if (draggedItem && draggedItem.parentElement !== list) {
                           list.appendChild(draggedItem);
                           updatePrioritySetting(tier);
                        } else if (draggedItem) { // Reordering within the same list
                           const rect = list.getBoundingClientRect();
                           const afterElement = [...list.children].find(child => e.clientY < child.getBoundingClientRect().top + child.offsetHeight / 2);
                           if (afterElement) {
                               list.insertBefore(draggedItem, afterElement);
                           } else {
                               list.appendChild(draggedItem);
                           }
                           updatePrioritySetting(tier);
                        }
                        list.classList.remove('drag-over');
                    });
                });
            }
        }
        addSearchBox(parent) { const searchBox = document.createElement('input'); searchBox.type = 'text'; searchBox.id = 'settings-search-box'; searchBox.placeholder = 'Search settings...'; searchBox.oninput = (e) => { const query = e.target.value.toLowerCase().trim(); this.container.querySelectorAll('[data-setting-name]').forEach(el => { let isParentOfVisible = false; if (el.tagName === 'DETAILS' && !el.classList.contains('settings-sub-group')) { el.querySelectorAll('[data-setting-name]').forEach(childEl => { if (childEl.dataset.settingName.includes(query) && childEl.style.display !== 'none') isParentOfVisible = true; }); } const matchesQuery = el.dataset.settingName.includes(query); el.style.display = (matchesQuery || isParentOfVisible) ? '' : 'none'; if (isParentOfVisible && el.tagName === 'DETAILS' && query) el.open = true; }); }; parent.appendChild(searchBox); }
        addProfileManager(parent) { const managerDiv = document.createElement('div'); managerDiv.id = 'profile-manager'; const selectLabel = document.createElement('span'); selectLabel.innerText = 'Profile: '; selectLabel.style.marginRight = '5px'; managerDiv.appendChild(selectLabel); const selectElement = document.createElement('select'); selectElement.id = 'profile-select-v17'; managerDiv.appendChild(selectElement); this.profileSelectElement = selectElement; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.id = 'profile-name-input-v17'; nameInput.placeholder = 'Profile Name'; nameInput.style.width = '100px'; managerDiv.appendChild(nameInput); this.addButton("Save", () => GatsModCore.saveProfile?.(nameInput.value), managerDiv, 'action-btn-small profile-btn'); this.addButton("Load", () => GatsModCore.loadProfile?.(selectElement.value), managerDiv, 'action-btn-small profile-btn'); this.addButton("Delete", () => GatsModCore.deleteProfile?.(selectElement.value), managerDiv, 'action-btn-small profile-btn'); parent.appendChild(managerDiv); }
        updateProfileList() { if (!this.profileSelectElement || !GatsModCore.SETTINGS?.settingProfiles) { if (this.profileSelectElement) this.profileSelectElement.innerHTML = '<option value="">No Profiles</option>'; return; } this.profileSelectElement.innerHTML = ''; const profileNames = Object.keys(GatsModCore.SETTINGS.settingProfiles); if (profileNames.length === 0) { this.profileSelectElement.innerHTML = '<option value="">No Profiles</option>'; return; } profileNames.forEach(name => { const option = document.createElement('option'); option.value = name; option.innerText = name; this.profileSelectElement.appendChild(option); }); }
        addHideButton(parent) { const btn = this.addButton('Hide GUIs (0)', () => { this.container.style.display = 'none'; gatsModInstance?.colorGui?.container && (gatsModInstance.colorGui.container.style.display = 'none'); }, parent, 'custom-btn'); btn.style.backgroundColor = 'var(--secondary-bg)'; btn.style.borderColor = 'var(--accent-border)'; btn.style.marginTop = '15px'; }
        createPlayerListModal() { if (document.getElementById('player-list-modal')) return; const modal = document.createElement('div'); modal.id = 'player-list-modal'; modal.onclick = (e) => { if (e.target === modal) modal.style.display = 'none'; }; const content = document.createElement('div'); content.id = 'player-list-content'; const head = document.createElement('h4'); head.innerText = 'Select Player to Follow'; content.appendChild(head); const grid = document.createElement('div'); grid.id = 'player-list-grid'; content.appendChild(grid); modal.appendChild(content); document.body.appendChild(modal); }
        updateStatusDisplay() { if (!this.statusDisplay || !GatsModCore.SETTINGS) return; const s = GatsModCore.SETTINGS; const esp = s.espEnabled ? `<span class="status-on">ON</span>` : `<span class="status-off">OFF</span>`; const aimbot = s.aimbotEnabled ? `<span class="status-on">ON</span>` : `<span class="status-off">OFF</span>`; const spin = s.spinbotEnabled ? ` | Spin: <span class="status-on">ON</span>` : ``; let follow = `<span class="status-off">INACTIVE</span>`; if(s.followBotEnabled && gatsModInstance?.isFollowingPlayer && s.followBotTargetName) { follow = `Following: <span class="status-on">${s.followBotTargetName.substring(0,15)}</span>`; } else if (s.followBotEnabled) { follow = `<span class="status-neutral">ENABLED</span>`; } this.statusDisplay.innerHTML = `ESP: ${esp} | Aimbot: ${aimbot}${spin} | Follow: ${follow}`; }
        updateFollowBotStatusDisplay() { if (!this.followBotStatusDisplay || !GatsModCore.SETTINGS || !gatsModInstance) return; const { followBotTargetName, aimbotEnabled } = GatsModCore.SETTINGS; const isFollowing = gatsModInstance.isFollowingPlayer; const statusText = `Target: ${followBotTargetName || 'N/A'} (${isFollowing ? "<span class='status-on'>Active</span>" : "<span class='status-off'>Stopped</span>"})`; const isAttacking = isFollowing && aimbotEnabled; this.followBotStatusDisplay.innerHTML = `${statusText}<br>FollowBot Attack (if Aimbot On): ${isAttacking ? '<span class="status-on">POSSIBLE</span>' : '<span class="status-off">OFF</span>'}`; }
        updateScrollingTextDisplay(newText) { if (this.currentScrollingTextDisplay && GatsModCore.SETTINGS) { const maxLength = GatsModCore.SETTINGS.chatScrollMaxLength || 30; this.currentScrollingTextDisplay.innerText = `Scrolling: ${newText.length > maxLength ? newText.substring(0, maxLength - 3) + "..." : newText}`; } }
        updatePresetButtonLabel(index, newText) { if (this.presetEditButtons?.[index]?.useBtn) { const labelText = `${index + 1}: ${newText.substring(0,12)}${newText.length > 12 ? '...' : ''}`; this.presetEditButtons[index].useBtn.innerText = labelText; this.presetEditButtons[index].useBtn.title = `Use: ${newText.trim()}`; } }
        updateAllGUIToReflectSettings() { if (!GatsModCore.SETTINGS) { modLog("Cannot update GUI: GatsModCore.SETTINGS not available.", true); return; } const settings = GatsModCore.SETTINGS; this.container.querySelectorAll('input[type="checkbox"]').forEach(cb => { const key = cb.id.replace('-v17', ''); if (settings.hasOwnProperty(key)) cb.checked = settings[key]; }); this.container.querySelectorAll('input[type="range"]').forEach(slider => { const key = slider.id.replace('-slider-v17', ''); let obj = settings; if (!obj.hasOwnProperty(key) && settings.weaponBulletSpeeds?.hasOwnProperty(key)) obj = settings.weaponBulletSpeeds; if (obj.hasOwnProperty(key)) { slider.value = obj[key]; const valueDisplay = slider.parentElement.querySelector('input[type="number"].value-display'); if (valueDisplay) { const decimals = slider.step.toString().includes('.') ? slider.step.toString().split('.')[1].length : 0; valueDisplay.value = parseFloat(obj[key]).toFixed(decimals); } } }); this.container.querySelectorAll('input[type="text"][id$="-text-v17"]').forEach(input => { const key = input.id.replace('-text-v17', ''); if (settings.hasOwnProperty(key)) input.value = settings[key]; }); if (this.currentScrollingTextDisplay && settings.hasOwnProperty('chatScrollText')) this.updateScrollingTextDisplay(settings.chatScrollText); if (gatsModInstance?.colorGui?.container && settings.espColors) { for (const key in settings.espColors) { const picker = document.getElementById(`${key}-color-v17`); if (picker) picker.value = settings.espColors[key]; } } this.updateProfileList(); this.updateStatusDisplay(); this.updateFollowBotStatusDisplay?.(); this.updateAimbotExclusionListDisplay?.(); if (this.presetEditButtons?.length && settings.chatPresetMessages) { settings.chatPresetMessages.forEach((msg, i) => this.updatePresetButtonLabel(i, msg)); } modLog("SimpleGUI updated to reflect current settings."); }
    }


    // --- Main GatsModCore Class ---
    class GatsModCore {
        static SETTINGS = {};
        static isInputActive = false;
        static chatScrollIntervalId = null;
        static chatScrollCurrentIndex = 0;

        constructor() {
            modLog("GatsModCore v17.1.0 constructor called.");
            this.gameCanvas = document.getElementById('canvas');
            if (!this.gameCanvas) { modLog("FATAL: Game canvas not found.", true); return; }

            this.originalUpdateMouseData = null;
            this.aimTargetScreenCoords = null;
            this.spinbotTargetScreenCoords = null;
            this.currentAimAssistTargetCoords = null;
            this.isFollowingPlayer = false;
            this.followingPlayerId = null;
            this.simulatedKeys = { w: false, a: false, s: false, d: false };
            this.lastAttackTime = 0;
            this.tickCounter = 0;
            this.realMouseButtons = 0;
            this.realMouseCanvasX = 0;
            this.realMouseCanvasY = 0;
            this.spinbotCurrentTargetIndex = 0;
            this.lastSpinTime = 0;

            this.initializeSettings();
            this.simpleGui = new SimpleGUI();
            this.colorGui = new ColorCustomizerGUI();
            this.setupGUI();
            this.setupOverlay();
            this.addEventListeners();
            this.hookMouseEvents();
            this.simpleGui.updateAllGUIToReflectSettings();
            modLog(`GatsModCore v17.1.0 initialized successfully.`);
        }

        initializeSettings() {
            let savedSettings = {};
            try { const item = localStorage.getItem(SETTINGS_KEY); if (item) savedSettings = JSON.parse(item); } catch (e) { modLog(`Error loading settings: ${e.message}`, true); }
            const defaultSettings = {
                // ESP
                espEnabled: true, espShowHP: true, espHighlightLowHP: true, lowHPThreshold: 30, espShowFacingLine: true,
                espHighlightCloaked: true, espShowTeammates: true, espOffsetX: 0, espOffsetY: 0, espScale: 0.89,
                espShowBulletDots: false, bulletEspDotRadius: 2,
                // Aimbot (v10.33 defaults)
                aimbotEnabled: true, alwaysAim: false, aimbotOnMousePress: true, aimAtMouseClosest: true,
                aimbotFov: 2250, aimbotIgnoreList: [],
                // Prediction (v10.33 defaults)
                predictionEnabled: true, predictionFactor: 2.5, enableDynamicPredictionFactor: true,
                minPredictionDistance: 0, maxPredictionDistance: 200, predictionFactorAtMinDistance: 0.0,
                useCustomAimbotOrigin: true, aimbotOriginForwardOffset: -5, aimbotOriginSidewaysOffset: -18,
                // Spinbot
                spinbotEnabled: false, spinbotSpeedMs: 75, spinbotDistance: 150,
                // Follow Bot
                followBotEnabled: false, followBotTargetName: "", followBotAttackRadius: 500,
                // Chat Scroller
                chatScrollEnabled: false, chatScrollText: "GatsMod v17.1 Active!", chatScrollActive: false,
                chatScrollSpeed: 200, chatScrollMaxLength: 28,
                chatPresetMessages: [ "Nice shot!", "lol", "glhf", "brb", "re", "oops", "lag", "thx", "gg" ],
                // Auto Skill Upgrade (NEW)
                autoSkillUpgrade: true,
                skillPriority1: "bipod,longRange,thickSkin,lightweight,grip,thermal,optics,silencer",
                skillPriority2: "knife,dash,medKit,gasGrenade,grenade,fragGrenade,landMine,ghillie",
                skillPriority3: "extended,silencer,lightweight,bipod,longRange,thickSkin,grip,thermal,optics",
                // Others
                weaponBulletSpeeds: { 'pistol': 7, 'smg': 4, 'shotgun': 16, 'assault': 5, 'sniper': 32, 'lmg': 8 },
                espColors: { enemyEspColor: '#FF0000', lowHpEnemyEspColor: '#FFA500', teammateEspColor: '#0096FF', cloakedTextColor: '#E0E0E0', enemyNameColor: '#000000', teammateNameColor: '#ADD8E6', hpBarHighColor: '#00FF00', hpBarMediumColor: '#FFFF00', hpBarLowColor: '#FF0000', facingLineColor: '#00FFFF', bulletDotColor: '#000000', aimbotTargetLineColor: '#00FF00' },
                settingProfiles: {}
            };
            GatsModCore.SETTINGS = { ...defaultSettings, ...savedSettings };
            GatsModCore.SETTINGS.weaponBulletSpeeds = { ...defaultSettings.weaponBulletSpeeds, ...(savedSettings.weaponBulletSpeeds || {}) };
            GatsModCore.SETTINGS.espColors = { ...defaultSettings.espColors, ...(savedSettings.espColors || {}) };
            GatsModCore.SETTINGS.settingProfiles = savedSettings.settingProfiles || {};
            GatsModCore.SETTINGS.aimbotIgnoreList = Array.isArray(savedSettings.aimbotIgnoreList) ? savedSettings.aimbotIgnoreList : [];
            GatsModCore.SETTINGS.chatPresetMessages = Array.isArray(savedSettings.chatPresetMessages) && savedSettings.chatPresetMessages.length === 9 ? savedSettings.chatPresetMessages : defaultSettings.chatPresetMessages;
            if (GatsModCore.SETTINGS.alwaysAim) GatsModCore.SETTINGS.aimbotOnMousePress = false;
        }

        setupGUI() {
            if (!this.simpleGui) return;
            this.simpleGui.applyStyles();
            this.simpleGui.populateColumn1_ESP();
            this.simpleGui.populateColumn2_Aimbot();
            this.simpleGui.populateColumn3_Utilities();
            this.simpleGui.createPlayerListModal();
            const mainWrapper = this.simpleGui.container.querySelector('#gui-main-content-wrapper');
            if (mainWrapper) this.simpleGui.addHideButton(mainWrapper);
        }

        setupOverlay() {
            const existingOverlay = document.getElementById('gemini-gats-mod-overlay-v17');
            if (existingOverlay) this.overlayCanvas = existingOverlay;
            else { this.overlayCanvas = document.createElement('canvas'); this.overlayCanvas.id = 'gemini-gats-mod-overlay-v17'; document.body.appendChild(this.overlayCanvas); }
            this.overlayCanvas.width = this.gameCanvas.width; this.overlayCanvas.height = this.gameCanvas.height;
            this.overlayCanvas.style.position = 'absolute';
            this.overlayCanvas.style.left = this.gameCanvas.offsetLeft + 'px';
            this.overlayCanvas.style.top = this.gameCanvas.offsetTop + 'px';
            this.overlayCanvas.style.pointerEvents = 'none';
            this.overlayCanvas.style.zIndex = (parseInt(this.gameCanvas.style.zIndex || '0') + 1).toString();
            this.overlayCtx = this.overlayCanvas.getContext('2d');
        }

        addEventListeners() {
            new ResizeObserver(() => { if (this.gameCanvas && this.overlayCanvas) { this.overlayCanvas.width = this.gameCanvas.width; this.overlayCanvas.height = this.gameCanvas.height; this.overlayCanvas.style.left = this.gameCanvas.offsetLeft + 'px'; this.overlayCanvas.style.top = this.gameCanvas.offsetTop + 'px'; } }).observe(this.gameCanvas);
            window.addEventListener('keydown', (e) => { if (GatsModCore.isInputActive) { if (e.key === "Escape" && document.activeElement?.blur) document.activeElement.blur(); return; } const key = e.key.toLowerCase(); let settingChanged = false, hotkeyPressed = true; switch (key) { case 'f': GatsModCore.SETTINGS.espEnabled = !GatsModCore.SETTINGS.espEnabled; settingChanged = true; break; case 'g': GatsModCore.SETTINGS.aimbotEnabled = !GatsModCore.SETTINGS.aimbotEnabled; settingChanged = true; break; case 'n': GatsModCore.SETTINGS.spinbotEnabled = !GatsModCore.SETTINGS.spinbotEnabled; settingChanged = true; break; case '0': if (this.simpleGui?.container) { const isVisible = this.simpleGui.container.style.display !== 'none'; this.simpleGui.container.style.display = isVisible ? 'none' : 'block'; if (this.colorGui?.container) this.colorGui.container.style.display = isVisible ? 'none' : 'block'; } break; default: hotkeyPressed = false; break; } if (!hotkeyPressed && GatsModCore.SETTINGS.chatScrollEnabled && e.keyCode >= 49 && e.keyCode <= 57) { GatsModCore.setScrollPreset?.(e.keyCode - 49); e.preventDefault(); } if (settingChanged) { GatsModCore.saveSettings(); this.simpleGui?.updateAllGUIToReflectSettings(); } });
            const guiIdsToIgnore = [this.simpleGui.container.id, this.colorGui?.container.id, 'player-list-modal'].filter(Boolean);
            document.addEventListener('mousedown', (e) => { if (!e.target.closest(guiIdsToIgnore.map(id => `#${id}`).join(', '))) this.realMouseButtons = e.buttons; }, true);
            document.addEventListener('mouseup', (e) => { this.realMouseButtons = e.buttons; }, true);
        }

        hookMouseEvents() { const self = this; this.gameCanvas.addEventListener('mousemove', function(event) { const rect = self.gameCanvas.getBoundingClientRect(); self.realMouseCanvasX = event.clientX - rect.left; self.realMouseCanvasY = event.clientY - rect.top; }); }

        mainGameTick() {
            this.tickCounter++;
            const me = Player.pool?.[selfId];
            if (!me?.activated) {
                if (this.isFollowingPlayer) GatsModCore.stopFollowingPlayer(true);
                this.currentAimAssistTargetCoords = null;
                this.overlayCtx?.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
                return;
            }
            if (!this.overlayCtx || !camera?.ctx) return;
            this.overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);

            if (GatsModCore.SETTINGS.autoSkillUpgrade) GatsModCore.checkAndAcquireSkill();

            if (GatsModCore.SETTINGS.followBotEnabled && this.isFollowingPlayer) this.performFollowBotActions(me);
            else if (this.isFollowingPlayer) GatsModCore.stopFollowingPlayer(true);
            else if (Object.values(this.simulatedKeys).some(s => s)) this.updateSimulatedKeys([]);

            const mouseClicked = this.realMouseButtons > 0;
            const aimbotActive = GatsModCore.SETTINGS.aimbotEnabled && (GatsModCore.SETTINGS.alwaysAim || (GatsModCore.SETTINGS.aimbotOnMousePress && mouseClicked));
            const spinbotActive = GatsModCore.SETTINGS.spinbotEnabled && !mouseClicked;

            this.currentAimAssistTargetCoords = null;
            if (spinbotActive) this.performSpinbotActions(me);
            if (aimbotActive) this.performAimbotTargeting(me);

            if (aimbotActive && this.aimTargetScreenCoords) this.currentAimAssistTargetCoords = this.aimTargetScreenCoords;
            else if (spinbotActive && this.spinbotTargetScreenCoords) this.currentAimAssistTargetCoords = this.spinbotTargetScreenCoords;

            if (this.currentAimAssistTargetCoords && this.originalUpdateMouseData) {
                let clientX = this.currentAimAssistTargetCoords.x, clientY = this.currentAimAssistTargetCoords.y;
                if (this.gameCanvas) { const rect = this.gameCanvas.getBoundingClientRect(); clientX += rect.left; clientY += rect.top; }
                const fakeEvent = { clientX: clientX, clientY: clientY, target: this.gameCanvas, buttons: this.realMouseButtons };
                this.originalUpdateMouseData(fakeEvent);
            }

            if (GatsModCore.SETTINGS.espEnabled) {
                this.drawESP(this.overlayCtx, me, !!this.currentAimAssistTargetCoords);
            }

            if (this.tickCounter % 30 === 0) {
                this.simpleGui?.updateStatusDisplay();
                this.simpleGui?.updateFollowBotStatusDisplay();
            }
        }

        // *** AIMBOT LOGIC REPLACED WITH V14.10 / V10.33 STABLE VERSION ***
        performAimbotTargeting(me) {
            if (!this.gameCanvas) { this.aimTargetScreenCoords = null; return; }
            const settings = GatsModCore.SETTINGS;
            const canvasCenterX = this.gameCanvas.width / 2;
            const canvasCenterY = this.gameCanvas.height / 2;
            let targetCandidate = null;

            let shotOriginX_world = me.x;
            let shotOriginY_world = me.y;
            if (settings.useCustomAimbotOrigin) {
                const angleRad = (me.playerAngle || 0) * Math.PI / 180;
                const forward = settings.aimbotOriginForwardOffset;
                const sideways = settings.aimbotOriginSidewaysOffset;
                shotOriginX_world += forward * Math.cos(angleRad) + sideways * Math.cos(angleRad + Math.PI / 2);
                shotOriginY_world += forward * Math.sin(angleRad) + sideways * Math.sin(angleRad + Math.PI / 2);
            }

            let refX = settings.aimAtMouseClosest ? this.realMouseCanvasX : canvasCenterX;
            let refY = settings.aimAtMouseClosest ? this.realMouseCanvasY : canvasCenterY;
            let closestPlayerDistSq = (settings.aimbotFov / (settings.aimAtMouseClosest ? 1 : (settings.espScale || 1)))**2;

            for (const id in Player.pool) {
                const p = Player.pool[id];
                if (!p?.activated || p.hp <= 0 || id == selfId || (me.teamCode !== 0 && p.teamCode === me.teamCode) || settings.aimbotIgnoreList?.includes(p.username)) continue;
                if (settings.followBotEnabled && this.isFollowingPlayer && getDistance(p, me) > settings.followBotAttackRadius) continue;

                let worldTargetX = p.x;
                let worldTargetY = p.y;

                if (settings.predictionEnabled && p.spdX !== undefined && p.spdY !== undefined) {
                    const currentWeaponClass = me.class || 'pistol';
                    let bulletSpeed = settings.weaponBulletSpeeds[currentWeaponClass] || 20;
                    bulletSpeed = Math.max(0.1, bulletSpeed);

                    let timeToHit = 0;
                    let futureX_intermediate = p.x;
                    let futureY_intermediate = p.y;
                    // Iterative calculation for timeToHit (2 iterations as in v10.33)
                    for (let i = 0; i < 2; i++) {
                        const distanceToFuturePos = Math.sqrt((futureX_intermediate - shotOriginX_world)**2 + (futureY_intermediate - shotOriginY_world)**2);
                        timeToHit = distanceToFuturePos < 1 ? 0 : distanceToFuturePos / bulletSpeed;
                        timeToHit = Math.min(timeToHit, 5);

                        let displacementX_iter = p.spdX * timeToHit;
                        let displacementY_iter = p.spdY * timeToHit;
                        futureX_intermediate = p.x + displacementX_iter;
                        futureY_intermediate = p.y + displacementY_iter;
                    }

                    let baseDisplacementX = p.spdX * timeToHit;
                    let baseDisplacementY = p.spdY * timeToHit;
                    let actualPredictionFactor = settings.predictionFactor;

                    if (settings.enableDynamicPredictionFactor) {
                        const distanceToEnemy = Math.sqrt((p.x - shotOriginX_world)**2 + (p.y - shotOriginY_world)**2);
                        const { predictionFactorAtMinDistance: minF, predictionFactor: maxF, minPredictionDistance: minD, maxPredictionDistance: maxD } = settings;

                        if (distanceToEnemy <= minD) {
                            actualPredictionFactor = minF;
                        } else if (distanceToEnemy >= maxD) {
                            actualPredictionFactor = maxF;
                        } else if (maxD > minD) {
                            const ratio = (distanceToEnemy - minD) / (maxD - minD);
                            actualPredictionFactor = minF + (maxF - minF) * ratio;
                        } else {
                            actualPredictionFactor = maxF;
                        }
                        actualPredictionFactor = Math.max(0, actualPredictionFactor);
                    }
                    worldTargetX = p.x + (baseDisplacementX * actualPredictionFactor);
                    worldTargetY = p.y + (baseDisplacementY * actualPredictionFactor);
                }

                // Convert to screen coordinates for FOV check
                const screenTargetXUnscaled = canvasCenterX + (worldTargetX - me.x);
                const screenTargetYUnscaled = canvasCenterY + (worldTargetY - me.y);
                const distToRefSq = (screenTargetXUnscaled - refX)**2 + (screenTargetYUnscaled - refY)**2;

                if (distToRefSq < closestPlayerDistSq) {
                    closestPlayerDistSq = distToRefSq;
                    targetCandidate = { x_world: worldTargetX, y_world: worldTargetY };
                }
            }

            if (targetCandidate) {
                // Convert final world target to screen coordinates for aiming
                this.aimTargetScreenCoords = {
                    x: canvasCenterX + (targetCandidate.x_world - me.x) / (settings.espScale || 1) + settings.espOffsetX,
                    y: canvasCenterY + (targetCandidate.y_world - me.y) / (settings.espScale || 1) + settings.espOffsetY
                };
            } else {
                this.aimTargetScreenCoords = null;
            }
        }

        drawESP(ctx, me, hasTarget) {
            const { espColors, espScale, espOffsetX, espOffsetY } = GatsModCore.SETTINGS;
            if (!this.gameCanvas || !camera?.ctx) return;
            const canvasCenterX = this.gameCanvas.width / 2, canvasCenterY = this.gameCanvas.height / 2;
            ctx.save();
            ctx.textAlign = 'center';
            ctx.font = 'bold 10px Arial';

            for (const id in Player.pool) {
                const p = Player.pool[id];
                if (!p?.activated || p.hp <= 0 || id == selfId) continue;
                const isTeammate = (me.teamCode !== 0 && p.teamCode === me.teamCode);
                if (isTeammate && !GatsModCore.SETTINGS.espShowTeammates) continue;

                const relX = (p.x - me.x) / espScale, relY = (p.y - me.y) / espScale;
                const screenX = canvasCenterX + relX + espOffsetX, screenY = canvasCenterY + relY + espOffsetY;
                const radiusOnScreen = (p.radius || 15) / espScale;

                let boxColor = isTeammate ? espColors.teammateEspColor : espColors.enemyEspColor;
                if (!isTeammate && GatsModCore.SETTINGS.espHighlightLowHP && p.hp < GatsModCore.SETTINGS.lowHPThreshold) {
                    boxColor = espColors.lowHpEnemyEspColor;
                }
                ctx.strokeStyle = boxColor;
                ctx.lineWidth = 1.5;
                ctx.strokeRect(screenX - radiusOnScreen, screenY - radiusOnScreen, radiusOnScreen * 2, radiusOnScreen * 2);
                ctx.beginPath();
                ctx.moveTo(canvasCenterX + espOffsetX, canvasCenterY + espOffsetY);
                ctx.lineTo(screenX, screenY);
                ctx.stroke();

                let nameYOffset = screenY - radiusOnScreen - 15;
                if (GatsModCore.SETTINGS.espHighlightCloaked && p.ghillie && !isTeammate) {
                    ctx.font = 'bold 12px Arial';
                    ctx.fillStyle = espColors.cloakedTextColor;
                    ctx.fillText('CLOAKED', screenX, nameYOffset); // Changed text to English for clarity
                    nameYOffset -= 14;
                }

                if (p.username) {
                    ctx.font = 'bold 10px Arial';
                    ctx.fillStyle = isTeammate ? espColors.teammateNameColor : espColors.enemyNameColor;
                    ctx.fillText(p.username, screenX, nameYOffset);
                }

                if (GatsModCore.SETTINGS.espShowHP) {
                    const hpPercent = p.hp / (p.hpMax || 100);
                    const barW = radiusOnScreen * 1.8, barH = 4;
                    const barX = screenX - barW / 2, barY = screenY + radiusOnScreen + 4;
                    ctx.fillStyle = 'rgba(0,0,0,0.5)';
                    ctx.fillRect(barX, barY, barW, barH);
                    ctx.fillStyle = hpPercent > 0.6 ? espColors.hpBarHighColor : hpPercent > 0.3 ? espColors.hpBarMediumColor : espColors.hpBarLowColor;
                    ctx.fillRect(barX, barY, barW * hpPercent, barH);
                }

                if (GatsModCore.SETTINGS.espShowFacingLine && p.playerAngle !== undefined) {
                    const angleRad = p.playerAngle * Math.PI / 180;
                    const lineLen = radiusOnScreen * 1.2;
                    ctx.beginPath();
                    ctx.moveTo(screenX, screenY);
                    ctx.lineTo(screenX + lineLen * Math.cos(angleRad), screenY + lineLen * Math.sin(angleRad));
                    ctx.strokeStyle = espColors.facingLineColor;
                    ctx.lineWidth = 2;
                    ctx.stroke();
                }
            }

            if (GatsModCore.SETTINGS.espShowBulletDots && Bullet.pool) {
                ctx.fillStyle = espColors.bulletDotColor;
                for (const id in Bullet.pool) {
                    const b = Bullet.pool[id];
                    if (!b?.activated || String(b.ownerId) === String(selfId) || (me.teamCode !== 0 && b.teamCode === me.teamCode)) continue;
                    const bulletX = canvasCenterX + (b.x - me.x) / espScale + espOffsetX;
                    const bulletY = canvasCenterY + (b.y - me.y) / espScale + espOffsetY;
                    if (bulletX < 0 || bulletX > this.overlayCanvas.width || bulletY < 0 || bulletY > this.overlayCanvas.height) continue;
                    ctx.beginPath();
                    ctx.arc(bulletX, bulletY, GatsModCore.SETTINGS.bulletEspDotRadius, 0, 2 * Math.PI);
                    ctx.fill();
                }
            }

            if (hasTarget && this.currentAimAssistTargetCoords) {
                const { x, y } = this.currentAimAssistTargetCoords;
                let originX = canvasCenterX + espOffsetX, originY = canvasCenterY + espOffsetY;
                if (GatsModCore.SETTINGS.useCustomAimbotOrigin) {
                    const angleRad = (me.playerAngle || 0) * Math.PI / 180;
                    const forward = GatsModCore.SETTINGS.aimbotOriginForwardOffset;
                    const sideways = GatsModCore.SETTINGS.aimbotOriginSidewaysOffset;
                    const gunOffsetX = (forward * Math.cos(angleRad) + sideways * Math.cos(angleRad + Math.PI / 2));
                    const gunOffsetY = (forward * Math.sin(angleRad) + sideways * Math.sin(angleRad + Math.PI / 2));
                    originX += gunOffsetX / espScale;
                    originY += gunOffsetY / espScale;
                }
                ctx.strokeStyle = espColors.aimbotTargetLineColor;
                ctx.lineWidth = 1.0;
                ctx.beginPath();
                ctx.moveTo(originX, originY);
                ctx.lineTo(x, y);
                ctx.stroke();
                ctx.beginPath();
                ctx.arc(x, y, 15, 0, 2 * Math.PI);
                ctx.stroke();
            }
            ctx.restore();
        }

        performSpinbotActions(me) { if (!GatsModCore.SETTINGS.spinbotEnabled) { this.spinbotTargetScreenCoords = null; return; } const now = performance.now(); if (now - this.lastSpinTime > GatsModCore.SETTINGS.spinbotSpeedMs) { this.spinbotCurrentTargetIndex = (this.spinbotCurrentTargetIndex + 1) % 3; this.lastSpinTime = now; } if(!this.gameCanvas) return; const canvasCenterX = this.gameCanvas.width / 2, canvasCenterY = this.gameCanvas.height / 2; const spinDist = GatsModCore.SETTINGS.spinbotDistance; let worldTargetX, worldTargetY; switch (this.spinbotCurrentTargetIndex) { case 0: worldTargetX = me.x; worldTargetY = me.y - spinDist; break; case 1: worldTargetX = me.x + spinDist; worldTargetY = me.y + spinDist; break; case 2: worldTargetX = me.x - spinDist; worldTargetY = me.y + spinDist; break; default: worldTargetX = me.x; worldTargetY = me.y; } this.spinbotTargetScreenCoords = { x: canvasCenterX + (worldTargetX - me.x), y: canvasCenterY + (worldTargetY - me.y) }; }
        performFollowBotActions(me) { if (!this.isFollowingPlayer || this.followingPlayerId === null) { this.updateSimulatedKeys([]); if(GatsModCore.SETTINGS.followBotEnabled) GatsModCore.stopFollowingPlayer(true); return; } const targetPlayer = Player.pool[this.followingPlayerId]; if (!targetPlayer?.activated || targetPlayer.hp <= 0) { this.updateSimulatedKeys([]); GatsModCore.stopFollowingPlayer(); return; } const dx = targetPlayer.x - me.x, dy = targetPlayer.y - me.y; const deadzone = 30; let keys = []; if (dy < -deadzone) keys.push('w'); else if (dy > deadzone) keys.push('s'); if (dx < -deadzone) keys.push('a'); else if (dx > deadzone) keys.push('d'); this.updateSimulatedKeys(keys); }
        updateSimulatedKeys(keysToPress) { const allKeys = ['w', 'a', 's', 'd']; allKeys.forEach(key => { const shouldBePressed = keysToPress.includes(key); if (shouldBePressed !== this.simulatedKeys[key]) { this._fireKeyEvent(shouldBePressed ? 'keydown' : 'keyup', key); this.simulatedKeys[key] = shouldBePressed; } });}
        _fireKeyEvent(type, key) { const keyMap = { 'w': 87, 'a': 65, 's': 83, 'd': 68 }; const codeMap = { 'w': 'KeyW', 'a': 'KeyA', 's': 'KeyS', 'd': 'KeyD' }; if (!keyMap[key]) return; document.dispatchEvent(new KeyboardEvent(type, { key: key, code: codeMap[key], keyCode: keyMap[key], bubbles: true, cancelable: true, composed: true })); }
        startFollowingPlayer() { const targetName = GatsModCore.SETTINGS.followBotTargetName; if (!targetName) { alert("Set a target name first using the 'Follow Bot Control' panel."); return; } const foundPlayer = Object.values(Player.pool).find(p => p.username === targetName && p.activated && p.id !== selfId); if (foundPlayer) { this.isFollowingPlayer = true; this.followingPlayerId = foundPlayer.id; modLog(`Started following ${targetName}`); } else { alert(`Player "${targetName}" not found or is not active.`); this.isFollowingPlayer = false; this.followingPlayerId = null; } this.simpleGui.updateFollowBotStatusDisplay(); }

        static saveSettings() { try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(GatsModCore.SETTINGS)); } catch (e) { modLog("Error saving settings: " + e.message, true); }};
        static saveProfile(name) { if (!name) { alert("Please enter a profile name."); return; } if (!GatsModCore.SETTINGS.settingProfiles) GatsModCore.SETTINGS.settingProfiles = {}; const settingsToSave = {...GatsModCore.SETTINGS}; delete settingsToSave.settingProfiles; GatsModCore.SETTINGS.settingProfiles[name] = JSON.stringify(settingsToSave); GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateProfileList(); alert(`Profile "${name}" saved.`); };
        static loadProfile(name) { if (!name) { alert("Select a profile to load."); return; } const profileDataString = GatsModCore.SETTINGS.settingProfiles?.[name]; if (!profileDataString) { alert(`Profile "${name}" not found.`); return; } try { const loadedProfileSettings = JSON.parse(profileDataString); const preservedProfiles = GatsModCore.SETTINGS.settingProfiles; const preservedEspColors = GatsModCore.SETTINGS.espColors; GatsModCore.SETTINGS = {...GatsModCore.SETTINGS, ...loadedProfileSettings}; GatsModCore.SETTINGS.settingProfiles = preservedProfiles; GatsModCore.SETTINGS.espColors = GatsModCore.SETTINGS.espColors || preservedEspColors; GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateAllGUIToReflectSettings(); if (gatsModInstance?.colorGui && GatsModCore.SETTINGS.espColors) { Object.keys(GatsModCore.SETTINGS.espColors).forEach(key => {const picker = document.getElementById(key + '-color-v17'); if(picker) picker.value = GatsModCore.SETTINGS.espColors[key]; });} alert(`Profile "${name}" loaded.`); } catch (e) { alert("Error loading profile. It may be corrupt: " + e.message); } };
        static deleteProfile(name) { if (!name || !confirm(`Are you sure you want to delete the profile "${name}"?`)) return; if (GatsModCore.SETTINGS.settingProfiles?.[name]) { delete GatsModCore.SETTINGS.settingProfiles[name]; GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateProfileList(); alert(`Profile "${name}" deleted.`); } };
        static setFollowTargetName() { const newName = prompt("Enter player name to follow:", GatsModCore.SETTINGS.followBotTargetName); if (newName !== null) { const oldName = GatsModCore.SETTINGS.followBotTargetName; GatsModCore.SETTINGS.followBotTargetName = newName.trim(); GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateFollowBotStatusDisplay(); if (gatsModInstance?.isFollowingPlayer && oldName !== newName.trim()) GatsModCore.stopFollowingPlayer(true); alert(`Follow target set to: ${newName.trim()}`); } };
        static stopFollowingPlayer(silent = false) { if (!gatsModInstance) return; if (gatsModInstance.isFollowingPlayer || Object.values(gatsModInstance.simulatedKeys).some(s => s)) gatsModInstance.updateSimulatedKeys([]); gatsModInstance.isFollowingPlayer = false; gatsModInstance.followingPlayerId = null; if (!silent) modLog("FollowBot stopped."); gatsModInstance?.simpleGui?.updateFollowBotStatusDisplay(); };
        static showPlayerList() { const modal = document.getElementById('player-list-modal'); const grid = document.getElementById('player-list-grid'); if (!modal || !grid || typeof Player === 'undefined' || !Player.pool || typeof selfId === 'undefined') { alert("Player data not available yet."); return; } grid.innerHTML = ''; Object.values(Player.pool).filter(p => p?.activated && p.id !== selfId && p.username).forEach(p => { const btn = document.createElement('button'); btn.className = 'player-list-button'; btn.innerText = p.username; btn.title = p.username; btn.onclick = () => { GatsModCore.SETTINGS.followBotTargetName = p.username; GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateFollowBotStatusDisplay(); gatsModInstance?.startFollowingPlayer(); modal.style.display = 'none'; }; grid.appendChild(btn); }); modal.style.display = 'flex'; };
        static startChatScroll() { if (!GatsModCore.SETTINGS.chatScrollEnabled) { alert("Chat Scroller is disabled in settings."); return; } if (GatsModCore.SETTINGS.chatScrollActive) GatsModCore.stopChatScroll(); GatsModCore.SETTINGS.chatScrollActive = true; GatsModCore.chatScrollCurrentIndex = 0; gatsModInstance?.simpleGui?.updateScrollingTextDisplay(GatsModCore.SETTINGS.chatScrollText); modLog("ChatScroller: Started."); GatsModCore.chatScrollLoop(); };
        static stopChatScroll() { if (GatsModCore.chatScrollIntervalId) { clearTimeout(GatsModCore.chatScrollIntervalId); GatsModCore.chatScrollIntervalId = null; } GatsModCore.SETTINGS.chatScrollActive = false; modLog("ChatScroller: Stopped."); };
        static chatScrollLoop() { if (!GatsModCore.SETTINGS.chatScrollActive || !GatsModCore.SETTINGS.chatScrollEnabled || GatsModCore.isInputActive) { if (!GatsModCore.SETTINGS.chatScrollEnabled || GatsModCore.isInputActive) GatsModCore.stopChatScroll(); return; } if (!Connection.list?.[0]?.socket?.send) { modLog("ChatScroller: Connection not available.", true); GatsModCore.stopChatScroll(); return; } const s = GatsModCore.SETTINGS.chatScrollText; if (!s?.length) { GatsModCore.stopChatScroll(); return; } let maxLength = GatsModCore.SETTINGS.chatScrollMaxLength; if (s.length < maxLength) maxLength = s.length; let displayText = s.substring(GatsModCore.chatScrollCurrentIndex, GatsModCore.chatScrollCurrentIndex + maxLength); if (displayText.length < maxLength && s.length > maxLength) displayText += " " + s.substring(0, maxLength - displayText.length -1); displayText = displayText.trim(); let z = displayText.replaceAll(",", "~"); try { Connection.list[0].socket.send(`c,${z}\x00`); } catch (e) { modLog(`ChatScroller: Error sending message: ${e.message}`, true); GatsModCore.stopChatScroll(); } GatsModCore.chatScrollCurrentIndex = (GatsModCore.chatScrollCurrentIndex + 1) % s.length; if (GatsModCore.SETTINGS.chatScrollActive) GatsModCore.chatScrollIntervalId = setTimeout(GatsModCore.chatScrollLoop, GatsModCore.SETTINGS.chatScrollSpeed); };
        static editScrollPreset(index) { if (GatsModCore.SETTINGS.chatPresetMessages?.[index] === undefined) return; const currentMessage = GatsModCore.SETTINGS.chatPresetMessages[index]; const newMessage = prompt(`Edit Preset ${index + 1}:`, currentMessage.trim()); if (newMessage !== null ) { GatsModCore.SETTINGS.chatPresetMessages[index] = newMessage.trim() === "" ? " " : newMessage; GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updatePresetButtonLabel(index, GatsModCore.SETTINGS.chatPresetMessages[index]); if(GatsModCore.SETTINGS.chatScrollText.trim() === currentMessage.trim()) { GatsModCore.SETTINGS.chatScrollText = GatsModCore.SETTINGS.chatPresetMessages[index]; gatsModInstance?.simpleGui?.updateScrollingTextDisplay(GatsModCore.SETTINGS.chatScrollText); const textInput = document.getElementById('chatScrollText-text-v17'); if(textInput) textInput.value = GatsModCore.SETTINGS.chatScrollText;} alert(`Preset ${index + 1} updated.`);}};
        static setScrollPreset(index) { if (GatsModCore.SETTINGS.chatPresetMessages?.[index] !== undefined) { const presetText = GatsModCore.SETTINGS.chatPresetMessages[index]; GatsModCore.SETTINGS.chatScrollText = presetText; GatsModCore.saveSettings(); gatsModInstance?.simpleGui?.updateScrollingTextDisplay(presetText); const textInput = document.getElementById('chatScrollText-text-v17'); if (textInput) textInput.value = presetText; if (Connection.list?.[0]?.socket?.send && presetText.trim() !== "") { try { Connection.list[0].socket.send(`c,${presetText.trim().replaceAll(",", "~")}\x00`); } catch (e) { modLog(`Error sending preset message: ${e.message}`, true); }} if (GatsModCore.SETTINGS.chatScrollActive) GatsModCore.startChatScroll(); modLog(`ChatScroller: Preset ${index + 1} set and sent once.`);}};
        static checkAndAcquireSkill() {
            if (typeof playerLevel === 'undefined' || typeof levelUpgrades === 'undefined' || typeof prepareMessage !== 'function' || !Connection.list?.[0]) return;
            let availableSlot = 0;
            if (playerLevel >= 3 && !levelUpgrades[3]) availableSlot = 3;
            else if (playerLevel >= 2 && !levelUpgrades[2]) availableSlot = 2;
            else if (playerLevel >= 1 && !levelUpgrades[1]) availableSlot = 1;

            if (availableSlot === 0) return;

            const priorityKey = `skillPriority${availableSlot}`;
            const priorityList = GatsModCore.SETTINGS[priorityKey]?.split(',').map(s => s.trim()).filter(Boolean) || [];
            const alreadyTakenSkills = Object.values(levelUpgrades).filter(Boolean);

            for (const skillName of priorityList) {
                // Ensure we don't pick a skill already taken in another slot
                if (alreadyTakenSkills.includes(skillName)) continue;

                modLog(`Auto-upgrading Tier ${availableSlot} with skill: ${skillName}`);
                try {
                    Connection.list[0].send(prepareMessage('upgrade', { 'upgrade': skillName, 'upgradeLevel': availableSlot }));
                    return; // Stop after acquiring one skill
                } catch (e) {
                    modLog(`Error sending skill upgrade command for '${skillName}': ${e.message}`, true);
                }
            }
        }
    }

    // --- Launcher Logic ---
    let gatsModInstance = null;
    let game_original_updateMouseData_ref = null;
    let modInitializationAttempted = false;

    const gatsModLauncherInterval = setInterval(() => {
        if (modInitializationAttempted) { clearInterval(gatsModLauncherInterval); return; }

        if (typeof updateMouseData === 'function' && typeof Player !== 'undefined' && Player.pool && typeof Connection !== 'undefined' && Connection.list?.[0]?.socket && typeof prepareMessage === 'function' && typeof camera === 'object' && camera?.ctx && typeof selfId !== 'undefined' && document.getElementById('canvas')) {
            modLog(`[Launcher] Game ready. Initializing mod.`);
            clearInterval(gatsModLauncherInterval);
            modInitializationAttempted = true;

            game_original_updateMouseData_ref = updateMouseData;

            updateMouseData = function(eventData) {
                if (!gatsModInstance) {
                    try {
                        gatsModInstance = new GatsModCore();
                        gatsModInstance.originalUpdateMouseData = game_original_updateMouseData_ref;

                        const original_clearRect = CanvasRenderingContext2D.prototype.clearRect;
                        CanvasRenderingContext2D.prototype.clearRect = function(...args) {
                            original_clearRect.apply(this, args);
                            if (gatsModInstance && this.canvas.id === 'canvas' && Player.pool?.[selfId]?.activated) {
                                try { gatsModInstance.mainGameTick(); } catch (e) { modLog(`mainGameTick error: ${e.stack}`, true); }
                            }
                        };
                        modLog(`[Launcher] clearRect hooked for game tick.`);
                    } catch (e) {
                        modLog(`[Launcher] GatsModCore instantiation failed: ${e.stack}`, true);
                        updateMouseData = game_original_updateMouseData_ref; // Revert
                        return;
                    }
                }
                return game_original_updateMouseData_ref.call(this, eventData);
            };

            if (typeof processMessage === 'function') {
                let originalProcessMessage = processMessage;
                processMessage = function(event) {
                    originalProcessMessage(event);
                    try {
                        let decoded = new TextDecoder().decode(event.data);
                        if (decoded.includes("a,") && typeof camera !== 'undefined' && camera.update) {
                             camera.update = function() {
                                let player = Player.pool[selfId];
                                if (camera.trackingId && player) {
                                    camera.x = player.x - hudXPosition;
                                    camera.y = player.y - hudYPosition;
                                }
                            }
                        }
                    } catch (e) { /* ignore */ }
                }
            }
            modLog(`[Launcher] updateMouseData wrapped. Mod will fully initialize on first mouse event.`);
        }
    }, 250);

    setTimeout(() => {
        if (!modInitializationAttempted) {
            modLog(`[Launcher] Timeout: Game did not initialize required components. Mod will not start.`, true);
            clearInterval(gatsModLauncherInterval);
        }
    }, 20000);

})();
(function() {
    'use strict';

    window.addEventListener ("load", onload);

    function onload() {
        let processMessageTmp = processMessage;
        processMessage = function(event) {
            processMessageTmp(event);
            let decoded = new TextDecoder().decode(event.data);
            if (!decoded.includes("a,")) return;
            camera.update = function() {
                let player = Player.pool[selfId];
                if (camera.trackingId) {
                    camera.x = player.x - hudXPosition;
                    camera.y = player.y - hudYPosition;
                }
            }
        }
    }
})();