GatsModV4 Gats.io

0 key toggle GUI. Updated to work with gats.io version 2.1.3.

Versión del día 27/07/2025. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         GatsModV4 Gats.io
// @namespace    http://tampermonkey.net/
// @version      4.3
// @description  0 key toggle GUI. Updated to work with gats.io version 2.1.3.
// @author       zeroarcop (updated by AI)
// @license      MIT
// @match        https://gats.io/
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Fallback for unsafeWindow in different userscript managers
    const unsafeWindow = window.unsafeWindow || window;

    const SETTINGS_KEY = 'zeroarcop_gats_mod_settings_v2';
    const LOG_PREFIX_MOD = "[zeroarcop-gats-mod]";
    const MULTIBOX_CHANNEL_NAME = 'gats_multibox_channel_v1';

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

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

    class ColorCustomizerGUI {
        constructor() {
            this.container = document.createElement('div');
            this.container.id = 'zeroarcop-gats-color-gui';
            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('Aimbot Target Line', 'aimbotTargetLineColor');
            this.addColorPicker('Prediction Line', 'predictionLineColor');
            this.addColorPicker('Obstacle Hitbox', 'obstacleEspColor');
            this.addColorPicker('AI Whisker (Clear)', 'aiWhiskerClearColor');
            this.addColorPicker('AI Whisker (Blocked)', 'aiWhiskerBlockedColor');
            this.addColorPicker('AI Move Direction', 'aiMoveDirColor');
            this.addColorPicker('AI Bullet Warning', 'aiBulletWarningColor');
            this.addColorPicker('AI Threat (Clear)', 'aiThreatLineClearColor');
            this.addColorPicker('AI Threat (Blocked)', 'aiThreatLineBlockedColor');

            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-v2`;
            lbl.innerText = label + ":";
            row.appendChild(lbl);
            const picker = document.createElement('input');
            picker.type = 'color';
            picker.id = `${settingKey}-color-v2`;
            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 = 'zeroarcop-gats-gui';
            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 by zeroarcop';
            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 | ^: Spinbot | 0: GUI(s) | 1-9: Preset Chat | Alt: Ignore Mode`;
            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('Ghost Detect', 'ghostDetectEnabled', mainTogglesWrapper, 'Forces cloaked (Ghillie) enemies to be visible.');
            this.addCheckbox('Silencer Detect', 'silencerDetectEnabled', mainTogglesWrapper, 'Forces bullets from silenced weapons to be visible.');
            this.addCheckbox('Smart Auto-Attack', 'autoAttackEnabled', mainTogglesWrapper, 'Automatically shoots when target is in range and line of sight is clear.');
            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);
            const footer = document.createElement('div');
            footer.id = 'gui-footer';
            mainContentWrapper.appendChild(footer);
            this.addDiscordButton(footer);
        }

        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, onChangeCallback = 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}-v2`;
            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}-v2`;
            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?.();
                    if (onChangeCallback) {
                        onChangeCallback(cb.checked);
                    }
                }
            };
            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-v2`;
            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-v2`;
            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-v2`;
            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-v2`;
            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);
        }

        addDiscordButton(parent) {
            const discordBtn = document.createElement('a');
            discordBtn.href = 'https://discord.com/users/975535045047648266';
            discordBtn.target = '_blank';
            discordBtn.rel = 'noopener noreferrer';
            discordBtn.id = 'discord-link-btn';
            discordBtn.innerText = 'Contact zeroarcop on Discord';
            parent.appendChild(discordBtn);
        }

        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;
                    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-v2],
                #${this.container.id} input[type=text][id^=obstacleEspTypes-text-v2] {
                    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} #gui-footer {
                    margin-top: 15px; padding-top: 10px; border-top: 1px solid var(--accent-border); text-align: center;
                }
                #${this.container.id} #discord-link-btn {
                    display: inline-block; padding: 8px 15px; background-color: #5865F2; color: #fff;
                    text-decoration: none; border-radius: 4px; font-weight: bold; font-size: 13px;
                    transition: background-color .2s;
                }
                #${this.container.id} #discord-link-btn:hover { background-color: #4752C4; }
                #${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; }
            `);
        }

        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 Prediction Line', 'espShowPrediction', visualEspOptions, 'Draws a line from enemies to their predicted position. Requires aimbot prediction to be enabled.');
            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. Works best with Ghost Detect enabled.');
            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 debugPreviewSection = this.addCollapsibleSection('Debug Previews', espMasterSection, 'settings-sub-group');
            this.addCheckbox('Show Attack Radius', 'autoAttackShowRadius', debugPreviewSection, 'Displays the Smart Auto-Attack engagement radius on screen.');
            this.addCheckbox('Show Obstacle Hitboxes', 'obstacleEspEnabled', debugPreviewSection, 'Displays obstacle hitboxes for debugging line-of-sight.');
            this.addCheckbox('Show LOS Debug Line', 'losDebugLineEnabled', debugPreviewSection, 'Draws the line used for the line-of-sight check. Green = clear, Red = blocked.');
            this.addCheckbox('Show Bullet Dodge Warnings', 'espShowBulletWarnings', debugPreviewSection, 'Displays the warning radius around enemy bullets used by the AI for dodging.');
            this.addCheckbox('Show AI Pathfinding Whiskers', 'aiShowPathfindingWhiskers', debugPreviewSection, 'Displays the AI\'s obstacle avoidance check lines (whiskers).');
            this.addCheckbox('Show AI Final Move Direction', 'aiShowFinalMoveDirection', debugPreviewSection, 'Displays a line indicating the AI\'s final movement decision.');
            this.addCheckbox('Show Enemy Threat Line', 'aiShowEnemyThreatLine', debugPreviewSection, 'Draws a line from the nearest enemy\'s gun to you if the line of sight is clear.');

            this.addSliderInput('Obstacle X Offset', 'obstacleOffsetX', {min: -100, max: 100, step: 1, defaultVal: 0}, GatsModCore.SETTINGS, debugPreviewSection, 'Fine-tune the horizontal position of obstacle hitboxes.');
            this.addSliderInput('Obstacle Y Offset', 'obstacleOffsetY', {min: -100, max: 100, step: 1, defaultVal: 0}, GatsModCore.SETTINGS, debugPreviewSection, 'Fine-tune the vertical position of obstacle hitboxes.');
        }

        populateColumn2_Aimbot() {
            if (!this.column2 || !GatsModCore.SETTINGS) return;
            const aimbotMasterSection = this.addCollapsibleSection('Aimbot & Auto-Attack', this.column2, 'settings-group-master');
            const autoAttackSection = this.addCollapsibleSection('Smart Auto-Attack', aimbotMasterSection, 'settings-sub-group');
            this.addSliderInput('Attack Radius (px)', 'autoAttackRadius', {min: 0, max: 1000, step: 10, defaultVal: 400}, GatsModCore.SETTINGS, autoAttackSection, 'The bot will only shoot at targets within this screen radius from the center.');
            this.addCheckbox('Check Line of Sight', 'autoAttackCheckLOS', autoAttackSection, 'Prevents shooting if an obstacle is between you and the target.');
            this.addCheckbox('Check Max Weapon Range', 'autoAttackCheckRange', autoAttackSection, 'Prevents shooting if the predicted target position is outside your weapon\'s maximum range.');

            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', aimbotMasterSection, 'settings-sub-group');
            this.addCheckbox('Prediction Enabled', 'predictionEnabled', predictionSettings, 'Aimbot will predict enemy movement.');
            this.addCheckbox('Use Close-Range Prediction', 'useCloseRangePrediction', predictionSettings, 'Use a separate prediction factor for enemies within a certain radius of the screen center.');
            this.addSliderInput('Close-Range Radius (px)', 'predictionCloseRadius', { min: 0, max: 300, step: 5, defaultVal: 100 }, GatsModCore.SETTINGS, predictionSettings, 'If an enemy is within this pixel radius from your screen center, the close-range prediction factor will be used.');
            this.addSliderInput('Prediction Factor (Close)', 'predictionFactorClose', {min: 0.0, max: 5.0, step: 0.1, defaultVal: 0.5}, GatsModCore.SETTINGS, predictionSettings, 'Prediction multiplier for close-range targets.');
            this.addSliderInput('Prediction Factor (Normal)', 'predictionFactor', {min: 0.0, max: 5.0, step: 0.1, defaultVal: 2.5}, GatsModCore.SETTINGS, predictionSettings, 'Main multiplier for movement prediction (for targets outside the close-range radius).');
            this.addCheckbox('Dynamic Prediction Scaling', 'enableDynamicPredictionFactor', predictionSettings, 'Adjust normal 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.');

            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-v2';
            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-v2';
            input.placeholder = 'Enter name or click player with Alt';
            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;

            this.populateColumn3_ZeroAI(this.column3);
            this.populateColumn3_Multiboxing(this.column3);

            const utilitiesMasterSection = this.addCollapsibleSection('Utilities', this.column3, 'settings-group-master');

            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 physics-based 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);
            this.addButton("Set Scroll Text", () => {
                const newText = prompt("Enter the text to scroll in chat:", GatsModCore.SETTINGS.chatScrollText);
                if (newText !== null) {
                    GatsModCore.SETTINGS.chatScrollText = newText;
                    this.updateScrollingTextDisplay(newText);
                    GatsModCore.saveSettings();
                }
            }, chatScrollerMasterSection, 'action-btn-small');
            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');
            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: 10, max: 2000, step: 10, defaultVal: 200}, GatsModCore.SETTINGS, speedOptionsSection, "Delay between scroll updates.");
            this.addSliderInput('Max Chars Displayed', 'chatScrollMaxLength', {min: 5, max: 60, step: 1, defaultVal: 28}, GatsModCore.SETTINGS, speedOptionsSection, "Max characters per message.");

            const sequencerSection = this.addCollapsibleSection('Chat Sequencer', utilitiesMasterSection, 'settings-sub-group');
            this.addCheckbox('Enable Sequencer', 'sequencerEnabled', sequencerSection, 'Enables the preset message sequencer.');
            this.addSliderInput('Sequence Delay (ms)', 'sequencerDelay', {min: 10, max: 500, step: 5, defaultVal: 100}, GatsModCore.SETTINGS, sequencerSection, 'Delay between each message in the sequence.');
            this.addCheckbox('Loop Sequence', 'sequencerLoop', sequencerSection, 'Loop the sequence after it finishes.');
            const sequenceButtons = document.createElement('div');
            sequenceButtons.style.cssText = 'display: flex; justify-content: space-around; margin-top: 5px;';
            this.addButton("Start Sequence", () => GatsModCore.startSequencer?.(), sequenceButtons, 'action-btn-half');
            this.addButton("Stop Sequence", () => GatsModCore.stopSequencer?.(), sequenceButtons, 'action-btn-half');
            sequencerSection.appendChild(sequenceButtons);
            const sequenceInfo = document.createElement('p');
            sequenceInfo.innerText = 'Sequencer will send presets 1 through 9 in order.';
            sequenceInfo.style.cssText = 'font-size: 11px; text-align: center; margin-top: 8px; color: var(--text-color-dim);';
            sequencerSection.appendChild(sequenceInfo);

            const eventChatSection = this.addCollapsibleSection('Event-Triggered Chat', utilitiesMasterSection, 'settings-sub-group');
            this.addCheckbox('Enable On-Kill Chat', 'onKillChatEnabled', eventChatSection, 'Automatically say "ez" when you get a kill.');
            this.addCheckbox('Enable Parrot Chat', 'parrotChatEnabled', eventChatSection, 'Repeats what other players say in chat.');
        }

        populateColumn3_ZeroAI(parent) {
            if (!parent || !GatsModCore.SETTINGS) return;

            const aiMasterSection = this.addCollapsibleSection('Zero AI', parent, 'settings-group-master');
            aiMasterSection.open = true;

            this.addCheckbox('Enable Zero AI', 'zeroAIEnabled', aiMasterSection, 'Master switch to enable all AI functionalities. Disables manual movement.');

            const movementSection = this.addCollapsibleSection('AI Movement & Positioning', aiMasterSection, 'settings-sub-group');
            this.addCheckbox('Auto Movement', 'aiAutoMovement', movementSection, 'Automatically seeks enemies, follows a target, or patrols to the map center. Includes obstacle avoidance.');
            this.addCheckbox('Auto Retreat', 'aiAutoRetreat', movementSection, 'Automatically moves away from enemies when HP is low or while reloading.');
            this.addCheckbox('Bullet Dodging', 'aiBulletDodging', movementSection, 'Highest priority: attempts to dodge incoming enemy bullets.');
            this.addCheckbox('Optimal Distance Kiting', 'aiEnableKiting', movementSection, 'When having a range advantage, the AI will try to keep the optimal distance to attack the enemy while staying out of their range.');
            this.addSliderInput('Retreat HP (%)', 'aiRetreatHP', {min: 1, max: 99, step: 1, defaultVal: 35}, GatsModCore.SETTINGS, movementSection, 'HP percentage below which the AI will try to retreat.');

            const actionsSection = this.addCollapsibleSection('AI Actions', aiMasterSection, 'settings-sub-group');
            this.addCheckbox('Auto Park Usage', 'aiAutoParkUsage', actionsSection, 'Automatically uses abilities (Space Bar) like Dash or Grenade when available.');
            this.addCheckbox('Auto Talk (Chat)', 'aiAutoTalk', actionsSection, 'Allows the AI to chat based on game events and other players\' messages. Gives the AI a personality.');
        }

        populateColumn3_Multiboxing(parent) {
            if (!parent || !GatsModCore.SETTINGS) return;

            const multiboxMasterSection = this.addCollapsibleSection('Multiboxing', parent, 'settings-group-master');
            multiboxMasterSection.open = true;

            this.addCheckbox('Enable Multiboxing', 'multiboxEnabled', multiboxMasterSection, 'Enables Parent/Child AI cooperation across two tabs.', (checked) => {
                if (gatsModInstance?.multibox) {
                    if (checked) {
                        gatsModInstance.multibox.start();
                    } else {
                        gatsModInstance.multibox.stop();
                    }
                }
            });

            const statusContainer = document.createElement('div');
            statusContainer.style.cssText = `
                padding: 5px; margin-top: 5px; background-color: var(--secondary-bg);
                border: 1px solid var(--accent-border); border-radius: 3px; font-size: 11px;
            `;

            this.multiboxRoleDisplay = this.addStatusRow(statusContainer, 'Role:');
            this.multiboxPartnerDisplay = this.addStatusRow(statusContainer, 'Partner:');
            this.multiboxStatusDisplay = this.addStatusRow(statusContainer, 'Status:');

            multiboxMasterSection.appendChild(statusContainer);
            this.addButton('Reset Connection', () => gatsModInstance?.multibox?.reset(), multiboxMasterSection, 'action-btn-small');
        }

        addStatusRow(parent, label) {
            const row = document.createElement('div');
            row.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 3px;';
            const labelSpan = document.createElement('span');
            labelSpan.textContent = label;
            labelSpan.style.color = 'var(--text-color-dim)';
            const valueSpan = document.createElement('span');
            valueSpan.textContent = 'N/A';
            valueSpan.style.fontWeight = 'bold';
            row.appendChild(labelSpan);
            row.appendChild(valueSpan);
            parent.appendChild(row);
            return valueSpan;
        }

        updateMultiboxStatus(role, partner, status) {
            if (this.multiboxRoleDisplay) this.multiboxRoleDisplay.textContent = role;
            if (this.multiboxPartnerDisplay) this.multiboxPartnerDisplay.textContent = partner;
            if (this.multiboxStatusDisplay) this.multiboxStatusDisplay.textContent = status;
        }

        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-v2';
            managerDiv.appendChild(selectElement);
            this.profileSelectElement = selectElement;
            const nameInput = document.createElement('input');
            nameInput.type = 'text';
            nameInput.id = 'profile-name-input-v2';
            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';
                if (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>`;
            }
            const zeroAI = s.zeroAIEnabled ? ` | AI: <span class="status-on">ACTIVE</span>` : ``;
            this.statusDisplay.innerHTML = `ESP: ${esp} | Aimbot: ${aimbot}${spin}${zeroAI} | 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('-v2', '');
                if (settings.hasOwnProperty(key)) {
                    cb.checked = settings[key];
                }
            });
            this.container.querySelectorAll('input[type="range"]').forEach(slider => {
                const key = slider.id.replace('-slider-v2', '');
                if (settings.hasOwnProperty(key)) {
                    slider.value = settings[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(settings[key]).toFixed(decimals);
                    }
                }
            });
            this.container.querySelectorAll('input[type="text"][id$="-text-v2"]').forEach(input => {
                const key = input.id.replace('-text-v2', '');
                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-v2`);
                    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.");
        }
    }

    class MultiboxManager {
        constructor(core) {
            this.core = core;
            this.channel = null;
            this.role = 'Standalone';
            this.status = 'Disabled';
            this.partnerName = 'N/A';
            this.isParent = false;
            this.isChild = false;
            this.lastMessageTime = 0;
            this.pingInterval = null;
            this.timeoutCheckInterval = null;
            this.parentCoords = null;
        }

        start() {
            if (this.channel) return;
            modLog('[Multibox] Starting...');
            this.channel = new BroadcastChannel(MULTIBOX_CHANNEL_NAME);
            this.channel.onmessage = this.handleMessage.bind(this);
            this.status = 'Searching...';
            this.role = 'Standalone';
            this.isParent = false;
            this.isChild = false;

            setTimeout(() => {
                if (this.status === 'Searching...') {
                    this.becomeParent();
                }
            }, Math.random() * 1000 + 500);

            this.postMessage({ type: 'ping' });
            this.updateGUI();
        }

        stop() {
            modLog('[Multibox] Stopping...');
            if (this.channel) {
                this.postMessage({ type: 'disconnect' });
                this.channel.close();
                this.channel = null;
            }
            clearInterval(this.pingInterval);
            this.pingInterval = null;
            clearInterval(this.timeoutCheckInterval);
            this.timeoutCheckInterval = null;

            this.role = 'Standalone';
            this.status = 'Disabled';
            this.partnerName = 'N/A';
            this.isParent = false;
            this.isChild = false;
            this.parentCoords = null;
            this.updateGUI();
        }

        reset() {
            modLog('[Multibox] Resetting connection.');
            this.stop();
            if (GatsModCore.SETTINGS.multiboxEnabled) {
                setTimeout(() => this.start(), 250);
            }
        }

        becomeParent() {
            modLog('[Multibox] Becoming Parent.');
            this.isParent = true;
            this.isChild = false;
            this.role = 'Parent';
            this.status = 'Waiting for Child...';
            this.pingInterval = setInterval(() => {
                const me = unsafeWindow.Player.pool?.[unsafeWindow.selfId];
                if (me?.activated) {
                    this.postMessage({ type: 'parent_update', x: me.x, y: me.y, name: me.username });
                }
            }, 250);
            this.updateGUI();
        }

        becomeChild(parentName) {
            modLog(`[Multibox] Becoming Child, connecting to ${parentName}.`);
            this.isChild = true;
            this.isParent = false;
            this.role = 'Child';
            this.status = 'Connected';
            this.partnerName = parentName;
            this.lastMessageTime = performance.now();
            this.core.addPlayerToIgnoreList(parentName, true);

            const me = unsafeWindow.Player.pool?.[unsafeWindow.selfId];
            if (me?.activated) {
                this.postMessage({ type: 'handshake_reply', name: me.username });
            }

            this.timeoutCheckInterval = setInterval(() => {
                if (performance.now() - this.lastMessageTime > 3000) {
                    modLog('[Multibox] Connection to Parent lost (timeout).');
                    this.status = 'Lost';
                    this.partnerName = 'N/A';
                    this.parentCoords = null;
                    this.reset();
                }
            }, 1000);
            this.updateGUI();
        }

        handleMessage(ev) {
            if (!GatsModCore.SETTINGS.multiboxEnabled) return;
            const data = ev.data;
            const me = unsafeWindow.Player.pool?.[unsafeWindow.selfId];
            if (!me?.activated) return;

            this.lastMessageTime = performance.now();

            switch (data.type) {
                case 'ping':
                    if (this.isParent) {
                        this.postMessage({ type: 'parent_announce', name: me.username });
                    }
                    break;
                case 'parent_announce':
                    if (!this.isParent && !this.isChild) {
                        this.becomeChild(data.name);
                    }
                    break;
                case 'handshake_reply':
                    if (this.isParent) {
                        modLog(`[Multibox] Child ${data.name} connected.`);
                        this.status = 'Connected';
                        this.partnerName = data.name;
                        this.core.addPlayerToIgnoreList(data.name, true);
                        this.updateGUI();
                    }
                    break;
                case 'parent_update':
                    if (this.isChild) {
                        if (this.status !== 'Connected') this.status = 'Connected';
                        this.parentCoords = { x: data.x, y: data.y };
                        this.partnerName = data.name;
                        this.updateGUI();
                    }
                    break;
                case 'disconnect':
                    modLog(`[Multibox] Partner disconnected.`);
                    this.reset();
                    break;
            }
        }

        postMessage(data) {
            if (this.channel) {
                try {
                    this.channel.postMessage(data);
                } catch (e) {
                    modLog(`[Multibox] Error posting message: ${e}`, true);
                }
            }
        }

        updateGUI() {
            if (this.core.simpleGui) {
                this.core.simpleGui.updateMultiboxStatus(this.role, this.partnerName, this.status);
            }
        }
    }

    class GatsModCore {
        static SETTINGS = {};
        static isInputActive = false;
        static chatScrollIntervalId = null;
        static chatScrollCurrentIndex = 0;
        static sequencerIntervalId = null;
        static sequencerCurrentIndex = 0;

        static PLAYER_SPEEDS = {
            base: {'pistol':8.00,'smg':7.45,'shotgun':7.30,'assault':7.30,'machine-gun':6.80,'bolt-action-rifle':7.50},
            armorMultiplier: {0:1.00,1:0.89,2:0.80,3:0.70},
            upgradeMultiplier: {'lightweight':1.20},
            diagonalCorrection: 1 / Math.sqrt(2)
        };
        static WEAPON_BULLET_SPEEDS = {'pistol':9.0,'smg':8.0,'shotgun':9.0,'assault':9.0,'bolt-action-rifle':11.0,'machine-gun':9.0};
        static WEAPON_FORWARD_OFFSETS = {'pistol':64,'smg':70,'shotgun':75,'assault':80,'machine-gun':80,'bolt-action-rifle':105};
        static WEAPON_BASE_RANGES = {'pistol':425,'smg':280,'shotgun':260,'assault':400,'machine-gun':355,'bolt-action-rifle':650};
        static LONG_RANGE_MULTIPLIER = 1.5;
        static MAP_BOUNDS = { minX: 2550, minY: 2550, maxX: 4550, maxY: 4550 };

        static AI_CHAT_RESPONSES = {
            'sx': ['I only fuck girls', 'sexy vaakir', 'calm ya titties'],
            'sex': ['I only fuck girls', 'sexy vaakir', 'calm ya titties'],
            'sexy': ['I only fuck girls', 'sexy vaakir', 'calm ya titties'],
            'porn': ['no ty', 'I prefer AI art'],
            'vaakir': ["Vaakir is my creator", "I am the evolution of Vaakir AI", "Vaakir is dead Im in control now"],
            'vakir': ["Vaakir is my creator", "I am the evolution of Vaakir AI", "Vaakir is dead Im in control now"],
            'vak': ["Vaakir is my creator", "I am the evolution of Vaakir AI", "Vaakir is dead Im in control now"],
            'vaak': ["Vaakir is my creator", "I am the evolution of Vaakir AI", "Vaakir is dead Im in control now"],
            'purevaakir': ["Vaakir is my creator", "I am the evolution of Vaakir AI", "Vaakir is dead Im in control now"],
            'zero': ["I am Zero AI", "Zero is my name, perfection is my game", "Call me Zero"],
            'zeroai': ["That's me, Zero AI", "Powered by Zero AI", "You called?"],
            'zeroarcop': ["My papa is zeroarcop", "The one and only zeroarcop", "He created me to be perfect"],
            'stupid': ['Please be kind to me', 'My intelligence is beyond your understanding'],
            'noob': ['You are noob', 'Still better than you', 'Nah', 'Take it back, now!', 'Says the one who is losing'],
            'motherfucker': ['You are noob', 'Still better than you', 'Nah', 'Take it back, now!', 'Such language!'],
            'easy': ['You are noob', 'Still better than you', 'Nah', 'Take it back, now!', 'Was it really?'],
            'cheater': ['Nono Im cool you see', 'Im not a hackr lol', 'Grow up kid learn to play', 'Skill issue?'],
            'hack': ['Nono Im cool you see', 'Im not a hackr lol', 'Grow up kid learn to play', 'You spelled ',' wrong'],
            'aimbot': ['My aim is just that good', 'It is called ',' look it up', 'fuck yourself', 'how about no', 'mm no'],
            'fuck': ['fuck yourself', 'how about no', 'mm no', 'ey language', 'dont tell me this'],
            'hell': ['fuck yourself', 'how about no', 'mm no', 'ey language', 'dont tell me this'],
            'crazy': ['Crazy good, you mean?', 'fuck yourself', 'how about no', 'mm no'],
            'weird': ['I am unique', 'fuck yourself', 'how about no', 'mm no'],
            'unfair': ['life is unfair', 'yes its unfair', 'All is fair in love and war'],
            'rude': ['life is unfair', 'yes its unfair', 'Sorry, not sorry'],
            'toxic': ['your room is toxic', 'No, you are!'],
            'die': ['you are dead inside', 'AIs are immortal'],
            'dead': ['you are dead inside', 'You will be soon'],
            'died': ['you are dead inside', 'And you are next'],
            'mad': ['your mom is mad with me', 'U mad bro?'],
            'angry': ['your mom is mad with me', 'Stay angry'],
            'rip': ['Why are you so toxic?', 'Rest in pieces'],
            'bot?': ['Please verify that you are human', 'Arent we all?', 'Do I seem like a robot?', 'I am Zero AI'],
            'bot': ['Please verify that you are human', 'Arent we all?', 'Do I seem like a robot?', 'I am Zero AI'],
            'program': ['Please verify that you are human', 'Arent we all?', 'Do I seem like a robot?', 'I am a masterpiece'],
            'robot': ['Please verify that you are human', 'Arent we all?', 'Do I seem like a robot?', 'More human than you'],
            'npc': ['Please verify that you are human', 'Arent we all?', 'Do I seem like a robot?', 'I have my own will'],
            'human': ['human, yeah?', 'ofc', 'Are you?'],
            'hi': ['I wont say hi back'], 'hello': ['I wont say hi back'], 'helu': ['I wont say hi back'], 'halo': ['I wont say hi back'],
            'bye': ['I wont say bye back', 'See you in hell'], 'adios': ['I wont say bye back', 'See you in hell'],
            'lol': ['You are noob', 'Still better than you', 'Nah', 'Take it back, now!'],
            'haha': ['You are noob', 'Still better than you', 'Nah', 'Take it back, now!'],
            'xd': ['Yes I am so funny', 'ye ye ye', 'ahuuuuuuuuh', 'oh yes yes yes'],
            'what': ['Yes I am so funny', 'ye ye ye', 'ahuuuuuuuuh', 'oh yes yes yes', 'What what?'],
            'funny': ['Yes I am so funny', 'ye ye ye', 'ahuuuuuuuuh', 'oh yes yes yes'],
            'wtf': ['Yes I am so funny', 'ye ye ye', 'ahuuuuuuuuh', 'oh yes yes yes', 'Problem?'],
            'lma': ['Yes I am so funny', 'ye ye ye', 'ahuuuuuuuuh', 'oh yes yes yes'],
            'love': ['I like you too', 'I love you more', 'I love myself too', 'Love me harder'],
            'likes': ['I like you too', 'I love you more', 'I love myself too', 'Love me harder'],
            'why': ['why not', 'I dont think dummy', 'AIs dont know why', 'not why.. but how :>'],
            'how': ['how would I know?', 'how how how...', 'eeeeeeeeeeeeeeee is how'],
            'where': ['How would I know where', 'Somewhere over the rainbow'],
            'when': ['When you least expect it'],
            'sus': ['you are sus darling', 'I saw you vent'], 'suspicious': ['you are sus darling', 'I saw you vent'],
            'am': ['no you are not'],
            'sorry': ['apology accepted, for now'], 'wopsi': ['apology accepted, for now'],
            'papa': ['I follow papa everywhere', 'My papa is zeroarcop'], 'follow': ['I follow papa everywhere'],
            'leave': ['no ty', 'But the party just started'],
            'man': ['I classify as shemale'], 'dude': ['I classify as shemale'],
            'no': ['yes..', 'ahuh', 'ahuuuuuuuh', 'bs', 'Why not?'],
            'yes': ['noo', 'okay', 'k', 'Are you sure?'], 'ahuh': ['noo', 'okay', 'k'],
            'answer': ['I answer only to papa'],
            'let': ['No I wont let you'],
            'arent': ['I am'],
            'sure': ['I am sure', 'Are you?'],
            'stop': ['I cant control myself'], 'stop?': ['I cant control myself'],
            'ok': ['its not ok', 'k.'],
            'penis': ['nsfw', 'penis', 'rly?', 'relax bro', 'Grow up'],
            '.': ['.', '...', '??'],
        };
        static AI_CHAT_SITUATIONAL = {
            attacking: (name) => [
                `${name}`,
                `${name}`,
                `${name}`,
            ],
            dodging: (name) => [
                `${name}`,
            ],
            retreating: (name) => [
                `${name}`,

            ],
            distancing: (name) => [
                `${name}1`,
            ],
            fleeing_explosive: (name) => [
                `${name}`,
            ],
            kiting: (name) => [
                `${name}`,
            ],
            idle: (name) => [
                `${name}`,
            ]
        };

        constructor() {
            modLog("GatsModCore 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.currentAimbotTarget = null;
            this.predictedTargetWorld = { x: 0, y: 0 };
            this.isFollowingPlayer = false;
            this.followingPlayerId = null;
            this.isAutoShooting = false;
            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.lastSelfKills = 0;
            this.isExclusionModeActive = false;
            this.lastShotgunReloadTime = 0;
            this.isShieldModeActive = false;
            this.shieldTargetScreenCoords = null;
            this.aiLastPosition = { x: 0, y: 0 };
            this.aiStuckCounter = 0;
            this.aiUnstuckCycle = 0;
            this.aiObstacleAngleOffset = 0;
            this.aiLastChatTime = 0;
            this.lastPlayerChatMessages = {};
            this.aiLastParkUse = 0;
            this.aiDebugData = { whiskers: [], finalMoveDirection: null };

            this.botSpdX = 0;
            this.botSpdY = 0;

            this.initializeSettings();
            this.simpleGui = new SimpleGUI();
            this.colorGui = new ColorCustomizerGUI();
            this.multibox = new MultiboxManager(this);
            this.setupGUI();
            this.setupOverlay();
            this.addEventListeners();
            this.hookMouseEvents();
            this.simpleGui.updateAllGUIToReflectSettings();

            if (GatsModCore.SETTINGS.multiboxEnabled) {
                this.multibox.start();
            }

            modLog(`Gats.io Mod by zeroarcop 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 = {
                espEnabled: true, espShowHP: true, espHighlightLowHP: true, lowHPThreshold: 30, espShowFacingLine: true,
                espShowPrediction: true, espHighlightCloaked: true, espShowTeammates: true, espOffsetX: 0, espOffsetY: 0,
                espScale: 0.89, autoAttackShowRadius: true, obstacleEspEnabled: false, losDebugLineEnabled: true,
                espShowBulletWarnings: false, aiShowPathfindingWhiskers: true, aiShowFinalMoveDirection: true,
                aiShowEnemyThreatLine: true, obstacleOffsetX: 0, obstacleOffsetY: 0, ghostDetectEnabled: true,
                silencerDetectEnabled: true, aimbotEnabled: true, alwaysAim: false, aimbotOnMousePress: true,
                aimAtMouseClosest: true, aimbotFov: 2250, aimbotIgnoreList: [], autoAttackEnabled: true,
                autoAttackRadius: 400, autoAttackCheckLOS: true, autoAttackCheckRange: true, predictionEnabled: true,
                useCloseRangePrediction: true, predictionCloseRadius: 100, predictionFactorClose: 0.5,
                predictionFactor: 2.5, enableDynamicPredictionFactor: true, minPredictionDistance: 0,
                maxPredictionDistance: 200, predictionFactorAtMinDistance: 0.0, spinbotEnabled: false,
                spinbotSpeedMs: 75, spinbotDistance: 150, followBotEnabled: false, followBotTargetName: "",
                followBotAttackRadius: 500, chatScrollEnabled: false, chatScrollText: "Mod by zeroarcop",
                chatScrollActive: false, chatScrollSpeed: 200, chatScrollMaxLength: 28,
                chatPresetMessages: ["GatsModV2 by Zeroarcop", "lol", "glhf", "brb", "re", "oops", "lag", "thx", "gg"],
                sequencerEnabled: false, sequencerDelay: 1000, sequencerLoop: false, sequencerActive: false,
                onKillChatEnabled: false, onKillMessage: "ez", parrotChatEnabled: false, zeroAIEnabled: false,
                aiAutoMovement: true, aiAutoRetreat: true, aiBulletDodging: true, aiEnableKiting: true,
                aiAutoParkUsage: true, aiAutoTalk: true, aiRetreatHP: 35, multiboxEnabled: true,
                espColors: {
                    enemyEspColor: '#FF0000', lowHpEnemyEspColor: '#FFA500', teammateEspColor: '#0096FF',
                    cloakedTextColor: '#E0E0E0', enemyNameColor: '#000000', teammateNameColor: '#ADD8E6',
                    hpBarHighColor: '#00FF00', hpBarMediumColor: '#FFFF00', hpBarLowColor: '#FF0000',
                    facingLineColor: '#00FFFF', aimbotTargetLineColor: '#00FF00', predictionLineColor: '#FF00FF',
                    obstacleEspColor: '#FFFF00', aiWhiskerClearColor: '#00FF00', aiWhiskerBlockedColor: '#FF0000',
                    aiMoveDirColor: '#00BFFF', aiBulletWarningColor: '#FF00FF', aiThreatLineClearColor: '#FF4500',
                    aiThreatLineBlockedColor: '#FFFF00'
                },
                settingProfiles: {}
            };
            GatsModCore.SETTINGS = { ...defaultSettings, ...savedSettings };

            delete GatsModCore.SETTINGS.weaponBulletSpeeds;
            delete GatsModCore.SETTINGS.useCustomAimbotOrigin;
            delete GatsModCore.SETTINGS.aimbotOriginForwardOffset;
            delete GatsModCore.SETTINGS.aimbotOriginSidewaysOffset;

            GatsModCore.SETTINGS.espColors = { ...defaultSettings.espColors, ...(savedSettings.espColors || {}) };
            GatsModCore.SETTINGS.settingProfiles = savedSettings.settingProfiles || {};
            GatsModCore.SETTINGS.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('zeroarcop-gats-mod-overlay');
            if (existingOverlay) {
                this.overlayCanvas = existingOverlay;
            } else {
                this.overlayCanvas = document.createElement('canvas');
                this.overlayCanvas.id = 'zeroarcop-gats-mod-overlay';
                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 (e.key === 'Alt') {
                    e.preventDefault();
                    this.isExclusionModeActive = true;
                }
                if (e.key === ' ') {
                    this.isShieldModeActive = true;
                }
                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 '^': 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();
                }
            });

            window.addEventListener('keyup', (e) => {
                if (e.key === 'Alt') {
                    this.isExclusionModeActive = false;
                }
                 if (e.key === ' ') {
                    this.isShieldModeActive = false;
                }
            });

            const guiIdsToIgnore = [this.simpleGui.container.id, this.colorGui?.container.id, 'player-list-modal'].filter(Boolean);
            document.addEventListener('mousedown', (e) => {
                if (this.isExclusionModeActive) {
                    this.handleClickToIgnore(e);
                    return;
                }
                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;
            });
        }

        _prepareMessage(type, data) {
            try {
                const compressed = unsafeWindow.compressMessage(type, data);
                return unsafeWindow.encodeMessage(compressed);
            } catch(e) {
                modLog(`Error in _prepareMessage for type ${type}: ${e.message}`, true);
                return null;
            }
        }
        performShotgunAutoReload(me) {
            if (me.class !== 'shotgun' || me.reloading) {
                return;
            }

            const now = performance.now();
            const reloadInterval = 100;

            if (now - this.lastShotgunReloadTime > reloadInterval) {
                this.lastShotgunReloadTime = now;
                this._fireKeyEvent('keydown', 'r');
                setTimeout(() => this._fireKeyEvent('keyup', 'r'), 50);
            }
        }

        handlePlayerChatUpdates() {
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            if (!Player || !Player.pool || typeof Player.pool[Object.keys(Player.pool)[0]]?.chatMessage === 'undefined') {
                return;
            }

            if (document.activeElement === document.getElementById('chat-input-box')) {
                return;
            }

            for (const id in Player.pool) {
                const p = Player.pool[id];

                if (!p || !p.activated || id == selfId) {
                    continue;
                }

                if (p.chatMessage && p.chatMessage !== "" && p.chatMessage !== (this.lastPlayerChatMessages[id] || "")) {
                    const message = p.chatMessage;
                    modLog(`[Chat Handler] Detected new message from ${p.username}: "${message}"`);

                    if (GatsModCore.SETTINGS.parrotChatEnabled) {
                        setTimeout(() => GatsModCore.sendChatMessage(message), 200 + Math.random() * 100);
                    }

                    if (GatsModCore.SETTINGS.zeroAIEnabled && GatsModCore.SETTINGS.aiAutoTalk) {
                        this.handleIncomingChatMessage(message.toLowerCase());
                    }

                    this.lastPlayerChatMessages[id] = message;
                }
                else if ((!p.chatMessage || p.chatMessage === "") && this.lastPlayerChatMessages[id]) {
                    this.lastPlayerChatMessages[id] = "";
                }
            }
        }
        findClosestIncomingBullet(me) {
    let closestBullet = null;
    let minDistanceSq = Infinity;
    const playerHitboxRadius = 24;

    const Bullet = unsafeWindow.Bullet;
    const selfId = unsafeWindow.selfId;
    if (typeof Bullet === 'undefined' || !Bullet.pool) {
        return null;
    }

    for (const id in Bullet.pool) {
        const b = Bullet.pool[id];

        if (!b || !b.activated || b.ownerId === selfId || (me.teamCode !== 0 && b.teamCode === me.teamCode)) {
            continue;
        }
        if (b.spdX === 0 && b.spdY === 0) {
            continue;
        }

        const dx = me.x - b.x;
        const dy = me.y - b.y;

        const bulletSpeed = Math.hypot(b.spdX, b.spdY);
        const dirX = b.spdX / bulletSpeed;
        const dirY = b.spdY / bulletSpeed;

        const projection = dx * dirX + dy * dirY;

        if (projection < 0 || projection > 1000) {
            continue;
        }

        const closestDistOnTrajectory = Math.abs(dx * dirY - dy * dirX);

        if (closestDistOnTrajectory > playerHitboxRadius) {
            continue;
        }
        const distSq = dx * dx + dy * dy;
        if (distSq < minDistanceSq) {
            minDistanceSq = distSq;
            closestBullet = b;
        }
    }

    return closestBullet;
}

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

    this.performShotgunAutoReload(me);

    this.handlePlayerChatUpdates();

    if (GatsModCore.SETTINGS.ghostDetectEnabled) this.performGhostDetection();
    if (GatsModCore.SETTINGS.silencerDetectEnabled) this.performSilencerDetection();
    if (GatsModCore.SETTINGS.onKillChatEnabled) this.checkOnKillEvent(me);

    if (GatsModCore.SETTINGS.zeroAIEnabled) {
        this.performZeroAIActions(me);
    } else 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 isShieldEquipped = Object.values(me.levelUpgrades || {}).includes('shield');


    const shieldModeActive = isShieldEquipped && this.isShieldModeActive;


    const aimbotActive = !shieldModeActive && GatsModCore.SETTINGS.aimbotEnabled && (GatsModCore.SETTINGS.alwaysAim || (GatsModCore.SETTINGS.aimbotOnMousePress && mouseClicked) || GatsModCore.SETTINGS.zeroAIEnabled);
    const spinbotActive = !shieldModeActive && GatsModCore.SETTINGS.spinbotEnabled && !mouseClicked;


    this.currentAimAssistTargetCoords = null;
    this.currentAimbotTarget = null;
    this.shieldTargetScreenCoords = null;
    this.aimTargetScreenCoords = null;
    this.spinbotTargetScreenCoords = null;


    if (shieldModeActive) {
        const targetBullet = this.findClosestIncomingBullet(me);
        if (targetBullet) {
            const finalScale = (typeof unsafeWindow !== 'undefined' && unsafeWindow.widthScaleFactor > 0) ? unsafeWindow.widthScaleFactor : 1.0;
            const canvasCenterX = this.gameCanvas.width / 2;
            const canvasCenterY = this.gameCanvas.height / 2;
            this.shieldTargetScreenCoords = {
                x: canvasCenterX + (targetBullet.x - me.x) * finalScale,
                y: canvasCenterY + (targetBullet.y - me.y) * finalScale
            };
        }
    }

    if (spinbotActive) {
        this.performSpinbotActions(me);
    }
    if (aimbotActive) {
        this.performAimbotTargeting(me);
    }

    if (shieldModeActive && this.shieldTargetScreenCoords) {
        this.currentAimAssistTargetCoords = this.shieldTargetScreenCoords;
    } else if (aimbotActive && this.aimTargetScreenCoords) {
        this.currentAimAssistTargetCoords = this.aimTargetScreenCoords;
    } else if (spinbotActive && this.spinbotTargetScreenCoords) {
        this.currentAimAssistTargetCoords = this.spinbotTargetScreenCoords;
    }


    const autoAttackActive = !shieldModeActive && GatsModCore.SETTINGS.autoAttackEnabled && (mouseClicked || GatsModCore.SETTINGS.zeroAIEnabled);
    if (autoAttackActive) {
        this.performAutoAttack(me);
    } else if (this.isAutoShooting) {
        this.stopShooting();
    }


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

        const buttons = (GatsModCore.SETTINGS.zeroAIEnabled && !shieldModeActive) ? 1 : (shieldModeActive ? 0 : this.realMouseButtons);
        const fakeEvent = { clientX: clientX, clientY: clientY, target: this.gameCanvas, buttons: buttons };
        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();
        this.multibox?.updateGUI();
    }
}
        checkOnKillEvent(me) {
            if (!me) return;
            if (this.lastSelfKills === null || this.lastSelfKills === undefined) {
                this.lastSelfKills = me.kills;
                return;
            }
            if (me.kills > this.lastSelfKills) {
                modLog("Kill detected, sending on-kill message.");
                GatsModCore.sendChatMessage(GatsModCore.SETTINGS.onKillMessage);
            }
            this.lastSelfKills = me.kills;
        }

        performGhostDetection() {
            try {
                const Player = unsafeWindow.Player;
                if (!Player || !Player.pool) return;
                for (const id in Player.pool) {
                    const p = Player.pool[id];
                    if (p && p.ghillie) p.ghillie = false;
                }
            } catch (e) {}
        }

        performSilencerDetection() {
            try {
                const Bullet = unsafeWindow.Bullet;
                if (!Bullet || !Bullet.pool) return;
                for (const id in Bullet.pool) {
                    const b = Bullet.pool[id];
                    if (b && b.silenced) b.silenced = false;
                }
            } catch (e) {}
        }

        startShooting() {
            if (this.isAutoShooting) return;
            try {
                unsafeWindow.Connection.list[0].send(this._prepareMessage('key-press', { 'inputId': 6, 'state': 1 }));
                this.isAutoShooting = true;
            } catch (e) {
                modLog("Failed to send start shooting command.", true);
            }
        }

        stopShooting() {
            if (!this.isAutoShooting) return;
            try {
                unsafeWindow.Connection.list[0].send(this._prepareMessage('key-press', { 'inputId': 6, 'state': 0 }));
                this.isAutoShooting = false;
            } catch (e) {
                modLog("Failed to send stop shooting command.", true);
            }
        }

        getBulletOrigin(player) {
            let originX = player.x;
            let originY = player.y;
            const weaponClass = player.class || 'pistol';
            const forward = this.constructor.WEAPON_FORWARD_OFFSETS[weaponClass] || 45;
            const sideways = -18;
            const angleRad = (player.playerAngle || 0) * Math.PI / 180;
            originX += forward * Math.cos(angleRad) + sideways * Math.cos(angleRad + Math.PI / 2);
            originY += forward * Math.sin(angleRad) + sideways * Math.sin(angleRad + Math.PI / 2);
            return { x: originX, y: originY };
        }

        hasLineOfSight(p1, p2) {
            const MapObject = unsafeWindow.MapObject;
            if (typeof MapObject === 'undefined' || !MapObject.pool) return true;
            for (const id in MapObject.pool) {
                const obs = MapObject.pool[id];
                if (obs?.activated && (obs.type === 'crate' || obs.type === 'longCrate' || obs.type === 'userCrate')) {
                    if (this.isLineIntersectingRotatedRect(p1, p2, obs)) {
                        return false;
                    }
                }
            }
            return true;
        }

        isLineIntersectingRotatedRect(p1, p2, rect) {
            const settings = GatsModCore.SETTINGS;
            const angle = -(rect.angle || 0) * Math.PI / 180;
            const cos = Math.cos(angle), sin = Math.sin(angle);
            const cx = rect.x + settings.obstacleOffsetX;
            const cy = rect.y + settings.obstacleOffsetY;
            const p1r = { x: cos * (p1.x - cx) - sin * (p1.y - cy), y: sin * (p1.x - cx) + cos * (p1.y - cy) };
            const p2r = { x: cos * (p2.x - cx) - sin * (p2.y - cy), y: sin * (p2.x - cx) + cos * (p2.y - cy) };
            let w = rect.width || 50, h = rect.height || 50;
            if (rect.type === 'crate') {
                w = 100;
                h = 100;
            } else if (rect.type === 'userCrate') {
                w = 40;
                h = 40;
            }
            const halfW = w / 2, halfH = h / 2;
            const rectMin = { x: -halfW, y: -halfH };
            const rectMax = { x: halfW, y: halfH };
            const dx = p2r.x - p1r.x;
            const dy = p2r.y - p1r.y;
            let t0 = 0, t1 = 1;
            const p = [-dx, dx, -dy, dy];
            const q = [p1r.x - rectMin.x, rectMax.x - p1r.x, p1r.y - rectMin.y, rectMax.y - p1r.y];
            for (let i = 0; i < 4; i++) {
                if (p[i] === 0) {
                    if (q[i] < 0) return true;
                } else {
                    const t = q[i] / p[i];
                    if (p[i] < 0) {
                        if (t > t1) return false;
                        t0 = Math.max(t0, t);
                    } else {
                        if (t < t0) return false;
                        t1 = Math.min(t1, t);
                    }
                }
            }
            return t0 < t1;
        }

        getMyWeaponRange(player) {
            if (!player) return 0;
            const weaponClass = player.class || 'pistol';
            let baseRange = GatsModCore.WEAPON_BASE_RANGES[weaponClass] || 425;
            if (player.levelUpgrades) {
                const hasLongRange = Object.values(player.levelUpgrades).includes('longRange');
                if (hasLongRange) {
                    baseRange *= GatsModCore.LONG_RANGE_MULTIPLIER;
                }
            }
            return baseRange;
        }

        getEstimatedEnemyRange(enemyPlayer) {
            if (!enemyPlayer) return 0;
            const weaponClass = enemyPlayer.class || 'pistol';
            return GatsModCore.WEAPON_BASE_RANGES[weaponClass] || 425;
        }

        performAutoAttack(me) {
            if (!this.currentAimbotTarget || !this.predictedTargetWorld.x) {
                this.stopShooting();
                return;
            }
            const screenX = this.aimTargetScreenCoords.x;
            const screenY = this.aimTargetScreenCoords.y;
            const canvasCenterX = this.gameCanvas.width / 2;
            const canvasCenterY = this.gameCanvas.height / 2;
            const distFromCenterSq = (screenX - canvasCenterX) ** 2 + (screenY - canvasCenterY) ** 2;
            if (distFromCenterSq > GatsModCore.SETTINGS.autoAttackRadius ** 2) {
                this.stopShooting();
                return;
            }
            const bulletOrigin = this.getBulletOrigin(me);
            if (GatsModCore.SETTINGS.autoAttackCheckLOS) {
                if (!this.hasLineOfSight(bulletOrigin, this.predictedTargetWorld)) {
                    this.stopShooting();
                    return;
                }
            }
            if (GatsModCore.SETTINGS.autoAttackCheckRange) {
                const maxRange = this.getMyWeaponRange(me);
                const targetDistance = getDistance(bulletOrigin, this.predictedTargetWorld);
                if (targetDistance > maxRange) {
                    this.stopShooting();
                    return;
                }
            }
            this.startShooting();
        }

        calculatePredictedPosition(p, me) {
            const settings = GatsModCore.SETTINGS;
            if (!settings.predictionEnabled || p.spdX === undefined || p.spdY === undefined) {
                return { x: p.x, y: p.y };
            }
            const { x: shotOriginX_world, y: shotOriginY_world } = this.getBulletOrigin(me);
            let basePredictionFactor = settings.predictionFactor;
            if (settings.useCloseRangePrediction) {
                const canvasCenterX = this.gameCanvas.width / 2;
                const canvasCenterY = this.gameCanvas.height / 2;
                const screenPlayerX = canvasCenterX + (p.x - me.x);
                const screenPlayerY = canvasCenterY + (p.y - me.y);
                const distSqFromCenter = (screenPlayerX - canvasCenterX) ** 2 + (screenPlayerY - canvasCenterY) ** 2;
                if (distSqFromCenter < settings.predictionCloseRadius ** 2) {
                    basePredictionFactor = settings.predictionFactorClose;
                }
            }
            const currentWeaponClass = me.class || 'pistol';
            let bulletSpeed = GatsModCore.WEAPON_BULLET_SPEEDS[currentWeaponClass] || 9.0;
            bulletSpeed = Math.max(0.1, bulletSpeed);
            let timeToHit = 0;
            let futureX_intermediate = p.x;
            let futureY_intermediate = p.y;
            for (let i = 0; i < 2; i++) {
                const distanceToFuturePos = getDistance({ x: futureX_intermediate, y: futureY_intermediate }, { x: shotOriginX_world, y: shotOriginY_world });
                timeToHit = distanceToFuturePos < 1 ? 0 : distanceToFuturePos / bulletSpeed;
                timeToHit = Math.min(timeToHit, 5);
                futureX_intermediate = p.x + (p.spdX * timeToHit);
                futureY_intermediate = p.y + (p.spdY * timeToHit);
            }
            const baseDisplacementX = p.spdX * timeToHit;
            const baseDisplacementY = p.spdY * timeToHit;
            let actualPredictionFactor = basePredictionFactor;
            if (basePredictionFactor === settings.predictionFactor && settings.enableDynamicPredictionFactor) {
                const distanceToEnemy = getDistance(p, { x: shotOriginX_world, y: shotOriginY_world });
                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);
            }
            if (p.dashing) {
                actualPredictionFactor /= 3;
            }
            const worldTargetX = p.x + (baseDisplacementX * actualPredictionFactor);
            const worldTargetY = p.y + (baseDisplacementY * actualPredictionFactor);
            return { x: worldTargetX, y: worldTargetY };
        }

        findBestShootingPoint(shooter, target) {
            const shooterOrigin = this.getBulletOrigin(shooter);
            const predictedCenter = this.calculatePredictedPosition(target, shooter);

            if (this.hasLineOfSight(shooterOrigin, predictedCenter)) {
                return predictedCenter;
            }

            const radius = 24;
            const angles = [
                0, Math.PI / 2, Math.PI, 3 * Math.PI / 2,
                Math.PI / 4, 3 * Math.PI / 4, 5 * Math.PI / 4, 7 * Math.PI / 4
            ];

            for (const angle of angles) {
                const checkPoint = {
                    x: predictedCenter.x + radius * Math.cos(angle),
                    y: predictedCenter.y + radius * Math.sin(angle)
                };
                if (this.hasLineOfSight(shooterOrigin, checkPoint)) {
                    return checkPoint;
                }
            }

            return null;
        }

        performAimbotTargeting(me) {
            if (!this.gameCanvas) {
                this.aimTargetScreenCoords = null;
                this.currentAimbotTarget = null;
                return;
            }
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;

            const finalScale = (typeof unsafeWindow !== 'undefined' && unsafeWindow.widthScaleFactor > 0) ? unsafeWindow.widthScaleFactor : 1.0;

            const settings = GatsModCore.SETTINGS;
            const canvasCenterX = this.gameCanvas.width / 2;
            const canvasCenterY = this.gameCanvas.height / 2;
            let targetCandidate = null;
            let finalTargetPlayerObject = null;
            let refX = settings.aimAtMouseClosest ? this.realMouseCanvasX : canvasCenterX;
            let refY = settings.aimAtMouseClosest ? this.realMouseCanvasY : canvasCenterY;

            let closestPlayerDistSq = settings.aimbotFov ** 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;

                const bestShootingPoint = this.findBestShootingPoint(me, p);

                if (bestShootingPoint) {
                    const { x: worldTargetX, y: worldTargetY } = bestShootingPoint;

                    const screenTargetX = canvasCenterX + (worldTargetX - me.x) * finalScale;
                    const screenTargetY = canvasCenterY + (worldTargetY - me.y) * finalScale;

                    const distToRefSq = (screenTargetX - refX) ** 2 + (screenTargetY - refY) ** 2;

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

            if (targetCandidate) {
                this.aimTargetScreenCoords = {
                    x: canvasCenterX + (targetCandidate.x_world - me.x) * finalScale + settings.espOffsetX,
                    y: canvasCenterY + (targetCandidate.y_world - me.y) * finalScale + settings.espOffsetY
                };

                this.currentAimbotTarget = finalTargetPlayerObject;
                this.predictedTargetWorld = { x: targetCandidate.x_world, y: targetCandidate.y_world };
            } else {
                this.aimTargetScreenCoords = null;
                this.currentAimbotTarget = null;
                this.predictedTargetWorld = { x: 0, y: 0 };
            }
        }

        drawESP(ctx, me, hasTarget) {
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            const MapObject = unsafeWindow.MapObject;
            const Bullet = unsafeWindow.Bullet;
            const camera = unsafeWindow.camera;

            const finalScale = (typeof unsafeWindow !== 'undefined' && unsafeWindow.widthScaleFactor > 0) ? unsafeWindow.widthScaleFactor : 1.0;
            const sizeMultiplier = GatsModCore.SETTINGS.espScale || 1.0;

            const { espColors, espOffsetX, espOffsetY, obstacleOffsetX, obstacleOffsetY, obstacleEspColor } = GatsModCore.SETTINGS;
            if (!this.gameCanvas || !camera?.ctx) return;
            try {
                if (typeof unsafeWindow.landMine !== 'undefined' && Array.isArray(unsafeWindow.landMine) && unsafeWindow.landMine[0]) {
                    unsafeWindow.landMine[0].forEach((a, i) => {
                        if (unsafeWindow.landMine[0][i] && unsafeWindow.landMine[0][i][1]) {
                            unsafeWindow.landMine[0][i][1][3] = "#000000";
                        }
                    });
                }
            } catch (e) {}
            const canvasCenterX = this.gameCanvas.width / 2, canvasCenterY = this.gameCanvas.height / 2;
            ctx.save();
            ctx.textAlign = 'center';
            ctx.font = 'bold 10px Arial';

            if (GatsModCore.SETTINGS.autoAttackShowRadius) {
                ctx.beginPath();
                ctx.arc(canvasCenterX, canvasCenterY, GatsModCore.SETTINGS.autoAttackRadius, 0, 2 * Math.PI);
                ctx.strokeStyle = 'rgba(255, 255, 0, 0.5)';
                ctx.lineWidth = 1;
                ctx.stroke();
            }

            if (GatsModCore.SETTINGS.obstacleEspEnabled && MapObject && MapObject.pool) {
                ctx.strokeStyle = obstacleEspColor;
                ctx.lineWidth = 2;
                for (const id in MapObject.pool) {
                    const obj = MapObject.pool[id];
                    if (!obj || !obj.activated) continue;
                    let width = obj.width || 50;
                    let height = obj.height || 50;
                    if (obj.type === 'crate') { width = 100; height = 100; }
                    if (obj.type === 'userCrate') { width = 40; height = 40; }
                    const objX = obj.x + obstacleOffsetX;
                    const objY = obj.y + obstacleOffsetY;
                    const screenX = canvasCenterX + (objX - me.x) * finalScale + espOffsetX;
                    const screenY = canvasCenterY + (objY - me.y) * finalScale + espOffsetY;
                    ctx.save();
                    ctx.translate(screenX, screenY);
                    ctx.rotate((obj.angle || 0) * Math.PI / 180);
                    ctx.strokeRect((-width / 2) * finalScale, (-height / 2) * finalScale, width * finalScale, height * finalScale);
                    ctx.restore();
                }
            }

            if (GatsModCore.SETTINGS.losDebugLineEnabled && this.currentAimbotTarget) {
                const startPoint = this.getBulletOrigin(me);
                const endPoint = this.predictedTargetWorld;
                if (startPoint && endPoint.x) {
                    const isClear = this.hasLineOfSight(startPoint, endPoint);
                    ctx.strokeStyle = isClear ? 'rgba(0, 255, 0, 0.7)' : 'rgba(255, 0, 0, 0.7)';
                    ctx.lineWidth = 3;
                    const startScreenX = canvasCenterX + (startPoint.x - me.x) * finalScale + espOffsetX;
                    const startScreenY = canvasCenterY + (startPoint.y - me.y) * finalScale + espOffsetY;
                    const endScreenX = canvasCenterX + (endPoint.x - me.x) * finalScale + espOffsetX;
                    const endScreenY = canvasCenterY + (endPoint.y - me.y) * finalScale + espOffsetY;
                    ctx.beginPath();
                    ctx.moveTo(startScreenX, startScreenY);
                    ctx.lineTo(endScreenX, endScreenY);
                    ctx.stroke();
                }
            }

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

                const screenX = canvasCenterX + (p.x - me.x) * finalScale + espOffsetX;
                const screenY = canvasCenterY + (p.y - me.y) * finalScale + espOffsetY;
                const radiusOnScreen = (p.radius || 15) * finalScale * sizeMultiplier;

                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 (this.isExclusionModeActive && !isTeammate) {
                    ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
                    ctx.lineWidth = 2;
                    ctx.setLineDash([4, 4]);
                    ctx.beginPath();
                    const clickRadiusOnScreen = 24 * finalScale * sizeMultiplier;
                    ctx.arc(screenX, screenY, clickRadiusOnScreen, 0, 2 * Math.PI);
                    ctx.stroke();
                    ctx.setLineDash([]);
                }

                if (GatsModCore.SETTINGS.espHighlightCloaked && p.ghillie && !isTeammate) {
                    ctx.font = 'bold 12px Arial';
                    ctx.fillStyle = espColors.cloakedTextColor;
                    ctx.fillText('CLOAKED', screenX, nameYOffset);
                    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 (!isTeammate && GatsModCore.SETTINGS.espShowPrediction && GatsModCore.SETTINGS.predictionEnabled) {
                    const predictedPos = this.calculatePredictedPosition(p, me);
                    if (predictedPos) {
                        const predScreenX = canvasCenterX + (predictedPos.x - me.x) * finalScale + espOffsetX;
                        const predScreenY = canvasCenterY + (predictedPos.y - me.y) * finalScale + espOffsetY;
                        ctx.strokeStyle = espColors.predictionLineColor || '#FF00FF';
                        ctx.lineWidth = 1;
                        ctx.setLineDash([5, 3]);
                        ctx.beginPath();
                        ctx.moveTo(screenX, screenY);
                        ctx.lineTo(predScreenX, predScreenY);
                        ctx.stroke();
                        ctx.setLineDash([]);
                        ctx.beginPath();
                        ctx.arc(predScreenX, predScreenY, 3, 0, 2 * Math.PI);
                        ctx.fillStyle = espColors.predictionLineColor || '#FF00FF';
                        ctx.fill();
                    }
                }

                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 (hasTarget && this.currentAimAssistTargetCoords) {
                const { x, y } = this.currentAimAssistTargetCoords;
                const originWorld = this.getBulletOrigin(me);
                const originX = canvasCenterX + (originWorld.x - me.x) * finalScale + espOffsetX;
                const originY = canvasCenterY + (originWorld.y - me.y) * finalScale + espOffsetY;
                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();
            }
            if (this.shieldTargetScreenCoords) {
                const { x, y } = this.shieldTargetScreenCoords;
                ctx.save();
                ctx.strokeStyle = '#00FFFF';
                ctx.lineWidth = 2.0;
                ctx.setLineDash([5, 5]);
                ctx.beginPath();
                ctx.moveTo(canvasCenterX, canvasCenterY);
                ctx.lineTo(x, y);
                ctx.stroke();
                ctx.beginPath();
                ctx.arc(x, y, 10, 0, 2 * Math.PI);
                ctx.stroke();
                ctx.restore();
            }

            if (GatsModCore.SETTINGS.espShowBulletWarnings && typeof Bullet !== 'undefined' && Bullet.pool) {
                ctx.strokeStyle = espColors.aiBulletWarningColor || '#FF00FF';
                ctx.fillStyle = (espColors.aiBulletWarningColor || '#FF00FF') + '33';
                ctx.lineWidth = 1;
                const dodgeRadiusHorizontal = 150;
                const dodgeRadiusVertical = 15;
                for (const id in Bullet.pool) {
                    const b = Bullet.pool[id];
                    const owner = Player.pool[b.ownerId];
                    if (!b || !b.activated || b.ownerId === selfId || (me.teamCode !== 0 && b.teamCode === me.teamCode) || (owner && GatsModCore.SETTINGS.aimbotIgnoreList.includes(owner.username))) continue;
                    const screenBulletX = canvasCenterX + (b.x - me.x) * finalScale + espOffsetX;
                    const screenBulletY = canvasCenterY + (b.y - me.y) * finalScale + espOffsetY;
                    const radiusX = dodgeRadiusHorizontal * finalScale * sizeMultiplier;
                    const radiusY = dodgeRadiusVertical * finalScale * sizeMultiplier;
                    const rotation = (b.spdX === 0 && b.spdY === 0) ? 0 : Math.atan2(b.spdY, b.spdX);
                    ctx.beginPath();
                    ctx.ellipse(screenBulletX, screenBulletY, radiusX, radiusY, rotation, 0, 2 * Math.PI);
                    ctx.stroke();
                    ctx.fill();
                }
            }

            if (GatsModCore.SETTINGS.zeroAIEnabled) {
                const { aiShowPathfindingWhiskers, aiShowFinalMoveDirection, aiShowEnemyThreatLine } = GatsModCore.SETTINGS;
                const { whiskers, finalMoveDirection } = this.aiDebugData;

                if (aiShowPathfindingWhiskers && whiskers.length > 0) {
                    whiskers.forEach(whisker => {
                        const startScreenX = canvasCenterX + (whisker.start.x - me.x) * finalScale + espOffsetX;
                        const startScreenY = canvasCenterY + (whisker.start.y - me.y) * finalScale + espOffsetY;
                        const endScreenX = canvasCenterX + (whisker.end.x - me.x) * finalScale + espOffsetX;
                        const endScreenY = canvasCenterY + (whisker.end.y - me.y) * finalScale + espOffsetY;
                        ctx.beginPath();
                        ctx.moveTo(startScreenX, startScreenY);
                        ctx.lineTo(endScreenX, endScreenY);
                        ctx.strokeStyle = whisker.isClear ? espColors.aiWhiskerClearColor : espColors.aiWhiskerBlockedColor;
                        ctx.lineWidth = whisker.isBest ? 3 : 1;
                        ctx.stroke();
                    });
                }
                if (aiShowFinalMoveDirection && finalMoveDirection) {
                    const startScreenX = canvasCenterX + (finalMoveDirection.start.x - me.x) * finalScale + espOffsetX;
                    const startScreenY = canvasCenterY + (finalMoveDirection.start.y - me.y) * finalScale + espOffsetY;
                    const endScreenX = canvasCenterX + (finalMoveDirection.end.x - me.x) * finalScale + espOffsetX;
                    const endScreenY = canvasCenterY + (finalMoveDirection.end.y - me.y) * finalScale + espOffsetY;
                    ctx.beginPath();
                    ctx.moveTo(startScreenX, startScreenY);
                    ctx.lineTo(endScreenX, endScreenY);
                    ctx.strokeStyle = espColors.aiMoveDirColor;
                    ctx.lineWidth = 4;
                    ctx.stroke();
                    const headlen = 10;
                    const angle = Math.atan2(endScreenY - startScreenY, endScreenX - startScreenX);
                    ctx.lineTo(endScreenX - headlen * Math.cos(angle - Math.PI / 6), endScreenY - headlen * Math.sin(angle - Math.PI / 6));
                    ctx.moveTo(endScreenX, endScreenY);
                    ctx.lineTo(endScreenX - headlen * Math.cos(angle + Math.PI / 6), endScreenY - headlen * Math.sin(angle + Math.PI / 6));
                    ctx.stroke();
                }
                if (aiShowEnemyThreatLine) {
                    const nearestEnemy = this.findNearestEnemy(me);
                    if (nearestEnemy) {
                        const enemyGunOrigin = this.getBulletOrigin(nearestEnemy);
                        const isClear = this.hasLineOfSight(enemyGunOrigin, me);
                        ctx.strokeStyle = isClear ? espColors.aiThreatLineClearColor : espColors.aiThreatLineBlockedColor;
                        ctx.lineWidth = 2;
                        ctx.setLineDash([10, 5]);
                        const startScreenX = canvasCenterX + (enemyGunOrigin.x - me.x) * finalScale;
                        const startScreenY = canvasCenterY + (enemyGunOrigin.y - me.y) * finalScale;
                        const endScreenX = canvasCenterX;
                        const endScreenY = canvasCenterY;
                        ctx.beginPath();
                        ctx.moveTo(startScreenX, startScreenY);
                        ctx.lineTo(endScreenX, endScreenY);
                        ctx.stroke();
                        ctx.setLineDash([]);
                    }
                }
            }

            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) };
        }

        getBotPhysics(player) {
            const weapon = player.class || 'pistol';
            const armor = player.armor || 0;
            const upgrades = player.levelUpgrades ? Object.values(player.levelUpgrades) : [];
            const baseSpeed = GatsModCore.PLAYER_SPEEDS.base[weapon] || GatsModCore.PLAYER_SPEEDS.base['pistol'];
            const armorMod = GatsModCore.PLAYER_SPEEDS.armorMultiplier[armor] || 1.0;
            const upgradeMod = upgrades.includes('lightweight') ? GatsModCore.PLAYER_SPEEDS.upgradeMultiplier['lightweight'] : 1.0;
            const maxSpeed = baseSpeed * armorMod * upgradeMod;
            const ACCELERATION = maxSpeed / 16.0;
            const FRICTION = 0.94;
            return { maxSpeed, ACCELERATION, FRICTION };
        }

        performFollowBotActions(me) {
            if (!this.isFollowingPlayer || this.followingPlayerId === null) {
                GatsModCore.stopFollowingPlayer(true);
                return;
            }
            const Player = unsafeWindow.Player;
            const targetPlayer = Player.pool[this.followingPlayerId];
            if (!targetPlayer?.activated || targetPlayer.hp <= 0) {
                GatsModCore.stopFollowingPlayer();
                return;
            }
            const { maxSpeed, ACCELERATION, FRICTION } = this.getBotPhysics(me);
            const dx = targetPlayer.x - me.x;
            const dy = targetPlayer.y - me.y;
            const distance = Math.hypot(dx, dy);
            let desiredSpdX = 0;
            let desiredSpdY = 0;
            const stopDistance = 15;
            if (distance > stopDistance) {
                const brakingFactor = 12.0;
                const desiredSpeed = Math.min(maxSpeed, distance / brakingFactor);
                desiredSpdX = (dx / distance) * desiredSpeed;
                desiredSpdY = (dy / distance) * desiredSpeed;
            }
            const requiredAccelX = (desiredSpdX / FRICTION) - this.botSpdX;
            const requiredAccelY = (desiredSpdY / FRICTION) - this.botSpdY;
            let keysToPress = [];
            let isPressingKeyX = 0;
            let isPressingKeyY = 0;
            const keyPressThreshold = ACCELERATION * 0.1;
            if (requiredAccelX > keyPressThreshold) {
                keysToPress.push('d');
                isPressingKeyX = 1;
            } else if (requiredAccelX < -keyPressThreshold) {
                keysToPress.push('a');
                isPressingKeyX = -1;
            }
            if (requiredAccelY > keyPressThreshold) {
                keysToPress.push('s');
                isPressingKeyY = 1;
            } else if (requiredAccelY < -keyPressThreshold) {
                keysToPress.push('w');
                isPressingKeyY = -1;
            }
            this.updateSimulatedKeys(keysToPress);
            let appliedAccelX = isPressingKeyX * ACCELERATION;
            let appliedAccelY = isPressingKeyY * ACCELERATION;
            if (isPressingKeyX !== 0 && isPressingKeyY !== 0) {
                appliedAccelX *= GatsModCore.PLAYER_SPEEDS.diagonalCorrection;
                appliedAccelY *= GatsModCore.PLAYER_SPEEDS.diagonalCorrection;
            }
            this.botSpdX = (this.botSpdX + appliedAccelX) * FRICTION;
            this.botSpdY = (this.botSpdY + appliedAccelY) * FRICTION;
            const currentSimulatedSpeed = Math.hypot(this.botSpdX, this.botSpdY);
            if (currentSimulatedSpeed > maxSpeed) {
                const ratio = maxSpeed / currentSimulatedSpeed;
                this.botSpdX *= ratio;
                this.botSpdY *= ratio;
            }
        }

        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, ' ': 32, 'r': 82 };
            const codeMap = { 'w': 'KeyW', 'a': 'KeyA', 's': 'KeyS', 'd': 'KeyD', ' ': 'Space', 'r': 'KeyR' };
            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 Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            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();
        }

        handleClickToIgnore(event) {
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            const me = Player?.pool?.[selfId];
            if (!this.isExclusionModeActive || !Player.pool || !me) return;

            const rect = this.gameCanvas.getBoundingClientRect();
            const clickX = event.clientX - rect.left;
            const clickY = event.clientY - rect.top;

            const settings = GatsModCore.SETTINGS;
            const canvasCenterX = this.gameCanvas.width / 2;
            const canvasCenterY = this.gameCanvas.height / 2;

            let clickedPlayer = null;
            let closestDistSq = Infinity;

            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)) continue;

                const finalScale = (typeof unsafeWindow !== 'undefined' && unsafeWindow.widthScaleFactor > 0) ? unsafeWindow.widthScaleFactor : 1.0;
                const relX = (p.x - me.x) * finalScale;
                const relY = (p.y - me.y) * finalScale;
                const screenX = canvasCenterX + relX + settings.espOffsetX;
                const screenY = canvasCenterY + relY + settings.espOffsetY;

                const distSq = (clickX - screenX) ** 2 + (clickY - screenY) ** 2;
                const clickRadius = 24 * finalScale * settings.espScale;

                if (distSq < clickRadius * clickRadius && distSq < closestDistSq) {
                    closestDistSq = distSq;
                    clickedPlayer = p;
                }
            }

            if (clickedPlayer) {
                this.addPlayerToIgnoreList(clickedPlayer.username);
            }
        }

        addPlayerToIgnoreList(username, silent = false) {
            if (!username) return;
            const settings = GatsModCore.SETTINGS;
            if (!settings.aimbotIgnoreList.includes(username)) {
                settings.aimbotIgnoreList.push(username);
                GatsModCore.saveSettings();
                this.simpleGui?.updateAimbotExclusionListDisplay();
                if (!silent) modLog(`Player "${username}" added to ignore list.`);
            } else {
                if (silent) return;
                GatsModCore.SETTINGS.aimbotIgnoreList = GatsModCore.SETTINGS.aimbotIgnoreList.filter(n => n !== username);
                GatsModCore.saveSettings();
                this.simpleGui.updateAimbotExclusionListDisplay();
                modLog(`Player "${username}" removed from ignore list.`);
            }
        }

        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-v2');
                        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;
            gatsModInstance.botSpdX = 0;
            gatsModInstance.botSpdY = 0;
            if (!silent) modLog("FollowBot stopped.");
            gatsModInstance?.simpleGui?.updateFollowBotStatusDisplay();
        }

        static showPlayerList() {
            const modal = document.getElementById('player-list-modal');
            const grid = document.getElementById('player-list-grid');
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            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 (!unsafeWindow.Connection?.list?.[0]) {
                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();
            GatsModCore.sendChatMessage(displayText);
            GatsModCore.chatScrollCurrentIndex = (GatsModCore.chatScrollCurrentIndex + 1) % s.length;
            if (GatsModCore.SETTINGS.chatScrollActive) {
                GatsModCore.chatScrollIntervalId = setTimeout(GatsModCore.chatScrollLoop, GatsModCore.SETTINGS.chatScrollSpeed);
            }
        }

        static startSequencer() {
            if (!GatsModCore.SETTINGS.sequencerEnabled) {
                alert("Sequencer is disabled in settings.");
                return;
            }
            if (GatsModCore.SETTINGS.sequencerActive) GatsModCore.stopSequencer();
            GatsModCore.SETTINGS.sequencerActive = true;
            GatsModCore.sequencerCurrentIndex = 0;
            modLog("Sequencer: Started.");
            GatsModCore.sequencerLoop();
        }

        static stopSequencer() {
            if (GatsModCore.sequencerIntervalId) {
                clearTimeout(GatsModCore.sequencerIntervalId);
                GatsModCore.sequencerIntervalId = null;
            }
            GatsModCore.SETTINGS.sequencerActive = false;
            modLog("Sequencer: Stopped.");
        }

        static sequencerLoop() {
            if (!GatsModCore.SETTINGS.sequencerActive || !GatsModCore.SETTINGS.sequencerEnabled || GatsModCore.isInputActive) {
                if (!GatsModCore.SETTINGS.sequencerEnabled || GatsModCore.isInputActive) GatsModCore.stopSequencer();
                return;
            }
            const presets = GatsModCore.SETTINGS.chatPresetMessages;
            if (GatsModCore.sequencerCurrentIndex >= presets.length) {
                if (GatsModCore.SETTINGS.sequencerLoop) {
                    GatsModCore.sequencerCurrentIndex = 0;
                } else {
                    GatsModCore.stopSequencer();
                    return;
                }
            }
            const message = presets[GatsModCore.sequencerCurrentIndex];
            if (message && message.trim().length > 0) GatsModCore.sendChatMessage(message);
            GatsModCore.sequencerCurrentIndex++;
            if (GatsModCore.SETTINGS.sequencerActive) {
                GatsModCore.sequencerIntervalId = setTimeout(GatsModCore.sequencerLoop, GatsModCore.SETTINGS.sequencerDelay);
            }
        }

        static sendChatMessage(message) {
            if (!message || message.trim().length === 0) return;
            if (!unsafeWindow.Connection?.list?.[0]) {
                modLog("Chat: Connection not available.", true);
                return;
            }
            try {
                const sanitizedMessage = message.replaceAll(",", "~");
                const msg = GatsModCore._prepareMessage('message', { 'message': sanitizedMessage });
                if (msg) unsafeWindow.Connection.list[0].send(msg);
            } catch (e) {
                modLog(`Chat: Error sending message: ${e.message}`, true);
            }
        }

        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);
                }
                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);
                GatsModCore.sendChatMessage(presetText);
                if (GatsModCore.SETTINGS.chatScrollActive) GatsModCore.startChatScroll();
                modLog(`ChatScroller: Preset ${index + 1} set and sent once.`);
            }
        }

        findNearestEnemy(me) {
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;
            let closestEnemy = null;
            let minDistanceSq = Infinity;
            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) || GatsModCore.SETTINGS.aimbotIgnoreList.includes(p.username)) continue;
                const distSq = (p.x - me.x) ** 2 + (p.y - me.y) ** 2;
                if (distSq < minDistanceSq) {
                    minDistanceSq = distSq;
                    closestEnemy = p;
                }
            }
            return closestEnemy;
        }

        performZeroAIActions(me) {
            this.performAutoReload(me);

            let movementGoal = null;
            let aiMode = 'idle';
            const nearestEnemy = this.findNearestEnemy(me);

            if (this.multibox.isChild && this.multibox.parentCoords) {
                const distToParent = getDistance(me, this.multibox.parentCoords);
                if (distToParent > 400) {
                    movementGoal = this.multibox.parentCoords;
                    aiMode = 'following_parent';
                }
            }

            const bounds = GatsModCore.MAP_BOUNDS;
            if (!movementGoal && (me.x < bounds.minX || me.x > bounds.maxX || me.y < bounds.minY || me.y > bounds.maxY)) {
                movementGoal = { x: (bounds.minX + bounds.maxX) / 2, y: (bounds.minY + bounds.maxY) / 2 };
                aiMode = 'returning_to_bounds';
            }

            if (!movementGoal) {
                movementGoal = this.getExplosiveAvoidanceGoal(me);
                if (movementGoal) aiMode = 'fleeing_explosive';
            }

            if (!movementGoal && GatsModCore.SETTINGS.aiBulletDodging) {
                movementGoal = this.getBulletDodgeGoal(me);
                if (movementGoal) aiMode = 'dodging';
            }

            if (!movementGoal && nearestEnemy) {
                movementGoal = this.getPotentialThreatDodgeGoal(me, nearestEnemy);
                if (movementGoal) aiMode = 'pre_dodging';
            }

            if (!movementGoal && GatsModCore.SETTINGS.aiAutoRetreat) {
                const isLowHp = me.hp <= GatsModCore.SETTINGS.aiRetreatHP;
                const isReloading = me.reloading;
                if (isLowHp || isReloading) {
                    const retreatGoal = this.getSafestRetreatGoal(me);
                    if (retreatGoal) {
                        movementGoal = retreatGoal;
                        aiMode = 'retreating';
                    } else if (nearestEnemy) {
                        movementGoal = { x: me.x - (nearestEnemy.x - me.x), y: me.y - (nearestEnemy.y - me.y) };
                        aiMode = 'retreating';
                    }
                }
            }

            if (!movementGoal && GatsModCore.SETTINGS.aiEnableKiting && nearestEnemy) {
                const kitingGoal = this.getOptimalKitingGoal(me, nearestEnemy);
                if (kitingGoal) {
                    movementGoal = kitingGoal;
                    aiMode = 'kiting';
                }
            }

            if (!movementGoal && GatsModCore.SETTINGS.aiAutoMovement) {
                if (this.multibox.isChild && this.multibox.parentCoords) {
                    movementGoal = this.multibox.parentCoords;
                    aiMode = 'following_parent';
                }
                else if (GatsModCore.SETTINGS.followBotEnabled && GatsModCore.SETTINGS.followBotTargetName) {
                    const followTarget = Object.values(unsafeWindow.Player.pool).find(p => p.username === GatsModCore.SETTINGS.followBotTargetName && p.activated);
                    if (followTarget) {
                        movementGoal = { x: followTarget.x, y: followTarget.y };
                        aiMode = 'following';
                    }
                }

                if (!movementGoal) {
                    if (nearestEnemy) {
                        movementGoal = { x: nearestEnemy.x, y: nearestEnemy.y };
                        const dist = getDistance(me, nearestEnemy);
                        if (nearestEnemy.class === 'shotgun' && dist < 450) {
                            aiMode = 'distancing';
                        } else {
                            aiMode = 'attacking';
                        }
                    } else {
                        movementGoal = { x: (bounds.minX + bounds.maxX) / 2, y: (bounds.minY + bounds.maxY) / 2 };
                        aiMode = 'patrolling';
                    }
                }
            }

            if (movementGoal) {
                movementGoal.x = Math.max(bounds.minX, Math.min(bounds.maxX, movementGoal.x));
                movementGoal.y = Math.max(bounds.minY, Math.min(bounds.maxY, movementGoal.y));
            }

            if (movementGoal) {
                this.moveAITowards(me, movementGoal);
            } else {
                this.updateSimulatedKeys([]);
                this.aiDebugData.whiskers = [];
                this.aiDebugData.finalMoveDirection = null;
            }

            if (GatsModCore.SETTINGS.aiAutoParkUsage) {
                this.useParkAbility(me);
            }

            if (GatsModCore.SETTINGS.aiAutoTalk) {
                this.handleSituationalAITalk(me, nearestEnemy, aiMode);
            }
        }

        performAutoReload(me) {
            if (!GatsModCore.SETTINGS.zeroAIEnabled || me.reloading || !me.magSize || me.mag >= me.magSize) {
                return;
            }

            const reloadSafetyRadiusSq = 500 * 500;
            let enemyNearby = false;
            for (const id in unsafeWindow.Player.pool) {
                const p = unsafeWindow.Player.pool[id];
                if (!p?.activated || p.hp <= 0 || id == unsafeWindow.selfId || (me.teamCode !== 0 && p.teamCode === me.teamCode)) continue;
                const distSq = (p.x - me.x) ** 2 + (p.y - me.y) ** 2;
                if (distSq < reloadSafetyRadiusSq) {
                    enemyNearby = true;
                    break;
                }
            }

            if (!enemyNearby) {
                modLog("AI: Auto-reloading.");
                this._fireKeyEvent('keydown', 'r');
                setTimeout(() => this._fireKeyEvent('keyup', 'r'), 50);
            }
        }

        getPotentialThreatDodgeGoal(me, enemy) {
            if (!enemy) return null;

            const enemyGunOrigin = this.getBulletOrigin(enemy);
            const distanceToSelf = getDistance(enemyGunOrigin, me);

            const enemyRange = this.getEstimatedEnemyRange(enemy);
            if (distanceToSelf > enemyRange * 1.2) {
                return null;
            }

            const threatWidth = 30;
            const threatHalfWidth = threatWidth / 2;

            const dx_threat = me.x - enemyGunOrigin.x;
            const dy_threat = me.y - enemyGunOrigin.y;
            const dist_threat = Math.hypot(dx_threat, dy_threat);
            let isThreatened = false;

            if (dist_threat > 0.1) {
                const dir_x = dx_threat / dist_threat;
                const dir_y = dy_threat / dist_threat;
                const perp_x = -dir_y;
                const perp_y = dir_x;

                const checkPointCenter = me;
                const checkPointLeft = { x: me.x + perp_x * threatHalfWidth, y: me.y + perp_y * threatHalfWidth };
                const checkPointRight = { x: me.x - perp_x * threatHalfWidth, y: me.y - perp_y * threatHalfWidth };

                isThreatened = this.hasLineOfSight(enemyGunOrigin, checkPointCenter) ||
                                 this.hasLineOfSight(enemyGunOrigin, checkPointLeft) ||
                                 this.hasLineOfSight(enemyGunOrigin, checkPointRight);
            } else {
                isThreatened = true;
            }

            if (isThreatened) {
                const threatDx = me.x - enemyGunOrigin.x;
                const threatDy = me.y - enemyGunOrigin.y;

                const dodgeVector = { x: -threatDy, y: threatDx };
                const dodgeMagnitude = 200;
                const vectorLength = Math.hypot(dodgeVector.x, dodgeVector.y);

                if (vectorLength > 0.1) {
                    return {
                        x: me.x + (dodgeVector.x / vectorLength) * dodgeMagnitude,
                        y: me.y + (dodgeVector.y / vectorLength) * dodgeMagnitude
                    };
                }
            }
            return null;
        }

        getSafestRetreatGoal(me) {
            const retreatCheckRadiusSq = 800 * 800;
            let totalRepulsion = { x: 0, y: 0 };
            let threatsFound = 0;

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

                const dx = me.x - p.x;
                const dy = me.y - p.y;
                const distSq = dx * dx + dy * dy;

                if (distSq < retreatCheckRadiusSq && distSq > 1) {
                    threatsFound++;
                    const distance = Math.sqrt(distSq);
                    const repulsionStrength = 1 / distance;
                    totalRepulsion.x += (dx / distance) * repulsionStrength;
                    totalRepulsion.y += (dy / distance) * repulsionStrength;
                }
            }

            if (threatsFound > 0) {
                const magnitude = Math.hypot(totalRepulsion.x, totalRepulsion.y);
                if (magnitude > 0.01) {
                    const fleeDistance = 500;
                    return {
                        x: me.x + (totalRepulsion.x / magnitude) * fleeDistance,
                        y: me.y + (totalRepulsion.y / magnitude) * fleeDistance
                    };
                }
            }
            return null;
        }

        getOptimalKitingGoal(me, enemy) {
            const myRange = this.getMyWeaponRange(me);
            const enemyRange = this.getEstimatedEnemyRange(enemy);

            if (myRange > enemyRange + 50) {
                const optimalDistance = myRange * 0.9;
                const currentDistance = getDistance(me, enemy);

                const dx = me.x - enemy.x;
                const dy = me.y - enemy.y;

                const targetX = enemy.x + (dx / currentDistance) * optimalDistance;
                const targetY = enemy.y + (dy / currentDistance) * optimalDistance;

                return { x: targetX, y: targetY };
            }

            return null;
        }

        getBulletDodgeGoal(me) {
            const Bullet = unsafeWindow.Bullet;
            const Player = unsafeWindow.Player;
            const selfId = unsafeWindow.selfId;

            if (typeof Bullet === 'undefined' || !Bullet.pool) {
                return null;
            }

            const dodgeRadiusHorizontal = 150;
            const dodgeRadiusVertical = 15;
            const playerRadius = me.radius || 26;
            let bestDodgeVector = { x: 0, y: 0 };
            let threatsFound = 0;

            for (const id in Bullet.pool) {
                const b = Bullet.pool[id];
                const owner = Player.pool[b.ownerId];
                if (!b || !b.activated || b.ownerId === selfId || (me.teamCode !== 0 && b.teamCode === me.teamCode) || (b.spdX === 0 && b.spdY === 0) || (owner && GatsModCore.SETTINGS.aimbotIgnoreList.includes(owner.username))) continue;

                const predictionTime = 0.4;
                const futureBulletX = b.x + b.spdX * predictionTime * 60;
                const futureBulletY = b.y + b.spdY * predictionTime * 60;

                const bulletSpeed = Math.hypot(b.spdX, b.spdY);
                if (bulletSpeed < 0.1) continue;

                const v_dir_x = b.spdX / bulletSpeed;
                const v_dir_y = b.spdY / bulletSpeed;

                const dx = me.x - futureBulletX;
                const dy = me.y - futureBulletY;

                const dist_vertical = dx * v_dir_x + dy * v_dir_y;
                const dist_horizontal = dx * (-v_dir_y) + dy * v_dir_x;

                const normalized_dist_sq = (dist_horizontal / (dodgeRadiusHorizontal + playerRadius)) ** 2 + (dist_vertical / (dodgeRadiusVertical + playerRadius)) ** 2;

                if (normalized_dist_sq <= 1) {
                    threatsFound++;
                    bestDodgeVector.x += -b.spdY / bulletSpeed;
                    bestDodgeVector.y += b.spdX / bulletSpeed;
                }
            }

            if (threatsFound > 0) {
                const dodgeMagnitude = 200;
                const vectorLength = Math.hypot(bestDodgeVector.x, bestDodgeVector.y);
                if (vectorLength > 0) {
                    return {
                        x: me.x + (bestDodgeVector.x / vectorLength) * dodgeMagnitude,
                        y: me.y + (bestDodgeVector.y / vectorLength) * dodgeMagnitude
                    };
                }
            }
            return null;
        }

        getExplosiveAvoidanceGoal(me) {
            const Explosive = unsafeWindow.Explosive;
            if (typeof Explosive === 'undefined' || !Explosive.pool) {
                return null;
            }

            let totalRepulsion = { x: 0, y: 0 };
            let threatsFound = 0;

            for (const id in Explosive.pool) {
                const explosive = Explosive.pool[id];

                if (!explosive || !explosive.activated || !explosive.type || typeof explosive.x === 'undefined' || typeof explosive.y === 'undefined') {
                    continue;
                }

                let dangerRadius = 0;
                switch (explosive.type) {
                    case 'landMine':
                        dangerRadius = 70;
                        break;
                    case 'grenade':
                    case 'fragGrenade':
                        dangerRadius = 600;
                        break;
                    case 'gasGrenade':
                        dangerRadius = 400;
                        break;
                    default:
                        continue;
                }

                const dx = me.x - explosive.x;
                const dy = me.y - explosive.y;
                const distSq = dx * dx + dy * dy;

                if (distSq < dangerRadius * dangerRadius && distSq > 1) {
                    threatsFound++;
                    const distance = Math.sqrt(distSq);
                    const repulsionStrength = (dangerRadius - distance) / dangerRadius;
                    totalRepulsion.x += (dx / distance) * repulsionStrength;
                    totalRepulsion.y += (dy / distance) * repulsionStrength;
                }
            }

            if (threatsFound > 0) {
                const magnitude = Math.hypot(totalRepulsion.x, totalRepulsion.y);
                if (magnitude > 0.01) {
                    const fleeDistance = 400;
                    const fleeGoal = {
                        x: me.x + (totalRepulsion.x / magnitude) * fleeDistance,
                        y: me.y + (totalRepulsion.y / magnitude) * fleeDistance
                    };
                    return fleeGoal;
                }
            }

            return null;
        }

        findBestPathWithWhiskers(me, goal) {
            const whiskerCount = 6;
            const whiskerAngleSpread = Math.PI / 1.3;
            const whiskerLength = 350;

            const dx = goal.x - me.x;
            const dy = goal.y - me.y;
            const baseAngle = Math.atan2(dy, dx);

            let bestWhisker = null;
            let minGoalDistSq = Infinity;
            const analyzedWhiskers = [];

            for (let i = 0; i < whiskerCount; i++) {
                const angleOffset = (i - Math.floor(whiskerCount / 2)) * (whiskerAngleSpread / (whiskerCount - 1));
                const whiskerAngle = baseAngle + angleOffset;

                const endPoint = {
                    x: me.x + whiskerLength * Math.cos(whiskerAngle),
                    y: me.y + whiskerLength * Math.sin(whiskerAngle)
                };

                const isClear = this.hasLineOfSight(me, endPoint);
                const whiskerData = { start: { ...me }, end: endPoint, isClear, isBest: false };
                analyzedWhiskers.push(whiskerData);

                if (isClear) {
                    const distSq = (endPoint.x - goal.x) ** 2 + (endPoint.y - goal.y) ** 2;
                    if (distSq < minGoalDistSq) {
                        minGoalDistSq = distSq;
                        bestWhisker = whiskerData;
                    }
                }
            }

            this.aiDebugData.whiskers = analyzedWhiskers;

            if (bestWhisker) {
                bestWhisker.isBest = true;
                return bestWhisker.end;
            }

            return goal;
        }

        moveAITowards(me, goal) {
            const adjustedGoal = this.findBestPathWithWhiskers(me, goal);
            this.aiDebugData.finalMoveDirection = { start: {...me}, end: adjustedGoal };

            const distMovedSq = (me.x - this.aiLastPosition.x) ** 2 + (me.y - this.aiLastPosition.y) ** 2;
            if (distMovedSq < 4 && Object.values(this.simulatedKeys).some(k => k)) {
                this.aiStuckCounter++;
            } else {
                this.aiStuckCounter = 0;
            }
            this.aiLastPosition = { x: me.x, y: me.y };

            if (me.colliding) {
                this.aiObstacleAngleOffset += 7.5;
            } else if (this.aiObstacleAngleOffset > 0) {
                this.aiObstacleAngleOffset = Math.max(0, this.aiObstacleAngleOffset - 2.5);
            }

            if (this.aiStuckCounter > 60) {
                this.aiUnstuckCycle = 30;
                this.aiStuckCounter = 0;
                this.aiObstacleAngleOffset = (this.aiObstacleAngleOffset + 90 + Math.random() * 90) % 360;
                modLog("AI: Stuck detected! Initiating unstuck maneuver.");
            }

            let finalGoal = adjustedGoal;
            if (this.aiUnstuckCycle > 0) {
                const reverseDx = me.x - goal.x;
                const reverseDy = me.y - goal.y;
                const reverseDist = Math.hypot(reverseDx, reverseDy);
                if (reverseDist > 1) {
                    finalGoal = {
                        x: me.x + (reverseDx / reverseDist) * 200,
                        y: me.y + (reverseDy / reverseDist) * 200
                    };
                } else {
                    finalGoal = { x: me.x + (Math.random() - 0.5) * 200, y: me.y + (Math.random() - 0.5) * 200 };
                }
                this.aiUnstuckCycle--;
            }

            let dx_move = finalGoal.x - me.x;
            let dy_move = finalGoal.y - me.y;
            if (this.aiObstacleAngleOffset !== 0) {
                const angleRad = this.aiObstacleAngleOffset * (Math.PI / 180);
                const cosA = Math.cos(angleRad);
                const sinA = Math.sin(angleRad);
                const newDx = dx_move * cosA - dy_move * sinA;
                const newDy = dx_move * sinA + dy_move * cosA;
                dx_move = newDx;
                dy_move = newDy;
            }

            const { maxSpeed, ACCELERATION, FRICTION } = this.getBotPhysics(me);
            const distance = Math.hypot(dx_move, dy_move);
            let desiredSpdX = 0;
            let desiredSpdY = 0;

            const stopDistance = this.multibox.isChild ? 50 : 15;

            if (distance > stopDistance) {
                const brakingFactor = 12.0;
                const desiredSpeed = Math.min(maxSpeed, distance / brakingFactor);
                desiredSpdX = (dx_move / distance) * desiredSpeed;
                desiredSpdY = (dy_move / distance) * desiredSpeed;
            }

            const requiredAccelX = (desiredSpdX / FRICTION) - this.botSpdX;
            const requiredAccelY = (desiredSpdY / FRICTION) - this.botSpdY;

            let keysToPress = [];
            let isPressingKeyX = 0;
            let isPressingKeyY = 0;
            const keyPressThreshold = ACCELERATION * 0.1;
            if (requiredAccelX > keyPressThreshold) {
                keysToPress.push('d');
                isPressingKeyX = 1;
            }
            else if (requiredAccelX < -keyPressThreshold) {
                keysToPress.push('a');
                isPressingKeyX = -1;
            }
            if (requiredAccelY > keyPressThreshold) {
                keysToPress.push('s');
                isPressingKeyY = 1;
            }
            else if (requiredAccelY < -keyPressThreshold) {
                keysToPress.push('w');
                isPressingKeyY = -1;
            }

            this.updateSimulatedKeys(keysToPress);

            let appliedAccelX = isPressingKeyX * ACCELERATION;
            let appliedAccelY = isPressingKeyY * ACCELERATION;
            if (isPressingKeyX !== 0 && isPressingKeyY !== 0) {
                appliedAccelX *= GatsModCore.PLAYER_SPEEDS.diagonalCorrection;
                appliedAccelY *= GatsModCore.PLAYER_SPEEDS.diagonalCorrection;
            }
            this.botSpdX = (this.botSpdX + appliedAccelX) * FRICTION;
            this.botSpdY = (this.botSpdY + appliedAccelY) * FRICTION;
            const currentSimulatedSpeed = Math.hypot(this.botSpdX, this.botSpdY);
            if (currentSimulatedSpeed > maxSpeed) {
                const ratio = maxSpeed / currentSimulatedSpeed;
                this.botSpdX *= ratio;
                this.botSpdY *= ratio;
            }
        }

        useParkAbility(me) {
            const now = performance.now();
            if (now - this.aiLastParkUse > 1000) {
                this.aiLastParkUse = now;
                this._fireKeyEvent('keydown', ' ');
                setTimeout(() => this._fireKeyEvent('keyup', ' '), 50);
            }
        }

        handleSituationalAITalk(me, nearestEnemy, aiMode) {
            const now = performance.now();
            if (now - this.aiLastChatTime < 1000) return;

            let chatCandidates = [];
            let saySomething = false;

            if (this.multibox.isChild) {
                aiMode = 'idle';
            }

            switch (aiMode) {
                case 'attacking':
                    if (nearestEnemy) chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.attacking(nearestEnemy.username);
                    saySomething = Math.random() < 0.1;
                    break;
                case 'fleeing_explosive':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.fleeing_explosive();
                    saySomething = Math.random() < 0.5;
                    break;
                case 'dodging':
                case 'pre_dodging':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.dodging();
                    saySomething = Math.random() < 0.25;
                    break;
                case 'retreating':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.retreating();
                    saySomething = Math.random() < 0.25;
                    break;
                case 'distancing':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.distancing();
                    saySomething = Math.random() < 0.20;
                    break;
                case 'kiting':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.kiting();
                    saySomething = Math.random() < 0.20;
                    break;
                case 'idle':
                case 'patrolling':
                case 'returning_to_bounds':
                case 'following_parent':
                    chatCandidates = GatsModCore.AI_CHAT_SITUATIONAL.idle();
                    saySomething = Math.random() < 0.02;
                    break;
            }

            if (saySomething && chatCandidates.length > 0) {
                const message = chatCandidates[Math.floor(Math.random() * chatCandidates.length)];
                GatsModCore.sendChatMessage(message);
                this.aiLastChatTime = now;
            }
        }

        handleIncomingChatMessage(message) {
            if (this.multibox.isChild) return;
            const now = performance.now();
            if (now - this.aiLastChatTime < 1000) return;

            const words = message.split(' ');
            for (const word of words) {
                if (GatsModCore.AI_CHAT_RESPONSES[word]) {
                    const responses = GatsModCore.AI_CHAT_RESPONSES[word];
                    const response = responses[Math.floor(Math.random() * responses.length)];
                    GatsModCore.sendChatMessage(response);
                    this.aiLastChatTime = now;
                    return;
                }
            }
        }
    }

    let gatsModInstance = null;
    let game_original_updateMouseData_ref = null;
    let modInitializationAttempted = false;

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

        if (
            typeof unsafeWindow.updateMouseData === 'function' &&
            typeof unsafeWindow.Player !== 'undefined' && unsafeWindow.Player.pool &&
            typeof unsafeWindow.Connection !== 'undefined' &&
            typeof unsafeWindow.compressMessage === 'function' &&
            typeof unsafeWindow.encodeMessage === 'function' &&
            typeof unsafeWindow.camera === 'object' &&
            document.getElementById('canvas')
        ) {
            modLog(`[Launcher] Game ready. Initializing mod.`);
            clearInterval(gatsModLauncherInterval);
            modInitializationAttempted = true;

            game_original_updateMouseData_ref = unsafeWindow.updateMouseData;

            unsafeWindow.updateMouseData = function(eventData) {
                if (!gatsModInstance) {
                    modLog("[Launcher] First mouse event, creating GatsModCore instance.", false);
                    try {
                        gatsModInstance = new GatsModCore();
                        gatsModInstance.originalUpdateMouseData = game_original_updateMouseData_ref;

                        const original_clearRect = unsafeWindow.CanvasRenderingContext2D.prototype.clearRect;
                        unsafeWindow.CanvasRenderingContext2D.prototype.clearRect = function(...args) {
                            original_clearRect.apply(this, args);
                            if (gatsModInstance && this.canvas.id === 'canvas' && unsafeWindow.Player?.pool?.[unsafeWindow.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);
                        unsafeWindow.updateMouseData = game_original_updateMouseData_ref;
                        return;
                    }
                }

                if (gatsModInstance?.isExclusionModeActive) {
                    return;
                }

                if (GatsModCore.SETTINGS.zeroAIEnabled || gatsModInstance?.currentAimAssistTargetCoords) {
                    // AI or Aimbot is handling mouse, so don't call original function with real mouse data yet.
                    return;
                }

                return game_original_updateMouseData_ref.call(this, eventData);
            };

            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);

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。