Загрузка данных


"use strict";

/// Виталя шота сделал

var nickMaskBrowser = null;
var isMaskVisible   = false;
var logBrowser      = null;

var logBrowserHTML = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
  html, body {
    margin: 0; padding: 0;
    background: transparent !important;
    overflow: hidden;
    font-family: 'Segoe UI', Verdana, sans-serif;
    user-select: none;
  }
  @keyframes snowfall {
    0%   { transform: translateY(-10px) translateX(0);   opacity: 0.8; }
    100% { transform: translateY(200px)  translateX(15px); opacity: 0;   }
  }
  .snowflake {
    position: absolute; background: white; border-radius: 50%;
    top: -10px; z-index: 1; pointer-events: none;
    animation: snowfall linear infinite;
    box-shadow: 0 0 5px rgba(255,255,255,0.8);
  }
  #log-menu {
    position: absolute; left: 15px; top: 330px; width: 220px;
    background: linear-gradient(160deg, rgba(107,0,179,0.75), rgba(0,0,0,0.8));
    border-radius: 12px; color: #fff; z-index: 9998; cursor: move;
    box-shadow: 0 8px 20px rgba(0,0,0,0.3);
    border: 1px solid rgba(255,255,255,0.2); overflow: hidden;
  }
  h1 {
    font-size: 14px; margin: 0; padding: 10px; text-align: center;
    font-weight: 700; text-transform: uppercase; letter-spacing: 1px;
    color: #fff; background: rgba(0,0,0,0.15);
    border-bottom: 1px solid rgba(255,255,255,0.1);
  }
  #status-container { padding: 10px 15px; position: relative; z-index: 2; }
  .info-row {
    display: flex; justify-content: space-between; align-items: center;
    margin-bottom: 6px; font-size: 12px;
  }
  .info-row:last-child { margin-bottom: 0; }
  .label {
    color: rgba(255,255,255,0.9); font-weight: 600;
    font-size: 11px; text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  }
  .value-box {
    background: rgba(255,255,255,0.15); padding: 2px 8px;
    border-radius: 6px; font-weight: 700; font-size: 11px;
    color: #fff; text-align: right; min-width: 60px; max-width: 120px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: background 0.3s; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  }
</style>
</head>
<body>
  <div id="log-menu">
    <div class="snowflake" style="left:10%;width:4px;height:4px;animation-duration:4s;animation-delay:0s;"></div>
    <div class="snowflake" style="left:30%;width:3px;height:3px;animation-duration:5s;animation-delay:1s;"></div>
    <div class="snowflake" style="left:50%;width:5px;height:5px;animation-duration:3.5s;animation-delay:2s;"></div>
    <div class="snowflake" style="left:70%;width:3px;height:3px;animation-duration:4.5s;animation-delay:0.5s;"></div>
    <div class="snowflake" style="left:90%;width:4px;height:4px;animation-duration:3s;animation-delay:1.5s;"></div>

    <h1>BOT by PROMERHIC</h1>
    <div id="status-container">
      <div class="info-row">
        <span class="label">СТАТУС БОТА:</span>
        <span id="status-value" class="value-box">ОСТАНОВЛЕН (F8)</span>
      </div>
      <div class="info-row">
        <span class="label">ПОЗИЦИЯ:</span>
        <span id="route-value" class="value-box">Н/Д</span>
      </div>
      <div class="info-row">
        <span class="label">ТАЙМЕР АНТИ-ЗАСТРЕВАНИЯ:</span>
        <span id="timer-value" class="value-box">Н/Д</span>
      </div>
      <div class="info-row">
        <span class="label">АДМИНЫ РЯДОМ</span>
        <span id="admin-value" class="value-box">0</span>
      </div>
      <div class="info-row">
        <span class="label">КЛИКЕР:</span>
        <span id="clicker-value" class="value-box">ОТКЛЮЧЕН</span>
      </div>
    </div>
  </div>

  <script>
    const logMenu = document.getElementById('log-menu');
    let isDragging = false, offsetX, offsetY;
    logMenu.onmousedown = function(e) {
      isDragging = true;
      offsetX = e.clientX - logMenu.getBoundingClientRect().left;
      offsetY = e.clientY - logMenu.getBoundingClientRect().top;
      e.preventDefault();
    };
    document.onmousemove = function(e) {
      if (isDragging) {
        logMenu.style.left = (e.clientX - offsetX) + 'px';
        logMenu.style.top  = (e.clientY - offsetY) + 'px';
      }
    };
    document.onmouseup = function() { isDragging = false; };
  </script>
</body>
</html>`;

if (logBrowser) { logBrowser.destroy(); logBrowser = null; }
logBrowser = mp.browsers.new("data:text/html," + encodeURIComponent(logBrowserHTML));

// ---- HUD helpers ----

function updateStatus(status, route) {
    if (!logBrowser) return;
    logBrowser.execute(`document.getElementById('status-value').innerText = ${JSON.stringify(status)};`);
    logBrowser.execute(`document.getElementById('route-value').innerText  = ${JSON.stringify(route)};`);
    var bg = "rgba(255,255,255,0.15)";
    if (status.startsWith("Фармит...") || status.startsWith("Активирован..")) bg = "rgba(100,255,100,0.35)";
    else if (status.includes("АФК..")) bg = "rgba(255,50,50,0.6)";
    logBrowser.execute(`document.getElementById('status-value').style.background = '${bg}';`);
}

function updateTimer(val) {
    if (!logBrowser) return;
    logBrowser.execute(`document.getElementById('timer-value').innerText = ${JSON.stringify(val)};`);
    var bg = val === "Телепортирую..." ? "rgba(255,200,50,0.6)" : "rgba(255,255,255,0.15)";
    logBrowser.execute(`document.getElementById('timer-value').style.background = '${bg}';`);
}

function updateAdminCount(count) {
    if (!logBrowser) return;
    logBrowser.execute(`document.getElementById('admin-value').innerText = '${count}';`);
    logBrowser.execute(`document.getElementById('admin-value').style.background = 'rgba(255,255,255,0.15)';`);
}

function updateClickerStatus(status) {
    if (!logBrowser) return;
    var bg = status === "ВКЛЮЧЕН" ? "rgba(100,255,100,0.35)" : "rgba(255,255,255,0.15)";
    logBrowser.execute(`
        var el = document.getElementById('clicker-value');
        if (el) { el.innerText = '${status}'; el.style.background = '${bg}'; }
    `);
}

// ============================================================
//  Safety zone
// ============================================================

var SAFETY_ZONE_HEIGHT = 4.0;
var SAFETY_ZONE_COLOR  = [255, 0, 0, 180];
var SAFETY_ZONE_COORDS = [
    new mp.Vector3(-451.88, -2677.97, 5.01),
    new mp.Vector3(-494.98, -2721.12, 5.01),
    new mp.Vector3(-452.69, -2762.40, 5.01),
    new mp.Vector3(-448.32, -2758.15, 5.01),
    new mp.Vector3(-390.93, -2757.68, 5.01),
    new mp.Vector3(-376.61, -2742.29, 5.01),
    new mp.Vector3(-368.58, -2728.36, 5.01),
];
var SAFETY_ZONE_POLYGON = SAFETY_ZONE_COORDS.map(p => ({ x: p.x, y: p.y }));

function isPlayerInSafetyZone(point, polygon) {
    var inside = false;
    for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
        var xi = polygon[i].x, yi = polygon[i].y;
        var xj = polygon[j].x, yj = polygon[j].y;
        var intersect = ((yi > point.y) !== (yj > point.y))
            && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
        if (intersect) inside = !inside;
    }
    return inside;
}

function drawSafetyZone() {
    var len = SAFETY_ZONE_COORDS.length;
    for (var i = 0; i < len; i++) {
        var p1b = SAFETY_ZONE_COORDS[i];
        var p2b = SAFETY_ZONE_COORDS[(i + 1) % len];
        var p1t = new mp.Vector3(p1b.x, p1b.y, p1b.z + SAFETY_ZONE_HEIGHT);
        var p2t = new mp.Vector3(p2b.x, p2b.y, p2b.z + SAFETY_ZONE_HEIGHT);
        var c = SAFETY_ZONE_COLOR;
        mp.game.graphics.drawLine(p1b.x, p1b.y, p1b.z, p2b.x, p2b.y, p2b.z, ...c);
        mp.game.graphics.drawLine(p1t.x, p1t.y, p1t.z, p2t.x, p2t.y, p2t.z, ...c);
        mp.game.graphics.drawLine(p1b.x, p1b.y, p1b.z, p1t.x, p1t.y, p1t.z, ...c);
        mp.game.graphics.drawLine(p1b.x, p1b.y, p1b.z, p2t.x, p2t.y, p2t.z, ...c);
        mp.game.graphics.drawLine(p2b.x, p2b.y, p2b.z, p1t.x, p1t.y, p1t.z, ...c);
    }
}

// ============================================================
//  Routes
// ============================================================

var ROUTE_CARRY_1 = [
    [new mp.Vector3(-438.91,-2733.70,6)],[new mp.Vector3(-440.44,-2733.29,6)],
    [new mp.Vector3(-441.66,-2732.68,6)],[new mp.Vector3(-442.62,-2731.79,6)],
    [new mp.Vector3(-451.99,-2722.89,6)],[new mp.Vector3(-453.12,-2722.03,6)],
    [new mp.Vector3(-454.13,-2721.39,6)],[new mp.Vector3(-455.85,-2720.27,6)],
    [new mp.Vector3(-458.47,-2718.62,6)],[new mp.Vector3(-461.15,-2719.19,6)],
    [new mp.Vector3(-462.59,-2718.26,6)],[new mp.Vector3(-463.39,-2716.36,6)],
    [new mp.Vector3(-464.65,-2715.30,6)],[new mp.Vector3(-465.85,-2714.71,6)],
    [new mp.Vector3(-466.43,-2715.41,6)],[new mp.Vector3(-466.03,-2716.70,6)],
    [new mp.Vector3(-460.90,-2722.01,6)],[new mp.Vector3(-466.67,-2714.87,6)],
    [new mp.Vector3(-464.83,-2715.07,6)],[new mp.Vector3(-462.25,-2714.48,6)],
    [new mp.Vector3(-463.38,-2717.35,6)],[new mp.Vector3(-461.58,-2719.22,6)],
    [new mp.Vector3(-459.80,-2719.14,6)],[new mp.Vector3(-459.23,-2717.72,6)],
    [new mp.Vector3(-440.34,-2735.44,6)],[new mp.Vector3(-440.33,-2735.44,6)],
];
var ROUTE_CARRY_2 = [
    [new mp.Vector3(-441.51,-2732.93,6)],[new mp.Vector3(-450.88,-2723.85,6)],
    [new mp.Vector3(-453.53,-2721.41,6)],[new mp.Vector3(-456.53,-2719.36,6)],
    [new mp.Vector3(-458.47,-2718.62,6)],[new mp.Vector3(-461.15,-2719.19,6)],
    [new mp.Vector3(-462.59,-2718.26,6)],[new mp.Vector3(-463.39,-2716.36,6)],
    [new mp.Vector3(-464.65,-2715.30,6)],[new mp.Vector3(-465.85,-2714.71,6)],
    [new mp.Vector3(-466.43,-2715.41,6)],[new mp.Vector3(-466.03,-2716.70,6)],
    [new mp.Vector3(-460.90,-2722.01,6)],[new mp.Vector3(-466.67,-2714.87,6)],
    [new mp.Vector3(-464.83,-2715.07,6)],[new mp.Vector3(-462.25,-2714.48,6)],
    [new mp.Vector3(-463.38,-2717.35,6)],[new mp.Vector3(-461.58,-2719.22,6)],
    [new mp.Vector3(-459.80,-2719.14,6)],[new mp.Vector3(-459.23,-2717.72,6)],
    [new mp.Vector3(-440.34,-2735.44,6)],[new mp.Vector3(-440.33,-2735.44,6)],
];
var ROUTE_CARRY_3 = [
    [new mp.Vector3(-438.53,-2730.49,6)],[new mp.Vector3(-438.89,-2729.58,6)],
    [new mp.Vector3(-439.70,-2728.34,6)],[new mp.Vector3(-445.42,-2723.11,6)],
    [new mp.Vector3(-446.62,-2722.23,6)],[new mp.Vector3(-448.10,-2721.45,6)],
    [new mp.Vector3(-450.13,-2720.79,6)],[new mp.Vector3(-458.47,-2718.62,6)],
    [new mp.Vector3(-461.15,-2719.19,6)],[new mp.Vector3(-462.59,-2718.26,6)],
    [new mp.Vector3(-463.39,-2716.36,6)],[new mp.Vector3(-464.65,-2715.30,6)],
    [new mp.Vector3(-465.85,-2714.71,6)],[new mp.Vector3(-466.43,-2715.41,6)],
    [new mp.Vector3(-466.03,-2716.70,6)],[new mp.Vector3(-460.90,-2722.01,6)],
    [new mp.Vector3(-466.67,-2714.87,6)],[new mp.Vector3(-464.83,-2715.07,6)],
    [new mp.Vector3(-462.25,-2714.48,6)],[new mp.Vector3(-463.38,-2717.35,6)],
    [new mp.Vector3(-461.58,-2719.22,6)],[new mp.Vector3(-459.80,-2719.14,6)],
    [new mp.Vector3(-459.23,-2717.72,6)],[new mp.Vector3(-440.34,-2735.44,6)],
    [new mp.Vector3(-440.33,-2735.44,6)],
];
var ROUTE_CARRY_4 = [
    [new mp.Vector3(-436.03,-2732.87,6)],[new mp.Vector3(-436.69,-2731.33,6)],
    [new mp.Vector3(-437.45,-2729.32,6)],[new mp.Vector3(-438.34,-2727.31,6)],
    [new mp.Vector3(-440.11,-2725.16,6)],[new mp.Vector3(-444.86,-2721.01,6)],
    [new mp.Vector3(-448.86,-2719.13,6)],[new mp.Vector3(-452.04,-2718.73,6)],
    [new mp.Vector3(-458.47,-2718.62,6)],[new mp.Vector3(-461.15,-2719.19,6)],
    [new mp.Vector3(-462.59,-2718.26,6)],[new mp.Vector3(-463.39,-2716.36,6)],
    [new mp.Vector3(-464.65,-2715.30,6)],[new mp.Vector3(-465.85,-2714.71,6)],
    [new mp.Vector3(-466.43,-2715.41,6)],[new mp.Vector3(-466.03,-2716.70,6)],
    [new mp.Vector3(-460.90,-2722.01,6)],[new mp.Vector3(-466.67,-2714.87,6)],
    [new mp.Vector3(-464.83,-2715.07,6)],[new mp.Vector3(-462.25,-2714.48,6)],
    [new mp.Vector3(-463.38,-2717.35,6)],[new mp.Vector3(-461.58,-2719.22,6)],
    [new mp.Vector3(-459.80,-2719.14,6)],[new mp.Vector3(-459.23,-2717.72,6)],
    [new mp.Vector3(-440.34,-2735.44,6)],[new mp.Vector3(-440.33,-2735.44,6)],
];

var ALL_CARRY_ROUTES_TEMPLATES = [
    { name: "Доставка #1", route: ROUTE_CARRY_1 },
    { name: "Доставка #2", route: ROUTE_CARRY_2 },
    { name: "Доставка #3", route: ROUTE_CARRY_3 },
    { name: "Доставка #4", route: ROUTE_CARRY_4 },
];

var ROUTE_EMPTY_TEMPLATE = {
    name: "Беру ящик..",
    route: [
        [new mp.Vector3(-458.64,-2727.29,6)],[new mp.Vector3(-457.58,-2729.58,6)],
        [new mp.Vector3(-455.32,-2732.09,6)],[new mp.Vector3(-451.80,-2735.55,6)],
        [new mp.Vector3(-450.33,-2736.48,6)],[new mp.Vector3(-448.08,-2737.56,6)],
        [new mp.Vector3(-445.60,-2738.64,6)],[new mp.Vector3(-443.24,-2739.44,6)],
        [new mp.Vector3(-439.61,-2740.32,6)],[new mp.Vector3(-437.18,-2740.92,6)],
        [new mp.Vector3(-435.74,-2740.88,6)],[new mp.Vector3(-432.77,-2740.68,6)],
        [new mp.Vector3(-430.55,-2741.53,6)],[new mp.Vector3(-429.67,-2739.96,6)],
        [new mp.Vector3(-428.10,-2740.10,6)],[new mp.Vector3(-426.61,-2741.02,6)],
        [new mp.Vector3(-424.23,-2741.72,6)],[new mp.Vector3(-426.01,-2739.83,6)],
        [new mp.Vector3(-428.19,-2739.98,6)],[new mp.Vector3(-429.40,-2740.48,6)],
        [new mp.Vector3(-430.08,-2742.11,6)],[new mp.Vector3(-431.22,-2741.90,6)],
        [new mp.Vector3(-432.11,-2740.45,6)],[new mp.Vector3(-435.86,-2740.50,6)],
        [new mp.Vector3(-436.62,-2741.33,6)],[new mp.Vector3(-447.82,-2741.47,6)],
    ],
};

// ============================================================
//  Bot state
// ============================================================

var autorunActive     = false;
var currentRouteData  = null;
var routeStep         = 0;
var lastCarryingState = null;
var isAdminDetected   = false;
var adminPauseTriggered = false;
var stuckTimerTimestamp = 0;
var STUCK_TIMEOUT       = 7500;
var lastMenuUpdateTime  = 0;
var MENU_UPDATE_INTERVAL = 200;

// ============================================================
//  Autoclicker
// ============================================================

var portClicker = {
    active:        false,
    checkInterval: null,
    browser:       null,
    gameWasVisible: false,
    hasPressed:    false,
    clickDelayMin: 500,
    clickDelayMax: 503,

    getRandomDelay: function() {
        return Math.floor(Math.random() * (this.clickDelayMax - this.clickDelayMin + 1)) + this.clickDelayMin;
    },

    findBrowser: function() {
        var browsers = mp.browsers.toArray();
        for (var i = 0; i < browsers.length; i++) {
            var url = browsers[i].url || '';
            if (url.includes('__menu.html') || url.includes('Menu')) return browsers[i];
        }
        return null;
    },

    checkAndPress: function() {
        this.browser = this.findBrowser();
        if (!this.browser) { this.gameWasVisible = false; this.hasPressed = false; return; }
        var checkCode = `(function(){
            var gc = document.getElementById('portGameContent');
            mp.trigger('clicker:gameStatus', !!(gc && gc.style.display !== 'none'));
        })();`;
        try { this.browser.execute(checkCode); } catch(e) {}
    },

    pressE: function() {
        if (!this.browser) return;
        var pressCode = `(function(){
            ['keydown','keypress','keyup'].forEach(function(type){
                var e = new KeyboardEvent(type,{key:'E',code:'KeyE',bubbles:true,cancelable:true,view:window});
                document.body.dispatchEvent(e);
                document.dispatchEvent(e);
            });
        })();`;
        try { this.browser.execute(pressCode); } catch(e) {}
    },

    onGameStatus: function(isVisible) {
        if (isVisible && !this.gameWasVisible && !this.hasPressed) {
            this.gameWasVisible = true;
            var self = this;
            setTimeout(function() {
                if (self.active) { self.pressE(); self.hasPressed = true; }
            }, this.getRandomDelay());
        }
        if (!isVisible) { this.gameWasVisible = false; this.hasPressed = false; }
    },

    start: function() {
        if (this.active) return;
        this.active = true;
        this.gameWasVisible = false;
        this.hasPressed = false;
        var self = this;
        this.checkInterval = setInterval(function() { if (self.active) self.checkAndPress(); }, 200);
        updateClickerStatus("ВКЛ.");
    },

    stop: function() {
        this.active = false;
        if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; }
        this.gameWasVisible = false;
        this.hasPressed = false;
        updateClickerStatus("ВЫКЛ.");
    },
};

// ============================================================
//  Bot logic
// ============================================================

function playerIsCarryingBox() {
    return mp.players.local.isPlayingAnim("anim@heists@box_carry@", "idle", 3);
}

function stopAutorun() {
    var player = mp.players.local;
    if (player && player.handle) {
        mp.game.invoke('0xE1EF3C1216AFF2CD', player.handle);
        player.setMoveRateOverride(1.0);
        try { mp.game.task.setPedDesiredMoveBlendRatio(player.handle, 1.0); } catch(e) {}
    }
    autorunActive = false;
    stuckTimerTimestamp = 0;
    portClicker.stop();
    updateStatus("Остановлен.. (F7)", "Н/Д");
    updateTimer("Н/Д");
}

function chooseRoute(findNearest) {
    var carrying = playerIsCarryingBox();
    lastCarryingState = carrying;

    var template, name;
    if (carrying) {
        var idx = Math.floor(Math.random() * ALL_CARRY_ROUTES_TEMPLATES.length);
        template = ALL_CARRY_ROUTES_TEMPLATES[idx].route;
        name     = ALL_CARRY_ROUTES_TEMPLATES[idx].name;
    } else {
        template = ROUTE_EMPTY_TEMPLATE.route;
        name     = ROUTE_EMPTY_TEMPLATE.name;
    }

    currentRouteData = { name: name, route: template };

    if (findNearest) {
        var playerPos = mp.players.local.position;
        var minDist = Infinity, closestIdx = 0;
        for (var i = 0; i < template.length; i++) {
            var pt = template[i][0];
            var d  = mp.game.gameplay.getDistanceBetweenCoords(
                playerPos.x, playerPos.y, playerPos.z, pt.x, pt.y, pt.z, true
            );
            if (d < minDist) { minDist = d; closestIdx = i; }
        }
        routeStep = closestIdx;
    } else {
        routeStep = 0;
    }

    stuckTimerTimestamp = Date.now();
    updateStatus("Фармит... " + (routeStep + 1) + "/" + template.length, name);
    updateTimer("7.5 сек.");
}

function moveTo(target) {
    var player = mp.players.local;
    if (!player || !player.handle) return;
    player.setMoveRateOverride(1.4);
    try { mp.game.task.setPedDesiredMoveBlendRatio(player.handle, 1.35); } catch(e) {}
    var pos = player.position;
    var heading = Math.atan2(target.y - pos.y, target.x - pos.x) * 180 / Math.PI;
    mp.game.task.taskGoStraightToCoord(player.handle, target.x, target.y, target.z, 4.0, 60000, heading, 0);
}

// ============================================================
//  Key handlers
// ============================================================

var startHandler = function() {
    if (autorunActive) return;
    var playerPos = mp.players.local.position;
    if (!isPlayerInSafetyZone({ x: playerPos.x, y: playerPos.y }, SAFETY_ZONE_POLYGON)) {
        mp.gui.chat.push("!{FF0000}[ПОРТ-БОТ]: !{FFFFFF}Тебе русским языком сказали! НЕ МОГУ РАБОТАТЬ ВНЕ КРАСНОЙ ЗОНЫ ИШАК!!");
        return;
    }
    autorunActive = true;
    portClicker.start();
    updateStatus("Активирован", "Ищу...");
    chooseRoute(true);
    stuckTimerTimestamp = Date.now();
    updateTimer("7.5 сек.");
};

var stopHandler = function() {
    if (!autorunActive) return;
    stopAutorun();
};

var f9TeleportHandler = function() {
    var player = mp.players.local;
    if (player && player.handle) player.position = new mp.Vector3(-457.70, -2750.95, 8.00);
};

// ============================================================
//  Render
// ============================================================

var renderHandler = function() {
    var player = mp.players.local;
    if (!player || !player.handle) return;

    var playerPos = player.position;
    var now = Date.now();

    // Admin scan + draw lines
    var adminCount = 0;
    mp.players.forEachInStreamRange(function(p) {
        if (p === player || !p.doesExist() || p.handle === 0) return;
        if (p.__displayIsAdmin) {
            adminCount++;
            var ap = p.position;
            mp.game.graphics.drawLine(playerPos.x, playerPos.y, playerPos.z, ap.x, ap.y, ap.z, 255, 0, 0, 255);
            var dist = mp.game.system.vdist(playerPos.x, playerPos.y, playerPos.z, ap.x, ap.y, ap.z);
            mp.game.graphics.drawText(
                "ADMIN\n~r~" + dist.toFixed(1) + " m.",
                [ap.x, ap.y, ap.z + 1.2],
                { font: 0, color: [255,0,0,255], scale: [0.3,0.3], outline: true, centre: true }
            );
        }
    });

    isAdminDetected = adminCount > 0;

    if (now - lastMenuUpdateTime > MENU_UPDATE_INTERVAL) {
        updateAdminCount(adminCount);
    }

    if (!autorunActive) return;

    if (player.isDead() || player.vehicle) { stopAutorun(); return; }

    if (!isPlayerInSafetyZone({ x: playerPos.x, y: playerPos.y }, SAFETY_ZONE_POLYGON)) {
        mp.gui.chat.push("!{FF0000}[ПОРТ-БОТ]: !{FFFFFF}Ты вышел из красной зоны! Ишак!");
        stopAutorun();
        return;
    }

    if (isAdminDetected) {
        if (!adminPauseTriggered) {
            mp.game.invoke('0xE1EF3C1216AFF2CD', player.handle);
            player.setMoveRateOverride(1.0);
            try { mp.game.task.setPedDesiredMoveBlendRatio(player.handle, 1.0); } catch(e) {}
            adminPauseTriggered = true;
        }
        stuckTimerTimestamp = Date.now();
        if (now - lastMenuUpdateTime > MENU_UPDATE_INTERVAL) {
            updateStatus("Админ рядом! Стою АФК..", "Остановился..");
            updateTimer("Жду...");
            lastMenuUpdateTime = now;
        }
        return;
    } else {
        adminPauseTriggered = false;
    }

    if (now - lastMenuUpdateTime > MENU_UPDATE_INTERVAL) {
        lastMenuUpdateTime = now;
        if (stuckTimerTimestamp !== 0) {
            var timeLeft = Math.max(0, (STUCK_TIMEOUT - (now - stuckTimerTimestamp)) / 1000);
            updateTimer(timeLeft.toFixed(1) + " сек.");
        }
    }

    var carrying = playerIsCarryingBox();
    if (carrying !== lastCarryingState) chooseRoute(false);

    if (!currentRouteData || !currentRouteData.route.length) return;

    var route = currentRouteData.route;

    if (routeStep >= route.length) { routeStep = 0; stuckTimerTimestamp = Date.now(); }

    var possibleTargets = route[routeStep];
    var target = possibleTargets[Math.floor(Math.random() * possibleTargets.length)];

    var dist = mp.game.gameplay.getDistanceBetweenCoords(
        playerPos.x, playerPos.y, playerPos.z, target.x, target.y, target.z, true
    );
    var elapsed = Date.now() - stuckTimerTimestamp;

    if (dist < 1.5) {
        routeStep = (routeStep + 1) % route.length;
        stuckTimerTimestamp = Date.now();
        updateTimer("7.5 сек.");
        updateStatus("Фармит... " + (routeStep + 1) + "/" + route.length, currentRouteData.name);
    } else if (elapsed > STUCK_TIMEOUT) {
        mp.gui.chat.push("[ПОРТ-БОТ]: Ты застрялъ, телепортирую тя.");
        updateTimer("Телепортирую...");
        player.position = new mp.Vector3(target.x, target.y, target.z);
        routeStep = (routeStep + 1) % route.length;
        stuckTimerTimestamp = Date.now();
        updateStatus("Фармит... " + (routeStep + 1) + "/" + route.length, currentRouteData.name);
    }

    moveTo(target);
};

// ============================================================
//  Unload
// ============================================================

var fullUnloadScript = function() {
    if (autorunActive) stopAutorun();
    portClicker.stop();

    mp.keys.unbind(0x76, false, startHandler);
    mp.keys.unbind(0x77, false, stopHandler);
    mp.keys.unbind(0x78, false, f9TeleportHandler);
    mp.keys.unbind(0x7B, false, fullUnloadScript);

    mp.events.remove('render', renderHandler);
    mp.events.remove('render', drawSafetyZone);
    mp.events.remove('clicker:gameStatus', clickerGameStatusHandler);

    if (nickMaskBrowser) { nickMaskBrowser.destroy(); nickMaskBrowser = null; isMaskVisible = false; }
    if (logBrowser)      { logBrowser.destroy(); logBrowser = null; }

    currentRouteData  = null;
    lastCarryingState = null;
    stuckTimerTimestamp = 0;
    lastMenuUpdateTime  = 0;
};

// ============================================================
//  Events + binds
// ============================================================

var clickerGameStatusHandler = function(isVisible) {
    portClicker.onGameStatus(isVisible);
};

mp.events.add('clicker:gameStatus', clickerGameStatusHandler);
mp.events.add('render', renderHandler);
mp.events.add('render', drawSafetyZone);

mp.keys.bind(0x76, false, startHandler);      // F7
mp.keys.bind(0x77, false, stopHandler);       // F8
mp.keys.bind(0x78, false, f9TeleportHandler); // F9
mp.keys.bind(0x7B, false, fullUnloadScript);  // F12

// ============================================================
//  Init messages
// ============================================================

mp.gui.chat.push("!{FFFFFF}.");
mp.gui.chat.push("!{00FF00}[ПОРТ-БОТ v1 + КЛИКЕР]: !{FFFFFF}Я заинжектился!");
mp.gui.chat.push("!{FFFF00}ВНИМАНИЕ!: !{FFFFFF}Я работаю только в !{FF0000}красной зоне!{FFFFFF}.");
mp.gui.chat.push("!{FFFFFF}.");